No longer index templates tagged as #template
This commit is contained in:
parent
366b2ed395
commit
d58db6aa1a
@ -11,12 +11,17 @@ import {
|
|||||||
|
|
||||||
export type FrontMatter = { tags: string[] } & Record<string, any>;
|
export type FrontMatter = { tags: string[] } & Record<string, any>;
|
||||||
|
|
||||||
// Extracts front matter (or legacy "meta" code blocks) from a markdown document
|
export type FrontmatterExtractOptions = {
|
||||||
|
removeKeys?: string[];
|
||||||
|
removeTags?: string[] | true;
|
||||||
|
removeFrontmatterSection?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extracts front matter from a markdown document
|
||||||
// optionally removes certain keys from the front matter
|
// optionally removes certain keys from the front matter
|
||||||
export async function extractFrontmatter(
|
export async function extractFrontmatter(
|
||||||
tree: ParseTree,
|
tree: ParseTree,
|
||||||
removeKeys: string[] = [],
|
options: FrontmatterExtractOptions = {},
|
||||||
removeFrontmatterSection = false,
|
|
||||||
): Promise<FrontMatter> {
|
): Promise<FrontMatter> {
|
||||||
let data: FrontMatter = {
|
let data: FrontMatter = {
|
||||||
tags: [],
|
tags: [],
|
||||||
@ -37,6 +42,12 @@ export async function extractFrontmatter(
|
|||||||
if (!data.tags.includes(tagname)) {
|
if (!data.tags.includes(tagname)) {
|
||||||
data.tags.push(tagname);
|
data.tags.push(tagname);
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
options.removeTags === true || options.removeTags?.includes(tagname)
|
||||||
|
) {
|
||||||
|
// Ugly hack to remove the hashtag
|
||||||
|
h.children![0].text = "";
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Find FrontMatter and parse it
|
// Find FrontMatter and parse it
|
||||||
@ -55,10 +66,10 @@ export async function extractFrontmatter(
|
|||||||
if (typeof data.tags === "string") {
|
if (typeof data.tags === "string") {
|
||||||
data.tags = (data.tags as string).split(/,\s*/);
|
data.tags = (data.tags as string).split(/,\s*/);
|
||||||
}
|
}
|
||||||
if (removeKeys.length > 0) {
|
if (options.removeKeys && options.removeKeys.length > 0) {
|
||||||
let removedOne = false;
|
let removedOne = false;
|
||||||
|
|
||||||
for (const key of removeKeys) {
|
for (const key of options.removeKeys) {
|
||||||
if (key in newData) {
|
if (key in newData) {
|
||||||
delete newData[key];
|
delete newData[key];
|
||||||
removedOne = true;
|
removedOne = true;
|
||||||
@ -69,7 +80,9 @@ export async function extractFrontmatter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If nothing is left, let's just delete this whole block
|
// If nothing is left, let's just delete this whole block
|
||||||
if (Object.keys(newData).length === 0 || removeFrontmatterSection) {
|
if (
|
||||||
|
Object.keys(newData).length === 0 || options.removeFrontmatterSection
|
||||||
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
@ -97,7 +97,7 @@ export function evalQueryExpression(
|
|||||||
return !(val1.length === val2.length &&
|
return !(val1.length === val2.length &&
|
||||||
val1.every((v) => val2.includes(v)));
|
val1.every((v) => val2.includes(v)));
|
||||||
}
|
}
|
||||||
return val1 !== val2;
|
return val1 != val2;
|
||||||
}
|
}
|
||||||
case "=~": {
|
case "=~": {
|
||||||
if (!Array.isArray(val2)) {
|
if (!Array.isArray(val2)) {
|
||||||
|
@ -28,7 +28,9 @@ export async function updateDirectivesOnPageCommand() {
|
|||||||
}
|
}
|
||||||
const text = await editor.getText();
|
const text = await editor.getText();
|
||||||
const tree = await markdown.parseMarkdown(text);
|
const tree = await markdown.parseMarkdown(text);
|
||||||
const metaData = await extractFrontmatter(tree, ["$disableDirectives"]);
|
const metaData = await extractFrontmatter(tree, {
|
||||||
|
removeKeys: ["$disableDirectives"],
|
||||||
|
});
|
||||||
|
|
||||||
if (isFederationPath(currentPage)) {
|
if (isFederationPath(currentPage)) {
|
||||||
console.info("Current page is a federation page, not updating directives.");
|
console.info("Current page is a federation page, not updating directives.");
|
||||||
@ -173,7 +175,9 @@ async function updateDirectivesForPage(
|
|||||||
const pageMeta = await space.getPageMeta(pageName);
|
const pageMeta = await space.getPageMeta(pageName);
|
||||||
const currentText = await space.readPage(pageName);
|
const currentText = await space.readPage(pageName);
|
||||||
const tree = await markdown.parseMarkdown(currentText);
|
const tree = await markdown.parseMarkdown(currentText);
|
||||||
const metaData = await extractFrontmatter(tree, ["$disableDirectives"]);
|
const metaData = await extractFrontmatter(tree, {
|
||||||
|
removeKeys: ["$disableDirectives"],
|
||||||
|
});
|
||||||
|
|
||||||
if (isFederationPath(pageName)) {
|
if (isFederationPath(pageName)) {
|
||||||
console.info("Current page is a federation page, not updating directives.");
|
console.info("Current page is a federation page, not updating directives.");
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { events } from "$sb/syscalls.ts";
|
import { events } from "$sb/syscalls.ts";
|
||||||
|
|
||||||
import { replaceTemplateVars } from "../template/template.ts";
|
import { replaceTemplateVars } from "../template/template.ts";
|
||||||
import { renderTemplate } from "./util.ts";
|
import { renderQueryTemplate } from "./util.ts";
|
||||||
import { jsonToMDTable } from "./util.ts";
|
import { jsonToMDTable } from "./util.ts";
|
||||||
import { ParseTree, parseTreeToAST } from "$sb/lib/tree.ts";
|
import { ParseTree, parseTreeToAST } from "$sb/lib/tree.ts";
|
||||||
import { astToKvQuery } from "$sb/lib/parse-query.ts";
|
import { astToKvQuery } from "$sb/lib/parse-query.ts";
|
||||||
@ -38,7 +38,7 @@ export async function queryDirectiveRenderer(
|
|||||||
// console.log("Parsed query", parsedQuery);
|
// console.log("Parsed query", parsedQuery);
|
||||||
const allResults = results.flat();
|
const allResults = results.flat();
|
||||||
if (parsedQuery.render) {
|
if (parsedQuery.render) {
|
||||||
const rendered = await renderTemplate(
|
const rendered = await renderQueryTemplate(
|
||||||
pageMeta,
|
pageMeta,
|
||||||
parsedQuery.render,
|
parsedQuery.render,
|
||||||
allResults,
|
allResults,
|
||||||
|
@ -8,6 +8,7 @@ import { directiveRegex } from "./directives.ts";
|
|||||||
import { updateDirectives } from "./command.ts";
|
import { updateDirectives } from "./command.ts";
|
||||||
import { resolvePath, rewritePageRefs } from "$sb/lib/resolve.ts";
|
import { resolvePath, rewritePageRefs } from "$sb/lib/resolve.ts";
|
||||||
import { PageMeta } from "$sb/types.ts";
|
import { PageMeta } from "$sb/types.ts";
|
||||||
|
import { renderTemplate } from "../template/plug_api.ts";
|
||||||
|
|
||||||
const templateRegex = /\[\[([^\]]+)\]\]\s*(.*)\s*/;
|
const templateRegex = /\[\[([^\]]+)\]\]\s*(.*)\s*/;
|
||||||
|
|
||||||
@ -52,7 +53,7 @@ export async function templateDirectiveRenderer(
|
|||||||
templateText = await space.readPage(templatePath);
|
templateText = await space.readPage(templatePath);
|
||||||
}
|
}
|
||||||
const tree = await markdown.parseMarkdown(templateText);
|
const tree = await markdown.parseMarkdown(templateText);
|
||||||
await extractFrontmatter(tree, [], true); // Remove entire frontmatter section, if any
|
await extractFrontmatter(tree, { removeFrontmatterSection: true }); // Remove entire frontmatter section, if any
|
||||||
|
|
||||||
// Resolve paths in the template
|
// Resolve paths in the template
|
||||||
rewritePageRefs(tree, templatePath);
|
rewritePageRefs(tree, templatePath);
|
||||||
@ -63,9 +64,7 @@ export async function templateDirectiveRenderer(
|
|||||||
|
|
||||||
// if it's a template injection (not a literal "include")
|
// if it's a template injection (not a literal "include")
|
||||||
if (directive === "use") {
|
if (directive === "use") {
|
||||||
newBody = await handlebars.renderTemplate(newBody, parsedArgs, {
|
newBody = await renderTemplate(newBody, pageMeta, parsedArgs);
|
||||||
page: pageMeta,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Recursively render directives
|
// Recursively render directives
|
||||||
const tree = await markdown.parseMarkdown(newBody);
|
const tree = await markdown.parseMarkdown(newBody);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { handlebars, space } from "$sb/syscalls.ts";
|
import { handlebars, space } from "$sb/syscalls.ts";
|
||||||
import { handlebarHelpers } from "../../common/syscalls/handlebar_helpers.ts";
|
import { handlebarHelpers } from "../../common/syscalls/handlebar_helpers.ts";
|
||||||
import { PageMeta } from "$sb/types.ts";
|
import { PageMeta } from "$sb/types.ts";
|
||||||
|
import { cleanTemplate, renderTemplate } from "../template/plug_api.ts";
|
||||||
|
|
||||||
export function defaultJsonTransformer(_k: string, v: any) {
|
export function defaultJsonTransformer(_k: string, v: any) {
|
||||||
if (v === undefined) {
|
if (v === undefined) {
|
||||||
@ -53,13 +54,14 @@ export function jsonToMDTable(
|
|||||||
return lines.join("\n");
|
return lines.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function renderTemplate(
|
export async function renderQueryTemplate(
|
||||||
pageMeta: PageMeta,
|
pageMeta: PageMeta,
|
||||||
renderTemplate: string,
|
templatePage: string,
|
||||||
data: any[],
|
data: any[],
|
||||||
renderAll: boolean,
|
renderAll: boolean,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
let templateText = await space.readPage(renderTemplate);
|
let templateText = await space.readPage(templatePage);
|
||||||
|
templateText = await cleanTemplate(templateText);
|
||||||
if (!renderAll) {
|
if (!renderAll) {
|
||||||
templateText = `{{#each .}}\n${templateText}\n{{/each}}`;
|
templateText = `{{#each .}}\n${templateText}\n{{/each}}`;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { editor, events, markdown, mq, space, system } from "$sb/syscalls.ts";
|
|||||||
import { sleep } from "$sb/lib/async.ts";
|
import { sleep } from "$sb/lib/async.ts";
|
||||||
import { IndexEvent } from "$sb/app_event.ts";
|
import { IndexEvent } from "$sb/app_event.ts";
|
||||||
import { MQMessage } from "$sb/types.ts";
|
import { MQMessage } from "$sb/types.ts";
|
||||||
|
import { isTemplate } from "../template/util.ts";
|
||||||
|
|
||||||
export async function reindexCommand() {
|
export async function reindexCommand() {
|
||||||
await editor.flashNotification("Performing full page reindex...");
|
await editor.flashNotification("Performing full page reindex...");
|
||||||
@ -42,9 +43,16 @@ export async function processIndexQueue(messages: MQMessage[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function parseIndexTextRepublish({ name, text }: IndexEvent) {
|
export async function parseIndexTextRepublish({ name, text }: IndexEvent) {
|
||||||
// console.log("Reindexing", name);
|
if (isTemplate(text)) {
|
||||||
await events.dispatchEvent("page:index", {
|
console.log("Indexing", name, "as template");
|
||||||
name,
|
await events.dispatchEvent("page:indexTemplate", {
|
||||||
tree: await markdown.parseMarkdown(text),
|
name,
|
||||||
});
|
tree: await markdown.parseMarkdown(text),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await events.dispatchEvent("page:index", {
|
||||||
|
name,
|
||||||
|
tree: await markdown.parseMarkdown(text),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,9 +41,5 @@ export function renderHtml(t: Tag | null): string {
|
|||||||
if (t.name === Fragment) {
|
if (t.name === Fragment) {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
// if (t.body) {
|
|
||||||
return `<${t.name}${attrs}>${body}</${t.name}>`;
|
return `<${t.name}${attrs}>${body}</${t.name}>`;
|
||||||
// } else {
|
|
||||||
// return `<${t.name}${attrs}/>`;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import type { WidgetContent } from "$sb/app_event.ts";
|
|||||||
import { events, language, space, system } from "$sb/syscalls.ts";
|
import { events, language, space, system } from "$sb/syscalls.ts";
|
||||||
import { parseTreeToAST } from "$sb/lib/tree.ts";
|
import { parseTreeToAST } from "$sb/lib/tree.ts";
|
||||||
import { astToKvQuery } from "$sb/lib/parse-query.ts";
|
import { astToKvQuery } from "$sb/lib/parse-query.ts";
|
||||||
import { jsonToMDTable, renderTemplate } from "../directive/util.ts";
|
import { jsonToMDTable, renderQueryTemplate } from "../directive/util.ts";
|
||||||
import { loadPageObject, replaceTemplateVars } from "../template/template.ts";
|
import { loadPageObject, replaceTemplateVars } from "../template/template.ts";
|
||||||
|
|
||||||
export async function widget(
|
export async function widget(
|
||||||
@ -44,7 +44,7 @@ export async function widget(
|
|||||||
} else {
|
} else {
|
||||||
if (parsedQuery.render) {
|
if (parsedQuery.render) {
|
||||||
// Configured a custom rendering template, let's use it!
|
// Configured a custom rendering template, let's use it!
|
||||||
const rendered = await renderTemplate(
|
const rendered = await renderQueryTemplate(
|
||||||
pageObject,
|
pageObject,
|
||||||
parsedQuery.render,
|
parsedQuery.render,
|
||||||
allResults,
|
allResults,
|
||||||
|
51
plugs/template/api.ts
Normal file
51
plugs/template/api.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { handlebars, markdown, YAML } from "$sb/syscalls.ts";
|
||||||
|
import type { PageMeta } from "$sb/types.ts";
|
||||||
|
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
|
||||||
|
import { TemplateObject } from "./types.ts";
|
||||||
|
import { renderToText } from "$sb/lib/tree.ts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strips the template from its frontmatter and renders it.
|
||||||
|
* The assumption is that the frontmatter has already been parsed and should not appear in thhe rendered output.
|
||||||
|
* @param templateText the template text
|
||||||
|
* @param data data to be rendered by the template
|
||||||
|
* @param globals a set of global variables
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function renderTemplate(
|
||||||
|
templateText: string,
|
||||||
|
pageMeta: PageMeta,
|
||||||
|
data: any = {},
|
||||||
|
): Promise<string> {
|
||||||
|
const tree = await markdown.parseMarkdown(templateText);
|
||||||
|
const frontmatter: Partial<TemplateObject> = await extractFrontmatter(tree, {
|
||||||
|
removeFrontmatterSection: true,
|
||||||
|
removeTags: ["template"],
|
||||||
|
});
|
||||||
|
templateText = renderToText(tree).trimStart();
|
||||||
|
// console.log(`Trimmed template: |${templateText}|`);
|
||||||
|
// If a 'frontmatter' key was specified in the frontmatter, use that as the frontmatter
|
||||||
|
if (frontmatter.frontmatter) {
|
||||||
|
if (typeof frontmatter.frontmatter === "string") {
|
||||||
|
templateText = "---\n" + frontmatter.frontmatter + "---\n" + templateText;
|
||||||
|
} else {
|
||||||
|
templateText = "---\n" + (await YAML.stringify(frontmatter.frontmatter)) +
|
||||||
|
"---\n" + templateText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return handlebars.renderTemplate(templateText, data, { page: pageMeta });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strips a template text from its frontmatter and #template tag
|
||||||
|
*/
|
||||||
|
export async function cleanTemplate(
|
||||||
|
templateText: string,
|
||||||
|
): Promise<string> {
|
||||||
|
const tree = await markdown.parseMarkdown(templateText);
|
||||||
|
await extractFrontmatter(tree, {
|
||||||
|
removeFrontmatterSection: true,
|
||||||
|
removeTags: ["template"],
|
||||||
|
});
|
||||||
|
return renderToText(tree).trimStart();
|
||||||
|
}
|
7
plugs/template/index.ts
Normal file
7
plugs/template/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import type { IndexTreeEvent } from "$sb/app_event.ts";
|
||||||
|
import { system } from "$sb/syscalls.ts";
|
||||||
|
|
||||||
|
export async function indexTemplate({ name, tree }: IndexTreeEvent) {
|
||||||
|
// Just delegate to the index plug
|
||||||
|
await system.invokeFunction("index.indexPage", { name, tree });
|
||||||
|
}
|
24
plugs/template/plug_api.ts
Normal file
24
plugs/template/plug_api.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import type { PageMeta } from "$sb/types.ts";
|
||||||
|
import { system } from "../../plug-api/syscalls.ts";
|
||||||
|
|
||||||
|
export function renderTemplate(
|
||||||
|
templateText: string,
|
||||||
|
pageMeta: PageMeta,
|
||||||
|
data: any = {},
|
||||||
|
): Promise<string> {
|
||||||
|
return system.invokeFunction(
|
||||||
|
"template.renderTemplate",
|
||||||
|
templateText,
|
||||||
|
pageMeta,
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cleanTemplate(
|
||||||
|
templateText: string,
|
||||||
|
): Promise<string> {
|
||||||
|
return system.invokeFunction(
|
||||||
|
"template.cleanTemplate",
|
||||||
|
templateText,
|
||||||
|
);
|
||||||
|
}
|
@ -1,5 +1,19 @@
|
|||||||
name: template
|
name: template
|
||||||
functions:
|
functions:
|
||||||
|
# API
|
||||||
|
renderTemplate:
|
||||||
|
path: api.ts:renderTemplate
|
||||||
|
cleanTemplate:
|
||||||
|
path: api.ts:cleanTemplate
|
||||||
|
|
||||||
|
insertTemplateText:
|
||||||
|
path: template.ts:insertTemplateText
|
||||||
|
|
||||||
|
|
||||||
|
indexTemplate:
|
||||||
|
path: ./index.ts:indexTemplate
|
||||||
|
events:
|
||||||
|
- page:indexTemplate
|
||||||
|
|
||||||
templateSlashCommand:
|
templateSlashCommand:
|
||||||
path: ./template.ts:templateSlashComplete
|
path: ./template.ts:templateSlashComplete
|
||||||
@ -10,8 +24,6 @@ functions:
|
|||||||
path: ./template.ts:insertSlashTemplate
|
path: ./template.ts:insertSlashTemplate
|
||||||
|
|
||||||
# Template commands
|
# Template commands
|
||||||
insertTemplateText:
|
|
||||||
path: "./template.ts:insertTemplateText"
|
|
||||||
applyLineReplace:
|
applyLineReplace:
|
||||||
path: ./template.ts:applyLineReplace
|
path: ./template.ts:applyLineReplace
|
||||||
insertFrontMatter:
|
insertFrontMatter:
|
||||||
|
@ -4,20 +4,19 @@ import { renderToText } from "$sb/lib/tree.ts";
|
|||||||
import { niceDate, niceTime } from "$sb/lib/dates.ts";
|
import { niceDate, niceTime } from "$sb/lib/dates.ts";
|
||||||
import { readSettings } from "$sb/lib/settings_page.ts";
|
import { readSettings } from "$sb/lib/settings_page.ts";
|
||||||
import { cleanPageRef } from "$sb/lib/resolve.ts";
|
import { cleanPageRef } from "$sb/lib/resolve.ts";
|
||||||
import { ObjectValue, PageMeta } from "$sb/types.ts";
|
import { PageMeta } from "$sb/types.ts";
|
||||||
import { CompleteEvent, SlashCompletion } from "$sb/app_event.ts";
|
import { CompleteEvent, SlashCompletion } from "$sb/app_event.ts";
|
||||||
import { getObjectByRef, queryObjects } from "../index/plug_api.ts";
|
import { getObjectByRef, queryObjects } from "../index/plug_api.ts";
|
||||||
|
import { TemplateObject } from "./types.ts";
|
||||||
export type TemplateObject = ObjectValue<{
|
import { renderTemplate } from "./api.ts";
|
||||||
trigger?: string; // has to start with # for now
|
|
||||||
scope?: string;
|
|
||||||
frontmatter?: Record<string, any> | string;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export async function templateSlashComplete(
|
export async function templateSlashComplete(
|
||||||
completeEvent: CompleteEvent,
|
completeEvent: CompleteEvent,
|
||||||
): Promise<SlashCompletion[]> {
|
): Promise<SlashCompletion[]> {
|
||||||
const allTemplates = await queryObjects<TemplateObject>("template", {});
|
const allTemplates = await queryObjects<TemplateObject>("template", {
|
||||||
|
// Only return templates that have a trigger
|
||||||
|
filter: ["!=", ["attr", "trigger"], ["null"]],
|
||||||
|
});
|
||||||
return allTemplates.map((template) => ({
|
return allTemplates.map((template) => ({
|
||||||
label: template.trigger!,
|
label: template.trigger!,
|
||||||
detail: "template",
|
detail: "template",
|
||||||
@ -31,14 +30,7 @@ export async function insertSlashTemplate(slashCompletion: SlashCompletion) {
|
|||||||
const pageObject = await loadPageObject(slashCompletion.pageName);
|
const pageObject = await loadPageObject(slashCompletion.pageName);
|
||||||
|
|
||||||
let templateText = await space.readPage(slashCompletion.templatePage);
|
let templateText = await space.readPage(slashCompletion.templatePage);
|
||||||
templateText = await replaceTemplateVars(templateText, pageObject);
|
templateText = await renderTemplate(templateText, pageObject);
|
||||||
const parseTree = await markdown.parseMarkdown(templateText);
|
|
||||||
const frontmatter = await extractFrontmatter(parseTree, [], true);
|
|
||||||
templateText = renderToText(parseTree).trim();
|
|
||||||
if (frontmatter.frontmatter) {
|
|
||||||
templateText = "---\n" + (await YAML.stringify(frontmatter.frontmatter)) +
|
|
||||||
"---\n" + templateText;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cursorPos = await editor.getCursor();
|
const cursorPos = await editor.getCursor();
|
||||||
const carretPos = templateText.indexOf("|^|");
|
const carretPos = templateText.indexOf("|^|");
|
||||||
@ -76,10 +68,12 @@ export async function instantiateTemplateCommand() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const parseTree = await markdown.parseMarkdown(text);
|
const parseTree = await markdown.parseMarkdown(text);
|
||||||
const additionalPageMeta = await extractFrontmatter(parseTree, [
|
const additionalPageMeta = await extractFrontmatter(parseTree, {
|
||||||
"$name",
|
removeKeys: [
|
||||||
"$disableDirectives",
|
"$name",
|
||||||
]);
|
"$disableDirectives",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
const tempPageMeta: PageMeta = {
|
const tempPageMeta: PageMeta = {
|
||||||
tags: ["page"],
|
tags: ["page"],
|
||||||
|
10
plugs/template/types.ts
Normal file
10
plugs/template/types.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { ObjectValue } from "$sb/types.ts";
|
||||||
|
|
||||||
|
export type TemplateFrontmatter = {
|
||||||
|
trigger?: string; // slash command name
|
||||||
|
scope?: string;
|
||||||
|
// Frontmatter can be encoded as an object (in which case we'll serialize it) or as a string
|
||||||
|
frontmatter?: Record<string, any> | string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TemplateObject = ObjectValue<TemplateFrontmatter>;
|
76
plugs/template/util.test.ts
Normal file
76
plugs/template/util.test.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { assertEquals } from "../../test_deps.ts";
|
||||||
|
import { isTemplate } from "./util.ts";
|
||||||
|
|
||||||
|
Deno.test("Test template extraction", () => {
|
||||||
|
assertEquals(
|
||||||
|
isTemplate(`---
|
||||||
|
name: bla
|
||||||
|
tags: template
|
||||||
|
---
|
||||||
|
|
||||||
|
Sup`),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
isTemplate(`---
|
||||||
|
tags: template, something else
|
||||||
|
---
|
||||||
|
`),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
isTemplate(`---
|
||||||
|
tags: something else, template
|
||||||
|
---
|
||||||
|
`),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
isTemplate(`---
|
||||||
|
tags:
|
||||||
|
- bla
|
||||||
|
- template
|
||||||
|
---
|
||||||
|
`),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
isTemplate(`#template`),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
isTemplate(` #template This is a template`),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
isTemplate(`---
|
||||||
|
tags:
|
||||||
|
- bla
|
||||||
|
somethingElse:
|
||||||
|
- template
|
||||||
|
---
|
||||||
|
`),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
isTemplate(`---
|
||||||
|
name: bla
|
||||||
|
tags: aefe
|
||||||
|
---
|
||||||
|
|
||||||
|
Sup`),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
isTemplate(`Sup`),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
47
plugs/template/util.ts
Normal file
47
plugs/template/util.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
const frontMatterRegex = /^---\n(([^\n]|\n)*?)---\n/;
|
||||||
|
const yamlKvRegex = /^\s*(\w+):\s*(.*)/;
|
||||||
|
const yamlListItemRegex = /^\s*-\s+(.+)/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quick and dirty way to check if a page is a template or not
|
||||||
|
* @param pageText
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function isTemplate(pageText: string): boolean {
|
||||||
|
const frontmatter = frontMatterRegex.exec(pageText);
|
||||||
|
// Poor man's YAML frontmatter parsing
|
||||||
|
if (frontmatter) {
|
||||||
|
pageText = pageText.slice(frontmatter[0].length);
|
||||||
|
const frontmatterText = frontmatter[1];
|
||||||
|
const lines = frontmatterText.split("\n");
|
||||||
|
let inTagsSection = false;
|
||||||
|
for (const line of lines) {
|
||||||
|
const yamlKv = yamlKvRegex.exec(line);
|
||||||
|
if (yamlKv) {
|
||||||
|
const [key, value] = yamlKv.slice(1);
|
||||||
|
// Looking for a 'tags' key
|
||||||
|
if (key === "tags") {
|
||||||
|
inTagsSection = true;
|
||||||
|
// 'template' there? Yay!
|
||||||
|
if (value.split(/,\s*/).includes("template")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
inTagsSection = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const yamlListem = yamlListItemRegex.exec(line);
|
||||||
|
if (yamlListem && inTagsSection) {
|
||||||
|
// List item is 'template'? Yay!
|
||||||
|
if (yamlListem[1] === "template") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Or if the page text starts with a #template tag
|
||||||
|
if (/^\s*#template(\W|$)/.test(pageText)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
@ -25,15 +25,15 @@ export class MainUI {
|
|||||||
viewState: AppViewState = initialViewState;
|
viewState: AppViewState = initialViewState;
|
||||||
viewDispatch: (action: Action) => void = () => {};
|
viewDispatch: (action: Action) => void = () => {};
|
||||||
|
|
||||||
constructor(private editor: Client) {
|
constructor(private client: Client) {
|
||||||
// Make keyboard shortcuts work even when the editor is in read only mode or not focused
|
// Make keyboard shortcuts work even when the editor is in read only mode or not focused
|
||||||
globalThis.addEventListener("keydown", (ev) => {
|
globalThis.addEventListener("keydown", (ev) => {
|
||||||
if (!editor.editorView.hasFocus) {
|
if (!client.editorView.hasFocus) {
|
||||||
if ((ev.target as any).closest(".cm-editor")) {
|
if ((ev.target as any).closest(".cm-editor")) {
|
||||||
// In some cm element, let's back out
|
// In some cm element, let's back out
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (runScopeHandlers(editor.editorView, ev, "editor")) {
|
if (runScopeHandlers(client.editorView, ev, "editor")) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,7 +52,7 @@ export class MainUI {
|
|||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
this.viewDispatch({
|
this.viewDispatch({
|
||||||
type: "show-palette",
|
type: "show-palette",
|
||||||
context: editor.getContext(),
|
context: client.getContext(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -63,7 +63,7 @@ export class MainUI {
|
|||||||
this.viewState = viewState;
|
this.viewState = viewState;
|
||||||
this.viewDispatch = dispatch;
|
this.viewDispatch = dispatch;
|
||||||
|
|
||||||
const editor = this.editor;
|
const editor = this.client;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (viewState.currentPage) {
|
if (viewState.currentPage) {
|
||||||
@ -78,8 +78,8 @@ export class MainUI {
|
|||||||
}, [viewState.uiOptions.forcedROMode]);
|
}, [viewState.uiOptions.forcedROMode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
this.editor.rebuildEditorState();
|
this.client.rebuildEditorState();
|
||||||
this.editor.dispatchAppEvent("editor:modeswitch");
|
this.client.dispatchAppEvent("editor:modeswitch");
|
||||||
}, [viewState.uiOptions.vimMode]);
|
}, [viewState.uiOptions.vimMode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -208,24 +208,24 @@ export class MainUI {
|
|||||||
// If we support syncOnly, don't show this toggle button
|
// If we support syncOnly, don't show this toggle button
|
||||||
? [{
|
? [{
|
||||||
icon: RefreshCwIcon,
|
icon: RefreshCwIcon,
|
||||||
description: this.editor.syncMode
|
description: this.client.syncMode
|
||||||
? "Currently in Sync mode, click to switch to Online mode"
|
? "Currently in Sync mode, click to switch to Online mode"
|
||||||
: "Currently in Online mode, click to switch to Sync mode",
|
: "Currently in Online mode, click to switch to Sync mode",
|
||||||
class: this.editor.syncMode ? "sb-enabled" : undefined,
|
class: this.client.syncMode ? "sb-enabled" : undefined,
|
||||||
callback: () => {
|
callback: () => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const newValue = !this.editor.syncMode;
|
const newValue = !this.client.syncMode;
|
||||||
|
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
localStorage.setItem("syncMode", "true");
|
localStorage.setItem("syncMode", "true");
|
||||||
this.editor.flashNotification(
|
this.client.flashNotification(
|
||||||
"Now switching to sync mode, one moment please...",
|
"Now switching to sync mode, one moment please...",
|
||||||
);
|
);
|
||||||
await sleep(1000);
|
await sleep(1000);
|
||||||
location.reload();
|
location.reload();
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem("syncMode");
|
localStorage.removeItem("syncMode");
|
||||||
this.editor.flashNotification(
|
this.client.flashNotification(
|
||||||
"Now switching to online mode, one moment please...",
|
"Now switching to online mode, one moment please...",
|
||||||
);
|
);
|
||||||
await sleep(1000);
|
await sleep(1000);
|
||||||
|
@ -3,7 +3,7 @@ For various use cases, SilverBullet uses [Handlebars templates](https://handleba
|
|||||||
Generally templates are stored in your space as regular pages, which allows for reuse. Some examples include [[template/task]] and [[template/page]].
|
Generally templates are stored in your space as regular pages, which allows for reuse. Some examples include [[template/task]] and [[template/page]].
|
||||||
As a convention, we often name templates with a `template/` prefix, although this is purely a convention.
|
As a convention, we often name templates with a `template/` prefix, although this is purely a convention.
|
||||||
|
|
||||||
[[Live Templates]] allow templates to be define inline, for instance:
|
[[Live Templates]] allow templates to be defined inline, for instance:
|
||||||
```template
|
```template
|
||||||
template: |
|
template: |
|
||||||
Hello, {{name}}! Today is _{{today}}_
|
Hello, {{name}}! Today is _{{today}}_
|
||||||
|
Loading…
Reference in New Issue
Block a user