mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
450 lines
13 KiB
JavaScript
450 lines
13 KiB
JavaScript
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";
|
|
|
|
const $noteTitle = $("#note-title");
|
|
|
|
const $noteDetailComponents = $(".note-detail-component");
|
|
|
|
const $protectButton = $("#protect-button");
|
|
const $unprotectButton = $("#unprotect-button");
|
|
const $noteDetailWrapper = $("#note-detail-wrapper");
|
|
const $noteDetailComponentWrapper = $("#note-detail-component-wrapper");
|
|
const $noteIdDisplay = $("#note-id-display");
|
|
const $attributeList = $("#attribute-list");
|
|
const $attributeListInner = $("#attribute-list-inner");
|
|
const $labelList = $("#label-list");
|
|
const $labelListInner = $("#label-list-inner");
|
|
const $relationList = $("#relation-list");
|
|
const $relationListInner = $("#relation-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;
|
|
|
|
$noteDetailComponentWrapper.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);
|
|
|
|
const labels = await loadLabelList();
|
|
|
|
const hideChildrenOverview = labels.some(label => label.name === 'hideChildrenOverview');
|
|
await showChildrenOverview(hideChildrenOverview);
|
|
|
|
await loadRelationList();
|
|
|
|
$scriptArea.html('');
|
|
|
|
await bundleService.executeRelationBundles(getCurrentNote(), 'runOnNoteView');
|
|
|
|
await loadAttributes();
|
|
}
|
|
|
|
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 = $('<a>', {
|
|
href: 'javascript:',
|
|
text: await treeUtils.getNoteTitle(childBranch.noteId, childBranch.parentNoteId)
|
|
}).attr('action', 'note').attr('note-path', notePath + '/' + childBranch.noteId);
|
|
|
|
const childEl = $('<div class="child-overview">').html(link);
|
|
$childrenOverview.append(childEl);
|
|
}
|
|
|
|
$childrenOverview.show();
|
|
}
|
|
|
|
async function loadAttributes() {
|
|
$promotedAttributesContainer.empty();
|
|
|
|
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;
|
|
|
|
if (promoted.length > 0) {
|
|
for (const definitionAttr of promoted) {
|
|
const definitionType = definitionAttr.type;
|
|
const definition = definitionAttr.value;
|
|
const valueType = definitionType.substr(0, definitionType.length - 11);
|
|
|
|
const valueAttrs = attributes.filter(el => el.name === definitionAttr.name && el.type === valueType);
|
|
|
|
if (valueAttrs.length === 0) {
|
|
valueAttrs.push({
|
|
attributeId: "",
|
|
type: valueType,
|
|
name: definitionAttr.name,
|
|
value: ""
|
|
});
|
|
}
|
|
|
|
for (const valueAttr of valueAttrs) {
|
|
const inputId = "promoted-input-" + idx;
|
|
const $tr = $("<tr>");
|
|
const $labelCell = $("<th>").append(valueAttr.name);
|
|
const $input = $("<input>")
|
|
.prop("id", inputId)
|
|
.prop("attribute-id", valueAttr.attributeId)
|
|
.prop("attribute-type", valueAttr.type)
|
|
.prop("attribute-name", valueAttr.name)
|
|
.prop("value", valueAttr.value)
|
|
.addClass("form-control")
|
|
.addClass("promoted-attribute-input");
|
|
|
|
const $inputCell = $("<td>").append($input);
|
|
|
|
if (valueAttr.type === 'label') {
|
|
if (definition.labelType === 'text') {
|
|
$input.prop("type", "text");
|
|
}
|
|
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 (definitionAttr.labelType === 'date') {
|
|
$input.prop("type", "text");
|
|
$input.addClass("date");
|
|
}
|
|
else if (definitionAttr.labelType === 'datetime') {
|
|
$input.prop("type", "text");
|
|
$input.addClass("datetime");
|
|
}
|
|
}
|
|
|
|
$tr.append($labelCell).append($inputCell);
|
|
|
|
$promotedAttributesContainer.append($tr);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
$attributeListInner.html('');
|
|
|
|
if (attributes.length > 0) {
|
|
for (const attribute of attributes) {
|
|
if (attribute.type === 'label') {
|
|
$attributeListInner.append(utils.formatLabel(attribute) + " ");
|
|
}
|
|
else if (attribute.type === 'relation') {
|
|
$attributeListInner.append(attribute.name + "=");
|
|
$attributeListInner.append(await linkService.createNoteLink(attribute.value));
|
|
$attributeListInner.append(" ");
|
|
}
|
|
else if (attribute.type === 'label-definition' || attribute.type === 'relation-definition') {
|
|
$attributeListInner.append(attribute.name + " definition ");
|
|
}
|
|
else {
|
|
messagingService.logError("Unknown attr type: " + attribute.type);
|
|
}
|
|
}
|
|
|
|
$attributeList.show();
|
|
}
|
|
else {
|
|
$attributeList.hide();
|
|
}
|
|
}
|
|
}
|
|
|
|
async function loadLabelList() {
|
|
const noteId = getCurrentNoteId();
|
|
|
|
const labels = await server.get('notes/' + noteId + '/labels');
|
|
|
|
$labelListInner.html('');
|
|
|
|
if (labels.length > 0) {
|
|
for (const label of labels) {
|
|
$labelListInner.append(utils.formatLabel(label) + " ");
|
|
}
|
|
|
|
$labelList.show();
|
|
}
|
|
else {
|
|
$labelList.hide();
|
|
}
|
|
|
|
return labels;
|
|
}
|
|
|
|
async function loadRelationList() {
|
|
const noteId = getCurrentNoteId();
|
|
|
|
const relations = await server.get('notes/' + noteId + '/relations');
|
|
|
|
$relationListInner.html('');
|
|
|
|
if (relations.length > 0) {
|
|
for (const relation of relations) {
|
|
$relationListInner.append(relation.name + " = ");
|
|
$relationListInner.append(await linkService.createNoteLink(relation.targetNoteId));
|
|
$relationListInner.append(" ");
|
|
}
|
|
|
|
$relationList.show();
|
|
}
|
|
else {
|
|
$relationList.hide();
|
|
}
|
|
|
|
return relations;
|
|
}
|
|
|
|
async function loadNote(noteId) {
|
|
const row = await server.get('notes/' + noteId);
|
|
|
|
return new NoteFull(treeCache, row);
|
|
}
|
|
|
|
function focus() {
|
|
const note = getCurrentNote();
|
|
|
|
getComponent(note.type).focus();
|
|
}
|
|
|
|
messagingService.subscribeToSyncMessages(syncData => {
|
|
if (syncData.some(sync => sync.entityName === 'notes' && sync.entityId === getCurrentNoteId())) {
|
|
infoService.showMessage('Reloading note because of background changes');
|
|
|
|
reload();
|
|
}
|
|
});
|
|
|
|
$promotedAttributesContainer.on('change', '.promoted-attribute-input', async event => {
|
|
const $attr = $(event.target);
|
|
|
|
let value;
|
|
|
|
if ($attr.prop("type") === "checkbox") {
|
|
value = $attr.is(':checked') ? "true" : "false";
|
|
}
|
|
else {
|
|
value = $attr.val();
|
|
}
|
|
|
|
await server.put("notes/" + getCurrentNoteId() + "/attribute", {
|
|
attributeId: $attr.prop("attribute-id"),
|
|
type: $attr.prop("attribute-type"),
|
|
name: $attr.prop("attribute-name"),
|
|
value: value
|
|
});
|
|
|
|
infoService.showMessage("Attribute has been saved.");
|
|
});
|
|
|
|
$(document).ready(() => {
|
|
$noteTitle.on('input', () => {
|
|
noteChanged();
|
|
|
|
const title = $noteTitle.val();
|
|
|
|
treeService.setNoteTitle(getCurrentNoteId(), title);
|
|
});
|
|
|
|
noteDetailText.focus();
|
|
});
|
|
|
|
// 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
|
|
$(window).on('beforeunload', () => { saveNoteIfChanged(); }); // don't convert to short form, handler doesn't like returned promise
|
|
|
|
setInterval(saveNoteIfChanged, 5000);
|
|
|
|
export default {
|
|
reload,
|
|
switchToNote,
|
|
setNoteBackgroundIfProtected,
|
|
loadNote,
|
|
getCurrentNote,
|
|
getCurrentNoteType,
|
|
getCurrentNoteId,
|
|
newNoteCreated,
|
|
focus,
|
|
loadAttributes,
|
|
loadLabelList,
|
|
loadRelationList,
|
|
saveNote,
|
|
saveNoteIfChanged,
|
|
noteChanged
|
|
}; |