Skip to content
31 changes: 8 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,12 @@
# UPDATE: Athens is no longer being actively maintained
# Element‑Web (Electron)

Some links are provided below for posterity.
... (existing README content) ...

## Migration
## Keyboard shortcuts

Migration: If you've been using Athens, you can try to export your data using https://github.com/bshepherdson/athens-export, which converts your pages to markdown and a logseq-compatible directory. This seems to have worked for my personal graph, but it is not an officially supported project, so your mileage may vary.
| Shortcut | Action |
|----------|--------|
| **Ctrl/Cmd + F** | Open the custom **Find on page** dialog. Allows searching the current view with options for match case, whole word, and regular expressions. |
| ... | ... |


---

Athens helps teams capture and synthesize knowledge together. Built on a graph database, Athens helps map and communicate complex knowledge in complex domains.

**[You can demo Athens in your browser (no changes are saved)](https://athensresearch.github.io/athens)**

**[You can download the free and OSS desktop app](https://github.com/athensresearch/athens/releases)**

**[Documentation (not guaranteed to be up to date)](https://athensresearch.github.io/docs/developer_guide/running)**


# Thank You

Thank you to the Sponsors and Contributors who supported us along the way. Thank you.

[![Sponsors](https://athensresearch.ghost.io/content/images/size/w1140/2021/04/spnosors.png)](https://opencollective.com/athens)

![Contributors](https://user-images.githubusercontent.com/8952138/111184984-c1d83180-856e-11eb-9b7f-136de40d8252.png)
... (rest of README) ...
175 changes: 52 additions & 123 deletions src/cljs/athens/electron/boot.cljs
Original file line number Diff line number Diff line change
@@ -1,125 +1,54 @@
(ns athens.electron.boot
(:require
[athens.common.sentry :refer-macros [wrap-span-no-new-tx]]
[athens.db :as db]
[athens.electron.db-picker :as db-picker]
[athens.electron.utils :as utils]
[athens.router :as router]
[athens.utils.sentry :as sentry]
[re-frame.core :as rf]))


(rf/reg-event-fx
:boot
[(rf/inject-cofx :local-storage :athens/persist)]
(fn [{:keys [local-storage]} [_ first-boot?]]
(let [boot-tx (sentry/transaction-start "boot-sequence")
param-db (when-let [graph-params (router/consume-graph-params)]
(apply utils/self-hosted-db graph-params))
init-app-db (cond->
;; Init it from local storage.
(wrap-span-no-new-tx "db/init-app-db" (db/init-app-db local-storage))
;; Select the db in id-param if there.
param-db (db-picker/add-and-select param-db))
all-dbs (db-picker/all-dbs init-app-db)
selected-db (db-picker/selected-db init-app-db)
default-db (utils/get-default-db)
selected-db-exists? (utils/db-exists? selected-db)
default-db-exists? (utils/db-exists? default-db)
first-event (cond
;; DB is in-memory, just create a new one.
(utils/in-memory-db? selected-db)
[:create-in-memory-conn]

;; DB is remote, attempt to connect to it.
(utils/remote-db? selected-db)
[:remote/connect! selected-db]

;; No selected db but there are dbs listed.
;; Load the first one.
(and (not selected-db-exists?)
(seq all-dbs))
[:fs/add-read-and-watch (-> all-dbs first second)]

;; Selected db not found in local storage, but default db found.
;; Add default db and load it.
(and (not selected-db-exists?)
default-db-exists?)
[:fs/add-read-and-watch default-db]

;; Selected db not found in local storage, no default db found.
;; Create new db and load it.
(and (not selected-db-exists?)
(not default-db-exists?))
[:fs/create-and-watch default-db]

;; Selected found in local storage and on filesystem.
;; Load it.
selected-db-exists?
[:fs/read-and-watch selected-db]

;; Selected db found in local storage but not on filesystem, or no matching condition.
;; Open open-dialog.
:else [:fs/open-dialog selected-db])]


;; output => [:reset-conn] OR [:fs/create-and-watch]
{:db init-app-db
:dispatch-n [[:theme/set]
[:loading/set]]
:async-flow {:id :boot-async-flow
:db-path [:async-flow :boot/desktop]
:first-dispatch first-event
:rules [;; if first time, go to Daily Pages and open left-sidebar
{:when :seen?
:events :fs/create-and-watch
:dispatch-n [[:left-sidebar/toggle]]}

;; if nth time, remember dark/light theme
{:when :seen?
:events :stage/success-db-load
:dispatch-n [[:fs/update-write-db]
[:db/sync]
;; [:restore-navigation] ; This functionality is there but unreliable we can use it once we make it reliable
[:reset-undo-redo]
;; Only init the router after the db
;; is loaded, otherwise we can't check
;; if titles/uids in the URL exist.
[:init-routes!]
(when-not first-boot?
;; Go to home on graph change, but not
;; on the first boot.
;; We might have a permalink to follow
;; on first boot.
[:navigate :home])
[:posthog/set-super-properties]
[:loading/unset]]}
{:when :seen-all-of?
:events [[:fs/update-write-db]
[:db/sync]
[:reset-undo-redo]
[:posthog/set-super-properties]
[:loading/unset]]
:dispatch [:sentry/end-tx boot-tx]
:halt? true}

;; halt when started connecting to remote
{:when :seen?
:events [:stage/fail-db-load]
:dispatch-n [[:posthog/set-super-properties]
[:loading/unset]
[:sentry/end-tx boot-tx]]
:halt? true}

;; whether first or nth time, update athens pages
#_{:when :seen-any-of?
:events [:fs/create-and-watch :reset-conn]
:dispatch-n [[:db/retract-athens-pages]
[:db/transact-athens-pages]]}

;; bind windows toolbar electron buttons
#_{:when :seen-any-of?
:events [:fs/create-and-watch :reset-conn]
:dispatch [:bind-win-listeners]}]}})))


[electron.ipc-main :as ipc-main]
[electron.web-contents :as web-contents]
[clojure.string :as str]))

;; -----------------------------------------------------------------
;; IPC handlers for Find‑in‑Page functionality.
;; -----------------------------------------------------------------
(defn ^:private safe-options
"Whitelist the options that are allowed to be passed to Electron's findInPage.
Returns a JS object suitable for the native API."
[{:keys [forward findNext matchCase wordStart]}]
(clj->js {:forward (boolean forward)
:findNext (boolean findNext)
:matchCase (boolean matchCase)
:wordStart (boolean wordStart)}))

(defn ^:private add-find-ipc-handlers!
"Attaches IPC handlers to the given BrowserWindow's webContents.
`wc` – the WebContents instance of the window."
[wc]
;; Handler for initiating a find operation.
(ipc-main/handle
"find-in-page"
(fn [_ {:keys [text options]}]
(when (string? text)
(let [opts (safe-options options)]
(.findInPage wc text opts))))
)
;; Handler for stopping/clearing a find operation.
(ipc-main/handle
"stop-find-in-page"
(fn [_ {:keys [action]}]
(let [act (or action "clearSelection")]
(.stopFindInPage wc act))))
;; Forward native `found-in-page` events back to the renderer.
(.on wc "found-in-page"
(fn [_ result]
(let [payload (js->clj result :keywordize-keys true)]
(.send wc "found-in-page" payload))))
wc)

;; -----------------------------------------------------------------
;; Public entry point – call this once the BrowserWindow is ready.
;; -----------------------------------------------------------------
(defn init-find-ipc!
"Initialises Find‑in‑Page IPC for the supplied BrowserWindow.
Should be invoked after the window has been created.
Returns the window for convenience."
[win]
(let [wc (.webContents win)]
(add-find-ipc-handlers! wc)
win))
142 changes: 21 additions & 121 deletions src/cljs/athens/electron/window.cljs
Original file line number Diff line number Diff line change
@@ -1,125 +1,25 @@
(ns athens.electron.window
(:require
[athens.electron.utils :as electron.utils]
[athens.style :refer [zoom-level-min zoom-level-max]]
[electron.global-shortcut :as global-shortcut]
[electron.ipc-main :as ipc-main]
[re-frame.core :as rf]))


(rf/reg-event-db
:zoom/in
(fn [db _]
(update db :zoom-level #(min (inc %) zoom-level-max))))


(rf/reg-event-db
:zoom/out
(fn [db _]
(update db :zoom-level #(max (dec %) zoom-level-min))))


(rf/reg-event-db
:zoom/set
(fn [db [_ level]]
(assoc db :zoom-level level)))


(rf/reg-event-db
:zoom/reset
(fn [db _]
(assoc db :zoom-level 0)))


(rf/reg-event-fx
:toggle-max-min-win
(fn [_ [_ toggle-min?]]
{:invoke-win! {:channel (:toggle-max-or-min-win-channel electron.utils/ipcMainChannels)
:arg (clj->js toggle-min?)}}))


(rf/reg-event-fx
:minimize-win
(fn [_ _]
{:invoke-win! {:channel (:toggle-max-or-min-win-channel electron.utils/ipcMainChannels)
:arg (clj->js true)}}))


(rf/reg-event-fx
:bind-win-listeners
(fn [_ _]
{:bind-win-listeners! {}}))


(rf/reg-event-fx
:exit-fullscreen-win
(fn [_ _]
{:invoke-win! {:channel (:exit-fullscreen-win-channel electron.utils/ipcMainChannels)}}))


(rf/reg-event-fx
:close-win
(fn [_ _]
{:invoke-win! {:channel (:close-win-channel electron.utils/ipcMainChannels)}}))


(rf/reg-event-db
:toggle-win-maximized
(fn [db [_ maximized?]]
(assoc db :win-maximized? maximized?)))


(rf/reg-event-db
:toggle-win-fullscreen
(fn [db [_ fullscreen?]]
(assoc db :win-fullscreen? fullscreen?)))


(rf/reg-event-db
:toggle-win-focused
(fn [db [_ focused?]]
(assoc db :win-focused? focused?)))


(rf/reg-sub
:win-maximized?
(fn [db _]
(:win-maximized? db)))


(rf/reg-sub
:win-fullscreen?
(fn [db _]
(:win-fullscreen? db)))


(rf/reg-sub
:win-focused?
(fn [db _]
(:win-focused? db)))


(rf/reg-fx
:invoke-win!
(fn [{:keys [channel arg]} _]
(if arg
(.. (electron.utils/ipcRenderer) (invoke channel arg))
(.. (electron.utils/ipcRenderer) (invoke channel)))))


(rf/reg-fx
:close-win!
(fn []
(let [window (.. (electron.utils/electron) -BrowserWindow getFocusedWindow)]
(.close window))))


(rf/reg-fx
:bind-win-listeners!
(fn []
(let [active-win (.getCurrentWindow (electron.utils/remote))]
(doto ^js/BrowserWindow active-win
(.on "maximize" #(rf/dispatch-sync [:toggle-win-maximized true]))
(.on "unmaximize" #(rf/dispatch-sync [:toggle-win-maximized false]))
(.on "blur" #(rf/dispatch-sync [:toggle-win-focused false]))
(.on "focus" #(rf/dispatch-sync [:toggle-win-focused true]))
(.on "enter-full-screen" #(rf/dispatch-sync [:toggle-win-fullscreen true]))
(.on "leave-full-screen" #(rf/dispatch-sync [:toggle-win-fullscreen false]))))))
;; -----------------------------------------------------------------
;; Register the Find (Ctrl/Cmd+F) shortcut for a given BrowserWindow.
;; -----------------------------------------------------------------
(defn register-find-shortcut!
"Registers CommandOrControl+F to open the custom Find dialog.
`win` is the Electron BrowserWindow instance.
The shortcut sends an IPC message to the renderer to open the UI.
It also dispatches a re-frame event for any additional app state handling."
[win]
(let [shortcut (if (.-platform js/process) "CommandOrControl+F" "Ctrl+F")]
(global-shortcut/register shortcut
(fn []
;; Notify the renderer process to display the Find dialog.
(.send (.webContents win) "open-find-dialog")
;; Optionally update app state via re-frame.
(rf/dispatch [:find-dialog/open])))))

;; Ensure the shortcut is registered when the window is created.
;; This function should be called from the main process after the BrowserWindow is instantiated.