Added pageNamespace hook support
This commit is contained in:
parent
cd89634fd6
commit
9a6a86f8b5
@ -4,12 +4,14 @@ import { CronHookT } from "@plugos/plugos/hooks/node_cron";
|
|||||||
import { EventHookT } from "@plugos/plugos/hooks/event";
|
import { EventHookT } from "@plugos/plugos/hooks/event";
|
||||||
import { CommandHookT } from "@silverbulletmd/web/hooks/command";
|
import { CommandHookT } from "@silverbulletmd/web/hooks/command";
|
||||||
import { SlashCommandHookT } from "@silverbulletmd/web/hooks/slash_command";
|
import { SlashCommandHookT } from "@silverbulletmd/web/hooks/slash_command";
|
||||||
|
import { PageNamespaceHookT } from "../server/hooks/page_namespace";
|
||||||
|
|
||||||
export type SilverBulletHooks = CommandHookT &
|
export type SilverBulletHooks = CommandHookT &
|
||||||
SlashCommandHookT &
|
SlashCommandHookT &
|
||||||
EndpointHookT &
|
EndpointHookT &
|
||||||
CronHookT &
|
CronHookT &
|
||||||
EventHookT;
|
EventHookT &
|
||||||
|
PageNamespaceHookT;
|
||||||
|
|
||||||
export type SyntaxExtensions = {
|
export type SyntaxExtensions = {
|
||||||
syntax?: { [key: string]: NodeDef };
|
syntax?: { [key: string]: NodeDef };
|
||||||
|
@ -56,6 +56,7 @@ export class DiskSpacePrimitives implements SpacePrimitives {
|
|||||||
meta: {
|
meta: {
|
||||||
name: pageName,
|
name: pageName,
|
||||||
lastModified: s.mtime.getTime(),
|
lastModified: s.mtime.getTime(),
|
||||||
|
perm: "rw",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -88,6 +89,7 @@ export class DiskSpacePrimitives implements SpacePrimitives {
|
|||||||
return {
|
return {
|
||||||
name: pageName,
|
name: pageName,
|
||||||
lastModified: s.mtime.getTime(),
|
lastModified: s.mtime.getTime(),
|
||||||
|
perm: "rw",
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error while writing page", pageName, e);
|
console.error("Error while writing page", pageName, e);
|
||||||
@ -102,6 +104,7 @@ export class DiskSpacePrimitives implements SpacePrimitives {
|
|||||||
return {
|
return {
|
||||||
name: pageName,
|
name: pageName,
|
||||||
lastModified: s.mtime.getTime(),
|
lastModified: s.mtime.getTime(),
|
||||||
|
perm: "rw",
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error while getting page meta", pageName, e);
|
console.error("Error while getting page meta", pageName, e);
|
||||||
@ -132,6 +135,7 @@ export class DiskSpacePrimitives implements SpacePrimitives {
|
|||||||
pages.add({
|
pages.add({
|
||||||
name: this.pathToPageName(fullPath),
|
name: this.pathToPageName(fullPath),
|
||||||
lastModified: s.mtime.getTime(),
|
lastModified: s.mtime.getTime(),
|
||||||
|
perm: "rw",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ export class HttpSpacePrimitives implements SpacePrimitives {
|
|||||||
result.add({
|
result.add({
|
||||||
name: pageName,
|
name: pageName,
|
||||||
lastModified: meta.lastModified,
|
lastModified: meta.lastModified,
|
||||||
|
perm: "rw",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -160,10 +161,10 @@ export class HttpSpacePrimitives implements SpacePrimitives {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private responseToMeta(name: string, res: Response): PageMeta {
|
private responseToMeta(name: string, res: Response): PageMeta {
|
||||||
const meta = {
|
return {
|
||||||
name,
|
name,
|
||||||
lastModified: +(res.headers.get("Last-Modified") || "0"),
|
lastModified: +(res.headers.get("Last-Modified") || "0"),
|
||||||
|
perm: (res.headers.get("X-Permission") as "rw" | "ro") || "rw",
|
||||||
};
|
};
|
||||||
return meta;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,9 +72,10 @@ export class IndexedDBSpacePrimitives implements SpacePrimitives {
|
|||||||
selfUpdate?: boolean,
|
selfUpdate?: boolean,
|
||||||
lastModified?: number
|
lastModified?: number
|
||||||
): Promise<PageMeta> {
|
): Promise<PageMeta> {
|
||||||
let meta = {
|
const meta: PageMeta = {
|
||||||
name,
|
name,
|
||||||
lastModified: lastModified ? lastModified : Date.now() + this.timeSkew,
|
lastModified: lastModified ? lastModified : Date.now() + this.timeSkew,
|
||||||
|
perm: "rw",
|
||||||
};
|
};
|
||||||
await this.pageTable.put({
|
await this.pageTable.put({
|
||||||
name,
|
name,
|
||||||
|
@ -31,9 +31,10 @@ export class Space extends EventEmitter<SpaceEvents> {
|
|||||||
newPageList.pages.forEach((meta) => {
|
newPageList.pages.forEach((meta) => {
|
||||||
const pageName = meta.name;
|
const pageName = meta.name;
|
||||||
const oldPageMeta = this.pageMetaCache.get(pageName);
|
const oldPageMeta = this.pageMetaCache.get(pageName);
|
||||||
const newPageMeta = {
|
const newPageMeta: PageMeta = {
|
||||||
name: pageName,
|
name: pageName,
|
||||||
lastModified: meta.lastModified,
|
lastModified: meta.lastModified,
|
||||||
|
perm: meta.perm,
|
||||||
};
|
};
|
||||||
if (
|
if (
|
||||||
!oldPageMeta &&
|
!oldPageMeta &&
|
||||||
|
@ -8,3 +8,19 @@ functions:
|
|||||||
path: ./search.ts:queryProvider
|
path: ./search.ts:queryProvider
|
||||||
events:
|
events:
|
||||||
- query:full-text
|
- query:full-text
|
||||||
|
searchCommand:
|
||||||
|
path: ./search.ts:searchCommand
|
||||||
|
command:
|
||||||
|
name: "Search Space"
|
||||||
|
key: Ctrl-Shift-f
|
||||||
|
mac: Cmd-Shift-f
|
||||||
|
readPageSearch:
|
||||||
|
path: ./search.ts:readPageSearch
|
||||||
|
pageNamespace:
|
||||||
|
pattern: "@search/.+"
|
||||||
|
operation: readPage
|
||||||
|
getPageMetaSearch:
|
||||||
|
path: ./search.ts:getPageMetaSearch
|
||||||
|
pageNamespace:
|
||||||
|
pattern: "@search/.+"
|
||||||
|
operation: getPageMeta
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import { fullTextIndex, fullTextSearch } from "@plugos/plugos-syscall/fulltext";
|
import { fullTextIndex, fullTextSearch } from "@plugos/plugos-syscall/fulltext";
|
||||||
import { renderToText } from "@silverbulletmd/common/tree";
|
import { renderToText } from "@silverbulletmd/common/tree";
|
||||||
|
import { PageMeta } from "@silverbulletmd/common/types";
|
||||||
import { scanPrefixGlobal } from "@silverbulletmd/plugos-silverbullet-syscall";
|
import { scanPrefixGlobal } from "@silverbulletmd/plugos-silverbullet-syscall";
|
||||||
|
import {
|
||||||
|
navigate,
|
||||||
|
prompt,
|
||||||
|
} from "@silverbulletmd/plugos-silverbullet-syscall/editor";
|
||||||
import { IndexTreeEvent } from "@silverbulletmd/web/app_event";
|
import { IndexTreeEvent } from "@silverbulletmd/web/app_event";
|
||||||
import { applyQuery, QueryProviderEvent } from "../query/engine";
|
import { applyQuery, QueryProviderEvent } from "../query/engine";
|
||||||
import { removeQueries } from "../query/util";
|
import { removeQueries } from "../query/util";
|
||||||
@ -38,3 +43,37 @@ export async function queryProvider({
|
|||||||
results = applyQuery(query, results);
|
results = applyQuery(query, results);
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function searchCommand() {
|
||||||
|
let phrase = await prompt("Search for: ");
|
||||||
|
if (phrase) {
|
||||||
|
await navigate(`@search/${phrase}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function readPageSearch(
|
||||||
|
name: string
|
||||||
|
): Promise<{ text: string; meta: PageMeta }> {
|
||||||
|
let phrase = name.substring("@search/".length);
|
||||||
|
let results = await fullTextSearch(phrase, 100);
|
||||||
|
const text = `# Search results for "${phrase}"\n${results
|
||||||
|
.map((r: any) => `* [[${r.name}]] (score: ${r.rank})`)
|
||||||
|
.join("\n")}
|
||||||
|
`;
|
||||||
|
return {
|
||||||
|
text: text,
|
||||||
|
meta: {
|
||||||
|
name,
|
||||||
|
lastModified: 0,
|
||||||
|
perm: "ro",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPageMetaSearch(name: string): Promise<PageMeta> {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
lastModified: 0,
|
||||||
|
perm: "ro",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -36,6 +36,8 @@ import {
|
|||||||
ensureFTSTable,
|
ensureFTSTable,
|
||||||
fullTextSearchSyscalls,
|
fullTextSearchSyscalls,
|
||||||
} from "@plugos/plugos/syscalls/fulltext.knex_sqlite";
|
} from "@plugos/plugos/syscalls/fulltext.knex_sqlite";
|
||||||
|
import { PlugSpacePrimitives } from "./hooks/plug_space_primitives";
|
||||||
|
import { PageNamespaceHook } from "./hooks/page_namespace";
|
||||||
|
|
||||||
const safeFilename = /^[a-zA-Z0-9_\-\.]+$/;
|
const safeFilename = /^[a-zA-Z0-9_\-\.]+$/;
|
||||||
|
|
||||||
@ -69,9 +71,14 @@ export class ExpressServer {
|
|||||||
// Setup system
|
// Setup system
|
||||||
this.eventHook = new EventHook();
|
this.eventHook = new EventHook();
|
||||||
this.system.addHook(this.eventHook);
|
this.system.addHook(this.eventHook);
|
||||||
|
let namespaceHook = new PageNamespaceHook();
|
||||||
|
this.system.addHook(namespaceHook);
|
||||||
this.space = new Space(
|
this.space = new Space(
|
||||||
new EventedSpacePrimitives(
|
new EventedSpacePrimitives(
|
||||||
new DiskSpacePrimitives(options.pagesPath),
|
new PlugSpacePrimitives(
|
||||||
|
new DiskSpacePrimitives(options.pagesPath),
|
||||||
|
namespaceHook
|
||||||
|
),
|
||||||
this.eventHook
|
this.eventHook
|
||||||
),
|
),
|
||||||
true
|
true
|
||||||
@ -227,6 +234,7 @@ export class ExpressServer {
|
|||||||
let pageData = await this.space.readPage(pageName);
|
let pageData = await this.space.readPage(pageName);
|
||||||
res.status(200);
|
res.status(200);
|
||||||
res.header("Last-Modified", "" + pageData.meta.lastModified);
|
res.header("Last-Modified", "" + pageData.meta.lastModified);
|
||||||
|
res.header("X-Permission", pageData.meta.perm);
|
||||||
res.header("Content-Type", "text/markdown");
|
res.header("Content-Type", "text/markdown");
|
||||||
res.send(pageData.text);
|
res.send(pageData.text);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -251,6 +259,7 @@ export class ExpressServer {
|
|||||||
);
|
);
|
||||||
res.status(200);
|
res.status(200);
|
||||||
res.header("Last-Modified", "" + meta.lastModified);
|
res.header("Last-Modified", "" + meta.lastModified);
|
||||||
|
res.header("X-Permission", meta.perm);
|
||||||
res.send("OK");
|
res.send("OK");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(500);
|
res.status(500);
|
||||||
@ -264,6 +273,7 @@ export class ExpressServer {
|
|||||||
const meta = await this.space.getPageMeta(pageName);
|
const meta = await this.space.getPageMeta(pageName);
|
||||||
res.status(200);
|
res.status(200);
|
||||||
res.header("Last-Modified", "" + meta.lastModified);
|
res.header("Last-Modified", "" + meta.lastModified);
|
||||||
|
res.header("X-Permission", meta.perm);
|
||||||
res.header("Content-Type", "text/markdown");
|
res.header("Content-Type", "text/markdown");
|
||||||
res.send("");
|
res.send("");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
90
packages/server/hooks/page_namespace.ts
Normal file
90
packages/server/hooks/page_namespace.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { Plug } from "@plugos/plugos/plug";
|
||||||
|
import { System } from "@plugos/plugos/system";
|
||||||
|
import { Hook, Manifest } from "@plugos/plugos/types";
|
||||||
|
import { Express, NextFunction, Request, Response, Router } from "express";
|
||||||
|
|
||||||
|
export type PageNamespaceOperation =
|
||||||
|
| "readPage"
|
||||||
|
| "writePage"
|
||||||
|
| "listPages"
|
||||||
|
| "getPageMeta"
|
||||||
|
| "deletePage";
|
||||||
|
|
||||||
|
export type PageNamespaceDef = {
|
||||||
|
pattern: string;
|
||||||
|
operation: PageNamespaceOperation;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PageNamespaceHookT = {
|
||||||
|
pageNamespace?: PageNamespaceDef;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SpaceFunction = {
|
||||||
|
operation: PageNamespaceOperation;
|
||||||
|
pattern: RegExp;
|
||||||
|
plug: Plug<PageNamespaceHookT>;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class PageNamespaceHook implements Hook<PageNamespaceHookT> {
|
||||||
|
spaceFunctions: SpaceFunction[] = [];
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
apply(system: System<PageNamespaceHookT>): void {
|
||||||
|
system.on({
|
||||||
|
plugLoaded: () => {
|
||||||
|
this.updateCache(system);
|
||||||
|
},
|
||||||
|
plugUnloaded: () => {
|
||||||
|
this.updateCache(system);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCache(system: System<PageNamespaceHookT>) {
|
||||||
|
this.spaceFunctions = [];
|
||||||
|
for (let plug of system.loadedPlugs.values()) {
|
||||||
|
if (plug.manifest?.functions) {
|
||||||
|
for (let [funcName, funcDef] of Object.entries(
|
||||||
|
plug.manifest.functions
|
||||||
|
)) {
|
||||||
|
if (funcDef.pageNamespace) {
|
||||||
|
this.spaceFunctions.push({
|
||||||
|
operation: funcDef.pageNamespace.operation,
|
||||||
|
pattern: new RegExp(funcDef.pageNamespace.pattern),
|
||||||
|
plug,
|
||||||
|
name: funcName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validateManifest(manifest: Manifest<PageNamespaceHookT>): string[] {
|
||||||
|
let errors: string[] = [];
|
||||||
|
if (!manifest.functions) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
for (let [funcName, funcDef] of Object.entries(manifest.functions)) {
|
||||||
|
if (funcDef.pageNamespace) {
|
||||||
|
if (!funcDef.pageNamespace.pattern) {
|
||||||
|
errors.push(`Function ${funcName} has a namespace but no pattern`);
|
||||||
|
}
|
||||||
|
if (!funcDef.pageNamespace.operation) {
|
||||||
|
errors.push(`Function ${funcName} has a namespace but no operation`);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!["readPage", "writePage", "getPageMeta", "listPages"].includes(
|
||||||
|
funcDef.pageNamespace.operation
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
errors.push(
|
||||||
|
`Function ${funcName} has an invalid operation ${funcDef.pageNamespace.operation}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
}
|
103
packages/server/hooks/plug_space_primitives.ts
Normal file
103
packages/server/hooks/plug_space_primitives.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import { Plug } from "@plugos/plugos/plug";
|
||||||
|
import { SpacePrimitives } from "@silverbulletmd/common/spaces/space_primitives";
|
||||||
|
import { PageMeta } from "@silverbulletmd/common/types";
|
||||||
|
import { PageNamespaceHook, PageNamespaceOperation } from "./page_namespace";
|
||||||
|
|
||||||
|
export class PlugSpacePrimitives implements SpacePrimitives {
|
||||||
|
constructor(
|
||||||
|
private wrapped: SpacePrimitives,
|
||||||
|
private hook: PageNamespaceHook
|
||||||
|
) {}
|
||||||
|
|
||||||
|
performOperation(
|
||||||
|
type: PageNamespaceOperation,
|
||||||
|
pageName: string,
|
||||||
|
...args: any[]
|
||||||
|
): Promise<any> | false {
|
||||||
|
for (let { operation, pattern, plug, name } of this.hook.spaceFunctions) {
|
||||||
|
if (operation === type && pageName.match(pattern)) {
|
||||||
|
return plug.invoke(name, [pageName, ...args]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchPageList(): Promise<{
|
||||||
|
pages: Set<PageMeta>;
|
||||||
|
nowTimestamp: number;
|
||||||
|
}> {
|
||||||
|
let allPages = new Set<PageMeta>();
|
||||||
|
for (let { plug, name, operation } of this.hook.spaceFunctions) {
|
||||||
|
if (operation === "listPages") {
|
||||||
|
for (let pm of await plug.invoke(name, [])) {
|
||||||
|
allPages.add(pm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let result = await this.wrapped.fetchPageList();
|
||||||
|
for (let pm of result.pages) {
|
||||||
|
allPages.add(pm);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
nowTimestamp: result.nowTimestamp,
|
||||||
|
pages: allPages,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
readPage(name: string): Promise<{ text: string; meta: PageMeta }> {
|
||||||
|
let result = this.performOperation("readPage", name);
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return this.wrapped.readPage(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPageMeta(name: string): Promise<PageMeta> {
|
||||||
|
let result = this.performOperation("getPageMeta", name);
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return this.wrapped.getPageMeta(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
writePage(
|
||||||
|
name: string,
|
||||||
|
text: string,
|
||||||
|
selfUpdate?: boolean,
|
||||||
|
lastModified?: number
|
||||||
|
): Promise<PageMeta> {
|
||||||
|
let result = this.performOperation(
|
||||||
|
"writePage",
|
||||||
|
name,
|
||||||
|
text,
|
||||||
|
selfUpdate,
|
||||||
|
lastModified
|
||||||
|
);
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.wrapped.writePage(name, text, selfUpdate, lastModified);
|
||||||
|
}
|
||||||
|
|
||||||
|
deletePage(name: string): Promise<void> {
|
||||||
|
let result = this.performOperation("deletePage", name);
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return this.wrapped.deletePage(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
proxySyscall(plug: Plug<any>, name: string, args: any[]): Promise<any> {
|
||||||
|
return this.wrapped.proxySyscall(plug, name, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeFunction(
|
||||||
|
plug: Plug<any>,
|
||||||
|
env: string,
|
||||||
|
name: string,
|
||||||
|
args: any[]
|
||||||
|
): Promise<any> {
|
||||||
|
return this.wrapped.invokeFunction(plug, env, name, args);
|
||||||
|
}
|
||||||
|
}
|
@ -159,6 +159,7 @@ export function FilterList({
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
/>
|
/>
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
highlightSpecialChars,
|
highlightSpecialChars,
|
||||||
KeyBinding,
|
KeyBinding,
|
||||||
keymap,
|
keymap,
|
||||||
|
runScopeHandlers,
|
||||||
ViewPlugin,
|
ViewPlugin,
|
||||||
ViewUpdate,
|
ViewUpdate,
|
||||||
} from "@codemirror/view";
|
} from "@codemirror/view";
|
||||||
@ -56,7 +57,7 @@ import {
|
|||||||
MDExt,
|
MDExt,
|
||||||
} from "@silverbulletmd/common/markdown_ext";
|
} from "@silverbulletmd/common/markdown_ext";
|
||||||
import { FilterList } from "./components/filter";
|
import { FilterList } from "./components/filter";
|
||||||
import { FilterOption } from "@silverbulletmd/common/types";
|
import { FilterOption, PageMeta } from "@silverbulletmd/common/types";
|
||||||
import { syntaxTree } from "@codemirror/language";
|
import { syntaxTree } from "@codemirror/language";
|
||||||
import sandboxSyscalls from "@plugos/plugos/syscalls/sandbox";
|
import sandboxSyscalls from "@plugos/plugos/syscalls/sandbox";
|
||||||
import globalModules from "../common/dist/global.plug.json";
|
import globalModules from "../common/dist/global.plug.json";
|
||||||
@ -144,6 +145,16 @@ export class Editor {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Make keyboard shortcuts work even when the editor is in read only mode or not focused
|
||||||
|
window.addEventListener("keydown", (ev) => {
|
||||||
|
if (!this.editorView?.hasFocus) {
|
||||||
|
console.log("Window-level keyboard event", ev);
|
||||||
|
if (runScopeHandlers(this.editorView!, ev, "editor")) {
|
||||||
|
ev.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get currentPage(): string | undefined {
|
get currentPage(): string | undefined {
|
||||||
@ -454,7 +465,10 @@ export class Editor {
|
|||||||
this.createEditorState(this.currentPage, editorView.state.sliceDoc())
|
this.createEditorState(this.currentPage, editorView.state.sliceDoc())
|
||||||
);
|
);
|
||||||
if (editorView.contentDOM) {
|
if (editorView.contentDOM) {
|
||||||
this.tweakEditorDOM(editorView.contentDOM);
|
this.tweakEditorDOM(
|
||||||
|
editorView.contentDOM,
|
||||||
|
this.viewState.perm === "ro"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.restoreState(this.currentPage);
|
this.restoreState(this.currentPage);
|
||||||
@ -516,30 +530,31 @@ export class Editor {
|
|||||||
console.log("Creating new page", pageName);
|
console.log("Creating new page", pageName);
|
||||||
doc = {
|
doc = {
|
||||||
text: "",
|
text: "",
|
||||||
meta: { name: pageName, lastModified: 0 },
|
meta: { name: pageName, lastModified: 0, perm: "rw" } as PageMeta,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let editorState = this.createEditorState(pageName, doc.text);
|
let editorState = this.createEditorState(pageName, doc.text);
|
||||||
editorView.setState(editorState);
|
editorView.setState(editorState);
|
||||||
if (editorView.contentDOM) {
|
if (editorView.contentDOM) {
|
||||||
this.tweakEditorDOM(editorView.contentDOM);
|
this.tweakEditorDOM(editorView.contentDOM, doc.meta.perm === "ro");
|
||||||
}
|
}
|
||||||
this.restoreState(pageName);
|
this.restoreState(pageName);
|
||||||
this.space.watchPage(pageName);
|
this.space.watchPage(pageName);
|
||||||
|
|
||||||
this.viewDispatch({
|
this.viewDispatch({
|
||||||
type: "page-loaded",
|
type: "page-loaded",
|
||||||
name: pageName,
|
meta: doc.meta,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.eventHook.dispatchEvent("editor:pageSwitched");
|
await this.eventHook.dispatchEvent("editor:pageSwitched");
|
||||||
}
|
}
|
||||||
|
|
||||||
tweakEditorDOM(contentDOM: HTMLElement) {
|
tweakEditorDOM(contentDOM: HTMLElement, readOnly: boolean) {
|
||||||
contentDOM.spellcheck = true;
|
contentDOM.spellcheck = true;
|
||||||
contentDOM.setAttribute("autocorrect", "on");
|
contentDOM.setAttribute("autocorrect", "on");
|
||||||
contentDOM.setAttribute("autocapitalize", "on");
|
contentDOM.setAttribute("autocapitalize", "on");
|
||||||
|
contentDOM.setAttribute("contenteditable", readOnly ? "false" : "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
private restoreState(pageName: string) {
|
private restoreState(pageName: string) {
|
||||||
|
@ -14,12 +14,13 @@ export default function reducer(
|
|||||||
...state,
|
...state,
|
||||||
allPages: new Set(
|
allPages: new Set(
|
||||||
[...state.allPages].map((pageMeta) =>
|
[...state.allPages].map((pageMeta) =>
|
||||||
pageMeta.name === action.name
|
pageMeta.name === action.meta.name
|
||||||
? { ...pageMeta, lastOpened: Date.now() }
|
? { ...pageMeta, lastOpened: Date.now() }
|
||||||
: pageMeta
|
: pageMeta
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
currentPage: action.name,
|
perm: action.meta.perm,
|
||||||
|
currentPage: action.meta.name,
|
||||||
};
|
};
|
||||||
case "page-changed":
|
case "page-changed":
|
||||||
return {
|
return {
|
||||||
|
@ -18,6 +18,8 @@ export type ActionButton = {
|
|||||||
|
|
||||||
export type AppViewState = {
|
export type AppViewState = {
|
||||||
currentPage?: string;
|
currentPage?: string;
|
||||||
|
perm: "ro" | "rw";
|
||||||
|
|
||||||
showPageNavigator: boolean;
|
showPageNavigator: boolean;
|
||||||
showCommandPalette: boolean;
|
showCommandPalette: boolean;
|
||||||
unsavedChanges: boolean;
|
unsavedChanges: boolean;
|
||||||
@ -45,6 +47,7 @@ export type AppViewState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const initialViewState: AppViewState = {
|
export const initialViewState: AppViewState = {
|
||||||
|
perm: "rw",
|
||||||
showPageNavigator: false,
|
showPageNavigator: false,
|
||||||
showCommandPalette: false,
|
showCommandPalette: false,
|
||||||
unsavedChanges: false,
|
unsavedChanges: false,
|
||||||
@ -68,7 +71,7 @@ export const initialViewState: AppViewState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type Action =
|
export type Action =
|
||||||
| { type: "page-loaded"; name: string }
|
| { type: "page-loaded"; meta: PageMeta }
|
||||||
| { type: "pages-listed"; pages: Set<PageMeta> }
|
| { type: "pages-listed"; pages: Set<PageMeta> }
|
||||||
| { type: "page-changed" }
|
| { type: "page-changed" }
|
||||||
| { type: "page-saved" }
|
| { type: "page-saved" }
|
||||||
|
Loading…
Reference in New Issue
Block a user