import treeService from './tree.js'; import treeUtils from './tree_utils.js'; import noteTypeService from './note_type.js'; import protectedSessionService from './protected_session.js'; import protectedSessionHolder from './protected_session_holder.js'; import utils from './utils.js'; import server from './server.js'; import messagingService from "./messaging.js"; import infoService from "./info.js"; import linkService from "./link.js"; import treeCache from "./tree_cache.js"; import NoteFull from "../entities/note_full.js"; import noteDetailCode from './note_detail_code.js'; import noteDetailText from './note_detail_text.js'; import noteDetailFile from './note_detail_file.js'; import noteDetailSearch from './note_detail_search.js'; import noteDetailRender from './note_detail_render.js'; import bundleService from "./bundle.js"; import noteAutocompleteService from "./note_autocomplete.js"; const $noteTitle = $("#note-title"); const $noteDetailComponents = $(".note-detail-component"); const $protectButton = $("#protect-button"); const $unprotectButton = $("#unprotect-button"); const $noteDetailWrapper = $("#note-detail-wrapper"); const $noteIdDisplay = $("#note-id-display"); const $attributeList = $("#attribute-list"); const $attributeListInner = $("#attribute-list-inner"); const $childrenOverview = $("#children-overview"); const $scriptArea = $("#note-detail-script-area"); const $promotedAttributesContainer = $("#note-detail-promoted-attributes"); let currentNote = null; let noteChangeDisabled = false; let isNoteChanged = false; const components = { 'code': noteDetailCode, 'text': noteDetailText, 'file': noteDetailFile, 'search': noteDetailSearch, 'render': noteDetailRender }; function getComponent(type) { if (components[type]) { return components[type]; } else { infoService.throwError("Unrecognized type: " + type); } } function getCurrentNote() { return currentNote; } function getCurrentNoteId() { return currentNote ? currentNote.noteId : null; } function getCurrentNoteType() { const currentNote = getCurrentNote(); return currentNote ? currentNote.type : null; } function noteChanged() { if (noteChangeDisabled) { return; } isNoteChanged = true; } async function reload() { // no saving here await loadNoteDetail(getCurrentNoteId()); } async function switchToNote(noteId) { if (getCurrentNoteId() !== noteId) { await saveNoteIfChanged(); await loadNoteDetail(noteId); } } async function saveNote() { const note = getCurrentNote(); note.title = $noteTitle.val(); note.content = getComponent(note.type).getContent(); treeService.setNoteTitle(note.noteId, note.title); await server.put('notes/' + note.noteId, note.dto); isNoteChanged = false; if (note.isProtected) { protectedSessionHolder.touchProtectedSession(); } infoService.showMessage("Saved!"); } async function saveNoteIfChanged() { if (!isNoteChanged) { return; } await saveNote(); } function setNoteBackgroundIfProtected(note) { const isProtected = !!note.isProtected; $noteDetailWrapper.toggleClass("protected", isProtected); $protectButton.toggleClass("active", isProtected); $unprotectButton.toggleClass("active", !isProtected); } let isNewNoteCreated = false; function newNoteCreated() { isNewNoteCreated = true; } async function handleProtectedSession() { await protectedSessionService.ensureProtectedSession(currentNote.isProtected, false); 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(); } async function loadNoteDetail(noteId) { currentNote = await loadNote(noteId); if (isNewNoteCreated) { isNewNoteCreated = false; $noteTitle.focus().select(); } $noteIdDisplay.html(noteId); setNoteBackgroundIfProtected(currentNote); await handleProtectedSession(); $noteDetailWrapper.show(); noteChangeDisabled = true; try { $noteTitle.val(currentNote.title); noteTypeService.setNoteType(currentNote.type); noteTypeService.setNoteMime(currentNote.mime); $noteDetailComponents.hide(); await getComponent(currentNote.type).show(); } finally { noteChangeDisabled = false; } treeService.setBranchBackgroundBasedOnProtectedStatus(noteId); // after loading new note make sure editor is scrolled to the top $noteDetailWrapper.scrollTop(0); $scriptArea.html(''); await bundleService.executeRelationBundles(getCurrentNote(), 'runOnNoteView'); const attributes = await loadAttributes(); const hideChildrenOverview = attributes.some(attr => attr.type === 'label' && attr.name === 'hideChildrenOverview'); await showChildrenOverview(hideChildrenOverview); } async function showChildrenOverview(hideChildrenOverview) { if (hideChildrenOverview) { $childrenOverview.hide(); return; } const note = getCurrentNote(); $childrenOverview.empty(); const notePath = treeService.getCurrentNotePath(); for (const childBranch of await note.getChildBranches()) { const link = $('', { href: 'javascript:', text: await treeUtils.getNoteTitle(childBranch.noteId, childBranch.parentNoteId) }).attr('data-action', 'note').attr('data-note-path', notePath + '/' + childBranch.noteId); const childEl = $('
').html(link); $childrenOverview.append(childEl); } $childrenOverview.show(); } async function loadAttributes() { $promotedAttributesContainer.empty(); $attributeList.hide(); const noteId = getCurrentNoteId(); const attributes = await server.get('notes/' + noteId + '/attributes'); const promoted = attributes.filter(attr => (attr.type === 'label-definition' || attr.type === 'relation-definition') && attr.value.isPromoted); let idx = 1; async function createRow(definitionAttr, valueAttr) { const definition = definitionAttr.value; const inputId = "promoted-input-" + idx; const $tr = $(""); const $labelCell = $("").append(valueAttr.name); const $input = $("") .prop("id", inputId) .prop("tabindex", definitionAttr.position) .prop("attribute-id", valueAttr.isOwned ? valueAttr.attributeId : '') // if not owned, we'll force creation of a new attribute instead of updating the inherited one .prop("attribute-type", valueAttr.type) .prop("attribute-name", valueAttr.name) .prop("value", valueAttr.value) .addClass("form-control") .addClass("promoted-attribute-input"); idx++; const $inputCell = $("").append($("
").addClass("input-group").append($input)); const $actionCell = $(""); const $multiplicityCell = $(""); $tr .append($labelCell) .append($inputCell) .append($actionCell) .append($multiplicityCell); if (valueAttr.type === 'label') { if (definition.labelType === 'text') { $input.prop("type", "text"); // no need to await for this, can be done asynchronously server.get('attributes/values/' + encodeURIComponent(valueAttr.name)).then(attributeValues => { if (attributeValues.length === 0) { return; } $input.autocomplete({ // shouldn't be required and autocomplete should just accept array of strings, but that fails // because we have overriden filter() function in autocomplete.js source: attributeValues.map(attribute => { return { attribute: attribute, value: attribute } }), minLength: 0 }); $input.focus(() => $input.autocomplete("search", "")); }); } else if (definition.labelType === 'number') { $input.prop("type", "number"); } else if (definition.labelType === 'boolean') { $input.prop("type", "checkbox"); if (valueAttr.value === "true") { $input.prop("checked", "checked"); } } else if (definition.labelType === 'date') { $input.prop("type", "text"); $input.datepicker({ changeMonth: true, changeYear: true, dateFormat: "yy-mm-dd" }); const $todayButton = $("