fix: Fix collection description (#2065)
Fixes https://github.com/webrecorder/browsertrix/issues/2064 ### Changes - Switches MDE library to one that supports shadow DOM - Refactors collection components to btrix components - Fixes collection detail not expanding and contracting correctly
This commit is contained in:
parent
4c36c80351
commit
b4e34d1c3c
@ -4,6 +4,10 @@
|
||||
|
||||
You can create a collection from the Collections page, or the _Create New ..._ shortcut from the org overview.
|
||||
|
||||
## Collection Description
|
||||
|
||||
The description can be formatted with basic [Markdown](https://github.github.com/gfm/#what-is-markdown-) syntax to include headings, bolded and italicized text, lists, and links. The editor is powered by [ink-mde](https://github.com/davidmyersdev/ink-mde), an open source Markdown editor.
|
||||
|
||||
## Sharing Collections
|
||||
|
||||
Collections are private by default, but can be made public by marking them as sharable in the Metadata step of collection creation, or by toggling the _Collection is Shareable_ switch in the share collection dialogue.
|
||||
|
@ -18,7 +18,6 @@
|
||||
"@types/sinon": "^10.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
||||
"@typescript-eslint/parser": "^6.20.0",
|
||||
"@wysimark/standalone": "3.0.20",
|
||||
"@xstate/fsm": "^1.6.2",
|
||||
"@zxcvbn-ts/core": "^3.0.4",
|
||||
"@zxcvbn-ts/language-common": "^3.0.4",
|
||||
@ -49,6 +48,7 @@
|
||||
"html-loader": "^3.0.1",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"immutable": "^4.1.0",
|
||||
"ink-mde": "~0.33.0",
|
||||
"iso-639-1": "^2.1.15",
|
||||
"lit": "3.1.1",
|
||||
"lit-shared-state": "^0.2.1",
|
||||
|
@ -1,11 +1,11 @@
|
||||
// cSpell:words wysimark
|
||||
|
||||
import { createWysimark } from "@wysimark/standalone";
|
||||
import { html, LitElement, type PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { guard } from "lit/directives/guard.js";
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { wrap, type AwaitableInstance } from "ink-mde";
|
||||
import { css, html, type PropertyValues } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators.js";
|
||||
|
||||
import { TailwindElement } from "@/classes/TailwindElement";
|
||||
import { getHelpText } from "@/utils/form";
|
||||
import { formatNumber } from "@/utils/localization";
|
||||
|
||||
type MarkdownChangeDetail = {
|
||||
value: string;
|
||||
@ -15,116 +15,172 @@ export type MarkdownChangeEvent = CustomEvent<MarkdownChangeDetail>;
|
||||
/**
|
||||
* Edit and preview text in markdown
|
||||
*
|
||||
* @event on-change MarkdownChangeEvent
|
||||
* @fires btrix-change MarkdownChangeEvent
|
||||
*/
|
||||
@customElement("btrix-markdown-editor")
|
||||
export class MarkdownEditor extends LitElement {
|
||||
export class MarkdownEditor extends TailwindElement {
|
||||
static styles = css`
|
||||
:host {
|
||||
--ink-border-radius: var(--sl-input-border-radius-medium);
|
||||
--ink-color: var(--sl-input-color);
|
||||
--ink-block-background-color: var(--sl-color-neutral-50);
|
||||
--ink-block-padding: var(--sl-input-spacing-small);
|
||||
}
|
||||
|
||||
.ink-mde {
|
||||
border: solid var(--sl-input-border-width) var(--sl-input-border-color);
|
||||
}
|
||||
|
||||
.ink-mde-toolbar {
|
||||
border-top-left-radius: var(--ink-border-radius);
|
||||
border-top-right-radius: var(--ink-border-radius);
|
||||
border-bottom: 1px solid var(--sl-panel-border-color);
|
||||
}
|
||||
|
||||
.ink-mde .ink-mde-toolbar .ink-button {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
/* TODO check why style wasn't applied */
|
||||
.cm-announced {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
@property({ type: String })
|
||||
label = "";
|
||||
|
||||
@property({ type: String })
|
||||
initialValue = "";
|
||||
|
||||
@property({ type: String })
|
||||
name = "markdown";
|
||||
value = "";
|
||||
|
||||
@property({ type: Number })
|
||||
maxlength?: number;
|
||||
|
||||
@state()
|
||||
value = "";
|
||||
@query("#editor-textarea")
|
||||
private readonly textarea?: HTMLTextAreaElement | null;
|
||||
|
||||
createRenderRoot() {
|
||||
// Disable shadow DOM for styles to work
|
||||
return this;
|
||||
private editor?: AwaitableInstance;
|
||||
|
||||
public checkValidity() {
|
||||
return this.textarea?.checkValidity();
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues<this>) {
|
||||
if (changedProperties.has("initialValue") && this.initialValue) {
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
if (
|
||||
changedProperties.has("initialValue") &&
|
||||
this.initialValue &&
|
||||
!this.value
|
||||
) {
|
||||
this.value = this.initialValue;
|
||||
this.initEditor();
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this.editor?.destroy();
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
if (!this.initialValue) {
|
||||
this.initEditor();
|
||||
}
|
||||
this.initEditor();
|
||||
}
|
||||
|
||||
render() {
|
||||
const isInvalid = this.maxlength && this.value.length > this.maxlength;
|
||||
return html`
|
||||
<fieldset
|
||||
class="markdown-editor-wrapper with-max-help-text"
|
||||
?data-invalid=${isInvalid}
|
||||
?data-user-invalid=${isInvalid}
|
||||
>
|
||||
<input name=${this.name} type="hidden" value="${this.value}" />
|
||||
${guard(
|
||||
[this.initialValue],
|
||||
() => html`
|
||||
<style>
|
||||
.markdown-editor-wrapper[data-user-invalid] {
|
||||
--select-editor-color: var(--sl-color-danger-400);
|
||||
}
|
||||
.markdown-editor-wrapper[data-user-invalid]
|
||||
.markdown-editor
|
||||
> div {
|
||||
border: 1px solid var(--sl-color-danger-400);
|
||||
}
|
||||
.markdown-editor {
|
||||
--blue-100: var(--sl-color-blue-100);
|
||||
}
|
||||
/* NOTE wysimark doesn't support customization or
|
||||
a way of selecting elements as of 2.2.15
|
||||
https://github.com/portive/wysimark/issues/10 */
|
||||
/* Editor container: */
|
||||
.markdown-editor > div {
|
||||
overflow: hidden;
|
||||
border-radius: var(--sl-input-border-radius-medium);
|
||||
font-family: var(--sl-font-sans);
|
||||
font-size: 1rem;
|
||||
}
|
||||
/* Hide unsupported button features */
|
||||
/* Table, images: */
|
||||
.markdown-editor > div > div > div > div:nth-child(9),
|
||||
.markdown-editor > div > div > div > div:nth-child(10) {
|
||||
display: none !important;
|
||||
}
|
||||
.markdown-editor div[role="textbox"] {
|
||||
font-size: var(--sl-font-size-medium);
|
||||
padding: var(--sl-spacing-small) var(--sl-spacing-medium);
|
||||
}
|
||||
</style>
|
||||
<div class="markdown-editor font-sm"></div>
|
||||
`,
|
||||
)}
|
||||
${this.maxlength
|
||||
? html`<div class="form-help-text">
|
||||
${getHelpText(this.maxlength, this.value.length)}
|
||||
</div>`
|
||||
: ""}
|
||||
<fieldset ?data-invalid=${isInvalid} ?data-user-invalid=${isInvalid}>
|
||||
<label class="form-label">${this.label}</label>
|
||||
<textarea id="editor-textarea"></textarea>
|
||||
<div class="helpText flex items-baseline justify-between">
|
||||
<p class="text-xs">
|
||||
${msg(
|
||||
html`Supports
|
||||
<a
|
||||
class="text-blue-500 hover:text-blue-600"
|
||||
href="https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>GitHub Flavored Markdown</a
|
||||
>.`,
|
||||
)}
|
||||
</p>
|
||||
|
||||
${this.maxlength
|
||||
? html`<div>
|
||||
<p class="form-help-text">
|
||||
${getHelpText(this.maxlength, this.value.length)}
|
||||
</p>
|
||||
</div>`
|
||||
: ""}
|
||||
</div>
|
||||
</fieldset>
|
||||
`;
|
||||
}
|
||||
|
||||
private initEditor() {
|
||||
const editor = createWysimark(this.querySelector(".markdown-editor")!, {
|
||||
initialMarkdown: this.initialValue,
|
||||
minHeight: "12rem",
|
||||
onChange: async () => {
|
||||
const value = editor.getMarkdown();
|
||||
const input = this.querySelector<HTMLTextAreaElement>(
|
||||
`input[name=${this.name}]`,
|
||||
);
|
||||
input!.value = value;
|
||||
this.value = value;
|
||||
await this.updateComplete;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent<MarkdownChangeDetail>("on-change", {
|
||||
detail: {
|
||||
value: value,
|
||||
},
|
||||
}),
|
||||
);
|
||||
if (!this.textarea) return;
|
||||
|
||||
if (this.editor) {
|
||||
this.editor.destroy();
|
||||
}
|
||||
|
||||
this.editor = wrap(this.textarea, {
|
||||
doc: this.initialValue,
|
||||
hooks: {
|
||||
beforeUpdate: (doc: string) => {
|
||||
if (this.maxlength) {
|
||||
this.textarea?.setCustomValidity(
|
||||
doc.length > this.maxlength
|
||||
? msg(
|
||||
str`Please shorten the description to ${formatNumber(this.maxlength)} or fewer characters.`,
|
||||
)
|
||||
: "",
|
||||
);
|
||||
}
|
||||
},
|
||||
afterUpdate: async (doc: string) => {
|
||||
this.value = doc;
|
||||
|
||||
await this.updateComplete;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent<MarkdownChangeDetail>("btrix-change", {
|
||||
detail: {
|
||||
value: doc,
|
||||
},
|
||||
}),
|
||||
);
|
||||
},
|
||||
},
|
||||
interface: {
|
||||
appearance: "light",
|
||||
attribution: false,
|
||||
autocomplete: false,
|
||||
toolbar: true,
|
||||
},
|
||||
toolbar: {
|
||||
bold: true,
|
||||
code: false,
|
||||
codeBlock: false,
|
||||
heading: true,
|
||||
image: false,
|
||||
italic: true,
|
||||
link: true,
|
||||
list: true,
|
||||
orderedList: true,
|
||||
quote: false,
|
||||
taskList: false,
|
||||
upload: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -1,14 +1,22 @@
|
||||
import { localized, msg, str } from "@lit/localize";
|
||||
import { type SlInput } from "@shoelace-style/shoelace";
|
||||
import { serialize } from "@shoelace-style/shoelace/dist/utilities/form.js";
|
||||
import { customElement, property, queryAsync, state } from "lit/decorators.js";
|
||||
import { html } from "lit";
|
||||
import {
|
||||
customElement,
|
||||
property,
|
||||
query,
|
||||
queryAsync,
|
||||
state,
|
||||
} from "lit/decorators.js";
|
||||
import { when } from "lit/directives/when.js";
|
||||
|
||||
import { BtrixElement } from "@/classes/BtrixElement";
|
||||
import type { Dialog } from "@/components/ui/dialog";
|
||||
import type { MarkdownEditor } from "@/components/ui/markdown-editor";
|
||||
import type { Collection } from "@/types/collection";
|
||||
import { isApiError } from "@/utils/api";
|
||||
import { maxLengthValidator } from "@/utils/form";
|
||||
import LiteElement, { html } from "@/utils/LiteElement";
|
||||
|
||||
export type CollectionSavedEvent = CustomEvent<{
|
||||
id: string;
|
||||
@ -19,77 +27,37 @@ export type CollectionSavedEvent = CustomEvent<{
|
||||
*/
|
||||
@localized()
|
||||
@customElement("btrix-collection-metadata-dialog")
|
||||
export class CollectionMetadataDialog extends LiteElement {
|
||||
export class CollectionMetadataDialog extends BtrixElement {
|
||||
@property({ type: Object })
|
||||
collection?: Collection;
|
||||
|
||||
@property({ type: Boolean })
|
||||
open = false;
|
||||
|
||||
@state()
|
||||
isDialogVisible = false;
|
||||
|
||||
@state()
|
||||
private isSubmitting = false;
|
||||
|
||||
@query("btrix-markdown-editor")
|
||||
private readonly descriptionEditor?: MarkdownEditor | null;
|
||||
|
||||
@queryAsync("#collectionForm")
|
||||
private readonly form!: Promise<HTMLFormElement>;
|
||||
|
||||
private readonly validateNameMax = maxLengthValidator(50);
|
||||
|
||||
render() {
|
||||
return html` <btrix-dialog
|
||||
label=${this.collection
|
||||
? msg("Edit Collection Metadata")
|
||||
: msg("Create a New Collection")}
|
||||
?open=${this.open}
|
||||
@sl-show=${() => (this.isDialogVisible = true)}
|
||||
@sl-after-hide=${() => (this.isDialogVisible = false)}
|
||||
style="--width: 46rem"
|
||||
>
|
||||
<form id="collectionForm" @reset=${this.onReset} @submit=${this.onSubmit}>
|
||||
<sl-input
|
||||
class="with-max-help-text mb-2"
|
||||
id="collectionForm-name-input"
|
||||
name="name"
|
||||
label=${msg("Collection Name")}
|
||||
value=${this.collection?.name || ""}
|
||||
placeholder=${msg("My Collection")}
|
||||
autocomplete="off"
|
||||
required
|
||||
help-text=${this.validateNameMax.helpText}
|
||||
@sl-input=${this.validateNameMax.validate}
|
||||
autofocus
|
||||
></sl-input>
|
||||
|
||||
<fieldset>
|
||||
<label class="form-label">${msg("Description")}</label>
|
||||
<btrix-markdown-editor
|
||||
name="description"
|
||||
initialValue=${this.collection?.description || ""}
|
||||
maxlength=${4000}
|
||||
></btrix-markdown-editor>
|
||||
</fieldset>
|
||||
${when(
|
||||
!this.collection,
|
||||
() => html`
|
||||
<label>
|
||||
<sl-switch name="isPublic"
|
||||
>${msg("Publicly Accessible")}</sl-switch
|
||||
>
|
||||
<sl-tooltip
|
||||
content=${msg(
|
||||
"Enable public access to make Collections shareable. Only people with the shared link can view your Collection.",
|
||||
)}
|
||||
hoist
|
||||
@sl-hide=${this.stopProp}
|
||||
@sl-after-hide=${this.stopProp}
|
||||
><sl-icon
|
||||
class="ml-1 inline-block align-middle text-slate-500"
|
||||
name="info-circle"
|
||||
></sl-icon
|
||||
></sl-tooltip>
|
||||
</label>
|
||||
`,
|
||||
)}
|
||||
|
||||
<input class="invisible size-0" type="submit" />
|
||||
</form>
|
||||
${when(this.isDialogVisible, () => this.renderForm())}
|
||||
<div slot="footer" class="flex items-center justify-end gap-3">
|
||||
<sl-button
|
||||
class="mr-auto"
|
||||
@ -132,6 +100,57 @@ export class CollectionMetadataDialog extends LiteElement {
|
||||
</btrix-dialog>`;
|
||||
}
|
||||
|
||||
private renderForm() {
|
||||
return html`
|
||||
<form id="collectionForm" @reset=${this.onReset} @submit=${this.onSubmit}>
|
||||
<sl-input
|
||||
class="with-max-help-text mb-2"
|
||||
id="collectionForm-name-input"
|
||||
name="name"
|
||||
label=${msg("Collection Name")}
|
||||
value=${this.collection?.name || ""}
|
||||
placeholder=${msg("My Collection")}
|
||||
autocomplete="off"
|
||||
required
|
||||
help-text=${this.validateNameMax.helpText}
|
||||
@sl-input=${this.validateNameMax.validate}
|
||||
></sl-input>
|
||||
<sl-divider></sl-divider>
|
||||
<btrix-markdown-editor
|
||||
label=${msg("Description")}
|
||||
name="description"
|
||||
initialValue=${this.collection?.description || ""}
|
||||
maxlength=${4000}
|
||||
></btrix-markdown-editor>
|
||||
${when(
|
||||
!this.collection,
|
||||
() => html`
|
||||
<sl-divider></sl-divider>
|
||||
<label>
|
||||
<sl-switch name="isPublic"
|
||||
>${msg("Publicly Accessible")}</sl-switch
|
||||
>
|
||||
<sl-tooltip
|
||||
content=${msg(
|
||||
"Enable public access to make Collections shareable. Only people with the shared link can view your Collection.",
|
||||
)}
|
||||
hoist
|
||||
@sl-hide=${this.stopProp}
|
||||
@sl-after-hide=${this.stopProp}
|
||||
><sl-icon
|
||||
class="ml-1 inline-block align-middle text-slate-500"
|
||||
name="info-circle"
|
||||
></sl-icon
|
||||
></sl-tooltip>
|
||||
</label>
|
||||
`,
|
||||
)}
|
||||
|
||||
<input class="invisible size-0" type="submit" />
|
||||
</form>
|
||||
`;
|
||||
}
|
||||
|
||||
private async hideDialog() {
|
||||
void (await this.form).closest<Dialog>("btrix-dialog")!.hide();
|
||||
}
|
||||
@ -146,11 +165,16 @@ export class CollectionMetadataDialog extends LiteElement {
|
||||
|
||||
const form = event.target as HTMLFormElement;
|
||||
const nameInput = form.querySelector<SlInput>('sl-input[name="name"]');
|
||||
if (!nameInput?.checkValidity()) {
|
||||
if (
|
||||
!nameInput?.checkValidity() ||
|
||||
!this.descriptionEditor?.checkValidity()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { name, description, isPublic } = serialize(form);
|
||||
const { name, isPublic } = serialize(form);
|
||||
const description = this.descriptionEditor.value;
|
||||
|
||||
this.isSubmitting = true;
|
||||
try {
|
||||
const body = JSON.stringify({
|
||||
@ -164,7 +188,7 @@ export class CollectionMetadataDialog extends LiteElement {
|
||||
path = `/orgs/${this.orgId}/collections/${this.collection.id}`;
|
||||
method = "PATCH";
|
||||
}
|
||||
const data = await this.apiFetch<Collection>(path, {
|
||||
const data = await this.api.fetch<Collection>(path, {
|
||||
method,
|
||||
body,
|
||||
});
|
||||
@ -176,7 +200,7 @@ export class CollectionMetadataDialog extends LiteElement {
|
||||
},
|
||||
}) as CollectionSavedEvent,
|
||||
);
|
||||
this.notify({
|
||||
this.notify.toast({
|
||||
message: msg(
|
||||
str`Successfully saved "${data.name || name}" Collection.`,
|
||||
),
|
||||
@ -189,7 +213,7 @@ export class CollectionMetadataDialog extends LiteElement {
|
||||
if (message === "collection_name_taken") {
|
||||
message = msg("This name is already taken.");
|
||||
}
|
||||
this.notify({
|
||||
this.notify.toast({
|
||||
message: message || msg("Something unexpected went wrong"),
|
||||
variant: "danger",
|
||||
icon: "exclamation-octagon",
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { localized, msg, str } from "@lit/localize";
|
||||
import type { SlCheckbox } from "@shoelace-style/shoelace";
|
||||
import { html, nothing, type PropertyValues, type TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { customElement, property, query, state } from "lit/decorators.js";
|
||||
import { choose } from "lit/directives/choose.js";
|
||||
import { guard } from "lit/directives/guard.js";
|
||||
import { repeat } from "lit/directives/repeat.js";
|
||||
@ -53,6 +53,12 @@ export class CollectionDetail extends BtrixElement {
|
||||
@state()
|
||||
private showShareInfo = false;
|
||||
|
||||
@query(".description")
|
||||
private readonly description?: HTMLElement | null;
|
||||
|
||||
@query(".descriptionExpandBtn")
|
||||
private readonly descriptionExpandBtn?: HTMLElement | null;
|
||||
|
||||
// Use to cancel requests
|
||||
private getArchivedItemsController: AbortController | null = null;
|
||||
|
||||
@ -726,16 +732,19 @@ export class CollectionDetail extends BtrixElement {
|
||||
|
||||
private async checkTruncateDescription() {
|
||||
await this.updateComplete;
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
const description = this.querySelector<HTMLElement>(".description");
|
||||
if (description?.scrollHeight ?? 0 > (description?.clientHeight ?? 0)) {
|
||||
this.querySelector(".descriptionExpandBtn")?.classList.remove("hidden");
|
||||
if (
|
||||
this.description?.scrollHeight ??
|
||||
0 > (this.description?.clientHeight ?? 0)
|
||||
) {
|
||||
this.descriptionExpandBtn?.classList.remove("hidden");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private readonly toggleTruncateDescription = () => {
|
||||
const description = this.querySelector<HTMLElement>(".description");
|
||||
const description = this.description;
|
||||
if (!description) {
|
||||
console.debug("no .description");
|
||||
return;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { localized, msg, str } from "@lit/localize";
|
||||
import type { SlInput, SlMenuItem } from "@shoelace-style/shoelace";
|
||||
import Fuse from "fuse.js";
|
||||
import { type PropertyValues } from "lit";
|
||||
import { html, type PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { guard } from "lit/directives/guard.js";
|
||||
import { when } from "lit/directives/when.js";
|
||||
@ -10,6 +10,7 @@ import queryString from "query-string";
|
||||
|
||||
import type { SelectNewDialogEvent } from ".";
|
||||
|
||||
import { BtrixElement } from "@/classes/BtrixElement";
|
||||
import type { PageChangeEvent } from "@/components/ui/pagination";
|
||||
import type { CollectionSavedEvent } from "@/features/collections/collection-metadata-dialog";
|
||||
import { pageHeader } from "@/layouts/pageHeader";
|
||||
@ -17,7 +18,6 @@ import type { APIPaginatedList, APIPaginationQuery } from "@/types/api";
|
||||
import type { Collection, CollectionSearchValues } from "@/types/collection";
|
||||
import type { UnderlyingFunction } from "@/types/utils";
|
||||
import { isApiError } from "@/utils/api";
|
||||
import LiteElement, { html } from "@/utils/LiteElement";
|
||||
import { getLocale } from "@/utils/localization";
|
||||
import { tw } from "@/utils/tailwind";
|
||||
import noCollectionsImg from "~assets/images/no-collections-found.webp";
|
||||
@ -54,7 +54,7 @@ const MIN_SEARCH_LENGTH = 2;
|
||||
|
||||
@localized()
|
||||
@customElement("btrix-collections-list")
|
||||
export class CollectionsList extends LiteElement {
|
||||
export class CollectionsList extends BtrixElement {
|
||||
@property({ type: Boolean })
|
||||
isCrawler?: boolean;
|
||||
|
||||
@ -159,43 +159,51 @@ export class CollectionsList extends LiteElement {
|
||||
|
||||
<btrix-dialog
|
||||
.label=${msg("Delete Collection?")}
|
||||
.open=${this.openDialogName === "delete"}
|
||||
?open=${this.openDialogName === "delete"}
|
||||
@sl-hide=${() => (this.openDialogName = undefined)}
|
||||
@sl-after-hide=${() => (this.isDialogVisible = false)}
|
||||
>
|
||||
${msg(
|
||||
html`Are you sure you want to delete
|
||||
<strong>${this.selectedCollection?.name}</strong>?`,
|
||||
${when(
|
||||
this.isDialogVisible,
|
||||
() => html`
|
||||
${msg(
|
||||
html`Are you sure you want to delete
|
||||
<strong>${this.selectedCollection?.name}</strong>?`,
|
||||
)}
|
||||
<div slot="footer" class="flex justify-between">
|
||||
<sl-button
|
||||
size="small"
|
||||
@click=${() => (this.openDialogName = undefined)}
|
||||
>${msg("Cancel")}</sl-button
|
||||
>
|
||||
<sl-button
|
||||
size="small"
|
||||
variant="primary"
|
||||
@click=${async () => {
|
||||
await this.deleteCollection(this.selectedCollection!);
|
||||
this.openDialogName = undefined;
|
||||
}}
|
||||
>${msg("Delete Collection")}</sl-button
|
||||
>
|
||||
</div>
|
||||
`,
|
||||
)}
|
||||
<div slot="footer" class="flex justify-between">
|
||||
<sl-button
|
||||
size="small"
|
||||
@click=${() => (this.openDialogName = undefined)}
|
||||
>${msg("Cancel")}</sl-button
|
||||
>
|
||||
<sl-button
|
||||
size="small"
|
||||
variant="primary"
|
||||
@click=${async () => {
|
||||
await this.deleteCollection(this.selectedCollection!);
|
||||
this.openDialogName = undefined;
|
||||
}}
|
||||
>${msg("Delete Collection")}</sl-button
|
||||
>
|
||||
</div>
|
||||
</btrix-dialog>
|
||||
<btrix-collection-metadata-dialog
|
||||
.collection=${this.openDialogName === "create"
|
||||
? undefined
|
||||
: this.selectedCollection}
|
||||
?open=${this.openDialogName === "create" ||
|
||||
this.openDialogName === "editMetadata"}
|
||||
.collection=${
|
||||
this.openDialogName === "create" ? undefined : this.selectedCollection
|
||||
}
|
||||
?open=${
|
||||
this.openDialogName === "create" ||
|
||||
this.openDialogName === "editMetadata"
|
||||
}
|
||||
@sl-hide=${() => (this.openDialogName = undefined)}
|
||||
@sl-after-hide=${() => (this.selectedCollection = undefined)}
|
||||
@btrix-collection-saved=${(e: CollectionSavedEvent) => {
|
||||
if (this.openDialogName === "create") {
|
||||
this.navTo(
|
||||
`${this.orgBasePath}/collections/view/${e.detail.id}/items`,
|
||||
this.navigate.to(
|
||||
`${this.navigate.orgBasePath}/collections/view/${e.detail.id}/items`,
|
||||
);
|
||||
} else {
|
||||
void this.fetchCollections();
|
||||
@ -511,8 +519,8 @@ export class CollectionsList extends LiteElement {
|
||||
<btrix-table-cell rowClickTarget="a">
|
||||
<a
|
||||
class="block truncate py-2"
|
||||
href=${`${this.orgBasePath}/collections/view/${col.id}`}
|
||||
@click=${this.navLink}
|
||||
href=${`${this.navigate.orgBasePath}/collections/view/${col.id}`}
|
||||
@click=${this.navigate.link}
|
||||
>
|
||||
${col.name}
|
||||
</a>
|
||||
@ -638,7 +646,7 @@ export class CollectionsList extends LiteElement {
|
||||
});
|
||||
|
||||
private async onTogglePublic(coll: Collection, isPublic: boolean) {
|
||||
await this.apiFetch(`/orgs/${this.orgId}/collections/${coll.id}`, {
|
||||
await this.api.fetch(`/orgs/${this.orgId}/collections/${coll.id}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify({ isPublic }),
|
||||
});
|
||||
@ -665,7 +673,7 @@ export class CollectionsList extends LiteElement {
|
||||
private async deleteCollection(collection: Collection): Promise<void> {
|
||||
try {
|
||||
const name = collection.name;
|
||||
await this.apiFetch(
|
||||
await this.api.fetch(
|
||||
`/orgs/${this.orgId}/collections/${collection.id}`,
|
||||
// FIXME API method is GET right now
|
||||
{
|
||||
@ -676,13 +684,13 @@ export class CollectionsList extends LiteElement {
|
||||
this.selectedCollection = undefined;
|
||||
void this.fetchCollections();
|
||||
|
||||
this.notify({
|
||||
this.notify.toast({
|
||||
message: msg(html`Deleted <strong>${name}</strong> Collection.`),
|
||||
variant: "success",
|
||||
icon: "check2-circle",
|
||||
});
|
||||
} catch {
|
||||
this.notify({
|
||||
this.notify.toast({
|
||||
message: msg("Sorry, couldn't delete Collection at this time."),
|
||||
variant: "danger",
|
||||
icon: "exclamation-octagon",
|
||||
@ -692,7 +700,7 @@ export class CollectionsList extends LiteElement {
|
||||
|
||||
private async fetchSearchValues() {
|
||||
try {
|
||||
const searchValues: CollectionSearchValues = await this.apiFetch(
|
||||
const searchValues: CollectionSearchValues = await this.api.fetch(
|
||||
`/orgs/${this.orgId}/collections/search-values`,
|
||||
);
|
||||
const names = searchValues.names;
|
||||
@ -719,7 +727,7 @@ export class CollectionsList extends LiteElement {
|
||||
if (isApiError(e)) {
|
||||
this.fetchErrorStatusCode = e.statusCode;
|
||||
} else {
|
||||
this.notify({
|
||||
this.notify.toast({
|
||||
message: msg("Sorry, couldn't retrieve Collections at this time."),
|
||||
variant: "danger",
|
||||
icon: "exclamation-octagon",
|
||||
@ -745,7 +753,7 @@ export class CollectionsList extends LiteElement {
|
||||
},
|
||||
);
|
||||
|
||||
const data = await this.apiFetch<APIPaginatedList<Collection>>(
|
||||
const data = await this.api.fetch<APIPaginatedList<Collection>>(
|
||||
`/orgs/${this.orgId}/collections?${query}`,
|
||||
);
|
||||
|
||||
|
@ -70,9 +70,6 @@ export default {
|
||||
"@shoelace-style/shoelace/dist/themes/light.css": fileURLToPath(
|
||||
new URL("./src/__mocks__/_empty.js", import.meta.url),
|
||||
),
|
||||
"@wysimark/standalone": fileURLToPath(
|
||||
new URL("./src/__mocks__/_empty.js", import.meta.url),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
1074
frontend/yarn.lock
1074
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user