Big refactors and fixes
* Query regen * Fix anchor completion * Dependency fixes * Changelog update
This commit is contained in:
@@ -180,12 +180,13 @@ export function attachmentExtension(editor: Editor) {
|
||||
if (!finalFileName) {
|
||||
return;
|
||||
}
|
||||
await editor.space.writeAttachment(finalFileName, new Uint8Array(data));
|
||||
let attachmentMarkdown = `[${finalFileName}](${
|
||||
encodeURIComponent(finalFileName)
|
||||
})`;
|
||||
await editor.space.writeAttachment(
|
||||
finalFileName,
|
||||
new Uint8Array(data),
|
||||
);
|
||||
let attachmentMarkdown = `[${finalFileName}](${encodeURI(finalFileName)})`;
|
||||
if (mimeType.startsWith("image/")) {
|
||||
attachmentMarkdown = `})`;
|
||||
attachmentMarkdown = `})`;
|
||||
}
|
||||
editor.editorView!.dispatch({
|
||||
changes: [
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import { decoratorStateField } from "./util.ts";
|
||||
|
||||
import type { Space } from "../space.ts";
|
||||
import type { Editor } from "../editor.tsx";
|
||||
|
||||
class InlineImageWidget extends WidgetType {
|
||||
constructor(
|
||||
@@ -39,13 +40,7 @@ class InlineImageWidget extends WidgetType {
|
||||
this.space.setCachedImageHeight(this.url, img.height);
|
||||
}
|
||||
};
|
||||
if (this.url.startsWith("http")) {
|
||||
img.src = this.url;
|
||||
} else {
|
||||
// This is an attachment image, rewrite the URL a little
|
||||
img.src = `/.fs/${decodeURIComponent(this.url)}`;
|
||||
}
|
||||
|
||||
img.src = this.url;
|
||||
img.alt = this.title;
|
||||
img.title = this.title;
|
||||
img.style.display = "block";
|
||||
@@ -58,7 +53,7 @@ class InlineImageWidget extends WidgetType {
|
||||
}
|
||||
}
|
||||
|
||||
export function inlineImagesPlugin(space: Space) {
|
||||
export function inlineImagesPlugin(editor: Editor) {
|
||||
return decoratorStateField((state: EditorState) => {
|
||||
const widgets: Range<Decoration>[] = [];
|
||||
const imageRegex = /!\[(?<title>[^\]]*)\]\((?<url>.+)\)/;
|
||||
@@ -76,11 +71,14 @@ export function inlineImagesPlugin(space: Space) {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = imageRexexResult.groups.url;
|
||||
let url = imageRexexResult.groups.url;
|
||||
const title = imageRexexResult.groups.title;
|
||||
if (url.indexOf("://") === -1) {
|
||||
url = `/.fs/${url}`;
|
||||
}
|
||||
widgets.push(
|
||||
Decoration.widget({
|
||||
widget: new InlineImageWidget(url, title, space),
|
||||
widget: new InlineImageWidget(url, title, editor.space),
|
||||
block: true,
|
||||
}).range(node.to),
|
||||
);
|
||||
|
||||
@@ -37,7 +37,7 @@ class TableViewWidget extends WidgetType {
|
||||
// Annotate every element with its position so we can use it to put
|
||||
// the cursor there when the user clicks on the table.
|
||||
annotationPositions: true,
|
||||
inlineAttachments: (url) => {
|
||||
translateUrls: (url) => {
|
||||
if (!url.includes("://")) {
|
||||
return `/.fs/${url}`;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ export function cleanWikiLinkPlugin(editor: Editor) {
|
||||
if (page.includes("@")) {
|
||||
cleanPage = page.split("@")[0];
|
||||
}
|
||||
// console.log("Resolved page", resolvedPage);
|
||||
for (const pageMeta of allPages) {
|
||||
if (pageMeta.name === cleanPage) {
|
||||
pageExists = true;
|
||||
@@ -76,8 +77,10 @@ export function cleanWikiLinkPlugin(editor: Editor) {
|
||||
widget: new LinkWidget(
|
||||
{
|
||||
text: linkText,
|
||||
title: pageExists ? `Navigate to ${page}` : `Create ${page}`,
|
||||
href: `/${page}`,
|
||||
title: pageExists
|
||||
? `Navigate to ${cleanPage}`
|
||||
: `Create ${cleanPage}`,
|
||||
href: `/${cleanPage}`,
|
||||
cssClass: pageExists
|
||||
? "sb-wiki-link-page"
|
||||
: "sb-wiki-link-page-missing",
|
||||
|
||||
+8
-13
@@ -15,7 +15,7 @@ export class CollabManager {
|
||||
"editor:pageLoaded",
|
||||
(pageName, previousPage) => {
|
||||
console.log("Page loaded", pageName, previousPage);
|
||||
this.updatePresence(pageName, previousPage).catch(console.error);
|
||||
this.updatePresence(pageName).catch(console.error);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -26,25 +26,20 @@ export class CollabManager {
|
||||
}, collabPingInterval);
|
||||
}
|
||||
|
||||
async updatePresence(currentPage?: string, previousPage?: string) {
|
||||
async updatePresence(currentPage: string) {
|
||||
try {
|
||||
// This is signaled through an OPTIONS call on the file we have open
|
||||
const resp = await this.editor.remoteSpacePrimitives.authenticatedFetch(
|
||||
this.editor.remoteSpacePrimitives.url,
|
||||
`${this.editor.remoteSpacePrimitives.url}/${currentPage}.md`,
|
||||
{
|
||||
method: "POST",
|
||||
method: "OPTIONS",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Client-Id": this.clientId,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
operation: "presence",
|
||||
clientId: this.clientId,
|
||||
previousPage,
|
||||
currentPage,
|
||||
}),
|
||||
keepalive: true, // important for beforeunload event
|
||||
},
|
||||
);
|
||||
const { collabId } = await resp.json();
|
||||
const collabId = resp.headers.get("X-Collab-Id");
|
||||
// Not reading body at all, is that a problem?
|
||||
|
||||
if (this.editor.collabState && !this.editor.collabState.isLocalCollab) {
|
||||
// We're in a remote collab mode, don't do anything
|
||||
|
||||
@@ -8,7 +8,6 @@ import type { ComponentChildren, FunctionalComponent } from "../deps.ts";
|
||||
import { Notification } from "../types.ts";
|
||||
import { FeatherProps } from "https://esm.sh/v99/preact-feather@4.2.1/dist/types";
|
||||
import { MiniEditor } from "./mini_editor.tsx";
|
||||
import process from "https://deno.land/std@0.177.1/node/process.ts";
|
||||
|
||||
export type ActionButton = {
|
||||
icon: FunctionalComponent<FeatherProps>;
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ export {
|
||||
yCollab,
|
||||
yUndoManagerKeymap,
|
||||
} from "https://esm.sh/y-codemirror.next@0.3.2?external=yjs,@codemirror/state,@codemirror/commands,@codemirror/history,@codemirror/view";
|
||||
export { HocuspocusProvider } from "https://esm.sh/@hocuspocus/provider@2.1.0?external=yjs,ws&target=es2022";
|
||||
export { HocuspocusProvider } from "https://esm.sh/@hocuspocus/provider@2.2.0?deps=lib0@0.2.70&external=yjs,ws&target=es2022";
|
||||
|
||||
// Vim mode
|
||||
export {
|
||||
|
||||
+19
-13
@@ -242,15 +242,8 @@ export class Editor {
|
||||
true,
|
||||
);
|
||||
|
||||
const plugSpacePrimitives = new PlugSpacePrimitives(
|
||||
// Using fallback space primitives here to allow (by default) local reads to "fall through" to HTTP when files aren't synced yet
|
||||
new FallbackSpacePrimitives(
|
||||
new IndexedDBSpacePrimitives(
|
||||
`${dbPrefix}_space`,
|
||||
globalThis.indexedDB,
|
||||
),
|
||||
this.remoteSpacePrimitives,
|
||||
),
|
||||
const plugSpaceRemotePrimitives = new PlugSpacePrimitives(
|
||||
this.remoteSpacePrimitives,
|
||||
namespaceHook,
|
||||
);
|
||||
|
||||
@@ -258,7 +251,14 @@ export class Editor {
|
||||
const localSpacePrimitives = new FilteredSpacePrimitives(
|
||||
new FileMetaSpacePrimitives(
|
||||
new EventedSpacePrimitives(
|
||||
plugSpacePrimitives,
|
||||
// Using fallback space primitives here to allow (by default) local reads to "fall through" to HTTP when files aren't synced yet
|
||||
new FallbackSpacePrimitives(
|
||||
new IndexedDBSpacePrimitives(
|
||||
`${dbPrefix}_space`,
|
||||
globalThis.indexedDB,
|
||||
),
|
||||
plugSpaceRemotePrimitives,
|
||||
),
|
||||
this.eventHook,
|
||||
),
|
||||
indexSyscalls,
|
||||
@@ -279,12 +279,16 @@ export class Editor {
|
||||
|
||||
this.syncService = new SyncService(
|
||||
localSpacePrimitives,
|
||||
this.remoteSpacePrimitives,
|
||||
plugSpaceRemotePrimitives,
|
||||
this.kvStore,
|
||||
this.eventHook,
|
||||
(path) => {
|
||||
// TODO: At some point we should remove the data.db exception here
|
||||
return path !== "data.db" && !plugSpacePrimitives.isLikelyHandled(path);
|
||||
return path !== "data.db" &&
|
||||
// Exclude all plug space primitives paths
|
||||
!plugSpaceRemotePrimitives.isLikelyHandled(path) ||
|
||||
// Except federated ones
|
||||
path.startsWith("!");
|
||||
},
|
||||
);
|
||||
|
||||
@@ -892,7 +896,7 @@ export class Editor {
|
||||
),
|
||||
],
|
||||
}),
|
||||
inlineImagesPlugin(this.space),
|
||||
inlineImagesPlugin(this),
|
||||
highlightSpecialChars(),
|
||||
history(),
|
||||
drawSelection(),
|
||||
@@ -1117,6 +1121,7 @@ export class Editor {
|
||||
const linePrefix = line.text.slice(0, selection.from - line.from);
|
||||
|
||||
const results = await this.dispatchAppEvent(eventName, {
|
||||
pageName: this.currentPage!,
|
||||
linePrefix,
|
||||
pos: selection.from,
|
||||
} as CompleteEvent);
|
||||
@@ -1127,6 +1132,7 @@ export class Editor {
|
||||
console.error(
|
||||
"Got completion results from multiple sources, cannot deal with that",
|
||||
);
|
||||
console.error("Previously had", actualResult, "now also got", result);
|
||||
return null;
|
||||
}
|
||||
actualResult = result;
|
||||
|
||||
@@ -82,10 +82,13 @@ self.addEventListener("fetch", (event: any) => {
|
||||
}
|
||||
|
||||
const requestUrl = new URL(event.request.url);
|
||||
|
||||
const pathname = requestUrl.pathname;
|
||||
// console.log("In service worker, pathname is", pathname);
|
||||
// Are we fetching a URL from the same origin as the app? If not, we don't handle it here
|
||||
const fetchingLocal = location.host === requestUrl.host;
|
||||
// If this is a /.fs request, this can either be a plug worker load or an attachment load
|
||||
if (pathname.startsWith("/.fs")) {
|
||||
if (fetchingLocal && pathname.startsWith("/.fs")) {
|
||||
if (fileContentTable && !event.request.headers.has("x-sync-mode")) {
|
||||
// console.log(
|
||||
// "Attempting to serve file from locally synced space:",
|
||||
@@ -101,8 +104,10 @@ self.addEventListener("fetch", (event: any) => {
|
||||
// console.log("Serving from space", path);
|
||||
return new Response(data.data, {
|
||||
headers: {
|
||||
"Content-type": mime.getType(path) ||
|
||||
"application/octet-stream",
|
||||
"Content-type": data.meta.contentType,
|
||||
"Content-Length": "" + data.meta.size,
|
||||
"X-Permission": data.meta.perm,
|
||||
"X-Last-Modified": "" + data.meta.lastModified,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
@@ -120,7 +125,7 @@ self.addEventListener("fetch", (event: any) => {
|
||||
// Just fetch the file directly
|
||||
return fetch(event.request);
|
||||
}
|
||||
} else if (pathname !== "/.auth") {
|
||||
} else if (fetchingLocal && pathname !== "/.auth") {
|
||||
// Must be a page URL, let's serve index.html which will handle it
|
||||
return caches.match(precacheFiles["/"]).then((response) => {
|
||||
// This shouldnt't happen, index.html not in the cache for some reason
|
||||
|
||||
+3
-3
@@ -88,8 +88,8 @@ export class SyncService {
|
||||
|
||||
async hasInitialSyncCompleted(): Promise<boolean> {
|
||||
// Initial sync has happened when sync progress has been reported at least once, but the syncStartTime has been reset (which happens after sync finishes)
|
||||
return !!(!(await this.kvStore.get(syncStartTimeKey)) &&
|
||||
(await this.kvStore.get(syncLastActivityKey)));
|
||||
return !(await this.kvStore.has(syncStartTimeKey)) &&
|
||||
(await this.kvStore.has(syncLastActivityKey));
|
||||
}
|
||||
|
||||
async registerSyncStart(): Promise<void> {
|
||||
@@ -371,7 +371,7 @@ export class SyncService {
|
||||
name,
|
||||
data,
|
||||
false,
|
||||
meta.lastModified,
|
||||
meta,
|
||||
);
|
||||
// Update snapshot
|
||||
snapshot.set(name, [
|
||||
|
||||
@@ -182,6 +182,10 @@ export function editorSyscalls(editor: Editor): SysCallMapping {
|
||||
const cm = vimGetCm(editor.editorView!)!;
|
||||
return Vim.handleEx(cm, exCommand);
|
||||
},
|
||||
// Sync
|
||||
"editor.syncSpace": () => {
|
||||
return editor.syncService.syncSpace();
|
||||
},
|
||||
// Folding
|
||||
"editor.fold": () => {
|
||||
foldCode(editor.editorView!);
|
||||
|
||||
Reference in New Issue
Block a user