mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
attributes and children overview working again
This commit is contained in:
parent
b21568806a
commit
7f0c92c56b
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "trilium",
|
||||
"version": "0.31.4",
|
||||
"version": "0.31.5",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -152,8 +152,6 @@ noteTooltipService.setupGlobalTooltip();
|
||||
|
||||
bundle.executeStartupBundles();
|
||||
|
||||
noteTypeService.init();
|
||||
|
||||
linkService.init();
|
||||
|
||||
noteAutocompleteService.init();
|
||||
|
@ -2,7 +2,6 @@ import noteDetailService from '../services/note_detail.js';
|
||||
import server from '../services/server.js';
|
||||
import infoService from "../services/info.js";
|
||||
import treeUtils from "../services/tree_utils.js";
|
||||
import attributeService from "../services/attributes.js";
|
||||
import attributeAutocompleteService from "../services/attribute_autocomplete.js";
|
||||
|
||||
const $dialog = $("#attributes-dialog");
|
||||
@ -168,7 +167,9 @@ function AttributesModel() {
|
||||
|
||||
infoService.showMessage("Attributes have been saved.");
|
||||
|
||||
attributeService.refreshAttributes();
|
||||
const ctx = noteDetailService.getActiveContext();
|
||||
|
||||
ctx.attributes.refreshAttributes();
|
||||
|
||||
noteDetailService.reload();
|
||||
};
|
||||
|
@ -4,308 +4,306 @@ import messagingService from "./messaging.js";
|
||||
import treeUtils from "./tree_utils.js";
|
||||
import noteAutocompleteService from "./note_autocomplete.js";
|
||||
import linkService from "./link.js";
|
||||
import noteDetailService from "./note_detail.js";
|
||||
|
||||
const $attributeList = $("#attribute-list");
|
||||
const $attributeListInner = $("#attribute-list-inner");
|
||||
const $promotedAttributesContainer = $("#note-detail-promoted-attributes");
|
||||
const $savedIndicator = $(".saved-indicator");
|
||||
|
||||
let attributePromise;
|
||||
|
||||
function invalidateAttributes() {
|
||||
attributePromise = null;
|
||||
}
|
||||
|
||||
function reloadAttributes() {
|
||||
attributePromise = server.get('notes/' + noteDetailService.getActiveNoteId() + '/attributes');
|
||||
}
|
||||
|
||||
async function refreshAttributes() {
|
||||
reloadAttributes();
|
||||
|
||||
await showAttributes();
|
||||
}
|
||||
|
||||
async function getAttributes() {
|
||||
if (!attributePromise) {
|
||||
reloadAttributes();
|
||||
class Attributes {
|
||||
/**
|
||||
* @param {NoteContext} ctx
|
||||
*/
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx;
|
||||
this.$attributeList = ctx.$noteTabContent.find(".attribute-list");
|
||||
this.$attributeListInner = ctx.$noteTabContent.find(".attribute-list-inner");
|
||||
this.$promotedAttributesContainer = ctx.$noteTabContent.find(".note-detail-promoted-attributes");
|
||||
this.$savedIndicator = ctx.$noteTabContent.find(".saved-indicator");
|
||||
this.attributePromise = null;
|
||||
}
|
||||
|
||||
return await attributePromise;
|
||||
}
|
||||
invalidateAttributes() {
|
||||
this.attributePromise = null;
|
||||
}
|
||||
|
||||
async function showAttributes() {
|
||||
// FIXME tabs
|
||||
return;
|
||||
reloadAttributes() {
|
||||
this.attributePromise = server.get(`notes/${this.ctx.note.noteId}/attributes`);
|
||||
}
|
||||
|
||||
$promotedAttributesContainer.empty();
|
||||
$attributeList.hide();
|
||||
$attributeListInner.empty();
|
||||
async refreshAttributes() {
|
||||
this.reloadAttributes();
|
||||
|
||||
const note = noteDetailService.getActiveNote();
|
||||
await this.showAttributes();
|
||||
}
|
||||
|
||||
const attributes = await attributePromise;
|
||||
|
||||
const promoted = attributes.filter(attr =>
|
||||
(attr.type === 'label-definition' || attr.type === 'relation-definition')
|
||||
&& !attr.name.startsWith("child:")
|
||||
&& attr.value.isPromoted);
|
||||
|
||||
const hidePromotedAttributes = attributes.some(attr => attr.type === 'label' && attr.name === 'hidePromotedAttributes');
|
||||
|
||||
if (promoted.length > 0 && !hidePromotedAttributes) {
|
||||
const $tbody = $("<tbody>");
|
||||
|
||||
for (const definitionAttr of promoted) {
|
||||
const definitionType = definitionAttr.type;
|
||||
const valueType = definitionType.substr(0, definitionType.length - 11);
|
||||
|
||||
let valueAttrs = attributes.filter(el => el.name === definitionAttr.name && el.type === valueType);
|
||||
|
||||
if (valueAttrs.length === 0) {
|
||||
valueAttrs.push({
|
||||
attributeId: "",
|
||||
type: valueType,
|
||||
name: definitionAttr.name,
|
||||
value: ""
|
||||
});
|
||||
}
|
||||
|
||||
if (definitionAttr.value.multiplicityType === 'singlevalue') {
|
||||
valueAttrs = valueAttrs.slice(0, 1);
|
||||
}
|
||||
|
||||
for (const valueAttr of valueAttrs) {
|
||||
const $tr = await createPromotedAttributeRow(definitionAttr, valueAttr);
|
||||
|
||||
$tbody.append($tr);
|
||||
}
|
||||
async getAttributes() {
|
||||
if (!this.attributePromise) {
|
||||
this.reloadAttributes();
|
||||
}
|
||||
|
||||
// we replace the whole content in one step so there can't be any race conditions
|
||||
// (previously we saw promoted attributes doubling)
|
||||
$promotedAttributesContainer.empty().append($tbody);
|
||||
return await this.attributePromise;
|
||||
}
|
||||
else if (note.type !== 'relation-map') {
|
||||
// display only "own" notes
|
||||
const ownedAttributes = attributes.filter(attr => attr.noteId === note.noteId);
|
||||
|
||||
if (ownedAttributes.length > 0) {
|
||||
for (const attribute of ownedAttributes) {
|
||||
if (attribute.type === 'label') {
|
||||
$attributeListInner.append(utils.formatLabel(attribute) + " ");
|
||||
async showAttributes() {
|
||||
this.$promotedAttributesContainer.empty();
|
||||
this.$attributeList.hide();
|
||||
this.$attributeListInner.empty();
|
||||
|
||||
const note = this.ctx.note;
|
||||
|
||||
const attributes = await this.getAttributes();
|
||||
|
||||
const promoted = attributes.filter(attr =>
|
||||
(attr.type === 'label-definition' || attr.type === 'relation-definition')
|
||||
&& !attr.name.startsWith("child:")
|
||||
&& attr.value.isPromoted);
|
||||
|
||||
const hidePromotedAttributes = attributes.some(attr => attr.type === 'label' && attr.name === 'hidePromotedAttributes');
|
||||
|
||||
if (promoted.length > 0 && !hidePromotedAttributes) {
|
||||
const $tbody = $("<tbody>");
|
||||
|
||||
for (const definitionAttr of promoted) {
|
||||
const definitionType = definitionAttr.type;
|
||||
const valueType = definitionType.substr(0, definitionType.length - 11);
|
||||
|
||||
let valueAttrs = attributes.filter(el => el.name === definitionAttr.name && el.type === valueType);
|
||||
|
||||
if (valueAttrs.length === 0) {
|
||||
valueAttrs.push({
|
||||
attributeId: "",
|
||||
type: valueType,
|
||||
name: definitionAttr.name,
|
||||
value: ""
|
||||
});
|
||||
}
|
||||
else if (attribute.type === 'relation') {
|
||||
if (attribute.value) {
|
||||
$attributeListInner.append('@' + attribute.name + "=");
|
||||
$attributeListInner.append(await linkService.createNoteLink(attribute.value));
|
||||
$attributeListInner.append(" ");
|
||||
|
||||
if (definitionAttr.value.multiplicityType === 'singlevalue') {
|
||||
valueAttrs = valueAttrs.slice(0, 1);
|
||||
}
|
||||
|
||||
for (const valueAttr of valueAttrs) {
|
||||
const $tr = await this.createPromotedAttributeRow(definitionAttr, valueAttr);
|
||||
|
||||
$tbody.append($tr);
|
||||
}
|
||||
}
|
||||
|
||||
// we replace the whole content in one step so there can't be any race conditions
|
||||
// (previously we saw promoted attributes doubling)
|
||||
this.$promotedAttributesContainer.empty().append($tbody);
|
||||
}
|
||||
else if (note.type !== 'relation-map') {
|
||||
// display only "own" notes
|
||||
const ownedAttributes = attributes.filter(attr => attr.noteId === note.noteId);
|
||||
|
||||
if (ownedAttributes.length > 0) {
|
||||
for (const attribute of ownedAttributes) {
|
||||
if (attribute.type === 'label') {
|
||||
this.$attributeListInner.append(utils.formatLabel(attribute) + " ");
|
||||
}
|
||||
else if (attribute.type === 'relation') {
|
||||
if (attribute.value) {
|
||||
this.$attributeListInner.append('@' + attribute.name + "=");
|
||||
this.$attributeListInner.append(await linkService.createNoteLink(attribute.value));
|
||||
this.$attributeListInner.append(" ");
|
||||
}
|
||||
else {
|
||||
messagingService.logError(`Relation ${attribute.attributeId} has empty target`);
|
||||
}
|
||||
}
|
||||
else if (attribute.type === 'label-definition' || attribute.type === 'relation-definition') {
|
||||
this.$attributeListInner.append(attribute.name + " definition ");
|
||||
}
|
||||
else {
|
||||
messagingService.logError(`Relation ${attribute.attributeId} has empty target`);
|
||||
messagingService.logError("Unknown attr type: " + attribute.type);
|
||||
}
|
||||
}
|
||||
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();
|
||||
this.$attributeList.show();
|
||||
}
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
async createPromotedAttributeRow(definitionAttr, valueAttr) {
|
||||
const definition = definitionAttr.value;
|
||||
const $tr = $("<tr>");
|
||||
const $labelCell = $("<th>").append(valueAttr.name);
|
||||
const $input = $("<input>")
|
||||
.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")
|
||||
.change(event => this.promotedAttributeChanged(event));
|
||||
|
||||
async function createPromotedAttributeRow(definitionAttr, valueAttr) {
|
||||
const definition = definitionAttr.value;
|
||||
const $tr = $("<tr>");
|
||||
const $labelCell = $("<th>").append(valueAttr.name);
|
||||
const $input = $("<input>")
|
||||
.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")
|
||||
.change(promotedAttributeChanged);
|
||||
const $inputCell = $("<td>").append($("<div>").addClass("input-group").append($input));
|
||||
|
||||
const $inputCell = $("<td>").append($("<div>").addClass("input-group").append($input));
|
||||
const $actionCell = $("<td>");
|
||||
const $multiplicityCell = $("<td>")
|
||||
.addClass("multiplicity")
|
||||
.attr("nowrap", true);
|
||||
|
||||
const $actionCell = $("<td>");
|
||||
const $multiplicityCell = $("<td>")
|
||||
.addClass("multiplicity")
|
||||
.attr("nowrap", true);
|
||||
$tr
|
||||
.append($labelCell)
|
||||
.append($inputCell)
|
||||
.append($actionCell)
|
||||
.append($multiplicityCell);
|
||||
|
||||
$tr
|
||||
.append($labelCell)
|
||||
.append($inputCell)
|
||||
.append($actionCell)
|
||||
.append($multiplicityCell);
|
||||
if (valueAttr.type === 'label') {
|
||||
if (definition.labelType === 'text') {
|
||||
$input.prop("type", "text");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// no need to await for this, can be done asynchronously
|
||||
server.get('attributes/values/' + encodeURIComponent(valueAttr.name)).then(attributeValues => {
|
||||
if (attributeValues.length === 0) {
|
||||
return;
|
||||
attributeValues = attributeValues.map(attribute => { return { value: attribute }; });
|
||||
|
||||
$input.autocomplete({
|
||||
appendTo: document.querySelector('body'),
|
||||
hint: false,
|
||||
autoselect: false,
|
||||
openOnFocus: true,
|
||||
minLength: 0,
|
||||
tabAutocomplete: false
|
||||
}, [{
|
||||
displayKey: 'value',
|
||||
source: function (term, cb) {
|
||||
term = term.toLowerCase();
|
||||
|
||||
const filtered = attributeValues.filter(attr => attr.value.toLowerCase().includes(term));
|
||||
|
||||
cb(filtered);
|
||||
}
|
||||
}]);
|
||||
});
|
||||
}
|
||||
else if (definition.labelType === 'number') {
|
||||
$input.prop("type", "number");
|
||||
|
||||
let step = 1;
|
||||
|
||||
for (let i = 0; i < (definition.numberPrecision || 0) && i < 10; i++) {
|
||||
step /= 10;
|
||||
}
|
||||
|
||||
attributeValues = attributeValues.map(attribute => { return { value: attribute }; });
|
||||
$input.prop("step", step);
|
||||
}
|
||||
else if (definition.labelType === 'boolean') {
|
||||
$input.prop("type", "checkbox");
|
||||
|
||||
$input.autocomplete({
|
||||
appendTo: document.querySelector('body'),
|
||||
hint: false,
|
||||
autoselect: false,
|
||||
openOnFocus: true,
|
||||
minLength: 0,
|
||||
tabAutocomplete: false
|
||||
}, [{
|
||||
displayKey: 'value',
|
||||
source: function (term, cb) {
|
||||
term = term.toLowerCase();
|
||||
if (valueAttr.value === "true") {
|
||||
$input.prop("checked", "checked");
|
||||
}
|
||||
}
|
||||
else if (definition.labelType === 'date') {
|
||||
$input.prop("type", "date");
|
||||
}
|
||||
else if (definition.labelType === 'url') {
|
||||
$input.prop("placeholder", "http://website...");
|
||||
|
||||
const filtered = attributeValues.filter(attr => attr.value.toLowerCase().includes(term));
|
||||
const $openButton = $("<span>")
|
||||
.addClass("input-group-text open-external-link-button jam jam-arrow-up-right")
|
||||
.prop("title", "Open external link")
|
||||
.click(() => window.open($input.val(), '_blank'));
|
||||
|
||||
cb(filtered);
|
||||
}
|
||||
}]);
|
||||
$input.after($("<div>")
|
||||
.addClass("input-group-append")
|
||||
.append($openButton));
|
||||
}
|
||||
else {
|
||||
messagingService.logError("Unknown labelType=" + definitionAttr.labelType);
|
||||
}
|
||||
}
|
||||
else if (valueAttr.type === 'relation') {
|
||||
if (valueAttr.value) {
|
||||
$input.val(await treeUtils.getNoteTitle(valueAttr.value));
|
||||
}
|
||||
|
||||
// no need to wait for this
|
||||
noteAutocompleteService.initNoteAutocomplete($input);
|
||||
|
||||
$input.on('autocomplete:selected', (event, suggestion, dataset) => {
|
||||
this.promotedAttributeChanged(event);
|
||||
});
|
||||
}
|
||||
else if (definition.labelType === 'number') {
|
||||
$input.prop("type", "number");
|
||||
|
||||
let step = 1;
|
||||
|
||||
for (let i = 0; i < (definition.numberPrecision || 0) && i < 10; i++) {
|
||||
step /= 10;
|
||||
}
|
||||
|
||||
$input.prop("step", step);
|
||||
}
|
||||
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", "date");
|
||||
}
|
||||
else if (definition.labelType === 'url') {
|
||||
$input.prop("placeholder", "http://website...");
|
||||
|
||||
const $openButton = $("<span>")
|
||||
.addClass("input-group-text open-external-link-button jam jam-arrow-up-right")
|
||||
.prop("title", "Open external link")
|
||||
.click(() => window.open($input.val(), '_blank'));
|
||||
|
||||
$input.after($("<div>")
|
||||
.addClass("input-group-append")
|
||||
.append($openButton));
|
||||
$input.setSelectedPath(valueAttr.value);
|
||||
}
|
||||
else {
|
||||
messagingService.logError("Unknown labelType=" + definitionAttr.labelType);
|
||||
}
|
||||
}
|
||||
else if (valueAttr.type === 'relation') {
|
||||
if (valueAttr.value) {
|
||||
$input.val(await treeUtils.getNoteTitle(valueAttr.value));
|
||||
messagingService.logError("Unknown attribute type=" + valueAttr.type);
|
||||
return;
|
||||
}
|
||||
|
||||
// no need to wait for this
|
||||
noteAutocompleteService.initNoteAutocomplete($input);
|
||||
if (definition.multiplicityType === "multivalue") {
|
||||
const addButton = $("<span>")
|
||||
.addClass("jam jam-plus pointer")
|
||||
.prop("title", "Add new attribute")
|
||||
.click(async () => {
|
||||
const $new = await this.createPromotedAttributeRow(definitionAttr, {
|
||||
attributeId: "",
|
||||
type: valueAttr.type,
|
||||
name: definitionAttr.name,
|
||||
value: ""
|
||||
});
|
||||
|
||||
$input.on('autocomplete:selected', function(event, suggestion, dataset) {
|
||||
promotedAttributeChanged(event);
|
||||
});
|
||||
$tr.after($new);
|
||||
|
||||
$input.setSelectedPath(valueAttr.value);
|
||||
}
|
||||
else {
|
||||
messagingService.logError("Unknown attribute type=" + valueAttr.type);
|
||||
return;
|
||||
}
|
||||
|
||||
if (definition.multiplicityType === "multivalue") {
|
||||
const addButton = $("<span>")
|
||||
.addClass("jam jam-plus pointer")
|
||||
.prop("title", "Add new attribute")
|
||||
.click(async () => {
|
||||
const $new = await createPromotedAttributeRow(definitionAttr, {
|
||||
attributeId: "",
|
||||
type: valueAttr.type,
|
||||
name: definitionAttr.name,
|
||||
value: ""
|
||||
$new.find('input').focus();
|
||||
});
|
||||
|
||||
$tr.after($new);
|
||||
const removeButton = $("<span>")
|
||||
.addClass("jam jam-trash-alt pointer")
|
||||
.prop("title", "Remove this attribute")
|
||||
.click(async () => {
|
||||
if (valueAttr.attributeId) {
|
||||
await server.remove("notes/" + noteId + "/attributes/" + valueAttr.attributeId);
|
||||
}
|
||||
|
||||
$new.find('input').focus();
|
||||
});
|
||||
$tr.remove();
|
||||
});
|
||||
|
||||
const removeButton = $("<span>")
|
||||
.addClass("jam jam-trash-alt pointer")
|
||||
.prop("title", "Remove this attribute")
|
||||
.click(async () => {
|
||||
if (valueAttr.attributeId) {
|
||||
await server.remove("notes/" + noteId + "/attributes/" + valueAttr.attributeId);
|
||||
}
|
||||
$multiplicityCell.append(addButton).append(" ").append(removeButton);
|
||||
}
|
||||
|
||||
$tr.remove();
|
||||
});
|
||||
|
||||
$multiplicityCell.append(addButton).append(" ").append(removeButton);
|
||||
return $tr;
|
||||
}
|
||||
|
||||
return $tr;
|
||||
}
|
||||
async promotedAttributeChanged(event) {
|
||||
const $attr = $(event.target);
|
||||
|
||||
async function promotedAttributeChanged(event) {
|
||||
const $attr = $(event.target);
|
||||
let value;
|
||||
|
||||
let value;
|
||||
if ($attr.prop("type") === "checkbox") {
|
||||
value = $attr.is(':checked') ? "true" : "false";
|
||||
}
|
||||
else if ($attr.prop("attribute-type") === "relation") {
|
||||
const selectedPath = $attr.getSelectedPath();
|
||||
|
||||
if ($attr.prop("type") === "checkbox") {
|
||||
value = $attr.is(':checked') ? "true" : "false";
|
||||
}
|
||||
else if ($attr.prop("attribute-type") === "relation") {
|
||||
const selectedPath = $attr.getSelectedPath();
|
||||
value = selectedPath ? treeUtils.getNoteIdFromNotePath(selectedPath) : "";
|
||||
}
|
||||
else {
|
||||
value = $attr.val();
|
||||
}
|
||||
|
||||
value = selectedPath ? treeUtils.getNoteIdFromNotePath(selectedPath) : "";
|
||||
}
|
||||
else {
|
||||
value = $attr.val();
|
||||
}
|
||||
const result = await server.put(`notes/${this.ctx.note.noteId}/attribute`, {
|
||||
attributeId: $attr.prop("attribute-id"),
|
||||
type: $attr.prop("attribute-type"),
|
||||
name: $attr.prop("attribute-name"),
|
||||
value: value
|
||||
});
|
||||
|
||||
const result = await server.put("notes/" + noteDetailService.getActiveNoteId() + "/attribute", {
|
||||
attributeId: $attr.prop("attribute-id"),
|
||||
type: $attr.prop("attribute-type"),
|
||||
name: $attr.prop("attribute-name"),
|
||||
value: value
|
||||
});
|
||||
$attr.prop("attribute-id", result.attributeId);
|
||||
|
||||
$attr.prop("attribute-id", result.attributeId);
|
||||
|
||||
// animate only if it's not being animated already, this is important especially for e.g. number inputs
|
||||
// which can be changed many times in a second by clicking on higher/lower buttons.
|
||||
if ($savedIndicator.queue().length === 0) {
|
||||
$savedIndicator.fadeOut();
|
||||
$savedIndicator.fadeIn();
|
||||
// animate only if it's not being animated already, this is important especially for e.g. number inputs
|
||||
// which can be changed many times in a second by clicking on higher/lower buttons.
|
||||
if (this.$savedIndicator.queue().length === 0) {
|
||||
this.$savedIndicator.fadeOut();
|
||||
this.$savedIndicator.fadeIn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
getAttributes,
|
||||
showAttributes,
|
||||
refreshAttributes,
|
||||
invalidateAttributes
|
||||
}
|
||||
export default Attributes;
|
@ -2,9 +2,11 @@ import treeService from "./tree.js";
|
||||
import protectedSessionHolder from "./protected_session_holder.js";
|
||||
import server from "./server.js";
|
||||
import bundleService from "./bundle.js";
|
||||
import attributeService from "./attributes.js";
|
||||
import Attributes from "./attributes.js";
|
||||
import treeUtils from "./tree_utils.js";
|
||||
import utils from "./utils.js";
|
||||
import {NoteTypeContext} from "./note_type.js";
|
||||
import noteDetailService from "./note_detail.js";
|
||||
import noteDetailCode from "./note_detail_code.js";
|
||||
import noteDetailText from "./note_detail_text.js";
|
||||
import noteDetailFile from "./note_detail_file.js";
|
||||
@ -44,6 +46,8 @@ class NoteContext {
|
||||
this.$savedIndicator = this.$noteTabContent.find(".saved-indicator");
|
||||
this.noteChangeDisabled = false;
|
||||
this.isNoteChanged = false;
|
||||
this.attributes = new Attributes(this);
|
||||
this.noteType = new NoteTypeContext(this);
|
||||
this.components = {};
|
||||
|
||||
this.$noteTitle.on('input', () => {
|
||||
@ -70,6 +74,8 @@ class NoteContext {
|
||||
this.$noteTabContent.attr('data-note-id', note.noteId);
|
||||
|
||||
chromeTabs.updateTab(this.tab, {title: note.title});
|
||||
|
||||
this.attributes.invalidateAttributes();
|
||||
}
|
||||
|
||||
getComponent(type) {
|
||||
@ -90,7 +96,7 @@ class NoteContext {
|
||||
}
|
||||
|
||||
this.note.title = this.$noteTitle.val();
|
||||
this.note.content = getActiveNoteContent(this.note);
|
||||
this.note.content = noteDetailService.getActiveNoteContent();
|
||||
|
||||
// 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)
|
||||
@ -127,9 +133,7 @@ class NoteContext {
|
||||
}
|
||||
|
||||
async showChildrenOverview() {
|
||||
return; // FIXME
|
||||
|
||||
const attributes = await attributeService.getAttributes();
|
||||
const attributes = await this.attributes.getAttributes();
|
||||
const hideChildrenOverview = attributes.some(attr => attr.type === 'label' && attr.name === 'hideChildrenOverview')
|
||||
|| this.note.type === 'relation-map'
|
||||
|| this.note.type === 'image'
|
||||
|
@ -9,7 +9,6 @@ import infoService from "./info.js";
|
||||
import treeCache from "./tree_cache.js";
|
||||
import NoteFull from "../entities/note_full.js";
|
||||
import bundleService from "./bundle.js";
|
||||
import attributeService from "./attributes.js";
|
||||
import utils from "./utils.js";
|
||||
import importDialog from "../dialogs/import.js";
|
||||
|
||||
@ -167,8 +166,8 @@ async function loadNoteDetail(noteId, newTab = false) {
|
||||
ctx.$noteTitle.val(ctx.note.title);
|
||||
|
||||
if (utils.isDesktop()) {
|
||||
noteTypeService.setNoteType(ctx.note.type);
|
||||
noteTypeService.setNoteMime(ctx.note.mime);
|
||||
ctx.noteType.type(ctx.note.type);
|
||||
ctx.noteType.mime(ctx.note.mime);
|
||||
}
|
||||
|
||||
for (const componentType in ctx.components) {
|
||||
@ -204,11 +203,11 @@ async function loadNoteDetail(noteId, newTab = false) {
|
||||
|
||||
await bundleService.executeRelationBundles(ctx.note, 'runOnNoteView');
|
||||
|
||||
// if (utils.isDesktop()) {
|
||||
// await attributeService.showAttributes();
|
||||
//
|
||||
// await ctx.showChildrenOverview();
|
||||
// }
|
||||
if (utils.isDesktop()) {
|
||||
await ctx.attributes.showAttributes();
|
||||
|
||||
await ctx.showChildrenOverview();
|
||||
}
|
||||
}
|
||||
|
||||
async function loadNote(noteId) {
|
||||
@ -293,5 +292,6 @@ export default {
|
||||
focusAndSelectTitle,
|
||||
saveNotesIfChanged,
|
||||
onNoteChange,
|
||||
addDetailLoadedListener
|
||||
addDetailLoadedListener,
|
||||
getActiveContext
|
||||
};
|
@ -8,6 +8,7 @@ class NoteDetailImage {
|
||||
* @param {NoteContext} ctx
|
||||
*/
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx;
|
||||
this.$component = ctx.$noteTabContent.find('.note-detail-image');
|
||||
this.$imageWrapper = ctx.$noteTabContent.find('.note-detail-image-wrapper');
|
||||
this.$imageView = ctx.$noteTabContent.find('.note-detail-image-view');
|
||||
@ -42,18 +43,16 @@ class NoteDetailImage {
|
||||
}
|
||||
|
||||
async show() {
|
||||
const activeNote = noteDetailService.getActiveNote();
|
||||
|
||||
const attributes = await server.get('notes/' + activeNote.noteId + '/attributes');
|
||||
const attributes = await server.get('notes/' + this.ctx.note.noteId + '/attributes');
|
||||
const attributeMap = utils.toObject(attributes, l => [l.name, l.value]);
|
||||
|
||||
this.$component.show();
|
||||
|
||||
this.$fileName.text(attributeMap.originalFileName || "?");
|
||||
this.$fileSize.text((attributeMap.fileSize || "?") + " bytes");
|
||||
this.$fileType.text(activeNote.mime);
|
||||
this.$fileType.text(this.ctx.note.mime);
|
||||
|
||||
this.$imageView.prop("src", `api/images/${activeNote.noteId}/${activeNote.title}`);
|
||||
this.$imageView.prop("src", `api/images/${this.ctx.note.noteId}/${this.ctx.note.title}`);
|
||||
}
|
||||
|
||||
selectImage(element) {
|
||||
|
@ -1,7 +1,5 @@
|
||||
import libraryLoader from "./library_loader.js";
|
||||
import noteDetailService from './note_detail.js';
|
||||
import treeService from './tree.js';
|
||||
import attributeService from "./attributes.js";
|
||||
|
||||
class NoteDetailText {
|
||||
/**
|
||||
@ -69,7 +67,7 @@ class NoteDetailText {
|
||||
}
|
||||
|
||||
async isReadOnly() {
|
||||
const attributes = await attributeService.getAttributes();
|
||||
const attributes = await this.ctx.attributes.getAttributes();
|
||||
|
||||
return attributes.some(attr => attr.type === 'label' && attr.name === 'readOnly');
|
||||
}
|
||||
|
@ -4,10 +4,6 @@ import server from './server.js';
|
||||
import infoService from "./info.js";
|
||||
import confirmDialog from "../dialogs/confirm.js";
|
||||
|
||||
const $executeScriptButton = $("#execute-script-button");
|
||||
const $toggleEditButton = $('#toggle-edit-button');
|
||||
const $renderButton = $('#render-button');
|
||||
|
||||
const DEFAULT_MIME_TYPES = [
|
||||
{ mime: 'text/x-csrc', title: 'C' },
|
||||
{ mime: 'text/x-c++src', title: 'C++' },
|
||||
@ -45,15 +41,24 @@ const DEFAULT_MIME_TYPES = [
|
||||
{ mime: 'text/x-yaml', title: 'YAML' }
|
||||
];
|
||||
|
||||
let noteTypeModel;
|
||||
let mimeTypes = DEFAULT_MIME_TYPES;
|
||||
|
||||
function NoteTypeModel() {
|
||||
/**
|
||||
* @param {NoteContext} ctx
|
||||
* @constructor
|
||||
*/
|
||||
function NoteTypeContext(ctx) {
|
||||
const self = this;
|
||||
|
||||
this.$executeScriptButton = ctx.$noteTabContent.find(".execute-script-button");
|
||||
this.$toggleEditButton = ctx.$noteTabContent.find('.toggle-edit-button');
|
||||
this.$renderButton = ctx.$noteTabContent.find('.render-button');
|
||||
|
||||
this.ctx = ctx;
|
||||
this.type = ko.observable('text');
|
||||
this.mime = ko.observable('');
|
||||
|
||||
this.codeMimeTypes = ko.observableArray(DEFAULT_MIME_TYPES);
|
||||
this.codeMimeTypes = ko.observableArray(mimeTypes);
|
||||
|
||||
this.typeString = function() {
|
||||
const type = self.type();
|
||||
@ -97,9 +102,7 @@ function NoteTypeModel() {
|
||||
};
|
||||
|
||||
async function save() {
|
||||
const note = noteDetailService.getActiveNote();
|
||||
|
||||
await server.put('notes/' + note.noteId
|
||||
await server.put('notes/' + self.ctx.note.noteId
|
||||
+ '/type/' + encodeURIComponent(self.type())
|
||||
+ '/mime/' + encodeURIComponent(self.mime()));
|
||||
|
||||
@ -175,32 +178,21 @@ function NoteTypeModel() {
|
||||
};
|
||||
|
||||
this.updateExecuteScriptButtonVisibility = function() {
|
||||
$executeScriptButton.toggle(self.mime().startsWith('application/javascript'));
|
||||
self.$executeScriptButton.toggle(self.mime().startsWith('application/javascript'));
|
||||
|
||||
$toggleEditButton.toggle(self.type() === 'render');
|
||||
$renderButton.toggle(self.type() === 'render');
|
||||
}
|
||||
}
|
||||
self.$toggleEditButton.toggle(self.type() === 'render');
|
||||
self.$renderButton.toggle(self.type() === 'render');
|
||||
};
|
||||
|
||||
function init() {
|
||||
noteTypeModel = new NoteTypeModel();
|
||||
|
||||
ko.applyBindings(noteTypeModel, document.getElementById('note-type-wrapper'));
|
||||
ko.applyBindings(this, ctx.$noteTabContent.find('.note-type-wrapper')[0])
|
||||
}
|
||||
|
||||
export default {
|
||||
getNoteType: () => noteTypeModel.type(),
|
||||
setNoteType: type => noteTypeModel.type(type),
|
||||
|
||||
getNoteMime: () => noteTypeModel.mime(),
|
||||
setNoteMime: mime => {
|
||||
noteTypeModel.mime(mime);
|
||||
|
||||
noteTypeModel.updateExecuteScriptButtonVisibility();
|
||||
},
|
||||
|
||||
getDefaultCodeMimeTypes: () => DEFAULT_MIME_TYPES.slice(),
|
||||
getCodeMimeTypes: () => noteTypeModel.codeMimeTypes(),
|
||||
setCodeMimeTypes: types => noteTypeModel.codeMimeTypes(types),
|
||||
init
|
||||
getCodeMimeTypes: () => mimeTypes,
|
||||
setCodeMimeTypes: types => { mimeTypes = types; }
|
||||
};
|
||||
|
||||
export {
|
||||
NoteTypeContext
|
||||
};
|
@ -412,7 +412,7 @@ div.ui-tooltip {
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
#children-overview {
|
||||
.children-overview {
|
||||
flex-grow: 1000;
|
||||
flex-shrink: 1000;
|
||||
flex-basis: 0;
|
||||
|
Loading…
x
Reference in New Issue
Block a user