mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-08-10 18:39:22 +08:00
chore(client/ts): port promoted_attributes
This commit is contained in:
parent
c27d5afdf2
commit
e682f01c47
@ -8,9 +8,10 @@ interface Token {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Attribute {
|
export interface Attribute {
|
||||||
|
attributeId?: string;
|
||||||
type: AttributeType;
|
type: AttributeType;
|
||||||
name: string;
|
name: string;
|
||||||
isInheritable: boolean;
|
isInheritable?: boolean;
|
||||||
value?: string;
|
value?: string;
|
||||||
startIndex?: number;
|
startIndex?: number;
|
||||||
endIndex?: number;
|
endIndex?: number;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
type LabelType = "text" | "number" | "boolean" | "date" | "datetime" | "url";
|
type LabelType = "text" | "number" | "boolean" | "date" | "datetime" | "time" | "url";
|
||||||
type Multiplicity = "single" | "multi";
|
type Multiplicity = "single" | "multi";
|
||||||
|
|
||||||
interface DefinitionObject {
|
interface DefinitionObject {
|
||||||
|
2
src/public/app/types.d.ts
vendored
2
src/public/app/types.d.ts
vendored
@ -74,7 +74,7 @@ declare global {
|
|||||||
|
|
||||||
interface AutoCompleteArg {
|
interface AutoCompleteArg {
|
||||||
displayKey: "name" | "value" | "notePathTitle";
|
displayKey: "name" | "value" | "notePathTitle";
|
||||||
cache: boolean;
|
cache?: boolean;
|
||||||
source: (term: string, cb: AutoCompleteCallback) => void,
|
source: (term: string, cb: AutoCompleteCallback) => void,
|
||||||
templates?: {
|
templates?: {
|
||||||
suggestion: (suggestion: Suggestion) => string | undefined
|
suggestion: (suggestion: Suggestion) => string | undefined
|
||||||
|
@ -7,6 +7,10 @@ import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
|||||||
import attributeService from "../../services/attributes.js";
|
import attributeService from "../../services/attributes.js";
|
||||||
import options from "../../services/options.js";
|
import options from "../../services/options.js";
|
||||||
import utils from "../../services/utils.js";
|
import utils from "../../services/utils.js";
|
||||||
|
import type FNote from "../../entities/fnote.js";
|
||||||
|
import type { Attribute } from "../../services/attribute_parser.js";
|
||||||
|
import type FAttribute from "../../entities/fattribute.js";
|
||||||
|
import type { EventData } from "../../components/app_context.js";
|
||||||
|
|
||||||
const TPL = `
|
const TPL = `
|
||||||
<div class="promoted-attributes-widget">
|
<div class="promoted-attributes-widget">
|
||||||
@ -60,12 +64,20 @@ const TPL = `
|
|||||||
<div class="promoted-attributes-container"></div>
|
<div class="promoted-attributes-container"></div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
|
// TODO: Deduplicate
|
||||||
|
interface AttributeResult {
|
||||||
|
attributeId: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This widget is quite special because it's used in the desktop ribbon, but in mobile outside of ribbon.
|
* This widget is quite special because it's used in the desktop ribbon, but in mobile outside of ribbon.
|
||||||
* This works without many issues (apart from autocomplete), but it should be kept in mind when changing things
|
* This works without many issues (apart from autocomplete), but it should be kept in mind when changing things
|
||||||
* and testing.
|
* and testing.
|
||||||
*/
|
*/
|
||||||
export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
||||||
|
|
||||||
|
private $container!: JQuery<HTMLElement>;
|
||||||
|
|
||||||
get name() {
|
get name() {
|
||||||
return "promotedAttributes";
|
return "promotedAttributes";
|
||||||
}
|
}
|
||||||
@ -80,7 +92,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
|||||||
this.$container = this.$widget.find(".promoted-attributes-container");
|
this.$container = this.$widget.find(".promoted-attributes-container");
|
||||||
}
|
}
|
||||||
|
|
||||||
getTitle(note) {
|
getTitle(note: FNote) {
|
||||||
const promotedDefAttrs = note.getPromotedDefinitionAttributes();
|
const promotedDefAttrs = note.getPromotedDefinitionAttributes();
|
||||||
|
|
||||||
if (promotedDefAttrs.length === 0) {
|
if (promotedDefAttrs.length === 0) {
|
||||||
@ -95,7 +107,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshWithNote(note) {
|
async refreshWithNote(note: FNote) {
|
||||||
this.$container.empty();
|
this.$container.empty();
|
||||||
|
|
||||||
const promotedDefAttrs = note.getPromotedDefinitionAttributes();
|
const promotedDefAttrs = note.getPromotedDefinitionAttributes();
|
||||||
@ -116,7 +128,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
|||||||
const valueType = definitionAttr.name.startsWith("label:") ? "label" : "relation";
|
const valueType = definitionAttr.name.startsWith("label:") ? "label" : "relation";
|
||||||
const valueName = definitionAttr.name.substr(valueType.length + 1);
|
const valueName = definitionAttr.name.substr(valueType.length + 1);
|
||||||
|
|
||||||
let valueAttrs = ownedAttributes.filter((el) => el.name === valueName && el.type === valueType);
|
let valueAttrs = ownedAttributes.filter((el) => el.name === valueName && el.type === valueType) as Attribute[];
|
||||||
|
|
||||||
if (valueAttrs.length === 0) {
|
if (valueAttrs.length === 0) {
|
||||||
valueAttrs.push({
|
valueAttrs.push({
|
||||||
@ -134,9 +146,11 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
|||||||
for (const valueAttr of valueAttrs) {
|
for (const valueAttr of valueAttrs) {
|
||||||
const $cell = await this.createPromotedAttributeCell(definitionAttr, valueAttr, valueName);
|
const $cell = await this.createPromotedAttributeCell(definitionAttr, valueAttr, valueName);
|
||||||
|
|
||||||
|
if ($cell) {
|
||||||
$cells.push($cell);
|
$cells.push($cell);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// we replace the whole content in one step, so there can't be any race conditions
|
// we replace the whole content in one step, so there can't be any race conditions
|
||||||
// (previously we saw promoted attributes doubling)
|
// (previously we saw promoted attributes doubling)
|
||||||
@ -144,14 +158,14 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
|||||||
this.toggleInt(true);
|
this.toggleInt(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createPromotedAttributeCell(definitionAttr, valueAttr, valueName) {
|
async createPromotedAttributeCell(definitionAttr: FAttribute, valueAttr: Attribute, valueName: string) {
|
||||||
const definition = definitionAttr.getDefinition();
|
const definition = definitionAttr.getDefinition();
|
||||||
const id = `value-${valueAttr.attributeId}`;
|
const id = `value-${valueAttr.attributeId}`;
|
||||||
|
|
||||||
const $input = $("<input>")
|
const $input = $("<input>")
|
||||||
.prop("tabindex", 200 + definitionAttr.position)
|
.prop("tabindex", 200 + definitionAttr.position)
|
||||||
.prop("id", id)
|
.prop("id", id)
|
||||||
.attr("data-attribute-id", valueAttr.noteId === this.noteId ? valueAttr.attributeId : "") // if not owned, we'll force creation of a new attribute instead of updating the inherited one
|
.attr("data-attribute-id", valueAttr.noteId === this.noteId ? valueAttr.attributeId ?? "" : "") // if not owned, we'll force creation of a new attribute instead of updating the inherited one
|
||||||
.attr("data-attribute-type", valueAttr.type)
|
.attr("data-attribute-type", valueAttr.type)
|
||||||
.attr("data-attribute-name", valueAttr.name)
|
.attr("data-attribute-name", valueAttr.name)
|
||||||
.prop("value", valueAttr.value)
|
.prop("value", valueAttr.value)
|
||||||
@ -161,7 +175,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
|||||||
.on("change", (event) => this.promotedAttributeChanged(event));
|
.on("change", (event) => this.promotedAttributeChanged(event));
|
||||||
|
|
||||||
const $actionCell = $("<div>");
|
const $actionCell = $("<div>");
|
||||||
const $multiplicityCell = $("<td>").addClass("multiplicity").attr("nowrap", true);
|
const $multiplicityCell = $("<td>").addClass("multiplicity").attr("nowrap", "true");
|
||||||
|
|
||||||
const $wrapper = $('<div class="promoted-attribute-cell">')
|
const $wrapper = $('<div class="promoted-attribute-cell">')
|
||||||
.append(
|
.append(
|
||||||
@ -180,12 +194,12 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
|||||||
// autocomplete for label values is just nice to have, mobile can keep labels editable without autocomplete
|
// autocomplete for label values is just nice to have, mobile can keep labels editable without autocomplete
|
||||||
if (utils.isDesktop()) {
|
if (utils.isDesktop()) {
|
||||||
// no need to await for this, can be done asynchronously
|
// no need to await for this, can be done asynchronously
|
||||||
server.get(`attribute-values/${encodeURIComponent(valueAttr.name)}`).then((attributeValues) => {
|
server.get<string[]>(`attribute-values/${encodeURIComponent(valueAttr.name)}`).then((_attributeValues) => {
|
||||||
if (attributeValues.length === 0) {
|
if (_attributeValues.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
attributeValues = attributeValues.map((attribute) => ({ value: attribute }));
|
const attributeValues = _attributeValues.map((attribute) => ({ value: attribute }));
|
||||||
|
|
||||||
$input.autocomplete(
|
$input.autocomplete(
|
||||||
{
|
{
|
||||||
@ -245,11 +259,11 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
|||||||
const $openButton = $("<span>")
|
const $openButton = $("<span>")
|
||||||
.addClass("input-group-text open-external-link-button bx bx-window-open")
|
.addClass("input-group-text open-external-link-button bx bx-window-open")
|
||||||
.prop("title", t("promoted_attributes.open_external_link"))
|
.prop("title", t("promoted_attributes.open_external_link"))
|
||||||
.on("click", () => window.open($input.val(), "_blank"));
|
.on("click", () => window.open($input.val() as string, "_blank"));
|
||||||
|
|
||||||
$input.after($openButton);
|
$input.after($openButton);
|
||||||
} else {
|
} else {
|
||||||
ws.logError(t("promoted_attributes.unknown_label_type", { type: definitionAttr.labelType }));
|
ws.logError(t("promoted_attributes.unknown_label_type", { type: definition.labelType }));
|
||||||
}
|
}
|
||||||
} else if (valueAttr.type === "relation") {
|
} else if (valueAttr.type === "relation") {
|
||||||
if (valueAttr.value) {
|
if (valueAttr.value) {
|
||||||
@ -290,9 +304,11 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
|||||||
valueName
|
valueName
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ($new) {
|
||||||
$wrapper.after($new);
|
$wrapper.after($new);
|
||||||
|
|
||||||
$new.find("input").trigger("focus");
|
$new.find("input").trigger("focus");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const $removeButton = $("<span>")
|
const $removeButton = $("<span>")
|
||||||
@ -320,8 +336,10 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
|||||||
valueName
|
valueName
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ($new) {
|
||||||
$wrapper.after($new);
|
$wrapper.after($new);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$wrapper.remove();
|
$wrapper.remove();
|
||||||
});
|
});
|
||||||
@ -332,7 +350,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
|||||||
return $wrapper;
|
return $wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
async promotedAttributeChanged(event) {
|
async promotedAttributeChanged(event: JQuery.TriggeredEvent<HTMLElement, undefined, HTMLElement, HTMLElement>) {
|
||||||
const $attr = $(event.target);
|
const $attr = $(event.target);
|
||||||
|
|
||||||
let value;
|
let value;
|
||||||
@ -347,7 +365,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
|||||||
value = $attr.val();
|
value = $attr.val();
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await server.put(
|
const result = await server.put<AttributeResult>(
|
||||||
`notes/${this.noteId}/attribute`,
|
`notes/${this.noteId}/attribute`,
|
||||||
{
|
{
|
||||||
attributeId: $attr.attr("data-attribute-id"),
|
attributeId: $attr.attr("data-attribute-id"),
|
||||||
@ -365,7 +383,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
|||||||
this.$widget.find(".promoted-attribute-input:first").focus();
|
this.$widget.find(".promoted-attribute-input:first").focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
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