mirror of
https://github.com/zadam/trilium.git
synced 2024-12-27 01:34:05 +08: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
41 changed files with 253 additions and 192 deletions
|
@ -16,7 +16,7 @@ noBackup=false
|
|||
# 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=8080
|
||||
# true for TLS/SSL/HTTPS (secure), false for HTTP (unsecure).
|
||||
# true for TLS/SSL/HTTPS (secure), false for HTTP (insecure).
|
||||
https=false
|
||||
# path to certificate (run "bash bin/generate-cert.sh" to generate self-signed certificate). Relevant only if https=true
|
||||
certPath=
|
||||
|
|
|
@ -4,7 +4,7 @@ const fs = require("fs");
|
|||
const dataDir = require("./src/services/data_dir");
|
||||
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
|
||||
// for reverse proxy terminated TLS this will works since config.https will be false
|
||||
process.exit(0);
|
||||
|
|
|
@ -667,7 +667,7 @@ class BNote extends AbstractBeccaEntity {
|
|||
return this.ownedAttributes.filter(attr => attr.name === name);
|
||||
}
|
||||
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
|
||||
* 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.
|
||||
*/
|
||||
const {utcDateCreated} = candidateNote;
|
||||
|
|
|
@ -231,7 +231,7 @@ paths:
|
|||
schema:
|
||||
$ref: '#/components/schemas/EntityId'
|
||||
get:
|
||||
description: Returns note content idenfied by its ID
|
||||
description: Returns note content identified by its ID
|
||||
operationId: getNoteContent
|
||||
responses:
|
||||
'200':
|
||||
|
@ -241,7 +241,7 @@ paths:
|
|||
schema:
|
||||
type: string
|
||||
put:
|
||||
description: Updates note content idenfied by its ID
|
||||
description: Updates note content identified by its ID
|
||||
operationId: putNoteContentById
|
||||
requestBody:
|
||||
description: html content of note
|
||||
|
|
|
@ -41,8 +41,8 @@ function initAttributeNameAutocomplete({ $el, attributeType, open }) {
|
|||
|
||||
async function initLabelValueAutocomplete({ $el, open, nameCallback }) {
|
||||
if ($el.hasClass("aa-input")) {
|
||||
// we reinit everytime because autocomplete seems to have a bug where it retains state from last
|
||||
// open even though the value was resetted
|
||||
// we reinit every time because autocomplete seems to have a bug where it retains state from last
|
||||
// open even though the value was reset
|
||||
$el.autocomplete('destroy');
|
||||
}
|
||||
|
||||
|
|
|
@ -133,7 +133,7 @@ function initNoteAutocomplete($el, options) {
|
|||
showRecentNotes($el);
|
||||
|
||||
// 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;
|
||||
});
|
||||
|
||||
|
|
|
@ -99,7 +99,7 @@ function parseSelectedHtml(selectedHtml) {
|
|||
|
||||
if (dom.length > 0 && dom[0].tagName && dom[0].tagName.match(/h[1-6]/i)) {
|
||||
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, "");
|
||||
|
||||
return [title, content];
|
||||
|
|
|
@ -161,7 +161,7 @@ class NoteListRenderer {
|
|||
constructor($parent, parentNote, noteIds, showNotePath = false) {
|
||||
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();
|
||||
|
||||
this.parentNote = parentNote;
|
||||
|
|
|
@ -21,7 +21,7 @@ async function getHeaders(headers) {
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* 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) {
|
||||
if (!noteId) {
|
||||
|
|
|
@ -26,7 +26,7 @@ export default class AbstractBulkAction {
|
|||
}
|
||||
}
|
||||
|
||||
// to be overriden
|
||||
// to be overridden
|
||||
doRender() {}
|
||||
|
||||
async saveAction(data) {
|
||||
|
|
|
@ -50,7 +50,7 @@ export default class RightDropdownButtonWidget extends BasicWidget {
|
|||
this.$widget.find(".dropdown-menu").append(this.$dropdownContent);
|
||||
}
|
||||
|
||||
// to be overriden
|
||||
// to be overridden
|
||||
async dropdownShow() {}
|
||||
|
||||
hideDropdown() {
|
||||
|
|
|
@ -25,7 +25,7 @@ const TPL = `
|
|||
</div>
|
||||
|
||||
<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">
|
||||
|
||||
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 OnClickButtonWidget from "./buttons/onclick_button.js";
|
||||
|
||||
const TPL = `<div class="highlists-list-widget">
|
||||
const TPL = `<div class="highlights-list-widget">
|
||||
<style>
|
||||
.highlists-list-widget {
|
||||
.highlights-list-widget {
|
||||
padding: 10px;
|
||||
contain: none;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.highlists-list > ol {
|
||||
.highlights-list > ol {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.highlists-list li {
|
||||
.highlights-list li {
|
||||
cursor: pointer;
|
||||
margin-bottom: 3px;
|
||||
text-align: justify;
|
||||
|
@ -32,18 +32,18 @@ const TPL = `<div class="highlists-list-widget">
|
|||
hyphens: auto;
|
||||
}
|
||||
|
||||
.highlists-list li:hover {
|
||||
.highlights-list li:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.close-highlists-list {
|
||||
.close-highlights-list {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 0px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<span class="highlists-list"></span>
|
||||
<span class="highlights-list"></span>
|
||||
</div>`;
|
||||
|
||||
export default class HighlightsListWidget extends RightPanelWidget {
|
||||
|
@ -55,61 +55,61 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
|||
}
|
||||
|
||||
get widgetTitle() {
|
||||
return "Highlighted Text";
|
||||
return "Highlights List";
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return super.isEnabled()
|
||||
&& this.note.type === 'text'
|
||||
&& !this.noteContext.viewScope.highlightedTextTemporarilyHidden
|
||||
&& !this.noteContext.viewScope.highlightsListTemporarilyHidden
|
||||
&& this.noteContext.viewScope.viewMode === 'default';
|
||||
}
|
||||
|
||||
async doRenderBody() {
|
||||
this.$body.empty().append($(TPL));
|
||||
this.$highlightsList = this.$body.find('.highlists-list');
|
||||
this.$body.find('.highlists-list-widget').append(this.closeHltButton.render());
|
||||
this.$highlightsList = this.$body.find('.highlights-list');
|
||||
this.$body.find('.highlights-list-widget').append(this.closeHltButton.render());
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
/* The reason for adding highlightedTextPreviousVisible 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.
|
||||
/* The reason for adding highlightsListPreviousVisible is to record whether the previous state
|
||||
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,
|
||||
every time the note content is changed, highlighttext Widget will appear and then close immediately,
|
||||
because getHlt function will consume time */
|
||||
if (this.noteContext.viewScope.highlightedTextPreviousVisible) {
|
||||
if (this.noteContext.viewScope.highlightsListPreviousVisible) {
|
||||
this.toggleInt(true);
|
||||
} else {
|
||||
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.triggerCommand("reEvaluateRightPaneVisibility");
|
||||
return;
|
||||
}
|
||||
|
||||
let $highlightsList = "", hltLiCount = -1;
|
||||
let $highlightsList = "", hlLiCount = -1;
|
||||
// Check for type text unconditionally in case alwaysShowWidget is set
|
||||
if (this.note.type === 'text') {
|
||||
const {content} = await note.getNoteComplement();
|
||||
({$highlightsList, hltLiCount} = this.getHighlightList(content, optionsHlt));
|
||||
({$highlightsList, hlLiCount} = this.getHighlightList(content, optionsHighlightsList));
|
||||
}
|
||||
this.$highlightsList.empty().append($highlightsList);
|
||||
if (hltLiCount > 0) {
|
||||
if (hlLiCount > 0) {
|
||||
this.toggleInt(true);
|
||||
this.noteContext.viewScope.highlightedTextPreviousVisible = true;
|
||||
this.noteContext.viewScope.highlightsListPreviousVisible = true;
|
||||
} else {
|
||||
this.toggleInt(false);
|
||||
this.noteContext.viewScope.highlightedTextPreviousVisible = false;
|
||||
this.noteContext.viewScope.highlightsListPreviousVisible = false;
|
||||
}
|
||||
|
||||
this.triggerCommand("reEvaluateRightPaneVisibility");
|
||||
}
|
||||
|
||||
getHighlightList(content, optionsHlt) {
|
||||
getHighlightList(content, optionsHighlightsList) {
|
||||
// matches a span containing background-color
|
||||
const regex1 = /<span[^>]*style\s*=\s*[^>]*background-color:[^>]*?>[\s\S]*?<\/span>/gi;
|
||||
// matches a span containing color
|
||||
|
@ -120,27 +120,27 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
|||
const regex4 = /<strong>[\s\S]*?<\/strong>/gi;
|
||||
// match underline
|
||||
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
|
||||
let findSubStr = "", combinedRegexStr = "";
|
||||
if (optionsHlt.includes("bgColor")) {
|
||||
findSubStr += `,span[style*="background-color"]`;
|
||||
if (optionsHighlightsList.includes("bgColor")) {
|
||||
findSubStr += `,span[style*="background-color"]:not(section.include-note span[style*="background-color"])`;
|
||||
combinedRegexStr += `|${regex1.source}`;
|
||||
}
|
||||
if (optionsHlt.includes("color")) {
|
||||
findSubStr += `,span[style*="color"]`;
|
||||
if (optionsHighlightsList.includes("color")) {
|
||||
findSubStr += `,span[style*="color"]:not(section.include-note span[style*="color"])`;
|
||||
combinedRegexStr += `|${regex2.source}`;
|
||||
}
|
||||
if (optionsHlt.includes("italic")) {
|
||||
findSubStr += `,i`;
|
||||
if (optionsHighlightsList.includes("italic")) {
|
||||
findSubStr += `,i:not(section.include-note i)`;
|
||||
combinedRegexStr += `|${regex3.source}`;
|
||||
}
|
||||
if (optionsHlt.indexOf("bold")) {
|
||||
findSubStr += `,strong`;
|
||||
if (optionsHighlightsList.includes("bold")) {
|
||||
findSubStr += `,strong:not(section.include-note strong)`;
|
||||
combinedRegexStr += `|${regex4.source}`;
|
||||
}
|
||||
if (optionsHlt.includes("underline")) {
|
||||
findSubStr += `,u`;
|
||||
if (optionsHighlightsList.includes("underline")) {
|
||||
findSubStr += `,u:not(section.include-note u)`;
|
||||
combinedRegexStr += `|${regex5.source}`;
|
||||
}
|
||||
|
||||
|
@ -148,7 +148,7 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
|||
combinedRegexStr = `(` + combinedRegexStr.substring(1) + `)`;
|
||||
const combinedRegex = new RegExp(combinedRegexStr, 'gi');
|
||||
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++) {
|
||||
const subHtml = match[0];
|
||||
const startIndex = match.index;
|
||||
|
@ -158,16 +158,18 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
|||
$highlightsList.children().last().append(subHtml);
|
||||
} else {
|
||||
// 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) {
|
||||
$highlightsList.append(
|
||||
$('<li>')
|
||||
.html(subHtml)
|
||||
.on("click", () => this.jumpToHighlightedText(findSubStr, hltIndex))
|
||||
.on("click", () => this.jumpToHighlightsList(findSubStr, hltIndex))
|
||||
);
|
||||
|
||||
hltLiCount++;
|
||||
hlLiCount++;
|
||||
} else {
|
||||
// hide li if its text content is empty
|
||||
continue;
|
||||
|
@ -177,11 +179,11 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
|||
}
|
||||
return {
|
||||
$highlightsList,
|
||||
hltLiCount
|
||||
hlLiCount
|
||||
};
|
||||
}
|
||||
|
||||
async jumpToHighlightedText(findSubStr, itemIndex) {
|
||||
async jumpToHighlightsList(findSubStr, itemIndex) {
|
||||
const isReadOnly = await this.noteContext.isReadOnly();
|
||||
let targetElement;
|
||||
if (isReadOnly) {
|
||||
|
@ -224,7 +226,7 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
|||
}
|
||||
|
||||
async closeHltCommand() {
|
||||
this.noteContext.viewScope.highlightedTextTemporarilyHidden = true;
|
||||
this.noteContext.viewScope.highlightsListTemporarilyHidden = true;
|
||||
await this.refresh();
|
||||
this.triggerCommand('reEvaluateRightPaneVisibility');
|
||||
}
|
||||
|
@ -245,13 +247,13 @@ class CloseHltButton extends OnClickButtonWidget {
|
|||
super();
|
||||
|
||||
this.icon("bx-x")
|
||||
.title("Close HighlightedTextWidget")
|
||||
.title("Close HighlightsListWidget")
|
||||
.titlePlacement("bottom")
|
||||
.onClick((widget, e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
widget.triggerCommand("closeHlt");
|
||||
})
|
||||
.class("icon-action close-highlists-list");
|
||||
.class("icon-action close-highlights-list");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9584,7 +9584,7 @@ const icons = [
|
|||
"term": [
|
||||
"honor",
|
||||
"honour",
|
||||
"acheivement"
|
||||
"achievement"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -9595,7 +9595,7 @@ const icons = [
|
|||
"term": [
|
||||
"honor",
|
||||
"honour",
|
||||
"acheivement"
|
||||
"achievement"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -166,7 +166,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
|
|||
|
||||
generateColorFromString(str) {
|
||||
if (this.themeStyle === "dark") {
|
||||
str = `0${str}`; // magic lightening modifier
|
||||
str = `0${str}`; // magic lightning modifier
|
||||
}
|
||||
|
||||
let hash = 0;
|
||||
|
|
|
@ -1116,7 +1116,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||
const note = froca.getNoteFromCache(ecAttr.noteId);
|
||||
|
||||
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,
|
||||
// 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);
|
||||
|
|
|
@ -39,7 +39,7 @@ export default class AbstractSearchOption extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
// to be overriden
|
||||
// to be overridden
|
||||
doRender() {}
|
||||
|
||||
async deleteOption() {
|
||||
|
|
|
@ -2,6 +2,7 @@ import AbstractSearchOption from "./abstract_search_option.js";
|
|||
import SpacedUpdate from "../../services/spaced_update.js";
|
||||
import server from "../../services/server.js";
|
||||
import shortcutService from "../../services/shortcuts.js";
|
||||
import appContext from "../../components/app_context.js";
|
||||
|
||||
const TPL = `
|
||||
<tr>
|
||||
|
@ -56,6 +57,7 @@ export default class SearchString extends AbstractSearchOption {
|
|||
|
||||
this.spacedUpdate = new SpacedUpdate(async () => {
|
||||
const searchString = this.$searchString.val();
|
||||
appContext.lastSearchString = searchString;
|
||||
|
||||
await this.setAttribute('label', 'searchString', searchString);
|
||||
|
||||
|
@ -84,6 +86,7 @@ export default class SearchString extends AbstractSearchOption {
|
|||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
headingElement.scrollIntoView({ behavior: "smooth" });
|
||||
|
@ -206,7 +206,7 @@ export default class TocWidget extends RightPanelWidget {
|
|||
// navigate (note that the TOC rendering and other TOC
|
||||
// entries' navigation could be wrong too)
|
||||
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'
|
||||
});
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ const TPL = `
|
|||
*
|
||||
* 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.
|
||||
* - 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.
|
||||
*
|
||||
* Roadmap:
|
||||
|
|
|
@ -7,7 +7,7 @@ import MaxContentWidthOptions from "./options/appearance/max_content_width.js";
|
|||
import KeyboardShortcutsOptions from "./options/shortcuts.js";
|
||||
import HeadingStyleOptions from "./options/text_notes/heading_style.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 VimKeyBindingsOptions from "./options/code_notes/vim_key_bindings.js";
|
||||
import WrapLinesOptions from "./options/code_notes/wrap_lines.js";
|
||||
|
@ -63,7 +63,7 @@ const CONTENT_WIDGETS = {
|
|||
_optionsTextNotes: [
|
||||
HeadingStyleOptions,
|
||||
TableOfContentsOptions,
|
||||
HighlightedTextOptions,
|
||||
HighlightsListOptions,
|
||||
TextAutoReadOnlySizeOptions
|
||||
],
|
||||
_optionsCodeNotes: [
|
||||
|
|
|
@ -96,7 +96,7 @@ export default class EtapiOptions extends OptionsWidget {
|
|||
.append($("<td>").append(
|
||||
$('<span class="bx bx-pen token-table-button" title="Rename this token"></span>')
|
||||
.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))
|
||||
))
|
||||
);
|
||||
|
|
|
@ -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) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
const attributeService = require("../../services/attributes");
|
||||
const cloneService = require("../../services/cloning");
|
||||
const noteService = require('../../services/notes');
|
||||
const dateNoteService = require('../../services/date_notes');
|
||||
const dateUtils = require('../../services/date_utils');
|
||||
|
@ -13,46 +14,25 @@ const path = require('path');
|
|||
const BAttribute = require('../../becca/entities/battribute');
|
||||
const htmlSanitizer = require('../../services/html_sanitizer');
|
||||
const {formatAttrForSearch} = require("../../services/attribute_formatter");
|
||||
|
||||
function findClippingNote(clipperInboxNote, pageUrl) {
|
||||
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;
|
||||
}
|
||||
const jsdom = require("jsdom");
|
||||
const { JSDOM } = jsdom;
|
||||
|
||||
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;
|
||||
const clipType = 'clippings';
|
||||
|
||||
const clipperInbox = getClipperInboxNote();
|
||||
const dailyNote = dateNoteService.getDayNote(dateUtils.localNowDate());
|
||||
|
||||
pageUrl = htmlSanitizer.sanitizeUrl(pageUrl);
|
||||
let clippingNote = findClippingNote(clipperInbox, pageUrl);
|
||||
let clippingNote = findClippingNote(clipperInbox, pageUrl, clipType);
|
||||
|
||||
if (!clippingNote) {
|
||||
clippingNote = noteService.createNewNote({
|
||||
parentNoteId: clipperInbox.noteId,
|
||||
parentNoteId: dailyNote.noteId,
|
||||
title: title,
|
||||
content: '',
|
||||
type: 'text'
|
||||
|
@ -67,13 +47,45 @@ function addClipping(req) {
|
|||
|
||||
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 {
|
||||
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) {
|
||||
let {title, content, pageUrl, images, clipType, labels} = req.body;
|
||||
|
||||
|
@ -81,26 +93,31 @@ function createNote(req) {
|
|||
title = `Clipped note from ${pageUrl}`;
|
||||
}
|
||||
|
||||
const clipperInbox = getClipperInboxNote();
|
||||
|
||||
const {note} = noteService.createNewNote({
|
||||
parentNoteId: clipperInbox.noteId,
|
||||
title,
|
||||
content,
|
||||
type: 'text'
|
||||
});
|
||||
|
||||
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) {
|
||||
pageUrl = htmlSanitizer.sanitizeUrl(pageUrl);
|
||||
if (!note) {
|
||||
note = noteService.createNewNote({
|
||||
parentNoteId: dailyNote.noteId,
|
||||
title,
|
||||
content: '',
|
||||
type: 'text'
|
||||
}).note;
|
||||
|
||||
note.setLabel('pageUrl', pageUrl);
|
||||
note.setLabel('iconClass', 'bx bx-globe');
|
||||
note.setLabel('clipType', clipType);
|
||||
|
||||
if (pageUrl) {
|
||||
pageUrl = htmlSanitizer.sanitizeUrl(pageUrl);
|
||||
|
||||
note.setLabel('pageUrl', pageUrl);
|
||||
note.setLabel('iconClass', 'bx bx-globe');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (labels) {
|
||||
for (const labelName in labels) {
|
||||
const labelValue = htmlSanitizer.sanitize(labels[labelName]);
|
||||
|
@ -108,9 +125,9 @@ function createNote(req) {
|
|||
}
|
||||
}
|
||||
|
||||
const existingContent = note.getContent();
|
||||
const rewrittenContent = processContent(images, note, content);
|
||||
|
||||
note.setContent(rewrittenContent);
|
||||
note.setContent(`${existingContent}${existingContent.trim() ? "<br/>" : ""}${rewrittenContent}`);
|
||||
|
||||
return {
|
||||
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 (
|
||||
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;
|
||||
}
|
||||
|
@ -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 = {
|
||||
createNote,
|
||||
addClipping,
|
||||
openNote,
|
||||
handshake
|
||||
handshake,
|
||||
findNotesByUrl
|
||||
};
|
||||
|
|
|
@ -49,7 +49,7 @@ const ALLOWED_OPTIONS = new Set([
|
|||
'compressImages',
|
||||
'downloadImagesAutomatically',
|
||||
'minTocHeadings',
|
||||
'highlightedText',
|
||||
'highlightsList',
|
||||
'checkForUpdates',
|
||||
'disableTray',
|
||||
'eraseUnusedAttachmentsAfterSeconds',
|
||||
|
|
|
@ -11,7 +11,7 @@ function addRecentNote(req) {
|
|||
}).save();
|
||||
|
||||
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));
|
||||
|
||||
sql.execute(`DELETE FROM recent_notes WHERE utcDateCreated < ?`, [cutOffDate]);
|
||||
|
|
|
@ -28,6 +28,12 @@ function execute(req) {
|
|||
for (let query of queries) {
|
||||
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) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ function checkSync() {
|
|||
function syncNow() {
|
||||
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();
|
||||
|
||||
return syncService.sync();
|
||||
|
|
|
@ -269,6 +269,7 @@ function register(app) {
|
|||
route(PST, '/api/clipper/clippings', clipperMiddleware, clipperRoute.addClipping, apiResultHandler);
|
||||
route(PST, '/api/clipper/notes', clipperMiddleware, clipperRoute.createNote, 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/days/:date', specialNotesRoute.getDayNote);
|
||||
|
|
|
@ -395,7 +395,7 @@ class ConsistencyChecks {
|
|||
({noteId, isProtected, type, mime}) => {
|
||||
if (this.autoFix) {
|
||||
// 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.
|
||||
const fakeDate = "2000-01-01 00:00:00Z";
|
||||
|
||||
|
|
|
@ -57,5 +57,7 @@ function sanitize(dirtyHtml) {
|
|||
|
||||
module.exports = {
|
||||
sanitize,
|
||||
sanitizeUrl
|
||||
sanitizeUrl: url => {
|
||||
return sanitizeUrl(url).trim();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -83,7 +83,7 @@ const defaultOptions = [
|
|||
{ name: 'compressImages', value: 'true', isSynced: true },
|
||||
{ name: 'downloadImagesAutomatically', value: 'true', 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: 'disableTray', value: 'false', isSynced: false },
|
||||
{ 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.
|
||||
*
|
||||
* 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 {string[]} tokens
|
||||
* @param {string[]} path
|
||||
* @param {string[]} remainingTokens - tokens still needed to be found in the path towards root
|
||||
* @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) => {
|
||||
if (tokens.length === 0) {
|
||||
const retPath = this.getNotePath(note, path);
|
||||
const searchPathTowardsRoot = (note, remainingTokens, takenPath) => {
|
||||
if (remainingTokens.length === 0) {
|
||||
// we're done, just build the result
|
||||
const resultPath = this.getNotePath(note, takenPath);
|
||||
|
||||
if (retPath) {
|
||||
const noteId = retPath[retPath.length - 1];
|
||||
if (resultPath) {
|
||||
const noteId = resultPath[resultPath.length - 1];
|
||||
|
||||
if (!resultNoteSet.hasNoteId(noteId)) {
|
||||
// we could get here from multiple paths, the first one wins because the paths
|
||||
// are sorted by importance
|
||||
executionContext.noteIdToNotePath[noteId] = retPath;
|
||||
executionContext.noteIdToNotePath[noteId] = resultPath;
|
||||
|
||||
resultNoteSet.add(becca.notes[noteId]);
|
||||
}
|
||||
|
@ -42,22 +44,23 @@ class NoteFlatTextExp extends Expression {
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const foundAttrTokens = [];
|
||||
|
||||
for (const token of tokens) {
|
||||
for (const token of remainingTokens) {
|
||||
if (note.type.includes(token) || note.mime.includes(token)) {
|
||||
foundAttrTokens.push(token);
|
||||
}
|
||||
}
|
||||
|
||||
for (const attribute of note.ownedAttributes) {
|
||||
for (const attribute of note.getOwnedAttributes()) {
|
||||
const normalizedName = utils.normalize(attribute.name);
|
||||
const normalizedValue = utils.normalize(attribute.value);
|
||||
|
||||
for (const token of tokens) {
|
||||
for (const token of remainingTokens) {
|
||||
if (normalizedName.includes(token) || normalizedValue.includes(token)) {
|
||||
foundAttrTokens.push(token);
|
||||
}
|
||||
|
@ -68,19 +71,19 @@ class NoteFlatTextExp extends Expression {
|
|||
const title = utils.normalize(beccaService.getNoteTitle(note.noteId, parentNote.noteId));
|
||||
const foundTokens = foundAttrTokens.slice();
|
||||
|
||||
for (const token of tokens) {
|
||||
for (const token of remainingTokens) {
|
||||
if (title.includes(token)) {
|
||||
foundTokens.push(token);
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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) {
|
||||
// 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]) {
|
||||
searchDownThePath(note, [], []);
|
||||
searchPathTowardsRoot(note, [], [note.noteId]);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -123,7 +126,7 @@ class NoteFlatTextExp extends Expression {
|
|||
if (foundTokens.length > 0) {
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
} else {
|
||||
const closestNoteId = path[0];
|
||||
const closestNoteBestNotePath = becca.getNote(closestNoteId).getBestNotePath();
|
||||
// this note is the closest to root containing the last matching token(s), thus completing the requirements
|
||||
// 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 utils = require('../../utils');
|
||||
const log = require('../../log');
|
||||
const scriptService = require("../../script");
|
||||
const hoistedNoteService = require("../../hoisted_note");
|
||||
|
||||
function searchFromNote(note) {
|
||||
|
@ -73,6 +72,7 @@ function searchFromRelation(note, relationName) {
|
|||
return [];
|
||||
}
|
||||
|
||||
const scriptService = require("../../script"); // to avoid circular dependency
|
||||
const result = scriptService.executeNote(scriptNote, {originEntity: note});
|
||||
|
||||
if (!Array.isArray(result)) {
|
||||
|
|
|
@ -13,7 +13,7 @@ class TaskContext {
|
|||
this.noteDeletionHandlerTriggered = false;
|
||||
|
||||
// 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
|
||||
|
||||
// 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>Click on Sync tab.</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>
|
||||
</ol>
|
||||
|
||||
|
|
Loading…
Reference in a new issue