WIP: Plug API document change event (#488)
* add support for basic on doc change event * move change API core into plug-api/lib; add docs * add overlap utility * Maintain modal focus * Federated URL backend handling * Fix small typo in Query.md (#483) * Federation progress * Cleanup and federation prep * Robustness and federation sync * Federation: rewrite page references in federated content * Don't sync service worker and index.json to client on silverbullet.md * Federation listing timeouts * Switching onboarding over to federation links * Reduce amount of sync related log messages a bit * Attribute indexing and code completion * Shift-Enter in the page navigator now takes the input literally * Updated changelog * Completion for handlebar template variables * Make 'pos' a number in tasks * Updated install instructions to include edge builds * WIP: CLI running of plugs * Upgrade deno in Docker to 1.36.0 * Implement CLI store using Deno store * Rerun directives * Fixes #485 * 0.3.8 * 0.3.9 * Changelog * Instantly sync updated pages when ticking off a task in a directive * Sync current open page every 5s * Optimize requests * Make attribute extensible * Debugging sync getting stuck * Misaligning sync cycles (to avoid no-op cycles) * Fixes #500: New apply page template command * Changelog * More sync debugging statements * More sync debugging * Even more debug * Dial down excessive debug logging * Fixes #115: By introducing MQ workers * Use MQ for updating directives in entire space * Work on plug:run * touch up docs * Fix htmlLanguage dependency --------- Co-authored-by: Zef Hemel <zef@zef.me> Co-authored-by: johnl <johnlunney@users.noreply.github.com>
This commit is contained in:
parent
f22b9c4a1f
commit
7d3303251d
@ -106,7 +106,7 @@ export {
|
||||
} from "https://esm.sh/@codemirror/legacy-modes@6.3.2/mode/sql?external=@codemirror/language&target=es2022";
|
||||
export { rust as rustLanguage } from "https://esm.sh/@codemirror/legacy-modes@6.3.2/mode/rust?external=@codemirror/language&target=es2022";
|
||||
export { css as cssLanguage } from "https://esm.sh/@codemirror/legacy-modes@6.3.2/mode/css?external=@codemirror/language&target=es2022";
|
||||
export { html as htmlLanguage } from "@codemirror/lang-html";
|
||||
export { htmlLanguage } from "@codemirror/lang-html";
|
||||
export { python as pythonLanguage } from "https://esm.sh/@codemirror/legacy-modes@6.3.2/mode/python?external=@codemirror/language&target=es2022";
|
||||
export { protobuf as protobufLanguage } from "https://esm.sh/@codemirror/legacy-modes@6.3.2/mode/protobuf?external=@codemirror/language&target=es2022";
|
||||
export { shell as shellLanguage } from "https://esm.sh/@codemirror/legacy-modes@6.3.2/mode/shell?external=@codemirror/language&target=es2022";
|
||||
|
@ -1,5 +1,6 @@
|
||||
import type { ParseTree } from "$sb/lib/tree.ts";
|
||||
import { ParsedQuery } from "$sb/lib/query.ts";
|
||||
import { TextChange } from "$sb/lib/change.ts";
|
||||
|
||||
export type AppEvent =
|
||||
| "page:click"
|
||||
@ -11,7 +12,8 @@ export type AppEvent =
|
||||
| "editor:pageReloaded"
|
||||
| "editor:pageSaved"
|
||||
| "editor:modeswitch"
|
||||
| "plugs:loaded";
|
||||
| "plugs:loaded"
|
||||
| "editor:pageModified";
|
||||
|
||||
export type QueryProviderEvent = {
|
||||
query: ParsedQuery;
|
||||
@ -56,3 +58,9 @@ export type WidgetContent = {
|
||||
height?: number;
|
||||
width?: number;
|
||||
};
|
||||
|
||||
/** PageModifiedEvent payload for "editor:pageModified". Fired when the document text changes
|
||||
*/
|
||||
export type PageModifiedEvent = {
|
||||
changes: TextChange[];
|
||||
};
|
||||
|
26
plug-api/lib/change.test.ts
Normal file
26
plug-api/lib/change.test.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { rangeLength, rangesOverlap } from "$sb/lib/change.ts";
|
||||
import { assertEquals } from "https://deno.land/std@0.165.0/testing/asserts.ts";
|
||||
|
||||
Deno.test("rangeLength", () => {
|
||||
assertEquals(rangeLength({ from: 4, to: 11 }), 7);
|
||||
});
|
||||
|
||||
Deno.test("rangesOverlap", () => {
|
||||
assertEquals(
|
||||
rangesOverlap({ from: 0, to: 5 }, { from: 3, to: 8 }),
|
||||
true,
|
||||
);
|
||||
assertEquals(
|
||||
rangesOverlap({ from: 0, to: 5 }, { from: 6, to: 8 }),
|
||||
false,
|
||||
);
|
||||
// `to` is exclusive
|
||||
assertEquals(
|
||||
rangesOverlap({ from: 0, to: 6 }, { from: 6, to: 8 }),
|
||||
false,
|
||||
);
|
||||
assertEquals(
|
||||
rangesOverlap({ from: 3, to: 6 }, { from: 0, to: 4 }),
|
||||
true,
|
||||
);
|
||||
});
|
43
plug-api/lib/change.ts
Normal file
43
plug-api/lib/change.ts
Normal file
@ -0,0 +1,43 @@
|
||||
// API for working with changes to document text
|
||||
|
||||
/** Denotes a region in the document, based on character indicices
|
||||
*/
|
||||
export type Range = {
|
||||
/** The starting index of the span, 0-based, inclusive
|
||||
*/
|
||||
from: number;
|
||||
|
||||
/** The ending index of the span, 0-based, exclusive
|
||||
*/
|
||||
to: number;
|
||||
};
|
||||
|
||||
/** A modification to the document */
|
||||
export type TextChange = {
|
||||
/** The new text */
|
||||
inserted: string;
|
||||
|
||||
/** The modified range **before** this change took effect.
|
||||
*
|
||||
* Example: "aaabbbccc" => "aaaccc", oldRange is [3, 6)
|
||||
*/
|
||||
oldRange: Range;
|
||||
|
||||
/** The modified range **after** this change took effect.
|
||||
*
|
||||
* Example: "aaabbbccc" => "aaaccc", newRange is [3, 3)
|
||||
*/
|
||||
newRange: Range;
|
||||
};
|
||||
|
||||
/** Get this distance between the start and end of a range */
|
||||
export function rangeLength(range: Range): number {
|
||||
return range.to - range.from;
|
||||
}
|
||||
|
||||
export function rangesOverlap(a: Range, b: Range): boolean {
|
||||
// `b.from >= a.to` => "b" starts after "a"
|
||||
// `a.from >= b.to` => "b" ends before "a"
|
||||
// if neither is true these two ranges must overlap
|
||||
return !(b.from >= a.to || a.from >= b.to);
|
||||
}
|
@ -34,7 +34,6 @@ import { createEditorState } from "./editor_state.ts";
|
||||
import { OpenPages } from "./open_pages.ts";
|
||||
import { MainUI } from "./editor_ui.tsx";
|
||||
import { DexieMQ } from "../plugos/lib/mq.dexie.ts";
|
||||
import { async } from "https://cdn.skypack.dev/-/regenerator-runtime@v0.13.9-4Dxus9nU31cBsHxnWq2H/dist=es2020,mode=imports/optimized/regenerator-runtime.js";
|
||||
const frontMatterRegex = /^---\n(([^\n]|\n)*?)---\n/;
|
||||
|
||||
const autoSaveInterval = 1000;
|
||||
|
@ -18,6 +18,7 @@ import {
|
||||
highlightSpecialChars,
|
||||
history,
|
||||
historyKeymap,
|
||||
htmlLanguage,
|
||||
indentOnInput,
|
||||
indentWithTab,
|
||||
javaLanguage,
|
||||
@ -61,7 +62,7 @@ import {
|
||||
attachmentExtension,
|
||||
pasteLinkExtension,
|
||||
} from "./cm_plugins/editor_paste.ts";
|
||||
import { htmlLanguage } from "https://esm.sh/v130/@codemirror/lang-html@6.4.3/X-ZS9AY29kZW1pcnJvci9hdXRvY29tcGxldGUsQGNvZGVtaXJyb3IvbGFuZy1jc3MsQGNvZGVtaXJyb3IvbGFuZ3VhZ2UsQGNvZGVtaXJyb3Ivc3RhdGUsQGxlemVyL2h0bWwsQGxlemVyL2xy/dist/index.js";
|
||||
import { TextChange } from "$sb/lib/change.ts";
|
||||
|
||||
export function createEditorState(
|
||||
editor: Client,
|
||||
@ -424,6 +425,15 @@ export function createEditorState(
|
||||
class {
|
||||
update(update: ViewUpdate): void {
|
||||
if (update.docChanged) {
|
||||
const changes: TextChange[] = [];
|
||||
update.changes.iterChanges((fromA, toA, fromB, toB, inserted) =>
|
||||
changes.push({
|
||||
inserted: inserted.toString(),
|
||||
oldRange: { from: fromA, to: toA },
|
||||
newRange: { from: fromB, to: toB },
|
||||
})
|
||||
);
|
||||
editor.dispatchAppEvent("editor:pageModified", { changes });
|
||||
editor.ui.viewDispatch({ type: "page-changed" });
|
||||
editor.debouncedUpdateEvent();
|
||||
editor.save().catch((e) => console.error("Error saving", e));
|
||||
|
@ -250,6 +250,8 @@ export class SyncService {
|
||||
}
|
||||
try {
|
||||
remoteHash = (await this.remoteSpace!.getFileMeta(name)).lastModified;
|
||||
// HEAD
|
||||
//
|
||||
if (!remoteHash) {
|
||||
console.info(
|
||||
"Not syncing file, because remote didn't send X-Last-Modified header",
|
||||
@ -260,6 +262,7 @@ export class SyncService {
|
||||
// Jumping out, not saving snapshot nor triggering a sync event, because we did nothing
|
||||
return;
|
||||
}
|
||||
//main
|
||||
} catch (e: any) {
|
||||
if (e.message === "Not found") {
|
||||
// File doesn't exist remotely, that's ok
|
||||
@ -279,6 +282,10 @@ export class SyncService {
|
||||
}
|
||||
await this.saveSnapshot(snapshot);
|
||||
await this.registerSyncStop(false);
|
||||
// HEAD
|
||||
// console.log("And done with file sync for", name);
|
||||
//
|
||||
//main
|
||||
}
|
||||
|
||||
async saveSnapshot(snapshot: Map<string, SyncStatusItem>) {
|
||||
|
Loading…
Reference in New Issue
Block a user