Fixes #643
This commit is contained in:
parent
232a0d8df8
commit
8404256ccb
3
.github/workflows/test.yml
vendored
3
.github/workflows/test.yml
vendored
@ -25,5 +25,8 @@ jobs:
|
|||||||
- name: Run build
|
- name: Run build
|
||||||
run: deno task build
|
run: deno task build
|
||||||
|
|
||||||
|
- name: Run type check
|
||||||
|
run: deno task check
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: deno task test --trace-ops
|
run: deno task test --trace-ops
|
||||||
|
24
common/command.test.ts
Normal file
24
common/command.test.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { assertEquals } from "../test_deps.ts";
|
||||||
|
import { parseCommand } from "./command.ts";
|
||||||
|
|
||||||
|
Deno.test("Command parser", () => {
|
||||||
|
assertEquals(parseCommand("Hello world"), { name: "Hello world", args: [] });
|
||||||
|
assertEquals(parseCommand("{[Hello world]}"), {
|
||||||
|
name: "Hello world",
|
||||||
|
args: [],
|
||||||
|
});
|
||||||
|
assertEquals(parseCommand("{[Hello world|sup]}"), {
|
||||||
|
name: "Hello world",
|
||||||
|
alias: "sup",
|
||||||
|
args: [],
|
||||||
|
});
|
||||||
|
assertEquals(parseCommand("{[Hello world](1, 2, 3)}"), {
|
||||||
|
name: "Hello world",
|
||||||
|
args: [1, 2, 3],
|
||||||
|
});
|
||||||
|
assertEquals(parseCommand("{[Hello world|sup](1, 2, 3)}"), {
|
||||||
|
name: "Hello world",
|
||||||
|
alias: "sup",
|
||||||
|
args: [1, 2, 3],
|
||||||
|
});
|
||||||
|
});
|
23
common/command.ts
Normal file
23
common/command.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
export const commandLinkRegex =
|
||||||
|
/^\{\[([^\]\|]+)(\|([^\]]+))?\](\(([^\)]+)\))?\}/;
|
||||||
|
|
||||||
|
export type ParsedCommand = {
|
||||||
|
name: string;
|
||||||
|
args: any[];
|
||||||
|
alias?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function parseCommand(command: string): ParsedCommand {
|
||||||
|
const parsedCommand: ParsedCommand = { name: command, args: [] };
|
||||||
|
const commandMatch = commandLinkRegex.exec(command);
|
||||||
|
if (commandMatch) {
|
||||||
|
parsedCommand.name = commandMatch[1];
|
||||||
|
if (commandMatch[3]) {
|
||||||
|
parsedCommand.alias = commandMatch[3];
|
||||||
|
}
|
||||||
|
parsedCommand.args = commandMatch[5]
|
||||||
|
? JSON.parse(`[${commandMatch[5]}]`)
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
return parsedCommand;
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
import { commandLinkRegex } from "../command.ts";
|
||||||
import {
|
import {
|
||||||
BlockContext,
|
BlockContext,
|
||||||
LeafBlock,
|
LeafBlock,
|
||||||
@ -13,7 +14,6 @@ import {
|
|||||||
yamlLanguage,
|
yamlLanguage,
|
||||||
} from "../deps.ts";
|
} from "../deps.ts";
|
||||||
import * as ct from "./customtags.ts";
|
import * as ct from "./customtags.ts";
|
||||||
import { HashtagTag, TaskDeadlineTag } from "./customtags.ts";
|
|
||||||
import { NakedURLTag } from "./customtags.ts";
|
import { NakedURLTag } from "./customtags.ts";
|
||||||
import { TaskList } from "./extended_task.ts";
|
import { TaskList } from "./extended_task.ts";
|
||||||
|
|
||||||
@ -65,9 +65,6 @@ const WikiLink: MarkdownConfig = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const commandLinkRegex =
|
|
||||||
/^\{\[([^\]\|]+)(\|([^\]]+))?\](\(([^\)]+)\))?\}/;
|
|
||||||
|
|
||||||
const CommandLink: MarkdownConfig = {
|
const CommandLink: MarkdownConfig = {
|
||||||
defineNodes: [
|
defineNodes: [
|
||||||
{ name: "CommandLink", style: { "CommandLink/...": ct.CommandLinkTag } },
|
{ name: "CommandLink", style: { "CommandLink/...": ct.CommandLinkTag } },
|
||||||
|
@ -48,13 +48,35 @@ export function parseYamlSettings(settingsMarkdown: string): {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const defaultSettings: BuiltinSettings = {
|
||||||
|
indexPage: "index",
|
||||||
|
hideSyncButton: false,
|
||||||
|
actionButtons: [
|
||||||
|
{
|
||||||
|
icon: "Home",
|
||||||
|
description: "Go to the index page",
|
||||||
|
command: "Navigate: Home",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "Book",
|
||||||
|
description: `Open page`,
|
||||||
|
command: "Navigate: Page Picker",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "Terminal",
|
||||||
|
description: `Run command`,
|
||||||
|
command: "Open Command Palette",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures that the settings and index page exist in the given space.
|
* Ensures that the settings and index page exist in the given space.
|
||||||
* If they don't exist, default settings and index page will be created.
|
* If they don't exist, default settings and index page will be created.
|
||||||
* @param space - The SpacePrimitives object representing the space.
|
* @param space - The SpacePrimitives object representing the space.
|
||||||
* @returns A promise that resolves to the built-in settings.
|
* @returns A promise that resolves to the built-in settings.
|
||||||
*/
|
*/
|
||||||
export async function ensureSettingsAndIndex(
|
export async function ensureAndLoadSettingsAndIndex(
|
||||||
space: SpacePrimitives,
|
space: SpacePrimitives,
|
||||||
): Promise<BuiltinSettings> {
|
): Promise<BuiltinSettings> {
|
||||||
let settingsText: string | undefined;
|
let settingsText: string | undefined;
|
||||||
@ -73,9 +95,7 @@ export async function ensureSettingsAndIndex(
|
|||||||
} else {
|
} else {
|
||||||
console.error("Error reading settings", e.message);
|
console.error("Error reading settings", e.message);
|
||||||
console.warn("Falling back to default settings");
|
console.warn("Falling back to default settings");
|
||||||
return {
|
return defaultSettings;
|
||||||
indexPage: "index",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
settingsText = SETTINGS_TEMPLATE;
|
settingsText = SETTINGS_TEMPLATE;
|
||||||
// Ok, then let's also check the index page
|
// Ok, then let's also check the index page
|
||||||
@ -95,5 +115,5 @@ export async function ensureSettingsAndIndex(
|
|||||||
|
|
||||||
const settings: any = parseYamlSettings(settingsText);
|
const settings: any = parseYamlSettings(settingsText);
|
||||||
expandPropertyNames(settings);
|
expandPropertyNames(settings);
|
||||||
return settings;
|
return { ...defaultSettings, ...settings };
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"deep-clean-mac": "rm -f deno.lock && rm -rf $HOME/Library/Caches/deno && deno task clean",
|
"deep-clean-mac": "rm -f deno.lock && rm -rf $HOME/Library/Caches/deno && deno task clean",
|
||||||
"install": "deno install -f --unstable -A --importmap import_map.json silverbullet.ts",
|
"install": "deno install -f --unstable -A --importmap import_map.json silverbullet.ts",
|
||||||
"check": "find . -name '*.ts*' | xargs deno check",
|
"check": "find . -name '*.ts*' | xargs deno check",
|
||||||
"test": "deno task check && deno test -A --unstable",
|
"test": "deno test -A --unstable",
|
||||||
"build": "deno run -A build_plugs.ts && deno run -A --unstable build_web.ts",
|
"build": "deno run -A build_plugs.ts && deno run -A --unstable build_web.ts",
|
||||||
"plugs": "deno run -A build_plugs.ts",
|
"plugs": "deno run -A build_plugs.ts",
|
||||||
"server": "deno run -A --unstable --check silverbullet.ts",
|
"server": "deno run -A --unstable --check silverbullet.ts",
|
||||||
|
@ -59,7 +59,7 @@ functions:
|
|||||||
linkNavigate:
|
linkNavigate:
|
||||||
path: "./navigate.ts:linkNavigate"
|
path: "./navigate.ts:linkNavigate"
|
||||||
command:
|
command:
|
||||||
name: Navigate To page
|
name: "Navigate: To This Page"
|
||||||
key: Ctrl-Enter
|
key: Ctrl-Enter
|
||||||
mac: Cmd-Enter
|
mac: Cmd-Enter
|
||||||
clickNavigate:
|
clickNavigate:
|
||||||
@ -76,6 +76,11 @@ functions:
|
|||||||
path: "./editor.ts:moveToPosCommand"
|
path: "./editor.ts:moveToPosCommand"
|
||||||
command:
|
command:
|
||||||
name: "Navigate: Move Cursor to Position"
|
name: "Navigate: Move Cursor to Position"
|
||||||
|
navigateToPage:
|
||||||
|
path: "./navigate.ts:navigateToPage"
|
||||||
|
command:
|
||||||
|
name: "Navigate: To Page"
|
||||||
|
hide: true
|
||||||
|
|
||||||
# Text editing commands
|
# Text editing commands
|
||||||
quoteSelectionCommand:
|
quoteSelectionCommand:
|
||||||
|
@ -132,3 +132,7 @@ export async function clickNavigate(event: ClickEvent) {
|
|||||||
export async function navigateCommand(cmdDef: any) {
|
export async function navigateCommand(cmdDef: any) {
|
||||||
await editor.navigate({ page: cmdDef.page, pos: 0 });
|
await editor.navigate({ page: cmdDef.page, pos: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function navigateToPage(_cmdDef: any, pageName: string) {
|
||||||
|
await editor.navigate({ page: pageName, pos: 0 });
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { SilverBulletHooks } from "../common/manifest.ts";
|
|||||||
import { AssetBundlePlugSpacePrimitives } from "../common/spaces/asset_bundle_space_primitives.ts";
|
import { AssetBundlePlugSpacePrimitives } from "../common/spaces/asset_bundle_space_primitives.ts";
|
||||||
import { FilteredSpacePrimitives } from "../common/spaces/filtered_space_primitives.ts";
|
import { FilteredSpacePrimitives } from "../common/spaces/filtered_space_primitives.ts";
|
||||||
import { SpacePrimitives } from "../common/spaces/space_primitives.ts";
|
import { SpacePrimitives } from "../common/spaces/space_primitives.ts";
|
||||||
import { ensureSettingsAndIndex } from "../common/util.ts";
|
import { ensureAndLoadSettingsAndIndex } from "../common/util.ts";
|
||||||
import { AssetBundle } from "../plugos/asset_bundle/bundle.ts";
|
import { AssetBundle } from "../plugos/asset_bundle/bundle.ts";
|
||||||
import { KvPrimitives } from "../plugos/lib/kv_primitives.ts";
|
import { KvPrimitives } from "../plugos/lib/kv_primitives.ts";
|
||||||
import { System } from "../plugos/system.ts";
|
import { System } from "../plugos/system.ts";
|
||||||
@ -113,10 +113,11 @@ export class SpaceServer {
|
|||||||
async reloadSettings() {
|
async reloadSettings() {
|
||||||
if (!this.clientEncryption) {
|
if (!this.clientEncryption) {
|
||||||
// Only attempt this when the space is not encrypted
|
// Only attempt this when the space is not encrypted
|
||||||
this.settings = await ensureSettingsAndIndex(this.spacePrimitives);
|
this.settings = await ensureAndLoadSettingsAndIndex(this.spacePrimitives);
|
||||||
} else {
|
} else {
|
||||||
this.settings = {
|
this.settings = {
|
||||||
indexPage: "index",
|
indexPage: "index",
|
||||||
|
actionButtons: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
} from "../common/deps.ts";
|
} from "../common/deps.ts";
|
||||||
import { Space } from "./space.ts";
|
import { Space } from "./space.ts";
|
||||||
import { FilterOption } from "./types.ts";
|
import { FilterOption } from "./types.ts";
|
||||||
import { ensureSettingsAndIndex } from "../common/util.ts";
|
import { ensureAndLoadSettingsAndIndex } from "../common/util.ts";
|
||||||
import { EventHook } from "../plugos/hooks/event.ts";
|
import { EventHook } from "../plugos/hooks/event.ts";
|
||||||
import { AppCommand } from "./hooks/command.ts";
|
import { AppCommand } from "./hooks/command.ts";
|
||||||
import {
|
import {
|
||||||
@ -242,7 +242,13 @@ export class Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async loadSettings() {
|
async loadSettings() {
|
||||||
this.settings = await ensureSettingsAndIndex(this.space.spacePrimitives);
|
this.settings = await ensureAndLoadSettingsAndIndex(
|
||||||
|
this.space.spacePrimitives,
|
||||||
|
);
|
||||||
|
this.ui.viewDispatch({
|
||||||
|
type: "settings-loaded",
|
||||||
|
settings: this.settings,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initSync() {
|
private async initSync() {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { commandLinkRegex } from "../../common/markdown_parser/parser.ts";
|
|
||||||
import { ClickEvent } from "$sb/app_event.ts";
|
import { ClickEvent } from "$sb/app_event.ts";
|
||||||
import { Decoration, syntaxTree } from "../deps.ts";
|
import { Decoration, syntaxTree } from "../deps.ts";
|
||||||
import { Client } from "../client.ts";
|
import { Client } from "../client.ts";
|
||||||
@ -8,6 +7,7 @@ import {
|
|||||||
invisibleDecoration,
|
invisibleDecoration,
|
||||||
isCursorInRange,
|
isCursorInRange,
|
||||||
} from "./util.ts";
|
} from "./util.ts";
|
||||||
|
import { commandLinkRegex } from "../../common/command.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin to hide path prefix when the cursor is not inside.
|
* Plugin to hide path prefix when the cursor is not inside.
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { isMacLike } from "../../common/util.ts";
|
import { isMacLike } from "../../common/util.ts";
|
||||||
import { FilterList } from "./filter.tsx";
|
import { FilterList } from "./filter.tsx";
|
||||||
import { CompletionContext, CompletionResult, TerminalIcon } from "../deps.ts";
|
import { CompletionContext, CompletionResult, featherIcons } from "../deps.ts";
|
||||||
import { AppCommand } from "../hooks/command.ts";
|
import { AppCommand } from "../hooks/command.ts";
|
||||||
import { BuiltinSettings, FilterOption } from "../types.ts";
|
import { BuiltinSettings, FilterOption } from "../types.ts";
|
||||||
import { commandLinkRegex } from "../../common/markdown_parser/parser.ts";
|
import { parseCommand } from "../../common/command.ts";
|
||||||
|
|
||||||
export function CommandPalette({
|
export function CommandPalette({
|
||||||
commands,
|
commands,
|
||||||
@ -25,6 +25,9 @@ export function CommandPalette({
|
|||||||
const options: FilterOption[] = [];
|
const options: FilterOption[] = [];
|
||||||
const isMac = isMacLike();
|
const isMac = isMacLike();
|
||||||
for (const [name, def] of commands.entries()) {
|
for (const [name, def] of commands.entries()) {
|
||||||
|
if (def.command.hide) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let shortcut: { key?: string; mac?: string; priority?: number } =
|
let shortcut: { key?: string; mac?: string; priority?: number } =
|
||||||
def.command;
|
def.command;
|
||||||
// Let's see if there's a shortcut override
|
// Let's see if there's a shortcut override
|
||||||
@ -32,11 +35,9 @@ export function CommandPalette({
|
|||||||
const commandOverride = settings.shortcuts.find((
|
const commandOverride = settings.shortcuts.find((
|
||||||
shortcut,
|
shortcut,
|
||||||
) => {
|
) => {
|
||||||
const commandMatch = commandLinkRegex.exec(shortcut.command);
|
const parsedCommand = parseCommand(shortcut.command);
|
||||||
// If this is a command link, we want to match the command name but also make sure no arguments were set
|
// If this is a command link, we want to match the command name but also make sure no arguments were set
|
||||||
return commandMatch && commandMatch[1] === name && !commandMatch[5] ||
|
return parsedCommand.name === name && parsedCommand.args.length === 0;
|
||||||
// or if it's not a command link, let's match exactly
|
|
||||||
shortcut.command === name;
|
|
||||||
});
|
});
|
||||||
if (commandOverride) {
|
if (commandOverride) {
|
||||||
shortcut = commandOverride;
|
shortcut = commandOverride;
|
||||||
@ -58,7 +59,7 @@ export function CommandPalette({
|
|||||||
placeholder="Command"
|
placeholder="Command"
|
||||||
options={options}
|
options={options}
|
||||||
allowNew={false}
|
allowNew={false}
|
||||||
icon={TerminalIcon}
|
icon={featherIcons.Terminal}
|
||||||
completer={completer}
|
completer={completer}
|
||||||
vimMode={vimMode}
|
vimMode={vimMode}
|
||||||
darkMode={darkMode}
|
darkMode={darkMode}
|
||||||
|
@ -1,9 +1,4 @@
|
|||||||
import {
|
import { CompletionContext, CompletionResult, useEffect } from "../deps.ts";
|
||||||
CompletionContext,
|
|
||||||
CompletionResult,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
} from "../deps.ts";
|
|
||||||
import type { ComponentChildren, FunctionalComponent } from "../deps.ts";
|
import type { ComponentChildren, FunctionalComponent } from "../deps.ts";
|
||||||
import { Notification } from "../types.ts";
|
import { Notification } from "../types.ts";
|
||||||
import { FeatherProps } from "https://esm.sh/v99/preact-feather@4.2.1/dist/types";
|
import { FeatherProps } from "https://esm.sh/v99/preact-feather@4.2.1/dist/types";
|
||||||
@ -65,8 +60,9 @@ export function TopBar({
|
|||||||
const innerDiv = currentPageElement.parentElement!.parentElement!;
|
const innerDiv = currentPageElement.parentElement!.parentElement!;
|
||||||
|
|
||||||
// Then calculate a new width
|
// Then calculate a new width
|
||||||
|
const substract = 60 + actionButtons.length * 31;
|
||||||
currentPageElement.style.width = `${
|
currentPageElement.style.width = `${
|
||||||
Math.min(editorWidth - 170, innerDiv.clientWidth - 170)
|
Math.min(editorWidth - substract, innerDiv.clientWidth - substract)
|
||||||
}px`;
|
}px`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,13 +9,7 @@ export {
|
|||||||
useState,
|
useState,
|
||||||
} from "https://esm.sh/preact@10.11.1/hooks";
|
} from "https://esm.sh/preact@10.11.1/hooks";
|
||||||
|
|
||||||
export {
|
export * as featherIcons from "https://esm.sh/preact-feather@4.2.1?external=preact";
|
||||||
Book as BookIcon,
|
|
||||||
Home as HomeIcon,
|
|
||||||
RefreshCw as RefreshCwIcon,
|
|
||||||
Terminal as TerminalIcon,
|
|
||||||
Type as TemplateIcon,
|
|
||||||
} from "https://esm.sh/preact-feather@4.2.1?external=preact";
|
|
||||||
|
|
||||||
// Vim mode
|
// Vim mode
|
||||||
export {
|
export {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { commandLinkRegex } from "../common/markdown_parser/parser.ts";
|
|
||||||
import { readonlyMode } from "./cm_plugins/readonly.ts";
|
import { readonlyMode } from "./cm_plugins/readonly.ts";
|
||||||
import customMarkdownStyle from "./style.ts";
|
import customMarkdownStyle from "./style.ts";
|
||||||
import {
|
import {
|
||||||
@ -45,6 +44,7 @@ import { languageFor } from "../common/languages.ts";
|
|||||||
import { plugLinter } from "./cm_plugins/lint.ts";
|
import { plugLinter } from "./cm_plugins/lint.ts";
|
||||||
import { Compartment, Extension } from "@codemirror/state";
|
import { Compartment, Extension } from "@codemirror/state";
|
||||||
import { extendedMarkdownLanguage } from "../common/markdown_parser/parser.ts";
|
import { extendedMarkdownLanguage } from "../common/markdown_parser/parser.ts";
|
||||||
|
import { parseCommand } from "../common/command.ts";
|
||||||
|
|
||||||
export function createEditorState(
|
export function createEditorState(
|
||||||
client: Client,
|
client: Client,
|
||||||
@ -270,28 +270,24 @@ export function createCommandKeyBindings(client: Client): KeyBinding[] {
|
|||||||
if (client.settings?.shortcuts) {
|
if (client.settings?.shortcuts) {
|
||||||
for (const shortcut of client.settings.shortcuts) {
|
for (const shortcut of client.settings.shortcuts) {
|
||||||
// Figure out if we're using the command link syntax here, if so: parse it out
|
// Figure out if we're using the command link syntax here, if so: parse it out
|
||||||
const commandMatch = commandLinkRegex.exec(shortcut.command);
|
const parsedCommand = parseCommand(shortcut.command);
|
||||||
let cleanCommandName = shortcut.command;
|
if (parsedCommand.args.length === 0) {
|
||||||
let args: any[] = [];
|
|
||||||
if (commandMatch) {
|
|
||||||
cleanCommandName = commandMatch[1];
|
|
||||||
args = commandMatch[5] ? JSON.parse(`[${commandMatch[5]}]`) : [];
|
|
||||||
}
|
|
||||||
if (args.length === 0) {
|
|
||||||
// If there was no "specialization" of this command (that is, we effectively created a keybinding for an existing command but with arguments), let's add it to the overridden command set:
|
// If there was no "specialization" of this command (that is, we effectively created a keybinding for an existing command but with arguments), let's add it to the overridden command set:
|
||||||
overriddenCommands.add(cleanCommandName);
|
overriddenCommands.add(parsedCommand.name);
|
||||||
}
|
}
|
||||||
commandKeyBindings.push({
|
commandKeyBindings.push({
|
||||||
key: shortcut.key,
|
key: shortcut.key,
|
||||||
mac: shortcut.mac,
|
mac: shortcut.mac,
|
||||||
run: (): boolean => {
|
run: (): boolean => {
|
||||||
client.runCommandByName(cleanCommandName, args).catch((e: any) => {
|
client.runCommandByName(parsedCommand.name, parsedCommand.args).catch(
|
||||||
|
(e: any) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
client.flashNotification(
|
client.flashNotification(
|
||||||
`Error running command: ${e.message}`,
|
`Error running command: ${e.message}`,
|
||||||
"error",
|
"error",
|
||||||
);
|
);
|
||||||
}).then(() => {
|
},
|
||||||
|
).then(() => {
|
||||||
// Always be focusing the editor after running a command
|
// Always be focusing the editor after running a command
|
||||||
client.focus();
|
client.focus();
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { isMacLike, safeRun } from "../common/util.ts";
|
import { safeRun } from "../common/util.ts";
|
||||||
import { Confirm, Prompt } from "./components/basic_modals.tsx";
|
import { Confirm, Prompt } from "./components/basic_modals.tsx";
|
||||||
import { CommandPalette } from "./components/command_palette.tsx";
|
import { CommandPalette } from "./components/command_palette.tsx";
|
||||||
import { FilterList } from "./components/filter.tsx";
|
import { FilterList } from "./components/filter.tsx";
|
||||||
@ -7,12 +7,9 @@ import { TopBar } from "./components/top_bar.tsx";
|
|||||||
import reducer from "./reducer.ts";
|
import reducer from "./reducer.ts";
|
||||||
import { Action, AppViewState, initialViewState } from "./types.ts";
|
import { Action, AppViewState, initialViewState } from "./types.ts";
|
||||||
import {
|
import {
|
||||||
BookIcon,
|
featherIcons,
|
||||||
HomeIcon,
|
|
||||||
preactRender,
|
preactRender,
|
||||||
RefreshCwIcon,
|
|
||||||
runScopeHandlers,
|
runScopeHandlers,
|
||||||
TerminalIcon,
|
|
||||||
useEffect,
|
useEffect,
|
||||||
useReducer,
|
useReducer,
|
||||||
} from "./deps.ts";
|
} from "./deps.ts";
|
||||||
@ -20,6 +17,7 @@ import type { Client } from "./client.ts";
|
|||||||
import { Panel } from "./components/panel.tsx";
|
import { Panel } from "./components/panel.tsx";
|
||||||
import { h } from "./deps.ts";
|
import { h } from "./deps.ts";
|
||||||
import { sleep } from "$sb/lib/async.ts";
|
import { sleep } from "$sb/lib/async.ts";
|
||||||
|
import { parseCommand } from "../common/command.ts";
|
||||||
|
|
||||||
export class MainUI {
|
export class MainUI {
|
||||||
viewState: AppViewState = initialViewState;
|
viewState: AppViewState = initialViewState;
|
||||||
@ -210,10 +208,11 @@ export class MainUI {
|
|||||||
client.focus();
|
client.focus();
|
||||||
}}
|
}}
|
||||||
actionButtons={[
|
actionButtons={[
|
||||||
...!window.silverBulletConfig.syncOnly
|
...(!window.silverBulletConfig.syncOnly &&
|
||||||
|
!viewState.settings.hideSyncButton)
|
||||||
// If we support syncOnly, don't show this toggle button
|
// If we support syncOnly, don't show this toggle button
|
||||||
? [{
|
? [{
|
||||||
icon: RefreshCwIcon,
|
icon: featherIcons.RefreshCw,
|
||||||
description: this.client.syncMode
|
description: this.client.syncMode
|
||||||
? "Currently in Sync mode, click to switch to Online mode"
|
? "Currently in Sync mode, click to switch to Online mode"
|
||||||
: "Currently in Online mode, click to switch to Sync mode",
|
: "Currently in Online mode, click to switch to Sync mode",
|
||||||
@ -241,33 +240,20 @@ export class MainUI {
|
|||||||
},
|
},
|
||||||
}]
|
}]
|
||||||
: [],
|
: [],
|
||||||
{
|
...viewState.settings.actionButtons.map((button) => {
|
||||||
icon: HomeIcon,
|
const parsedCommand = parseCommand(button.command);
|
||||||
description: `Go to the index page (Alt-h)`,
|
return {
|
||||||
|
icon: (featherIcons as any)[button.icon],
|
||||||
|
description: button.description || "",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
client.navigate({ page: "", pos: 0 });
|
client.runCommandByName(
|
||||||
// And let's make sure all panels are closed
|
parsedCommand.name,
|
||||||
dispatch({ type: "hide-filterbox" });
|
parsedCommand.args,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
href: "",
|
href: "",
|
||||||
},
|
};
|
||||||
{
|
}),
|
||||||
icon: BookIcon,
|
|
||||||
description: `Open page (${isMacLike() ? "Cmd-k" : "Ctrl-k"})`,
|
|
||||||
callback: () => {
|
|
||||||
client.startPageNavigate("page");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: TerminalIcon,
|
|
||||||
description: `Run command (${isMacLike() ? "Cmd-/" : "Ctrl-/"})`,
|
|
||||||
callback: () => {
|
|
||||||
dispatch({
|
|
||||||
type: "show-palette",
|
|
||||||
context: client.getContext(),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}
|
]}
|
||||||
rhs={!!viewState.panels.rhs.mode && (
|
rhs={!!viewState.panels.rhs.mode && (
|
||||||
<div
|
<div
|
||||||
|
@ -20,6 +20,8 @@ export type CommandDef = {
|
|||||||
// Bind to keyboard shortcut
|
// Bind to keyboard shortcut
|
||||||
key?: string;
|
key?: string;
|
||||||
mac?: string;
|
mac?: string;
|
||||||
|
|
||||||
|
hide?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AppCommand = {
|
export type AppCommand = {
|
||||||
|
@ -45,6 +45,11 @@ export default function reducer(
|
|||||||
...state,
|
...state,
|
||||||
syncFailures: action.syncSuccess ? 0 : state.syncFailures + 1,
|
syncFailures: action.syncSuccess ? 0 : state.syncFailures + 1,
|
||||||
};
|
};
|
||||||
|
case "settings-loaded":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
settings: action.settings,
|
||||||
|
};
|
||||||
case "update-page-list": {
|
case "update-page-list": {
|
||||||
// Let's move over any "lastOpened" times to the "allPages" list
|
// Let's move over any "lastOpened" times to the "allPages" list
|
||||||
const oldPageMeta = new Map(
|
const oldPageMeta = new Map(
|
||||||
|
17
web/types.ts
17
web/types.ts
@ -1,6 +1,7 @@
|
|||||||
import { Manifest } from "../common/manifest.ts";
|
import { Manifest } from "../common/manifest.ts";
|
||||||
import { PageMeta } from "$sb/types.ts";
|
import { PageMeta } from "$sb/types.ts";
|
||||||
import { AppCommand } from "./hooks/command.ts";
|
import { AppCommand } from "./hooks/command.ts";
|
||||||
|
import { defaultSettings } from "../common/util.ts";
|
||||||
|
|
||||||
// Used by FilterBox
|
// Used by FilterBox
|
||||||
export type FilterOption = {
|
export type FilterOption = {
|
||||||
@ -26,11 +27,20 @@ export type Shortcut = {
|
|||||||
command: string;
|
command: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ActionButton = {
|
||||||
|
icon: string;
|
||||||
|
description?: string;
|
||||||
|
command: string;
|
||||||
|
args?: any[];
|
||||||
|
};
|
||||||
|
|
||||||
export type BuiltinSettings = {
|
export type BuiltinSettings = {
|
||||||
indexPage: string;
|
indexPage: string;
|
||||||
customStyles?: string | string[];
|
customStyles?: string | string[];
|
||||||
plugOverrides?: Record<string, Partial<Manifest>>;
|
plugOverrides?: Record<string, Partial<Manifest>>;
|
||||||
shortcuts?: Shortcut[];
|
shortcuts?: Shortcut[];
|
||||||
|
hideSyncButton?: boolean;
|
||||||
|
actionButtons: ActionButton[];
|
||||||
// Format: compatible with docker ignore
|
// Format: compatible with docker ignore
|
||||||
spaceIgnore?: string;
|
spaceIgnore?: string;
|
||||||
};
|
};
|
||||||
@ -44,6 +54,8 @@ export type PanelConfig = {
|
|||||||
export type AppViewState = {
|
export type AppViewState = {
|
||||||
currentPage?: string;
|
currentPage?: string;
|
||||||
currentPageMeta?: PageMeta;
|
currentPageMeta?: PageMeta;
|
||||||
|
allPages: PageMeta[];
|
||||||
|
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
showPageNavigator: boolean;
|
showPageNavigator: boolean;
|
||||||
showCommandPalette: boolean;
|
showCommandPalette: boolean;
|
||||||
@ -52,11 +64,12 @@ export type AppViewState = {
|
|||||||
syncFailures: number; // Reset everytime a sync succeeds
|
syncFailures: number; // Reset everytime a sync succeeds
|
||||||
progressPerc?: number;
|
progressPerc?: number;
|
||||||
panels: { [key: string]: PanelConfig };
|
panels: { [key: string]: PanelConfig };
|
||||||
allPages: PageMeta[];
|
|
||||||
commands: Map<string, AppCommand>;
|
commands: Map<string, AppCommand>;
|
||||||
notifications: Notification[];
|
notifications: Notification[];
|
||||||
recentCommands: Map<string, Date>;
|
recentCommands: Map<string, Date>;
|
||||||
|
|
||||||
|
settings: BuiltinSettings;
|
||||||
|
|
||||||
uiOptions: {
|
uiOptions: {
|
||||||
vimMode: boolean;
|
vimMode: boolean;
|
||||||
darkMode: boolean;
|
darkMode: boolean;
|
||||||
@ -104,6 +117,7 @@ export const initialViewState: AppViewState = {
|
|||||||
bhs: {},
|
bhs: {},
|
||||||
modal: {},
|
modal: {},
|
||||||
},
|
},
|
||||||
|
settings: defaultSettings,
|
||||||
allPages: [],
|
allPages: [],
|
||||||
commands: new Map(),
|
commands: new Map(),
|
||||||
recentCommands: new Map(),
|
recentCommands: new Map(),
|
||||||
@ -126,6 +140,7 @@ export type Action =
|
|||||||
| { type: "page-saved" }
|
| { type: "page-saved" }
|
||||||
| { type: "sync-change"; syncSuccess: boolean }
|
| { type: "sync-change"; syncSuccess: boolean }
|
||||||
| { type: "update-page-list"; allPages: PageMeta[] }
|
| { type: "update-page-list"; allPages: PageMeta[] }
|
||||||
|
| { type: "settings-loaded"; settings: BuiltinSettings }
|
||||||
| { type: "start-navigate"; mode: "page" | "template" }
|
| { type: "start-navigate"; mode: "page" | "template" }
|
||||||
| { type: "stop-navigate" }
|
| { type: "stop-navigate" }
|
||||||
| {
|
| {
|
||||||
|
@ -7,6 +7,7 @@ release.
|
|||||||
_The changes below are not yet released “properly”. To them out early, check out [the docs on edge](https://community.silverbullet.md/t/living-on-the-edge-builds/27)._
|
_The changes below are not yet released “properly”. To them out early, check out [the docs on edge](https://community.silverbullet.md/t/living-on-the-edge-builds/27)._
|
||||||
|
|
||||||
* Tag pages: when you click on a #tag you will now be directed to a page that shows all pages, tasks, items and paragraphs tagged with that tag.
|
* Tag pages: when you click on a #tag you will now be directed to a page that shows all pages, tasks, items and paragraphs tagged with that tag.
|
||||||
|
* Action buttons (top right buttons) can now be configured, see [[SETTINGS]] how to do this.
|
||||||
* Bug fixes:
|
* Bug fixes:
|
||||||
* Improved Ctrl/Cmd-click (to open links in a new window) behavior: now actually follow `@pos` and `$anchor` links.
|
* Improved Ctrl/Cmd-click (to open links in a new window) behavior: now actually follow `@pos` and `$anchor` links.
|
||||||
* Right-clicking links now opens browser native context menu again
|
* Right-clicking links now opens browser native context menu again
|
||||||
|
@ -17,7 +17,7 @@ The `editor` plug implements foundational editor functionality for SilverBullet.
|
|||||||
|
|
||||||
## Navigation
|
## Navigation
|
||||||
* {[Navigate: Home]}: navigate to the home (index) page
|
* {[Navigate: Home]}: navigate to the home (index) page
|
||||||
* {[Navigate To page]}: navigate to the page under the cursor
|
* {[Navigate: To This Page]}: navigate to the page under the cursor
|
||||||
* {[Navigate: Center Cursor]}: center the cursor at the center of the screen
|
* {[Navigate: Center Cursor]}: center the cursor at the center of the screen
|
||||||
* {[Navigate: Move Cursor to Position]}: move cursor to a specific (numeric) cursor position (# of characters from the start of the document)
|
* {[Navigate: Move Cursor to Position]}: move cursor to a specific (numeric) cursor position (# of characters from the start of the document)
|
||||||
|
|
||||||
|
@ -7,7 +7,25 @@ indexPage: "[[SilverBullet]]"
|
|||||||
# Load custom CSS styles from the following page, can also be an array
|
# Load custom CSS styles from the following page, can also be an array
|
||||||
customStyles: "[[STYLES]]"
|
customStyles: "[[STYLES]]"
|
||||||
|
|
||||||
# It is possible to override keyboard shortcuts and command priority
|
# Hide the sync button
|
||||||
|
hideSyncButton: false
|
||||||
|
|
||||||
|
# Configure the shown action buttons (top right bar)
|
||||||
|
actionButtons:
|
||||||
|
- icon: Home # Capitalized version of an icon from https://feathericons.com
|
||||||
|
command: "{[Navigate: Home]}"
|
||||||
|
description: "Go to the index page"
|
||||||
|
- icon: Activity
|
||||||
|
description: "What's new"
|
||||||
|
command: '{[Navigate: To Page]("CHANGELOG")}'
|
||||||
|
- icon: Book
|
||||||
|
command: "{[Navigate: Page Picker]}"
|
||||||
|
description: Open page
|
||||||
|
- icon: Terminal
|
||||||
|
command: "{[Open Command Palette]}"
|
||||||
|
description: Run command
|
||||||
|
|
||||||
|
# Override keyboard shortcuts and command priority
|
||||||
shortcuts:
|
shortcuts:
|
||||||
- command: "{[Stats: Show]}" # Using the command link syntax here
|
- command: "{[Stats: Show]}" # Using the command link syntax here
|
||||||
mac: "Cmd-s" # Mac-specific keyboard shortcut
|
mac: "Cmd-s" # Mac-specific keyboard shortcut
|
||||||
|
Loading…
Reference in New Issue
Block a user