2018-03-25 13:41:29 -04:00
|
|
|
import treeService from './tree.js';
|
2019-05-01 22:19:29 +02:00
|
|
|
import NoteContext from './note_context.js';
|
2018-03-25 13:41:29 -04:00
|
|
|
import noteTypeService from './note_type.js';
|
|
|
|
import protectedSessionService from './protected_session.js';
|
2018-03-25 21:16:57 -04:00
|
|
|
import protectedSessionHolder from './protected_session_holder.js';
|
2018-03-25 11:09:17 -04:00
|
|
|
import server from './server.js';
|
2018-03-25 21:16:57 -04:00
|
|
|
import messagingService from "./messaging.js";
|
2018-03-25 21:29:35 -04:00
|
|
|
import infoService from "./info.js";
|
2018-03-25 23:25:17 -04:00
|
|
|
import treeCache from "./tree_cache.js";
|
|
|
|
import NoteFull from "../entities/note_full.js";
|
2018-07-29 16:06:13 +02:00
|
|
|
import bundleService from "./bundle.js";
|
2018-11-08 20:01:25 +01:00
|
|
|
import attributeService from "./attributes.js";
|
2018-12-24 10:10:36 +01:00
|
|
|
import utils from "./utils.js";
|
2019-02-26 21:37:15 +01:00
|
|
|
import importDialog from "../dialogs/import.js";
|
2018-03-25 11:09:17 -04:00
|
|
|
|
2019-05-01 10:17:17 +02:00
|
|
|
const $noteTabsContainer = $("#note-tab-container");
|
2018-11-22 20:25:49 +01:00
|
|
|
const $savedIndicator = $("#saved-indicator");
|
2018-03-25 11:09:17 -04:00
|
|
|
|
|
|
|
let noteChangeDisabled = false;
|
|
|
|
|
2019-01-01 19:32:34 +01:00
|
|
|
let detailLoadedListeners = [];
|
|
|
|
|
2019-03-14 20:21:27 +01:00
|
|
|
function getActiveNote() {
|
2019-05-01 22:19:29 +02:00
|
|
|
const activeContext = getActiveContext();
|
|
|
|
return activeContext ? activeContext.note : null;
|
2018-03-25 11:09:17 -04:00
|
|
|
}
|
|
|
|
|
2019-03-14 20:21:27 +01:00
|
|
|
function getActiveNoteId() {
|
2019-05-01 22:19:29 +02:00
|
|
|
const activeNote = getActiveNote();
|
|
|
|
|
2019-03-14 20:21:27 +01:00
|
|
|
return activeNote ? activeNote.noteId : null;
|
2018-03-25 11:09:17 -04:00
|
|
|
}
|
|
|
|
|
2019-03-14 20:21:27 +01:00
|
|
|
function getActiveNoteType() {
|
|
|
|
const activeNote = getActiveNote();
|
2018-03-27 00:22:02 -04:00
|
|
|
|
2019-03-14 20:21:27 +01:00
|
|
|
return activeNote ? activeNote.type : null;
|
2018-03-27 00:22:02 -04:00
|
|
|
}
|
|
|
|
|
2018-03-25 11:09:17 -04:00
|
|
|
async function reload() {
|
|
|
|
// no saving here
|
2017-11-04 17:54:27 -04:00
|
|
|
|
2019-03-14 20:21:27 +01:00
|
|
|
await loadNoteDetail(getActiveNoteId());
|
2018-03-25 11:09:17 -04:00
|
|
|
}
|
2017-11-04 17:54:27 -04:00
|
|
|
|
2018-12-29 10:04:59 +01:00
|
|
|
async function switchToNote(noteId) {
|
2019-05-01 23:06:18 +02:00
|
|
|
if (Object.keys(noteContexts).length === 0) {
|
|
|
|
const tabContent = $("#note-tab-content-template").clone();
|
|
|
|
|
|
|
|
tabContent.removeAttr('id');
|
|
|
|
tabContent.attr('data-note-id', noteId);
|
|
|
|
|
|
|
|
$noteTabsContainer.append(tabContent);
|
|
|
|
|
|
|
|
noteContexts[noteId] = new NoteContext(noteId);
|
|
|
|
}
|
|
|
|
|
|
|
|
//if (getActiveNoteId() !== noteId) {
|
2019-05-01 22:19:29 +02:00
|
|
|
await saveNotesIfChanged();
|
2017-11-04 17:54:27 -04:00
|
|
|
|
2018-12-29 10:04:59 +01:00
|
|
|
await loadNoteDetail(noteId);
|
2019-05-01 23:06:18 +02:00
|
|
|
//}
|
2018-03-25 11:09:17 -04:00
|
|
|
}
|
2017-11-04 17:54:27 -04:00
|
|
|
|
2019-03-14 20:21:27 +01:00
|
|
|
function getActiveNoteContent() {
|
2019-05-01 22:19:29 +02:00
|
|
|
return getActiveContext().getComponent().getContent();
|
2018-09-03 16:05:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function onNoteChange(func) {
|
2019-05-01 22:19:29 +02:00
|
|
|
return getActiveContext().getComponent().onNoteChange(func);
|
2018-09-03 16:05:28 +02:00
|
|
|
}
|
|
|
|
|
2019-05-01 22:19:29 +02:00
|
|
|
async function saveNotesIfChanged() {
|
2019-05-01 23:06:18 +02:00
|
|
|
for (const ctx of Object.values(noteContexts)) {
|
2019-05-01 22:19:29 +02:00
|
|
|
await ctx.saveNoteIfChanged();
|
2018-12-27 20:22:33 +01:00
|
|
|
}
|
|
|
|
|
2019-05-01 22:19:29 +02:00
|
|
|
// make sure indicator is visible in a case there was some race condition.
|
|
|
|
$savedIndicator.fadeIn();
|
|
|
|
}
|
2017-11-04 17:54:27 -04:00
|
|
|
|
2019-05-01 22:19:29 +02:00
|
|
|
async function handleProtectedSession() {
|
|
|
|
const newSessionCreated = await protectedSessionService.ensureProtectedSession(getActiveNote().isProtected, false);
|
2017-11-14 22:50:56 -05:00
|
|
|
|
2019-05-01 22:19:29 +02:00
|
|
|
if (getActiveNote().isProtected) {
|
2018-04-08 08:21:49 -04:00
|
|
|
protectedSessionHolder.touchProtectedSession();
|
|
|
|
}
|
|
|
|
|
2019-05-01 22:19:29 +02:00
|
|
|
// 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();
|
2019-01-16 22:52:32 +01:00
|
|
|
|
2019-05-01 22:19:29 +02:00
|
|
|
return newSessionCreated;
|
2018-03-25 11:09:17 -04:00
|
|
|
}
|
2018-01-26 19:54:27 -05:00
|
|
|
|
2019-05-01 22:19:29 +02:00
|
|
|
/** @type {Object.<string, NoteContext>} */
|
|
|
|
const noteContexts = {};
|
2018-04-08 08:21:49 -04:00
|
|
|
|
2019-05-01 22:19:29 +02:00
|
|
|
/** @returns {NoteContext} */
|
|
|
|
function getContext(noteId) {
|
|
|
|
if (noteId in noteContexts) {
|
|
|
|
return noteContexts[noteId];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
throw new Error(`Can't find note context for ${noteId}`);
|
|
|
|
}
|
2018-04-08 08:21:49 -04:00
|
|
|
}
|
|
|
|
|
2019-05-01 22:19:29 +02:00
|
|
|
/** @returns {NoteContext} */
|
|
|
|
function getActiveContext() {
|
|
|
|
const currentTreeNode = treeService.getActiveNode();
|
2019-01-28 21:42:37 +01:00
|
|
|
|
2019-05-01 22:19:29 +02:00
|
|
|
return getContext(currentTreeNode.data.noteId);
|
2018-03-25 11:09:17 -04:00
|
|
|
}
|
2017-11-04 17:54:27 -04:00
|
|
|
|
2019-05-01 22:19:29 +02:00
|
|
|
function showTab(noteId) {
|
2019-05-01 23:06:18 +02:00
|
|
|
for (const ctx of Object.values(noteContexts)) {
|
2019-05-01 22:19:29 +02:00
|
|
|
ctx.$noteTab.toggle(ctx.noteId === noteId);
|
2018-03-27 00:22:02 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-08 22:38:52 -04:00
|
|
|
async function loadNoteDetail(noteId) {
|
2019-05-01 22:19:29 +02:00
|
|
|
const ctx = getContext(noteId);
|
2018-10-31 19:08:31 +01: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
|
2019-03-20 22:28:54 +01:00
|
|
|
const currentTreeNode = treeService.getActiveNode();
|
2018-10-31 19:08:31 +01:00
|
|
|
if (currentTreeNode && currentTreeNode.data.noteId !== loadedNote.noteId) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-03-14 20:21:27 +01:00
|
|
|
// only now that we're in sync with tree active node we will switch activeNote
|
2019-05-01 22:19:29 +02:00
|
|
|
ctx.note = loadedNote;
|
|
|
|
ctx.noteId = loadedNote.noteId;
|
2018-10-31 19:08:31 +01:00
|
|
|
|
2018-12-24 10:10:36 +01:00
|
|
|
if (utils.isDesktop()) {
|
2019-03-20 22:28:54 +01:00
|
|
|
// needs to happen after loading the note itself because it references active noteId
|
2019-05-01 22:19:29 +02:00
|
|
|
// FIXME
|
|
|
|
//attributeService.refreshAttributes();
|
2018-12-24 10:10:36 +01:00
|
|
|
}
|
2018-12-24 23:08:43 +01:00
|
|
|
else {
|
|
|
|
// mobile usually doesn't need attributes so we just invalidate
|
2019-05-01 22:19:29 +02:00
|
|
|
// FIXME
|
|
|
|
//attributeService.invalidateAttributes();
|
2018-12-24 23:08:43 +01:00
|
|
|
}
|
2018-02-12 23:53:00 -05:00
|
|
|
|
2019-05-01 22:19:29 +02:00
|
|
|
ctx.updateNoteView();
|
2018-06-02 11:47:16 -04:00
|
|
|
|
2019-05-01 22:19:29 +02:00
|
|
|
showTab(noteId);
|
2017-11-04 17:54:27 -04:00
|
|
|
|
2018-03-25 11:09:17 -04:00
|
|
|
noteChangeDisabled = true;
|
2017-11-04 17:54:27 -04:00
|
|
|
|
2018-03-27 00:22:02 -04:00
|
|
|
try {
|
2019-05-01 22:19:29 +02:00
|
|
|
ctx.$noteTitle.val(ctx.note.title);
|
2017-12-25 09:46:11 -05:00
|
|
|
|
2018-12-24 10:10:36 +01:00
|
|
|
if (utils.isDesktop()) {
|
2019-05-01 22:19:29 +02:00
|
|
|
noteTypeService.setNoteType(ctx.note.type);
|
|
|
|
noteTypeService.setNoteMime(ctx.note.mime);
|
2018-12-24 10:10:36 +01:00
|
|
|
}
|
2017-11-04 17:54:27 -04:00
|
|
|
|
2019-05-01 22:19:29 +02:00
|
|
|
for (const componentType in ctx.components) {
|
|
|
|
if (componentType !== ctx.note.type) {
|
|
|
|
ctx.components[componentType].cleanup();
|
2018-10-31 12:29:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-01 22:19:29 +02:00
|
|
|
ctx.$noteDetailComponents.hide();
|
2017-11-14 22:50:56 -05:00
|
|
|
|
2018-08-28 15:03:23 +02: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 15:21:59 +02:00
|
|
|
|
2019-05-01 22:19:29 +02:00
|
|
|
ctx.$noteTitle.removeAttr("readonly"); // this can be set by protected session service
|
2018-12-27 20:22:33 +01:00
|
|
|
|
2019-05-01 22:19:29 +02:00
|
|
|
await ctx.getComponent(ctx.note.type).show(ctx);
|
2018-03-26 23:48:45 -04:00
|
|
|
}
|
2018-03-27 00:22:02 -04:00
|
|
|
finally {
|
|
|
|
noteChangeDisabled = false;
|
2018-03-25 11:09:17 -04:00
|
|
|
}
|
2018-01-21 23:36:09 -05:00
|
|
|
|
2018-03-25 11:09:17 -04:00
|
|
|
treeService.setBranchBackgroundBasedOnProtectedStatus(noteId);
|
2018-01-23 23:41:22 -05:00
|
|
|
|
2018-03-25 11:09:17 -04:00
|
|
|
// after loading new note make sure editor is scrolled to the top
|
2019-05-01 22:19:29 +02:00
|
|
|
ctx.getComponent(ctx.note.type).scrollToTop();
|
2018-01-23 23:41:22 -05:00
|
|
|
|
2019-01-01 19:32:34 +01:00
|
|
|
fireDetailLoaded();
|
|
|
|
|
2019-05-01 22:19:29 +02:00
|
|
|
ctx.$scriptArea.empty();
|
2018-07-29 20:51:28 +02:00
|
|
|
|
2019-03-14 20:21:27 +01:00
|
|
|
await bundleService.executeRelationBundles(getActiveNote(), 'runOnNoteView');
|
2018-08-06 08:59:26 +02:00
|
|
|
|
2019-01-01 19:32:34 +01:00
|
|
|
if (utils.isDesktop()) {
|
2018-12-24 10:10:36 +01:00
|
|
|
await attributeService.showAttributes();
|
2018-08-07 12:48:11 +02:00
|
|
|
|
2019-05-01 22:19:29 +02:00
|
|
|
await ctx.showChildrenOverview();
|
2018-12-24 10:10:36 +01:00
|
|
|
}
|
2018-04-08 22:38:52 -04:00
|
|
|
}
|
|
|
|
|
2018-08-29 22:28:58 +02:00
|
|
|
async function loadNote(noteId) {
|
|
|
|
const row = await server.get('notes/' + noteId);
|
|
|
|
|
|
|
|
return new NoteFull(treeCache, row);
|
|
|
|
}
|
|
|
|
|
2018-09-07 10:50:05 +02:00
|
|
|
function focusOnTitle() {
|
2019-05-01 22:19:29 +02:00
|
|
|
getActiveContext().$noteTitle.focus();
|
2018-08-29 22:28:58 +02:00
|
|
|
}
|
|
|
|
|
2019-01-10 22:46:08 +01:00
|
|
|
function focusAndSelectTitle() {
|
2019-05-01 22:19:29 +02:00
|
|
|
getActiveContext().$noteTitle.focus().select();
|
2019-01-10 22:46:08 +01:00
|
|
|
}
|
|
|
|
|
2019-01-01 19:32:34 +01: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) {
|
2019-05-01 22:19:29 +02:00
|
|
|
if (noteId === getActiveNoteId()) {
|
2019-01-01 19:32:34 +01:00
|
|
|
callback();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// all the listeners are one time only
|
|
|
|
detailLoadedListeners = [];
|
|
|
|
}
|
|
|
|
|
2018-08-29 22:28:58 +02:00
|
|
|
messagingService.subscribeToSyncMessages(syncData => {
|
2019-03-14 20:21:27 +01:00
|
|
|
if (syncData.some(sync => sync.entityName === 'notes' && sync.entityId === getActiveNoteId())) {
|
2018-08-29 22:28:58 +02:00
|
|
|
infoService.showMessage('Reloading note because of background changes');
|
|
|
|
|
|
|
|
reload();
|
|
|
|
}
|
2018-08-06 14:43:42 +02:00
|
|
|
});
|
|
|
|
|
2019-05-01 10:17:17 +02:00
|
|
|
$noteTabsContainer.on("dragover", e => e.preventDefault());
|
2019-02-26 21:37:15 +01:00
|
|
|
|
2019-05-01 10:17:17 +02:00
|
|
|
$noteTabsContainer.on("dragleave", e => e.preventDefault());
|
2019-02-26 21:37:15 +01:00
|
|
|
|
2019-05-01 10:17:17 +02:00
|
|
|
$noteTabsContainer.on("drop", e => {
|
2019-03-14 20:21:27 +01:00
|
|
|
importDialog.uploadFiles(getActiveNoteId(), e.originalEvent.dataTransfer.files, {
|
2019-02-26 21:37:15 +01:00
|
|
|
safeImport: true,
|
|
|
|
shrinkImages: true,
|
|
|
|
textImportedAsText: true,
|
|
|
|
codeImportedAsCode: true,
|
|
|
|
explodeArchives: true
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2018-03-26 22:29:14 -04: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
|
2019-05-01 22:19:29 +02:00
|
|
|
$(window).on('beforeunload', () => { saveNotesIfChanged(); }); // don't convert to short form, handler doesn't like returned promise
|
2018-03-26 22:29:14 -04:00
|
|
|
|
2019-05-01 22:19:29 +02:00
|
|
|
setInterval(saveNotesIfChanged, 3000);
|
2018-03-25 11:09:17 -04:00
|
|
|
|
|
|
|
export default {
|
|
|
|
reload,
|
|
|
|
switchToNote,
|
|
|
|
loadNote,
|
2019-03-14 20:21:27 +01:00
|
|
|
getActiveNote,
|
|
|
|
getActiveNoteContent,
|
|
|
|
getActiveNoteType,
|
|
|
|
getActiveNoteId,
|
2018-09-07 10:50:05 +02:00
|
|
|
focusOnTitle,
|
2019-01-10 22:46:08 +01:00
|
|
|
focusAndSelectTitle,
|
2019-05-01 22:19:29 +02:00
|
|
|
saveNotesIfChanged,
|
2019-01-01 19:32:34 +01:00
|
|
|
onNoteChange,
|
|
|
|
addDetailLoadedListener
|
2018-03-25 11:09:17 -04:00
|
|
|
};
|