diff --git a/src/public/app/widgets/note_attributes.js b/src/public/app/widgets/note_attributes.js index 328529bbd..5af12943f 100644 --- a/src/public/app/widgets/note_attributes.js +++ b/src/public/app/widgets/note_attributes.js @@ -7,6 +7,8 @@ import utils from "../services/utils.js"; import ws from "../services/ws.js"; import SpacedUpdate from "../services/spaced_update.js"; import attributesParser from "../services/attribute_parser.js"; +import linkService from "../services/link.js"; +import treeService from "../services/tree.js"; const mentionSetup = { feeds: [ @@ -72,10 +74,41 @@ const mentionSetup = { const TPL = `
+ + + +
`; +const DISPLAYED_NOTES = 10; + export default class NoteAttributesWidget extends TabAwareWidget { constructor() { super(); @@ -90,6 +123,10 @@ export default class NoteAttributesWidget extends TabAwareWidget { doRender() { this.$widget = $(TPL); this.$editor = this.$widget.find('.note-attributes-editor'); + this.$attrExtras = this.$widget.find('.attr-extras'); + 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.initialized = this.initEditor(); this.$editor.keypress(async e => { @@ -114,18 +151,85 @@ export default class NoteAttributesWidget extends TabAwareWidget { // display of $widget in both branches. this.$widget.show(); - this.$editor.on("click", () => { + this.$editor.on("click", async e => { const pos = this.textEditor.model.document.selection.getFirstPosition(); if (pos && pos.textNode && pos.textNode.data) { - const attr = pos.textNode.data; - const index = pos.offset - pos.textNode.startOffset; + const attrText = pos.textNode.data; + const clickIndex = pos.offset - pos.textNode.startOffset; - const attrs = attributesParser.lexAndParse(attr, true); + const parsedAttrs = attributesParser.lexAndParse(attrText, true); - console.log(attrs); + let matchedAttr = null; + let matchedPart = null; - console.log(attr.substr(0, index) + '|' + attr.substr(index)); + for (const attr of parsedAttrs) { + if (clickIndex >= attr.nameStartIndex && clickIndex <= attr.nameEndIndex) { + matchedAttr = attr; + matchedPart = 'name'; + break; + } + + if (clickIndex >= attr.valueStartIndex && clickIndex <= attr.valueEndIndex) { + matchedAttr = attr; + matchedPart = 'value'; + break; + } + } + + if (!matchedAttr) { + console.log(`Not found attribute for index ${clickIndex}, attr: ${JSON.stringify(parsedAttrs)}, text: ${attrText}`); + + return; + } + + let noteIds = await server.post('attributes/notes-with-attribute', { + type: matchedAttr.type, + name: matchedAttr.name, + value: matchedPart === 'value' ? matchedAttr.value : undefined + }); + + noteIds = noteIds.filter(noteId => noteId !== this.noteId); + + if (noteIds.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" : "")); + } + 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.$attrExtrasList.empty(); + + const displayedNoteIds = noteIds.length <= DISPLAYED_NOTES ? noteIds : noteIds.slice(0, DISPLAYED_NOTES); + const displayedNotes = await treeCache.getNotes(displayedNoteIds); +console.log(displayedNoteIds, displayedNotes); + for (const note of displayedNotes) { + const notePath = treeService.getSomeNotePath(note); + const $noteLink = await linkService.createNoteLink(notePath, {showNotePath: true}); + + this.$attrExtrasList.append( + $("
  • ").append($noteLink) + ); + } + + if (noteIds.length > DISPLAYED_NOTES) { + this.$attrExtrasMoreNotes.show().text(`... and ${noteIds.length - DISPLAYED_NOTES} more.`); + } + else { + this.$attrExtrasMoreNotes.hide(); + } + + this.$attrExtras.css("left", e.pageX - this.$attrExtras.width() / 2); + this.$attrExtras.css("top", e.pageY + 20); + this.$attrExtras.show(); } }); diff --git a/src/public/app/widgets/side_pane_toggles.js b/src/public/app/widgets/side_pane_toggles.js index df443ae13..43ba442f2 100644 --- a/src/public/app/widgets/side_pane_toggles.js +++ b/src/public/app/widgets/side_pane_toggles.js @@ -9,14 +9,14 @@ const TPL = ` position: fixed; bottom: 10px; right: 10px; - z-index: 1000; + z-index: 100; } .hide-left-pane-button, .show-left-pane-button { position: fixed; bottom: 10px; left: 10px; - z-index: 1000; + z-index: 100; } diff --git a/src/public/stylesheets/style.css b/src/public/stylesheets/style.css index 6c1dbed32..d1a4a20af 100644 --- a/src/public/stylesheets/style.css +++ b/src/public/stylesheets/style.css @@ -730,7 +730,7 @@ body { #context-menu-container, #context-menu-container .dropdown-menu { padding: 3px 0 0; - z-index: 1111; + z-index: 1000; } #context-menu-container .dropdown-item { diff --git a/src/routes/api/attributes.js b/src/routes/api/attributes.js index 4f6baa4b6..c5988d210 100644 --- a/src/routes/api/attributes.js +++ b/src/routes/api/attributes.js @@ -258,6 +258,14 @@ async function deleteRelation(req) { } } +async function getNotesWithAttribute(req) { + const {type, name, value} = req.body; + + const notes = await attributeService.getNotesWithAttribute(type, name, value); + + return notes.map(note => note.noteId); +} + module.exports = { updateNoteAttributes, updateNoteAttributes2, @@ -267,5 +275,6 @@ module.exports = { getValuesForAttribute, getEffectiveNoteAttributes, createRelation, - deleteRelation + deleteRelation, + getNotesWithAttribute }; diff --git a/src/routes/routes.js b/src/routes/routes.js index 7f202f1dc..3748b01e2 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -177,6 +177,7 @@ function register(app) { apiRoute(DELETE, '/api/notes/:noteId/attributes/:attributeId', attributesRoute.deleteNoteAttribute); apiRoute(GET, '/api/attributes/names', attributesRoute.getAttributeNames); apiRoute(GET, '/api/attributes/values/:attributeName', attributesRoute.getValuesForAttribute); + apiRoute(POST, '/api/attributes/notes-with-attribute', attributesRoute.getNotesWithAttribute); apiRoute(POST, '/api/notes/:noteId/link-map', linkMapRoute.getLinkMap); diff --git a/src/services/attributes.js b/src/services/attributes.js index 571f9e332..0f8fcd783 100644 --- a/src/services/attributes.js +++ b/src/services/attributes.js @@ -41,6 +41,27 @@ const BUILTIN_ATTRIBUTES = [ { type: 'relation', name: 'renderNote', isDangerous: true } ]; +async function getNotesWithAttribute(type, name, value) { + let valueCondition = ""; + let params = [type, name]; + + if (value !== undefined) { + valueCondition = " AND attributes.value = ?"; + params.push(value); + } + + return await repository.getEntities(` + SELECT notes.* + FROM notes + JOIN attributes USING (noteId) + WHERE notes.isDeleted = 0 + AND attributes.isDeleted = 0 + AND attributes.type = ? + AND attributes.name = ? + ${valueCondition} + ORDER BY position`, params); +} + async function getNotesWithLabel(name, value) { let valueCondition = ""; let params = [name]; @@ -134,6 +155,7 @@ function getBuiltinAttributeNames() { } module.exports = { + getNotesWithAttribute, getNotesWithLabel, getNotesWithLabels, getNoteWithLabel,