use standard search to find notes with same attrs

This commit is contained in:
zadam 2020-06-18 22:28:18 +02:00
parent a87e59cecb
commit f700e6e12b
7 changed files with 93 additions and 58 deletions

View File

@ -93,12 +93,18 @@ const TPL = `
max-height: 500px; max-height: 500px;
overflow: auto; overflow: auto;
} }
.attr-extras-list {
padding-left: 20px;
margin-top: 10px;
margin-bottom: 10px;
}
</style> </style>
<div class="attr-extras" style="display: none;"> <div class="attr-extras" style="display: none;">
<div class="attr-extras-title"></div> <div class="attr-extras-title"></div>
<div class="attr-extras-list"></div> <ul class="attr-extras-list"></ul>
<div class="attr-extras-more-notes"></div> <div class="attr-extras-more-notes"></div>
</div> </div>
@ -117,6 +123,8 @@ export default class NoteAttributesWidget extends TabAwareWidget {
const content = this.textEditor.getData(); const content = this.textEditor.getData();
this.parse(content); this.parse(content);
this.$attrExtras.hide();
}); });
} }
@ -129,16 +137,19 @@ export default class NoteAttributesWidget extends TabAwareWidget {
this.$attrExtrasMoreNotes = this.$widget.find('.attr-extras-more-notes'); this.$attrExtrasMoreNotes = this.$widget.find('.attr-extras-more-notes');
this.initialized = this.initEditor(); this.initialized = this.initEditor();
this.$editor.keypress(async e => { this.$editor.on('keydown', async e => {
const keycode = (e.keyCode ? e.keyCode : e.which); const keycode = (e.keyCode ? e.keyCode : e.which);
if (keycode === 13) { if (keycode === 13) {
const attributes = attributesParser.lexAndParse(this.textEditor.getData()); const attributes = attributesParser.lexAndParse(this.textEditor.getData());
await server.put(`notes/${this.noteId}/attributes2`, attributes, this.componentId); await server.put(`notes/${this.noteId}/attributes2`, attributes, this.componentId);
console.log("Saved!");
} }
})
this.$attrExtras.hide();
});
this.$editor.on('blur', () => this.$attrExtras.hide());
return this.$widget; return this.$widget;
} }
@ -178,20 +189,26 @@ export default class NoteAttributesWidget extends TabAwareWidget {
} }
if (!matchedAttr) { if (!matchedAttr) {
console.log(`Not found attribute for index ${clickIndex}, attr: ${JSON.stringify(parsedAttrs)}, text: ${attrText}`); this.$attrExtras.hide();
return; return;
} }
let noteIds = await server.post('attributes/notes-with-attribute', { const searchString = this.formatAttrForSearch(matchedAttr);
let {count, results} = await server.get('search/' + encodeURIComponent(searchString), {
type: matchedAttr.type, type: matchedAttr.type,
name: matchedAttr.name, name: matchedAttr.name,
value: matchedPart === 'value' ? matchedAttr.value : undefined value: matchedPart === 'value' ? matchedAttr.value : undefined
}); });
noteIds = noteIds.filter(noteId => noteId !== this.noteId); for (const res of results) {
res.noteId = res.notePathArray[res.notePathArray.length - 1];
}
if (noteIds.length === 0) { results = results.filter(({noteId}) => noteId !== this.noteId);
if (results.length === 0) {
this.$attrExtrasTitle.text( this.$attrExtrasTitle.text(
`There are no other notes with ${matchedAttr.type} name "${matchedAttr.name}"` `There are no other notes with ${matchedAttr.type} name "${matchedAttr.name}"`
// not displaying value since it can be long // not displaying value since it can be long
@ -208,9 +225,9 @@ export default class NoteAttributesWidget extends TabAwareWidget {
this.$attrExtrasList.empty(); this.$attrExtrasList.empty();
const displayedNoteIds = noteIds.length <= DISPLAYED_NOTES ? noteIds : noteIds.slice(0, DISPLAYED_NOTES); const displayedResults = results.length <= DISPLAYED_NOTES ? results : results.slice(0, DISPLAYED_NOTES);
const displayedNotes = await treeCache.getNotes(displayedNoteIds); const displayedNotes = await treeCache.getNotes(displayedResults.map(res => res.noteId));
console.log(displayedNoteIds, displayedNotes);
for (const note of displayedNotes) { for (const note of displayedNotes) {
const notePath = treeService.getSomeNotePath(note); const notePath = treeService.getSomeNotePath(note);
const $noteLink = await linkService.createNoteLink(notePath, {showNotePath: true}); const $noteLink = await linkService.createNoteLink(notePath, {showNotePath: true});
@ -220,15 +237,15 @@ console.log(displayedNoteIds, displayedNotes);
); );
} }
if (noteIds.length > DISPLAYED_NOTES) { if (results.length > DISPLAYED_NOTES) {
this.$attrExtrasMoreNotes.show().text(`... and ${noteIds.length - DISPLAYED_NOTES} more.`); this.$attrExtrasMoreNotes.show().text(`... and ${count - DISPLAYED_NOTES} more.`);
} }
else { else {
this.$attrExtrasMoreNotes.hide(); this.$attrExtrasMoreNotes.hide();
} }
this.$attrExtras.css("left", e.pageX - this.$attrExtras.width() / 2); this.$attrExtras.css("left", e.pageX - this.$attrExtras.width() / 2);
this.$attrExtras.css("top", e.pageY + 20); this.$attrExtras.css("top", e.pageY + 30);
this.$attrExtras.show(); this.$attrExtras.show();
} }
}); });
@ -315,7 +332,7 @@ console.log(displayedNoteIds, displayedNotes);
const $attributesContainer = $("<div>"); const $attributesContainer = $("<div>");
await this.renderAttributes(ownedAttributes, $attributesContainer); await this.renderAttributes(ownedAttributes, $attributesContainer);
console.log($attributesContainer.html());
this.textEditor.setData($attributesContainer.html()); this.textEditor.setData($attributesContainer.html());
} }
@ -330,14 +347,20 @@ console.log($attributesContainer.html());
async renderAttributes(attributes, $container) { async renderAttributes(attributes, $container) {
for (const attribute of attributes) { for (const attribute of attributes) {
if (attribute.type === 'label') { if (attribute.type === 'label') {
$container.append(utils.formatLabel(attribute) + " "); $container.append(document.createTextNode('#' + attribute.name));
if (attribute.value) {
$container.append('=');
$container.append(document.createTextNode(this.formatValue(attribute.value)));
$container.append(' ');
}
} else if (attribute.type === 'relation') { } else if (attribute.type === 'relation') {
if (attribute.isAutoLink) { if (attribute.isAutoLink) {
continue; continue;
} }
if (attribute.value) { if (attribute.value) {
$container.append('~' + attribute.name + "="); $container.append(document.createTextNode('~' + attribute.name + "="));
$container.append(this.createNoteLink(attribute.value)); $container.append(this.createNoteLink(attribute.value));
$container.append(" "); $container.append(" ");
} else { } else {
@ -349,6 +372,47 @@ console.log($attributesContainer.html());
} }
} }
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, '\\"') + '"';
}
}
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;
}
parse(content) { parse(content) {
if (content.startsWith('<p>')) { if (content.startsWith('<p>')) {
content = content.substr(3); content = content.substr(3);

View File

@ -46,7 +46,7 @@ export default class NoteTitleWidget extends TabAwareWidget {
this.$noteTitle.on('input', () => this.spacedUpdate.scheduleUpdate()); this.$noteTitle.on('input', () => this.spacedUpdate.scheduleUpdate());
utils.bindElShortcut(this.$noteTitle, 'return', () => { utils.bindElShortcut(this.$noteTitle, 'return', () => {
this.triggerCommand('focusOnDetail', {tabId: this.tabContext.tabId}); this.triggerCommand('focusOnAttributes', {tabId: this.tabContext.tabId});
}); });
return this.$widget; return this.$widget;

View File

@ -258,14 +258,6 @@ 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 = { module.exports = {
updateNoteAttributes, updateNoteAttributes,
updateNoteAttributes2, updateNoteAttributes2,
@ -275,6 +267,5 @@ module.exports = {
getValuesForAttribute, getValuesForAttribute,
getEffectiveNoteAttributes, getEffectiveNoteAttributes,
createRelation, createRelation,
deleteRelation, deleteRelation
getNotesWithAttribute
}; };

View File

@ -7,12 +7,13 @@ const scriptService = require('../../services/script');
const searchService = require('../../services/search/search'); const searchService = require('../../services/search/search');
async function searchNotes(req) { async function searchNotes(req) {
const notePaths = await searchService.searchNotes(req.params.searchString); const {count, results} = await searchService.searchNotes(req.params.searchString);
try { try {
return { return {
success: true, success: true,
results: notePaths count,
results
} }
} }
catch { catch {

View File

@ -177,7 +177,6 @@ function register(app) {
apiRoute(DELETE, '/api/notes/:noteId/attributes/:attributeId', attributesRoute.deleteNoteAttribute); apiRoute(DELETE, '/api/notes/:noteId/attributes/:attributeId', attributesRoute.deleteNoteAttribute);
apiRoute(GET, '/api/attributes/names', attributesRoute.getAttributeNames); apiRoute(GET, '/api/attributes/names', attributesRoute.getAttributeNames);
apiRoute(GET, '/api/attributes/values/:attributeName', attributesRoute.getValuesForAttribute); 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); apiRoute(POST, '/api/notes/:noteId/link-map', linkMapRoute.getLinkMap);

View File

@ -41,27 +41,6 @@ const BUILTIN_ATTRIBUTES = [
{ type: 'relation', name: 'renderNote', isDangerous: true } { 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) { async function getNotesWithLabel(name, value) {
let valueCondition = ""; let valueCondition = "";
let params = [name]; let params = [name];
@ -155,7 +134,6 @@ function getBuiltinAttributeNames() {
} }
module.exports = { module.exports = {
getNotesWithAttribute,
getNotesWithLabel, getNotesWithLabel,
getNotesWithLabels, getNotesWithLabels,
getNoteWithLabel, getNoteWithLabel,

View File

@ -87,11 +87,13 @@ async function searchNotes(query) {
fuzzyAttributeSearch: false fuzzyAttributeSearch: false
}); });
let searchResults = await findNotesWithQuery(query, parsingContext); const allSearchResults = await findNotesWithQuery(query, parsingContext);
const trimmedSearchResults = allSearchResults.slice(0, 200);
searchResults = searchResults.slice(0, 200); return {
count: allSearchResults.length,
return searchResults; results: trimmedSearchResults
};
} }
async function searchNotesForAutocomplete(query) { async function searchNotesForAutocomplete(query) {