170 lines
4.1 KiB
TypeScript
170 lines
4.1 KiB
TypeScript
|
import {
|
||
|
findNodeOfType,
|
||
|
removeParentPointers,
|
||
|
renderToText,
|
||
|
} from "$sb/lib/tree.ts";
|
||
|
import { getText } from "$sb/silverbullet-syscall/editor.ts";
|
||
|
import { parseMarkdown } from "$sb/silverbullet-syscall/markdown.ts";
|
||
|
import {
|
||
|
extractFrontmatter,
|
||
|
prepareFrontmatterDispatch,
|
||
|
} from "$sb/lib/frontmatter.ts";
|
||
|
import * as YAML from "yaml";
|
||
|
import {
|
||
|
clientStore,
|
||
|
collab,
|
||
|
editor,
|
||
|
markdown,
|
||
|
space,
|
||
|
} from "$sb/silverbullet-syscall/mod.ts";
|
||
|
|
||
|
import { nanoid } from "https://esm.sh/nanoid@4.0.0";
|
||
|
import {
|
||
|
FileData,
|
||
|
FileEncoding,
|
||
|
} from "../../common/spaces/space_primitives.ts";
|
||
|
import { FileMeta } from "../../common/types.ts";
|
||
|
import { base64EncodedDataUrl } from "../../plugos/asset_bundle/base64.ts";
|
||
|
|
||
|
const defaultServer = "wss://collab.silverbullet.md";
|
||
|
|
||
|
async function ensureUsername(): Promise<string> {
|
||
|
let username = await clientStore.get("collabUsername");
|
||
|
if (!username) {
|
||
|
username = await editor.prompt(
|
||
|
"Please enter a publicly visible user name (or cancel for 'anonymous'):",
|
||
|
);
|
||
|
if (!username) {
|
||
|
return "anonymous";
|
||
|
} else {
|
||
|
await clientStore.set("collabUsername", username);
|
||
|
}
|
||
|
}
|
||
|
return username;
|
||
|
}
|
||
|
|
||
|
export async function joinCommand() {
|
||
|
let collabUri = await editor.prompt(
|
||
|
"Collab share URI:",
|
||
|
);
|
||
|
if (!collabUri) {
|
||
|
return;
|
||
|
}
|
||
|
if (!collabUri.startsWith("collab:")) {
|
||
|
collabUri = "collab:" + collabUri;
|
||
|
}
|
||
|
await editor.navigate(collabUri);
|
||
|
}
|
||
|
|
||
|
export async function shareCommand() {
|
||
|
const serverUrl = await editor.prompt(
|
||
|
"Please enter the URL of the collab server to use:",
|
||
|
defaultServer,
|
||
|
);
|
||
|
if (!serverUrl) {
|
||
|
return;
|
||
|
}
|
||
|
const roomId = nanoid();
|
||
|
await editor.save();
|
||
|
const text = await editor.getText();
|
||
|
const tree = await markdown.parseMarkdown(text);
|
||
|
let { $share } = extractFrontmatter(tree);
|
||
|
if (!$share) {
|
||
|
$share = [];
|
||
|
}
|
||
|
if (!Array.isArray($share)) {
|
||
|
$share = [$share];
|
||
|
}
|
||
|
|
||
|
removeParentPointers(tree);
|
||
|
const dispatchData = prepareFrontmatterDispatch(tree, {
|
||
|
$share: [...$share, `collab:${serverUrl}/${roomId}`],
|
||
|
});
|
||
|
|
||
|
await editor.dispatch(dispatchData);
|
||
|
|
||
|
collab.start(
|
||
|
serverUrl,
|
||
|
roomId,
|
||
|
await ensureUsername(),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
export async function detectPage() {
|
||
|
const tree = await parseMarkdown(await getText());
|
||
|
const frontMatter = findNodeOfType(tree, "FrontMatter");
|
||
|
if (frontMatter) {
|
||
|
const yamlText = renderToText(frontMatter.children![1].children![0]);
|
||
|
try {
|
||
|
let { $share } = YAML.parse(yamlText) as any;
|
||
|
if (!$share) {
|
||
|
return;
|
||
|
}
|
||
|
if (!Array.isArray($share)) {
|
||
|
$share = [$share];
|
||
|
}
|
||
|
for (const uri of $share) {
|
||
|
if (uri.startsWith("collab:")) {
|
||
|
console.log("Going to enable collab");
|
||
|
const uriPieces = uri.substring("collab:".length).split("/");
|
||
|
await collab.start(
|
||
|
// All parts except the last one
|
||
|
uriPieces.slice(0, uriPieces.length - 1).join("/"),
|
||
|
// because the last one is the room ID
|
||
|
uriPieces[uriPieces.length - 1],
|
||
|
await ensureUsername(),
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
} catch (e) {
|
||
|
console.error("Error parsing YAML", e);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export async function readFileCollab(
|
||
|
name: string,
|
||
|
encoding: FileEncoding,
|
||
|
): Promise<{ data: FileData; meta: FileMeta }> {
|
||
|
if (!name.endsWith(".md")) {
|
||
|
throw new Error("File not found");
|
||
|
}
|
||
|
const collabUri = name.substring(0, name.length - ".md".length);
|
||
|
const text = `---\n$share: ${collabUri}\n---\n`;
|
||
|
|
||
|
return {
|
||
|
// encoding === "arraybuffer" is not an option, so either it's "string" or "dataurl"
|
||
|
data: encoding === "string" ? text : base64EncodedDataUrl(
|
||
|
"text/markdown",
|
||
|
new TextEncoder().encode(text),
|
||
|
),
|
||
|
meta: {
|
||
|
name,
|
||
|
contentType: "text/markdown",
|
||
|
size: text.length,
|
||
|
lastModified: 0,
|
||
|
perm: "rw",
|
||
|
},
|
||
|
};
|
||
|
}
|
||
|
|
||
|
export function getFileMetaCollab(name: string): FileMeta {
|
||
|
return {
|
||
|
name,
|
||
|
contentType: "text/markdown",
|
||
|
size: -1,
|
||
|
lastModified: 0,
|
||
|
perm: "rw",
|
||
|
};
|
||
|
}
|
||
|
|
||
|
export function writeFileCollab(name: string): FileMeta {
|
||
|
return {
|
||
|
name,
|
||
|
contentType: "text/markdown",
|
||
|
size: -1,
|
||
|
lastModified: 0,
|
||
|
perm: "rw",
|
||
|
};
|
||
|
}
|