diff --git a/doc/ekg.org b/doc/ekg.org index 7835093..2ee3f1a 100644 --- a/doc/ekg.org +++ b/doc/ekg.org @@ -250,13 +250,14 @@ The note buffer is a buffer of the particular mode for the node, which can chang The properties shown in the note property area come from the data stored in the database for the entity. At a minimum, there will be tags. -For the common cases, there are special commands, with default keybindinds: +For the common cases, there are special commands, available under the =C-c /= prefix keymap: -- =ekg-note-add-tag= to add a tag (=C-c +=). -- =ekg-note-remove-tag= to remove a tag (=C-c -=). -- =ekg-note-add-title= to add a title (=C-c C-t +=). -- =ekg-note-remove-title= to remove a title (=C-c C-t -=). -- =ekg-note-edit-property= to edit any property (=C-c C-p=), including the "resource" of the note, which is its ID. For editing, multi-values properties are editable as a list with values separated by =", "=. Maintain the space after the comma when editing. +- =a=: =ekg-note-add-tag=, to add a tag. +- =d=: =ekg-note-remove-tag=, to remove a tag. +- =t=: =ekg-note-add-title=, to add a title. +- =T=: =ekg-note-remove-title=, to remove a title. +- =p=: =ekg-note-edit-property=, to edit any property, including the "resource" of the note, which is its ID. For editing, multi-values properties are editable as a list with values separated by =", "=. Maintain the space after the comma when editing. +- =i=: =ekg-edit-add-inline=, to add an inline command. When altering properties, nothing is saved until the note is saved. @@ -345,8 +346,7 @@ default, it will have the current date tag, such as "date/2023-02-21". a given title as the title of the page. The idea is that the note is annotating the reference itself as a "literature note". The title also appears as a tag, so other notes can reference this if needed. For example, if the URL is - http://example.com, and the title is "An example URL", the properties buffer - will have the following: + http://example.com, and the title is "An example URL", the properties will be: #+begin_quote Resource: http://example.com diff --git a/doc/ekg.texi b/doc/ekg.texi index 7bfa1f0..dc9f0bc 100644 --- a/doc/ekg.texi +++ b/doc/ekg.texi @@ -26,9 +26,1849 @@ @node Top @top ekg, the Emacs Knowledge Graph -note +author: Andrew Hyatt +@insertcopying + +This is the ekg manual, which describes the operation and customization of the +ekg package. All information here is relevant to the released version only. + +The README is also informative, has screenshots, and can be found in the source +and in the git repository, which is at @uref{https://github.com/ahyatt/ekg}. + @end ifnottex +@menu +* Introduction:: +* Installation:: +* Changelog:: +* Database:: +* Concepts and data model in ekg:: +* Understanding and editing the note buffer:: +* Capturing notes:: +* Viewing tags or notes:: +* Searching for notes:: +* Magic tags:: +* The trash:: +* Links to ekg in org-mode:: +* Importing from org-roam:: +* Backups:: +* Database maintenance:: +* Customizing ekg with hooks:: +* Integration with ekg:: +* Extras:: +* Design:: + +@detailmenu +--- The Detailed Node Listing --- + +Installation + +* Sqlite dependency:: +* Installing via Melpa:: +* Installing by hand:: + +Changelog + +* Version 0.9.0: Version 090. +* Version 0.8.0: Version 080. +* Version 0.7.1: Version 071. +* Version 0.7.0: Version 070. +* Version 0.6.4: Version 064. +* Version 0.6.3: Version 063. +* Version 0.6.2: Version 062. +* Version 0.6.1: Version 061. +* Version 0.6.0: Version 060. +* Version 0.5.1: Version 051. +* Version 0.5: Version 05. +* Version 0.4.3: Version 043. +* Version 0.4.2: Version 042. +* Version 0.4.1: Version 041. +* Version 0.4: Version 04. +* Version 0.3.3: Version 033. +* Version 0.3.2: Version 032. +* Version 0.3.1: Version 031. +* Version 0.3: Version 03. +* Version 0.2.1: Version 021. +* Version 0.2: Version 02. + +Understanding and editing the note buffer + +* Drafts:: +* A warning about org-mode:: + +Capturing notes + +* Templates:: +* Changing the initial tags of a note:: +* Inline commands:: + +Viewing tags or notes + +* Commands in the notes buffer:: +* Customizing note display in @samp{ekg-notes-mode}:: + +Customizing ekg with hooks + +* Text Truncation Method:: + +Extras + +* Embeddings:: +* Logseq:: +* LLM:: +* Auto save:: +* Denote:: +* Email:: + +Logseq + +* Exporting:: +* Importing:: + +LLM + +* Augmenting notes with LLM output:: +* Using ekg notes as prompts:: +* Querying your ekg database:: + +Design + +* The triple database:: +* The metadata section:: + +@end detailmenu +@end menu + +@node Introduction +@chapter Introduction + +The ekg module is a simple but opinionated note taking application, for emacs. +It is a substitute for such other emacs applications such as org-roam or denote. +ekg stands for @emph{emacs knowledge graph}. + +Data is completely stored in a sqlite database. Notes are organized around +tags, and you can view many notes by looking at one or more tags. This provides +a read-only view of various notes, which you can navigate between and choose to +edit in a separate buffer. + +The editing of notes combines the editing of the text of the note with various +properties stored in the database such as the tags of the note. + +@node Installation +@chapter Installation + +@menu +* Sqlite dependency:: +* Installing via Melpa:: +* Installing by hand:: +@end menu + +@node Sqlite dependency +@section Sqlite dependency + +If using Emacs version 29, you likely have @samp{sqlite} as a library compiled into +Emacs. Prior versions use @samp{emacsql} for access to sqlite. If you are using a +pre-Emacs 29 version, you need to have @samp{sqlite} installed on your system. + +@node Installing via Melpa +@section Installing via Melpa + +This is the easiest and recommended way to install ekg. If you have not set up +Melpa yet, follow the instructions at @uref{https://melpa.org/#/getting-started}. + +There is no need to use Melpa Stable, because development happens in a branch +and is not integrated until it is deemed stable. When bugs are discovered, +fixes are pushed to the main branch soon, so it is usually a good idea to keep +your version up to date. + +There are many ways to use Melpa to download ekg, but using @samp{use-package} is the +easiest way, and recommended because it allows you to get the pacakge and +configure it in one place. An example use is below. + +@lisp +(use-package ekg + :bind (([f11] . ekg-capture))) +@end lisp + +@node Installing by hand +@section Installing by hand + +If you wish to install by hand, you need to make sure to install the triples +library, found in GNU ELPA, and at @uref{https://github.com/ahyatt/triples}. + +Clone the ekg library, from whatever branch you would like to use (@samp{main} +corresponds to the release version, and @samp{develop} is where development of the +next version happens). Add your source directory and require ekg. The +following is an example assuming you cloned ekg into @samp{~/src/ekg}, and the +triples library is already installed. + +@lisp +(add-to-list 'load-path "~/src/ekg") +(require 'ekg) +@end lisp + +@node Changelog +@chapter Changelog + +@menu +* Version 0.9.0: Version 090. +* Version 0.8.0: Version 080. +* Version 0.7.1: Version 071. +* Version 0.7.0: Version 070. +* Version 0.6.4: Version 064. +* Version 0.6.3: Version 063. +* Version 0.6.2: Version 062. +* Version 0.6.1: Version 061. +* Version 0.6.0: Version 060. +* Version 0.5.1: Version 051. +* Version 0.5: Version 05. +* Version 0.4.3: Version 043. +* Version 0.4.2: Version 042. +* Version 0.4.1: Version 041. +* Version 0.4: Version 04. +* Version 0.3.3: Version 033. +* Version 0.3.2: Version 032. +* Version 0.3.1: Version 031. +* Version 0.3: Version 03. +* Version 0.2.1: Version 021. +* Version 0.2: Version 02. +@end menu + +@node Version 090 +@section Version 0.9.0 + +@itemize +@item +Changed how note metadata is represented in the note editing UI@. +@item +Pushed more configuration into the triple database itself. +@end itemize + +@node Version 080 +@section Version 0.8.0 + +@itemize +@item +Add support for the @samp{vecdb} package, for embedding storing and retrieval. +@item +Add @samp{ekg-truncation-method} variable to control how truncation happens. +@item +Fix various truncation issues with quotes, underscores and all sorts of other symbols. +@end itemize + +@node Version 071 +@section Version 0.7.1 + +@itemize +@item +Don't show inactive notes with @samp{ekg-search}. +@end itemize + +@node Version 070 +@section Version 0.7.0 + +@itemize +@item +Add incremental search with @samp{ekg-search}. +@item +Add formatting options for ekg note templates. +@item +Added @samp{ekg-show-notes-for-query} using @samp{triples-fts}. +@item +Speed up for LLM calls with notes as context +@item +Support multiple values in @samp{ekg-llm-providers} so that they can be chosen from. +@item +Ensure SQL connection and schema for embedding calls. +@end itemize + +@node Version 064 +@section Version 0.6.4 + +@itemize +@item +Fix display of extended structured data in notes. +@item +Add contrib directory and @samp{ekg-email} as a contribution to import emails to notes in a structured way. +@item +Fix inlines not expanding in the contextual notes in @samp{ekg-llm}. +@item +Set @samp{default-directory} to db location in notes buffers (by @uref{https://github.com/monkpearman, S Pearman}). +@item +Add tags when saving and exiting notes for any linkified markdown links. +@item +Generate embeddings in batch in @samp{ekg-embedding-generate-all}. +@end itemize + +@node Version 063 +@section Version 0.6.3 + +@itemize +@item +Fix issue in trying to get a single-valued item to show up in the metadata line. +@item +Remove the never-used named, email, and person schema. +@item +Fix issue with tag completions triggering incorrectly if they contained spaces. +@item +Fix error when displaying unknown schema. +@item +Improve JSONification of notes when sending context to LLMs. +@end itemize + +@node Version 062 +@section Version 0.6.2 + +@itemize +@item +Add export to @uref{https://github.com/protesilaos/denote, denote} (by @uref{https://github.com/jayrajput, Jay Rajput}) +@end itemize + +@node Version 061 +@section Version 0.6.1 + +@itemize +@item +Run tag hooks when a valid tag is typed, instead of relying on completion, which often doesn't work. +@item +Fix issue in @samp{markdown-mode} where the beginning of the tag values was incorrectly read-only. +@item +Allow emojis as tags. +@end itemize + +@node Version 060 +@section Version 0.6.0 + +@itemize +@item +Add tag and similar notes to the context when having LLMs add to or replace +notes. @code{ekg-llm-default-instructions} replaces the previous variable, +@code{ekg-llm-default-prompt} (because the prompt now contains the instructions). +@item +Add inline links for Markdown, as wiki links. +@end itemize + +@node Version 051 +@section Version 0.5.1 + +@itemize +@item +Improve regex for inline tag linkification so we linkify more valid inline tags. +@item +Improve logseq import to import notes without org structure, and use filetags. +@item +Fix inline tag bug that test and real failures. +@end itemize + +@node Version 05 +@section Version 0.5 + +@itemize +@item +Simplified trashed tags from using tag prefixes to using a simple trash tag (@samp{ekg-trash-tag}). +@item +Added @samp{ekg-show-notes-with-tag-prefix}. +@item +Add tags while writing the body of notes by completing with "#". See @ref{Understanding and editing the note buffer, , Note text} section for details. +@item +Added functionality so that org-mode commands, and potentially others, narrow automatically so the metadata line and the read-only nature don't mess things up. See @ref{Understanding and editing the note buffer, , Note text} section for details. +@item +@samp{ekg-auto-save}, a new module by @uref{https://github.com/qingshuizheng, Qingshui Zheng}. +@item +Added canceling a note as an actual command, also by @uref{https://github.com/qingshuizheng, Qingshui Zheng}. +@item +Fix issue with trashing notes with tags that are partially trashed. +@item +Saving while editing a note will no longer add the draft tag. This means that if you are editing a draft, it will not keep it as a draft, so it will become a normal note. +@item +Fix issue with using ekg-llm with vertex, which doesn't understand system prompts. +@item +Fix metadata face which didn't work well with all themes - now the metadata section is just bolded. +@item +Improve display regenerating embeddings via @samp{ekg-embedding-generate-all}. +@item +Change multi-title note display from using newlines to commas +@item +Made deleting titles and resources possible, and properly skipped empty properties for multi-line propeties. +@item +Handle backup errors better, warning and proceding if not a forced backup, erroring out with a better message if forced. +@item +Don't show trashed notes in drafts. +@end itemize + +@node Version 043 +@section Version 0.4.3 + +@itemize +@item +Added autoloads +@item +Fixed issue that could occur when saving with malformed buffers. +@end itemize + +@node Version 042 +@section Version 0.4.2 + +@itemize +@item +Switch LLM chat output to streaming when available. +@item +Fix inclusion of title-based transclusion ">t", which included the "t" as part of the completion. +@item +Fix tag renaming possibly causing duplication. +@item +Ensure renamed tags are normalized. +@item +Support metadata where fields are specified via multiple property lines and make "title" such a field, so now titles can have commas. +@end itemize + +@node Version 041 +@section Version 0.4.1 + +@itemize +@item +Fix issues using default emacs in-buffer completion, and allowing completion in places we shouldn't. +@item +Add @samp{ekg-embedding-generate-on-save} and @samp{ekg-embedding-disable-generate-on-save} to turn off generating embeddings for notes. +@end itemize + +@node Version 04 +@section Version 0.4 + +@itemize +@item +Added ability to save in-progress notes. +@item +Added "magic tags", tags that cause elisp to be executed. See the @ref{Magic tags, , magic tags} section for more detail. +@item +Added @samp{ekg-llm}, a separate module, so LLMs can append to or rewrite notes, using other notes as prompts. As with ekg-embeddings, a Open API key is required. +@item +Added @samp{ekg-capture-file} to save notes associated with a file, or go that same note. +@item +Depend on the @uref{https://github.com/ahyatt/llm, llm package} for embeddings and llm functionality, so the user can choose different providers. +@item +Improved @samp{ekg-clean-db} to fix bugs and change empty-note deletion logic. +@item +Improved @samp{ekg-embedding} package to make it more robust to missing embeddings (like what can happen if you save notes without loading the package first). +@item +Added commas to the tag header. +@item +Made separator in @samp{ekg-capture-mode} and @samp{ekg-edit-mode} customizable. +@item +Fixed display of org notes to have properly formatted links and images. Links can be opened with @samp{[C-c o]}. +@item +Fixed bug where in embedding search and buffer similarity, the highest match was discarded. +@item +Fixed bug in title transclusion for company users. +@item +Changed template behavior to also use parent tags (so templates on "foo" tag, will work if the note tag is "foo/bar"). + +Thanks to contributors @uref{https://github.com/Gleek, Gleek} and @uref{https://github.com/qingshuizheng, Qingshui Zheng}. +@end itemize + +@node Version 033 +@section Version 0.3.3 + +@itemize +@item +Fix native compilation errors. Upgrade to triples version 0.3.5, which contains more important bug fixes. +@end itemize + +@node Version 032 +@section Version 0.3.2 + +@itemize +@item +Upgrade to triples version 0.3.2, which contains important bug fixes. +@end itemize + +@node Version 031 +@section Version 0.3.1 + +@itemize +@item +Upgrade to triples module 0.3, which changes how integers are stored in the built-in sqlite (for users of Emacs 29+). Users of sqlite will have their database automatically upgraded. A backup will always be made beforehand - you may want to find it (alongside your normal emacs backups), and make sure to keep it around in case the upgrade went wrong in some way. @strong{Important: if you created your database before this version on the built-in sqlite, and afterwards switched to emacsql, you must switch back to the built-in sqlite for the upgrade.} +@item +Store the ekg version in the database so we know when we need to do updates in the future. +@item +Remove older database updates that should no longer be needed. +@item +Ensure we always are connected to the database before any call to the database happens. +@item +Make ekg-close interactive. +@item +Fixes to ignore bad embeddings which otherwise would cause errors. +@item +Added the ability to kill notes in a notes view, which does not change the database, it only alters the view. Thanks to Jay Rajput for the contribution! +@item +Fix for tag cleanups, which were sometimes not cleaned up if the tag had other data (such as embeddings). +@item +Added variable @samp{ekg-embedding-text-selector} with a default function so that large notes can have their embeddings taken. +@item +Added @samp{ekg-get-notes-with-title}, which is offered as a useful function for clients. +@end itemize + +@node Version 03 +@section Version 0.3 + +@itemize +@item +Added inline commands, see the @ref{Inline commands, , inlines} section for more detail. +@item +Added customization of note display, using inline commands. +@item +Added logseq import / export in its own module, and removed it from the @samp{ekg-org-roam} module. See the @ref{Logseq, , logseq} section for more details. +@item +Improve window management, now we play nicer with customized window configuration, and now opening a list of notes will also switch to that window. +@item +Improved metadata overlay look and function, it now looks just like @samp{message-mode}, which hopefully will help with people's intuitions on how it works. +@item +Added a blank line between notes in notes list buffers, for a cleaner look. +@item +Renamed @samp{ekg-rename-tag} to @samp{ekg-global-rename-tag} to clarify this isn't for changing an individual tag in a note. +@item +Added arg prefix behavior to @samp{ekg-notes-delete} to allow deleting notes without a warning. +@item +Made @samp{ekg-capture} have unique buffer names, so the user can capture multiple notes at the same time. +@end itemize + +Thanks especially to users and contributors: @uref{https://github.com/jayrajput, Jay Rajput}, @uref{https://github.com/qingshuizheng, Qingshui Zheng}, And @uref{https://github.com/cuprum, cuprum}. + +@node Version 021 +@section Version 0.2.1 + +@itemize +@item +Removed @samp{ekg-notes-remove}, which removed one or more tags from a note from a note list buffer. +@item +New keybinding, "q" in the notes buffer, which kills the buffer (thanks to Jay Rajput for the idea). +@end itemize + +@node Version 02 +@section Version 0.2 + +@itemize +@item +Added hooks @code{ekg-add-schema-hook}, @code{ekg-note-pre-save-hook}, @code{ekg-note-save-hook}, @code{ekg-note-delete-hook}, and @code{ekg-note-add-tag-hook} to enable customization. +@item +New commands @samp{ekg-show-notes-latest-captured}, @samp{ekg-show-notes-latest-modified}, for showing notes created or modified recently. +@item +Introduced variable @code{ekg-notes-size} to control the default page size for limited views such as @samp{ekg-show-notes-latest-captured}. +@item +Added templating. +@item +Added embedding as an optional add-on, to enable note similarity and note search; requires an account at OpenAI or similar embedding provider. +@item +Added new function @code{ekg-active-notes} to easily get all non-trashed notes. +@item +Improved ability to have note list buffers that have flexible titles and operation, notably improving the @samp{ekg-show-notes-in-trash} command. +@item +Standardized buffer names for @samp{ekg-notes-mode} buffers, which all are prepended with "ekg" and surrounded by asterisks, to denote that they are non-file-based. +@item +Added the documentation you are reading right now. +@item +Fixed bug interfering with completion at the beginning of the tag property line. +@item +Fix for @code{ekg-notes-refresh} incorrectly calling @code{ekg--show-notes}. +@end itemize + +@node Database +@chapter Database + +By default, ekg uses the default triple database, which is set in the variable +@code{triples-default-database-filename}. The default value of this is +@samp{~/.emacs.d/triples.db}. You can specify a different name if you want the ekg +database to not be shared with any other user of the triple package, by +customizing the variable @code{ekg-db-file}. When this is @code{nil}, it leaves the +filename up to the triples package. + +@node Concepts and data model in ekg +@chapter Concepts and data model in ekg + +The ekg package is built on a flexible database scheme called "triples", where +everything is stored as a graph structure: a subject, a predicate, and an +object. The implication for the ekg package is that new kinds of data are easy +to add, and live alongside other data. Values of properties, stored as "objects", +can themselves have values by adding data where the same value is the +"subject". If you plan to do extensive integration work in elisp, it will help +to understand these concepts, and the best way to do so is reading the triples +package README@. + +For notes, we can think of the subject of the triples as an ID@. Notes are +created, and have the following types by default, with the type having +properties. +@itemize +@item +@samp{tagged}: Tags +@item +@samp{text}: Text, its major mode, and any inline commands. +@item +@samp{time-tracked}: Creation time and modification time +@item +@samp{titled}: Title +@end itemize + +The ID for notes is by default an integer UUID@. However, you can have notes +about anything. In EKG an ID can be a resource identifier as well, such as a +URL@. When this happens, the ID is interesting data in its own right. + +Tags may have spaces, but cannot have commas, which are used to separate them +when showing them to the user and parsing them back out into properties to +store. Tags also may not contain uppercase letters. + +Because of the triples model, there is data about the tags for each note. Tags +themselves just have type markers indicating they are tags, and can dynamically +query for all notes with their tag, so tags always have a current list of notes +with their tag. + +@node Understanding and editing the note buffer +@chapter Understanding and editing the note buffer + +The note buffer is a buffer of the particular mode for the node, which can change based off the setting, and can be different for different notes. It acts mosty just like that mode, but has a header that displays some of the properties of the note, and lets you edit those properties. + +The properties shown in the note property area come from the data stored in the +database for the entity. At a minimum, there will be tags. + +For the common cases, there are special commands, available under the @samp{C-c /} prefix keymap: + +@itemize +@item +@samp{a}: @samp{ekg-note-add-tag}, to add a tag. +@item +@samp{d}: @samp{ekg-note-remove-tag}, to remove a tag. +@item +@samp{t}: @samp{ekg-note-add-title}, to add a title. +@item +@samp{T}: @samp{ekg-note-remove-title}, to remove a title. +@item +@samp{p}: @samp{ekg-note-edit-property}, to edit any property, including the "resource" of the note, which is its ID@. For editing, multi-values properties are editable as a list with values separated by @samp{", "}. Maintain the space after the comma when editing. +@item +@samp{i}: @samp{ekg-edit-add-inline}, to add an inline command. + +When altering properties, nothing is saved until the note is saved. +@end itemize + +The buffer has the text of the note. + +There are three modes for the note text: @samp{text-mode}, @samp{markdown-mode}, and @samp{org-mode}. +More can be added by customizing the variable @code{ekg-acceptable-modes}, just make +sure its a mode that makes sense for notes. The default mode is configured in +@code{ekg-capture-default-mode}, but can be changed when capturing with the command +@samp{ekg-change-mode}. + +The note text provides various options for completions. The most common +completion is typing tags inline while writing notes. These tags will be added +to the note automatically upon saving, regardless of whether completion is used +or not. The tag completion is triggered by the "#" symbol. In @samp{org-mode}, if +@samp{ekg-linkify-inline-tags} is set to non-nil (which is the default), the tag will +be turned into an org link to the tag. It is necessary to enclose the tag in +square brackets to be detected as an inline tag. In @samp{markdown-mode}, the tag will +be turned into a wiki link (denoted by double square brackets). It is +acceptable to finish the completion with a tag that is not currently defined, as +the tag will be added when the note is saved. The detection of plaintext tags of +various types can be turned off by setting @samp{ekg-inline-populate-inline-text-tags} +to @samp{nil}. There are other customizable tag symbols available, indicating different +prefixes. By default, in addition to the "#" symbol (representing a tag with +that name), there is also the "@@" symbol for tags prefixed with "person/", and +the "!" symbol for tags prefixed with "idea/". These other symbols and the +prefixes they mean are controlled by +@samp{ekg-inline-custom-tag-completion-symbols}. By default, we have "@@" which will +denote a tag with the "person/" prefix, and "!" which will denote a tag with the +"idea/" prefix. So if, in either @samp{org-mode} or @samp{markdown-mode}, the text has the +following: + +@example +Everything related to #[emacs] should be colored #ffa500. This is the opinion of @@[rms]. +@end example + +the tags that will be detected are "emacs" and, because of the special "@@" +prefix that indicates a tag prefix, "person/rms". The color will not be picked +up as a tag, because it is not enclosed in brackets. This helps us avoid false +positives. + +In @samp{org-mode} and @samp{markdown-mode}, linkified links, which you get with tag +completion if @samp{ekg-linkify-inline-tags} is true, the links will be detected as +tags, and the tags will be added to the list of note tags on saving and exiting +the note. + +There is no functionality to remove inline tags that are deleted in the tag +section. If an inline tag is not deleted in the note text, the tag will be +re-added. + +Another type of completion is for inline commands, the ">t" completion, +mentioned in the @ref{Inline commands}. + +The metadata section above the notes often can be problematic for some commands, +especially org commands. Because of this, before commands are executed, we check +the command name against the @samp{ekg-command-regex-for-narrowing}, and if there is a +match to one of the regexes, we narrow to the note section just when the command +is running. It defaults to all org-insert commands and @code{org-meta-return}, but if +there are any weird behaviors caused by the metadata section, consider +customizing this variable. Right now this just works for keybindings, and not +using @code{execute-extended-command}. + +@menu +* Drafts:: +* A warning about org-mode:: +@end menu + +@node Drafts +@section Drafts + +Notes can be saved midway through editing, both for capturing and editing notes. +The normal buffer-save keybinding (typically C-x C-s) will save a draft. A +draft is like a normal note, but has a special tag, by default "draft". (This +can be customized in @samp{ekg-draft-tag}.) Having this tag means the note doesn't +show up in most views, much like the notes in "trash". Once a note is saved +normally, it loses the draft tag. + +See also the section on the @ref{Auto save, , auto save}, to see how to turn on and set up auto +save, which can automatically save drafts for new notes. + +@node A warning about org-mode +@section A warning about org-mode + +Org-mode notes are primarily to use org-mode formatting on. Org-mode has a lot +of funtionality, but much of it depends on the assumption that the buffer is all +for use by org-mode (not true in this case, because of the properties portion), +and the assumption that the buffer is visiting a file, which is also not +true. In particular, attachments will not work, and ekg-notes cannot be added to +the agenda. + +@node Capturing notes +@chapter Capturing notes + +@samp{ekg-capture} is the command to capture a note. In ekg this is probably the most +frequently used command. It will create a new buffer called @samp{*EKG Capture*}. By +default, it will have the current date tag, such as "date/2023-02-21". + +@noindent +@samp{ekg-capture-url} will capture a note associated with a URL resource, and with +a given title as the title of the page. The idea is that the note is annotating +the reference itself as a "literature note". The title also appears as a tag, +so other notes can reference this if needed. For example, if the URL is +@uref{http://example.com}, and the title is "An example URL", the properties will be: + +@quotation +Resource: @uref{http://example.com} +Tags: doc/an example url, date/2023-02-25 +Title: An example URL + +@end quotation + +Capturing URLs is a bit clunky as is. If you can wrap it in a function to +supply the name and url of the active browser tab, then you can create a much +easier experience. The following is an example for users of Google Chrome on +Mac OS X@. + +@lisp +(defun my/ekg-capture-url () + (interactive) + + (ekg-capture-url + (do-applescript "tell application \"Google Chrome\" to return URL of active tab of front window") + (do-applescript "tell application \"Google Chrome\" to return Title of active tab of front window"))) +@end lisp + +@noindent +URL can also point to local files, which will be browsed using @samp{find-file} by +default. The idea is that you can tag files and folders to make them easier to +find. Here is an example note similar to web address URL: + +@quotation +Resource: @uref{file:///Users/andrewhyatt/notes/20230510T162600__emacs_init-file.org} +Tags: doc/emacs config, date/2023-05-13, emacs/init +Title: Emacs Config + +@end quotation + +You can use the function @samp{ekg-capture-file} to either capture or edit a note +associated with a file from a buffer visiting that file. (If there is already a +note associated with the buffer's file, the note will be opened.) You can use +this to store TODOs and other notes about a file. + +ekg opens web addresses in a browser using @samp{browse-url} and everything else in +Emacs using @samp{find-file}. + +@noindent +A final way to capture notes comes from a buffer that is viewing a list of +notes, in @samp{ekg-notes-mode}. You can call @samp{ekg-notes-create}, which will capture +a new note with whatever tags (if any) are associated with the notes buffer. + +@noindent +To save any note that is being captured, press @samp{C-c C-c} or call +@samp{ekg-capture-finalize}. To cancel, just kill the buffer. You can also abort with @samp{C-c C-k}, or @samp{ekg-capture-abort}, which will not only kill the buffer but delete any draft saved. + +@menu +* Templates:: +* Changing the initial tags of a note:: +* Inline commands:: +@end menu + +@node Templates +@section Templates + +Ekg comes with a built-in way to have templates. When you add a tag to a note, +ekg searches for notes with both the tag being added, and the tag "template". +Any note with those two tags will be added by default to the text of the buffer. + +For example, if there is a note with tags "daily reflection" and "template", +with the text "What did you learn today?", adding the tag "daily reflection" to +a note will cause the text "What did you learn today?" to appear. The parents +of tags are also searched, so the same thing will happen if the tag you add is +"daily reflection/morning" - it will get the template for "daily reflection" as +long as it exists. + +The adding of templates happens whether intially when setting up the capture +buffer, or later when the user types a tag that is a valid tag. Because of +this, it's best to avoid adding templates to tags that are prefixes of other +tags you'd like to use, but don't want the template on, because as soon as ekg +sees the prefix that's a valid tag being typed, it will trigger that tag's +templates. + +You can choose a tag other than "template" as the trigger for this templating +behavior, by customizing @code{ekg-template-tag}. + +This functionality is enabled through the function +@samp{ekg-on-add-tag-insert-template} in the variable @code{ekg-note-add-tag-hook}, and +can be turned off by removing it from that hook. + +@lisp +(remove-hook 'ekg-note-add-tag-hook #'ekg-on-add-tag-insert-template) +@end lisp + +@node Changing the initial tags of a note +@section Changing the initial tags of a note + +The variable @code{ekg-capture-auto-tag-funcs} has a list of functions to call to add +tags. Each function is called, and returns a list of tags (or @code{nil}, the empty +list), which are all added to a new note. By default, this variable has the +function @samp{ekg-date-tag}, which returns the tag of today's date. If you do not +want this, you can remove this function. You can also add your own functions to +add the year, the week number, or any tag you feel is appropriate. + +@node Inline commands +@section Inline commands + +An inline command is a way to insert generated content into notes. A command +has a representation, and can be evaluated. The representation is an +s-expression limited to a subset of functions. An example of a representation +is "My .emacs file: %(transclude-file \"~/.emacs.d/init.el\")". When you are +capturing or editing the note, you can create this representation, or see one +already created. When viewing the note in a notes buffer, the inline command is +evaluated and the results are inserted into the note. + +There are two kinds of inline commands: a normal command, and a note command. A +normal command can do anything, and takes the form "%( +@dots{} )". In other words, this is just like an elisp function call, except +with a "%" in front. When executing one of these we look for a function +starting with @samp{ekg-inline-command-}. So, for example, we have the following +commands available for use: + +@itemize +@item +@samp{%(transclude-note id )}: Include the contents of another note. +@samp{numwords} is optional, and controls the maximum number of words to include. +If not included, there is no limit. +@item +@samp{%(transclude-file filename )}: Include the contents of a file. +@samp{numwords} functions the same here as in @samp{transclude-note}. +@item +@samp{%(transclude-website url )}: Include the contents of a website. As +of now, no attempt is made to only include the "main content", so this is best +suited to simple text sites that have content without any navigational +elements. +@end itemize + +These are defined in @code{ekg-inline-command-transclude-note}, and so on. A user +can define new commands just by creating new functions that fit this pattern. +All of these will be executed and content calculated every time the note +containing them is re-displayed. Note that there is currently no automatic +refresh when the content being transcluded changes. + +The other kind of inline command is a note command. These function similarly to +normal inline commands, with the key difference that the form is now +"%n( @dots{} )"; note the "n" in front. The +difference here is that there is an implicit first argument that is the note +that is being displayed in the current context. After that note argument "" and so on will be added. These are used primarily for controlling the +read-only display of notes in notes lists. The note commands are primarily +driven by types, with the idea that a note can have many types, and each type +has a note command that displays information related to that type. Note +commands are defined in functions with the prefix @samp{ekg-display-note-}. The +following note commands exist: + +@itemize +@item +@samp{%n(id )}: Shows the ID of the note, if it is interesting. Interesting +mainly means it isn't a random-seeming ID that we normally generate for notes, +and is instead some sort of resource. If @samp{force} is true, then show it +whether it is interesting or not. +@item +@samp{%n(text ... )}: The text of a note (with any inline commands calculated +and their results displayed inline). @samp{numwords} functions as noted above. The @samp{format} options are an optional set of formatting symbols which control how the text is formatted. See below for details. +@item +@samp{%n(tagged ... )}: The tags of a note. +@item +@samp{%n(time-tracked ... )}: The created and modified time of a note. +@samp{format-str}, if passed, controls how the times are formatted (see +documentation for @code{format-time-string}, default is @code{%Y-%m-%d}). +@item +@samp{%n(titled ... )}: The title of a note. +@item +@samp{%n(other)}: A special note command that will substitute itself with all +type-relevant note commands that haven't already appeared. So, for example, +if there is a type such as @samp{person}, and a note has information with this +type, that information will be shown in the @samp{other} command, as if it was +substituted by @samp{%n(person)}. However, if @samp{%n(person)} already appears as a +command, it will not add it again in the @samp{other} command. +@end itemize + +The formatting options can be left out, as can @samp{numwords}, for the default implementation. However, if you want to control the formatting, there is a set of formatting options that exist. That set is: +@itemize +@item +@samp{oneline}, the results must be on one line only. To see an example of this in use, see @code{ekg-oneliner-note-template}. +@item +@samp{plaintext}, the result must not have any string properties. +@end itemize + +The @samp{%n(id )} is implemented in @code{ekg-display-note-id}, @samp{%n(text +)} is implemented in @code{ekg-display-note-text}, and so on. All these +are designed to be useful for customizing the note display (see @ref{Customizing note display in @samp{ekg-notes-mode}, , Customizing note +display in ekg-notes-mode}). Because we want to have these possibly not insert +anything, each function must end with a newline if the content is likely to be +needing a line to itself. The functions must always return a string. Although +the default note commands are all based around types, a note command could be +anything that needs a note. + +Inlines can be added by simply typing them, or a few special commands. +@samp{ekg-edit-add-inline} will add an inline note or file. For notes, it will +prompt to select a note by title or tag and then text. For files, it will +prompt for the file name. The other way is to use completion at point, by +typing ">t" and completing by notes with titles. After completion, the ">t" +will be replaced with the correct @samp{transclude-note} command that refers to the +titled note selected. This is only useful for notes with titles, since they are +more easily selected by completion. + +@node Viewing tags or notes +@chapter Viewing tags or notes + +There are several functions to view notes in various ways. All of these show a +list of notes in read-only view, that can be navigated and interacted with. +This is a @samp{ekg-notes-mode} buffer. + +@samp{ekg-show-notes-with-tag} will show all notes tagged with the given tag. + +@samp{ekg-show-notes-with-any-tags} will show all notes that have any of the tags given. + +@samp{ekg-show-notes-with-all-tags} will show all notes that have all of the tags given. + +@samp{ekg-show-notes-for-query} will do a ranked text search, showing all results. The query is token-based so querying for individual characters and syntax will not work; the query has to be words. Prefixes such as "tag", "title" and "text" can be used to restrict matches to tags, titles and text respectively. For example, the query "tag:apartment sink" will show any article with "sink" in it (it could be anywhere, even a tag), but it also has to have a tag with "apartment" in it. Every term is ANDed together. Use quotes if you want to quote a phrase such as "tag:\"apartment problems\" sink". This does not search anything included via inline commands. This is only possible for those using the built-in sqlite backend in Emacs 29.1 or later. + +@samp{ekg-show-notes-for-today} will show the notes taken today. + +@samp{ekg-show-notes-latest-captured} will show a number of notes from newest to +oldest. The number is 20 by default, but can be changed by customizing +@code{ekg-notes-size}. + +@samp{ekg-show-notes-latest-modified} will show a number of notes from newest to +oldest, but by modification time, not by creation time. The number is also 20 +by default and can be changed by customizing @code{ekg-notes-size}. + +@samp{ekg-show-notes-in-trash} will show the notes in the trash (see the @ref{The trash, , trash} +section for details on how this works). + +@menu +* Commands in the notes buffer:: +* Customizing note display in @samp{ekg-notes-mode}:: +@end menu + +@node Commands in the notes buffer +@section Commands in the notes buffer + +The notes buffer is navigated via the following commands (the default binding is +also given): + +@samp{ekg-notes-tag} (@samp{t}), open another notes buffer showing notes with one of the +tags of current note. + +@samp{ekg-notes-open} (@samp{o}), edit the currently selected note. + +@samp{ekg-notes-delete} (@samp{d}), trash the current note (or, if this is the trash list, +truly delete it). + +@samp{ekg-notes-browse} (@samp{b}), open the resource attached to the current note, if it +exists, otherwise do nothing. + +@samp{ekg-notes-select-and-browse-url} (@samp{B}), select from all the titles of URL +resources in the any of the notes, and browse the URL@. + +@samp{ekg-notes-refresh} (@samp{g}), refresh the list of notes in the current buffer, to +make sure any new notes or removed notes are updated in the list. + +@samp{ekg-notes-create} (@samp{c}), capture a new note with all the tags associated with +the list. + +@samp{ekg-notes-next} (@samp{n}), move selection to the next note. + +@samp{ekg-notes-previous} (@samp{p}), move selection to the previous node. + +@samp{ekg-notes-any-note-tags} (@samp{a}), open a new notes list showing any of the tags +that appear in the selected note. + +@samp{ekg-notes-any-tags} (@samp{A}), open a new notes list showing any of the tags that +appear in any of the notes in the note list. For example, if the buffer was +displaying notes with tag @samp{emacs}, and there are two notes displayed, one with +tags @samp{emacs} and @samp{org-mode}, and the other with @samp{emacs} and @samp{ekg}, a new buffer +displaying notes with any of the tags @samp{emacs}, @samp{org-mode}, or @samp{ekg} is created. + +@samp{ekg-notes-kill} (@samp{k}), kill a note from the current view. This only removes +the note from the current buffer; the database is not changed. If the view is +refreshed, the note will come back. + +@samp{q} will kill the notes buffer. + +Many of these commands use the notion that notes lists have associated lists of +tags. That is the case for many commands, but not all. For example, +@samp{ekg-show-notes-latest-captured}, @samp{ekg-show-notes-latest-modified}, and +@samp{ekg-show-notes-in-trash} have no associated tags. + +@node Customizing note display in @samp{ekg-notes-mode} +@section Customizing note display in @samp{ekg-notes-mode} + +The main way to customize displays is via the variable +@code{ekg-display-note-template}, which is a string that has inline commands in it +(normally inline note commands). See the @ref{Inline commands, , inlines} section for more details on +these commands. Through changing this, the ordering or inclusion of various +type-related information can be configured, or extra text added, or anything, +really. + +The variable @code{ekg-format-funcs} has functions to run to format what ekg displays +to the user. Each format function runs in turn on a temporary buffer with the +note text in it, and can make whatever changes it needs to before the buffer's +contents are displayed in a note list. + +@node Searching for notes +@chapter Searching for notes + +There are a few different ways to search for notes, each with their own advantages and disadvantages. + +@code{ekg-search} will perform an incrementally displaying list of notes where the text or title matches the query. It is intended to be used as a quick way to find text. After the notes have been narrow down to the extent you want, you can press the enter key to make a notes buffer out of the remaining items. There is no ranking or any semantic sophistication here, it's a straight text match. + +@code{ekg-show-notes-for-query} will display a notes buffer with entries matching the query, in order of relevance. This searches for text and titles. It has some sophistication due to the use of @uref{https://www.sqlite.org/fts5.html, SQLite's FTS5 extension}. This should work essentially like a Google query, except it does not accept @samp{AND}, @samp{OR}, or any other search operators. All terms are treated as required. + +In the optional @samp{ekg-embedding} module, there is a third option: @samp{ekg-embedding-search}, which displays a notes buffer similar to @samp{ekg-show-notes-for-query}, but with everything ranked by semantic relevance according to their embeddings. See the @ref{Embeddings, , embeddings} section for more details. + +@node Magic tags +@chapter Magic tags + +Sometimes you want to have behaviors that are associated with particular tags. +For example, if some of your notes are in Chinese, you may want to tag them all +with the same tag. Going further, it would be nice if all notes tagged with +"chinese" had your favorite Chinese input method on by default. With magic +tags, you can enable this tag-based customization. + +This works in a similar manner to @ref{Templates, , templates}, except that a template tag only +takes effect when you add it, while a magic tag takes effect both when first +adding it and when editing a note with the tag. But they also share the same +shortcoming: if the tag is a prefix, it will trigger as soon as typed, even if +you wanted to use a different tag that is prefixed with the tag. + +Creating magic tags is also like creating templates. You create a note and use +a special tag that indicates this tag is a magic tag. That special tag is +"tag-defun" (but the name can be changed by customizing the variable +@samp{ekg-function-tag}). This tag is itself a "magic tag", and once you add it to a +note, the note will change to be in @samp{emacs-lisp-mode}. Notes co-tagged with +this will take effect for any notes with those co-tags (again, just like +templates). For this reason, it's probably best to avoid having any date tags +co-tagged, since users probably don't want them to be magic tags. To illustrate +the example that in this section, you could have a note with tags "chinese" and +"tag-defun". This note could have the following content: + +@lisp +(set-input-method 'chinese-b5-quick) +@end lisp + +@noindent +In this example, once a note is added with "chinese", this function will be run, +and all subsequent editing of the note will have this function run. Note that +there can be only one elisp expression in the note; if you have multiple, only +the first will be used. It is not advised to have complicated elisp here, since +it is not amenable to debugging. The code is run in the context of the note +buffer, after the text has been inserted. + +For tags that are a hierarchy, each level in the hierarchy is tried in order, +from least specific to most specific. So, for example, if the tag was +"chinese/writing practice", first we would try "chinese", apply any functions +found there, then try "chinese/writing practice", and apply any functions found +there. + +@node The trash +@chapter The trash + +Notes deleted from note lists (@samp{ekg-notes-mode}) buffers are not deleted outright, +but rather put in the trash, which is done by adding the @samp{ekg-trash-tag}, by +default, "trash", to the note. Any note with the "trash" tag will not be shown +in normal tag buffers. + +Trashed notes can be seen by calling @samp{ekg-show-notes-in-trash}. If notes are +deleted from this list via @samp{ekg-notes-delete} again, they are deleted permanently. +The function @samp{ekg-notes-delete} will always delete a note if the note is in the +trash, and trash it otherwise. If you want to un-trash the note, you can remove +the trash tag. + +@node Links to ekg in org-mode +@chapter Links to ekg in org-mode + +Both notes in ekg and certain note list buffers can be stored and linked to in +org-mode. To store a link to a note, you have to edit that note and call +@samp{org-store-link}. That function can also be called in a @samp{ekg-notes-mode} buffer +created by @samp{ekg-show-notes-with-any-tags}. Other list types currently will just +store their tags assuming the user wants a link to a list with any of the tags +in the list. + +@node Importing from org-roam +@chapter Importing from org-roam + +You can import your notes from org-roam. This will turn all titles into tags, +and all links will become tags as well, as well as any tags org-roam thought +were in the document. At the moment, the import is started via executing elisp, +since importing can be fairly idiosyncratic, and ekg and org-roam have different +ways of expressing the same thing that you may want to change. It's best if you +looked over @samp{ekg-org-roam.el} and see what is going on, but at least read the +following description before manually executing @code{(ekg-org-roam-import)}. + +The import is idempotent, so it always will import to the same entities, +overwriting older data with new data. If you want to update what is in ekg, you +can just rerun the import. In the import, titles and links will be normalized +to ekg's tag format (they will be downcased and have any commas removed). If +you have tags you want to turn into prefixes (which is a good idea for tags +widely applied, which essentially act as a categorization), you can add those +tags to the list at @code{ekg-org-roam-import-tag-to-prefix}. For example, + +@lisp +(setq ekg-org-roam-import-tag-to-prefix (append ekg-org-roam-import-tag-to-prefix '("idea" "person"))) +@end lisp + +Then, when a note is found that is tagged with "idea", but with title "emacs is +a powerful tool", then the title in org-roam will be turned into the ekg tag +"idea/emacs is a powerful tool", and anything linked to it will also get the +same prefix. + +@node Backups +@chapter Backups + +By default, the ekg package will back up its database, using the backup +functionality built into the triples library. The backup behavior is controlled +by @code{ekg-default-num-backups}, set to @samp{5} by default, and +@code{ekg-default-backups-strategy}, set to @samp{daily}. These are, on first use of ekg, +stored in the database itself, but it can be set again at any time by running: + +@lisp +(triples-backups-setup ekg-db ekg-default-num-backups ekg-default-backups-strategy) +@end lisp + +The strategy can be one of the defaults of @samp{daily}, @samp{weekly}, @samp{every-change}, or +@samp{never}, and new methods can be defined as well. See the implementation in +@samp{triples-backups.el} for more information. + +@node Database maintenance +@chapter Database maintenance + +You may occasionally notice that certain tags are obsolete and have no notes, or +notes exist that are empty, or various other annoyances. You can call +@samp{ekg-clean-db}, which will: + +@itemize +@item +First, force a backup. +@item +Remove all tags with no uses. +@item +Remove notes with no text, or just a "*", which is something that often +happens with org-mode buffers. +@end itemize + +Tags may need to be renamed because the concept has changed in some way. The +command @samp{ekg-global-rename-tag} can quickly rename one tag to another globally +across the database, so all tags with the old tag now have the new tag. (Note +that the new tag may already exist, in which case this operation cannot be +easily undone.) + +@node Customizing ekg with hooks +@chapter Customizing ekg with hooks + +You can customize the behavior of ekg in a number of ways. + +First, you can create your own schema to store your own data. The hook +@code{ekg-add-schema-hook} is called whenver the database is connected to. At that +point, ekg adds all of its schema, and runs the hooks in this variable. Adding +schema is idempotent, so it can be called any number of times without causing +problems. Adding schema can be done by calling the triples library. For +details on how to create schema, you can either look at the ekg implementation +for example, or the triples library README for an overview of how it works. + +The @code{ekg-note-pre-save-hook} is called before saving a note, and +@code{ekg-note-save-hook} is called after saving, but in the same database +transaction as the save. + +The @code{ekg-note-delete-hook} is called when deleting a note. + +The @code{ekg-note-add-tag-hook} is called when adding a tag, either via the initial +tags added to a new note, or tags added after completing a new tag in the note's +property list. + +@menu +* Text Truncation Method:: +@end menu + +@node Text Truncation Method +@section Text Truncation Method + +EKG provides a way to customize how text is truncated for display (e.g., in note +lists or inline transclusions) and for generating embeddings. This is controlled +by the `ekg-truncation-method` variable. + +You can set @samp{ekg-truncation-method} to one of the following values: +@itemize +@item +@samp{'word} (default): Text is truncated based on word counts. This is suitable +for languages that use spaces to separate words. +@item +@samp{'character}: Text is truncated based on character counts. This method is +recommended for Asian languages (such as Chinese, Japanese, Korean) that do +not rely on spaces for word segmentation. +@end itemize + +When @samp{ekg-truncation-method} is set to @samp{'character}: +@itemize +@item +Functions like @samp{ekg-truncate-at} will interpret their numerical argument +(e.g., @samp{numwords} or @samp{num}) as a character limit. +@item +Numbers used in display templates (e.g., @samp{%n(text 500)}) will refer to +character limits. +@item +The @samp{ekg-embedding-text-selector-initial} function (used by the @samp{ekg-embedding} +module) will select text for embeddings based on a character limit. +@end itemize + +Users working with Asian languages, or those who prefer character-based limits +for other reasons, should set @samp{ekg-truncation-method} in their Emacs +configuration: +@lisp +(setq ekg-truncation-method 'character) +@end lisp + +Be mindful that if you switch to @samp{'character} mode, numerical limits you might +have previously set for word counts (e.g., in @samp{ekg-display-note-template} or +@samp{ekg-note-inline-max-words}) will now be interpreted as character counts. This +might require adjusting those numbers to achieve your desired display +length. For example, a limit of 500 words is very different from 500 characters. + +@node Integration with ekg +@chapter Integration with ekg + +The ekg package is designed to be easy to integrate with. For example, if you +want to create a note automatically in one of your functions, you can write: + +@lisp +(defun my/log-to-ekg (text) + "Log TEXT as a note to EKG's date" + (ekg-save-note (ekg-note-create :text text :mode 'text-mode :tags `(,(ekg-tag-for-date) "log")))) +@end lisp + +@noindent +If you wanted to re-use an existing note and append to it, you can do that as well. + +@lisp +(defun my/log-to-ekg (text) + "Log TEXT as a note to EKG's date, appending if possible." + (let ((notes (ekg-get-notes-with-tags (list (ekg-tag-for-date) "log")))) + (if notes + (progn + (setf (ekg-note-text (car notes)) (concat (ekg-note-text (car notes)) "\n" text)) + (ekg-save-note (car notes))) + (ekg-save-note (ekg-note-create :text text :mode 'text-mode :tags `(,(ekg-tag-for-date) "log")))))) +@end lisp + +There isn't a special API, but the basic defuns such as @code{ekg-save-note}, +@code{ekg-note-create-text}, @code{ekg-get-notes-with-tags}, @code{ekg-get-note-with-id}, along +with the struct @code{ekg-note} are good starting points. Capturing notes in +different ways can be done by wrapping @code{ekg-capture}, and is how functions such +as @code{ekg-capture-url} work. + +@noindent +If you add schema and you want the user to be able to modify it, you should +supply new alist entries to @code{ekg-metadata-parsers} and @code{ekg-metadata-labels}. + +@noindent +Because inline commands exist, the complete text of a note should be retrieved +with @code{ekg-display-note}. The function @code{ekg-note-text}, will only get the text +as stored, which is missing mode related text properties and any text generated +from inline commands. + +@node Extras +@chapter Extras + +The ekg module can have any number of functionality additions. These may appear +as other packages with other maintainers, but some are included as part of this +package. + +@menu +* Embeddings:: +* Logseq:: +* LLM:: +* Auto save:: +* Denote:: +* Email:: +@end menu + +@node Embeddings +@section Embeddings + +The embeddings functionality, for integration with an LLM, can be turned on by +requiring the ekg-embeddings file and enabling it, such as: + +@lisp +(require 'ekg-embedding) +(ekg-embedding-generate-on-save) +@end lisp + +This module contains functionality to explore similar notes and search using +techniques associated with large language models. Embeddings let you do +searches at a semantic level, based on an understood meaning that is separate +from the words used. For example, if I have a note with a recipe for linguini, +embeddings will let me see that it is similar to notes about spaghetti, and not +similar to notes about cold fusion. Because the search is not based on words, +but meaning derived from those words, notes that describe the same thing in two +different languages should be very similar. In ekg these let you find notes +similar to a current note, or in fact any buffer. You can also do a query via +embeddings. + +The idea behind an embedding is that it is an abstract representation of text, +represented as a multi-dimensional vector. Because it is just a vector, you can +compare the distance between different embeddings, and embedding vectors that +are similar should represent similar concepts. This can be used to find similar +notes, but also to search, where the search string is transformed into an +embedding. + +The embedding interfaces with your preferred LLM provider via the @samp{llm} package. +This package allows the user to define their preferred llm backends, which will +be stored in @code{ekg-embedding-provider}. Please see the @uref{https://github.com/ahyatt/llm, LLM module project page} for +a complete description on how to do this, but an example would be the following: + +@lisp +(use-package ekg + :init + (require 'llm-openai) ;; the specific LLM provider must be required + (require 'ekg-embedding) + (require 'ekg-llm) + (ekg-embedding-generate-on-save) + (let ((my-provider (make-llm-openai :key "my-openai-api-key"))) + (setq ekg-llm-provider my-provider + ekg-embedding-provider my-provider))) +@end lisp + +The embedding provider must be the same for all notes. If you want to switch to +a new provider, you will need to call @samp{ekg-embedding-generate-all} with a prefix +argument (@samp{C-u M-x ekg-embedding-generate-all}), which will regenerate all +embeddings asynchronously. The embedding provider does not have to be the same +as the LLM provider (if you also use the @ref{LLM} add-on.) Also note that the +provider will get the text of all your notes, so if that bothers you, do not use +any provider on a server. + +It is highly recommended to install the @samp{vecdb} package (installable on GNU ELPA), +to get access to fast embeddings from a variety of possible backends. An +example of configuration to do that would be: + +@lisp +(use-package vecdb + :config + ;; Config for qdrant, other providers for other backends are available, see + ;; https://github.com/ahyatt/vecdb for details. For this provider, you will + ;; need a key and a URL. + (defvar my-vecdb-provider (make-vecdb-qdrant-provider :api-key :url )) + (setq ekg-vecdb-provider (cons ash/vecdb-provider (make-vecdb-collection :name "ekg" :vector-size 1536)))) +@end lisp + +If @samp{ekg-vecdb-provider} is not set, the SQLite database is used, which can get +fairly slow when there is a lot of data. + +Once you have this set up, and you have already called @code{(require +'ekg-embedding)} and @code{(ekg-embedding-generate-on-save)} you can call @samp{M-x +ekg-embedding-generate-all}. This may take a long time as each embedding has to +be generated separately with its own API call. Once you've done this, you can +call, in @samp{ekg-notes-mode}, @samp{ekg-embedding-show-similar} to get a list of similar +notes. You can also call @samp{ekg-embedding-search} to perform a search over your +notes using embeddings. In any buffer, you can call +@samp{ekg-embedding-show-similar-to-current-buffer} to show notes similar to whatever +the text is in the current buffer. + +The variable @samp{ekg-embedding-text-selector} has a function that will pre-process +all text that is sent for embeddings. The default value is +@samp{ekg-embedding-text-selector-initial}, which will estimate the size of the +tokens sent and limit the text to the first 8k tokens. Right now the function +is tuned to the limits of Open AI's embedding framework, and a different +function may be needed for other embedding APIs. + +If you would like to stop generating embeddings for notes in a session, you can +call @code{(ekg-embedding-disable-generate-on-save)}. + +@node Logseq +@section Logseq + +ekg can sync with logseq, a PKMS application that can run on a laptop or phone. +Logseq is particularly convenient as a way to view or enter notes on your phone, +and various synchronization solutions exist to sync local files with your phone. +Because ekg and logseq have different designs, these apps are not perfectly +compatible. The ekg and logseq syncing is designed to favor ekg's system when a +conflict arises. + +There are two ways to use logseq with ekg. One is maintaining logseq as an +export-only copy of ekg data, where you don't plan to modify anything in logseq, +just using it to access your notes on other platforms. Exporting from ekg is +destructive, though, so without an initial import, @strong{exporting will overwrite +logseq files with data from ekg, so it may destroy data}. The other way is to +sync bidirectionally. This starts by importing anything from logseq that has +never been imported before, and then writing ekg's data on top. This will +preserve data, but will lose the initial ordering of pages. Both of these +methods, then, will significantly impact your logseq notes. @strong{It is highly +advised to back up your logseq files before starting}. + +To export to logseq, start by requiring the @samp{ekg-logseq} module and setting up +@code{ekg-logseq-dir}, which points to the base directory of your logseq data (where +there is a "pages" and "journals" directory): +@lisp +(require 'ekg-logseq) +(setq ekg-logseq-dir "~/my/logseq") +@end lisp + +If you wish to maintain logseq as a read-only copy of ekg, just run +@samp{ekg-logseq-export} when you wish to export data. This currently may take a few +seconds to a minute, depending on how much data you have. We attempt to not +write any files that are unchanged. To have a bidirectional synchronization, +run @samp{ekg-logseq-sync}, which will first import data from logseq, then export +data. + +@menu +* Exporting:: +* Importing:: +@end menu + +@node Exporting +@subsection Exporting + +When exporting, it's important to understand the differences between ekg and +logseq. Logseq is organized by pages, where one page is one file. Within the +page there are many sections, which can be individually referenced. The ekg +integration treats logseq pages like ekg tags, and logseq sections like ekg +notes. In logseq, the user mostly sees one page at a time. In ekg, notes are +shown in a variety of contexts, mostly tag related, but not always. In logseq, +a note lives in one page and is referenced from other pages. In ekg, each note +has its own identity and is tied to other notes solely via the tags it shares +with them. To compensate for this difference, we export notes based on their +first non-date tag as the page where the text will apear, and reference other +tags, where they will appear as backlinks. In addition, in org-mode, notes in a +page appear as top-level outlines, which are supposed to have text for the +outline node. If there is an ekg note with a title, the title will appear as +the text, otherwise the outline node will just read "Untitled note". Because +this initial headline is where various properties are stored, and is followed +immediately by tags, it makes sense that this is a title instead of just part of +the content. + +@noindent +For example, take the following note: +@example +Tags: date/2023-04-05, ekg, logseq + +ekg can export into logseq! +@end example + +@noindent +This will be exported into "pages/ekg.org": + +@example +#+title: ekg + +* Untitled note +:PROPERTIES: +:ID: 33134561605 +:EKG_HASH: 89471eadbd7cc56b088f5513c11f68cb1d11d045 +:END: +#[[2023-04-05]] #[[logseq]] +ekg can export into logseq +@end example + +@noindent +Each node points to its ID which is from ekg (but, if it was originally +imported, the ekg ID might originally be from logseq). We also encode the hash +of the exported data. This is to keep track of what was exported, so we do not +re-import it unless it has changed. For now, even if the data is changed, it is +not re-imported. Files for "pages/logseq.org" and "journals/2023-04-05" will +also be created, although they won't have any content from this note. + +@noindent +When exporting, inline commands (see @ref{Inline commands, , inlines} section), are evaluated before +exporting to logseq, with the exception of note transclusions, which turn into +logseq embeds to the same ID@. So, other kinds of transclusions or any other +commands will evaluate to whatever text they normally evaluate to when viewing +the note before exporting to logseq. For example, if the note has a file +tranclusion inline command, the file contents will be exported to logseq. +Logseq embeds are roughly equivalent to note transclusions, but only roughly, +since a key difference is that logseq embeds occupy their own lines and appear +visually distinct, and ekg transclusions don't. Because of this, some +formatting strangeness between the two may happen. + +@node Importing +@subsection Importing + +@noindent +Imports from logseq will return all top-level items as separate notes. So, for +example, assuming we're reading from the logseq file "pages/logseq.org": + +@example +* This is my first time trying logseq #testing +* The org compatibility here is especially nice #org + + It really helps me feel comfortable in using the various formatting options I had gotten used to. +@end example + +@noindent +This will turn into two notes, one that has text "* This is my first time trying +logseq #testing", and with tags @samp{logseq}, and @samp{testing}, and the other with the +rest of the text, with the tags @samp{logseq} and @samp{org}. + +@noindent +There are a few things to be aware of. In logseq, any level of the hierarchy +can have an id and be referenced separately. In ekg, we don't support notes +inside of other notes, so these will be imported in the context of the parent +note, and won't be available to reference as its own separate note. Also, +logseq has other functionality not supported by ekg, such as queries and +potentially anything provided by plugins. These will be imported as-is to ekg, +but without any corresponding functionality. + +@noindent +Logseq embeds are imported as note transclusions. + +@node LLM +@section LLM + +@noindent +The ekg-llm module provides a second way to use large language models (LLMs) +with ekg, separately from the ekg-embeddings integration. While the +ekg-embeddings module lets you find notes based on their meanings, the ekg-llm +module lets you prompt an LLM with the contents of a note, and then capture the +LLM's response in the note. + +As with ekg-embeddings, this is based on the @samp{llm} package, which allows the +user to define their preferred llm backends, which will be stored in +@code{ekg-llm-provider}. Please see the @uref{https://github.com/ahyatt/llm, LLM module project page} for a complete +description of how to do this, but an example would be the following: + +@lisp +(use-package ekg + :init + (require 'llm-openai) ;; the specific LLM provider must be required + (require 'ekg-llm) + (require 'ekg-embedding) + (let ((my-provider (make-llm-openai :key "my-openai-api-key"))) + (setq ekg-llm-provider my-provider + ekg-embedding-provider my-provider))) +@end lisp + +The embedding and LLM providers can be different. The LLM provider can change +at will, while the embedding provider must be the same for all embeddings in the +database. It is necessary to create both of these providers, because some LLM +functionality depends on having embeddings. + +The variable @code{ekg-llm-provider}, can be a single provider, similar to +@code{ekg-embedding-provider}, or a list. If it is a list, the first element will be +used by default. However, when the user invokes @samp{ekg-llm-send-and-append-note} or +@samp{ekg-llm-send-and-replace-note} with a prefix argument (normally via @samp{C-u M-x}), the +user can select among the different providers. + +@menu +* Augmenting notes with LLM output:: +* Using ekg notes as prompts:: +* Querying your ekg database:: +@end menu + +@node Augmenting notes with LLM output +@subsection Augmenting notes with LLM output + +To send a note to an LLM and capture its response, call +@samp{ekg-llm-send-and-append-note}, which is by default bound to @samp{[C-c .]}. A prefix +argument (@samp{[C-u C-c .]}) will let you edit the prompt, temperature, and possibly +the provider used before it is sent. The output from the LLM is appended at the +end of the note, in a special section. + +In addition to the contents of the note, ekg will construct a larger prompt for +the LLM@. The prompt consists of context about previous notes that contain the +tags of your note, and similar notes, which is what will generate high-quality +content that is appropriate in the context of your notes. It also contains +instructions to the LLM to how to generate the note text to be added or +replaced. The default instructions are a fixed string you can configure in +@code{ekg-llm-default-instructions}. But alternatively, you can create alternative +prompts for different ekg tags in the same way that @ref{Templates, , templates} work, by creating +a note tagged with "prompt" and any other tag (the special "prompt" tag can be +changed by customizing @code{ekg-llm-prompt-tag}). The alternate prompt is created by +appending all "prompt"-tagged notes. Note that, as with templates, hierarchical +tags can have prompts attached at any or all levels of the hierarchy. + +To take an example, imagine that you have a note tagged with @samp{prompt} and @samp{recipe}, +containing instructions saying the LLM should imagine itself an authority on +cooking and provide you helpful tips to improve your recipes. You then create a +note with a child tag of @samp{recipe}, let's say @samp{recipe/monkfish}, with some details of +your attempt to cook monkfish, and then hit @samp{[C-c .]}. Because @samp{recipe} is a parent +of @samp{recipe/monkfish}, ekg-llm will use these instructions instead of the default +one, and will also append your note, and place the LLM's response in a special +section at the end of your note. For example: + +@example +Making monkfish again. It is thick but tends to be wet and hard to get a good +sear on. Maybe I should sous vide it and then blast it with the searzall torch? + +#+BEGIN_LLM_OUTPUT +Monkfish can indeed be challenging to sear properly due to its high water +content. Sous-vide cooking followed by searing with a torch can be a great +technique to achieve the desired result. The sous-vide process will help to cook +the fish evenly and retain moisture, while the searzall torch can give it a +beautiful caramelized crust. Just be sure to pat the fish dry before searing for +better browning and use high heat to quickly sear the exterior without +overcooking the inside. Happy cooking! +#+END_LLM_OUTPUT +@end example + +Instead of appending, the note can be replaced with the output of the LLM by +using @samp{ekg-llm-send-and-replace-note} which is bound to @samp{[C-c ,]}. As with the +append command, using a prefix argument will let you edit the instructions +before sending it. + +All prompts sent from a note in org or markdown modes have a prelude that notes +the format of the input and expected output. However, LLMs typically will +produce markdown regardless of what you ask it to do, so if you want to use +LLMs, you may want to use markdown as a default note format. + +@node Using ekg notes as prompts +@subsection Using ekg notes as prompts + +ekg notes are especially well suited for LLM prompting, both because of the +ability to create prompts for different tags, and the ability to transclude one +note's contents within another note. While each "prompt"-tagged note should +work as a standalone LLM prompt, it may be helpful to build up a set of partial +prompts that you can share among many full prompts using transclusion. + +For example, imagine a prompt that is designed to give an Aristotelian response +to a note. A note with tags "aristotle" and "prompt" could have the basics: +"You are Aristotle. Give a response to the note using Aristotle's writing style +and ideas, referencing existing works when possible." But perhaps you also want +some standard behaviors found in other prompts, such as a prompt to encourage +the LLM to ask you questions when appropriate. There may be many prompts in +which that sub-prompt may be applicable. You can use transclusion @ref{Inline commands, , inlines} for +this, adding the transclusion to the appropriate part of the prompt. You can +then iterate on each sub-part, trying to get the best behavior. + +Additionally, transclusion or other inline commands could help in other ways in +forming the prompt, by sharing your schedule, or your current org agenda items +as context to the LLM when it is necessary. These advanced uses will require +inline commands that are not part of the base ekg package, but once written, +they can be seamlessly used in prompts. + +@node Querying your ekg database +@subsection Querying your ekg database + +If you also use embeddings, you can use the interactive function +@samp{ekg-llm-query-with-notes} to find your notes that best match a query, and send +the LLM a prompt consisting of those notes. This essentially will let your +notes act as a natural language queryable knowledge base. It will work for +queries in which you have the relevant information. The answer to the query +will appear in a new buffer. + +The initial part of the prompt instructing the LLM for this case is defined in +@samp{ekg-llm-query-prompt-intro}. This can be changed to tune how the LLM responds +to the query. + +Note that anything in your database could potentially be retrieved and sent to +the LLM, so if you have notes that you consider too private to send for +processing, you may not want to use this. + +@node Auto save +@section Auto save + +The @samp{ekg-auto-save} module is useful for users who enter longer notes, so that the notes are protected against accidentally killing the buffer, or emacs crashing, or any similar problem. It is designed to work similarly to the built-in auto-save functionality, and has it's own variables that default to the auto-save equivalent. So, for example, there is @code{ekg-auto-save-timeout}, which defaults to the value of @code{auto-save-timeout}. + +To start using this, you need to require the module and turn on @samp{ekg-auto-save-mode} in the @samp{ekg-edit-mode` and =ekg-capture-mode}. For example: +@lisp +(require 'ekg-auto-save) +(add-hook 'ekg-capture-mode-hook 'ekg-auto-save-mode) +(add-hook 'ekg-edit-mode-hook 'ekg-auto-save-mode) +@end lisp + +In the capture mode, this. is equivalent to saving periodically (to drafts). In edit mode, it will save the latest version while editing. + +@node Denote +@section Denote + +ekg can export notes as denotes. Denote is a note taking and file +naming tool. Primary reason for export is taking backup of your notes +in a git backed repository. Import is in road map (PR is welcome). To +export to denote, @samp{ekg-denote} module is required. + +@lisp +(use-package ekg-denote + :init + (setq ekg-denote-export-add-front-matter nil) + (setq ekg-denote-export-backup-on-conflict t) + (setq ekg-denote-export-title-max-len 50) + (setq ekg-denote-export-combined-keywords-len 150)) +@end lisp + +To export, call @samp{ekg-denote-export} which will export any modified +note after the last export as a denote in the @samp{denote-directory} +defined by @samp{denote} package. If it is your first time exporting, all +the notes will be exported to the @samp{denote-directory}. + +@samp{ekg-denote} keeps record of the last export time in the ekg db and +use that to find the notes changed since last export. This way the +exports are much faster. It is suggested to export your notes every +day. + +User can optionally enable adding of denote front-matter to exported +denotes by setting @samp{ekg-denote-export-add-front-matter} to +@samp{t}. Denote front matter is added using @samp{denote-add-front-matter} +function defined by @samp{denote} package which open note in an emacs +buffer and requires manual execution of @samp{save-buffer} by the user. + +Ekg and denote have differences, due to which following customization +are made available: + +@itemize +@item +@samp{ekg-denote-export-title-max-len} to trim the title during export. Default +is 50 characters. +@item +@samp{ekg-denote-export-combined-keywords-len} to trim the combined length of +keywords. Default is 150 characters. +@item +@samp{ekg-denote-export-backup-on-conflict} to backup the denote if both +ekg and denote are found to be updated after last export. Default is +@samp{t}. +@end itemize + +It is user responsibility to backup the denotes before and after +export to protect against accidental deletes. This can be easily done +by keeping denotes in a git repository and making sure to check-in any +changes before and after export. + +If ekg and denote are both found to be updated after the last export +which should ideally not happen, denote is updated with ekg. A backup +is taken based on @samp{ekg-denote-export-backup-on-conflict} setting. + +@node Email +@section Email + +The @samp{ekg-email} module adds schema for storing the @samp{to}, @samp{from}, and @samp{cc} email +addresses associated with an email, and a way to import an email into notes from +Gnus. Other importing methods may be available in the future. + +While in a Gnus article buffer, you can execute the command @samp{ekg-email-from-gnus}. + +Once mails are imported, you can search for email addresses associated with them with @samp{ekg-email-show-notes-with-address}. + +@node Design +@chapter Design + +@menu +* The triple database:: +* The metadata section:: +@end menu + +@node The triple database +@section The triple database + +The ekg package uses the triples package to interface with a sqlite database. +The reason a database is useful, even for text, is because databases are +extremely fast, very flexible, and extremely easy to change. In general, the +less your data looks like just files with text in them, the more a database +makes sense. In ekg, we can separate the notion of tags from the text, which +makes writing functions such as @samp{ekg-global-rename-tag} trivial, and the +execution extremely fast. + +The decision to use the triples package, though, is related to a different +design choice. In a triple-based system, there's only one database table with +four columns, a @samp{subject}, @samp{predicate}, @samp{object}, and @samp{properties}. One way to +think of this schema is that it defines links of different types from a subject +to an object. This is combined with a schema, itself defined in triples. The +triples define that subjects can have types, and those types can have +properties. Those properties are expressed in this triple format. In ekg, the +subjects correspond to the IDs of the notes, or tags. Subjects can have +multiple types, and data is factored into types that belong together, with a +specific meaning. To give an example, listing out the data for a note might +look something like: + +@example +33204698034|base/type|tagged|() +33204698034|tagged/tag|"date/2022-11-06"|(:index 0) +33204698034|tagged/tag|"lentil stew"|(:index 1) +33204698034|base/type|text|() +33204698034|text/text|"Made a great lentil stew with dried porcini mushrooms and delicata squash." +33204698034|text/mode|org-mode|() +33204698034|base/type|time-tracked|() +33204698034|time-tracked/creation-time|1667787928|() +33204698034|time-tracked/modified-time|1667787986|() +@end example + +In this example, @samp{33204698034} is the ID for this note. It has a type +(@samp{base/type}), of @samp{tagged}, which means this is something that has tags. The +tags are a list, so the properties contain their index in the list. Because +each one is stored individually, we can easily find all entities with each tag, +by querying on all subjects with a particular object value. This is how reverse +links work in the triples package. In this case, there are two tags, +"date/2022-11-06", and "lentil stew". The note comes from another type, @samp{text}. +And yet another important property, the modification time, is on yet another +type, @samp{time-tracked}. These are all independent. It is possible to have +subjects that have tags but not text, although this doesn't happen currently in +ekg. It's also possible to have any object have a creation and modified time. + +Using a triples scheme has the advantage that it is very easy to integrate with. +All data is very "flat", without having to worry about tables and their schemas. +The uniformity means that it lends itself well to integrations, which typically +would provide a new type and new data. The disadvantage is that it is typically +less efficient to query, at least for more complicated queries. On databases +that typically will be used with ekg, this should be not noticeable. + +IDs, stored as subjects, can be resources. This is useful when we want to store +data about some unique thing, such as an URL@. Because triples define a graph, +every object can be a subject. For an example, if some data in the graph has a +value of "@uref{http://emacs.org}", then we can attach more data to that value, such as +tags, notes or anything else. This is how we store notes about web pages +(@samp{ekg-capture-url}). Having IDs that are meaningful is also useful to enforce +unique data, and force that data isn't duplicated. For example, with this +design, you couldn't have both a "tag" entity and a "page" entity that are +separate: if they are the same object value, they will be the same subject, with +the same ID@. This leads, in our opinion, to a better design. Also it's useful +to note that IDs can be anything, even different types of objects. Integers, +strings, symbols. This is useful, because objects can be anything. Because of +the design of the triple database, all data can be expanded on with their own +data, and that data itself expanded on. This seems like a useful property to +have for a personal knowledge system. + +@node The metadata section +@section The metadata section +Because the user may want to modify or create both the text and other database +properties at the same time, we use a single buffer that lets the user do both. +Because of this design choice, we have to divide the buffer up into two +sections: a metadata section and the text section. The metadata section is on +top, and has a specific format. Because of this, some @samp{org-mode} functions may +not work correctly, because they assume the whole buffer is an org-mode file. +Without this design, however, it isn't clear how the user could easily see and +modify everything they need. Theoretically, having another window might work, +but this would add other complications: the user might not want several windows, +the user might select or bury one of them, and more. There isn't an obvious +ideal solution. It's possible that the design of the capture/edit buffer may +change in the future to fix some of the issues we see with the current +implementation. @bye diff --git a/ekg.el b/ekg.el index 581ed42..a7ecd97 100644 --- a/ekg.el +++ b/ekg.el @@ -977,16 +977,22 @@ ARG is the prefix argument, if used it opens in another window." [remap markdown-follow-thing-at-point] #'ekg--markdown-follow-thing-at-point))) +(defvar ekg-edit-commands-map + (let ((map (make-sparse-keymap))) + (define-key map "a" #'ekg-note-add-tag) + (define-key map "d" #'ekg-note-remove-tag) + (define-key map "t" #'ekg-note-add-title) + (define-key map "T" #'ekg-note-remove-title) + (define-key map "p" #'ekg-note-edit-property) + (define-key map "i" #'ekg-edit-add-inline) + map) + "Keymap for ekg note editing commands.") + (defvar ekg-capture-mode-map (let ((map (make-sparse-keymap))) (define-key map "\C-c\C-c" #'ekg-capture-finalize) (define-key map "\C-c\C-k" #'ekg-capture-abort) - (define-key map "\C-c#" #'ekg-edit-add-inline) - (define-key map "\C-c\C-p" #'ekg-note-edit-property) - (define-key map "\C-c+" #'ekg-note-add-tag) - (define-key map "\C-c-" #'ekg-note-remove-tag) - (define-key map "\C-c\C-t+" #'ekg-note-add-title) - (define-key map "\C-c\C-t-" #'ekg-note-remove-title) + (define-key map "\C-c/" ekg-edit-commands-map) (substitute-key-definition #'save-buffer #'ekg-save-draft map global-map) map) "Key map for `ekg-capture-mode', a minor mode. @@ -1005,12 +1011,7 @@ This is used when capturing new notes.") (defvar ekg-edit-mode-map (let ((map (make-sparse-keymap))) (define-key map "\C-c\C-c" #'ekg-edit-finalize) - (define-key map "\C-c#" #'ekg-edit-add-inline) - (define-key map "\C-c\C-p" #'ekg-note-edit-property) - (define-key map "\C-c+" #'ekg-note-add-tag) - (define-key map "\C-c-" #'ekg-note-remove-tag) - (define-key map "\C-c\C-t+" #'ekg-note-add-title) - (define-key map "\C-c\C-t-" #'ekg-note-remove-title) + (define-key map "\C-c/" ekg-edit-commands-map) (substitute-key-definition #'save-buffer #'ekg-edit-save map global-map) map) "Key map for `ekg-edit-mode', a minor mode.