mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
fixes, separation of notefull from noteshort
This commit is contained in:
parent
516e6c35da
commit
ac7d5f2e81
@ -172,8 +172,6 @@ function AttributesModel() {
|
|||||||
|
|
||||||
toastService.showMessage("Attributes have been saved.");
|
toastService.showMessage("Attributes have been saved.");
|
||||||
|
|
||||||
appContext.getActiveTabContext().attributes.refreshAttributes();
|
|
||||||
|
|
||||||
// FIXME detail should be also reloaded
|
// FIXME detail should be also reloaded
|
||||||
appContext.trigger('reloadTree');
|
appContext.trigger('reloadTree');
|
||||||
};
|
};
|
||||||
|
@ -16,13 +16,13 @@ export function showDialog() {
|
|||||||
|
|
||||||
$dialog.modal();
|
$dialog.modal();
|
||||||
|
|
||||||
const activeNote = appContext.getActiveTabNote();
|
const {note, noteFull} = appContext.getActiveTabContext();
|
||||||
|
|
||||||
$noteId.text(activeNote.noteId);
|
$noteId.text(note.noteId);
|
||||||
$dateCreated.text(activeNote.dateCreated);
|
$dateCreated.text(noteFull.dateCreated);
|
||||||
$dateModified.text(activeNote.dateModified);
|
$dateModified.text(noteFull.dateModified);
|
||||||
$type.text(activeNote.type);
|
$type.text(note.type);
|
||||||
$mime.text(activeNote.mime);
|
$mime.text(note.mime);
|
||||||
}
|
}
|
||||||
|
|
||||||
$okButton.on('click', () => $dialog.modal('hide'));
|
$okButton.on('click', () => $dialog.modal('hide'));
|
||||||
|
@ -3,10 +3,8 @@ import NoteShort from './note_short.js';
|
|||||||
/**
|
/**
|
||||||
* Represents full note, specifically including note's content.
|
* Represents full note, specifically including note's content.
|
||||||
*/
|
*/
|
||||||
class NoteFull extends NoteShort {
|
class NoteFull {
|
||||||
constructor(treeCache, row, noteShort) {
|
constructor(row) {
|
||||||
super(treeCache, row, []);
|
|
||||||
|
|
||||||
/** @param {string} */
|
/** @param {string} */
|
||||||
this.content = row.content;
|
this.content = row.content;
|
||||||
|
|
||||||
@ -21,12 +19,6 @@ class NoteFull extends NoteShort {
|
|||||||
|
|
||||||
/** @param {string} */
|
/** @param {string} */
|
||||||
this.utcDateModified = row.utcDateModified;
|
this.utcDateModified = row.utcDateModified;
|
||||||
|
|
||||||
/* ugly */
|
|
||||||
this.parents = noteShort.parents;
|
|
||||||
this.parentToBranch = noteShort.parentToBranch;
|
|
||||||
this.children = noteShort.children;
|
|
||||||
this.childToBranch = noteShort.childToBranch;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,12 +15,10 @@ function getActiveEditor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadNote(noteId) {
|
async function loadNoteFull(noteId) {
|
||||||
const row = await server.get('notes/' + noteId);
|
const row = await server.get('notes/' + noteId);
|
||||||
|
|
||||||
const noteShort = await treeCache.getNote(noteId);
|
return new NoteFull(row);
|
||||||
|
|
||||||
return new NoteFull(treeCache, row, noteShort);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function focusOnTitle() {
|
function focusOnTitle() {
|
||||||
@ -65,7 +63,7 @@ $(window).on('beforeunload', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
loadNote,
|
loadNoteFull,
|
||||||
focusOnTitle,
|
focusOnTitle,
|
||||||
focusAndSelectTitle,
|
focusAndSelectTitle,
|
||||||
getActiveEditor,
|
getActiveEditor,
|
||||||
|
@ -2,6 +2,7 @@ import noteDetailService from "./note_detail.js";
|
|||||||
import treeService from "./tree.js";
|
import treeService from "./tree.js";
|
||||||
import linkService from "./link.js";
|
import linkService from "./link.js";
|
||||||
import server from "./server.js";
|
import server from "./server.js";
|
||||||
|
import treeCache from "./tree_cache.js";
|
||||||
|
|
||||||
function setupGlobalTooltip() {
|
function setupGlobalTooltip() {
|
||||||
$(document).on("mouseenter", "a", mouseEnterHandler);
|
$(document).on("mouseenter", "a", mouseEnterHandler);
|
||||||
@ -42,12 +43,10 @@ async function mouseEnterHandler() {
|
|||||||
|
|
||||||
const noteId = treeService.getNoteIdFromNotePath(notePath);
|
const noteId = treeService.getNoteIdFromNotePath(notePath);
|
||||||
|
|
||||||
const notePromise = noteDetailService.loadNote(noteId);
|
const note = await treeCache.getNote(noteId);
|
||||||
const attributePromise = server.get(`notes/${noteId}/attributes`);
|
const noteFull = await noteDetailService.loadNoteFull(noteId);
|
||||||
|
|
||||||
const [note, attributes] = await Promise.all([notePromise, attributePromise]);
|
const html = await renderTooltip(note, noteFull);
|
||||||
|
|
||||||
const html = await renderTooltip(note, attributes);
|
|
||||||
|
|
||||||
// we need to check if we're still hovering over the element
|
// we need to check if we're still hovering over the element
|
||||||
// since the operation to get tooltip content was async, it is possible that
|
// since the operation to get tooltip content was async, it is possible that
|
||||||
@ -72,7 +71,9 @@ function mouseLeaveHandler() {
|
|||||||
$(this).tooltip('dispose');
|
$(this).tooltip('dispose');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderTooltip(note, attributes) {
|
async function renderTooltip(note, noteFull) {
|
||||||
|
const attributes = await note.getAttributes();
|
||||||
|
|
||||||
let content = '';
|
let content = '';
|
||||||
const promoted = attributes.filter(attr =>
|
const promoted = attributes.filter(attr =>
|
||||||
(attr.type === 'label-definition' || attr.type === 'relation-definition')
|
(attr.type === 'label-definition' || attr.type === 'relation-definition')
|
||||||
@ -116,11 +117,11 @@ async function renderTooltip(note, attributes) {
|
|||||||
if (note.type === 'text') {
|
if (note.type === 'text') {
|
||||||
// surround with <div> for a case when note's content is pure text (e.g. "[protected]") which
|
// surround with <div> for a case when note's content is pure text (e.g. "[protected]") which
|
||||||
// then fails the jquery non-empty text test
|
// then fails the jquery non-empty text test
|
||||||
content += '<div>' + note.content + '</div>';
|
content += '<div>' + noteFull.content + '</div>';
|
||||||
}
|
}
|
||||||
else if (note.type === 'code') {
|
else if (note.type === 'code') {
|
||||||
content += $("<pre>")
|
content += $("<pre>")
|
||||||
.text(note.content)
|
.text(noteFull.content)
|
||||||
.prop('outerHTML');
|
.prop('outerHTML');
|
||||||
}
|
}
|
||||||
else if (note.type === 'image') {
|
else if (note.type === 'image') {
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import protectedSessionHolder from "./protected_session_holder.js";
|
import protectedSessionHolder from "./protected_session_holder.js";
|
||||||
import server from "./server.js";
|
import server from "./server.js";
|
||||||
import bundleService from "./bundle.js";
|
import bundleService from "./bundle.js";
|
||||||
import Attributes from "./attributes.js";
|
|
||||||
import utils from "./utils.js";
|
import utils from "./utils.js";
|
||||||
import optionsService from "./options.js";
|
import optionsService from "./options.js";
|
||||||
import appContext from "./app_context.js";
|
import appContext from "./app_context.js";
|
||||||
import treeService from "./tree.js";
|
import treeService from "./tree.js";
|
||||||
import noteDetailService from "./note_detail.js";
|
import noteDetailService from "./note_detail.js";
|
||||||
import Component from "../widgets/component.js";
|
import Component from "../widgets/component.js";
|
||||||
|
import treeCache from "./tree_cache.js";
|
||||||
|
|
||||||
let showSidebarInNewTab = true;
|
let showSidebarInNewTab = true;
|
||||||
|
|
||||||
@ -28,10 +28,6 @@ class TabContext extends Component {
|
|||||||
this.tabId = state.tabId || utils.randomString(4);
|
this.tabId = state.tabId || utils.randomString(4);
|
||||||
this.state = state;
|
this.state = state;
|
||||||
|
|
||||||
this.attributes = new Attributes(this.appContext, this);
|
|
||||||
|
|
||||||
this.children.push(this.attributes);
|
|
||||||
|
|
||||||
this.trigger('tabOpened', {tabId: this.tabId});
|
this.trigger('tabOpened', {tabId: this.tabId});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,8 +48,11 @@ class TabContext extends Component {
|
|||||||
this.notePath = notePath;
|
this.notePath = notePath;
|
||||||
const noteId = treeService.getNoteIdFromNotePath(notePath);
|
const noteId = treeService.getNoteIdFromNotePath(notePath);
|
||||||
|
|
||||||
|
/** @property {NoteShort} */
|
||||||
|
this.note = await treeCache.getNote(noteId);
|
||||||
|
|
||||||
/** @property {NoteFull} */
|
/** @property {NoteFull} */
|
||||||
this.note = await noteDetailService.loadNote(noteId);
|
this.noteFull = await noteDetailService.loadNoteFull(noteId);
|
||||||
|
|
||||||
//this.cleanup(); // esp. on windows autocomplete is not getting closed automatically
|
//this.cleanup(); // esp. on windows autocomplete is not getting closed automatically
|
||||||
|
|
||||||
@ -85,30 +84,6 @@ class TabContext extends Component {
|
|||||||
this.trigger('tabRemoved', {tabId: this.tabId});
|
this.trigger('tabRemoved', {tabId: this.tabId});
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveNote() {
|
|
||||||
return; // FIXME
|
|
||||||
|
|
||||||
if (this.note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.note.title = this.$noteTitle.val();
|
|
||||||
this.note.content = this.getComponent().getContent();
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
this.isNoteChanged = false;
|
|
||||||
|
|
||||||
const resp = await server.put('notes/' + this.note.noteId, this.note.dto);
|
|
||||||
|
|
||||||
this.note.dateModified = resp.dateModified;
|
|
||||||
this.note.utcDateModified = resp.utcDateModified;
|
|
||||||
|
|
||||||
if (this.note.isProtected) {
|
|
||||||
protectedSessionHolder.touchProtectedSession();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isActive() {
|
isActive() {
|
||||||
return this.appContext.activeTabId === this.tabId;
|
return this.appContext.activeTabId === this.tabId;
|
||||||
}
|
}
|
||||||
|
@ -23,9 +23,9 @@ class AttributesWidget extends StandardWidget {
|
|||||||
return [$showFullButton];
|
return [$showFullButton];
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshWithNote() {
|
async refreshWithNote(note) {
|
||||||
const attributes = await this.tabContext.attributes.getAttributes();
|
const attributes = await note.getAttributes();
|
||||||
const ownedAttributes = attributes.filter(attr => attr.noteId === this.tabContext.note.noteId);
|
const ownedAttributes = note.getOwnedAttributes();
|
||||||
|
|
||||||
if (attributes.length === 0) {
|
if (attributes.length === 0) {
|
||||||
this.$body.text("No attributes yet...");
|
this.$body.text("No attributes yet...");
|
||||||
|
@ -36,16 +36,19 @@ export default class NoteDetailWidget extends TabAwareWidget {
|
|||||||
this.typeWidgetPromises = {};
|
this.typeWidgetPromises = {};
|
||||||
|
|
||||||
this.spacedUpdate = new SpacedUpdate(async () => {
|
this.spacedUpdate = new SpacedUpdate(async () => {
|
||||||
const note = this.tabContext.note;
|
const {noteFull} = this.tabContext;
|
||||||
note.content = this.getTypeWidget().getContent();
|
const {noteId} = this.tabContext.note;
|
||||||
|
|
||||||
const resp = await server.put('notes/' + note.noteId, note.dto);
|
const dto = note.dto;
|
||||||
|
dto.content = noteFull.content = this.getTypeWidget().getContent();
|
||||||
|
|
||||||
|
const resp = await server.put('notes/' + noteId, dto);
|
||||||
|
|
||||||
// FIXME: minor - does not propagate to other tab contexts with this note though
|
// FIXME: minor - does not propagate to other tab contexts with this note though
|
||||||
note.dateModified = resp.dateModified;
|
noteFull.dateModified = resp.dateModified;
|
||||||
note.utcDateModified = resp.utcDateModified;
|
noteFull.utcDateModified = resp.utcDateModified;
|
||||||
|
|
||||||
this.trigger('noteChangesSaved', {noteId: note.noteId})
|
this.trigger('noteChangesSaved', {noteId})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,7 +159,7 @@ export default class NoteDetailWidget extends TabAwareWidget {
|
|||||||
let type = note.type;
|
let type = note.type;
|
||||||
|
|
||||||
if (type === 'text' && !disableAutoBook
|
if (type === 'text' && !disableAutoBook
|
||||||
&& utils.isHtmlEmpty(note.content)
|
&& utils.isHtmlEmpty(this.tabContext.noteFull.content)
|
||||||
&& note.hasChildren()) {
|
&& note.hasChildren()) {
|
||||||
|
|
||||||
type = 'book';
|
type = 'book';
|
||||||
|
@ -49,14 +49,16 @@ class NoteInfoWidget extends StandardWidget {
|
|||||||
const $type = this.$body.find(".note-info-type");
|
const $type = this.$body.find(".note-info-type");
|
||||||
const $mime = this.$body.find(".note-info-mime");
|
const $mime = this.$body.find(".note-info-mime");
|
||||||
|
|
||||||
|
const {noteFull} = this.tabContext;
|
||||||
|
|
||||||
$noteId.text(note.noteId);
|
$noteId.text(note.noteId);
|
||||||
$dateCreated
|
$dateCreated
|
||||||
.text(note.dateCreated)
|
.text(noteFull.dateCreated)
|
||||||
.attr("title", note.dateCreated);
|
.attr("title", noteFull.dateCreated);
|
||||||
|
|
||||||
$dateModified
|
$dateModified
|
||||||
.text(note.dateModified)
|
.text(noteFull.dateModified)
|
||||||
.attr("title", note.dateCreated);
|
.attr("title", noteFull.dateCreated);
|
||||||
|
|
||||||
$type.text(note.type);
|
$type.text(note.type);
|
||||||
|
|
||||||
|
@ -35,10 +35,10 @@ export default class PromotedAttributesWidget extends TabAwareWidget {
|
|||||||
return this.$widget;
|
return this.$widget;
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshWithNote() {
|
async refreshWithNote(note) {
|
||||||
this.$container.empty();
|
this.$container.empty();
|
||||||
|
|
||||||
const attributes = await this.tabContext.attributes.getAttributes();
|
const attributes = await note.getAttributes();
|
||||||
|
|
||||||
const promoted = attributes.filter(attr =>
|
const promoted = attributes.filter(attr =>
|
||||||
(attr.type === 'label-definition' || attr.type === 'relation-definition')
|
(attr.type === 'label-definition' || attr.type === 'relation-definition')
|
||||||
|
@ -75,7 +75,7 @@ export default class CodeTypeWidget extends TypeWidget {
|
|||||||
this.spacedUpdate.allowUpdateWithoutChange(() => {
|
this.spacedUpdate.allowUpdateWithoutChange(() => {
|
||||||
// CodeMirror breaks pretty badly on null so even though it shouldn't happen (guarded by consistency check)
|
// CodeMirror breaks pretty badly on null so even though it shouldn't happen (guarded by consistency check)
|
||||||
// we provide fallback
|
// we provide fallback
|
||||||
this.codeEditor.setValue(note.content || "");
|
this.codeEditor.setValue(this.tabContext.noteFull.content || "");
|
||||||
|
|
||||||
const info = CodeMirror.findModeByMIME(note.mime);
|
const info = CodeMirror.findModeByMIME(note.mime);
|
||||||
|
|
||||||
|
@ -118,7 +118,7 @@ export default class FileTypeWidget extends TypeWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async doRefresh(note) {
|
async doRefresh(note) {
|
||||||
const attributes = await server.get('notes/' + note.noteId + '/attributes');
|
const attributes = await note.getAttributes();
|
||||||
const attributeMap = utils.toObject(attributes, l => [l.name, l.value]);
|
const attributeMap = utils.toObject(attributes, l => [l.name, l.value]);
|
||||||
|
|
||||||
this.$widget.show();
|
this.$widget.show();
|
||||||
@ -128,9 +128,9 @@ export default class FileTypeWidget extends TypeWidget {
|
|||||||
this.$fileSize.text(note.contentLength + " bytes");
|
this.$fileSize.text(note.contentLength + " bytes");
|
||||||
this.$fileType.text(note.mime);
|
this.$fileType.text(note.mime);
|
||||||
|
|
||||||
if (note.content) {
|
if (this.tabContext.noteFull.content) {
|
||||||
this.$previewContent.show();
|
this.$previewContent.show();
|
||||||
this.$previewContent.text(note.content);
|
this.$previewContent.text(this.tabContext.noteFull.content);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.$previewContent.empty().hide();
|
this.$previewContent.empty().hide();
|
||||||
|
@ -123,7 +123,7 @@ class NoteDetailImage extends TypeWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async doRefresh(note) {
|
async doRefresh(note) {
|
||||||
const attributes = await server.get('notes/' + note.noteId + '/attributes');
|
const attributes = await note.getAttributes();
|
||||||
const attributeMap = utils.toObject(attributes, l => [l.name, l.value]);
|
const attributeMap = utils.toObject(attributes, l => [l.name, l.value]);
|
||||||
|
|
||||||
this.$widget.show();
|
this.$widget.show();
|
||||||
@ -132,7 +132,7 @@ class NoteDetailImage extends TypeWidget {
|
|||||||
this.$fileSize.text(note.contentLength + " bytes");
|
this.$fileSize.text(note.contentLength + " bytes");
|
||||||
this.$fileType.text(note.mime);
|
this.$fileType.text(note.mime);
|
||||||
|
|
||||||
const imageHash = note.utcDateModified.replace(" ", "_");
|
const imageHash = this.tabContext.noteFull.utcDateModified.replace(" ", "_");
|
||||||
|
|
||||||
this.$imageView.prop("src", `api/images/${note.noteId}/${note.title}?${imageHash}`);
|
this.$imageView.prop("src", `api/images/${note.noteId}/${note.title}?${imageHash}`);
|
||||||
}
|
}
|
||||||
|
@ -254,9 +254,9 @@ export default class RelationMapTypeWidget extends TypeWidget {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.tabContext.note.content) {
|
if (this.tabContext.noteFull.content) {
|
||||||
try {
|
try {
|
||||||
this.mapData = JSON.parse(this.tabContext.note.content);
|
this.mapData = JSON.parse(this.tabContext.noteFull.content);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Could not parse content: ", e);
|
console.log("Could not parse content: ", e);
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ export default class SearchTypeWidget extends TypeWidget {
|
|||||||
this.$component.show();
|
this.$component.show();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const json = JSON.parse(note.content);
|
const json = JSON.parse(this.tabContext.noteFull.content);
|
||||||
|
|
||||||
this.$searchString.val(json.searchString);
|
this.$searchString.val(json.searchString);
|
||||||
}
|
}
|
||||||
|
@ -137,10 +137,10 @@ export default class TextTypeWidget extends TypeWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async doRefresh(note) {
|
async doRefresh(note) {
|
||||||
this.textEditor.isReadOnly = await this.isReadOnly();
|
this.textEditor.isReadOnly = await note.hasLabel('readOnly');
|
||||||
|
|
||||||
this.spacedUpdate.allowUpdateWithoutChange(() => {
|
this.spacedUpdate.allowUpdateWithoutChange(() => {
|
||||||
this.textEditor.setData(note.content);
|
this.textEditor.setData(this.tabContext.noteFull.content);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,12 +160,6 @@ export default class TextTypeWidget extends TypeWidget {
|
|||||||
&& !content.includes("<section")
|
&& !content.includes("<section")
|
||||||
}
|
}
|
||||||
|
|
||||||
async isReadOnly() {
|
|
||||||
const attributes = await this.tabContext.attributes.getAttributes();
|
|
||||||
|
|
||||||
return attributes.some(attr => attr.type === 'label' && attr.name === 'readOnly');
|
|
||||||
}
|
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
this.$editor.trigger('focus');
|
this.$editor.trigger('focus');
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ export default class TypeWidget extends TabAwareWidget {
|
|||||||
static getType() {}
|
static getType() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {NoteFull} note
|
* @param {NoteShort} note
|
||||||
*/
|
*/
|
||||||
doRefresh(note) {}
|
doRefresh(note) {}
|
||||||
|
|
||||||
|
@ -8,14 +8,7 @@ async function getNotesAndBranchesAndAttributes(noteIds) {
|
|||||||
noteIds = Array.from(new Set(noteIds));
|
noteIds = Array.from(new Set(noteIds));
|
||||||
const notes = await treeService.getNotes(noteIds);
|
const notes = await treeService.getNotes(noteIds);
|
||||||
|
|
||||||
const noteMap = {};
|
noteIds = notes.map(note => note.noteId);
|
||||||
noteIds = [];
|
|
||||||
|
|
||||||
for (const note of notes) {
|
|
||||||
note.attributes = [];
|
|
||||||
noteMap[note.noteId] = note;
|
|
||||||
noteIds.push(note.noteId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// joining child note to filter out not completely synchronised notes which would then cause errors later
|
// joining child note to filter out not completely synchronised notes which would then cause errors later
|
||||||
// cannot do that with parent because of root note's 'none' parent
|
// cannot do that with parent because of root note's 'none' parent
|
||||||
@ -37,14 +30,19 @@ async function getNotesAndBranchesAndAttributes(noteIds) {
|
|||||||
|
|
||||||
const attributes = await sql.getManyRows(`
|
const attributes = await sql.getManyRows(`
|
||||||
SELECT
|
SELECT
|
||||||
|
attributeId,
|
||||||
noteId,
|
noteId,
|
||||||
type,
|
type,
|
||||||
name,
|
name,
|
||||||
value,
|
value,
|
||||||
|
position,
|
||||||
isInheritable
|
isInheritable
|
||||||
FROM attributes
|
FROM attributes
|
||||||
WHERE isDeleted = 0 AND noteId IN (???)`, noteIds);
|
WHERE isDeleted = 0 AND noteId IN (???)`, noteIds);
|
||||||
|
|
||||||
|
// sorting in memory is faster
|
||||||
|
attributes.sort((a, b) => a.position - b.position < 0 ? -1 : 1);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
branches,
|
branches,
|
||||||
notes,
|
notes,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user