mirror of
https://github.com/zadam/trilium.git
synced 2025-10-30 18:05:55 +01:00
Compare commits
18 Commits
v0.7.0-bet
...
v0.8.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2acff07368 | ||
|
|
bea1d24f07 | ||
|
|
adc270c59f | ||
|
|
66064f7a94 | ||
|
|
1501fa8dbf | ||
|
|
60bba46d80 | ||
|
|
12c06ae97e | ||
|
|
f0bea9cf71 | ||
|
|
a555b6319c | ||
|
|
5dd93e4cdc | ||
|
|
3b4509d833 | ||
|
|
19308bbfbd | ||
|
|
4acc5432c3 | ||
|
|
08b8141fdf | ||
|
|
e1200aa308 | ||
|
|
89666eb078 | ||
|
|
d5605aa64d | ||
|
|
2582b016f9 |
@@ -1,3 +1,7 @@
|
|||||||
|
[General]
|
||||||
|
# Instance name can be used to distinguish between different instances
|
||||||
|
instanceName=
|
||||||
|
|
||||||
[Network]
|
[Network]
|
||||||
port=8080
|
port=8080
|
||||||
# true for TLS/SSL/HTTPS (secure), false for HTTP (unsecure).
|
# true for TLS/SSL/HTTPS (secure), false for HTTP (unsecure).
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "trilium",
|
"name": "trilium",
|
||||||
"version": "0.6.2",
|
"version": "0.7.0-beta",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "trilium",
|
"name": "trilium",
|
||||||
"description": "Trilium Notes",
|
"description": "Trilium Notes",
|
||||||
"version": "0.7.0-beta",
|
"version": "0.8.0-beta",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"main": "electron.js",
|
"main": "electron.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -57,6 +57,7 @@
|
|||||||
"session-file-store": "^1.1.2",
|
"session-file-store": "^1.1.2",
|
||||||
"simple-node-logger": "^0.93.30",
|
"simple-node-logger": "^0.93.30",
|
||||||
"sqlite": "^2.9.0",
|
"sqlite": "^2.9.0",
|
||||||
|
"tar-stream": "^1.5.5",
|
||||||
"unescape": "^1.0.1",
|
"unescape": "^1.0.1",
|
||||||
"ws": "^3.3.2"
|
"ws": "^3.3.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -73,8 +73,6 @@ require('./services/backup');
|
|||||||
// trigger consistency checks timer
|
// trigger consistency checks timer
|
||||||
require('./services/consistency_checks');
|
require('./services/consistency_checks');
|
||||||
|
|
||||||
require('./plugins/reddit');
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
app,
|
app,
|
||||||
sessionParser
|
sessionParser
|
||||||
|
|||||||
@@ -43,6 +43,48 @@ class Note extends Entity {
|
|||||||
return this.repository.getEntities("SELECT * FROM note_tree WHERE isDeleted = 0 AND noteId = ?", [this.noteId]);
|
return this.repository.getEntities("SELECT * FROM note_tree WHERE isDeleted = 0 AND noteId = ?", [this.noteId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getChild(name) {
|
||||||
|
return this.repository.getEntity(`
|
||||||
|
SELECT notes.*
|
||||||
|
FROM note_tree
|
||||||
|
JOIN notes USING(noteId)
|
||||||
|
WHERE notes.isDeleted = 0
|
||||||
|
AND note_tree.isDeleted = 0
|
||||||
|
AND note_tree.parentNoteId = ?
|
||||||
|
AND notes.title = ?`, [this.noteId, name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getChildren() {
|
||||||
|
return this.repository.getEntities(`
|
||||||
|
SELECT notes.*
|
||||||
|
FROM note_tree
|
||||||
|
JOIN notes USING(noteId)
|
||||||
|
WHERE notes.isDeleted = 0
|
||||||
|
AND note_tree.isDeleted = 0
|
||||||
|
AND note_tree.parentNoteId = ?`, [this.noteId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getParents() {
|
||||||
|
return this.repository.getEntities(`
|
||||||
|
SELECT parent_notes.*
|
||||||
|
FROM
|
||||||
|
note_tree AS child_tree
|
||||||
|
JOIN notes AS parent_notes ON parent_notes.noteId = child_tree.parentNoteId
|
||||||
|
WHERE child_tree.noteId = ?
|
||||||
|
AND child_tree.isDeleted = 0
|
||||||
|
AND parent_notes.isDeleted = 0`, [this.noteId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getNoteTree() {
|
||||||
|
return this.repository.getEntities(`
|
||||||
|
SELECT note_tree.*
|
||||||
|
FROM note_tree
|
||||||
|
JOIN notes USING(noteId)
|
||||||
|
WHERE notes.isDeleted = 0
|
||||||
|
AND note_tree.isDeleted = 0
|
||||||
|
AND note_tree.noteId = ?`, [this.noteId]);
|
||||||
|
}
|
||||||
|
|
||||||
beforeSaving() {
|
beforeSaving() {
|
||||||
this.content = JSON.stringify(this.jsonContent, null, '\t');
|
this.content = JSON.stringify(this.jsonContent, null, '\t');
|
||||||
|
|
||||||
|
|||||||
@@ -1,144 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
const sql = require('../services/sql');
|
|
||||||
const notes = require('../services/notes');
|
|
||||||
const axios = require('axios');
|
|
||||||
const log = require('../services/log');
|
|
||||||
const utils = require('../services/utils');
|
|
||||||
const unescape = require('unescape');
|
|
||||||
const attributes = require('../services/attributes');
|
|
||||||
const sync_mutex = require('../services/sync_mutex');
|
|
||||||
const config = require('../services/config');
|
|
||||||
const date_notes = require('../services/date_notes');
|
|
||||||
|
|
||||||
// "reddit" date note is subnote of date note which contains all reddit comments from that date
|
|
||||||
const REDDIT_DATE_ATTRIBUTE = 'reddit_date_note';
|
|
||||||
|
|
||||||
async function createNote(parentNoteId, noteTitle, noteText) {
|
|
||||||
return (await notes.createNewNote(parentNoteId, {
|
|
||||||
title: noteTitle,
|
|
||||||
content: noteText,
|
|
||||||
target: 'into',
|
|
||||||
isProtected: false
|
|
||||||
})).noteId;
|
|
||||||
}
|
|
||||||
|
|
||||||
function redditId(kind, id) {
|
|
||||||
return kind + "_" + id;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getDateNoteIdForReddit(dateTimeStr, rootNoteId) {
|
|
||||||
const dateStr = dateTimeStr.substr(0, 10);
|
|
||||||
|
|
||||||
let redditDateNoteId = await attributes.getNoteIdWithAttribute(REDDIT_DATE_ATTRIBUTE, dateStr);
|
|
||||||
|
|
||||||
if (!redditDateNoteId) {
|
|
||||||
const dateNoteId = await date_notes.getDateNoteId(dateTimeStr, rootNoteId);
|
|
||||||
|
|
||||||
redditDateNoteId = await createNote(dateNoteId, "Reddit");
|
|
||||||
|
|
||||||
await attributes.createAttribute(redditDateNoteId, REDDIT_DATE_ATTRIBUTE, dateStr);
|
|
||||||
await attributes.createAttribute(redditDateNoteId, "hide_in_autocomplete");
|
|
||||||
}
|
|
||||||
|
|
||||||
return redditDateNoteId;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function importComments(rootNoteId, accountName, afterId = null) {
|
|
||||||
let url = `https://www.reddit.com/user/${accountName}.json`;
|
|
||||||
|
|
||||||
if (afterId) {
|
|
||||||
url += "?after=" + afterId;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await axios.get(url);
|
|
||||||
const listing = response.data;
|
|
||||||
|
|
||||||
if (listing.kind !== 'Listing') {
|
|
||||||
log.info(`Reddit: Unknown object kind ${listing.kind}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const children = listing.data.children;
|
|
||||||
|
|
||||||
let importedComments = 0;
|
|
||||||
|
|
||||||
for (const child of children) {
|
|
||||||
const comment = child.data;
|
|
||||||
|
|
||||||
let commentNoteId = await attributes.getNoteIdWithAttribute('reddit_id', redditId(child.kind, comment.id));
|
|
||||||
|
|
||||||
if (commentNoteId) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dateTimeStr = utils.dateStr(new Date(comment.created_utc * 1000));
|
|
||||||
|
|
||||||
const permaLink = 'https://reddit.com' + comment.permalink;
|
|
||||||
|
|
||||||
const noteText =
|
|
||||||
`<p><a href="${permaLink}">${permaLink}</a></p>
|
|
||||||
<p>author: <a href="https://reddit.com/u/${comment.author}">${comment.author}</a>,
|
|
||||||
subreddit: <a href="https://reddit.com/r/${comment.subreddit}">${comment.subreddit}</a>,
|
|
||||||
karma: ${comment.score}, created at ${dateTimeStr}</p><p></p>`
|
|
||||||
+ unescape(comment.body_html);
|
|
||||||
|
|
||||||
let parentNoteId = await getDateNoteIdForReddit(dateTimeStr, rootNoteId);
|
|
||||||
|
|
||||||
await sql.doInTransaction(async () => {
|
|
||||||
commentNoteId = await createNote(parentNoteId, comment.link_title, noteText);
|
|
||||||
|
|
||||||
log.info("Reddit: Imported comment to note " + commentNoteId);
|
|
||||||
importedComments++;
|
|
||||||
|
|
||||||
await attributes.createAttribute(commentNoteId, "reddit_kind", child.kind);
|
|
||||||
await attributes.createAttribute(commentNoteId, "reddit_id", redditId(child.kind, comment.id));
|
|
||||||
await attributes.createAttribute(commentNoteId, "reddit_created_utc", comment.created_utc);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there have been no imported comments on this page, there shouldn't be any to import
|
|
||||||
// on the next page since those are older
|
|
||||||
if (listing.data.after && importedComments > 0) {
|
|
||||||
importedComments += await importComments(rootNoteId, accountName, listing.data.after);
|
|
||||||
}
|
|
||||||
|
|
||||||
return importedComments;
|
|
||||||
}
|
|
||||||
|
|
||||||
let redditAccounts = [];
|
|
||||||
|
|
||||||
async function runImport() {
|
|
||||||
const rootNoteId = await date_notes.getRootNoteId();
|
|
||||||
|
|
||||||
// technically mutex shouldn't be necessary but we want to avoid doing potentially expensive import
|
|
||||||
// concurrently with sync
|
|
||||||
await sync_mutex.doExclusively(async () => {
|
|
||||||
let importedComments = 0;
|
|
||||||
|
|
||||||
for (const account of redditAccounts) {
|
|
||||||
importedComments += await importComments(rootNoteId, account);
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info(`Reddit: Imported ${importedComments} comments.`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
sql.dbReady.then(async () => {
|
|
||||||
if (!config['Reddit'] || config['Reddit']['enabled'] !== true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const redditAccountsStr = config['Reddit']['accounts'];
|
|
||||||
|
|
||||||
if (!redditAccountsStr) {
|
|
||||||
log.info("Reddit: No reddit accounts defined in option 'reddit_accounts'");
|
|
||||||
}
|
|
||||||
|
|
||||||
redditAccounts = redditAccountsStr.split(",").map(s => s.trim());
|
|
||||||
|
|
||||||
const pollingIntervalInSeconds = config['Reddit']['pollingIntervalInSeconds'] || (4 * 3600);
|
|
||||||
|
|
||||||
setInterval(runImport, pollingIntervalInSeconds * 1000);
|
|
||||||
setTimeout(runImport, 10000); // 10 seconds after startup - intentionally after initial sync
|
|
||||||
});
|
|
||||||
BIN
src/public/images/icons/paperclip.png
Normal file
BIN
src/public/images/icons/paperclip.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 358 B |
@@ -13,9 +13,9 @@ const api = (function() {
|
|||||||
$pluginButtons.append(button);
|
$pluginButtons.append(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
addButtonToToolbar,
|
addButtonToToolbar,
|
||||||
activateNote
|
activateNote,
|
||||||
|
getInstanceName: noteTree.getInstanceName
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
@@ -85,9 +85,12 @@ const contextMenu = (function() {
|
|||||||
{title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "ui-icon-clipboard"},
|
{title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "ui-icon-clipboard"},
|
||||||
{title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"},
|
{title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"},
|
||||||
{title: "----"},
|
{title: "----"},
|
||||||
{title: "Collapse sub-tree <kbd>Alt+-</kbd>", cmd: "collapse-sub-tree", uiIcon: "ui-icon-minus"},
|
{title: "Export sub-tree", cmd: "exportSubTree", uiIcon: " ui-icon-arrowthick-1-ne"},
|
||||||
{title: "Force note sync", cmd: "force-note-sync", uiIcon: "ui-icon-refresh"},
|
{title: "Import sub-tree into", cmd: "importSubTree", uiIcon: "ui-icon-arrowthick-1-sw"},
|
||||||
{title: "Sort alphabetically <kbd>Alt+S</kbd>", cmd: "sort-alphabetically", uiIcon: " ui-icon-arrowthick-2-n-s"}
|
{title: "----"},
|
||||||
|
{title: "Collapse sub-tree <kbd>Alt+-</kbd>", cmd: "collapseSubTree", uiIcon: "ui-icon-minus"},
|
||||||
|
{title: "Force note sync", cmd: "forceNoteSync", uiIcon: "ui-icon-refresh"},
|
||||||
|
{title: "Sort alphabetically <kbd>Alt+S</kbd>", cmd: "sortAlphabetically", uiIcon: " ui-icon-arrowthick-2-n-s"}
|
||||||
|
|
||||||
],
|
],
|
||||||
beforeOpen: (event, ui) => {
|
beforeOpen: (event, ui) => {
|
||||||
@@ -139,13 +142,19 @@ const contextMenu = (function() {
|
|||||||
else if (ui.cmd === "delete") {
|
else if (ui.cmd === "delete") {
|
||||||
treeChanges.deleteNodes(noteTree.getSelectedNodes(true));
|
treeChanges.deleteNodes(noteTree.getSelectedNodes(true));
|
||||||
}
|
}
|
||||||
else if (ui.cmd === "collapse-sub-tree") {
|
else if (ui.cmd === "exportSubTree") {
|
||||||
|
exportSubTree(node.data.noteId);
|
||||||
|
}
|
||||||
|
else if (ui.cmd === "importSubTree") {
|
||||||
|
importSubTree(node.data.noteId);
|
||||||
|
}
|
||||||
|
else if (ui.cmd === "collapseSubTree") {
|
||||||
noteTree.collapseTree(node);
|
noteTree.collapseTree(node);
|
||||||
}
|
}
|
||||||
else if (ui.cmd === "force-note-sync") {
|
else if (ui.cmd === "forceNoteSync") {
|
||||||
forceNoteSync(node.data.noteId);
|
forceNoteSync(node.data.noteId);
|
||||||
}
|
}
|
||||||
else if (ui.cmd === "sort-alphabetically") {
|
else if (ui.cmd === "sortAlphabetically") {
|
||||||
noteTree.sortAlphabetically(node.data.noteId);
|
noteTree.sortAlphabetically(node.data.noteId);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
31
src/public/javascripts/export.js
Normal file
31
src/public/javascripts/export.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
function exportSubTree(noteId) {
|
||||||
|
const url = getHost() + "/api/export/" + noteId;
|
||||||
|
|
||||||
|
download(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
let importNoteId;
|
||||||
|
|
||||||
|
function importSubTree(noteId) {
|
||||||
|
importNoteId = noteId;
|
||||||
|
|
||||||
|
$("#import-upload").trigger('click');
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#import-upload").change(async function() {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('upload', this.files[0]);
|
||||||
|
|
||||||
|
await $.ajax({
|
||||||
|
url: baseApiUrl + 'import/' + importNoteId,
|
||||||
|
headers: server.getHeaders(),
|
||||||
|
data: formData,
|
||||||
|
type: 'POST',
|
||||||
|
contentType: false, // NEEDED, DON'T OMIT THIS
|
||||||
|
processData: false, // NEEDED, DON'T OMIT THIS
|
||||||
|
});
|
||||||
|
|
||||||
|
await noteTree.reload();
|
||||||
|
});
|
||||||
@@ -114,22 +114,32 @@ $.ui.autocomplete.filter = (array, terms) => {
|
|||||||
const tokens = terms.toLowerCase().split(" ");
|
const tokens = terms.toLowerCase().split(" ");
|
||||||
|
|
||||||
for (const item of array) {
|
for (const item of array) {
|
||||||
let found = true;
|
|
||||||
const lcLabel = item.label.toLowerCase();
|
const lcLabel = item.label.toLowerCase();
|
||||||
|
|
||||||
for (const token of tokens) {
|
const found = tokens.every(token => lcLabel.indexOf(token) !== -1);
|
||||||
if (lcLabel.indexOf(token) === -1) {
|
if (!found) {
|
||||||
found = false;
|
continue;
|
||||||
break;
|
}
|
||||||
|
|
||||||
|
// this is not completely correct and might cause minor problems with note with names containing this " / "
|
||||||
|
const lastSegmentIndex = lcLabel.lastIndexOf(" / ");
|
||||||
|
|
||||||
|
if (lastSegmentIndex !== -1) {
|
||||||
|
const lastSegment = lcLabel.substr(lastSegmentIndex + 3);
|
||||||
|
|
||||||
|
// at least some token needs to be in the last segment (leaf note), otherwise this
|
||||||
|
// particular note is not that interesting (query is satisfied by parent note)
|
||||||
|
const foundInLastSegment = tokens.some(token => lastSegment.indexOf(token) !== -1);
|
||||||
|
|
||||||
|
if (!foundInLastSegment) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (found) {
|
results.push(item);
|
||||||
results.push(item);
|
|
||||||
|
|
||||||
if (results.length > 100) {
|
if (results.length > 100) {
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,10 +226,10 @@ if (isElectron()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function uploadAttachment() {
|
function uploadAttachment() {
|
||||||
$("#file-upload").trigger('click');
|
$("#attachment-upload").trigger('click');
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#file-upload").change(async function() {
|
$("#attachment-upload").change(async function() {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('upload', this.files[0]);
|
formData.append('upload', this.files[0]);
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ const messaging = (function() {
|
|||||||
let connectionBrokenNotification = null;
|
let connectionBrokenNotification = null;
|
||||||
|
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
if (new Date().getTime() - lastPingTs > 5000) {
|
if (new Date().getTime() - lastPingTs > 30000) {
|
||||||
if (!connectionBrokenNotification) {
|
if (!connectionBrokenNotification) {
|
||||||
connectionBrokenNotification = $.notify({
|
connectionBrokenNotification = $.notify({
|
||||||
// options
|
// options
|
||||||
|
|||||||
@@ -154,7 +154,10 @@ const noteEditor = (function() {
|
|||||||
indentUnit: 4,
|
indentUnit: 4,
|
||||||
matchBrackets: true,
|
matchBrackets: true,
|
||||||
matchTags: { bothTags: true },
|
matchTags: { bothTags: true },
|
||||||
highlightSelectionMatches: { showToken: /\w/, annotateScrollbar: false }
|
highlightSelectionMatches: { showToken: /\w/, annotateScrollbar: false },
|
||||||
|
lint: true,
|
||||||
|
gutters: ["CodeMirror-lint-markers"],
|
||||||
|
lineNumbers: true
|
||||||
});
|
});
|
||||||
|
|
||||||
codeEditor.on('change', noteChanged);
|
codeEditor.on('change', noteChanged);
|
||||||
@@ -171,6 +174,8 @@ const noteEditor = (function() {
|
|||||||
codeEditor.setOption("mode", info.mime);
|
codeEditor.setOption("mode", info.mime);
|
||||||
CodeMirror.autoLoadMode(codeEditor, info.mode);
|
CodeMirror.autoLoadMode(codeEditor, info.mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
codeEditor.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,16 +304,7 @@ const noteEditor = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$attachmentDownload.click(() => {
|
$attachmentDownload.click(() => download(getAttachmentUrl()));
|
||||||
if (isElectron()) {
|
|
||||||
const remote = require('electron').remote;
|
|
||||||
|
|
||||||
remote.getCurrentWebContents().downloadURL(getAttachmentUrl());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
window.location.href = getAttachmentUrl();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$attachmentOpen.click(() => {
|
$attachmentOpen.click(() => {
|
||||||
if (isElectron()) {
|
if (isElectron()) {
|
||||||
@@ -323,12 +319,8 @@ const noteEditor = (function() {
|
|||||||
|
|
||||||
function getAttachmentUrl() {
|
function getAttachmentUrl() {
|
||||||
// electron needs absolute URL so we extract current host, port, protocol
|
// electron needs absolute URL so we extract current host, port, protocol
|
||||||
const url = new URL(window.location.href);
|
return getHost() + "/api/attachments/download/" + getCurrentNoteId()
|
||||||
const host = url.protocol + "//" + url.hostname + ":" + url.port;
|
+ "?protectedSessionId=" + encodeURIComponent(protected_session.getProtectedSessionId());
|
||||||
|
|
||||||
const downloadUrl = "/api/attachments/download/" + getCurrentNoteId();
|
|
||||||
|
|
||||||
return host + downloadUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(() => {
|
$(document).ready(() => {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ const noteTree = (function() {
|
|||||||
const $parentList = $("#parent-list");
|
const $parentList = $("#parent-list");
|
||||||
const $parentListList = $("#parent-list-inner");
|
const $parentListList = $("#parent-list-inner");
|
||||||
|
|
||||||
|
let instanceName = null; // should have better place
|
||||||
|
|
||||||
let startNotePath = null;
|
let startNotePath = null;
|
||||||
let notesTreeMap = {};
|
let notesTreeMap = {};
|
||||||
|
|
||||||
@@ -155,6 +157,9 @@ const noteTree = (function() {
|
|||||||
if (note.type === 'code') {
|
if (note.type === 'code') {
|
||||||
extraClasses.push("code");
|
extraClasses.push("code");
|
||||||
}
|
}
|
||||||
|
else if (note.type === 'file') {
|
||||||
|
extraClasses.push('attachment');
|
||||||
|
}
|
||||||
|
|
||||||
return extraClasses.join(" ");
|
return extraClasses.join(" ");
|
||||||
}
|
}
|
||||||
@@ -645,6 +650,7 @@ const noteTree = (function() {
|
|||||||
async function loadTree() {
|
async function loadTree() {
|
||||||
const resp = await server.get('tree');
|
const resp = await server.get('tree');
|
||||||
startNotePath = resp.start_note_path;
|
startNotePath = resp.start_note_path;
|
||||||
|
instanceName = resp.instanceName;
|
||||||
|
|
||||||
if (document.location.hash) {
|
if (document.location.hash) {
|
||||||
startNotePath = getNotePathFromAddress();
|
startNotePath = getNotePathFromAddress();
|
||||||
@@ -710,6 +716,9 @@ const noteTree = (function() {
|
|||||||
titlePath = '';
|
titlePath = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/zadam/trilium/issues/46
|
||||||
|
// unfortunately not easy to implement because we don't have an easy access to note's isProtected property
|
||||||
|
|
||||||
const autocompleteItems = [];
|
const autocompleteItems = [];
|
||||||
|
|
||||||
for (const childNoteId of parentToChildren[parentNoteId]) {
|
for (const childNoteId of parentToChildren[parentNoteId]) {
|
||||||
@@ -820,6 +829,10 @@ const noteTree = (function() {
|
|||||||
return !!childToParents[noteId];
|
return !!childToParents[noteId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getInstanceName() {
|
||||||
|
return instanceName;
|
||||||
|
}
|
||||||
|
|
||||||
$(document).bind('keydown', 'ctrl+o', e => {
|
$(document).bind('keydown', 'ctrl+o', e => {
|
||||||
const node = getCurrentNode();
|
const node = getCurrentNode();
|
||||||
const parentNoteId = node.data.parentNoteId;
|
const parentNoteId = node.data.parentNoteId;
|
||||||
@@ -894,6 +907,7 @@ const noteTree = (function() {
|
|||||||
setParentChildRelation,
|
setParentChildRelation,
|
||||||
getSelectedNodes,
|
getSelectedNodes,
|
||||||
sortAlphabetically,
|
sortAlphabetically,
|
||||||
noteExists
|
noteExists,
|
||||||
|
getInstanceName
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
@@ -25,7 +25,9 @@ const protected_session = (function() {
|
|||||||
if (requireProtectedSession && !isProtectedSessionAvailable()) {
|
if (requireProtectedSession && !isProtectedSessionAvailable()) {
|
||||||
protectedSessionDeferred = dfd;
|
protectedSessionDeferred = dfd;
|
||||||
|
|
||||||
$noteDetailWrapper.hide();
|
if (noteTree.getCurrentNode().data.isProtected) {
|
||||||
|
$noteDetailWrapper.hide();
|
||||||
|
}
|
||||||
|
|
||||||
$dialog.dialog({
|
$dialog.dialog({
|
||||||
modal: modal,
|
modal: modal,
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ const server = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function prepareParams(params) {
|
function prepareParams(params) {
|
||||||
|
if (!params) {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
return params.map(p => {
|
return params.map(p => {
|
||||||
if (typeof p === "function") {
|
if (typeof p === "function") {
|
||||||
return "!@#Function: " + p.toString();
|
return "!@#Function: " + p.toString();
|
||||||
@@ -52,6 +56,13 @@ const server = (function() {
|
|||||||
return ret.executionResult;
|
return ret.executionResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function setJob(opts) {
|
||||||
|
opts.job = opts.job.toString();
|
||||||
|
opts.params = prepareParams(opts.params);
|
||||||
|
|
||||||
|
await post('script/job', opts);
|
||||||
|
}
|
||||||
|
|
||||||
let i = 1;
|
let i = 1;
|
||||||
const reqResolves = {};
|
const reqResolves = {};
|
||||||
|
|
||||||
@@ -116,6 +127,7 @@ const server = (function() {
|
|||||||
put,
|
put,
|
||||||
remove,
|
remove,
|
||||||
exec,
|
exec,
|
||||||
|
setJob,
|
||||||
ajax,
|
ajax,
|
||||||
// don't remove, used from CKEditor image upload!
|
// don't remove, used from CKEditor image upload!
|
||||||
getHeaders
|
getHeaders
|
||||||
|
|||||||
@@ -143,13 +143,18 @@ const CODE_MIRROR = {
|
|||||||
"libraries/codemirror/addon/edit/matchbrackets.js",
|
"libraries/codemirror/addon/edit/matchbrackets.js",
|
||||||
"libraries/codemirror/addon/edit/matchtags.js",
|
"libraries/codemirror/addon/edit/matchtags.js",
|
||||||
"libraries/codemirror/addon/search/match-highlighter.js",
|
"libraries/codemirror/addon/search/match-highlighter.js",
|
||||||
"libraries/codemirror/mode/meta.js"
|
"libraries/codemirror/mode/meta.js",
|
||||||
|
"libraries/codemirror/addon/lint/lint.js",
|
||||||
|
"libraries/codemirror/addon/lint/eslint.js"
|
||||||
],
|
],
|
||||||
css: [
|
css: [
|
||||||
"libraries/codemirror/codemirror.css"
|
"libraries/codemirror/codemirror.css",
|
||||||
|
"libraries/codemirror/addon/lint/lint.css"
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ESLINT = { js: [ "libraries/eslint.js" ] };
|
||||||
|
|
||||||
async function requireLibrary(library) {
|
async function requireLibrary(library) {
|
||||||
if (library.css) {
|
if (library.css) {
|
||||||
library.css.map(cssUrl => requireCss(cssUrl));
|
library.css.map(cssUrl => requireCss(cssUrl));
|
||||||
@@ -162,13 +167,13 @@ async function requireLibrary(library) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function requireScript(url) {
|
const dynamicallyLoadedScripts = [];
|
||||||
const scripts = Array
|
|
||||||
.from(document.querySelectorAll('script'))
|
|
||||||
.map(scr => scr.src);
|
|
||||||
|
|
||||||
if (!scripts.includes(url)) {
|
async function requireScript(url) {
|
||||||
return $.ajax({
|
if (!dynamicallyLoadedScripts.includes(url)) {
|
||||||
|
dynamicallyLoadedScripts.push(url);
|
||||||
|
|
||||||
|
return await $.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
dataType: "script",
|
dataType: "script",
|
||||||
cache: true
|
cache: true
|
||||||
@@ -184,4 +189,20 @@ async function requireCss(url) {
|
|||||||
if (!css.includes(url)) {
|
if (!css.includes(url)) {
|
||||||
$('head').append($('<link rel="stylesheet" type="text/css" />').attr('href', url));
|
$('head').append($('<link rel="stylesheet" type="text/css" />').attr('href', url));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHost() {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
return url.protocol + "//" + url.hostname + ":" + url.port;
|
||||||
|
}
|
||||||
|
|
||||||
|
function download(url) {
|
||||||
|
if (isElectron()) {
|
||||||
|
const remote = require('electron').remote;
|
||||||
|
|
||||||
|
remote.getCurrentWebContents().downloadURL(url);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
window.location.href = url;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -102,18 +102,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentlyHighlighted = null;
|
|
||||||
function doMatchBrackets(cm) {
|
function doMatchBrackets(cm) {
|
||||||
cm.operation(function() {
|
cm.operation(function() {
|
||||||
if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
|
if (cm.state.matchBrackets.currentlyHighlighted) {
|
||||||
currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets);
|
cm.state.matchBrackets.currentlyHighlighted();
|
||||||
|
cm.state.matchBrackets.currentlyHighlighted = null;
|
||||||
|
}
|
||||||
|
cm.state.matchBrackets.currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) {
|
CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) {
|
||||||
if (old && old != CodeMirror.Init) {
|
if (old && old != CodeMirror.Init) {
|
||||||
cm.off("cursorActivity", doMatchBrackets);
|
cm.off("cursorActivity", doMatchBrackets);
|
||||||
if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
|
if (cm.state.matchBrackets && cm.state.matchBrackets.currentlyHighlighted) {
|
||||||
|
cm.state.matchBrackets.currentlyHighlighted();
|
||||||
|
cm.state.matchBrackets.currentlyHighlighted = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (val) {
|
if (val) {
|
||||||
cm.state.matchBrackets = typeof val == "object" ? val : {};
|
cm.state.matchBrackets = typeof val == "object" ? val : {};
|
||||||
|
|||||||
@@ -138,7 +138,7 @@
|
|||||||
var iter = new Iter(cm, start.line, 0);
|
var iter = new Iter(cm, start.line, 0);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
var openTag = toNextTag(iter), end;
|
var openTag = toNextTag(iter), end;
|
||||||
if (!openTag || iter.line != start.line || !(end = toTagEnd(iter))) return;
|
if (!openTag || !(end = toTagEnd(iter)) || iter.line != start.line) return;
|
||||||
if (!openTag[1] && end != "selfClose") {
|
if (!openTag[1] && end != "selfClose") {
|
||||||
var startPos = Pos(iter.line, iter.ch);
|
var startPos = Pos(iter.line, iter.ch);
|
||||||
var endPos = findMatchingClose(iter, openTag[2]);
|
var endPos = findMatchingClose(iter, openTag[2]);
|
||||||
|
|||||||
87
src/public/libraries/codemirror/addon/lint/eslint.js
vendored
Normal file
87
src/public/libraries/codemirror/addon/lint/eslint.js
vendored
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
async function validatorHtml(text, options) {
|
||||||
|
const result = /<script[^>]*>([\s\S]+)<\/script>/ig.exec(text);
|
||||||
|
|
||||||
|
if (result !== null) {
|
||||||
|
// preceding code is copied over but any (non-newline) character is replaced with space
|
||||||
|
// this will preserve line numbers etc.
|
||||||
|
const prefix = text.substr(0, result.index).replace(/./g, " ");
|
||||||
|
|
||||||
|
const js = prefix + result[1];
|
||||||
|
|
||||||
|
return await validatorJavaScript(js, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function validatorJavaScript(text, options) {
|
||||||
|
await requireLibrary(ESLINT);
|
||||||
|
|
||||||
|
if (text.length > 20000) {
|
||||||
|
console.log("Skipping linting because of large size: ", text.length);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const errors = new eslint().verify(text, {
|
||||||
|
root: true,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2017
|
||||||
|
},
|
||||||
|
extends: ['eslint:recommended', 'airbnb-base'],
|
||||||
|
env: {
|
||||||
|
'node': true
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'import/no-unresolved': 'off',
|
||||||
|
'func-names': 'off',
|
||||||
|
'comma-dangle': ['warn'],
|
||||||
|
'padded-blocks': 'off',
|
||||||
|
'linebreak-style': 'off',
|
||||||
|
'class-methods-use-this': 'off',
|
||||||
|
'no-unused-vars': ['warn', { vars: 'local', args: 'after-used' }],
|
||||||
|
'no-nested-ternary': 'off',
|
||||||
|
'no-underscore-dangle': ['error', {'allow': ['_super', '_lookupFactory']}]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = [];
|
||||||
|
if (errors) {
|
||||||
|
parseErrors(errors, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.registerHelper("lint", "javascript", validatorJavaScript);
|
||||||
|
CodeMirror.registerHelper("lint", "html", validatorHtml);
|
||||||
|
|
||||||
|
function parseErrors(errors, output) {
|
||||||
|
for (const error of errors) {
|
||||||
|
const startLine = error.line - 1;
|
||||||
|
const endLine = error.endLine !== undefined ? error.endLine - 1 : startLine;
|
||||||
|
const startCol = error.column - 1;
|
||||||
|
const endCol = error.endColumn !== undefined ? error.endColumn - 1 : startCol + 1;
|
||||||
|
|
||||||
|
output.push({
|
||||||
|
message: error.message,
|
||||||
|
severity: error.severity === 1 ? "warning" : "error",
|
||||||
|
from: CodeMirror.Pos(startLine, startCol),
|
||||||
|
to: CodeMirror.Pos(endLine, endCol)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
73
src/public/libraries/codemirror/addon/lint/lint.css
vendored
Normal file
73
src/public/libraries/codemirror/addon/lint/lint.css
vendored
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/* The lint marker gutter */
|
||||||
|
.CodeMirror-lint-markers {
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-tooltip {
|
||||||
|
background-color: #ffd;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 4px 4px 4px 4px;
|
||||||
|
color: black;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 10pt;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 2px 5px;
|
||||||
|
position: fixed;
|
||||||
|
white-space: pre;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
z-index: 100;
|
||||||
|
max-width: 600px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity .4s;
|
||||||
|
-moz-transition: opacity .4s;
|
||||||
|
-webkit-transition: opacity .4s;
|
||||||
|
-o-transition: opacity .4s;
|
||||||
|
-ms-transition: opacity .4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning {
|
||||||
|
background-position: left bottom;
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-mark-error {
|
||||||
|
background-image:
|
||||||
|
url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==")
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-mark-warning {
|
||||||
|
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII=");
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-marker-error, .CodeMirror-lint-marker-warning {
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
vertical-align: middle;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-message-error, .CodeMirror-lint-message-warning {
|
||||||
|
padding-left: 18px;
|
||||||
|
background-position: top left;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error {
|
||||||
|
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=");
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning {
|
||||||
|
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=");
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-marker-multiple {
|
||||||
|
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right bottom;
|
||||||
|
width: 100%; height: 100%;
|
||||||
|
}
|
||||||
252
src/public/libraries/codemirror/addon/lint/lint.js
vendored
Normal file
252
src/public/libraries/codemirror/addon/lint/lint.js
vendored
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
var GUTTER_ID = "CodeMirror-lint-markers";
|
||||||
|
|
||||||
|
function showTooltip(e, content) {
|
||||||
|
var tt = document.createElement("div");
|
||||||
|
tt.className = "CodeMirror-lint-tooltip";
|
||||||
|
tt.appendChild(content.cloneNode(true));
|
||||||
|
document.body.appendChild(tt);
|
||||||
|
|
||||||
|
function position(e) {
|
||||||
|
if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position);
|
||||||
|
tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + "px";
|
||||||
|
tt.style.left = (e.clientX + 5) + "px";
|
||||||
|
}
|
||||||
|
CodeMirror.on(document, "mousemove", position);
|
||||||
|
position(e);
|
||||||
|
if (tt.style.opacity != null) tt.style.opacity = 1;
|
||||||
|
return tt;
|
||||||
|
}
|
||||||
|
function rm(elt) {
|
||||||
|
if (elt.parentNode) elt.parentNode.removeChild(elt);
|
||||||
|
}
|
||||||
|
function hideTooltip(tt) {
|
||||||
|
if (!tt.parentNode) return;
|
||||||
|
if (tt.style.opacity == null) rm(tt);
|
||||||
|
tt.style.opacity = 0;
|
||||||
|
setTimeout(function() { rm(tt); }, 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTooltipFor(e, content, node) {
|
||||||
|
var tooltip = showTooltip(e, content);
|
||||||
|
function hide() {
|
||||||
|
CodeMirror.off(node, "mouseout", hide);
|
||||||
|
if (tooltip) { hideTooltip(tooltip); tooltip = null; }
|
||||||
|
}
|
||||||
|
var poll = setInterval(function() {
|
||||||
|
if (tooltip) for (var n = node;; n = n.parentNode) {
|
||||||
|
if (n && n.nodeType == 11) n = n.host;
|
||||||
|
if (n == document.body) return;
|
||||||
|
if (!n) { hide(); break; }
|
||||||
|
}
|
||||||
|
if (!tooltip) return clearInterval(poll);
|
||||||
|
}, 400);
|
||||||
|
CodeMirror.on(node, "mouseout", hide);
|
||||||
|
}
|
||||||
|
|
||||||
|
function LintState(cm, options, hasGutter) {
|
||||||
|
this.marked = [];
|
||||||
|
this.options = options;
|
||||||
|
this.timeout = null;
|
||||||
|
this.hasGutter = hasGutter;
|
||||||
|
this.onMouseOver = function(e) { onMouseOver(cm, e); };
|
||||||
|
this.waitingFor = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseOptions(_cm, options) {
|
||||||
|
if (options instanceof Function) return {getAnnotations: options};
|
||||||
|
if (!options || options === true) options = {};
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearMarks(cm) {
|
||||||
|
var state = cm.state.lint;
|
||||||
|
if (state.hasGutter) cm.clearGutter(GUTTER_ID);
|
||||||
|
for (var i = 0; i < state.marked.length; ++i)
|
||||||
|
state.marked[i].clear();
|
||||||
|
state.marked.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeMarker(labels, severity, multiple, tooltips) {
|
||||||
|
var marker = document.createElement("div"), inner = marker;
|
||||||
|
marker.className = "CodeMirror-lint-marker-" + severity;
|
||||||
|
if (multiple) {
|
||||||
|
inner = marker.appendChild(document.createElement("div"));
|
||||||
|
inner.className = "CodeMirror-lint-marker-multiple";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tooltips != false) CodeMirror.on(inner, "mouseover", function(e) {
|
||||||
|
showTooltipFor(e, labels, inner);
|
||||||
|
});
|
||||||
|
|
||||||
|
return marker;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMaxSeverity(a, b) {
|
||||||
|
if (a == "error") return a;
|
||||||
|
else return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
function groupByLine(annotations) {
|
||||||
|
var lines = [];
|
||||||
|
for (var i = 0; i < annotations.length; ++i) {
|
||||||
|
var ann = annotations[i], line = ann.from.line;
|
||||||
|
(lines[line] || (lines[line] = [])).push(ann);
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
function annotationTooltip(ann) {
|
||||||
|
var severity = ann.severity;
|
||||||
|
if (!severity) severity = "error";
|
||||||
|
var tip = document.createElement("div");
|
||||||
|
tip.className = "CodeMirror-lint-message-" + severity;
|
||||||
|
if (typeof ann.messageHTML != 'undefined') {
|
||||||
|
tip.innerHTML = ann.messageHTML;
|
||||||
|
} else {
|
||||||
|
tip.appendChild(document.createTextNode(ann.message));
|
||||||
|
}
|
||||||
|
return tip;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lintAsync(cm, getAnnotations, passOptions) {
|
||||||
|
var state = cm.state.lint
|
||||||
|
var id = ++state.waitingFor
|
||||||
|
function abort() {
|
||||||
|
id = -1
|
||||||
|
cm.off("change", abort)
|
||||||
|
}
|
||||||
|
cm.on("change", abort)
|
||||||
|
getAnnotations(cm.getValue(), function(annotations, arg2) {
|
||||||
|
cm.off("change", abort)
|
||||||
|
if (state.waitingFor != id) return
|
||||||
|
if (arg2 && annotations instanceof CodeMirror) annotations = arg2
|
||||||
|
cm.operation(function() {updateLinting(cm, annotations)})
|
||||||
|
}, passOptions, cm);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startLinting(cm) {
|
||||||
|
var state = cm.state.lint, options = state.options;
|
||||||
|
/*
|
||||||
|
* Passing rules in `options` property prevents JSHint (and other linters) from complaining
|
||||||
|
* about unrecognized rules like `onUpdateLinting`, `delay`, `lintOnChange`, etc.
|
||||||
|
*/
|
||||||
|
var passOptions = options.options || options;
|
||||||
|
var getAnnotations = options.getAnnotations || cm.getHelper(CodeMirror.Pos(0, 0), "lint");
|
||||||
|
if (!getAnnotations) return;
|
||||||
|
if (options.async || getAnnotations.async) {
|
||||||
|
lintAsync(cm, getAnnotations, passOptions)
|
||||||
|
} else {
|
||||||
|
var annotations = getAnnotations(cm.getValue(), passOptions, cm);
|
||||||
|
if (!annotations) return;
|
||||||
|
if (annotations.then) annotations.then(function(issues) {
|
||||||
|
cm.operation(function() {updateLinting(cm, issues)})
|
||||||
|
});
|
||||||
|
else cm.operation(function() {updateLinting(cm, annotations)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLinting(cm, annotationsNotSorted) {
|
||||||
|
clearMarks(cm);
|
||||||
|
var state = cm.state.lint, options = state.options;
|
||||||
|
|
||||||
|
var annotations = groupByLine(annotationsNotSorted);
|
||||||
|
|
||||||
|
for (var line = 0; line < annotations.length; ++line) {
|
||||||
|
var anns = annotations[line];
|
||||||
|
if (!anns) continue;
|
||||||
|
|
||||||
|
var maxSeverity = null;
|
||||||
|
var tipLabel = state.hasGutter && document.createDocumentFragment();
|
||||||
|
|
||||||
|
for (var i = 0; i < anns.length; ++i) {
|
||||||
|
var ann = anns[i];
|
||||||
|
var severity = ann.severity;
|
||||||
|
if (!severity) severity = "error";
|
||||||
|
maxSeverity = getMaxSeverity(maxSeverity, severity);
|
||||||
|
|
||||||
|
if (options.formatAnnotation) ann = options.formatAnnotation(ann);
|
||||||
|
if (state.hasGutter) tipLabel.appendChild(annotationTooltip(ann));
|
||||||
|
|
||||||
|
if (ann.to) state.marked.push(cm.markText(ann.from, ann.to, {
|
||||||
|
className: "CodeMirror-lint-mark-" + severity,
|
||||||
|
__annotation: ann
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.hasGutter)
|
||||||
|
cm.setGutterMarker(line, GUTTER_ID, makeMarker(tipLabel, maxSeverity, anns.length > 1,
|
||||||
|
state.options.tooltips));
|
||||||
|
}
|
||||||
|
if (options.onUpdateLinting) options.onUpdateLinting(annotationsNotSorted, annotations, cm);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChange(cm) {
|
||||||
|
var state = cm.state.lint;
|
||||||
|
if (!state) return;
|
||||||
|
clearTimeout(state.timeout);
|
||||||
|
state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay || 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
function popupTooltips(annotations, e) {
|
||||||
|
var target = e.target || e.srcElement;
|
||||||
|
var tooltip = document.createDocumentFragment();
|
||||||
|
for (var i = 0; i < annotations.length; i++) {
|
||||||
|
var ann = annotations[i];
|
||||||
|
tooltip.appendChild(annotationTooltip(ann));
|
||||||
|
}
|
||||||
|
showTooltipFor(e, tooltip, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseOver(cm, e) {
|
||||||
|
var target = e.target || e.srcElement;
|
||||||
|
if (!/\bCodeMirror-lint-mark-/.test(target.className)) return;
|
||||||
|
var box = target.getBoundingClientRect(), x = (box.left + box.right) / 2, y = (box.top + box.bottom) / 2;
|
||||||
|
var spans = cm.findMarksAt(cm.coordsChar({left: x, top: y}, "client"));
|
||||||
|
|
||||||
|
var annotations = [];
|
||||||
|
for (var i = 0; i < spans.length; ++i) {
|
||||||
|
var ann = spans[i].__annotation;
|
||||||
|
if (ann) annotations.push(ann);
|
||||||
|
}
|
||||||
|
if (annotations.length) popupTooltips(annotations, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.defineOption("lint", false, function(cm, val, old) {
|
||||||
|
if (old && old != CodeMirror.Init) {
|
||||||
|
clearMarks(cm);
|
||||||
|
if (cm.state.lint.options.lintOnChange !== false)
|
||||||
|
cm.off("change", onChange);
|
||||||
|
CodeMirror.off(cm.getWrapperElement(), "mouseover", cm.state.lint.onMouseOver);
|
||||||
|
clearTimeout(cm.state.lint.timeout);
|
||||||
|
delete cm.state.lint;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val) {
|
||||||
|
var gutters = cm.getOption("gutters"), hasLintGutter = false;
|
||||||
|
for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true;
|
||||||
|
var state = cm.state.lint = new LintState(cm, parseOptions(cm, val), hasLintGutter);
|
||||||
|
if (state.options.lintOnChange !== false)
|
||||||
|
cm.on("change", onChange);
|
||||||
|
if (state.options.tooltips != false && state.options.tooltips != "gutter")
|
||||||
|
CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver);
|
||||||
|
|
||||||
|
startLinting(cm);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("performLint", function() {
|
||||||
|
if (this.state.lint) startLinting(this);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
var state = cm.state.matchHighlighter;
|
var state = cm.state.matchHighlighter;
|
||||||
cm.addOverlay(state.overlay = makeOverlay(query, hasBoundary, style));
|
cm.addOverlay(state.overlay = makeOverlay(query, hasBoundary, style));
|
||||||
if (state.options.annotateScrollbar && cm.showMatchesOnScrollbar) {
|
if (state.options.annotateScrollbar && cm.showMatchesOnScrollbar) {
|
||||||
var searchFor = hasBoundary ? new RegExp("\\b" + query + "\\b") : query;
|
var searchFor = hasBoundary ? new RegExp("\\b" + query.replace(/[\\\[+*?(){|^$]/g, "\\$&") + "\\b") : query;
|
||||||
state.matchesonscroll = cm.showMatchesOnScrollbar(searchFor, false,
|
state.matchesonscroll = cm.showMatchesOnScrollbar(searchFor, false,
|
||||||
{className: "CodeMirror-selection-highlight-scrollbar"});
|
{className: "CodeMirror-selection-highlight-scrollbar"});
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
101349
src/public/libraries/eslint.js
Normal file
101349
src/public/libraries/eslint.js
Normal file
File diff suppressed because one or more lines are too long
@@ -72,6 +72,11 @@ span.fancytree-node.fancytree-folder.code > span.fancytree-icon {
|
|||||||
background-image: url("../images/icons/code-folder.png");
|
background-image: url("../images/icons/code-folder.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.fancytree-node.attachment > span.fancytree-icon {
|
||||||
|
background-position: 0 0;
|
||||||
|
background-image: url("../images/icons/paperclip.png");
|
||||||
|
}
|
||||||
|
|
||||||
span.fancytree-node.protected > span.fancytree-icon {
|
span.fancytree-node.protected > span.fancytree-icon {
|
||||||
filter: drop-shadow(2px 2px 2px black);
|
filter: drop-shadow(2px 2px 2px black);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const sql = require('../../services/sql');
|
|||||||
const auth = require('../../services/auth');
|
const auth = require('../../services/auth');
|
||||||
const notes = require('../../services/notes');
|
const notes = require('../../services/notes');
|
||||||
const attributes = require('../../services/attributes');
|
const attributes = require('../../services/attributes');
|
||||||
|
const protected_session = require('../../services/protected_session');
|
||||||
const multer = require('multer')();
|
const multer = require('multer')();
|
||||||
const wrap = require('express-promise-wrap').wrap;
|
const wrap = require('express-promise-wrap').wrap;
|
||||||
|
|
||||||
@@ -44,9 +45,21 @@ router.post('/upload/:parentNoteId', auth.checkApiAuthOrElectron, multer.single(
|
|||||||
router.get('/download/:noteId', auth.checkApiAuthOrElectron, wrap(async (req, res, next) => {
|
router.get('/download/:noteId', auth.checkApiAuthOrElectron, wrap(async (req, res, next) => {
|
||||||
const noteId = req.params.noteId;
|
const noteId = req.params.noteId;
|
||||||
const note = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]);
|
const note = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]);
|
||||||
|
const protectedSessionId = req.query.protectedSessionId;
|
||||||
|
|
||||||
if (!note) {
|
if (!note) {
|
||||||
return res.status(404).send(`Note ${parentNoteId} doesn't exist.`);
|
return res.status(404).send(`Note ${noteId} doesn't exist.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (note.isProtected) {
|
||||||
|
const dataKey = protected_session.getDataKeyForProtectedSessionId(protectedSessionId);
|
||||||
|
|
||||||
|
if (!dataKey) {
|
||||||
|
res.status(401).send("Protected session not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected_session.decryptNote(dataKey, note);
|
||||||
}
|
}
|
||||||
|
|
||||||
const attributeMap = await attributes.getNoteAttributeMap(noteId);
|
const attributeMap = await attributes.getNoteAttributeMap(noteId);
|
||||||
|
|||||||
@@ -2,56 +2,72 @@
|
|||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const rimraf = require('rimraf');
|
|
||||||
const fs = require('fs');
|
|
||||||
const sql = require('../../services/sql');
|
const sql = require('../../services/sql');
|
||||||
const data_dir = require('../../services/data_dir');
|
const attributes = require('../../services/attributes');
|
||||||
const html = require('html');
|
const html = require('html');
|
||||||
const auth = require('../../services/auth');
|
const auth = require('../../services/auth');
|
||||||
const wrap = require('express-promise-wrap').wrap;
|
const wrap = require('express-promise-wrap').wrap;
|
||||||
|
const tar = require('tar-stream');
|
||||||
|
const sanitize = require("sanitize-filename");
|
||||||
|
|
||||||
router.get('/:noteId/to/:directory', auth.checkApiAuth, wrap(async (req, res, next) => {
|
router.get('/:noteId/', auth.checkApiAuth, wrap(async (req, res, next) => {
|
||||||
const noteId = req.params.noteId;
|
const noteId = req.params.noteId;
|
||||||
const directory = req.params.directory.replace(/[^0-9a-zA-Z_-]/gi, '');
|
|
||||||
|
|
||||||
if (!fs.existsSync(data_dir.EXPORT_DIR)) {
|
|
||||||
fs.mkdirSync(data_dir.EXPORT_DIR);
|
|
||||||
}
|
|
||||||
|
|
||||||
const completeExportDir = data_dir.EXPORT_DIR + '/' + directory;
|
|
||||||
|
|
||||||
if (fs.existsSync(completeExportDir)) {
|
|
||||||
rimraf.sync(completeExportDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mkdirSync(completeExportDir);
|
|
||||||
|
|
||||||
const noteTreeId = await sql.getValue('SELECT noteTreeId FROM note_tree WHERE noteId = ?', [noteId]);
|
const noteTreeId = await sql.getValue('SELECT noteTreeId FROM note_tree WHERE noteId = ?', [noteId]);
|
||||||
|
|
||||||
await exportNote(noteTreeId, completeExportDir);
|
const pack = tar.pack();
|
||||||
|
|
||||||
res.send({});
|
const name = await exportNote(noteTreeId, '', pack);
|
||||||
|
|
||||||
|
pack.finalize();
|
||||||
|
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename="' + name + '.tar"');
|
||||||
|
res.setHeader('Content-Type', 'application/tar');
|
||||||
|
|
||||||
|
pack.pipe(res);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
async function exportNote(noteTreeId, dir) {
|
async function exportNote(noteTreeId, directory, pack) {
|
||||||
const noteTree = await sql.getRow("SELECT * FROM note_tree WHERE noteTreeId = ?", [noteTreeId]);
|
const noteTree = await sql.getRow("SELECT * FROM note_tree WHERE noteTreeId = ?", [noteTreeId]);
|
||||||
const note = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteTree.noteId]);
|
const note = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteTree.noteId]);
|
||||||
|
|
||||||
const pos = (noteTree.notePosition + '').padStart(4, '0');
|
if (note.isProtected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
fs.writeFileSync(dir + '/' + pos + '-' + note.title + '.html', html.prettyPrint(note.content, {indent_size: 2}));
|
const metadata = await getMetadata(note);
|
||||||
|
|
||||||
|
if ('exclude_from_export' in metadata.attributes) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadataJson = JSON.stringify(metadata, null, '\t');
|
||||||
|
const childFileName = directory + sanitize(note.title);
|
||||||
|
|
||||||
|
pack.entry({ name: childFileName + ".meta", size: metadataJson.length }, metadataJson);
|
||||||
|
|
||||||
|
const content = note.type === 'text' ? html.prettyPrint(note.content, {indent_size: 2}) : note.content;
|
||||||
|
|
||||||
|
pack.entry({ name: childFileName + ".dat", size: content.length }, content);
|
||||||
|
|
||||||
const children = await sql.getRows("SELECT * FROM note_tree WHERE parentNoteId = ? AND isDeleted = 0", [note.noteId]);
|
const children = await sql.getRows("SELECT * FROM note_tree WHERE parentNoteId = ? AND isDeleted = 0", [note.noteId]);
|
||||||
|
|
||||||
if (children.length > 0) {
|
if (children.length > 0) {
|
||||||
const childrenDir = dir + '/' + pos + '-' + note.title;
|
|
||||||
|
|
||||||
fs.mkdirSync(childrenDir);
|
|
||||||
|
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
await exportNote(child.noteTreeId, childrenDir);
|
await exportNote(child.noteTreeId, childFileName + "/", pack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return childFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getMetadata(note) {
|
||||||
|
return {
|
||||||
|
title: note.title,
|
||||||
|
type: note.type,
|
||||||
|
mime: note.mime,
|
||||||
|
attributes: await attributes.getNoteAttributeMap(note.noteId)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
@@ -2,104 +2,128 @@
|
|||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const fs = require('fs');
|
|
||||||
const sql = require('../../services/sql');
|
const sql = require('../../services/sql');
|
||||||
const data_dir = require('../../services/data_dir');
|
|
||||||
const utils = require('../../services/utils');
|
|
||||||
const sync_table = require('../../services/sync_table');
|
|
||||||
const auth = require('../../services/auth');
|
const auth = require('../../services/auth');
|
||||||
|
const notes = require('../../services/notes');
|
||||||
const wrap = require('express-promise-wrap').wrap;
|
const wrap = require('express-promise-wrap').wrap;
|
||||||
|
const tar = require('tar-stream');
|
||||||
|
const multer = require('multer')();
|
||||||
|
const stream = require('stream');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
router.get('/:directory/to/:parentNoteId', auth.checkApiAuth, wrap(async (req, res, next) => {
|
function getFileName(name) {
|
||||||
const directory = req.params.directory.replace(/[^0-9a-zA-Z_-]/gi, '');
|
let key;
|
||||||
|
|
||||||
|
if (name.endsWith(".dat")) {
|
||||||
|
key = "data";
|
||||||
|
name = name.substr(0, name.length - 4);
|
||||||
|
}
|
||||||
|
else if (name.endsWith((".meta"))) {
|
||||||
|
key = "meta";
|
||||||
|
name = name.substr(0, name.length - 5);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new Error("Unknown file type in import archive: " + name);
|
||||||
|
}
|
||||||
|
return {name, key};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function parseImportFile(file) {
|
||||||
|
const fileMap = {};
|
||||||
|
const files = [];
|
||||||
|
|
||||||
|
const extract = tar.extract();
|
||||||
|
|
||||||
|
extract.on('entry', function(header, stream, next) {
|
||||||
|
let {name, key} = getFileName(header.name);
|
||||||
|
|
||||||
|
let file = fileMap[name];
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
file = fileMap[name] = {
|
||||||
|
children: []
|
||||||
|
};
|
||||||
|
|
||||||
|
let parentFileName = path.dirname(header.name);
|
||||||
|
|
||||||
|
if (parentFileName && parentFileName !== '.') {
|
||||||
|
fileMap[parentFileName].children.push(file);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
files.push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunks = [];
|
||||||
|
|
||||||
|
stream.on("data", function (chunk) {
|
||||||
|
chunks.push(chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
// header is the tar header
|
||||||
|
// stream is the content body (might be an empty stream)
|
||||||
|
// call next when you are done with this entry
|
||||||
|
|
||||||
|
stream.on('end', function() {
|
||||||
|
file[key] = Buffer.concat(chunks);
|
||||||
|
|
||||||
|
if (key === "meta") {
|
||||||
|
file[key] = JSON.parse(file[key].toString("UTF-8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
next(); // ready for next entry
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.resume(); // just auto drain the stream
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
extract.on('finish', function() {
|
||||||
|
resolve(files);
|
||||||
|
});
|
||||||
|
|
||||||
|
const bufferStream = new stream.PassThrough();
|
||||||
|
bufferStream.end(file.buffer);
|
||||||
|
|
||||||
|
bufferStream.pipe(extract);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
router.post('/:parentNoteId', auth.checkApiAuthOrElectron, multer.single('upload'), wrap(async (req, res, next) => {
|
||||||
|
const sourceId = req.headers.source_id;
|
||||||
const parentNoteId = req.params.parentNoteId;
|
const parentNoteId = req.params.parentNoteId;
|
||||||
|
const file = req.file;
|
||||||
|
|
||||||
const dir = data_dir.EXPORT_DIR + '/' + directory;
|
const note = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [parentNoteId]);
|
||||||
|
|
||||||
await sql.doInTransaction(async () => await importNotes(dir, parentNoteId));
|
if (!note) {
|
||||||
|
return res.status(404).send(`Note ${parentNoteId} doesn't exist.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = await parseImportFile(file);
|
||||||
|
|
||||||
|
await sql.doInTransaction(async () => {
|
||||||
|
await importNotes(files, parentNoteId, sourceId);
|
||||||
|
});
|
||||||
|
|
||||||
res.send({});
|
res.send({});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
async function importNotes(dir, parentNoteId) {
|
async function importNotes(files, parentNoteId, sourceId) {
|
||||||
const parent = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [parentNoteId]);
|
for (const file of files) {
|
||||||
|
if (file.meta.type !== 'file') {
|
||||||
if (!parent) {
|
file.data = file.data.toString("UTF-8");
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileList = fs.readdirSync(dir);
|
|
||||||
|
|
||||||
for (const file of fileList) {
|
|
||||||
const path = dir + '/' + file;
|
|
||||||
|
|
||||||
if (fs.lstatSync(path).isDirectory()) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!file.endsWith('.html')) {
|
const noteId = await notes.createNote(parentNoteId, file.meta.title, file.data, {
|
||||||
continue;
|
type: file.meta.type,
|
||||||
}
|
mime: file.meta.mime,
|
||||||
|
attributes: file.meta.attributes,
|
||||||
const fileNameWithoutExt = file.substr(0, file.length - 5);
|
sourceId: sourceId
|
||||||
|
|
||||||
let noteTitle;
|
|
||||||
let notePos;
|
|
||||||
|
|
||||||
const match = fileNameWithoutExt.match(/^([0-9]{4})-(.*)$/);
|
|
||||||
if (match) {
|
|
||||||
notePos = parseInt(match[1]);
|
|
||||||
noteTitle = match[2];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
let maxPos = await sql.getValue("SELECT MAX(notePosition) FROM note_tree WHERE parentNoteId = ? AND isDeleted = 0", [parentNoteId]);
|
|
||||||
if (maxPos) {
|
|
||||||
notePos = maxPos + 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
notePos = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
noteTitle = fileNameWithoutExt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const noteText = fs.readFileSync(path, "utf8");
|
|
||||||
|
|
||||||
const noteId = utils.newNoteId();
|
|
||||||
const noteTreeId = utils.newNoteRevisionId();
|
|
||||||
|
|
||||||
const now = utils.nowDate();
|
|
||||||
|
|
||||||
await sql.insert('note_tree', {
|
|
||||||
noteTreeId: noteTreeId,
|
|
||||||
noteId: noteId,
|
|
||||||
parentNoteId: parentNoteId,
|
|
||||||
notePosition: notePos,
|
|
||||||
isExpanded: 0,
|
|
||||||
isDeleted: 0,
|
|
||||||
dateModified: now
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await sync_table.addNoteTreeSync(noteTreeId);
|
if (file.children.length > 0) {
|
||||||
|
await importNotes(file.children, noteId, sourceId);
|
||||||
await sql.insert('notes', {
|
|
||||||
noteId: noteId,
|
|
||||||
title: noteTitle,
|
|
||||||
content: noteText,
|
|
||||||
isDeleted: 0,
|
|
||||||
isProtected: 0,
|
|
||||||
type: 'text',
|
|
||||||
mime: 'text/html',
|
|
||||||
dateCreated: now,
|
|
||||||
dateModified: now
|
|
||||||
});
|
|
||||||
|
|
||||||
await sync_table.addNoteSync(noteId);
|
|
||||||
|
|
||||||
const noteDir = dir + '/' + fileNameWithoutExt;
|
|
||||||
|
|
||||||
if (fs.existsSync(noteDir) && fs.lstatSync(noteDir).isDirectory()) {
|
|
||||||
await importNotes(noteDir, noteId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ router.post('/exec', auth.checkApiAuth, wrap(async (req, res, next) => {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
router.post('/job', auth.checkApiAuth, wrap(async (req, res, next) => {
|
||||||
|
await script.setJob(req.body);
|
||||||
|
|
||||||
|
res.send({});
|
||||||
|
}));
|
||||||
|
|
||||||
router.get('/startup', auth.checkApiAuth, wrap(async (req, res, next) => {
|
router.get('/startup', auth.checkApiAuth, wrap(async (req, res, next) => {
|
||||||
const noteIds = await attributes.getNoteIdsWithAttribute("run_on_startup");
|
const noteIds = await attributes.getNoteIdsWithAttribute("run_on_startup");
|
||||||
const repository = new Repository(req);
|
const repository = new Repository(req);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const sql = require('../../services/sql');
|
|||||||
const options = require('../../services/options');
|
const options = require('../../services/options');
|
||||||
const utils = require('../../services/utils');
|
const utils = require('../../services/utils');
|
||||||
const auth = require('../../services/auth');
|
const auth = require('../../services/auth');
|
||||||
|
const config = require('../../services/config');
|
||||||
const protected_session = require('../../services/protected_session');
|
const protected_session = require('../../services/protected_session');
|
||||||
const sync_table = require('../../services/sync_table');
|
const sync_table = require('../../services/sync_table');
|
||||||
const wrap = require('express-promise-wrap').wrap;
|
const wrap = require('express-promise-wrap').wrap;
|
||||||
@@ -41,6 +42,7 @@ router.get('/', auth.checkApiAuth, wrap(async (req, res, next) => {
|
|||||||
AND notes.isDeleted = 0`);
|
AND notes.isDeleted = 0`);
|
||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
|
instanceName: config.General ? config.General.instanceName : null,
|
||||||
notes: notes,
|
notes: notes,
|
||||||
hiddenInAutocomplete: hiddenInAutocomplete,
|
hiddenInAutocomplete: hiddenInAutocomplete,
|
||||||
start_note_path: await options.getOption('start_note_path')
|
start_note_path: await options.getOption('start_note_path')
|
||||||
|
|||||||
BIN
src/scripts/Reddit Importer.tar
Normal file
BIN
src/scripts/Reddit Importer.tar
Normal file
Binary file not shown.
@@ -9,7 +9,8 @@ const BUILTIN_ATTRIBUTES = [
|
|||||||
'run_on_startup',
|
'run_on_startup',
|
||||||
'disable_versioning',
|
'disable_versioning',
|
||||||
'calendar_root',
|
'calendar_root',
|
||||||
'hide_in_autocomplete'
|
'hide_in_autocomplete',
|
||||||
|
'exclude_from_export'
|
||||||
];
|
];
|
||||||
|
|
||||||
async function getNoteAttributeMap(noteId) {
|
async function getNoteAttributeMap(noteId) {
|
||||||
|
|||||||
@@ -20,6 +20,5 @@ module.exports = {
|
|||||||
DOCUMENT_PATH,
|
DOCUMENT_PATH,
|
||||||
BACKUP_DIR,
|
BACKUP_DIR,
|
||||||
LOG_DIR,
|
LOG_DIR,
|
||||||
EXPORT_DIR,
|
|
||||||
ANONYMIZED_DB_DIR
|
ANONYMIZED_DB_DIR
|
||||||
};
|
};
|
||||||
@@ -88,7 +88,7 @@ function noteTitleIv(iv) {
|
|||||||
return "0" + iv;
|
return "0" + iv;
|
||||||
}
|
}
|
||||||
|
|
||||||
function noteTextIv(iv) {
|
function noteContentIv(iv) {
|
||||||
return "1" + iv;
|
return "1" + iv;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,5 +97,5 @@ module.exports = {
|
|||||||
decrypt,
|
decrypt,
|
||||||
decryptString,
|
decryptString,
|
||||||
noteTitleIv,
|
noteTitleIv,
|
||||||
noteTextIv
|
noteContentIv
|
||||||
};
|
};
|
||||||
@@ -29,7 +29,7 @@ async function getNoteStartingWith(parentNoteId, startsWith) {
|
|||||||
AND note_tree.isDeleted = 0`, [parentNoteId]);
|
AND note_tree.isDeleted = 0`, [parentNoteId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getRootNoteId() {
|
async function getRootCalendarNoteId() {
|
||||||
let rootNoteId = await sql.getValue(`SELECT notes.noteId FROM notes JOIN attributes USING(noteId)
|
let rootNoteId = await sql.getValue(`SELECT notes.noteId FROM notes JOIN attributes USING(noteId)
|
||||||
WHERE attributes.name = '${CALENDAR_ROOT_ATTRIBUTE}' AND notes.isDeleted = 0`);
|
WHERE attributes.name = '${CALENDAR_ROOT_ATTRIBUTE}' AND notes.isDeleted = 0`);
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ async function getMonthNoteId(dateTimeStr, rootNoteId) {
|
|||||||
|
|
||||||
async function getDateNoteId(dateTimeStr, rootNoteId = null) {
|
async function getDateNoteId(dateTimeStr, rootNoteId = null) {
|
||||||
if (!rootNoteId) {
|
if (!rootNoteId) {
|
||||||
rootNoteId = await getRootNoteId();
|
rootNoteId = await getRootCalendarNoteId();
|
||||||
}
|
}
|
||||||
|
|
||||||
const dateStr = dateTimeStr.substr(0, 10);
|
const dateStr = dateTimeStr.substr(0, 10);
|
||||||
@@ -119,7 +119,7 @@ async function getDateNoteId(dateTimeStr, rootNoteId = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getRootNoteId,
|
getRootCalendarNoteId,
|
||||||
getYearNoteId,
|
getYearNoteId,
|
||||||
getMonthNoteId,
|
getMonthNoteId,
|
||||||
getDateNoteId
|
getDateNoteId
|
||||||
|
|||||||
@@ -83,6 +83,40 @@ async function createNewNote(parentNoteId, noteOpts, dataKey, sourceId) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createNote(parentNoteId, title, content = "", extraOptions = {}) {
|
||||||
|
if (!parentNoteId) throw new Error("Empty parentNoteId");
|
||||||
|
if (!title) throw new Error("Empty title");
|
||||||
|
|
||||||
|
const note = {
|
||||||
|
title: title,
|
||||||
|
content: extraOptions.json ? JSON.stringify(content, null, '\t') : content,
|
||||||
|
target: 'into',
|
||||||
|
isProtected: extraOptions.isProtected !== undefined ? extraOptions.isProtected : false,
|
||||||
|
type: extraOptions.type,
|
||||||
|
mime: extraOptions.mime
|
||||||
|
};
|
||||||
|
|
||||||
|
if (extraOptions.json) {
|
||||||
|
note.type = "code";
|
||||||
|
note.mime = "application/json";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!note.type) {
|
||||||
|
note.type = "text";
|
||||||
|
note.mime = "text/html";
|
||||||
|
}
|
||||||
|
|
||||||
|
const {noteId} = await createNewNote(parentNoteId, note, extraOptions.dataKey, extraOptions.sourceId);
|
||||||
|
|
||||||
|
if (extraOptions.attributes) {
|
||||||
|
for (const attrName in extraOptions.attributes) {
|
||||||
|
await attributes.createAttribute(noteId, attrName, extraOptions.attributes[attrName]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return noteId;
|
||||||
|
}
|
||||||
|
|
||||||
async function protectNoteRecursively(noteId, dataKey, protect, sourceId) {
|
async function protectNoteRecursively(noteId, dataKey, protect, sourceId) {
|
||||||
const note = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]);
|
const note = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]);
|
||||||
|
|
||||||
@@ -148,10 +182,14 @@ async function protectNoteHistory(noteId, dataKey, protect, sourceId) {
|
|||||||
async function saveNoteHistory(noteId, dataKey, sourceId, nowStr) {
|
async function saveNoteHistory(noteId, dataKey, sourceId, nowStr) {
|
||||||
const oldNote = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]);
|
const oldNote = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]);
|
||||||
|
|
||||||
|
if (oldNote.type === 'file') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (oldNote.isProtected) {
|
if (oldNote.isProtected) {
|
||||||
protected_session.decryptNote(dataKey, oldNote);
|
protected_session.decryptNote(dataKey, oldNote);
|
||||||
|
|
||||||
note.isProtected = false;
|
oldNote.isProtected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newNoteRevisionId = utils.newNoteRevisionId();
|
const newNoteRevisionId = utils.newNoteRevisionId();
|
||||||
@@ -217,7 +255,21 @@ async function saveNoteImages(noteId, noteText, sourceId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadFile(noteId, newNote, dataKey) {
|
||||||
|
const oldNote = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]);
|
||||||
|
|
||||||
|
if (oldNote.isProtected) {
|
||||||
|
await protected_session.decryptNote(dataKey, oldNote);
|
||||||
|
}
|
||||||
|
|
||||||
|
newNote.detail.content = oldNote.content;
|
||||||
|
}
|
||||||
|
|
||||||
async function updateNote(noteId, newNote, dataKey, sourceId) {
|
async function updateNote(noteId, newNote, dataKey, sourceId) {
|
||||||
|
if (newNote.detail.type === 'file') {
|
||||||
|
await loadFile(noteId, newNote, dataKey);
|
||||||
|
}
|
||||||
|
|
||||||
if (newNote.detail.isProtected) {
|
if (newNote.detail.isProtected) {
|
||||||
await protected_session.encryptNote(dataKey, newNote.detail);
|
await protected_session.encryptNote(dataKey, newNote.detail);
|
||||||
}
|
}
|
||||||
@@ -289,6 +341,7 @@ async function deleteNote(noteTreeId, sourceId) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createNewNote,
|
createNewNote,
|
||||||
|
createNote,
|
||||||
updateNote,
|
updateNote,
|
||||||
deleteNote,
|
deleteNote,
|
||||||
protectNoteRecursively
|
protectNoteRecursively
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ function getDataKey(obj) {
|
|||||||
|
|
||||||
const protectedSessionId = getProtectedSessionId(obj);
|
const protectedSessionId = getProtectedSessionId(obj);
|
||||||
|
|
||||||
|
return getDataKeyForProtectedSessionId(protectedSessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDataKeyForProtectedSessionId(protectedSessionId) {
|
||||||
if (protectedSessionId && session.protectedSessionId === protectedSessionId) {
|
if (protectedSessionId && session.protectedSessionId === protectedSessionId) {
|
||||||
return session.decryptedDataKey;
|
return session.decryptedDataKey;
|
||||||
}
|
}
|
||||||
@@ -52,7 +56,14 @@ function decryptNote(dataKey, note) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (note.content) {
|
if (note.content) {
|
||||||
note.content = data_encryption.decryptString(dataKey, data_encryption.noteTextIv(note.noteId), note.content);
|
const contentIv = data_encryption.noteContentIv(note.noteId);
|
||||||
|
|
||||||
|
if (note.type === 'file') {
|
||||||
|
note.content = data_encryption.decrypt(dataKey, contentIv, note.content);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
note.content = data_encryption.decryptString(dataKey, contentIv, note.content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +87,7 @@ function decryptNoteHistoryRow(dataKey, hist) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hist.content) {
|
if (hist.content) {
|
||||||
hist.content = data_encryption.decryptString(dataKey, data_encryption.noteTextIv(hist.noteRevisionId), hist.content);
|
hist.content = data_encryption.decryptString(dataKey, data_encryption.noteContentIv(hist.noteRevisionId), hist.content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,19 +103,20 @@ function encryptNote(dataKey, note) {
|
|||||||
dataKey = getDataKey(dataKey);
|
dataKey = getDataKey(dataKey);
|
||||||
|
|
||||||
note.title = data_encryption.encrypt(dataKey, data_encryption.noteTitleIv(note.noteId), note.title);
|
note.title = data_encryption.encrypt(dataKey, data_encryption.noteTitleIv(note.noteId), note.title);
|
||||||
note.content = data_encryption.encrypt(dataKey, data_encryption.noteTextIv(note.noteId), note.content);
|
note.content = data_encryption.encrypt(dataKey, data_encryption.noteContentIv(note.noteId), note.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
function encryptNoteHistoryRow(dataKey, history) {
|
function encryptNoteHistoryRow(dataKey, history) {
|
||||||
dataKey = getDataKey(dataKey);
|
dataKey = getDataKey(dataKey);
|
||||||
|
|
||||||
history.title = data_encryption.encrypt(dataKey, data_encryption.noteTitleIv(history.noteRevisionId), history.title);
|
history.title = data_encryption.encrypt(dataKey, data_encryption.noteTitleIv(history.noteRevisionId), history.title);
|
||||||
history.content = data_encryption.encrypt(dataKey, data_encryption.noteTextIv(history.noteRevisionId), history.content);
|
history.content = data_encryption.encrypt(dataKey, data_encryption.noteContentIv(history.noteRevisionId), history.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
setDataKey,
|
setDataKey,
|
||||||
getDataKey,
|
getDataKey,
|
||||||
|
getDataKeyForProtectedSessionId,
|
||||||
isProtectedSessionAvailable,
|
isProtectedSessionAvailable,
|
||||||
decryptNote,
|
decryptNote,
|
||||||
decryptNotes,
|
decryptNotes,
|
||||||
|
|||||||
@@ -1,24 +1,67 @@
|
|||||||
const log = require('./log');
|
|
||||||
const sql = require('./sql');
|
const sql = require('./sql');
|
||||||
const ScriptContext = require('./script_context');
|
const ScriptContext = require('./script_context');
|
||||||
|
|
||||||
async function executeScript(dataKey, script, params) {
|
async function executeScript(dataKey, script, params) {
|
||||||
log.info('Executing script: ' + script);
|
|
||||||
|
|
||||||
const ctx = new ScriptContext(dataKey);
|
const ctx = new ScriptContext(dataKey);
|
||||||
|
|
||||||
const paramsStr = getParams(params);
|
const paramsStr = getParams(params);
|
||||||
|
|
||||||
let ret;
|
return await sql.doInTransaction(async () => execute(ctx, script, paramsStr));
|
||||||
|
}
|
||||||
|
|
||||||
await sql.doInTransaction(async () => {
|
async function execute(ctx, script, paramsStr) {
|
||||||
ret = await (function() { return eval(`(${script})(${paramsStr})`); }.call(ctx));
|
return await (function() { return eval(`const api = this; (${script})(${paramsStr})`); }.call(ctx));
|
||||||
});
|
}
|
||||||
|
|
||||||
return ret;
|
const timeouts = {};
|
||||||
|
const intervals = {};
|
||||||
|
|
||||||
|
function clearExistingJob(name) {
|
||||||
|
if (timeouts[name]) {
|
||||||
|
clearTimeout(timeouts[name]);
|
||||||
|
|
||||||
|
delete timeouts[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intervals[name]) {
|
||||||
|
clearInterval(intervals[name]);
|
||||||
|
|
||||||
|
delete intervals[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeJob(script, params, manualTransactionHandling) {
|
||||||
|
const ctx = new ScriptContext();
|
||||||
|
const paramsStr = getParams(params);
|
||||||
|
|
||||||
|
if (manualTransactionHandling) {
|
||||||
|
return await execute(ctx, script, paramsStr);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return await sql.doInTransaction(async () => execute(ctx, script, paramsStr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setJob(opts) {
|
||||||
|
const { name, runEveryMs, initialRunAfterMs } = opts;
|
||||||
|
|
||||||
|
clearExistingJob(name);
|
||||||
|
|
||||||
|
const jobFunc = () => executeJob(opts.job, opts.params, opts.manualTransactionHandling);
|
||||||
|
|
||||||
|
if (runEveryMs && runEveryMs > 0) {
|
||||||
|
intervals[name] = setInterval(jobFunc, runEveryMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initialRunAfterMs && initialRunAfterMs > 0) {
|
||||||
|
timeouts[name] = setTimeout(jobFunc, initialRunAfterMs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getParams(params) {
|
function getParams(params) {
|
||||||
|
if (!params) {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
return params.map(p => {
|
return params.map(p => {
|
||||||
if (typeof p === "string" && p.startsWith("!@#Function: ")) {
|
if (typeof p === "string" && p.startsWith("!@#Function: ")) {
|
||||||
return p.substr(13);
|
return p.substr(13);
|
||||||
@@ -30,5 +73,6 @@ function getParams(params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
executeScript
|
executeScript,
|
||||||
|
setJob
|
||||||
};
|
};
|
||||||
@@ -1,20 +1,33 @@
|
|||||||
const log = require('./log');
|
const log = require('./log');
|
||||||
const protected_session = require('./protected_session');
|
const protected_session = require('./protected_session');
|
||||||
const notes = require('./notes');
|
const notes = require('./notes');
|
||||||
|
const sql = require('./sql');
|
||||||
|
const utils = require('./utils');
|
||||||
const attributes = require('./attributes');
|
const attributes = require('./attributes');
|
||||||
const date_notes = require('./date_notes');
|
const date_notes = require('./date_notes');
|
||||||
|
const config = require('./config');
|
||||||
const Repository = require('./repository');
|
const Repository = require('./repository');
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
function ScriptContext(noteId, dataKey) {
|
function ScriptContext(dataKey) {
|
||||||
this.dataKey = protected_session.getDataKey(dataKey);
|
dataKey = protected_session.getDataKey(dataKey);
|
||||||
this.repository = new Repository(dataKey);
|
const repository = new Repository(dataKey);
|
||||||
|
|
||||||
|
this.axios = axios;
|
||||||
|
|
||||||
|
this.utils = {
|
||||||
|
unescapeHtml: utils.unescapeHtml,
|
||||||
|
isoDateTimeStr: utils.dateStr
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getInstanceName = () => config.General ? config.General.instanceName : null;
|
||||||
|
|
||||||
this.getNoteById = async function(noteId) {
|
this.getNoteById = async function(noteId) {
|
||||||
return this.repository.getNote(noteId);
|
return repository.getNote(noteId);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getNotesWithAttribute = async function (attrName, attrValue) {
|
this.getNotesWithAttribute = async function (attrName, attrValue) {
|
||||||
return await attributes.getNotesWithAttribute(this.dataKey, attrName, attrValue);
|
return await attributes.getNotesWithAttribute(dataKey, attrName, attrValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getNoteWithAttribute = async function (attrName, attrValue) {
|
this.getNoteWithAttribute = async function (attrName, attrValue) {
|
||||||
@@ -23,46 +36,22 @@ function ScriptContext(noteId, dataKey) {
|
|||||||
return notes.length > 0 ? notes[0] : null;
|
return notes.length > 0 ? notes[0] : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.createNote = async function (parentNoteId, name, jsonContent, extraOptions = {}) {
|
this.createNote = async function(parentNoteId, title, content = "", extraOptions = {}) {
|
||||||
const note = {
|
extraOptions.dataKey = dataKey;
|
||||||
title: name,
|
|
||||||
content: extraOptions.json ? JSON.stringify(jsonContent, null, '\t') : jsonContent,
|
|
||||||
target: 'into',
|
|
||||||
isProtected: extraOptions.isProtected !== undefined ? extraOptions.isProtected : false,
|
|
||||||
type: extraOptions.type,
|
|
||||||
mime: extraOptions.mime
|
|
||||||
};
|
|
||||||
|
|
||||||
if (extraOptions.json) {
|
return await notes.createNote(parentNoteId, title, content, extraOptions);
|
||||||
note.type = "code";
|
|
||||||
note.mime = "application/json";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!note.type) {
|
|
||||||
note.type = "text";
|
|
||||||
note.mime = "text/html";
|
|
||||||
}
|
|
||||||
|
|
||||||
const noteId = (await notes.createNewNote(parentNoteId, note, this.dataKey)).noteId;
|
|
||||||
|
|
||||||
if (extraOptions.attributes) {
|
|
||||||
for (const attrName in extraOptions.attributes) {
|
|
||||||
await attributes.createAttribute(noteId, attrName, extraOptions.attributes[attrName]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return noteId;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.createAttribute = attributes.createAttribute;
|
this.createAttribute = attributes.createAttribute;
|
||||||
|
|
||||||
this.updateEntity = this.repository.updateEntity;
|
this.updateEntity = repository.updateEntity;
|
||||||
|
|
||||||
this.log = function(message) {
|
this.log = message => log.info(`Script: ${message}`);
|
||||||
log.info(`Script: ${message}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
this.getRootCalendarNoteId = date_notes.getRootCalendarNoteId;
|
||||||
this.getDateNoteId = date_notes.getDateNoteId;
|
this.getDateNoteId = date_notes.getDateNoteId;
|
||||||
|
|
||||||
|
this.transaction = sql.doInTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ScriptContext;
|
module.exports = ScriptContext;
|
||||||
@@ -195,6 +195,7 @@ async function doInTransaction(func) {
|
|||||||
await transactionPromise;
|
await transactionPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let ret = null;
|
||||||
const error = new Error(); // to capture correct stack trace in case of exception
|
const error = new Error(); // to capture correct stack trace in case of exception
|
||||||
|
|
||||||
transactionActive = true;
|
transactionActive = true;
|
||||||
@@ -202,7 +203,7 @@ async function doInTransaction(func) {
|
|||||||
try {
|
try {
|
||||||
await beginTransaction();
|
await beginTransaction();
|
||||||
|
|
||||||
await func();
|
ret = await func();
|
||||||
|
|
||||||
await commit();
|
await commit();
|
||||||
|
|
||||||
@@ -223,6 +224,8 @@ async function doInTransaction(func) {
|
|||||||
if (transactionActive) {
|
if (transactionActive) {
|
||||||
await transactionPromise;
|
await transactionPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isDbUpToDate() {
|
async function isDbUpToDate() {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const randtoken = require('rand-token').generator({source: 'crypto'});
|
const randtoken = require('rand-token').generator({source: 'crypto'});
|
||||||
|
const unescape = require('unescape');
|
||||||
|
|
||||||
function newNoteId() {
|
function newNoteId() {
|
||||||
return randomString(12);
|
return randomString(12);
|
||||||
@@ -129,6 +130,10 @@ async function stopWatch(what, func) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function unescapeHtml(str) {
|
||||||
|
return unescape(str);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
randomSecureToken,
|
randomSecureToken,
|
||||||
randomString,
|
randomString,
|
||||||
@@ -153,5 +158,6 @@ module.exports = {
|
|||||||
getDateTimeForFile,
|
getDateTimeForFile,
|
||||||
sanitizeSql,
|
sanitizeSql,
|
||||||
assertArguments,
|
assertArguments,
|
||||||
stopWatch
|
stopWatch,
|
||||||
|
unescapeHtml
|
||||||
};
|
};
|
||||||
@@ -56,6 +56,8 @@
|
|||||||
<img src="images/icons/search.png" alt="Search in notes"/>
|
<img src="images/icons/search.png" alt="Search in notes"/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<input type="file" id="import-upload" style="display: none" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="search-box" class="hide-toggle" style="grid-area: search; display: none; padding: 10px; margin-top: 10px;">
|
<div id="search-box" class="hide-toggle" style="grid-area: search; display: none; padding: 10px; margin-top: 10px;">
|
||||||
@@ -167,7 +169,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="file" id="file-upload" style="display: none" />
|
<input type="file" id="attachment-upload" style="display: none" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="attribute-list">
|
<div id="attribute-list">
|
||||||
@@ -497,6 +499,7 @@
|
|||||||
<script src="javascripts/drag_and_drop.js"></script>
|
<script src="javascripts/drag_and_drop.js"></script>
|
||||||
<script src="javascripts/context_menu.js"></script>
|
<script src="javascripts/context_menu.js"></script>
|
||||||
<script src="javascripts/search_tree.js"></script>
|
<script src="javascripts/search_tree.js"></script>
|
||||||
|
<script src="javascripts/export.js"></script>
|
||||||
|
|
||||||
<!-- Note detail -->
|
<!-- Note detail -->
|
||||||
<script src="javascripts/note_editor.js"></script>
|
<script src="javascripts/note_editor.js"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user