mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
use standard search to find notes with same attrs
This commit is contained in:
parent
a87e59cecb
commit
f700e6e12b
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
|
||||||
};
|
};
|
||||||
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user