tags-input:

* add a "copy" button
* fix some css class (adding a prefix to avoid side effects)
* other minor fixes
This commit is contained in:
John Livingston 2024-06-13 11:55:02 +02:00
parent 591e0ad3fd
commit 618dc6aeae
No known key found for this signature in database
GPG Key ID: B17B5640CE66CDBC
6 changed files with 257 additions and 48 deletions

View File

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
SPDX-License-Identifier: AGPL-3.0-only

150
assets/images/copy.svg Normal file
View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
viewBox="0 0 4.2333332 4.2333334"
version="1.1"
id="svg1428"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="copy.svg"
inkscape:export-xdpi="9.6000004"
inkscape:export-ydpi="9.6000004"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs1422">
<linearGradient
id="linearGradient5158"
inkscape:swatch="solid">
<stop
style="stop-color:#7d7d7d;stop-opacity:1;"
offset="0"
id="stop5156" />
</linearGradient>
<filter
style="color-interpolation-filters:sRGB"
inkscape:label="Drop Shadow"
id="filter1150">
<feFlood
flood-opacity="1"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood1140" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite1142" />
<feGaussianBlur
in="composite1"
stdDeviation="0.2"
result="blur"
id="feGaussianBlur1144" />
<feOffset
dx="0"
dy="0"
result="offset"
id="feOffset1146" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite1148" />
</filter>
<filter
style="color-interpolation-filters:sRGB"
inkscape:label="Drop Shadow"
id="filter1174">
<feFlood
flood-opacity="1"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood1164" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite1166" />
<feGaussianBlur
in="composite1"
stdDeviation="0.2"
result="blur"
id="feGaussianBlur1168" />
<feOffset
dx="0"
dy="0"
result="offset"
id="feOffset1170" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite1172" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="32"
inkscape:cx="5.375"
inkscape:cy="9.15625"
inkscape:document-units="px"
inkscape:current-layer="g1910"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="1842"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="1082"
inkscape:window-maximized="1"
units="px"
showguides="true"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" />
<metadata
id="metadata1425">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g1910"
transform="matrix(0.45207703,0,0,0.45207703,-0.52758049,1.2946618)"
style="stroke-width:1.00021;stroke-miterlimit:4;stroke-dasharray:none">
<path
id="rect1876-6"
style="opacity:0.998;fill:none;fill-opacity:1;stroke:#deddda;stroke-width:1.17052;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 4.0844838,4.0458314 3.467999,4.0611713 2.8228277,4.0569294 C 2.3008575,4.0534975 1.8806228,3.6367061 1.8806228,3.1147245 v -4.3981761 c 0,-0.5219815 0.4202234,-0.9422049 0.9422049,-0.9422049 H 7.221004 c 0.5219815,0 0.9422049,0.4202234 0.9422049,0.9422049 l -0.00609,0.33403788 -0.079441,0.96263648"
sodipodi:nodetypes="ccsssssscc" />
<path
id="rect1876-6-3"
style="opacity:0.998;fill:none;fill-opacity:1;stroke:#deddda;stroke-width:1.17052;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 8.4339763,5.8496964 c -0.421831,0.00883 -1.3377873,0.00925 -1.7597066,0.011098 -0.7330224,0.00321 -2.1990882,0 -2.1990882,0 -0.5219815,0 -0.9422049,-0.4202233 -0.9422049,-0.9422049 V 0.52041339 c 0,-0.5219815 0.4202234,-0.9422049 0.9422049,-0.9422049 H 8.873358 c 0.5219815,0 0.9422055,0.4202234 0.9422055,0.9422049 0,0 0.00454,1.46607281 0,2.19908821 -0.00259,0.4185566 -0.018291,1.2519554 -0.00588,1.6703359 0.02409,0.811826 -0.382054,1.4390656 -1.375711,1.4598589 z"
sodipodi:nodetypes="sssssssssss" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -88,7 +88,8 @@ livechat-help-button {
input[type="reset"],
button[type="submit"],
button[type="reset"],
.peertube-plugin-livechat-configuration-actions button {
.peertube-plugin-livechat-configuration-actions button,
.peertube-plugin-livechat-tags-input-copy {
// Peertube rounded-line-height-1-5 mixins
line-height: $button-calc-line-height;
@ -104,7 +105,8 @@ livechat-help-button {
input[type="submit"],
button[type="submit"],
.peertube-plugin-livechat-configuration-actions button {
.peertube-plugin-livechat-configuration-actions button,
.peertube-plugin-livechat-tags-input-copy {
// Peertube orange-button mixin
&,
&:active,
@ -331,6 +333,13 @@ livechat-tags-input {
flex-wrap: wrap;
max-width: 320px;
.livechat-tags-container {
align-items: center;
display: flex;
flex-flow: row;
justify-content: space-between;
}
// stylelint trying to rearrange unrelated root blocks because of smaller blocks in them
/* stylelint-disable-next-line no-descending-specificity */
input {
@ -347,46 +356,50 @@ livechat-tags-input {
}
}
#tags,
#tags-searched {
.livechat-tags,
.livechat-tags-searched {
display: flex;
flex-wrap: wrap;
padding: 0;
margin: var(--tag-padding-vertical) 0;
max-height: 150px;
overflow-y: auto;
border-bottom: 1px dashed var(--greyForegroundColor);
transition-property: height, margin;
transition-duration: 0.3s;
&.empty {
height: 0;
margin: 0;
border: none;
}
@supports (scrollbar-width: auto) {
scrollbar-color: var(--greyForegroundColor) transparent;
scrollbar-width: thin;
}
}
#tags-searched {
.livechat-tags-container,
.livechat-tags-searched {
border-bottom: 1px dashed var(--greyForegroundColor);
&.livechat-empty {
height: 0;
margin: 0;
border: none;
}
}
.livechat-tags-searched {
&::after {
content: "\1F50D";
flex-grow: 1;
text-align: right;
}
&.empty {
&.livechat-empty {
&::after {
display: none;
}
}
}
.tag,
.tag-searched {
.livechat-tag,
.livechat-tag-searched {
width: auto;
height: 24px;
display: flex;
@ -400,7 +413,7 @@ livechat-tags-input {
margin: 0 3px 3px 0;
transition: 0.3s filter;
.tag-close {
.livechat-tag-close {
display: block;
width: 12px;
height: 12px;
@ -424,7 +437,7 @@ livechat-tags-input {
color: #fff;
background-color: var(--mainColor);
.tag-close {
.livechat-tag-close {
color: var(--mainColor);
}
}
@ -433,7 +446,7 @@ livechat-tags-input {
color: #fff;
background-color: var(--mainHoverColor);
.tag-close {
.livechat-tag-close {
color: var(--mainHoverColor);
}
}
@ -444,12 +457,12 @@ livechat-tags-input {
color: #fff;
background-color: var(--inputBorderColor);
.tag-close {
.livechat-tag-close {
color: var(--inputBorderColor);
}
}
.tag-name {
.livechat-tag-name {
margin-top: 3px;
text-overflow: ellipsis;
overflow: hidden;
@ -458,7 +471,7 @@ livechat-tags-input {
}
}
#tags.unfocused .tag {
.livechat-tags.livechat-unfocused .livechat-tag {
filter: opacity(50%) grayscale(80%);
}
}

View File

@ -23,6 +23,7 @@ declare const LOC_SHOW_SCROLLBARR: string
declare const LOC_TRANSPARENT_BACKGROUND: string
declare const LOC_TIPS_FOR_STREAMERS: string
declare const LOC_COPY: string
declare const LOC_COPIED: string
declare const LOC_LINK_COPIED: string
declare const LOC_ERROR: string
declare const LOC_OPEN: string

View File

@ -2,14 +2,30 @@
//
// SPDX-License-Identifier: AGPL-3.0-only
import { html } from 'lit'
import { customElement, property, state } from 'lit/decorators.js'
import { LivechatElement } from './livechat'
import { ptTr } from '../directives/translation'
import { html } from 'lit'
import { unsafeHTML } from 'lit/directives/unsafe-html.js'
import { customElement, property, state } from 'lit/decorators.js'
import { ifDefined } from 'lit/directives/if-defined.js'
import { classMap } from 'lit/directives/class-map.js'
import { animate, fadeOut, fadeIn } from '@lit-labs/motion'
import { repeat } from 'lit/directives/repeat.js'
// FIXME: find a better way to store this image.
// This content comes from the file assets/images/copy.svg, after svgo cleaning.
// To get the formated content, you can do:
// xmllint dist/client/images/copy.svg --format
// Then replace the main color by «currentColor»
const copySVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 4.233 4.233">
<g style="stroke-width:1.00021;stroke-miterlimit:4;stroke-dasharray:none">` +
// eslint-disable-next-line max-len
'<path style="opacity:.998;fill:none;fill-opacity:1;stroke:currentColor;stroke-width:1.17052;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m4.084 4.046-.616.015-.645-.004a.942.942 0 0 1-.942-.942v-4.398a.94.94 0 0 1 .942-.943H7.22a.94.94 0 0 1 .942.943l-.006.334-.08.962" transform="matrix(.45208 0 0 .45208 -.528 1.295)"/>' +
// eslint-disable-next-line max-len
'<path style="opacity:.998;fill:none;fill-opacity:1;stroke:currentColor;stroke-width:1.17052;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M8.434 5.85c-.422.009-1.338.009-1.76.01-.733.004-2.199 0-2.199 0a.94.94 0 0 1-.942-.941V.52a.94.94 0 0 1 .942-.942h4.398a.94.94 0 0 1 .943.942s.004 1.466 0 2.2c-.003.418-.019 1.251-.006 1.67.024.812-.382 1.439-1.376 1.46z" transform="matrix(.45208 0 0 .45208 -.528 1.295)"/>' +
`</g>
</svg>`
@customElement('livechat-tags-input')
export class TagsInputElement extends LivechatElement {
@property({ attribute: false })
@ -71,30 +87,55 @@ export class TagsInputElement extends LivechatElement {
}
protected override render = (): unknown => {
return html`<ul
id="tags"
class=${classMap({
empty: !this.value.length,
unfocused: this._searchedTagsIndex.length
})}>
${repeat(this.value, tag => tag,
(tag, index) => html`<li key=${index} class="tag" title=${tag} ${animate({
keyframeOptions: {
duration: this.animDuration,
fill: 'both'
},
in: fadeIn,
out: fadeOut
})}>
<span class='tag-name'>${tag}</span>
<span class='tag-close'
@click=${() => this._handleDeleteTag(index)}></span>
</li>`
)}
</ul>
<ul id="tags-searched" class=${classMap({ empty: !this._searchedTagsIndex.length })}>
return html`
<div class=${classMap({
'livechat-empty': !this.value.length,
'livechat-tags-container': true
})}
>
<ul
class=${classMap({
'livechat-empty': !this.value.length,
'livechat-unfocused': this._searchedTagsIndex.length,
'livechat-tags': true
})}>
${repeat(this.value, tag => tag,
(tag, index) => html`<li key=${index} class="livechat-tag" title=${tag} ${animate({
keyframeOptions: {
duration: this.animDuration,
fill: 'both'
},
in: fadeIn,
out: fadeOut
})}>
<span class='livechat-tag-name'>${tag}</span>
<span class='livechat-tag-close'
@click=${() => this._handleDeleteTag(index)}></span>
</li>`
)}
</ul>
${
this.value?.length === 0
? ''
: html`<button
type="button"
class="peertube-plugin-livechat-tags-input-copy"
title=${ptTr(LOC_COPY) as any}
@click=${async (ev: Event) => {
ev.preventDefault()
await navigator.clipboard.writeText(this.value.join(this.separator))
this.ptNotifier.success(await this.ptTranslate(LOC_COPIED))
}}
>${unsafeHTML(copySVG)}</button>`
}
</div>
<ul class=${classMap({
'livechat-empty': !this._searchedTagsIndex.length,
'livechat-tags-searched': true
})}
>
${repeat(this._searchedTagsIndex, index => index,
(index) => html`<li key=${index} class="tag-searched" title=${this.value[index]} ${animate({
(index) => html`<li key=${index} class="livechat-tag-searched" title=${this.value[index]} ${animate({
keyframeOptions: {
duration: this.animDuration,
fill: 'both'
@ -102,8 +143,8 @@ export class TagsInputElement extends LivechatElement {
in: fadeIn,
out: fadeOut
})}>
<span class='tag-name'>${this.value[index]}</span>
<span class='tag-close'
<span class='livechat-tag-name'>${this.value[index]}</span>
<span class='livechat-tag-close'
@click=${() => this._handleDeleteTag(index)}>
</span>
</li>`

View File

@ -12,6 +12,7 @@ transparent_background: "Transparent background (for stream integration, with OB
tips_for_streamers: "Tips for streamers: To add the chat to your OBS, generate a read-only
link and use it as a browser source."
copy: "Copy"
copied: "Copied"
link_copied: "Link copied"
error: "Error"
open: "Open"