### Install Notablog CLI Source: https://github.com/dragonman225/notablog/blob/master/README.md Install the Notablog command-line interface globally using npm. Ensure Node.js v15.0.0 or higher is installed. ```bash npm i -g notablog ``` -------------------------------- ### Minimal EJS Post Template Example Source: https://context7.com/dragonman225/notablog/llms.txt This is a basic example of an EJS template for rendering individual blog posts. It includes navigation, post title, date, and content. ```html <%= post.title %> | <%= siteMeta.title %>

<%= post.title %>

<%- post.contentHTML %>
``` -------------------------------- ### Notablog Generate Command Output Source: https://context7.com/dragonman225/notablog/llms.txt This is an example of the expected output when running the `notablog generate` command, showing the progress and completion time. ```text [notablog] Copy theme assets [notablog] Fetch site metadata [notablog] Render home page and tags [notablog] Fetch and render pages [notablog] 3 of 10 posts have been updated [notablog] 8 of 10 posts are published [notablog] Done in 4.2s. Run 'notablog preview ./my-blog' to preview ``` -------------------------------- ### Programmatic API - preview(workDir) Source: https://context7.com/dragonman225/notablog/llms.txt Opens the generated blog in a browser synchronously. Throws if previewBrowser is not configured. ```APIDOC ## Programmatic API — `preview(workDir)` Opens the generated blog in a browser synchronously. Throws if `previewBrowser` is not configured. ### Parameters - `workDir` (string): The working directory of the notablog-starter. ### Usage ```typescript import { preview } from 'notablog' try { preview('./my-blog') // Browser opens public/index.html in a detached process } catch (error) { // Throws: '"previewBrowser" property is not set in your Notablog config file.' console.error(error.message) } ``` ``` -------------------------------- ### Preview Generated Blog with Notablog CLI Source: https://context7.com/dragonman225/notablog/llms.txt Use the `preview` command to open the generated `public/index.html` in a browser. The `help` command displays usage information. ```bash notablog preview ./my-blog ``` ```bash notablog help ``` -------------------------------- ### Clone Notablog Starter Repository Source: https://github.com/dragonman225/notablog/blob/master/README.md Clone the starter repository to begin setting up your Notablog project. This provides a basic structure for your blog. ```bash git clone https://github.com/dragonman225/notablog-starter.git ``` -------------------------------- ### Notablog Starter Folder Structure Source: https://github.com/dragonman225/notablog/blob/master/README.md Illustrates the directory layout for a Notablog starter project, including configuration, public assets, cache, and themes. ```tree notablog-starter ├── config.json ├── public ├── cache └── themes ├── pure └── pure-ejs ``` -------------------------------- ### Generate Static Site with Notablog CLI Source: https://context7.com/dragonman225/notablog/llms.txt Use the `generate` command to build your static blog. Options include forcing a re-fetch of all pages (`--fresh`) and enabling verbose logging (`--verbose`). ```bash notablog generate ./my-blog ``` ```bash notablog generate ./my-blog --fresh ``` ```bash notablog generate ./my-blog --verbose ``` ```bash notablog generate ./my-blog --fresh --verbose ``` -------------------------------- ### CLI - notablog generate Source: https://context7.com/dragonman225/notablog/llms.txt Generates the entire static site from a notablog-starter directory. Fetches all Notion pages, renders the index, tag pages, and individual post pages, then writes output to /public/. Uses a file-based cache so only updated pages are re-fetched from Notion. ```APIDOC ## CLI - `notablog generate` Generates the entire static site from a `notablog-starter` directory. Fetches all Notion pages, renders the index, tag pages, and individual post pages, then writes output to `/public/`. Uses a file-based cache so only updated pages are re-fetched from Notion. ### Usage ```bash # Basic generation (reads config.json from ./my-blog) notablog generate ./my-blog # Force re-fetch all pages, ignoring the local cache notablog generate ./my-blog --fresh # Enable verbose / debug logging notablog generate ./my-blog --verbose # Combined flags notablog generate ./my-blog --fresh --verbose ``` ### Expected Output ``` [notablog] Copy theme assets [notablog] Fetch site metadata [notablog] Render home page and tags [notablog] Fetch and render pages [notablog] 3 of 10 posts have been updated [notablog] 8 of 10 posts are published [notablog] Done in 4.2s. Run 'notablog preview ./my-blog' to preview ``` ``` -------------------------------- ### CLI - notablog preview Source: https://context7.com/dragonman225/notablog/llms.txt Opens the generated public/index.html in a detached browser process. The browser executable path must be set in the starter's config.json under previewBrowser. ```APIDOC ## CLI - `notablog preview` Opens the generated `public/index.html` in a detached browser process. The browser executable path must be set in the starter's `config.json` under `previewBrowser`. ### Usage ```bash # Preview the generated blog notablog preview ./my-blog # Help text notablog help ``` ### Configuration (`config.json`) `config.json` must contain the `previewBrowser` field: ```json { "url": "https://www.notion.so/b6fcf809ca5047b89f423948dce013a0?v=03ddc4d6130a47f8b68e74c9d0061de2", "theme": "pure-ejs", "previewBrowser": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "autoSlug": true, "locales": "en-US" } ``` ``` -------------------------------- ### Preview Blog with Notablog Source: https://github.com/dragonman225/notablog/blob/master/README.md Preview your generated blog. You can specify a custom browser path in config.json or use the command with the path to your project directory. ```bash notablog preview ``` -------------------------------- ### Notablog Configuration for Preview Source: https://context7.com/dragonman225/notablog/llms.txt The `config.json` file must include the `previewBrowser` field to specify the browser executable path for the `preview` command. ```json { "url": "https://www.notion.so/b6fcf809ca5047b89f423948dce013a0?v=03ddc4d6130a47f8b68e74c9d0061de2", "theme": "pure-ejs", "previewBrowser": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "autoSlug": true, "locales": "en-US" } ``` -------------------------------- ### Preview Blog Programmatically with Notablog Source: https://context7.com/dragonman225/notablog/llms.txt Use the `preview` function from the Notablog library to open the generated blog in a browser. This function is synchronous and will throw an error if `previewBrowser` is not configured. ```typescript import { preview } from 'notablog' try { preview('./my-blog') // Browser opens public/index.html in a detached process } catch (error) { // Throws: '"previewBrowser" property is not set in your Notablog config file.' console.error(error.message) } ``` -------------------------------- ### Generate Blog with Notablog Source: https://github.com/dragonman225/notablog/blob/master/README.md Generate the blog content from your Notion table. Run this command inside the notablog-starter directory after configuring your Notion table URL in config.json. ```bash notablog generate . ``` -------------------------------- ### Programmatic API - generate(workDir, opts) Source: https://context7.com/dragonman225/notablog/llms.txt Generates the blog programmatically. Returns a Promise that resolves to 0 on success. Accepts an options object for concurrency, verbosity, and cache control. ```APIDOC ## Programmatic API — `generate(workDir, opts)` Generates the blog programmatically. Returns a `Promise` that resolves to `0` on success. Accepts an options object for concurrency, verbosity, and cache control. ### Parameters - `workDir` (string): The working directory of the notablog-starter. - `opts` (object): Options for generation. - `concurrency` (number): Number of parallel page fetch/render tasks (default: 3). - `verbose` (boolean): Enable debug-level logging (default: false). - `ignoreCache` (boolean): Set true to force re-fetch all pages (default: false). ### Usage ```typescript import { generate } from 'notablog' async function buildBlog() { try { const exitCode = await generate('./my-blog', { concurrency: 5, // number of parallel page fetch/render tasks (default: 3) verbose: false, // enable debug-level logging ignoreCache: false, // set true to force re-fetch all pages }) console.log(`Build finished with exit code ${exitCode}`) // 0 = success } catch (error) { console.error('Build failed:', error.message) // Common errors: // - Cannot find theme "pure-ejs" in themes/ // - Required property "publish" is missing in the Notion table. // - Failed to load config from "./my-blog/config.json". } } buildBlog() ``` ``` -------------------------------- ### Manage Notablog Configuration Source: https://context7.com/dragonman225/notablog/llms.txt Use the Config class to read, update, and persist blog configuration from a JSON file. Ensure the config file path is correctly provided. ```typescript import { Config } from 'notablog/dist/config' const config = new Config('./my-blog/config.json') // Read values const notionUrl: string = config.get('url') const theme: string = config.get('theme') const locales: string | string[] | undefined = config.get('locales') const autoSlug: boolean = config.get('autoSlug') // Update a value in memory config.set('theme', 'pure-ejs') config.set('locales', ['en-US', 'zh-TW']) // Persist changes to config.json await config.sync() ``` -------------------------------- ### Generate Blog Programmatically with Notablog Source: https://context7.com/dragonman225/notablog/llms.txt Use the `generate` function from the Notablog library to build your site programmatically. Configure concurrency, verbosity, and cache behavior via the options object. The function returns a Promise that resolves to an exit code. ```typescript import { generate } from 'notablog' async function buildBlog() { try { const exitCode = await generate('./my-blog', { concurrency: 5, // number of parallel page fetch/render tasks (default: 3) verbose: false, // enable debug-level logging ignoreCache: false, // set true to force re-fetch all pages }) console.log(`Build finished with exit code ${exitCode}`) // 0 = success } catch (error) { console.error('Build failed:', error.message) // Common errors: // - Cannot find theme "pure-ejs" in themes/ // - Required property "publish" is missing in the Notion table. // - Failed to load config from "./my-blog/config.json". } } buildBlog() ``` -------------------------------- ### TemplateProvider Class Source: https://context7.com/dragonman225/notablog/llms.txt Provides a lazy-loading cache for HTML template files, reading them from a specified layouts directory. ```APIDOC ## `TemplateProvider` class Lazy-loading template cache that reads HTML template files from a layouts directory by name (without `.html` extension). Used internally by renderer strategies. ```typescript import { TemplateProvider } from 'notablog/dist/templateProvider' const provider = new TemplateProvider('./my-blog/themes/pure-ejs/layouts') // Load and cache the template (reads from layouts/index.html) const indexTemplate = provider.get('index') console.log(indexTemplate.content) // EJS template string console.log(indexTemplate.filePath) // Absolute path to the .html file // If template doesn't exist, returns an error string instead of throwing: const missing = provider.get('nonexistent') // missing.content === 'Cannot find "nonexistent.html" in ".../layouts".' ``` ``` -------------------------------- ### Update Blog with Notablog Source: https://github.com/dragonman225/notablog/blob/master/README.md Re-generate your blog content to reflect any updates made to your Notion table. This command can be run from inside or outside the project directory. ```bash notablog generate ``` -------------------------------- ### Config Class Source: https://context7.com/dragonman225/notablog/llms.txt Manages the config.json file, providing typed accessors and a sync method to persist changes. ```APIDOC ## `Config` class Reads and manages the `config.json` file from a `notablog-starter` directory. Provides typed `get`/`set` accessors and an async `sync()` method to persist changes back to disk. ```typescript import { Config } from 'notablog/dist/config' const config = new Config('./my-blog/config.json') // Read values const notionUrl: string = config.get('url') const theme: string = config.get('theme') const locales: string | string[] | undefined = config.get('locales') const autoSlug: boolean = config.get('autoSlug') // Update a value in memory config.set('theme', 'pure-ejs') config.set('locales', ['en-US', 'zh-TW']) // Persist changes to config.json await config.sync() ``` The full `NotablogStarterConfig` interface: ```typescript interface NotablogStarterConfig { url: string // Public Notion collection page URL theme: string // Theme folder name inside themes/ previewBrowser: string // Absolute path to browser binary autoSlug: boolean // Auto-generate URL slugs from page titles locales: string | string[] | undefined // BCP 47 locale(s) for date formatting } ``` ``` -------------------------------- ### Notablog Theme manifest.json Configuration Source: https://context7.com/dragonman225/notablog/llms.txt The manifest.json file is required in your theme directory. It declares the Notablog version and the template engine used. ```json { "notablogVersion": "^0.6.0", "templateEngine": "ejs" } ``` -------------------------------- ### Renderer and Strategies Source: https://context7.com/dragonman225/notablog/llms.txt Decouples template engines from rendering logic using a strategy pattern. Supports EJS (default) and Squirrelly. ```APIDOC ## `Renderer` / `EJSStrategy` / `SqrlStrategy` Strategy-pattern renderer that decouples template engines from rendering logic. `EJSStrategy` (default) uses EJS; `SqrlStrategy` uses Squirrelly. Both load templates lazily from disk via `TemplateProvider`. ```typescript import { Renderer, EJSStrategy, SqrlStrategy } from 'notablog/dist/renderer' // --- EJS (default, used by the built-in "pure-ejs" theme) --- const ejsStrategy = new EJSStrategy('./my-blog/themes/pure-ejs/layouts') const renderer = new Renderer(ejsStrategy) // Render the index page const indexHtml = renderer.render('index', { siteMeta: { title: 'My Blog', iconUrl: undefined, cover: undefined, descriptionPlain: 'A blog about things.', descriptionHTML: '

