basic saving of attributes in the widget

This commit is contained in:
zadam 2020-06-05 17:25:14 +02:00
parent ad48b59893
commit f245d51746
8 changed files with 130 additions and 20 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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();

View File

@ -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("&nbsp;", " ");
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
} }

View File

@ -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;
} }

View File

@ -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;
}

View File

@ -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,

View File

@ -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);