aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.org52
-rw-r--r--CHANGES.org37
-rw-r--r--README.org22
-rw-r--r--lisp/seam-export.el8
-rw-r--r--lisp/seam-html.el42
-rw-r--r--lisp/seam-test.el36
-rw-r--r--lisp/seam.el25
7 files changed, 136 insertions, 86 deletions
diff --git a/CHANGELOG.org b/CHANGELOG.org
new file mode 100644
index 0000000..1350e0e
--- /dev/null
+++ b/CHANGELOG.org
@@ -0,0 +1,52 @@
+** Changes since 0.1.0
+
+*** Breaking changes
+
+- Seam's code has been moved to the =lisp/= subdirectory, where it
+ should have been all along. Make sure to update your =init.el=
+ accordingly.
+
+**** Renamed functions
+
+- =seam-replace-string-in-notes= is now
+ =seam-replace-string-in-all-notes=.
+
+- =seam-visited-files= is now =seam-visited-notes=.
+
+*** New features
+
+- An option has been added to export internal links with a custom CSS
+ class. The default is set by =seam-export-internal-link-class=, and
+ can overridden per-export using =:internal-link-class=.
+
+- Custom slugs can now be set by adding the =SEAM_SLUG= property to a
+ note's title headline.
+
+*** Improvements
+
+- Notes are no longer re-exported unnecessarily whenever a linked note
+ is changed.
+
+- When invoking =seam-delete-note=, the note's title is now mentioned.
+ This is to reduce the risk of deleting the wrong note by mistake.
+
+- Completion support is somewhat improved. =ido-completing-read= now
+ works properly, and Seam no longer binds =completion-ignore-case=.
+
+*** Bugfixes
+
+- Notes with single quotes in the name (') are no longer broken.
+
+- =seam-visited-notes= no longer returns buffers that visit non-note
+ files within =seam-note-directory=. This could have resulted in
+ Seam inappropriately modifying those files (e.g. updating links).
+
+- Buffer titles are now set correctly from narrowed buffers.
+
+- An issue with regexp escape sequences being interpreted in template
+ variable replacements has been fixed.
+
+- Seam now validates note types entered with =C-u seam-set-note-type=,
+ averting any mishaps if an invalid type is entered.
+
+- It is no longer possible to create a note with an empty slug.
diff --git a/CHANGES.org b/CHANGES.org
deleted file mode 100644
index bee357b..0000000
--- a/CHANGES.org
+++ /dev/null
@@ -1,37 +0,0 @@
-** Changes since 0.1.0
-
-*** Breaking changes
-
-- Code has been moved to =lisp/= subdirectory, where it should have
- been all along. Make sure to add =seam/lisp= to your =load-path=
- instead of just =seam=.
-
-**** Renamed functions
-
-- =seam-replace-string-in-notes= is now =seam-replace-string-in-all-notes=.
-- =seam-visited-files= is now =seam-visited-notes=.
-
-*** New features
-
-- Option to export internal links with a custom CSS class
- (=seam-export-internal-link-class= / =:internal-link-class=).
-- =SEAM_SLUG= property can be added to title headline to set a custom
- slug.
-
-*** Improvements
-
-- Notes are no longer re-exported unnecessarily whenever a linked note
- is changed.
-- Title is now mentioned when deleting a note, to make it less likely
- you delete the wrong one by mistake.
-
-*** Bugfixes
-
-- =seam-visited-notes= no longer returns buffers visiting non-note
- files within the Seam directory.
-- Buffer titles are now set correctly from narrowed buffers.
-- An issue with regexp escape sequences being interpreted in template
- variable replacements has been fixed.
-- Seam now validates note types entered with =C-u seam-set-note-type=,
- averting any mishaps if your completing-read function returns an
- invalid type.
diff --git a/README.org b/README.org
index f0b9069..4e2bff5 100644
--- a/README.org
+++ b/README.org
@@ -10,14 +10,18 @@ Three of Seam's key design tenets are:
- Org files and their resultant HTML files should always be kept in
sync.
+
- It should be easy to create multiple sites using different subsets
of the same note collection.
+
- Notes should not be unnecessarily clouded with metadata.
Be aware that Seam is a fully self-contained package, and is not
likely to be compatible with things like [[https://www.orgroam.com/][Org-roam]] due to its vastly
different approach.
+*Note:* Requires Emacs 29 or greater, with Org 9.6 or greater.
+
*** Getting started
The easiest way to begin is to follow the brief [[https://wiki.plexwave.org/seam-tutorial][tutorial]].
@@ -29,3 +33,21 @@ page]] contains some more tidbits you might find useful.
I have endeavored to make Seam fairly self-documenting, so check the
docstrings and the Seam customization group when in any doubt.
+
+*** Known issues
+
+- =find-file= does not create notes properly. You should use
+ =seam-find-note= instead.
+
+- Commented-out links are not ignored, e.g. for determining backlinks.
+
+- Tags in note title headlines are not ignored; they are treated as
+ part of the title.
+
+- =seam:= links /must/ have a description. Bare links are not
+ supported.
+
+*** Upgrading
+
+As a new project, Seam is very much in flux. Whenever you upgrade it,
+please see the [[file:CHANGELOG.org][changelog]] for breaking changes, new features, etc.
diff --git a/lisp/seam-export.el b/lisp/seam-export.el
index 7fa6c2d..d0f59cc 100644
--- a/lisp/seam-export.el
+++ b/lisp/seam-export.el
@@ -226,12 +226,12 @@ notes)."
(defun seam-export--generate-backlinks (file)
(seam-export--to-string
- (let ((files (sort
+ (let ((files (cl-sort
(let ((seam--subset seam-export--types))
(cl-loop for x in (seam-get-links-to-file file)
collect (cons (seam-get-title-from-file x) x)))
- :key #'car
- :lessp #'string<)))
+ #'string<
+ :key #'car)))
(when files
(cl-loop for (title . file) in files
do (insert (format "- [[seam:%s][%s]]\n" (file-name-base file) title)))))))
@@ -264,7 +264,7 @@ notes)."
"contents"
(seam-export--to-string
(insert-file-contents note-file)
- (re-search-forward (org-headline-re 1))
+ (re-search-forward "^\\* ")
(org-mode) ;Needed for `org-set-property'.
(org-set-property "seam-title-p" "t")))
(seam-export--replace-variable
diff --git a/lisp/seam-html.el b/lisp/seam-html.el
index 018e56f..e95ff9d 100644
--- a/lisp/seam-html.el
+++ b/lisp/seam-html.el
@@ -1,6 +1,7 @@
;;; seam-html.el --- Seam HTML exporter -*- lexical-binding: t -*-
;; Copyright (C) 2025 Spencer Williams
+;; Copyright (C) 2011-2025 Free Software Foundation, Inc.
;; Author: Spencer Williams <spnw@plexwave.org>
@@ -28,15 +29,34 @@
;; This was blithely hacked together using large chunks of code lifted
;; straight from ox-html.el, and could do with much improvement.
;;
-;; Original ox-html code is licensed under GPL v3+. Copyright (c)
-;; 2011-2025 Free Software Foundation, Inc. Original authors: Carsten
-;; Dominik <carsten.dominik@gmail.com> and Jambunathan K <kjambunathan
-;; at gmail dot com>.
+;; The original authors of ox-html are:
+;; Carsten Dominik <carsten.dominik@gmail.com>
+;; Jambunathan K <kjambunathan at gmail dot com>
;;; Code:
(require 'ox-html)
+;;; Org <9.7 compatibility.
+
+(fset 'seam-html--element-parent-element
+ (if (fboundp 'org-element-parent-element)
+ 'org-element-parent-element
+ 'org-export-get-parent-element))
+
+(fset 'seam-html--element-parent
+ (if (fboundp 'org-element-parent)
+ 'org-element-parent
+ (lambda (node)
+ (org-element-property :parent node))))
+
+(fset 'seam-html--element-type-p
+ (if (fboundp 'org-element-type-p)
+ 'org-element-type-p
+ (lambda (node types)
+ (memq (org-element-type node)
+ (ensure-list types)))))
+
;;; NOTE: This function does not respect `:headline-levels' or
;;; `:html-self-link-headlines'.
(defun seam-html-headline (headline contents info)
@@ -102,8 +122,8 @@ images, set it to:
(lambda (paragraph) (org-element-property :caption paragraph))"
(let ((paragraph (pcase (org-element-type element)
(`paragraph element)
- (`link (org-element-parent element)))))
- (and (org-element-type-p paragraph 'paragraph)
+ (`link (seam-html--element-parent element)))))
+ (and (seam-html--element-type-p paragraph 'paragraph)
(or (not (and (boundp 'seam-html-standalone-image-predicate)
(fboundp seam-html-standalone-image-predicate)))
(funcall seam-html-standalone-image-predicate paragraph))
@@ -187,9 +207,9 @@ INFO is a plist holding contextual information. See
;; do this for the first link in parent (inner image link
;; for inline images). This is needed as long as
;; attributes cannot be set on a per link basis.
- (let* ((parent (org-element-parent-element link))
- (link (let ((container (org-element-parent link)))
- (if (and (org-element-type-p container 'link)
+ (let* ((parent (seam-html--element-parent-element link))
+ (link (let ((container (seam-html--element-parent link)))
+ (if (and (seam-html--element-type-p container 'link)
(org-html-inline-image-p link info))
container
link))))
@@ -268,7 +288,7 @@ INFO is a plist holding contextual information. See
(_
(if (and destination
(memq (plist-get info :with-latex) '(mathjax t))
- (org-element-type-p destination 'latex-environment)
+ (seam-html--element-type-p destination 'latex-environment)
(eq 'math (org-latex--environment-type destination)))
;; Caption and labels are introduced within LaTeX
;; environment. Use "ref" or "eqref" macro, depending on user
@@ -279,7 +299,7 @@ INFO is a plist holding contextual information. See
(seam-html-standalone-image-predicate
#'org-html--has-caption-p)
(counter-predicate
- (if (org-element-type-p destination 'latex-environment)
+ (if (seam-html--element-type-p destination 'latex-environment)
#'org-html--math-environment-p
#'org-html--has-caption-p))
(number
diff --git a/lisp/seam-test.el b/lisp/seam-test.el
index 5d4a562..9ab1b22 100644
--- a/lisp/seam-test.el
+++ b/lisp/seam-test.el
@@ -39,7 +39,7 @@
(defmacro seam-test-environment (&rest body)
(declare (indent 0))
- `(let* ((seam-test-directory (make-temp-file "seam-test" t))
+ `(let* ((seam-test-directory (file-name-as-directory (make-temp-file "seam-test" t)))
(seam-note-directory seam-test-directory)
(default-directory seam-test-directory)
(seam-note-types '("private" "public"))
@@ -66,15 +66,17 @@
(let ,options
(let ,(cl-loop for (name . args) in varlist
collect `(,name (seam-make-note ,@args)))
+ ;; FIXME: It's quite possible for tests to fail in such a way
+ ;; that this does not kill the buffers.
(unwind-protect (progn ,@body)
(mapcar #'kill-buffer (list ,@(mapcar #'car varlist))))))))
-(defun seam-test-remove-testdir (filename)
- (string-remove-prefix (concat seam-test-directory "/") filename))
+(defun seam-test-strip-testdir (filename)
+ (string-remove-prefix seam-test-directory filename))
(defun seam-test-list-files ()
(mapcar
- #'seam-test-remove-testdir
+ #'seam-test-strip-testdir
(directory-files-recursively seam-test-directory "")))
(defun seam-test-add-contents (buffer contents)
@@ -119,9 +121,9 @@
(ert-deftest seam-test-make-note-weird-filename ()
(should
(equal
- '("./Weird file name!" ("private/weird-file-name.org"))
+ '("./Weir'd file name!" ("private/weird-file-name.org"))
(seam-test-with-notes ()
- ((weird "./Weird file name! "))
+ ((weird "./Weir'd file name! "))
(list (buffer-name weird)
(seam-test-list-files))))))
@@ -222,7 +224,7 @@
(seam-test-replace-contents bar "* qux")
(list
(seam-test-links-from-html "html/foo.html")
- (mapcar #'seam-test-remove-testdir (seam-get-links-from-file (buffer-file-name foo)))
+ (mapcar #'seam-test-strip-testdir (seam-get-links-from-file (buffer-file-name foo)))
(seam-test-list-files)))))))
(ert-deftest seam-test-link-update-no-unnecessary-export ()
@@ -297,9 +299,9 @@ the class."
(seam-test-add-contents foo (seam-test-link-to-buffer qux))
(seam-test-add-contents bar (seam-test-link-to-buffer qux))
(list
- (mapcar #'seam-test-remove-testdir (seam-get-links-from-file (buffer-file-name foo)))
- (mapcar #'seam-test-remove-testdir (seam-get-links-to-file (buffer-file-name bar)))
- (mapcar #'seam-test-remove-testdir (seam-get-links-to-file (buffer-file-name qux))))))))
+ (mapcar #'seam-test-strip-testdir (seam-get-links-from-file (buffer-file-name foo)))
+ (mapcar #'seam-test-strip-testdir (seam-get-links-to-file (buffer-file-name bar)))
+ (mapcar #'seam-test-strip-testdir (seam-get-links-to-file (buffer-file-name qux))))))))
(ert-deftest seam-test-delete-note ()
"Test that deleting a note also deletes its HTML and re-exports linking
@@ -358,20 +360,6 @@ backlink."
(insert-file-contents "html/bar.html")
(re-search-forward "<a href=\"/foo.html\">")))))
-(ert-deftest seam-test-backlinks-comment ()
- "Test that a commented-out link does not add a backlink."
- :expected-result :failed
- (should-error
- (identity
- (seam-test-with-notes ((seam-export-template-string "{{backlinks}}"))
- ((foo "foo" "public")
- (bar "bar" "public"))
- (with-current-buffer foo
- (seam-test-add-contents foo (concat "# " (seam-test-link-to-buffer bar))))
- (with-temp-buffer
- (insert-file-contents "html/bar.html")
- (re-search-forward "<a href=\"/foo.html\">"))))))
-
(ert-deftest seam-test-set-type-private ()
"Test that setting a public note to private will delete its HTML file and
update linking HTML files such that they no longer link to it."
diff --git a/lisp/seam.el b/lisp/seam.el
index 589e947..4cc21e7 100644
--- a/lisp/seam.el
+++ b/lisp/seam.el
@@ -33,6 +33,7 @@
;;; Code:
(require 'seam-export)
+(require 'org)
(require 'cl-lib)
(require 'grep)
@@ -71,7 +72,10 @@ naming. Must be a function taking two arguments: TITLE and TYPE."
(mapcar #'car seam-export-alist))
(defun seam-slugify (title)
- (downcase (string-join (string-split title "\\W+" t) "-")))
+ (setq title (string-replace "'" "" title))
+ (setq title (string-split title "\\W+" t))
+ (setq title (string-join title "-"))
+ (downcase title))
(defun seam-lookup-slug (slug)
(cl-dolist (type seam-note-types)
@@ -156,8 +160,7 @@ naming. Must be a function taking two arguments: TITLE and TYPE."
(save-restriction
(widen)
(goto-char 1)
- (ignore-errors
- (re-search-forward (org-headline-re 1))
+ (when (re-search-forward "^\\* " nil t)
(let ((start (point)))
(end-of-line)
(let ((title (string-trim (buffer-substring-no-properties start (point)))))
@@ -175,8 +178,7 @@ naming. Must be a function taking two arguments: TITLE and TYPE."
(save-restriction
(widen)
(goto-char 1)
- (ignore-errors
- (re-search-forward (org-headline-re 1))
+ (when (re-search-forward "^\\* " nil t)
(org-element-property :SEAM_SLUG (org-element-at-point))))))
(seam-slugify (seam-get-title-from-buffer buffer))))
@@ -196,6 +198,8 @@ naming. Must be a function taking two arguments: TITLE and TYPE."
(file (file-name-concat seam-note-directory
type
(concat slug ".org"))))
+ (when (string= "" slug)
+ (error "Cannot create a note with an empty slug"))
(seam--check-conflict slug)
(let ((buffer (funcall (if select #'find-file #'find-file-noselect) file)))
(with-current-buffer buffer
@@ -214,7 +218,7 @@ naming. Must be a function taking two arguments: TITLE and TYPE."
(and self (list self)))))
(let ((files (cl-loop for (title . file) in notes
collect (cons (seam-format-title title (seam-get-note-type file)) file))))
- (let ((completion (string-trim (funcall seam-completing-read-function prompt files))))
+ (let ((completion (string-trim (funcall seam-completing-read-function prompt (mapcar #'car files)))))
(or (assoc completion files)
(cons completion nil)))))))
@@ -503,10 +507,11 @@ Otherwise, it's nil."
update-count)))
(cl-defun seam-set-buffer-name (&optional (buffer (current-buffer)))
- (with-current-buffer buffer
- (rename-buffer
- (seam-format-title (seam-get-title-from-buffer)
- (seam-get-note-type (buffer-file-name buffer))))))
+ (when-let ((title (seam-get-title-from-buffer)))
+ (with-current-buffer buffer
+ (rename-buffer
+ (seam-format-title title
+ (seam-get-note-type (buffer-file-name buffer)))))))
(defun seam-setup-buffer ()
"Setup hooks when loading a Seam file."