").addClass("input-group").append($input));
+ const $actionCell = $("
");
+ const $multiplicityCell = $(" | ")
+ .addClass("multiplicity")
+ .attr("nowrap", true);
- const $actionCell = $(" | ");
- const $multiplicityCell = $(" | ")
- .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 = $("")
+ .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($("")
+ .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 = $(" ")
- .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($("")
- .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 = $("")
+ .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 = $("")
- .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 = $("")
+ .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 = $("")
- .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
-}
\ No newline at end of file
+export default Attributes;
\ No newline at end of file
diff --git a/src/public/javascripts/services/note_context.js b/src/public/javascripts/services/note_context.js
index 3f3f536a3..718e8273a 100644
--- a/src/public/javascripts/services/note_context.js
+++ b/src/public/javascripts/services/note_context.js
@@ -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'
diff --git a/src/public/javascripts/services/note_detail.js b/src/public/javascripts/services/note_detail.js
index 1f70aee96..161241bbd 100644
--- a/src/public/javascripts/services/note_detail.js
+++ b/src/public/javascripts/services/note_detail.js
@@ -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
};
\ No newline at end of file
diff --git a/src/public/javascripts/services/note_detail_image.js b/src/public/javascripts/services/note_detail_image.js
index 027410afa..315d64baa 100644
--- a/src/public/javascripts/services/note_detail_image.js
+++ b/src/public/javascripts/services/note_detail_image.js
@@ -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) {
diff --git a/src/public/javascripts/services/note_detail_text.js b/src/public/javascripts/services/note_detail_text.js
index 1aba4c3e7..e235c99b1 100644
--- a/src/public/javascripts/services/note_detail_text.js
+++ b/src/public/javascripts/services/note_detail_text.js
@@ -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');
}
diff --git a/src/public/javascripts/services/note_type.js b/src/public/javascripts/services/note_type.js
index b54ba3436..f6e5ffb0d 100644
--- a/src/public/javascripts/services/note_type.js
+++ b/src/public/javascripts/services/note_type.js
@@ -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
};
\ No newline at end of file
diff --git a/src/public/stylesheets/style.css b/src/public/stylesheets/style.css
index 1895e8d3e..aefcc7e69 100644
--- a/src/public/stylesheets/style.css
+++ b/src/public/stylesheets/style.css
@@ -412,7 +412,7 @@ div.ui-tooltip {
font-size: larger;
}
-#children-overview {
+.children-overview {
flex-grow: 1000;
flex-shrink: 1000;
flex-basis: 0;
|