mirror of
https://github.com/zadam/trilium.git
synced 2025-01-31 03:19:11 +08:00
attribute progress
This commit is contained in:
parent
92e49214c7
commit
1f05638609
4 changed files with 102 additions and 77 deletions
src
|
@ -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)}`);
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue