Tags redo (#624)

Introduction of `tag` and `itags`
This commit is contained in:
Zef Hemel
2024-01-11 13:20:50 +01:00
committed by GitHub
parent 9a07c4c90a
commit 848211120c
31 changed files with 286 additions and 170 deletions
+1 -1
View File
@@ -83,7 +83,7 @@ function fileMetaToPageMeta(fileMeta: FileMeta): PageMeta {
return {
...fileMeta,
ref: fileMeta.name,
tags: ["page"],
tag: "page",
name,
created: new Date(fileMeta.created).toISOString(),
lastModified: new Date(fileMeta.lastModified).toISOString(),
+1 -1
View File
@@ -16,7 +16,7 @@ export async function indexAnchors({ name: pageName, tree }: IndexTreeEvent) {
const aName = n.children![0].text!.substring(1);
anchors.push({
ref: `${pageName}$${aName}`,
tags: ["anchor"],
tag: "anchor",
name: aName,
page: pageName,
pos: n.from!,
+11 -5
View File
@@ -71,7 +71,13 @@ export async function indexObjects<T>(
const kvs: KV<T>[] = [];
const allAttributes = new Map<string, string>(); // tag:name -> attributeType
for (const obj of objects) {
for (const tag of obj.tags) {
if (!obj.tag) {
console.error("Object has no tag", obj, "this shouldn't happen");
continue;
}
// Index as all the tag + any additional tags specified
const allTags = [obj.tag, ...obj.tags || []];
for (const tag of allTags) {
// The object itself
kvs.push({
key: [tag, cleanKey(obj.ref, page)],
@@ -91,7 +97,7 @@ export async function indexObjects<T>(
}
// Check for all tags attached to this object if they're builtins
// If so: if `attrName` is defined in the builtin, use the attributeType from there (mostly to preserve readOnly aspects)
for (const otherTag of obj.tags) {
for (const otherTag of allTags) {
const builtinAttributes = builtins[otherTag];
if (builtinAttributes && builtinAttributes[attrName]) {
allAttributes.set(
@@ -124,14 +130,14 @@ export async function indexObjects<T>(
await indexObjects<AttributeObject>(
page,
[...allAttributes].map(([key, value]) => {
const [tag, name] = key.split(":");
const [tagName, name] = key.split(":");
const attributeType = value.startsWith("!")
? value.substring(1)
: value;
return {
ref: key,
tags: ["attribute"],
tag,
tag: "attribute",
tagName,
name,
attributeType,
readOnly: value.startsWith("!"),
+3 -3
View File
@@ -7,7 +7,7 @@ import { determineTags } from "../../plug-api/lib/cheap_yaml.ts";
export type AttributeObject = ObjectValue<{
name: string;
attributeType: string;
tag: string;
tagName: string;
page: string;
readOnly: boolean;
}>;
@@ -49,7 +49,7 @@ export async function objectAttributeCompleter(
const attributeFilter: QueryExpression | undefined =
attributeCompleteEvent.source === ""
? prefixFilter
: ["and", prefixFilter, ["=", ["attr", "tag"], [
: ["and", prefixFilter, ["=", ["attr", "tagName"], [
"string",
attributeCompleteEvent.source,
]]];
@@ -63,7 +63,7 @@ export async function objectAttributeCompleter(
return allAttributes.map((value) => {
return {
name: value.name,
source: value.tag,
source: value.tagName,
attributeType: value.attributeType,
readOnly: value.readOnly,
} as AttributeCompletion;
+14 -8
View File
@@ -29,6 +29,12 @@ export const builtins: Record<string, Record<string, string>> = {
pos: "!number",
tags: "string[]",
},
item: {
ref: "!string",
name: "!string",
page: "!string",
tags: "string[]",
},
taskstate: {
ref: "!string",
tags: "!string[]",
@@ -46,7 +52,7 @@ export const builtins: Record<string, Record<string, string>> = {
ref: "!string",
name: "!string",
attributeType: "!string",
type: "!string",
tagName: "!string",
page: "!string",
readOnly: "!boolean",
},
@@ -84,11 +90,11 @@ export const builtins: Record<string, Record<string, string>> = {
export async function loadBuiltinsIntoIndex() {
console.log("Loading builtins attributes into index");
const allTags: ObjectValue<TagObject>[] = [];
for (const [tag, attributes] of Object.entries(builtins)) {
for (const [tagName, attributes] of Object.entries(builtins)) {
allTags.push({
ref: tag,
tags: ["tag"],
name: tag,
ref: tagName,
tag: "tag",
name: tagName,
page: builtinPseudoPage,
parent: "builtin",
});
@@ -96,9 +102,9 @@ export async function loadBuiltinsIntoIndex() {
builtinPseudoPage,
Object.entries(attributes).map(([name, attributeType]) => {
return {
ref: `${tag}:${name}`,
tags: ["attribute"],
tag,
ref: `${tagName}:${name}`,
tag: "attribute",
tagName,
name,
attributeType: attributeType.startsWith("!")
? attributeType.substring(1)
+9 -4
View File
@@ -4,6 +4,8 @@ import { collectNodesOfType, findNodeOfType } from "$sb/lib/tree.ts";
import { ObjectValue } from "$sb/types.ts";
import { indexObjects } from "./api.ts";
import { TagObject } from "./tags.ts";
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
import { updateITags } from "$sb/lib/tags.ts";
type DataObject = ObjectValue<
{
@@ -14,6 +16,7 @@ type DataObject = ObjectValue<
export async function indexData({ name, tree }: IndexTreeEvent) {
const dataObjects: ObjectValue<DataObject>[] = [];
const frontmatter = await extractFrontmatter(tree);
await Promise.all(
collectNodesOfType(tree, "FencedCode").map(async (t) => {
@@ -41,19 +44,21 @@ export async function indexData({ name, tree }: IndexTreeEvent) {
continue;
}
const pos = t.from! + i;
dataObjects.push({
const dataObj = {
ref: `${name}@${pos}`,
tags: [dataType],
tag: dataType,
...doc,
pos,
page: name,
});
};
updateITags(dataObj, frontmatter);
dataObjects.push(dataObj);
}
// console.log("Parsed data", parsedData);
await indexObjects<TagObject>(name, [
{
ref: dataType,
tags: ["tag"],
tag: "tag",
name: dataType,
page: name,
parent: "data",
+6 -6
View File
@@ -87,13 +87,13 @@ functions:
indexParagraphs:
path: "./paragraph.ts:indexParagraphs"
events:
- page:index
- page:index
# Backlinks
indexLinks:
path: "./page_links.ts:indexLinks"
events:
- page:index
- page:index
attributeComplete:
path: "./attributes.ts:attributeComplete"
@@ -109,13 +109,13 @@ functions:
indexItem:
path: "./item.ts:indexItems"
events:
- page:index
- page:index
# Anchors
indexAnchors:
path: "./anchor.ts:indexAnchors"
events:
- page:index
- page:index
anchorComplete:
path: "./anchor.ts:anchorComplete"
events:
@@ -125,13 +125,13 @@ functions:
indexData:
path: data.ts:indexData
events:
- page:index
- page:index
# Hashtags
indexTags:
path: tags.ts:indexTags
events:
- page:index
- page:index
tagComplete:
path: tags.ts:tagComplete
events:
+10 -5
View File
@@ -5,6 +5,8 @@ import { extractAttributes } from "$sb/lib/attribute.ts";
import { rewritePageRefs } from "$sb/lib/resolve.ts";
import { ObjectValue } from "$sb/types.ts";
import { indexObjects } from "./api.ts";
import { updateITags } from "$sb/lib/tags.ts";
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
export type ItemObject = ObjectValue<
{
@@ -17,7 +19,7 @@ export type ItemObject = ObjectValue<
export async function indexItems({ name, tree }: IndexTreeEvent) {
const items: ObjectValue<ItemObject>[] = [];
// console.log("Indexing items", name);
const frontmatter = await extractFrontmatter(tree);
const coll = collectNodesOfType(tree, "ListItem");
@@ -30,11 +32,10 @@ export async function indexItems({ name, tree }: IndexTreeEvent) {
continue;
}
const tags = new Set<string>(["item"]);
const tags = new Set<string>();
const item: ItemObject = {
ref: `${name}@${n.from}`,
tags: [],
tag: "item",
name: "", // to be replaced
page: name,
pos: n.from!,
@@ -62,7 +63,11 @@ export async function indexItems({ name, tree }: IndexTreeEvent) {
}
item.name = textNodes.map(renderToText).join("").trim();
item.tags = [...tags.values()];
if (tags.size > 0) {
item.tags = [...tags];
}
updateITags(item, frontmatter);
items.push(item);
}
+1 -1
View File
@@ -16,7 +16,7 @@ export async function lintYAML({ tree }: LintEvent): Promise<LintDiagnostic[]> {
const tags = ["page", ...frontmatter.tags || []];
// Query all readOnly attributes for pages with this tag set
const readOnlyAttributes = await queryObjects<AttributeObject>("attribute", {
filter: ["and", ["=", ["attr", "tag"], [
filter: ["and", ["=", ["attr", "tagName"], [
"array",
tags.map((tag): QueryExpression => ["string", tag]),
]], [
+6 -14
View File
@@ -10,6 +10,7 @@ import {
renderToText,
traverseTreeAsync,
} from "$sb/lib/tree.ts";
import { updateITags } from "$sb/lib/tags.ts";
export async function indexPage({ name, tree }: IndexTreeEvent) {
if (name.startsWith("_")) {
@@ -24,7 +25,7 @@ export async function indexPage({ name, tree }: IndexTreeEvent) {
// Note the order here, making sure that the actual page meta data overrules
// any attempt to manually set built-in attributes like 'name' or 'lastModified'
// pageMeta appears at the beginning and the end due to the ordering behavior of ojects in JS (making builtin attributes appear first)
const combinedPageMeta = {
const combinedPageMeta: PageMeta = {
...pageMeta,
...frontmatter,
...toplevelAttributes,
@@ -33,18 +34,16 @@ export async function indexPage({ name, tree }: IndexTreeEvent) {
combinedPageMeta.tags = [
...new Set([
"page",
...frontmatter.tags || [],
...toplevelAttributes.tags || [],
]),
];
// if (pageMeta.tags.includes("template")) {
// // If this is a template, we don't want to index it as a page or anything else, just a template
// pageMeta.tags = ["template"];
// }
combinedPageMeta.tag = "page";
// console.log("Page object", pageObj);
updateITags(combinedPageMeta, frontmatter);
// console.log("Page object", combinedPageMeta);
await indexObjects<PageMeta>(name, [combinedPageMeta]);
}
@@ -109,13 +108,6 @@ async function lintYaml(
const errorMatch = errorRegex.exec(e.message);
if (errorMatch) {
console.log("YAML error", e.message);
// const line = parseInt(errorMatch[1], 10) - 1;
// const yamlLines = yamlText.split("\n");
// let pos = posOffset;
// for (let i = 0; i < line; i++) {
// pos += yamlLines[i].length + 1;
// }
// const endPos = pos + yamlLines[line].length;
return {
from,
+12 -9
View File
@@ -3,12 +3,12 @@ import { IndexTreeEvent } from "$sb/app_event.ts";
import { resolvePath } from "$sb/lib/resolve.ts";
import { indexObjects, queryObjects } from "./api.ts";
import { ObjectValue } from "$sb/types.ts";
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
import { updateITags } from "$sb/lib/tags.ts";
const pageRefRegex = /\[\[([^\]]+)\]\]/g;
export type LinkObject = {
ref: string;
tags: string[];
export type LinkObject = ObjectValue<{
// The page the link points to
toPage: string;
// The page the link occurs in
@@ -17,7 +17,7 @@ export type LinkObject = {
snippet: string;
alias?: string;
asTemplate: boolean;
};
}>;
export function extractSnippet(text: string, pos: number): string {
let prefix = "";
@@ -47,7 +47,7 @@ export async function indexLinks({ name, tree }: IndexTreeEvent) {
const links: ObjectValue<LinkObject>[] = [];
// [[Style Links]]
// console.log("Now indexing links for", name);
const frontmatter = await extractFrontmatter(tree);
const pageText = renderToText(tree);
traverseTree(tree, (n): boolean => {
@@ -59,7 +59,7 @@ export async function indexLinks({ name, tree }: IndexTreeEvent) {
toPage = toPage.split(/[@$]/)[0];
const link: LinkObject = {
ref: `${name}@${pos}`,
tags: ["link"],
tag: "link",
toPage: toPage,
snippet: extractSnippet(pageText, pos),
pos,
@@ -69,6 +69,7 @@ export async function indexLinks({ name, tree }: IndexTreeEvent) {
if (wikiLinkAlias) {
link.alias = wikiLinkAlias.children![0].text!;
}
updateITags(link, frontmatter);
links.push(link);
return true;
}
@@ -90,15 +91,17 @@ export async function indexLinks({ name, tree }: IndexTreeEvent) {
for (const match of matches) {
const pageRefName = resolvePath(name, match[1]);
const pos = codeText.from! + match.index! + 2;
links.push({
const link = {
ref: `${name}@${pos}`,
tags: ["link"],
tag: "link",
toPage: pageRefName,
page: name,
snippet: extractSnippet(pageText, pos),
pos: pos,
asTemplate: true,
});
};
updateITags(link, frontmatter);
links.push(link);
}
}
}
+31 -16
View File
@@ -9,6 +9,9 @@ import {
} from "$sb/lib/tree.ts";
import { extractAttributes } from "$sb/lib/attribute.ts";
import { ObjectValue } from "$sb/types.ts";
import a from "https://esm.sh/v135/node_process.js";
import { updateITags } from "$sb/lib/tags.ts";
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
/** ParagraphObject An index object for the top level text nodes */
export type ParagraphObject = ObjectValue<
@@ -21,41 +24,53 @@ export type ParagraphObject = ObjectValue<
export async function indexParagraphs({ name: page, tree }: IndexTreeEvent) {
const objects: ParagraphObject[] = [];
addParentPointers(tree);
let paragraphCounter = 0;
const frontmatter = await extractFrontmatter(tree);
await traverseTreeAsync(tree, async (p) => {
if (p.type !== "Paragraph") {
return false;
}
paragraphCounter++;
if (findParentMatching(p, (n) => n.type === "ListItem")) {
// Not looking at paragraphs nested in a list
return false;
}
// So we're looking at indexable a paragraph now
const tags = new Set<string>(["paragraph"]);
if (paragraphCounter > 1) {
// Only attach hashtags to later paragraphs than the first
const attrs = await extractAttributes(p, true);
const tags = new Set<string>();
const text = renderToText(p);
// tag the paragraph with any hashtags inside it
collectNodesOfType(p, "Hashtag").forEach((tagNode) => {
tags.add(tagNode.children![0].text!.substring(1));
});
// So we're looking at indexable a paragraph now
collectNodesOfType(p, "Hashtag").forEach((tagNode) => {
tags.add(tagNode.children![0].text!.substring(1));
// Hacky way to remove the hashtag
tagNode.children = [];
});
const textWithoutTags = renderToText(p);
if (!textWithoutTags.trim()) {
// Empty paragraph, just tags and attributes maybe
return true;
}
const attrs = await extractAttributes(p, false);
const pos = p.from!;
objects.push({
const paragraph: ParagraphObject = {
ref: `${page}@${pos}`,
text: renderToText(p),
tags: [...tags.values()],
text,
tag: "paragraph",
page,
pos,
...attrs,
});
};
if (tags.size > 0) {
paragraph.tags = [...tags];
paragraph.itags = [...tags];
}
updateITags(paragraph, frontmatter);
objects.push(paragraph);
// stop on every element except document, including paragraphs
return true;
+2 -2
View File
@@ -17,7 +17,7 @@ export type TagObject = ObjectValue<{
export async function indexTags({ name, tree }: IndexTreeEvent) {
const tags = new Set<string>(); // name:parent
addParentPointers(tree);
const pageTags: string[] = (await extractFrontmatter(tree)).tags;
const pageTags: string[] = (await extractFrontmatter(tree)).tags || [];
for (const pageTag of pageTags) {
tags.add(`${pageTag}:page`);
}
@@ -41,7 +41,7 @@ export async function indexTags({ name, tree }: IndexTreeEvent) {
const [tagName, parent] = tag.split(":");
return {
ref: tag,
tags: ["tag"],
tag: "tag",
name: tagName,
page: name,
parent,
+8 -7
View File
@@ -2,8 +2,6 @@ import { editor, markdown, YAML } from "$sb/syscalls.ts";
import { CodeWidgetContent } from "$sb/types.ts";
import { renderToText, traverseTree } from "$sb/lib/tree.ts";
const defaultHeaderThreshold = 0;
type Header = {
name: string;
pos: number;
@@ -11,7 +9,10 @@ type Header = {
};
type TocConfig = {
// Only show the TOC if there are at least this many headers
minHeaders?: number;
// Don't show the TOC if there are more than this many headers
maxHeaders?: number;
header?: boolean;
};
@@ -40,14 +41,14 @@ export async function widget(
return false;
});
let headerThreshold = defaultHeaderThreshold;
if (config.minHeaders) {
headerThreshold = config.minHeaders;
}
if (headers.length < headerThreshold) {
if (config.minHeaders && headers.length < config.minHeaders) {
// Not enough headers, not showing TOC
return null;
}
if (config.maxHeaders && headers.length > config.maxHeaders) {
// Too many headers, not showing TOC
return null;
}
let headerText = "# Table of Contents\n";
if (config.header === false) {
headerText = "";
+11 -6
View File
@@ -18,6 +18,8 @@ import { extractAttributes } from "$sb/lib/attribute.ts";
import { rewritePageRefs } from "$sb/lib/resolve.ts";
import { ObjectValue } from "$sb/types.ts";
import { indexObjects, queryObjects } from "../index/plug_api.ts";
import { updateITags } from "$sb/lib/tags.ts";
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
export type TaskObject = ObjectValue<
{
@@ -46,9 +48,8 @@ const incompleteStates = [" "];
export async function indexTasks({ name, tree }: IndexTreeEvent) {
const tasks: ObjectValue<TaskObject>[] = [];
const taskStates = new Map<string, { count: number; firstPos: number }>();
addParentPointers(tree);
// const allAttributes: AttributeObject[] = [];
// const allTags = new Set<string>();
const frontmatter = await extractFrontmatter(tree);
await traverseTreeAsync(tree, async (n) => {
if (n.type !== "Task") {
return false;
@@ -65,7 +66,7 @@ export async function indexTasks({ name, tree }: IndexTreeEvent) {
const complete = completeStates.includes(state);
const task: TaskObject = {
ref: `${name}@${n.from}`,
tags: [],
tag: "task",
name: "",
done: complete,
page: name,
@@ -84,10 +85,12 @@ export async function indexTasks({ name, tree }: IndexTreeEvent) {
if (tree.type === "Hashtag") {
// Push the tag to the list, removing the initial #
const tagName = tree.children![0].text!.substring(1);
if (!task.tags) {
task.tags = [];
}
task.tags.push(tagName);
}
});
task.tags = ["task", ...task.tags];
// Extract attributes and remove from tree
const extractedAttributes = await extractAttributes(n, true);
@@ -97,6 +100,8 @@ export async function indexTasks({ name, tree }: IndexTreeEvent) {
task.name = n.children!.slice(1).map(renderToText).join("").trim();
updateITags(task, frontmatter);
tasks.push(task);
return true;
});
@@ -107,7 +112,7 @@ export async function indexTasks({ name, tree }: IndexTreeEvent) {
name,
Array.from(taskStates.entries()).map(([state, { firstPos, count }]) => ({
ref: `${name}@${firstPos}`,
tags: ["taskstate"],
tag: "taskstate",
state,
count,
page: name,
+1 -5
View File
@@ -35,15 +35,11 @@ functions:
indexTasks:
path: "./task.ts:indexTasks"
events:
- page:index
- page:index
taskToggle:
path: "./task.ts:taskToggle"
events:
- page:click
# itemQueryProvider:
# path: ./task.ts:queryProvider
# events:
# - query:task
taskToggleCommand:
path: ./task.ts:taskCycleCommand
command:
+3 -3
View File
@@ -37,7 +37,7 @@ export async function newPageCommand(
const templateText = await space.readPage(templateName!);
const tempPageMeta: PageMeta = {
tags: ["page"],
tag: "page",
ref: "",
name: "",
created: "",
@@ -169,7 +169,7 @@ export async function dailyNoteCommand() {
await space.writePage(
pageName,
await replaceTemplateVars(dailyNoteTemplateText, {
tags: ["page"],
tag: "page",
ref: pageName,
name: pageName,
created: "",
@@ -218,7 +218,7 @@ export async function weeklyNoteCommand() {
await replaceTemplateVars(weeklyNoteTemplateText, {
name: pageName,
ref: pageName,
tags: ["page"],
tag: "page",
created: "",
lastModified: "",
perm: "rw",
+1 -1
View File
@@ -19,7 +19,7 @@ export function defaultJsonTransformer(v: any): string {
}
if (Array.isArray(v)) {
return v.map(defaultJsonTransformer).join(", ");
} else if (typeof v === "object") {
} else if (v && typeof v === "object") {
return Object.entries(v).map(([k, v]: [string, any]) =>
`${k}: ${defaultJsonTransformer(v)}`
).join(", ");