A blog about things.

', pages: [], // PageMetadata[] tagMap: new Map(), }, }) // Render a post page (template name comes from the "template" column in Notion) const postHtml = renderer.render('post', { siteMeta: { /* SiteContext */ }, post: { id: 'abc123', title: 'Hello World', url: 'hello-world-abc123.html', publish: true, inMenu: false, inList: true, tags: [{ value: 'tech', color: 'blue' }], date: '2024-01-15', dateString: 'Mon, Jan 15, 2024', descriptionPlain: 'My first post.', descriptionHTML: '

My first post.

', contentHTML: '

Hello World

Content here...

', }, }) // --- Squirrelly alternative --- const sqrlStrategy = new SqrlStrategy('./my-blog/themes/my-sqrl-theme/layouts') const sqrlRenderer = new Renderer(sqrlStrategy) const html = sqrlRenderer.render('index', { siteMeta: { /* ... */ } }) ``` ``` -------------------------------- ### Fetch and Parse Notion Collection Page Source: https://context7.com/dragonman225/notablog/llms.txt Use this function to fetch data from a public Notion collection page and convert it into a SiteContext object. Ensure the Notion page has the required columns. ```typescript import { createAgent } from 'notionapi-agent' import { parseTable } from 'notablog/dist/parseTable' import { Config } from 'notablog/dist/config' const notionAgent = createAgent({ debug: false }) const config = new Config('./my-blog/config.json') const siteContext = await parseTable( 'https://www.notion.so/b6fcf809ca5047b89f423948dce013a0?v=03ddc4d6130a47f8b68e74c9d0061de2', notionAgent, config ) // SiteContext shape: console.log(siteContext.title) // Blog name from the collection page console.log(siteContext.descriptionPlain) // Plain-text description console.log(siteContext.pages.length) // Total number of rows in the table console.log(siteContext.tagMap.get('tech')) // PageMetadata[] for the "tech" tag // Each PageMetadata in siteContext.pages: const page = siteContext.pages[0] console.log(page.id) // Notion page ID (no dashes) console.log(page.title) // Page title console.log(page.url) // Generated filename, e.g. "hello-world-abc123.html" console.log(page.publish) // true/false — whether to render this page console.log(page.inMenu) // true/false — show in nav bar console.log(page.inList) // true/false — show in article list console.log(page.template) // EJS template name, e.g. "post" console.log(page.tags) // [{ value: 'tech', color: 'blue' }, ...] console.log(page.date) // "2024-01-15" (ISO date string or undefined) console.log(page.dateString) // "Mon, Jan 15, 2024" (locale-formatted) console.log(page.lastEditedTime) // Unix ms timestamp for cache invalidation ``` -------------------------------- ### Render Content with EJS or Squirrelly Source: https://context7.com/dragonman225/notablog/llms.txt Use the Renderer class with either EJSStrategy or SqrlStrategy to render templates. Templates are loaded lazily from disk. Ensure the correct layout directory is specified. ```typescript import { Renderer, EJSStrategy, SqrlStrategy } from 'notablog/dist/renderer' // --- EJS (default, used by the built-in "pure-ejs" theme) --- const ejsStrategy = new EJSStrategy('./my-blog/themes/pure-ejs/layouts') const renderer = new Renderer(ejsStrategy) // Render the index page const indexHtml = renderer.render('index', { siteMeta: { title: 'My Blog', iconUrl: undefined, cover: undefined, descriptionPlain: 'A blog about things.', descriptionHTML: '

