mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
Merge branch 'master' into next61
# Conflicts: # package-lock.json # src/public/app/services/note_content_renderer.js # src/public/app/widgets/note_tree.js # src/routes/routes.js # src/services/consistency_checks.js # src/services/notes.js # src/services/task_context.js
This commit is contained in:
commit
b7f0fd2db3
@ -16,7 +16,7 @@ noBackup=false
|
|||||||
# host=0.0.0.0
|
# host=0.0.0.0
|
||||||
# port setting is relevant only for web deployments, desktop builds run on a fixed port (changeable with TRILIUM_PORT environment variable)
|
# port setting is relevant only for web deployments, desktop builds run on a fixed port (changeable with TRILIUM_PORT environment variable)
|
||||||
port=8080
|
port=8080
|
||||||
# true for TLS/SSL/HTTPS (secure), false for HTTP (unsecure).
|
# true for TLS/SSL/HTTPS (secure), false for HTTP (insecure).
|
||||||
https=false
|
https=false
|
||||||
# path to certificate (run "bash bin/generate-cert.sh" to generate self-signed certificate). Relevant only if https=true
|
# path to certificate (run "bash bin/generate-cert.sh" to generate self-signed certificate). Relevant only if https=true
|
||||||
certPath=
|
certPath=
|
||||||
|
@ -4,7 +4,7 @@ const fs = require("fs");
|
|||||||
const dataDir = require("./src/services/data_dir");
|
const dataDir = require("./src/services/data_dir");
|
||||||
const config = ini.parse(fs.readFileSync(dataDir.CONFIG_INI_PATH, 'utf-8'));
|
const config = ini.parse(fs.readFileSync(dataDir.CONFIG_INI_PATH, 'utf-8'));
|
||||||
|
|
||||||
if (config.https) {
|
if (config.Network.https) {
|
||||||
// built-in TLS (terminated by trilium) is not supported yet, PRs are welcome
|
// built-in TLS (terminated by trilium) is not supported yet, PRs are welcome
|
||||||
// for reverse proxy terminated TLS this will works since config.https will be false
|
// for reverse proxy terminated TLS this will works since config.https will be false
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
@ -667,7 +667,7 @@ class BNote extends AbstractBeccaEntity {
|
|||||||
return this.ownedAttributes.filter(attr => attr.name === name);
|
return this.ownedAttributes.filter(attr => attr.name === name);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return this.ownedAttributes.slice();
|
return this.ownedAttributes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,7 +367,7 @@ async function findSimilarNotes(noteId) {
|
|||||||
* We want to improve the standing of notes which have been created in similar time to each other since
|
* We want to improve the standing of notes which have been created in similar time to each other since
|
||||||
* there's a good chance they are related.
|
* there's a good chance they are related.
|
||||||
*
|
*
|
||||||
* But there's an exception - if they were created really close to each other (withing few seconds) then
|
* But there's an exception - if they were created really close to each other (within few seconds) then
|
||||||
* they are probably part of the import and not created by hand - these OTOH should not benefit.
|
* they are probably part of the import and not created by hand - these OTOH should not benefit.
|
||||||
*/
|
*/
|
||||||
const {utcDateCreated} = candidateNote;
|
const {utcDateCreated} = candidateNote;
|
||||||
|
@ -231,7 +231,7 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/EntityId'
|
$ref: '#/components/schemas/EntityId'
|
||||||
get:
|
get:
|
||||||
description: Returns note content idenfied by its ID
|
description: Returns note content identified by its ID
|
||||||
operationId: getNoteContent
|
operationId: getNoteContent
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
@ -241,7 +241,7 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
put:
|
put:
|
||||||
description: Updates note content idenfied by its ID
|
description: Updates note content identified by its ID
|
||||||
operationId: putNoteContentById
|
operationId: putNoteContentById
|
||||||
requestBody:
|
requestBody:
|
||||||
description: html content of note
|
description: html content of note
|
||||||
|
@ -41,8 +41,8 @@ function initAttributeNameAutocomplete({ $el, attributeType, open }) {
|
|||||||
|
|
||||||
async function initLabelValueAutocomplete({ $el, open, nameCallback }) {
|
async function initLabelValueAutocomplete({ $el, open, nameCallback }) {
|
||||||
if ($el.hasClass("aa-input")) {
|
if ($el.hasClass("aa-input")) {
|
||||||
// we reinit everytime because autocomplete seems to have a bug where it retains state from last
|
// we reinit every time because autocomplete seems to have a bug where it retains state from last
|
||||||
// open even though the value was resetted
|
// open even though the value was reset
|
||||||
$el.autocomplete('destroy');
|
$el.autocomplete('destroy');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +133,7 @@ function initNoteAutocomplete($el, options) {
|
|||||||
showRecentNotes($el);
|
showRecentNotes($el);
|
||||||
|
|
||||||
// this will cause the click not give focus to the "show recent notes" button
|
// this will cause the click not give focus to the "show recent notes" button
|
||||||
// this is important because otherwise input will lose focus immediatelly and not show the results
|
// this is important because otherwise input will lose focus immediately and not show the results
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ function parseSelectedHtml(selectedHtml) {
|
|||||||
|
|
||||||
if (dom.length > 0 && dom[0].tagName && dom[0].tagName.match(/h[1-6]/i)) {
|
if (dom.length > 0 && dom[0].tagName && dom[0].tagName.match(/h[1-6]/i)) {
|
||||||
const title = $(dom[0]).text();
|
const title = $(dom[0]).text();
|
||||||
// remove the title from content (only first occurence)
|
// remove the title from content (only first occurrence)
|
||||||
const content = selectedHtml.replace(dom[0].outerHTML, "");
|
const content = selectedHtml.replace(dom[0].outerHTML, "");
|
||||||
|
|
||||||
return [title, content];
|
return [title, content];
|
||||||
|
@ -161,7 +161,7 @@ class NoteListRenderer {
|
|||||||
constructor($parent, parentNote, noteIds, showNotePath = false) {
|
constructor($parent, parentNote, noteIds, showNotePath = false) {
|
||||||
this.$noteList = $(TPL);
|
this.$noteList = $(TPL);
|
||||||
|
|
||||||
// note list must be added to the DOM immediatelly, otherwise some functionality scripting (canvas) won't work
|
// note list must be added to the DOM immediately, otherwise some functionality scripting (canvas) won't work
|
||||||
$parent.empty();
|
$parent.empty();
|
||||||
|
|
||||||
this.parentNote = parentNote;
|
this.parentNote = parentNote;
|
||||||
|
@ -21,7 +21,7 @@ async function getHeaders(headers) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (utils.isElectron()) {
|
if (utils.isElectron()) {
|
||||||
// passing it explicitely here because of the electron HTTP bypass
|
// passing it explicitly here because of the electron HTTP bypass
|
||||||
allHeaders.cookie = document.cookie;
|
allHeaders.cookie = document.cookie;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Fetch note with given ID from backend
|
* Fetch note with given ID from backend
|
||||||
*
|
*
|
||||||
* @param noteId of the given note to be fetched. If falsy, fetches current note.
|
* @param noteId of the given note to be fetched. If false, fetches current note.
|
||||||
*/
|
*/
|
||||||
async function fetchNote(noteId = null) {
|
async function fetchNote(noteId = null) {
|
||||||
if (!noteId) {
|
if (!noteId) {
|
||||||
|
@ -26,7 +26,7 @@ export default class AbstractBulkAction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// to be overriden
|
// to be overridden
|
||||||
doRender() {}
|
doRender() {}
|
||||||
|
|
||||||
async saveAction(data) {
|
async saveAction(data) {
|
||||||
|
@ -50,7 +50,7 @@ export default class RightDropdownButtonWidget extends BasicWidget {
|
|||||||
this.$widget.find(".dropdown-menu").append(this.$dropdownContent);
|
this.$widget.find(".dropdown-menu").append(this.$dropdownContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// to be overriden
|
// to be overridden
|
||||||
async dropdownShow() {}
|
async dropdownShow() {}
|
||||||
|
|
||||||
hideDropdown() {
|
hideDropdown() {
|
||||||
|
@ -25,7 +25,7 @@ const TPL = `
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label title="Normal (soft) deletion only marks the notes as deleted and they can be undeleted (in recent changes dialog) within a period of time. Checking this option will erase the notes immediatelly and it won't be possible to undelete the notes.">
|
<label title="Normal (soft) deletion only marks the notes as deleted and they can be undeleted (in recent changes dialog) within a period of time. Checking this option will erase the notes immediately and it won't be possible to undelete the notes.">
|
||||||
<input class="erase-notes" value="1" type="checkbox">
|
<input class="erase-notes" value="1" type="checkbox">
|
||||||
|
|
||||||
erase notes permanently (can't be undone), including all clones. This will force application reload.
|
erase notes permanently (can't be undone), including all clones. This will force application reload.
|
||||||
|
@ -10,20 +10,20 @@ import RightPanelWidget from "./right_panel_widget.js";
|
|||||||
import options from "../services/options.js";
|
import options from "../services/options.js";
|
||||||
import OnClickButtonWidget from "./buttons/onclick_button.js";
|
import OnClickButtonWidget from "./buttons/onclick_button.js";
|
||||||
|
|
||||||
const TPL = `<div class="highlists-list-widget">
|
const TPL = `<div class="highlights-list-widget">
|
||||||
<style>
|
<style>
|
||||||
.highlists-list-widget {
|
.highlights-list-widget {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
contain: none;
|
contain: none;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlists-list > ol {
|
.highlights-list > ol {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlists-list li {
|
.highlights-list li {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
text-align: justify;
|
text-align: justify;
|
||||||
@ -32,18 +32,18 @@ const TPL = `<div class="highlists-list-widget">
|
|||||||
hyphens: auto;
|
hyphens: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlists-list li:hover {
|
.highlights-list li:hover {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-highlists-list {
|
.close-highlights-list {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<span class="highlists-list"></span>
|
<span class="highlights-list"></span>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
export default class HighlightsListWidget extends RightPanelWidget {
|
export default class HighlightsListWidget extends RightPanelWidget {
|
||||||
@ -55,61 +55,61 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get widgetTitle() {
|
get widgetTitle() {
|
||||||
return "Highlighted Text";
|
return "Highlights List";
|
||||||
}
|
}
|
||||||
|
|
||||||
isEnabled() {
|
isEnabled() {
|
||||||
return super.isEnabled()
|
return super.isEnabled()
|
||||||
&& this.note.type === 'text'
|
&& this.note.type === 'text'
|
||||||
&& !this.noteContext.viewScope.highlightedTextTemporarilyHidden
|
&& !this.noteContext.viewScope.highlightsListTemporarilyHidden
|
||||||
&& this.noteContext.viewScope.viewMode === 'default';
|
&& this.noteContext.viewScope.viewMode === 'default';
|
||||||
}
|
}
|
||||||
|
|
||||||
async doRenderBody() {
|
async doRenderBody() {
|
||||||
this.$body.empty().append($(TPL));
|
this.$body.empty().append($(TPL));
|
||||||
this.$highlightsList = this.$body.find('.highlists-list');
|
this.$highlightsList = this.$body.find('.highlights-list');
|
||||||
this.$body.find('.highlists-list-widget').append(this.closeHltButton.render());
|
this.$body.find('.highlights-list-widget').append(this.closeHltButton.render());
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshWithNote(note) {
|
async refreshWithNote(note) {
|
||||||
/* The reason for adding highlightedTextPreviousVisible is to record whether the previous state
|
/* The reason for adding highlightsListPreviousVisible is to record whether the previous state
|
||||||
of the highlightedText is hidden or displayed, and then let it be displayed/hidden at the initial time.
|
of the highlightsList is hidden or displayed, and then let it be displayed/hidden at the initial time.
|
||||||
If there is no such value, when the right panel needs to display toc but not highlighttext,
|
If there is no such value, when the right panel needs to display toc but not highlighttext,
|
||||||
every time the note content is changed, highlighttext Widget will appear and then close immediately,
|
every time the note content is changed, highlighttext Widget will appear and then close immediately,
|
||||||
because getHlt function will consume time */
|
because getHlt function will consume time */
|
||||||
if (this.noteContext.viewScope.highlightedTextPreviousVisible) {
|
if (this.noteContext.viewScope.highlightsListPreviousVisible) {
|
||||||
this.toggleInt(true);
|
this.toggleInt(true);
|
||||||
} else {
|
} else {
|
||||||
this.toggleInt(false);
|
this.toggleInt(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const optionsHlt = JSON.parse(options.get('highlightedText'));
|
const optionsHighlightsList = JSON.parse(options.get('highlightsList'));
|
||||||
|
|
||||||
if (note.isLabelTruthy('hideHighlightWidget') || !optionsHlt) {
|
if (note.isLabelTruthy('hideHighlightWidget') || !optionsHighlightsList) {
|
||||||
this.toggleInt(false);
|
this.toggleInt(false);
|
||||||
this.triggerCommand("reEvaluateRightPaneVisibility");
|
this.triggerCommand("reEvaluateRightPaneVisibility");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let $highlightsList = "", hltLiCount = -1;
|
let $highlightsList = "", hlLiCount = -1;
|
||||||
// Check for type text unconditionally in case alwaysShowWidget is set
|
// Check for type text unconditionally in case alwaysShowWidget is set
|
||||||
if (this.note.type === 'text') {
|
if (this.note.type === 'text') {
|
||||||
const {content} = await note.getNoteComplement();
|
const {content} = await note.getNoteComplement();
|
||||||
({$highlightsList, hltLiCount} = this.getHighlightList(content, optionsHlt));
|
({$highlightsList, hlLiCount} = this.getHighlightList(content, optionsHighlightsList));
|
||||||
}
|
}
|
||||||
this.$highlightsList.empty().append($highlightsList);
|
this.$highlightsList.empty().append($highlightsList);
|
||||||
if (hltLiCount > 0) {
|
if (hlLiCount > 0) {
|
||||||
this.toggleInt(true);
|
this.toggleInt(true);
|
||||||
this.noteContext.viewScope.highlightedTextPreviousVisible = true;
|
this.noteContext.viewScope.highlightsListPreviousVisible = true;
|
||||||
} else {
|
} else {
|
||||||
this.toggleInt(false);
|
this.toggleInt(false);
|
||||||
this.noteContext.viewScope.highlightedTextPreviousVisible = false;
|
this.noteContext.viewScope.highlightsListPreviousVisible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.triggerCommand("reEvaluateRightPaneVisibility");
|
this.triggerCommand("reEvaluateRightPaneVisibility");
|
||||||
}
|
}
|
||||||
|
|
||||||
getHighlightList(content, optionsHlt) {
|
getHighlightList(content, optionsHighlightsList) {
|
||||||
// matches a span containing background-color
|
// matches a span containing background-color
|
||||||
const regex1 = /<span[^>]*style\s*=\s*[^>]*background-color:[^>]*?>[\s\S]*?<\/span>/gi;
|
const regex1 = /<span[^>]*style\s*=\s*[^>]*background-color:[^>]*?>[\s\S]*?<\/span>/gi;
|
||||||
// matches a span containing color
|
// matches a span containing color
|
||||||
@ -120,27 +120,27 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
|||||||
const regex4 = /<strong>[\s\S]*?<\/strong>/gi;
|
const regex4 = /<strong>[\s\S]*?<\/strong>/gi;
|
||||||
// match underline
|
// match underline
|
||||||
const regex5 = /<u>[\s\S]*?<\/u>/g;
|
const regex5 = /<u>[\s\S]*?<\/u>/g;
|
||||||
// Possible values in optionsHlt: '["bold","italic","underline","color","bgColor"]'
|
// Possible values in optionsHighlightsList: '["bold","italic","underline","color","bgColor"]'
|
||||||
// element priority: span>i>strong>u
|
// element priority: span>i>strong>u
|
||||||
let findSubStr = "", combinedRegexStr = "";
|
let findSubStr = "", combinedRegexStr = "";
|
||||||
if (optionsHlt.includes("bgColor")) {
|
if (optionsHighlightsList.includes("bgColor")) {
|
||||||
findSubStr += `,span[style*="background-color"]`;
|
findSubStr += `,span[style*="background-color"]:not(section.include-note span[style*="background-color"])`;
|
||||||
combinedRegexStr += `|${regex1.source}`;
|
combinedRegexStr += `|${regex1.source}`;
|
||||||
}
|
}
|
||||||
if (optionsHlt.includes("color")) {
|
if (optionsHighlightsList.includes("color")) {
|
||||||
findSubStr += `,span[style*="color"]`;
|
findSubStr += `,span[style*="color"]:not(section.include-note span[style*="color"])`;
|
||||||
combinedRegexStr += `|${regex2.source}`;
|
combinedRegexStr += `|${regex2.source}`;
|
||||||
}
|
}
|
||||||
if (optionsHlt.includes("italic")) {
|
if (optionsHighlightsList.includes("italic")) {
|
||||||
findSubStr += `,i`;
|
findSubStr += `,i:not(section.include-note i)`;
|
||||||
combinedRegexStr += `|${regex3.source}`;
|
combinedRegexStr += `|${regex3.source}`;
|
||||||
}
|
}
|
||||||
if (optionsHlt.indexOf("bold")) {
|
if (optionsHighlightsList.includes("bold")) {
|
||||||
findSubStr += `,strong`;
|
findSubStr += `,strong:not(section.include-note strong)`;
|
||||||
combinedRegexStr += `|${regex4.source}`;
|
combinedRegexStr += `|${regex4.source}`;
|
||||||
}
|
}
|
||||||
if (optionsHlt.includes("underline")) {
|
if (optionsHighlightsList.includes("underline")) {
|
||||||
findSubStr += `,u`;
|
findSubStr += `,u:not(section.include-note u)`;
|
||||||
combinedRegexStr += `|${regex5.source}`;
|
combinedRegexStr += `|${regex5.source}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,7 +148,7 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
|||||||
combinedRegexStr = `(` + combinedRegexStr.substring(1) + `)`;
|
combinedRegexStr = `(` + combinedRegexStr.substring(1) + `)`;
|
||||||
const combinedRegex = new RegExp(combinedRegexStr, 'gi');
|
const combinedRegex = new RegExp(combinedRegexStr, 'gi');
|
||||||
const $highlightsList = $("<ol>");
|
const $highlightsList = $("<ol>");
|
||||||
let prevEndIndex = -1, hltLiCount = 0;
|
let prevEndIndex = -1, hlLiCount = 0;
|
||||||
for (let match = null, hltIndex = 0; ((match = combinedRegex.exec(content)) !== null); hltIndex++) {
|
for (let match = null, hltIndex = 0; ((match = combinedRegex.exec(content)) !== null); hltIndex++) {
|
||||||
const subHtml = match[0];
|
const subHtml = match[0];
|
||||||
const startIndex = match.index;
|
const startIndex = match.index;
|
||||||
@ -158,16 +158,18 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
|||||||
$highlightsList.children().last().append(subHtml);
|
$highlightsList.children().last().append(subHtml);
|
||||||
} else {
|
} else {
|
||||||
// TODO: can't be done with $(subHtml).text()?
|
// TODO: can't be done with $(subHtml).text()?
|
||||||
const hasText = [...subHtml.matchAll(/(?<=^|>)[^><]+?(?=<|$)/g)].map(matchTmp => matchTmp[0]).join('').trim();
|
//Can’t remember why regular expressions are used here, but modified to $(subHtml).text() works as expected
|
||||||
|
//const hasText = [...subHtml.matchAll(/(?<=^|>)[^><]+?(?=<|$)/g)].map(matchTmp => matchTmp[0]).join('').trim();
|
||||||
|
const hasText = $(subHtml).text().trim();
|
||||||
|
|
||||||
if (hasText) {
|
if (hasText) {
|
||||||
$highlightsList.append(
|
$highlightsList.append(
|
||||||
$('<li>')
|
$('<li>')
|
||||||
.html(subHtml)
|
.html(subHtml)
|
||||||
.on("click", () => this.jumpToHighlightedText(findSubStr, hltIndex))
|
.on("click", () => this.jumpToHighlightsList(findSubStr, hltIndex))
|
||||||
);
|
);
|
||||||
|
|
||||||
hltLiCount++;
|
hlLiCount++;
|
||||||
} else {
|
} else {
|
||||||
// hide li if its text content is empty
|
// hide li if its text content is empty
|
||||||
continue;
|
continue;
|
||||||
@ -177,11 +179,11 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
$highlightsList,
|
$highlightsList,
|
||||||
hltLiCount
|
hlLiCount
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async jumpToHighlightedText(findSubStr, itemIndex) {
|
async jumpToHighlightsList(findSubStr, itemIndex) {
|
||||||
const isReadOnly = await this.noteContext.isReadOnly();
|
const isReadOnly = await this.noteContext.isReadOnly();
|
||||||
let targetElement;
|
let targetElement;
|
||||||
if (isReadOnly) {
|
if (isReadOnly) {
|
||||||
@ -224,7 +226,7 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async closeHltCommand() {
|
async closeHltCommand() {
|
||||||
this.noteContext.viewScope.highlightedTextTemporarilyHidden = true;
|
this.noteContext.viewScope.highlightsListTemporarilyHidden = true;
|
||||||
await this.refresh();
|
await this.refresh();
|
||||||
this.triggerCommand('reEvaluateRightPaneVisibility');
|
this.triggerCommand('reEvaluateRightPaneVisibility');
|
||||||
}
|
}
|
||||||
@ -245,13 +247,13 @@ class CloseHltButton extends OnClickButtonWidget {
|
|||||||
super();
|
super();
|
||||||
|
|
||||||
this.icon("bx-x")
|
this.icon("bx-x")
|
||||||
.title("Close HighlightedTextWidget")
|
.title("Close HighlightsListWidget")
|
||||||
.titlePlacement("bottom")
|
.titlePlacement("bottom")
|
||||||
.onClick((widget, e) => {
|
.onClick((widget, e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
widget.triggerCommand("closeHlt");
|
widget.triggerCommand("closeHlt");
|
||||||
})
|
})
|
||||||
.class("icon-action close-highlists-list");
|
.class("icon-action close-highlights-list");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9584,7 +9584,7 @@ const icons = [
|
|||||||
"term": [
|
"term": [
|
||||||
"honor",
|
"honor",
|
||||||
"honour",
|
"honour",
|
||||||
"acheivement"
|
"achievement"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -9595,7 +9595,7 @@ const icons = [
|
|||||||
"term": [
|
"term": [
|
||||||
"honor",
|
"honor",
|
||||||
"honour",
|
"honour",
|
||||||
"acheivement"
|
"achievement"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -166,7 +166,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
|
|||||||
|
|
||||||
generateColorFromString(str) {
|
generateColorFromString(str) {
|
||||||
if (this.themeStyle === "dark") {
|
if (this.themeStyle === "dark") {
|
||||||
str = `0${str}`; // magic lightening modifier
|
str = `0${str}`; // magic lightning modifier
|
||||||
}
|
}
|
||||||
|
|
||||||
let hash = 0;
|
let hash = 0;
|
||||||
|
@ -1116,7 +1116,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
const note = froca.getNoteFromCache(ecAttr.noteId);
|
const note = froca.getNoteFromCache(ecAttr.noteId);
|
||||||
|
|
||||||
if (note && note.getChildNoteIds().includes(ecAttr.value)) {
|
if (note && note.getChildNoteIds().includes(ecAttr.value)) {
|
||||||
// there's a new /deleted imageLink betwen note and its image child - which can show/hide
|
// there's a new /deleted imageLink between note and its image child - which can show/hide
|
||||||
// the image (if there is an imageLink relation between parent and child,
|
// the image (if there is an imageLink relation between parent and child,
|
||||||
// then it is assumed to be "contained" in the note and thus does not have to be displayed in the tree)
|
// then it is assumed to be "contained" in the note and thus does not have to be displayed in the tree)
|
||||||
noteIdsToReload.add(ecAttr.noteId);
|
noteIdsToReload.add(ecAttr.noteId);
|
||||||
|
@ -39,7 +39,7 @@ export default class AbstractSearchOption extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// to be overriden
|
// to be overridden
|
||||||
doRender() {}
|
doRender() {}
|
||||||
|
|
||||||
async deleteOption() {
|
async deleteOption() {
|
||||||
|
@ -2,6 +2,7 @@ import AbstractSearchOption from "./abstract_search_option.js";
|
|||||||
import SpacedUpdate from "../../services/spaced_update.js";
|
import SpacedUpdate from "../../services/spaced_update.js";
|
||||||
import server from "../../services/server.js";
|
import server from "../../services/server.js";
|
||||||
import shortcutService from "../../services/shortcuts.js";
|
import shortcutService from "../../services/shortcuts.js";
|
||||||
|
import appContext from "../../components/app_context.js";
|
||||||
|
|
||||||
const TPL = `
|
const TPL = `
|
||||||
<tr>
|
<tr>
|
||||||
@ -56,6 +57,7 @@ export default class SearchString extends AbstractSearchOption {
|
|||||||
|
|
||||||
this.spacedUpdate = new SpacedUpdate(async () => {
|
this.spacedUpdate = new SpacedUpdate(async () => {
|
||||||
const searchString = this.$searchString.val();
|
const searchString = this.$searchString.val();
|
||||||
|
appContext.lastSearchString = searchString;
|
||||||
|
|
||||||
await this.setAttribute('label', 'searchString', searchString);
|
await this.setAttribute('label', 'searchString', searchString);
|
||||||
|
|
||||||
@ -84,6 +86,7 @@ export default class SearchString extends AbstractSearchOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
focusOnSearchDefinitionEvent() {
|
focusOnSearchDefinitionEvent() {
|
||||||
this.$searchString.focus();
|
this.$searchString.val(appContext.lastSearchString).focus().select();
|
||||||
|
this.spacedUpdate.scheduleUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -187,7 +187,7 @@ export default class TocWidget extends RightPanelWidget {
|
|||||||
|
|
||||||
if (isReadOnly) {
|
if (isReadOnly) {
|
||||||
const $container = await this.noteContext.getContentElement();
|
const $container = await this.noteContext.getContentElement();
|
||||||
const headingElement = $container.find(":header")[headingIndex];
|
const headingElement = $container.find(":header:not(section.include-note :header)")[headingIndex];
|
||||||
|
|
||||||
if (headingElement != null) {
|
if (headingElement != null) {
|
||||||
headingElement.scrollIntoView({ behavior: "smooth" });
|
headingElement.scrollIntoView({ behavior: "smooth" });
|
||||||
@ -206,7 +206,7 @@ export default class TocWidget extends RightPanelWidget {
|
|||||||
// navigate (note that the TOC rendering and other TOC
|
// navigate (note that the TOC rendering and other TOC
|
||||||
// entries' navigation could be wrong too)
|
// entries' navigation could be wrong too)
|
||||||
if (headingNode != null) {
|
if (headingNode != null) {
|
||||||
$(textEditor.editing.view.domRoots.values().next().value).find(':header')[headingIndex].scrollIntoView({
|
$(textEditor.editing.view.domRoots.values().next().value).find(':header:not(section.include-note :header)')[headingIndex].scrollIntoView({
|
||||||
behavior: 'smooth'
|
behavior: 'smooth'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@ const TPL = `
|
|||||||
*
|
*
|
||||||
* Discussion of storing svg in the note:
|
* Discussion of storing svg in the note:
|
||||||
* - Pro: we will combat bit-rot. Showing the SVG will be very fast and easy, since it is already there.
|
* - Pro: we will combat bit-rot. Showing the SVG will be very fast and easy, since it is already there.
|
||||||
* - Con: The note will get bigger (~40-50%?), we will generate more bandwith. However, using trilium
|
* - Con: The note will get bigger (~40-50%?), we will generate more bandwidth. However, using trilium
|
||||||
* desktop instance mitigates that issue.
|
* desktop instance mitigates that issue.
|
||||||
*
|
*
|
||||||
* Roadmap:
|
* Roadmap:
|
||||||
|
@ -7,7 +7,7 @@ import MaxContentWidthOptions from "./options/appearance/max_content_width.js";
|
|||||||
import KeyboardShortcutsOptions from "./options/shortcuts.js";
|
import KeyboardShortcutsOptions from "./options/shortcuts.js";
|
||||||
import HeadingStyleOptions from "./options/text_notes/heading_style.js";
|
import HeadingStyleOptions from "./options/text_notes/heading_style.js";
|
||||||
import TableOfContentsOptions from "./options/text_notes/table_of_contents.js";
|
import TableOfContentsOptions from "./options/text_notes/table_of_contents.js";
|
||||||
import HighlightedTextOptions from "./options/text_notes/highlighted_text.js";
|
import HighlightsListOptions from "./options/text_notes/highlights_list.js";
|
||||||
import TextAutoReadOnlySizeOptions from "./options/text_notes/text_auto_read_only_size.js";
|
import TextAutoReadOnlySizeOptions from "./options/text_notes/text_auto_read_only_size.js";
|
||||||
import VimKeyBindingsOptions from "./options/code_notes/vim_key_bindings.js";
|
import VimKeyBindingsOptions from "./options/code_notes/vim_key_bindings.js";
|
||||||
import WrapLinesOptions from "./options/code_notes/wrap_lines.js";
|
import WrapLinesOptions from "./options/code_notes/wrap_lines.js";
|
||||||
@ -63,7 +63,7 @@ const CONTENT_WIDGETS = {
|
|||||||
_optionsTextNotes: [
|
_optionsTextNotes: [
|
||||||
HeadingStyleOptions,
|
HeadingStyleOptions,
|
||||||
TableOfContentsOptions,
|
TableOfContentsOptions,
|
||||||
HighlightedTextOptions,
|
HighlightsListOptions,
|
||||||
TextAutoReadOnlySizeOptions
|
TextAutoReadOnlySizeOptions
|
||||||
],
|
],
|
||||||
_optionsCodeNotes: [
|
_optionsCodeNotes: [
|
||||||
|
@ -96,7 +96,7 @@ export default class EtapiOptions extends OptionsWidget {
|
|||||||
.append($("<td>").append(
|
.append($("<td>").append(
|
||||||
$('<span class="bx bx-pen token-table-button" title="Rename this token"></span>')
|
$('<span class="bx bx-pen token-table-button" title="Rename this token"></span>')
|
||||||
.on("click", () => this.renameToken(token.etapiTokenId, token.name)),
|
.on("click", () => this.renameToken(token.etapiTokenId, token.name)),
|
||||||
$('<span class="bx bx-trash token-table-button" title="Delete / deactive this token"></span>')
|
$('<span class="bx bx-trash token-table-button" title="Delete / deactivate this token"></span>')
|
||||||
.on("click", () => this.deleteToken(token.etapiTokenId, token.name))
|
.on("click", () => this.deleteToken(token.etapiTokenId, token.name))
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
import OptionsWidget from "../options_widget.js";
|
|
||||||
|
|
||||||
const TPL = `
|
|
||||||
<div class="options-section">
|
|
||||||
<h4>Highlighted Text</h4>
|
|
||||||
|
|
||||||
<p>You can customize the highlighted text displayed in the right panel:</p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<label><input type="checkbox" class="highlighted-text-check" value="bold"> Bold font </label>
|
|
||||||
<label><input type="checkbox" class="highlighted-text-check" value="italic"> Italic font </label>
|
|
||||||
<label><input type="checkbox" class="highlighted-text-check" value="underline"> Underlined font </label>
|
|
||||||
<label><input type="checkbox" class="highlighted-text-check" value="color"> Font with color </label>
|
|
||||||
<label><input type="checkbox" class="highlighted-text-check" value="bgColor"> Font with background color </label>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
export default class HighlightedTextOptions extends OptionsWidget {
|
|
||||||
doRender() {
|
|
||||||
this.$widget = $(TPL);
|
|
||||||
this.$hlt = this.$widget.find("input.highlighted-text-check");
|
|
||||||
this.$hlt.on('change', () => {
|
|
||||||
const hltVals = this.$widget.find('input.highlighted-text-check[type="checkbox"]:checked').map(function () {
|
|
||||||
return this.value;
|
|
||||||
}).get();
|
|
||||||
this.updateOption('highlightedText', JSON.stringify(hltVals));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async optionsLoaded(options) {
|
|
||||||
const hltVals = JSON.parse(options.highlightedText);
|
|
||||||
this.$widget.find('input.highlighted-text-check[type="checkbox"]').each(function () {
|
|
||||||
if ($.inArray($(this).val(), hltVals) !== -1) {
|
|
||||||
$(this).prop("checked", true);
|
|
||||||
} else {
|
|
||||||
$(this).prop("checked", false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,40 @@
|
|||||||
|
import OptionsWidget from "../options_widget.js";
|
||||||
|
|
||||||
|
const TPL = `
|
||||||
|
<div class="options-section">
|
||||||
|
<h4>Highlights List</h4>
|
||||||
|
|
||||||
|
<p>You can customize the highlights list displayed in the right panel:</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<label><input type="checkbox" class="highlights-list-check" value="bold"> Bold font </label>
|
||||||
|
<label><input type="checkbox" class="highlights-list-check" value="italic"> Italic font </label>
|
||||||
|
<label><input type="checkbox" class="highlights-list-check" value="underline"> Underlined font </label>
|
||||||
|
<label><input type="checkbox" class="highlights-list-check" value="color"> Font with color </label>
|
||||||
|
<label><input type="checkbox" class="highlights-list-check" value="bgColor"> Font with background color </label>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
export default class HighlightsListOptions extends OptionsWidget {
|
||||||
|
doRender() {
|
||||||
|
this.$widget = $(TPL);
|
||||||
|
this.$hlt = this.$widget.find("input.highlights-list-check");
|
||||||
|
this.$hlt.on('change', () => {
|
||||||
|
const hltVals = this.$widget.find('input.highlights-list-check[type="checkbox"]:checked').map(function () {
|
||||||
|
return this.value;
|
||||||
|
}).get();
|
||||||
|
this.updateOption('highlightsList', JSON.stringify(hltVals));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async optionsLoaded(options) {
|
||||||
|
const hltVals = JSON.parse(options.highlightsList);
|
||||||
|
this.$widget.find('input.highlights-list-check[type="checkbox"]').each(function () {
|
||||||
|
if ($.inArray($(this).val(), hltVals) !== -1) {
|
||||||
|
$(this).prop("checked", true);
|
||||||
|
} else {
|
||||||
|
$(this).prop("checked", false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -412,7 +412,7 @@ export default class RelationMapTypeWidget extends TypeWidget {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// if there's no event, then this has been triggered programatically
|
// if there's no event, then this has been triggered programmatically
|
||||||
if (!originalEvent) {
|
if (!originalEvent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const attributeService = require("../../services/attributes");
|
const attributeService = require("../../services/attributes");
|
||||||
|
const cloneService = require("../../services/cloning");
|
||||||
const noteService = require('../../services/notes');
|
const noteService = require('../../services/notes');
|
||||||
const dateNoteService = require('../../services/date_notes');
|
const dateNoteService = require('../../services/date_notes');
|
||||||
const dateUtils = require('../../services/date_utils');
|
const dateUtils = require('../../services/date_utils');
|
||||||
@ -13,46 +14,25 @@ const path = require('path');
|
|||||||
const BAttribute = require('../../becca/entities/battribute');
|
const BAttribute = require('../../becca/entities/battribute');
|
||||||
const htmlSanitizer = require('../../services/html_sanitizer');
|
const htmlSanitizer = require('../../services/html_sanitizer');
|
||||||
const {formatAttrForSearch} = require("../../services/attribute_formatter");
|
const {formatAttrForSearch} = require("../../services/attribute_formatter");
|
||||||
|
const jsdom = require("jsdom");
|
||||||
function findClippingNote(clipperInboxNote, pageUrl) {
|
const { JSDOM } = jsdom;
|
||||||
const notes = clipperInboxNote.searchNotesInSubtree(
|
|
||||||
formatAttrForSearch({
|
|
||||||
type: 'label',
|
|
||||||
name: "pageUrl",
|
|
||||||
value: pageUrl
|
|
||||||
}, true)
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const note of notes) {
|
|
||||||
if (note.getOwnedLabelValue('clipType') === 'clippings') {
|
|
||||||
return note;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getClipperInboxNote() {
|
|
||||||
let clipperInbox = attributeService.getNoteWithLabel('clipperInbox');
|
|
||||||
|
|
||||||
if (!clipperInbox) {
|
|
||||||
clipperInbox = dateNoteService.getDayNote(dateUtils.localNowDate());
|
|
||||||
}
|
|
||||||
|
|
||||||
return clipperInbox;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addClipping(req) {
|
function addClipping(req) {
|
||||||
|
// if a note under the clipperInbox as the same 'pageUrl' attribute,
|
||||||
|
// add the content to that note and clone it under today's inbox
|
||||||
|
// otherwise just create a new note under today's inbox
|
||||||
let {title, content, pageUrl, images} = req.body;
|
let {title, content, pageUrl, images} = req.body;
|
||||||
|
const clipType = 'clippings';
|
||||||
|
|
||||||
const clipperInbox = getClipperInboxNote();
|
const clipperInbox = getClipperInboxNote();
|
||||||
|
const dailyNote = dateNoteService.getDayNote(dateUtils.localNowDate());
|
||||||
|
|
||||||
pageUrl = htmlSanitizer.sanitizeUrl(pageUrl);
|
pageUrl = htmlSanitizer.sanitizeUrl(pageUrl);
|
||||||
let clippingNote = findClippingNote(clipperInbox, pageUrl);
|
let clippingNote = findClippingNote(clipperInbox, pageUrl, clipType);
|
||||||
|
|
||||||
if (!clippingNote) {
|
if (!clippingNote) {
|
||||||
clippingNote = noteService.createNewNote({
|
clippingNote = noteService.createNewNote({
|
||||||
parentNoteId: clipperInbox.noteId,
|
parentNoteId: dailyNote.noteId,
|
||||||
title: title,
|
title: title,
|
||||||
content: '',
|
content: '',
|
||||||
type: 'text'
|
type: 'text'
|
||||||
@ -67,13 +47,45 @@ function addClipping(req) {
|
|||||||
|
|
||||||
const existingContent = clippingNote.getContent();
|
const existingContent = clippingNote.getContent();
|
||||||
|
|
||||||
clippingNote.setContent(`${existingContent}${existingContent.trim() ? "<br/>" : ""}${rewrittenContent}`);
|
clippingNote.setContent(`${existingContent}${existingContent.trim() ? "<br>" : ""}${rewrittenContent}`);
|
||||||
|
|
||||||
|
if (clippingNote.parentNoteId !== dailyNote.noteId) {
|
||||||
|
cloneService.cloneNoteToParentNote(clippingNote.noteId, dailyNote.noteId);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
noteId: clippingNote.noteId
|
noteId: clippingNote.noteId
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findClippingNote(clipperInboxNote, pageUrl, clipType) {
|
||||||
|
if (!pageUrl) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const notes = clipperInboxNote.searchNotesInSubtree(
|
||||||
|
formatAttrForSearch({
|
||||||
|
type: 'label',
|
||||||
|
name: "pageUrl",
|
||||||
|
value: pageUrl
|
||||||
|
}, true)
|
||||||
|
);
|
||||||
|
|
||||||
|
return clipType
|
||||||
|
? notes.find(note => note.getOwnedLabelValue('clipType') === clipType)
|
||||||
|
: notes[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getClipperInboxNote() {
|
||||||
|
let clipperInbox = attributeService.getNoteWithLabel('clipperInbox');
|
||||||
|
|
||||||
|
if (!clipperInbox) {
|
||||||
|
clipperInbox = dateNoteService.getRootCalendarNote();
|
||||||
|
}
|
||||||
|
|
||||||
|
return clipperInbox;
|
||||||
|
}
|
||||||
|
|
||||||
function createNote(req) {
|
function createNote(req) {
|
||||||
let {title, content, pageUrl, images, clipType, labels} = req.body;
|
let {title, content, pageUrl, images, clipType, labels} = req.body;
|
||||||
|
|
||||||
@ -81,26 +93,31 @@ function createNote(req) {
|
|||||||
title = `Clipped note from ${pageUrl}`;
|
title = `Clipped note from ${pageUrl}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const clipperInbox = getClipperInboxNote();
|
|
||||||
|
|
||||||
const {note} = noteService.createNewNote({
|
|
||||||
parentNoteId: clipperInbox.noteId,
|
|
||||||
title,
|
|
||||||
content,
|
|
||||||
type: 'text'
|
|
||||||
});
|
|
||||||
|
|
||||||
clipType = htmlSanitizer.sanitize(clipType);
|
clipType = htmlSanitizer.sanitize(clipType);
|
||||||
|
|
||||||
note.setLabel('clipType', clipType);
|
const clipperInbox = getClipperInboxNote();
|
||||||
|
const dailyNote = dateNoteService.getDayNote(dateUtils.localNowDate());
|
||||||
|
pageUrl = htmlSanitizer.sanitizeUrl(pageUrl);
|
||||||
|
let note = findClippingNote(clipperInbox, pageUrl, clipType);
|
||||||
|
|
||||||
if (pageUrl) {
|
if (!note) {
|
||||||
pageUrl = htmlSanitizer.sanitizeUrl(pageUrl);
|
note = noteService.createNewNote({
|
||||||
|
parentNoteId: dailyNote.noteId,
|
||||||
|
title,
|
||||||
|
content: '',
|
||||||
|
type: 'text'
|
||||||
|
}).note;
|
||||||
|
|
||||||
note.setLabel('pageUrl', pageUrl);
|
note.setLabel('clipType', clipType);
|
||||||
note.setLabel('iconClass', 'bx bx-globe');
|
|
||||||
|
if (pageUrl) {
|
||||||
|
pageUrl = htmlSanitizer.sanitizeUrl(pageUrl);
|
||||||
|
|
||||||
|
note.setLabel('pageUrl', pageUrl);
|
||||||
|
note.setLabel('iconClass', 'bx bx-globe');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (labels) {
|
if (labels) {
|
||||||
for (const labelName in labels) {
|
for (const labelName in labels) {
|
||||||
const labelValue = htmlSanitizer.sanitize(labels[labelName]);
|
const labelValue = htmlSanitizer.sanitize(labels[labelName]);
|
||||||
@ -108,9 +125,9 @@ function createNote(req) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const existingContent = note.getContent();
|
||||||
const rewrittenContent = processContent(images, note, content);
|
const rewrittenContent = processContent(images, note, content);
|
||||||
|
note.setContent(`${existingContent}${existingContent.trim() ? "<br/>" : ""}${rewrittenContent}`);
|
||||||
note.setContent(rewrittenContent);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
noteId: note.noteId
|
noteId: note.noteId
|
||||||
@ -158,6 +175,15 @@ function processContent(images, note, content) {
|
|||||||
|
|
||||||
// fallback if parsing/downloading images fails for some reason on the extension side (
|
// fallback if parsing/downloading images fails for some reason on the extension side (
|
||||||
rewrittenContent = noteService.downloadImages(note.noteId, rewrittenContent);
|
rewrittenContent = noteService.downloadImages(note.noteId, rewrittenContent);
|
||||||
|
// Check if rewrittenContent contains at least one HTML tag
|
||||||
|
if (!/<.+?>/.test(rewrittenContent)) {
|
||||||
|
rewrittenContent = `<p>${rewrittenContent}</p>`;
|
||||||
|
}
|
||||||
|
// Create a JSDOM object from the existing HTML content
|
||||||
|
const dom = new JSDOM(rewrittenContent);
|
||||||
|
|
||||||
|
// Get the content inside the body tag and serialize it
|
||||||
|
rewrittenContent = dom.window.document.body.innerHTML;
|
||||||
|
|
||||||
return rewrittenContent;
|
return rewrittenContent;
|
||||||
}
|
}
|
||||||
@ -187,9 +213,19 @@ function handshake() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findNotesByUrl(req){
|
||||||
|
let pageUrl = req.params.noteUrl;
|
||||||
|
const clipperInbox = getClipperInboxNote();
|
||||||
|
let foundPage = findClippingNote(clipperInbox, pageUrl, null);
|
||||||
|
return {
|
||||||
|
noteId: foundPage ? foundPage.noteId : null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createNote,
|
createNote,
|
||||||
addClipping,
|
addClipping,
|
||||||
openNote,
|
openNote,
|
||||||
handshake
|
handshake,
|
||||||
|
findNotesByUrl
|
||||||
};
|
};
|
||||||
|
@ -49,7 +49,7 @@ const ALLOWED_OPTIONS = new Set([
|
|||||||
'compressImages',
|
'compressImages',
|
||||||
'downloadImagesAutomatically',
|
'downloadImagesAutomatically',
|
||||||
'minTocHeadings',
|
'minTocHeadings',
|
||||||
'highlightedText',
|
'highlightsList',
|
||||||
'checkForUpdates',
|
'checkForUpdates',
|
||||||
'disableTray',
|
'disableTray',
|
||||||
'eraseUnusedAttachmentsAfterSeconds',
|
'eraseUnusedAttachmentsAfterSeconds',
|
||||||
|
@ -11,7 +11,7 @@ function addRecentNote(req) {
|
|||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
if (Math.random() < 0.05) {
|
if (Math.random() < 0.05) {
|
||||||
// it's not necessary to run this everytime ...
|
// it's not necessary to run this every time ...
|
||||||
const cutOffDate = dateUtils.utcDateTimeStr(new Date(Date.now() - 24 * 3600 * 1000));
|
const cutOffDate = dateUtils.utcDateTimeStr(new Date(Date.now() - 24 * 3600 * 1000));
|
||||||
|
|
||||||
sql.execute(`DELETE FROM recent_notes WHERE utcDateCreated < ?`, [cutOffDate]);
|
sql.execute(`DELETE FROM recent_notes WHERE utcDateCreated < ?`, [cutOffDate]);
|
||||||
|
@ -28,6 +28,12 @@ function execute(req) {
|
|||||||
for (let query of queries) {
|
for (let query of queries) {
|
||||||
query = query.trim();
|
query = query.trim();
|
||||||
|
|
||||||
|
while (query.startsWith('-- ')) {
|
||||||
|
// Query starts with one or more SQL comments, discard these before we execute.
|
||||||
|
const pivot = query.indexOf('\n');
|
||||||
|
query = pivot > 0 ? query.substr(pivot + 1).trim() : "";
|
||||||
|
}
|
||||||
|
|
||||||
if (!query) {
|
if (!query) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ function checkSync() {
|
|||||||
function syncNow() {
|
function syncNow() {
|
||||||
log.info("Received request to trigger sync now.");
|
log.info("Received request to trigger sync now.");
|
||||||
|
|
||||||
// when explicitly asked for set in progress status immediatelly for faster user feedback
|
// when explicitly asked for set in progress status immediately for faster user feedback
|
||||||
ws.syncPullInProgress();
|
ws.syncPullInProgress();
|
||||||
|
|
||||||
return syncService.sync();
|
return syncService.sync();
|
||||||
|
@ -269,6 +269,7 @@ function register(app) {
|
|||||||
route(PST, '/api/clipper/clippings', clipperMiddleware, clipperRoute.addClipping, apiResultHandler);
|
route(PST, '/api/clipper/clippings', clipperMiddleware, clipperRoute.addClipping, apiResultHandler);
|
||||||
route(PST, '/api/clipper/notes', clipperMiddleware, clipperRoute.createNote, apiResultHandler);
|
route(PST, '/api/clipper/notes', clipperMiddleware, clipperRoute.createNote, apiResultHandler);
|
||||||
route(PST, '/api/clipper/open/:noteId', clipperMiddleware, clipperRoute.openNote, apiResultHandler);
|
route(PST, '/api/clipper/open/:noteId', clipperMiddleware, clipperRoute.openNote, apiResultHandler);
|
||||||
|
route(GET, '/api/clipper/notes-by-url/:noteUrl', clipperMiddleware, clipperRoute.findNotesByUrl, apiResultHandler);
|
||||||
|
|
||||||
apiRoute(GET, '/api/special-notes/inbox/:date', specialNotesRoute.getInboxNote);
|
apiRoute(GET, '/api/special-notes/inbox/:date', specialNotesRoute.getInboxNote);
|
||||||
apiRoute(GET, '/api/special-notes/days/:date', specialNotesRoute.getDayNote);
|
apiRoute(GET, '/api/special-notes/days/:date', specialNotesRoute.getDayNote);
|
||||||
|
@ -395,7 +395,7 @@ class ConsistencyChecks {
|
|||||||
({noteId, isProtected, type, mime}) => {
|
({noteId, isProtected, type, mime}) => {
|
||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
// it might be possible that the blob is not available only because of the interrupted
|
// it might be possible that the blob is not available only because of the interrupted
|
||||||
// sync, and it will come later. It's therefore important to guarantee that this artifical
|
// sync, and it will come later. It's therefore important to guarantee that this artificial
|
||||||
// record won't overwrite the real one coming from the sync.
|
// record won't overwrite the real one coming from the sync.
|
||||||
const fakeDate = "2000-01-01 00:00:00Z";
|
const fakeDate = "2000-01-01 00:00:00Z";
|
||||||
|
|
||||||
|
@ -57,5 +57,7 @@ function sanitize(dirtyHtml) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
sanitize,
|
sanitize,
|
||||||
sanitizeUrl
|
sanitizeUrl: url => {
|
||||||
|
return sanitizeUrl(url).trim();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -83,7 +83,7 @@ const defaultOptions = [
|
|||||||
{ name: 'compressImages', value: 'true', isSynced: true },
|
{ name: 'compressImages', value: 'true', isSynced: true },
|
||||||
{ name: 'downloadImagesAutomatically', value: 'true', isSynced: true },
|
{ name: 'downloadImagesAutomatically', value: 'true', isSynced: true },
|
||||||
{ name: 'minTocHeadings', value: '5', isSynced: true },
|
{ name: 'minTocHeadings', value: '5', isSynced: true },
|
||||||
{ name: 'highlightedText', value: '["bold","italic","underline","color","bgColor"]', isSynced: true },
|
{ name: 'highlightsList', value: '["bold","italic","underline","color","bgColor"]', isSynced: true },
|
||||||
{ name: 'checkForUpdates', value: 'true', isSynced: true },
|
{ name: 'checkForUpdates', value: 'true', isSynced: true },
|
||||||
{ name: 'disableTray', value: 'false', isSynced: false },
|
{ name: 'disableTray', value: 'false', isSynced: false },
|
||||||
{ name: 'eraseUnusedAttachmentsAfterSeconds', value: '2592000', isSynced: true },
|
{ name: 'eraseUnusedAttachmentsAfterSeconds', value: '2592000', isSynced: true },
|
||||||
|
@ -55,7 +55,7 @@ ${bundle.script}\r
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* THIS METHOD CANT BE ASYNC, OTHERWISE TRANSACTION WRAPPER WON'T BE EFFECTIVE AND WE WILL BE LOSING THE
|
* THIS METHOD CAN'T BE ASYNC, OTHERWISE TRANSACTION WRAPPER WON'T BE EFFECTIVE AND WE WILL BE LOSING THE
|
||||||
* ENTITY CHANGES IN CLS.
|
* ENTITY CHANGES IN CLS.
|
||||||
*
|
*
|
||||||
* This method preserves frontend startNode - that's why we start execution from currentNote and override
|
* This method preserves frontend startNode - that's why we start execution from currentNote and override
|
||||||
|
@ -19,20 +19,22 @@ class NoteFlatTextExp extends Expression {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {BNote} note
|
* @param {BNote} note
|
||||||
* @param {string[]} tokens
|
* @param {string[]} remainingTokens - tokens still needed to be found in the path towards root
|
||||||
* @param {string[]} path
|
* @param {string[]} takenPath - path so far taken towards from candidate note towards the root.
|
||||||
|
* It contains the suffix fragment of the full note path.
|
||||||
*/
|
*/
|
||||||
const searchDownThePath = (note, tokens, path) => {
|
const searchPathTowardsRoot = (note, remainingTokens, takenPath) => {
|
||||||
if (tokens.length === 0) {
|
if (remainingTokens.length === 0) {
|
||||||
const retPath = this.getNotePath(note, path);
|
// we're done, just build the result
|
||||||
|
const resultPath = this.getNotePath(note, takenPath);
|
||||||
|
|
||||||
if (retPath) {
|
if (resultPath) {
|
||||||
const noteId = retPath[retPath.length - 1];
|
const noteId = resultPath[resultPath.length - 1];
|
||||||
|
|
||||||
if (!resultNoteSet.hasNoteId(noteId)) {
|
if (!resultNoteSet.hasNoteId(noteId)) {
|
||||||
// we could get here from multiple paths, the first one wins because the paths
|
// we could get here from multiple paths, the first one wins because the paths
|
||||||
// are sorted by importance
|
// are sorted by importance
|
||||||
executionContext.noteIdToNotePath[noteId] = retPath;
|
executionContext.noteIdToNotePath[noteId] = resultPath;
|
||||||
|
|
||||||
resultNoteSet.add(becca.notes[noteId]);
|
resultNoteSet.add(becca.notes[noteId]);
|
||||||
}
|
}
|
||||||
@ -42,22 +44,23 @@ class NoteFlatTextExp extends Expression {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (note.parents.length === 0 || note.noteId === 'root') {
|
if (note.parents.length === 0 || note.noteId === 'root') {
|
||||||
|
// we've reached root, but there are still remaining tokens -> this candidate note produced no result
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundAttrTokens = [];
|
const foundAttrTokens = [];
|
||||||
|
|
||||||
for (const token of tokens) {
|
for (const token of remainingTokens) {
|
||||||
if (note.type.includes(token) || note.mime.includes(token)) {
|
if (note.type.includes(token) || note.mime.includes(token)) {
|
||||||
foundAttrTokens.push(token);
|
foundAttrTokens.push(token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const attribute of note.ownedAttributes) {
|
for (const attribute of note.getOwnedAttributes()) {
|
||||||
const normalizedName = utils.normalize(attribute.name);
|
const normalizedName = utils.normalize(attribute.name);
|
||||||
const normalizedValue = utils.normalize(attribute.value);
|
const normalizedValue = utils.normalize(attribute.value);
|
||||||
|
|
||||||
for (const token of tokens) {
|
for (const token of remainingTokens) {
|
||||||
if (normalizedName.includes(token) || normalizedValue.includes(token)) {
|
if (normalizedName.includes(token) || normalizedValue.includes(token)) {
|
||||||
foundAttrTokens.push(token);
|
foundAttrTokens.push(token);
|
||||||
}
|
}
|
||||||
@ -68,19 +71,19 @@ class NoteFlatTextExp extends Expression {
|
|||||||
const title = utils.normalize(beccaService.getNoteTitle(note.noteId, parentNote.noteId));
|
const title = utils.normalize(beccaService.getNoteTitle(note.noteId, parentNote.noteId));
|
||||||
const foundTokens = foundAttrTokens.slice();
|
const foundTokens = foundAttrTokens.slice();
|
||||||
|
|
||||||
for (const token of tokens) {
|
for (const token of remainingTokens) {
|
||||||
if (title.includes(token)) {
|
if (title.includes(token)) {
|
||||||
foundTokens.push(token);
|
foundTokens.push(token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foundTokens.length > 0) {
|
if (foundTokens.length > 0) {
|
||||||
const remainingTokens = tokens.filter(token => !foundTokens.includes(token));
|
const newRemainingTokens = remainingTokens.filter(token => !foundTokens.includes(token));
|
||||||
|
|
||||||
searchDownThePath(parentNote, remainingTokens, [...path, note.noteId]);
|
searchPathTowardsRoot(parentNote, newRemainingTokens, [note.noteId, ...takenPath]);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
searchDownThePath(parentNote, tokens, [...path, note.noteId]);
|
searchPathTowardsRoot(parentNote, remainingTokens, [note.noteId, ...takenPath]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,7 +93,7 @@ class NoteFlatTextExp extends Expression {
|
|||||||
for (const note of candidateNotes) {
|
for (const note of candidateNotes) {
|
||||||
// autocomplete should be able to find notes by their noteIds as well (only leafs)
|
// autocomplete should be able to find notes by their noteIds as well (only leafs)
|
||||||
if (this.tokens.length === 1 && note.noteId.toLowerCase() === this.tokens[0]) {
|
if (this.tokens.length === 1 && note.noteId.toLowerCase() === this.tokens[0]) {
|
||||||
searchDownThePath(note, [], []);
|
searchPathTowardsRoot(note, [], [note.noteId]);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +126,7 @@ class NoteFlatTextExp extends Expression {
|
|||||||
if (foundTokens.length > 0) {
|
if (foundTokens.length > 0) {
|
||||||
const remainingTokens = this.tokens.filter(token => !foundTokens.includes(token));
|
const remainingTokens = this.tokens.filter(token => !foundTokens.includes(token));
|
||||||
|
|
||||||
searchDownThePath(parentNote, remainingTokens, [note.noteId]);
|
searchPathTowardsRoot(parentNote, remainingTokens, [note.noteId]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,14 +134,22 @@ class NoteFlatTextExp extends Expression {
|
|||||||
return resultNoteSet;
|
return resultNoteSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
getNotePath(note, path) {
|
/**
|
||||||
if (path.length === 0) {
|
* @param {BNote} note
|
||||||
|
* @param {string[]} takenPath
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
getNotePath(note, takenPath) {
|
||||||
|
if (takenPath.length === 0) {
|
||||||
|
throw new Error("Path is not expected to be empty.");
|
||||||
|
} else if (takenPath.length === 1 && takenPath[0] === note.noteId) {
|
||||||
return note.getBestNotePath();
|
return note.getBestNotePath();
|
||||||
} else {
|
} else {
|
||||||
const closestNoteId = path[0];
|
// this note is the closest to root containing the last matching token(s), thus completing the requirements
|
||||||
const closestNoteBestNotePath = becca.getNote(closestNoteId).getBestNotePath();
|
// what's in this note's predecessors does not matter, thus we'll choose the best note path
|
||||||
|
const topMostMatchingTokenNotePath = becca.getNote(takenPath[0]).getBestNotePath();
|
||||||
|
|
||||||
return [...closestNoteBestNotePath, ...path.slice(1)];
|
return [...topMostMatchingTokenNotePath, ...takenPath.slice(1)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ const becca = require('../../../becca/becca');
|
|||||||
const beccaService = require('../../../becca/becca_service');
|
const beccaService = require('../../../becca/becca_service');
|
||||||
const utils = require('../../utils');
|
const utils = require('../../utils');
|
||||||
const log = require('../../log');
|
const log = require('../../log');
|
||||||
const scriptService = require("../../script");
|
|
||||||
const hoistedNoteService = require("../../hoisted_note");
|
const hoistedNoteService = require("../../hoisted_note");
|
||||||
|
|
||||||
function searchFromNote(note) {
|
function searchFromNote(note) {
|
||||||
@ -73,6 +72,7 @@ function searchFromRelation(note, relationName) {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const scriptService = require("../../script"); // to avoid circular dependency
|
||||||
const result = scriptService.executeNote(scriptNote, {originEntity: note});
|
const result = scriptService.executeNote(scriptNote, {originEntity: note});
|
||||||
|
|
||||||
if (!Array.isArray(result)) {
|
if (!Array.isArray(result)) {
|
||||||
|
@ -13,7 +13,7 @@ class TaskContext {
|
|||||||
this.noteDeletionHandlerTriggered = false;
|
this.noteDeletionHandlerTriggered = false;
|
||||||
|
|
||||||
// progressCount is meant to represent just some progress - to indicate the task is not stuck
|
// progressCount is meant to represent just some progress - to indicate the task is not stuck
|
||||||
this.progressCount = -1; // we're incrementing immediatelly
|
this.progressCount = -1; // we're incrementing immediately
|
||||||
this.lastSentCountTs = 0; // 0 will guarantee the first message will be sent
|
this.lastSentCountTs = 0; // 0 will guarantee the first message will be sent
|
||||||
|
|
||||||
// just the fact this has been initialized is a progress which should be sent to clients
|
// just the fact this has been initialized is a progress which should be sent to clients
|
||||||
|
@ -96,7 +96,7 @@
|
|||||||
<li>From the Trilium Menu, click Options.</li>
|
<li>From the Trilium Menu, click Options.</li>
|
||||||
<li>Click on Sync tab.</li>
|
<li>Click on Sync tab.</li>
|
||||||
<li>Change server instance address to: <span id="current-host"></span> and click save.</li>
|
<li>Change server instance address to: <span id="current-host"></span> and click save.</li>
|
||||||
<li>Click "Test sync" button to verify connection is successfull.</li>
|
<li>Click "Test sync" button to verify connection is successful.</li>
|
||||||
<li>Once you've completed these steps, click <a href="/">here</a>.</li>
|
<li>Once you've completed these steps, click <a href="/">here</a>.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user