@silverbulletmd/silverbullet 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/LICENSE.md +18 -0
  2. package/README.md +98 -0
  3. package/client/asset_bundle/bundle.ts +95 -0
  4. package/client/data/datastore.ts +85 -0
  5. package/client/data/kv_primitives.ts +25 -0
  6. package/client/markdown_parser/constants.ts +13 -0
  7. package/client/plugos/event.ts +36 -0
  8. package/client/plugos/eventhook.ts +8 -0
  9. package/client/plugos/hooks/code_widget.ts +59 -0
  10. package/client/plugos/hooks/command.ts +104 -0
  11. package/client/plugos/hooks/document_editor.ts +77 -0
  12. package/client/plugos/hooks/event.ts +187 -0
  13. package/client/plugos/hooks/mq.ts +154 -0
  14. package/client/plugos/hooks/plug_namespace.ts +85 -0
  15. package/client/plugos/hooks/slash_command.ts +192 -0
  16. package/client/plugos/hooks/syscall.ts +66 -0
  17. package/client/plugos/manifest_cache.ts +67 -0
  18. package/client/plugos/plug.ts +99 -0
  19. package/client/plugos/plug_compile.ts +202 -0
  20. package/client/plugos/protocol.ts +40 -0
  21. package/client/plugos/proxy_fetch.ts +53 -0
  22. package/client/plugos/sandboxes/deno_worker_sandbox.ts +6 -0
  23. package/client/plugos/sandboxes/sandbox.ts +14 -0
  24. package/client/plugos/sandboxes/web_worker_sandbox.ts +17 -0
  25. package/client/plugos/sandboxes/worker_sandbox.ts +132 -0
  26. package/client/plugos/syscalls/asset.ts +35 -0
  27. package/client/plugos/syscalls/clientStore.ts +21 -0
  28. package/client/plugos/syscalls/client_code_widget.ts +12 -0
  29. package/client/plugos/syscalls/code_widget.ts +24 -0
  30. package/client/plugos/syscalls/config.ts +46 -0
  31. package/client/plugos/syscalls/datastore.ts +89 -0
  32. package/client/plugos/syscalls/editor.ts +673 -0
  33. package/client/plugos/syscalls/event.ts +36 -0
  34. package/client/plugos/syscalls/fetch.ts +128 -0
  35. package/client/plugos/syscalls/index.ts +102 -0
  36. package/client/plugos/syscalls/jsonschema.ts +69 -0
  37. package/client/plugos/syscalls/language.ts +23 -0
  38. package/client/plugos/syscalls/lua.ts +58 -0
  39. package/client/plugos/syscalls/markdown.ts +84 -0
  40. package/client/plugos/syscalls/mq.ts +52 -0
  41. package/client/plugos/syscalls/service_registry.ts +43 -0
  42. package/client/plugos/syscalls/shell.ts +39 -0
  43. package/client/plugos/syscalls/space.ts +139 -0
  44. package/client/plugos/syscalls/sync.ts +77 -0
  45. package/client/plugos/syscalls/system.ts +150 -0
  46. package/client/plugos/system.ts +201 -0
  47. package/client/plugos/types.ts +60 -0
  48. package/client/plugos/util.ts +14 -0
  49. package/client/plugos/worker_runtime.ts +195 -0
  50. package/client/space_lua/ast.ts +328 -0
  51. package/client/space_lua/ast_narrow.ts +81 -0
  52. package/client/space_lua/eval.ts +2478 -0
  53. package/client/space_lua/labels.ts +416 -0
  54. package/client/space_lua/numeric.ts +240 -0
  55. package/client/space_lua/parse.ts +1522 -0
  56. package/client/space_lua/query_collection.ts +232 -0
  57. package/client/space_lua/rp.ts +27 -0
  58. package/client/space_lua/runtime.ts +1702 -0
  59. package/client/space_lua/stdlib/crypto.ts +10 -0
  60. package/client/space_lua/stdlib/encoding.ts +19 -0
  61. package/client/space_lua/stdlib/format.ts +770 -0
  62. package/client/space_lua/stdlib/js.ts +73 -0
  63. package/client/space_lua/stdlib/load.ts +52 -0
  64. package/client/space_lua/stdlib/math.ts +193 -0
  65. package/client/space_lua/stdlib/net.ts +113 -0
  66. package/client/space_lua/stdlib/os.ts +368 -0
  67. package/client/space_lua/stdlib/space_lua.ts +153 -0
  68. package/client/space_lua/stdlib/string.ts +286 -0
  69. package/client/space_lua/stdlib/table.ts +401 -0
  70. package/client/space_lua/stdlib.ts +489 -0
  71. package/client/space_lua/tonumber.ts +501 -0
  72. package/client/space_lua/util.ts +96 -0
  73. package/dist/plug-compile.js +1513 -0
  74. package/package.json +120 -0
  75. package/plug-api/constants.ts +42 -0
  76. package/plug-api/lib/async.ts +162 -0
  77. package/plug-api/lib/crypto.ts +202 -0
  78. package/plug-api/lib/dates.ts +13 -0
  79. package/plug-api/lib/json.ts +136 -0
  80. package/plug-api/lib/limited_map.ts +72 -0
  81. package/plug-api/lib/memory_cache.ts +21 -0
  82. package/plug-api/lib/native_fetch.ts +6 -0
  83. package/plug-api/lib/ref.ts +275 -0
  84. package/plug-api/lib/resolve.ts +90 -0
  85. package/plug-api/lib/tags.ts +15 -0
  86. package/plug-api/lib/transclusion.ts +122 -0
  87. package/plug-api/lib/tree.ts +232 -0
  88. package/plug-api/lib/yaml.ts +284 -0
  89. package/plug-api/syscall.ts +15 -0
  90. package/plug-api/syscalls/asset.ts +36 -0
  91. package/plug-api/syscalls/client_store.ts +33 -0
  92. package/plug-api/syscalls/code_widget.ts +8 -0
  93. package/plug-api/syscalls/config.ts +58 -0
  94. package/plug-api/syscalls/datastore.ts +96 -0
  95. package/plug-api/syscalls/editor.ts +517 -0
  96. package/plug-api/syscalls/event.ts +47 -0
  97. package/plug-api/syscalls/index.ts +77 -0
  98. package/plug-api/syscalls/jsonschema.ts +25 -0
  99. package/plug-api/syscalls/language.ts +23 -0
  100. package/plug-api/syscalls/lua.ts +20 -0
  101. package/plug-api/syscalls/markdown.ts +38 -0
  102. package/plug-api/syscalls/mq.ts +79 -0
  103. package/plug-api/syscalls/shell.ts +14 -0
  104. package/plug-api/syscalls/space.ts +212 -0
  105. package/plug-api/syscalls/sync.ts +28 -0
  106. package/plug-api/syscalls/system.ts +102 -0
  107. package/plug-api/syscalls/yaml.ts +28 -0
  108. package/plug-api/syscalls.ts +21 -0
  109. package/plug-api/system_mock.ts +89 -0
  110. package/plug-api/types/client.ts +116 -0
  111. package/plug-api/types/config.ts +22 -0
  112. package/plug-api/types/datastore.ts +28 -0
  113. package/plug-api/types/event.ts +27 -0
  114. package/plug-api/types/index.ts +56 -0
  115. package/plug-api/types/manifest.ts +98 -0
  116. package/plug-api/types/namespace.ts +6 -0
  117. package/plugs/builtin_plugs.ts +14 -0
