diff --git a/src/public/app/services/attribute_parser.js b/src/public/app/services/attribute_parser.js index b1ef50d38..7507aec89 100644 --- a/src/public/app/services/attribute_parser.js +++ b/src/public/app/services/attribute_parser.js @@ -129,8 +129,8 @@ function parser(tokens, str, allowEmptyRelations = false) { type: 'label', name: text.substr(1), isInheritable: false, // FIXME - nameStartIndex: startIndex, - nameEndIndex: endIndex + startIndex: startIndex, + endIndex: endIndex }; if (i + 1 < tokens.length && tokens[i + 1].text === "=") { @@ -141,8 +141,7 @@ function parser(tokens, str, allowEmptyRelations = false) { i += 2; attr.value = tokens[i].text; - attr.valueStartIndex = tokens[i].startIndex; - attr.valueEndIndex = tokens[i].endIndex; + attr.endIndex = tokens[i].endIndex; } attrs.push(attr); @@ -152,8 +151,8 @@ function parser(tokens, str, allowEmptyRelations = false) { type: 'relation', name: text.substr(1), isInheritable: false, // FIXME - nameStartIndex: startIndex, - nameEndIndex: endIndex + startIndex: startIndex, + endIndex: endIndex }; attrs.push(attr); @@ -177,8 +176,7 @@ function parser(tokens, str, allowEmptyRelations = false) { const noteId = notePath.split('/').pop(); attr.value = noteId; - attr.valueStartIndex = tokens[i].startIndex; - attr.valueEndIndex = tokens[i].endIndex; + attr.endIndex = tokens[i].endIndex; } else { throw new Error(`Unrecognized attribute "${text}" in ${context(i)}`); diff --git a/src/public/app/widgets/note_attributes.js b/src/public/app/widgets/note_attributes.js index 851326f14..a23245b30 100644 --- a/src/public/app/widgets/note_attributes.js +++ b/src/public/app/widgets/note_attributes.js @@ -83,7 +83,7 @@ const TPL = ` border: 0 !important; outline: 0 !important; box-shadow: none !important; - padding: 0 !important; + padding: 0 0 0 5px !important; margin: 0 !important; color: var(--muted-text-color); max-height: 200px; @@ -142,15 +142,15 @@ const TPL = ` - + - + - +
Name:
Value:
Inheritable:
@@ -170,14 +170,7 @@ const TPL = `
-
Other notes with this label
- -
- - -
- -
+
Other notes with this label
    @@ -275,6 +268,10 @@ export default class NoteAttributesWidget extends TabAwareWidget { this.$attrExtrasTitle = this.$widget.find('.attr-extras-title'); this.$attrExtrasList = this.$widget.find('.attr-extras-list'); this.$attrExtrasMoreNotes = this.$widget.find('.attr-extras-more-notes'); + this.$attrEditName = this.$attrExtras.find('.attr-edit-name'); + this.$attrEditValue = this.$attrExtras.find('.attr-edit-value'); + this.$attrEditInheritable = this.$attrExtras.find('.attr-edit-inheritable'); + this.initialized = this.initEditor(); this.$attrDisplay = this.$widget.find('.attr-display'); @@ -319,11 +316,11 @@ export default class NoteAttributesWidget extends TabAwareWidget { this.$attrExtras.hide(); }); - // this.$editor.on('blur', () => { - // this.save(); - // - // this.$attrExtras.hide(); - // }); + this.$editor.on('blur', () => { + this.save(); + + this.$attrExtras.hide(); + }); return this.$widget; } @@ -375,18 +372,10 @@ export default class NoteAttributesWidget extends TabAwareWidget { const parsedAttrs = attributesParser.lexAndParse(attrText, true); let matchedAttr = null; - let matchedPart = null; for (const attr of parsedAttrs) { - if (clickIndex >= attr.nameStartIndex && clickIndex <= attr.nameEndIndex) { + if (clickIndex >= attr.startIndex && clickIndex <= attr.endIndex) { matchedAttr = attr; - matchedPart = 'name'; - break; - } - - if (clickIndex >= attr.valueStartIndex && clickIndex <= attr.valueEndIndex) { - matchedAttr = attr; - matchedPart = 'value'; break; } } @@ -397,13 +386,7 @@ export default class NoteAttributesWidget extends TabAwareWidget { return; } - const searchString = this.formatAttrForSearch(matchedAttr); - - let {count, results} = await server.get('search/' + encodeURIComponent(searchString), { - type: matchedAttr.type, - name: matchedAttr.name, - value: matchedPart === 'value' ? matchedAttr.value : undefined - }); + let {results, count} = await server.post('search-related', matchedAttr); for (const res of results) { res.noteId = res.notePathArray[res.notePathArray.length - 1]; @@ -412,22 +395,12 @@ export default class NoteAttributesWidget extends TabAwareWidget { results = results.filter(({noteId}) => noteId !== this.noteId); if (results.length === 0) { - this.$attrExtrasTitle.text( - `There are no other notes with ${matchedAttr.type} name "${matchedAttr.name}"` - // not displaying value since it can be long - + (matchedPart === 'value' ? " and matching value" : "")); + this.$attrExtrasTitle.hide(); } else { - this.$attrExtrasTitle.text( - `Notes with ${matchedAttr.type} name "${matchedAttr.name}"` - // not displaying value since it can be long - + (matchedPart === 'value' ? " and matching value" : "") - + ":" - ); + this.$attrExtrasTitle.text(`Other notes with ${matchedAttr.type} name "${matchedAttr.name}"`); } - this.$attrExtrasTitle.hide(); - this.$attrExtrasList.empty(); const displayedResults = results.length <= DISPLAYED_NOTES ? results : results.slice(0, DISPLAYED_NOTES); @@ -449,6 +422,9 @@ export default class NoteAttributesWidget extends TabAwareWidget { this.$attrExtrasMoreNotes.hide(); } + this.$attrEditName.val(matchedAttr.name); + this.$attrEditValue.val(matchedAttr.value); + this.$attrExtras.css("left", e.pageX - this.$attrExtras.width() / 2); this.$attrExtras.css("top", e.pageY + 30); this.$attrExtras.show(); @@ -510,6 +486,11 @@ export default class NoteAttributesWidget extends TabAwareWidget { this.textEditor.model.document.on('change:data', () => this.spacedUpdate.scheduleUpdate()); + // disable spellcheck for attribute editor + this.textEditor.editing.view.change( writer => { + writer.setAttribute( 'spellcheck', 'false', this.textEditor.editing.view.document.getRoot() ); + } ); + //await import(/* webpackIgnore: true */'../../libraries/ckeditor/inspector.js'); //CKEditorInspector.attach(this.textEditor); } @@ -616,29 +597,6 @@ export default class NoteAttributesWidget extends TabAwareWidget { } } - formatAttrForSearch(attr) { - let searchStr = ''; - - if (attr.type === 'label') { - searchStr += '#'; - } - else if (attr.type === 'relation') { - searchStr += '~'; - } - else { - throw new Error(`Unrecognized attribute type ${JSON.stringify(attr)}`); - } - - searchStr += attr.name; - - if (attr.value) { - searchStr += '='; - searchStr += this.formatValue(attr.value); - } - - return searchStr; - } - async focusOnAttributesEvent({tabId}) { if (this.tabContext.tabId === tabId) { this.$editor.trigger('focus'); diff --git a/src/routes/api/search.js b/src/routes/api/search.js index c5ed5a095..866f9678b 100644 --- a/src/routes/api/search.js +++ b/src/routes/api/search.js @@ -108,7 +108,75 @@ function searchFromRelation(note, relationName) { return typeof result[0] === 'string' ? result : result.map(item => item.noteId); } +function getRelatedNotes(req) { + const attr = req.body; + + const matchingNameAndValue = searchService.searchNotes(formatAttrForSearch(attr, true)); + const matchingName = searchService.searchNotes(formatAttrForSearch(attr, false)); + + const results = []; + + for (const record of matchingNameAndValue.concat(matchingName)) { + if (results.length >= 20) { + break; + } + + if (results.find(res => res.noteId === record.noteId)) { + continue; + } + + results.push(record); + } + + return { + count: matchingName.length, + results + }; +} + +function formatAttrForSearch(attr, searchWithValue) { + let searchStr = ''; + + if (attr.type === 'label') { + searchStr += '#'; + } + else if (attr.type === 'relation') { + searchStr += '~'; + } + else { + throw new Error(`Unrecognized attribute type ${JSON.stringify(attr)}`); + } + + searchStr += attr.name; + + if (searchWithValue && attr.value) { + searchStr += '='; + searchStr += formatValue(attr.value); + } + + return searchStr; +} + +function formatValue(val) { + if (!/[^\w_-]/.test(val)) { + return val; + } + else if (!val.includes('"')) { + return '"' + val + '"'; + } + else if (!val.includes("'")) { + return "'" + val + "'"; + } + else if (!val.includes("`")) { + return "`" + val + "`"; + } + else { + return '"' + val.replace(/"/g, '\\"') + '"'; + } +} + module.exports = { searchNotes, - searchFromNote + searchFromNote, + getRelatedNotes }; diff --git a/src/routes/routes.js b/src/routes/routes.js index 0cde69324..e8b34d505 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -256,6 +256,7 @@ function register(app) { apiRoute(GET, '/api/search/:searchString', searchRoute.searchNotes); apiRoute(GET, '/api/search-note/:noteId', searchRoute.searchFromNote); + apiRoute(POST, '/api/search-related', searchRoute.getRelatedNotes); route(POST, '/api/login/sync', [], loginApiRoute.loginSync, apiResultHandler); // this is for entering protected mode so user has to be already logged-in (that's the reason we don't require username)