2024-05-28 02:33:41 +08:00
|
|
|
import {
|
|
|
|
AttachmentMeta,
|
|
|
|
CompleteEvent,
|
|
|
|
FileMeta,
|
|
|
|
PageMeta,
|
|
|
|
} from "$sb/types.ts";
|
2024-07-12 02:36:26 +08:00
|
|
|
import { listFilesCached } from "../federation/federation.ts";
|
2023-12-22 01:37:50 +08:00
|
|
|
import { queryObjects } from "../index/plug_api.ts";
|
2024-05-28 02:33:41 +08:00
|
|
|
import { folderName } from "$sb/lib/resolve.ts";
|
2024-07-14 17:29:43 +08:00
|
|
|
import { decoration } from "$sb/syscalls.ts";
|
2023-09-01 22:57:29 +08:00
|
|
|
// Completion
|
|
|
|
export async function pageComplete(completeEvent: CompleteEvent) {
|
2024-05-28 02:33:41 +08:00
|
|
|
// Try to match [[wikilink]]
|
|
|
|
let isWikilink = true;
|
|
|
|
let match = /\[\[([^\]@$#:\{}]*)$/.exec(completeEvent.linePrefix);
|
|
|
|
if (!match) {
|
|
|
|
// Try to match [markdown link]()
|
|
|
|
match = /\[.*\]\(([^\]\)@$#:\{}]*)$/.exec(completeEvent.linePrefix);
|
|
|
|
isWikilink = false;
|
|
|
|
}
|
2023-09-01 22:57:29 +08:00
|
|
|
if (!match) {
|
|
|
|
return null;
|
|
|
|
}
|
2024-02-03 02:19:07 +08:00
|
|
|
|
2024-05-28 02:33:41 +08:00
|
|
|
let allPages: (PageMeta | AttachmentMeta)[] = [];
|
2024-02-03 02:19:07 +08:00
|
|
|
|
|
|
|
if (
|
2023-12-22 01:37:50 +08:00
|
|
|
completeEvent.parentNodes.find((node) => node.startsWith("FencedCode")) &&
|
2024-02-03 02:19:07 +08:00
|
|
|
// either a render [[bla]] clause
|
2024-02-04 23:36:59 +08:00
|
|
|
/(render\s+|template\()\[\[/.test(
|
2023-12-22 01:37:50 +08:00
|
|
|
completeEvent.linePrefix,
|
2024-02-03 02:19:07 +08:00
|
|
|
)
|
|
|
|
) {
|
|
|
|
// We're in a template context, let's only complete templates
|
|
|
|
allPages = await queryObjects<PageMeta>("template", {}, 5);
|
|
|
|
} else if (
|
|
|
|
completeEvent.parentNodes.find((node) =>
|
|
|
|
node.startsWith("FencedCode:include") ||
|
|
|
|
node.startsWith("FencedCode:template")
|
|
|
|
)
|
|
|
|
) {
|
2024-07-12 02:36:26 +08:00
|
|
|
// Include both pages and meta in page completion in ```include and ```template blocks
|
2024-02-03 02:19:07 +08:00
|
|
|
allPages = await queryObjects<PageMeta>("page", {}, 5);
|
|
|
|
} else {
|
2024-07-12 02:36:26 +08:00
|
|
|
// Otherwise, just complete non-meta pages
|
2024-02-03 02:19:07 +08:00
|
|
|
allPages = await queryObjects<PageMeta>("page", {
|
2024-07-12 02:36:26 +08:00
|
|
|
filter: ["and", ["!=", ["attr", "tags"], ["string", "template"]], ["!=", [
|
|
|
|
"attr",
|
|
|
|
"tags",
|
|
|
|
], ["string", "meta"]]],
|
2024-01-21 02:16:07 +08:00
|
|
|
}, 5);
|
2024-05-28 02:33:41 +08:00
|
|
|
// and attachments
|
|
|
|
allPages = allPages.concat(
|
|
|
|
await queryObjects<AttachmentMeta>("attachment", {}, 5),
|
|
|
|
);
|
2024-02-03 02:19:07 +08:00
|
|
|
}
|
|
|
|
|
2023-09-01 22:57:29 +08:00
|
|
|
const prefix = match[1];
|
|
|
|
if (prefix.startsWith("!")) {
|
|
|
|
// Federation prefix, let's first see if we're matching anything from federation that is locally synced
|
|
|
|
const prefixMatches = allPages.filter((pageMeta) =>
|
|
|
|
pageMeta.name.startsWith(prefix)
|
|
|
|
);
|
|
|
|
if (prefixMatches.length === 0) {
|
|
|
|
// Ok, nothing synced in via federation, let's see if this URI is complete enough to try to fetch index.json
|
|
|
|
if (prefix.includes("/")) {
|
|
|
|
// Yep
|
|
|
|
const domain = prefix.split("/")[0];
|
|
|
|
// Cached listing
|
2024-07-12 02:36:26 +08:00
|
|
|
const federationPages = (await listFilesCached(domain)).filter((fm) =>
|
2023-09-01 22:57:29 +08:00
|
|
|
fm.name.endsWith(".md")
|
2023-11-06 16:14:16 +08:00
|
|
|
).map(fileMetaToPageMeta);
|
2023-09-01 22:57:29 +08:00
|
|
|
if (federationPages.length > 0) {
|
|
|
|
allPages = allPages.concat(federationPages);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-05-28 02:33:41 +08:00
|
|
|
|
|
|
|
const folder = folderName(completeEvent.pageName);
|
2024-07-14 17:29:43 +08:00
|
|
|
|
|
|
|
allPages = await decoration.applyDecorationsToPages(allPages as PageMeta[]);
|
|
|
|
|
2023-09-01 22:57:29 +08:00
|
|
|
return {
|
|
|
|
from: completeEvent.pos - match[1].length,
|
|
|
|
options: allPages.map((pageMeta) => {
|
2023-12-22 01:37:50 +08:00
|
|
|
const completions: any[] = [];
|
2024-07-13 19:56:00 +08:00
|
|
|
let namePrefix = "";
|
2024-07-14 17:29:43 +08:00
|
|
|
if ((pageMeta as PageMeta).pageDecoration?.prefix) {
|
|
|
|
namePrefix = pageMeta.pageDecoration?.prefix;
|
2024-07-13 19:56:00 +08:00
|
|
|
}
|
2024-05-28 02:33:41 +08:00
|
|
|
if (isWikilink) {
|
|
|
|
if (pageMeta.displayName) {
|
2024-07-13 19:56:00 +08:00
|
|
|
const decoratedName = namePrefix + pageMeta.displayName;
|
2023-12-22 01:37:50 +08:00
|
|
|
completions.push({
|
2024-05-28 02:33:41 +08:00
|
|
|
label: `${pageMeta.displayName}`,
|
2024-07-13 19:56:00 +08:00
|
|
|
displayLabel: decoratedName,
|
2023-12-22 22:55:50 +08:00
|
|
|
boost: new Date(pageMeta.lastModified).getTime(),
|
2024-02-03 02:19:07 +08:00
|
|
|
apply: pageMeta.tag === "template"
|
2023-12-22 01:37:50 +08:00
|
|
|
? pageMeta.name
|
2024-05-28 02:33:41 +08:00
|
|
|
: `${pageMeta.name}|${pageMeta.displayName}`,
|
|
|
|
detail: `displayName for: ${pageMeta.name}`,
|
2023-12-22 01:37:50 +08:00
|
|
|
type: "page",
|
|
|
|
});
|
|
|
|
}
|
2024-05-28 02:33:41 +08:00
|
|
|
if (Array.isArray(pageMeta.aliases)) {
|
|
|
|
for (const alias of pageMeta.aliases) {
|
2024-07-13 19:56:00 +08:00
|
|
|
const decoratedName = namePrefix + alias;
|
2024-05-28 02:33:41 +08:00
|
|
|
completions.push({
|
|
|
|
label: `${alias}`,
|
2024-07-13 19:56:00 +08:00
|
|
|
displayLabel: decoratedName,
|
2024-05-28 02:33:41 +08:00
|
|
|
boost: new Date(pageMeta.lastModified).getTime(),
|
|
|
|
apply: pageMeta.tag === "template"
|
|
|
|
? pageMeta.name
|
|
|
|
: `${pageMeta.name}|${alias}`,
|
|
|
|
detail: `alias to: ${pageMeta.name}`,
|
|
|
|
type: "page",
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2024-07-13 19:56:00 +08:00
|
|
|
const decoratedName = namePrefix + pageMeta.name;
|
2024-05-28 02:33:41 +08:00
|
|
|
completions.push({
|
|
|
|
label: `${pageMeta.name}`,
|
2024-07-13 19:56:00 +08:00
|
|
|
displayLabel: decoratedName,
|
2024-05-28 02:33:41 +08:00
|
|
|
boost: new Date(pageMeta.lastModified).getTime(),
|
|
|
|
type: "page",
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
let labelText = pageMeta.name;
|
|
|
|
let boost = new Date(pageMeta.lastModified).getTime();
|
|
|
|
// Relative path if in the same folder or a subfolder
|
|
|
|
if (folder.length > 0 && labelText.startsWith(folder)) {
|
|
|
|
labelText = labelText.slice(folder.length + 1);
|
|
|
|
boost = boost * 1.1;
|
|
|
|
} else {
|
|
|
|
// Absolute path otherwise
|
|
|
|
labelText = "/" + labelText;
|
|
|
|
}
|
|
|
|
completions.push({
|
|
|
|
label: labelText,
|
|
|
|
boost: boost,
|
|
|
|
apply: labelText.includes(" ") ? "<" + labelText + ">" : labelText,
|
|
|
|
type: "page",
|
|
|
|
});
|
2023-12-22 01:37:50 +08:00
|
|
|
}
|
|
|
|
return completions;
|
|
|
|
}).flat(),
|
2023-09-01 22:57:29 +08:00
|
|
|
};
|
|
|
|
}
|
2023-11-06 16:14:16 +08:00
|
|
|
|
|
|
|
function fileMetaToPageMeta(fileMeta: FileMeta): PageMeta {
|
|
|
|
const name = fileMeta.name.substring(0, fileMeta.name.length - 3);
|
|
|
|
return {
|
|
|
|
...fileMeta,
|
|
|
|
ref: fileMeta.name,
|
2024-01-11 20:20:50 +08:00
|
|
|
tag: "page",
|
2023-11-06 16:14:16 +08:00
|
|
|
name,
|
|
|
|
created: new Date(fileMeta.created).toISOString(),
|
|
|
|
lastModified: new Date(fileMeta.lastModified).toISOString(),
|
|
|
|
} as PageMeta;
|
|
|
|
}
|