Preparing for an initial release
This commit is contained in:
parent
be92bf2311
commit
16fcd91d6c
8
.idea/.gitignore
generated
vendored
8
.idea/.gitignore
generated
vendored
@ -1,8 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
48
.idea/codeStyles/Project.xml
generated
48
.idea/codeStyles/Project.xml
generated
@ -1,48 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<HTMLCodeStyleSettings>
|
||||
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||
<option name="HTML_ENFORCE_QUOTES" value="true" />
|
||||
</HTMLCodeStyleSettings>
|
||||
<JSCodeStyleSettings version="0">
|
||||
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||
</JSCodeStyleSettings>
|
||||
<TypeScriptCodeStyleSettings version="0">
|
||||
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||
</TypeScriptCodeStyleSettings>
|
||||
<codeStyleSettings language="HTML">
|
||||
<option name="SOFT_MARGINS" value="80" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="JavaScript">
|
||||
<option name="SOFT_MARGINS" value="80" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="TypeScript">
|
||||
<option name="SOFT_MARGINS" value="80" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
5
.idea/codeStyles/codeStyleConfig.xml
generated
5
.idea/codeStyles/codeStyleConfig.xml
generated
@ -1,5 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
17
.idea/dataSources.xml
generated
17
.idea/dataSources.xml
generated
@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="data" uuid="b34630b0-d7b1-473e-9067-ffd03fa15451">
|
||||
<driver-ref>sqlite.xerial</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/pages/data.db</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
<libraries>
|
||||
<library>
|
||||
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.36.0.3/sqlite-jdbc-3.36.0.3.jar</url>
|
||||
</library>
|
||||
</libraries>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
8
.idea/misc.xml
generated
8
.idea/misc.xml
generated
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="SwUserDefinedSpecifications">
|
||||
<option name="specTypeByUrl">
|
||||
<map />
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
8
.idea/modules.xml
generated
8
.idea/modules.xml
generated
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/silverbullet.iml" filepath="$PROJECT_DIR$/.idea/silverbullet.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
6
.idea/prettier.xml
generated
6
.idea/prettier.xml
generated
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="PrettierConfiguration">
|
||||
<option name="myRunOnSave" value="true" />
|
||||
</component>
|
||||
</project>
|
11
.idea/silverbullet.iml
generated
11
.idea/silverbullet.iml
generated
@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/dist" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
1
.vscode/configurationCache.log
vendored
Normal file
1
.vscode/configurationCache.log
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"buildTargets":["release"],"launchTargets":[],"customConfigurationProvider":{"workspaceBrowse":{"browsePath":[],"compilerArgs":[]},"fileIndex":[]}}
|
6
.vscode/dryrun.log
vendored
Normal file
6
.vscode/dryrun.log
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
make --dry-run --always-make --keep-going --print-directory
|
||||
make: Entering directory `/Users/zef/git/silverbullet'
|
||||
make: `release' is up to date.
|
||||
|
||||
make: Leaving directory `/Users/zef/git/silverbullet'
|
||||
|
241
.vscode/targets.log
vendored
Normal file
241
.vscode/targets.log
vendored
Normal file
@ -0,0 +1,241 @@
|
||||
make all --print-data-base --no-builtin-variables --no-builtin-rules --question
|
||||
# GNU Make 3.81
|
||||
# Copyright (C) 2006 Free Software Foundation, Inc.
|
||||
# This is free software; see the source for copying conditions.
|
||||
# There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE.
|
||||
|
||||
# This program built for i386-apple-darwin11.3.0
|
||||
|
||||
make: *** No rule to make target `all'. Stop.
|
||||
|
||||
|
||||
# Make data base, printed on Tue Jun 28 12:27:45 2022
|
||||
|
||||
# Variables
|
||||
|
||||
# automatic
|
||||
<D = $(patsubst %/,%,$(dir $<))
|
||||
# automatic
|
||||
?F = $(notdir $?)
|
||||
# environment
|
||||
VSCODE_LOG_NATIVE = false
|
||||
# environment
|
||||
NVM_INC = /Users/zef/.nvm/versions/node/v16.13.2/include/node
|
||||
# automatic
|
||||
?D = $(patsubst %/,%,$(dir $?))
|
||||
# automatic
|
||||
@D = $(patsubst %/,%,$(dir $@))
|
||||
# automatic
|
||||
@F = $(notdir $@)
|
||||
# makefile
|
||||
CURDIR := /Users/zef/git/silverbullet
|
||||
# makefile
|
||||
SHELL = /bin/sh
|
||||
# environment
|
||||
VSCODE_NLS_CONFIG = {"locale":"en-us","availableLanguages":{},"_languagePackSupport":true}
|
||||
# environment
|
||||
_ = /usr/bin/make
|
||||
# makefile (from `Makefile', line 1)
|
||||
MAKEFILE_LIST := Makefile
|
||||
# environment
|
||||
VSCODE_VERBOSE_LOGGING = true
|
||||
# environment
|
||||
__CFBundleIdentifier = com.microsoft.VSCode
|
||||
# environment
|
||||
INFOPATH = /opt/homebrew/share/info:
|
||||
# environment
|
||||
VSCODE_IPC_HOOK_EXTHOST = /var/folders/s2/4nqrw2192hngtxg672qzc0nr0000gn/T/vscode-ipc-94d58bf8-97af-4a54-9bba-ba61d0cf0d30.sock
|
||||
# environment
|
||||
VSCODE_CWD = /
|
||||
# environment
|
||||
PATH = /Users/zef/.nvm/versions/node/v16.13.2/bin:/Library/Frameworks/Python.framework/Versions/2.7/bin:/Users/zef/.local/share/solana/install/active_release/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin:/Users/zef/.cargo/bin:/Users/zef/.fig/bin:/Users/zef/.local/bin
|
||||
# environment
|
||||
LSCOLORS = Gxfxcxdxbxegedabagacad
|
||||
# environment
|
||||
NVM_BIN = /Users/zef/.nvm/versions/node/v16.13.2/bin
|
||||
# environment
|
||||
VSCODE_LOG_STACK = false
|
||||
# environment
|
||||
ELECTRON_RUN_AS_NODE = 1
|
||||
# default
|
||||
.FEATURES := target-specific order-only second-expansion else-if archives jobserver check-symlink
|
||||
# environment
|
||||
SSH_AUTH_SOCK = /private/tmp/com.apple.launchd.qE7FAVvbDO/Listeners
|
||||
# automatic
|
||||
%F = $(notdir $%)
|
||||
# environment
|
||||
TTY = not a tty
|
||||
# environment
|
||||
VSCODE_PIPE_LOGGING = true
|
||||
# environment
|
||||
FIG_PID = 89255
|
||||
# environment
|
||||
PWD = /Users/zef/git/silverbullet
|
||||
# environment
|
||||
HOMEBREW_CELLAR = /opt/homebrew/Cellar
|
||||
# environment
|
||||
ORIGINAL_XDG_CURRENT_DESKTOP = undefined
|
||||
# environment
|
||||
MANPATH = /Users/zef/.nvm/versions/node/v16.13.2/share/man:/opt/homebrew/share/man::
|
||||
# environment
|
||||
VSCODE_AMD_ENTRYPOINT = vs/workbench/api/node/extensionHostProcess
|
||||
# environment
|
||||
HOME = /Users/zef
|
||||
# default
|
||||
MAKEFILEPATH := /Applications/Xcode.app/Contents/Developer/Makefiles
|
||||
# environment
|
||||
VSCODE_CODE_CACHE_PATH = /Users/zef/Library/Application Support/Code/CachedData/30d9c6cd9483b2cc586687151bcbcd635f373630
|
||||
# environment
|
||||
LOGNAME = zef
|
||||
# environment
|
||||
APPLICATION_INSIGHTS_NO_DIAGNOSTIC_CHANNEL = true
|
||||
# environment
|
||||
NVM_CD_FLAGS = -q
|
||||
# environment
|
||||
ZSH = /Users/zef/.local/share/fig/plugins/ohmyzsh
|
||||
# environment
|
||||
VSCODE_HANDLES_UNCAUGHT_ERRORS = true
|
||||
# automatic
|
||||
^D = $(patsubst %/,%,$(dir $^))
|
||||
# environment
|
||||
XPC_FLAGS = 0x0
|
||||
# default
|
||||
MAKE = $(MAKE_COMMAND)
|
||||
# default
|
||||
MAKECMDGOALS := all
|
||||
# environment
|
||||
SHLVL = 1
|
||||
# default
|
||||
MAKE_VERSION := 3.81
|
||||
# environment
|
||||
USER = zef
|
||||
# makefile
|
||||
.DEFAULT_GOAL := release
|
||||
# environment
|
||||
LESS = -R
|
||||
# automatic
|
||||
%D = $(patsubst %/,%,$(dir $%))
|
||||
# default
|
||||
MAKE_COMMAND := /Applications/Xcode.app/Contents/Developer/usr/bin/make
|
||||
# default
|
||||
.VARIABLES :=
|
||||
# environment
|
||||
TMPDIR = /var/folders/s2/4nqrw2192hngtxg672qzc0nr0000gn/T/
|
||||
# automatic
|
||||
*F = $(notdir $*)
|
||||
# environment
|
||||
VSCODE_IPC_HOOK = /Users/zef/Library/Application Support/Code/1.68.1-main.sock
|
||||
# makefile
|
||||
MAKEFLAGS = Rrqp
|
||||
# environment
|
||||
MFLAGS = -Rrqp
|
||||
# automatic
|
||||
*D = $(patsubst %/,%,$(dir $*))
|
||||
# environment
|
||||
NVM_DIR = /Users/zef/.nvm
|
||||
# environment
|
||||
XPC_SERVICE_NAME = application.com.microsoft.VSCode.109834161.109834167
|
||||
# environment
|
||||
HOMEBREW_PREFIX = /opt/homebrew
|
||||
# automatic
|
||||
+D = $(patsubst %/,%,$(dir $+))
|
||||
# automatic
|
||||
+F = $(notdir $+)
|
||||
# environment
|
||||
HOMEBREW_REPOSITORY = /opt/homebrew
|
||||
# environment
|
||||
__CF_USER_TEXT_ENCODING = 0x1F5:0x0:0x0
|
||||
# environment
|
||||
COMMAND_MODE = unix2003
|
||||
# default
|
||||
MAKEFILES :=
|
||||
# automatic
|
||||
<F = $(notdir $<)
|
||||
# environment
|
||||
PAGER = less
|
||||
# environment
|
||||
LC_ALL = C
|
||||
# automatic
|
||||
^F = $(notdir $^)
|
||||
# default
|
||||
SUFFIXES :=
|
||||
# environment
|
||||
MAKELEVEL := 0
|
||||
# environment
|
||||
LANG = C
|
||||
# environment
|
||||
VSCODE_PID = 89247
|
||||
# variable set hash-table stats:
|
||||
# Load=76/1024=7%, Rehash=0, Collisions=1/99=1%
|
||||
|
||||
# Pattern-specific Variable Values
|
||||
|
||||
# No pattern-specific variable values.
|
||||
|
||||
# Directories
|
||||
|
||||
# . (device 16777232, inode 91684251): 20 files, no impossibilities.
|
||||
|
||||
# 20 files, no impossibilities in 1 directories.
|
||||
|
||||
# Implicit Rules
|
||||
|
||||
# No implicit rules.
|
||||
|
||||
# Files
|
||||
|
||||
# Not a target:
|
||||
all:
|
||||
# Command-line target.
|
||||
# Implicit rule search has been done.
|
||||
# File does not exist.
|
||||
# File has not been updated.
|
||||
# variable set hash-table stats:
|
||||
# Load=0/32=0%, Rehash=0, Collisions=0/0=0%
|
||||
|
||||
# Not a target:
|
||||
.SUFFIXES:
|
||||
# Implicit rule search has not been done.
|
||||
# Modification time never checked.
|
||||
# File has not been updated.
|
||||
|
||||
# Not a target:
|
||||
Makefile:
|
||||
# Implicit rule search has been done.
|
||||
# Last modified 2022-06-28 12:27:42
|
||||
# File has been updated.
|
||||
# Successfully updated.
|
||||
# variable set hash-table stats:
|
||||
# Load=0/32=0%, Rehash=0, Collisions=0/0=0%
|
||||
|
||||
release:
|
||||
# Implicit rule search has not been done.
|
||||
# Modification time never checked.
|
||||
# File has not been updated.
|
||||
# commands to execute (from `Makefile', line 2):
|
||||
|
||||
|
||||
# Not a target:
|
||||
.DEFAULT:
|
||||
# Implicit rule search has not been done.
|
||||
# Modification time never checked.
|
||||
# File has not been updated.
|
||||
|
||||
# files hash-table stats:
|
||||
# Load=5/1024=0%, Rehash=0, Collisions=0/17=0%
|
||||
# VPATH Search Paths
|
||||
|
||||
# No `vpath' search paths.
|
||||
|
||||
|
||||
# No general (`VPATH' variable) search path.
|
||||
|
||||
# # of strings in strcache: 1
|
||||
# # of strcache buffers: 1
|
||||
# strcache size: total = 4096 / max = 4096 / min = 4096 / avg = 4096
|
||||
# strcache free: total = 4087 / max = 4087 / min = 4087 / avg = 4087
|
||||
|
||||
# Finished Make data base on Tue Jun 28 12:27:45 2022
|
||||
|
||||
|
8
LICENSE.md
Normal file
8
LICENSE.md
Normal file
@ -0,0 +1,8 @@
|
||||
Copyright 2022, Zef Hemel
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
55
README.md
55
README.md
@ -1,31 +1,66 @@
|
||||
# Silver Bullet
|
||||
Silver Bullet (SB) is a highly extensible, open source **personal knowledge playground**. At its core it’s effectively a Markdown-based writing/note taking application that stores your _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. This makes it a simple tool for [Personal Knowledge Management](https://en.wikipedia.org/wiki/Personal_knowledge_management). However, once you leverage its various extensions (called _plugs_) it can feel more like a _knowledge playground_, allowing you to annotate, combine and query your accumulated knowledge in creative ways specific to you.
|
||||
|
||||
Mono repo using npm workspaces.
|
||||
So what is it SB _really_? That is hard to answer. It can do tons of stuff, and I’m constantly finding new use cases. It’s like… a silver bullet.
|
||||
|
||||
To install, after clone:
|
||||
Here’s how I use it today (but this has grown significantly over time):
|
||||
|
||||
* Basic note taking, e.g. during meetings, about books I read, blogs I read, podcasts I listen to, movies I watch.
|
||||
* Getting a quick glance of the work people in my team are doing pulling data from our 1:1s, recent activity on Github (such as recent pull requests) and other sources.
|
||||
* Writing:
|
||||
* [My blog](https://zef.plus) is published via SB’s [Ghost](https://ghost.org) plugin.
|
||||
* An internal newsletter that I write is written in SB.
|
||||
* Performance reviews for my team (I work as a people manager) are written and managed using SB (for which I extensively use SB’s meta data features and query that data in various ways).
|
||||
* A custom SB plugin aggregates data from our OpsGenie account every week, and publishes it to our mattermost instance.
|
||||
* It powers part of my smart home: I wired HomeBridge webhooks up to custom HTTP endpoints exposed by my custom smart home SB plug.
|
||||
|
||||
More documentation can be found in the [docs space](https://github.com/zefhemel/silverbullet/tree/main/docs)
|
||||
|
||||
## Features
|
||||
* **Free and open source**
|
||||
* **Minimalistic** UI with [What You See is What You Mean](https://en.wikipedia.org/wiki/WYSIWYM) Markdown editing.
|
||||
* **Future proof**: stores all notes in a regular folder with markdown files, no proprietary file formats. While SB uses a SQLite database for indexes, this database can be wiped and rebuilt based on your pages at any time. Your Markdown files are the single source of truth.
|
||||
* **Run anywhere**: run it on your local machine, or install it on a server. You access it via your web browser (desktop or mobile), or install it as a PWA (giving it its own window frame and dock/launcher/dock icon).
|
||||
* **Keyboard oriented:** you can fully operate SB via the keyboard.
|
||||
* **Extensible** through plugs.
|
||||
## Stack
|
||||
* Written in [TypeScript](https://www.typescriptlang.org/)
|
||||
* Built on the excellent [CodeMirror 6](https://codemirror.net/) editor component
|
||||
* Front-end (beside CodeMirror) is built using React.js
|
||||
* [ParcelJS](https://parceljs.org/) is used to build both the front-end and back-end
|
||||
* Backend runs on node.js using express
|
||||
## Development
|
||||
This Silver Bullet repo is a monorepo using npm's "workspaces" feature.
|
||||
|
||||
Requirements: node 16+ and npm 8+ as well as C/C++ compilers (for compiling SQLite, on debian/ubuntu style systems you get these via the `build-essential` package)
|
||||
|
||||
To run, after clone:
|
||||
|
||||
```shell
|
||||
# The path for pages, hardcoded for `npm run server`
|
||||
mkdir -p pages
|
||||
# Install dependencies
|
||||
npm install
|
||||
# Run initial build (web app, server, etc.)
|
||||
npm run build
|
||||
# Again, to install the CLIs just built
|
||||
# Again, to install the CLIs just built (plugos-bundler, silverbullet)
|
||||
npm install
|
||||
# Build plugs (ctrl-c after done, it's watching)
|
||||
npm run plugs
|
||||
# Build built-in plugs
|
||||
npm run build-plugs
|
||||
# Launch server
|
||||
npm run server
|
||||
npm run server -- <PATH-TO-YOUR-SPACE>
|
||||
```
|
||||
|
||||
Open at http://localhost:3000
|
||||
This `<PATH-TO-YOUR-SPACE>` can be any folder with markdown files, upon first boot SB will ensure there is an `index.md` file (root page) and `PLUGS.md` file (with default list of plugs to load). SB will also create a SQLite `data.db` file with various data caches and indices (you can delete this file at any time and use the `Space: Reindex` command to reindex everything).
|
||||
|
||||
Open SB at http://localhost:3000 If you're using a browser supporting PWAs, you can install this page as a PWA. This also works on iOS (use the "Add to homescreen" option in the share menu).
|
||||
|
||||
General development workflow:
|
||||
|
||||
Run these in separate terminals
|
||||
```shell
|
||||
# Run these in separate terminals
|
||||
# Runs ParcelJS in watch mode, rebuilding the server and webapp continuously on change
|
||||
npm run watch
|
||||
# Runs the silverbullet server
|
||||
npm run server
|
||||
# Builds (and watches for changes) all builtin plugs (in packages/plugs)
|
||||
npm run plugs
|
||||
```
|
||||
|
89
desktop/.gitignore
vendored
89
desktop/.gitignore
vendored
@ -1,89 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
.DS_Store
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# Webpack
|
||||
.webpack/
|
||||
|
||||
# Electron-Forge
|
||||
out/
|
19905
desktop/package-lock.json
generated
19905
desktop/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,103 +0,0 @@
|
||||
{
|
||||
"name": "desktop",
|
||||
"productName": "Silver Bullet",
|
||||
"version": "1.0.0",
|
||||
"description": "My Electron application description",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"start": "electron-forge start",
|
||||
"package": "electron-forge package",
|
||||
"make": "electron-forge make",
|
||||
"publish": "electron-forge publish",
|
||||
"lint": "echo \"No linting configured\""
|
||||
},
|
||||
"keywords": [],
|
||||
"author": {
|
||||
"name": "Zef Hemel",
|
||||
"email": "zef@zef.me"
|
||||
},
|
||||
"targets": {
|
||||
"desktop": {
|
||||
"source": [
|
||||
"src/index.ts",
|
||||
"src/preload.ts"
|
||||
],
|
||||
"outputFormat": "commonjs",
|
||||
"isLibrary": true,
|
||||
"context": "electron-main",
|
||||
"includeNodeModules": [
|
||||
"@plugos/plugos",
|
||||
"@silverbulletmd/common",
|
||||
"@silverbulletmd/web",
|
||||
"@silverbulletmd/server"
|
||||
]
|
||||
}
|
||||
},
|
||||
"license": "MIT",
|
||||
"config": {
|
||||
"forge": {
|
||||
"packagerConfig": {},
|
||||
"makers": [
|
||||
{
|
||||
"name": "@electron-forge/maker-squirrel",
|
||||
"config": {
|
||||
"name": "desktop"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "@electron-forge/maker-zip",
|
||||
"platforms": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "@electron-forge/maker-deb",
|
||||
"config": {}
|
||||
},
|
||||
{
|
||||
"name": "@electron-forge/maker-rpm",
|
||||
"config": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"electron": "^18.0.3",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"@codemirror/lang-javascript": "^0.19.7",
|
||||
"@codemirror/lang-markdown": "^0.19.6",
|
||||
"@codemirror/language": "^0.19.0",
|
||||
"@codemirror/legacy-modes": "^0.19.1",
|
||||
"@codemirror/stream-parser": "^0.19.9",
|
||||
"@jest/globals": "^27.5.1",
|
||||
"@lezer/markdown": "^0.15.0",
|
||||
"better-sqlite3": "^7.5.0",
|
||||
"body-parser": "^1.19.2",
|
||||
"buffer": "^6.0.3",
|
||||
"cors": "^2.8.5",
|
||||
"dexie": "^3.2.1",
|
||||
"events": "^3.3.0",
|
||||
"express": "^4.17.3",
|
||||
"fake-indexeddb": "^3.1.7",
|
||||
"jest": "^27.5.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"knex": "^1.0.4",
|
||||
"node-cron": "^3.0.0",
|
||||
"node-fetch": "2",
|
||||
"node-watch": "^0.7.3",
|
||||
"nodemon": "^2.0.15",
|
||||
"vm2": "^3.9.9",
|
||||
"ws": "^8.5.0",
|
||||
"yaml": "^1.10.2",
|
||||
"yargs": "^17.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-forge/cli": "^6.0.0-beta.63",
|
||||
"@electron-forge/maker-deb": "^6.0.0-beta.63",
|
||||
"@electron-forge/maker-rpm": "^6.0.0-beta.63",
|
||||
"@electron-forge/maker-squirrel": "^6.0.0-beta.63",
|
||||
"@electron-forge/maker-zip": "^6.0.0-beta.63",
|
||||
"electron": "18.0.3",
|
||||
"electron-rebuild": "^3.2.7"
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,
|
||||
Arial, sans-serif;
|
||||
margin: auto;
|
||||
max-width: 38rem;
|
||||
padding: 2rem;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Hello World!</title>
|
||||
<link rel="stylesheet" href="index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>💖 Hello World!</h1>
|
||||
<p>Welcome to your Electron application.</p>
|
||||
</body>
|
||||
</html>
|
@ -1,116 +0,0 @@
|
||||
import { app, BrowserWindow, dialog, Menu } from "electron";
|
||||
const path = require("path");
|
||||
import { ExpressServer } from "@silverbulletmd/server/express_server";
|
||||
import * as fs from "fs";
|
||||
|
||||
let mainWindow: BrowserWindow | undefined;
|
||||
|
||||
const mainMenuTemplate: Electron.MenuItemConstructorOptions[] = [
|
||||
{
|
||||
label: "File",
|
||||
submenu: [
|
||||
{
|
||||
label: "Switch Folder",
|
||||
click: async () => {
|
||||
let result = await dialog.showOpenDialog(mainWindow!, {
|
||||
properties: ["openDirectory"],
|
||||
});
|
||||
if (result.canceled) {
|
||||
return;
|
||||
}
|
||||
openFolder(result.filePaths[0]).catch(console.error);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Exit",
|
||||
click: () => {
|
||||
app.quit();
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{ label: "Edit" },
|
||||
{ label: "Tools", submenu: [{ label: "Sup" }] },
|
||||
];
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
const name = "Fire Sale";
|
||||
mainMenuTemplate.unshift({ label: name });
|
||||
}
|
||||
const mainMenu = Menu.buildFromTemplate(mainMenuTemplate);
|
||||
Menu.setApplicationMenu(mainMenu);
|
||||
|
||||
const port = 3002;
|
||||
const distDir = path.resolve(
|
||||
`${__dirname}/../../packages/silverbullet-web/dist`
|
||||
);
|
||||
|
||||
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
||||
if (require("electron-squirrel-startup")) {
|
||||
// eslint-disable-line global-require
|
||||
app.quit();
|
||||
}
|
||||
|
||||
let currentFolder = app.getPath("userData");
|
||||
|
||||
fs.mkdirSync(currentFolder, { recursive: true });
|
||||
let server: ExpressServer | undefined;
|
||||
|
||||
async function openFolder(path: string) {
|
||||
console.log("Opening folder", path);
|
||||
if (server) {
|
||||
await server.stop();
|
||||
}
|
||||
currentFolder = path;
|
||||
console.log("Starting new server");
|
||||
server = new ExpressServer(port, path, distDir);
|
||||
await server.start();
|
||||
console.log("Reloading page");
|
||||
mainWindow!.reload();
|
||||
}
|
||||
|
||||
async function createWindow() {
|
||||
// Create the browser window.
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, "preload.js"),
|
||||
},
|
||||
});
|
||||
|
||||
await openFolder(currentFolder);
|
||||
|
||||
// and load the index.html of the app.
|
||||
mainWindow.loadURL(`http://localhost:${port}`);
|
||||
|
||||
// Open the DevTools.
|
||||
mainWindow.webContents.openDevTools();
|
||||
}
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.on("ready", async () => {
|
||||
await createWindow();
|
||||
});
|
||||
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
// for applications and their menu bar to stay active until the user quits
|
||||
// explicitly with Cmd + Q.
|
||||
app.on("window-all-closed", () => {
|
||||
if (process.platform !== "darwin") {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on("activate", () => {
|
||||
// On OS X it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
|
||||
// In this file you can include the rest of your app's specific main process
|
||||
// code. You can also put them in separate files and import them here.
|
@ -1,3 +0,0 @@
|
||||
const { contextBridge } = require("electron");
|
||||
|
||||
contextBridge.exposeInMainWorld("desktop", true);
|
3
docs/.gitignore
vendored
Normal file
3
docs/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
data.db
|
||||
_plug
|
||||
_trash
|
14
docs/PLUGS.md
Normal file
14
docs/PLUGS.md
Normal file
@ -0,0 +1,14 @@
|
||||
This file lists all plugs that SilverBullet will load. Run the `Plugs: Update` command to update and reload this list of plugs.
|
||||
|
||||
```yaml
|
||||
- builtin:core
|
||||
- builtin:emoji
|
||||
- builtin:ghost
|
||||
- builtin:git
|
||||
- builtin:github
|
||||
- builtin:markdown
|
||||
- builtin:mattermost
|
||||
- builtin:plugmd
|
||||
- builtin:query
|
||||
- builtin:tasks
|
||||
```
|
55
docs/index.md
Normal file
55
docs/index.md
Normal file
@ -0,0 +1,55 @@
|
||||
# Silver Bullet
|
||||
Silver Bullet (SB) is a highly extensible, open source **personal knowledge playground**. At its core it’s a Markdown-based writing/note taking application 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. This makes it a simple tool for [Personal Knowledge Management](https://en.wikipedia.org/wiki/Personal_knowledge_management). However, once you leverage its various extensions (called _plugs_) it can feel more like a _knowledge playground_, allowing you to annotate, combine and query your accumulated knowledge in creative ways, specific to you.
|
||||
|
||||
So what is it SB _really_? That is hard to answer. It can do a ton of stuff out of the box, and I’m constantly finding new use cases. It’s like... a silver bullet!
|
||||
|
||||
Here’s how I use it today (but this has grown significantly over time):
|
||||
|
||||
* Basic note taking, e.g. meeting notes, notes on books I read, blogs I read, podcasts I listen to, movies I watch.
|
||||
* Getting a quick glance at the work people in my team are doing by pulling data from our 1:1 notes, recent activity on Github (such as recent pull requests) and other sources.
|
||||
* Writing:
|
||||
* [My blog](https://zef.plus) is published via SB’s [Ghost](https://ghost.org) plugin.
|
||||
* An internal newsletter that I write is written in SB.
|
||||
* Performance reviews for my team (I work as a people manager) are written and managed using SB (for which I extensively use SB’s meta data features and query that data in various ways).
|
||||
* A custom SB plugin aggregates data from our OpsGenie account every week, and publishes it to our [Mattermost](https://mattermost.com/) instance.
|
||||
* It powers part of my smart home: I wired HomeBridge webhooks up to custom HTTP endpoints exposed by my custom smart home SB plug.
|
||||
|
||||
That’s a pretty crazy wide range of use cases!
|
||||
|
||||
I know, right?
|
||||
|
||||
**Disclaimer:** Silver Bullet is under heavy development and significant changes under the hood happen constantly. It’s also low on automated tests and documentation. All this will improve over time. I’ll do better, I promise.
|
||||
|
||||
[[🤯 Features]]
|
||||
[[💡 Inspiration]]
|
||||
[[🔌 Plugs]]
|
||||
[[🔨 Development]]
|
||||
|
||||
## Installing and running Silver Bullet
|
||||
To run a release version, you need to have a recent version of npm (8+) and node.js (16+) installed as well as some basic build infrastructure (make, cpp). Silver Bullet has only been tested on MacOS and Linux thus far.
|
||||
|
||||
To install and run, create a folder for your pages (can be empty or an existing folder with `.md` files) and run:
|
||||
|
||||
npx @silverbullet/server <path-to-folder>
|
||||
|
||||
Optionally you can use the `--port` argument to specify a HTTP port (defaults to `3000`) and you can pass a `--password` flag to require a password to access. Note this is a rather weak security mechanism, so it’s recommended to add additional layers of security on top of this if you run this on a public server somewhere (at least add TLS). Personally I run it on a tiny Linux VM on my server at home, and use a VPN (Tailscale) to access it from outside my home.
|
||||
|
||||
## Roadmap
|
||||
More details on the [[🗺️ Roadmap]] page.
|
||||
<!-- #query task render "template/tasks" -->
|
||||
* [ ] [[🗺️ Roadmap@34]] Persistent recent commands (saved between sessions)
|
||||
* [ ] [[🗺️ Roadmap@92]] Add ==marker== syntax
|
||||
* [ ] [[🗺️ Roadmap@120]] Two finger tap gesture to bring up command palette
|
||||
* [ ] [[🗺️ Roadmap@177]] Change indent level command
|
||||
* [ ] [[🗺️ Roadmap@212]] Keyboard shortcuts for specific notes (e.g. `index` note)
|
||||
* [ ] [[🗺️ Roadmap@276]] RevealJS slides plug
|
||||
* [ ] [[🗺️ Roadmap@303]] Pinned notes and actions?
|
||||
* [ ] [[🗺️ Roadmap@335]] Template for deadline, with 📅 emoji and perhaps defaulting to today?
|
||||
* [ ] [[🗺️ Roadmap@411]] Use webauthn https://www.npmjs.com/package/webauthn
|
||||
* [ ] [[🗺️ Roadmap@469]] Proper sign up and login
|
||||
* [ ] [[🗺️ Roadmap@500]] Data store pagination API
|
||||
* [ ] [[🗺️ Roadmap@532]] Hashtag plug:
|
||||
* [ ] [[🗺️ Roadmap@656]] Extract `MarkdownEditor` component.
|
||||
* [ ] [[🗺️ Roadmap@725]] PUT page with `If-Last-Modified-Before` type header. Rejects if not matching. Client creates a revision, navigates to it.
|
||||
* [ ] [[🗺️ Roadmap@858]] Put retries exponential back off
|
||||
<!-- /query -->
|
3
docs/template/tasks.md
vendored
Normal file
3
docs/template/tasks.md
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{{#each .}}
|
||||
* [{{#if done}}x{{else}} {{/if}}] [[{{page}}@{{pos}}]] {{name}}
|
||||
{{/each}}
|
3
docs/💡 Inspiration.md
Normal file
3
docs/💡 Inspiration.md
Normal file
@ -0,0 +1,3 @@
|
||||
Inspiration for Silver Bullet comes primarily from [Obsidian](https://obsidian.md/) and its various plugs (the work-in-progress plugs around querying and tasks are inspired by Obsidian’s tasks and dataview plugins), but also [Roam Research](https://roamresearch.com/) was an inspiration.
|
||||
|
||||
Why start something new? Neither of these tools are open source, and they make some different choices, specifically on how extensibility is implemented — more on the differences some time in the future.
|
21
docs/🔌 Plugs.md
Normal file
21
docs/🔌 Plugs.md
Normal file
@ -0,0 +1,21 @@
|
||||
Silver Bullet at its core is bare bones in terms of functionality, most of its power it gains from **plugs**.
|
||||
|
||||
Plugs are an extension mechanism (implemented using a library called `plugos` that runs plug code on the server in a sandboxed v8 node.js process, and in the browser using web workers). Plugs can hook into SB in various ways: plugs can extend the Markdown parser and its syntax, define new commands and keybindings, respond to various events triggered either on the server or client side, as well as run recurring and background tasks. Plugs can even define their own extension mechanisms through custom events. Each plug runs in its own sandboxed environment and communicates with SB via _syscalls_ that expose a vast range of functionality. Plugs can be loaded, unloaded and updated without having to restart SB itself.
|
||||
|
||||
Examples of functionality implemented as plugs:
|
||||
|
||||
* _Core functionality_ such as:
|
||||
* Navigation between pages by clicking or hitting `Cmd/Ctrl-Enter`
|
||||
* Page auto complete when using the `[[page link]]` syntax
|
||||
* Indexing of cross-page links and automatically updating all references to them when a page is renamed
|
||||
* Text editing commands such as bold (`Cmd/Ctrl-b`) and italics (`Cmd/Ctrl-i`) or quote or itemize entire sections.
|
||||
* Full text indexing and search
|
||||
* Slash commands such as `/today`, `/tomorrow` and `/meta` (to insert page meta data)
|
||||
* Emoji auto complete using the `:emoji:` syntax
|
||||
* An embedded query language that can be used to query various sets of indexed entities, such as:
|
||||
* Tasks using the Markdown task syntax
|
||||
* Page backlinks
|
||||
* Page in your space and its meta data
|
||||
* Data objects embedded in your pages
|
||||
* Git integration
|
||||
* Github integration
|
38
docs/🔨 Development.md
Normal file
38
docs/🔨 Development.md
Normal file
@ -0,0 +1,38 @@
|
||||
## Stack
|
||||
Silver Bullet 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 React.js. [ParcelJS](https://parceljs.org/) is used to build both the front-end and back-end bundles. The server backend runs as a HTTP server on node.js using express.
|
||||
|
||||
## Development
|
||||
This [Silver Bullet repo](https://github.com/zefhemel/silverbullet) is a monorepo using npm's "workspaces" feature.
|
||||
|
||||
Requirements: node 16+ and npm 8+ as well as C/C++ compilers (for compiling SQLite, on debian/ubuntu style systems you get these via the `build-essential` package)
|
||||
|
||||
To run, after clone:
|
||||
|
||||
```shell
|
||||
# Install dependencies
|
||||
npm install
|
||||
# Run initial build (web app, server, etc.)
|
||||
npm run build
|
||||
# Again, to install the CLIs just built (plugos-bundler, silverbullet)
|
||||
npm install
|
||||
# Build built-in plugs
|
||||
npm run build-plugs
|
||||
# Launch server
|
||||
npm run server -- <PATH-TO-YOUR-SPACE>
|
||||
```
|
||||
|
||||
This `<PATH-TO-YOUR-SPACE>` can be any folder with markdown files, upon first boot SB will ensure there is an `index.md` file (root page) and `PLUGS.md` file (with default list of plugs to load). SB will also create a SQLite `data.db` file with various data caches and indices (you can delete this file at any time and use the `Space: Reindex` command to reindex everything).
|
||||
|
||||
Open SB at http://localhost:3000 If you're using a browser supporting PWAs, you can install this page as a PWA. This also works on iOS (use the "Add to homescreen" option in the share menu).
|
||||
|
||||
General development workflow:
|
||||
|
||||
Run these in separate terminals
|
||||
```shell
|
||||
# Runs ParcelJS in watch mode, rebuilding the server and webapp continuously on change
|
||||
npm run watch
|
||||
# Runs the silverbullet server
|
||||
npm run server
|
||||
# Builds (and watches for changes) all builtin plugs (in packages/plugs)
|
||||
npm run plugs
|
||||
```
|
22
docs/🗺️ Roadmap.md
Normal file
22
docs/🗺️ Roadmap.md
Normal file
@ -0,0 +1,22 @@
|
||||
Some things I want to work on:
|
||||
|
||||
* [ ] Persistent recent commands (saved between sessions)
|
||||
* [ ] Add ==marker== syntax
|
||||
* [ ] Two finger tap gesture to bring up command palette
|
||||
* [ ] Change indent level command
|
||||
* [ ] Keyboard shortcuts for specific notes (e.g. `index` note)
|
||||
* [ ] RevealJS slides plug
|
||||
* [ ] Pinned notes and actions?
|
||||
* [ ] Template for deadline, with 📅 emoji and perhaps defaulting to today?
|
||||
* [ ] Use webauthn https://www.npmjs.com/package/webauthn
|
||||
* [ ] Proper sign up and login
|
||||
* [ ] Data store pagination API
|
||||
* [ ] Hashtag plug:
|
||||
* Higlighting
|
||||
* Page indexing/item indexing
|
||||
* Tag completion
|
||||
* Query providers: ht-page ht-item
|
||||
* [ ] Extract `MarkdownEditor` component.
|
||||
* REST API safeguards:
|
||||
* [ ] PUT page with `If-Last-Modified-Before` type header. Rejects if not matching. Client creates a revision, navigates to it.
|
||||
* [ ] Put retries exponential back off
|
10
docs/🤯 Features.md
Normal file
10
docs/🤯 Features.md
Normal file
@ -0,0 +1,10 @@
|
||||
* **Free and open source** (MIT licensed)
|
||||
* **Distraction free** UI with [What You See is What You Mean](https://en.wikipedia.org/wiki/WYSIWYM) Markdown editing.
|
||||
* **Future proof**: stores all notes in a regular folder with markdown files, no proprietary file formats. While SB uses a SQLite database for indexes, this database can be wiped and rebuilt based on your pages at any time. Your Markdown files are the single source of truth.
|
||||
* **Run anywhere**: run it on your local machine, or install it on a server. You access it via your web browser (desktop or mobile), or install it as a PWA (giving it its own window frame and dock/launcher/dock icon).
|
||||
* **Keyboard oriented:** you can fully operate SB via the keyboard (on laptop/desktop machines as well as iPads with a keyboard):
|
||||
* Switch between pages using `Cmd-k` (Mac) or `Ctrl-k` (Linux/Windows)
|
||||
* Open the command palette using `Cmd-/` (Mac) or `Ctrl-/` (Linux/Windows)
|
||||
* Use slash commands by entering a `/` in your note’s text
|
||||
* Various plugs add additional keyboard shortcuts and auto completions like when entering `[[page links]]` and typing emoji via the `:lemon:` syntax.
|
||||
* **Extensible** through plugs (see below)
|
2380
package-lock.json
generated
2380
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "silverbulletmd",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"watch": "rm -rf .parcel-cache && parcel watch --no-hmr packages/{web,server,plugos,plugs} desktop",
|
||||
@ -9,8 +8,10 @@
|
||||
"nuke": "rm -rf node_modules && npm run clean",
|
||||
"build": "parcel build packages/{web,server,plugos}",
|
||||
"plugs": "cd packages/plugs && npm run watch",
|
||||
"server": "nodemon -w packages/server/dist --exec 'silverbullet pages'",
|
||||
"build-plugs": "cd packages/plugs && npm run build",
|
||||
"server": "nodemon -w packages/server/dist --exec silverbullet",
|
||||
"test": "jest packages/*/dist/test",
|
||||
"clean-build": "npm run clean && npm run build && npm i && npm run build-plugs",
|
||||
"publish-all": "npm publish --access public --ws"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -4,7 +4,7 @@
|
||||
"name": "Zef Hemel",
|
||||
"email": "zef@zef.me"
|
||||
},
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.7",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "6.0.1",
|
||||
@ -16,8 +16,12 @@
|
||||
"@codemirror/search": "6.0.0",
|
||||
"@codemirror/state": "6.0.0",
|
||||
"@codemirror/view": "6.0.0",
|
||||
"@lezer/common": "1.0.0",
|
||||
"@lezer/highlight": "1.0.0",
|
||||
"@lezer/markdown": "1.0.0",
|
||||
"@lezer/common": "1.0.0"
|
||||
"@silverbulletmd/common": "^0.0.7",
|
||||
"@silverbulletmd/plugs": "^0.0.7",
|
||||
"@silverbulletmd/web": "^0.0.7",
|
||||
"server": "^1.0.37"
|
||||
}
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ export class DiskSpacePrimitives implements SpacePrimitives {
|
||||
perm: "rw",
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error while getting page meta", pageName, e);
|
||||
// console.error("Error while getting page meta", pageName, e);
|
||||
throw Error(`Could not get meta for ${pageName}`);
|
||||
}
|
||||
}
|
||||
|
@ -18,17 +18,17 @@ test("Test store", async () => {
|
||||
async function conflictResolver(pageMeta1: PageMeta, pageMeta2: PageMeta) {}
|
||||
|
||||
// Write one page to primary
|
||||
await primary.writePage("start", "Hello");
|
||||
await primary.writePage("index", "Hello");
|
||||
expect((await secondary.listPages()).size).toBe(0);
|
||||
await syncPages(conflictResolver);
|
||||
expect((await secondary.listPages()).size).toBe(1);
|
||||
expect((await secondary.readPage("start")).text).toBe("Hello");
|
||||
expect((await secondary.readPage("index")).text).toBe("Hello");
|
||||
|
||||
// Should be a no-op
|
||||
expect(await syncPages()).toBe(0);
|
||||
|
||||
// Now let's make a change on the secondary
|
||||
await secondary.writePage("start", "Hello!!");
|
||||
await secondary.writePage("index", "Hello!!");
|
||||
await secondary.writePage("test", "Test page");
|
||||
|
||||
// And sync it
|
||||
@ -37,13 +37,13 @@ test("Test store", async () => {
|
||||
expect(primary.listPages().size).toBe(2);
|
||||
expect(secondary.listPages().size).toBe(2);
|
||||
|
||||
expect((await primary.readPage("start")).text).toBe("Hello!!");
|
||||
expect((await primary.readPage("index")).text).toBe("Hello!!");
|
||||
|
||||
// Let's make some random edits on both ends
|
||||
await primary.writePage("start", "1");
|
||||
await primary.writePage("start2", "2");
|
||||
await secondary.writePage("start3", "3");
|
||||
await secondary.writePage("start4", "4");
|
||||
await primary.writePage("index", "1");
|
||||
await primary.writePage("index2", "2");
|
||||
await secondary.writePage("index3", "3");
|
||||
await secondary.writePage("index4", "4");
|
||||
await syncPages();
|
||||
|
||||
expect((await primary.listPages()).size).toBe(5);
|
||||
@ -53,8 +53,8 @@ test("Test store", async () => {
|
||||
|
||||
console.log("Deleting pages");
|
||||
// Delete some pages
|
||||
await primary.deletePage("start");
|
||||
await primary.deletePage("start3");
|
||||
await primary.deletePage("index");
|
||||
await primary.deletePage("index3");
|
||||
|
||||
console.log("Pages", await primary.listPages());
|
||||
console.log("Trash", await primary.listTrash());
|
||||
@ -67,8 +67,8 @@ test("Test store", async () => {
|
||||
// No-op
|
||||
expect(await syncPages()).toBe(0);
|
||||
|
||||
await secondary.deletePage("start4");
|
||||
await primary.deletePage("start2");
|
||||
await secondary.deletePage("index4");
|
||||
await primary.deletePage("index2");
|
||||
|
||||
await syncPages();
|
||||
|
||||
@ -79,15 +79,15 @@ test("Test store", async () => {
|
||||
// No-op
|
||||
expect(await syncPages()).toBe(0);
|
||||
|
||||
await secondary.writePage("start", "I'm back");
|
||||
await secondary.writePage("index", "I'm back");
|
||||
|
||||
await syncPages();
|
||||
|
||||
expect((await primary.readPage("start")).text).toBe("I'm back");
|
||||
expect((await primary.readPage("index")).text).toBe("I'm back");
|
||||
|
||||
// Cause a conflict
|
||||
await primary.writePage("start", "Hello 1");
|
||||
await secondary.writePage("start", "Hello 2");
|
||||
await primary.writePage("index", "Hello 1");
|
||||
await secondary.writePage("index", "Hello 2");
|
||||
|
||||
await syncPages(SpaceSync.primaryConflictResolver(primary, secondary));
|
||||
|
||||
@ -95,10 +95,10 @@ test("Test store", async () => {
|
||||
await syncPages();
|
||||
|
||||
// Verify that primary won
|
||||
expect((await primary.readPage("start")).text).toBe("Hello 1");
|
||||
expect((await secondary.readPage("start")).text).toBe("Hello 1");
|
||||
expect((await primary.readPage("index")).text).toBe("Hello 1");
|
||||
expect((await secondary.readPage("index")).text).toBe("Hello 1");
|
||||
|
||||
// test + start + start.conflicting copy
|
||||
// test + index + index.conflicting copy
|
||||
expect((await primary.listPages()).size).toBe(3);
|
||||
expect((await secondary.listPages()).size).toBe(3);
|
||||
|
||||
|
@ -4,6 +4,12 @@
|
||||
"name": "Zef Hemel",
|
||||
"email": "zef@zef.me"
|
||||
},
|
||||
"version": "0.0.2",
|
||||
"license": "MIT"
|
||||
"version": "0.0.7",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@silverbulletmd/common": "^0.0.7",
|
||||
"@silverbulletmd/plugs": "^0.0.7",
|
||||
"@silverbulletmd/web": "^0.0.7",
|
||||
"server": "^1.0.37"
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,12 @@
|
||||
"name": "Zef Hemel",
|
||||
"email": "zef@zef.me"
|
||||
},
|
||||
"version": "0.0.2",
|
||||
"license": "MIT"
|
||||
"version": "0.0.7",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@silverbulletmd/common": "^0.0.7",
|
||||
"@silverbulletmd/plugs": "^0.0.7",
|
||||
"@silverbulletmd/web": "^0.0.7",
|
||||
"server": "^1.0.37"
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
"name": "Zef Hemel",
|
||||
"email": "zef@zef.me"
|
||||
},
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.7",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"plugos-bundle": "./dist/plugos/plugos-bundle.js",
|
||||
@ -40,6 +40,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@jest/globals": "^27.5.1",
|
||||
"@silverbulletmd/common": "^0.0.7",
|
||||
"@silverbulletmd/plugs": "^0.0.7",
|
||||
"@silverbulletmd/web": "^0.0.7",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/jsonwebtoken": "^8.5.8",
|
||||
@ -56,12 +59,13 @@
|
||||
"node-cron": "^3.0.0",
|
||||
"node-fetch": "2",
|
||||
"node-watch": "^0.7.3",
|
||||
"server": "^1.0.37",
|
||||
"supertest": "^6.2.2",
|
||||
"typescript": "^4.6.2",
|
||||
"vm2": "^3.9.9",
|
||||
"ws": "^8.5.0",
|
||||
"yaml": "^1.10.2",
|
||||
"yargs": "^17.3.1",
|
||||
"typescript": "^4.6.2"
|
||||
"yargs": "^17.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lezer/lr": "1.0.0",
|
||||
|
@ -279,7 +279,6 @@ function $11a7e2bff790f35a$export$195ba6d62321b933(message, defaultValue = "") {
|
||||
}
|
||||
|
||||
|
||||
const $c3893eec0c49ec96$var$dateMatchRegex = /(\d{4}\-\d{2}\-\d{2})/g;
|
||||
function $c3893eec0c49ec96$export$5dc1410f87262ed6(d) {
|
||||
return d.toISOString().split("T")[0];
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -35,6 +35,13 @@ functions:
|
||||
path: "./page.ts:parseIndexTextRepublish"
|
||||
events:
|
||||
- page:index_text
|
||||
reindexSpaceCommand:
|
||||
path: "./page.ts:reindexCommand"
|
||||
command:
|
||||
name: "Space: Reindex"
|
||||
reindexSpace:
|
||||
path: "./page.ts:reindexSpace"
|
||||
env: server
|
||||
indexLinks:
|
||||
path: "./page.ts:indexLinks"
|
||||
events:
|
||||
@ -43,25 +50,10 @@ functions:
|
||||
path: ./page.ts:linkQueryProvider
|
||||
events:
|
||||
- query:link
|
||||
# indexItems:
|
||||
# path: "./item.ts:indexItems"
|
||||
# events:
|
||||
# - page:index
|
||||
# itemQueryProvider:
|
||||
# path: ./item.ts:queryProvider
|
||||
# events:
|
||||
# - query:item
|
||||
deletePage:
|
||||
path: "./page.ts:deletePage"
|
||||
command:
|
||||
name: "Page: Delete"
|
||||
reindexSpaceCommand:
|
||||
path: "./page.ts:reindexCommand"
|
||||
command:
|
||||
name: "Space: Reindex"
|
||||
reindexSpace:
|
||||
path: "./page.ts:reindexSpace"
|
||||
env: server
|
||||
renamePage:
|
||||
path: "./page.ts:renamePage"
|
||||
command:
|
||||
@ -82,38 +74,39 @@ functions:
|
||||
path: "./navigate.ts:clickNavigate"
|
||||
events:
|
||||
- page:click
|
||||
insertToday:
|
||||
path: "./dates.ts:insertToday"
|
||||
slashCommand:
|
||||
name: today
|
||||
insertTomorrow:
|
||||
path: "./dates.ts:insertTomorrow"
|
||||
slashCommand:
|
||||
name: tomorrow
|
||||
parseServerCommand:
|
||||
path: ./debug.ts:parseServerPageCommand
|
||||
command:
|
||||
name: "Debug: Parse Document on Server"
|
||||
parsePage:
|
||||
path: ./debug.ts:parsePage
|
||||
parseCommand:
|
||||
path: ./debug.ts:parsePageCommand
|
||||
command:
|
||||
name: "Debug: Parse Document"
|
||||
showLogsCommand:
|
||||
path: ./debug.ts:showLogsCommand
|
||||
command:
|
||||
name: "Debug: Show Logs"
|
||||
key: "Ctrl-Alt-l"
|
||||
mac: "Cmd-Alt-l"
|
||||
|
||||
# Full text search
|
||||
searchIndex:
|
||||
path: ./search.ts:index
|
||||
events:
|
||||
- log:reload
|
||||
hideBhsCommand:
|
||||
path: ./debug.ts:hideBhsCommand
|
||||
- page:index
|
||||
searchUnindex:
|
||||
path: "./search.ts:unindex"
|
||||
env: server
|
||||
events:
|
||||
- page:deleted
|
||||
searchQueryProvider:
|
||||
path: ./search.ts:queryProvider
|
||||
events:
|
||||
- query:full-text
|
||||
searchCommand:
|
||||
path: ./search.ts:searchCommand
|
||||
command:
|
||||
name: "UI: Hide BHS"
|
||||
key: "Ctrl-Alt-b"
|
||||
mac: "Cmd-Alt-b"
|
||||
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
|
||||
|
||||
# Template commands
|
||||
insertPageMeta:
|
||||
path: "./page.ts:insertPageMeta"
|
||||
slashCommand:
|
||||
@ -132,7 +125,16 @@ functions:
|
||||
path: ./template.ts:instantiateTemplateCommand
|
||||
command:
|
||||
name: "Template: Instantiate for Page"
|
||||
insertToday:
|
||||
path: "./dates.ts:insertToday"
|
||||
slashCommand:
|
||||
name: today
|
||||
insertTomorrow:
|
||||
path: "./dates.ts:insertTomorrow"
|
||||
slashCommand:
|
||||
name: tomorrow
|
||||
|
||||
# Text editing commands
|
||||
quoteSelection:
|
||||
path: ./text.ts:quoteSelection
|
||||
command:
|
||||
@ -159,3 +161,40 @@ functions:
|
||||
name: "Text: Italic"
|
||||
key: "Ctrl-i"
|
||||
mac: "Cmd-i"
|
||||
|
||||
# Plug manager
|
||||
updatePlugsCommand:
|
||||
path: ./plugmanager.ts:updatePlugsCommand
|
||||
command:
|
||||
name: "Plugs: Update"
|
||||
key: "Ctrl-Shift-p"
|
||||
mac: "Cmd-Shift-p"
|
||||
updatePlugs:
|
||||
path: ./plugmanager.ts:updatePlugs
|
||||
env: server
|
||||
|
||||
# Debug commands
|
||||
parseServerCommand:
|
||||
path: ./debug.ts:parseServerPageCommand
|
||||
command:
|
||||
name: "Debug: Parse Document on Server"
|
||||
parsePage:
|
||||
path: ./debug.ts:parsePage
|
||||
parseCommand:
|
||||
path: ./debug.ts:parsePageCommand
|
||||
command:
|
||||
name: "Debug: Parse Document"
|
||||
showLogsCommand:
|
||||
path: ./debug.ts:showLogsCommand
|
||||
command:
|
||||
name: "Debug: Show Logs"
|
||||
key: "Ctrl-Alt-l"
|
||||
mac: "Cmd-Alt-l"
|
||||
events:
|
||||
- log:reload
|
||||
hideBhsCommand:
|
||||
path: ./debug.ts:hideBhsCommand
|
||||
command:
|
||||
name: "UI: Hide BHS"
|
||||
key: "Ctrl-Alt-b"
|
||||
mac: "Cmd-Alt-b"
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { insertAtCursor } from "@silverbulletmd/plugos-silverbullet-syscall/editor";
|
||||
|
||||
const dateMatchRegex = /(\d{4}\-\d{2}\-\d{2})/g;
|
||||
|
||||
export function niceDate(d: Date): string {
|
||||
return d.toISOString().split("T")[0];
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { getLogs } from "@plugos/plugos-syscall/sandbox";
|
||||
import { LogEntry } from "@plugos/plugos/sandbox";
|
||||
import {
|
||||
getText,
|
||||
hideBhs,
|
||||
|
@ -1,38 +0,0 @@
|
||||
import {
|
||||
getCursor,
|
||||
getText,
|
||||
insertAtPos,
|
||||
replaceRange,
|
||||
} from "@silverbulletmd/plugos-silverbullet-syscall/editor";
|
||||
|
||||
export async function toggleH1() {
|
||||
await togglePrefix("# ");
|
||||
}
|
||||
|
||||
export async function toggleH2() {
|
||||
await togglePrefix("## ");
|
||||
}
|
||||
|
||||
function lookBack(s: string, pos: number, backString: string): boolean {
|
||||
return s.substring(pos - backString.length, pos) === backString;
|
||||
}
|
||||
|
||||
async function togglePrefix(prefix: string) {
|
||||
let text = await getText();
|
||||
let pos = await getCursor();
|
||||
if (text[pos] === "\n") {
|
||||
pos--;
|
||||
}
|
||||
while (pos > 0 && text[pos] !== "\n") {
|
||||
if (lookBack(text, pos, prefix)) {
|
||||
// Already has this prefix, let's flip it
|
||||
await replaceRange(pos - prefix.length, pos, "");
|
||||
return;
|
||||
}
|
||||
pos--;
|
||||
}
|
||||
if (pos) {
|
||||
pos++;
|
||||
}
|
||||
await insertAtPos(prefix, pos);
|
||||
}
|
@ -8,8 +8,6 @@ import {
|
||||
import { parseMarkdown } from "@silverbulletmd/plugos-silverbullet-syscall/markdown";
|
||||
import { nodeAtPos, ParseTree } from "@silverbulletmd/common/tree";
|
||||
|
||||
const materializedQueryPrefix = /<!--\s*#query\s+/;
|
||||
|
||||
async function actionClickOrActionEnter(mdTree: ParseTree | null) {
|
||||
if (!mdTree) {
|
||||
return;
|
||||
|
@ -35,9 +35,7 @@ import {
|
||||
replaceNodesMatching,
|
||||
} from "@silverbulletmd/common/tree";
|
||||
import { applyQuery, QueryProviderEvent } from "../query/engine";
|
||||
import { PageMeta } from "@silverbulletmd/common/types";
|
||||
import { extractMeta } from "../query/data";
|
||||
import { jsonToMDTable } from "../query/util";
|
||||
|
||||
// Key space:
|
||||
// pl:toPage:pos => pageName
|
||||
@ -101,8 +99,8 @@ export async function linkQueryProvider({
|
||||
|
||||
export async function deletePage() {
|
||||
let pageName = await getCurrentPage();
|
||||
console.log("Navigating to start page");
|
||||
await navigate("start");
|
||||
console.log("Navigating to index page");
|
||||
await navigate("index");
|
||||
console.log("Deleting page from space");
|
||||
await deletePageSyscall(pageName);
|
||||
}
|
||||
|
69
packages/plugs/core/plugmanager.ts
Normal file
69
packages/plugs/core/plugmanager.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { dispatch } from "@plugos/plugos-syscall/event";
|
||||
import { findNodeOfType } from "@silverbulletmd/common/tree";
|
||||
import { flashNotification } from "@silverbulletmd/plugos-silverbullet-syscall/editor";
|
||||
import { parseMarkdown } from "@silverbulletmd/plugos-silverbullet-syscall/markdown";
|
||||
import {
|
||||
deletePage,
|
||||
listPages,
|
||||
readPage,
|
||||
writePage,
|
||||
} from "@silverbulletmd/plugos-silverbullet-syscall/space";
|
||||
import {
|
||||
invokeFunction,
|
||||
reloadPlugs,
|
||||
} from "@silverbulletmd/plugos-silverbullet-syscall/system";
|
||||
import YAML from "yaml";
|
||||
|
||||
async function listPlugs(): Promise<string[]> {
|
||||
let unfilteredPages = await listPages(true);
|
||||
return unfilteredPages
|
||||
.filter((p) => p.name.startsWith("_plug/"))
|
||||
.map((p) => p.name.substring("_plug/".length));
|
||||
}
|
||||
|
||||
export async function updatePlugsCommand() {
|
||||
flashNotification("Updating plugs...");
|
||||
await invokeFunction("server", "updatePlugs");
|
||||
flashNotification("And... done!");
|
||||
await reloadPlugs();
|
||||
}
|
||||
|
||||
export async function updatePlugs() {
|
||||
let { text: plugPageText } = await readPage("PLUGS");
|
||||
|
||||
let tree = await parseMarkdown(plugPageText);
|
||||
|
||||
let codeTextNode = findNodeOfType(tree, "CodeText");
|
||||
if (!codeTextNode) {
|
||||
console.error("Could not find yaml block in PLUGS");
|
||||
return;
|
||||
}
|
||||
let plugYaml = codeTextNode.children![0].text;
|
||||
let plugList = YAML.parse(plugYaml!);
|
||||
console.log("Plug YAML", plugList);
|
||||
let allPlugNames: string[] = [];
|
||||
for (let plugUri of plugList) {
|
||||
let [protocol, ...rest] = plugUri.split(":");
|
||||
let manifests = await dispatch(`get-plug:${protocol}`, rest.join(":"));
|
||||
if (manifests.length === 0) {
|
||||
console.error("Could not resolve plug", plugUri);
|
||||
}
|
||||
// console.log("Got manifests", plugUri, protocol, manifests);
|
||||
let manifest = manifests[0];
|
||||
allPlugNames.push(manifest.name);
|
||||
// console.log("Writing", `_plug/${manifest.name}`);
|
||||
await writePage(
|
||||
`_plug/${manifest.name}`,
|
||||
JSON.stringify(manifest, null, 2)
|
||||
);
|
||||
}
|
||||
|
||||
// And delete extra ones
|
||||
for (let existingPlug of await listPlugs()) {
|
||||
if (!allPlugNames.includes(existingPlug)) {
|
||||
console.log("Removing plug", existingPlug);
|
||||
await deletePage(`_plug/${existingPlug}`);
|
||||
}
|
||||
}
|
||||
await reloadPlugs();
|
||||
}
|
@ -1,4 +1,8 @@
|
||||
import { fullTextIndex, fullTextSearch } from "@plugos/plugos-syscall/fulltext";
|
||||
import {
|
||||
fullTextDelete,
|
||||
fullTextIndex,
|
||||
fullTextSearch,
|
||||
} from "@plugos/plugos-syscall/fulltext";
|
||||
import { renderToText } from "@silverbulletmd/common/tree";
|
||||
import { PageMeta } from "@silverbulletmd/common/types";
|
||||
import { queryPrefix } from "@silverbulletmd/plugos-silverbullet-syscall";
|
||||
@ -16,6 +20,10 @@ export async function index(data: IndexTreeEvent) {
|
||||
await fullTextIndex(data.name, cleanText);
|
||||
}
|
||||
|
||||
export async function unindex(pageName: string) {
|
||||
await fullTextDelete(pageName);
|
||||
}
|
||||
|
||||
export async function queryProvider({
|
||||
query,
|
||||
}: QueryProviderEvent): Promise<any[]> {
|
@ -1,12 +0,0 @@
|
||||
import {
|
||||
EndpointRequest,
|
||||
EndpointResponse,
|
||||
} from "@plugos/plugos/hooks/endpoint";
|
||||
|
||||
export function endpointTest(req: EndpointRequest): EndpointResponse {
|
||||
console.log("I'm running on the server!", req);
|
||||
return {
|
||||
status: 200,
|
||||
body: "Hello world!",
|
||||
};
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
function countWords(str: string): number {
|
||||
const matches = str.match(/[\w\d\'-]+/gi);
|
||||
return matches ? matches.length : 0;
|
||||
}
|
||||
|
||||
function readingTime(wordCount: number): number {
|
||||
// 225 is average word reading speed for adults
|
||||
return Math.ceil(wordCount / 225);
|
||||
}
|
@ -2,8 +2,6 @@
|
||||
import emojis from "./emoji.json";
|
||||
import { matchBefore } from "@silverbulletmd/plugos-silverbullet-syscall/editor";
|
||||
|
||||
const emojiMatcher = /\(([^\)]+)\)\s+(.+)$/;
|
||||
|
||||
export async function emojiCompleter() {
|
||||
let prefix = await matchBefore(":[\\w]+");
|
||||
if (!prefix) {
|
||||
|
@ -1,12 +1,5 @@
|
||||
name: ghost
|
||||
functions:
|
||||
downloadAllPostsCommand:
|
||||
path: "./ghost.ts:downloadAllPostsCommand"
|
||||
command:
|
||||
name: "Ghost: Download Posts"
|
||||
downloadAllPosts:
|
||||
path: "./ghost.ts:downloadAllPosts"
|
||||
env: server
|
||||
publishCommand:
|
||||
path: "./ghost.ts:publishCommand"
|
||||
command:
|
||||
|
@ -1,7 +1,4 @@
|
||||
import {
|
||||
readPage,
|
||||
writePage,
|
||||
} from "@silverbulletmd/plugos-silverbullet-syscall/space";
|
||||
import { readPage } from "@silverbulletmd/plugos-silverbullet-syscall/space";
|
||||
import { invokeFunction } from "@silverbulletmd/plugos-silverbullet-syscall/system";
|
||||
import {
|
||||
getCurrentPage,
|
||||
@ -195,21 +192,6 @@ async function getConfig(): Promise<GhostConfig> {
|
||||
return pageMeta as GhostConfig;
|
||||
}
|
||||
|
||||
export async function downloadAllPostsCommand() {
|
||||
await invokeFunction("server", "downloadAllPosts");
|
||||
}
|
||||
|
||||
export async function downloadAllPosts() {
|
||||
let config = await getConfig();
|
||||
let admin = new GhostAdmin(config.url, config.adminKey);
|
||||
await admin.init();
|
||||
let allPosts = await admin.listMarkdownPosts();
|
||||
for (let post of allPosts) {
|
||||
let text = mobileDocToMarkdown(post.mobiledoc);
|
||||
text = `# ${post.title}\n${text}`;
|
||||
await writePage(`${config.postPrefix}/${post.slug}`, text);
|
||||
}
|
||||
}
|
||||
export async function publishCommand() {
|
||||
await invokeFunction(
|
||||
"server",
|
||||
|
@ -7,7 +7,6 @@ export async function togglePreview() {
|
||||
await clientStore.set("enableMarkdownPreview", !currentValue);
|
||||
if (!currentValue) {
|
||||
await invokeFunction("client", "preview");
|
||||
// updateMarkdownPreview();
|
||||
} else {
|
||||
await hideMarkdownPreview();
|
||||
}
|
||||
|
4
packages/plugs/package-lock.json
generated
4
packages/plugs/package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@silverbulletmd/plugs",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.7",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@silverbulletmd/plugs",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.7",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jest/globals": "^27.5.1",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"name": "Zef Hemel",
|
||||
"email": "zef@zef.me"
|
||||
},
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.7",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"generate": "lezer-generator query/query.grammar -o query/parse-query.js",
|
||||
@ -34,13 +34,17 @@
|
||||
"@jest/globals": "^27.5.1",
|
||||
"@lezer/generator": "1.0.0",
|
||||
"@lezer/lr": "1.0.0",
|
||||
"@mattermost/client": "^6.7.0-0",
|
||||
"@mattermost/types": "^6.7.0-0",
|
||||
"@mattermost/client": "^7.0.0",
|
||||
"@mattermost/types": "^7.0.0",
|
||||
"@silverbulletmd/common": "^0.0.7",
|
||||
"@silverbulletmd/plugs": "^0.0.7",
|
||||
"@silverbulletmd/web": "^0.0.7",
|
||||
"@types/yaml": "^1.9.7",
|
||||
"handlebars": "^4.7.7",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-task-lists": "^2.1.1",
|
||||
"yaml": "^2.0.0",
|
||||
"handlebars": "^4.7.7"
|
||||
"server": "^1.0.37",
|
||||
"yaml": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/markdown-it": "^12.2.3"
|
||||
|
@ -1,33 +0,0 @@
|
||||
name: plugmanager
|
||||
functions:
|
||||
updatePlugsCommand:
|
||||
path: ./plugmanager.ts:updatePlugsCommand
|
||||
command:
|
||||
name: "Plugs: Update"
|
||||
key: "Ctrl-Shift-p"
|
||||
mac: "Cmd-Shift-p"
|
||||
updatePlugs:
|
||||
path: ./plugmanager.ts:updatePlugs
|
||||
env: server
|
||||
check:
|
||||
path: "./plugmanager.ts:checkCommand"
|
||||
command:
|
||||
name: "Plug: Check"
|
||||
mac: "Cmd-Alt-c"
|
||||
key: "Ctrl-Alt-c"
|
||||
compile:
|
||||
path: "./plugmanager.ts:compileCommand"
|
||||
command:
|
||||
name: "Plug: Compile"
|
||||
mac: "Cmd-Shift-c"
|
||||
key: "Ctrl-Shift-c"
|
||||
compileJS:
|
||||
path: "./plugmanager.ts:compileJS"
|
||||
env: server
|
||||
compileModule:
|
||||
path: "./plugmanager.ts:compileModule"
|
||||
env: server
|
||||
getPlugPlugMd:
|
||||
path: "./plugmanager.ts:getPlugPlugMd"
|
||||
events:
|
||||
- get-plug:plugmd
|
24
packages/plugs/plugmd/plugmd.plug.yaml
Normal file
24
packages/plugs/plugmd/plugmd.plug.yaml
Normal file
@ -0,0 +1,24 @@
|
||||
name: plugmd
|
||||
functions:
|
||||
check:
|
||||
path: "./plugmd.ts:checkCommand"
|
||||
command:
|
||||
name: "Plug: Check"
|
||||
mac: "Cmd-Alt-c"
|
||||
key: "Ctrl-Alt-c"
|
||||
compile:
|
||||
path: "./plugmd.ts:compileCommand"
|
||||
command:
|
||||
name: "Plug: Compile"
|
||||
mac: "Cmd-Shift-c"
|
||||
key: "Ctrl-Shift-c"
|
||||
compileJS:
|
||||
path: "./plugmd.ts:compileJS"
|
||||
env: server
|
||||
compileModule:
|
||||
path: "./plugmd.ts:compileModule"
|
||||
env: server
|
||||
getPlugPlugMd:
|
||||
path: "./plugmd.ts:getPlugPlugMd"
|
||||
events:
|
||||
- get-plug:plugmd
|
@ -1,19 +1,14 @@
|
||||
import { dispatch } from "@plugos/plugos-syscall/event";
|
||||
import {
|
||||
addParentPointers,
|
||||
collectNodesOfType,
|
||||
findNodeOfType,
|
||||
} from "@silverbulletmd/common/tree";
|
||||
import {
|
||||
flashNotification,
|
||||
getText,
|
||||
hideBhs,
|
||||
showBhs,
|
||||
} from "@silverbulletmd/plugos-silverbullet-syscall/editor";
|
||||
import { parseMarkdown } from "@silverbulletmd/plugos-silverbullet-syscall/markdown";
|
||||
import {
|
||||
deletePage,
|
||||
listPages,
|
||||
readPage,
|
||||
writePage,
|
||||
} from "@silverbulletmd/plugos-silverbullet-syscall/space";
|
||||
@ -23,8 +18,6 @@ import {
|
||||
} from "@silverbulletmd/plugos-silverbullet-syscall/system";
|
||||
import YAML from "yaml";
|
||||
|
||||
import { extractMeta } from "../query/data";
|
||||
|
||||
import type { Manifest } from "@silverbulletmd/common/manifest";
|
||||
|
||||
export async function compileCommand() {
|
||||
@ -131,64 +124,6 @@ export async function compileModule(moduleName: string): Promise<string> {
|
||||
return self.syscall("esbuild.compileModule", moduleName);
|
||||
}
|
||||
|
||||
async function listPlugs(): Promise<string[]> {
|
||||
let unfilteredPages = await listPages(true);
|
||||
return unfilteredPages
|
||||
.filter((p) => p.name.startsWith("_plug/"))
|
||||
.map((p) => p.name.substring("_plug/".length));
|
||||
}
|
||||
|
||||
export async function listCommand() {
|
||||
console.log(await listPlugs());
|
||||
}
|
||||
|
||||
export async function updatePlugsCommand() {
|
||||
flashNotification("Updating plugs...");
|
||||
await invokeFunction("server", "updatePlugs");
|
||||
flashNotification("And... done!");
|
||||
await reloadPlugs();
|
||||
}
|
||||
|
||||
export async function updatePlugs() {
|
||||
let { text: plugPageText } = await readPage("PLUGS");
|
||||
|
||||
let tree = await parseMarkdown(plugPageText);
|
||||
|
||||
let codeTextNode = findNodeOfType(tree, "CodeText");
|
||||
if (!codeTextNode) {
|
||||
console.error("Could not find yaml block in PLUGS");
|
||||
return;
|
||||
}
|
||||
let plugYaml = codeTextNode.children![0].text;
|
||||
let plugList = YAML.parse(plugYaml!);
|
||||
console.log("Plug YAML", plugList);
|
||||
let allPlugNames: string[] = [];
|
||||
for (let plugUri of plugList) {
|
||||
let [protocol, ...rest] = plugUri.split(":");
|
||||
let manifests = await dispatch(`get-plug:${protocol}`, rest.join(":"));
|
||||
if (manifests.length === 0) {
|
||||
console.error("Could not resolve plug", plugUri);
|
||||
}
|
||||
// console.log("Got manifests", plugUri, protocol, manifests);
|
||||
let manifest = manifests[0];
|
||||
allPlugNames.push(manifest.name);
|
||||
// console.log("Writing", `_plug/${manifest.name}`);
|
||||
await writePage(
|
||||
`_plug/${manifest.name}`,
|
||||
JSON.stringify(manifest, null, 2)
|
||||
);
|
||||
}
|
||||
|
||||
// And delete extra ones
|
||||
for (let existingPlug of await listPlugs()) {
|
||||
if (!allPlugNames.includes(existingPlug)) {
|
||||
console.log("Removing plug", existingPlug);
|
||||
await deletePage(`_plug/${existingPlug}`);
|
||||
}
|
||||
}
|
||||
await reloadPlugs();
|
||||
}
|
||||
|
||||
export async function getPlugPlugMd(pageName: string): Promise<Manifest> {
|
||||
let { text } = await readPage(pageName);
|
||||
console.log("Compiling", pageName);
|
@ -1,7 +1,4 @@
|
||||
name: query
|
||||
# dependencies:
|
||||
# yaml: "yaml@2"
|
||||
# "@lezer/lr": "@lezer/lr@0.15.4"
|
||||
functions:
|
||||
updateMaterializedQueriesOnPage:
|
||||
path: ./materialized_queries.ts:updateMaterializedQueriesOnPage
|
||||
|
@ -1,26 +0,0 @@
|
||||
name: search
|
||||
functions:
|
||||
index:
|
||||
path: ./search.ts:index
|
||||
events:
|
||||
- page:index
|
||||
queryProvider:
|
||||
path: ./search.ts:queryProvider
|
||||
events:
|
||||
- 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
|
@ -41,7 +41,7 @@ functions:
|
||||
taskPostponeCommand:
|
||||
path: ./task.ts:postponeCommand
|
||||
command:
|
||||
name: "Task: Postpone by 1 day"
|
||||
name: "Task: Postpone"
|
||||
key: Alt-+
|
||||
contexts:
|
||||
- DeadlineDate
|
||||
|
@ -57,7 +57,7 @@ export type ServerOptions = {
|
||||
pagesPath: string;
|
||||
distDir: string;
|
||||
builtinPlugDir: string;
|
||||
token?: string;
|
||||
password?: string;
|
||||
};
|
||||
export class ExpressServer {
|
||||
app: Express;
|
||||
@ -69,21 +69,27 @@ export class ExpressServer {
|
||||
private port: number;
|
||||
private server?: Server;
|
||||
builtinPlugDir: string;
|
||||
token?: string;
|
||||
password?: string;
|
||||
|
||||
constructor(options: ServerOptions) {
|
||||
this.port = options.port;
|
||||
this.app = express();
|
||||
this.builtinPlugDir = options.builtinPlugDir;
|
||||
this.distDir = options.distDir;
|
||||
this.system = new System<SilverBulletHooks>("server");
|
||||
this.token = options.token;
|
||||
this.password = options.password;
|
||||
|
||||
// Setup system
|
||||
// Set up the PlugOS System
|
||||
this.system = new System<SilverBulletHooks>("server");
|
||||
|
||||
// Instantiate the event bus hook
|
||||
this.eventHook = new EventHook();
|
||||
this.system.addHook(this.eventHook);
|
||||
|
||||
// And the page namespace hook
|
||||
let namespaceHook = new PageNamespaceHook();
|
||||
this.system.addHook(namespaceHook);
|
||||
|
||||
// The space
|
||||
this.space = new Space(
|
||||
new EventedSpacePrimitives(
|
||||
new PlugSpacePrimitives(
|
||||
@ -94,6 +100,8 @@ export class ExpressServer {
|
||||
),
|
||||
true
|
||||
);
|
||||
|
||||
// The database used for persistence (SQLite)
|
||||
this.db = knex({
|
||||
client: "better-sqlite3",
|
||||
connection: {
|
||||
@ -102,9 +110,11 @@ export class ExpressServer {
|
||||
useNullAsDefault: true,
|
||||
});
|
||||
|
||||
this.system.registerSyscalls(["shell"], shellSyscalls(options.pagesPath));
|
||||
// The cron hook
|
||||
this.system.addHook(new NodeCronHook());
|
||||
|
||||
// Register syscalls available on the server sid
|
||||
this.system.registerSyscalls(["shell"], shellSyscalls(options.pagesPath));
|
||||
this.system.registerSyscalls(
|
||||
[],
|
||||
pageIndexSyscalls(this.db),
|
||||
@ -117,10 +127,13 @@ export class ExpressServer {
|
||||
sandboxSyscalls(this.system),
|
||||
jwtSyscalls()
|
||||
);
|
||||
|
||||
// Register the HTTP endpoint hook (with "/_/<plug-name>"" prefix, hardcoded for now)
|
||||
this.system.addHook(new EndpointHook(this.app, "/_"));
|
||||
|
||||
this.system.on({
|
||||
plugLoaded: (plug) => {
|
||||
// Automatically inject some modules into each plug
|
||||
safeRun(async () => {
|
||||
for (let [modName, code] of Object.entries(
|
||||
globalModules.dependencies
|
||||
@ -131,6 +144,8 @@ export class ExpressServer {
|
||||
},
|
||||
});
|
||||
|
||||
// Hook into some "get-plug:" to allow loading plugs from disk (security of this TBD)
|
||||
// First, for builtins (loaded from the packages/plugs/ folder)
|
||||
this.eventHook.addLocalListener(
|
||||
"get-plug:builtin",
|
||||
async (plugName: string): Promise<Manifest> => {
|
||||
@ -149,6 +164,7 @@ export class ExpressServer {
|
||||
}
|
||||
);
|
||||
|
||||
// Second, for loading plug JSON files with absolute or relative (from CWD) paths
|
||||
this.eventHook.addLocalListener(
|
||||
"get-plug:file",
|
||||
async (plugPath: string): Promise<Manifest> => {
|
||||
@ -169,9 +185,12 @@ export class ExpressServer {
|
||||
}
|
||||
);
|
||||
|
||||
// Rescan disk every 5s to detect any out-of-process file changes
|
||||
setInterval(() => {
|
||||
this.space.updatePageList().catch(console.error);
|
||||
}, 5000);
|
||||
|
||||
// Load plugs
|
||||
this.reloadPlugs().catch(console.error);
|
||||
}
|
||||
|
||||
@ -182,6 +201,7 @@ export class ExpressServer {
|
||||
);
|
||||
}
|
||||
|
||||
// In case of a new space with no `PLUGS` file, generate a default one based on all built-in plugs
|
||||
private async bootstrapBuiltinPlugs() {
|
||||
let allPlugFiles = await readdir(this.builtinPlugDir);
|
||||
let pluginNames = [];
|
||||
@ -225,9 +245,10 @@ export class ExpressServer {
|
||||
}
|
||||
|
||||
async start() {
|
||||
const tokenMiddleware: (req: any, res: any, next: any) => void = this.token
|
||||
const passwordMiddleware: (req: any, res: any, next: any) => void = this
|
||||
.password
|
||||
? (req, res, next) => {
|
||||
if (req.headers.authorization === `Bearer ${this.token}`) {
|
||||
if (req.headers.authorization === `Bearer ${this.password}`) {
|
||||
next();
|
||||
} else {
|
||||
res.status(401).send("Unauthorized");
|
||||
@ -239,6 +260,7 @@ export class ExpressServer {
|
||||
|
||||
await ensureTable(this.db);
|
||||
await ensureFTSTable(this.db, "fts");
|
||||
await this.ensureIndexPage();
|
||||
console.log("Setting up router");
|
||||
|
||||
let auth = new Authenticator(this.db);
|
||||
@ -328,7 +350,7 @@ export class ExpressServer {
|
||||
|
||||
this.app.use(
|
||||
"/fs",
|
||||
tokenMiddleware,
|
||||
passwordMiddleware,
|
||||
cors({
|
||||
methods: "GET,HEAD,PUT,OPTIONS,POST,DELETE",
|
||||
preflightContinue: true,
|
||||
@ -393,7 +415,7 @@ export class ExpressServer {
|
||||
|
||||
this.app.use(
|
||||
"/plug",
|
||||
tokenMiddleware,
|
||||
passwordMiddleware,
|
||||
cors({
|
||||
methods: "GET,HEAD,PUT,OPTIONS,POST,DELETE",
|
||||
preflightContinue: true,
|
||||
@ -412,6 +434,14 @@ export class ExpressServer {
|
||||
});
|
||||
}
|
||||
|
||||
async ensureIndexPage() {
|
||||
try {
|
||||
await this.space.getPageMeta("index");
|
||||
} catch (e) {
|
||||
await this.space.writePage("index", `Welcome to your new space!`);
|
||||
}
|
||||
}
|
||||
|
||||
async stop() {
|
||||
if (this.server) {
|
||||
console.log("Stopping");
|
||||
|
@ -4,7 +4,7 @@
|
||||
"name": "Zef Hemel",
|
||||
"email": "zef@zef.me"
|
||||
},
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.7",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"silverbullet": "./dist/server/server.js"
|
||||
@ -41,11 +41,15 @@
|
||||
"@codemirror/legacy-modes": "6.0.0",
|
||||
"@jest/globals": "^27.5.1",
|
||||
"@lezer/markdown": "1.0.0",
|
||||
"@silverbulletmd/common": "^0.0.7",
|
||||
"@silverbulletmd/plugs": "^0.0.7",
|
||||
"@silverbulletmd/web": "^0.0.7",
|
||||
"better-sqlite3": "^7.5.0",
|
||||
"body-parser": "^1.19.2",
|
||||
"buffer": "^6.0.3",
|
||||
"cors": "^2.8.5",
|
||||
"dexie": "^3.2.1",
|
||||
"esbuild": "^0.14.27",
|
||||
"events": "^3.3.0",
|
||||
"express": "^4.17.3",
|
||||
"fake-indexeddb": "^3.1.7",
|
||||
@ -56,6 +60,7 @@
|
||||
"node-fetch": "2",
|
||||
"node-watch": "^0.7.3",
|
||||
"nodemon": "^2.0.15",
|
||||
"server": "^1.0.37",
|
||||
"uuid": "^8.3.2",
|
||||
"vm2": "^3.9.9",
|
||||
"ws": "^8.5.0",
|
||||
|
@ -11,14 +11,14 @@ let args = yargs(hideBin(process.argv))
|
||||
type: "number",
|
||||
default: 3000,
|
||||
})
|
||||
.option("token", {
|
||||
.option("password", {
|
||||
type: "string",
|
||||
})
|
||||
.parse();
|
||||
|
||||
if (!args._.length) {
|
||||
console.error(
|
||||
"Usage: silverbullet [--port 3000] [--token mysecrettoken] <path-to-pages>"
|
||||
"Usage: silverbullet [--port 3000] [--password mysecretpassword] <path-to-pages>"
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
@ -40,7 +40,7 @@ const expressServer = new ExpressServer({
|
||||
pagesPath: pagesPath,
|
||||
distDir: webappDistDir,
|
||||
builtinPlugDir: plugDistDir,
|
||||
token: args.token,
|
||||
password: args.password,
|
||||
});
|
||||
expressServer.start().catch((e) => {
|
||||
console.error(e);
|
||||
|
@ -6,22 +6,23 @@ import { HttpSpacePrimitives } from "@silverbulletmd/common/spaces/http_space_pr
|
||||
safeRun(async () => {
|
||||
// let localSpace = new Space(new IndexedDBSpacePrimitives("pages"), true);
|
||||
// localSpace.watch();
|
||||
let token: string | undefined = localStorage.getItem("token") || undefined;
|
||||
let password: string | undefined =
|
||||
localStorage.getItem("password") || undefined;
|
||||
|
||||
let httpPrimitives = new HttpSpacePrimitives("", token);
|
||||
let httpPrimitives = new HttpSpacePrimitives("", password);
|
||||
while (true) {
|
||||
try {
|
||||
await httpPrimitives.getPageMeta("start");
|
||||
await httpPrimitives.getPageMeta("index");
|
||||
break;
|
||||
} catch (e: any) {
|
||||
if (e.message === "Unauthorized") {
|
||||
token = prompt("Token: ") || undefined;
|
||||
if (!token) {
|
||||
alert("Sorry, that's it then");
|
||||
password = prompt("Password: ") || undefined;
|
||||
if (!password) {
|
||||
alert("Sorry, need a password");
|
||||
return;
|
||||
}
|
||||
localStorage.setItem("token", token!);
|
||||
httpPrimitives = new HttpSpacePrimitives("", token);
|
||||
localStorage.setItem("password", password!);
|
||||
httpPrimitives = new HttpSpacePrimitives("", password);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
60
packages/web/commands.ts
Normal file
60
packages/web/commands.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { EditorSelection, StateCommand, Transaction } from "@codemirror/state";
|
||||
import { Text } from "@codemirror/state";
|
||||
|
||||
export function insertMarker(marker: string): StateCommand {
|
||||
return ({ state, dispatch }) => {
|
||||
const changes = state.changeByRange((range) => {
|
||||
const isBoldBefore =
|
||||
state.sliceDoc(range.from - marker.length, range.from) === marker;
|
||||
const isBoldAfter =
|
||||
state.sliceDoc(range.to, range.to + marker.length) === marker;
|
||||
const changes = [];
|
||||
|
||||
changes.push(
|
||||
isBoldBefore
|
||||
? {
|
||||
from: range.from - marker.length,
|
||||
to: range.from,
|
||||
insert: Text.of([""]),
|
||||
}
|
||||
: {
|
||||
from: range.from,
|
||||
insert: Text.of([marker]),
|
||||
}
|
||||
);
|
||||
|
||||
changes.push(
|
||||
isBoldAfter
|
||||
? {
|
||||
from: range.to,
|
||||
to: range.to + marker.length,
|
||||
insert: Text.of([""]),
|
||||
}
|
||||
: {
|
||||
from: range.to,
|
||||
insert: Text.of([marker]),
|
||||
}
|
||||
);
|
||||
|
||||
const extendBefore = isBoldBefore ? -marker.length : marker.length;
|
||||
const extendAfter = isBoldAfter ? -marker.length : marker.length;
|
||||
|
||||
return {
|
||||
changes,
|
||||
range: EditorSelection.range(
|
||||
range.from + extendBefore,
|
||||
range.to + extendAfter
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
dispatch(
|
||||
state.update(changes, {
|
||||
scrollIntoView: true,
|
||||
annotations: Transaction.userEvent.of("input"),
|
||||
})
|
||||
);
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
@ -221,7 +221,7 @@ export class Editor {
|
||||
});
|
||||
|
||||
if (this.pageNavigator.getCurrentPage() === "") {
|
||||
await this.pageNavigator.navigate("start");
|
||||
await this.pageNavigator.navigate("index");
|
||||
}
|
||||
await this.reloadPlugs();
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
"name": "Zef Hemel",
|
||||
"email": "zef@zef.me"
|
||||
},
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.7",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"watch": "rm -rf .parcel-cache && parcel watch",
|
||||
@ -41,12 +41,16 @@
|
||||
"@jest/globals": "^27.5.1",
|
||||
"@lezer/highlight": "1.0.0",
|
||||
"@lezer/markdown": "1.0.0",
|
||||
"@silverbulletmd/common": "^0.0.7",
|
||||
"@silverbulletmd/plugs": "^0.0.7",
|
||||
"@silverbulletmd/web": "^0.0.7",
|
||||
"fake-indexeddb": "^3.1.7",
|
||||
"fuzzysort": "^1.9.0",
|
||||
"jest": "^27.5.1",
|
||||
"knex": "^1.0.4",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"react-dom": "^17.0.2",
|
||||
"server": "^1.0.37"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@parcel/packager-raw-url": "2.5.0",
|
||||
|
@ -21,9 +21,9 @@ export function spaceSyscalls(editor: Editor): SysCallMapping {
|
||||
return await editor.space.writePage(name, text);
|
||||
},
|
||||
"space.deletePage": async (ctx, name: string) => {
|
||||
// If we're deleting the current page, navigate to the start page
|
||||
// If we're deleting the current page, navigate to the index page
|
||||
if (editor.currentPage === name) {
|
||||
await editor.navigate("start");
|
||||
await editor.navigate("index");
|
||||
}
|
||||
// Remove page from open pages in editor
|
||||
editor.openPages.delete(name);
|
||||
|
7
scripts/release.sh
Executable file
7
scripts/release.sh
Executable file
@ -0,0 +1,7 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
VERSION=$1
|
||||
npm version --ws $VERSION || true
|
||||
npm install --ws server --save @silverbulletmd/web@$VERSION @silverbulletmd/plugs@$VERSION @silverbulletmd/common@$VERSION
|
||||
npm run clean-build
|
||||
npm run publish-all
|
Loading…
Reference in New Issue
Block a user