Compare commits

..

16 Commits

Author SHA1 Message Date
azivner 4f3aa9cd79 release 0.22.0 2018-09-09 10:08:45 +02:00
azivner d248994143 updated windows scrypt binary 2018-09-09 09:46:15 +02:00
azivner 78518fbc60 note about platform support in README 2018-09-07 13:55:13 +02:00
azivner 50cd0c792c provide H4-H6, fixes #177 2018-09-07 13:47:26 +02:00
azivner 22e905aa99 enter on note in note tree leads to note title instead of content (second enter then goes to content) 2018-09-07 10:50:05 +02:00
azivner 8b41c1ef31 fix pasting HTML content into subnotes 2018-09-07 10:26:23 +02:00
azivner 492fde6f46 fix copy scrypt binary to correct directory 2018-09-06 23:23:19 +02:00
azivner 892a254e5a put missing options also to sqlInit 2018-09-06 23:21:14 +02:00
zadam 1ef3188213 cut selection and put it into subnote, fixes #171 2018-09-06 22:58:46 +02:00
azivner c76329e671 increased default min pane width to 350px 2018-09-06 12:02:42 +02:00
azivner c95e409b5e left pane sizing taking effect 2018-09-06 11:54:04 +02:00
azivner ff086569f2 saving options for left pane sizing 2018-09-06 11:17:30 +02:00
azivner bdc2fa1019 small refactoring of template and CSS 2018-09-06 09:33:38 +02:00
azivner 6668b55d6e use node 10 for docker and server package 2018-09-05 20:35:33 +02:00
azivner 46f37f7426 using @mlink/scrypt for node 10 compatibility 2018-09-05 15:14:20 +02:00
azivner bc669c7b81 markdown wiki reference on README 2018-09-03 21:06:24 +02:00
27 changed files with 578 additions and 420 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$USER_HOME$/trilium-data/document.db</jdbc-url>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/../trilium-data/document.db</jdbc-url>
<driver-properties>
<property name="enable_load_extension" value="true" />
</driver-properties>
+1 -1
View File
@@ -1,4 +1,4 @@
FROM node:8.11.2
FROM node:10.9.0
RUN apt-get update && apt-get install -y nasm
+2 -1
View File
@@ -21,12 +21,13 @@ See other pictures in [screenshot tour](https://github.com/zadam/trilium/wiki/Sc
* [Scripting](https://github.com/zadam/trilium/wiki/Scripts) - see [Advanced showcases](https://github.com/zadam/trilium/wiki/Advanced-showcases)
* Scales well in both usability and performance upwards of 100 000 notes
* [Night theme](https://github.com/zadam/trilium/wiki/Themes)
* [Markdown import & export](https://github.com/zadam/trilium/wiki/Markdown)
## Builds
Trilium is provided as either desktop application ([Electron](https://electronjs.org)-based) or web application hosted on your server.
* If you want to use Trilium on the desktop, download binary release for your platform from [latest release](https://github.com/zadam/trilium/releases/latest), unzip the package and run ```trilium``` executable.
* If you want to use Trilium on the desktop, download binary release for your platform (currently Linux and Windows are supported) from [latest release](https://github.com/zadam/trilium/releases/latest), unzip the package and run ```trilium``` executable.
* If you want to install Trilium on server, follow [this page](https://github.com/zadam/trilium/wiki/Server-installation).
* Currently only recent Chrome and Firefox are supported (tested) browsers.
+2 -2
View File
@@ -7,13 +7,13 @@ fi
VERSION=$1
PKG_DIR=dist/trilium-linux-x64-server
NODE_VERSION=8.11.4
NODE_VERSION=10.9.0
rm -r $PKG_DIR
mkdir $PKG_DIR
cd $PKG_DIR
wget https://nodejs.org/dist/latest-v8.x/node-v${NODE_VERSION}-linux-x64.tar.xz
wget https://nodejs.org/dist/latest-v10.x/node-v${NODE_VERSION}-linux-x64.tar.xz
tar xvfJ node-v${NODE_VERSION}-linux-x64.tar.xz
rm node-v${NODE_VERSION}-linux-x64.tar.xz
+1 -1
View File
@@ -29,7 +29,7 @@ cp -r bin/deps/sqlite/* $WIN_RES_DIR/node_modules/sqlite3/lib/binding/
cp bin/deps/image/cjpeg.exe $WIN_RES_DIR/node_modules/mozjpeg/vendor/
cp bin/deps/image/pngquant.exe $WIN_RES_DIR/node_modules/imagemin-pngquant/node_modules/pngquant-bin/vendor/
cp bin/deps/image/gifsicle.exe $WIN_RES_DIR/node_modules/giflossy/vendor/
cp bin/deps/scrypt.node $WIN_RES_DIR/node_modules/scrypt/build/Release/
cp bin/deps/scrypt.node $WIN_RES_DIR/node_modules/@mlink/scrypt/build/Release/
echo "Cleaning up unnecessary binaries from all builds"
Binary file not shown.
@@ -0,0 +1,5 @@
INSERT INTO options (name, value, dateCreated, dateModified, isSynced)
VALUES ('leftPaneMinWidth', '350', '2018-07-29T18:31:00.874Z', '2018-07-29T18:31:00.874Z', 0);
INSERT INTO options (name, value, dateCreated, dateModified, isSynced)
VALUES ('leftPaneWidthPercent', '20', '2018-07-29T18:31:00.874Z', '2018-07-29T18:31:00.874Z', 0);
+22 -14
View File
@@ -1,6 +1,6 @@
{
"name": "trilium",
"version": "0.20.2",
"version": "0.21.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -502,6 +502,22 @@
}
}
},
"@mlink/scrypt": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/@mlink/scrypt/-/scrypt-6.1.2.tgz",
"integrity": "sha512-7w1QoOih2eW0d0Lj5SK9mFV/8OhtZqYnT0IQiY/iCEcjdGAAt145YAAiq21iVV+H0Xe62fnF6VbZPMO3+qrSAQ==",
"requires": {
"bindings": "1.3.0",
"nan": "2.11.0"
},
"dependencies": {
"nan": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz",
"integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw=="
}
}
},
"@mrmlnc/readdir-enhanced": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
@@ -1908,6 +1924,11 @@
"chainsaw": "0.1.0"
}
},
"bindings": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz",
"integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw=="
},
"bl": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
@@ -8953,11 +8974,6 @@
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
"dev": true
},
"nan": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz",
"integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY="
},
"nanomatch": {
"version": "1.2.9",
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz",
@@ -11272,14 +11288,6 @@
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"scrypt": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/scrypt/-/scrypt-6.0.3.tgz",
"integrity": "sha1-BOAUpWgrU/pQwtXM4WfXGcBthw0=",
"requires": {
"nan": "2.7.0"
}
},
"seek-bzip": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz",
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "trilium",
"description": "Trilium Notes",
"version": "0.21.0",
"version": "0.22.0",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
@@ -60,7 +60,6 @@
"request-promise": "4.2.2",
"rimraf": "2.6.2",
"sanitize-filename": "1.6.1",
"scrypt": "6.0.3",
"serve-favicon": "2.5.0",
"session-file-store": "1.2.0",
"simple-node-logger": "0.93.37",
@@ -69,7 +68,8 @@
"turndown": "^5.0.1",
"unescape": "1.0.1",
"ws": "6.0.0",
"xml2js": "0.4.19"
"xml2js": "0.4.19",
"@mlink/scrypt": "6.1.2"
},
"devDependencies": {
"electron": "2.0.7",
+30
View File
@@ -48,7 +48,10 @@ export default {
addTabHandler((function() {
const $themeSelect = $("#theme-select");
const $zoomFactorSelect = $("#zoom-factor-select");
const $leftPaneMinWidth = $("#left-pane-min-width");
const $leftPaneWidthPercent = $("#left-pane-width-percent");
const $html = $("html");
const $container = $("#container");
function optionsLoaded(options) {
$themeSelect.val(options.theme);
@@ -59,6 +62,9 @@ addTabHandler((function() {
else {
$zoomFactorSelect.prop('disabled', true);
}
$leftPaneMinWidth.val(options.leftPaneMinWidth);
$leftPaneWidthPercent.val(options.leftPaneWidthPercent);
}
$themeSelect.change(function() {
@@ -75,6 +81,30 @@ addTabHandler((function() {
zoomService.setZoomFactorAndSave(newZoomFactor);
});
function resizeLeftPanel() {
const leftPanePercent = parseInt($leftPaneWidthPercent.val());
const rightPanePercent = 100 - leftPanePercent;
const leftPaneMinWidth = $leftPaneMinWidth.val();
$container.css("grid-template-columns", `minmax(${leftPaneMinWidth}px, ${leftPanePercent}fr) ${rightPanePercent}fr`);
}
$leftPaneMinWidth.change(function() {
const newMinWidth = $(this).val();
resizeLeftPanel();
server.put('options/leftPaneMinWidth/' + newMinWidth);
});
$leftPaneWidthPercent.change(function() {
const newWidthPercent = $(this).val();
resizeLeftPanel();
server.put('options/leftPaneWidthPercent/' + newWidthPercent);
});
return {
optionsLoaded
};
@@ -1,4 +1,5 @@
import treeService from './tree.js';
import infoService from './info.js';
import protectedSessionHolder from './protected_session_holder.js';
import utils from './utils.js';
import server from './server.js';
@@ -8,6 +9,8 @@ function exportSubtree(noteId, format) {
"?protectedSessionId=" + encodeURIComponent(protectedSessionHolder.getProtectedSessionId());
utils.download(url);
infoService.showMessage("Export to file has been finished.");
}
let importNoteId;
@@ -513,10 +513,8 @@ async function loadNote(noteId) {
return new NoteFull(treeCache, row);
}
function focus() {
const note = getCurrentNote();
getComponent(note.type).focus();
function focusOnTitle() {
$noteTitle.focus();
}
messagingService.subscribeToSyncMessages(syncData => {
@@ -554,7 +552,7 @@ export default {
getCurrentNoteType,
getCurrentNoteId,
newNoteCreated,
focus,
focusOnTitle,
getAttributes,
showAttributes,
refreshAttributes,
@@ -18,16 +18,7 @@ async function show() {
// textEditor might have been initialized during previous await so checking again
// looks like double initialization can freeze CKEditor pretty badly
if (!textEditor) {
textEditor = await BalloonEditor.create($noteDetailText[0], {
heading: {
options: [
{ model: 'paragraph', title: 'Paragraph', class: 'ck-heading_paragraph' },
{ model: 'heading1', view: 'h1', title: 'Heading 1', class: 'ck-heading_heading1' },
{ model: 'heading2', view: 'h2', title: 'Heading 2', class: 'ck-heading_heading2' },
{ model: 'heading3', view: 'h3', title: 'Heading 3', class: 'ck-heading_heading3' }
]
}
});
textEditor = await BalloonEditor.create($noteDetailText[0]);
onNoteChange(noteDetailService.noteChanged);
}
+45 -7
View File
@@ -443,10 +443,10 @@ async function setNoteTitle(noteId, title) {
async function createNewTopLevelNote() {
const rootNode = getNodesByNoteId('root')[0];
await createNote(rootNode, "root", "into");
await createNote(rootNode, "root", "into", false);
}
async function createNote(node, parentNoteId, target, isProtected) {
async function createNote(node, parentNoteId, target, isProtected, saveSelection = false) {
utils.assertArguments(node, parentNoteId, target);
// if isProtected isn't available (user didn't enter password yet), then note is created as unencrypted
@@ -455,15 +455,33 @@ async function createNote(node, parentNoteId, target, isProtected) {
isProtected = false;
}
const newNoteName = "new note";
if (noteDetailService.getCurrentNoteType() !== 'text') {
saveSelection = false;
}
let title, content;
if (saveSelection) {
[title, content] = parseSelectedHtml(window.cutToNote.getSelectedHtml());
}
const newNoteName = title || "new note";
const {note, branch} = await server.post('notes/' + parentNoteId + '/children', {
title: newNoteName,
content: content,
target: target,
target_branchId: node.data.branchId,
isProtected: isProtected
});
if (saveSelection) {
// we remove the selection only after it was saved to server to make sure we don't lose anything
window.cutToNote.removeSelection();
}
await noteDetailService.saveNoteIfChanged();
const noteEntity = new NoteShort(treeCache, note);
const branchEntity = new Branch(treeCache, branch);
@@ -506,6 +524,22 @@ async function createNote(node, parentNoteId, target, isProtected) {
infoService.showMessage("Created!");
}
/* If first element is heading, parse it out and use it as a new heading. */
function parseSelectedHtml(selectedHtml) {
const dom = $.parseHTML(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)
const content = selectedHtml.replace(dom[0].outerHTML, "");
return [title, content];
}
else {
return [null, selectedHtml];
}
}
async function sortAlphabetically(noteId) {
await server.put('notes/' + noteId + '/sort');
@@ -539,14 +573,18 @@ utils.bindShortcut('ctrl+o', () => {
const parentNoteId = node.data.parentNoteId;
const isProtected = treeUtils.getParentProtectedStatus(node);
createNote(node, parentNoteId, 'after', isProtected);
createNote(node, parentNoteId, 'after', isProtected, true);
});
utils.bindShortcut('ctrl+p', () => {
function createNoteInto() {
const node = getCurrentNode();
createNote(node, node.data.noteId, 'into', node.data.isProtected);
});
createNote(node, node.data.noteId, 'into', node.data.isProtected, true);
}
window.glob.createNoteInto = createNoteInto;
utils.bindShortcut('ctrl+p', createNoteInto);
utils.bindShortcut('ctrl+del', () => {
const node = getCurrentNode();
@@ -100,7 +100,7 @@ const keyBindings = {
return false;
},
"return": node => {
noteDetailService.focus();
noteDetailService.focusOnTitle();
return false;
},
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+39 -1
View File
@@ -1,3 +1,14 @@
/* We change h5 and h6 because by default (bootstrap) they are smaller than normal text */
h5 {
font-size: 16px;
font-weight: 500;
}
h6 {
font-size: 14px;
font-weight: 600;
}
#container {
margin: 0 auto; /* center */
height: 100vh;
@@ -6,7 +17,6 @@
grid-template-areas: "header header"
"left-pane title"
"left-pane note-detail";
grid-template-columns: 29% 69.5%;
grid-template-rows: auto
auto
1fr;
@@ -23,6 +33,34 @@
padding: 4px;
}
#left-pane {
grid-area: left-pane;
display: flex;
flex-direction: column;
}
#global-buttons {
display: flex;
justify-content: space-around;
padding: 10px 0 10px 0;
margin: 0 10px 0 16px;
border: 1px solid #ccc;
}
#search-box {
display: none;
padding: 10px;
margin-top: 10px;
}
#title-container {
grid-area: title;
}
.tdialog {
display: none; /* hidden by default */
}
#note-detail-wrapper {
position: relative;
overflow: hidden;
+4
View File
@@ -15,6 +15,10 @@ async function importToBranch(req) {
const parentNoteId = req.params.parentNoteId;
const file = req.file;
if (file) {
return [400, "No file has been uploaded"];
}
const parentNote = await repository.getNote(parentNoteId);
if (!parentNote) {
+2 -5
View File
@@ -6,13 +6,10 @@ const log = require('../../services/log');
// options allowed to be updated directly in options dialog
const ALLOWED_OPTIONS = ['protectedSessionTimeout', 'noteRevisionSnapshotTimeInterval',
'zoomFactor', 'theme', 'syncServerHost', 'syncServerTimeout', 'syncProxy'];
'zoomFactor', 'theme', 'syncServerHost', 'syncServerTimeout', 'syncProxy', 'leftPaneMinWidth', 'leftPaneWidthPercent'];
async function getOptions() {
const options = await sql.getMap("SELECT name, value FROM options WHERE name IN ("
+ ALLOWED_OPTIONS.map(x => '?').join(",") + ")", ALLOWED_OPTIONS);
return options;
return await optionService.getOptionsMap(ALLOWED_OPTIONS);
}
async function updateOption(req) {
+6 -1
View File
@@ -7,8 +7,13 @@ const config = require('../services/config');
const optionService = require('../services/options');
async function index(req, res) {
const options = await optionService.getOptionsMap();
res.render('index', {
theme: await optionService.getOption('theme'),
theme: options.theme,
leftPaneMinWidth: parseInt(options.leftPaneMinWidth),
leftPaneWidthPercent: parseInt(options.leftPaneWidthPercent),
rightPaneWidthPercent: 100 - parseInt(options.leftPaneWidthPercent),
sourceId: await sourceIdService.generateSourceId(),
maxSyncIdAtLoad: await sql.getValue("SELECT MAX(id) FROM sync"),
instanceName: config.General ? config.General.instanceName : null,
+1 -1
View File
@@ -3,7 +3,7 @@
const build = require('./build');
const packageJson = require('../../package');
const APP_DB_VERSION = 112;
const APP_DB_VERSION = 113;
const SYNC_VERSION = 1;
module.exports = {
+1 -1
View File
@@ -1 +1 @@
module.exports = { buildDate:"2018-09-03T20:18:28+02:00", buildRevision: "f2712e92bbf8ded9f4d56a6a64c92793b586df65" };
module.exports = { buildDate:"2018-09-09T10:08:45+02:00", buildRevision: "d24899414364b37fc7b270e11fcabf0305b58bb8" };
+2 -2
View File
@@ -1,7 +1,7 @@
"use strict";
const optionService = require('./options');
const scrypt = require('scrypt');
const scrypt = require('@mlink/scrypt');
async function getVerificationHash(password) {
const salt = await optionService.getOption('passwordVerificationSalt');
@@ -17,7 +17,7 @@ async function getPasswordDerivedKey(password) {
async function getScryptHash(password, salt) {
const hashed = scrypt.hashSync(password,
{N: 16384, r:8, p:1},
{N: 14, r:8, p:1},
32,
salt);
+21 -1
View File
@@ -1,3 +1,5 @@
const utils = require('./utils');
async function getOption(name) {
const option = await require('./repository').getOption(name);
@@ -31,8 +33,26 @@ async function createOption(name, value, isSynced) {
}).save();
}
async function getOptions(allowedOptions) {
let options = await require('./repository').getEntities("SELECT * FROM options ORDER BY name");
if (allowedOptions) {
options = options.filter(opt => allowedOptions.includes(opt.name));
}
return options;
}
async function getOptionsMap(allowedOptions) {
const options = await getOptions(allowedOptions);
return utils.toObject(options, opt => [opt.name, opt.value]);
}
module.exports = {
getOption,
setOption,
createOption
createOption,
getOptions,
getOptionsMap
};
+3
View File
@@ -42,6 +42,9 @@ async function initNotSyncedOptions(initialized, startNotePath = 'root', syncSer
await optionService.createOption('zoomFactor', 1.0, false);
await optionService.createOption('theme', 'white', false);
await optionService.createOption('leftPaneMinWidth', '350', false);
await optionService.createOption('leftPaneWidthPercent', '20', false);
await optionService.createOption('syncServerHost', syncServerHost, false);
await optionService.createOption('syncServerTimeout', 5000, false);
await optionService.createOption('syncProxy', syncProxy, false);
+377 -360
View File
@@ -5,7 +5,7 @@
<title>Trilium Notes</title>
</head>
<body>
<div id="container" style="display:none;">
<div id="container" style="display:none; grid-template-columns: minmax(<%= leftPaneMinWidth %>px, <%= leftPaneWidthPercent %>fr) <%= rightPaneWidthPercent %>fr">
<div id="header" class="hide-toggle">
<div id="history-navigation" style="display: none;">
<a id="history-back-button" title="Go to previous note." class="icon-action"
@@ -49,8 +49,8 @@
</div>
</div>
<div style="grid-area: left-pane; display: flex; flex-direction: column;" class="hide-toggle">
<div style="display: flex; justify-content: space-around; padding: 10px 0 10px 0; margin: 0 10px 0 16px; border: 1px solid #ccc;">
<div id="left-pane" class="hide-toggle">
<div id="global-buttons">
<a id="create-top-level-note-button" title="Create new top level note" class="icon-action"
style="background: url('/images/icons/file-plus-24.png')"></a>
@@ -66,7 +66,7 @@
<input type="file" id="import-upload" style="display: none" />
<div id="search-box" style="display: none; padding: 10px; margin-top: 10px;">
<div id="search-box">
<div style="display: flex; align-items: center;">
<input name="search-text" placeholder="Search text, labels" style="flex-grow: 100; margin-left: 5px; margin-right: 5px;" autocomplete="off">
<button id="do-search-button" class="btn btn-sm icon-button" title="Search (enter)"
@@ -97,7 +97,7 @@
<div id="tree"></div>
</div>
<div style="grid-area: title;">
<div id="title-container">
<div style="display: flex; align-items: center;">
<div class="dropdown hide-toggle">
<button id="note-path-list-button" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle">
@@ -269,363 +269,379 @@
<span id="attribute-list-inner"></span>
</div>
</div>
</div>
<div id="add-link-dialog" title="Add note link" style="display: none;">
<form id="add-link-form">
<div id="add-link-type-div" class="radio">
<label title="Add HTML link to the selected note at cursor in current note">
<input type="radio" name="add-link-type" value="html"/>
add normal HTML link</label>
<div id="add-link-dialog" class="tdialog" title="Add note link">
<form id="add-link-form">
<div id="add-link-type-div" class="radio">
<label title="Add HTML link to the selected note at cursor in current note">
<input type="radio" name="add-link-type" value="html"/>
add normal HTML link</label>
<label title="Add selected note as a child of current note">
<input type="radio" name="add-link-type" value="selected-to-current"/>
add selected note to current note</label>
<label title="Add selected note as a child of current note">
<input type="radio" name="add-link-type" value="selected-to-current"/>
add selected note to current note</label>
<label title="Add current note as a child of the selected note">
<input type="radio" name="add-link-type" value="current-to-selected"/>
add current note to selected note</label>
</div>
<label title="Add current note as a child of the selected note">
<input type="radio" name="add-link-type" value="current-to-selected"/>
add current note to selected note</label>
</div>
<div class="form-group">
<label for="note-autocomplete">Note</label>
<div class="form-group">
<label for="note-autocomplete">Note</label>
<div class="input-group">
<input id="note-autocomplete" class="form-control" placeholder="search for note by its name" style="width: 100%;">
<input id="note-autocomplete" class="form-control" placeholder="search for note by its name" style="width: 100%;">
<span class="input-group-addon show-recent-notes-button" title="Show recent notes"></span>
</div>
</div>
<div class="form-group" id="add-link-title-form-group">
<label for="link-title">Link title</label>
<input id="link-title" class="form-control" style="width: 100%;">
</div>
<div class="form-group" id="add-link-prefix-form-group" title="Cloned note will be shown in note tree with given prefix">
<label for="clone-prefix">Prefix (optional)</label>
<input id="clone-prefix" class="form-control" style="width: 100%;">
</div>
<div style="display: flex; justify-content: space-between">
<button class="btn btn-primary">Add note link <kbd>enter</kbd></button>
<button class="btn btn-sm" type="button" data-help-page="Links">
<i class="glyphicon glyphicon-info-sign"></i> Help
</button>
</div>
</form>
</div>
<div id="jump-to-note-dialog" title="Jump to note" style="display: none;">
<div class="form-group">
<label for="jump-to-note-autocomplete">Note</label>
<div class="input-group">
<input id="jump-to-note-autocomplete" class="form-control" placeholder="search for note by its name" style="width: 100%;">
<span class="input-group-addon show-recent-notes-button" title="Show recent notes"></span>
</div>
</div>
<button id="show-in-full-text-button" class="btn btn-sm">Search in full text <kbd>ctrl+enter</kbd></button>
</div>
<div id="protected-session-password-dialog" title="Protected session" style="display: none;">
<form id="protected-session-password-form">
<div class="form-group">
<label for="protected-session-password">To proceed with requested action you need to start protected session by entering password:</label>
<input id="protected-session-password" class="form-control" type="password">
</div>
<div style="display: flex; justify-content: space-between;">
<button class="btn btn-sm">Start protected session <kbd>enter</kbd></button>
<button class="btn btn-sm" type="button" data-help-page="Protected-notes">
<i class="glyphicon glyphicon-info-sign"></i> Help
</button>
</div>
</form>
</div>
<div id="options-dialog" title="Options" style="display: none;">
<div id="options-tabs">
<ul>
<li><a href="#appearance">Appearance</a></li>
<li><a href="#change-password">Change password</a></li>
<li><a href="#protected-session-timeout">Protected session</a></li>
<li><a href="#note-revision-snapshot-time-interval">Note revisions</a></li>
<li><a href="#sync-setup">Sync</a></li>
<li><a href="#advanced">Advanced</a></li>
<li><a href="#about">About Trilium</a></li>
</ul>
<div id="appearance">
<p>Settings on this options tab are saved automatically after each change.</p>
<form>
<div class="form-group">
<label for="theme-select">Theme</label>
<select class="form-control" id="theme-select">
<option value="white">White</option>
<option value="dark">Dark</option>
<option value="black">Black</option>
</select>
</div>
<div class="form-group">
<label for="zoom-factor-select">Zoom factor (desktop build only)</label>
<input type="number" class="form-control" id="zoom-factor-select" min="0.3" max="2.0" step="0.1"/>
</div>
<p>Zooming can be controlled with CTRL-+ and CTRL-= shortcuts as well.</p>
</form>
</div>
<div id="change-password">
<form id="change-password-form">
<div class="form-group">
<label for="old-password">Old password</label>
<input class="form-control" id="old-password" type="password">
</div>
<div class="form-group">
<label for="new-password1">New password</label>
<input class="form-control" id="new-password1" type="password">
</div>
<div class="form-group">
<label for="new-password2">New password once more</label>
<input class="form-control" id="new-password2" type="password">
</div>
<button class="btn btn-sm">Change password</button>
</form>
</div>
<div id="protected-session-timeout">
<p>Protected session timeout is a time period after which the protected session is wiped out from
browser's memory. This is measured from the last interaction with protected notes.</p>
<form id="protected-session-timeout-form">
<div class="form-group">
<label for="protected-session-timeout-in-seconds">Protected session timeout (in seconds)</label>
<input class="form-control" id="protected-session-timeout-in-seconds" type="number">
</div>
<div style="display: flex; justify-content: space-between;">
<button class="btn btn-sm">Save</button>
<button class="btn btn-sm" type="button" data-help-page="Protected-notes">
<i class="glyphicon glyphicon-info-sign"></i> Help
</button>
</div>
</form>
</div>
<div id="note-revision-snapshot-time-interval">
<p>Note revision snapshot time interval is time in seconds after which new note revision will be created for the note.</p>
<form id="note-revision-snapshot-time-interval-form">
<div class="form-group">
<label for="note-revision-snapshot-time-interval-in-seconds">Note revision snapshot time interval (in seconds)</label>
<input class="form-control" id="note-revision-snapshot-time-interval-in-seconds" type="number">
</div>
<div style="display: flex; justify-content: space-between;">
<button class="btn btn-sm">Save</button>
<button class="btn btn-sm" type="button" data-help-page="Note-revisions">
<i class="glyphicon glyphicon-info-sign"></i> Help
</button>
</div>
</form>
</div>
<div id="sync-setup">
<h4 style="margin-top: 0px;">Sync configuration</h4>
<form id="sync-setup-form">
<div class="form-group">
<label for="sync-server-host">Server instance address</label>
<input class="form-control" id="sync-server-host" placeholder="https://<host>:<port>">
</div>
<div class="form-group">
<label for="sync-server-timeout">Sync timeout (milliseconds)</label>
<input class="form-control" id="sync-server-timeout" min="1" max="10000000" type="number">
</div>
<div class="form-group">
<label for="sync-proxy">Sync proxy server (optional)</label>
<input class="form-control" id="sync-proxy" placeholder="https://<host>:<port>">
</div>
<div style="display: flex; justify-content: space-between;">
<button class="btn btn-sm">Save</button>
<button class="btn btn-sm" type="button" data-help-page="Synchronization">
<i class="glyphicon glyphicon-info-sign"></i> Help
</button>
</div>
</form>
<h4>Sync test</h4>
<p>This will test connection and handshake to the sync server.</p>
<button id="test-sync-button" class="btn btn-sm">Test sync</button>
<h4>Sync document to the server instance</h4>
<p>This is used when you want to sync your local document to the server instance configured above. This is a one time action after which the documents are synced automatically and transparently.</p>
<button id="sync-to-server-button" class="btn btn-sm">Sync local document to the server instance</button>
</div>
<div id="advanced">
<h4 style="margin-top: 0px;">Sync</h4>
<button id="force-full-sync-button" class="btn btn-sm">Force full sync</button>
<br/>
<br/>
<button id="fill-sync-rows-button" class="btn btn-sm">Fill sync rows</button>
<h4>Debugging</h4>
<button id="anonymize-button" class="btn btn-sm">Save anonymized database</button><br/><br/>
<p>This action will create a new copy of the database and anonymise it (remove all note content and leave only structure and metadata)
for sharing online for debugging purposes without fear of leaking your personal data.</p>
<h4>Image cleanup</h4>
<p>This will remove all image data of images not used in any current version of note from the database (metadata will remain).
This means that some images can disappear from note revisions.</p>
<button id="cleanup-unused-images-button" class="btn btn-sm">Permanently cleanup unused images</button>
<h4>Vacuum database</h4>
<p>This will rebuild database which will typically result in smaller database file. No data will be actually changed.</p>
<button id="vacuum-database-button" class="btn btn-sm">Vacuum database</button>
</div>
<div id="about">
<table class="table">
<tr>
<th>App version:</th>
<td id="app-version"></td>
</tr>
<tr>
<th>DB version:</th>
<td id="db-version"></td>
</tr>
<tr>
<th>Sync version:</th>
<td id="sync-version"></td>
</tr>
<tr>
<th>Build date:</th>
<td id="build-date"></td>
</tr>
<tr>
<th>Build revision:</th>
<td><a href="" target="_blank" id="build-revision"></a></td>
</tr>
</table>
</div>
</div>
</div>
<div id="note-revisions-dialog" title="Note revisions" style="display: none;">
<div style="display: flex;">
<select id="note-revision-list" size="25" style="width: 150px; height: 630px;">
</select>
<div id="note-revision-content-wrapper" style="flex-grow: 1; margin-left: 20px;">
<div style="display: flex">
<h3 id="note-revision-title" style="margin: 3px; flex-grow: 100;"></h3>
<div>
<button class="btn btn-sm" type="button" data-help-page="Note-revisions">
<i class="glyphicon glyphicon-info-sign"></i> Help
</button>
<span class="input-group-addon show-recent-notes-button" title="Show recent notes"></span>
</div>
</div>
<div id="note-revision-content" style="height: 600px; width: 600px; overflow: auto;"></div>
</div>
<div class="form-group" id="add-link-title-form-group">
<label for="link-title">Link title</label>
<input id="link-title" class="form-control" style="width: 100%;">
</div>
<div class="form-group" id="add-link-prefix-form-group" title="Cloned note will be shown in note tree with given prefix">
<label for="clone-prefix">Prefix (optional)</label>
<input id="clone-prefix" class="form-control" style="width: 100%;">
</div>
<div style="display: flex; justify-content: space-between">
<button class="btn btn-primary">Add note link <kbd>enter</kbd></button>
<button class="btn btn-sm" type="button" data-help-page="Links">
<i class="glyphicon glyphicon-info-sign"></i> Help
</button>
</div>
</form>
</div>
</div>
<div id="recent-changes-dialog" title="Recent changes" style="display: none; padding: 20px;">
</div>
<div id="event-log-dialog" title="Event log" style="display: none; padding: 20px;">
<ul id="event-log-list"></ul>
</div>
<div id="edit-tree-prefix-dialog" title="Edit tree prefix" style="display: none; padding: 20px;">
<form id="edit-tree-prefix-form">
<div id="jump-to-note-dialog" class="tdialog" title="Jump to note">
<div class="form-group">
<label for="tree-prefix-input">Prefix: </label> &nbsp;
<input id="tree-prefix-input" style="width: 20em;"> - <span id="tree-prefix-note-title"></span>
<label for="jump-to-note-autocomplete">Note</label>
<div class="input-group">
<input id="jump-to-note-autocomplete" class="form-control" placeholder="search for note by its name" style="width: 100%;">
<span class="input-group-addon show-recent-notes-button" title="Show recent notes"></span>
</div>
</div>
<div style="display: flex; justify-content: space-between;">
<button class="btn btn-sm">Save</button>
<button class="btn btn-sm" type="button" data-help-page="Tree-concepts#prefix">
<i class="glyphicon glyphicon-info-sign"></i> Help
</button>
</div>
</form>
</div>
<div id="sql-console-dialog" title="SQL console" style="display: none; padding: 20px;">
<div id="sql-console-query"></div>
<div style="text-align: center">
<button class="btn btn-danger" id="sql-console-execute">Execute <kbd>CTRL+ENTER</kbd></button>
<button id="show-in-full-text-button" class="btn btn-sm">Search in full text <kbd>ctrl+enter</kbd></button>
</div>
<table id="sql-console-results" class="table table-striped" style="overflow: scroll; width: 100%;">
<thead></thead>
<tbody></tbody>
</table>
</div>
<div id="protected-session-password-dialog" class="tdialog" title="Protected session">
<form id="protected-session-password-form">
<div class="form-group">
<label for="protected-session-password">To proceed with requested action you need to start protected session by entering password:</label>
<input id="protected-session-password" class="form-control" type="password">
</div>
<div id="note-source-dialog" title="Note source" style="display: none; padding: 20px;">
<textarea id="note-source" readonly="readonly"></textarea>
</div>
<div style="display: flex; justify-content: space-between;">
<button class="btn btn-sm">Start protected session <kbd>enter</kbd></button>
<div id="attributes-dialog" title="Note attributes" style="display: none; padding: 20px;">
<form data-bind="submit: save">
<div style="display: flex; justify-content: space-between;">
<button class="btn btn-large" style="width: 200px;" id="save-attributes-button" type="submit">Save changes <kbd>enter</kbd></button>
<button class="btn btn-sm" type="button" data-help-page="Protected-notes">
<i class="glyphicon glyphicon-info-sign"></i> Help
</button>
</div>
</form>
</div>
<button class="btn btn-sm" type="button" data-help-page="Attributes">
<i class="glyphicon glyphicon-info-sign"></i> Help
</button>
<div id="options-dialog" class="tdialog" title="Options">
<div id="options-tabs">
<ul>
<li><a href="#appearance">Appearance</a></li>
<li><a href="#change-password">Change password</a></li>
<li><a href="#protected-session-timeout">Protected session</a></li>
<li><a href="#note-revision-snapshot-time-interval">Note revisions</a></li>
<li><a href="#sync-setup">Sync</a></li>
<li><a href="#advanced">Advanced</a></li>
<li><a href="#about">About Trilium</a></li>
</ul>
<div id="appearance">
<p>Settings on this options tab are saved automatically after each change.</p>
<form>
<div class="form-group">
<label for="theme-select">Theme</label>
<select class="form-control" id="theme-select">
<option value="white">White</option>
<option value="dark">Dark</option>
<option value="black">Black</option>
</select>
</div>
<div class="form-group">
<label for="zoom-factor-select">Zoom factor (desktop build only)</label>
<input type="number" class="form-control" id="zoom-factor-select" min="0.3" max="2.0" step="0.1"/>
</div>
<p>Zooming can be controlled with CTRL-+ and CTRL-= shortcuts as well.</p>
<h3>Left pane sizing</h3>
<div class="form-group">
<label for="left-pane-min-width">Left pane minimum width (in pixels)</label>
<input type="number" class="form-control" id="left-pane-min-width" min="100" max="2000" step="1"/>
</div>
<div class="form-group">
<label for="left-pane-min-width">Left pane width percent of window size</label>
<input type="number" class="form-control" id="left-pane-width-percent" min="0" max="99" step="1"/>
</div>
<p>Left pane width is calculated from the percent of window size, if this is smaller than minimum width, then minimum width is used. If you want to have fixed width left pane, set minimum width to the desired width and set percent to 0.</p>
</form>
</div>
<div id="change-password">
<form id="change-password-form">
<div class="form-group">
<label for="old-password">Old password</label>
<input class="form-control" id="old-password" type="password">
</div>
<div class="form-group">
<label for="new-password1">New password</label>
<input class="form-control" id="new-password1" type="password">
</div>
<div class="form-group">
<label for="new-password2">New password once more</label>
<input class="form-control" id="new-password2" type="password">
</div>
<button class="btn btn-sm">Change password</button>
</form>
</div>
<div id="protected-session-timeout">
<p>Protected session timeout is a time period after which the protected session is wiped out from
browser's memory. This is measured from the last interaction with protected notes.</p>
<form id="protected-session-timeout-form">
<div class="form-group">
<label for="protected-session-timeout-in-seconds">Protected session timeout (in seconds)</label>
<input class="form-control" id="protected-session-timeout-in-seconds" type="number">
</div>
<div style="display: flex; justify-content: space-between;">
<button class="btn btn-sm">Save</button>
<button class="btn btn-sm" type="button" data-help-page="Protected-notes">
<i class="glyphicon glyphicon-info-sign"></i> Help
</button>
</div>
</form>
</div>
<div id="note-revision-snapshot-time-interval">
<p>Note revision snapshot time interval is time in seconds after which new note revision will be created for the note.</p>
<form id="note-revision-snapshot-time-interval-form">
<div class="form-group">
<label for="note-revision-snapshot-time-interval-in-seconds">Note revision snapshot time interval (in seconds)</label>
<input class="form-control" id="note-revision-snapshot-time-interval-in-seconds" type="number">
</div>
<div style="display: flex; justify-content: space-between;">
<button class="btn btn-sm">Save</button>
<button class="btn btn-sm" type="button" data-help-page="Note-revisions">
<i class="glyphicon glyphicon-info-sign"></i> Help
</button>
</div>
</form>
</div>
<div id="sync-setup">
<h4 style="margin-top: 0px;">Sync configuration</h4>
<form id="sync-setup-form">
<div class="form-group">
<label for="sync-server-host">Server instance address</label>
<input class="form-control" id="sync-server-host" placeholder="https://<host>:<port>">
</div>
<div class="form-group">
<label for="sync-server-timeout">Sync timeout (milliseconds)</label>
<input class="form-control" id="sync-server-timeout" min="1" max="10000000" type="number">
</div>
<div class="form-group">
<label for="sync-proxy">Sync proxy server (optional)</label>
<input class="form-control" id="sync-proxy" placeholder="https://<host>:<port>">
</div>
<div style="display: flex; justify-content: space-between;">
<button class="btn btn-sm">Save</button>
<button class="btn btn-sm" type="button" data-help-page="Synchronization">
<i class="glyphicon glyphicon-info-sign"></i> Help
</button>
</div>
</form>
<h4>Sync test</h4>
<p>This will test connection and handshake to the sync server.</p>
<button id="test-sync-button" class="btn btn-sm">Test sync</button>
<h4>Sync document to the server instance</h4>
<p>This is used when you want to sync your local document to the server instance configured above. This is a one time action after which the documents are synced automatically and transparently.</p>
<button id="sync-to-server-button" class="btn btn-sm">Sync local document to the server instance</button>
</div>
<div id="advanced">
<h4 style="margin-top: 0px;">Sync</h4>
<button id="force-full-sync-button" class="btn btn-sm">Force full sync</button>
<br/>
<br/>
<button id="fill-sync-rows-button" class="btn btn-sm">Fill sync rows</button>
<h4>Debugging</h4>
<button id="anonymize-button" class="btn btn-sm">Save anonymized database</button><br/><br/>
<p>This action will create a new copy of the database and anonymise it (remove all note content and leave only structure and metadata)
for sharing online for debugging purposes without fear of leaking your personal data.</p>
<h4>Image cleanup</h4>
<p>This will remove all image data of images not used in any current version of note from the database (metadata will remain).
This means that some images can disappear from note revisions.</p>
<button id="cleanup-unused-images-button" class="btn btn-sm">Permanently cleanup unused images</button>
<h4>Vacuum database</h4>
<p>This will rebuild database which will typically result in smaller database file. No data will be actually changed.</p>
<button id="vacuum-database-button" class="btn btn-sm">Vacuum database</button>
</div>
<div id="about">
<table class="table">
<tr>
<th>App version:</th>
<td id="app-version"></td>
</tr>
<tr>
<th>DB version:</th>
<td id="db-version"></td>
</tr>
<tr>
<th>Sync version:</th>
<td id="sync-version"></td>
</tr>
<tr>
<th>Build date:</th>
<td id="build-date"></td>
</tr>
<tr>
<th>Build revision:</th>
<td><a href="" target="_blank" id="build-revision"></a></td>
</tr>
</table>
</div>
</div>
</div>
<div id="note-revisions-dialog" class="tdialog" title="Note revisions">
<div style="display: flex;">
<select id="note-revision-list" size="25" style="width: 150px; height: 630px;">
</select>
<div id="note-revision-content-wrapper" style="flex-grow: 1; margin-left: 20px;">
<div style="display: flex">
<h3 id="note-revision-title" style="margin: 3px; flex-grow: 100;"></h3>
<div>
<button class="btn btn-sm" type="button" data-help-page="Note-revisions">
<i class="glyphicon glyphicon-info-sign"></i> Help
</button>
</div>
</div>
<div id="note-revision-content" style="height: 600px; width: 600px; overflow: auto;"></div>
</div>
</div>
</div>
<div id="recent-changes-dialog" class="tdialog" title="Recent changes">
</div>
<div id="event-log-dialog" class="tdialog" title="Event log">
<ul id="event-log-list"></ul>
</div>
<div id="edit-tree-prefix-dialog" class="tdialog" title="Edit tree prefix">
<form id="edit-tree-prefix-form">
<div class="form-group">
<label for="tree-prefix-input">Prefix: </label> &nbsp;
<input id="tree-prefix-input" style="width: 20em;"> - <span id="tree-prefix-note-title"></span>
</div>
<div style="display: flex; justify-content: space-between;">
<button class="btn btn-sm">Save</button>
<button class="btn btn-sm" type="button" data-help-page="Tree-concepts#prefix">
<i class="glyphicon glyphicon-info-sign"></i> Help
</button>
</div>
</form>
</div>
<div id="sql-console-dialog" class="tdialog" title="SQL console">
<div id="sql-console-query"></div>
<div style="text-align: center">
<button class="btn btn-danger" id="sql-console-execute">Execute <kbd>CTRL+ENTER</kbd></button>
</div>
<div style="height: 97%; overflow: auto">
<table id="owned-attributes-table" class="table">
<thead>
<tr>
<th></th>
<th>Type</th>
<th>Name</th>
<th>Value</th>
<th>Inheritable</th>
</tr>
</thead>
<tbody data-bind="foreach: ownedAttributes">
<table id="sql-console-results" class="table table-striped" style="overflow: scroll; width: 100%;">
<thead></thead>
<tbody></tbody>
</table>
</div>
<div id="note-source-dialog" class="tdialog" title="Note source">
<textarea id="note-source" readonly="readonly"></textarea>
</div>
<div id="attributes-dialog" class="tdialog" title="Note attributes">
<form data-bind="submit: save">
<div style="display: flex; justify-content: space-between;">
<button class="btn btn-large" style="width: 200px;" id="save-attributes-button" type="submit">Save changes <kbd>enter</kbd></button>
<button class="btn btn-sm" type="button" data-help-page="Attributes">
<i class="glyphicon glyphicon-info-sign"></i> Help
</button>
</div>
<div style="height: 97%; overflow: auto">
<table id="owned-attributes-table" class="table">
<thead>
<tr>
<th></th>
<th>Type</th>
<th>Name</th>
<th>Value</th>
<th>Inheritable</th>
</tr>
</thead>
<tbody data-bind="foreach: ownedAttributes">
<tr data-bind="if: !isDeleted">
<td class="handle">
<span class="glyphicon glyphicon-resize-vertical"></span>
<input type="hidden" name="position" data-bind="value: position"/>
</td>
<td>
<select class="form-control attribute-type-select" style="width: auto;" data-bind="options: $parent.availableTypes, optionsText: 'text', optionsValue: 'value', value: type, event: { change: $parent.typeChanged }"></select>
<select class="form-control attribute-type-select" style="width: auto;" data-bind="options: $parent.availableTypes, optionsText: 'text', optionsValue: 'value', value: type, event: { change: $parent.typeChanged }"></select>
</td>
<td>
<!-- Change to valueUpdate: blur is necessary because jQuery UI autocomplete hijacks change event -->
@@ -647,8 +663,8 @@
<select data-bind="options: $parent.multiplicityTypes, optionsText: 'text', optionsValue: 'value', value: labelDefinition.multiplicityType"></select>
<label>
<input type="checkbox" value="true" data-bind="checked: labelDefinition.isPromoted" />
Promoted
<input type="checkbox" value="true" data-bind="checked: labelDefinition.isPromoted" />
Promoted
</label>
</div>
@@ -667,22 +683,22 @@
<span title="Delete" style="padding: 13px; cursor: pointer;" class="glyphicon glyphicon-trash" data-bind="click: $parent.deleteAttribute"></span>
</td>
</tr>
</tbody>
</table>
</tbody>
</table>
<div data-bind="if: inheritedAttributes().length > 0">
<h4>Inherited attributes</h4>
<div data-bind="if: inheritedAttributes().length > 0">
<h4>Inherited attributes</h4>
<table class="table">
<thead>
<table class="table">
<thead>
<tr>
<th>Type</th>
<th>Name</th>
<th>Value</th>
<th>Owning note</th>
</tr>
</thead>
<tbody data-bind="foreach: inheritedAttributes">
</thead>
<tbody data-bind="foreach: inheritedAttributes">
<tr>
<td data-bind="text: type"></td>
<td data-bind="text: name"></td>
@@ -702,26 +718,27 @@
<span data-bind="text: value.multiplicityType"></span>
promoted: <span data-bind="text: value.isPromoted"></span>
</span>
<td data-bind="noteLink: noteId"></td>
</tr>
<td data-bind="noteLink: noteId"></td>
</tr>
</tbody>
</table>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</form>
</form>
</div>
<div id="markdown-import-dialog" class="tdialog" title="Markdown import" style="padding: 20px;">
<p>Because of browser sandbox it's not possible to directly read clipboard from JavaScript. Please paste the Markdown to import to textarea below and click on Import button</p>
<textarea id="markdown-import-textarea" style="height: 340px; width: 100%"></textarea>
<button id="markdown-import-button" class="btn btn-primary">Import <kbd>CTRL+Enter</kbd></button>
</div>
<div id="tooltip" style="display: none;"></div>
</div>
<div id="markdown-import-dialog" title="Markdown import" style="display: none; padding: 20px;">
<p>Because of browser sandbox it's not possible to directly read clipboard from JavaScript. Please paste the Markdown to import to textarea below and click on Import button</p>
<textarea id="markdown-import-textarea" style="height: 340px; width: 100%"></textarea>
<button id="markdown-import-button" class="btn btn-primary">Import <kbd>CTRL+Enter</kbd></button>
</div>
<div id="tooltip" style="display: none;"></div>
<script type="text/javascript">
window.baseApiUrl = 'api/';
window.glob = {