mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
backlinks WIP, #2349
This commit is contained in:
parent
89f117da5b
commit
bbceb6251a
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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user