diff --git a/doc/org-roam.org b/doc/org-roam.org
index 4879517..4619166 100644
--- a/doc/org-roam.org
+++ b/doc/org-roam.org
@@ -1439,6 +1439,82 @@ Essentially, to migrate notes from v1 to v2, one must:
and the ~ROAM_TAGS~ property for headline nodes
6. Replace existing file links with ID links.
+** How do I publish my notes with an Internet-friendly graph?
+
+The default graph builder creates a graph with an [[https://orgmode.org/worg/org-contrib/org-protocol.html][org-protocol]]
+handler which is convenient when you're working locally but
+inconvenient when you want to publish your notes for remote access.
+Likewise, it defaults to displaying the graph in Emacs which has the
+exact same caveats. This problem is solvable in the following way
+using org-mode's native [[https://orgmode.org/manual/Publishing.html][publishing]] capability:
+
+1. configure org-mode to publish your org-roam notes as a project.
+2. create a function that overrides the default org-protocol link
+ creation function(=org-roam-default-link-builder=).
+3. create a hook that's called at the end of graph creation to copy
+ the generated graph to the appropriate place.
+
+The example code below is used to publish to a local directory where a
+separate shell script copies the files to the remote site.
+
+*** Configure org-mode for publishing
+This has two steps:
+1. Setting of a /roam/ project that publishes your notes.
+2. Configuring the /sitemap.html/ generation.
+3. Setting up =org-publish= to generate the graph.
+
+This will require code like the following:
+#+begin_src emacs-lisp
+ (defun roam-sitemap (title list)
+ (concat "#+OPTIONS: ^:nil author:nil html-postamble:nil\n"
+ "#+SETUPFILE: ./simple_inline.theme\n"
+ "#+TITLE: " title "\n\n"
+ (org-list-to-org list) "\nfile:sitemap.svg"))
+
+ (setq my-publish-time 0) ; see the next section for context
+ (defun roam-publication-wrapper (plist filename pubdir)
+ (org-roam-graph)
+ (org-html-publish-to-html plist filename pubdir)
+ (setq my-publish-time (cadr (current-time))))
+
+ (setq org-publish-project-alist
+ '(("roam"
+ :base-directory "~/roam"
+ :auto-sitemap t
+ :sitemap-function roam-sitemap
+ :sitemap-title "Roam notes"
+ :publishing-function roam-publication-wrapper
+ :publishing-directory "~/roam-export"
+ :section-number nil
+ :table-of-contents nil
+ :style "")))
+#+end_src
+
+*** Overriding the default link creation function
+The code below will generate a link to the generated html file instead
+of the default org-protocol link.
+#+begin_src emacs-lisp
+ (defun org-roam-custom-link-builder (node)
+ (let ((file (org-roam-node-file node)))
+ (concat (file-name-base file) ".html")))
+
+ (setq org-roam-graph-link-builder 'org-roam-custom-link-builder)
+#+end_src
+
+*** Copying the generated file to the export directory
+The default behavior of =org-roam-graph= is to generate the graph and
+display it in Emacs. There is an =org-roam-graph-generation-hook=
+available that provides access to the file names so they can be copied
+to the publishing directory. Example code follows:
+
+#+begin_src emacs-lisp
+ (add-hook 'org-roam-graph-generation-hook
+ (lambda (dot svg) (if (< (- (cadr (current-time)) my-publish-time) 5)
+ (progn (copy-file svg "~/roam-export/sitemap.svg" 't)
+ (kill-buffer (file-name-nondirectory svg))
+ (setq my-publish-time 0)))))
+#+end_src
+
* Developer's Guide to Org-roam
** Org-roam's Design Principle
diff --git a/doc/org-roam.texi b/doc/org-roam.texi
index d6d584d..0e7b33d 100644
--- a/doc/org-roam.texi
+++ b/doc/org-roam.texi
@@ -86,7 +86,6 @@ General Public License for more details.
* Command Index::
* Function Index::
* Variable Index::
-* Bibliography: Bibliography (1).
@detailmenu
--- The Detailed Node Listing ---
@@ -190,6 +189,13 @@ FAQ
* How can I stop Org-roam from creating IDs everywhere?::
* How do I migrate from Roam Research?::
* How to migrate from Org-roam v1?::
+* How do I publish my notes with an Internet-friendly graph?::
+
+How do I publish my notes with an Internet-friendly graph?
+
+* Configure org-mode for publishing::
+* Overriding the default link creation function::
+* Copying the generated file to the export directory::
Developer's Guide to Org-roam
@@ -1059,7 +1065,7 @@ Remove a ref from the node at point.
Since version 9.5, Org has first-class support for citations. Org-roam supports
the caching of both these in-built citations (of form @code{[cite:@@key]}) and @uref{https://github.com/jkitchin/org-ref, org-ref}
-citations (of form (NO@math{_ITEM}@math{_DATA}:key)).
+citations (of form @uref{key}).
Org-roam attempts to load both the @code{org-ref} and @code{org-cite} package when
indexing files, so no further setup from the user is required for citation
@@ -1914,6 +1920,7 @@ Org-mode, and sync your cards to Anki via @uref{https://github.com/FooSoft/anki-
* How can I stop Org-roam from creating IDs everywhere?::
* How do I migrate from Roam Research?::
* How to migrate from Org-roam v1?::
+* How do I publish my notes with an Internet-friendly graph?::
@end menu
@node How do I have more than one Org-roam directory?
@@ -2008,6 +2015,109 @@ and the @code{ROAM_TAGS} property for headline nodes
Replace existing file links with ID links.
@end itemize
+@node How do I publish my notes with an Internet-friendly graph?
+@section How do I publish my notes with an Internet-friendly graph?
+
+The default graph builder creates a graph with an @uref{https://orgmode.org/worg/org-contrib/org-protocol.html, org-protocol}
+handler which is convenient when you're working locally but
+inconvenient when you want to publish your notes for remote access.
+Likewise, it defaults to displaying the graph in Emacs which has the
+exact same caveats. This problem is solvable in the following way
+using org-mode's native @uref{https://orgmode.org/manual/Publishing.html, publishing} capability:
+
+@itemize
+@item
+configure org-mode to publish your org-roam notes as a project.
+
+@item
+create a function that overrides the default org-protocol link
+creation function(@samp{org-roam-default-link-builder}).
+
+@item
+create a hook that's called at the end of graph creation to copy
+the generated graph to the appropriate place.
+@end itemize
+
+The example code below is used to publish to a local directory where a
+separate shell script copies the files to the remote site.
+
+@menu
+* Configure org-mode for publishing::
+* Overriding the default link creation function::
+* Copying the generated file to the export directory::
+@end menu
+
+@node Configure org-mode for publishing
+@subsection Configure org-mode for publishing
+
+This has two steps:
+@itemize
+@item
+Setting of a @emph{roam} project that publishes your notes.
+
+@item
+Configuring the @emph{sitemap.html} generation.
+
+@item
+Setting up @samp{org-publish} to generate the graph.
+@end itemize
+
+This will require code like the following:
+@lisp
+(defun roam-sitemap (title list)
+ (concat "#+OPTIONS: ^:nil author:nil html-postamble:nil\n"
+ "#+SETUPFILE: ./simple_inline.theme\n"
+ "#+TITLE: " title "\n\n"
+ (org-list-to-org list) "\nfile:sitemap.svg"))
+
+(setq my-publish-time 0) ; see the next section for context
+(defun roam-publication-wrapper (plist filename pubdir)
+ (org-roam-graph)
+ (org-html-publish-to-html plist filename pubdir)
+ (setq my-publish-time (cadr (current-time))))
+
+(setq org-publish-project-alist
+ '(("roam"
+ :base-directory "~/roam"
+ :auto-sitemap t
+ :sitemap-function roam-sitemap
+ :sitemap-title "Roam notes"
+ :publishing-function roam-publication-wrapper
+ :publishing-directory "~/roam-export"
+ :section-number nil
+ :table-of-contents nil
+ :style "")))
+@end lisp
+
+@node Overriding the default link creation function
+@subsection Overriding the default link creation function
+
+The code below will generate a link to the generated html file instead
+of the default org-protocol link.
+@lisp
+(defun org-roam-custom-link-builder (node)
+ (let ((file (org-roam-node-file node)))
+ (concat (file-name-base file) ".html")))
+
+(setq org-roam-graph-link-builder 'org-roam-custom-link-builder)
+@end lisp
+
+@node Copying the generated file to the export directory
+@subsection Copying the generated file to the export directory
+
+The default behavior of @samp{org-roam-graph} is to generate the graph and
+display it in Emacs. There is an @samp{org-roam-graph-generation-hook}
+available that provides access to the file names so they can be copied
+to the publishing directory. Example code follows:
+
+@lisp
+(add-hook 'org-roam-graph-generation-hook
+ (lambda (dot svg) (if (< (- (cadr (current-time)) my-publish-time) 5)
+ (progn (copy-file svg "~/roam-export/sitemap.svg" 't)
+ (kill-buffer (file-name-nondirectory svg))
+ (setq my-publish-time 0)))))
+@end lisp
+
@node Developer's Guide to Org-roam
@chapter Developer's Guide to Org-roam
@@ -2260,10 +2370,5 @@ When GOTO is non-nil, go the note without creating an entry."
@printindex vr
-@node Bibliography (1)
-@chapter Bibliography
-
-NO@math{_ITEM}@math{_DATA}:key
-
-Emacs 28.0.50 (Org mode N/A)
+Emacs 29.0.50 (Org mode 9.6)
@bye
diff --git a/extensions/org-roam-graph.el b/extensions/org-roam-graph.el
index 152e620..c7c56d1 100644
--- a/extensions/org-roam-graph.el
+++ b/extensions/org-roam-graph.el
@@ -113,6 +113,25 @@ All other values including nil will have no effect."
(const :tag "no" nil))
:group 'org-roam)
+(defcustom org-roam-graph-link-builder 'org-roam-org-protocol-link-builder
+ "Function used to build the Org-roam graph links.
+Given a node name, return a string to be used for the link fed to
+the graph generation utility."
+ :type 'function
+ :group 'org-roam)
+
+(defcustom org-roam-graph-generation-hook nil
+ "Functions to run after the graph has been generated.
+Each function is called with two arguments: the filename
+containing the graph generation tool, and the generated graph."
+ :type 'hook
+ :group 'org-roam)
+
+(defun org-roam-org-protocol-link-builder (node)
+ "Default org-roam link builder. Generate an org-protocol link using NODE."
+ (concat "org-protocol://roam-node?node="
+ (url-hexify-string (org-roam-node-id node))))
+
;;; Interactive command
;;;###autoload
(defun org-roam-graph (&optional arg node)
@@ -153,7 +172,8 @@ CALLBACK is passed the graph file as its sole argument."
:sentinel (when callback
(lambda (process _event)
(when (= 0 (process-exit-status process))
- (funcall callback temp-graph)))))))
+ (progn (funcall callback temp-graph)
+ (run-hook-with-args 'org-roam-graph-generation-hook temp-dot temp-graph))))))))
(defun org-roam-graph--dot (&optional edges all-nodes)
"Build the graphviz given the EDGES of the graph.
@@ -250,8 +270,7 @@ Handles both Org-roam nodes, and string nodes (e.g. urls)."
(_ title)))))
(setq node-id (org-roam-node-id node)
node-properties `(("label" . ,shortened-title)
- ("URL" . ,(concat "org-protocol://roam-node?node="
- (url-hexify-string (org-roam-node-id node))))
+ ("URL" . ,(funcall org-roam-graph-link-builder node))
("tooltip" . ,(xml-escape-string title)))))
(setq node-id node
node-properties (append `(("label" . ,(concat type ":" node)))