mirror of
https://gitlab.com/octospacc/octospacc.gitlab.io
synced 2025-02-01 19:17:14 +01:00
656 lines
24 KiB
HTML
656 lines
24 KiB
HTML
<!DOCTYPE html>
|
||
<meta charset="utf-8"/>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||
<title>🃏️ [Matrix] Sticker Helper</title>
|
||
|
||
<!--
|
||
<script src="../../../SpaccDotWeb/SpaccDotWeb.js" module="SpaccDotWeb"></script>
|
||
<script src="//SpaccInc.gitlab.io/SpaccDotWeb/SpaccDotWeb.js" module="SpaccDotWeb"></script>
|
||
-->
|
||
<script src="../../../SpaccDotWeb/SpaccDotWeb.Alt.js" module="SpaccDotWeb"></script>
|
||
<script src="//SpaccInc.gitlab.io/SpaccDotWeb/SpaccDotWeb.Alt.js" module="SpaccDotWeb"></script>
|
||
<link rel="stylesheet" href="./paper.min.css"/>
|
||
|
||
<!--
|
||
<script module="Meta">({
|
||
Name: "🃏️ [Matrix] Sticker Helper",
|
||
})</script>
|
||
-->
|
||
|
||
<!--
|
||
TODO:
|
||
* more error handling
|
||
* allow reordering, renaming, deleting stickers and packs
|
||
* import from file, telegram, line
|
||
* save a linkback to a sticker pack in every single sticker optionally, allowing stickers to be copied, possibly from matrix.to URL
|
||
** set this and other options as persisting in the user account data
|
||
* write changelog, help, license
|
||
* fix sticker reimport protection not working?
|
||
* operations/status log
|
||
* animated sticker and automatic media conversion
|
||
* in a lot of places that only do HTTP(s), handle also mxc:// URLs
|
||
* maybe show the sticker packs in the same order in the list as the picker does
|
||
* PWA things (webmanifest, worker for offline support)
|
||
-->
|
||
|
||
<div id="Main" hidden="true">
|
||
<div id="LayoutAccountSelect"></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>
|
||
|
||
<hr class="margin-large"/>
|
||
|
||
<p>
|
||
🃏️ [Matrix] Sticker Helper <a name="version" title="Click to show changelog" href="javascript:;">Early Version</a>,
|
||
created with ☕️ by <a href="https://hub.octt.eu.org">OctoSpacc</a>.
|
||
<br/>
|
||
Made possible by <a href="https://github.com/maunium/stickerpicker" target="_blank">Maunium sticker picker</a>,
|
||
brought to paper thanks to <a href="https://www.getpapercss.com" target="_blank">PaperCSS</a>.
|
||
</p>
|
||
|
||
<details class="col border margin">
|
||
<summary>
|
||
<p>Help and Information</p>
|
||
</summary>
|
||
<h3>What is [Matrix] Sticker Helper?</h3>
|
||
<p>
|
||
I created this app for the pursuit of an user-friendly way
|
||
of managing sticker packs on <a href="https://matrix.org" target="_blank">Matrix</a>,
|
||
that could also work well for other people,
|
||
after finding the <code>maunium/stickerpicker</code> import procedure uncomfortable.
|
||
<br/>
|
||
Read more (and follow the development I guess?) at
|
||
<a href="https://octospacc.altervista.org/tag/matrixstickerhelper/">https://octospacc.altervista.org/tag/matrixstickerhelper/</a>.
|
||
</p>
|
||
<h3>How can I add sticker packs to my collection?</h3>
|
||
<p>
|
||
To add new sticker packs to your collection, you can use the dedicated section to
|
||
input an URL pointing to the JSON data file (in the format used by <code>maunium/stickerpicker</code>)
|
||
of an already existing collection (<code>index.json</code>) or individual pack (created by other users, or from your backups).
|
||
<br/>
|
||
The ability to create sticker packs by uploading files from your device's storage will soon be available,
|
||
as well as from the messages sent by other Matrix users.
|
||
<br/>
|
||
Importing stickers from LINE or Telegram is planned for the more distant future.
|
||
</p>
|
||
<h3>Stickerpicker information</h3>
|
||
<p>
|
||
This project is not a substitute of <code>maunium/stickerpicker</code>, and is not affiliated with it, but rather complements it.
|
||
It sets that up as the sticker picker integration for your Matrix account if you don't have it already,
|
||
and allows you to manage your sticker collection in a way that is fully compatible with it, but without the manual setup.
|
||
</p>
|
||
<h3>Open-source, licensing, security, collaboration</h3>
|
||
<p>
|
||
This app is open-source, and licensed under the
|
||
<a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank">GNU Affero General Public License Version 3</a>.
|
||
To modify the app, or to check its code for security reasons, you can simply "View Page Source" in your browser.
|
||
<br/>
|
||
This project additionally relies on the following third-party libraries:
|
||
<li><a href="https://gitlab.com/SpaccInc/SpaccDotWeb" target="_blank">SpaccDotWeb</a></li>
|
||
<br/>
|
||
Additionally, if there is any issue you want to report, or if you want to send a pull request,
|
||
feel free to do so at the OctoSpacc Hub repository:
|
||
<a href="https://gitlab.com/octospacc/octospacc.gitlab.io">https://gitlab.com/octospacc/octospacc.gitlab.io</a>
|
||
(or contact me via Matrix, see below).
|
||
</p>
|
||
<h3>Other help</h3>
|
||
<p>
|
||
There is no one around to actually help here though... yet.
|
||
Maybe join my Matrix space, if you need some support:
|
||
<a href="https://matrix.to/#/#Spacc:matrix.org">https://matrix.to/#/#Spacc:matrix.org</a>.
|
||
</p>
|
||
</details>
|
||
|
||
<script module="Main" type="module">
|
||
const Spacc = SpaccDotWeb.AppInit();
|
||
|
||
const State = {};
|
||
const Defaults = {
|
||
stickerSelectorUrl: "https://maunium.net/stickers-demo/",
|
||
appIdentity: "org.eu.octt.MatrixStickerHelper",
|
||
appInterface: "v1",
|
||
Strings: {
|
||
mConfirmCommit: "Confirm committing account data?",
|
||
mMustInit: "Your account must first be initialized to handle stickers.",
|
||
mNotManaged: `
|
||
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.
|
||
<br/>
|
||
You can try to continue anyway if you think it should work, otherwise you should reinitialize sticker data.
|
||
`,
|
||
mCreatePackHint: `
|
||
<!-- Optionally include --> Include the URL of a sticker pack in JSON format to import it.
|
||
<!-- Otherwise, leave the field empty --> The option to create a brand-new pack will soon be available.
|
||
`,
|
||
mLoginHint: `
|
||
Please login with your Matrix account.
|
||
<br/>
|
||
(Your login details are processed locally and only sent to the homeserver you specified.)
|
||
`,
|
||
}
|
||
};
|
||
|
||
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));
|
||
|
||
const $ = (query, ...params) => (query
|
||
? document.querySelector(Array.isArray(query)
|
||
? (params.length > 0
|
||
? params.map((a, i) => `${query[i]}${a}`).join('')
|
||
: query.join(''))
|
||
: query)
|
||
: document);
|
||
|
||
const GetMatrixUserTag = (account) => `@${account.username}:${account.homeserver.split('://')[1]}`;
|
||
|
||
const GetMatrixMediaUrl = (mxcId, props) => (mxcId ? `${props?.homeserver || `https://${mxcId.split('mxc://')[1].split('/')[0]}`}/_matrix/media/r0/${props?.type || 'download'}/${mxcId.split('mxc://')[1]}` : undefined);
|
||
|
||
function ResetLayouts () {
|
||
$`#Main`.hidden = false;
|
||
for (const id of ['LayoutInfo', 'LayoutAccountSelect', 'LayoutPacksList', 'LayoutPackGrid']) {
|
||
$`#${id}`.innerHTML = '';
|
||
}
|
||
for (const id of ['LayoutCollectionActions', 'LayoutCollectionOptions']) {
|
||
$`#${id}`.hidden = true;
|
||
}
|
||
for (const id of ['LayoutCollectionOptions']) {
|
||
$`#${id}`.open = false;
|
||
}
|
||
}
|
||
|
||
function InitializeState () {
|
||
$`#LayoutCollectionActions button[name="back"]`.onclick = () => DisplayAccountSelect();
|
||
$`#LayoutCollectionOptions input[name="pickerUrl"]`.value = Defaults.stickerSelectorUrl;
|
||
$`a[name="version"]`.onclick = () => Spacc.ShowModal({ extraHTML: `
|
||
<p>
|
||
[2024-01-04]
|
||
<li>Importing entire sticker collections from URL</li>
|
||
<li>Fix stickers not loaded from accounts already setup with stock stickerpicker</li>
|
||
<li>Add help and changelog</li>
|
||
</p>
|
||
<p>
|
||
[2024-01-03]
|
||
<li>First release</li>
|
||
<li>Account management (logging in, reading and writing data)</li>
|
||
<li>Visualization of current account stickers</li>
|
||
<li>Importing sticker packs from URL</li>
|
||
</p>
|
||
`});
|
||
}
|
||
|
||
async function RequestAccountWidgetsData (postData) {
|
||
const request = await fetch(`${State.account.homeserver}/_matrix/client/v3/user/${GetMatrixUserTag(State.account)}/account_data/m.widgets`, {
|
||
method: (postData ? "PUT" : "GET"),
|
||
headers: { Authorization: `Bearer ${State.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)}`);
|
||
}
|
||
}
|
||
|
||
async function RequestUploadFile (fileData, fileMime) {
|
||
const request = await fetch(`${State.account.homeserver}/_matrix/media/v3/upload`, {
|
||
method: "POST",
|
||
headers: { Authorization: `Bearer ${State.account.token}`, ...(fileMime && { "Content-Type": fileMime }) },
|
||
body: fileData,
|
||
});
|
||
const result = await request.json();
|
||
if (request.status === 200) {
|
||
return await result;
|
||
} else {
|
||
Spacc.ShowModal(`Error: ${JSON.stringify(result)}`);
|
||
}
|
||
}
|
||
|
||
async function PreparePacksEditor (account) {
|
||
if (account) {
|
||
State.account = account;
|
||
}
|
||
ResetLayouts();
|
||
State.packsData = { homeserver_url: State.account.homeserver, packs: [] };
|
||
State.stickersData = [];
|
||
$`#LayoutCollectionActions`.hidden = false;
|
||
$`#LayoutCollectionOptions`.hidden = false;
|
||
$`#LayoutCollectionOptions button[name="reinit"]`.onclick = () => Spacc.ShowModal({
|
||
label: Defaults.Strings.mConfirmCommit,
|
||
action: () => ReinitStickersAccountData(),
|
||
});
|
||
$`#LayoutInfo`.innerHTML = `<p>
|
||
Fetching account data...
|
||
</p>`;
|
||
State.widgetsData = await RequestAccountWidgetsData();
|
||
if (State.widgetsData) {
|
||
const isManaged = State.widgetsData?.stickerpicker?.content?.managedBy?.includes(`${Defaults.appIdentity}/${Defaults.appInterface}`);
|
||
const packsUrl = (new URLSearchParams(State.widgetsData?.stickerpicker?.content?.url?.split('?')[1])).get('config');
|
||
if (!isManaged || !State.widgetsData?.stickerpicker) {
|
||
$`#LayoutCollectionOptions`.open = true;
|
||
if (!State.widgetsData?.stickerpicker) {
|
||
$`#LayoutInfo`.innerHTML = `<p>${Defaults.Strings.mMustInit}</p>`;
|
||
} else
|
||
if (!isManaged) {
|
||
$`#LayoutInfo`.innerHTML = `
|
||
<p>${Defaults.Strings.mNotManaged}</p>
|
||
<button name="continue">⏭️ Continue</button>
|
||
`;
|
||
$`#LayoutInfo > button[name="continue"]`.onclick = () => {
|
||
$`#LayoutCollectionOptions`.open = false;
|
||
if (State.widgetsData?.stickerpicker?.content?.url && !packsUrl) {
|
||
// assuming the user is probably coming from stock maunium/stickerpicker,
|
||
// and we know that it loads data from a fixed URL when no config param is specified,
|
||
// we can try loading that when a generic URL is present
|
||
const packsUrlsPrefix = `${State.widgetsData?.stickerpicker?.content?.url.split('?')[0]}/packs/`;
|
||
DisplayPacksEditor(`${packsUrlsPrefix}index.json`, packsUrlsPrefix);
|
||
return;
|
||
}
|
||
DisplayPacksEditor(packsUrl);
|
||
};
|
||
}
|
||
} else {
|
||
DisplayPacksEditor(packsUrl);
|
||
}
|
||
}
|
||
}
|
||
|
||
async function DisplayPacksEditor (packsUrl, packsUrlPrefix) {
|
||
ResetLayouts();
|
||
$`#LayoutCollectionActions`.hidden = false;
|
||
$`#LayoutCollectionOptions`.hidden = false;
|
||
$`#LayoutCollectionActions button[name="commit"]`.onclick = () => Spacc.ShowModal({
|
||
label: Defaults.Strings.mConfirmCommit,
|
||
action: () => CommitNewAccountStickersAndData(),
|
||
});
|
||
if (packsUrl) {
|
||
try {
|
||
const request = await fetch(packsUrl);
|
||
if (request.status === 200) {
|
||
State.packsData = await request.json();
|
||
}
|
||
} catch(err) {
|
||
Spacc.ShowModal(`${err} ${packsUrl}`);
|
||
}
|
||
}
|
||
if (packsUrlPrefix) {
|
||
for (const packIndex in State.packsData.packs) {
|
||
State.packsData.packs[packIndex] = `${packsUrlPrefix}${State.packsData.packs[packIndex]}`;
|
||
}
|
||
}
|
||
const addButton = $().createElement('button');
|
||
addButton.name = 'add';
|
||
addButton.innerHTML = '➕️ Create/Import New Pack';
|
||
addButton.onclick = (event) => Spacc.ShowModal({ label: Defaults.Strings.mCreatePackHint, extraHTML: `
|
||
<input name="packUrl" type="text" placeholder="https://example.com/packs/example.json" style="width: 100%;"/>
|
||
`, action: (event, modalButton) => CreateNewPack(event, modalButton) });
|
||
$`#LayoutPacksList`.appendChild(addButton);
|
||
LoadStickerPacksList();
|
||
}
|
||
|
||
async function LoadStickerPacksList () {
|
||
for (const pack of State.packsData?.packs) {
|
||
try {
|
||
const request = await fetch(pack);
|
||
const packData = await request.json();
|
||
State.stickersData.push({ edited: false, data: packData });
|
||
AddNewPackButton(packData);
|
||
} catch(err) {
|
||
Spacc.ShowModal(`${err} ${pack}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
async function CreateNewPack (event, modalButton, packUrl) {
|
||
let packData = { stickers: [] };
|
||
// if the user specified an URL, try downloading data from there
|
||
packUrl ||= modalButton.parentElement.querySelector('input[name="packUrl"]').value;
|
||
// TODO: warn if an existing pack is imported from that URL and let the user choose if to continue or cancel
|
||
//if (packUrl && IsAnyPackImportedFrom(packUrl) && await Spacc.ShowModal({ label: Defaults.Strings.mAlreadyImported, action: () => 'continue', actionCancel: () => 'cancel' }) === 'continue') {
|
||
// return;
|
||
//};
|
||
if (packUrl) {
|
||
try {
|
||
const request = await fetch(packUrl);
|
||
packData = await request.json();
|
||
// import JSON is an index, so we try to import all its packs
|
||
if (packData.packs && !packData.stickers) {
|
||
for (const pack of packData.packs) {
|
||
const packLower = pack.toLowerCase();
|
||
const packUrlPrefix = (packLower.startsWith('http://') || packLower.startsWith('http://') ? '' : packUrl.split('/').slice(0, -1).join('/'));
|
||
await CreateNewPack(null, null, `${packUrlPrefix}/${pack}`);
|
||
}
|
||
return;
|
||
}
|
||
$`#LayoutCollectionActions button[name="commit"]`.disabled = false;
|
||
} catch(err) {
|
||
Spacc.ShowModal(`${err} ${packUrl}`);
|
||
return;
|
||
}
|
||
}
|
||
packData[Defaults.appIdentity] = {
|
||
...(packUrl && { importedFrom: packUrl }),
|
||
};
|
||
State.packsData.packs.push(null);
|
||
State.stickersData.push({ edited: true, data: packData });
|
||
AddNewPackButton(packData, event, modalButton);
|
||
}
|
||
|
||
function IsAnyPackImportedFrom (packUrl) {
|
||
for (const pack of State.stickersData) {
|
||
if (pack.data[Defaults.appIdentity].importedFrom === packUrl) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
function AddNewPackButton (packData, event, modalButton) {
|
||
for (const elem of $`#LayoutPacksList`.querySelectorAll('button')) {
|
||
elem.disabled = false;
|
||
}
|
||
const packButton = MakeStickerPackButton(packData);
|
||
$`#LayoutPacksList`.insertBefore(packButton, $`#LayoutPacksList > button[name="add"]`.nextElementSibling);
|
||
packButton.click();
|
||
}
|
||
|
||
function MakeStickerPackButton (packData) {
|
||
const packButton = $().createElement('button');
|
||
packButton.innerHTML = `<img src="${GetMatrixMediaUrl(packData.stickers[0]?.info?.thumbnail_url, { homeserver: State.packsData?.homeserver_url, type: 'thumbnail' }) || ''}?&height=64&width=64&method=scale"/>`;
|
||
packButton.onclick = (event) => ShowStickerPack(event, packData);
|
||
return packButton;
|
||
}
|
||
|
||
function ShowStickerPack (event, packData) {
|
||
const thisElem = (event.srcElement.tagName.toLowerCase() === 'button' ? event.srcElement : event.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, { homeserver: State.packsData?.homeserver_url, type: 'thumbnail' }) || ''}?&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"/>
|
||
`;
|
||
/* TODO
|
||
addButton.querySelector('input[type="file"]').onchange = async (event) => {
|
||
let newPackStickers = [];
|
||
for (const file of event.target.files) {
|
||
const result = await RequestUploadFile(State.account, file);
|
||
if (result) {
|
||
newPackStickers.push({
|
||
id: result.content_uri,
|
||
url: result.content_uri,
|
||
msgtype: "m.sticker",
|
||
body: file.name,
|
||
info: {
|
||
// ... TODO read real image dimensions
|
||
w: 256,
|
||
h: 256,
|
||
size: file.size,
|
||
mimetype: file.type,
|
||
thumbnail_url: result.content_uri,
|
||
thumbnail_info: {
|
||
// ... TODO read real image dimensions
|
||
w: 256,
|
||
h: 256,
|
||
size: file.size,
|
||
mimetype: file.type,
|
||
},
|
||
},
|
||
});
|
||
// ... 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
|
||
} else {
|
||
const answer = await Spacc.ShowModal({
|
||
label: 'File upload failed. What to do?',
|
||
action: () => 'continue',
|
||
actionCancel: () => 'cancel',
|
||
labelSecondary: '🔄️ Retry', actionSecondary: () => 'retry',
|
||
});
|
||
if (answer === 'cancel') {
|
||
newPackStickers = [];
|
||
break;
|
||
} else if (answer === 'retry') {
|
||
// ... find out how to handle this
|
||
} else if (answer === 'continue') {
|
||
continue;
|
||
}
|
||
}
|
||
}
|
||
// ... we must handle new stickers to add them to the initial object
|
||
packData.stickers = [...packData.stickers, newPackStickers];
|
||
if (newPackStickers.length > 0) {
|
||
$`#LayoutCollectionActions button[name="commit"]`.disabled = false;
|
||
}
|
||
};
|
||
*/
|
||
addButton.onclick = (event) => event.srcElement.querySelector('input[type="file"]')?.click();
|
||
// TODO: $`#LayoutPackGrid`.appendChild(addButton);
|
||
}
|
||
|
||
async function ReinitStickersAccountData () {
|
||
const userTag = GetMatrixUserTag(State.account);
|
||
State.packsData = { homeserver_url: State.account.homeserver, packs: [] };
|
||
State.stickersData = [];
|
||
State.widgetsData = {
|
||
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",
|
||
},
|
||
};
|
||
await RequestAccountWidgetsData(State.widgetsData);
|
||
PreparePacksEditor();
|
||
}
|
||
|
||
async function CommitNewAccountStickersAndData () {
|
||
$`#LayoutCollectionActions button[name="commit"]`.disabled = true;
|
||
// upload new metadata for sticker packs which have been edited
|
||
for (const packIndex in State.stickersData) {
|
||
const pack = State.stickersData[packIndex];
|
||
if (pack.edited && pack.data.stickers.length > 0) {
|
||
const uploadResult = await RequestUploadFile(JSON.stringify(pack.data), 'application/json');
|
||
State.packsData.packs[packIndex] = GetMatrixMediaUrl(uploadResult.content_uri);
|
||
}
|
||
}
|
||
// remove empty sticker packs before committing, they break the picker
|
||
for (const packIndex in State.stickersData) {
|
||
if (State.stickersData[packIndex].data.stickers.length === 0) {
|
||
State.stickersData[packIndex].data.stickers.splice(packIndex, 1);
|
||
State.packsData.packs.splice(packIndex, 1);
|
||
}
|
||
}
|
||
// finally upload new index file and update profile data
|
||
const uploadResult = await RequestUploadFile(JSON.stringify(State.packsData), 'application/json');
|
||
const packsUrlNew = GetMatrixMediaUrl(uploadResult.content_uri);
|
||
State.widgetsData.stickerpicker.content.url = `${$`#LayoutCollectionOptions input[name="pickerUrl"]`.value}?&config=${packsUrlNew}&theme=$theme`;
|
||
if (await RequestAccountWidgetsData(State.widgetsData)) {
|
||
PreparePacksEditor();
|
||
} else {
|
||
$`#LayoutCollectionActions button[name="commit"]`.disabled = false;
|
||
}
|
||
|
||
}
|
||
|
||
function DisplayAccountSelect () {
|
||
ResetLayouts();
|
||
for (const accountIndex in Config.accounts) {
|
||
const account = Config.accounts[accountIndex];
|
||
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.splice(accountIndex, 1);
|
||
Config.Save();
|
||
DisplayAccountSelect();
|
||
} });
|
||
$`#LayoutAccountSelect`.appendChild(deleteButton);
|
||
}
|
||
const addButton = $().createElement('button');
|
||
addButton.innerHTML += '🆕️ Add new account';
|
||
addButton.onclick = async () => {
|
||
const modal = await Spacc.ShowModal({ label: Defaults.Strings.mLoginHint, extraHTML: `
|
||
<label>
|
||
Homeserver
|
||
<input name="homeserver" type="url" placeholder="https://matrix.example.com" value="https://matrix.org"/>
|
||
</label>
|
||
<label>
|
||
Username
|
||
<input name="username" type="text" placeholder="AzureDiamond"/>
|
||
</label>
|
||
<label>
|
||
Password
|
||
<input name="password" type="password" placeholder="*******"/>
|
||
</label>
|
||
<label>
|
||
Alternatively, import account via <code>access_token</code> instead
|
||
<input name="useToken" type="checkbox"/>
|
||
</label>
|
||
`, action: async (event, modalButton) => {
|
||
const modal = modalButton.parentElement;
|
||
const homeserver = modal.querySelector('input[name="homeserver"]').value;
|
||
const username = modal.querySelector('input[name="username"]').value;
|
||
const password = modal.querySelector('input[name="password"]').value;
|
||
const loginData = {
|
||
homeserver: homeserver,
|
||
username: username,
|
||
};
|
||
if (modal.querySelector('input[name="useToken"]').checked) {
|
||
loginData.token = password;
|
||
} else {
|
||
try {
|
||
const response = await fetch(`${homeserver}/_matrix/client/v3/login`, {
|
||
method: "POST",
|
||
body: JSON.stringify({
|
||
type: "m.login.password",
|
||
identifier: {
|
||
type: "m.id.user",
|
||
user: username,
|
||
},
|
||
password: password,
|
||
}),
|
||
});
|
||
const result = await response.json();
|
||
if (response.status === 200) {
|
||
loginData.token = result.access_token;
|
||
} else {
|
||
throw JSON.stringify(result);
|
||
}
|
||
} catch(err) {
|
||
Spacc.ShowModal(`Error trying to get a session token: ${err}`);
|
||
return;
|
||
}
|
||
}
|
||
Config.accounts.push(loginData);
|
||
Config.Save();
|
||
DisplayAccountSelect();
|
||
} });
|
||
const buttonConfirm = modal.querySelector('button[name="confirm"]');
|
||
buttonConfirm.disabled = true;
|
||
for (const requiredElem of modal.querySelectorAll('input[type="url"], input[type="text"], input[type="password"]')) {
|
||
for (const event of ['change', 'input', 'paste']) {
|
||
requiredElem[`on${event}`] = () => {
|
||
let allFilled = true;
|
||
for (const requiredElem of modal.querySelectorAll('input[type="url"], input[type="text"], input[type="password"]')) {
|
||
if (!requiredElem.value) {
|
||
allFilled = false;
|
||
}
|
||
}
|
||
buttonConfirm.disabled = !allFilled;
|
||
};
|
||
}
|
||
}
|
||
};
|
||
$`#LayoutAccountSelect`.appendChild(addButton);
|
||
}
|
||
|
||
function Main () {
|
||
InitializeState();
|
||
DisplayAccountSelect();
|
||
}
|
||
Main();
|
||
</script>
|
||
|
||
<style>
|
||
:root {
|
||
--margin: 8px;
|
||
}
|
||
|
||
* {
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body, .ActionForm, .TableForm {
|
||
margin: var(--margin);
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
details > summary > * {
|
||
display: inline-block;
|
||
}
|
||
|
||
#LayoutPacksList img {
|
||
max-width: 64px;
|
||
}
|
||
|
||
#LayoutPackGrid img {
|
||
max-width: 128px;
|
||
}
|
||
</style>
|