mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-29 19:12:27 +08:00
chore(client/ts): port widgets/attribute_editor
This commit is contained in:
parent
b01725101d
commit
a349223e54
@ -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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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) {
|
||||||
|
@ -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)) {
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
85
src/public/app/types.d.ts
vendored
85
src/public/app/types.d.ts
vendored
@ -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;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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";
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user