package/LICENSE.md ADDED
@@ -0,0 +1,18 @@
1
+ Copyright 2022, Zef Hemel
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,98 @@
1
+ ![GitHub Repo stars](https://img.shields.io/github/stars/silverbulletmd/silverbullet)
2
+ ![Docker Pulls](https://img.shields.io/docker/pulls/zefhemel/silverbullet)
3
+ ![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/silverbulletmd/silverbullet/total)
4
+ ![GitHub contributors](https://img.shields.io/github/contributors/silverbulletmd/silverbullet)
5
+
6
+ # SilverBullet
7
+ SilverBullet is a Programmable, Private, Browser-based, Open Source, Self Hosted, Personal Knowledge Management Platform.
8
+
9
+ _Yowza!_ That surely is a lot of adjectives to describe a browser-based Markdown editor programmable with Lua.
10
+
11
+ Let’s get more specific.
12
+
13
+ In SilverBullet you keep your content as a collection of Markdown Pages (called a Space). You navigate your space using the Page Picker like a traditional notes app, or through Links like a wiki (except they are bi-directional).
14
+
15
+ If you are the **writer** type, you’ll appreciate SilverBullet as a clean Markdown editor with Live Preview. If you have more of an **outliner** personality, SilverBullet has Outlining tools for you. Productivity freak? Have a look at Tasks. More of a **database** person? You will appreciate Objects and Queries.
16
+
17
+ And if you are comfortable **programming** a little bit — now we’re really talking. You will love _dynamically generating content_ with Space Lua (SilverBullet’s Lua dialect), or to use it to create custom Commands, Page Templates or Widgets.
18
+
19
+ [Much more detail can be found on silverbullet.md](https://silverbullet.md)
20
+
21
+ ## Installing SilverBullet
22
+ Check out the [instructions](https://silverbullet.md/Install).
23
+
24
+ ## Developing SilverBullet
25
+ SilverBullet's frontend is written in [TypeScript](https://www.typescriptlang.org/) and built on top of the excellent [CodeMirror 6](https://codemirror.net/) editor component. Additional UI is built using [Preact](https://preactjs.com). [ESBuild](https://esbuild.github.io)) running through Deno is used to build both the front-end.
26
+
27
+ The server backend is written in Go.
28
+
29
+ If you're considering contributing changes, be aware of the [LLM use policy](https://silverbullet.md/LLM%20Use).
30
+
31
+ ## Code structure
32
+ * `client/`: The SilverBullet client, implemented with TypeScript
33
+ * `server/`: The SilverBullet server, written in Go
34
+ * `plugs`: Set of built-in plugs that are distributed with SilverBullet
35
+ * `libraries`: A set of libraries (space scripts, page templates, slash templates) distributed with SilverBullet
36
+ * `plug-api/`: Useful APIs for use in plugs
37
+ * `lib/`: Useful libraries to be used in plugs
38
+ * `syscalls/`: TypeScript wrappers around syscalls
39
+ * `types/`: Various (client) types that can be references from plugs
40
+ * `bin`
41
+ * `plug_compile.ts` the plug compiler
42
+ * `scripts/`: Useful scripts
43
+ * `website/`: silverbullet.md website content
44
+
45
+ ### Requirements
46
+ * [Deno](https://deno.com/): Used to build the frontend and plugs
47
+ * [Go](https://go.dev/): Used to build the backend
48
+
49
+ It's convenient to also install [air](https://github.com/air-verse/air) for development, this will automatically rebuild both the frontend and backend when changes are made:
50
+
51
+ ```shell
52
+ go install github.com/air-verse/air@latest
53
+ ```
54
+ Make sure your `$GOPATH/bin` is in your $PATH.
55
+
56
+ To build everything and run the server:
57
+
58
+ ```shell
59
+ air <PATH-TO-YOUR-SPACE>
60
+ ```
61
+
62
+ Alternatively, to build:
63
+
64
+ ```shell
65
+ make
66
+ ```
67
+
68
+ To run the resulting server:
69
+
70
+ ```shell
71
+ ./silverbullet <PATH-TO-YOUR-SPACE>
72
+ ```
73
+
74
+ ### Useful development tasks
75
+
76
+ ```shell
77
+ # Clean all generated files
78
+ make clean
79
+ # Typecheck and lint all code
80
+ make check
81
+ # Format all code
82
+ make fmt
83
+ # Run all tests
84
+ make test
85
+ ```
86
+
87
+ ### Build a docker container
88
+ Note, you do not need Deno nor Go locally installed for this to work:
89
+
90
+ ```shell
91
+ docker build -t silverbullet .
92
+ ```
93
+
94
+ To run:
95
+
96
+ ```shell
97
+ docker run -p 3000:3000 -v <PATH-TO-YOUR-SPACE>:/space silverbullet
98
+ ```
@@ -0,0 +1,95 @@
1
+ import {
2
+ base64Decode,
3
+ base64EncodedDataUrl,
4
+ } from "../../plug-api/lib/crypto.ts";
5
+
6
+ type DataUrl = string;
7
+
8
+ // Mapping from path -> `data:mimetype;base64,base64-encoded-data` strings
9
+ export type AssetJson = Record<string, { data: DataUrl; mtime: number }>;
10
+
11
+ export class AssetBundle {
12
+ readonly bundle: AssetJson;
13
+
14
+ constructor(bundle: AssetJson = {}) {
15
+ this.bundle = bundle;
16
+ }
17
+
18
+ has(path: string): boolean {
19
+ return path in this.bundle;
20
+ }
21
+
22
+ listFiles(): string[] {
23
+ return Object.keys(this.bundle);
24
+ }
25
+
26
+ readFileSync(
27
+ path: string,
28
+ ): Uint8Array {
29
+ const content = this.bundle[path];
30
+ if (!content) {
31
+ throw new Error(`No such file ${path}`);
32
+ }
33
+ const data = content.data.split(",", 2)[1];
34
+ return base64Decode(data);
35
+ }
36
+
37
+ readFileAsDataUrl(path: string): string {
38
+ const content = this.bundle[path];
39
+ if (!content) {
40
+ throw new Error(`No such file ${path}`);
41
+ }
42
+ return content.data;
43
+ }
44
+
45
+ readTextFileSync(
46
+ path: string,
47
+ ): string {
48
+ return new TextDecoder().decode(this.readFileSync(path));
49
+ }
50
+
51
+ getMimeType(
52
+ path: string,
53
+ ): string {
54
+ const entry = this.bundle[path];
55
+ if (!entry) {
56
+ throw new Error(`No such file ${path}`);
57
+ }
58
+ return entry.data.split(";")[0].split(":")[1];
59
+ }
60
+
61
+ getMtime(path: string): number {
62
+ const entry = this.bundle[path];
63
+ if (!entry) {
64
+ throw new Error(`No such file ${path}`);
65
+ }
66
+ return entry.mtime;
67
+ }
68
+
69
+ writeFileSync(
70
+ path: string,
71
+ mimeType: string,
72
+ data: Uint8Array,
73
+ mtime: number = Date.now(),
74
+ ) {
75
+ // Replace \ with / for windows
76
+ path = path.replaceAll("\\", "/");
77
+ this.bundle[path] = {
78
+ data: base64EncodedDataUrl(mimeType, data),
79
+ mtime,
80
+ };
81
+ }
82
+
83
+ writeTextFileSync(
84
+ path: string,
85
+ mimeType: string,
86
+ s: string,
87
+ mtime: number = Date.now(),
88
+ ) {
89
+ this.writeFileSync(path, mimeType, new TextEncoder().encode(s), mtime);
90
+ }
91
+
92
+ toJSON(): AssetJson {
93
+ return this.bundle;
94
+ }
95
+ }
@@ -0,0 +1,85 @@
1
+ import {
2
+ type LuaCollectionQuery,
3
+ queryLua,
4
+ } from "../space_lua/query_collection.ts";
5
+ import { LuaEnv, LuaStackFrame } from "../space_lua/runtime.ts";
6
+ import type { KvPrimitives, KvQueryOptions } from "./kv_primitives.ts";
7
+
8
+ import type { KV, KvKey } from "../../plug-api/types/datastore.ts";
9
+
10
+ /**
11
+ * This is the data store class you'll actually want to use, wrapping the primitives
12
+ * in a more user-friendly way
13
+ */
14
+ export class DataStore {
15
+ constructor(
16
+ readonly kv: KvPrimitives,
17
+ ) {
18
+ }
19
+
20
+ async get<T = any>(key: KvKey): Promise<T | null> {
21
+ return (await this.batchGet([key]))[0];
22
+ }
23
+
24
+ batchGet<T = any>(keys: KvKey[]): Promise<(T | null)[]> {
25
+ if (keys.length === 0) {
26
+ return Promise.resolve([]);
27
+ }
28
+ return this.kv.batchGet(keys);
29
+ }
30
+
31
+ set(key: KvKey, value: any): Promise<void> {
32
+ return this.batchSet([{ key, value }]);
33
+ }
34
+
35
+ batchSet<T = any>(entries: KV<T>[]): Promise<void> {
36
+ if (entries.length === 0) {
37
+ return Promise.resolve();
38
+ }
39
+ const allKeyStrings = new Set<string>();
40
+ const uniqueEntries: KV[] = [];
41
+ for (const { key, value } of entries) {
42
+ const keyString = JSON.stringify(key);
43
+ if (allKeyStrings.has(keyString)) {
44
+ console.warn(`Duplicate key ${keyString} in batchSet, skipping`);
45
+ } else {
46
+ allKeyStrings.add(keyString);
47
+ uniqueEntries.push({ key, value });
48
+ }
49
+ }
50
+ return this.kv.batchSet(uniqueEntries);
51
+ }
52
+
53
+ delete(key: KvKey): Promise<void> {
54
+ return this.batchDelete([key]);
55
+ }
56
+
57
+ batchDelete(keys: KvKey[]): Promise<void> {
58
+ if (keys.length === 0) {
59
+ return Promise.resolve();
60
+ }
61
+ return this.kv.batchDelete(keys);
62
+ }
63
+
64
+ async batchDeletePrefix(prefix: KvKey): Promise<void> {
65
+ const keys: KvKey[] = [];
66
+ for await (const { key } of this.kv.query({ prefix })) {
67
+ keys.push(key);
68
+ }
69
+ return this.batchDelete(keys);
70
+ }
71
+
72
+ query<T = any>(options: KvQueryOptions): AsyncIterableIterator<KV<T>> {
73
+ return this.kv.query<T>(options);
74
+ }
75
+
76
+ luaQuery<T = any>(
77
+ prefix: KvKey,
78
+ query: LuaCollectionQuery,
79
+ env: LuaEnv = new LuaEnv(),
80
+ sf: LuaStackFrame = LuaStackFrame.lostFrame,
81
+ enricher?: (key: KvKey, item: any) => any,
82
+ ): Promise<T[]> {
83
+ return queryLua(this.kv, prefix, query, env, sf, enricher);
84
+ }
85
+ }
@@ -0,0 +1,25 @@
1
+ import type { KV, KvKey } from "../../plug-api/types/datastore.ts";
2
+
3
+ export type KvQueryOptions = {
4
+ prefix?: KvKey;
5
+ };
6
+
7
+ export interface KvPrimitives {
8
+ batchGet(keys: KvKey[]): Promise<(any | undefined)[]>;
9
+
10
+ batchSet(entries: KV[]): Promise<void>;
11
+
12
+ batchDelete(keys: KvKey[]): Promise<void>;
13
+
14
+ query<T = any>(options: KvQueryOptions): AsyncIterableIterator<KV<T>>;
15
+
16
+ /**
17
+ * A more efficient way to do counts
18
+ */
19
+ countQuery(options: KvQueryOptions): Promise<number>;
20
+
21
+ // Completely clear all data from this datastore
22
+ clear(): Promise<void>;
23
+
24
+ close(): void;
25
+ }
@@ -0,0 +1,13 @@
1
+ export const wikiLinkRegex =
2
+ /(?<leadingTrivia>!?\[\[)(?<stringRef>.*?)(?:\|(?<alias>.*?))?(?<trailingTrivia>\]\])/g;
3
+ export const mdLinkRegex = /!?\[(?<title>[^\]]*)\]\((?<url>.+)\)/g;
4
+ export const tagRegex =
5
+ /#(?:(?:\d*[^\d\s!@#$%^&*(),.?":{}|<>\\][^\s!@#$%^&*(),.?":{}|<>\\]*)|(?:<[^>\n]+>))/;
6
+ export const nakedUrlRegex =
7
+ /(^https?:\/\/([-a-zA-Z0-9@:%_\+~#=]|(?:[.](?!(\s|$)))){1,256})(([-a-zA-Z0-9(@:%_\+~#?&=\/]|(?:[.,:;)](?!(\s|$))))*)/;
8
+ export const frontmatterQuotesRegex = /["'].*["']/g;
9
+ export const frontmatterUrlRegex = /([a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^\s"']+)/g;
10
+ export const frontmatterWikiLinkRegex =
11
+ /(?<leadingTrivia>!?\[\[)(?<stringRef>.*?)(?:\|(?<alias>.*?))?(?<trailingTrivia>\]\])/g;
12
+ export const frontmatterMailtoRegex = /(mailto:[^@\s]+@[^@\s"']+)/ig;
13
+ export const pWikiLinkRegex = new RegExp("^" + wikiLinkRegex.source); // Modified regex used only in parser
@@ -0,0 +1,36 @@
1
+ /**
2
+ * EventEmitter implementation, similar to the one used in CodeMirror
3
+ */
4
+ export abstract class EventEmitter<HandlerT> {
5
+ private handlers: Partial<HandlerT>[] = [];
6
+
7
+ /**
8
+ * Subscribe to events
9
+ * @param handlers
10
+ */
11
+ on(handlers: Partial<HandlerT>) {
12
+ this.handlers.push(handlers);
13
+ }
14
+
15
+ /**
16
+ * Unsubscribe from events
17
+ * @param handlers
18
+ */
19
+ off(handlers: Partial<HandlerT>) {
20
+ this.handlers = this.handlers.filter((h) => h !== handlers);
21
+ }
22
+
23
+ /**
24
+ * Broadcast an event to all subscribers
25
+ * @param eventName
26
+ * @param args
27
+ */
28
+ async emit(eventName: keyof HandlerT, ...args: any[]): Promise<void> {
29
+ for (const handler of this.handlers) {
30
+ const fn: any = handler[eventName];
31
+ if (fn) {
32
+ await Promise.resolve(fn(...args));
33
+ }
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,8 @@
1
+ import type { EventHookT } from "@silverbulletmd/silverbullet/type/manifest";
2
+ import type { Hook } from "./types.ts";
3
+
4
+ export interface EventHookI extends Hook<EventHookT> {
5
+ dispatchEvent(eventName: string, ...args: unknown[]): Promise<unknown[]>;
6
+
7
+ listEvents(): string[];
8
+ }
@@ -0,0 +1,59 @@
1
+ import type { Hook, Manifest } from "../types.ts";
2
+ import type { System } from "../system.ts";
3
+ import type { CodeWidgetT } from "@silverbulletmd/silverbullet/type/manifest";
4
+ import type { CodeWidgetCallback } from "@silverbulletmd/silverbullet/type/client";
5
+
6
+ export class CodeWidgetHook implements Hook<CodeWidgetT> {
7
+ codeWidgetCallbacks = new Map<string, CodeWidgetCallback>();
8
+ codeWidgetModes = new Map<string, "markdown" | "iframe">();
9
+
10
+ constructor() {
11
+ }
12
+
13
+ collectAllCodeWidgets(system: System<CodeWidgetT>) {
14
+ this.codeWidgetCallbacks.clear();
15
+ for (const plug of system.loadedPlugs.values()) {
16
+ for (
17
+ const [name, functionDef] of Object.entries(
18
+ plug.manifest!.functions,
19
+ )
20
+ ) {
21
+ if (!functionDef.codeWidget) {
22
+ continue;
23
+ }
24
+ this.codeWidgetModes.set(
25
+ functionDef.codeWidget,
26
+ functionDef.renderMode || "iframe",
27
+ );
28
+ this.codeWidgetCallbacks.set(
29
+ functionDef.codeWidget,
30
+ (bodyText, pageName) => {
31
+ return plug.invoke(name, [bodyText, pageName]);
32
+ },
33
+ );
34
+ }
35
+ }
36
+ }
37
+
38
+ apply(system: System<CodeWidgetT>): void {
39
+ this.collectAllCodeWidgets(system);
40
+ system.on({
41
+ plugLoaded: () => {
42
+ this.collectAllCodeWidgets(system);
43
+ },
44
+ });
45
+ }
46
+
47
+ validateManifest(manifest: Manifest<CodeWidgetT>): string[] {
48
+ const errors = [];
49
+ for (const functionDef of Object.values(manifest.functions)) {
50
+ if (!functionDef.codeWidget) {
51
+ continue;
52
+ }
53
+ if (typeof functionDef.codeWidget !== "string") {
54
+ errors.push(`Codewidgets require a string name.`);
55
+ }
56
+ }
57
+ return errors;
58
+ }
59
+ }
@@ -0,0 +1,104 @@
1
+ import type { Hook, Manifest } from "../types.ts";
2
+ import type { System } from "../system.ts";
3
+ import { EventEmitter } from "../event.ts";
4
+ import { throttle } from "@silverbulletmd/silverbullet/lib/async";
5
+ import type { Command, CommandHookEvents } from "../../types/command.ts";
6
+ import type { CommandHookT } from "@silverbulletmd/silverbullet/type/manifest";
7
+
8
+ export class CommandHook extends EventEmitter<CommandHookEvents>
9
+ implements Hook<CommandHookT> {
10
+ system?: System<CommandHookT>;
11
+ public throttledBuildAllCommandsAndEmit = throttle(() => {
12
+ this.buildAllCommandsAndEmit();
13
+ }, 200);
14
+
15
+ constructor(
16
+ private readOnly: boolean,
17
+ private additionalCommands: Map<string, Command>,
18
+ ) {
19
+ super();
20
+ }
21
+
22
+ /**
23
+ * Build the command map
24
+ */
25
+ buildAllCommands(): Map<string, Command> {
26
+ const commands = new Map<string, Command>();
27
+ // Add commands from plugs
28
+ if (!this.system) {
29
+ // Not initialized yet
30
+ return commands;
31
+ }
32
+ for (const plug of this.system.loadedPlugs.values()) {
33
+ for (
34
+ const [name, functionDef] of Object.entries(
35
+ plug.manifest!.functions,
36
+ )
37
+ ) {
38
+ if (!functionDef.command) {
39
+ continue;
40
+ }
41
+ const cmd = functionDef.command;
42
+ if (cmd.requireMode === "rw" && this.readOnly) {
43
+ // Bit hacky, but don't expose commands that require write mode in read-only mode
44
+ continue;
45
+ }
46
+ commands.set(cmd.name, {
47
+ ...cmd,
48
+ run: (args?: string[]) => {
49
+ return plug.invoke(name, [cmd, ...args ?? []]);
50
+ },
51
+ });
52
+ }
53
+ }
54
+ for (const [name, cmd] of this.additionalCommands) {
55
+ if (commands.has(name)) {
56
+ // Existing command, let's do some inline patching
57
+ const existingCommand = commands.get(name)!;
58
+ const command: Command = {
59
+ ...existingCommand,
60
+ ...cmd,
61
+ };
62
+ if (cmd.run) {
63
+ command.run = cmd.run;
64
+ }
65
+ commands.set(name, command);
66
+ } else {
67
+ // New command, let's just set
68
+ commands.set(name, cmd);
69
+ }
70
+ }
71
+ return commands;
72
+ }
73
+
74
+ public buildAllCommandsAndEmit() {
75
+ this.emit("commandsUpdated", this.buildAllCommands());
76
+ }
77
+
78
+ apply(system: System<CommandHookT>): void {
79
+ this.system = system;
80
+ system.on({
81
+ plugLoaded: () => {
82
+ this.throttledBuildAllCommandsAndEmit();
83
+ },
84
+ });
85
+ // On next tick
86
+ setTimeout(() => {
87
+ this.throttledBuildAllCommandsAndEmit();
88
+ });
89
+ }
90
+
91
+ validateManifest(manifest: Manifest<CommandHookT>): string[] {
92
+ const errors = [];
93
+ for (const [name, functionDef] of Object.entries(manifest.functions)) {
94
+ if (!functionDef.command) {
95
+ continue;
96
+ }
97
+ const cmd = functionDef.command;
98
+ if (!cmd.name) {
99
+ errors.push(`Function ${name} has a command but no name`);
100
+ }
101
+ }
102
+ return [];
103
+ }
104
+ }
@@ -0,0 +1,77 @@
1
+ import type { Hook, Manifest } from "../types.ts";
2
+ import type { DocumentEditorT } from "@silverbulletmd/silverbullet/type/manifest";
3
+ import type { System } from "../system.ts";
4
+ import type { DocumentEditorCallback } from "@silverbulletmd/silverbullet/type/client";
5
+
6
+ export class DocumentEditorHook implements Hook<DocumentEditorT> {
7
+ documentEditors = new Map<
8
+ string,
9
+ { extensions: string[]; callback: DocumentEditorCallback }
10
+ >();
11
+
12
+ constructor() {
13
+ }
14
+
15
+ collectAllDocumentEditors(system: System<DocumentEditorT>) {
16
+ this.documentEditors.clear();
17
+ for (const plug of system.loadedPlugs.values()) {
18
+ for (
19
+ const [name, functionDef] of Object.entries(
20
+ plug.manifest!.functions,
21
+ )
22
+ ) {
23
+ if (!functionDef.editor) {
24
+ continue;
25
+ }
26
+
27
+ const keys = Array.isArray(functionDef.editor)
28
+ ? functionDef.editor
29
+ : [functionDef.editor];
30
+
31
+ const conflict = Array.from(this.documentEditors.entries()).find((
32
+ [_, { extensions }],
33
+ ) => keys.some((key) => extensions.includes(key)));
34
+
35
+ if (conflict) {
36
+ console.log(
37
+ `Extension definition of document editor ${name}: [${keys}] conflicts with the one from ${
38
+ conflict[0]
39
+ }: [${conflict[1].extensions}]! Using the latter.`,
40
+ );
41
+ }
42
+
43
+ this.documentEditors.set(
44
+ name,
45
+ { extensions: keys, callback: () => plug.invoke(name, []) },
46
+ );
47
+ }
48
+ }
49
+ }
50
+
51
+ apply(system: System<DocumentEditorT>): void {
52
+ this.collectAllDocumentEditors(system);
53
+ system.on({
54
+ plugLoaded: () => {
55
+ this.collectAllDocumentEditors(system);
56
+ },
57
+ });
58
+ }
59
+
60
+ validateManifest(manifest: Manifest<DocumentEditorT>): string[] {
61
+ const errors = [];
62
+ for (const functionDef of Object.values(manifest.functions)) {
63
+ if (!functionDef.editor) {
64
+ continue;
65
+ }
66
+ if (
67
+ typeof functionDef.editor !== "string" &&
68
+ !Array.isArray(functionDef.editor)
69
+ ) {
70
+ errors.push(
71
+ `Document editors require a string name or an array of string names.`,
72
+ );
73
+ }
74
+ }
75
+ return errors;
76
+ }
77
+ }