Merge branch 'demo'
commit
101d124115
|
@ -69,5 +69,7 @@ npm run server -- <PATH-TO-YOUR-SPACE>
|
||||||
npm run plugs
|
npm run plugs
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Feedback
|
## Feedback
|
||||||
If you (hypothetically) find bugs or have feature requests, post them in [our issue tracker](https://github.com/silverbulletmd/silverbullet/issues). Would you like to contribute? [Check out the code](https://github.com/silverbulletmd/silverbullet), and the issue tracker as well for ideas on what to work on.
|
If you (hypothetically) find bugs or have feature requests, post them in [our issue tracker](https://github.com/silverbulletmd/silverbullet/issues). Would you like to contribute? [Check out the code](https://github.com/silverbulletmd/silverbullet), and the issue tracker as well for ideas on what to work on.
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import type { CommandDef } from "@silverbulletmd/web/hooks/command";
|
||||||
import { syscall } from "./syscall";
|
import { syscall } from "./syscall";
|
||||||
|
|
||||||
export async function invokeFunction(
|
export async function invokeFunction(
|
||||||
|
@ -8,10 +9,16 @@ export async function invokeFunction(
|
||||||
return syscall("system.invokeFunction", env, name, ...args);
|
return syscall("system.invokeFunction", env, name, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only available on the client
|
||||||
export async function invokeCommand(name: string): Promise<any> {
|
export async function invokeCommand(name: string): Promise<any> {
|
||||||
return syscall("system.invokeCommand", name);
|
return syscall("system.invokeCommand", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only available on the client
|
||||||
|
export async function listCommands(): Promise<{ [key: string]: CommandDef }> {
|
||||||
|
return syscall("system.listCommands");
|
||||||
|
}
|
||||||
|
|
||||||
export async function getVersion(): Promise<string> {
|
export async function getVersion(): Promise<string> {
|
||||||
return syscall("system.getVersion");
|
return syscall("system.getVersion");
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { queryPrefix } from "@silverbulletmd/plugos-silverbullet-syscall";
|
||||||
|
import { matchBefore } from "@silverbulletmd/plugos-silverbullet-syscall/editor";
|
||||||
|
import { listCommands } from "@silverbulletmd/plugos-silverbullet-syscall/system";
|
||||||
|
import { applyQuery, QueryProviderEvent } from "../query/engine";
|
||||||
|
|
||||||
|
export async function commandComplete() {
|
||||||
|
let prefix = await matchBefore("\\{\\[[^\\]]*");
|
||||||
|
if (!prefix) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let allCommands = await listCommands();
|
||||||
|
|
||||||
|
return {
|
||||||
|
from: prefix.from + 2,
|
||||||
|
options: Object.keys(allCommands).map((commandName) => ({
|
||||||
|
label: commandName,
|
||||||
|
type: "command",
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
|
@ -74,6 +74,12 @@ functions:
|
||||||
events:
|
events:
|
||||||
- page:complete
|
- page:complete
|
||||||
|
|
||||||
|
# Commands
|
||||||
|
commandComplete:
|
||||||
|
path: "./command.ts:commandComplete"
|
||||||
|
events:
|
||||||
|
- page:complete
|
||||||
|
|
||||||
# Item indexing
|
# Item indexing
|
||||||
indexItem:
|
indexItem:
|
||||||
path: "./item.ts:indexItems"
|
path: "./item.ts:indexItems"
|
||||||
|
@ -162,6 +168,7 @@ functions:
|
||||||
path: "./template.ts:insertTemplateText"
|
path: "./template.ts:insertTemplateText"
|
||||||
slashCommand:
|
slashCommand:
|
||||||
name: meta
|
name: meta
|
||||||
|
description: Insert a page metadata block
|
||||||
value: |
|
value: |
|
||||||
```meta
|
```meta
|
||||||
|^|
|
|^|
|
||||||
|
@ -176,6 +183,7 @@ functions:
|
||||||
path: "./template.ts:insertTemplateText"
|
path: "./template.ts:insertTemplateText"
|
||||||
slashCommand:
|
slashCommand:
|
||||||
name: query
|
name: query
|
||||||
|
description: Insert a query
|
||||||
value: |
|
value: |
|
||||||
<!-- #query |^| -->
|
<!-- #query |^| -->
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,14 @@ import { parseMarkdown } from "@silverbulletmd/plugos-silverbullet-syscall/markd
|
||||||
import { nodeAtPos, ParseTree } from "@silverbulletmd/common/tree";
|
import { nodeAtPos, ParseTree } from "@silverbulletmd/common/tree";
|
||||||
import { invokeCommand } from "@silverbulletmd/plugos-silverbullet-syscall/system";
|
import { invokeCommand } from "@silverbulletmd/plugos-silverbullet-syscall/system";
|
||||||
|
|
||||||
|
// Checks if the URL contains a protocol, if so keeps it, otherwise assumes an attachment
|
||||||
|
function patchUrl(url: string): string {
|
||||||
|
if (url.indexOf("://") === -1) {
|
||||||
|
return `attachment/${url}`;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
async function actionClickOrActionEnter(mdTree: ParseTree | null) {
|
async function actionClickOrActionEnter(mdTree: ParseTree | null) {
|
||||||
if (!mdTree) {
|
if (!mdTree) {
|
||||||
return;
|
return;
|
||||||
|
@ -33,10 +41,10 @@ async function actionClickOrActionEnter(mdTree: ParseTree | null) {
|
||||||
break;
|
break;
|
||||||
case "URL":
|
case "URL":
|
||||||
case "NakedURL":
|
case "NakedURL":
|
||||||
await openUrl(mdTree.children![0].text!);
|
await openUrl(patchUrl(mdTree.children![0].text!));
|
||||||
break;
|
break;
|
||||||
case "Link":
|
case "Link":
|
||||||
const url = mdTree.children![4].children![0].text!;
|
const url = patchUrl(mdTree.children![4].children![0].text!);
|
||||||
if (url.length <= 1) {
|
if (url.length <= 1) {
|
||||||
return flashNotification("Empty link, ignoring", "error");
|
return flashNotification("Empty link, ignoring", "error");
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,13 @@ export async function cleanMarkdown(
|
||||||
text: `__${n.children![0].text}__`,
|
text: `__${n.children![0].text}__`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (n.type === "URL") {
|
||||||
|
const url = n.children![0].text!;
|
||||||
|
if (url.indexOf("://") === -1) {
|
||||||
|
n.children![0].text = `attachment/${url}`;
|
||||||
|
}
|
||||||
|
console.log("Link", url);
|
||||||
|
}
|
||||||
if (n.type === "FencedCode") {
|
if (n.type === "FencedCode") {
|
||||||
let codeInfoNode = findNodeOfType(n, "CodeInfo");
|
let codeInfoNode = findNodeOfType(n, "CodeInfo");
|
||||||
if (!codeInfoNode) {
|
if (!codeInfoNode) {
|
||||||
|
|
|
@ -655,6 +655,20 @@ export class Editor {
|
||||||
contentDOM.setAttribute("autocorrect", "on");
|
contentDOM.setAttribute("autocorrect", "on");
|
||||||
contentDOM.setAttribute("autocapitalize", "on");
|
contentDOM.setAttribute("autocapitalize", "on");
|
||||||
contentDOM.setAttribute("contenteditable", readOnly ? "false" : "true");
|
contentDOM.setAttribute("contenteditable", readOnly ? "false" : "true");
|
||||||
|
|
||||||
|
if (isMobileSafari() && readOnly) {
|
||||||
|
console.log("Safari read only hack");
|
||||||
|
contentDOM.classList.add("ios-safari-readonly");
|
||||||
|
} else {
|
||||||
|
contentDOM.classList.remove("ios-safari-readonly");
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMobileSafari() {
|
||||||
|
return (
|
||||||
|
navigator.userAgent.match(/(iPod|iPhone|iPad)/) &&
|
||||||
|
navigator.userAgent.match(/AppleWebKit/)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private restoreState(pageName: string) {
|
private restoreState(pageName: string) {
|
||||||
|
@ -670,6 +684,10 @@ export class Editor {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
editorView.scrollDOM.scrollTop = 0;
|
editorView.scrollDOM.scrollTop = 0;
|
||||||
|
editorView.dispatch({
|
||||||
|
selection: { anchor: 0 },
|
||||||
|
scrollIntoView: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
editorView.focus();
|
editorView.focus();
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,9 +119,9 @@ export function attachmentExtension(editor: Editor) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await editor.space.writeAttachment(finalFileName, data!);
|
await editor.space.writeAttachment(finalFileName, data!);
|
||||||
let attachmentMarkdown = `[${finalFileName}](attachment/${finalFileName})`;
|
let attachmentMarkdown = `[${finalFileName}](${finalFileName})`;
|
||||||
if (mimeType.startsWith("image/")) {
|
if (mimeType.startsWith("image/")) {
|
||||||
attachmentMarkdown = `![](attachment/${finalFileName})`;
|
attachmentMarkdown = `![](${finalFileName})`;
|
||||||
}
|
}
|
||||||
editor.editorView!.dispatch({
|
editor.editorView!.dispatch({
|
||||||
changes: [
|
changes: [
|
||||||
|
|
|
@ -20,7 +20,11 @@ class InlineImageWidget extends WidgetType {
|
||||||
|
|
||||||
toDOM() {
|
toDOM() {
|
||||||
const img = document.createElement("img");
|
const img = document.createElement("img");
|
||||||
img.src = this.url;
|
if (this.url.startsWith("http")) {
|
||||||
|
img.src = this.url;
|
||||||
|
} else {
|
||||||
|
img.src = `attachment/${this.url}`;
|
||||||
|
}
|
||||||
img.alt = this.title;
|
img.alt = this.title;
|
||||||
img.title = this.title;
|
img.title = this.title;
|
||||||
img.style.display = "block";
|
img.style.display = "block";
|
||||||
|
|
|
@ -21,48 +21,49 @@ function wrapLines(view: EditorView, wrapElements: WrapElement[]) {
|
||||||
const doc = view.state.doc;
|
const doc = view.state.doc;
|
||||||
// Disabling the visible ranges for now, because it may be a bit buggy.
|
// Disabling the visible ranges for now, because it may be a bit buggy.
|
||||||
// RISK: this may actually become slow for large documents.
|
// RISK: this may actually become slow for large documents.
|
||||||
// for (let { from, to } of view.visibleRanges) {
|
for (let { from, to } of view.visibleRanges) {
|
||||||
syntaxTree(view.state).iterate({
|
syntaxTree(view.state).iterate({
|
||||||
// from,
|
from,
|
||||||
// to,
|
to,
|
||||||
enter: ({ type, from, to }) => {
|
enter: ({ type, from, to }) => {
|
||||||
for (let wrapElement of wrapElements) {
|
for (let wrapElement of wrapElements) {
|
||||||
if (type.name == wrapElement.selector) {
|
if (type.name == wrapElement.selector) {
|
||||||
if (wrapElement.nesting) {
|
|
||||||
elementStack.push(type.name);
|
|
||||||
}
|
|
||||||
const bodyText = doc.sliceString(from, to);
|
|
||||||
let idx = from;
|
|
||||||
for (let line of bodyText.split("\n")) {
|
|
||||||
let cls = wrapElement.class;
|
|
||||||
if (wrapElement.nesting) {
|
if (wrapElement.nesting) {
|
||||||
cls = `${cls} ${cls}-${elementStack.length}`;
|
elementStack.push(type.name);
|
||||||
|
}
|
||||||
|
const bodyText = doc.sliceString(from, to);
|
||||||
|
let idx = from;
|
||||||
|
for (let line of bodyText.split("\n")) {
|
||||||
|
let cls = wrapElement.class;
|
||||||
|
if (wrapElement.nesting) {
|
||||||
|
cls = `${cls} ${cls}-${elementStack.length}`;
|
||||||
|
}
|
||||||
|
widgets.push(
|
||||||
|
Decoration.line({
|
||||||
|
class: cls,
|
||||||
|
}).range(doc.lineAt(idx).from)
|
||||||
|
);
|
||||||
|
idx += line.length + 1;
|
||||||
}
|
}
|
||||||
widgets.push(
|
|
||||||
Decoration.line({
|
|
||||||
class: cls,
|
|
||||||
}).range(doc.lineAt(idx).from)
|
|
||||||
);
|
|
||||||
idx += line.length + 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
leave({ type }) {
|
||||||
leave({ type }) {
|
for (let wrapElement of wrapElements) {
|
||||||
for (let wrapElement of wrapElements) {
|
if (type.name == wrapElement.selector && wrapElement.nesting) {
|
||||||
if (type.name == wrapElement.selector && wrapElement.nesting) {
|
elementStack.pop();
|
||||||
elementStack.pop();
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
});
|
||||||
});
|
}
|
||||||
// }
|
|
||||||
// Widgets have to be sorted by `from` in ascending order
|
// Widgets have to be sorted by `from` in ascending order
|
||||||
widgets = widgets.sort((a, b) => {
|
widgets = widgets.sort((a, b) => {
|
||||||
return a.from < b.from ? -1 : 1;
|
return a.from < b.from ? -1 : 1;
|
||||||
});
|
});
|
||||||
return Decoration.set(widgets);
|
return Decoration.set(widgets);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const lineWrapper = (wrapElements: WrapElement[]) =>
|
export const lineWrapper = (wrapElements: WrapElement[]) =>
|
||||||
ViewPlugin.fromClass(
|
ViewPlugin.fromClass(
|
||||||
class {
|
class {
|
||||||
|
|
|
@ -16,6 +16,11 @@
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Weird hack to readjust iOS's safari font-size when contenteditable is disabled
|
||||||
|
.ios-safari-readonly {
|
||||||
|
font-size: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
// Indentation of follow-up lines
|
// Indentation of follow-up lines
|
||||||
@mixin lineOverflow($baseIndent) {
|
@mixin lineOverflow($baseIndent) {
|
||||||
text-indent: -1 * ($baseIndent + 2ch);
|
text-indent: -1 * ($baseIndent + 2ch);
|
||||||
|
|
|
@ -96,6 +96,29 @@
|
||||||
background-color: #d7e1f6 !important;
|
background-color: #d7e1f6 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cm-editor .cm-tooltip-autocomplete {
|
||||||
|
.cm-completionDetail {
|
||||||
|
font-style: normal;
|
||||||
|
display: block;
|
||||||
|
font-size: 80%;
|
||||||
|
margin-left: 5px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
li[aria-selected] .cm-completionDetail {
|
||||||
|
color: #d2d2d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-completionLabel {
|
||||||
|
display: block;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-completionIcon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.sb-line-h1,
|
.sb-line-h1,
|
||||||
.sb-line-h2,
|
.sb-line-h2,
|
||||||
.sb-line-h3 {
|
.sb-line-h3 {
|
||||||
|
@ -273,12 +296,13 @@
|
||||||
background-color: rgba(77, 141, 255, 0.07);
|
background-color: rgba(77, 141, 255, 0.07);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
|
white-space: nowrap;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sb-wiki-link {
|
.sb-wiki-link {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #a8abbd;
|
color: #8f96c2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sb-task-marker {
|
.sb-task-marker {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { SysCallMapping } from "@plugos/plugos/system";
|
import { SysCallMapping } from "@plugos/plugos/system";
|
||||||
import type { Editor } from "../editor";
|
import type { Editor } from "../editor";
|
||||||
|
import { AppCommand, CommandDef } from "../hooks/command";
|
||||||
import { version } from "../package.json";
|
import { version } from "../package.json";
|
||||||
|
|
||||||
export function systemSyscalls(editor: Editor): SysCallMapping {
|
export function systemSyscalls(editor: Editor): SysCallMapping {
|
||||||
|
@ -23,6 +24,15 @@ export function systemSyscalls(editor: Editor): SysCallMapping {
|
||||||
"system.invokeCommand": async (ctx, name: string) => {
|
"system.invokeCommand": async (ctx, name: string) => {
|
||||||
return editor.runCommandByName(name);
|
return editor.runCommandByName(name);
|
||||||
},
|
},
|
||||||
|
"system.listCommands": async (
|
||||||
|
ctx
|
||||||
|
): Promise<{ [key: string]: CommandDef }> => {
|
||||||
|
let allCommands: { [key: string]: CommandDef } = {};
|
||||||
|
for (let [cmd, def] of editor.commandHook.editorCommands) {
|
||||||
|
allCommands[cmd] = def.command;
|
||||||
|
}
|
||||||
|
return allCommands;
|
||||||
|
},
|
||||||
"system.getVersion": async () => {
|
"system.getVersion": async () => {
|
||||||
return version;
|
return version;
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
An attempt at documenting of the changes/new features introduced in each release.
|
An attempt at documenting of the changes/new features introduced in each release.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
## 0.0.34
|
||||||
|
* Change to attachment handling: the `attachment/` prefix for links and images is no longer used, if you already had links to attachments in your notes, you will need to remove the `attachment/` prefix manually. Sorry about that.
|
||||||
|
* Improved styling for completion (especially slash commands)
|
||||||
|
* Completion for commands using the (undocumented) `{[Command Syntax]}` — yep, that exists.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 0.0.33
|
## 0.0.33
|
||||||
|
|
|
@ -3,7 +3,7 @@ Silver Bullet (SB) is highly-extensible, [open source](https://github.com/silver
|
||||||
|
|
||||||
Here is a screenshot:
|
Here is a screenshot:
|
||||||
|
|
||||||
![Silver Bullet PWA screenshot](attachment/silverbullet-pwa.png)
|
![Silver Bullet PWA screenshot](silverbullet-pwa.png)
|
||||||
|
|
||||||
At its core, SB is a Markdown editor that stores _pages_ (notes) as plain markdown files in a folder referred to as a _space_. Pages can be cross-linked using the `[[link to other page]]` syntax. However, once you leverage its various extensions (called _plugs_) it can feel more like a _knowledge platform_, allowing you to annotate, combine and query your accumulated knowledge in creative ways, specific to you. To get a good feel for it, [watch this video](https://youtu.be/RYdc3UF9gok).
|
At its core, SB is a Markdown editor that stores _pages_ (notes) as plain markdown files in a folder referred to as a _space_. Pages can be cross-linked using the `[[link to other page]]` syntax. However, once you leverage its various extensions (called _plugs_) it can feel more like a _knowledge platform_, allowing you to annotate, combine and query your accumulated knowledge in creative ways, specific to you. To get a good feel for it, [watch this video](https://youtu.be/RYdc3UF9gok).
|
||||||
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 316 KiB After Width: | Height: | Size: 144 KiB |
Loading…
Reference in New Issue