Collection share dialog + copy updates (#1056)
- Always shows primary "Share" action button in Collection detail page. - Enables toggling shareable status and share info from dialog. Difference from mockups: I made the "Done" button neutral do differentiate from our submit action buttons in the dialog, since toggling will apply changes immediately. - Menu item: "Go to Public View"/"Go to Shareable View" -> "Visit Shareable URL". - Toggle label: "Make Collection Shareable" -> "Collection is Shareable". - Additional dialog copy: adds "This collection can be viewed by anyone with the link." under "Link to Share" and "Share this collection by embedding it into an existing webpage." under "Embed Collection". - Moves share status icon to its own column in list view. - Adds new syntax-highlighted code component that supports js and html. Co-authored-by: Henry Wilkinson <henry@wilkinson.graphics>
This commit is contained in:
parent
de3e5907a7
commit
b494070e43
@ -32,6 +32,7 @@
|
||||
"eslint-webpack-plugin": "^3.1.1",
|
||||
"fuse.js": "^6.5.3",
|
||||
"glob": "^8.1.0",
|
||||
"highlight.js": "^11.8.0",
|
||||
"html-loader": "^3.0.1",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"immutable": "^4.1.0",
|
||||
|
56
frontend/src/components/code.ts
Normal file
56
frontend/src/components/code.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
import { html as staticHtml, unsafeStatic } from "lit/static-html.js";
|
||||
import hljs from "highlight.js/lib/core";
|
||||
import javascript from "highlight.js/lib/languages/javascript";
|
||||
import xml from "highlight.js/lib/languages/xml";
|
||||
|
||||
/**
|
||||
* Syntax highlighting for javascript and HTML (XML)
|
||||
*/
|
||||
export class Code extends LitElement {
|
||||
static styles = [
|
||||
css`
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
font-family: var(--sl-font-mono);
|
||||
font-size: 0.9em;
|
||||
color: #24292e;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hljs-name {
|
||||
color: #22863a;
|
||||
}
|
||||
|
||||
.hljs-attr {
|
||||
color: #6f42c1;
|
||||
}
|
||||
|
||||
.hljs-string {
|
||||
color: #032f62;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@property({ type: String })
|
||||
value: string = "";
|
||||
|
||||
@property({ type: String })
|
||||
language: "javascript" | "xml" = "xml";
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
hljs.registerLanguage("javascript", javascript);
|
||||
hljs.registerLanguage("xml", xml);
|
||||
}
|
||||
|
||||
render() {
|
||||
const htmlStr = hljs.highlight(this.value, {
|
||||
language: this.language,
|
||||
}).value;
|
||||
return html`<pre><code>${staticHtml`${unsafeStatic(
|
||||
htmlStr
|
||||
)}`}</code></pre>`;
|
||||
}
|
||||
}
|
@ -150,6 +150,9 @@ import("./file-list").then(({ FileList, FileListItem }) => {
|
||||
import("./collections-add").then(({ CollectionsAdd }) => {
|
||||
customElements.define("btrix-collections-add", CollectionsAdd);
|
||||
});
|
||||
import("./code").then(({ Code }) => {
|
||||
customElements.define("btrix-code", Code);
|
||||
});
|
||||
|
||||
customElements.define("btrix-alert", Alert);
|
||||
customElements.define("btrix-input", Input);
|
||||
|
@ -5,6 +5,7 @@ import { when } from "lit/directives/when.js";
|
||||
import { guard } from "lit/directives/guard.js";
|
||||
import queryString from "query-string";
|
||||
import type { TemplateResult } from "lit";
|
||||
import type { SlCheckbox } from "@shoelace-style/shoelace";
|
||||
|
||||
import type { AuthState } from "../../utils/AuthService";
|
||||
import LiteElement, { html } from "../../utils/LiteElement";
|
||||
@ -52,7 +53,7 @@ export class CollectionDetail extends LiteElement {
|
||||
private isDescriptionExpanded = false;
|
||||
|
||||
@state()
|
||||
private showEmbedInfo = false;
|
||||
private showShareInfo = false;
|
||||
|
||||
// Use to cancel requests
|
||||
private getArchivedItemsController: AbortController | null = null;
|
||||
@ -109,15 +110,14 @@ export class CollectionDetail extends LiteElement {
|
||||
html`<sl-skeleton class="w-96"></sl-skeleton>`}
|
||||
</h1>
|
||||
</div>
|
||||
${when(
|
||||
this.collection?.isPublic,
|
||||
() => html`
|
||||
<sl-button size="small" @click=${() => (this.showEmbedInfo = true)}>
|
||||
<sl-icon name="code-slash"></sl-icon>
|
||||
View Embed Code
|
||||
</sl-button>
|
||||
`
|
||||
)}
|
||||
<sl-button
|
||||
variant="primary"
|
||||
size="small"
|
||||
@click=${() => (this.showShareInfo = true)}
|
||||
>
|
||||
<sl-icon name="box-arrow-up" slot="prefix"></sl-icon>
|
||||
Share
|
||||
</sl-button>
|
||||
${when(this.isCrawler, this.renderActions)}
|
||||
</header>
|
||||
<div class="border rounded-lg py-2 mb-3">${this.renderInfoBar()}</div>
|
||||
@ -159,7 +159,7 @@ export class CollectionDetail extends LiteElement {
|
||||
>
|
||||
</div>
|
||||
</btrix-dialog>
|
||||
${this.renderShareInfo()}`;
|
||||
${this.renderShareDialog()}`;
|
||||
}
|
||||
|
||||
private getPublicReplayURL() {
|
||||
@ -169,34 +169,83 @@ export class CollectionDetail extends LiteElement {
|
||||
).href;
|
||||
}
|
||||
|
||||
private renderShareInfo = () => {
|
||||
if (!this.collection?.isPublic) {
|
||||
return;
|
||||
}
|
||||
private renderShareDialog() {
|
||||
return html`
|
||||
<btrix-dialog
|
||||
label=${msg("Share Collection")}
|
||||
?open=${this.showShareInfo}
|
||||
@sl-request-close=${() => (this.showShareInfo = false)}
|
||||
style="--width: 32rem;"
|
||||
>
|
||||
${this.collection?.isPublic
|
||||
? ""
|
||||
: html`<p class="mb-3">
|
||||
${msg(
|
||||
"Make this collection shareable to enable a public viewing link."
|
||||
)}
|
||||
</p>`}
|
||||
<div class="mb-5">
|
||||
<sl-switch
|
||||
?checked=${this.collection?.isPublic}
|
||||
@sl-change=${(e: CustomEvent) =>
|
||||
this.onTogglePublic((e.target as SlCheckbox).checked)}
|
||||
>${msg("Collection is Shareable")}</sl-switch
|
||||
>
|
||||
</div>
|
||||
${when(this.collection?.isPublic, this.renderShareInfo)}
|
||||
<div slot="footer" class="flex justify-end">
|
||||
<sl-button size="small" @click=${() => (this.showShareInfo = false)}
|
||||
>${msg("Done")}</sl-button
|
||||
>
|
||||
</div>
|
||||
</btrix-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
const embedCode = `<replay-web-page source="${this.getPublicReplayURL()}"></replay-web-page>`;
|
||||
private renderShareInfo = () => {
|
||||
const replaySrc = this.getPublicReplayURL();
|
||||
const publicReplayUrl = `https://replayweb.page?source=${replaySrc}`;
|
||||
const embedCode = `<replay-web-page source="${replaySrc}"></replay-web-page>`;
|
||||
const importCode = `importScripts("https://replayweb.page/sw.js");`;
|
||||
|
||||
return html` <btrix-dialog
|
||||
label=${msg(str`Embed Code for “${this.collection?.name}”`)}
|
||||
?open=${this.showEmbedInfo}
|
||||
@sl-request-close=${() => (this.showEmbedInfo = false)}
|
||||
>
|
||||
<div class="text-left">
|
||||
<p class="mb-5">
|
||||
return html` <btrix-section-heading
|
||||
>${msg("Link to Share")}</btrix-section-heading
|
||||
>
|
||||
<section class="mt-3 mb-5">
|
||||
<p class="mb-3">
|
||||
${msg("This collection can be viewed by anyone with the link.")}
|
||||
</p>
|
||||
<div class="flex items-center rounded border">
|
||||
<div class="text-base">
|
||||
<btrix-copy-button
|
||||
.getValue=${() => publicReplayUrl}
|
||||
content=${msg("Copy Public URL")}
|
||||
></btrix-copy-button>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0 truncate">${publicReplayUrl}</div>
|
||||
<div class="text-base">
|
||||
<sl-icon-button
|
||||
href=${publicReplayUrl}
|
||||
name="box-arrow-up-right"
|
||||
target="_blank"
|
||||
>
|
||||
</sl-icon-button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<btrix-section-heading>${msg("Embed Collection")}</btrix-section-heading>
|
||||
<section class="mt-3">
|
||||
<p class="mb-3">
|
||||
${msg(
|
||||
html`Embed this collection in another site using these
|
||||
<strong class="font-medium">ReplayWeb.page</strong> code snippets.`
|
||||
html`Share this collection by embedding it into an existing webpage.`
|
||||
)}
|
||||
</p>
|
||||
<p class="mb-3">
|
||||
${msg(html`Add the following embed code to your HTML page:`)}
|
||||
</p>
|
||||
<div class="relative">
|
||||
<pre
|
||||
class="whitespace-pre-wrap mb-5 rounded p-4 bg-slate-50 text-slate-600 text-[0.9em]"
|
||||
><code>${embedCode}</code></pre>
|
||||
<div class="absolute top-0 right-0">
|
||||
<div class="relative mb-5 border rounded p-3 pr-9 bg-slate-50">
|
||||
<btrix-code value=${embedCode}></btrix-code>
|
||||
<div class="absolute top-1.5 right-1.5 border rounded bg-white shadow-sm">
|
||||
<btrix-copy-button
|
||||
.getValue=${() => embedCode}
|
||||
content=${msg("Copy Embed Code")}
|
||||
@ -205,15 +254,13 @@ export class CollectionDetail extends LiteElement {
|
||||
</div>
|
||||
<p class="mb-3">
|
||||
${msg(
|
||||
html`Add the following JavaScript to
|
||||
<code class="text-[0.9em]">./replay/sw.js</code>:`
|
||||
html`Add the following JavaScript to your
|
||||
<code class="text-[0.9em]">/replay/sw.js</code>:`
|
||||
)}
|
||||
</p>
|
||||
<div class="relative">
|
||||
<pre
|
||||
class="whitespace-pre-wrap mb-5 rounded p-4 bg-slate-50 text-slate-600 text-[0.9em]"
|
||||
><code>${importCode}</code></pre>
|
||||
<div class="absolute top-0 right-0">
|
||||
<div class="relative mb-5 border rounded p-3 pr-9 bg-slate-50">
|
||||
<btrix-code language="javascript" value=${importCode}></btrix-code>
|
||||
<div class="absolute top-1.5 right-1.5 border rounded bg-white shadow-sm">
|
||||
<btrix-copy-button
|
||||
.getValue=${() => importCode}
|
||||
content=${msg("Copy JS")}
|
||||
@ -233,8 +280,7 @@ export class CollectionDetail extends LiteElement {
|
||||
for more details.`
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</btrix-dialog>`;
|
||||
</section>`;
|
||||
};
|
||||
|
||||
private renderHeader = () => html`
|
||||
@ -314,7 +360,7 @@ export class CollectionDetail extends LiteElement {
|
||||
slot="prefix"
|
||||
href="https://replayweb.page?source=${this.getPublicReplayURL()}"
|
||||
>
|
||||
Go to Public View
|
||||
Visit Shareable URL
|
||||
</a>
|
||||
</sl-menu-item>
|
||||
<sl-menu-item
|
||||
|
@ -369,10 +369,16 @@ export class CollectionsList extends LiteElement {
|
||||
return html`
|
||||
<header class="py-2 text-neutral-600 leading-none">
|
||||
<div
|
||||
class="hidden md:grid md:grid-cols-[repeat(2,1fr)_repeat(3,12ch)_16ch_2.5rem] gap-3"
|
||||
class="hidden md:grid md:grid-cols-[2rem_1fr_repeat(3,12ch)_18ch_2.5rem] gap-3"
|
||||
>
|
||||
<div class="col-span-1 text-xs pl-3">${msg("Collection Name")}</div>
|
||||
<div class="col-span-1 text-xs">${msg("Top 3 Tags")}</div>
|
||||
<div class="col-span-1 pl-3 text-center">
|
||||
<sl-icon
|
||||
class="block text-[15px]"
|
||||
name="eye"
|
||||
label=${msg("Collection share access")}
|
||||
></sl-icon>
|
||||
</div>
|
||||
<div class="col-span-1 text-xs">${msg("Name")}</div>
|
||||
<div class="col-span-1 text-xs">${msg("Archived Items")}</div>
|
||||
<div class="col-span-1 text-xs">${msg("Total Size")}</div>
|
||||
<div class="col-span-1 text-xs">${msg("Total Pages")}</div>
|
||||
@ -446,42 +452,38 @@ export class CollectionsList extends LiteElement {
|
||||
html`<li class="mb-2 last:mb-0">
|
||||
<div class="block border rounded leading-none">
|
||||
<div
|
||||
class="relative p-3 md:p-0 grid grid-cols-1 md:grid-cols-[repeat(2,1fr)_repeat(3,12ch)_16ch_2.5rem] gap-3 lg:h-10 items-center"
|
||||
class="relative p-3 md:p-0 grid grid-cols-1 md:grid-cols-[2rem_1fr_repeat(3,12ch)_18ch_2.5rem] gap-3 lg:h-10 items-center"
|
||||
>
|
||||
<div class="col-span-1 md:pl-3 truncate font-semibold">
|
||||
<div class="col-span-1 md:pl-3 text-base text-neutral-500">
|
||||
${col?.isPublic
|
||||
? html`
|
||||
<sl-tooltip content=${msg("Shareable")}>
|
||||
<sl-icon
|
||||
class="inline-block align-middle"
|
||||
name="people-fill"
|
||||
label=${msg("Shareable Collection")}
|
||||
></sl-icon>
|
||||
</sl-tooltip>
|
||||
`
|
||||
: html`
|
||||
<sl-tooltip content=${msg("Private")}>
|
||||
<sl-icon
|
||||
class="inline-block align-middle"
|
||||
name="eye-slash-fill"
|
||||
label=${msg("Private Collection")}
|
||||
></sl-icon>
|
||||
</sl-tooltip>
|
||||
`}
|
||||
</div>
|
||||
<div class="col-span-1 truncate font-semibold">
|
||||
<a
|
||||
href=${`/orgs/${this.orgId}/collections/view/${col.id}`}
|
||||
class="block text-primary hover:text-indigo-500"
|
||||
@click=${this.navLink}
|
||||
>
|
||||
${col?.isPublic
|
||||
? html`
|
||||
<sl-tooltip content=${msg("Shareable")}>
|
||||
<sl-icon
|
||||
style="margin-right: 4px; vertical-align: bottom; font-size: 14px;"
|
||||
name="people-fill"
|
||||
></sl-icon>
|
||||
</sl-tooltip>
|
||||
`
|
||||
: html`
|
||||
<sl-tooltip content=${msg("Private")}>
|
||||
<sl-icon
|
||||
style="margin-right: 4px; vertical-align: bottom; font-size: 14px;"
|
||||
name="eye-slash-fill"
|
||||
></sl-icon>
|
||||
</sl-tooltip>
|
||||
`}
|
||||
${col.name}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-span-1 order-last md:order-none truncate">
|
||||
${col.tags
|
||||
.slice(0, 3)
|
||||
.map(
|
||||
(tag) =>
|
||||
html`<btrix-tag class="mr-1" size="small">${tag}</btrix-tag>`
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
class="col-span-1 truncate text-xs text-neutral-500 font-monostyle"
|
||||
>
|
||||
@ -560,7 +562,7 @@ export class CollectionsList extends LiteElement {
|
||||
col
|
||||
)}"
|
||||
>
|
||||
Go to Shared View
|
||||
Visit Shareable URL
|
||||
</a>
|
||||
</sl-menu-item>
|
||||
<sl-menu-item
|
||||
|
@ -3720,6 +3720,11 @@ he@^1.2.0:
|
||||
resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
|
||||
highlight.js@^11.8.0:
|
||||
version "11.8.0"
|
||||
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.8.0.tgz#966518ea83257bae2e7c9a48596231856555bb65"
|
||||
integrity sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg==
|
||||
|
||||
hoist-non-react-statics@^3.3.1:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||
|
Loading…
Reference in New Issue
Block a user