mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
renaming attributes to labels, fixes #79
This commit is contained in:
parent
4c472ce78b
commit
95bb2cf0bb
22
db/migrations/0080__rename_attributes_to_labels.sql
Normal file
22
db/migrations/0080__rename_attributes_to_labels.sql
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
create table labels
|
||||||
|
(
|
||||||
|
labelId TEXT not null primary key,
|
||||||
|
noteId TEXT not null,
|
||||||
|
name TEXT not null,
|
||||||
|
value TEXT default '' not null,
|
||||||
|
position INT default 0 not null,
|
||||||
|
dateCreated TEXT not null,
|
||||||
|
dateModified TEXT not null,
|
||||||
|
isDeleted INT not null
|
||||||
|
);
|
||||||
|
|
||||||
|
create index IDX_labels_name_value
|
||||||
|
on labels (name, value);
|
||||||
|
|
||||||
|
create index IDX_labels_noteId
|
||||||
|
on labels (noteId);
|
||||||
|
|
||||||
|
INSERT INTO labels (labelId, noteId, name, "value", "position", dateCreated, dateModified, isDeleted)
|
||||||
|
SELECT attributeId, noteId, name, "value", "position", dateCreated, dateModified, isDeleted FROM attributes;
|
||||||
|
|
||||||
|
DROP TABLE attributes;
|
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
const Entity = require('./entity');
|
const Entity = require('./entity');
|
||||||
|
|
||||||
class Attribute extends Entity {
|
class Label extends Entity {
|
||||||
static get tableName() { return "attributes"; }
|
static get tableName() { return "labels"; }
|
||||||
static get primaryKeyName() { return "attributeId"; }
|
static get primaryKeyName() { return "labelId"; }
|
||||||
|
|
||||||
async getNote() {
|
async getNote() {
|
||||||
return this.repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]);
|
return this.repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Attribute;
|
module.exports = Label;
|
@ -48,30 +48,30 @@ class Note extends Entity {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAttributes() {
|
async getLabels() {
|
||||||
return this.repository.getEntities("SELECT * FROM attributes WHERE noteId = ? AND isDeleted = 0", [this.noteId]);
|
return this.repository.getEntities("SELECT * FROM labels WHERE noteId = ? AND isDeleted = 0", [this.noteId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// WARNING: this doesn't take into account the possibility to have multi-valued attributes!
|
// WARNING: this doesn't take into account the possibility to have multi-valued labels!
|
||||||
async getAttributeMap() {
|
async getLabelMap() {
|
||||||
const map = {};
|
const map = {};
|
||||||
|
|
||||||
for (const attr of await this.getAttributes()) {
|
for (const attr of await this.getLabels()) {
|
||||||
map[attr.name] = attr.value;
|
map[attr.name] = attr.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
async hasAttribute(name) {
|
async hasLabel(name) {
|
||||||
const map = await this.getAttributeMap();
|
const map = await this.getLabelMap();
|
||||||
|
|
||||||
return map.hasOwnProperty(name);
|
return map.hasOwnProperty(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// WARNING: this doesn't take into account the possibility to have multi-valued attributes!
|
// WARNING: this doesn't take into account the possibility to have multi-valued labels!
|
||||||
async getAttribute(name) {
|
async getLabel(name) {
|
||||||
return this.repository.getEntity("SELECT * FROM attributes WHERE noteId = ? AND name = ?", [this.noteId, name]);
|
return this.repository.getEntity("SELECT * FROM labels WHERE noteId = ? AND name = ?", [this.noteId, name]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRevisions() {
|
async getRevisions() {
|
||||||
|
@ -1,43 +1,43 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const attributesDialog = (function() {
|
const labelsDialog = (function() {
|
||||||
const $showDialogButton = $(".show-attributes-button");
|
const $showDialogButton = $(".show-labels-button");
|
||||||
const $dialog = $("#attributes-dialog");
|
const $dialog = $("#labels-dialog");
|
||||||
const $saveAttributesButton = $("#save-attributes-button");
|
const $saveLabelsButton = $("#save-labels-button");
|
||||||
const $attributesBody = $('#attributes-table tbody');
|
const $labelsBody = $('#labels-table tbody');
|
||||||
|
|
||||||
const attributesModel = new AttributesModel();
|
const labelsModel = new LabelsModel();
|
||||||
let attributeNames = [];
|
let labelNames = [];
|
||||||
|
|
||||||
function AttributesModel() {
|
function LabelsModel() {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
this.attributes = ko.observableArray();
|
this.labels = ko.observableArray();
|
||||||
|
|
||||||
this.loadAttributes = async function() {
|
this.loadLabels = async function() {
|
||||||
const noteId = noteEditor.getCurrentNoteId();
|
const noteId = noteEditor.getCurrentNoteId();
|
||||||
|
|
||||||
const attributes = await server.get('notes/' + noteId + '/attributes');
|
const labels = await server.get('notes/' + noteId + '/labels');
|
||||||
|
|
||||||
self.attributes(attributes.map(ko.observable));
|
self.labels(labels.map(ko.observable));
|
||||||
|
|
||||||
addLastEmptyRow();
|
addLastEmptyRow();
|
||||||
|
|
||||||
attributeNames = await server.get('attributes/names');
|
labelNames = await server.get('labels/names');
|
||||||
|
|
||||||
// attribute might not be rendered immediatelly so could not focus
|
// label might not be rendered immediatelly so could not focus
|
||||||
setTimeout(() => $(".attribute-name:last").focus(), 100);
|
setTimeout(() => $(".label-name:last").focus(), 100);
|
||||||
|
|
||||||
$attributesBody.sortable({
|
$labelsBody.sortable({
|
||||||
handle: '.handle',
|
handle: '.handle',
|
||||||
containment: $attributesBody,
|
containment: $labelsBody,
|
||||||
update: function() {
|
update: function() {
|
||||||
let position = 0;
|
let position = 0;
|
||||||
|
|
||||||
// we need to update positions by searching in the DOM, because order of the
|
// we need to update positions by searching in the DOM, because order of the
|
||||||
// attributes in the viewmodel (self.attributes()) stays the same
|
// labels in the viewmodel (self.labels()) stays the same
|
||||||
$attributesBody.find('input[name="position"]').each(function() {
|
$labelsBody.find('input[name="position"]').each(function() {
|
||||||
const attr = self.getTargetAttribute(this);
|
const attr = self.getTargetLabel(this);
|
||||||
|
|
||||||
attr().position = position++;
|
attr().position = position++;
|
||||||
});
|
});
|
||||||
@ -45,8 +45,8 @@ const attributesDialog = (function() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.deleteAttribute = function(data, event) {
|
this.deleteLabel = function(data, event) {
|
||||||
const attr = self.getTargetAttribute(event.target);
|
const attr = self.getTargetLabel(event.target);
|
||||||
const attrData = attr();
|
const attrData = attr();
|
||||||
|
|
||||||
if (attrData) {
|
if (attrData) {
|
||||||
@ -59,7 +59,7 @@ const attributesDialog = (function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function isValid() {
|
function isValid() {
|
||||||
for (let attrs = self.attributes(), i = 0; i < attrs.length; i++) {
|
for (let attrs = self.labels(), i = 0; i < attrs.length; i++) {
|
||||||
if (self.isEmptyName(i)) {
|
if (self.isEmptyName(i)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -72,7 +72,7 @@ const attributesDialog = (function() {
|
|||||||
// we need to defocus from input (in case of enter-triggered save) because value is updated
|
// we need to defocus from input (in case of enter-triggered save) because value is updated
|
||||||
// on blur event (because of conflict with jQuery UI Autocomplete). Without this, input would
|
// on blur event (because of conflict with jQuery UI Autocomplete). Without this, input would
|
||||||
// stay in focus, blur wouldn't be triggered and change wouldn't be updated in the viewmodel.
|
// stay in focus, blur wouldn't be triggered and change wouldn't be updated in the viewmodel.
|
||||||
$saveAttributesButton.focus();
|
$saveLabelsButton.focus();
|
||||||
|
|
||||||
if (!isValid()) {
|
if (!isValid()) {
|
||||||
alert("Please fix all validation errors and try saving again.");
|
alert("Please fix all validation errors and try saving again.");
|
||||||
@ -81,28 +81,28 @@ const attributesDialog = (function() {
|
|||||||
|
|
||||||
const noteId = noteEditor.getCurrentNoteId();
|
const noteId = noteEditor.getCurrentNoteId();
|
||||||
|
|
||||||
const attributesToSave = self.attributes()
|
const labelsToSave = self.labels()
|
||||||
.map(attr => attr())
|
.map(attr => attr())
|
||||||
.filter(attr => attr.attributeId !== "" || attr.name !== "");
|
.filter(attr => attr.labelId !== "" || attr.name !== "");
|
||||||
|
|
||||||
const attributes = await server.put('notes/' + noteId + '/attributes', attributesToSave);
|
const labels = await server.put('notes/' + noteId + '/labels', labelsToSave);
|
||||||
|
|
||||||
self.attributes(attributes.map(ko.observable));
|
self.labels(labels.map(ko.observable));
|
||||||
|
|
||||||
addLastEmptyRow();
|
addLastEmptyRow();
|
||||||
|
|
||||||
showMessage("Attributes have been saved.");
|
showMessage("Labels have been saved.");
|
||||||
|
|
||||||
noteEditor.loadAttributeList();
|
noteEditor.loadLabelList();
|
||||||
};
|
};
|
||||||
|
|
||||||
function addLastEmptyRow() {
|
function addLastEmptyRow() {
|
||||||
const attrs = self.attributes().filter(attr => attr().isDeleted === 0);
|
const attrs = self.labels().filter(attr => attr().isDeleted === 0);
|
||||||
const last = attrs.length === 0 ? null : attrs[attrs.length - 1]();
|
const last = attrs.length === 0 ? null : attrs[attrs.length - 1]();
|
||||||
|
|
||||||
if (!last || last.name.trim() !== "" || last.value !== "") {
|
if (!last || last.name.trim() !== "" || last.value !== "") {
|
||||||
self.attributes.push(ko.observable({
|
self.labels.push(ko.observable({
|
||||||
attributeId: '',
|
labelId: '',
|
||||||
name: '',
|
name: '',
|
||||||
value: '',
|
value: '',
|
||||||
isDeleted: 0,
|
isDeleted: 0,
|
||||||
@ -111,22 +111,22 @@ const attributesDialog = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.attributeChanged = function (data, event) {
|
this.labelChanged = function (data, event) {
|
||||||
addLastEmptyRow();
|
addLastEmptyRow();
|
||||||
|
|
||||||
const attr = self.getTargetAttribute(event.target);
|
const attr = self.getTargetLabel(event.target);
|
||||||
|
|
||||||
attr.valueHasMutated();
|
attr.valueHasMutated();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.isNotUnique = function(index) {
|
this.isNotUnique = function(index) {
|
||||||
const cur = self.attributes()[index]();
|
const cur = self.labels()[index]();
|
||||||
|
|
||||||
if (cur.name.trim() === "") {
|
if (cur.name.trim() === "") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let attrs = self.attributes(), i = 0; i < attrs.length; i++) {
|
for (let attrs = self.labels(), i = 0; i < attrs.length; i++) {
|
||||||
const attr = attrs[i]();
|
const attr = attrs[i]();
|
||||||
|
|
||||||
if (index !== i && cur.name === attr.name) {
|
if (index !== i && cur.name === attr.name) {
|
||||||
@ -138,23 +138,23 @@ const attributesDialog = (function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.isEmptyName = function(index) {
|
this.isEmptyName = function(index) {
|
||||||
const cur = self.attributes()[index]();
|
const cur = self.labels()[index]();
|
||||||
|
|
||||||
return cur.name.trim() === "" && (cur.attributeId !== "" || cur.value !== "");
|
return cur.name.trim() === "" && (cur.labelId !== "" || cur.value !== "");
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getTargetAttribute = function(target) {
|
this.getTargetLabel = function(target) {
|
||||||
const context = ko.contextFor(target);
|
const context = ko.contextFor(target);
|
||||||
const index = context.$index();
|
const index = context.$index();
|
||||||
|
|
||||||
return self.attributes()[index];
|
return self.labels()[index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function showDialog() {
|
async function showDialog() {
|
||||||
glob.activeDialog = $dialog;
|
glob.activeDialog = $dialog;
|
||||||
|
|
||||||
await attributesModel.loadAttributes();
|
await labelsModel.loadLabels();
|
||||||
|
|
||||||
$dialog.dialog({
|
$dialog.dialog({
|
||||||
modal: true,
|
modal: true,
|
||||||
@ -169,14 +169,14 @@ const attributesDialog = (function() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
ko.applyBindings(attributesModel, document.getElementById('attributes-dialog'));
|
ko.applyBindings(labelsModel, document.getElementById('labels-dialog'));
|
||||||
|
|
||||||
$(document).on('focus', '.attribute-name', function (e) {
|
$(document).on('focus', '.label-name', function (e) {
|
||||||
if (!$(this).hasClass("ui-autocomplete-input")) {
|
if (!$(this).hasClass("ui-autocomplete-input")) {
|
||||||
$(this).autocomplete({
|
$(this).autocomplete({
|
||||||
// shouldn't be required and autocomplete should just accept array of strings, but that fails
|
// shouldn't be required and autocomplete should just accept array of strings, but that fails
|
||||||
// because we have overriden filter() function in init.js
|
// because we have overriden filter() function in init.js
|
||||||
source: attributeNames.map(attr => {
|
source: labelNames.map(attr => {
|
||||||
return {
|
return {
|
||||||
label: attr,
|
label: attr,
|
||||||
value: attr
|
value: attr
|
||||||
@ -189,24 +189,24 @@ const attributesDialog = (function() {
|
|||||||
$(this).autocomplete("search", $(this).val());
|
$(this).autocomplete("search", $(this).val());
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('focus', '.attribute-value', async function (e) {
|
$(document).on('focus', '.label-value', async function (e) {
|
||||||
if (!$(this).hasClass("ui-autocomplete-input")) {
|
if (!$(this).hasClass("ui-autocomplete-input")) {
|
||||||
const attributeName = $(this).parent().parent().find('.attribute-name').val();
|
const labelName = $(this).parent().parent().find('.label-name').val();
|
||||||
|
|
||||||
if (attributeName.trim() === "") {
|
if (labelName.trim() === "") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const attributeValues = await server.get('attributes/values/' + encodeURIComponent(attributeName));
|
const labelValues = await server.get('labels/values/' + encodeURIComponent(labelName));
|
||||||
|
|
||||||
if (attributeValues.length === 0) {
|
if (labelValues.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$(this).autocomplete({
|
$(this).autocomplete({
|
||||||
// shouldn't be required and autocomplete should just accept array of strings, but that fails
|
// shouldn't be required and autocomplete should just accept array of strings, but that fails
|
||||||
// because we have overriden filter() function in init.js
|
// because we have overriden filter() function in init.js
|
||||||
source: attributeValues.map(attr => {
|
source: labelValues.map(attr => {
|
||||||
return {
|
return {
|
||||||
label: attr,
|
label: attr,
|
||||||
value: attr
|
value: attr
|
@ -13,8 +13,8 @@ const noteEditor = (function() {
|
|||||||
const $unprotectButton = $("#unprotect-button");
|
const $unprotectButton = $("#unprotect-button");
|
||||||
const $noteDetailWrapper = $("#note-detail-wrapper");
|
const $noteDetailWrapper = $("#note-detail-wrapper");
|
||||||
const $noteIdDisplay = $("#note-id-display");
|
const $noteIdDisplay = $("#note-id-display");
|
||||||
const $attributeList = $("#attribute-list");
|
const $labelList = $("#label-list");
|
||||||
const $attributeListInner = $("#attribute-list-inner");
|
const $labelListInner = $("#label-list-inner");
|
||||||
const $attachmentFileName = $("#attachment-filename");
|
const $attachmentFileName = $("#attachment-filename");
|
||||||
const $attachmentFileType = $("#attachment-filetype");
|
const $attachmentFileType = $("#attachment-filetype");
|
||||||
const $attachmentFileSize = $("#attachment-filesize");
|
const $attachmentFileSize = $("#attachment-filesize");
|
||||||
@ -253,8 +253,8 @@ const noteEditor = (function() {
|
|||||||
else if (currentNote.detail.type === 'file') {
|
else if (currentNote.detail.type === 'file') {
|
||||||
$noteDetailAttachment.show();
|
$noteDetailAttachment.show();
|
||||||
|
|
||||||
$attachmentFileName.text(currentNote.attributes.original_file_name);
|
$attachmentFileName.text(currentNote.labels.original_file_name);
|
||||||
$attachmentFileSize.text(currentNote.attributes.file_size + " bytes");
|
$attachmentFileSize.text(currentNote.labels.file_size + " bytes");
|
||||||
$attachmentFileType.text(currentNote.detail.mime);
|
$attachmentFileType.text(currentNote.detail.mime);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -269,25 +269,25 @@ const noteEditor = (function() {
|
|||||||
// after loading new note make sure editor is scrolled to the top
|
// after loading new note make sure editor is scrolled to the top
|
||||||
$noteDetailWrapper.scrollTop(0);
|
$noteDetailWrapper.scrollTop(0);
|
||||||
|
|
||||||
loadAttributeList();
|
loadLabelList();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadAttributeList() {
|
async function loadLabelList() {
|
||||||
const noteId = getCurrentNoteId();
|
const noteId = getCurrentNoteId();
|
||||||
|
|
||||||
const attributes = await server.get('notes/' + noteId + '/attributes');
|
const labels = await server.get('notes/' + noteId + '/labels');
|
||||||
|
|
||||||
$attributeListInner.html('');
|
$labelListInner.html('');
|
||||||
|
|
||||||
if (attributes.length > 0) {
|
if (labels.length > 0) {
|
||||||
for (const attr of attributes) {
|
for (const attr of labels) {
|
||||||
$attributeListInner.append(formatAttribute(attr) + " ");
|
$labelListInner.append(formatLabel(attr) + " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
$attributeList.show();
|
$labelList.show();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$attributeList.hide();
|
$labelList.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -394,7 +394,7 @@ const noteEditor = (function() {
|
|||||||
getEditor,
|
getEditor,
|
||||||
focus,
|
focus,
|
||||||
executeCurrentNote,
|
executeCurrentNote,
|
||||||
loadAttributeList,
|
loadLabelList,
|
||||||
setContent
|
setContent
|
||||||
};
|
};
|
||||||
})();
|
})();
|
@ -125,7 +125,7 @@ function formatValueWithWhitespace(val) {
|
|||||||
return /[^\w_-]/.test(val) ? '"' + val + '"' : val;
|
return /[^\w_-]/.test(val) ? '"' + val + '"' : val;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatAttribute(attr) {
|
function formatLabel(attr) {
|
||||||
let str = "@" + formatValueWithWhitespace(attr.name);
|
let str = "@" + formatValueWithWhitespace(attr.name);
|
||||||
|
|
||||||
if (attr.value !== "") {
|
if (attr.value !== "") {
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"search note-content"
|
"search note-content"
|
||||||
"tree note-content"
|
"tree note-content"
|
||||||
"parent-list note-content"
|
"parent-list note-content"
|
||||||
"parent-list attribute-list";
|
"parent-list label-list";
|
||||||
grid-template-columns: 2fr 5fr;
|
grid-template-columns: 2fr 5fr;
|
||||||
grid-template-rows: auto
|
grid-template-rows: auto
|
||||||
auto
|
auto
|
||||||
@ -269,14 +269,14 @@ div.ui-tooltip {
|
|||||||
|
|
||||||
.cm-matchhighlight {background-color: #eeeeee}
|
.cm-matchhighlight {background-color: #eeeeee}
|
||||||
|
|
||||||
#attribute-list {
|
#label-list {
|
||||||
grid-area: attribute-list;
|
grid-area: label-list;
|
||||||
color: #777777;
|
color: #777777;
|
||||||
border-top: 1px solid #eee;
|
border-top: 1px solid #eee;
|
||||||
padding: 5px; display: none;
|
padding: 5px; display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#attribute-list button {
|
#label-list button {
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ const router = express.Router();
|
|||||||
const sql = require('../../services/sql');
|
const sql = require('../../services/sql');
|
||||||
const auth = require('../../services/auth');
|
const auth = require('../../services/auth');
|
||||||
const notes = require('../../services/notes');
|
const notes = require('../../services/notes');
|
||||||
const attributes = require('../../services/attributes');
|
const labels = require('../../services/labels');
|
||||||
const protected_session = require('../../services/protected_session');
|
const protected_session = require('../../services/protected_session');
|
||||||
const multer = require('multer')();
|
const multer = require('multer')();
|
||||||
const wrap = require('express-promise-wrap').wrap;
|
const wrap = require('express-promise-wrap').wrap;
|
||||||
@ -33,8 +33,8 @@ router.post('/upload/:parentNoteId', auth.checkApiAuthOrElectron, multer.single(
|
|||||||
mime: file.mimetype
|
mime: file.mimetype
|
||||||
}, req, sourceId)).noteId;
|
}, req, sourceId)).noteId;
|
||||||
|
|
||||||
await attributes.createAttribute(noteId, "original_file_name", originalName, sourceId);
|
await labels.createLabel(noteId, "original_file_name", originalName, sourceId);
|
||||||
await attributes.createAttribute(noteId, "file_size", size, sourceId);
|
await labels.createLabel(noteId, "file_size", size, sourceId);
|
||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
noteId: noteId
|
noteId: noteId
|
||||||
@ -62,8 +62,8 @@ router.get('/download/:noteId', auth.checkApiAuthOrElectron, wrap(async (req, re
|
|||||||
protected_session.decryptNote(dataKey, note);
|
protected_session.decryptNote(dataKey, note);
|
||||||
}
|
}
|
||||||
|
|
||||||
const attributeMap = await attributes.getNoteAttributeMap(noteId);
|
const labelMap = await labels.getNoteLabelMap(noteId);
|
||||||
const fileName = attributeMap.original_file_name ? attributeMap.original_file_name : note.title;
|
const fileName = labelMap.original_file_name ? labelMap.original_file_name : note.title;
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=' + fileName);
|
res.setHeader('Content-Disposition', 'attachment; filename=' + fileName);
|
||||||
res.setHeader('Content-Type', note.mime);
|
res.setHeader('Content-Type', note.mime);
|
||||||
|
@ -22,7 +22,7 @@ router.post('/cleanup-soft-deleted-items', auth.checkApiAuth, wrap(async (req, r
|
|||||||
|
|
||||||
await sql.execute(`DELETE FROM note_images WHERE noteId IN (${noteIdsSql})`);
|
await sql.execute(`DELETE FROM note_images WHERE noteId IN (${noteIdsSql})`);
|
||||||
|
|
||||||
await sql.execute(`DELETE FROM attributes WHERE noteId IN (${noteIdsSql})`);
|
await sql.execute(`DELETE FROM labels WHERE noteId IN (${noteIdsSql})`);
|
||||||
|
|
||||||
await sql.execute("DELETE FROM branches WHERE isDeleted = 1");
|
await sql.execute("DELETE FROM branches WHERE isDeleted = 1");
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ async function exportNote(branchId, directory, pack, repo) {
|
|||||||
|
|
||||||
const metadata = await getMetadata(note);
|
const metadata = await getMetadata(note);
|
||||||
|
|
||||||
if (metadata.attributes.find(attr => attr.name === 'exclude_from_export')) {
|
if (metadata.labels.find(attr => attr.name === 'exclude_from_export')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ async function getMetadata(note) {
|
|||||||
title: note.title,
|
title: note.title,
|
||||||
type: note.type,
|
type: note.type,
|
||||||
mime: note.mime,
|
mime: note.mime,
|
||||||
attributes: (await note.getAttributes()).map(attr => {
|
labels: (await note.getLabels()).map(attr => {
|
||||||
return {
|
return {
|
||||||
name: attr.name,
|
name: attr.name,
|
||||||
value: attr.value
|
value: attr.value
|
||||||
|
@ -4,7 +4,7 @@ const express = require('express');
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const sql = require('../../services/sql');
|
const sql = require('../../services/sql');
|
||||||
const auth = require('../../services/auth');
|
const auth = require('../../services/auth');
|
||||||
const attributes = require('../../services/attributes');
|
const labels = require('../../services/labels');
|
||||||
const notes = require('../../services/notes');
|
const notes = require('../../services/notes');
|
||||||
const wrap = require('express-promise-wrap').wrap;
|
const wrap = require('express-promise-wrap').wrap;
|
||||||
const tar = require('tar-stream');
|
const tar = require('tar-stream');
|
||||||
@ -126,8 +126,8 @@ async function importNotes(files, parentNoteId, sourceId) {
|
|||||||
sourceId: sourceId
|
sourceId: sourceId
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const attr of file.meta.attributes) {
|
for (const attr of file.meta.labels) {
|
||||||
await attributes.createAttribute(noteId, attr.name, attr.value);
|
await labels.createLabel(noteId, attr.name, attr.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.children.length > 0) {
|
if (file.children.length > 0) {
|
||||||
|
@ -7,24 +7,24 @@ const auth = require('../../services/auth');
|
|||||||
const sync_table = require('../../services/sync_table');
|
const sync_table = require('../../services/sync_table');
|
||||||
const utils = require('../../services/utils');
|
const utils = require('../../services/utils');
|
||||||
const wrap = require('express-promise-wrap').wrap;
|
const wrap = require('express-promise-wrap').wrap;
|
||||||
const attributes = require('../../services/attributes');
|
const labels = require('../../services/labels');
|
||||||
|
|
||||||
router.get('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => {
|
router.get('/notes/:noteId/labels', auth.checkApiAuth, wrap(async (req, res, next) => {
|
||||||
const noteId = req.params.noteId;
|
const noteId = req.params.noteId;
|
||||||
|
|
||||||
res.send(await sql.getRows("SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ? ORDER BY position, dateCreated", [noteId]));
|
res.send(await sql.getRows("SELECT * FROM labels WHERE isDeleted = 0 AND noteId = ? ORDER BY position, dateCreated", [noteId]));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => {
|
router.put('/notes/:noteId/labels', auth.checkApiAuth, wrap(async (req, res, next) => {
|
||||||
const noteId = req.params.noteId;
|
const noteId = req.params.noteId;
|
||||||
const attributes = req.body;
|
const labels = req.body;
|
||||||
const now = utils.nowDate();
|
const now = utils.nowDate();
|
||||||
|
|
||||||
await sql.doInTransaction(async () => {
|
await sql.doInTransaction(async () => {
|
||||||
for (const attr of attributes) {
|
for (const attr of labels) {
|
||||||
if (attr.attributeId) {
|
if (attr.labelId) {
|
||||||
await sql.execute("UPDATE attributes SET name = ?, value = ?, dateModified = ?, isDeleted = ?, position = ? WHERE attributeId = ?",
|
await sql.execute("UPDATE labels SET name = ?, value = ?, dateModified = ?, isDeleted = ?, position = ? WHERE labelId = ?",
|
||||||
[attr.name, attr.value, now, attr.isDeleted, attr.position, attr.attributeId]);
|
[attr.name, attr.value, now, attr.isDeleted, attr.position, attr.labelId]);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// if it was "created" and then immediatelly deleted, we just don't create it at all
|
// if it was "created" and then immediatelly deleted, we just don't create it at all
|
||||||
@ -32,10 +32,10 @@ router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
attr.attributeId = utils.newAttributeId();
|
attr.labelId = utils.newLabelId();
|
||||||
|
|
||||||
await sql.insert("attributes", {
|
await sql.insert("labels", {
|
||||||
attributeId: attr.attributeId,
|
labelId: attr.labelId,
|
||||||
noteId: noteId,
|
noteId: noteId,
|
||||||
name: attr.name,
|
name: attr.name,
|
||||||
value: attr.value,
|
value: attr.value,
|
||||||
@ -46,17 +46,17 @@ router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await sync_table.addAttributeSync(attr.attributeId);
|
await sync_table.addLabelSync(attr.labelId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
res.send(await sql.getRows("SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ? ORDER BY position, dateCreated", [noteId]));
|
res.send(await sql.getRows("SELECT * FROM labels WHERE isDeleted = 0 AND noteId = ? ORDER BY position, dateCreated", [noteId]));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
router.get('/attributes/names', auth.checkApiAuth, wrap(async (req, res, next) => {
|
router.get('/labels/names', auth.checkApiAuth, wrap(async (req, res, next) => {
|
||||||
const names = await sql.getColumn("SELECT DISTINCT name FROM attributes WHERE isDeleted = 0");
|
const names = await sql.getColumn("SELECT DISTINCT name FROM labels WHERE isDeleted = 0");
|
||||||
|
|
||||||
for (const attr of attributes.BUILTIN_ATTRIBUTES) {
|
for (const attr of labels.BUILTIN_LABELS) {
|
||||||
if (!names.includes(attr)) {
|
if (!names.includes(attr)) {
|
||||||
names.push(attr);
|
names.push(attr);
|
||||||
}
|
}
|
||||||
@ -67,10 +67,10 @@ router.get('/attributes/names', auth.checkApiAuth, wrap(async (req, res, next) =
|
|||||||
res.send(names);
|
res.send(names);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
router.get('/attributes/values/:attributeName', auth.checkApiAuth, wrap(async (req, res, next) => {
|
router.get('/labels/values/:labelName', auth.checkApiAuth, wrap(async (req, res, next) => {
|
||||||
const attributeName = req.params.attributeName;
|
const labelName = req.params.labelName;
|
||||||
|
|
||||||
const values = await sql.getColumn("SELECT DISTINCT value FROM attributes WHERE isDeleted = 0 AND name = ? AND value != '' ORDER BY value", [attributeName]);
|
const values = await sql.getColumn("SELECT DISTINCT value FROM labels WHERE isDeleted = 0 AND name = ? AND value != '' ORDER BY value", [labelName]);
|
||||||
|
|
||||||
res.send(values);
|
res.send(values);
|
||||||
}));
|
}));
|
@ -5,7 +5,7 @@ const router = express.Router();
|
|||||||
const auth = require('../../services/auth');
|
const auth = require('../../services/auth');
|
||||||
const sql = require('../../services/sql');
|
const sql = require('../../services/sql');
|
||||||
const notes = require('../../services/notes');
|
const notes = require('../../services/notes');
|
||||||
const attributes = require('../../services/attributes');
|
const labels = require('../../services/labels');
|
||||||
const log = require('../../services/log');
|
const log = require('../../services/log');
|
||||||
const utils = require('../../services/utils');
|
const utils = require('../../services/utils');
|
||||||
const protected_session = require('../../services/protected_session');
|
const protected_session = require('../../services/protected_session');
|
||||||
@ -26,19 +26,19 @@ router.get('/:noteId', auth.checkApiAuth, wrap(async (req, res, next) => {
|
|||||||
|
|
||||||
protected_session.decryptNote(req, detail);
|
protected_session.decryptNote(req, detail);
|
||||||
|
|
||||||
let attributeMap = null;
|
let labelMap = null;
|
||||||
|
|
||||||
if (detail.type === 'file') {
|
if (detail.type === 'file') {
|
||||||
// no need to transfer attachment payload for this request
|
// no need to transfer attachment payload for this request
|
||||||
detail.content = null;
|
detail.content = null;
|
||||||
|
|
||||||
// attributes contain important attachment metadata - filename and size
|
// labels contain important attachment metadata - filename and size
|
||||||
attributeMap = await attributes.getNoteAttributeMap(noteId);
|
labelMap = await labels.getNoteLabelMap(noteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
detail: detail,
|
detail: detail,
|
||||||
attributes: attributeMap
|
labels: labelMap
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ const express = require('express');
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const auth = require('../../services/auth');
|
const auth = require('../../services/auth');
|
||||||
const wrap = require('express-promise-wrap').wrap;
|
const wrap = require('express-promise-wrap').wrap;
|
||||||
const attributes = require('../../services/attributes');
|
const labels = require('../../services/labels');
|
||||||
const script = require('../../services/script');
|
const script = require('../../services/script');
|
||||||
const Repository = require('../../services/repository');
|
const Repository = require('../../services/repository');
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ router.post('/run/:noteId', auth.checkApiAuth, wrap(async (req, res, next) => {
|
|||||||
|
|
||||||
router.get('/startup', auth.checkApiAuth, wrap(async (req, res, next) => {
|
router.get('/startup', auth.checkApiAuth, wrap(async (req, res, next) => {
|
||||||
const repository = new Repository(req);
|
const repository = new Repository(req);
|
||||||
const notes = await attributes.getNotesWithAttribute(repository, "run", "frontend_startup");
|
const notes = await labels.getNotesWithLabel(repository, "run", "frontend_startup");
|
||||||
|
|
||||||
const scripts = [];
|
const scripts = [];
|
||||||
|
|
||||||
|
@ -144,10 +144,10 @@ router.get('/note_images/:noteImageId', auth.checkApiAuth, wrap(async (req, res,
|
|||||||
res.send(await sql.getRow("SELECT * FROM note_images WHERE noteImageId = ?", [noteImageId]));
|
res.send(await sql.getRow("SELECT * FROM note_images WHERE noteImageId = ?", [noteImageId]));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
router.get('/attributes/:attributeId', auth.checkApiAuth, wrap(async (req, res, next) => {
|
router.get('/labels/:labelId', auth.checkApiAuth, wrap(async (req, res, next) => {
|
||||||
const attributeId = req.params.attributeId;
|
const labelId = req.params.labelId;
|
||||||
|
|
||||||
res.send(await sql.getRow("SELECT * FROM attributes WHERE attributeId = ?", [attributeId]));
|
res.send(await sql.getRow("SELECT * FROM labels WHERE labelId = ?", [labelId]));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
router.get('/api_tokens/:apiTokenId', auth.checkApiAuth, wrap(async (req, res, next) => {
|
router.get('/api_tokens/:apiTokenId', auth.checkApiAuth, wrap(async (req, res, next) => {
|
||||||
@ -204,8 +204,8 @@ router.put('/note_images', auth.checkApiAuth, wrap(async (req, res, next) => {
|
|||||||
res.send({});
|
res.send({});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
router.put('/attributes', auth.checkApiAuth, wrap(async (req, res, next) => {
|
router.put('/labels', auth.checkApiAuth, wrap(async (req, res, next) => {
|
||||||
await syncUpdate.updateAttribute(req.body.entity, req.body.sourceId);
|
await syncUpdate.updateLabel(req.body.entity, req.body.sourceId);
|
||||||
|
|
||||||
res.send({});
|
res.send({});
|
||||||
}));
|
}));
|
||||||
|
@ -42,10 +42,10 @@ router.get('/', auth.checkApiAuth, wrap(async (req, res, next) => {
|
|||||||
notes.isProtected,
|
notes.isProtected,
|
||||||
notes.type,
|
notes.type,
|
||||||
notes.mime,
|
notes.mime,
|
||||||
hideInAutocomplete.attributeId AS 'hideInAutocomplete'
|
hideInAutocomplete.labelId AS 'hideInAutocomplete'
|
||||||
FROM
|
FROM
|
||||||
notes
|
notes
|
||||||
LEFT JOIN attributes AS hideInAutocomplete ON hideInAutocomplete.noteId = notes.noteId
|
LEFT JOIN labels AS hideInAutocomplete ON hideInAutocomplete.noteId = notes.noteId
|
||||||
AND hideInAutocomplete.name = 'hide_in_autocomplete'
|
AND hideInAutocomplete.name = 'hide_in_autocomplete'
|
||||||
AND hideInAutocomplete.isDeleted = 0
|
AND hideInAutocomplete.isDeleted = 0
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -106,7 +106,7 @@ router.put('/:branchId/expanded/:expanded', auth.checkApiAuth, wrap(async (req,
|
|||||||
await sql.doInTransaction(async () => {
|
await sql.doInTransaction(async () => {
|
||||||
await sql.execute("UPDATE branches SET isExpanded = ? WHERE branchId = ?", [expanded, branchId]);
|
await sql.execute("UPDATE branches SET isExpanded = ? WHERE branchId = ?", [expanded, branchId]);
|
||||||
|
|
||||||
// we don't sync expanded attribute
|
// we don't sync expanded label
|
||||||
});
|
});
|
||||||
|
|
||||||
res.send({});
|
res.send({});
|
||||||
|
@ -6,7 +6,7 @@ const auth = require('../services/auth');
|
|||||||
const source_id = require('../services/source_id');
|
const source_id = require('../services/source_id');
|
||||||
const sql = require('../services/sql');
|
const sql = require('../services/sql');
|
||||||
const Repository = require('../services/repository');
|
const Repository = require('../services/repository');
|
||||||
const attributes = require('../services/attributes');
|
const labels = require('../services/labels');
|
||||||
const wrap = require('express-promise-wrap').wrap;
|
const wrap = require('express-promise-wrap').wrap;
|
||||||
|
|
||||||
router.get('', auth.checkAuth, wrap(async (req, res, next) => {
|
router.get('', auth.checkAuth, wrap(async (req, res, next) => {
|
||||||
@ -21,7 +21,7 @@ router.get('', auth.checkAuth, wrap(async (req, res, next) => {
|
|||||||
|
|
||||||
async function getAppCss(repository) {
|
async function getAppCss(repository) {
|
||||||
let css = '';
|
let css = '';
|
||||||
const notes = attributes.getNotesWithAttribute(repository, 'app_css');
|
const notes = labels.getNotesWithLabel(repository, 'app_css');
|
||||||
|
|
||||||
for (const note of await notes) {
|
for (const note of await notes) {
|
||||||
css += `/* ${note.noteId} */
|
css += `/* ${note.noteId} */
|
||||||
|
@ -26,7 +26,7 @@ const sqlRoute = require('./api/sql');
|
|||||||
const anonymizationRoute = require('./api/anonymization');
|
const anonymizationRoute = require('./api/anonymization');
|
||||||
const cleanupRoute = require('./api/cleanup');
|
const cleanupRoute = require('./api/cleanup');
|
||||||
const imageRoute = require('./api/image');
|
const imageRoute = require('./api/image');
|
||||||
const attributesRoute = require('./api/attributes');
|
const labelsRoute = require('./api/labels');
|
||||||
const scriptRoute = require('./api/script');
|
const scriptRoute = require('./api/script');
|
||||||
const senderRoute = require('./api/sender');
|
const senderRoute = require('./api/sender');
|
||||||
const attachmentsRoute = require('./api/attachments');
|
const attachmentsRoute = require('./api/attachments');
|
||||||
@ -43,7 +43,7 @@ function register(app) {
|
|||||||
app.use('/api/notes', notesApiRoute);
|
app.use('/api/notes', notesApiRoute);
|
||||||
app.use('/api/tree', treeChangesApiRoute);
|
app.use('/api/tree', treeChangesApiRoute);
|
||||||
app.use('/api/notes', cloningApiRoute);
|
app.use('/api/notes', cloningApiRoute);
|
||||||
app.use('/api', attributesRoute);
|
app.use('/api', labelsRoute);
|
||||||
app.use('/api/notes-history', noteHistoryApiRoute);
|
app.use('/api/notes-history', noteHistoryApiRoute);
|
||||||
app.use('/api/recent-changes', recentChangesApiRoute);
|
app.use('/api/recent-changes', recentChangesApiRoute);
|
||||||
app.use('/api/settings', settingsApiRoute);
|
app.use('/api/settings', settingsApiRoute);
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -3,7 +3,7 @@
|
|||||||
const build = require('./build');
|
const build = require('./build');
|
||||||
const packageJson = require('../../package');
|
const packageJson = require('../../package');
|
||||||
|
|
||||||
const APP_DB_VERSION = 79;
|
const APP_DB_VERSION = 80;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
app_version: packageJson.version,
|
app_version: packageJson.version,
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
const sql = require('./sql');
|
|
||||||
const utils = require('./utils');
|
|
||||||
const sync_table = require('./sync_table');
|
|
||||||
|
|
||||||
const BUILTIN_ATTRIBUTES = [
|
|
||||||
'frontend_startup',
|
|
||||||
'backend_startup',
|
|
||||||
'disable_versioning',
|
|
||||||
'calendar_root',
|
|
||||||
'hide_in_autocomplete',
|
|
||||||
'exclude_from_export',
|
|
||||||
'run',
|
|
||||||
'manual_transaction_handling',
|
|
||||||
'disable_inclusion',
|
|
||||||
'app_css'
|
|
||||||
];
|
|
||||||
|
|
||||||
async function getNoteAttributeMap(noteId) {
|
|
||||||
return await sql.getMap(`SELECT name, value FROM attributes WHERE noteId = ? AND isDeleted = 0`, [noteId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getNoteIdWithAttribute(name, value) {
|
|
||||||
return await sql.getValue(`SELECT notes.noteId FROM notes JOIN attributes USING(noteId)
|
|
||||||
WHERE notes.isDeleted = 0
|
|
||||||
AND attributes.isDeleted = 0
|
|
||||||
AND attributes.name = ?
|
|
||||||
AND attributes.value = ?`, [name, value]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getNotesWithAttribute(repository, name, value) {
|
|
||||||
let notes;
|
|
||||||
|
|
||||||
if (value !== undefined) {
|
|
||||||
notes = await repository.getEntities(`SELECT notes.* FROM notes JOIN attributes USING(noteId)
|
|
||||||
WHERE notes.isDeleted = 0 AND attributes.isDeleted = 0 AND attributes.name = ? AND attributes.value = ?`, [name, value]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
notes = await repository.getEntities(`SELECT notes.* FROM notes JOIN attributes USING(noteId)
|
|
||||||
WHERE notes.isDeleted = 0 AND attributes.isDeleted = 0 AND attributes.name = ?`, [name]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return notes;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getNoteWithAttribute(repository, name, value) {
|
|
||||||
const notes = getNotesWithAttribute(repository, name, value);
|
|
||||||
|
|
||||||
return notes.length > 0 ? notes[0] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getNoteIdsWithAttribute(name) {
|
|
||||||
return await sql.getColumn(`SELECT DISTINCT notes.noteId FROM notes JOIN attributes USING(noteId)
|
|
||||||
WHERE notes.isDeleted = 0 AND attributes.isDeleted = 0 AND attributes.name = ? AND attributes.isDeleted = 0`, [name]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createAttribute(noteId, name, value = "", sourceId = null) {
|
|
||||||
if (value === null || value === undefined) {
|
|
||||||
value = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
const now = utils.nowDate();
|
|
||||||
const attributeId = utils.newAttributeId();
|
|
||||||
const position = 1 + await sql.getValue(`SELECT COALESCE(MAX(position), 0) FROM attributes WHERE noteId = ?`, [noteId]);
|
|
||||||
|
|
||||||
await sql.insert("attributes", {
|
|
||||||
attributeId: attributeId,
|
|
||||||
noteId: noteId,
|
|
||||||
name: name,
|
|
||||||
value: value,
|
|
||||||
position: position,
|
|
||||||
dateModified: now,
|
|
||||||
dateCreated: now,
|
|
||||||
isDeleted: false
|
|
||||||
});
|
|
||||||
|
|
||||||
await sync_table.addAttributeSync(attributeId, sourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
getNoteAttributeMap,
|
|
||||||
getNoteIdWithAttribute,
|
|
||||||
getNotesWithAttribute,
|
|
||||||
getNoteWithAttribute,
|
|
||||||
getNoteIdsWithAttribute,
|
|
||||||
createAttribute,
|
|
||||||
BUILTIN_ATTRIBUTES
|
|
||||||
};
|
|
@ -7,16 +7,16 @@ module.exports = function(attrFilters, searchText) {
|
|||||||
let i = 1;
|
let i = 1;
|
||||||
|
|
||||||
for (const filter of attrFilters) {
|
for (const filter of attrFilters) {
|
||||||
joins.push(`LEFT JOIN attributes AS attr${i} ON attr${i}.noteId = notes.noteId AND attr${i}.name = ?`);
|
joins.push(`LEFT JOIN labels AS attr${i} ON attr${i}.noteId = notes.noteId AND attr${i}.name = ?`);
|
||||||
joinParams.push(filter.name);
|
joinParams.push(filter.name);
|
||||||
|
|
||||||
where += " " + filter.relation + " ";
|
where += " " + filter.relation + " ";
|
||||||
|
|
||||||
if (filter.operator === 'exists') {
|
if (filter.operator === 'exists') {
|
||||||
where += `attr${i}.attributeId IS NOT NULL`;
|
where += `attr${i}.labelId IS NOT NULL`;
|
||||||
}
|
}
|
||||||
else if (filter.operator === 'not-exists') {
|
else if (filter.operator === 'not-exists') {
|
||||||
where += `attr${i}.attributeId IS NULL`;
|
where += `attr${i}.labelId IS NULL`;
|
||||||
}
|
}
|
||||||
else if (filter.operator === '=' || filter.operator === '!=') {
|
else if (filter.operator === '=' || filter.operator === '!=') {
|
||||||
where += `attr${i}.value ${filter.operator} ?`;
|
where += `attr${i}.value ${filter.operator} ?`;
|
||||||
|
@ -233,7 +233,7 @@ async function runAllChecks() {
|
|||||||
await runSyncRowChecks("recent_notes", "branchId", errorList);
|
await runSyncRowChecks("recent_notes", "branchId", errorList);
|
||||||
await runSyncRowChecks("images", "imageId", errorList);
|
await runSyncRowChecks("images", "imageId", errorList);
|
||||||
await runSyncRowChecks("note_images", "noteImageId", errorList);
|
await runSyncRowChecks("note_images", "noteImageId", errorList);
|
||||||
await runSyncRowChecks("attributes", "attributeId", errorList);
|
await runSyncRowChecks("labels", "labelId", errorList);
|
||||||
await runSyncRowChecks("api_tokens", "apiTokenId", errorList);
|
await runSyncRowChecks("api_tokens", "apiTokenId", errorList);
|
||||||
|
|
||||||
if (errorList.length === 0) {
|
if (errorList.length === 0) {
|
||||||
|
@ -83,16 +83,16 @@ async function getHashes() {
|
|||||||
FROM images
|
FROM images
|
||||||
ORDER BY imageId`)),
|
ORDER BY imageId`)),
|
||||||
|
|
||||||
attributes: getHash(await sql.getRows(`
|
labels: getHash(await sql.getRows(`
|
||||||
SELECT
|
SELECT
|
||||||
attributeId,
|
labelId,
|
||||||
noteId
|
noteId
|
||||||
name,
|
name,
|
||||||
value
|
value
|
||||||
dateModified,
|
dateModified,
|
||||||
dateCreated
|
dateCreated
|
||||||
FROM attributes
|
FROM labels
|
||||||
ORDER BY attributeId`))
|
ORDER BY labelId`))
|
||||||
};
|
};
|
||||||
|
|
||||||
const elapseTimeMs = new Date().getTime() - startTime.getTime();
|
const elapseTimeMs = new Date().getTime() - startTime.getTime();
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
const sql = require('./sql');
|
const sql = require('./sql');
|
||||||
const notes = require('./notes');
|
const notes = require('./notes');
|
||||||
const attributes = require('./attributes');
|
const labels = require('./labels');
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
|
|
||||||
const CALENDAR_ROOT_ATTRIBUTE = 'calendar_root';
|
const CALENDAR_ROOT_LABEL = 'calendar_root';
|
||||||
const YEAR_ATTRIBUTE = 'year_note';
|
const YEAR_LABEL = 'year_note';
|
||||||
const MONTH_ATTRIBUTE = 'month_note';
|
const MONTH_LABEL = 'month_note';
|
||||||
const DATE_ATTRIBUTE = 'date_note';
|
const DATE_LABEL = 'date_note';
|
||||||
|
|
||||||
const DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
|
const DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
|
||||||
const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
|
const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
|
||||||
@ -30,8 +30,8 @@ async function getNoteStartingWith(parentNoteId, startsWith) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getRootCalendarNoteId() {
|
async function getRootCalendarNoteId() {
|
||||||
let rootNoteId = await sql.getValue(`SELECT notes.noteId FROM notes JOIN attributes USING(noteId)
|
let rootNoteId = await sql.getValue(`SELECT notes.noteId FROM notes JOIN labels USING(noteId)
|
||||||
WHERE attributes.name = '${CALENDAR_ROOT_ATTRIBUTE}' AND notes.isDeleted = 0`);
|
WHERE labels.name = '${CALENDAR_ROOT_LABEL}' AND notes.isDeleted = 0`);
|
||||||
|
|
||||||
if (!rootNoteId) {
|
if (!rootNoteId) {
|
||||||
rootNoteId = (await notes.createNewNote('root', {
|
rootNoteId = (await notes.createNewNote('root', {
|
||||||
@ -40,7 +40,7 @@ async function getRootCalendarNoteId() {
|
|||||||
isProtected: false
|
isProtected: false
|
||||||
})).noteId;
|
})).noteId;
|
||||||
|
|
||||||
await attributes.createAttribute(rootNoteId, CALENDAR_ROOT_ATTRIBUTE);
|
await labels.createLabel(rootNoteId, CALENDAR_ROOT_LABEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
return rootNoteId;
|
return rootNoteId;
|
||||||
@ -49,7 +49,7 @@ async function getRootCalendarNoteId() {
|
|||||||
async function getYearNoteId(dateTimeStr, rootNoteId) {
|
async function getYearNoteId(dateTimeStr, rootNoteId) {
|
||||||
const yearStr = dateTimeStr.substr(0, 4);
|
const yearStr = dateTimeStr.substr(0, 4);
|
||||||
|
|
||||||
let yearNoteId = await attributes.getNoteIdWithAttribute(YEAR_ATTRIBUTE, yearStr);
|
let yearNoteId = await labels.getNoteIdWithLabel(YEAR_LABEL, yearStr);
|
||||||
|
|
||||||
if (!yearNoteId) {
|
if (!yearNoteId) {
|
||||||
yearNoteId = await getNoteStartingWith(rootNoteId, yearStr);
|
yearNoteId = await getNoteStartingWith(rootNoteId, yearStr);
|
||||||
@ -58,7 +58,7 @@ async function getYearNoteId(dateTimeStr, rootNoteId) {
|
|||||||
yearNoteId = await createNote(rootNoteId, yearStr);
|
yearNoteId = await createNote(rootNoteId, yearStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
await attributes.createAttribute(yearNoteId, YEAR_ATTRIBUTE, yearStr);
|
await labels.createLabel(yearNoteId, YEAR_LABEL, yearStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
return yearNoteId;
|
return yearNoteId;
|
||||||
@ -68,7 +68,7 @@ async function getMonthNoteId(dateTimeStr, rootNoteId) {
|
|||||||
const monthStr = dateTimeStr.substr(0, 7);
|
const monthStr = dateTimeStr.substr(0, 7);
|
||||||
const monthNumber = dateTimeStr.substr(5, 2);
|
const monthNumber = dateTimeStr.substr(5, 2);
|
||||||
|
|
||||||
let monthNoteId = await attributes.getNoteIdWithAttribute(MONTH_ATTRIBUTE, monthStr);
|
let monthNoteId = await labels.getNoteIdWithLabel(MONTH_LABEL, monthStr);
|
||||||
|
|
||||||
if (!monthNoteId) {
|
if (!monthNoteId) {
|
||||||
const yearNoteId = await getYearNoteId(dateTimeStr, rootNoteId);
|
const yearNoteId = await getYearNoteId(dateTimeStr, rootNoteId);
|
||||||
@ -83,7 +83,7 @@ async function getMonthNoteId(dateTimeStr, rootNoteId) {
|
|||||||
monthNoteId = await createNote(yearNoteId, noteTitle);
|
monthNoteId = await createNote(yearNoteId, noteTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
await attributes.createAttribute(monthNoteId, MONTH_ATTRIBUTE, monthStr);
|
await labels.createLabel(monthNoteId, MONTH_LABEL, monthStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
return monthNoteId;
|
return monthNoteId;
|
||||||
@ -97,7 +97,7 @@ async function getDateNoteId(dateTimeStr, rootNoteId = null) {
|
|||||||
const dateStr = dateTimeStr.substr(0, 10);
|
const dateStr = dateTimeStr.substr(0, 10);
|
||||||
const dayNumber = dateTimeStr.substr(8, 2);
|
const dayNumber = dateTimeStr.substr(8, 2);
|
||||||
|
|
||||||
let dateNoteId = await attributes.getNoteIdWithAttribute(DATE_ATTRIBUTE, dateStr);
|
let dateNoteId = await labels.getNoteIdWithLabel(DATE_LABEL, dateStr);
|
||||||
|
|
||||||
if (!dateNoteId) {
|
if (!dateNoteId) {
|
||||||
const monthNoteId = await getMonthNoteId(dateTimeStr, rootNoteId);
|
const monthNoteId = await getMonthNoteId(dateTimeStr, rootNoteId);
|
||||||
@ -112,7 +112,7 @@ async function getDateNoteId(dateTimeStr, rootNoteId = null) {
|
|||||||
dateNoteId = await createNote(monthNoteId, noteTitle);
|
dateNoteId = await createNote(monthNoteId, noteTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
await attributes.createAttribute(dateNoteId, DATE_ATTRIBUTE, dateStr);
|
await labels.createLabel(dateNoteId, DATE_LABEL, dateStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
return dateNoteId;
|
return dateNoteId;
|
||||||
|
89
src/services/labels.js
Normal file
89
src/services/labels.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const sql = require('./sql');
|
||||||
|
const utils = require('./utils');
|
||||||
|
const sync_table = require('./sync_table');
|
||||||
|
|
||||||
|
const BUILTIN_LABELS = [
|
||||||
|
'frontend_startup',
|
||||||
|
'backend_startup',
|
||||||
|
'disable_versioning',
|
||||||
|
'calendar_root',
|
||||||
|
'hide_in_autocomplete',
|
||||||
|
'exclude_from_export',
|
||||||
|
'run',
|
||||||
|
'manual_transaction_handling',
|
||||||
|
'disable_inclusion',
|
||||||
|
'app_css'
|
||||||
|
];
|
||||||
|
|
||||||
|
async function getNoteLabelMap(noteId) {
|
||||||
|
return await sql.getMap(`SELECT name, value FROM labels WHERE noteId = ? AND isDeleted = 0`, [noteId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getNoteIdWithLabel(name, value) {
|
||||||
|
return await sql.getValue(`SELECT notes.noteId FROM notes JOIN labels USING(noteId)
|
||||||
|
WHERE notes.isDeleted = 0
|
||||||
|
AND labels.isDeleted = 0
|
||||||
|
AND labels.name = ?
|
||||||
|
AND labels.value = ?`, [name, value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getNotesWithLabel(repository, name, value) {
|
||||||
|
let notes;
|
||||||
|
|
||||||
|
if (value !== undefined) {
|
||||||
|
notes = await repository.getEntities(`SELECT notes.* FROM notes JOIN labels USING(noteId)
|
||||||
|
WHERE notes.isDeleted = 0 AND labels.isDeleted = 0 AND labels.name = ? AND labels.value = ?`, [name, value]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
notes = await repository.getEntities(`SELECT notes.* FROM notes JOIN labels USING(noteId)
|
||||||
|
WHERE notes.isDeleted = 0 AND labels.isDeleted = 0 AND labels.name = ?`, [name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getNoteWithLabel(repository, name, value) {
|
||||||
|
const notes = getNotesWithLabel(repository, name, value);
|
||||||
|
|
||||||
|
return notes.length > 0 ? notes[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getNoteIdsWithLabel(name) {
|
||||||
|
return await sql.getColumn(`SELECT DISTINCT notes.noteId FROM notes JOIN labels USING(noteId)
|
||||||
|
WHERE notes.isDeleted = 0 AND labels.isDeleted = 0 AND labels.name = ? AND labels.isDeleted = 0`, [name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createLabel(noteId, name, value = "", sourceId = null) {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = utils.nowDate();
|
||||||
|
const labelId = utils.newLabelId();
|
||||||
|
const position = 1 + await sql.getValue(`SELECT COALESCE(MAX(position), 0) FROM labels WHERE noteId = ?`, [noteId]);
|
||||||
|
|
||||||
|
await sql.insert("labels", {
|
||||||
|
labelId: labelId,
|
||||||
|
noteId: noteId,
|
||||||
|
name: name,
|
||||||
|
value: value,
|
||||||
|
position: position,
|
||||||
|
dateModified: now,
|
||||||
|
dateCreated: now,
|
||||||
|
isDeleted: false
|
||||||
|
});
|
||||||
|
|
||||||
|
await sync_table.addLabelSync(labelId, sourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getNoteLabelMap,
|
||||||
|
getNoteIdWithLabel,
|
||||||
|
getNotesWithLabel,
|
||||||
|
getNoteWithLabel,
|
||||||
|
getNoteIdsWithLabel,
|
||||||
|
createLabel,
|
||||||
|
BUILTIN_LABELS
|
||||||
|
};
|
@ -2,7 +2,7 @@ const sql = require('./sql');
|
|||||||
const options = require('./options');
|
const options = require('./options');
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
const sync_table = require('./sync_table');
|
const sync_table = require('./sync_table');
|
||||||
const attributes = require('./attributes');
|
const labels = require('./labels');
|
||||||
const protected_session = require('./protected_session');
|
const protected_session = require('./protected_session');
|
||||||
|
|
||||||
async function createNewNote(parentNoteId, noteOpts, dataKey, sourceId) {
|
async function createNewNote(parentNoteId, noteOpts, dataKey, sourceId) {
|
||||||
@ -108,9 +108,9 @@ async function createNote(parentNoteId, title, content = "", extraOptions = {})
|
|||||||
|
|
||||||
const {noteId} = await createNewNote(parentNoteId, note, extraOptions.dataKey, extraOptions.sourceId);
|
const {noteId} = await createNewNote(parentNoteId, note, extraOptions.dataKey, extraOptions.sourceId);
|
||||||
|
|
||||||
if (extraOptions.attributes) {
|
if (extraOptions.labels) {
|
||||||
for (const attrName in extraOptions.attributes) {
|
for (const attrName in extraOptions.labels) {
|
||||||
await attributes.createAttribute(noteId, attrName, extraOptions.attributes[attrName]);
|
await labels.createLabel(noteId, attrName, extraOptions.labels[attrName]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,7 +274,7 @@ async function updateNote(noteId, newNote, dataKey, sourceId) {
|
|||||||
await protected_session.encryptNote(dataKey, newNote.detail);
|
await protected_session.encryptNote(dataKey, newNote.detail);
|
||||||
}
|
}
|
||||||
|
|
||||||
const attributesMap = await attributes.getNoteAttributeMap(noteId);
|
const labelsMap = await labels.getNoteLabelMap(noteId);
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const nowStr = utils.nowDate();
|
const nowStr = utils.nowDate();
|
||||||
@ -289,7 +289,7 @@ async function updateNote(noteId, newNote, dataKey, sourceId) {
|
|||||||
await sql.doInTransaction(async () => {
|
await sql.doInTransaction(async () => {
|
||||||
const msSinceDateCreated = now.getTime() - utils.parseDateTime(newNote.detail.dateCreated).getTime();
|
const msSinceDateCreated = now.getTime() - utils.parseDateTime(newNote.detail.dateCreated).getTime();
|
||||||
|
|
||||||
if (attributesMap.disable_versioning !== 'true'
|
if (labelsMap.disable_versioning !== 'true'
|
||||||
&& !existingnoteRevisionId
|
&& !existingnoteRevisionId
|
||||||
&& msSinceDateCreated >= historySnapshotTimeInterval * 1000) {
|
&& msSinceDateCreated >= historySnapshotTimeInterval * 1000) {
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ module.exports = function(searchText) {
|
|||||||
value: match[7] !== undefined ? trimQuotes(match[7]) : null
|
value: match[7] !== undefined ? trimQuotes(match[7]) : null
|
||||||
});
|
});
|
||||||
|
|
||||||
// remove attributes from further fulltext search
|
// remove labels from further fulltext search
|
||||||
searchText = searchText.split(match[0]).join('');
|
searchText = searchText.split(match[0]).join('');
|
||||||
|
|
||||||
match = attrRegex.exec(searchText);
|
match = attrRegex.exec(searchText);
|
||||||
|
@ -3,7 +3,7 @@ const protected_session = require('./protected_session');
|
|||||||
const Note = require('../entities/note');
|
const Note = require('../entities/note');
|
||||||
const NoteRevision = require('../entities/note_revision');
|
const NoteRevision = require('../entities/note_revision');
|
||||||
const Branch = require('../entities/branch');
|
const Branch = require('../entities/branch');
|
||||||
const Attribute = require('../entities/attribute');
|
const Label = require('../entities/label');
|
||||||
const sync_table = require('../services/sync_table');
|
const sync_table = require('../services/sync_table');
|
||||||
|
|
||||||
class Repository {
|
class Repository {
|
||||||
@ -40,8 +40,8 @@ class Repository {
|
|||||||
createEntityFromRow(row) {
|
createEntityFromRow(row) {
|
||||||
let entity;
|
let entity;
|
||||||
|
|
||||||
if (row.attributeId) {
|
if (row.labelId) {
|
||||||
entity = new Attribute(this, row);
|
entity = new Label(this, row);
|
||||||
}
|
}
|
||||||
else if (row.noteRevisionId) {
|
else if (row.noteRevisionId) {
|
||||||
entity = new NoteRevision(this, row);
|
entity = new NoteRevision(this, row);
|
||||||
|
@ -3,14 +3,14 @@ const Repository = require('./repository');
|
|||||||
|
|
||||||
const repo = new Repository();
|
const repo = new Repository();
|
||||||
|
|
||||||
async function runNotesWithAttribute(runAttrValue) {
|
async function runNotesWithLabel(runAttrValue) {
|
||||||
const notes = await repo.getEntities(`
|
const notes = await repo.getEntities(`
|
||||||
SELECT notes.*
|
SELECT notes.*
|
||||||
FROM notes
|
FROM notes
|
||||||
JOIN attributes ON attributes.noteId = notes.noteId
|
JOIN labels ON labels.noteId = notes.noteId
|
||||||
AND attributes.isDeleted = 0
|
AND labels.isDeleted = 0
|
||||||
AND attributes.name = 'run'
|
AND labels.name = 'run'
|
||||||
AND attributes.value = ?
|
AND labels.value = ?
|
||||||
WHERE
|
WHERE
|
||||||
notes.type = 'code'
|
notes.type = 'code'
|
||||||
AND notes.isDeleted = 0`, [runAttrValue]);
|
AND notes.isDeleted = 0`, [runAttrValue]);
|
||||||
@ -20,8 +20,8 @@ async function runNotesWithAttribute(runAttrValue) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => runNotesWithAttribute('backend_startup'), 10 * 1000);
|
setTimeout(() => runNotesWithLabel('backend_startup'), 10 * 1000);
|
||||||
|
|
||||||
setInterval(() => runNotesWithAttribute('hourly'), 3600 * 1000);
|
setInterval(() => runNotesWithLabel('hourly'), 3600 * 1000);
|
||||||
|
|
||||||
setInterval(() => runNotesWithAttribute('daily'), 24 * 3600 * 1000);
|
setInterval(() => runNotesWithLabel('daily'), 24 * 3600 * 1000);
|
@ -23,7 +23,7 @@ async function executeBundle(dataKey, bundle, startNote) {
|
|||||||
|
|
||||||
const ctx = new ScriptContext(dataKey, startNote, bundle.allNotes);
|
const ctx = new ScriptContext(dataKey, startNote, bundle.allNotes);
|
||||||
|
|
||||||
if (await bundle.note.hasAttribute('manual_transaction_handling')) {
|
if (await bundle.note.hasLabel('manual_transaction_handling')) {
|
||||||
return await execute(ctx, script, '');
|
return await execute(ctx, script, '');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -73,7 +73,7 @@ async function getScriptBundle(note, root = true, scriptEnv = null, includedNote
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!root && await note.hasAttribute('disable_inclusion')) {
|
if (!root && await note.hasLabel('disable_inclusion')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ const protected_session = require('./protected_session');
|
|||||||
const notes = require('./notes');
|
const notes = require('./notes');
|
||||||
const sql = require('./sql');
|
const sql = require('./sql');
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
const attributes = require('./attributes');
|
const labels = require('./labels');
|
||||||
const date_notes = require('./date_notes');
|
const date_notes = require('./date_notes');
|
||||||
const config = require('./config');
|
const config = require('./config');
|
||||||
const Repository = require('./repository');
|
const Repository = require('./repository');
|
||||||
@ -48,12 +48,12 @@ function ScriptApi(dataKey, startNote, currentNote) {
|
|||||||
return repository.getNote(noteId);
|
return repository.getNote(noteId);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getNotesWithAttribute = async function (attrName, attrValue) {
|
this.getNotesWithLabel = async function (attrName, attrValue) {
|
||||||
return await attributes.getNotesWithAttribute(repository, attrName, attrValue);
|
return await labels.getNotesWithLabel(repository, attrName, attrValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getNoteWithAttribute = async function (attrName, attrValue) {
|
this.getNoteWithLabel = async function (attrName, attrValue) {
|
||||||
const notes = await this.getNotesWithAttribute(attrName, attrValue);
|
const notes = await this.getNotesWithLabel(attrName, attrValue);
|
||||||
|
|
||||||
return notes.length > 0 ? notes[0] : null;
|
return notes.length > 0 ? notes[0] : null;
|
||||||
};
|
};
|
||||||
@ -64,7 +64,7 @@ function ScriptApi(dataKey, startNote, currentNote) {
|
|||||||
return await notes.createNote(parentNoteId, title, content, extraOptions);
|
return await notes.createNote(parentNoteId, title, content, extraOptions);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.createAttribute = attributes.createAttribute;
|
this.createLabel = labels.createLabel;
|
||||||
|
|
||||||
this.updateEntity = repository.updateEntity;
|
this.updateEntity = repository.updateEntity;
|
||||||
|
|
||||||
|
@ -146,8 +146,8 @@ async function pullSync(syncContext) {
|
|||||||
else if (sync.entityName === 'note_images') {
|
else if (sync.entityName === 'note_images') {
|
||||||
await syncUpdate.updateNoteImage(resp, syncContext.sourceId);
|
await syncUpdate.updateNoteImage(resp, syncContext.sourceId);
|
||||||
}
|
}
|
||||||
else if (sync.entityName === 'attributes') {
|
else if (sync.entityName === 'labels') {
|
||||||
await syncUpdate.updateAttribute(resp, syncContext.sourceId);
|
await syncUpdate.updateLabel(resp, syncContext.sourceId);
|
||||||
}
|
}
|
||||||
else if (sync.entityName === 'api_tokens') {
|
else if (sync.entityName === 'api_tokens') {
|
||||||
await syncUpdate.updateApiToken(resp, syncContext.sourceId);
|
await syncUpdate.updateApiToken(resp, syncContext.sourceId);
|
||||||
@ -235,8 +235,8 @@ async function pushEntity(sync, syncContext) {
|
|||||||
else if (sync.entityName === 'note_images') {
|
else if (sync.entityName === 'note_images') {
|
||||||
entity = await sql.getRow('SELECT * FROM note_images WHERE noteImageId = ?', [sync.entityId]);
|
entity = await sql.getRow('SELECT * FROM note_images WHERE noteImageId = ?', [sync.entityId]);
|
||||||
}
|
}
|
||||||
else if (sync.entityName === 'attributes') {
|
else if (sync.entityName === 'labels') {
|
||||||
entity = await sql.getRow('SELECT * FROM attributes WHERE attributeId = ?', [sync.entityId]);
|
entity = await sql.getRow('SELECT * FROM labels WHERE labelId = ?', [sync.entityId]);
|
||||||
}
|
}
|
||||||
else if (sync.entityName === 'api_tokens') {
|
else if (sync.entityName === 'api_tokens') {
|
||||||
entity = await sql.getRow('SELECT * FROM api_tokens WHERE apiTokenId = ?', [sync.entityId]);
|
entity = await sql.getRow('SELECT * FROM api_tokens WHERE apiTokenId = ?', [sync.entityId]);
|
||||||
|
@ -36,8 +36,8 @@ async function addNoteImageSync(noteImageId, sourceId) {
|
|||||||
await addEntitySync("note_images", noteImageId, sourceId);
|
await addEntitySync("note_images", noteImageId, sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addAttributeSync(attributeId, sourceId) {
|
async function addLabelSync(labelId, sourceId) {
|
||||||
await addEntitySync("attributes", attributeId, sourceId);
|
await addEntitySync("labels", labelId, sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addApiTokenSync(apiTokenId, sourceId) {
|
async function addApiTokenSync(apiTokenId, sourceId) {
|
||||||
@ -96,7 +96,7 @@ async function fillAllSyncRows() {
|
|||||||
await fillSyncRows("recent_notes", "branchId");
|
await fillSyncRows("recent_notes", "branchId");
|
||||||
await fillSyncRows("images", "imageId");
|
await fillSyncRows("images", "imageId");
|
||||||
await fillSyncRows("note_images", "noteImageId");
|
await fillSyncRows("note_images", "noteImageId");
|
||||||
await fillSyncRows("attributes", "attributeId");
|
await fillSyncRows("labels", "labelId");
|
||||||
await fillSyncRows("api_tokens", "apiTokenId");
|
await fillSyncRows("api_tokens", "apiTokenId");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +109,7 @@ module.exports = {
|
|||||||
addRecentNoteSync,
|
addRecentNoteSync,
|
||||||
addImageSync,
|
addImageSync,
|
||||||
addNoteImageSync,
|
addNoteImageSync,
|
||||||
addAttributeSync,
|
addLabelSync,
|
||||||
addApiTokenSync,
|
addApiTokenSync,
|
||||||
addEntitySync,
|
addEntitySync,
|
||||||
cleanupSyncRowsForMissingEntities,
|
cleanupSyncRowsForMissingEntities,
|
||||||
|
@ -130,17 +130,17 @@ async function updateNoteImage(entity, sourceId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateAttribute(entity, sourceId) {
|
async function updateLabel(entity, sourceId) {
|
||||||
const origAttribute = await sql.getRow("SELECT * FROM attributes WHERE attributeId = ?", [entity.attributeId]);
|
const origLabel = await sql.getRow("SELECT * FROM labels WHERE labelId = ?", [entity.labelId]);
|
||||||
|
|
||||||
if (!origAttribute || origAttribute.dateModified <= entity.dateModified) {
|
if (!origLabel || origLabel.dateModified <= entity.dateModified) {
|
||||||
await sql.doInTransaction(async () => {
|
await sql.doInTransaction(async () => {
|
||||||
await sql.replace("attributes", entity);
|
await sql.replace("labels", entity);
|
||||||
|
|
||||||
await sync_table.addAttributeSync(entity.attributeId, sourceId);
|
await sync_table.addLabelSync(entity.labelId, sourceId);
|
||||||
});
|
});
|
||||||
|
|
||||||
log.info("Update/sync attribute " + entity.attributeId);
|
log.info("Update/sync label " + entity.labelId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,6 +167,6 @@ module.exports = {
|
|||||||
updateRecentNotes,
|
updateRecentNotes,
|
||||||
updateImage,
|
updateImage,
|
||||||
updateNoteImage,
|
updateNoteImage,
|
||||||
updateAttribute,
|
updateLabel,
|
||||||
updateApiToken
|
updateApiToken
|
||||||
};
|
};
|
@ -24,7 +24,7 @@ function newNoteImageId() {
|
|||||||
return randomString(12);
|
return randomString(12);
|
||||||
}
|
}
|
||||||
|
|
||||||
function newAttributeId() {
|
function newLabelId() {
|
||||||
return randomString(12);
|
return randomString(12);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,7 +159,7 @@ module.exports = {
|
|||||||
newNoteRevisionId,
|
newNoteRevisionId,
|
||||||
newImageId,
|
newImageId,
|
||||||
newNoteImageId,
|
newNoteImageId,
|
||||||
newAttributeId,
|
newLabelId,
|
||||||
newApiTokenId,
|
newApiTokenId,
|
||||||
toBase64,
|
toBase64,
|
||||||
fromBase64,
|
fromBase64,
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
|
|
||||||
<div id="search-box" class="hide-toggle" style="grid-area: search; display: none; padding: 10px; margin-top: 10px;">
|
<div id="search-box" class="hide-toggle" style="grid-area: search; display: none; padding: 10px; margin-top: 10px;">
|
||||||
<div style="display: flex; align-items: center;">
|
<div style="display: flex; align-items: center;">
|
||||||
<input name="search-text" placeholder="Search text, attributes" style="flex-grow: 100; margin-left: 5px; margin-right: 5px;" autocomplete="off">
|
<input name="search-text" placeholder="Search text, labels" style="flex-grow: 100; margin-left: 5px; margin-right: 5px;" autocomplete="off">
|
||||||
<button id="do-search-button" class="btn btn-primary btn-sm" title="Search">Search</button>
|
<button id="do-search-button" class="btn btn-primary btn-sm" title="Search">Search</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -124,7 +124,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dLabel">
|
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dLabel">
|
||||||
<li><a id="show-history-button"><kbd>Alt+H</kbd> History</a></li>
|
<li><a id="show-history-button"><kbd>Alt+H</kbd> History</a></li>
|
||||||
<li><a class="show-attributes-button"><kbd>Alt+A</kbd> Attributes</a></li>
|
<li><a class="show-labels-button"><kbd>Alt+A</kbd> Labels</a></li>
|
||||||
<li><a id="show-source-button"><kbd>Ctrl+U</kbd> HTML source</a></li>
|
<li><a id="show-source-button"><kbd>Ctrl+U</kbd> HTML source</a></li>
|
||||||
<li><a id="upload-attachment-button">Upload attachment</a></li>
|
<li><a id="upload-attachment-button">Upload attachment</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -147,29 +147,29 @@
|
|||||||
<p>
|
<p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<code>@abc</code> - matches notes with attribute abc</li>
|
<code>@abc</code> - matches notes with label abc</li>
|
||||||
<li>
|
<li>
|
||||||
<code>@!abc</code> - matches notes without abc attribute (maybe not the best syntax)</li>
|
<code>@!abc</code> - matches notes without abc label (maybe not the best syntax)</li>
|
||||||
<li>
|
<li>
|
||||||
<code>@abc=true</code> - matches notes with attribute abc having value true</li>
|
<code>@abc=true</code> - matches notes with label abc having value true</li>
|
||||||
<li><code>@abc!=true</code></li>
|
<li><code>@abc!=true</code></li>
|
||||||
<li>
|
<li>
|
||||||
<code>@"weird attribute"="weird value"</code> - works also with whitespace inside names values</li>
|
<code>@"weird label"="weird value"</code> - works also with whitespace inside names values</li>
|
||||||
<li>
|
<li>
|
||||||
<code>@abc and @def</code> - matches notes with both abc and def</li>
|
<code>@abc and @def</code> - matches notes with both abc and def</li>
|
||||||
<li>
|
<li>
|
||||||
<code>@abc @def</code> - AND relation is implicit when specifying multiple attributes</li>
|
<code>@abc @def</code> - AND relation is implicit when specifying multiple labels</li>
|
||||||
<li>
|
<li>
|
||||||
<code>@abc or @def</code> - OR relation</li>
|
<code>@abc or @def</code> - OR relation</li>
|
||||||
<li>
|
<li>
|
||||||
<code>@abc<=5</code> - numerical comparison (also >, >=, <).</li>
|
<code>@abc<=5</code> - numerical comparison (also >, >=, <).</li>
|
||||||
<li>
|
<li>
|
||||||
<code>some search string @abc @def</code> - combination of fulltext and attribute search - both of them need to match (OR not supported)</li>
|
<code>some search string @abc @def</code> - combination of fulltext and label search - both of them need to match (OR not supported)</li>
|
||||||
<li>
|
<li>
|
||||||
<code>@abc @def some search string</code> - same combination</li>
|
<code>@abc @def some search string</code> - same combination</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<a href="https://github.com/zadam/trilium/wiki/Attributes">Complete help on search syntax</a>
|
<a href="https://github.com/zadam/trilium/wiki/Labels">Complete help on search syntax</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -204,10 +204,10 @@
|
|||||||
<input type="file" id="attachment-upload" style="display: none" />
|
<input type="file" id="attachment-upload" style="display: none" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="attribute-list">
|
<div id="label-list">
|
||||||
<button class="btn btn-sm show-attributes-button">Attributes:</button>
|
<button class="btn btn-sm show-labels-button">Labels:</button>
|
||||||
|
|
||||||
<span id="attribute-list-inner"></span>
|
<span id="label-list-inner"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -437,14 +437,14 @@
|
|||||||
<textarea id="note-source" readonly="readonly"></textarea>
|
<textarea id="note-source" readonly="readonly"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="attributes-dialog" title="Note attributes" style="display: none; padding: 20px;">
|
<div id="labels-dialog" title="Note labels" style="display: none; padding: 20px;">
|
||||||
<form data-bind="submit: save">
|
<form data-bind="submit: save">
|
||||||
<div style="text-align: center">
|
<div style="text-align: center">
|
||||||
<button class="btn btn-large" style="width: 200px;" id="save-attributes-button" type="submit">Save changes <kbd>enter</kbd></button>
|
<button class="btn btn-large" style="width: 200px;" id="save-labels-button" type="submit">Save changes <kbd>enter</kbd></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="height: 97%; overflow: auto">
|
<div style="height: 97%; overflow: auto">
|
||||||
<table id="attributes-table" class="table">
|
<table id="labels-table" class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
@ -454,25 +454,25 @@
|
|||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody data-bind="foreach: attributes">
|
<tbody data-bind="foreach: labels">
|
||||||
<tr data-bind="if: isDeleted == 0">
|
<tr data-bind="if: isDeleted == 0">
|
||||||
<td class="handle">
|
<td class="handle">
|
||||||
<span class="glyphicon glyphicon-resize-vertical"></span>
|
<span class="glyphicon glyphicon-resize-vertical"></span>
|
||||||
<input type="hidden" name="position" data-bind="value: position"/>
|
<input type="hidden" name="position" data-bind="value: position"/>
|
||||||
</td>
|
</td>
|
||||||
<!-- ID column has specific width because if it's empty its size can be deformed when dragging -->
|
<!-- ID column has specific width because if it's empty its size can be deformed when dragging -->
|
||||||
<td data-bind="text: attributeId" style="width: 150px;"></td>
|
<td data-bind="text: labelId" style="width: 150px;"></td>
|
||||||
<td>
|
<td>
|
||||||
<!-- Change to valueUpdate: blur is necessary because jQuery UI autocomplete hijacks change event -->
|
<!-- Change to valueUpdate: blur is necessary because jQuery UI autocomplete hijacks change event -->
|
||||||
<input type="text" class="attribute-name" data-bind="value: name, valueUpdate: 'blur', event: { blur: $parent.attributeChanged }"/>
|
<input type="text" class="label-name" data-bind="value: name, valueUpdate: 'blur', event: { blur: $parent.labelChanged }"/>
|
||||||
<div style="color: yellowgreen" data-bind="if: $parent.isNotUnique($index())"><span class="glyphicon glyphicon-info-sign"></span> Duplicate attribute.</div>
|
<div style="color: yellowgreen" data-bind="if: $parent.isNotUnique($index())"><span class="glyphicon glyphicon-info-sign"></span> Duplicate label.</div>
|
||||||
<div style="color: red" data-bind="if: $parent.isEmptyName($index())">Attribute name can't be empty.</div>
|
<div style="color: red" data-bind="if: $parent.isEmptyName($index())">Label name can't be empty.</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" class="attribute-value" data-bind="value: value, valueUpdate: 'blur', event: { blur: $parent.attributeChanged }" style="width: 300px"/>
|
<input type="text" class="label-value" data-bind="value: value, valueUpdate: 'blur', event: { blur: $parent.labelChanged }" style="width: 300px"/>
|
||||||
</td>
|
</td>
|
||||||
<td title="Delete" style="padding: 13px;">
|
<td title="Delete" style="padding: 13px;">
|
||||||
<span class="glyphicon glyphicon-trash" data-bind="click: $parent.deleteAttribute"></span>
|
<span class="glyphicon glyphicon-trash" data-bind="click: $parent.deleteLabel"></span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -550,7 +550,7 @@
|
|||||||
<script src="/javascripts/dialogs/edit_tree_prefix.js"></script>
|
<script src="/javascripts/dialogs/edit_tree_prefix.js"></script>
|
||||||
<script src="/javascripts/dialogs/sql_console.js"></script>
|
<script src="/javascripts/dialogs/sql_console.js"></script>
|
||||||
<script src="/javascripts/dialogs/note_source.js"></script>
|
<script src="/javascripts/dialogs/note_source.js"></script>
|
||||||
<script src="/javascripts/dialogs/attributes.js"></script>
|
<script src="/javascripts/dialogs/labels.js"></script>
|
||||||
|
|
||||||
<script src="/javascripts/link.js"></script>
|
<script src="/javascripts/link.js"></script>
|
||||||
<script src="/javascripts/sync.js"></script>
|
<script src="/javascripts/sync.js"></script>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user