Implementation of #117: sharing
parent
5b1ec14891
commit
b61e8ff681
|
@ -30,3 +30,9 @@ export type IndexTreeEvent = {
|
||||||
name: string;
|
name: string;
|
||||||
tree: ParseTree;
|
tree: ParseTree;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type PublishEvent = {
|
||||||
|
uri: string;
|
||||||
|
// Page name
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
|
@ -35,7 +35,8 @@ export function extractFrontmatter(
|
||||||
}
|
}
|
||||||
// Find FrontMatter and parse it
|
// Find FrontMatter and parse it
|
||||||
if (t.type === "FrontMatter") {
|
if (t.type === "FrontMatter") {
|
||||||
const yamlText = renderToText(t.children![1].children![0]);
|
const yamlNode = t.children![1].children![0];
|
||||||
|
const yamlText = renderToText(yamlNode);
|
||||||
try {
|
try {
|
||||||
const parsedData: any = YAML.parse(yamlText);
|
const parsedData: any = YAML.parse(yamlText);
|
||||||
const newData = { ...parsedData };
|
const newData = { ...parsedData };
|
||||||
|
@ -50,7 +51,7 @@ export function extractFrontmatter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (removedOne) {
|
if (removedOne) {
|
||||||
t.children![0].text = YAML.stringify(newData);
|
yamlNode.text = YAML.stringify(newData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If nothing is left, let's just delete this whole block
|
// If nothing is left, let's just delete this whole block
|
||||||
|
|
|
@ -15,6 +15,12 @@ functions:
|
||||||
path: "./collab.ts:shareCommand"
|
path: "./collab.ts:shareCommand"
|
||||||
command:
|
command:
|
||||||
name: "Share: Collab"
|
name: "Share: Collab"
|
||||||
|
shareNoop:
|
||||||
|
path: "./collab.ts:shareNoop"
|
||||||
|
events:
|
||||||
|
- share:collab
|
||||||
|
|
||||||
|
# Space extension
|
||||||
readPageCollab:
|
readPageCollab:
|
||||||
path: ./collab.ts:readFileCollab
|
path: ./collab.ts:readFileCollab
|
||||||
env: client
|
env: client
|
||||||
|
|
|
@ -15,7 +15,6 @@ import {
|
||||||
collab,
|
collab,
|
||||||
editor,
|
editor,
|
||||||
markdown,
|
markdown,
|
||||||
space,
|
|
||||||
} from "$sb/silverbullet-syscall/mod.ts";
|
} from "$sb/silverbullet-syscall/mod.ts";
|
||||||
|
|
||||||
import { nanoid } from "https://esm.sh/nanoid@4.0.0";
|
import { nanoid } from "https://esm.sh/nanoid@4.0.0";
|
||||||
|
@ -122,6 +121,10 @@ export async function detectPage() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function shareNoop() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
export async function readFileCollab(
|
export async function readFileCollab(
|
||||||
name: string,
|
name: string,
|
||||||
encoding: FileEncoding,
|
encoding: FileEncoding,
|
||||||
|
|
|
@ -386,17 +386,18 @@ functions:
|
||||||
path: ./stats.ts:statsCommand
|
path: ./stats.ts:statsCommand
|
||||||
command:
|
command:
|
||||||
name: "Stats: Show"
|
name: "Stats: Show"
|
||||||
key: "Ctrl-s"
|
key: "Shift-Alt-s"
|
||||||
mac: "Cmd-s"
|
|
||||||
|
|
||||||
# Cloud pages
|
# Cloud pages
|
||||||
readPageCloud:
|
readPageCloud:
|
||||||
path: ./cloud.ts:readFileCloud
|
path: ./cloud.ts:readFileCloud
|
||||||
|
env: server
|
||||||
pageNamespace:
|
pageNamespace:
|
||||||
pattern: "💭 .+"
|
pattern: "💭 .+"
|
||||||
operation: readFile
|
operation: readFile
|
||||||
getPageMetaCloud:
|
getPageMetaCloud:
|
||||||
path: ./cloud.ts:getFileMetaCloud
|
path: ./cloud.ts:getFileMetaCloud
|
||||||
|
env: server
|
||||||
pageNamespace:
|
pageNamespace:
|
||||||
pattern: "💭 .+"
|
pattern: "💭 .+"
|
||||||
operation: getFileMeta
|
operation: getFileMeta
|
||||||
|
|
|
@ -4,7 +4,9 @@ import { replaceAsync } from "$sb/lib/util.ts";
|
||||||
import { directiveRegex, renderDirectives } from "./directives.ts";
|
import { directiveRegex, renderDirectives } from "./directives.ts";
|
||||||
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
|
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
|
||||||
|
|
||||||
export async function updateDirectivesOnPageCommand() {
|
export async function updateDirectivesOnPageCommand(arg: any) {
|
||||||
|
// If `arg` is a string, it's triggered automatically via an event, not explicitly via a command
|
||||||
|
const explicitCall = typeof arg !== "string";
|
||||||
const pageName = await editor.getCurrentPage();
|
const pageName = await editor.getCurrentPage();
|
||||||
const text = await editor.getText();
|
const text = await editor.getText();
|
||||||
const tree = await markdown.parseMarkdown(text);
|
const tree = await markdown.parseMarkdown(text);
|
||||||
|
@ -14,6 +16,22 @@ export async function updateDirectivesOnPageCommand() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this page is shared ($share) via collab: disable directives as well
|
||||||
|
// due to security concerns
|
||||||
|
if (metaData.$share) {
|
||||||
|
for (const uri of metaData.$share) {
|
||||||
|
if (uri.startsWith("collab:")) {
|
||||||
|
if (explicitCall) {
|
||||||
|
await editor.flashNotification(
|
||||||
|
"Directives are disabled for 'collab' pages (safety reasons).",
|
||||||
|
"error",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Collect all directives and their body replacements
|
// Collect all directives and their body replacements
|
||||||
const replacements: { fullMatch: string; text?: string }[] = [];
|
const replacements: { fullMatch: string; text?: string }[] = [];
|
||||||
|
|
||||||
|
@ -62,10 +80,15 @@ export async function updateDirectivesOnPageCommand() {
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
const from = index, to = index + replacement.fullMatch.length;
|
||||||
|
if (text.substring(from, to) === replacement.text) {
|
||||||
|
// No change, skip
|
||||||
|
continue;
|
||||||
|
}
|
||||||
await editor.dispatch({
|
await editor.dispatch({
|
||||||
changes: {
|
changes: {
|
||||||
from: index,
|
from,
|
||||||
to: index + replacement.fullMatch.length,
|
to,
|
||||||
insert: replacement.text,
|
insert: replacement.text,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,7 +15,7 @@ export const directiveRegex =
|
||||||
/**
|
/**
|
||||||
* Looks for directives in the text dispatches them based on name
|
* Looks for directives in the text dispatches them based on name
|
||||||
*/
|
*/
|
||||||
export async function directiveDispatcher(
|
export function directiveDispatcher(
|
||||||
pageName: string,
|
pageName: string,
|
||||||
text: string,
|
text: string,
|
||||||
tree: ParseTree,
|
tree: ParseTree,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { markdown, space } from "$sb/silverbullet-syscall/mod.ts";
|
import { markdown, space } from "$sb/silverbullet-syscall/mod.ts";
|
||||||
import { fs } from "$sb/plugos-syscall/mod.ts";
|
import { fs } from "$sb/plugos-syscall/mod.ts";
|
||||||
import { asset } from "$sb/plugos-syscall/mod.ts";
|
import { asset } from "$sb/plugos-syscall/mod.ts";
|
||||||
import type { PublishEvent } from "../share/publish.ts";
|
|
||||||
import { renderMarkdownToHtml } from "./markdown_render.ts";
|
import { renderMarkdownToHtml } from "./markdown_render.ts";
|
||||||
|
import { PublishEvent } from "$sb/app_event.ts";
|
||||||
|
|
||||||
export async function sharePublisher(event: PublishEvent) {
|
export async function sharePublisher(event: PublishEvent) {
|
||||||
const path = event.uri.split(":")[1];
|
const path = event.uri.split(":")[1];
|
||||||
|
|
|
@ -1,26 +1,26 @@
|
||||||
import { events } from "$sb/plugos-syscall/mod.ts";
|
import { events } from "$sb/plugos-syscall/mod.ts";
|
||||||
import { editor, markdown, system } from "$sb/silverbullet-syscall/mod.ts";
|
import { editor, markdown, system } from "$sb/silverbullet-syscall/mod.ts";
|
||||||
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
|
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
|
||||||
|
import { PublishEvent } from "$sb/app_event.ts";
|
||||||
export type PublishEvent = {
|
|
||||||
uri: string;
|
|
||||||
// Page name
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function publishCommand() {
|
export async function publishCommand() {
|
||||||
await editor.save();
|
await editor.save();
|
||||||
const text = await editor.getText();
|
const text = await editor.getText();
|
||||||
const pageName = await editor.getCurrentPage();
|
const pageName = await editor.getCurrentPage();
|
||||||
const tree = await markdown.parseMarkdown(text);
|
const tree = await markdown.parseMarkdown(text);
|
||||||
let { $share } = extractFrontmatter(tree);
|
const { $share } = extractFrontmatter(tree);
|
||||||
if (!$share) {
|
if (!$share) {
|
||||||
await editor.flashNotification("No $share directive found", "error");
|
await editor.flashNotification("Saved.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!Array.isArray($share)) {
|
if (!Array.isArray($share)) {
|
||||||
$share = [$share];
|
await editor.flashNotification(
|
||||||
|
"$share front matter must be an array.",
|
||||||
|
"error",
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
await editor.flashNotification("Sharing...");
|
||||||
// Delegate actual publishing to the server
|
// Delegate actual publishing to the server
|
||||||
try {
|
try {
|
||||||
await system.invokeFunction("server", "publish", pageName, $share);
|
await system.invokeFunction("server", "publish", pageName, $share);
|
||||||
|
|
|
@ -4,6 +4,8 @@ functions:
|
||||||
path: publish.ts:publishCommand
|
path: publish.ts:publishCommand
|
||||||
command:
|
command:
|
||||||
name: "Share: Publish"
|
name: "Share: Publish"
|
||||||
|
key: "Ctrl-s"
|
||||||
|
mac: "Cmd-s"
|
||||||
publish:
|
publish:
|
||||||
path: publish.ts:publish
|
path: publish.ts:publish
|
||||||
env: server
|
env: server
|
||||||
|
|
|
@ -5,8 +5,13 @@ release.
|
||||||
|
|
||||||
## 0.2.1
|
## 0.2.1
|
||||||
|
|
||||||
* New `Plugs: Add` command
|
* New `Plugs: Add` command to quickly add a new plug (will create a `PLUGS` page if you don't have one yet).
|
||||||
* When holding `Shift` while pasting, rich test paste will be disabled.
|
* **Paste without formatting**: holding `Shift` while pasting will disable "rich text paste."
|
||||||
|
* General purpose, extensible, **share support** ([RFC](https://github.com/silverbulletmd/silverbullet/discussions/117)): using the `$share` key in frontmatter (with the {[Share: Publish]} bound to `Cmd-s`/`Ctrl-s`). This will enable plugs to support various types of page sharing. Two types of sharing are supported initially:
|
||||||
|
* `file:/full/path/to/file.html` will render the current page as HTML and write it to given path.
|
||||||
|
* `collab:*` will share the page via the new real-time collaboration feature (see next bullet)
|
||||||
|
* `gh-gist:<gist-id>` via the [[🔌 Github]] plug, which has been updated to add support for publishing to Gists.
|
||||||
|
* An initial version of **real-time collaboration** on individual pages: This will allow concurrent "Google Doc" style editing. To enable this, a central collaboration server is used (there is currently one deployed at `wss://collab.silverbullet.md`) which will become the source of truth for pages shared this way. To share an existing page, run the {[Share: Collab]} command. A random ID will be assigned to the page, which functions as a token for others to join the editing session. Copy and paste the resulting `collab:...` URI and send it your collaborator, who can join using {[Share: Join Collab]}. **Note:** the security of this is to be improved, it currently relies on "security through obscurity": if you guess an existing ID, you will have full access. Be careful what you share this way.
|
||||||
|
|
||||||
## 0.2.0
|
## 0.2.0
|
||||||
* The editor is now in "live preview" mode where a lot of markdown is hidden unless the cursor is present. This will take some getting used to, but results in a much more distraction free look.
|
* The editor is now in "live preview" mode where a lot of markdown is hidden unless the cursor is present. This will take some getting used to, but results in a much more distraction free look.
|
||||||
|
|
|
@ -7,7 +7,10 @@ author: Zef Hemel
|
||||||
|
|
||||||
<!-- #include [[https://raw.githubusercontent.com/silverbulletmd/silverbullet-github/main/README.md]] -->
|
<!-- #include [[https://raw.githubusercontent.com/silverbulletmd/silverbullet-github/main/README.md]] -->
|
||||||
# SilverBullet plug for Github
|
# SilverBullet plug for Github
|
||||||
Provides Github events, notifications and pull requests as query sources using SB's query mechanism
|
Provides various integrations with Github:
|
||||||
|
|
||||||
|
* Query sources for events, notifications and pull requests
|
||||||
|
* Ability to load and share pages as Gists
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
Open your `PLUGS` note in SilverBullet and add this plug to the list:
|
Open your `PLUGS` note in SilverBullet and add this plug to the list:
|
||||||
|
@ -19,8 +22,6 @@ Open your `PLUGS` note in SilverBullet and add this plug to the list:
|
||||||
Then run the `Plugs: Update` command and off you go!
|
Then run the `Plugs: Update` command and off you go!
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
This step is optional for anything but the `gh-notification` source, but without it you may be rate limited by the Github API,
|
|
||||||
|
|
||||||
To configure, add a `githubToken` key to your `SECRETS` page, this should be a [personal access token](https://github.com/settings/tokens):
|
To configure, add a `githubToken` key to your `SECRETS` page, this should be a [personal access token](https://github.com/settings/tokens):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -37,9 +38,14 @@ To configure, add a `githubToken` key to your `SECRETS` page, this should be a [
|
||||||
* `query`: [the search query](https://docs.github.com/en/rest/search#search-issues-and-pull-requests)
|
* `query`: [the search query](https://docs.github.com/en/rest/search#search-issues-and-pull-requests)
|
||||||
* `gh-notification` requires a `githubToken` to be configured in `SECRETS`.
|
* `gh-notification` requires a `githubToken` to be configured in `SECRETS`.
|
||||||
|
|
||||||
|
## Share as Gist support
|
||||||
|
|
||||||
|
To use: navigate to a page, and run the {[Share: Gist: Public Gist]} command, this will perform an initial publish, and add a `$share` attribute to your page's front matter. Subsequent updates can be performed via {[Share: Publish]}.
|
||||||
|
|
||||||
|
To pull an *existing* gist into your space, use the {[Share: Gist: Load]} command and paste the URL to the gist.
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
Example uses:
|
Example uses of the query providers:
|
||||||
|
|
||||||
## Recent pushes
|
## Recent pushes
|
||||||
<!-- #query gh-event where username = "zefhemel" and type = "PushEvent" select type, actor_login, created_at, payload_ref limit 3 -->
|
<!-- #query gh-event where username = "zefhemel" and type = "PushEvent" select type, actor_login, created_at, payload_ref limit 3 -->
|
||||||
|
|
Loading…
Reference in New Issue