chore(client/ts): port widgets/attribute_editor

This commit is contained in:
Elian Doran 2024-12-22 21:59:08 +02:00
parent b01725101d
commit a349223e54
No known key found for this signature in database
8 changed files with 207 additions and 72 deletions

View File

@ -19,7 +19,8 @@ import { ResolveOptions } from "../widgets/dialogs/delete_notes.js";
import { PromptDialogOptions } from "../widgets/dialogs/prompt.js"; import { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
import { ConfirmWithMessageOptions, ConfirmWithTitleOptions } from "../widgets/dialogs/confirm.js"; import { ConfirmWithMessageOptions, ConfirmWithTitleOptions } from "../widgets/dialogs/confirm.js";
import { Node } from "../services/tree.js"; import { Node } from "../services/tree.js";
import FAttribute from "../entities/fattribute.js"; import LoadResults from "../services/load_results.js";
import { Attribute } from "../services/attribute_parser.js";
interface Layout { interface Layout {
getRootWidget: (appContext: AppContext) => RootWidget; getRootWidget: (appContext: AppContext) => RootWidget;
@ -36,7 +37,7 @@ interface BeforeUploadListener extends Component {
/** /**
* Base interface for the data/arguments for a given command (see {@link CommandMappings}). * Base interface for the data/arguments for a given command (see {@link CommandMappings}).
*/ */
interface CommandData { export interface CommandData {
ntxId?: string; ntxId?: string;
} }
@ -144,8 +145,13 @@ export type CommandMappings = {
copyImageReferenceToClipboard: CommandData; copyImageReferenceToClipboard: CommandData;
copyImageToClipboard: CommandData; copyImageToClipboard: CommandData;
updateAttributesList: { updateAttributesList: {
attributes: FAttribute[]; attributes: Attribute[];
}; };
addNewLabel: CommandData;
addNewRelation: CommandData;
addNewLabelDefinition: CommandData;
addNewRelationDefinition: CommandData;
} }
type EventMappings = { type EventMappings = {
@ -162,8 +168,19 @@ type EventMappings = {
noteId: string; noteId: string;
messages: string[]; messages: string[];
}; };
entitiesReloaded: {
loadResults: LoadResults
};
addNewLabel: CommandData;
addNewRelation: CommandData;
} }
export type EventListener<T extends EventNames> = {
[key in T as `${key}Event`]: (data: EventData<T>) => void
}
export type EventData<T extends EventNames> = EventMappings[T];
type CommandAndEventMappings = (CommandMappings & EventMappings); type CommandAndEventMappings = (CommandMappings & EventMappings);
/** /**

View File

@ -7,13 +7,14 @@ interface Token {
endIndex: number; endIndex: number;
} }
interface Attribute { export interface Attribute {
type: AttributeType; type: AttributeType;
name: string; name: string;
isInheritable: boolean; isInheritable: boolean;
value?: string; value?: string;
startIndex: number; startIndex?: number;
endIndex: number; endIndex?: number;
noteId?: string;
} }
function lex(str: string) { function lex(str: string) {

View File

@ -1,6 +1,7 @@
import server from './server.js'; import server from './server.js';
import froca from './froca.js'; import froca from './froca.js';
import FNote from '../entities/fnote.js'; import FNote from '../entities/fnote.js';
import { AttributeRow } from './load_results.js';
async function addLabel(noteId: string, name: string, value: string = "") { async function addLabel(noteId: string, name: string, value: string = "") {
await server.put(`notes/${noteId}/attribute`, { await server.put(`notes/${noteId}/attribute`, {
@ -23,18 +24,18 @@ async function removeAttributeById(noteId: string, attributeId: string) {
} }
/** /**
* @returns {boolean} - returns true if this attribute has the potential to influence the note in the argument. * @returns - returns true if this attribute has the potential to influence the note in the argument.
* That can happen in multiple ways: * That can happen in multiple ways:
* 1. attribute is owned by the note * 1. attribute is owned by the note
* 2. attribute is owned by the template of the note * 2. attribute is owned by the template of the note
* 3. attribute is owned by some note's ancestor and is inheritable * 3. attribute is owned by some note's ancestor and is inheritable
*/ */
function isAffecting(this: { isInheritable: boolean }, attrRow: { noteId: string }, affectedNote: FNote) { function isAffecting(attrRow: AttributeRow, affectedNote: FNote) {
if (!affectedNote || !attrRow) { if (!affectedNote || !attrRow) {
return false; return false;
} }
const attrNote = froca.notes[attrRow.noteId]; const attrNote = attrRow.noteId && froca.notes[attrRow.noteId];
if (!attrNote) { if (!attrNote) {
// the note (owner of the attribute) is not even loaded into the cache, so it should not affect anything else // the note (owner of the attribute) is not even loaded into the cache, so it should not affect anything else
@ -50,6 +51,7 @@ function isAffecting(this: { isInheritable: boolean }, attrRow: { noteId: string
} }
// TODO: This doesn't seem right. // TODO: This doesn't seem right.
//@ts-ignore
if (this.isInheritable) { if (this.isInheritable) {
for (const owningNote of owningNotes) { for (const owningNote of owningNotes) {
if (owningNote.hasAncestor(attrNote.noteId, true)) { if (owningNote.hasAncestor(attrNote.noteId, true)) {

View File

@ -5,9 +5,11 @@ interface BranchRow {
componentId: string; componentId: string;
} }
interface AttributeRow { export interface AttributeRow {
noteId?: string;
attributeId: string; attributeId: string;
componentId: string; componentId: string;
isInheritable?: boolean;
} }
interface RevisionRow { interface RevisionRow {
@ -79,9 +81,9 @@ export default class LoadResults {
if (!this.noteIdToComponentId[noteId].includes(componentId)) { if (!this.noteIdToComponentId[noteId].includes(componentId)) {
this.noteIdToComponentId[noteId].push(componentId); this.noteIdToComponentId[noteId].push(componentId);
} }
this.componentIdToNoteIds[componentId] = this.componentIdToNoteIds[componentId] || []; this.componentIdToNoteIds[componentId] = this.componentIdToNoteIds[componentId] || [];
if (this.componentIdToNoteIds[componentId]) { if (this.componentIdToNoteIds[componentId]) {
this.componentIdToNoteIds[componentId].push(noteId); this.componentIdToNoteIds[componentId].push(noteId);
} }
@ -110,11 +112,11 @@ export default class LoadResults {
this.attributeRows.push({attributeId, componentId}); this.attributeRows.push({attributeId, componentId});
} }
getAttributeRows(componentId = 'none') { getAttributeRows(componentId = 'none'): AttributeRow[] {
return this.attributeRows return this.attributeRows
.filter(row => row.componentId !== componentId) .filter(row => row.componentId !== componentId)
.map(row => this.getEntityRow("attributes", row.attributeId)) .map(row => this.getEntityRow("attributes", row.attributeId))
.filter(attr => !!attr); .filter(attr => !!attr) as AttributeRow[];
} }
addRevision(revisionId: string, noteId?: string, componentId?: string | null) { addRevision(revisionId: string, noteId?: string, componentId?: string | null) {

View File

@ -30,14 +30,14 @@ interface Options {
} }
async function autocompleteSourceForCKEditor(queryText: string) { async function autocompleteSourceForCKEditor(queryText: string) {
return await new Promise((res, rej) => { return await new Promise<MentionItem[]>((res, rej) => {
autocompleteSource(queryText, rows => { autocompleteSource(queryText, rows => {
res(rows.map(row => { res(rows.map(row => {
return { return {
action: row.action, action: row.action,
noteTitle: row.noteTitle, noteTitle: row.noteTitle,
id: `@${row.notePathTitle}`, id: `@${row.notePathTitle}`,
name: row.notePathTitle, name: row.notePathTitle || "",
link: `#${row.notePath}`, link: `#${row.notePath}`,
notePath: row.notePath, notePath: row.notePath,
highlightedNotePathTitle: row.highlightedNotePathTitle highlightedNotePathTitle: row.highlightedNotePathTitle
@ -62,7 +62,7 @@ async function autocompleteSource(term: string, cb: (rows: Suggestion[]) => void
}] }]
); );
} }
const activeNoteId = appContext.tabManager.getActiveContextNoteId(); const activeNoteId = appContext.tabManager.getActiveContextNoteId();
let results: Suggestion[] = await server.get<Suggestion[]>(`autocomplete?query=${encodeURIComponent(term)}&activeNoteId=${activeNoteId}&fastSearch=${fastSearch}`); let results: Suggestion[] = await server.get<Suggestion[]>(`autocomplete?query=${encodeURIComponent(term)}&activeNoteId=${activeNoteId}&fastSearch=${fastSearch}`);
@ -135,7 +135,7 @@ function fullTextSearch($el: JQuery<HTMLElement>, options: Options){
const searchString = $el.autocomplete('val') as unknown as string; const searchString = $el.autocomplete('val') as unknown as string;
if (options.fastSearch === false || searchString?.trim().length === 0) { if (options.fastSearch === false || searchString?.trim().length === 0) {
return; return;
} }
$el.trigger('focus'); $el.trigger('focus');
options.fastSearch = false; options.fastSearch = false;
$el.autocomplete('val', ''); $el.autocomplete('val', '');
@ -168,7 +168,7 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
const $fullTextSearchButton = $("<button>") const $fullTextSearchButton = $("<button>")
.addClass("input-group-text full-text-search-button bx bx-search") .addClass("input-group-text full-text-search-button bx bx-search")
.prop("title", `${t("note_autocomplete.full-text-search")} (Shift+Enter)`); .prop("title", `${t("note_autocomplete.full-text-search")} (Shift+Enter)`);
const $goToSelectedNoteButton = $("<a>") const $goToSelectedNoteButton = $("<a>")
.addClass("input-group-text go-to-selected-note-button bx bx-arrow-to-right"); .addClass("input-group-text go-to-selected-note-button bx bx-arrow-to-right");
@ -218,7 +218,7 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
fullTextSearch($el,options) fullTextSearch($el,options)
} }
}); });
$el.autocomplete({ $el.autocomplete({
...autocompleteOptions, ...autocompleteOptions,
appendTo: document.querySelector('body'), appendTo: document.querySelector('body'),
@ -279,7 +279,7 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
appContext.triggerCommand('searchNotes', { searchString }); appContext.triggerCommand('searchNotes', { searchString });
return; return;
} }
$el.setSelectedNotePath(suggestion.notePath); $el.setSelectedNotePath(suggestion.notePath);
$el.setSelectedExternalLink(null); $el.setSelectedExternalLink(null);

View File

@ -144,4 +144,89 @@ declare global {
} }
}; };
var MERMAID_ELK: MermaidLoader; var MERMAID_ELK: MermaidLoader;
var CKEditor: {
BalloonEditor: {
create(el: HTMLElement, config: {
removePlugins?: string[];
toolbar: {
items: any[];
},
placeholder: string;
mention: MentionConfig
})
}
};
type TextEditorElement = {};
interface Writer {
setAttribute(name: string, value: string, el: TextEditorElement);
createPositionAt(el: TextEditorElement, opt?: "end");
setSelection(pos: number);
}
interface TextNode {
previousSibling?: TextNode;
name: string;
data: string;
startOffset: number;
_attrs: {
get(key: string): {
length: number
}
}
}
interface TextPosition {
textNode: TextNode;
offset: number;
}
interface TextEditor {
model: {
document: {
on(event: string, cb: () => void);
getRoot(): TextEditorElement;
selection: {
getFirstPosition(): undefined | TextPosition;
}
},
change(cb: (writer: Writer) => void)
},
editing: {
view: {
document: {
on(event: string, cb: (event: {
stop();
}, data: {
preventDefault();
}) => void, opts?: {
priority: "high"
});
getRoot(): TextEditorElement
},
change(cb: (writer: Writer) => void)
}
},
getData(): string;
setData(data: string): void;
}
interface MentionItem {
action?: string;
noteTitle?: string;
id: string;
name: string;
link?: string;
notePath?: string;
highlightedNotePathTitle?: string;
}
interface MentionConfig {
feeds: {
marker: string;
feed: (queryText: string) => MentionItem[] | Promise<MentionItem[]>;
itemRenderer?: (item: {
highlightedNotePathTitle: string
}) => void;
minimumCharacters: number;
}[];
}
} }

View File

@ -12,6 +12,7 @@ import shortcutService from "../../services/shortcuts.js";
import appContext from "../../components/app_context.js"; import appContext from "../../components/app_context.js";
import FAttribute from "../../entities/fattribute.js"; import FAttribute from "../../entities/fattribute.js";
import FNote, { FNoteRow } from "../../entities/fnote.js"; import FNote, { FNoteRow } from "../../entities/fnote.js";
import { Attribute } from "../../services/attribute_parser.js";
const TPL = ` const TPL = `
<div class="attr-detail"> <div class="attr-detail">
@ -269,12 +270,12 @@ const ATTR_HELP: Record<string, Record<string, string>> = {
}; };
interface AttributeDetailOpts { interface AttributeDetailOpts {
allAttributes: FAttribute[]; allAttributes: Attribute[];
attribute: FAttribute; attribute: Attribute;
isOwned: boolean; isOwned: boolean;
x: number; x: number;
y: number; y: number;
focus: "name"; focus?: "name";
} }
interface SearchRelatedResponse { interface SearchRelatedResponse {
@ -319,8 +320,8 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
private $attrHelp!: JQuery<HTMLElement>; private $attrHelp!: JQuery<HTMLElement>;
private relatedNotesSpacedUpdate!: SpacedUpdate; private relatedNotesSpacedUpdate!: SpacedUpdate;
private attribute!: FAttribute; private attribute!: Attribute;
private allAttributes!: FAttribute[]; private allAttributes!: Attribute[];
private attrType!: ReturnType<AttributeDetailWidget["getAttrType"]>; private attrType!: ReturnType<AttributeDetailWidget["getAttrType"]>;
async refresh() { async refresh() {
@ -475,7 +476,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
: (this.attrType === 'relation-definition' ? attribute.name.substr(9) : attribute.name); : (this.attrType === 'relation-definition' ? attribute.name.substr(9) : attribute.name);
const definition = this.attrType?.endsWith('-definition') const definition = this.attrType?.endsWith('-definition')
? promotedAttributeDefinitionParser.parse(attribute.value) ? promotedAttributeDefinitionParser.parse(attribute.value || "")
: {}; : {};
if (this.attrType) { if (this.attrType) {
@ -492,8 +493,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
if (isOwned) { if (isOwned) {
this.$attrIsOwnedBy.hide(); this.$attrIsOwnedBy.hide();
} } else if (attribute.noteId) {
else {
this.$attrIsOwnedBy this.$attrIsOwnedBy
.show() .show()
.empty() .empty()
@ -543,7 +543,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
if (attribute.type === 'label') { if (attribute.type === 'label') {
this.$inputValue this.$inputValue
.val(attribute.value) .val(attribute.value || "")
.attr('readonly', disabledFn); .attr('readonly', disabledFn);
} }
else if (attribute.type === 'relation') { else if (attribute.type === 'relation') {
@ -696,7 +696,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
} }
} }
getAttrType(attribute: FAttribute) { getAttrType(attribute: Attribute) {
if (attribute.type === 'label') { if (attribute.type === 'label') {
if (attribute.name.startsWith('label:')) { if (attribute.name.startsWith('label:')) {
return "label-definition"; return "label-definition";

View File

@ -3,13 +3,17 @@ import NoteContextAwareWidget from "../note_context_aware_widget.js";
import noteAutocompleteService from "../../services/note_autocomplete.js"; import noteAutocompleteService from "../../services/note_autocomplete.js";
import server from "../../services/server.js"; import server from "../../services/server.js";
import contextMenuService from "../../menus/context_menu.js"; import contextMenuService from "../../menus/context_menu.js";
import attributeParser from "../../services/attribute_parser.js"; import attributeParser, { Attribute } from "../../services/attribute_parser.js";
import libraryLoader from "../../services/library_loader.js"; import libraryLoader from "../../services/library_loader.js";
import froca from "../../services/froca.js"; import froca from "../../services/froca.js";
import attributeRenderer from "../../services/attribute_renderer.js"; import attributeRenderer from "../../services/attribute_renderer.js";
import noteCreateService from "../../services/note_create.js"; import noteCreateService from "../../services/note_create.js";
import attributeService from "../../services/attributes.js"; import attributeService from "../../services/attributes.js";
import linkService from "../../services/link.js"; import linkService from "../../services/link.js";
import AttributeDetailWidget from "./attribute_detail.js";
import { CommandData, EventData, EventListener, FilteredCommandNames } from "../../components/app_context.js";
import FAttribute, { AttributeType } from "../../entities/fattribute.js";
import FNote from "../../entities/fnote.js";
const HELP_TEXT = ` const HELP_TEXT = `
<p>${t("attribute_editor.help_text_body1")}</p> <p>${t("attribute_editor.help_text_body1")}</p>
@ -31,55 +35,55 @@ const TPL = `
overflow: auto; overflow: auto;
transition: opacity .1s linear; transition: opacity .1s linear;
} }
.attribute-list-editor.ck-content .mention { .attribute-list-editor.ck-content .mention {
color: var(--muted-text-color) !important; color: var(--muted-text-color) !important;
background: transparent !important; background: transparent !important;
} }
.save-attributes-button { .save-attributes-button {
color: var(--muted-text-color); color: var(--muted-text-color);
position: absolute; position: absolute;
bottom: 14px; bottom: 14px;
right: 25px; right: 25px;
cursor: pointer; cursor: pointer;
border: 1px solid transparent; border: 1px solid transparent;
font-size: 130%; font-size: 130%;
} }
.add-new-attribute-button { .add-new-attribute-button {
color: var(--muted-text-color); color: var(--muted-text-color);
position: absolute; position: absolute;
bottom: 13px; bottom: 13px;
right: 0; right: 0;
cursor: pointer; cursor: pointer;
border: 1px solid transparent; border: 1px solid transparent;
font-size: 130%; font-size: 130%;
} }
.add-new-attribute-button:hover, .save-attributes-button:hover { .add-new-attribute-button:hover, .save-attributes-button:hover {
border: 1px solid var(--button-border-color); border: 1px solid var(--button-border-color);
border-radius: var(--button-border-radius); border-radius: var(--button-border-radius);
background: var(--button-background-color); background: var(--button-background-color);
color: var(--button-text-color); color: var(--button-text-color);
} }
.attribute-errors { .attribute-errors {
color: red; color: red;
padding: 5px 50px 0px 5px; /* large right padding to avoid buttons */ padding: 5px 50px 0px 5px; /* large right padding to avoid buttons */
} }
</style> </style>
<div class="attribute-list-editor" tabindex="200"></div> <div class="attribute-list-editor" tabindex="200"></div>
<div class="bx bx-save save-attributes-button" title="${t("attribute_editor.save_attributes")}"></div> <div class="bx bx-save save-attributes-button" title="${t("attribute_editor.save_attributes")}"></div>
<div class="bx bx-plus add-new-attribute-button" title="${t("attribute_editor.add_a_new_attribute")}"></div> <div class="bx bx-plus add-new-attribute-button" title="${t("attribute_editor.add_a_new_attribute")}"></div>
<div class="attribute-errors" style="display: none;"></div> <div class="attribute-errors" style="display: none;"></div>
</div> </div>
`; `;
const mentionSetup = { const mentionSetup: MentionConfig = {
feeds: [ feeds: [
{ {
marker: '@', marker: '@',
@ -96,7 +100,7 @@ const mentionSetup = {
{ {
marker: '#', marker: '#',
feed: async queryText => { feed: async queryText => {
const names = await server.get(`attribute-names/?type=label&query=${encodeURIComponent(queryText)}`); const names = await server.get<string[]>(`attribute-names/?type=label&query=${encodeURIComponent(queryText)}`);
return names.map(name => { return names.map(name => {
return { return {
@ -110,7 +114,7 @@ const mentionSetup = {
{ {
marker: '~', marker: '~',
feed: async queryText => { feed: async queryText => {
const names = await server.get(`attribute-names/?type=relation&query=${encodeURIComponent(queryText)}`); const names = await server.get<string[]>(`attribute-names/?type=relation&query=${encodeURIComponent(queryText)}`);
return names.map(name => { return names.map(name => {
return { return {
@ -179,8 +183,24 @@ const editorConfig = {
mention: mentionSetup mention: mentionSetup
}; };
export default class AttributeEditorWidget extends NoteContextAwareWidget { type AttributeCommandNames = FilteredCommandNames<CommandData>;
constructor(attributeDetailWidget) {
export default class AttributeEditorWidget extends NoteContextAwareWidget implements
EventListener<"entitiesReloaded">,
EventListener<"addNewLabel">,
EventListener<"addNewRelation"> {
private attributeDetailWidget: AttributeDetailWidget;
private $editor!: JQuery<HTMLElement>;
private $addNewAttributeButton!: JQuery<HTMLElement>;
private $saveAttributesButton!: JQuery<HTMLElement>;
private $errors!: JQuery<HTMLElement>;
private textEditor!: TextEditor;
private lastUpdatedNoteId!: string | undefined;
private lastSavedContent!: string;
constructor(attributeDetailWidget: AttributeDetailWidget) {
super(); super();
this.attributeDetailWidget = attributeDetailWidget; this.attributeDetailWidget = attributeDetailWidget;
@ -212,8 +232,8 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
this.$errors = this.$widget.find('.attribute-errors'); this.$errors = this.$widget.find('.attribute-errors');
} }
addNewAttribute(e) { addNewAttribute(e: JQuery.ClickEvent) {
contextMenuService.show({ contextMenuService.show<AttributeCommandNames>({
x: e.pageX, x: e.pageX,
y: e.pageY, y: e.pageY,
orientation: 'left', orientation: 'left',
@ -229,7 +249,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
} }
// triggered from keyboard shortcut // triggered from keyboard shortcut
async addNewLabelEvent({ntxId}) { async addNewLabelEvent({ntxId}: EventData<"addNewLabel">) {
if (this.isNoteContext(ntxId)) { if (this.isNoteContext(ntxId)) {
await this.refresh(); await this.refresh();
@ -238,7 +258,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
} }
// triggered from keyboard shortcut // triggered from keyboard shortcut
async addNewRelationEvent({ntxId}) { async addNewRelationEvent({ntxId}: EventData<"addNewRelation">) {
if (this.isNoteContext(ntxId)) { if (this.isNoteContext(ntxId)) {
await this.refresh(); await this.refresh();
@ -246,14 +266,17 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
} }
} }
async handleAddNewAttributeCommand(command) { async handleAddNewAttributeCommand(command: AttributeCommandNames | undefined) {
const attrs = this.parseAttributes(); // TODO: Not sure what the relation between FAttribute[] and Attribute[] is.
const attrs = this.parseAttributes() as FAttribute[];
if (!attrs) { if (!attrs) {
return; return;
} }
let type, name, value; let type: AttributeType;
let name;
let value;
if (command === 'addNewLabel') { if (command === 'addNewLabel') {
type = 'label'; type = 'label';
@ -275,6 +298,8 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
return; return;
} }
// TODO: Incomplete type
//@ts-ignore
attrs.push({ attrs.push({
type, type,
name, name,
@ -326,8 +351,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
parseAttributes() { parseAttributes() {
try { try {
return attributeParser.lexAndParse(this.getPreprocessedData()); return attributeParser.lexAndParse(this.getPreprocessedData());
} } catch (e: any) {
catch (e) {
this.$errors.text(e.message).slideDown(); this.$errors.text(e.message).slideDown();
} }
} }
@ -376,7 +400,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
} }
} }
async handleEditorClick(e) { async handleEditorClick(e: JQuery.ClickEvent) {
const pos = this.textEditor.model.document.selection.getFirstPosition(); const pos = this.textEditor.model.document.selection.getFirstPosition();
if (pos && pos.textNode && pos.textNode.data) { if (pos && pos.textNode && pos.textNode.data) {
@ -395,7 +419,8 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
let matchedAttr = null; let matchedAttr = null;
for (const attr of parsedAttrs) { for (const attr of parsedAttrs) {
if (clickIndex > attr.startIndex && clickIndex <= attr.endIndex) { if (attr.startIndex && clickIndex > attr.startIndex &&
attr.endIndex && clickIndex <= attr.endIndex) {
matchedAttr = attr; matchedAttr = attr;
break; break;
} }
@ -437,7 +462,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
this.$editor.tooltip('show'); this.$editor.tooltip('show');
} }
getClickIndex(pos) { getClickIndex(pos: TextPosition) {
let clickIndex = pos.offset - pos.textNode.startOffset; let clickIndex = pos.offset - pos.textNode.startOffset;
let curNode = pos.textNode; let curNode = pos.textNode;
@ -455,20 +480,19 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
return clickIndex; return clickIndex;
} }
async loadReferenceLinkTitle($el, href) { async loadReferenceLinkTitle($el: JQuery<HTMLElement>, href: string) {
const {noteId} = linkService.parseNavigationStateFromUrl(href); const {noteId} = linkService.parseNavigationStateFromUrl(href);
const note = await froca.getNote(noteId, true); const note = noteId ? await froca.getNote(noteId, true) : null;
const title = note ? note.title : '[missing]'; const title = note ? note.title : '[missing]';
$el.text(title); $el.text(title);
} }
async refreshWithNote(note) { async refreshWithNote(note: FNote) {
await this.renderOwnedAttributes(note.getOwnedAttributes(), true); await this.renderOwnedAttributes(note.getOwnedAttributes(), true);
} }
async renderOwnedAttributes(ownedAttributes, saved) { async renderOwnedAttributes(ownedAttributes: FAttribute[], saved: boolean) {
// attrs are not resorted if position changes after the initial load // attrs are not resorted if position changes after the initial load
ownedAttributes.sort((a, b) => a.position - b.position); ownedAttributes.sort((a, b) => a.position - b.position);
@ -487,16 +511,19 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
} }
} }
async createNoteForReferenceLink(title) { async createNoteForReferenceLink(title: string) {
const {note} = await noteCreateService.createNoteWithTypePrompt(this.notePath, { let result;
activate: false, if (this.notePath) {
title: title result = await noteCreateService.createNoteWithTypePrompt(this.notePath, {
}); activate: false,
title: title
});
}
return note.getBestNotePathString(); return result?.note?.getBestNotePathString();
} }
async updateAttributeList(attributes) { async updateAttributeList(attributes: FAttribute[]) {
await this.renderOwnedAttributes(attributes, false); await this.renderOwnedAttributes(attributes, false);
} }
@ -510,9 +537,10 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
} ); } );
} }
entitiesReloadedEvent({loadResults}) { entitiesReloadedEvent({loadResults}: EventData<"entitiesReloaded">) {
if (loadResults.getAttributeRows(this.componentId).find(attr => attributeService.isAffecting(attr, this.note))) { if (loadResults.getAttributeRows(this.componentId).find(attr => attributeService.isAffecting(attr, this.note))) {
this.refresh(); this.refresh();
} }
} }
} }