No More Collab. Fixes #449
* Fully removes real-time collaboration * URL scheme rewrite
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
// TODO: Figure out how to keep this up-to-date automatically
|
||||
export const builtinPlugNames = [
|
||||
"collab",
|
||||
"core",
|
||||
"directive",
|
||||
"emoji",
|
||||
|
||||
@@ -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
|
||||
@@ -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 +0,0 @@
|
||||
export const collabPingInterval = 2500;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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> }[] =
|
||||
[];
|
||||
|
||||
@@ -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}`;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user