This little webapp allows you to keep track of your bookmarks/TODO lists/whatever else you like, from any device you want (so long as you have Internet access). This is intended for personal use; if you share access with someone else, they'll have full edit access to whatever data they can see. The "private tags" feature can limit shared access but is not particularly intended for such things.
The app is made using ClojureScript and based on reagent
/re-frame
(ReactJS) libraries; the cloud
backend/storage/hosting is Google Firebase. Additionally, following libraries and utilities are used:
- BlueprintJS (UI components)
- jsSHA (passphrases handling)
- MarkedJS (markdown rendering)
- qrcode-generator (QR code rendering)
- shadow-cljs (webapp building)
- wisp (CLI scripts)
The app usage, as well as its main features, are described in this gallery.
For a TL;DR version (what the app looks like) you can check out screenshots at the bottom.
git
(to work with repository)yarn
(for dependencies/scripting)- CLI for devtools (optional, install via
yarn global add
ornpm -g
):shadow-cljs
(for building/REPL)firebase-tools
(for deployment to Firebase hosting)
- a Google (GMail) account (for Firebase backend); a free Firebase plan should be enough for regular app usage
- Clone or download the sources to your computer.
- Run
yarn
in the project root to install dependencies. - Set up your Firebase backend:
- create a project in Firebase WebUI
- go to project settings and register a webapp
- take app config from the Config section and save it as
firebase.json
in the project root (make sure it's valid JSON); this step can be skipped if you're going to deploy to Firebase hosting (file will be created duringfirebase init
run)
- take app config from the Config section and save it as
- in the Authentication section, enable Email/Password sign-in and add an account to log in as (external sign-in options aren't used to avoid requirement of logging in to any other website)
- in Database section, open Realtime Database and set up Rules:
{ "rules": { ".read": "auth.uid !== null", ".write": "auth.uid !== null", "urls": { ".indexOn": ["parent"] } } }
- Run
yarn watch
to start dev server (onlocalhost:3000
)- alternatively, run
yarn repl
to start up REPL (then(shadow/watch :app)
to start server, and(shadow/repl :app)
to connect)
- alternatively, run
- Run
yarn release
to produce release build
After successful run of yarn release
, the public/
folder will contain the release version of webapp.
To deploy it to Firebase backend, you'll need to install firebase-tools
(see prerequisites).
After that:
- run
firebase login
to access your Google account (in WebUI) - run
firebase init
to link Firebase project and set up the app (single-page = yes, overwrite index.html = no);firebase.json
will be overwritten - make sure you've run
yarn release
to produce a working build - run
firebase deploy
to upload the build
Now you can access the app from any linked URL (default ones are <project-id>.web.app
and <project-id>.firebaseapp.com
).
If you don't want to lose all of your data by accident (which shouldn't happen but who knows?), you can set up database backups.
To do so, you'll need to open the Firebase WebUI, open Service Accounts in project settings,
and download a private key token from Firebase SDK section (save it as firebase-token.json
).
Take note that if you lose the token file, you'll have to generate a new one.
Now you can download a database backup at any time using yarn backup
command. You can set up crontab (or any other scheduler)
to run it regularly. Traffic limit for free accounts is 10 Gb/month so daily syncs won't be a problem unless your data grows over 300 Mb.
Backups will be saved as backups/<UTC-ISO-datetime>.json
. You can restore them in Firebase WebUI (use Import JSON menu command
in Realtime Database root). Yes, you can do backups by hand (using Export JSON); the CLI command is for regular/scheduled backups.
Additionally, you can produce batch updates using yarn convert
(run without arguments to get usage help). Though that's likely won't
be needed unless you're changing the app sources. Still, here's an example of how to fix lost entries (if you somehow manage to
remove an entry with existing children):
yarn convert -o out.json '(fn [x k db] (if (get (:urls db) (:parent x)) x (l/assoc x :parent "")))'
app.core.to_blank(mode)
sets to-blank mode (whether url target
attribute is set to _blank
or not).
app.core.to_blank()
returns current to-blank mode value.
app.core.path()
returns data of current tree path (breadcrumbs including the current node).
app.core.path(idx)
returns data from specific path()
index (path(-1)
for current node).
app.core.edit_dialog()
opens edit dialog for current node (equivalent to edit button click).
app.core.edit_dialog({...data})
opens the edit dialog with updated data based on passed fields.