2024-01-01 14:42:50 +01:00
<!DOCTYPE html>
2024-01-02 19:23:14 +01:00
< meta charset = "UTF-8" / >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" / >
<!--
< script src = "../../../SpaccDotWeb/SpaccDotWeb.js" module = "SpaccDotWeb" > < / script >
2024-01-01 14:42:50 +01:00
< script src = "//SpaccInc.gitlab.io/SpaccDotWeb/SpaccDotWeb.js" module = "SpaccDotWeb" > < / script >
2024-01-02 19:23:14 +01:00
-->
< script src = "../../../SpaccDotWeb/SpaccDotWeb.Alt.js" module = "SpaccDotWeb" > < / script >
< script src = "//SpaccInc.gitlab.io/SpaccDotWeb/SpaccDotWeb.Alt.js" module = "SpaccDotWeb" > < / script >
2024-01-01 19:11:18 +01:00
< script src = "https://googlechrome.github.io/dialog-polyfill/dist/dialog-polyfill.js" > < / script >
2024-01-02 19:23:14 +01:00
< link rel = "stylesheet" href = "./paper.min.css" / >
2024-01-01 14:42:50 +01:00
< script module = "Meta" > ( {
Name: "🃏️ [Matrix] Sticker Helper",
})< / script >
< script module = "Main" type = "module" >
2024-01-02 19:23:14 +01:00
//import { html, render } from 'https://esm.sh/htm/preact/standalone';
//import axios from 'https://cdn.skypack.dev/axios';
2024-01-01 19:11:18 +01:00
2024-01-02 19:23:14 +01:00
const Spacc = SpaccDotWeb.AppInit();
2024-01-01 14:42:50 +01:00
const State = {};
const Defaults = {
2024-01-01 19:11:18 +01:00
stickerSelectorUrl: "https://maunium.net/stickers-demo/",
2024-01-01 14:42:50 +01:00
appIdentity: "org.eu.octt.MatrixStickerHelper",
appInterface: "v1",
2024-01-02 19:23:14 +01:00
Strings: {
mustInitMessage: `Your account must first be initialized to handle stickers.`,
notManagedMessage: `
Your account is set-up with a sticker picker, but it's not marked as being managed by this app.
This could mean that you are currently using an incompatible sticker picker.
You can try to continue anyway if you think it should work, otherwise you should reinitialize sticker data.
`,
}
2024-01-01 14:42:50 +01:00
};
let Config = localStorage.getItem('SpaccInc-Matrix-Config');
if (Config) Config = JSON.parse(Config);
else Config = {
accounts: [],
};
Config.Save = () => localStorage.setItem('SpaccInc-Matrix-Config', JSON.stringify(Config));
2024-01-02 19:23:14 +01:00
/*
2024-01-01 14:42:50 +01:00
function ActionForm (props) {
let formElems = [];
for (const action of props.actionEntries) {
action.options ||= [];
formElems.push(html`
< button
style=${{ textAlign: 'left', width: `calc(100% - ${action.options.length * 3}em)`, display: 'inline-block' }}
dataIndex=${action.dataIndex}
onclick=${action.onclick}
disabled=${action.disabled}
2024-01-01 19:11:18 +01:00
name=${action.name}
2024-01-01 14:42:50 +01:00
>
${action.label}
< / button >
`)
for (const option of action.options) {
formElems.push(html`
< button
style=${{ width: '3em', display: 'inline-block' }}
dataIndex=${option.dataIndex}
onclick=${option.onclick}
>
${option.label}
< / button >
`)
}
}
return html`< div class = "ActionForm" >
${formElems}
< / div > `
}
function TableForm (props) {
let tableRows = [];
for (const entry of props.formEntries) {
tableRows.push(html`< tr >
< td >
${entry.type !== 'button' ? entry.label : ''}
< / td >
< td style = ${{ width: ' 100 % ' } } >
< input
style=${{ width: '100%' }}
name=${entry.name || entry.label.toLowerCase()}
type=${entry.type}
placeholder=${entry.placeholder || entry.value}
value=${entry.type !== 'button' ? entry.value : (entry.value || entry.label)}
onclick=${entry.onclick || entry.onInteract}
onselect=${entry.onInteract}
onchange=${entry.onInteract}
oninput=${entry.onInteract}
onpaste=${entry.onInteract}
/>
< / td >
< / tr > `)
}
}
function ClickImg (props) {
return html`< div class = "ClickImg" >
< img
src=${props.src}
onclick=${() => {
for (const openImg of document.querySelectorAll('.ClickImg[data-opened="1"]')) {
if (openImg !== this.base ) {
openImg.dataset.opened = '0';
}
}
const isOpened = this.base.dataset.opened;
if (isOpened === '1') {
this.base.dataset.opened = '0';
} else {
this.base.dataset.opened = '1';
}
}}
/>
2024-01-01 19:11:18 +01:00
< ${SecureButton}
2024-01-01 14:42:50 +01:00
name="delete"
2024-01-01 19:11:18 +01:00
modalText='❌️ Confirm delete?'
onclick=${() => {
alert(1)
}}
2024-01-01 14:42:50 +01:00
>
❌️
2024-01-01 19:11:18 +01:00
< /${SecureButton}>
2024-01-01 14:42:50 +01:00
< / div > `
}
2024-01-01 19:11:18 +01:00
async function TryMatrixLoginAndSaveAndUse (loginData) {
try {
const response = await axios.post(`${loginData.homeserver}/_matrix/client/v3/login`, {
2024-01-01 14:42:50 +01:00
type: "m.login.password",
identifier: {
type: "m.id.user",
user: loginData.username,
},
password: loginData.password,
2024-01-01 19:11:18 +01:00
});
State.account = {
homeserver: loginData.homeserver,
username: loginData.username,
token: response.data.access_token,
};
Config.accounts.push(State.account);
Config.Save();
Render(CollectionScreen);
} catch(err) {
alert(`Error trying to get a session token: ${err.response.request.response}`);
document.querySelector('#AccountLoginForm input[name="loginSave"]').disabled = false;
}
2024-01-01 14:42:50 +01:00
}
2024-01-01 19:11:18 +01:00
async function DataWidgetSaveRequest (configUrl) {
2024-01-01 14:42:50 +01:00
document.querySelector('input[name="reinit"]').disabled = true;
document.querySelector('button[name="commit"]').disabled = true;
State.dataWidgets.stickerpicker = {
content: {
type: "m.stickerpicker",
2024-01-01 19:11:18 +01:00
url: `${document.querySelector('input[name="sticker selector url"]').value}?& config=${configUrl || ''}& theme=$theme`,
2024-01-01 14:42:50 +01:00
name: "Stickerpicker",
creatorUserId: `${GetMatrixUserTag(State.account)}`,
managedBy: [
`${Defaults.appIdentity}/${Defaults.appInterface}`,
],
},
sender: `${GetMatrixUserTag(State.account)}`,
state_key: "stickerpicker",
type: "m.widget",
id: "stickerpicker",
};
2024-01-01 19:11:18 +01:00
try {
const response = await axios.put(`${State.account.homeserver}/_matrix/client/v3/user/${GetMatrixUserTag(State.account)}/account_data/m.widgets`, State.dataWidgets, {
headers: { "Authorization": `Bearer ${State.account.token}` },
});
console.log(response.data);
Render(CollectionScreen);
} catch(err) {
alert(`Error updating account widget data: ${err.response.request.response}`);
document.querySelector('button[name="commit"]').disabled = false;
}
document.querySelector('input[name="reinit"]').disabled = false;
2024-01-01 14:42:50 +01:00
}
function AccountChoice (props) {
let accountsActions = [];
for (let i=0; i< Config.accounts.length ; i + + ) {
let account = Config.accounts[i];
2024-01-01 19:11:18 +01:00
2024-01-01 14:42:50 +01:00
accountsActions.push({
label: `🐱️ ${GetMatrixUserTag(account)}`,
onclick: () => {
State.account = account;
Render(CollectionScreen);
},
options: [ {
dataIndex: i,
label: '❌️',
onclick: () => {
Config.accounts.pop(this.dataIndex);
Config.Save();
Render();
},
} ]
})
}
return html`
< ${ActionForm} actionEntries=${[...accountsActions,
{ label: '🆕️ Add new account', onclick: () => Render(AccountLogin) },
]}/>
`
}
function AccountLogin (props) {
return html`< Fragment >
< p >
Please login with your [Matrix] account.
(Your login details are processed locally and only sent to the homeserver you specified.)
< / p >
< ${TableForm} id="AccountLoginForm" formEntries=${[
{ label: 'Homeserver', type: 'text', value: 'https://matrix.org' },
{ label: 'Username', type: 'text', placeholder: 'AzureDiamond' },
{ label: 'Password', type: 'password', placeholder: '*******' },
{ label: 'Import via session token', type: 'checkbox' },
{ label: '🆕️ Login and Save', type: 'button', onclick: () => {
this.base.querySelector('input[name="loginSave"]').disabled = true;
const password = this.base.querySelector('input[name="password"]').value;
let loginData = {
homeserver: this.base.querySelector('input[name="homeserver"]').value,
username: this.base.querySelector('input[name="username"]').value,
};
if (this.base.querySelector('input[name="import via session token"]').checked) {
loginData.token = password;
Config.accounts.push(loginData);
Config.Save();
Render(CollectionScreen);
} else {
loginData.password = password;
TryMatrixLoginAndSaveAndUse(loginData);
}
}, name: 'loginSave' },
...(Config.accounts.length > 0
? [{ label: '✖️ Cancel', type: 'button', onclick: () => Render() }]
: [])
]}/>
< / Fragment > `
}
function CollectionList (props) {
2024-01-01 19:11:18 +01:00
let packElems = [];
for (const pack of (props.extras?.dataPacks?.packs || [])) {
packElems.push(html`
< img
src="${props.extras?.dataPacks?.homeserver_url}/_matrix/media/r0/thumbnail/${pack.stickers[0].info.thumbnail_url.split('mxc://')[1]}?& height=128& width=128& method=scale"
onclick=${null}
/>
`);
}
return html`< ${CollectionViewMenu} optionsOpen=${false}>
2024-01-01 14:42:50 +01:00
< div id = "CollectionList" > < div >
< button style = ${{ verticalAlign: ' top ' , height: ' 64px ' } } >
2024-01-01 19:11:18 +01:00
➕ ️ Create new pack
2024-01-01 14:42:50 +01:00
< / button >
2024-01-01 19:11:18 +01:00
${packElems}
2024-01-01 14:42:50 +01:00
< / div > < / div >
< ${CollectionGrid}/>
2024-01-01 19:11:18 +01:00
< /${CollectionViewMenu}>`
2024-01-01 14:42:50 +01:00
}
function CollectionGrid (props) {
return html`< div id = "CollectionGrid" >
< ${ClickImg} src="https://http.cat/100"/>
< ${ClickImg} src="https://http.cat/101"/>
< ${ClickImg} src="https://http.cat/102"/>
< ${ClickImg} src="https://http.cat/103"/>
< ${ClickImg} src="https://http.cat/200"/>
< ${ClickImg} src="https://http.cat/201"/>
< ${ClickImg} src="https://http.cat/202"/>
< ${ClickImg} src="https://http.cat/203"/>
< ${ClickImg} src="https://http.cat/204"/>
< button
style=${{ verticalAlign: 'top', width: '128px', height: '128px' }}
onclick=${() => this.base.querySelector('button > input[type="file"]').click()}
>
2024-01-01 19:11:18 +01:00
➕ ️ Upload new
< br / > < br / >
< small > (Only image files supported for now)< / small >
2024-01-01 14:42:50 +01:00
< input
style=${{ display: 'none' }}
type="file"
accept="image/jpeg,image/gif,image/png,image/webp"
multiple
onchange=${(data) => {
console.log(data.target.files);
document.querySelector('button[name="commit"]').disabled = false;
}}
/>
< / button >
< / div > `
}
2024-01-01 19:11:18 +01:00
function CollectionViewMenu (props) {
return html`< Fragment class = "CollectionViewMenu" >
2024-01-01 14:42:50 +01:00
< ${ActionForm} actionEntries=${[
{ label: '🔙️ Go Back', onclick: () => Render() },
2024-01-01 19:11:18 +01:00
{ name: 'commit', label: '📝️ Commit Changes', disabled: true, onclick: () => DataWidgetSaveRequest(packsUrl) },
2024-01-01 14:42:50 +01:00
]}/>
2024-01-01 19:11:18 +01:00
${props.children}
< details open = ${props.optionsOpen} >
2024-01-01 14:42:50 +01:00
< summary >
< h3 style = ${{ display: ' inline ' } } >
Options
< / h3 >
< / summary >
< p >
Reinitializing sticker data for your account will simply override
the < code > stickerpicker< / code > subfield in its < code > m.widgets< / code > field.
< / p >
< ${TableForm} formEntries=${[
2024-01-01 19:11:18 +01:00
{ name: 'reinit', label: '💡️ Reinitialize sticker data as new', type: 'button', onclick: () => DataWidgetSaveRequest(this.base.querySelector('input[name="import json from url"]').value) },
2024-01-01 14:42:50 +01:00
{ label: 'or', type: 'hidden' },
{ label: 'Import JSON from URL', placeholder: 'https://example.com', type: 'text', onInteract: () => {
const thisElem = this.base.querySelector('input[name="import json from url"]');
const button = this.base.querySelector('input[name="reinit"]');
if (thisElem.value === '') {
button.value = '💡️ Reinitialize sticker data as new';
} else {
button.value = '💡️ Reinitialize sticker packs from URL';
}
} },
]}/>
< details open = ${false} >
< summary >
< h4 style = ${{ display: ' inline ' } } >
Advanced
< / h4 >
< / summary >
< ${TableForm} formEntries=${[
{ label: 'Sticker selector URL', value: Defaults.stickerSelectorUrl, type: 'text' },
]}/>
< / details >
< / details >
< / Fragment > `
}
2024-01-02 19:23:14 +01:00
*/
2024-01-01 14:42:50 +01:00
2024-01-02 19:23:14 +01:00
const $ = (query, ...params) => (query
? document.querySelector(Array.isArray(query)
? (params.length > 0
? params.map((a, i) => `${query[i]}${a}`).join('')
: query.join(''))
: query)
: document);
2024-01-01 14:42:50 +01:00
2024-01-02 19:23:14 +01:00
const GetMatrixUserTag = (account) => `@${account.username}:${account.homeserver.split('://')[1]}`;
2024-01-01 19:11:18 +01:00
2024-01-02 19:23:14 +01:00
const GetMatrixMediaUrl = (mxcId, homeserverUrl) => `${homeserverUrl}/_matrix/media/r0/thumbnail/${mxcId?.split('mxc://')[1]}`;
2024-01-01 19:11:18 +01:00
2024-01-02 19:23:14 +01:00
function ResetLayouts () {
$`#Main`.hidden = false;
for (const id of ['LayoutInfo', 'LayoutAccountSelect', 'LayoutPacksList', 'LayoutPackGrid']) {
$`#${id}`.innerHTML = '';
}
for (const id of ['LayoutAccountLogin', 'LayoutCollectionActions', 'LayoutCollectionOptions']) {
$`#${id}`.hidden = true;
}
for (const id of ['LayoutCollectionOptions']) {
$`#${id}`.open = false;
}
}
2024-01-01 19:11:18 +01:00
2024-01-02 19:23:14 +01:00
function RegisterLayouts () {
$`#LayoutCollectionActions button[name="back"]`.onclick = () => DisplayAccountSelectLogin();
$`#LayoutCollectionOptions input[name="pickerUrl"]`.value = Defaults.stickerSelectorUrl;
}
2024-01-01 19:11:18 +01:00
2024-01-02 19:23:14 +01:00
async function RequestAccountWidgetsData (account, postData) {
const request = await fetch(`${account.homeserver}/_matrix/client/v3/user/${GetMatrixUserTag(account)}/account_data/m.widgets`, {
method: (postData ? "PUT" : "GET"),
headers: { Authorization: `Bearer ${account.token}` },
body: JSON.stringify(postData),
});
const result = await request.json();
if (request.status === 200 || result.errcode === 'M_NOT_FOUND') {
return result;
} else {
Spacc.ShowModal(`Error: ${JSON.stringify(result)}`);
}
2024-01-01 14:42:50 +01:00
}
2024-01-02 19:23:14 +01:00
async function RequestUploadFile (account, fileData, fileMime) {
const request = await fetch(`${account.homeserver}/_matrix/client/v3/upload`, {
method: "POST",
headers: { Authorization: `Bearer ${account.token}`, "Content-Type": (fileData.type || fileMime) },
body: fileData,
});
const result = await request.json();
if (request.status === 200) {
return result;
} else {
Spacc.ShowModal(`Error: ${JSON.stringify(result)}`);
}
}
2024-01-01 19:11:18 +01:00
2024-01-02 19:23:14 +01:00
async function PreparePacksEditor (account) {
ResetLayouts();
$`#LayoutCollectionActions`.hidden = false;
$`#LayoutCollectionOptions`.hidden = false;
$`#LayoutCollectionActions button[name="commit"]`.onclick = () => Spacc.ShowModal({ label: 'Confirm committing account data?', action: async () => {
// ... we must read updated widget data that is stored somewhere accessible
//if (await RequestAccountWidgetsData(account, {})) {
// `#LayoutCollectionActions button[name="commit"]`.disabled = true;
//}
} });
$`#LayoutCollectionOptions button[name="reinit"]`.onclick = () => Spacc.ShowModal({ label: 'Confirm committing account data?', action: async () => {
const userTag = GetMatrixUserTag(account);
await RequestAccountWidgetsData(account, {
stickerpicker: {
content: {
type: "m.stickerpicker",
url: `${$`#LayoutCollectionOptions input[name="pickerUrl"]`.value}?& config=& theme=$theme`,
name: "Stickerpicker",
creatorUserId: userTag,
managedBy: [
`${Defaults.appIdentity}`,
`${Defaults.appIdentity}/${Defaults.appInterface}`,
],
},
sender: userTag,
state_key: "stickerpicker",
type: "m.widget",
id: "stickerpicker",
},
});
DisplayPacksEditor(account);
} });
$`#LayoutInfo`.innerHTML = `< p >
Fetching account data...
< / p > `;
const widgetsData = await RequestAccountWidgetsData(account);
if (widgetsData) {
const isManaged = widgetsData?.stickerpicker?.content?.managedBy?.includes(`${Defaults.appIdentity}/${Defaults.appInterface}`);
const packsUrl = (new URLSearchParams(widgetsData?.stickerpicker?.content?.url?.split('?')[1])).get('config');
if (!isManaged || !widgetsData?.stickerpicker) {
$`#LayoutCollectionOptions`.open = true;
if (!widgetsData?.stickerpicker) {
$`#LayoutInfo`.innerHTML = `< p > ${Defaults.Strings.mustInitMessage}< / p > `;
} else
if (!isManaged) {
$`#LayoutInfo`.innerHTML = `
< p > ${Defaults.Strings.notManagedMessage}< / p >
< button name = "continue" > ⏭️ Continue< / button >
`;
$`#LayoutInfo > button[name="continue"]`.onclick = () => {
$`#LayoutCollectionOptions`.open = false;
DisplayPacksEditor(account, packsUrl);
};
}
} else {
DisplayPacksEditor(account, packsUrl);
2024-01-01 19:11:18 +01:00
}
}
2024-01-02 19:23:14 +01:00
}
async function DisplayPacksEditor (account, packsUrl) {
ResetLayouts();
$`#LayoutCollectionActions`.hidden = false;
$`#LayoutCollectionOptions`.hidden = false;
let packsData;
if (packsUrl) {
const request = await fetch(packsUrl);
packsData = await request.json();
}
const addButton = $().createElement('button');
addButton.name = 'add';
addButton.innerHTML = '➕ ️ Create New Pack';
addButton.onclick = (ev) => {
for (const elem of ev.srcElement.parentElement.querySelectorAll('button')) {
elem.disabled = false;
}
const packButton = MakeStickerPackButton(account, packsData);
$`#LayoutPacksList`.insertBefore(packButton, $`#LayoutPacksList > button[name="add"]`.nextElementSibling);
$`#LayoutCollectionActions button[name="commit"]`.disabled = false;
packButton.click();
};
$`#LayoutPacksList`.appendChild(addButton);
for (const pack of (packsData?.packs || [])) {
const request = await fetch(pack);
const packData = await request.json();
$`#LayoutPacksList`.appendChild(MakeStickerPackButton(account, packsData, packData));
}
}
function MakeStickerPackButton (account, packsData, packData) {
const packButton = $().createElement('button');
packButton.innerHTML = `< img src = "${GetMatrixMediaUrl(packData?.stickers[0]?.info?.thumbnail_url, packsData?.homeserver_url)}?&height=64&width=64&method=scale" / > `;
packButton.onclick = (ev) => {
const thisElem = (ev.srcElement.tagName.toLowerCase() === 'button' ? ev.srcElement : ev.srcElement.parentElement);
for (const elem of thisElem.parentElement.querySelectorAll('button')) {
elem.disabled = false;
}
thisElem.disabled = true;
$`#LayoutPackGrid`.innerHTML = '';
for (const sticker of (packData?.stickers || [])) {
const stickerElem = $().createElement('button');
stickerElem.innerHTML = `< img src = "${GetMatrixMediaUrl(sticker.info.thumbnail_url, packsData?.homeserver_url)}?&height=128&width=128&method=scale" / > `;
$`#LayoutPackGrid`.appendChild(stickerElem);
}
const addButton = $().createElement('button');
addButton.innerHTML = `
➕ ️ Upload New Sticker(s)
< input type = file hidden = "true" multiple = "true" accept = "image/jpeg,image/gif,image/png,image/webp" / >
`;
addButton.querySelector('input[type="file"]').onchange = async (ev) => {
for (const file in ev.target.files) {
const result = await RequestUploadFile(account, file);
// ... we must use the result.content_uri to add the new sticker to the screen, add it somewhere keeping the current pack state, and the packs state, ready for committing
if (!result) {
switch (Spacc.ShowModal({ label: 'File upload failed. What to do?', action: () => 'continue', actionCancel: () => 'cancel', labelSecondary: '🔄️ Retry', actionSecondary: () => 'retry' })) {
case 'cancel':
break;
break;
case 'continue':
continue;
break;
case 'retry':
// ... find out how to handle this
break;
}
}
}
};
addButton.onclick = (ev) => ev.srcElement.querySelector('input[type="file"]')?.click();
$`#LayoutPackGrid`.appendChild(addButton);
};
return packButton;
}
function DisplayAccountSelect () {
ResetLayouts();
for (const account of Config.accounts) {
const accountButton = $().createElement('button');
accountButton.style.width = 'calc(100% - 3em)';
accountButton.innerHTML += `🐱️ ${GetMatrixUserTag(account)}`;
accountButton.onclick = () => PreparePacksEditor(account);
$`#LayoutAccountSelect`.appendChild(accountButton);
const deleteButton = $().createElement('button');
deleteButton.innerHTML += `❌️`;
deleteButton.onclick = () => Spacc.ShowModal({ label: '❌️ Confirm remove account?', action: () => {
Config.accounts.pop(account);
Config.Save();
DisplayAccountSelectLogin();
} });
$`#LayoutAccountSelect`.appendChild(deleteButton);
}
const addButton = $().createElement('button');
addButton.innerHTML += '🆕️ Add new account';
addButton.onclick = () => DisplayAccountLogin();
$`#LayoutAccountSelect`.appendChild(addButton);
}
2024-01-01 19:11:18 +01:00
2024-01-02 19:23:14 +01:00
function DisplayAccountLogin() {
ResetLayouts();
$`#LayoutAccountLogin`.hidden = false;
2024-01-01 14:42:50 +01:00
}
2024-01-02 19:23:14 +01:00
function DisplayAccountSelectLogin () {
(Config.accounts.length > 0 ? DisplayAccountSelect : DisplayAccountLogin)();
}
RegisterLayouts();
DisplayAccountSelectLogin();
2024-01-01 14:42:50 +01:00
< / script >
2024-01-02 19:23:14 +01:00
< div id = "Main" hidden = "true" >
< div id = "LayoutAccountSelect" > < / div >
< div id = "LayoutAccountLogin" >
< label >
Homeserver
< input type = "text" / >
< / label >
< label >
Username
< input type = "text" / >
< / label >
< label >
Password
< input type = "password" / >
< / label >
< label >
Add account via session token instead
< input type = "checkbox" / >
< / label >
< label >
< input type = "button" value = "Add" / >
< / label >
< / div >
< div id = "LayoutCollectionActions" >
< button name = "back" > 🔙️ Go Back< / button >
< button name = "commit" disabled = "true" > 📝️ Commit Changes< / button >
< / div >
< div id = "LayoutPacksList" > < / div >
< div id = "LayoutPackGrid" > < / div >
< div id = "LayoutInfo" > < / div >
< details class = "col border margin" id = "LayoutCollectionOptions" >
< summary >
< p > Options< / p >
< / summary >
< p >
Reinitializing sticker data for your account will simply override
<!-- the <code>stickerpicker</code> subfield in --> its < code > m.widgets< / code > field.
< / p >
< button name = "reinit" > 💡️ Reinitialize sticker data as new< / button >
< details class = "col border margin" >
< summary >
< p > Advanced< / p >
< / summary >
< label >
Sticker selector app URL
< input name = "pickerUrl" type = "text" / >
< / label >
< / details >
< / details >
< / div >
< p >
🃏️ [Matrix] Sticker Helper < a name = "version" href = "javascript:;" > WIP< / a > ,
created with ☕️ by < a href = "https://hub.octt.eu.org" > OctoSpacc< / a > .
< br / >
Made possible by < a href = "https://github.com/maunium/stickerpicker" > Maunium sticker picker< / a > ,
brought to paper thanks to < a href = "https://www.getpapercss.com" > PaperCSS< / a > .
< / p >
< style >
:root {
--margin: 8px;
}
* {
box-sizing: border-box;
}
body, .ActionForm, .TableForm {
margin: var(--margin);
overflow-x: hidden;
}
details > summary > * {
display: inline-block;
}
/*
#CollectionList {
max-width: 100%;
overflow: auto;
}
#CollectionList > div {
width: max-content;
}
#CollectionList > div > * {
width: 64px;
margin: var(--margin);
}
.ActionButton,
.ActionForm > *,
.TableForm td > * {
display: block;
height: 2em;
width: 100%;
}
.ActionForm > * > * {
height: 2em;
}
#CollectionGrid > *,
.ClickImg {
display: inline-block;
margin: 8px;
}
.ClickImg,
.ClickImg img {
width: 128px;
}
.ClickImg[data-opened="1"], .ClickImg[data-opened="1"] > img {
width: calc(100% - var(--margin));
}
*/
< / style >