diff --git a/docs/site.json b/docs/site.json index 89193d5e29..f478492450 100644 --- a/docs/site.json +++ b/docs/site.json @@ -6,6 +6,16 @@ "codeTheme": "light" }, "pages": [ + { + "src": "userGuide/customFileTypes/sampleJson.md", + "fileExtension": ".json", + "searchable": "no" + }, + { + "src": "userGuide/customFileTypes/sampleTxt.md", + "fileExtension": ".txt", + "searchable": "no" + }, { "glob": ["*.md", "userGuide/*.md", "userGuide/components/*.md", "devGuide/*.md", "devGuide/*/*.md"] }, diff --git a/docs/userGuide/addingPages.md b/docs/userGuide/addingPages.md index 38e31524eb..b36d460e37 100644 --- a/docs/userGuide/addingPages.md +++ b/docs/userGuide/addingPages.md @@ -94,5 +94,56 @@ in your `site.json` file. See the [`pagesExclude` attribute](siteJsonFile.html#p +## Generating Custom File Types + +You can also use MarkBind to generate non-HTML files, such as `.json` or `.txt`, from your source `.md` files. This is useful for generating dynamic configuration files or other text-based assets that can benefit from MarkBind's variable system. + +To do this, specify the `fileExtension` property for the page configuration in your `site.json`, as explained [here](siteJsonFile.html#pages). + +
+ +{{ icon_example }} Generating a `sampleJson.json` from `sampleJson.md`: + +**site.json:** +```json +{ + "pages": [ + { + "src": "userGuide/customFileTypes/sampleJson.md", + "fileExtension": ".json", + "searchable": "no" + } + ] +} +``` +**sampleJson.md:** + +{% raw %} +```md +{ + "title": "globalVariables", + "description": "This file is served as a .json file, but is sourced from a .md file in the source code.", + "baseUrl": "{{baseUrl}}", + "timestamp": "{{timestamp}}", + "MarkBind": "{{MarkBind}}" +} +``` +{% endraw %} + + +**Examples of generated custom files:** +* [sampleJson]({{baseUrl}}/userGuide/customFileTypes/sampleJson.json){no-validation} - A JSON file generated from Markdown, utilizing default global nunjucks variables available in MarkBind sites. +* [sampleTxt]({{baseUrl}}/userGuide/customFileTypes/sampleTxt.txt){no-validation} - A plain text file generated from Markdown, also utilizing global variables. + + +
+ +**Key Points:** +* **Nunjucks Variables**: You can use Nunjucks global variables (like `baseUrl`, `timestamp`) and site variables within these files, just like in your HTML pages. +* **No Frontmatter or Scripts**: Unlike standard MarkBind pages, custom file types **do not support frontmatter or scripts**. Frontmatter and script tags will be treated as plain text if included. +* **Search**: By default, these files are searchable if `enableSearch` is true for the site. You can disable this by setting `"searchable": "no"` (or `false`) in the page config. +* **Intra-site validation**: If you are linking such files in other pages in your site, you can use the `{no-validation}` tag, e.g. `[sampleTxt]({{baseUrl}}/userGuide/customFileTypes/sampleTxt.txt){no-validation}` to disable intra-site validation warnings for the link, as MarkBind currently does not support intra-site validation for custom file type links. + + {% from "njk/common.njk" import previous_next %} {{ previous_next('authoringContents', 'markBindSyntaxOverview') }} diff --git a/docs/userGuide/customFileTypes/sampleJson.md b/docs/userGuide/customFileTypes/sampleJson.md new file mode 100644 index 0000000000..c3c2130bd3 --- /dev/null +++ b/docs/userGuide/customFileTypes/sampleJson.md @@ -0,0 +1,7 @@ +{ + "title": "globalVariables", + "description": "This file is served as a .json file, but is sourced from a .md file in the source code.", + "baseUrl": "{{baseUrl}}", + "timestamp": "{{timestamp}}", + "MarkBind": "{{MarkBind}}" +} \ No newline at end of file diff --git a/docs/userGuide/customFileTypes/sampleTxt.md b/docs/userGuide/customFileTypes/sampleTxt.md new file mode 100644 index 0000000000..8911c3f117 --- /dev/null +++ b/docs/userGuide/customFileTypes/sampleTxt.md @@ -0,0 +1,8 @@ +This file is served as a .txt file, sourced from a .md file. + +The content below demonstrates nunjucks variables usage for custom file types: + + +{% raw %} {{ baseUrl }} {% endraw %}: "{{ baseUrl }}" +{% raw %} {{ timestamp }} {% endraw %}: "{{ timestamp }}" +{% raw %} {{ MarkBind }} {% endraw %}: ”{{ MarkBind }}“ diff --git a/docs/userGuide/siteJsonFile.md b/docs/userGuide/siteJsonFile.md index 1ab30aed76..d1a740baea 100644 --- a/docs/userGuide/siteJsonFile.md +++ b/docs/userGuide/siteJsonFile.md @@ -138,6 +138,7 @@ _(Optional)_ **The styling options to be applied to the site.** This includes: * **`searchable`**: Specifies that the page(s) should be excluded from searching. Default: `yes`. * **`externalScripts`**: An array of external scripts to be referenced on the page. Scripts referenced will be run before the layout script. * **`frontmatter`**: Specifies properties to add to the frontmatter of a page or glob of pages. Overrides any existing properties if they have the same name, and overrides any frontmatter properties specified in `globalOverride`. +* **`fileExtension`**: A string that specifies the output file extension (e.g., `".json"`, `".txt"`) for the generated file. If not specified, defaults to `".html"`. Note that non-HTML files do not support frontmatter or scripts.
diff --git a/packages/core/src/Page/PageConfig.ts b/packages/core/src/Page/PageConfig.ts index 82a67b3031..6d37f73134 100644 --- a/packages/core/src/Page/PageConfig.ts +++ b/packages/core/src/Page/PageConfig.ts @@ -65,6 +65,11 @@ export class PageConfig { */ src: string; title?: string; + /** + * The compiled Nunjucks template for the page wrapper (page.njk). + * This is used to generate the HTML version of the page, handling the global structure + * (html, head, body, scripts) wrapping the processed content. + */ template: Template; variableProcessor: VariableProcessor; addressablePagesSource: string[]; diff --git a/packages/core/src/Page/PageVueServerRenderer.ts b/packages/core/src/Page/PageVueServerRenderer.ts index 3d42897f41..b2f52e0786 100644 --- a/packages/core/src/Page/PageVueServerRenderer.ts +++ b/packages/core/src/Page/PageVueServerRenderer.ts @@ -138,7 +138,7 @@ Bundle is regenerated by webpack and built pages are re-rendered with the latest Object.values(pageEntries).forEach(async (pageEntry) => { const { page, renderFn } = pageEntry; const renderedVuePageContent = await renderVuePage(renderFn); - page.outputPageHtml(renderedVuePageContent); + page.writeOutputFile(renderedVuePageContent); }); } diff --git a/packages/core/src/Page/index.ts b/packages/core/src/Page/index.ts index f4a0db76c1..47804da091 100644 --- a/packages/core/src/Page/index.ts +++ b/packages/core/src/Page/index.ts @@ -505,6 +505,16 @@ export class Page { return ''; } + /** + * Generates the page content by processing variables, nodes, frontmatter, and plugins. + * + * If the file extension is not .html, it simply processes variables and writes the content. + * + * For HTML files (usual case), it also builds the page navigation, handles layout, + * collects headings/keywords, and compiles the page into a Vue application for server-side rendering. + * Finally, it writes the rendered HTML to the output file. + * @param externalManager to manage external dependencies and configuration + */ async generate(externalManager: ExternalManager) { this.resetState(); // Reset for live reload @@ -531,6 +541,19 @@ export class Page { pluginManager, siteLinkManager, this.pageUserScriptsAndStyles); let content = variableProcessor.renderWithSiteVariables(this.pageConfig.sourcePath, pageSources); + + if (path.extname(this.pageConfig.resultPath) !== '.html') { + const hasFrontmatterLike = /^\s*---\s*[\s\S]*?---/m.test(content); + const hasScriptTagLike = /]/i.test(content); + if (hasFrontmatterLike || hasScriptTagLike) { + logger.warn('Detected frontmatter or