@@
183 | :PROPERTIES:
184 | :CUSTOM_ID: diff
185 | :END:
186 | **** New features
187 | - Each session is isolated, which means that it has its own frame with indirect buffers
188 | - Makes it possible to have several sessions simultaneously open
189 | - Doesn't narrow the original buffer, which continues completely accessible
190 | - Has precise notes (attached to a section of a page)
191 | - Also supports nov.el
192 | - Skeleton extraction (outline and/or annotations)
193 | - Being able to use the closest previous note when no notes are present on the current
194 | page
195 | - Closing all notes not related to the notes present in the current view
196 | - Possibility of overriding some global settings in each document or session
197 |
198 | **** Some annoyances fixed
199 | - Notes not sorted
200 | - Notes not synced when executing different page change commands, eg. goto-page or
201 | beginning-of-buffer
202 | - Sometimes it would start narrowing other parts of the buffer, giving errors when trying
203 | to go to notes.
204 |
205 | *** Changes to make in order to be compatible with documents created by Interleave
206 | This package only works like the multi-pdf mode of Interleave - you can't open a session
207 | without having a parent headline.
208 |
209 | For compatibility with existing notes made with Interleave, you can do one of two things:
210 | - Change the following property names inside the your documents:
211 | | Old | New |
212 | |------------------------+------------------|
213 | | =INTERLEAVE_PDF= | =NOTER_DOCUMENT= |
214 | | =INTERLEAVE_PAGE_NOTE= | =NOTER_PAGE= |
215 |
216 | - Set these variables on your init file:
217 | #+BEGIN_SRC emacs-lisp
218 | (setq org-noter-property-doc-file "INTERLEAVE_PDF"
219 | org-noter-property-note-location "INTERLEAVE_PAGE_NOTE")
220 | #+END_SRC
221 |
222 | ** Acknowledgments
223 | I must thank [[https://github.com/rudolfochrist][Sebastian]] for the original idea and inspiration. Also, many thanks to everyone who
224 | contributed more ideas, reported bugs and submitted PRs :)
225 |
--------------------------------------------------------------------------------
/docs/org-noter-demo.org:
--------------------------------------------------------------------------------
1 | * Opening a notes session
2 | - open PDF
3 | - ~M-x org-noter~
4 | - ~M-x org-noter-set-doc-split-fraction~
5 | - ~M-x org-noter-set-notes-window-location, M-n~ (org-noter-set-layout?) side-by-side | stacked
6 | - ~M-x org-noter-create-skeleton, M-n~
7 |
8 | * Navigating the document and the notes
9 | - ~SPC, n, Page-down, down~ to move forward in document
10 | - ~BACKSPACE, p, Page-up, up~ to move back in document
11 | - ~C-M-n, C-M-p~ to move to next/prev note
12 | - ~C-M-.~ sync document to notes
13 | - ~M-n, M-p~ to move to next/prev page with note(s). Always lands on the
14 | first note of the page.
15 | - ~M-.~ sync notes to document
16 |
17 | * Multicolumn setup (as needed)
18 | - Multiple column note ordering can be set up at the document or heading level
19 | - ~org-noter-pdf-set-columns~ inserts "COLUMN_EDGES" into the property drawer
20 | of the current heading. The command requests the number of columns and then
21 | asks you to click on the right edge of all but the last column. The
22 | property is inherited by all sub-headings.
23 |
24 | * Note insertion
25 |
26 | ** ~insert-note~ (~i~)
27 | - Inserts a note linked to the current page. If no title is specified, then
28 | default title "Notes for page " is used, where
is the pagelabel if
29 | it exists or the page number.
30 | - If text is selected AND it is "short" (see ~defcustom
31 | org-noter-max-short-selected-text-length~) the the selected text becomes
32 | the default title.
33 | - If you type in a title, then the selected text is quoted in the body of the
34 | note. Short selected text is set in ``LaTeX-style quotes,''
35 | #+begin_quote
36 | while long selected text is set inside QUOTE block delimiters.
37 | #+end_quote
38 | - At the title prompt =Note:=, you can use ~M-p~ to "up-arrow" prior note
39 | headings, or ~M-n~ to select from the defaults.
40 | - If you choose a prior note heading, then selected text will be quoted in
41 | that heading.
42 |
43 | ** ~insert-precise-note~ (~M-i~)
44 | - Precise notes always create a new note, even if you choose an existing
45 | prior heading.
46 | - Precise notes are linked to a specific point on the page specified with
47 | vertical and horizontal coordinates.
48 | - The multicolumn property ~COLUMN_EDGES~, set by
49 | ~org-noter-pdf-set-columns~, governs the ordering of precise notes on a
50 | page.
51 | - If no title is specified, then default title "Notes for page
V: % H:
52 | %" is used, where is the pagelabel if it exists or the page number,
53 | is the vertical distance from the top and is the horizontal
54 | position from the left.
55 | - The behavior with selected text (default title, quoting in the body) is the
56 | same as for ~insert-note~.
57 |
58 | ** No-questions note insertion
59 | - ~defcustom org-noter-insert-note-no-questions~ is default ~nil~. If set to
60 | ~t~, the note title minibuffer prompt is bypassed and a note is always
61 | create with the default title. Activate this setting if you rarely or
62 | never type in your own titles.
63 | - Both note insertion styles have a ~toggle-no-questions~ variant to get the
64 | non-default behavior.
65 | - Default keybinding for the ~toggle-no-questions~ variant adds the
66 | control-key (~C-i~ and ~C-M-i~, respectively).
67 | ** Highlighting
68 | - ~defcustom org-noter-highlight-selected-text~ controls the default
69 | highlighting behavior of selected text.
70 | - ~C-u~ prefix to any note insertion command toggles this behavior
71 |
--------------------------------------------------------------------------------
/docs/org_noter_tech_notes.org:
--------------------------------------------------------------------------------
1 | :PROPERTIES:
2 | :ID: 4333050B-D293-4A41-8A14-00E6248FD17B
3 | :DRILL_LAST_INTERVAL: -1.0
4 | :DRILL_REPEATS_SINCE_FAIL: 1
5 | :DRILL_TOTAL_REPEATS: 1
6 | :DRILL_FAILURE_COUNT: 1
7 | :DRILL_AVERAGE_QUALITY: 1.0
8 | :DRILL_EASE: 2.5
9 | :NEXT_REVIEW: [2022-12-29 Thu]
10 | :MATURITY: seedling
11 | :LAST_REVIEW: [2022-12-30 Fri]
12 | :END:
13 | #+title: org-noter-tech-notes
14 | #+filetags: :seedling:
15 |
16 | Context for developing org-noter.
17 |
18 | * TOC :TOC:
19 | - [[#brief-history-of-org-noter][Brief history of org-noter]]
20 | - [[#tech-notes][Tech Notes]]
21 | - [[#session][Session]]
22 | - [[#hooks][Hooks]]
23 | - [[#notes][Notes]]
24 | - [[#locations][Locations]]
25 | - [[#note-taking-behavior][Note taking behavior]]
26 | - [[#notes-file][Notes file]]
27 | - [[#solove-nothing-to-hide][solove-nothing-to-hide]]
28 | - [[#note-from-page-1][Note from page 1]]
29 | - [[#development][Development]]
30 | - [[#unit-tests][Unit tests]]
31 |
32 | * Brief history of org-noter
33 |
34 | [[https://github.com/weirdNox/org-noter][org-noter]] (2018-2020), a re-implementation of the [[https://github.com/rudolfochrist/interleave/][interleave packaage]] (2015-2018) by weirdNox:
35 |
36 | #+begin_quote
37 | Yeah, I made org-noter because it is something I need for studying everyday, but I bet that if I didn't use it, then I would probably lose interest too... We don't have time for everything, so decisions must be made :P
38 | #+end_quote
39 |
40 | [[https://github.com/rudolfochrist/interleave/issues/55][source]]
41 |
42 | In early 2022, c1-g created a fork, [[https://github.com/c1-g/org-noter-plus-djvu][org-noter-plus-djvu]] that split up note creation functionality from the underlying document format making it possible to take notes with pdf, epub and djvu documents.
43 |
44 |
45 | * Tech Notes
46 |
47 | ** Session
48 | org-noter session contains all the relevant info for the current session.
49 | - notes file (or buffer)
50 | - backing document
51 | - ...
52 |
53 | prereq for getting everything else going see =make-org-noter-session=.
54 |
55 | ** Hooks
56 | Hooks are used extensively to manage "non-core" functionality, that is functionality that is mode dependent.
57 |
58 | There are mode specific implementation in =modules/= directory, as well as in =tests/= (for testing).
59 |
60 |
61 | *** Errors
62 | An error like so:
63 | #+begin_src shell
64 | Lisp nesting exceeds ‘max-lisp-eval-depth’
65 | #+end_src
66 |
67 | possibly indicates that a ~run-hook-with-args-until-success~ has failed:
68 |
69 | #+begin_src elisp
70 | (run-hook-with-args-until-success 'org-noter-set-up-document-hook document-property-value)
71 | #+end_src
72 |
73 | in my experience the error indicates a problem with one of the hooks. For example in the code above one of the hooks in =org-noter-set-up-document-hook= may not be valid elisp code (requires more than one argument or another lisp issue).
74 |
75 | I haven't figured out a good way to identify these.
76 |
77 | ** Notes
78 |
79 | There are two types of notes:
80 |
81 | - regular notes
82 | - precise notes
83 |
84 |
85 |
86 | *** Regular Notes
87 |
88 | Notes attached to a "page".
89 |
90 | #+begin_src org-mode
91 | :PROPERTIES:
92 | :NOTER_PAGE: 2
93 | :END:
94 | #+end_src
95 |
96 |
97 | *** Precise notes
98 |
99 | Precise notes include a coordinate vector that allows to identify the selection in the backing document and act accordingly, ie create a highlight.
100 |
101 | #+begin_src org-mode
102 | :PROPERTIES:
103 | :NOTER_PAGE: (75 0.14417344173441735 0.7955390334572491 0.6834688346883468 0.8199091284593144)
104 | :END:
105 | #+end_src
106 |
107 | ** Locations
108 |
109 | Location is a property of every note (see [[Notes]]).
110 |
111 | The concept seems to be poorly defined currently, and most of the code lives in =core=, but maybe it should move to document implementation.
112 |
113 | Currently it's either
114 |
115 | =:NOTER_PAGE: (75 0.14417344173441735 0.7955390334572491 0.6834688346883468 0.8199091284593144)=
116 |
117 | or
118 |
119 | =:NOTER_PAGE: 2=
120 |
121 | ** Note taking behavior
122 |
123 | Sophisticated note taking behavior is possible, based on selection size, etc see [[https://github.com/petermao/org-noter/blob/doc/README.org][Peter's matrix]].
124 |
125 |
126 | ** Notes file
127 |
128 | =notes.org= is in this format:
129 |
130 | #+begin_src org-mode
131 | :PROPERTIES:
132 | :ID: FAKE_90283
133 | :END:
134 | #+TITLE: Test book notes
135 |
136 | * solove-nothing-to-hide
137 | :PROPERTIES:
138 | :NOTER_DOCUMENT: pubs/solove-nothing-to-hide.pdf
139 | :END:
140 | ** Note from page 1
141 | :PROPERTIES:
142 | :NOTER_PAGE: 99
143 | :END:
144 | #+end_src
145 |
146 | Omitting the header causes =org-noter--parse-root= to work incorrectly.
147 |
148 | * Development
149 | ** Unit tests
150 | *** Requirements
151 |
152 | - [[https://github.com/cask/cask][Cask]], a project management tool for Emacs
153 | - [[https://github.com/jorgenschaefer/emacs-buttercup][Buttercup]], behavior driven Emacs testing
154 |
155 | *** Mac
156 |
157 | #+begin_src shell
158 | brew install cask
159 | cask # install dependencies
160 | cask exec buttercup -L . # exec unit tests, in the root of the project
161 | #+end_src
162 |
163 | *** GNU/Linux
164 |
165 | #+begin_src shell
166 | git clone https://github.com/cask/cask.git
167 | make -C cask install
168 |
169 | cd
170 | cask # install dependencies (in root of project, 1 time)
171 | cask exec buttercup -L . # exec unit tests, in the root of the project
172 | #+end_src
173 |
--------------------------------------------------------------------------------
/docs/pre-merge_notes.org:
--------------------------------------------------------------------------------
1 | * Pre-merge deltas w/ *dmitrym0*
2 | In the diffs below the color coding is
3 | #+begin_src diff
4 | - Dmitry [f3f5a05]
5 | + Peter [6488cc6]
6 | #+end_src
7 | ** DONE *-get-buffer-file-name-*
8 | #+begin_src diff
9 | -(defun org-noter-get-buffer-file-name-* (&optional major-mode)
10 | +(defun org-noter-get-buffer-file-name-* (mode)
11 | (bound-and-true-p *-file-name))
12 |
13 | +(add-to-list 'org-noter-get-buffer-file-name-hook #'org-noter-get-buffer-file-name-*)
14 | #+end_src
15 |
16 | - =major-mode= is a native elisp function, =mode= is a better name
17 | - the arg is not used, so the =&optional= is appropriate
18 | - for the =pdf= variant, we both use =(&optional major-mode)=
19 |
20 | proposal: =(&optional mode)= or remove the argument completely.
21 |
22 | ACTIONS: Do this in our own repos before merge
23 | 1. major-mode -> mode in module files
24 | 2. use &optional when the argument is not used in the function
25 |
26 | ** DONE -get-buffer-file-name-hook
27 | #+begin_src diff
28 | -(defcustom org-noter-get-buffer-file-name-hook '(org-noter-get-buffer-file-name-nov org-noter-get-buffer-file-name-pdf)
29 | +(defcustom org-noter-get-buffer-file-name-hook nil
30 | #+end_src
31 |
32 | should be nil in =org-noter-core= and set in modules.
33 |
34 | ACTION: already converged
35 | ** DONE *-get-precise-info-*
36 | #+begin_src diff
37 | -(defun org-noter-*--get-precise-info (major-mode)
38 | +(defun org-noter-*--get-precise-info (major-mode window)
39 | (when (eq major-mode 'djvu-read-mode)
40 | (if (region-active-p)
41 | (cons (mark) (point))
42 | - (while (not (and (eq 'mouse-1 (car event))
43 | - (eq window (posn-window (event-start event)))))
44 | - (setq event (read-event "Click where you want the start of the note to be!")))
45 | - (posn-point (event-start event)))))
46 | + (let ((event nil))
47 | + (while (not (and (eq 'mouse-1 (car event))
48 | + (eq window (posn-window (event-start event)))))
49 | + (setq event (read-event "Click where you want the start of the note to be!")))
50 | + (posn-point (event-start event))))))
51 | #+end_src
52 |
53 | - calling function already calls =org-noter--get-doc-window=
54 | - =window= is used in all document modes
55 |
56 | proposal: change =major-mode= to =mode=, pass in =window=
57 |
58 | ACTION: (done) Dmitry took mine
59 | ** DONE *-goto-location
60 | #+begin_src diff
61 | -(defun org-noter-pdf-goto-location (mode location)
62 | +(defun org-noter-pdf-goto-location (mode location window)
63 | (when (memq mode '(doc-view-mode pdf-view-mode))
64 | (let ((top (org-noter--get-location-top location))
65 | - (window (org-noter--get-doc-window))
66 | (left (org-noter--get-location-left location)))
67 | #+end_src
68 | - calling function already calls =org-noter--get-doc-window=
69 | - nov and djvu don't need the =window= argument
70 |
71 | proposal: we discuss this one, but I think it's better to not call functions
72 | unnecessarily
73 |
74 | ACTION:
75 | pass in window, use &optional as appropriate.
76 |
77 | ** DONE *-check-location-property
78 | #+begin_src diff
79 | (defun org-noter-pdf-check-location-property (&optional property)
80 | "Check if PROPERTY is a valid location property"
81 | - (equal 5 (length (read property))))
82 | + t)
83 | #+end_src
84 |
85 | location can be
86 | 1. page
87 | 2. page v-pos
88 | 3. page v-pos . h-pos
89 |
90 | neither function works properly. need to read the calling function to
91 | determine course of action..
92 |
93 | ACTION: done, gone on Dmitry's side.
94 | P: check diff, remove if it's still there.
95 | ** DONE -doc--get-precise-info
96 | #+begin_src diff
97 | +(defun org-noter-doc--get-precise-info (major-mode window)
98 | + (when (eq major-mode 'doc-view-mode)
99 | (let ((event nil))
100 | (while (not (and (eq 'mouse-1 (car event))
101 | (eq window (posn-window (event-start event)))))
102 | (setq event (read-event "Click where you want the start of the note to be!")))
103 | - (let ((col-row (posn-col-row (event-start event))))
104 | - (org-noter--conv-page-scroll-percentage (+ (window-vscroll) (cdr col-row))
105 | - (+ (window-hscroll) (car col-row))))))))
106 | + (org-noter--conv-page-scroll-percentage (+ (window-vscroll)
107 | + (cdr (posn-col-row (event-start event))))))))
108 | #+end_src
109 | Dmitry removed this function at [9d437bf]
110 |
111 | ACTION: Dmitry revive on his side.
112 | ** DONE --doc-approx-location-hook
113 | #+begin_src diff
114 | (defcustom org-noter--doc-approx-location-hook nil
115 | - "This returns an approximate location if no precise info is passed: (PAGE 0)
116 | - or if precise info is passed, it's (PAGE 0 0 0 0) where 0s are the precise coords)
117 | -"
118 | + "TODO"
119 | :group 'org-noter
120 | :type 'hook)
121 | #+end_src
122 |
123 | docstring needs to be updated.
124 |
125 | ACTION: Dmitry reverted
126 | ** DONE --note-search-no-recurse :11fc0a8:9dfac53:
127 | #+begin_src diff
128 | +(defconst org-noter--note-search-no-recurse (delete 'headline (append org-element-all-elements nil))
129 | + "List of elements that shouldn't be recursed into when searching for notes.")
130 | #+end_src
131 |
132 | called in =org-noter--get-view-info= by =org-element-map=
133 | #+begin_src diff
134 | - nil nil (delete 'headline (append org-element-all-elements nil))))
135 | + nil nil org-noter--note-search-no-recurse)
136 | #+end_src
137 |
138 | but this defconst is used by =org-noter--map-ignore-headings-with-doc-file=, which is
139 | used by all of the sync functions
140 |
141 | probably should keep it, and since we keep it, use it in
142 | =org-noter--get-view-info=
143 |
144 | ACTION: safe for Dmitry to cherry-pick these commits, but
145 | =with-current-buffer= call gets removed. This is the one change I took from
146 | ~cbpnk~
147 | ** DONE org-noter--create-session :9dfac53:
148 | #+begin_src diff
149 | (defun org-noter--create-session (ast document-property-value notes-file-path)
150 | (let* ((raw-value-not-empty (> (length (org-element-property :raw-value ast)) 0))
151 | - (link-p (or (string-match-p org-bracket-link-regexp document-property-value)
152 | + (link-p (or (string-match-p org-link-bracket-re document-property-value)
153 | (string-match-p org-noter--url-regexp document-property-value)))
154 | #+end_src
155 | =org-bracket-link-regexp= is obsolete. keep mine.
156 |
157 | ACTION: safe for Dmitry to cherry-pick
158 | ** DONE org-noter--narrow-to-root (ast) :dfe7df2:
159 | #+begin_src diff
160 | - (when ast
161 | + (when (and ast (not (org-noter--no-heading-p)))
162 | (save-excursion
163 | (goto-char (org-element-property :contents-begin ast))
164 | (org-show-entry)
165 | - (when (org-at-heading-p) (org-narrow-to-subtree))
166 | + (org-narrow-to-subtree)
167 | (org-cycle-hide-drawers 'all))))
168 | #+end_src
169 | "I don't really understand this bit of code, especially what `ast' is, but
170 | it breaks narrowing when multiple documents' notes are stored in a single
171 | file."
172 |
173 | ACTION: safe for Dmitry to cherry-pick
174 | ** DONE org-noter--get-location-page (location) :DM:629fbb6:
175 | #+begin_src diff
176 | "Get the page number given a LOCATION of form (page top . left) or (page . top)."
177 | - (message "===> %s" location)
178 | - (if (listp location)
179 | - (car location)
180 | - location))
181 | + (car location))
182 | #+end_src
183 |
184 | ACTION: Peter -- what happens with a page note (no precise location)? does (car location) make an
185 | error?
186 | Answer: No, (car location) works fine because for a page note, location is a
187 | cons cell, e.g. (19 . 0) by the time it reaches this function.
188 |
189 | @DM -- I think we should go back to the original (car location).
190 |
191 | HISTORY:
192 | - 5bc5754 Ahmed Shariff original code
193 | - c1ed245 c1g moved code from org-noter.el to org-noter-core.el, changing
194 | function name
195 | - 629fbb6 introduced by DM
196 |
197 | ** DONE org-noter-kill-session :9dfac53:
198 | #+begin_src diff
199 | (with-current-buffer notes-buffer
200 | (remove-hook 'kill-buffer-hook 'org-noter--handle-kill-buffer t)
201 | (restore-buffer-modified-p nil))
202 | - (unless org-noter-use-indirect-buffer
203 | + (when org-noter-use-indirect-buffer
204 | (kill-buffer notes-buffer))
205 | #+end_src
206 | kill the notes buffer **when** an indirect buffer is used, not **unless** it
207 | is used
208 |
209 | ACTION: safe for Dmitry to cherry-pick
210 | ** DONE use cl-lib or native elisp hash tables rather than the =ht= package.
211 |
--------------------------------------------------------------------------------
/emacs-devel.el:
--------------------------------------------------------------------------------
1 | (require 'cask "/opt/homebrew/share/emacs/site-lisp/cask/cask.el")
2 | (cask-initialize ".")
3 |
4 | (setq mac-option-modifier 'meta)
5 |
6 | (push (expand-file-name ".") load-path)
7 | (require 'org-noter)
8 |
--------------------------------------------------------------------------------
/modules/org-noter-djvu.el:
--------------------------------------------------------------------------------
1 | ;;; org-noter-djvu.el --- Module for DJVU -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2022 c1-g
4 |
5 | ;; Author: c1-g
6 | ;; Keywords: multimedia
7 |
8 | ;; This program is free software; you can redistribute it and/or modify
9 | ;; it under the terms of the GNU General Public License as published by
10 | ;; the Free Software Foundation, either version 3 of the License, or
11 | ;; (at your option) any later version.
12 |
13 | ;; This program is distributed in the hope that it will be useful,
14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | ;; GNU General Public License for more details.
17 |
18 | ;; You should have received a copy of the GNU General Public License
19 | ;; along with this program. If not, see .
20 |
21 | ;;; Commentary:
22 |
23 | ;;
24 |
25 | ;;; Code:
26 | (require 'org-noter)
27 |
28 | (defun org-noter-djvu--pretty-print-location (location)
29 | (org-noter--with-valid-session
30 | (when (eq (org-noter--session-doc-mode session) 'djvu-read-mode)
31 | (format "%s" (if (or (not (org-noter--get-location-top location)) (<= (org-noter--get-location-top location) 0))
32 | (car location)
33 | location)))))
34 |
35 | (add-to-list 'org-noter--pretty-print-location-hook #'org-noter-djvu--pretty-print-location)
36 | (add-to-list 'org-noter--pretty-print-location-for-title-hook #'org-noter-djvu--pretty-print-location)
37 |
38 | (defun org-noter-djvu-approx-location-cons (mode &optional precise-info _force-new-ref)
39 | (when (eq mode 'djvu-read-mode)
40 | (cons djvu-doc-page (if (or (numberp precise-info)
41 | (and (consp precise-info)
42 | (numberp (car precise-info))
43 | (numberp (cdr precise-info))))
44 | precise-info
45 | (max 1 (/ (+ (window-start) (window-end nil t)) 2))))))
46 |
47 | (add-to-list 'org-noter--doc-approx-location-hook #'org-noter-djvu-approx-location-cons)
48 |
49 | (defun org-noter-djvu--get-precise-info (mode window)
50 | (when (eq mode 'djvu-read-mode)
51 | (if (region-active-p)
52 | (cons (mark) (point))
53 | (let ((event nil))
54 | (while (not (and (eq 'mouse-1 (car event))
55 | (eq window (posn-window (event-start event)))))
56 | (setq event (read-event "Click where you want the start of the note to be!")))
57 | (posn-point (event-start event))))))
58 |
59 | (add-to-list 'org-noter--get-precise-info-hook #'org-noter-djvu--get-precise-info)
60 |
61 | (defun org-noter-djvu-setup-handler (mode)
62 | (when (eq mode 'djvu-read-mode)
63 | (advice-add 'djvu-init-page :after 'org-noter--location-change-advice)
64 | t))
65 |
66 | (add-to-list 'org-noter-set-up-document-hook #'org-noter-djvu-setup-handler)
67 |
68 | (defun org-noter-djvu-goto-location (mode location &optional window)
69 | (when (eq mode 'djvu-read-mode)
70 | (djvu-goto-page (car location))
71 | (goto-char (org-noter--get-location-top location))))
72 |
73 | (add-to-list 'org-noter--doc-goto-location-hook #'org-noter-djvu-goto-location)
74 |
75 | (defun org-noter-djvu--get-current-view (mode)
76 | (when (eq mode 'djvu-read-mode)
77 | (vector 'paged (car (org-noter-djvu-approx-location-cons mode)))))
78 |
79 | (add-to-list 'org-noter--get-current-view-hook #'org-noter-djvu--get-current-view)
80 |
81 | (defun org-noter-djvu--get-selected-text (mode)
82 | (when (and (eq mode 'djvu-read-mode)
83 | (region-active-p))
84 | (buffer-substring-no-properties (mark) (point))))
85 |
86 | (add-to-list 'org-noter-get-selected-text-hook #'org-noter-djvu--get-selected-text)
87 |
88 | (defun org-noter-create-skeleton-djvu (mode)
89 | (when (eq mode 'djvu-read-mode)
90 | (org-noter--with-valid-session
91 | (let* ((ast (org-noter--parse-root))
92 | (top-level (or (org-element-property :level ast) 0))
93 | output-data)
94 | (require 'thingatpt)
95 | (with-current-buffer (djvu-ref outline-buf)
96 | (unless (string= (buffer-string) "")
97 | (push (vector "Skeleton" nil 1) output-data)
98 | (save-excursion
99 | (goto-char (point-min))
100 | (while (not (looking-at "^$"))
101 | (push (vector (string-trim-right (string-trim (thing-at-point 'line t)) " [[:digit:]]+")
102 | (list (string-trim-left (string-trim (thing-at-point 'line t)) ".* "))
103 | (+ 2 (how-many " " (point-at-bol) (point-at-eol)))) output-data)
104 | (forward-line)))))
105 |
106 | (with-current-buffer (org-noter--session-notes-buffer session)
107 | ;; NOTE(nox): org-with-wide-buffer can't be used because we want to reset the
108 | ;; narrow region to include the new headings
109 | (widen)
110 | (save-excursion
111 | (goto-char (org-element-property :end ast))
112 |
113 | (let (last-absolute-level
114 | title location relative-level contents
115 | level)
116 |
117 | (dolist (data (nreverse output-data))
118 | (setq title (aref data 0)
119 | location (aref data 1)
120 | relative-level (aref data 2))
121 |
122 | (setq last-absolute-level (+ top-level relative-level)
123 | level last-absolute-level)
124 |
125 | (org-noter--insert-heading level title)
126 |
127 | (when location
128 | (org-entry-put nil org-noter-property-note-location (org-noter--pretty-print-location location)))
129 |
130 | (when org-noter-doc-property-in-notes
131 | (org-entry-put nil org-noter-property-doc-file (org-noter--session-property-text session))
132 | (org-entry-put nil org-noter--property-auto-save-last-location "nil"))))
133 |
134 | (setq ast (org-noter--parse-root))
135 | (org-noter--narrow-to-root ast)
136 | (goto-char (org-element-property :begin ast))
137 | (when (org-at-heading-p) (outline-hide-subtree))
138 | (org-show-children 2)))
139 | output-data))))
140 |
141 | (add-to-list 'org-noter-create-skeleton-functions #'org-noter-create-skeleton-djvu)
142 |
143 | (provide 'org-noter-djvu)
144 | ;;; org-noter-djvu.el ends here
145 |
--------------------------------------------------------------------------------
/modules/org-noter-nov.el:
--------------------------------------------------------------------------------
1 | ;;; org-noter-nov.el --- Integration with Nov.el -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2022 c1-g
4 |
5 | ;; Author: c1-g
6 | ;; Keywords: multimedia
7 |
8 | ;; This program is free software; you can redistribute it and/or modify
9 | ;; it under the terms of the GNU General Public License as published by
10 | ;; the Free Software Foundation, either version 3 of the License, or
11 | ;; (at your option) any later version.
12 |
13 | ;; This program is distributed in the hope that it will be useful,
14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | ;; GNU General Public License for more details.
17 |
18 | ;; You should have received a copy of the GNU General Public License
19 | ;; along with this program. If not, see .
20 |
21 | ;;; Commentary:
22 |
23 | ;;
24 |
25 | ;;; Code:
26 | (require 'org-noter)
27 |
28 | (defvar nov-documents-index)
29 | (defvar nov-file-name)
30 |
31 | (defun org-noter-get-buffer-file-name-nov (&optional mode)
32 | (bound-and-true-p nov-file-name))
33 |
34 | (add-to-list 'org-noter-get-buffer-file-name-hook #'org-noter-get-buffer-file-name-nov)
35 |
36 | (defun org-noter-nov-approx-location-cons (mode &optional precise-info _force-new-ref)
37 | (org-noter--with-valid-session
38 | (when (eq mode 'nov-mode)
39 | (cons nov-documents-index (if (or (numberp precise-info)
40 | (and (consp precise-info)
41 | (numberp (car precise-info))
42 | (numberp (cdr precise-info))))
43 | precise-info
44 | (max 1 (/ (+ (window-start) (window-end nil t)) 2)))))))
45 |
46 | (add-to-list 'org-noter--doc-approx-location-hook #'org-noter-nov-approx-location-cons)
47 |
48 | (defun org-noter-nov-setup-handler (mode)
49 | (when (eq mode 'nov-mode)
50 | (advice-add 'nov-render-document :after 'org-noter--nov-scroll-handler)
51 | (add-hook 'window-scroll-functions 'org-noter--nov-scroll-handler nil t)
52 | t))
53 |
54 | (add-to-list 'org-noter-set-up-document-hook #'org-noter-nov-setup-handler)
55 |
56 | (defun org-noter-nov--pretty-print-location (location)
57 | (org-noter--with-valid-session
58 | (when (eq (org-noter--session-doc-mode session) 'nov-mode)
59 | (format "%s" (if (or (not (org-noter--get-location-top location)) (<= (org-noter--get-location-top location) 1))
60 | (org-noter--get-location-page location)
61 | location)))))
62 |
63 | (add-to-list 'org-noter--pretty-print-location-hook #'org-noter-nov--pretty-print-location)
64 | (add-to-list 'org-noter--pretty-print-location-for-title-hook #'org-noter-nov--pretty-print-location)
65 |
66 | (defun org-noter-nov--get-precise-info (mode window)
67 | (when (eq mode 'nov-mode)
68 | (if (region-active-p)
69 | (cons (mark) (point))
70 | (let ((event nil))
71 | (while (not (and (eq 'mouse-1 (car event))
72 | (eq window (posn-window (event-start event)))))
73 | (setq event (read-event "Click where you want the start of the note to be!")))
74 | (posn-point (event-start event))))))
75 |
76 | (add-to-list 'org-noter--get-precise-info-hook #'org-noter-nov--get-precise-info)
77 |
78 | (defun org-noter-nov-goto-location (mode location &optional window)
79 | (when (eq mode 'nov-mode)
80 | (setq nov-documents-index (org-noter--get-location-page location))
81 | (nov-render-document)
82 | (goto-char (org-noter--get-location-top location))
83 | ;; NOTE(nox): This needs to be here, because it would be issued anyway after
84 | ;; everything and would run org-noter--nov-scroll-handler.
85 | (recenter)))
86 |
87 | (add-to-list 'org-noter--doc-goto-location-hook #'org-noter-nov-goto-location)
88 |
89 | (defun org-noter-nov--get-current-view (mode)
90 | (when (eq mode 'nov-mode)
91 | (vector 'nov
92 | (org-noter-nov-approx-location-cons mode (window-start))
93 | (org-noter-nov-approx-location-cons mode (window-end nil t)))))
94 |
95 | (add-to-list 'org-noter--get-current-view-hook #'org-noter-nov--get-current-view)
96 |
97 | (defun org-noter-nov--get-selected-text (mode)
98 | (when (and (eq mode 'nov-mode) (region-active-p))
99 | (buffer-substring-no-properties (mark) (point))))
100 |
101 | (add-to-list 'org-noter-get-selected-text-hook #'org-noter-nov--get-selected-text)
102 |
103 |
104 | ;; Shamelessly stolen code from Yuchen Li.
105 | ;; This code is originally from org-noter-plus package.
106 | ;; At https://github.com/yuchen-lea/org-noter-plus
107 |
108 | (defun org-noter--handle-nov-toc-item (ol depth)
109 | (mapcar (lambda (li)
110 | (mapcar (lambda (a-or-ol)
111 | (pcase-exhaustive (dom-tag a-or-ol)
112 | ('a
113 | (vector :depth depth
114 | :title (dom-text a-or-ol)
115 | :href (esxml-node-attribute 'href a-or-ol)))
116 | ('ol
117 | (org-noter--handle-nov-toc-item a-or-ol
118 | (1+ depth)))))
119 | (dom-children li)))
120 | (dom-children ol)))
121 |
122 | (defun org-noter-create-skeleton-epub (mode)
123 | "Epub outline with nov link."
124 | (when (eq mode 'nov-mode)
125 | (require 'esxml)
126 | (require 'nov)
127 | (require 'dom)
128 | (org-noter--with-valid-session
129 | (let* ((ast (org-noter--parse-root))
130 | (top-level (or (org-element-property :level ast) 0))
131 | output-data)
132 | (with-current-buffer (org-noter--session-doc-buffer session)
133 | (let* ((toc-path (cdr (aref nov-documents 0)))
134 | (toc-tree (with-temp-buffer
135 | (insert (nov-ncx-to-html toc-path))
136 | (replace-regexp "\n"
137 | ""
138 | nil
139 | (point-min)
140 | (point-max))
141 | (libxml-parse-html-region (point-min)
142 | (point-max))))
143 | (origin-index nov-documents-index)
144 | (origin-point (point)))
145 | (dolist (item
146 | (nreverse (flatten-tree (org-noter--handle-nov-toc-item toc-tree 1))))
147 | (let ((relative-level (aref item 1))
148 | (title (aref item 3))
149 | (url (aref item 5)))
150 | (apply 'nov-visit-relative-file
151 | (nov-url-filename-and-target url))
152 | (when (not (integerp nov-documents-index))
153 | (setq nov-documents-index 0))
154 | (push (vector title (list nov-documents-index (point)) relative-level) output-data)))
155 | (push (vector "Skeleton" (list 0) 1) output-data)
156 |
157 | (nov-goto-document origin-index)
158 | (goto-char origin-point)))
159 | (save-excursion
160 | (goto-char (org-element-property :end ast))
161 | (with-current-buffer (org-noter--session-notes-buffer session)
162 | (dolist (data output-data)
163 | (setq title (aref data 0)
164 | location (aref data 1)
165 | relative-level (aref data 2))
166 |
167 | (setq last-absolute-level (+ top-level relative-level)
168 | level last-absolute-level)
169 |
170 | (org-noter--insert-heading level title)
171 |
172 | (when location
173 | (org-entry-put nil org-noter-property-note-location (org-noter--pretty-print-location location)))
174 |
175 | (when org-noter-doc-property-in-notes
176 | (org-entry-put nil org-noter-property-doc-file (org-noter--session-property-text session))
177 | (org-entry-put nil org-noter--property-auto-save-last-location "nil")))
178 | (setq ast (org-noter--parse-root))
179 | (org-noter--narrow-to-root ast)
180 | (goto-char (org-element-property :begin ast))
181 | (outline-hide-subtree)
182 | (org-show-children 2)))
183 | output-data))))
184 |
185 | (add-to-list 'org-noter-create-skeleton-functions #'org-noter-create-skeleton-epub)
186 |
187 | (provide 'org-noter-nov)
188 | ;;; org-noter-nov.el ends here
189 |
--------------------------------------------------------------------------------
/modules/org-noter-pdf.el:
--------------------------------------------------------------------------------
1 | ;;; org-noter-pdf.el --- Modules for PDF-Tools and DocView mode -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2022 c1-g
4 |
5 | ;; Author: c1-g
6 | ;; Keywords: multimedia
7 |
8 | ;; This program is free software; you can redistribute it and/or modify
9 | ;; it under the terms of the GNU General Public License as published by
10 | ;; the Free Software Foundation, either version 3 of the License, or
11 | ;; (at your option) any later version.
12 |
13 | ;; This program is distributed in the hope that it will be useful,
14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | ;; GNU General Public License for more details.
17 |
18 | ;; You should have received a copy of the GNU General Public License
19 | ;; along with this program. If not, see .
20 |
21 | ;;; Commentary:
22 |
23 | ;;
24 |
25 | ;;; Code:
26 | (require 'org-noter)
27 |
28 |
29 | (cl-defstruct pdf-highlight page coords)
30 |
31 |
32 | (defun org-noter-pdf--get-highlight ()
33 | "If there's an active pdf selection, returns a that contains all
34 | the relevant info (page, coordinates)
35 |
36 | Otherwise returns nil"
37 | (if-let* ((_ (pdf-view-active-region-p))
38 | (page (image-mode-window-get 'page))
39 | (coords (pdf-view-active-region)))
40 | (make-pdf-highlight :page page :coords coords)
41 | nil))
42 |
43 | (add-to-list 'org-noter--get-highlight-location-hook 'org-noter-pdf--get-highlight)
44 |
45 | (defun org-noter-pdf--pretty-print-highlight (highlight-info)
46 | (format "%s" highlight-info))
47 |
48 | (add-to-list 'org-noter--pretty-print-highlight-location-hook #'org-noter-pdf--pretty-print-highlight)
49 |
50 | (defun org-noter-pdf-approx-location-cons (mode &optional precise-info _force-new-ref)
51 | "Returns (page . 0) except when creating a precise-note,
52 | where (pabe v-pos) or (page v-pos . h-pos) is returned"
53 | (when (memq mode '(doc-view-mode pdf-view-mode))
54 | (cons (image-mode-window-get 'page) (if (or (numberp precise-info)
55 | (and (consp precise-info)
56 | (numberp (car precise-info))
57 | (numberp (cdr precise-info))))
58 | precise-info 0))))
59 |
60 | (add-to-list 'org-noter--doc-approx-location-hook #'org-noter-pdf-approx-location-cons)
61 |
62 | (defun org-noter-get-buffer-file-name-pdf (&optional mode)
63 | "Return the file naming backing the document buffer"
64 | (bound-and-true-p pdf-file-name))
65 |
66 | (add-to-list 'org-noter-get-buffer-file-name-hook #'org-noter-get-buffer-file-name-pdf)
67 |
68 | (defun org-noter-pdf-view-setup-handler (mode)
69 | (when (eq mode 'pdf-view-mode)
70 | ;; (setq buffer-file-name document-path)
71 | (pdf-view-mode)
72 | (add-hook 'pdf-view-after-change-page-hook 'org-noter--doc-location-change-handler nil t)
73 | t))
74 |
75 | (add-to-list 'org-noter-set-up-document-hook #'org-noter-pdf-view-setup-handler)
76 |
77 | (defun org-noter-doc-view-setup-handler (mode)
78 | (when (eq mode 'doc-view-mode)
79 | ;; (setq buffer-file-name document-path)
80 | (doc-view-mode)
81 | (advice-add 'doc-view-goto-page :after 'org-noter--location-change-advice)
82 | t))
83 |
84 | (add-to-list 'org-noter-set-up-document-hook #'org-noter-doc-view-setup-handler)
85 |
86 | (defun org-noter-pdf--pretty-print-location (location)
87 | "Full precision location for property drawers"
88 | (org-noter--with-valid-session
89 | (when (memq (org-noter--session-doc-mode session) '(doc-view-mode pdf-view-mode))
90 | (format "%s" (if (or (not (org-noter--get-location-top location)) (<= (org-noter--get-location-top location) 0))
91 | (car location)
92 | location)))))
93 |
94 | (add-to-list 'org-noter--pretty-print-location-hook #'org-noter-pdf--pretty-print-location)
95 |
96 | (defun org-noter-pdf--pretty-print-location-for-title (location)
97 | "Human readable location with page label and v/h percentages. Doc-view falls back to original pp function."
98 | (org-noter--with-valid-session
99 | (let ((mode (org-noter--session-doc-mode session))
100 | (vpos (org-noter--get-location-top location))
101 | (hpos (org-noter--get-location-left location))
102 | (vtxt "") (htxt "")
103 | pagelabel)
104 | (cond ((eq mode 'pdf-view-mode) ; for default title, reference pagelabel instead of page
105 | (if (> hpos 0)
106 | (setq htxt (format " H: %d%%" (round (* 100 hpos)))))
107 | (if (or (> vpos 0) (> hpos 0))
108 | (setq vtxt (format " V: %d%%" (round (* 100 vpos)))))
109 | (select-window (org-noter--get-doc-window))
110 | (setq pagelabel (pdf-view-current-pagelabel))
111 | (select-window (org-noter--get-notes-window))
112 | (format "%s%s%s" pagelabel vtxt htxt))
113 | ((eq mode 'doc-view-mode) ; fall back to original pp for doc-mode
114 | (org-noter-pdf--pretty-print-location location))))))
115 |
116 | (add-to-list 'org-noter--pretty-print-location-for-title-hook #'org-noter-pdf--pretty-print-location-for-title)
117 |
118 | (defun org-noter-pdf--get-precise-info (mode window)
119 | (when (eq mode 'pdf-view-mode)
120 | (let (v-position h-position)
121 | (if (pdf-view-active-region-p)
122 | (let ((edges (car (pdf-view-active-region))))
123 | (setq v-position (min (nth 1 edges) (nth 3 edges))
124 | h-position (min (nth 0 edges) (nth 2 edges))))
125 |
126 | (let ((event nil))
127 | (while (not (and (eq 'mouse-1 (car event))
128 | (eq window (posn-window (event-start event)))))
129 | (setq event (read-event "Click where you want the start of the note to be!")))
130 | (let* ((col-row (posn-col-row (event-start event)))
131 | (click-position (org-noter--conv-page-scroll-percentage (+ (window-vscroll) (cdr col-row))
132 | (+ (window-hscroll) (car col-row)))))
133 | (setq v-position (car click-position)
134 | h-position (cdr click-position)))))
135 | (cons v-position h-position))))
136 |
137 | (add-to-list 'org-noter--get-precise-info-hook #'org-noter-pdf--get-precise-info)
138 |
139 | (defun org-noter-doc--get-precise-info (mode window)
140 | (when (eq mode 'doc-view-mode)
141 | (let ((event nil))
142 | (while (not (and (eq 'mouse-1 (car event))
143 | (eq window (posn-window (event-start event)))))
144 | (setq event (read-event "Click where you want the start of the note to be!")))
145 | (org-noter--conv-page-scroll-percentage (+ (window-vscroll)
146 | (cdr (posn-col-row (event-start event))))))))
147 |
148 | (add-to-list 'org-noter--get-precise-info-hook #'org-noter-doc--get-precise-info)
149 |
150 | (defun org-noter-pdf-goto-location (mode location window)
151 | (when (memq mode '(doc-view-mode pdf-view-mode))
152 | (let ((top (org-noter--get-location-top location))
153 | (window (org-noter--get-doc-window))
154 | (left (org-noter--get-location-left location)))
155 |
156 | (if (eq mode 'doc-view-mode)
157 | (doc-view-goto-page (org-noter--get-location-page location))
158 | (pdf-view-goto-page (org-noter--get-location-page location))
159 | ;; NOTE(nox): This timer is needed because the tooltip may introduce a delay,
160 | ;; so syncing multiple pages was slow
161 | (when (>= org-noter-arrow-delay 0)
162 | (when org-noter--arrow-location (cancel-timer (aref org-noter--arrow-location 0)))
163 | (setq org-noter--arrow-location
164 | (vector (run-with-idle-timer org-noter-arrow-delay nil 'org-noter--show-arrow)
165 | window
166 | top
167 | left))))
168 | (image-scroll-up (- (org-noter--conv-page-percentage-scroll top)
169 | (floor (+ (window-vscroll) org-noter-vscroll-buffer)))))))
170 |
171 | (add-to-list 'org-noter--doc-goto-location-hook #'org-noter-pdf-goto-location)
172 |
173 | (defun org-noter-pdf--get-current-view (mode)
174 | (when (memq mode '(doc-view-mode pdf-view-mode))
175 | (vector 'paged (car (org-noter-pdf-approx-location-cons mode)))))
176 |
177 | (add-to-list 'org-noter--get-current-view-hook #'org-noter-pdf--get-current-view)
178 |
179 | (defun org-noter-pdf--get-selected-text (mode)
180 | (when (and (eq mode 'pdf-view-mode)
181 | (pdf-view-active-region-p))
182 | (mapconcat 'identity (pdf-view-active-region-text) ? )))
183 |
184 | (add-to-list 'org-noter-get-selected-text-hook #'org-noter-pdf--get-selected-text)
185 |
186 | ;; NOTE(nox): From machc/pdf-tools-org
187 | (defun org-noter--pdf-tools-edges-to-region (edges)
188 | "Get 4-entry region (LEFT TOP RIGHT BOTTOM) from several EDGES."
189 | (when edges
190 | (let ((left0 (nth 0 (car edges)))
191 | (top0 (nth 1 (car edges)))
192 | (bottom0 (nth 3 (car edges)))
193 | (top1 (nth 1 (car (last edges))))
194 | (right1 (nth 2 (car (last edges))))
195 | (bottom1 (nth 3 (car (last edges)))))
196 | (list left0
197 | (+ top0 (/ (- bottom0 top0) 3))
198 | right1
199 | (- bottom1 (/ (- bottom1 top1) 3))))))
200 |
201 | (defun org-noter-create-skeleton-pdf (mode)
202 | "Create notes skeleton with the PDF outline or annotations."
203 | (when (eq mode 'pdf-view-mode)
204 | (org-noter--with-valid-session
205 | (let* ((ast (org-noter--parse-root))
206 | (top-level (or (org-element-property :level ast) 0))
207 | (options '(("Outline" . (outline))
208 | ("Annotations" . (annots))
209 | ("Both" . (outline annots))))
210 | answer output-data)
211 | (with-current-buffer (org-noter--session-doc-buffer session)
212 | (setq answer (assoc (completing-read "What do you want to import? " options nil t) options))
213 |
214 | (when (memq 'outline answer)
215 | (dolist (item (pdf-info-outline))
216 | (let ((type (alist-get 'type item))
217 | (page (alist-get 'page item))
218 | (depth (alist-get 'depth item))
219 | (title (alist-get 'title item))
220 | (top (alist-get 'top item)))
221 | (when (and (eq type 'goto-dest) (> page 0))
222 | (push (vector title (cons page top) (1+ depth) nil) output-data)))))
223 |
224 | (when (memq 'annots answer)
225 | (let ((possible-annots (list '("Highlights" . highlight)
226 | '("Underlines" . underline)
227 | '("Squigglies" . squiggly)
228 | '("Text notes" . text)
229 | '("Strikeouts" . strike-out)
230 | '("Links" . link)
231 | '("ALL" . all)))
232 | chosen-annots insert-contents pages-with-links)
233 | (while (> (length possible-annots) 1)
234 | (let* ((chosen-string (completing-read "Which types of annotations do you want? "
235 | possible-annots nil t))
236 | (chosen-pair (assoc chosen-string possible-annots)))
237 | (cond ((eq (cdr chosen-pair) 'all)
238 | (dolist (annot possible-annots)
239 | (when (and (cdr annot) (not (eq (cdr annot) 'all)))
240 | (push (cdr annot) chosen-annots)))
241 | (setq possible-annots nil))
242 | ((cdr chosen-pair)
243 | (push (cdr chosen-pair) chosen-annots)
244 | (setq possible-annots (delq chosen-pair possible-annots))
245 | (when (= 1 (length chosen-annots)) (push '("DONE") possible-annots)))
246 | (t
247 | (setq possible-annots nil)))))
248 |
249 | (setq insert-contents (y-or-n-p "Should we insert the annotations contents? "))
250 |
251 | (dolist (item (pdf-info-getannots))
252 | (let* ((type (alist-get 'type item))
253 | (page (alist-get 'page item))
254 | (edges (or (org-noter--pdf-tools-edges-to-region (alist-get 'markup-edges item))
255 | (alist-get 'edges item)))
256 | (top (nth 1 edges))
257 | (item-subject (alist-get 'subject item))
258 | (item-contents (alist-get 'contents item))
259 | name contents)
260 | (when (and (memq type chosen-annots) (> page 0))
261 | (if (eq type 'link)
262 | (cl-pushnew page pages-with-links)
263 | (setq name (cond ((eq type 'highlight) "Highlight")
264 | ((eq type 'underline) "Underline")
265 | ((eq type 'squiggly) "Squiggly")
266 | ((eq type 'text) "Text note")
267 | ((eq type 'strike-out) "Strikeout")))
268 |
269 | (when insert-contents
270 | (setq contents (cons (pdf-info-gettext page edges)
271 | (and (or (and item-subject (> (length item-subject) 0))
272 | (and item-contents (> (length item-contents) 0)))
273 | (concat (or item-subject "")
274 | (if (and item-subject item-contents) "\n" "")
275 | (or item-contents ""))))))
276 |
277 | (push (vector (format "%s on page %d" name page) (cons page top) 'inside contents)
278 | output-data)))))
279 |
280 | (dolist (page pages-with-links)
281 | (let ((links (pdf-info-pagelinks page))
282 | type)
283 | (dolist (link links)
284 | (setq type (alist-get 'type link))
285 | (unless (eq type 'goto-dest) ;; NOTE(nox): Ignore internal links
286 | (let* ((edges (alist-get 'edges link))
287 | (title (alist-get 'title link))
288 | (top (nth 1 edges))
289 | (target-page (alist-get 'page link))
290 | target heading-text)
291 |
292 | (unless (and title (> (length title) 0)) (setq title (pdf-info-gettext page edges)))
293 |
294 | (cond
295 | ((eq type 'uri)
296 | (setq target (alist-get 'uri link)
297 | heading-text (format "Link on page %d: [[%s][%s]]" page target title)))
298 |
299 | ((eq type 'goto-remote)
300 | (setq target (concat "file:" (alist-get 'filename link))
301 | heading-text (format "Link to document on page %d: [[%s][%s]]" page target title))
302 | (when target-page
303 | (setq heading-text (concat heading-text (format " (target page: %d)" target-page)))))
304 |
305 | (t (error "Unexpected link type")))
306 |
307 | (push (vector heading-text (cons page top) 'inside nil) output-data))))))))
308 |
309 |
310 | (when output-data
311 | (if (memq 'annots answer)
312 | (setq output-data
313 | (sort output-data
314 | (lambda (e1 e2)
315 | (or (not (aref e1 1))
316 | (and (aref e2 1)
317 | (org-noter--compare-locations '< (aref e1 1) (aref e2 1)))))))
318 | (setq output-data (nreverse output-data)))
319 |
320 | (push (vector "Skeleton" nil 1 nil) output-data)))
321 |
322 | (with-current-buffer (org-noter--session-notes-buffer session)
323 | ;; NOTE(nox): org-with-wide-buffer can't be used because we want to reset the
324 | ;; narrow region to include the new headings
325 | (widen)
326 | (save-excursion
327 | (goto-char (org-element-property :end ast))
328 |
329 | (let (last-absolute-level
330 | title location relative-level contents
331 | level)
332 | (dolist (data output-data)
333 | (setq title (aref data 0)
334 | location (aref data 1)
335 | relative-level (aref data 2)
336 | contents (aref data 3))
337 |
338 | (if (symbolp relative-level)
339 | (setq level (1+ last-absolute-level))
340 | (setq last-absolute-level (+ top-level relative-level)
341 | level last-absolute-level))
342 |
343 | (org-noter--insert-heading level title)
344 |
345 | (when location
346 | (org-entry-put nil org-noter-property-note-location (org-noter--pretty-print-location location)))
347 |
348 | (when org-noter-doc-property-in-notes
349 | (org-entry-put nil org-noter-property-doc-file (org-noter--session-property-text session))
350 | (org-entry-put nil org-noter--property-auto-save-last-location "nil"))
351 |
352 | (when (car contents)
353 | (org-noter--insert-heading (1+ level) "Contents")
354 | (insert (car contents)))
355 | (when (cdr contents)
356 | (org-noter--insert-heading (1+ level) "Comment")
357 | (insert (cdr contents)))))
358 |
359 | (setq ast (org-noter--parse-root))
360 | (org-noter--narrow-to-root ast)
361 | (goto-char (org-element-property :begin ast))
362 | (outline-hide-subtree)
363 | (org-show-children 2)))
364 | output-data))))
365 |
366 | (add-to-list 'org-noter-create-skeleton-functions #'org-noter-create-skeleton-pdf)
367 |
368 | (defun org-noter-pdf--create-missing-annotation ()
369 | "Add a highlight from a selected note."
370 | (let* ((location (org-noter--parse-location-property (org-noter--get-containing-element))))
371 | (with-selected-window (org-noter--get-doc-window)
372 | (org-noter-pdf-goto-location 'pdf-view-mode location)
373 | (pdf-annot-add-highlight-markup-annotation (cdr location)))))
374 |
375 | (add-to-list 'org-noter--add-highlight-hook #'org-noter-pdf-highlight-location)
376 |
377 | (defun org-noter-pdf-highlight-location (mode precise-location)
378 | "Highlight a precise location in PDF"
379 | (message "---> %s %s" mode precise-location)
380 | (when (and (memq mode '(doc-view-mode pdf-view-mode))
381 | (pdf-view-active-region-p))
382 | (pdf-annot-add-highlight-markup-annotation (pdf-view-active-region))))
383 |
384 | (defun org-noter-pdf-convert-to-location-cons (location)
385 | "converts (page v . h) precise locations to (page v') such that
386 | v' represents the fractional distance through the page along
387 | columns, so it takes values between 0 and the number of columns.
388 | Each column is specified by its right edge as a fractional
389 | horizontal position. Output is nil for standard notes and (page
390 | v') for precise notes."
391 | (if-let* ((_ (and (consp location) (consp (cdr location))))
392 | (bb (current-buffer)) ; debugging code - we are in the doc window,
393 | ; but need to be in the notes window for next
394 | ; line to work
395 | (column-edges-string (org-entry-get nil "COLUMN_EDGES" t))
396 | (right-edge-list (car (read-from-string column-edges-string)))
397 | ;;(ncol (length left-edge-list))
398 | (page (car location))
399 | (v-pos (cadr location))
400 | (h-pos (cddr location))
401 | (column-index (seq-position right-edge-list h-pos #'>=)))
402 | (cons page (+ v-pos column-index))))
403 |
404 | (add-to-list 'org-noter--convert-to-location-cons-hook #'org-noter-pdf-convert-to-location-cons)
405 |
406 | (defun org-noter-pdf-set-columns (num-columns)
407 | "Interactively set the COLUMN_EDGES property for the current heading.
408 |
409 | The number of columns can be given as a prefix or in the
410 | minibuffer. The user is then prompted to click on the right edge
411 | of each column, except for the last one. Subheadings of the
412 | current heading inherit the COLUMN_EDGES property."
413 | (interactive "NEnter number of columns: ")
414 | (select-window (org-noter--get-doc-window))
415 | (let (event
416 | edge-list
417 | (window (car (window-list))))
418 | (dotimes (ii (1- num-columns))
419 | (while (not (and (eq 'mouse-1 (car event))
420 | (eq window (posn-window (event-start event)))))
421 | (setq event (read-event (format "Click on the right boundary of column %d" (1+ ii)))))
422 | (let* ((col-row (posn-col-row (event-start event)))
423 | (click-position (org-noter--conv-page-scroll-percentage (+ (window-vscroll) (cdr col-row))
424 | (+ (window-hscroll) (car col-row))))
425 | (h-position (cdr click-position)))
426 | (setq event nil)
427 | (setq edge-list (append edge-list (list h-position)))))
428 | (setq edge-list (append edge-list '(1)))
429 | (select-window (org-noter--get-notes-window))
430 | (org-entry-put nil "COLUMN_EDGES" (format "%s" (princ edge-list)))))
431 |
432 | (provide 'org-noter-pdf)
433 | ;;; org-noter-pdf.el ends here
434 |
--------------------------------------------------------------------------------
/org-noter-test-utils.el:
--------------------------------------------------------------------------------
1 |
2 | (require 'log4e)
3 |
4 | ;; org-noter-test logger = ont
5 | (log4e:deflogger "ont" "ont %t [%l] %m" "%H:%M:%S")
6 | (ont--log-enable-logging)
7 | (ont--log-enable-debugging)
8 | (ont--log-enable-messaging)
9 | (ont--log-set-level 'info)
10 | (ont--log-debug "ont")
11 |
12 |
13 | (defvar mock-contents-simple-notes-file
14 | "
15 | :PROPERTIES:
16 | :ID: FAKE_1
17 | :END:
18 | #+TITLE: Test book notes (simple)
19 | * solove-nothing-to-hide
20 | :PROPERTIES:
21 | :NOTER_DOCUMENT: pubs/solove-nothing-to-hide.pdf
22 | :END:
23 | ")
24 |
25 | (defvar mock-contents-simple-notes-file-with-a-single-note
26 | ":PROPERTIES:
27 | :ID: FAKE_90283
28 | :END:
29 | #+TITLE: Test book notes
30 |
31 | * solove-nothing-to-hide
32 | :PROPERTIES:
33 | :NOTER_DOCUMENT: pubs/solove-nothing-to-hide.pdf
34 | :END:
35 | ** Note from page 1
36 | :PROPERTIES:
37 | :NOTER_PAGE: 99
38 | :END:
39 | "
40 | )
41 |
42 |
43 |
44 |
45 | ;;;;;;;;;;;
46 | ;; helpers
47 | (defun org-noter-core-test-create-session ()
48 | "Call this manually with an existing notes buffer to generate a new session"
49 | (org-noter--create-session (org-noter--parse-root) "pubs/solove-nothing-to-hide.pdf" org-noter-test-file))
50 |
51 |
52 | (defun with-mock-contents (contents lambda)
53 | "Create a real buffer with CONTENTS and then execute the LAMBDA"
54 | (ont--log-debug "\n--------------------------------------------")
55 |
56 | ;; TODO: when an assert fails in buttercup, an exception (??) is thrown,
57 | ;; so temp file isnt being cleaned up. This is the sledgehammer approach.
58 | ;; Needs to be fixed so that it's cleaned up properly.
59 | (when (boundp 'org-noter-test-file)
60 | (progn
61 | (ont--log-debug (format "Removing org-noter-test-file: %s\n" org-noter-test-file))
62 | (delete-file org-noter-test-file)))
63 | (let* ((tempfile (make-temp-file "Notes" nil ".org" contents)))
64 | (ont--log-debug (format "Creating a tempfile: %s\n" tempfile))
65 | (setq org-noter-test-file tempfile)
66 | (ont--log-debug "Opening the file..")
67 | (org-mode)
68 | (find-file tempfile)
69 | (org-mode)
70 | (ont--log-debug "Starting the test..")
71 | (ont--log-debug "%s" (buffer-string))
72 | (funcall lambda)
73 | (ont--log-debug "About to kill buffer..")
74 | (kill-current-buffer)
75 | (ont--log-debug (format "Removing tempfile %s" tempfile))
76 | (delete-file tempfile)
77 | (ont--log-debug "+++++++++++++++++++++++++++++++++++++++++")
78 | ))
79 |
80 |
81 | ;;;;;;;;;;;;;;;;;;;;;;;;;;
82 | ;; hooks - org-noter calls these
83 |
84 | (defun org-noter-test-get-selected-text (mode)
85 | "⚠️org-noter-core-test-return-text
86 | org-noter-core-test-return-text
87 | org-noter-core-test-return-text
88 | org-noter-core-test-return-text
89 | org-noter-core-test-return-text
90 | ")
91 |
92 |
93 | (defun org-noter-core-test-document-property (&optional param)
94 | org-noter-test-file)
95 |
96 | (defun org-noter-core-test-view-setup-handler (&optional param)
97 | t)
98 |
99 | (defun org-noter-core-test-open-document-functions (&optional doc)
100 | (find-file (org-noter-core-test-document-property)))
101 |
102 | (defun org-noter-core-test-approx-location (major-mode &optional precise-info _force-new-ref)
103 | (cons 99 precise-info))
104 |
105 | (defun org-noter-core-test-get-current-view (mode)
106 | t)
107 |
108 | ;; TODO This doesn't look right
109 | (defun org-noter-core-test-get-precise-info (mode window)
110 | (list 1 2 3 4))
111 |
112 | (defun org-noter-core-test-pretty-print-location (location)
113 | (format "%s" location))
114 |
115 | (defun org-noter-core-test-add-highlight (major-mode precise-info)
116 | t)
117 |
118 | (defun org-noter-core-test-get-current-view (mode)
119 | 'org-noter-core-test-view)
120 |
121 | (defun org-noter-core-test-get-highlight-location ()
122 | "HARDCODED_HIGHLIGHT_LOCATION")
123 |
124 | (defun org-noter-core-test-pretty-print-location-for-title (location)
125 | "TEST PRETTY PRINT LOCATION")
126 |
127 | (defun create-org-noter-test-session ()
128 |
129 | ;; if this is not set; make-session fails and the test crashes with a stack overflow.
130 | (setq org-noter-always-create-frame nil)
131 |
132 | (setq org-noter-highlight-selected-text t)
133 |
134 | ;; setup spies so we can verify that things have been called
135 | (spy-on 'org-noter-test-get-selected-text :and-call-through)
136 | (spy-on 'org-noter-core-test-approx-location :and-call-through)
137 | (spy-on 'org-noter-core-test-get-precise-info :and-call-through)
138 | (spy-on 'org-noter-core-test-add-highlight :and-call-through)
139 | (spy-on 'org-noter-core-test-get-current-view :and-call-through)
140 |
141 | ;; register all the hooks so we can fake a org-noter-test mode
142 | (add-to-list 'org-noter-get-selected-text-hook #'org-noter-test-get-selected-text)
143 | (add-to-list 'org-noter-parse-document-property-hook #'org-noter-core-test-document-property)
144 | (add-to-list 'org-noter-set-up-document-hook #'org-noter-core-test-view-setup-handler)
145 | (add-to-list 'org-noter-open-document-functions #'org-noter-core-test-open-document-functions)
146 | (add-to-list 'org-noter--doc-approx-location-hook #'org-noter-core-test-approx-location)
147 | (add-to-list 'org-noter--get-current-view-hook #'org-noter-core-test-get-current-view)
148 | (add-to-list 'org-noter--get-precise-info-hook #'org-noter-core-test-get-precise-info)
149 | (add-to-list 'org-noter--pretty-print-location-hook #'org-noter-core-test-pretty-print-location)
150 | (add-to-list 'org-noter--pretty-print-location-for-title-hook #'org-noter-core-test-pretty-print-location-for-title)
151 | (add-to-list 'org-noter--add-highlight-hook #'org-noter-core-test-add-highlight)
152 | (add-to-list 'org-noter--get-highlight-location-hook #'org-noter-core-test-get-highlight-location)
153 | )
154 |
155 |
156 |
157 |
158 | (provide 'org-noter-test-utils)
159 |
--------------------------------------------------------------------------------
/org-noter.el:
--------------------------------------------------------------------------------
1 | ;;; org-noter.el --- A synchronized, Org-mode, document annotator -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2017-2018 Gonçalo Santos
4 |
5 | ;; Author: Gonçalo Santos (aka. weirdNox@GitHub)
6 | ;; Homepage: https://github.com/weirdNox/org-noter
7 | ;; Keywords: lisp pdf interleave annotate external sync notes documents org-mode
8 | ;; Package-Requires: ((emacs "24.4") (cl-lib "0.6") (org "9.0"))
9 | ;; Version: 1.4.1
10 |
11 | ;; This file is not part of GNU Emacs.
12 |
13 | ;; This program is free software; you can redistribute it and/or modify
14 | ;; it under the terms of the GNU General Public License as published by
15 | ;; the Free Software Foundation, either version 3 of the License, or
16 | ;; (at your option) any later version.
17 |
18 | ;; This program is distributed in the hope that it will be useful,
19 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 | ;; GNU General Public License for more details.
22 |
23 | ;; You should have received a copy of the GNU General Public License
24 | ;; along with this program. If not, see .
25 |
26 | ;;; Commentary:
27 |
28 | ;; The idea is to let you create notes that are kept in sync when you scroll through the
29 | ;; document, but that are external to it - the notes themselves live in an Org-mode file. As
30 | ;; such, this leverages the power of Org-mode (the notes may have outlines, latex fragments,
31 | ;; babel, etc...) while acting like notes that are made /in/ the document.
32 |
33 | ;; Also, I must thank Sebastian for the original idea and inspiration!
34 | ;; Link to the original Interleave package:
35 | ;; https://github.com/rudolfochrist/interleave
36 |
37 | ;;; Code:
38 | (require 'org-element)
39 | (require 'cl-lib)
40 |
41 | (require 'org-noter-core)
42 |
43 | (declare-function org-entry-put "org")
44 | (declare-function org-with-wide-buffer "org-macs")
45 |
46 | ;;;###autoload
47 | (defun org-noter (&optional arg)
48 | "Start `org-noter' session.
49 |
50 | There are two modes of operation. You may create the session from:
51 | - The Org notes file
52 | - The document to be annotated (PDF, EPUB, ...)
53 |
54 | - Creating the session from notes file -----------------------------------------
55 | This will open a session for taking your notes, with indirect
56 | buffers to the document and the notes side by side. Your current
57 | window configuration won't be changed, because this opens in a
58 | new frame.
59 |
60 | You only need to run this command inside a heading (which will
61 | hold the notes for this document). If no document path property is found,
62 | this command will ask you for the target file.
63 |
64 | With a prefix universal argument ARG, only check for the property
65 | in the current heading, don't inherit from parents.
66 |
67 | With 2 prefix universal arguments ARG, ask for a new document,
68 | even if the current heading annotates one.
69 |
70 | With a prefix number ARG:
71 | - Greater than 0: Open the document like `find-file'
72 | - Equal to 0: Create session with `org-noter-always-create-frame' toggled
73 | - Less than 0: Open the folder containing the document
74 |
75 | - Creating the session from the document ---------------------------------------
76 | This will try to find a notes file in any of the parent folders.
77 | The names it will search for are defined in `org-noter-default-notes-file-names'.
78 | It will also try to find a notes file with the same name as the
79 | document, giving it the maximum priority.
80 |
81 | When it doesn't find anything, it will interactively ask you what
82 | you want it to do. The target notes file must be in a parent
83 | folder (direct or otherwise) of the document.
84 |
85 | You may pass a prefix ARG in order to make it let you choose the
86 | notes file, even if it finds one."
87 | (interactive "P")
88 | (cond
89 | ;; NOTE(nox): Creating the session from notes file
90 | ((eq major-mode 'org-mode)
91 | (let* ((notes-file-path (buffer-file-name))
92 | (document-property (org-noter--get-or-read-document-property
93 | (not (equal arg '(4)))
94 | (equal arg '(16))))
95 | (org-noter-always-create-frame
96 | (if (and (numberp arg) (= arg 0))
97 | (not org-noter-always-create-frame)
98 | org-noter-always-create-frame))
99 | (ast (org-noter--parse-root (vector (current-buffer) document-property)))
100 | (session-id (get-text-property (org-element-property :begin ast) org-noter--id-text-property))
101 | session)
102 |
103 | ;; Check for prefix value
104 | (if (or (numberp arg) (eq arg '-))
105 | ;; Yes, user's given a prefix value.
106 | (cond ((> (prefix-numeric-value arg) 0)
107 | ;; Is the prefix value greater than 0?
108 | (find-file document-property))
109 | ;; Open the document like `find-file'.
110 |
111 | ;; Is the prefix value less than 0?
112 | ((< (prefix-numeric-value arg) 0)
113 | ;; Open the folder containing the document.
114 | (find-file (file-name-directory document-property))))
115 |
116 | ;; No, user didn't give a prefix value
117 | ;; NOTE(nox): Check if it is an existing session
118 | (when session-id
119 | (setq session (cl-loop for session in org-noter--sessions
120 | when (= (org-noter--session-id session) session-id)
121 | return session))))
122 |
123 | (if session
124 | (let* ((org-noter--session session)
125 | (location (org-noter--parse-location-property
126 | (org-noter--get-containing-element))))
127 | (org-noter--setup-windows session)
128 | (when location (org-noter--doc-goto-location location))
129 | (select-frame-set-input-focus (org-noter--session-frame session)))
130 | ;; It's not an existing session, create a new session.
131 | (org-noter--create-session ast document-property notes-file-path))))
132 |
133 | ;; NOTE(nox): Creating the session from the annotated document
134 | ((memq major-mode org-noter-supported-modes)
135 | (if (org-noter--valid-session org-noter--session)
136 | (progn (org-noter--setup-windows org-noter--session)
137 | (select-frame-set-input-focus (org-noter--session-frame org-noter--session)))
138 |
139 | ;; NOTE(nox): `buffer-file-truename' is a workaround for modes that delete
140 | ;; `buffer-file-name', and may not have the same results
141 | (let* ((buffer-file-name (or (run-hook-with-args-until-success 'org-noter-get-buffer-file-name-hook major-mode)
142 | buffer-file-name))
143 | (document-path (or buffer-file-name buffer-file-truename
144 | (error "This buffer does not seem to be visiting any file")))
145 | (document-name (file-name-nondirectory document-path))
146 | (document-base (file-name-base document-name))
147 | (document-directory (if buffer-file-name
148 | (file-name-directory buffer-file-name)
149 | (if (file-equal-p document-name buffer-file-truename)
150 | default-directory
151 | (file-name-directory buffer-file-truename))))
152 | ;; NOTE(nox): This is the path that is actually going to be used, and should
153 | ;; be the same as `buffer-file-name', but is needed for the truename workaround
154 | (document-used-path (expand-file-name document-name document-directory))
155 |
156 | (search-names (flatten-list
157 | (append org-noter-default-notes-file-names
158 | (list (concat document-base ".org"))
159 | (run-hook-with-args-until-success
160 | 'org-noter-find-additional-notes-functions document-path))))
161 | notes-files-annotating ; List of files annotating document
162 | notes-files ; List of found notes files (annotating or not)
163 |
164 | (document-location (org-noter--doc-approx-location)))
165 |
166 | ;; NOTE(nox): Check the search path
167 | (dolist (path org-noter-notes-search-path)
168 | (dolist (name search-names)
169 | (let ((file-name (expand-file-name name path)))
170 | (when (file-exists-p file-name)
171 | (push file-name notes-files)
172 | (when (org-noter--check-if-document-is-annotated-on-file document-path file-name)
173 | (push file-name notes-files-annotating))))))
174 |
175 | ;; NOTE(nox): `search-names' is in reverse order, so we only need to (push ...)
176 | ;; and it will end up in the correct order
177 | (dolist (name search-names)
178 | (let ((directory (locate-dominating-file document-directory name))
179 | file)
180 | (when directory
181 | (setq file (expand-file-name name directory))
182 | (unless (member file notes-files) (push file notes-files))
183 | (when (org-noter--check-if-document-is-annotated-on-file document-path file)
184 | (push file notes-files-annotating)))))
185 |
186 | (setq search-names (nreverse search-names))
187 |
188 | (when (or arg (not notes-files-annotating))
189 | (when (or arg (not notes-files))
190 | (let* ((notes-file-name (completing-read "What name do you want the notes to have? "
191 | search-names nil t))
192 | list-of-possible-targets
193 | target)
194 |
195 | ;; NOTE(nox): Create list of targets from current path
196 | (catch 'break
197 | (let ((current-directory document-directory)
198 | file-name)
199 | (while t
200 | (setq file-name (expand-file-name notes-file-name current-directory))
201 | (when (file-exists-p file-name)
202 | (setq file-name (propertize file-name 'display
203 | (concat file-name
204 | (propertize " -- Exists!" 'face '(:foregorund "green")))))
205 | (push file-name list-of-possible-targets)
206 | (throw 'break nil))
207 |
208 | (push file-name list-of-possible-targets)
209 |
210 | (when (string= current-directory
211 | (setq current-directory
212 | (file-name-directory (directory-file-name current-directory))))
213 | (throw 'break nil)))))
214 | (setq list-of-possible-targets (nreverse list-of-possible-targets))
215 |
216 | ;; NOTE(nox): Create list of targets from search path
217 | (dolist (path org-noter-notes-search-path)
218 | (when (file-exists-p path)
219 | (let ((file-name (expand-file-name notes-file-name path)))
220 | (unless (member file-name list-of-possible-targets)
221 | (when (file-exists-p file-name)
222 | (setq file-name (propertize file-name 'display
223 | (concat file-name
224 | (propertize " -- Exists!" 'face '(:foreground "green"))))))
225 | (push file-name list-of-possible-targets)))))
226 |
227 | (setq target (completing-read "Where do you want to save it? " list-of-possible-targets
228 | nil t))
229 | (set-text-properties 0 (length target) nil target)
230 | (unless (file-exists-p target) (write-region "" nil target))
231 |
232 | (setq notes-files (list target))))
233 |
234 | (when (> (length notes-files) 1)
235 | (setq notes-files (list (completing-read "In which notes file should we create the heading? "
236 | notes-files nil t))))
237 |
238 | (if (member (car notes-files) notes-files-annotating)
239 | ;; NOTE(nox): This is needed in order to override with the arg
240 | (setq notes-files-annotating notes-files)
241 | (with-current-buffer (find-file-noselect (car notes-files))
242 | (goto-char (point-max))
243 | (insert (if (save-excursion (beginning-of-line) (looking-at "[[:space:]]*$")) "" "\n")
244 | "* " document-base)
245 | (org-entry-put nil org-noter-property-doc-file
246 | (file-relative-name document-used-path
247 | (file-name-directory (car notes-files)))))
248 | (setq notes-files-annotating notes-files)))
249 |
250 | (when (> (length (delete-dups notes-files-annotating)) 1)
251 | (setq notes-files-annotating (list (completing-read "Which notes file should we open? "
252 | notes-files-annotating nil t))))
253 |
254 | (with-current-buffer (find-file-noselect (car notes-files-annotating))
255 | (org-with-point-at (point-min)
256 | (catch 'break
257 | (while (re-search-forward (org-re-property org-noter-property-doc-file) nil t)
258 | (when (file-equal-p (expand-file-name (match-string 3)
259 | (file-name-directory (car notes-files-annotating)))
260 | document-path)
261 | (let ((org-noter--start-location-override document-location))
262 | (org-noter arg))
263 | (throw 'break t)))))))))))
264 |
265 | (provide 'org-noter)
266 |
267 | ;;; org-noter.el ends here
268 |
--------------------------------------------------------------------------------
/other/org-noter-citar.el:
--------------------------------------------------------------------------------
1 | ;;; org-noter-citar.el --- Module for finding note files from `citar' -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2021 c1-g
4 |
5 | ;; Author: c1-g
6 | ;; Keywords: convenience
7 |
8 | ;; This program is free software; you can redistribute it and/or modify
9 | ;; it under the terms of the GNU General Public License as published by
10 | ;; the Free Software Foundation, either version 3 of the License, or
11 | ;; (at your option) any later version.
12 |
13 | ;; This program is distributed in the hope that it will be useful,
14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | ;; GNU General Public License for more details.
17 |
18 | ;; You should have received a copy of the GNU General Public License
19 | ;; along with this program. If not, see .
20 |
21 | ;;; Code:
22 | (require 'citar)
23 | (require 'org-ref)
24 | (require 'seq)
25 |
26 | ;; Regexp stolen from org-roam-bibtex; orb-utils-citekey-re.
27 | (defvar org-noter-citar-cite-key-re
28 | (rx
29 | (or
30 | (seq (group-n 2 (regexp
31 | ;; If Org-ref is available, use its types
32 | ;; default to "cite"
33 | (if (boundp 'org-ref-cite-types)
34 | (regexp-opt
35 | (mapcar
36 | (lambda (el)
37 | ;; Org-ref v3 cite type is a list of strings
38 | ;; Org-ref v2 cite type is a plain string
39 | (or (car-safe el) el))
40 | org-ref-cite-types))
41 | "cite")))
42 | ":"
43 | (or
44 | ;; Org-ref v2 style `cite:links'
45 | (group-n 1 (+ (any "a-zA-Z0-9_:.-")))
46 | ;; Org-ref v3 style `cite:Some&key'
47 | (seq (*? (not "&")) "&"
48 | (group-n 1 (+ (any "!#-+./:<>-@^-`{-~-" word))))))
49 | ;; Org-cite [cite/@citations]
50 | (seq "@" (group-n 1 (+ (any "!#-+./:<>-@^-`{-~-" word))))))
51 | "Universal regexp to match citations in ROAM_REFS.
52 |
53 | Supports Org-ref v2 and v3 and Org-cite.")
54 |
55 | (defun org-noter-citar-find-document-from-refs (cite-key)
56 | "Return a note file associated with CITE-KEY.
57 | When there is more than one note files associated with CITE-KEY, have
58 | user select one of them."
59 | (when (and (stringp cite-key) (string-match org-noter-citar-cite-key-re cite-key))
60 | (let* ((key (match-string 1 cite-key))
61 | (files (hash-table-values (citar-get-files key)))
62 | (url (hash-table-values (citar-get-links key)))
63 | (documents (seq-remove #'file-directory-p (append (flatten-list files) (flatten-list url)))))
64 | (cond ((= (length documents) 1)
65 | (car documents))
66 | ((> (length documents) 1)
67 | (completing-read (format "Which document from %s?: " key) documents))))))
68 |
69 | (defun org-noter-citar-find-notes-from-this-file (filename)
70 | (let* ((citekey)
71 | (entry-alist (maphash (lambda (key val)
72 | (when-let ((file (citar-get-value citar-file-variable val)))
73 | (when (string-match-p filename file)
74 | (push key citekey))))
75 | (citar-get-entries))))
76 |
77 | (flatten-list (mapcan (lambda (node)
78 | (org-id-find-id-file (car (split-string node))))
79 | (gethash (car citekey) (citar-get-notes citekey))))))
80 |
81 | (add-to-list 'org-noter-parse-document-property-hook #'org-noter-citar-find-document-from-refs)
82 |
83 | (add-to-list 'org-noter-find-additional-notes-functions #'org-noter-citar-find-notes-from-this-file)
84 |
85 | (provide 'org-noter-citar)
86 | ;;; org-noter-citar.el ends here
87 |
--------------------------------------------------------------------------------
/other/org-noter-dynamic-block.el:
--------------------------------------------------------------------------------
1 | ;;; org-noter-dynamic-block.el --- Use special blocks as notes -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2021 c1-g
4 |
5 | ;; Author: c1-g
6 | ;; Keywords: multimedia
7 |
8 | ;; This program is free software; you can redistribute it and/or modify
9 | ;; it under the terms of the GNU General Public License as published by
10 | ;; the Free Software Foundation, either version 3 of the License, or
11 | ;; (at your option) any later version.
12 |
13 | ;; This program is distributed in the hope that it will be useful,
14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | ;; GNU General Public License for more details.
17 |
18 | ;; You should have received a copy of the GNU General Public License
19 | ;; along with this program. If not, see .
20 |
21 | ;;; Commentary:
22 |
23 | ;;
24 |
25 | ;;; Code:
26 | (require 'org-noter-core)
27 |
28 | (defun org-noter-insert-precise-dynamic-block (&optional toggle-no-questions)
29 | "Insert note associated with a specific location.
30 | This will ask you to click where you want to scroll to when you
31 | sync the document to this note. You should click on the top of
32 | that part. Will always create a new note.
33 |
34 | When text is selected, it will automatically choose the top of
35 | the selected text as the location and the text itself as the
36 | title of the note (you may change it anyway!).
37 |
38 | See `org-noter-insert-note' docstring for more."
39 | (interactive "P")
40 | (org-noter--with-valid-session
41 | (let ((org-noter-insert-note-no-questions (if toggle-no-questions
42 | (not org-noter-insert-note-no-questions)
43 | org-noter-insert-note-no-questions)))
44 | (org-noter-insert-dynamic-block (org-noter--get-precise-info)))))
45 |
46 | (defun org-noter-insert-dynamic-block (&optional precise-info)
47 | "Insert note associated with the current location.
48 |
49 | This command will prompt for a title of the note and then insert
50 | it in the notes buffer. When the input is empty, a title based on
51 | `org-noter-default-heading-title' will be generated.
52 |
53 | If there are other notes related to the current location, the
54 | prompt will also suggest them. Depending on the value of the
55 | variable `org-noter-closest-tipping-point', it may also
56 | suggest the closest previous note.
57 |
58 | PRECISE-INFO makes the new note associated with a more
59 | specific location (see `org-noter-insert-precise-note' for more
60 | info).
61 |
62 | When you insert into an existing note and have text selected on
63 | the document buffer, the variable `org-noter-insert-selected-text-inside-note'
64 | defines if the text should be inserted inside the note."
65 | (interactive)
66 | (org-noter--with-valid-session
67 | (let* ((ast (org-noter--parse-root))
68 | (contents (org-element-contents ast))
69 | (window (org-noter--get-notes-window 'force))
70 | (selected-text
71 | (pcase (org-noter--session-doc-mode session)
72 | ('pdf-view-mode
73 | (when (pdf-view-active-region-p)
74 | (mapconcat 'identity (pdf-view-active-region-text) ? )))
75 |
76 | ((or 'nov-mode 'djvu-read-mode)
77 | (when (region-active-p)
78 | (buffer-substring-no-properties (mark) (point))))))
79 |
80 | force-new
81 | (location (org-noter--doc-approx-location (or precise-info 'interactive) (gv-ref force-new)))
82 | (view-info (org-noter--get-view-info (org-noter--get-current-view) location)))
83 |
84 | (let ((inhibit-quit t))
85 | (with-local-quit
86 | (select-frame-set-input-focus (window-frame window))
87 | (select-window window)
88 |
89 | ;; IMPORTANT(nox): Need to be careful changing the next part, it is a bit
90 | ;; complicated to get it right...
91 |
92 | (let ((point (point))
93 | (minibuffer-local-completion-map org-noter--completing-read-keymap)
94 | collection default default-begin title
95 | (empty-lines-number (if org-noter-separate-notes-from-heading 2 1)))
96 |
97 | (cond
98 | ;; NOTE(nox): Both precise and without questions will create new notes
99 | ((or precise-info force-new)
100 | (setq default (and selected-text (replace-regexp-in-string "\n" " " selected-text))))
101 | (org-noter-insert-note-no-questions)
102 | (t
103 | (dolist (note-cons (org-noter--view-info-notes view-info))
104 | (let ((display (org-element-property :raw-value (car note-cons)))
105 | (begin (org-element-property :begin (car note-cons))))
106 | (push (cons display note-cons) collection)
107 | (when (and (>= point begin) (> begin (or default-begin 0)))
108 | (setq default display
109 | default-begin begin))))))
110 |
111 | ;; NOTE(nox): Inserting a new note
112 | (let ((reference-element-cons (org-noter--view-info-reference-for-insertion view-info))
113 | level)
114 |
115 | (if reference-element-cons
116 | (progn
117 | (cond
118 | ((eq (car reference-element-cons) 'before)
119 | (goto-char (org-element-property :begin (cdr reference-element-cons))))
120 | ((eq (car reference-element-cons) 'after)
121 | (goto-char (org-element-property :end (cdr reference-element-cons)))))
122 | ;; NOTE(nox): This is here to make the automatic "should insert blank" work better.
123 | (when (org-at-heading-p) (backward-char))
124 | (setq level (org-element-property :level (cdr reference-element-cons))))
125 |
126 | (goto-char (or (org-element-map contents 'section
127 | (lambda (section) (org-element-property :end section))
128 | nil t org-element-all-elements)
129 | (org-element-map ast 'section
130 | (lambda (section) (org-element-property :end section))
131 | nil t org-element-all-elements))))
132 |
133 | ;; (setq level (1+ (or (org-element-property :level ast) 0))))
134 |
135 | ;; NOTE(nox): This is needed to insert in the right place
136 | (unless (org-noter--no-heading-p) (outline-show-entry))
137 | ;; (org-noter--insert-heading level title empty-lines-number location)
138 | (insert
139 | "\n"
140 | (string-join (list (format "#+BEGIN: note %s"
141 | (if location
142 | (concat ":" org-noter-property-note-location
143 | (format " %S" location))
144 | ""))
145 | (or selected-text "")
146 | "#+END:")
147 | "\n")
148 | "\n")
149 |
150 | (when (org-noter--session-hide-other session) (org-overview))
151 |
152 | (setf (org-noter--session-num-notes-in-view session)
153 | (1+ (org-noter--session-num-notes-in-view session)))))
154 |
155 | (org-show-set-visibility t)
156 | (org-cycle-hide-drawers 'all)
157 | (org-cycle-show-empty-lines t)))
158 |
159 | (when quit-flag
160 | ;; NOTE(nox): If this runs, it means the user quitted while creating a note, so
161 | ;; revert to the previous window.
162 | (select-frame-set-input-focus (org-noter--session-frame session))
163 | (select-window (get-buffer-window (org-noter--session-doc-buffer session)))))))
164 |
165 | (defun org-dblock-write:note (params)
166 | (let ((location (plist-get params
167 | (intern (concat ":" org-noter-property-note-location))))
168 | (content (plist-get params :content))
169 | (session org-noter--session)
170 | (origin-window (selected-window))
171 | (origin-location))
172 |
173 | (org-noter--with-valid-session
174 | (setq origin-location (org-noter--doc-approx-location))
175 | (when (and location
176 | (org-noter--get-location-top location)
177 | (org-noter--get-location-left location))
178 | (org-noter--doc-goto-location location)
179 | (with-current-buffer (org-noter--session-doc-buffer session)
180 | (setq content
181 | (pcase major-mode
182 | ('pdf-view-mode (pdf-info-gettext (car location) (cdr location)))
183 | ((or 'nov-mode 'djvu-read-mode)
184 | (buffer-substring (org-noter--get-location-top location)
185 | (org-noter--get-location-left location))))))
186 | (org-noter--doc-goto-location origin-location)
187 | (select-window origin-window)))
188 | (insert content)))
189 |
190 | (defun org-noter--get-location-dynamic-block (dblock)
191 | (let ((params (read (concat "(" (org-element-property :arguments dblock) ")"))))
192 | (format "%S" (plist-get params (intern (concat ":" org-noter-property-note-location))))))
193 |
194 | (defun org-noter-get-containing-dynamic-block (&optional _include-root)
195 | (org-noter--with-valid-session
196 | (org-with-wide-buffer
197 | (let ((elt (org-element-at-point)))
198 | (catch 'break
199 | (while (org-element-property :parent elt)
200 | (cond
201 | ((eq (org-element-type elt) 'dynamic-block)
202 | (throw 'break elt))
203 | (t
204 | (setq elt (org-element-property :parent elt))))))))))
205 |
206 | (add-hook 'org-noter--get-containing-element-hook #'org-noter-get-containing-dynamic-block)
207 |
208 | (add-hook 'org-noter--get-location-property-hook #'org-noter--get-location-dynamic-block)
209 |
210 | (provide 'org-noter-dynamic-block)
211 | ;;; org-noter-dynamic-block.el ends here
212 |
213 |
--------------------------------------------------------------------------------
/other/org-noter-nov-overlay.el:
--------------------------------------------------------------------------------
1 | ;;; org-noter-nov-overlay.el --- Module to highlight text in nov-mode with notes -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2021 Charlie Gordon
4 |
5 | ;; Author: Charlie Gordon
6 | ;; Keywords: multimedia
7 |
8 | ;; This program is free software; you can redistribute it and/or modify
9 | ;; it under the terms of the GNU General Public License as published by
10 | ;; the Free Software Foundation, either version 3 of the License,
11 | ;; any later version.
12 |
13 | ;; This program is distributed in the hope that it will be useful,
14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | ;; GNU General Public License for more details.
17 |
18 | ;; You should have received a copy of the GNU General Public License
19 | ;; along with this program. If not, see .
20 |
21 | ;;; Commentary:
22 |
23 | ;; Highlight your precise notes in nov with org-noter-nov-overlay.el
24 |
25 | ;;; Code:
26 | (require 'org-noter)
27 | (require 'nov)
28 | (require 'seq)
29 |
30 | (defcustom org-noter-nov-overlay-color-property "NOTER_OVERLAY"
31 | "A property that specifies the overlay color for `org-noter-nov-make-ov'.")
32 |
33 | (defcustom org-noter-nov-overlay-default-color "SkyBlue"
34 | "Name of the default background color of the overlay `org-noter-nov-make-ov' makes.
35 |
36 | Should be one of the element in `defined-colors'.")
37 |
38 | (defun org-noter-nov-make-overlays ()
39 | (org-noter--with-selected-notes-window
40 | (let* ((page (buffer-local-value 'nov-documents-index (org-noter--session-doc-buffer session)))
41 | (regexp (org-re-property org-noter-property-note-location t nil
42 | (format (rx "(" (* space) "%d" (+ space)
43 | (+ digit) (+ space) "." (+ space)
44 | (+ digit) (* space) ")")
45 | page))))
46 | (org-with-wide-buffer
47 | (goto-char (point-min))
48 | (while (re-search-forward regexp nil t)
49 | (when-let ((location (org-entry-get nil org-noter-property-note-location nil t)))
50 | (org-noter-nov-make-overlay-no-question)))))))
51 |
52 | (defun org-noter-nov-make-overlay ()
53 | "TODO"
54 | (org-noter--with-selected-notes-window
55 | "No notes window exists"
56 | (when (eq (org-noter--session-doc-mode session) 'nov-mode)
57 | (let* ((location-property (org-entry-get nil org-noter-property-note-location nil t))
58 | (location-cons (cdr (read location-property)))
59 | (beg (car location-cons))
60 | (end (cdr location-cons))
61 | (ov-pair (list (make-overlay beg end (org-noter--session-doc-buffer session))))
62 | (hl-color (or (org-entry-get nil org-noter-nov-overlay-color-property nil t)
63 | (if org-noter-insert-note-no-questions
64 | org-noter-nov-overlay-default-color
65 | (read-color "Highlight color: "))))
66 | (hl-color-alt (color-lighten-name hl-color 15))
67 | (action-functions (list
68 | #'org-noter-nov-overlay-sync-current-note
69 | #'org-noter-nov-overlay-sync-current-page-or-chapter)))
70 |
71 | (save-excursion
72 | (org-back-to-heading t)
73 | (re-search-forward org-heading-regexp nil t)
74 | (push (make-overlay (match-beginning 1) (match-end 1)) ov-pair))
75 |
76 | (dolist (ov ov-pair)
77 | (overlay-put ov 'button ov)
78 | (overlay-put ov 'category 'default-button)
79 | (overlay-put ov 'face (list :background hl-color
80 | :foreground (readable-foreground-color hl-color)))
81 |
82 | (org-entry-put nil org-noter-nov-overlay-color-property hl-color)
83 |
84 | (overlay-put ov 'mouse-face (list :background hl-color-alt
85 | :foreground (readable-foreground-color hl-color-alt)))
86 |
87 | (overlay-put ov 'action (pop action-functions)))))))
88 |
89 | (defun org-noter-nov-make-overlay-no-question ()
90 | "Like `org-noter-nov-make-ov', but doesn't ask user to select the overlay color."
91 | (org-noter--with-valid-session
92 | (let ((org-noter-insert-note-no-questions t))
93 | (org-noter-nov-make-overlay))))
94 |
95 | (defun org-noter-nov-overlay-sync-current-page-or-chapter (_overlay)
96 | "A wrapper function for `org-noter-sync-current-page-or-chapter'
97 | used exclusively with overlays made with `org-noter-nov-make-overlay'
98 |
99 | This wrapper ignores the first argument passed to it and just call
100 | `org-noter-sync-current-page-or-chapter'."
101 |
102 | (org-noter-sync-current-page-or-chapter))
103 |
104 | (defun org-noter-nov-overlay-sync-current-note (_overlay)
105 | "A wrapper function for `org-noter-nov-overlay-sync-current-note'
106 | used exclusively with overlays made with `org-noter-nov-make-overlay'
107 |
108 | This wrapper ignores the first argument passed to it and just call
109 | `org-noter-nov-overlay-sync-current-note'."
110 | (org-noter-sync-current-note))
111 |
112 | (add-hook 'nov-post-html-render-hook #'org-noter-nov-make-overlays)
113 |
114 | (provide 'org-noter-nov-overlay)
115 | ;;; org-noter-nov-ov.el ends here
116 |
117 |
--------------------------------------------------------------------------------
/tests/org-noter-core-tests.el:
--------------------------------------------------------------------------------
1 | (add-to-list 'load-path "modules")
2 | (require 'org-noter-pdf)
3 | (require 'with-simulated-input)
4 | (require 'org-noter-test-utils)
5 |
6 |
7 | (describe "org-noter-core"
8 | (before-each
9 | (create-org-noter-test-session)
10 | )
11 |
12 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
13 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
14 | (describe "note taking functionality"
15 | ;; checking to make sure that `with-mock-contents` works fine.
16 | (it "can parse a note file ast that is not empty"
17 | (with-mock-contents
18 | mock-contents-simple-notes-file
19 | '(lambda () (let ((mock-ast (org-noter--parse-root)))
20 | (expect mock-ast :not :to-be nil)))))
21 |
22 | ;; basic note should insert a default heading
23 | (it "can take a basic note"
24 | (with-mock-contents
25 | mock-contents-simple-notes-file
26 | '(lambda ()
27 | (org-noter-core-test-create-session)
28 | (let ((org-noter-insert-note-no-questions t))
29 | (org-noter-insert-note nil "NEW NOTE"))
30 | (expect 'org-noter-test-get-selected-text :to-have-been-called)
31 | (expect (string-match "Notes for page" (buffer-string)) :not :to-be nil))))
32 |
33 | ;; enter a heading when taking a precise note; expect the heading to be there.
34 | (it "can take a precise note"
35 | (with-mock-contents
36 | mock-contents-simple-notes-file
37 | '(lambda ()
38 | (org-noter-core-test-create-session)
39 | (with-simulated-input "precise SPC note RET"
40 | (org-noter-insert-precise-note))
41 | (expect (string-match "precise note" (buffer-string)) :not :to-be nil))))
42 |
43 | ;; there should be precise data in the note properties when entering a precise note
44 | (it "precise note has precise data"
45 | (with-mock-contents
46 | mock-contents-simple-notes-file
47 | '(lambda ()
48 | (org-noter-core-test-create-session)
49 | (with-simulated-input "precise SPC note RET"
50 | (org-noter-insert-precise-note))
51 | (expect (string-match "NOTER_PAGE:" (buffer-string)) :not :to-be nil)
52 | (expect (string-match "BEGIN_QUOTE" (buffer-string)) :not :to-be nil)
53 | (expect 'org-noter-core-test-get-precise-info :to-have-been-called)
54 | )))
55 |
56 | ;; highlight code should be called when a precise note is entered
57 | (it "precise note calls the highlight hook"
58 | (with-mock-contents
59 | mock-contents-simple-notes-file
60 | '(lambda ()
61 | (org-noter-core-test-create-session)
62 | (with-simulated-input "precise SPC note RET"
63 | (org-noter-insert-precise-note))
64 | (expect 'org-noter-core-test-add-highlight :to-have-been-called))))
65 |
66 | ;; hit C-g when entering a note; expect no highlight
67 | (it "precise note DOES NOT call the highlight hook when the note is aborted"
68 | (with-mock-contents
69 | mock-contents-simple-notes-file
70 | '(lambda ()
71 | (org-noter-core-test-create-session)
72 | ;; this is how you trap a C-g
73 | (condition-case nil
74 | (with-simulated-input "C-g" (org-noter-insert-precise-note))
75 | (quit nil))
76 | (expect 'org-noter-core-test-add-highlight :not :to-have-been-called))))
77 |
78 | )
79 |
80 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
81 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
82 | (describe "session creation"
83 | ;; check that the narrowed buffer is named correctly
84 | (it "narrowed buffer is named correctly"
85 | (with-mock-contents
86 | mock-contents-simple-notes-file-with-a-single-note
87 | '(lambda ()
88 | (org-noter-core-test-create-session)
89 | (let* ((session org-noter--session))
90 | (expect (buffer-name (org-noter--session-notes-buffer session)) :to-equal "Notes of solove-nothing-to-hide")
91 | ))))
92 |
93 | ;; check that session properties are set correctly
94 | (it "session properties are set correctly"
95 | (with-mock-contents
96 | mock-contents-simple-notes-file-with-a-single-note
97 | '(lambda ()
98 | (org-noter-core-test-create-session)
99 | (let* ((session org-noter--session))
100 | (expect (org-noter--session-property-text session) :to-equal "pubs/solove-nothing-to-hide.pdf")
101 | (expect (org-noter--session-display-name session) :to-equal "solove-nothing-to-hide")
102 | (expect (org-noter--session-notes-file-path session) :to-equal org-noter-test-file)
103 | (expect (buffer-file-name (org-noter--session-notes-buffer session)) :to-equal org-noter-test-file)
104 | ;; TODO: Need test-specific-major mode somehow?
105 | ;; (expect (org-noter--session-doc-mode session) :to-equal 'org-core-test)
106 | ))))
107 |
108 | )
109 |
110 |
111 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
112 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
113 | (describe "view-info"
114 | (it "can get view info"
115 | (with-mock-contents
116 | mock-contents-simple-notes-file-with-a-single-note
117 | '(lambda ()
118 | (org-noter-core-test-create-session)
119 | (let* ((view-info (org-noter--get-view-info (org-noter--get-current-view))))
120 | (expect 'org-noter-core-test-get-current-view :to-have-been-called)
121 | ))))
122 | )
123 |
124 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
125 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
126 | (describe "locations"
127 | (defvar test-precise-location '(3 1 . 0.1))
128 | (defvar test-simple-location '(3 1))
129 | (defvar test-extra-precise-location '(4 1 0.1 0.2 0.3))
130 |
131 | (describe "precise locations"
132 | (it "can get page from a precise location"
133 | (expect (org-noter--get-location-page test-precise-location) :to-equal 3))
134 |
135 | (it "can get top from a precise location"
136 | (expect (org-noter--get-location-top test-precise-location) :to-equal 1))
137 |
138 | (it "can get left from a precise location"
139 | (expect (org-noter--get-location-left test-precise-location) :to-equal 0.1))
140 | )
141 |
142 | (describe "simple locations"
143 |
144 | (it "doesn't get a left location for simple location"
145 | (expect (org-noter--get-location-left test-simple-location) :to-equal nil)
146 | )
147 |
148 | (it "can get top from a simple location"
149 | (expect (org-noter--get-location-top test-simple-location) :to-equal 1))
150 |
151 | (it "can get page from a simple location"
152 | (expect (org-noter--get-location-page test-simple-location) :to-equal 3))
153 | )
154 |
155 | (describe "extra precise locations"
156 | (it "can get page from an extra precise location"
157 | (expect (org-noter--get-location-page test-extra-precise-location) :to-equal 4))
158 |
159 | (it "can get top from an extra precise location"
160 | (expect (org-noter--get-location-top test-extra-precise-location) :to-equal 1))
161 |
162 |
163 | (it "can get left from an extra precise location"
164 | (expect (org-noter--get-location-left test-extra-precise-location) :to-equal 0.1)))
165 | )
166 |
167 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
168 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
169 | (describe "persistent highlights"
170 | (describe "no hooks are setup for precise note highlights"
171 | ;; if no hooks for highlights are setup we expect no :HIGHLIGHT: property
172 | (before-each
173 | (setq org-noter--get-highlight-location-hook '())
174 | )
175 | (it "can take a precise note without a highlight appearing"
176 | (with-mock-contents
177 | mock-contents-simple-notes-file
178 | '(lambda ()
179 | (org-noter-core-test-create-session)
180 | (with-simulated-input "precise SPC note RET"
181 | (org-noter-insert-precise-note))
182 | (expect (string-match ":HIGHLIGHT:" (buffer-string)) :to-be nil)))))
183 |
184 |
185 | (describe "hooks for persistent highlights are setup"
186 | ;; setup hooks for highlighting
187 | (before-each
188 | (add-to-list 'org-noter--get-highlight-location-hook #'org-noter-core-test-get-highlight-location)
189 | (spy-on 'org-noter-core-test-get-highlight-location :and-call-through)
190 | )
191 | ;; now that the hooks for highlights are setup, we expect :HIGHLIGHT: property to appear.
192 | (it "can take a precise note WITH a highlight appearing"
193 | (with-mock-contents
194 | mock-contents-simple-notes-file
195 | '(lambda ()
196 | (org-noter-core-test-create-session)
197 | (with-simulated-input "precise SPC note RET"
198 | (org-noter-insert-precise-note))
199 | (expect (string-match "\\:HIGHLIGHT\\:" (buffer-string)) :not :to-be nil)
200 | (expect (string-match "HARDCODED_HIGHLIGHT_LOCATION" (buffer-string)) :not :to-be nil)))))
201 | )
202 |
203 |
204 |
205 |
206 |
207 | )
208 |
--------------------------------------------------------------------------------
/tests/org-noter-extra-tests.el:
--------------------------------------------------------------------------------
1 |
2 | (add-to-list 'load-path "modules")
3 | (require 'org-noter-pdf)
4 | (require 'with-simulated-input)
5 | (require 'org-noter-test-utils)
6 |
7 |
8 | (describe "org-noter very custom behavior"
9 | (before-each
10 | (create-org-noter-test-session)
11 | )
12 | (describe "with advice"
13 | (before-each
14 | (setq org-noter-max-short-selected-text-length 700000)
15 |
16 | (define-advice org-noter--insert-heading (:after (level title &optional newlines-number location) add-full-body-quote)
17 | "Advice for org-noter--insert-heading.
18 |
19 | When inserting a precise note insert the text of the note in the body as an org mode QUOTE block.
20 |
21 | =org-noter-max-short-length= should be set to a large value to short circuit the normal behavior:
22 | =(setq org-noter-max-short-length 80000)="
23 |
24 | ;; this tells us it's a precise note that's being invoked.
25 | (if (consp location)
26 | (insert (format "#+BEGIN_QUOTE\n%s\n#+END_QUOTE" title))))
27 | (create-org-noter-test-session)
28 | )
29 | (after-each
30 | (setq org-noter-max-short-selected-text-length 80)
31 | (advice-remove #'org-noter--insert-heading 'org-noter--insert-heading@add-full-body-quote)
32 | )
33 | (it "should insert the highlighted text as an org-mode QUOTE when advice is enabled."
34 | (with-mock-contents
35 | mock-contents-simple-notes-file
36 | '(lambda ()
37 | (org-noter-core-test-create-session)
38 | ;; we're not specifying the note title
39 | (with-simulated-input "RET"
40 | (org-noter-insert-precise-note))
41 | (let* ((expected-heading (regexp-quote (format "** %s" (org-trim (replace-regexp-in-string "\n" " " (org-noter-test-get-selected-text nil)))))))
42 | (expect (string-match "HARDCODED_HIGHLIGHT_LOCATION" (buffer-string)) :not :to-be nil)
43 | (expect (string-match "BEGIN_QUOTE" (buffer-string)) :not :to-be nil)
44 | (expect (string-match "END_QUOTE" (buffer-string)) :not :to-be nil)
45 | (expect (string-match expected-heading (buffer-string)) :not :to-be nil))))))
46 |
47 |
48 | (describe "without advice"
49 | (it "should revert back to standard title"
50 | (with-mock-contents
51 | mock-contents-simple-notes-file
52 | '(lambda ()
53 | (org-noter-core-test-create-session)
54 | (with-simulated-input "RET"
55 | (org-noter-insert-precise-note))
56 | (expect (string-match "\\*\\* Notes for page" (buffer-string)) :not :to-be nil))))))
57 |
--------------------------------------------------------------------------------
/tests/org-noter-location-tests.el:
--------------------------------------------------------------------------------
1 | (add-to-list 'load-path "modules")
2 | (require 'org-noter-test-utils)
3 |
4 | (defvar mock-contents-simple-notes-file-with-locations
5 | "
6 | :PROPERTIES:
7 | :ID: FAKE_1
8 | :END:
9 | #+TITLE: Test book notes (simple)
10 | * solove-nothing-to-hide
11 | :PROPERTIES:
12 | :NOTER_DOCUMENT: pubs/solove-nothing-to-hide.pdf
13 | :END:
14 | ** Heading1
15 | :PROPERTIES:
16 | :NOTER_PAGE: 40
17 | :END:
18 | ** Heading2
19 | :PROPERTIES:
20 | :NOTER_PAGE: (41 0.09 . 0.16)
21 | :HIGHLIGHT: #s(pdf-highlight 41 ((0.18050847457627117 0.09406231628453851 0.6957627118644067 0.12110523221634333)))
22 | :END:
23 | #+BEGIN_QUOTE
24 | Test
25 | #+END_QUOTE
26 |
27 | ")
28 |
29 |
30 |
31 |
32 | (describe "org-noter locations"
33 | (describe "basic location parsing works"
34 | (before-each
35 | )
36 |
37 | (describe "page locations"
38 | (before-each
39 | (create-org-noter-test-session)
40 | )
41 | (it "can parse a page location"
42 | (with-mock-contents
43 | mock-contents-simple-notes-file-with-locations
44 | '(lambda ()
45 | (org-noter-core-test-create-session)
46 | (search-forward "Heading2")
47 | (expect (org-noter--get-containing-heading) :not :to-be nil)
48 | (expect (org-noter--parse-location-property (org-noter--get-containing-element)) :to-equal (read "(41 0.09 . 0.16)"))
49 | )
50 |
51 | ))
52 |
53 | )
54 |
55 | )
56 | )
57 |
--------------------------------------------------------------------------------
/tests/org-noter-pdf-tests.el:
--------------------------------------------------------------------------------
1 | (add-to-list 'load-path "modules")
2 | (require 'org-noter-pdf)
3 | (require 'org-noter-test-utils)
4 |
5 |
6 | (defvar expected-highlight-info (make-pdf-highlight :page 747 :coords '(0.1 0.2 0.3 0.4)))
7 |
8 | (describe "org-noter-pdf-functionality"
9 | ;; todo refactor 👇
10 | (describe "location functionality"
11 | )
12 |
13 |
14 | (describe "pdf specific highlight functionality"
15 | (before-each
16 | (spy-on 'pdf-view-active-region-p :and-return-value t)
17 | (spy-on 'pdf-view-active-region :and-return-value '(0.1 0.2 0.3 0.4))
18 | (spy-on 'image-mode-window-get :and-return-value 747)
19 | )
20 |
21 | (it "can get coordinates from pdf-view"
22 | (let ((highlight-info (org-noter-pdf--get-highlight)))
23 | (expect 'pdf-view-active-region-p :to-have-been-called)
24 | (expect highlight-info :to-equal expected-highlight-info)))
25 |
26 | (describe "highlight persistence"
27 | (before-each
28 | (create-org-noter-test-session)
29 | ;; (create-org-noter-test-session) sets up a highlight hook, so we have to reset it back.
30 | ;; this might be ok for now? maybe filter out all "-core-test-" hooks instead?
31 | (setq org-noter--get-highlight-location-hook '(org-noter-pdf--get-highlight))
32 | )
33 | (it "can take a precise note WITH a highlight appearing"
34 | (with-mock-contents
35 | mock-contents-simple-notes-file
36 | '(lambda ()
37 | (org-noter-core-test-create-session)
38 | (with-simulated-input "precise SPC note RET"
39 | (org-noter-insert-precise-note))
40 | (ont--log-debug "%s" (buffer-string))
41 | (expect (string-match "\\:HIGHLIGHT\\:" (buffer-string)) :not :to-be nil)
42 | (expect (string-match (format "%s" expected-highlight-info) (buffer-string)) :not :to-be nil)
43 | )
44 |
45 | ))
46 |
47 | )
48 |
49 | )
50 | )
51 |
--------------------------------------------------------------------------------