mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
basic saving of attributes in the widget
This commit is contained in:
parent
ad48b59893
commit
f245d51746
2
libraries/ckeditor/ckeditor.js
vendored
2
libraries/ckeditor/ckeditor.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -541,13 +541,14 @@ class Note extends Entity {
|
|||||||
/**
|
/**
|
||||||
* @return {Promise<Attribute>}
|
* @return {Promise<Attribute>}
|
||||||
*/
|
*/
|
||||||
async addAttribute(type, name, value = "", isInheritable = false) {
|
async addAttribute(type, name, value = "", isInheritable = false, position = 1000) {
|
||||||
const attr = new Attribute({
|
const attr = new Attribute({
|
||||||
noteId: this.noteId,
|
noteId: this.noteId,
|
||||||
type: type,
|
type: type,
|
||||||
name: name,
|
name: name,
|
||||||
value: value,
|
value: value,
|
||||||
isInheritable: isInheritable
|
isInheritable: isInheritable,
|
||||||
|
position: position
|
||||||
});
|
});
|
||||||
|
|
||||||
await attr.save();
|
await attr.save();
|
||||||
|
@ -1,9 +1,19 @@
|
|||||||
function preprocessRelations(str) {
|
function preprocess(str) {
|
||||||
return str.replace(/<a[^>]+href="(#root[A-Za-z0-9/]*)"[^>]*>[^<]*<\/a>/g, "$1");
|
if (str.startsWith('<p>')) {
|
||||||
|
str = str.substr(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str.endsWith('</p>')) {
|
||||||
|
str = str.substr(0, str.length - 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
str = str.replace(" ", " ");
|
||||||
|
|
||||||
|
return str.replace(/<a[^>]+href="(#[A-Za-z0-9/]*)"[^>]*>[^<]*<\/a>/g, "$1");
|
||||||
}
|
}
|
||||||
|
|
||||||
function lexer(str) {
|
function lexer(str) {
|
||||||
str = preprocessRelations(str);
|
str = preprocess(str);
|
||||||
|
|
||||||
const expressionTokens = [];
|
const expressionTokens = [];
|
||||||
|
|
||||||
@ -108,7 +118,8 @@ function parser(tokens) {
|
|||||||
if (token.startsWith('#')) {
|
if (token.startsWith('#')) {
|
||||||
const attr = {
|
const attr = {
|
||||||
type: 'label',
|
type: 'label',
|
||||||
name: token.substr(1)
|
name: token.substr(1),
|
||||||
|
isInheritable: false // FIXME
|
||||||
};
|
};
|
||||||
|
|
||||||
if (tokens[i + 1] === "=") {
|
if (tokens[i + 1] === "=") {
|
||||||
@ -124,18 +135,25 @@ function parser(tokens) {
|
|||||||
attrs.push(attr);
|
attrs.push(attr);
|
||||||
}
|
}
|
||||||
else if (token.startsWith('~')) {
|
else if (token.startsWith('~')) {
|
||||||
const attr = {
|
|
||||||
type: 'relation',
|
|
||||||
name: token.substr(1)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (i + 2 >= tokens.length || tokens[i + 1] !== '=') {
|
if (i + 2 >= tokens.length || tokens[i + 1] !== '=') {
|
||||||
throw new Error(`Relation "${token}" should point to a note.`);
|
throw new Error(`Relation "${token}" should point to a note.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
i += 2;
|
i += 2;
|
||||||
|
|
||||||
attr.value = tokens[i];
|
let notePath = tokens[i];
|
||||||
|
if (notePath.startsWith("#")) {
|
||||||
|
notePath = notePath.substr(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const noteId = notePath.split('/').pop();
|
||||||
|
|
||||||
|
const attr = {
|
||||||
|
type: 'relation',
|
||||||
|
name: token.substr(1),
|
||||||
|
isInheritable: false, // FIXME
|
||||||
|
value: noteId
|
||||||
|
};
|
||||||
|
|
||||||
attrs.push(attr);
|
attrs.push(attr);
|
||||||
}
|
}
|
||||||
@ -147,7 +165,14 @@ function parser(tokens) {
|
|||||||
return attrs;
|
return attrs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function lexAndParse(str) {
|
||||||
|
const tokens = lexer(str);
|
||||||
|
|
||||||
|
return parser(tokens);
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
lexer,
|
lexer,
|
||||||
parser
|
parser,
|
||||||
|
lexAndParse
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import server from "../services/server.js";
|
|||||||
import utils from "../services/utils.js";
|
import utils from "../services/utils.js";
|
||||||
import ws from "../services/ws.js";
|
import ws from "../services/ws.js";
|
||||||
import SpacedUpdate from "../services/spaced_update.js";
|
import SpacedUpdate from "../services/spaced_update.js";
|
||||||
import protectedSessionHolder from "../services/protected_session_holder.js";
|
import attributesParser from "../services/attribute_parser.js";
|
||||||
|
|
||||||
const mentionSetup = {
|
const mentionSetup = {
|
||||||
feeds: [
|
feeds: [
|
||||||
@ -15,8 +15,6 @@ const mentionSetup = {
|
|||||||
feed: queryText => {
|
feed: queryText => {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
noteAutocompleteService.autocompleteSource(queryText, rows => {
|
noteAutocompleteService.autocompleteSource(queryText, rows => {
|
||||||
console.log("rows", rows);
|
|
||||||
|
|
||||||
res(rows.map(row => {
|
res(rows.map(row => {
|
||||||
return {
|
return {
|
||||||
id: '@' + row.notePathTitle,
|
id: '@' + row.notePathTitle,
|
||||||
@ -51,7 +49,8 @@ const mentionSetup = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
minimumCharacters: 0
|
minimumCharacters: 0,
|
||||||
|
attributeMention: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
marker: '~',
|
marker: '~',
|
||||||
@ -65,7 +64,8 @@ const mentionSetup = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
minimumCharacters: 0
|
minimumCharacters: 0,
|
||||||
|
attributeMention: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@ -92,6 +92,17 @@ export default class NoteAttributesWidget extends TabAwareWidget {
|
|||||||
this.$editor = this.$widget.find('.note-attributes-editor');
|
this.$editor = this.$widget.find('.note-attributes-editor');
|
||||||
this.initialized = this.initEditor();
|
this.initialized = this.initEditor();
|
||||||
|
|
||||||
|
this.$editor.keypress(async e => {
|
||||||
|
const keycode = (e.keyCode ? e.keyCode : e.which);
|
||||||
|
if (keycode === 13) {
|
||||||
|
const attributes = attributesParser.lexAndParse(this.textEditor.getData());
|
||||||
|
|
||||||
|
await server.put(`notes/${this.noteId}/attributes2`, attributes, this.componentId);
|
||||||
|
|
||||||
|
console.log("Saved!");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return this.$widget;
|
return this.$widget;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -800,3 +800,7 @@ body {
|
|||||||
.hidden-int, .hidden-ext {
|
.hidden-int, .hidden-ext {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ck.ck-mentions > .ck-list__item {
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const sql = require('../../services/sql');
|
const sql = require('../../services/sql');
|
||||||
|
const log = require('../../services/log');
|
||||||
const attributeService = require('../../services/attributes');
|
const attributeService = require('../../services/attributes');
|
||||||
const repository = require('../../services/repository');
|
const repository = require('../../services/repository');
|
||||||
const Attribute = require('../../entities/attribute');
|
const Attribute = require('../../entities/attribute');
|
||||||
@ -82,6 +83,72 @@ async function deleteNoteAttribute(req) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function updateNoteAttributes2(req) {
|
||||||
|
const noteId = req.params.noteId;
|
||||||
|
const incomingAttributes = req.body;
|
||||||
|
|
||||||
|
const note = await repository.getNote(noteId);
|
||||||
|
|
||||||
|
let existingAttrs = await note.getAttributes();
|
||||||
|
|
||||||
|
let position = 0;
|
||||||
|
|
||||||
|
for (const incAttr of incomingAttributes) {
|
||||||
|
position += 10;
|
||||||
|
|
||||||
|
const perfectMatchAttr = existingAttrs.find(attr =>
|
||||||
|
attr.type === incAttr.type &&
|
||||||
|
attr.name === incAttr.name &&
|
||||||
|
attr.isInheritable === incAttr.isInheritable &&
|
||||||
|
attr.value === incAttr.value);
|
||||||
|
|
||||||
|
if (perfectMatchAttr) {
|
||||||
|
existingAttrs = existingAttrs.filter(attr => attr.attributeId !== perfectMatchAttr.attributeId);
|
||||||
|
|
||||||
|
if (perfectMatchAttr.position !== position) {
|
||||||
|
perfectMatchAttr.position = position;
|
||||||
|
await perfectMatchAttr.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
continue; // nothing to update
|
||||||
|
}
|
||||||
|
|
||||||
|
if (incAttr.type === 'relation') {
|
||||||
|
const targetNote = await repository.getNote(incAttr.value);
|
||||||
|
|
||||||
|
if (!targetNote || targetNote.isDeleted) {
|
||||||
|
log.error(`Target note of relation ${JSON.stringify(incAttr)} does not exist or is deleted`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchedAttr = existingAttrs.find(attr =>
|
||||||
|
attr.type === incAttr.type &&
|
||||||
|
attr.name === incAttr.name &&
|
||||||
|
attr.isInheritable === incAttr.isInheritable);
|
||||||
|
|
||||||
|
if (matchedAttr) {
|
||||||
|
matchedAttr.value = incAttr.value;
|
||||||
|
matchedAttr.position = position;
|
||||||
|
await matchedAttr.save();
|
||||||
|
|
||||||
|
existingAttrs = existingAttrs.filter(attr => attr.attributeId !== matchedAttr.attributeId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no existing attribute has been matched so we need to create a new one
|
||||||
|
// type, name and isInheritable are immutable so even if there is an attribute with matching type & name, we need to create a new one and delete the former one
|
||||||
|
|
||||||
|
await note.addAttribute(incAttr.type, incAttr.name, incAttr.value, incAttr.isInheritable, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
// all the remaining existing attributes are not defined anymore and should be deleted
|
||||||
|
for (const toDeleteAttr of existingAttrs) {
|
||||||
|
toDeleteAttr.isDeleted = true;
|
||||||
|
await toDeleteAttr.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function updateNoteAttributes(req) {
|
async function updateNoteAttributes(req) {
|
||||||
const noteId = req.params.noteId;
|
const noteId = req.params.noteId;
|
||||||
const attributes = req.body;
|
const attributes = req.body;
|
||||||
@ -193,6 +260,7 @@ async function deleteRelation(req) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
updateNoteAttributes,
|
updateNoteAttributes,
|
||||||
|
updateNoteAttributes2,
|
||||||
updateNoteAttribute,
|
updateNoteAttribute,
|
||||||
deleteNoteAttribute,
|
deleteNoteAttribute,
|
||||||
getAttributeNames,
|
getAttributeNames,
|
||||||
|
@ -167,6 +167,7 @@ function register(app) {
|
|||||||
|
|
||||||
apiRoute(GET, '/api/notes/:noteId/attributes', attributesRoute.getEffectiveNoteAttributes);
|
apiRoute(GET, '/api/notes/:noteId/attributes', attributesRoute.getEffectiveNoteAttributes);
|
||||||
apiRoute(PUT, '/api/notes/:noteId/attributes', attributesRoute.updateNoteAttributes);
|
apiRoute(PUT, '/api/notes/:noteId/attributes', attributesRoute.updateNoteAttributes);
|
||||||
|
apiRoute(PUT, '/api/notes/:noteId/attributes2', attributesRoute.updateNoteAttributes2);
|
||||||
apiRoute(PUT, '/api/notes/:noteId/attribute', attributesRoute.updateNoteAttribute);
|
apiRoute(PUT, '/api/notes/:noteId/attribute', attributesRoute.updateNoteAttribute);
|
||||||
apiRoute(PUT, '/api/notes/:noteId/relations/:name/to/:targetNoteId', attributesRoute.createRelation);
|
apiRoute(PUT, '/api/notes/:noteId/relations/:name/to/:targetNoteId', attributesRoute.createRelation);
|
||||||
apiRoute(DELETE, '/api/notes/:noteId/relations/:name/to/:targetNoteId', attributesRoute.deleteRelation);
|
apiRoute(DELETE, '/api/notes/:noteId/relations/:name/to/:targetNoteId', attributesRoute.deleteRelation);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user