No More Collab. Fixes #449

* Fully removes real-time collaboration
* URL scheme rewrite
This commit is contained in:
Zef Hemel
2023-07-06 16:47:50 +02:00
committed by GitHub
parent 021bf7e9a4
commit fb75ea1a65
45 changed files with 380 additions and 1356 deletions
-1
View File
@@ -1,6 +1,5 @@
// TODO: Figure out how to keep this up-to-date automatically
export const builtinPlugNames = [
"collab",
"core",
"directive",
"emoji",
-36
View File
@@ -1,36 +0,0 @@
name: collab
functions:
detectCollabPage:
path: "./collab.ts:detectPage"
events:
- editor:pageLoaded
- plugs:loaded
joinCommand:
path: "./collab.ts:joinCommand"
command:
name: "Share: Join Collab"
shareCommand:
path: "./collab.ts:shareCommand"
command:
name: "Share: Collab"
shareNoop:
path: "./collab.ts:shareNoop"
events:
- share:collab
# Space extension
readPageCollab:
path: ./collab.ts:readFileCollab
pageNamespace:
pattern: "collab:.+"
operation: readFile
writePageCollab:
path: ./collab.ts:writeFileCollab
pageNamespace:
pattern: "collab:.+"
operation: writeFile
getPageMetaCollab:
path: ./collab.ts:getFileMetaCollab
pageNamespace:
pattern: "collab:.+"
operation: getFileMeta
-158
View File
@@ -1,158 +0,0 @@
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 { store, YAML } from "$sb/plugos-syscall/mod.ts";
import { collab, editor, markdown } from "$sb/silverbullet-syscall/mod.ts";
import { nanoid } from "https://esm.sh/nanoid@4.0.0";
import { FileMeta } from "../../common/types.ts";
const defaultServer = "wss://collab.silverbullet.md";
async function ensureUsername(): Promise<string> {
let username = await store.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 store.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().replaceAll("_", "-");
await editor.save();
const text = await editor.getText();
const tree = await markdown.parseMarkdown(text);
let { $share } = await extractFrontmatter(tree);
if (!$share) {
$share = [];
}
if (!Array.isArray($share)) {
$share = [$share];
}
removeParentPointers(tree);
const dispatchData = await 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 } = await 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 function shareNoop() {
return true;
}
export function readFileCollab(
name: string,
): { data: Uint8Array; meta: FileMeta } {
if (!name.endsWith(".md")) {
throw new Error("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 "utf8" or "dataurl"
data: 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",
};
}
-1
View File
@@ -1 +0,0 @@
export const collabPingInterval = 2500;
+1 -1
View File
@@ -77,7 +77,7 @@ async function actionClickOrActionEnter(
return editor.flashNotification("Empty link, ignoring", "error");
}
if (url.indexOf("://") === -1 && !url.startsWith("mailto:")) {
return editor.openUrl(`/.fs/${decodeURI(url)}`);
return editor.openUrl(decodeURI(url));
} else {
await editor.openUrl(url);
}
+8
View File
@@ -23,6 +23,7 @@ import {
import { applyQuery, removeQueries } from "$sb/lib/query.ts";
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
import { invokeFunction } from "$sb/silverbullet-syscall/system.ts";
import { isValidPageName } from "$sb/lib/page.ts";
// Key space:
// pl:toPage:pos => pageName
@@ -136,6 +137,13 @@ export async function renamePage(cmdDef: any) {
return;
}
if (!isValidPageName(newName)) {
return editor.flashNotification(
"Invalid page name: page names cannot end with a file extension",
"error",
);
}
console.log("New name", newName);
if (newName.trim() === oldName.trim()) {
-21
View File
@@ -20,27 +20,6 @@ export async function updateDirectivesOnPageCommand(arg: any) {
return;
}
// if (!(await sync.hasInitialSyncCompleted())) {
// console.info("Initial sync hasn't completed yet, not updating directives.");
// return;
// }
// If this page is shared ($share) via collab: disable directives as well
// due to security concerns
if (metaData.$share) {
for (const uri of metaData.$share) {
if (uri.startsWith("collab:")) {
if (explicitCall) {
await editor.flashNotification(
"Directives are disabled for 'collab' pages (safety reasons).",
"error",
);
}
return;
}
}
}
// Collect all directives and their body replacements
const replacements: { fullMatch: string; textPromise: Promise<string> }[] =
[];
+24 -29
View File
@@ -1,6 +1,5 @@
import { queryRegex } from "$sb/lib/query.ts";
import { ParseTree, renderToText } from "$sb/lib/tree.ts";
import { replaceAsync } from "$sb/lib/util.ts";
import { markdown, space } from "$sb/silverbullet-syscall/mod.ts";
import Handlebars from "handlebars";
@@ -68,32 +67,28 @@ export async function templateDirectiveRenderer(
return newBody.trim();
}
export function cleanTemplateInstantiations(text: string): Promise<string> {
return replaceAsync(
text,
directiveRegex,
(
_fullMatch,
startInst,
type,
_args,
body,
endInst,
): Promise<string> => {
if (type === "use") {
body = body.replaceAll(
queryRegex,
(
_fullMatch: string,
_startQuery: string,
_query: string,
body: string,
) => {
return body.trim();
},
);
}
return Promise.resolve(`${startInst}${body}${endInst}`);
},
);
export function cleanTemplateInstantiations(text: string) {
return text.replaceAll(directiveRegex, (
_fullMatch,
startInst,
type,
_args,
body,
endInst,
): string => {
if (type === "use") {
body = body.replaceAll(
queryRegex,
(
_fullMatch: string,
_startQuery: string,
_query: string,
body: string,
) => {
return body.trim();
},
);
}
return `${startInst}${body}${endInst}`;
});
}
+1 -4
View File
@@ -5,9 +5,6 @@ import { readSetting } from "$sb/lib/settings_page.ts";
function resolveFederated(pageName: string): string {
// URL without the prefix "!""
let url = pageName.substring(1);
const pieces = url.split("/");
pieces.splice(1, 0, ".fs");
url = pieces.join("/");
if (!url.startsWith("127.0.0.1") && !url.startsWith("localhost")) {
url = `https://${url}`;
} else {
@@ -153,7 +150,7 @@ export async function deleteFile(
export async function getFileMeta(name: string): Promise<FileMeta> {
const url = resolveFederated(name);
console.log("Fetching federation file meta", url);
const r = await nativeFetch(url, { method: "OPTIONS" });
const r = await nativeFetch(url, { method: "HEAD" });
const fileMeta = await responseToFileMeta(r, name);
if (!r.ok) {
throw new Error("Not found");
+1 -2
View File
@@ -7,7 +7,6 @@ export async function updateMarkdownPreview() {
if (!(await store.get("enableMarkdownPreview"))) {
return;
}
const pageName = await editor.getCurrentPage();
const text = await editor.getText();
const mdTree = await parseMarkdown(text);
// const cleanMd = await cleanMarkdown(text);
@@ -18,7 +17,7 @@ export async function updateMarkdownPreview() {
annotationPositions: true,
translateUrls: (url) => {
if (!url.includes("://")) {
return `/.fs/${url}`;
return decodeURI(url);
}
return url;
},