Merge branch 'main' into weblate-peertube-livechat-peertube-plugin-livechat

This commit is contained in:
John Livingston 2025-05-21 10:51:41 +02:00 committed by GitHub
commit fdff085b37
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 268 additions and 45 deletions

View File

@ -1,5 +1,17 @@
# Changelog
## ??? (Not Released Yet)
### Minor changes and fixes
* Fix #329: auto focus message field after anonymous user has entered nickname.
* Fix #392: add draggable items touch screen handling
* Fix #506: hide offline users by default in occupant list
* Fix #547: add button to go to the end of the chat
* Fix #503: set custom emojis max height to text height + bigger when posted alone
* Fix: Converse bottom panel messages not visible on new Peertube v7 theme (for example for muted users)
* Fix #75: New short video urls makes it difficult to use the settings «Activate chat for these videos».
## 12.0.4
### Minor changes and fixes

View File

@ -1,3 +1,4 @@
SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
SPDX-FileCopyrightText: 2025 Mehdi Benadel <https://mehdibenadel.com>
SPDX-License-Identifier: AGPL-3.0-only

View File

@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
// SPDX-FileCopyrightText: 2025 Nicolas Chesnais <https://autre.space>
//
// SPDX-License-Identifier: AGPL-3.0-only
@ -11,6 +12,7 @@ import './styles/draggables.scss'
*/
export class DraggablesCustomElement extends CustomElement {
currentDragged = null
droppableEl = null
/**
* The tag name for draggable elements.
@ -37,6 +39,9 @@ export class DraggablesCustomElement extends CustomElement {
this._handleDragLeaveBinded = this._handleDragLeave.bind(this)
this._handleDragEndBinded = this._handleDragEnd.bind(this)
this._handleDropBinded = this._handleDrop.bind(this)
this._handleTouchStartBinded = this._handleTouchStart.bind(this)
this._handleTouchMoveBinded = this._handleTouchMove.bind(this)
this._handleTouchEndBinded = this._handleTouchEnd.bind(this)
return super.initialize()
}
@ -44,21 +49,29 @@ export class DraggablesCustomElement extends CustomElement {
connectedCallback () {
super.connectedCallback()
this.currentDragged = null
this.droppableEl = null
this.addEventListener('dragstart', this._handleDragStartBinded)
this.addEventListener('dragover', this._handleDragOverBinded)
this.addEventListener('dragleave', this._handleDragLeaveBinded)
this.addEventListener('dragend', this._handleDragEndBinded)
this.addEventListener('drop', this._handleDropBinded)
this.addEventListener('touchstart', this._handleTouchStartBinded)
this.addEventListener('touchmove', this._handleTouchMoveBinded)
this.addEventListener('touchend', this._handleTouchEndBinded)
}
disconnectedCallback () {
super.disconnectedCallback()
this.currentDragged = null
this.droppableEl = null
this.removeEventListener('dragstart', this._handleDragStartBinded)
this.removeEventListener('dragover', this._handleDragOverBinded)
this.removeEventListener('dragleave', this._handleDragLeaveBinded)
this.removeEventListener('dragend', this._handleDragEndBinded)
this.removeEventListener('drop', this._handleDropBinded)
this.removeEventListener('touchstart', this._handleTouchStartBinded)
this.removeEventListener('touchmove', this._handleTouchMoveBinded)
this.removeEventListener('touchend', this._handleTouchEndBinded)
}
_isADraggableEl (target) {
@ -69,8 +82,7 @@ export class DraggablesCustomElement extends CustomElement {
return target.closest?.(this.droppableTagNames.join(','))
}
_isOnTopHalf (ev, el) {
const y = ev.clientY
_isOnTopHalf (y, el) {
const bounding = el.getBoundingClientRect()
return (y <= bounding.y + (bounding.height / 2))
}
@ -81,13 +93,39 @@ export class DraggablesCustomElement extends CustomElement {
)
}
_setCurrentDragged (draggedEl) {
console.log('[livechat drag&drop] Starting to drag a ' + this.draggableTagName + '...')
this.currentDragged = draggedEl
this._resetDropOver()
}
_setDroppableClasses (droppableEl, y) {
// Are we on the top or bottom part of the droppableEl?
let topHalf = false
if (!this.droppableAlwaysBottomTagNames.includes(droppableEl.nodeName.toLowerCase())) {
topHalf = this._isOnTopHalf(y, droppableEl)
}
droppableEl.classList.add(topHalf ? 'livechat-drag-top-half' : 'livechat-drag-bottom-half')
droppableEl.classList.remove(topHalf ? 'livechat-drag-bottom-half' : 'livechat-drag-top-half')
}
_tryToDrop (droppedOnEl) {
if (!droppedOnEl) return
console.log('[livechat drag&drop] ' + this.draggableTagName + ' dropped...')
try {
this._dropDone(this.currentDragged, droppedOnEl, droppedOnEl.classList.contains('livechat-drag-top-half'))
} catch (err) {
console.error(err)
}
this._resetDropOver()
}
_handleDragStart (ev) {
// The draggable=true is on a child bode
const possibleEl = ev.target.parentElement
if (!this._isADraggableEl(possibleEl)) { return }
console.log('[livechat drag&drop] Starting to drag a ' + this.draggableTagName + '...')
this.currentDragged = possibleEl
this._resetDropOver()
this._setCurrentDragged(possibleEl)
}
_handleDragOver (ev) {
@ -97,14 +135,7 @@ export class DraggablesCustomElement extends CustomElement {
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/drop_event says we should preventDefault
ev.preventDefault()
// Are we on the top or bottom part of the droppableEl?
let topHalf = false
if (!this.droppableAlwaysBottomTagNames.includes(droppableEl.nodeName.toLowerCase())) {
topHalf = this._isOnTopHalf(ev, droppableEl)
}
droppableEl.classList.add(topHalf ? 'livechat-drag-top-half' : 'livechat-drag-bottom-half')
droppableEl.classList.remove(topHalf ? 'livechat-drag-bottom-half' : 'livechat-drag-top-half')
this._setDroppableClasses(droppableEl, ev.clientY)
}
_handleDragLeave (ev) {
@ -124,16 +155,33 @@ export class DraggablesCustomElement extends CustomElement {
let droppedOnEl = document.querySelector('.livechat-drag-bottom-half, .livechat-drag-top-half')
droppedOnEl = this._getParentDroppableEl(droppedOnEl)
if (!droppedOnEl) { return }
this._tryToDrop(droppedOnEl)
}
console.log('[livechat drag&drop] ' + this.draggableTagName + ' dropped...')
_handleTouchStart (ev) {
const possibleEl = this._getParentDroppableEl(ev.target)
if (!possibleEl) return
this._setCurrentDragged(possibleEl)
}
try {
this._dropDone(this.currentDragged, droppedOnEl, droppedOnEl.classList.contains('livechat-drag-top-half'))
} catch (err) {
console.error(err)
}
_handleTouchMove (ev) {
if (!this.currentDragged) return
const { clientX, clientY } = ev.touches[0]
const droppableEl = this._getParentDroppableEl(document.elementFromPoint(clientX, clientY))
if (!droppableEl || droppableEl === this.droppableEl) return
this.droppableEl = droppableEl
this._resetDropOver()
this._setDroppableClasses(droppableEl, clientY)
}
_handleTouchEnd (_ev) {
if (!this.currentDragged || !this.droppableEl) return
this._tryToDrop(this.droppableEl)
this.currentDragged = null
this.droppableEl = null
}
/**

View File

@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
* SPDX-FileCopyrightText: 2025 Nicolas Chesnais <https://autre.space>
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -16,4 +17,8 @@
.livechat-drag-top-half > .draggables-line {
border-top: 4px solid blue;
}
[draggable="true"] {
touch-action: none;
}
}

View File

@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
* SPDX-FileCopyrightText: 2025 Nicolas Chesnais <https://autre.space>
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -149,11 +150,17 @@
background-color: var(--peertube-grey-background) !important;
}
// Changing size for emojis, to have bigger custom emojis
// Resize custom emojis to text height
img.emoji {
width: unset !important;
height: unset !important;
max-height: 3em !important; // and no max-width
max-height: 1.5em !important; // and no max-width
vertical-align: -0.45em !important;
}
// Trick to enlarge a single custom emoji with no text in message
&[text^=":"][text$=":"] img.emoji:only-child {
max-height: 2.5em !important;
}
// underline links in chat messages

View File

@ -227,6 +227,21 @@ body.converse-embedded {
.occupants {
width: 100%;
// Put occupants filters items on a single line
converse-list-filter form > div {
display: flex;
align-items: center;
.filter-by {
margin-right: 4px;
}
// Let search input take the whole width when displayed
.btn-group:has(+ select.hidden) {
width: 100%;
}
}
}
}
}
@ -264,3 +279,13 @@ body.converse-embedded {
justify-content: normal !important;
}
}
/* stylelint-disable-next-line no-duplicate-selectors */
.conversejs {
converse-muc {
.muc-bottom-panel, converse-muc-bottom-panel {
// Fixing a color (Converse use a hardcoded "white", which does not work with Peertube v7 new theme)
color: var(--peertube-menu-foreground) !important;
}
}
}

View File

@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
// SPDX-FileCopyrightText: 2025 Nicolas Chesnais <https://autre.space>
//
// SPDX-License-Identifier: AGPL-3.0-only
@ -12,7 +13,7 @@ import tplMucBottomPanel from '../../src/plugins/muc-views/templates/muc-bottom-
import { CustomElement } from 'shared/components/element.js'
import 'shared/modals/livechat-external-login.js'
async function setNickname (ev, model) {
async function setNicknameAndFocus (ev, model) {
ev.preventDefault()
const nick = ev.target.nick.value.trim()
if (!nick) {
@ -22,6 +23,7 @@ async function setNickname (ev, model) {
_converse.api.trigger('livechatViewerModeSetNickname', model, nick, {
synchronous: true
})
document.querySelector('.chat-textarea')?.focus()
}
class SlowMode extends CustomElement {
@ -100,6 +102,54 @@ const tplEmojiOnly = (o) => {
</div>`
}
class BackToLastMsg extends CustomElement {
static get properties () {
return {
jid: { type: String }
}
}
show = false
async connectedCallback () {
super.connectedCallback()
this.model = _converse.chatboxes.get(this.jid)
await this.model.initialized
let scrolled = this.model.ui.get('scrolled')
let hasUnreadMsg = this.model.get('num_unread_general') > 0
this.listenTo(this.model.ui, 'change:scrolled', () => {
scrolled = this.model.ui.get('scrolled')
this.show = scrolled && !hasUnreadMsg
this.requestUpdate()
})
this.listenTo(this.model, 'change:num_unread_general', () => {
hasUnreadMsg = this.model.get('num_unread_general') > 0
// Do not show the element if there is new messages since there is another element for that
this.show = scrolled && !hasUnreadMsg
this.requestUpdate()
})
}
onClick (ev) {
ev?.preventDefault()
const chatContainer = document.querySelector('converse-chat-content')
chatContainer?.scrollTo({ top: chatContainer.scrollHeight })
}
render () {
return this.show
? html`<div class="livechat-back-to-last-msg new-msgs-indicator" @click=${this.onClick}>
${
// eslint-disable-next-line no-undef
__(LOC_back_to_last_msg)
}
</div>`
: ''
}
}
api.elements.define('livechat-back-to-last-msg', BackToLastMsg)
const tplViewerMode = (o) => {
if (!api.settings.get('livechat_enable_viewer_mode')) {
return html``
@ -112,7 +162,7 @@ const tplViewerMode = (o) => {
const i18nExternalLogin = __(LOC_login_using_external_account)
return html`
<div class="livechat-viewer-mode-content chatroom-form-container">
<form class="converse-form chatroom-form" @submit=${ev => setNickname(ev, model)}>
<form class="converse-form chatroom-form" @submit=${ev => setNicknameAndFocus(ev, model)}>
<label>${i18nHeading}</label>
<fieldset>
<input type="text"
@ -163,6 +213,7 @@ export default (o) => {
${tplViewerMode(o)}
${tplSlowMode(o)}
${tplEmojiOnly(o)}
<livechat-back-to-last-msg jid=${o.model.get('jid')}></livechat-back-to-last-msg>
${
mutedAnonymousMessage
? html`<span class="muc-bottom-panel muc-bottom-panel--muted">${mutedAnonymousMessage}</span>`

View File

@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
// SPDX-FileCopyrightText: 2025 Nicolas Chesnais <https://autre.space>
//
// SPDX-License-Identifier: AGPL-3.0-only
@ -53,6 +54,11 @@ function getToolbarButtons (this: any, toolbarEl: any, buttons: any[]): any {
toolbarEl.model.save({
hidden_occupants: !toolbarEl.model.get('hidden_occupants')
})
// Hide offline occupants by default
const sideBarEl = document.querySelector('converse-muc-sidebar') as unknown as any
sideBarEl?.model.set('filter_visible', true)
sideBarEl?.filter.set('type', 'state')
}}>
${icon}
</button>`

View File

@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
// SPDX-FileCopyrightText: 2025 Nicolas Chesnais <https://autre.space>
//
// SPDX-License-Identifier: AGPL-3.0-only
@ -68,7 +69,8 @@ const locKeys = [
'announcements_message_type_standard',
'announcements_message_type_announcement',
'announcements_message_type_highlight',
'announcements_message_type_warning'
'announcements_message_type_warning',
'back_to_last_msg'
]
module.exports = locKeys

View File

@ -149,7 +149,7 @@ all_non_lives_description: "If checked, the chat will be enabled for all video t
videos_list_label: "Activate chat for these videos"
videos_list_description: |
Videos UUIDs for which we want a web chat.
Videos UUIDs for which we want a web chat (short UUID or UUIDv4).
Can be non-live videos. One per line. <br />
You can add comments: everything after the # character will be stripped off, and empty lines ignored.<br />
Don't add private videos, the UUIDs will be sent to the frontend.
@ -682,3 +682,4 @@ converse_theme_warning_description: |
It is strongly recommanded to keep the "Peertube theme", in combinaison with the "Automatic color detection" feature.
Otherwise some user may experience issues depending on the Peertube theme they use.
</span>
back_to_last_msg: Go back to last message

View File

@ -152,7 +152,7 @@ prosody_muc_expiration_description: "Aquí puede elegir cuánto tiempo el servid
durante 1 <b>año</b>. Puede reemplazar 1 por cualquier valor entero.</li>\n \
\ <li><b>nunca</b>: el contenido nunca caducará y se mantendrá para siempre.</li>\n\
</ul>\n"
videos_list_description: "UUIDs de los videos para los cuales se desea un chat web.\n
videos_list_description: "UUIDs de los videos para los cuales se desea un chat web (corto UUID o UUIDv4).\n
Pueden ser videos no en vivo. Una por línea. <br />\nPuede agregar comentarios:
todo lo que este detras del caracter # no sera interpretado y las líneas vacías
ignoradas. <br />\nNo agregue videos privados, los UUID se enviarán al frontend.\n"

View File

@ -111,7 +111,7 @@ all_non_lives_description: "Si coché, il y aura un tchat pour toutes les vidéo
videos_list_label: "Activer le tchat pour ces vidéos"
videos_list_description: |
Mettez ici les UUIDs des vidéos pour lesquelles vous voulez forcer l'activation du tchat.
Mettez ici les UUIDs des vidéos pour lesquelles vous voulez forcer l'activation du tchat (UUID court ou UUIDv4).
Cela peut être des directs, ou non. Un UUID par ligne. <br />
Vous pouvez ajouter des commentaires: tout ce qui se trouve après le caractère # sera retiré, et les lignes vides ignorées. <br />
N'ajoutez pas de vidéos privées, les UUIDs fuiteraient.
@ -664,3 +664,4 @@ converse_theme_warning_description: "<span class=\"peertube-plugin-livechat-warn
la fonctionnalité «Détection automatique des couleurs».\n Sinon certain⋅es utilisateur⋅rices
pourraient rencontrer des problèmes selon le thème qu'iels utilisent pour Peertube.\n\
</span>\n"
back_to_last_msg: Retourner au dernier message

72
package-lock.json generated
View File

@ -17,6 +17,7 @@
"http-proxy": "^1.18.1",
"log-rotate": "^0.2.8",
"openid-client": "^5.7.1",
"short-uuid": "^5.2.0",
"validate-color": "^2.2.4",
"xmppjs-chat-bot": "^0.5.0"
},
@ -3250,6 +3251,20 @@
"node": "14 || >=16.14"
}
},
"node_modules/@peertube/peertube-types/node_modules/short-uuid": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-4.2.2.tgz",
"integrity": "sha512-IE7hDSGV2U/VZoCsjctKX6l5t5ak2jE0+aeGJi3KtvjIUNuZVmHVYUjNBhmo369FIWGDtaieRaO8A83Lvwfpqw==",
"dev": true,
"license": "MIT",
"dependencies": {
"any-base": "^1.1.0",
"uuid": "^8.3.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@rtsao/scc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@ -5138,8 +5153,7 @@
"node_modules/any-base": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz",
"integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==",
"dev": true
"integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="
},
"node_modules/anymatch": {
"version": "3.1.2",
@ -11637,16 +11651,29 @@
}
},
"node_modules/short-uuid": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-4.2.0.tgz",
"integrity": "sha512-r3cxuPPZSuF0QkKsK9bBR7u+7cwuCRzWzgjPh07F5N2iIUNgblnMHepBY16xgj5t1lG9iOP9k/TEafY1qhRzaw==",
"dev": true,
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-5.2.0.tgz",
"integrity": "sha512-296/Nzi4DmANh93iYBwT4NoYRJuHnKEzefrkSagQbTH/A6NTaB68hSPDjm5IlbI5dx9FXdmtqPcj6N5H+CPm6w==",
"license": "MIT",
"dependencies": {
"any-base": "^1.1.0",
"uuid": "^8.3.2"
"uuid": "^9.0.1"
},
"engines": {
"node": ">=8"
"node": ">=14"
}
},
"node_modules/short-uuid/node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/side-channel": {
@ -15504,6 +15531,16 @@
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.2.tgz",
"integrity": "sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==",
"dev": true
},
"short-uuid": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-4.2.2.tgz",
"integrity": "sha512-IE7hDSGV2U/VZoCsjctKX6l5t5ak2jE0+aeGJi3KtvjIUNuZVmHVYUjNBhmo369FIWGDtaieRaO8A83Lvwfpqw==",
"dev": true,
"requires": {
"any-base": "^1.1.0",
"uuid": "^8.3.2"
}
}
}
},
@ -16987,8 +17024,7 @@
"any-base": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz",
"integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==",
"dev": true
"integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="
},
"anymatch": {
"version": "3.1.2",
@ -21663,13 +21699,19 @@
"dev": true
},
"short-uuid": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-4.2.0.tgz",
"integrity": "sha512-r3cxuPPZSuF0QkKsK9bBR7u+7cwuCRzWzgjPh07F5N2iIUNgblnMHepBY16xgj5t1lG9iOP9k/TEafY1qhRzaw==",
"dev": true,
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-5.2.0.tgz",
"integrity": "sha512-296/Nzi4DmANh93iYBwT4NoYRJuHnKEzefrkSagQbTH/A6NTaB68hSPDjm5IlbI5dx9FXdmtqPcj6N5H+CPm6w==",
"requires": {
"any-base": "^1.1.0",
"uuid": "^8.3.2"
"uuid": "^9.0.1"
},
"dependencies": {
"uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="
}
}
},
"side-channel": {

View File

@ -1,3 +1,4 @@
SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
SPDX-FileCopyrightText: 2025 Mehdi Benadel <https://mehdibenadel.com>
SPDX-License-Identifier: AGPL-3.0-only

View File

@ -34,6 +34,7 @@
"http-proxy": "^1.18.1",
"log-rotate": "^0.2.8",
"openid-client": "^5.7.1",
"short-uuid": "^5.2.0",
"validate-color": "^2.2.4",
"xmppjs-chat-bot": "^0.5.0"
},

View File

@ -1,3 +1,4 @@
SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
SPDX-FileCopyrightText: 2025 Mehdi Benadel <https://mehdibenadel.com>
SPDX-License-Identifier: AGPL-3.0-only

View File

@ -2,7 +2,7 @@
"extends": "@tsconfig/node16/tsconfig.json",
"compilerOptions": {
"noImplicitReturns": true,
"noImplicitOverride": true,
"noImplicitOverride": true,
"noUnusedLocals": true,
"removeComments": true,
"sourceMap": true,

View File

@ -1,16 +1,35 @@
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
// SPDX-FileCopyrightText: 2025 Mehdi Benadel <https://mehdibenadel.com>
//
// SPDX-License-Identifier: AGPL-3.0-only
const short = require('short-uuid')
const translator = short()
function shortToUUID (shortUUID: string): string {
if (!shortUUID) return shortUUID
return translator.toUUID(shortUUID)
}
function isShortUUID (value: string): boolean {
if (!value) return false
return value.length === translator.maxLength
}
function parseConfigUUIDs (s: string): string[] {
if (!s) {
return []
}
let a = s.split('\n')
a = a.map(line => {
return line.replace(/#.*$/, '')
line = line.replace(/#.*$/, '')
.replace(/^\s+/, '')
.replace(/\s+$/, '')
return isShortUUID(line) ? shortToUUID(line) : line
})
return a.filter(line => line !== '')
}