Make attribute extensible
This commit is contained in:
+121
-24
@@ -1,5 +1,6 @@
|
||||
import { index } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import type { CompleteEvent } from "$sb/app_event.ts";
|
||||
import { events } from "$sb/plugos-syscall/mod.ts";
|
||||
|
||||
export type AttributeContext = "page" | "item" | "task";
|
||||
|
||||
@@ -7,6 +8,47 @@ type AttributeEntry = {
|
||||
type: string;
|
||||
};
|
||||
|
||||
export type AttributeCompleteEvent = {
|
||||
source: string;
|
||||
prefix: string;
|
||||
};
|
||||
|
||||
export type AttributeCompletion = {
|
||||
name: string;
|
||||
source: string;
|
||||
type: string;
|
||||
builtin?: boolean;
|
||||
};
|
||||
|
||||
const builtinAttributes: Record<string, Record<string, string>> = {
|
||||
page: {
|
||||
name: "string",
|
||||
lastModified: "number",
|
||||
perm: "rw|ro",
|
||||
contentType: "string",
|
||||
size: "number",
|
||||
tags: "array",
|
||||
},
|
||||
task: {
|
||||
name: "string",
|
||||
done: "boolean",
|
||||
page: "string",
|
||||
deadline: "string",
|
||||
pos: "number",
|
||||
tags: "array",
|
||||
},
|
||||
item: {
|
||||
name: "string",
|
||||
page: "string",
|
||||
pos: "number",
|
||||
tags: "array",
|
||||
},
|
||||
tag: {
|
||||
name: "string",
|
||||
freq: "number",
|
||||
},
|
||||
};
|
||||
|
||||
function determineType(v: any): string {
|
||||
const t = typeof v;
|
||||
if (t === "object") {
|
||||
@@ -37,6 +79,52 @@ export async function indexAttributes(
|
||||
);
|
||||
}
|
||||
|
||||
export async function customAttributeCompleter(
|
||||
attributeCompleteEvent: AttributeCompleteEvent,
|
||||
): Promise<AttributeCompletion[]> {
|
||||
const sourcePrefix = attributeCompleteEvent.source === "*"
|
||||
? ""
|
||||
: `${attributeCompleteEvent.source}:`;
|
||||
const allAttributes = await index.queryPrefix(
|
||||
`${attributeKeyPrefix}${sourcePrefix}`,
|
||||
);
|
||||
return allAttributes.map((attr) => {
|
||||
const [_prefix, context, name] = attr.key.split(":");
|
||||
return {
|
||||
name,
|
||||
source: context,
|
||||
type: attr.value.type,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function builtinAttributeCompleter(
|
||||
attributeCompleteEvent: AttributeCompleteEvent,
|
||||
): AttributeCompletion[] {
|
||||
let allAttributes = builtinAttributes[attributeCompleteEvent.source];
|
||||
if (attributeCompleteEvent.source === "*") {
|
||||
allAttributes = {};
|
||||
for (const [source, attributes] of Object.entries(builtinAttributes)) {
|
||||
for (const [name, type] of Object.entries(attributes)) {
|
||||
allAttributes[name] = `${type}|${source}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!allAttributes) {
|
||||
return [];
|
||||
}
|
||||
return Object.entries(allAttributes).map(([name, type]) => {
|
||||
return {
|
||||
name,
|
||||
source: attributeCompleteEvent.source === "*"
|
||||
? type.split("|")[1]
|
||||
: attributeCompleteEvent.source,
|
||||
type: attributeCompleteEvent.source === "*" ? type.split("|")[0] : type,
|
||||
builtin: true,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function attributeComplete(completeEvent: CompleteEvent) {
|
||||
const inlineAttributeMatch = /([^\[\{}]|^)\[(\w+)$/.exec(
|
||||
completeEvent.linePrefix,
|
||||
@@ -49,41 +137,50 @@ export async function attributeComplete(completeEvent: CompleteEvent) {
|
||||
} else if (completeEvent.parentNodes.includes("ListItem")) {
|
||||
type = "item";
|
||||
}
|
||||
const allAttributes = await index.queryPrefix(
|
||||
`${attributeKeyPrefix}${type}:`,
|
||||
);
|
||||
const completions = (await events.dispatchEvent(
|
||||
`attribute:complete:${type}`,
|
||||
{
|
||||
source: type,
|
||||
prefix: inlineAttributeMatch[2],
|
||||
} as AttributeCompleteEvent,
|
||||
)).flat() as AttributeCompletion[];
|
||||
return {
|
||||
from: completeEvent.pos - inlineAttributeMatch[2].length,
|
||||
options: allAttributes.map((attr) => {
|
||||
const [_prefix, _context, name] = attr.key.split(":");
|
||||
return {
|
||||
label: name,
|
||||
apply: `${name}: `,
|
||||
detail: attr.value.type,
|
||||
type: "attribute",
|
||||
};
|
||||
}),
|
||||
options: attributeCompletionsToCMCompletion(
|
||||
completions.filter((completion) => !completion.builtin),
|
||||
),
|
||||
};
|
||||
}
|
||||
const attributeMatch = /^(\w+)$/.exec(completeEvent.linePrefix);
|
||||
if (attributeMatch) {
|
||||
if (completeEvent.parentNodes.includes("FrontMatterCode")) {
|
||||
const allAttributes = await index.queryPrefix(
|
||||
`${attributeKeyPrefix}page:`,
|
||||
);
|
||||
const completions = (await events.dispatchEvent(
|
||||
`attribute:complete:page`,
|
||||
{
|
||||
source: "page",
|
||||
prefix: attributeMatch[1],
|
||||
} as AttributeCompleteEvent,
|
||||
)).flat() as AttributeCompletion[];
|
||||
return {
|
||||
from: completeEvent.pos - attributeMatch[1].length,
|
||||
options: allAttributes.map((attr) => {
|
||||
const [_prefix, _context, name] = attr.key.split(":");
|
||||
return {
|
||||
label: name,
|
||||
apply: `${name}: `,
|
||||
detail: attr.value.type,
|
||||
type: "attribute",
|
||||
};
|
||||
}),
|
||||
options: attributeCompletionsToCMCompletion(
|
||||
completions.filter((completion) => !completion.builtin),
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function attributeCompletionsToCMCompletion(
|
||||
completions: AttributeCompletion[],
|
||||
) {
|
||||
return completions.map(
|
||||
(completion) => ({
|
||||
label: completion.name,
|
||||
apply: `${completion.name}: `,
|
||||
detail: `${completion.type} (${completion.source})`,
|
||||
type: "attribute",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -96,6 +96,22 @@ functions:
|
||||
events:
|
||||
- editor:complete
|
||||
|
||||
customAttributeCompleter:
|
||||
path: ./attributes.ts:customAttributeCompleter
|
||||
events:
|
||||
- attribute:complete:page
|
||||
- attribute:complete:task
|
||||
- attribute:complete:item
|
||||
- attribute:complete:*
|
||||
|
||||
builtinAttributeCompleter:
|
||||
path: ./attributes.ts:builtinAttributeCompleter
|
||||
events:
|
||||
- attribute:complete:page
|
||||
- attribute:complete:task
|
||||
- attribute:complete:item
|
||||
- attribute:complete:*
|
||||
|
||||
# Commands
|
||||
commandComplete:
|
||||
path: "./command.ts:commandComplete"
|
||||
|
||||
+33
-69
@@ -2,36 +2,10 @@ import { events } from "$sb/plugos-syscall/mod.ts";
|
||||
import { CompleteEvent } from "$sb/app_event.ts";
|
||||
import { buildHandebarOptions } from "./util.ts";
|
||||
import type { PageMeta } from "../../web/types.ts";
|
||||
import { index } from "$sb/silverbullet-syscall/mod.ts";
|
||||
|
||||
const builtinAttributes: Record<string, Record<string, string>> = {
|
||||
page: {
|
||||
name: "string",
|
||||
lastModified: "number",
|
||||
perm: "rw|ro",
|
||||
contentType: "string",
|
||||
size: "number",
|
||||
tags: "array",
|
||||
},
|
||||
task: {
|
||||
name: "string",
|
||||
done: "boolean",
|
||||
page: "string",
|
||||
deadline: "string",
|
||||
pos: "number",
|
||||
tags: "array",
|
||||
},
|
||||
item: {
|
||||
name: "string",
|
||||
page: "string",
|
||||
pos: "number",
|
||||
tags: "array",
|
||||
},
|
||||
tag: {
|
||||
name: "string",
|
||||
freq: "number",
|
||||
},
|
||||
};
|
||||
import {
|
||||
AttributeCompleteEvent,
|
||||
AttributeCompletion,
|
||||
} from "../core/attributes.ts";
|
||||
|
||||
export async function queryComplete(completeEvent: CompleteEvent) {
|
||||
const querySourceMatch = /#query\s+([\w\-_]*)$/.exec(
|
||||
@@ -61,51 +35,22 @@ export async function queryComplete(completeEvent: CompleteEvent) {
|
||||
if (querySourceMatch && whereMatch) {
|
||||
const type = querySourceMatch[1];
|
||||
const attributePrefix = whereMatch[3];
|
||||
// console.log("Type", type);
|
||||
// console.log("Where", attributePrefix);
|
||||
const allAttributes = await index.queryPrefix(
|
||||
`attr:${type}:`,
|
||||
);
|
||||
|
||||
const completions = (await events.dispatchEvent(
|
||||
`attribute:complete:${type}`,
|
||||
{
|
||||
source: type,
|
||||
prefix: attributePrefix,
|
||||
} as AttributeCompleteEvent,
|
||||
)).flat() as AttributeCompletion[];
|
||||
return {
|
||||
from: completeEvent.pos - attributePrefix.length,
|
||||
options: compileAttributeCompletions(allAttributes, type),
|
||||
options: attributeCompletionsToCMCompletion(completions),
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function compileAttributeCompletions(
|
||||
allAttributes: { key: string; value: any }[],
|
||||
type?: string,
|
||||
) {
|
||||
let allCompletions: any[] = allAttributes.map((attr) => {
|
||||
const [_prefix, context, name] = attr.key.split(":");
|
||||
return {
|
||||
label: name,
|
||||
detail: `${attr.value.type} (${context})`,
|
||||
type: "attribute",
|
||||
};
|
||||
});
|
||||
const allContexts = type ? [type] : Object.keys(builtinAttributes);
|
||||
|
||||
for (const context of allContexts) {
|
||||
allCompletions = allCompletions.concat(
|
||||
builtinAttributes[context]
|
||||
? Object.entries(
|
||||
builtinAttributes[context],
|
||||
).map(([name, type]) => ({
|
||||
label: name,
|
||||
detail: `${type} (${context}: builtin)`,
|
||||
type: "attribute",
|
||||
}))
|
||||
: [],
|
||||
);
|
||||
}
|
||||
return allCompletions;
|
||||
}
|
||||
|
||||
export async function templateVariableComplete(completeEvent: CompleteEvent) {
|
||||
const match = /\{\{([\w@]*)$/.exec(completeEvent.linePrefix);
|
||||
if (!match) {
|
||||
@@ -123,9 +68,16 @@ export async function templateVariableComplete(completeEvent: CompleteEvent) {
|
||||
})),
|
||||
);
|
||||
|
||||
const allAttributes = await index.queryPrefix(`attr:`);
|
||||
const completions = (await events.dispatchEvent(
|
||||
`attribute:complete:*`,
|
||||
{
|
||||
source: "*",
|
||||
prefix: match[1],
|
||||
} as AttributeCompleteEvent,
|
||||
)).flat() as AttributeCompletion[];
|
||||
|
||||
allCompletions = allCompletions.concat(
|
||||
compileAttributeCompletions(allAttributes),
|
||||
attributeCompletionsToCMCompletion(completions),
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -133,3 +85,15 @@ export async function templateVariableComplete(completeEvent: CompleteEvent) {
|
||||
options: allCompletions,
|
||||
};
|
||||
}
|
||||
|
||||
export function attributeCompletionsToCMCompletion(
|
||||
completions: AttributeCompletion[],
|
||||
) {
|
||||
return completions.map(
|
||||
(completion) => ({
|
||||
label: completion.name,
|
||||
detail: `${completion.type} (${completion.source})`,
|
||||
type: "attribute",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user