1
1
Fork 0
mirror of https://github.com/zadam/trilium.git synced 2025-01-31 03:19:11 +08:00

attribute progress

This commit is contained in:
zadam 2020-06-25 23:56:06 +02:00
parent 92e49214c7
commit 1f05638609
4 changed files with 102 additions and 77 deletions
src

View file

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

View file

@ -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 = `
<table class="attr-edit">
<tr>
<th>Name:</th>
<td><input type="text" class="form-control form-control-sm" /></td>
<td><input type="text" class="attr-edit-name form-control form-control-sm" /></td>
</tr>
<tr>
<th>Value:</th>
<td><input type="text" class="form-control form-control-sm" /></td>
<td><input type="text" class="attr-edit-value form-control form-control-sm" /></td>
</tr>
<tr>
<th>Inheritable:</th>
<td><input type="checkbox" class="form-control form-control-sm" /></td>
<td><input type="checkbox" class="attr-edit-inheritable form-control form-control-sm" /></td>
</tr>
<tr>
<td colspan="2">
@ -170,14 +170,7 @@ const TPL = `
<br/>
<h5>Other notes with this label</h5>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="match-value-too">
<label class="form-check-label" for="match-value-too">match value too</label>
</div>
<div class="attr-extras-title"></div>
<h5 class="attr-extras-title">Other notes with this label</h5>
<ul class="attr-extras-list"></ul>
@ -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');

View file

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

View file

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