diff options
-rw-r--r-- | CHANGELOG.org | 52 | ||||
-rw-r--r-- | CHANGES.org | 37 | ||||
-rw-r--r-- | README.org | 22 | ||||
-rw-r--r-- | lisp/seam-export.el | 8 | ||||
-rw-r--r-- | lisp/seam-html.el | 42 | ||||
-rw-r--r-- | lisp/seam-test.el | 36 | ||||
-rw-r--r-- | lisp/seam.el | 25 |
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. @@ -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." |