mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	backlinks WIP, #2349
This commit is contained in:
		
							
								
								
									
										10
									
								
								.idea/runConfigurations.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								.idea/runConfigurations.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,10 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <project version="4"> |  | ||||||
|   <component name="RunConfigurationProducerService"> |  | ||||||
|     <option name="ignoredProducers"> |  | ||||||
|       <set> |  | ||||||
|         <option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" /> |  | ||||||
|       </set> |  | ||||||
|     </option> |  | ||||||
|   </component> |  | ||||||
| </project> |  | ||||||
							
								
								
									
										32
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										32
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1920,9 +1920,9 @@ | |||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "caniuse-lite": { |     "caniuse-lite": { | ||||||
|       "version": "1.0.30001282", |       "version": "1.0.30001283", | ||||||
|       "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001282.tgz", |       "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001283.tgz", | ||||||
|       "integrity": "sha512-YhF/hG6nqBEllymSIjLtR2iWDDnChvhnVJqp+vloyt2tEHFG1yBR+ac2B/rOw0qOK0m0lEXU2dv4E/sMk5P9Kg==", |       "integrity": "sha512-9RoKo841j1GQFSJz/nCXOj0sD7tHBtlowjYlrqIUS812x9/emfBLBt6IyMz1zIaYc/eRL8Cs6HPUVi2Hzq4sIg==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "caseless": { |     "caseless": { | ||||||
| @@ -2903,9 +2903,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "electron": { |     "electron": { | ||||||
|       "version": "16.0.1", |       "version": "16.0.3", | ||||||
|       "resolved": "https://registry.npmjs.org/electron/-/electron-16.0.1.tgz", |       "resolved": "https://registry.npmjs.org/electron/-/electron-16.0.3.tgz", | ||||||
|       "integrity": "sha512-6TSDBcoKGgmKL/+W+LyaXidRVeRl1V4I81ZOWcqsVksdTMfM4AlxTgfaoYdK/nUhqBrUtuPDcqOyJE6Bc4qMpw==", |       "integrity": "sha512-MzCYuEqrvyEtPSUWQwr88xWBrsbhmyOKp4wqP9WfAJTEDeUfBcrQYswHuYe17Gi00gRirQb9htoC/anYfaw20w==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@electron/get": "^1.13.0", |         "@electron/get": "^1.13.0", | ||||||
| @@ -3702,9 +3702,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "electron-to-chromium": { |     "electron-to-chromium": { | ||||||
|       "version": "1.4.0", |       "version": "1.4.8", | ||||||
|       "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.0.tgz", |       "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.8.tgz", | ||||||
|       "integrity": "sha512-+oXCt6SaIu8EmFTPx8wNGSB0tHQ5biDscnlf6Uxuz17e9CjzMRtGk9B8705aMPnj0iWr3iC74WuIkngCsLElmA==", |       "integrity": "sha512-Cu5+dbg55+1E3ohlsa8HT0s4b8D0gBewXEGG8s5wBl8ynWv60VuvYW25GpsOeTVXpulhyU/U8JYZH+yxASSJBQ==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "electron-window-state": { |     "electron-window-state": { | ||||||
| @@ -5100,9 +5100,9 @@ | |||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "jest-worker": { |     "jest-worker": { | ||||||
|       "version": "27.3.1", |       "version": "27.4.2", | ||||||
|       "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.3.1.tgz", |       "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.2.tgz", | ||||||
|       "integrity": "sha512-ks3WCzsiZaOPJl/oMsDjaf0TRiSv7ctNgs0FqRr2nARsovz6AWWy4oLElwcquGSz692DzgZQrCLScPNs5YlC4g==", |       "integrity": "sha512-0QMy/zPovLfUPyHuOuuU4E+kGACXXE84nRnq6lBVI9GJg5DCBiA97SATi+ZP8CpiJwEQy1oCPjRBf8AnLjN+Ag==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@types/node": "*", |         "@types/node": "*", | ||||||
| @@ -8118,9 +8118,9 @@ | |||||||
|       "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" |       "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" | ||||||
|     }, |     }, | ||||||
|     "webpack": { |     "webpack": { | ||||||
|       "version": "5.64.3", |       "version": "5.64.4", | ||||||
|       "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.64.3.tgz", |       "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.64.4.tgz", | ||||||
|       "integrity": "sha512-XF6/IL9Bw2PPQioiR1UYA8Bs4tX3QXJtSelezKECdLFeSFzWoe44zqTzPW5N+xI3fACaRl2/G3sNA4WYHD7Iww==", |       "integrity": "sha512-LWhqfKjCLoYJLKJY8wk2C3h77i8VyHowG3qYNZiIqD6D0ZS40439S/KVuc/PY48jp2yQmy0mhMknq8cys4jFMw==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@types/eslint-scope": "^3.7.0", |         "@types/eslint-scope": "^3.7.0", | ||||||
| @@ -8145,7 +8145,7 @@ | |||||||
|         "schema-utils": "^3.1.0", |         "schema-utils": "^3.1.0", | ||||||
|         "tapable": "^2.1.1", |         "tapable": "^2.1.1", | ||||||
|         "terser-webpack-plugin": "^5.1.3", |         "terser-webpack-plugin": "^5.1.3", | ||||||
|         "watchpack": "^2.2.0", |         "watchpack": "^2.3.0", | ||||||
|         "webpack-sources": "^3.2.2" |         "webpack-sources": "^3.2.2" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -82,7 +82,7 @@ | |||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "cross-env": "7.0.3", |     "cross-env": "7.0.3", | ||||||
|     "electron": "16.0.1", |     "electron": "16.0.3", | ||||||
|     "@electron/remote": "2.0.1", |     "@electron/remote": "2.0.1", | ||||||
|     "electron-builder": "22.14.5", |     "electron-builder": "22.14.5", | ||||||
|     "electron-packager": "15.4.0", |     "electron-packager": "15.4.0", | ||||||
| @@ -92,7 +92,7 @@ | |||||||
|     "jsdoc": "3.6.7", |     "jsdoc": "3.6.7", | ||||||
|     "lorem-ipsum": "2.0.4", |     "lorem-ipsum": "2.0.4", | ||||||
|     "rcedit": "3.0.1", |     "rcedit": "3.0.1", | ||||||
|     "webpack": "5.64.3", |     "webpack": "5.64.4", | ||||||
|     "webpack-cli": "4.9.1" |     "webpack-cli": "4.9.1" | ||||||
|   }, |   }, | ||||||
|   "optionalDependencies": { |   "optionalDependencies": { | ||||||
|   | |||||||
| @@ -46,6 +46,7 @@ import OpenNoteButtonWidget from "../widgets/buttons/open_note_button_widget.js" | |||||||
| import MermaidWidget from "../widgets/mermaid.js"; | import MermaidWidget from "../widgets/mermaid.js"; | ||||||
| import BookmarkButtons from "../widgets/bookmark_buttons.js"; | import BookmarkButtons from "../widgets/bookmark_buttons.js"; | ||||||
| import NoteWrapperWidget from "../widgets/note_wrapper.js"; | import NoteWrapperWidget from "../widgets/note_wrapper.js"; | ||||||
|  | import BacklinksWidget from "../widgets/backlinks.js"; | ||||||
|  |  | ||||||
| export default class DesktopLayout { | export default class DesktopLayout { | ||||||
|     constructor(customWidgets) { |     constructor(customWidgets) { | ||||||
| @@ -147,6 +148,7 @@ export default class DesktopLayout { | |||||||
|                                         .button(new NoteActionsWidget()) |                                         .button(new NoteActionsWidget()) | ||||||
|                                 ) |                                 ) | ||||||
|                                 .child(new NoteUpdateStatusWidget()) |                                 .child(new NoteUpdateStatusWidget()) | ||||||
|  |                                 .child(new BacklinksWidget()) | ||||||
|                                 .child(new MermaidWidget()) |                                 .child(new MermaidWidget()) | ||||||
|                                 .child( |                                 .child( | ||||||
|                                     new ScrollingContainer() |                                     new ScrollingContainer() | ||||||
|   | |||||||
| @@ -313,6 +313,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain | |||||||
|      * @param {object} [params] |      * @param {object} [params] | ||||||
|      * @param {boolean} [params.showTooltip=true] - enable/disable tooltip on the link |      * @param {boolean} [params.showTooltip=true] - enable/disable tooltip on the link | ||||||
|      * @param {boolean} [params.showNotePath=false] - show also whole note's path as part of the link |      * @param {boolean} [params.showNotePath=false] - show also whole note's path as part of the link | ||||||
|  |      * @param {boolean} [params.showNoteIcon=false] - show also note icon before the title | ||||||
|      * @param {string} [title=] - custom link tile with note's title as default |      * @param {string} [title=] - custom link tile with note's title as default | ||||||
|      */ |      */ | ||||||
|     this.createNoteLink = linkService.createNoteLink; |     this.createNoteLink = linkService.createNoteLink; | ||||||
|   | |||||||
							
								
								
									
										143
									
								
								src/public/app/widgets/backlinks.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								src/public/app/widgets/backlinks.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,143 @@ | |||||||
|  | import NoteContextAwareWidget from "./note_context_aware_widget.js"; | ||||||
|  | import linkService from "../services/link.js"; | ||||||
|  | import server from "../services/server.js"; | ||||||
|  | import froca from "../services/froca.js"; | ||||||
|  |  | ||||||
|  | const TPL = ` | ||||||
|  | <div class="backlinks-widget"> | ||||||
|  |     <style> | ||||||
|  |         .backlinks-widget { | ||||||
|  |             position: relative; | ||||||
|  |         } | ||||||
|  |      | ||||||
|  |         .backlinks-ticker { | ||||||
|  |             position: absolute; | ||||||
|  |             top: 10px; | ||||||
|  |             right: 10px; | ||||||
|  |             width: 130px; | ||||||
|  |             border-radius: 10px; | ||||||
|  |             border-color: var(--main-border-color); | ||||||
|  |             background-color: var(--more-accented-background-color); | ||||||
|  |             padding: 4px 10px 4px 10px; | ||||||
|  |             opacity: 70%; | ||||||
|  |             display: flex; | ||||||
|  |             justify-content: space-between; | ||||||
|  |             align-items: center; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .backlinks-count { | ||||||
|  |             cursor: pointer; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .backlinks-close-ticker { | ||||||
|  |             cursor: pointer; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .backlinks-ticker:hover { | ||||||
|  |             opacity: 100%; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .backlinks-items { | ||||||
|  |             z-index: 10; | ||||||
|  |             position: absolute; | ||||||
|  |             top: 50px; | ||||||
|  |             right: 10px; | ||||||
|  |             width: 400px; | ||||||
|  |             border-radius: 10px; | ||||||
|  |             background-color: #eeeeee; | ||||||
|  |             color: #444; | ||||||
|  |             padding: 20px; | ||||||
|  |             overflow-y: auto; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .backlink-excerpt { | ||||||
|  |             border-left: 2px solid var(--main-border-color); | ||||||
|  |             padding-left: 10px; | ||||||
|  |             opacity: 80%; | ||||||
|  |             font-size: 90%; | ||||||
|  |         } | ||||||
|  |     </style> | ||||||
|  |      | ||||||
|  |     <div class="backlinks-ticker"> | ||||||
|  |         <span class="backlinks-count"></span> | ||||||
|  |          | ||||||
|  |         <span class="bx bx-x backlinks-close-ticker"></span>  | ||||||
|  |     </div>    | ||||||
|  |      | ||||||
|  |     <div class="backlinks-items" style="display: none;"></div> | ||||||
|  | </div> | ||||||
|  | `; | ||||||
|  |  | ||||||
|  | export default class BacklinksWidget extends NoteContextAwareWidget { | ||||||
|  |     doRender() { | ||||||
|  |         this.$widget = $(TPL); | ||||||
|  |         this.$count = this.$widget.find('.backlinks-count'); | ||||||
|  |         this.$items = this.$widget.find('.backlinks-items'); | ||||||
|  |         this.$ticker = this.$widget.find('.backlinks-ticker'); | ||||||
|  |  | ||||||
|  |         this.$count.on("click", () => { | ||||||
|  |             this.$items.toggle(); | ||||||
|  |             this.$items.css("max-height", $(window).height() - this.$items.offset().top - 10); | ||||||
|  |  | ||||||
|  |             if (this.$items.is(":visible")) { | ||||||
|  |                 this.renderBacklinks(); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         this.$closeTickerButton = this.$widget.find('.backlinks-close-ticker'); | ||||||
|  |         this.$closeTickerButton.on("click", () => { | ||||||
|  |             this.$ticker.hide(); | ||||||
|  |  | ||||||
|  |             this.clearItems(); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         this.contentSized(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async refreshWithNote(note) { | ||||||
|  |         this.clearItems(); | ||||||
|  |  | ||||||
|  |         const targetRelationCount = note.getTargetRelations().length; | ||||||
|  |         if (targetRelationCount === 0) { | ||||||
|  |             this.$ticker.hide(); | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             this.$ticker.show(); | ||||||
|  |             this.$count.text( | ||||||
|  |                 `${targetRelationCount} backlink` | ||||||
|  |                 + (targetRelationCount === 1 ? '' : 's') | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     clearItems() { | ||||||
|  |         this.$items.empty().hide(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async renderBacklinks() { | ||||||
|  |         if (!this.note) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         this.$items.empty(); | ||||||
|  |  | ||||||
|  |         const backlinks = await server.get(`note-map/${this.noteId}/backlinks`); | ||||||
|  |  | ||||||
|  |         if (!backlinks.length) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         await froca.getNotes(backlinks.map(bl => bl.noteId)); // prefetch all | ||||||
|  |  | ||||||
|  |         for (const backlink of backlinks) { | ||||||
|  |             this.$items.append(await linkService.createNoteLink(backlink.noteId, { | ||||||
|  |                 showNoteIcon: true, | ||||||
|  |                 showNotePath: true, | ||||||
|  |                 showTooltip: false | ||||||
|  |             })); | ||||||
|  |  | ||||||
|  |             this.$items.append("<br/>"); | ||||||
|  |             this.$items.append(...backlink.excerpts); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,6 +1,7 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
|  |  | ||||||
| const becca = require("../../becca/becca"); | const becca = require("../../becca/becca"); | ||||||
|  | const { JSDOM } = require("jsdom"); | ||||||
|  |  | ||||||
| function buildDescendantCountMap() { | function buildDescendantCountMap() { | ||||||
|     const noteIdToCountMap = {}; |     const noteIdToCountMap = {}; | ||||||
| @@ -174,7 +175,131 @@ function getTreeMap(req) { | |||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function removeImages(document) { | ||||||
|  |     const images = document.getElementsByTagName('img'); | ||||||
|  |     while (images.length > 0) { | ||||||
|  |         images[0].parentNode.removeChild(images[0]); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function getBacklinks(req) { | ||||||
|  |     const {noteId} = req.params; | ||||||
|  |     const note = becca.getNote(noteId); | ||||||
|  |  | ||||||
|  |     if (!note) { | ||||||
|  |         return [404, `Note ${noteId} was not found`]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     let backlinks = note.getTargetRelations(); | ||||||
|  |  | ||||||
|  |     if (backlinks.length > 50) { | ||||||
|  |         backlinks = backlinks.slice(0, 50); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return backlinks.map(backlink => { | ||||||
|  |         const sourceNote = backlink.note; | ||||||
|  |  | ||||||
|  |         const html = sourceNote.getContent(); | ||||||
|  |         const dom = new JSDOM(html); | ||||||
|  |  | ||||||
|  |         const excerpts = []; | ||||||
|  |  | ||||||
|  |         const document = dom.window.document; | ||||||
|  |  | ||||||
|  |         removeImages(document); | ||||||
|  |  | ||||||
|  |         for (const linkEl of document.querySelectorAll("a")) { | ||||||
|  |             const href = linkEl.getAttribute("href"); | ||||||
|  |  | ||||||
|  |             if (!href || !href.includes(noteId)) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             linkEl.style.fontWeight = "bold"; | ||||||
|  |             linkEl.style.backgroundColor = "yellow"; | ||||||
|  |  | ||||||
|  |             const LIMIT = 200; | ||||||
|  |             let centerEl = linkEl; | ||||||
|  |  | ||||||
|  |             while (centerEl.tagName !== 'BODY' && centerEl.parentElement.textContent.length < LIMIT) { | ||||||
|  |                 centerEl = centerEl.parentElement; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             const sub = [centerEl]; | ||||||
|  |             let counter = centerEl.textContent.length; | ||||||
|  |             let left = centerEl; | ||||||
|  |             let right = centerEl; | ||||||
|  |  | ||||||
|  |             while (true) { | ||||||
|  |                 let added = false; | ||||||
|  |  | ||||||
|  |                 const prev = left.previousElementSibling; | ||||||
|  |  | ||||||
|  |                 if (prev) { | ||||||
|  |                     const prevText = prev.textContent; | ||||||
|  |  | ||||||
|  |                     if (prevText.length + counter > LIMIT) { | ||||||
|  |                         const prefix = prevText.substr(prevText.length - (LIMIT - counter)); | ||||||
|  |  | ||||||
|  |                         const textNode = document.createTextNode("…" + prefix); | ||||||
|  |                         sub.unshift(textNode); | ||||||
|  |  | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     left = prev; | ||||||
|  |                     sub.unshift(left); | ||||||
|  |                     counter += prevText.length; | ||||||
|  |                     added = true; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 const next = right.nextElementSibling; | ||||||
|  |  | ||||||
|  |                 if (next) { | ||||||
|  |                     const nextText = next.textContent; | ||||||
|  |  | ||||||
|  |                     if (nextText.length + counter > LIMIT) { | ||||||
|  |                         const suffix = nextText.substr(nextText.length - (LIMIT - counter)); | ||||||
|  |  | ||||||
|  |                         const textNode = document.createTextNode(suffix + "…"); | ||||||
|  |                         sub.push(textNode); | ||||||
|  |  | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     right = next; | ||||||
|  |                     sub.push(right); | ||||||
|  |                     counter += nextText.length; | ||||||
|  |                     added = true; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (!added) { | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             const div = document.createElement('div'); | ||||||
|  |             div.classList.add("ck-content"); | ||||||
|  |             div.classList.add("backlink-excerpt"); | ||||||
|  |  | ||||||
|  |             for (const childEl of sub) { | ||||||
|  |                 div.appendChild(childEl); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             const subHtml = div.outerHTML; | ||||||
|  |  | ||||||
|  |             excerpts.push(subHtml); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return { | ||||||
|  |             noteId: sourceNote.noteId, | ||||||
|  |             excerpts | ||||||
|  |         }; | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     getLinkMap, |     getLinkMap, | ||||||
|     getTreeMap |     getTreeMap, | ||||||
|  |     getBacklinks | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -260,6 +260,7 @@ function register(app) { | |||||||
|  |  | ||||||
|     apiRoute(POST, '/api/note-map/:noteId/tree', noteMapRoute.getTreeMap); |     apiRoute(POST, '/api/note-map/:noteId/tree', noteMapRoute.getTreeMap); | ||||||
|     apiRoute(POST, '/api/note-map/:noteId/link', noteMapRoute.getLinkMap); |     apiRoute(POST, '/api/note-map/:noteId/link', noteMapRoute.getLinkMap); | ||||||
|  |     apiRoute(GET, '/api/note-map/:noteId/backlinks', noteMapRoute.getBacklinks); | ||||||
|  |  | ||||||
|     apiRoute(GET, '/api/special-notes/inbox/:date', specialNotesRoute.getInboxNote); |     apiRoute(GET, '/api/special-notes/inbox/:date', specialNotesRoute.getInboxNote); | ||||||
|     apiRoute(GET, '/api/special-notes/date/:date', specialNotesRoute.getDateNote); |     apiRoute(GET, '/api/special-notes/date/:date', specialNotesRoute.getDateNote); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user