using ES6 modules for whole frontend SPA app

This commit is contained in:
azivner
2018-03-25 11:09:17 -04:00
parent b3c32a39e9
commit a699210a29
32 changed files with 3452 additions and 3383 deletions

View File

@@ -1,137 +1,141 @@
"use strict";
const addLink = (function() {
const $dialog = $("#add-link-dialog");
const $form = $("#add-link-form");
const $autoComplete = $("#note-autocomplete");
const $linkTitle = $("#link-title");
const $clonePrefix = $("#clone-prefix");
const $linkTitleFormGroup = $("#add-link-title-form-group");
const $prefixFormGroup = $("#add-link-prefix-form-group");
const $linkTypes = $("input[name='add-link-type']");
const $linkTypeHtml = $linkTypes.filter('input[value="html"]');
import treeService from '../note_tree.js';
import cloning from '../cloning.js';
import link from '../link.js';
import noteEditor from '../note_editor.js';
import treeUtils from '../tree_utils.js';
function setLinkType(linkType) {
$linkTypes.each(function () {
$(this).prop('checked', $(this).val() === linkType);
});
const $dialog = $("#add-link-dialog");
const $form = $("#add-link-form");
const $autoComplete = $("#note-autocomplete");
const $linkTitle = $("#link-title");
const $clonePrefix = $("#clone-prefix");
const $linkTitleFormGroup = $("#add-link-title-form-group");
const $prefixFormGroup = $("#add-link-prefix-form-group");
const $linkTypes = $("input[name='add-link-type']");
const $linkTypeHtml = $linkTypes.filter('input[value="html"]');
linkTypeChanged();
function setLinkType(linkType) {
$linkTypes.each(function () {
$(this).prop('checked', $(this).val() === linkType);
});
linkTypeChanged();
}
async function showDialog() {
glob.activeDialog = $dialog;
if (noteEditor.getCurrentNoteType() === 'text') {
$linkTypeHtml.prop('disabled', false);
setLinkType('html');
}
else {
$linkTypeHtml.prop('disabled', true);
setLinkType('selected-to-current');
}
async function showDialog() {
glob.activeDialog = $dialog;
$dialog.dialog({
modal: true,
width: 700
});
if (noteEditor.getCurrentNoteType() === 'text') {
$linkTypeHtml.prop('disabled', false);
$autoComplete.val('').focus();
$clonePrefix.val('');
$linkTitle.val('');
setLinkType('html');
}
else {
$linkTypeHtml.prop('disabled', true);
function setDefaultLinkTitle(noteId) {
const noteTitle = treeService.getNoteTitle(noteId);
setLinkType('selected-to-current');
}
$linkTitle.val(noteTitle);
}
$dialog.dialog({
modal: true,
width: 700
});
$autoComplete.autocomplete({
source: await treeService.getAutocompleteItems(),
minLength: 0,
change: () => {
const val = $autoComplete.val();
const notePath = link.getNodePathFromLabel(val);
if (!notePath) {
return;
}
$autoComplete.val('').focus();
$clonePrefix.val('');
$linkTitle.val('');
function setDefaultLinkTitle(noteId) {
const noteTitle = treeService.getNoteTitle(noteId);
$linkTitle.val(noteTitle);
}
$autoComplete.autocomplete({
source: await treeService.getAutocompleteItems(),
minLength: 0,
change: () => {
const val = $autoComplete.val();
const notePath = link.getNodePathFromLabel(val);
if (!notePath) {
return;
}
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
if (noteId) {
setDefaultLinkTitle(noteId);
}
},
// this is called when user goes through autocomplete list with keyboard
// at this point the item isn't selected yet so we use supplied ui.item to see WHERE the cursor is
focus: (event, ui) => {
const notePath = link.getNodePathFromLabel(ui.item.value);
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
if (noteId) {
setDefaultLinkTitle(noteId);
}
});
}
},
// this is called when user goes through autocomplete list with keyboard
// at this point the item isn't selected yet so we use supplied ui.item to see WHERE the cursor is
focus: (event, ui) => {
const notePath = link.getNodePathFromLabel(ui.item.value);
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
$form.submit(() => {
const value = $autoComplete.val();
const notePath = link.getNodePathFromLabel(value);
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
if (notePath) {
const linkType = $("input[name='add-link-type']:checked").val();
if (linkType === 'html') {
const linkTitle = $linkTitle.val();
$dialog.dialog("close");
link.addLinkToEditor(linkTitle, '#' + notePath);
}
else if (linkType === 'selected-to-current') {
const prefix = $clonePrefix.val();
cloning.cloneNoteTo(noteId, noteEditor.getCurrentNoteId(), prefix);
$dialog.dialog("close");
}
else if (linkType === 'current-to-selected') {
const prefix = $clonePrefix.val();
cloning.cloneNoteTo(noteEditor.getCurrentNoteId(), noteId, prefix);
$dialog.dialog("close");
}
setDefaultLinkTitle(noteId);
}
return false;
});
}
function linkTypeChanged() {
const value = $linkTypes.filter(":checked").val();
$form.submit(() => {
const value = $autoComplete.val();
if (value === 'html') {
$linkTitleFormGroup.show();
$prefixFormGroup.hide();
const notePath = link.getNodePathFromLabel(value);
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
if (notePath) {
const linkType = $("input[name='add-link-type']:checked").val();
if (linkType === 'html') {
const linkTitle = $linkTitle.val();
$dialog.dialog("close");
link.addLinkToEditor(linkTitle, '#' + notePath);
}
else {
$linkTitleFormGroup.hide();
$prefixFormGroup.show();
else if (linkType === 'selected-to-current') {
const prefix = $clonePrefix.val();
cloning.cloneNoteTo(noteId, noteEditor.getCurrentNoteId(), prefix);
$dialog.dialog("close");
}
else if (linkType === 'current-to-selected') {
const prefix = $clonePrefix.val();
cloning.cloneNoteTo(noteEditor.getCurrentNoteId(), noteId, prefix);
$dialog.dialog("close");
}
}
$linkTypes.change(linkTypeChanged);
return false;
});
$(document).bind('keydown', 'ctrl+l', e => {
showDialog();
function linkTypeChanged() {
const value = $linkTypes.filter(":checked").val();
e.preventDefault();
});
if (value === 'html') {
$linkTitleFormGroup.show();
$prefixFormGroup.hide();
}
else {
$linkTitleFormGroup.hide();
$prefixFormGroup.show();
}
}
return {
showDialog
};
})();
$linkTypes.change(linkTypeChanged);
$(document).bind('keydown', 'ctrl+l', e => {
showDialog();
e.preventDefault();
});
export default {
showDialog
};

View File

@@ -1,46 +1,46 @@
"use strict";
const editTreePrefix = (function() {
const $dialog = $("#edit-tree-prefix-dialog");
const $form = $("#edit-tree-prefix-form");
const $treePrefixInput = $("#tree-prefix-input");
const $noteTitle = $('#tree-prefix-note-title');
import treeService from '../note_tree.js';
let branchId;
const $dialog = $("#edit-tree-prefix-dialog");
const $form = $("#edit-tree-prefix-form");
const $treePrefixInput = $("#tree-prefix-input");
const $noteTitle = $('#tree-prefix-note-title');
async function showDialog() {
glob.activeDialog = $dialog;
let branchId;
await $dialog.dialog({
modal: true,
width: 500
});
async function showDialog() {
glob.activeDialog = $dialog;
const currentNode = treeService.getCurrentNode();
branchId = currentNode.data.branchId;
const nt = treeService.getBranch(branchId);
$treePrefixInput.val(nt.prefix).focus();
const noteTitle = treeService.getNoteTitle(currentNode.data.noteId);
$noteTitle.html(noteTitle);
}
$form.submit(() => {
const prefix = $treePrefixInput.val();
server.put('tree/' + branchId + '/set-prefix', {
prefix: prefix
}).then(() => treeService.setPrefix(branchId, prefix));
$dialog.dialog("close");
return false;
await $dialog.dialog({
modal: true,
width: 500
});
return {
showDialog
};
})();
const currentNode = treeService.getCurrentNode();
branchId = currentNode.data.branchId;
const nt = treeService.getBranch(branchId);
$treePrefixInput.val(nt.prefix).focus();
const noteTitle = treeService.getNoteTitle(currentNode.data.noteId);
$noteTitle.html(noteTitle);
}
$form.submit(() => {
const prefix = $treePrefixInput.val();
server.put('tree/' + branchId + '/set-prefix', {
prefix: prefix
}).then(() => treeService.setPrefix(branchId, prefix));
$dialog.dialog("close");
return false;
});
export default {
showDialog
};

View File

@@ -1,38 +1,39 @@
"use strict";
const eventLog = (function() {
const $dialog = $("#event-log-dialog");
const $list = $("#event-log-list");
import link from '../link.js';
import utils from '../utils.js';
async function showDialog() {
glob.activeDialog = $dialog;
const $dialog = $("#event-log-dialog");
const $list = $("#event-log-list");
$dialog.dialog({
modal: true,
width: 800,
height: 700
});
async function showDialog() {
glob.activeDialog = $dialog;
const result = await server.get('event-log');
$dialog.dialog({
modal: true,
width: 800,
height: 700
});
$list.html('');
const result = await server.get('event-log');
for (const event of result) {
const dateTime = utils.formatDateTime(utils.parseDate(event.dateAdded));
$list.html('');
if (event.noteId) {
const noteLink = link.createNoteLink(event.noteId).prop('outerHTML');
for (const event of result) {
const dateTime = utils.formatDateTime(utils.parseDate(event.dateAdded));
event.comment = event.comment.replace('<note>', noteLink);
}
if (event.noteId) {
const noteLink = link.createNoteLink(event.noteId).prop('outerHTML');
const eventEl = $('<li>').html(dateTime + " - " + event.comment);
$list.append(eventEl);
event.comment = event.comment.replace('<note>', noteLink);
}
}
return {
showDialog
};
})();
const eventEl = $('<li>').html(dateTime + " - " + event.comment);
$list.append(eventEl);
}
}
export default {
showDialog
};

View File

@@ -1,59 +1,61 @@
"use strict";
const jumpToNote = (function() {
const $showDialogButton = $("#jump-to-note-button");
const $dialog = $("#jump-to-note-dialog");
const $autoComplete = $("#jump-to-note-autocomplete");
const $form = $("#jump-to-note-form");
import treeService from '../note_tree.js';
import link from '../link.js';
import utils from '../utils.js';
async function showDialog() {
glob.activeDialog = $dialog;
const $showDialogButton = $("#jump-to-note-button");
const $dialog = $("#jump-to-note-dialog");
const $autoComplete = $("#jump-to-note-autocomplete");
const $form = $("#jump-to-note-form");
$autoComplete.val('');
async function showDialog() {
glob.activeDialog = $dialog;
$dialog.dialog({
modal: true,
width: 800
});
$autoComplete.val('');
await $autoComplete.autocomplete({
source: await utils.stopWatch("building autocomplete", treeService.getAutocompleteItems),
minLength: 0
});
}
function getSelectedNotePath() {
const val = $autoComplete.val();
return link.getNodePathFromLabel(val);
}
function goToNote() {
const notePath = getSelectedNotePath();
if (notePath) {
treeService.activateNode(notePath);
$dialog.dialog('close');
}
}
$(document).bind('keydown', 'ctrl+j', e => {
showDialog();
e.preventDefault();
$dialog.dialog({
modal: true,
width: 800
});
$form.submit(() => {
const action = $dialog.find("button:focus").val();
goToNote();
return false;
await $autoComplete.autocomplete({
source: await utils.stopWatch("building autocomplete", treeService.getAutocompleteItems),
minLength: 0
});
}
$showDialogButton.click(showDialog);
function getSelectedNotePath() {
const val = $autoComplete.val();
return link.getNodePathFromLabel(val);
}
return {
showDialog
};
})();
function goToNote() {
const notePath = getSelectedNotePath();
if (notePath) {
treeService.activateNode(notePath);
$dialog.dialog('close');
}
}
$(document).bind('keydown', 'ctrl+j', e => {
showDialog();
e.preventDefault();
});
$form.submit(() => {
const action = $dialog.find("button:focus").val();
goToNote();
return false;
});
$showDialogButton.click(showDialog);
export default {
showDialog
};

View File

@@ -1,227 +1,228 @@
"use strict";
const labelsDialog = (function() {
const $showDialogButton = $(".show-labels-button");
const $dialog = $("#labels-dialog");
const $saveLabelsButton = $("#save-labels-button");
const $labelsBody = $('#labels-table tbody');
import noteEditor from '../note_editor.js';
import utils from '../utils.js';
const labelsModel = new LabelsModel();
let labelNames = [];
const $showDialogButton = $(".show-labels-button");
const $dialog = $("#labels-dialog");
const $saveLabelsButton = $("#save-labels-button");
const $labelsBody = $('#labels-table tbody');
function LabelsModel() {
const self = this;
const labelsModel = new LabelsModel();
let labelNames = [];
this.labels = ko.observableArray();
function LabelsModel() {
const self = this;
this.loadLabels = async function() {
const noteId = noteEditor.getCurrentNoteId();
this.labels = ko.observableArray();
const labels = await server.get('notes/' + noteId + '/labels');
this.loadLabels = async function() {
const noteId = noteEditor.getCurrentNoteId();
self.labels(labels.map(ko.observable));
const labels = await server.get('notes/' + noteId + '/labels');
self.labels(labels.map(ko.observable));
addLastEmptyRow();
labelNames = await server.get('labels/names');
// label might not be rendered immediatelly so could not focus
setTimeout(() => $(".label-name:last").focus(), 100);
$labelsBody.sortable({
handle: '.handle',
containment: $labelsBody,
update: function() {
let position = 0;
// we need to update positions by searching in the DOM, because order of the
// labels in the viewmodel (self.labels()) stays the same
$labelsBody.find('input[name="position"]').each(function() {
const attr = self.getTargetLabel(this);
attr().position = position++;
});
}
});
};
this.deleteLabel = function(data, event) {
const attr = self.getTargetLabel(event.target);
const attrData = attr();
if (attrData) {
attrData.isDeleted = 1;
attr(attrData);
addLastEmptyRow();
labelNames = await server.get('labels/names');
// label might not be rendered immediatelly so could not focus
setTimeout(() => $(".label-name:last").focus(), 100);
$labelsBody.sortable({
handle: '.handle',
containment: $labelsBody,
update: function() {
let position = 0;
// we need to update positions by searching in the DOM, because order of the
// labels in the viewmodel (self.labels()) stays the same
$labelsBody.find('input[name="position"]').each(function() {
const attr = self.getTargetLabel(this);
attr().position = position++;
});
}
});
};
this.deleteLabel = function(data, event) {
const attr = self.getTargetLabel(event.target);
const attrData = attr();
if (attrData) {
attrData.isDeleted = 1;
attr(attrData);
addLastEmptyRow();
}
};
function isValid() {
for (let attrs = self.labels(), i = 0; i < attrs.length; i++) {
if (self.isEmptyName(i)) {
return false;
}
}
return true;
}
};
this.save = async function() {
// we need to defocus from input (in case of enter-triggered save) because value is updated
// on blur event (because of conflict with jQuery UI Autocomplete). Without this, input would
// stay in focus, blur wouldn't be triggered and change wouldn't be updated in the viewmodel.
$saveLabelsButton.focus();
if (!isValid()) {
alert("Please fix all validation errors and try saving again.");
return;
}
const noteId = noteEditor.getCurrentNoteId();
const labelsToSave = self.labels()
.map(attr => attr())
.filter(attr => attr.labelId !== "" || attr.name !== "");
const labels = await server.put('notes/' + noteId + '/labels', labelsToSave);
self.labels(labels.map(ko.observable));
addLastEmptyRow();
utils.showMessage("Labels have been saved.");
noteEditor.loadLabelList();
};
function addLastEmptyRow() {
const attrs = self.labels().filter(attr => attr().isDeleted === 0);
const last = attrs.length === 0 ? null : attrs[attrs.length - 1]();
if (!last || last.name.trim() !== "" || last.value !== "") {
self.labels.push(ko.observable({
labelId: '',
name: '',
value: '',
isDeleted: 0,
position: 0
}));
}
}
this.labelChanged = function (data, event) {
addLastEmptyRow();
const attr = self.getTargetLabel(event.target);
attr.valueHasMutated();
};
this.isNotUnique = function(index) {
const cur = self.labels()[index]();
if (cur.name.trim() === "") {
function isValid() {
for (let attrs = self.labels(), i = 0; i < attrs.length; i++) {
if (self.isEmptyName(i)) {
return false;
}
}
for (let attrs = self.labels(), i = 0; i < attrs.length; i++) {
const attr = attrs[i]();
return true;
}
if (index !== i && cur.name === attr.name) {
return true;
}
}
this.save = async function() {
// we need to defocus from input (in case of enter-triggered save) because value is updated
// on blur event (because of conflict with jQuery UI Autocomplete). Without this, input would
// stay in focus, blur wouldn't be triggered and change wouldn't be updated in the viewmodel.
$saveLabelsButton.focus();
return false;
};
if (!isValid()) {
alert("Please fix all validation errors and try saving again.");
return;
}
this.isEmptyName = function(index) {
const cur = self.labels()[index]();
const noteId = noteEditor.getCurrentNoteId();
return cur.name.trim() === "" && (cur.labelId !== "" || cur.value !== "");
};
const labelsToSave = self.labels()
.map(attr => attr())
.filter(attr => attr.labelId !== "" || attr.name !== "");
this.getTargetLabel = function(target) {
const context = ko.contextFor(target);
const index = context.$index();
const labels = await server.put('notes/' + noteId + '/labels', labelsToSave);
return self.labels()[index];
self.labels(labels.map(ko.observable));
addLastEmptyRow();
utils.showMessage("Labels have been saved.");
noteEditor.loadLabelList();
};
function addLastEmptyRow() {
const attrs = self.labels().filter(attr => attr().isDeleted === 0);
const last = attrs.length === 0 ? null : attrs[attrs.length - 1]();
if (!last || last.name.trim() !== "" || last.value !== "") {
self.labels.push(ko.observable({
labelId: '',
name: '',
value: '',
isDeleted: 0,
position: 0
}));
}
}
async function showDialog() {
glob.activeDialog = $dialog;
this.labelChanged = function (data, event) {
addLastEmptyRow();
await labelsModel.loadLabels();
const attr = self.getTargetLabel(event.target);
$dialog.dialog({
modal: true,
width: 800,
height: 500
attr.valueHasMutated();
};
this.isNotUnique = function(index) {
const cur = self.labels()[index]();
if (cur.name.trim() === "") {
return false;
}
for (let attrs = self.labels(), i = 0; i < attrs.length; i++) {
const attr = attrs[i]();
if (index !== i && cur.name === attr.name) {
return true;
}
}
return false;
};
this.isEmptyName = function(index) {
const cur = self.labels()[index]();
return cur.name.trim() === "" && (cur.labelId !== "" || cur.value !== "");
};
this.getTargetLabel = function(target) {
const context = ko.contextFor(target);
const index = context.$index();
return self.labels()[index];
}
}
async function showDialog() {
glob.activeDialog = $dialog;
await labelsModel.loadLabels();
$dialog.dialog({
modal: true,
width: 800,
height: 500
});
}
$(document).bind('keydown', 'alt+a', e => {
showDialog();
e.preventDefault();
});
ko.applyBindings(labelsModel, document.getElementById('labels-dialog'));
$(document).on('focus', '.label-name', function (e) {
if (!$(this).hasClass("ui-autocomplete-input")) {
$(this).autocomplete({
// shouldn't be required and autocomplete should just accept array of strings, but that fails
// because we have overriden filter() function in init.js
source: labelNames.map(attr => {
return {
label: attr,
value: attr
}
}),
minLength: 0
});
}
$(document).bind('keydown', 'alt+a', e => {
showDialog();
$(this).autocomplete("search", $(this).val());
});
e.preventDefault();
});
$(document).on('focus', '.label-value', async function (e) {
if (!$(this).hasClass("ui-autocomplete-input")) {
const labelName = $(this).parent().parent().find('.label-name').val();
ko.applyBindings(labelsModel, document.getElementById('labels-dialog'));
$(document).on('focus', '.label-name', function (e) {
if (!$(this).hasClass("ui-autocomplete-input")) {
$(this).autocomplete({
// shouldn't be required and autocomplete should just accept array of strings, but that fails
// because we have overriden filter() function in init.js
source: labelNames.map(attr => {
return {
label: attr,
value: attr
}
}),
minLength: 0
});
if (labelName.trim() === "") {
return;
}
$(this).autocomplete("search", $(this).val());
});
const labelValues = await server.get('labels/values/' + encodeURIComponent(labelName));
$(document).on('focus', '.label-value', async function (e) {
if (!$(this).hasClass("ui-autocomplete-input")) {
const labelName = $(this).parent().parent().find('.label-name').val();
if (labelName.trim() === "") {
return;
}
const labelValues = await server.get('labels/values/' + encodeURIComponent(labelName));
if (labelValues.length === 0) {
return;
}
$(this).autocomplete({
// shouldn't be required and autocomplete should just accept array of strings, but that fails
// because we have overriden filter() function in init.js
source: labelValues.map(attr => {
return {
label: attr,
value: attr
}
}),
minLength: 0
});
if (labelValues.length === 0) {
return;
}
$(this).autocomplete("search", $(this).val());
});
$(this).autocomplete({
// shouldn't be required and autocomplete should just accept array of strings, but that fails
// because we have overriden filter() function in init.js
source: labelValues.map(attr => {
return {
label: attr,
value: attr
}
}),
minLength: 0
});
}
$showDialogButton.click(showDialog);
$(this).autocomplete("search", $(this).val());
});
return {
showDialog
};
})();
$showDialogButton.click(showDialog);
export default {
showDialog
};

View File

@@ -1,81 +1,82 @@
"use strict";
const noteHistory = (function() {
const $showDialogButton = $("#show-history-button");
const $dialog = $("#note-history-dialog");
const $list = $("#note-history-list");
const $content = $("#note-history-content");
const $title = $("#note-history-title");
import noteEditor from '../note_editor.js';
import utils from '../utils.js';
let historyItems = [];
const $showDialogButton = $("#show-history-button");
const $dialog = $("#note-history-dialog");
const $list = $("#note-history-list");
const $content = $("#note-history-content");
const $title = $("#note-history-title");
async function showCurrentNoteHistory() {
await showNoteHistoryDialog(noteEditor.getCurrentNoteId());
let historyItems = [];
async function showCurrentNoteHistory() {
await showNoteHistoryDialog(noteEditor.getCurrentNoteId());
}
async function showNoteHistoryDialog(noteId, noteRevisionId) {
glob.activeDialog = $dialog;
$dialog.dialog({
modal: true,
width: 800,
height: 700
});
$list.empty();
$content.empty();
historyItems = await server.get('notes-history/' + noteId);
for (const item of historyItems) {
const dateModified = utils.parseDate(item.dateModifiedFrom);
$list.append($('<option>', {
value: item.noteRevisionId,
text: utils.formatDateTime(dateModified)
}));
}
async function showNoteHistoryDialog(noteId, noteRevisionId) {
glob.activeDialog = $dialog;
$dialog.dialog({
modal: true,
width: 800,
height: 700
});
$list.empty();
$content.empty();
historyItems = await server.get('notes-history/' + noteId);
for (const item of historyItems) {
const dateModified = utils.parseDate(item.dateModifiedFrom);
$list.append($('<option>', {
value: item.noteRevisionId,
text: utils.formatDateTime(dateModified)
}));
if (historyItems.length > 0) {
if (!noteRevisionId) {
noteRevisionId = $list.find("option:first").val();
}
if (historyItems.length > 0) {
if (!noteRevisionId) {
noteRevisionId = $list.find("option:first").val();
}
$list.val(noteRevisionId).trigger('change');
}
else {
$title.text("No history for this note yet...");
}
$list.val(noteRevisionId).trigger('change');
}
else {
$title.text("No history for this note yet...");
}
}
$(document).bind('keydown', 'alt+h', e => {
showCurrentNoteHistory();
$(document).bind('keydown', 'alt+h', e => {
showCurrentNoteHistory();
e.preventDefault();
});
e.preventDefault();
});
$list.on('change', () => {
const optVal = $list.find(":selected").val();
$list.on('change', () => {
const optVal = $list.find(":selected").val();
const historyItem = historyItems.find(r => r.noteRevisionId === optVal);
const historyItem = historyItems.find(r => r.noteRevisionId === optVal);
$title.html(historyItem.title);
$content.html(historyItem.content);
});
$title.html(historyItem.title);
$content.html(historyItem.content);
});
$(document).on('click', "a[action='note-history']", event => {
const linkEl = $(event.target);
const noteId = linkEl.attr('note-path');
const noteRevisionId = linkEl.attr('note-history-id');
$(document).on('click', "a[action='note-history']", event => {
const linkEl = $(event.target);
const noteId = linkEl.attr('note-path');
const noteRevisionId = linkEl.attr('note-history-id');
showNoteHistoryDialog(noteId, noteRevisionId);
showNoteHistoryDialog(noteId, noteRevisionId);
return false;
});
return false;
});
$showDialogButton.click(showCurrentNoteHistory);
$showDialogButton.click(showCurrentNoteHistory);
return {
showCurrentNoteHistory
};
})();
export default {
showCurrentNoteHistory
};

View File

@@ -1,60 +1,60 @@
"use strict";
const noteSource = (function() {
const $showDialogButton = $("#show-source-button");
const $dialog = $("#note-source-dialog");
const $noteSource = $("#note-source");
import noteEditor from '../note_editor.js';
function showDialog() {
glob.activeDialog = $dialog;
const $showDialogButton = $("#show-source-button");
const $dialog = $("#note-source-dialog");
const $noteSource = $("#note-source");
$dialog.dialog({
modal: true,
width: 800,
height: 500
});
function showDialog() {
glob.activeDialog = $dialog;
const noteText = noteEditor.getCurrentNote().detail.content;
$noteSource.text(formatHtml(noteText));
}
function formatHtml(str) {
const div = document.createElement('div');
div.innerHTML = str.trim();
return formatNode(div, 0).innerHTML.trim();
}
function formatNode(node, level) {
const indentBefore = new Array(level++ + 1).join(' ');
const indentAfter = new Array(level - 1).join(' ');
let textNode;
for (let i = 0; i < node.children.length; i++) {
textNode = document.createTextNode('\n' + indentBefore);
node.insertBefore(textNode, node.children[i]);
formatNode(node.children[i], level);
if (node.lastElementChild === node.children[i]) {
textNode = document.createTextNode('\n' + indentAfter);
node.appendChild(textNode);
}
}
return node;
}
$(document).bind('keydown', 'ctrl+u', e => {
showDialog();
e.preventDefault();
$dialog.dialog({
modal: true,
width: 800,
height: 500
});
$showDialogButton.click(showDialog);
const noteText = noteEditor.getCurrentNote().detail.content;
return {
showDialog
};
})();
$noteSource.text(formatHtml(noteText));
}
function formatHtml(str) {
const div = document.createElement('div');
div.innerHTML = str.trim();
return formatNode(div, 0).innerHTML.trim();
}
function formatNode(node, level) {
const indentBefore = new Array(level++ + 1).join(' ');
const indentAfter = new Array(level - 1).join(' ');
let textNode;
for (let i = 0; i < node.children.length; i++) {
textNode = document.createTextNode('\n' + indentBefore);
node.insertBefore(textNode, node.children[i]);
formatNode(node.children[i], level);
if (node.lastElementChild === node.children[i]) {
textNode = document.createTextNode('\n' + indentAfter);
node.appendChild(textNode);
}
}
return node;
}
$(document).bind('keydown', 'ctrl+u', e => {
showDialog();
e.preventDefault();
});
$showDialogButton.click(showDialog);
export default {
showDialog
};

View File

@@ -1,92 +1,93 @@
"use strict";
const recentChanges = (function() {
const $showDialogButton = $("#recent-changes-button");
const $dialog = $("#recent-changes-dialog");
import link from '../link.js';
import utils from '../utils.js';
async function showDialog() {
glob.activeDialog = $dialog;
const $showDialogButton = $("#recent-changes-button");
const $dialog = $("#recent-changes-dialog");
$dialog.dialog({
modal: true,
width: 800,
height: 700
});
async function showDialog() {
glob.activeDialog = $dialog;
const result = await server.get('recent-changes/');
$dialog.dialog({
modal: true,
width: 800,
height: 700
});
$dialog.html('');
const result = await server.get('recent-changes/');
const groupedByDate = groupByDate(result);
$dialog.html('');
for (const [dateDay, dayChanges] of groupedByDate) {
const changesListEl = $('<ul>');
const groupedByDate = groupByDate(result);
const dayEl = $('<div>').append($('<b>').html(utils.formatDate(dateDay))).append(changesListEl);
for (const [dateDay, dayChanges] of groupedByDate) {
const changesListEl = $('<ul>');
for (const change of dayChanges) {
const formattedTime = utils.formatTime(utils.parseDate(change.dateModifiedTo));
const dayEl = $('<div>').append($('<b>').html(utils.formatDate(dateDay))).append(changesListEl);
const revLink = $("<a>", {
href: 'javascript:',
text: 'rev'
}).attr('action', 'note-history')
.attr('note-path', change.noteId)
.attr('note-history-id', change.noteRevisionId);
for (const change of dayChanges) {
const formattedTime = utils.formatTime(utils.parseDate(change.dateModifiedTo));
let noteLink;
const revLink = $("<a>", {
href: 'javascript:',
text: 'rev'
}).attr('action', 'note-history')
.attr('note-path', change.noteId)
.attr('note-history-id', change.noteRevisionId);
if (change.current_isDeleted) {
noteLink = change.current_title;
}
else {
noteLink = link.createNoteLink(change.noteId, change.title);
}
let noteLink;
changesListEl.append($('<li>')
.append(formattedTime + ' - ')
.append(noteLink)
.append(' (').append(revLink).append(')'));
}
$dialog.append(dayEl);
}
}
function groupByDate(result) {
const groupedByDate = new Map();
const dayCache = {};
for (const row of result) {
let dateDay = utils.parseDate(row.dateModifiedTo);
dateDay.setHours(0);
dateDay.setMinutes(0);
dateDay.setSeconds(0);
dateDay.setMilliseconds(0);
// this stupidity is to make sure that we always use the same day object because Map uses only
// reference equality
if (dayCache[dateDay]) {
dateDay = dayCache[dateDay];
if (change.current_isDeleted) {
noteLink = change.current_title;
}
else {
dayCache[dateDay] = dateDay;
noteLink = link.createNoteLink(change.noteId, change.title);
}
if (!groupedByDate.has(dateDay)) {
groupedByDate.set(dateDay, []);
}
groupedByDate.get(dateDay).push(row);
changesListEl.append($('<li>')
.append(formattedTime + ' - ')
.append(noteLink)
.append(' (').append(revLink).append(')'));
}
return groupedByDate;
$dialog.append(dayEl);
}
}
$(document).bind('keydown', 'alt+r', showDialog);
function groupByDate(result) {
const groupedByDate = new Map();
const dayCache = {};
$showDialogButton.click(showDialog);
for (const row of result) {
let dateDay = utils.parseDate(row.dateModifiedTo);
dateDay.setHours(0);
dateDay.setMinutes(0);
dateDay.setSeconds(0);
dateDay.setMilliseconds(0);
return {
showDialog
};
})();
// this stupidity is to make sure that we always use the same day object because Map uses only
// reference equality
if (dayCache[dateDay]) {
dateDay = dayCache[dateDay];
}
else {
dayCache[dateDay] = dateDay;
}
if (!groupedByDate.has(dateDay)) {
groupedByDate.set(dateDay, []);
}
groupedByDate.get(dateDay).push(row);
}
return groupedByDate;
}
$(document).bind('keydown', 'alt+r', showDialog);
$showDialogButton.click(showDialog);
export default {
showDialog
};

View File

@@ -1,105 +1,107 @@
"use strict";
const recentNotes = (function() {
const $showDialogButton = $("#recent-notes-button");
const $dialog = $("#recent-notes-dialog");
const $searchInput = $('#recent-notes-search-input');
import treeService from '../note_tree.js';
import server from '../server.js';
import messaging from '../messaging.js';
// list of recent note paths
let list = [];
const $showDialogButton = $("#recent-notes-button");
const $dialog = $("#recent-notes-dialog");
const $searchInput = $('#recent-notes-search-input');
async function reload() {
const result = await server.get('recent-notes');
// list of recent note paths
let list = [];
list = result.map(r => r.notePath);
}
async function reload() {
const result = await server.get('recent-notes');
function addRecentNote(branchId, notePath) {
setTimeout(async () => {
// we include the note into recent list only if the user stayed on the note at least 5 seconds
if (notePath && notePath === treeService.getCurrentNotePath()) {
const result = await server.put('recent-notes/' + branchId + '/' + encodeURIComponent(notePath));
list = result.map(r => r.notePath);
}
list = result.map(r => r.notePath);
}
}, 1500);
}
function addRecentNote(branchId, notePath) {
setTimeout(async () => {
// we include the note into recent list only if the user stayed on the note at least 5 seconds
if (notePath && notePath === treeService.getCurrentNotePath()) {
const result = await server.put('recent-notes/' + branchId + '/' + encodeURIComponent(notePath));
function showDialog() {
glob.activeDialog = $dialog;
list = result.map(r => r.notePath);
}
}, 1500);
}
$dialog.dialog({
modal: true,
width: 800,
height: 100,
position: { my: "center top+100", at: "top", of: window }
});
function showDialog() {
glob.activeDialog = $dialog;
$searchInput.val('');
// remove the current note
const recNotes = list.filter(note => note !== treeService.getCurrentNotePath());
$searchInput.autocomplete({
source: recNotes.map(notePath => {
let noteTitle;
try {
noteTitle = treeService.getNotePathTitle(notePath);
}
catch (e) {
noteTitle = "[error - can't find note title]";
messaging.logError("Could not find title for notePath=" + notePath + ", stack=" + e.stack);
}
return {
label: noteTitle,
value: notePath
}
}),
minLength: 0,
autoFocus: true,
select: function (event, ui) {
treeService.activateNode(ui.item.value);
$searchInput.autocomplete('destroy');
$dialog.dialog('close');
},
focus: function (event, ui) {
event.preventDefault();
},
close: function (event, ui) {
if (event.keyCode === 27) { // escape closes dialog
$searchInput.autocomplete('destroy');
$dialog.dialog('close');
}
else {
// keep autocomplete open
// we're kind of abusing autocomplete to work in a way which it's not designed for
$searchInput.autocomplete("search", "");
}
},
create: () => $searchInput.autocomplete("search", ""),
classes: {
"ui-autocomplete": "recent-notes-autocomplete"
}
});
}
reload();
$(document).bind('keydown', 'ctrl+e', e => {
showDialog();
e.preventDefault();
$dialog.dialog({
modal: true,
width: 800,
height: 100,
position: { my: "center top+100", at: "top", of: window }
});
$showDialogButton.click(showDialog);
$searchInput.val('');
return {
showDialog,
addRecentNote,
reload
};
})();
// remove the current note
const recNotes = list.filter(note => note !== treeService.getCurrentNotePath());
$searchInput.autocomplete({
source: recNotes.map(notePath => {
let noteTitle;
try {
noteTitle = treeService.getNotePathTitle(notePath);
}
catch (e) {
noteTitle = "[error - can't find note title]";
messaging.logError("Could not find title for notePath=" + notePath + ", stack=" + e.stack);
}
return {
label: noteTitle,
value: notePath
}
}),
minLength: 0,
autoFocus: true,
select: function (event, ui) {
treeService.activateNode(ui.item.value);
$searchInput.autocomplete('destroy');
$dialog.dialog('close');
},
focus: function (event, ui) {
event.preventDefault();
},
close: function (event, ui) {
if (event.keyCode === 27) { // escape closes dialog
$searchInput.autocomplete('destroy');
$dialog.dialog('close');
}
else {
// keep autocomplete open
// we're kind of abusing autocomplete to work in a way which it's not designed for
$searchInput.autocomplete("search", "");
}
},
create: () => $searchInput.autocomplete("search", ""),
classes: {
"ui-autocomplete": "recent-notes-autocomplete"
}
});
}
reload();
$(document).bind('keydown', 'ctrl+e', e => {
showDialog();
e.preventDefault();
});
$showDialogButton.click(showDialog);
export default {
showDialog,
addRecentNote,
reload
};

View File

@@ -1,54 +1,56 @@
"use strict";
const settings = (function() {
const $showDialogButton = $("#settings-button");
const $dialog = $("#settings-dialog");
const $tabs = $("#settings-tabs");
import protected_session from '../protected_session.js';
import utils from '../utils.js';
import server from '../server.js';
const settingModules = [];
const $showDialogButton = $("#settings-button");
const $dialog = $("#settings-dialog");
const $tabs = $("#settings-tabs");
function addModule(module) {
settingModules.push(module);
}
const settingModules = [];
async function showDialog() {
glob.activeDialog = $dialog;
function addModule(module) {
settingModules.push(module);
}
const settings = await server.get('settings');
async function showDialog() {
glob.activeDialog = $dialog;
$dialog.dialog({
modal: true,
width: 900
});
const settings = await server.get('settings');
$tabs.tabs();
$dialog.dialog({
modal: true,
width: 900
});
for (const module of settingModules) {
if (module.settingsLoaded) {
module.settingsLoaded(settings);
}
$tabs.tabs();
for (const module of settingModules) {
if (module.settingsLoaded) {
module.settingsLoaded(settings);
}
}
}
async function saveSettings(settingName, settingValue) {
await server.post('settings', {
name: settingName,
value: settingValue
});
async function saveSettings(settingName, settingValue) {
await server.post('settings', {
name: settingName,
value: settingValue
});
utils.showMessage("Settings change have been saved.");
}
utils.showMessage("Settings change have been saved.");
}
$showDialogButton.click(showDialog);
$showDialogButton.click(showDialog);
return {
showDialog,
saveSettings,
addModule
};
})();
export default {
showDialog,
saveSettings,
addModule
};
settings.addModule((function() {
addModule((function() {
const $form = $("#change-password-form");
const $oldPassword = $("#old-password");
const $newPassword1 = $("#new-password1");
@@ -94,7 +96,7 @@ settings.addModule((function() {
};
})());
settings.addModule((function() {
addModule((function() {
const $form = $("#protected-session-timeout-form");
const $protectedSessionTimeout = $("#protected-session-timeout-in-seconds");
const settingName = 'protected_session_timeout';
@@ -118,7 +120,7 @@ settings.addModule((function() {
};
})());
settings.addModule((function () {
addModule((function () {
const $form = $("#history-snapshot-time-interval-form");
const $timeInterval = $("#history-snapshot-time-interval-in-seconds");
const settingName = 'history_snapshot_time_interval';
@@ -138,7 +140,7 @@ settings.addModule((function () {
};
})());
settings.addModule((async function () {
addModule((async function () {
const $appVersion = $("#app-version");
const $dbVersion = $("#db-version");
const $buildDate = $("#build-date");
@@ -155,7 +157,7 @@ settings.addModule((async function () {
return {};
})());
settings.addModule((async function () {
addModule((async function () {
const $forceFullSyncButton = $("#force-full-sync-button");
const $fillSyncRowsButton = $("#fill-sync-rows-button");
const $anonymizeButton = $("#anonymize-button");

View File

@@ -1,106 +1,106 @@
"use strict";
const sqlConsole = (function() {
const $dialog = $("#sql-console-dialog");
const $query = $('#sql-console-query');
const $executeButton = $('#sql-console-execute');
const $resultHead = $('#sql-console-results thead');
const $resultBody = $('#sql-console-results tbody');
import utils from '../utils.js';
let codeEditor;
const $dialog = $("#sql-console-dialog");
const $query = $('#sql-console-query');
const $executeButton = $('#sql-console-execute');
const $resultHead = $('#sql-console-results thead');
const $resultBody = $('#sql-console-results tbody');
function showDialog() {
glob.activeDialog = $dialog;
let codeEditor;
$dialog.dialog({
modal: true,
width: $(window).width(),
height: $(window).height(),
open: function() {
initEditor();
}
});
}
function showDialog() {
glob.activeDialog = $dialog;
async function initEditor() {
if (!codeEditor) {
await utils.requireLibrary(utils.CODE_MIRROR);
CodeMirror.keyMap.default["Shift-Tab"] = "indentLess";
CodeMirror.keyMap.default["Tab"] = "indentMore";
// removing Escape binding so that Escape will propagate to the dialog (which will close on escape)
delete CodeMirror.keyMap.basic["Esc"];
CodeMirror.modeURL = 'libraries/codemirror/mode/%N/%N.js';
codeEditor = CodeMirror($query[0], {
value: "",
viewportMargin: Infinity,
indentUnit: 4,
highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: false}
});
codeEditor.setOption("mode", "text/x-sqlite");
CodeMirror.autoLoadMode(codeEditor, "sql");
$dialog.dialog({
modal: true,
width: $(window).width(),
height: $(window).height(),
open: function() {
initEditor();
}
});
}
codeEditor.focus();
}
async function initEditor() {
if (!codeEditor) {
await utils.requireLibrary(utils.CODE_MIRROR);
async function execute(e) {
// stop from propagating upwards (dangerous especially with ctrl+enter executable javascript notes)
e.preventDefault();
e.stopPropagation();
CodeMirror.keyMap.default["Shift-Tab"] = "indentLess";
CodeMirror.keyMap.default["Tab"] = "indentMore";
const sqlQuery = codeEditor.getValue();
// removing Escape binding so that Escape will propagate to the dialog (which will close on escape)
delete CodeMirror.keyMap.basic["Esc"];
const result = await server.post("sql/execute", {
query: sqlQuery
CodeMirror.modeURL = 'libraries/codemirror/mode/%N/%N.js';
codeEditor = CodeMirror($query[0], {
value: "",
viewportMargin: Infinity,
indentUnit: 4,
highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: false}
});
if (!result.success) {
utils.showError(result.error);
return;
}
else {
utils.showMessage("Query was executed successfully.");
}
const rows = result.rows;
$resultHead.empty();
$resultBody.empty();
if (rows.length > 0) {
const result = rows[0];
const rowEl = $("<tr>");
for (const key in result) {
rowEl.append($("<th>").html(key));
}
$resultHead.append(rowEl);
}
for (const result of rows) {
const rowEl = $("<tr>");
for (const key in result) {
rowEl.append($("<td>").html(result[key]));
}
$resultBody.append(rowEl);
}
codeEditor.setOption("mode", "text/x-sqlite");
CodeMirror.autoLoadMode(codeEditor, "sql");
}
$(document).bind('keydown', 'alt+o', showDialog);
codeEditor.focus();
}
$query.bind('keydown', 'ctrl+return', execute);
async function execute(e) {
// stop from propagating upwards (dangerous especially with ctrl+enter executable javascript notes)
e.preventDefault();
e.stopPropagation();
$executeButton.click(execute);
const sqlQuery = codeEditor.getValue();
return {
showDialog
};
})();
const result = await server.post("sql/execute", {
query: sqlQuery
});
if (!result.success) {
utils.showError(result.error);
return;
}
else {
utils.showMessage("Query was executed successfully.");
}
const rows = result.rows;
$resultHead.empty();
$resultBody.empty();
if (rows.length > 0) {
const result = rows[0];
const rowEl = $("<tr>");
for (const key in result) {
rowEl.append($("<th>").html(key));
}
$resultHead.append(rowEl);
}
for (const result of rows) {
const rowEl = $("<tr>");
for (const key in result) {
rowEl.append($("<td>").html(result[key]));
}
$resultBody.append(rowEl);
}
}
$(document).bind('keydown', 'alt+o', showDialog);
$query.bind('keydown', 'ctrl+return', execute);
$executeButton.click(execute);
export default {
showDialog
};