2018-03-26 01:41:29 +08:00
|
|
|
import treeService from './tree.js';
|
2018-04-09 10:38:52 +08:00
|
|
|
import treeUtils from './tree_utils.js';
|
2018-03-26 01:41:29 +08:00
|
|
|
import noteTypeService from './note_type.js';
|
|
|
|
import protectedSessionService from './protected_session.js';
|
2018-03-26 09:16:57 +08:00
|
|
|
import protectedSessionHolder from './protected_session_holder.js';
|
2018-03-25 23:09:17 +08:00
|
|
|
import server from './server.js';
|
2018-03-26 09:16:57 +08:00
|
|
|
import messagingService from "./messaging.js";
|
2018-03-26 09:29:35 +08:00
|
|
|
import infoService from "./info.js";
|
2018-03-26 11:25:17 +08:00
|
|
|
import treeCache from "./tree_cache.js";
|
|
|
|
import NoteFull from "../entities/note_full.js";
|
2018-03-27 12:22:02 +08:00
|
|
|
import noteDetailCode from './note_detail_code.js';
|
|
|
|
import noteDetailText from './note_detail_text.js';
|
2018-03-28 10:11:06 +08:00
|
|
|
import noteDetailFile from './note_detail_file.js';
|
2018-11-08 17:30:35 +08:00
|
|
|
import noteDetailImage from './note_detail_image.js';
|
2018-03-28 09:36:01 +08:00
|
|
|
import noteDetailSearch from './note_detail_search.js';
|
|
|
|
import noteDetailRender from './note_detail_render.js';
|
2018-10-18 17:25:33 +08:00
|
|
|
import noteDetailRelationMap from './note_detail_relation_map.js';
|
2018-07-29 22:06:13 +08:00
|
|
|
import bundleService from "./bundle.js";
|
2018-11-09 03:01:25 +08:00
|
|
|
import attributeService from "./attributes.js";
|
2018-12-24 17:10:36 +08:00
|
|
|
import utils from "./utils.js";
|
2018-03-25 23:09:17 +08:00
|
|
|
|
|
|
|
const $noteTitle = $("#note-title");
|
|
|
|
|
2018-03-27 12:22:02 +08:00
|
|
|
const $noteDetailComponents = $(".note-detail-component");
|
2018-03-25 23:09:17 +08:00
|
|
|
|
|
|
|
const $protectButton = $("#protect-button");
|
|
|
|
const $unprotectButton = $("#unprotect-button");
|
|
|
|
const $noteDetailWrapper = $("#note-detail-wrapper");
|
2018-04-09 10:38:52 +08:00
|
|
|
const $childrenOverview = $("#children-overview");
|
2018-07-29 22:06:13 +08:00
|
|
|
const $scriptArea = $("#note-detail-script-area");
|
2018-11-23 03:25:49 +08:00
|
|
|
const $savedIndicator = $("#saved-indicator");
|
2019-01-29 04:42:37 +08:00
|
|
|
const $body = $("body");
|
2018-03-25 23:09:17 +08:00
|
|
|
|
|
|
|
let currentNote = null;
|
|
|
|
|
|
|
|
let noteChangeDisabled = false;
|
|
|
|
|
|
|
|
let isNoteChanged = false;
|
|
|
|
|
2019-01-02 02:32:34 +08:00
|
|
|
let detailLoadedListeners = [];
|
|
|
|
|
2018-03-28 09:46:38 +08:00
|
|
|
const components = {
|
|
|
|
'code': noteDetailCode,
|
|
|
|
'text': noteDetailText,
|
2018-03-28 10:11:06 +08:00
|
|
|
'file': noteDetailFile,
|
2018-11-08 17:30:35 +08:00
|
|
|
'image': noteDetailImage,
|
2018-03-28 09:46:38 +08:00
|
|
|
'search': noteDetailSearch,
|
2018-10-18 17:25:33 +08:00
|
|
|
'render': noteDetailRender,
|
|
|
|
'relation-map': noteDetailRelationMap
|
2018-03-28 09:46:38 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
function getComponent(type) {
|
2018-09-03 22:05:28 +08:00
|
|
|
if (!type) {
|
|
|
|
type = getCurrentNote().type;
|
|
|
|
}
|
|
|
|
|
2018-03-28 09:46:38 +08:00
|
|
|
if (components[type]) {
|
|
|
|
return components[type];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
infoService.throwError("Unrecognized type: " + type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
function getCurrentNote() {
|
|
|
|
return currentNote;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getCurrentNoteId() {
|
2018-03-26 11:25:17 +08:00
|
|
|
return currentNote ? currentNote.noteId : null;
|
2018-03-25 23:09:17 +08:00
|
|
|
}
|
|
|
|
|
2018-03-27 12:22:02 +08:00
|
|
|
function getCurrentNoteType() {
|
|
|
|
const currentNote = getCurrentNote();
|
|
|
|
|
|
|
|
return currentNote ? currentNote.type : null;
|
|
|
|
}
|
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
function noteChanged() {
|
|
|
|
if (noteChangeDisabled) {
|
|
|
|
return;
|
|
|
|
}
|
2018-03-24 12:54:50 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
isNoteChanged = true;
|
2018-11-23 03:25:49 +08:00
|
|
|
|
|
|
|
$savedIndicator.fadeOut();
|
2018-03-25 23:09:17 +08:00
|
|
|
}
|
2017-11-05 05:54:27 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
async function reload() {
|
|
|
|
// no saving here
|
2017-11-05 05:54:27 +08:00
|
|
|
|
2018-04-09 10:38:52 +08:00
|
|
|
await loadNoteDetail(getCurrentNoteId());
|
2018-03-25 23:09:17 +08:00
|
|
|
}
|
2017-11-05 05:54:27 +08:00
|
|
|
|
2018-12-29 17:04:59 +08:00
|
|
|
async function switchToNote(noteId) {
|
2018-03-25 23:09:17 +08:00
|
|
|
if (getCurrentNoteId() !== noteId) {
|
|
|
|
await saveNoteIfChanged();
|
2017-11-05 05:54:27 +08:00
|
|
|
|
2018-12-29 17:04:59 +08:00
|
|
|
await loadNoteDetail(noteId);
|
2017-11-05 05:54:27 +08:00
|
|
|
}
|
2018-03-25 23:09:17 +08:00
|
|
|
}
|
2017-11-05 05:54:27 +08:00
|
|
|
|
2018-09-03 22:05:28 +08:00
|
|
|
function getCurrentNoteContent() {
|
|
|
|
return getComponent().getContent();
|
|
|
|
}
|
|
|
|
|
|
|
|
function onNoteChange(func) {
|
|
|
|
return getComponent().onNoteChange(func);
|
|
|
|
}
|
|
|
|
|
2018-04-08 20:21:49 +08:00
|
|
|
async function saveNote() {
|
2018-03-25 23:09:17 +08:00
|
|
|
const note = getCurrentNote();
|
2017-11-05 05:54:27 +08:00
|
|
|
|
2018-12-28 03:22:33 +08:00
|
|
|
if (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-03-28 09:36:01 +08:00
|
|
|
note.title = $noteTitle.val();
|
2019-02-07 04:29:23 +08:00
|
|
|
note.noteContent.content = getCurrentNoteContent(note);
|
2017-11-05 05:54:27 +08:00
|
|
|
|
2018-08-31 06:08:04 +08:00
|
|
|
// it's important to set the flag back to false immediatelly after retrieving title and content
|
|
|
|
// otherwise we might overwrite another change (especially async code)
|
|
|
|
isNoteChanged = false;
|
|
|
|
|
2018-03-28 09:36:01 +08:00
|
|
|
treeService.setNoteTitle(note.noteId, note.title);
|
2017-11-05 05:54:27 +08:00
|
|
|
|
2018-04-08 20:21:49 +08:00
|
|
|
await server.put('notes/' + note.noteId, note.dto);
|
2017-11-15 11:50:56 +08:00
|
|
|
|
2018-04-08 20:21:49 +08:00
|
|
|
if (note.isProtected) {
|
|
|
|
protectedSessionHolder.touchProtectedSession();
|
|
|
|
}
|
|
|
|
|
2018-11-23 03:25:49 +08:00
|
|
|
$savedIndicator.fadeIn();
|
2019-01-17 05:52:32 +08:00
|
|
|
|
|
|
|
// run async
|
|
|
|
bundleService.executeRelationBundles(getCurrentNote(), 'runOnNoteChange');
|
2018-03-25 23:09:17 +08:00
|
|
|
}
|
2018-01-27 08:54:27 +08:00
|
|
|
|
2018-04-08 20:21:49 +08:00
|
|
|
async function saveNoteIfChanged() {
|
2018-11-23 03:25:49 +08:00
|
|
|
if (isNoteChanged) {
|
|
|
|
await saveNote();
|
2018-04-08 20:21:49 +08:00
|
|
|
}
|
|
|
|
|
2018-11-23 03:25:49 +08:00
|
|
|
// make sure indicator is visible in a case there was some race condition.
|
|
|
|
$savedIndicator.fadeIn();
|
2018-04-08 20:21:49 +08:00
|
|
|
}
|
|
|
|
|
2019-01-29 04:42:37 +08:00
|
|
|
function updateNoteView() {
|
|
|
|
$noteDetailWrapper.toggleClass("protected", currentNote.isProtected);
|
|
|
|
$protectButton.toggleClass("active", currentNote.isProtected);
|
|
|
|
$protectButton.prop("disabled", currentNote.isProtected);
|
|
|
|
$unprotectButton.toggleClass("active", !currentNote.isProtected);
|
|
|
|
$unprotectButton.prop("disabled", !currentNote.isProtected || !protectedSessionHolder.isProtectedSessionAvailable());
|
|
|
|
|
|
|
|
for (const clazz of Array.from($body[0].classList)) { // create copy to safely iterate over while removing classes
|
|
|
|
if (clazz.startsWith("type-") || clazz.startsWith("mime-")) {
|
|
|
|
$body.removeClass(clazz);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$body.addClass(utils.getNoteTypeClass(currentNote.type));
|
|
|
|
$body.addClass(utils.getMimeTypeClass(currentNote.mime));
|
2018-03-25 23:09:17 +08:00
|
|
|
}
|
2017-11-05 05:54:27 +08:00
|
|
|
|
2018-03-27 12:22:02 +08:00
|
|
|
async function handleProtectedSession() {
|
2018-08-28 21:03:23 +08:00
|
|
|
const newSessionCreated = await protectedSessionService.ensureProtectedSession(currentNote.isProtected, false);
|
2018-03-27 12:22:02 +08:00
|
|
|
|
|
|
|
if (currentNote.isProtected) {
|
|
|
|
protectedSessionHolder.touchProtectedSession();
|
|
|
|
}
|
|
|
|
|
|
|
|
// this might be important if we focused on protected note when not in protected note and we got a dialog
|
|
|
|
// to login, but we chose instead to come to another node - at that point the dialog is still visible and this will close it.
|
|
|
|
protectedSessionService.ensureDialogIsClosed();
|
2018-08-28 21:03:23 +08:00
|
|
|
|
|
|
|
return newSessionCreated;
|
2018-03-27 12:22:02 +08:00
|
|
|
}
|
|
|
|
|
2018-04-09 10:38:52 +08:00
|
|
|
async function loadNoteDetail(noteId) {
|
2018-11-01 02:08:31 +08:00
|
|
|
const loadedNote = await loadNote(noteId);
|
|
|
|
|
|
|
|
// we will try to render the new note only if it's still the active one in the tree
|
|
|
|
// this is useful when user quickly switches notes (by e.g. holding down arrow) so that we don't
|
|
|
|
// try to render all those loaded notes one after each other. This only guarantees that correct note
|
|
|
|
// will be displayed independent of timing
|
|
|
|
const currentTreeNode = treeService.getCurrentNode();
|
|
|
|
if (currentTreeNode && currentTreeNode.data.noteId !== loadedNote.noteId) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-11-01 06:55:14 +08:00
|
|
|
// only now that we're in sync with tree active node we will switch currentNote
|
2018-11-01 02:08:31 +08:00
|
|
|
currentNote = loadedNote;
|
|
|
|
|
2018-12-24 17:10:36 +08:00
|
|
|
if (utils.isDesktop()) {
|
|
|
|
// needs to happen after loading the note itself because it references current noteId
|
|
|
|
attributeService.refreshAttributes();
|
|
|
|
}
|
2018-12-25 06:08:43 +08:00
|
|
|
else {
|
|
|
|
// mobile usually doesn't need attributes so we just invalidate
|
|
|
|
attributeService.invalidateAttributes();
|
|
|
|
}
|
2018-02-13 12:53:00 +08:00
|
|
|
|
2019-01-29 04:42:37 +08:00
|
|
|
updateNoteView();
|
2018-06-02 23:47:16 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
$noteDetailWrapper.show();
|
2017-11-05 05:54:27 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
noteChangeDisabled = true;
|
2017-11-05 05:54:27 +08:00
|
|
|
|
2018-03-27 12:22:02 +08:00
|
|
|
try {
|
|
|
|
$noteTitle.val(currentNote.title);
|
2017-12-25 22:46:11 +08:00
|
|
|
|
2018-12-24 17:10:36 +08:00
|
|
|
if (utils.isDesktop()) {
|
|
|
|
noteTypeService.setNoteType(currentNote.type);
|
|
|
|
noteTypeService.setNoteMime(currentNote.mime);
|
|
|
|
}
|
2017-11-05 05:54:27 +08:00
|
|
|
|
2018-10-31 19:29:01 +08:00
|
|
|
for (const componentType in components) {
|
|
|
|
if (componentType !== currentNote.type) {
|
|
|
|
components[componentType].cleanup();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-27 12:22:02 +08:00
|
|
|
$noteDetailComponents.hide();
|
2017-11-15 11:50:56 +08:00
|
|
|
|
2018-08-28 21:03:23 +08:00
|
|
|
const newSessionCreated = await handleProtectedSession();
|
|
|
|
if (newSessionCreated) {
|
|
|
|
// in such case we're reloading note anyway so no need to continue here.
|
|
|
|
return;
|
|
|
|
}
|
2018-08-17 21:21:59 +08:00
|
|
|
|
2018-12-28 03:22:33 +08:00
|
|
|
$noteTitle.removeAttr("readonly"); // this can be set by protected session service
|
|
|
|
|
2018-03-28 09:46:38 +08:00
|
|
|
await getComponent(currentNote.type).show();
|
2018-03-27 11:48:45 +08:00
|
|
|
}
|
2018-03-27 12:22:02 +08:00
|
|
|
finally {
|
|
|
|
noteChangeDisabled = false;
|
2018-03-25 23:09:17 +08:00
|
|
|
}
|
2018-01-22 12:36:09 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
treeService.setBranchBackgroundBasedOnProtectedStatus(noteId);
|
2018-01-24 12:41:22 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
// after loading new note make sure editor is scrolled to the top
|
2018-12-24 16:47:00 +08:00
|
|
|
getComponent(currentNote.type).scrollToTop();
|
2018-01-24 12:41:22 +08:00
|
|
|
|
2019-01-02 02:32:34 +08:00
|
|
|
fireDetailLoaded();
|
|
|
|
|
|
|
|
$scriptArea.empty();
|
2018-07-30 02:51:28 +08:00
|
|
|
|
2019-01-02 02:32:34 +08:00
|
|
|
await bundleService.executeRelationBundles(getCurrentNote(), 'runOnNoteView');
|
2018-08-06 14:59:26 +08:00
|
|
|
|
2019-01-02 02:32:34 +08:00
|
|
|
if (utils.isDesktop()) {
|
2018-12-24 17:10:36 +08:00
|
|
|
await attributeService.showAttributes();
|
2018-08-07 18:48:11 +08:00
|
|
|
|
2018-12-24 17:10:36 +08:00
|
|
|
await showChildrenOverview();
|
|
|
|
}
|
2018-04-09 10:38:52 +08:00
|
|
|
}
|
|
|
|
|
2018-08-29 02:55:21 +08:00
|
|
|
async function showChildrenOverview() {
|
2018-10-25 23:11:50 +08:00
|
|
|
const note = getCurrentNote();
|
2018-11-09 03:01:25 +08:00
|
|
|
const attributes = await attributeService.getAttributes();
|
2018-11-08 17:11:00 +08:00
|
|
|
const hideChildrenOverview = attributes.some(attr => attr.type === 'label' && attr.name === 'hideChildrenOverview')
|
|
|
|
|| note.type === 'relation-map'
|
|
|
|
|| note.type === 'image'
|
|
|
|
|| note.type === 'file';
|
2018-08-29 02:55:21 +08:00
|
|
|
|
2018-04-11 11:15:41 +08:00
|
|
|
if (hideChildrenOverview) {
|
|
|
|
$childrenOverview.hide();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-04-09 10:38:52 +08:00
|
|
|
$childrenOverview.empty();
|
|
|
|
|
|
|
|
const notePath = treeService.getCurrentNotePath();
|
|
|
|
|
|
|
|
for (const childBranch of await note.getChildBranches()) {
|
|
|
|
const link = $('<a>', {
|
|
|
|
href: 'javascript:',
|
|
|
|
text: await treeUtils.getNoteTitle(childBranch.noteId, childBranch.parentNoteId)
|
2018-08-15 16:14:14 +08:00
|
|
|
}).attr('data-action', 'note').attr('data-note-path', notePath + '/' + childBranch.noteId);
|
2018-04-09 10:38:52 +08:00
|
|
|
|
2019-01-27 21:26:39 +08:00
|
|
|
const childEl = $('<div class="child-overview-item">').html(link);
|
2018-04-09 10:38:52 +08:00
|
|
|
$childrenOverview.append(childEl);
|
|
|
|
}
|
2018-04-11 11:15:41 +08:00
|
|
|
|
|
|
|
$childrenOverview.show();
|
2018-03-25 23:09:17 +08:00
|
|
|
}
|
2018-03-07 13:17:18 +08:00
|
|
|
|
2018-08-30 04:28:58 +08:00
|
|
|
async function loadNote(noteId) {
|
|
|
|
const row = await server.get('notes/' + noteId);
|
|
|
|
|
|
|
|
return new NoteFull(treeCache, row);
|
|
|
|
}
|
|
|
|
|
2018-09-07 16:50:05 +08:00
|
|
|
function focusOnTitle() {
|
|
|
|
$noteTitle.focus();
|
2018-08-30 04:28:58 +08:00
|
|
|
}
|
|
|
|
|
2019-01-11 05:46:08 +08:00
|
|
|
function focusAndSelectTitle() {
|
|
|
|
$noteTitle.focus().select();
|
|
|
|
}
|
|
|
|
|
2019-01-02 02:32:34 +08:00
|
|
|
/**
|
|
|
|
* Since detail loading may take some time and user might just browse through the notes using UP-DOWN keys,
|
|
|
|
* we intentionally decouple activation of the note in the tree and full load of the note so just avaiting on
|
|
|
|
* fancytree's activate() won't wait for the full load.
|
|
|
|
*
|
|
|
|
* This causes an issue where in some cases you want to do some action after detail is loaded. For this reason
|
|
|
|
* we provide the listeners here which will be triggered after the detail is loaded and if the loaded note
|
|
|
|
* is the one registered in the listener.
|
|
|
|
*/
|
|
|
|
function addDetailLoadedListener(noteId, callback) {
|
|
|
|
detailLoadedListeners.push({ noteId, callback });
|
|
|
|
}
|
|
|
|
|
|
|
|
function fireDetailLoaded() {
|
|
|
|
for (const {noteId, callback} of detailLoadedListeners) {
|
|
|
|
if (noteId === currentNote.noteId) {
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// all the listeners are one time only
|
|
|
|
detailLoadedListeners = [];
|
|
|
|
}
|
|
|
|
|
2018-08-30 04:28:58 +08:00
|
|
|
messagingService.subscribeToSyncMessages(syncData => {
|
|
|
|
if (syncData.some(sync => sync.entityName === 'notes' && sync.entityId === getCurrentNoteId())) {
|
|
|
|
infoService.showMessage('Reloading note because of background changes');
|
|
|
|
|
|
|
|
reload();
|
|
|
|
}
|
2018-08-06 20:43:42 +08:00
|
|
|
});
|
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
$(document).ready(() => {
|
|
|
|
$noteTitle.on('input', () => {
|
|
|
|
noteChanged();
|
2017-11-30 10:13:12 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
const title = $noteTitle.val();
|
2017-11-05 05:54:27 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
treeService.setNoteTitle(getCurrentNoteId(), title);
|
2017-11-05 05:54:27 +08:00
|
|
|
});
|
|
|
|
|
2018-03-27 12:22:02 +08:00
|
|
|
noteDetailText.focus();
|
2018-03-25 23:09:17 +08:00
|
|
|
});
|
|
|
|
|
2018-03-27 10:29:14 +08:00
|
|
|
// this makes sure that when user e.g. reloads the page or navigates away from the page, the note's content is saved
|
|
|
|
// this sends the request asynchronously and doesn't wait for result
|
2018-03-28 10:27:46 +08:00
|
|
|
$(window).on('beforeunload', () => { saveNoteIfChanged(); }); // don't convert to short form, handler doesn't like returned promise
|
2018-03-27 10:29:14 +08:00
|
|
|
|
2018-11-23 03:25:49 +08:00
|
|
|
setInterval(saveNoteIfChanged, 3000);
|
2018-03-25 23:09:17 +08:00
|
|
|
|
|
|
|
export default {
|
|
|
|
reload,
|
|
|
|
switchToNote,
|
2019-01-29 04:42:37 +08:00
|
|
|
updateNoteView,
|
2018-03-25 23:09:17 +08:00
|
|
|
loadNote,
|
|
|
|
getCurrentNote,
|
2019-02-07 03:19:25 +08:00
|
|
|
getCurrentNoteContent,
|
2018-03-25 23:09:17 +08:00
|
|
|
getCurrentNoteType,
|
|
|
|
getCurrentNoteId,
|
2018-09-07 16:50:05 +08:00
|
|
|
focusOnTitle,
|
2019-01-11 05:46:08 +08:00
|
|
|
focusAndSelectTitle,
|
2018-04-08 20:21:49 +08:00
|
|
|
saveNote,
|
2018-03-27 12:22:02 +08:00
|
|
|
saveNoteIfChanged,
|
2018-09-03 22:05:28 +08:00
|
|
|
noteChanged,
|
2019-01-02 02:32:34 +08:00
|
|
|
onNoteChange,
|
|
|
|
addDetailLoadedListener
|
2018-03-25 23:09:17 +08:00
|
|
|
};
|