A blog about things.

', pages: [], // PageMetadata[] tagMap: new Map(), }, }) // Render a post page (template name comes from the "template" column in Notion) const postHtml = renderer.render('post', { siteMeta: { /* SiteContext */ }, post: { id: 'abc123', title: 'Hello World', url: 'hello-world-abc123.html', publish: true, inMenu: false, inList: true, tags: [{ value: 'tech', color: 'blue' }], date: '2024-01-15', dateString: 'Mon, Jan 15, 2024', descriptionPlain: 'My first post.', descriptionHTML: '

My first post.

', contentHTML: '

Hello World

Content here...

', }, }) // --- Squirrelly alternative --- const sqrlStrategy = new SqrlStrategy('./my-blog/themes/my-sqrl-theme/layouts') const sqrlRenderer = new Renderer(sqrlStrategy) const html = sqrlRenderer.render('index', { siteMeta: { /* ... */ } }) ``` -------------------------------- ### Notablog Theme Folder Structure Source: https://github.com/dragonman225/notablog/blob/master/README.md Defines the standard directory layout for a Notablog theme, including layouts, assets, and the manifest file. ```tree ├── layouts ├── assets └── manifest.json ``` -------------------------------- ### Notablog Manifest JSON Configuration Source: https://github.com/dragonman225/notablog/blob/master/README.md Specifies the configuration options for a Notablog theme, including the supported Notablog version and the template engine. ```json { "notablogVersion": "string", "templateEngine": "string" } ``` -------------------------------- ### Implement File-Based Cache Source: https://context7.com/dragonman225/notablog/llms.txt Utilize the Cache class for efficient, file-based caching of serialized data. The directory is auto-created if it doesn't exist. Use `shouldUpdate` to check for modifications. ```typescript import { Cache } from 'notablog/dist/cache' const cache = new Cache('./my-blog/cache') // auto-creates directory if missing // Store any JSON-serializable object cache.set('notion', 'page-id-abc123', { title: 'My Post', content: '...' }) // Retrieve stored object (returns undefined if missing or corrupted) const data = cache.get('notion', 'page-id-abc123') if (data) { console.log(data) // { title: 'My Post', content: '...' } } // Check if the page was updated after the last cache write const lastEditedTime = 1691000000000 // Unix ms timestamp from Notion const needsUpdate = cache.shouldUpdate('notion', 'page-id-abc123', lastEditedTime) // Returns true → page is newer than cache, re-fetch required // Returns false → cached version is still current // Compute the hashed file path for a cached entry const filePath = cache.fPath('notion', 'page-id-abc123') // e.g. './my-blog/cache/a3f7c2b9...' ``` -------------------------------- ### Cache Class Source: https://context7.com/dragonman225/notablog/llms.txt A file-based cache for storing serialized NAST page trees, used to skip re-fetching unchanged Notion pages. ```APIDOC ## `Cache` class A namespace-scoped, file-based cache backed by SHA-256-hashed filenames. Used internally to store serialized NAST page trees and skip re-fetching unchanged Notion pages. ```typescript import { Cache } from 'notablog/dist/cache' const cache = new Cache('./my-blog/cache') // auto-creates directory if missing // Store any JSON-serializable object cache.set('notion', 'page-id-abc123', { title: 'My Post', content: '...' }) // Retrieve stored object (returns undefined if missing or corrupted) const data = cache.get('notion', 'page-id-abc123') if (data) { console.log(data) // { title: 'My Post', content: '...' } } // Check if the page was updated after the last cache write const lastEditedTime = 1691000000000 // Unix ms timestamp from Notion const needsUpdate = cache.shouldUpdate('notion', 'page-id-abc123', lastEditedTime) // Returns true → page is newer than cache, re-fetch required // Returns false → cached version is still current // Compute the hashed file path for a cached entry const filePath = cache.fPath('notion', 'page-id-abc123') // e.g. './my-blog/cache/a3f7c2b9...' ``` ``` -------------------------------- ### Provide Templates Lazily Source: https://context7.com/dragonman225/notablog/llms.txt The TemplateProvider class manages a cache of HTML templates loaded from a specified directory. It reads templates lazily by name without the `.html` extension. Returns an error string for missing templates. ```typescript import { TemplateProvider } from 'notablog/dist/templateProvider' const provider = new TemplateProvider('./my-blog/themes/pure-ejs/layouts') // Load and cache the template (reads from layouts/index.html) const indexTemplate = provider.get('index') console.log(indexTemplate.content) // EJS template string console.log(indexTemplate.filePath) // Absolute path to the .html file // If template doesn't exist, returns an error string instead of throwing: const missing = provider.get('nonexistent') // missing.content === 'Cannot find "nonexistent.html" in ".../layouts".' ``` -------------------------------- ### Notablog Index Template Data Source: https://github.com/dragonman225/notablog/blob/master/README.md The data object passed to the `index.html` template, containing site-wide metadata. ```typescript { siteMeta: SiteContext } ``` -------------------------------- ### Notablog Post Template Data Source: https://github.com/dragonman225/notablog/blob/master/README.md The data object passed to the `post.html` template, containing site metadata, post details, and the generated HTML content. ```typescript { siteMeta: SiteContext post: PageMetadata & { contentHTML: string } // All properties of PageMetadata plus contentHTML. } ``` -------------------------------- ### Notablog Tag Template Data Source: https://github.com/dragonman225/notablog/blob/master/README.md The data object passed to the `tag.html` template, including site metadata, the current tag name, and associated pages. ```typescript { siteMeta: SiteContext tagName: string pages: PageMetadata[] } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.