1
0
mirror of https://github.com/s427/MARL.git synced 2025-01-18 21:10:22 +01:00
MARL-Mastodon-Archive-Reade.../index.html

1686 lines
74 KiB
HTML
Raw Normal View History

2024-11-29 14:58:03 +01:00
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>MARL - Mastodon Archive Reader Lite</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="data:," />
<base target="_blank" />
2024-12-28 01:29:09 +01:00
<link rel="stylesheet" href="css/main.css?20241228" />
2024-11-29 14:58:03 +01:00
</head>
<body>
<noscript>
<div class="nojs">JavaScript is required for this app to run.</div>
</noscript>
<div
class="main-page"
id="app"
x-data
:class="$store.ui.appClasses"
x-init="$watch('$store.files.appReady', value => checkAppReady(value))"
2024-11-29 14:58:03 +01:00
>
2024-12-27 23:37:47 +01:00
<template
x-if="!$store.files.appReady && !$store.files.someFilesLoaded && !$store.files.loading"
>
<main class="welcome">
2024-11-29 14:58:03 +01:00
<div class="intro">
<h1>
Welcome to MARL
<span class="accronym">(Mastodon Archive Reader Lite)</span>
</h1>
<p>
MARL allows you to explore the content of your Mastodon archive
file in a user-friendly interface. Everything takes place in the
browser: your archive stays strictly on your computer; none of its
data is sent to any server.
</p>
<p>
You can request your Mastodon archive by logging into your account
on the web, then visiting "Preferences > Import and export >
2024-12-01 00:37:22 +01:00
Request your archive".<br />
<strong>Please note:</strong> only ZIP files are supported (not
GZ).
2024-11-29 14:58:03 +01:00
</p>
<p>
2024-12-01 00:37:22 +01:00
<strong>Start by opening your archive file with MARL.</strong
><br />
2024-11-29 14:58:03 +01:00
You can drag and drop it anywhere on this page, or
<label for="file-loader" tabindex="0"
>click here to select it</label
>.
</p>
<input
id="file-loader"
class="file-loader"
type="file"
2024-12-01 00:37:22 +01:00
accept=".zip"
multiple
2024-11-29 14:58:03 +01:00
@change="unZip(Object.values($event.target.files))"
/>
</div>
<div class="about">
<a href="https://github.com/s427/MARL">Project page (github)</a>
</div>
</main>
</template>
2024-12-27 23:37:47 +01:00
<template
x-if="(!$store.files.appReady && $store.files.someFilesLoaded) || $store.files.loading"
>
2024-12-05 19:35:07 +01:00
<main class="welcome loading-more">
<div class="intro">
<div class="file-loader loading">
2024-12-27 23:37:47 +01:00
<div>... Loading ...</div>
<div class="filename" x-text="$store.files.loadingName"></div>
2024-12-05 19:35:07 +01:00
</div>
</div>
</main>
</template>
<template x-if="$store.files.appReady">
2024-11-29 14:58:03 +01:00
<main
class="main-section"
id="main-section"
tabindex="-1"
x-on:resize.window="$store.ui.checkMenuState()"
>
2024-12-05 19:35:07 +01:00
<div
class="main-section-inner"
id="main-section-inner"
:class="$store.files.sources.length > 1 ? 'multiple-actors' : ''"
>
2024-11-29 14:58:03 +01:00
<div class="mobile-menu" id="mobile-menu">
<nav>
<ul>
<li>
<button
class="menu-actor"
@click="$store.ui.menuToggle('actor')"
>
<svg aria-hidden="true"><use href="#menu-actor" /></svg>
<span>Profile</span>
</button>
</li>
<li>
<button
class="menu-filters"
:class="$store.files.filtersActive ? 'filters-active' : ''"
@click="$store.ui.menuToggle('filters')"
>
<svg aria-hidden="true"><use href="#menu-filters" /></svg>
<span
>Filters
<em
x-show="$store.files.filtersActive"
class="visually-hidden"
>(some filters are active)</em
>
</span>
</button>
</li>
<li>
<button
class="menu-tags"
@click="$store.ui.menuToggle('tags')"
>
<svg aria-hidden="true"><use href="#menu-tags" /></svg>
<span>Tags</span>
</button>
</li>
<li>
<button class="menu-new" @click="startOver">
<svg aria-hidden="true"><use href="#menu-new" /></svg>
<span>New File</span>
</button>
</li>
</ul>
</nav>
</div>
<div
class="panel-backdrop"
aria-hidden="true"
@click="$store.ui.menuClose()"
@keyup.esc="$store.ui.menuClose()"
></div>
<div
class="actor mobile-menu-panel"
id="panel-actor"
role="region"
aria-labelledby="actor-title"
tabindex="-1"
>
<button
class="panel-close"
@click="$store.ui.menuClose()"
x-show="$store.ui.menuIsActive"
>
<svg class="btn-icon" aria-hidden="true">
<use href="#close" /></svg
><span class="visually-hidden">Close panel</span>
</button>
<h2 class="visually-hidden" id="actor-title">Account info</h2>
<h3 class="visually-hidden" id="actor-tabs-title">Accounts</h3>
2024-11-29 14:58:03 +01:00
2024-12-05 19:35:07 +01:00
<div
class="actors-wrapper"
:style="'--actor-hue: '+ $store.files.sources[$store.ui.actorPanel].hue"
>
<template x-if="$store.files.sources.length > 1">
<div
class="actors-tabs"
role="tablist"
aria-labelledby="actor-tabs-title"
>
<template x-for="(_, source) in $store.files.sources">
<button
type="button"
role="tab"
:id="'actortab-' + source"
:aria-selected="source === $store.ui.actorPanel ? 'true': 'false'"
:aria-controls="'actorpanel-' + source"
:style="'--actor-hue: '+ $store.files.sources[source].hue"
@click="$store.ui.openActorPanel(source)"
@keyup.right="$store.ui.switchActorPanel('up')"
@keyup.left="$store.ui.switchActorPanel('down')"
>
<span
x-text="$store.files.sources[source].actor.name"
></span>
</button>
</template>
2024-11-29 14:58:03 +01:00
</div>
</template>
2024-12-09 15:20:29 +01:00
<template x-if="$store.files.sources.length === 1">
<span
class="visually-hidden"
id="actortab-0"
x-text="$store.files.sources[0].actor.name"
></span>
</template>
2024-11-29 14:58:03 +01:00
2024-12-09 15:20:29 +01:00
<template x-for="source in $store.files.sources">
<div
class="actor-panel"
2024-12-09 15:20:29 +01:00
:id="'actorpanel-' + source.id"
role="tabpanel"
tabindex="0"
2024-12-09 15:20:29 +01:00
:aria-labelledby="'actortab-' + source.id"
x-data="{a: source.actor}"
x-show="$store.ui.actorPanel === source.id"
>
<div class="actor-pretty">
<div class="actor-banner">
2024-12-09 15:20:29 +01:00
<template x-if="source.header.noImg">
<img alt="header" src="img/no-header.png" />
</template>
2024-12-09 15:20:29 +01:00
<template x-if="! source.header.noImg">
<button
id="actor-header"
2024-12-09 15:20:29 +01:00
@click="$store.lightbox.openProfileImg('header', 'actor-header', source.id)"
>
<img
alt="header"
2024-12-09 15:20:29 +01:00
:src="`data:${source.header.type}; base64,${source.header.content}`"
/>
</button>
</template>
</div>
2024-11-29 14:58:03 +01:00
<div class="actor-id">
2024-12-09 15:20:29 +01:00
<template x-if="source.avatar.noImg">
<div class="actor-avatar no-avatar">
<img :alt="a.name" src="img/no-avatar.png" />
</div>
</template>
2024-12-09 15:20:29 +01:00
<template x-if="! source.avatar.noImg">
<button
id="actor-avatar"
class="actor-avatar"
2024-12-09 15:20:29 +01:00
@click="$store.lightbox.openProfileImg('avatar', 'actor-avatar', source.id)"
>
<img
:alt="a.name"
class="actor-avatar"
2024-12-09 15:20:29 +01:00
:src="`data:${source.avatar.type}; base64,${source.avatar.content}`"
/>
</button>
</template>
<h1 class="actor-name" x-text="await a.name"></h1>
<div class="actor-url">
<a :href="await a.url" x-text="await a.url"></a>
</div>
</div>
2024-11-29 14:58:03 +01:00
<div
class="actor-summary"
x-show="a.summary"
x-html="await a.summary"
></div>
2024-11-29 14:58:03 +01:00
<div class="actor-infos">
<dl>
<dt>Member since</dt>
<dd x-text="await formatDate(a.published)"></dd>
</dl>
<template x-for="item in await a.attachment">
<dl>
<dt x-text="item.name"></dt>
<dd x-html="item.value"></dd>
</dl>
</template>
</div>
2024-11-29 14:58:03 +01:00
<div class="actor-posts-count">
<div class="total">
<span
class="count"
2024-12-09 15:20:29 +01:00
x-text="formatNumber(source.outbox.totalItems)"
></span>
<span class="label">posts</span>
</div>
2024-11-29 14:58:03 +01:00
<div class="archive">
<span
class="count"
2024-12-09 15:20:29 +01:00
x-text="formatNumber(source.nbToots)"
></span>
<span class="label">in archive</span>
</div>
<details
class="comment"
2024-12-09 15:20:29 +01:00
x-show="source.nbToots != source.outbox.totalItems"
>
<summary>
<span class="summary-icon">
<svg aria-hidden="true">
<use href="#question" />
</svg>
</span>
<span class="summary-label"
>Why are those two numbers different?</span
>
</summary>
<div class="details-content">
<p>
Posts that are not directly hosted on your
instance are kept in a cache by your instance for
a given time, after what they are deleted from
that cache. Posts that are not in your instance
cache any more are not included in your archive.
This affects boosts, likes, and bookmarks.
</p>
</div>
</details>
</div>
2024-11-29 14:58:03 +01:00
</div>
<div class="actor-raw">
<div class="actor-raw-inner">
<details>
<summary>
<span class="summary-icon">
<svg aria-hidden="true">
<use href="#json" />
</svg>
</span>
2024-12-27 23:37:47 +01:00
<span
class="summary-label"
:id="source + 'raw-data-label'"
>Raw data <em>(actor.json)</em></span
>
</summary>
<div class="details-content">
2024-12-27 23:37:47 +01:00
<textarea
x-text="formatJson(await a)"
:aria-labelledby="source + 'raw-data-label'"
readonly
></textarea>
</div>
</details>
</div>
2024-11-29 14:58:03 +01:00
</div>
<div class="actor-likes-bookmarks">
<div class="actor-likes">
<details>
<summary>
<span class="summary-icon">
<svg aria-hidden="true">
<use href="#favorite" />
</svg>
</span>
<h2 class="summary-label">
Favorites (<span
class="count"
2024-12-09 15:20:29 +01:00
x-text="source.likes.length"
></span
>)
</h2>
</summary>
<div class="details-content">
2024-12-09 15:20:29 +01:00
<template x-if="source.likes.length">
<ul>
2024-12-09 15:20:29 +01:00
<template x-for="url in source.likes">
<li>
<a
:href="url"
x-html="formatLikesBookmarks(url)"
></a>
</li>
</template>
</ul>
</template>
2024-12-09 15:20:29 +01:00
<template x-if="! source.likes.length">
<p class="no-content">... no favorites ...</p>
</template>
</div>
</details>
</div>
<div class="actor-bookmarks">
<details>
<summary>
<span class="summary-icon">
<svg aria-hidden="true">
<use href="#bookmark" />
</svg>
</span>
<h2 class="summary-label">
Bookmarks (<span
class="count"
2024-12-09 15:20:29 +01:00
x-text="source.bookmarks.length"
></span
>)
</h2>
</summary>
<div class="details-content">
2024-12-09 15:20:29 +01:00
<template x-if="source.bookmarks.length">
<ul>
2024-12-09 15:20:29 +01:00
<template x-for="url in source.bookmarks">
<li>
<a
:href="url"
x-html="formatLikesBookmarks(url)"
></a>
</li>
</template>
</ul>
</template>
2024-12-09 15:20:29 +01:00
<template x-if="! source.bookmarks.length">
<p class="no-content">... no bookmarks ...</p>
</template>
</div>
</details>
</div>
2024-11-29 14:58:03 +01:00
</div>
</div>
</template>
2024-11-29 14:58:03 +01:00
</div>
</div>
<div
class="toots-filters mobile-menu-panel"
id="panel-filters"
role="search"
aria-labelledby="toots-filter-title"
tabindex="-1"
>
<button
class="panel-close"
@click="$store.ui.menuClose()"
x-show="$store.ui.menuIsActive"
>
<svg class="btn-icon" aria-hidden="true">
<use href="#close" /></svg
><span class="visually-hidden">Close panel</span>
</button>
<h2 id="toots-filter-title">Filter posts</h2>
<p class="visually-hidden">
The list of posts will be automatically updated based on the
active filters below.
</p>
<div class="toots-filters-group">
<div
class="toots-filter text"
:class="isFilterActive('fullText') ? 'active' : ''"
>
<label for="filter-full-text">Full text</label>
<div class="input-wrapper">
<input
id="filter-full-text"
type="text"
x-model.debounce="$store.files.filters.fullText"
@keyup.debounce="$store.files.setFilter()"
@change.debounce="$store.files.setFilter()"
/>
</div>
</div>
<div
class="toots-filter text"
:class="isFilterActive('hashtagText') ? 'active' : ''"
>
<label for="filter-hashtag-text">Hashtags</label>
<div class="input-wrapper">
<input
id="filter-hashtag-text"
type="text"
x-model.debounce="$store.files.filters.hashtagText"
@keyup.debounce="$store.files.setFilter()"
@change.debounce="$store.files.setFilter()"
/>
</div>
</div>
<div
class="toots-filter text"
:class="isFilterActive('mentionText') ? 'active' : ''"
>
<label for="filter-mention-text">Mentions</label>
<div class="input-wrapper">
<input
id="filter-mention-text"
type="text"
x-model.debounce="$store.files.filters.mentionText"
@keyup.debounce="$store.files.setFilter()"
@change.debounce="$store.files.setFilter()"
/>
</div>
</div>
<div
class="toots-filter text"
:class="isFilterActive('externalLink') ? 'active' : ''"
>
<label for="filter-ext-link">External links</label>
<div class="input-wrapper">
<input
id="filter-ext-link"
type="text"
x-model.debounce="$store.files.filters.externalLink"
@keyup.debounce="$store.files.setFilter()"
@change.debounce="$store.files.setFilter()"
/>
</div>
</div>
<div
class="toots-filter text"
:class="isFilterActive('summary') ? 'active' : ''"
>
<label for="filter-summary">Summary (CW)</label>
<div class="input-wrapper">
<input
id="filter-summary"
type="text"
x-model.debounce="$store.files.filters.summary"
@keyup.debounce="$store.files.setFilter()"
@change.debounce="$store.files.setFilter()"
/>
</div>
</div>
</div>
<div class="toots-filters-group">
2024-11-29 14:58:03 +01:00
<div
class="toots-filter checkbox sep-above"
:class="isFilterActive('isEdited') ? 'active' : ''"
>
<label for="filter-is-edited">
<input
id="filter-is-edited"
type="checkbox"
x-model.debounce="$store.files.filters.isEdited"
@change.debounce="$store.files.setFilter()"
/>
<span>Has been edited</span>
</label>
</div>
<div
x-show="$store.files.duplicates"
class="toots-filter checkbox"
:class="isFilterActive('isDuplicate') ? 'active' : ''"
>
<label for="filter-is-duplicate">
<input
id="filter-is-duplicate"
type="checkbox"
x-model.debounce="$store.files.filters.isDuplicate"
@change.debounce="$store.files.setFilter()"
/>
<span>Non-exact duplicates</span>
</label>
2024-11-29 14:58:03 +01:00
</div>
</div>
<div class="toots-filters-group">
<h3 class="toots-filters-group-title">Must contain</h3>
<div
class="toots-filter checkbox"
:class="isFilterActive('hasHashtags') ? 'active' : ''"
>
<label for="filter-has-hashtags">
<input
id="filter-has-hashtags"
type="checkbox"
x-model.debounce="$store.files.filters.hasHashtags"
@change.debounce="$store.files.setFilter()"
/>
<span>Hashtag(s)</span>
</label>
</div>
<div
class="toots-filter checkbox"
:class="isFilterActive('hasMentions') ? 'active' : ''"
>
<label for="filter-has-mentions">
<input
id="filter-has-mentions"
type="checkbox"
x-model.debounce="$store.files.filters.hasMentions"
@change.debounce="$store.files.setFilter()"
/>
<span>Mention(s)</span>
</label>
</div>
<div
class="toots-filter checkbox"
:class="isFilterActive('hasExternalLink') ? 'active' : ''"
>
<label for="filter-has-external-links">
<input
id="filter-has-external-links"
type="checkbox"
x-model.debounce="$store.files.filters.hasExternalLink"
@change.debounce="$store.files.setFilter()"
/>
<span>External link(s)</span>
</label>
</div>
<div
class="toots-filter checkbox"
:class="isFilterActive('hasSummary') ? 'active' : ''"
>
<label for="filter-has-summary">
<input
id="filter-has-summary"
type="checkbox"
x-model.debounce="$store.files.filters.hasSummary"
@change.debounce="$store.files.setFilter()"
/>
<span>Summary (CW)</span>
</label>
</div>
</div>
<div class="toots-filters-group">
<h3 class="toots-filters-group-title">Type</h3>
<div
class="toots-filter checkbox"
:class="isFilterActive('typeOriginal') ? 'active' : ''"
>
<label for="filter-type-original">
<input
id="filter-type-original"
type="checkbox"
x-model="$store.files.filters.typeOriginal"
@change="$store.files.setFilter()"
/>
<span>Original posts (incl. replies)</span>
</label>
</div>
<div
class="toots-filter checkbox"
:class="isFilterActive('typeBoost') ? 'active' : ''"
>
<label for="filter-type-boost">
<input
id="filter-type-boost"
type="checkbox"
x-model="$store.files.filters.typeBoost"
@change="$store.files.setFilter()"
/>
<span>Boosts</span>
</label>
</div>
<div
class="toots-filter checkbox sep-above"
:class="isFilterActive('noStartingAt') ? 'active' : ''"
>
<label for="filter-no-at">
<input
id="filter-no-at"
type="checkbox"
x-model.debounce="$store.files.filters.noStartingAt"
@change.debounce="$store.files.setFilter()"
/>
<span>Does not start with "@"</span>
</label>
</div>
<div
class="toots-filter checkbox"
:class="isFilterActive('isSensitive') ? 'active' : ''"
>
<label for="filter-is-sensitive">
<input
id="filter-is-sensitive"
type="checkbox"
x-model.debounce="$store.files.filters.isSensitive"
@change.debounce="$store.files.setFilter()"
/>
<span>Marked as sensitive</span>
</label>
</div>
</div>
<div class="toots-filters-group">
<h3 class="toots-filters-group-title">Must have attachment</h3>
<div
class="toots-filter checkbox"
:class="isFilterActive('attachmentAny') ? 'active' : ''"
>
<label for="filter-attachment-any">
<input
id="filter-attachment-any"
type="checkbox"
x-model="$store.files.filters.attachmentAny"
@change="$store.files.setFilter()"
/>
<span>Any type</span>
</label>
</div>
<div
class="toots-filter checkbox"
:class="isFilterActive('attachmentImage') ? 'active' : ''"
>
<label for="filter-attachment-image">
<input
id="filter-attachment-image"
type="checkbox"
x-model="$store.files.filters.attachmentImage"
@change="$store.files.setFilter()"
/>
<span>Image(s)</span>
</label>
</div>
<div
class="toots-filter checkbox"
:class="isFilterActive('attachmentVideo') ? 'active' : ''"
>
<label for="filter-attachment-video">
<input
id="filter-attachment-video"
type="checkbox"
x-model="$store.files.filters.attachmentVideo"
@change="$store.files.setFilter()"
/>
<span>Video(s)</span>
</label>
</div>
<div
class="toots-filter checkbox"
:class="isFilterActive('attachmentSound') ? 'active' : ''"
>
<label for="filter-attachment-sound">
<input
id="filter-attachment-sound"
type="checkbox"
x-model="$store.files.filters.attachmentSound"
@change="$store.files.setFilter()"
/>
<span>Sound(s)</span>
</label>
</div>
<div
class="toots-filter checkbox sep-above"
:class="isFilterActive('attachmentNoAltText') ? 'active' : ''"
>
<label for="filter-attachment-no-alt-text">
<input
id="filter-attachment-no-alt-text"
type="checkbox"
x-model="$store.files.filters.attachmentNoAltText"
@change="$store.files.setFilter()"
/>
<span>Without alt text</span>
</label>
</div>
<div
class="toots-filter checkbox"
:class="isFilterActive('attachmentWithAltText') ? 'active' : ''"
>
<label for="filter-attachment-with-alt-text">
<input
id="filter-attachment-with-alt-text"
type="checkbox"
x-model="$store.files.filters.attachmentWithAltText"
@change="$store.files.setFilter()"
/>
<span>With alt text</span>
</label>
</div>
</div>
<div class="toots-filters-group">
<h3 class="toots-filters-group-title">Visibility</h3>
<div
class="toots-filter checkbox"
:class="isFilterActive('visibilityPublic') ? 'active' : ''"
>
<label for="filter-visibility-public">
<input
id="filter-visibility-public"
type="checkbox"
x-model="$store.files.filters.visibilityPublic"
@change="$store.files.setFilter()"
/>
<span>Public</span>
</label>
</div>
<div
class="toots-filter checkbox"
:class="isFilterActive('visibilityUnlisted') ? 'active' : ''"
>
<label for="filter-visibility-unlisted">
<input
id="filter-visibility-unlisted"
type="checkbox"
x-model="$store.files.filters.visibilityUnlisted"
@change="$store.files.setFilter()"
/>
<span>Unlisted</span>
</label>
</div>
<div
class="toots-filter checkbox"
:class="isFilterActive('visibilityFollowers') ? 'active' : ''"
>
<label for="filter-visibility-followers">
<input
id="filter-visibility-followers"
type="checkbox"
x-model.debounce="$store.files.filters.visibilityFollowers"
@change.debounce="$store.files.setFilter()"
/>
<span>Followers only</span>
</label>
</div>
<div
class="toots-filter checkbox"
:class="isFilterActive('visibilityMentioned') ? 'active' : ''"
>
<label for="filter-visibility-mentioned">
<input
id="filter-visibility-mentioned"
type="checkbox"
x-model.debounce="$store.files.filters.visibilityMentioned"
@change.debounce="$store.files.setFilter()"
/>
<span>Mentioned people only</span>
</label>
</div>
</div>
2024-12-09 15:20:29 +01:00
<div
class="toots-filters-group"
x-show="$store.files.toots.length"
>
2024-11-29 14:58:03 +01:00
<h3 class="toots-filters-group-title">Language</h3>
<template x-for="lang in $store.files.sortedLanguages">
2024-11-29 14:58:03 +01:00
<div
class="toots-filter checkbox"
:class="isFilterActive('lang_' + lang[0]) ? 'active' : ''"
2024-11-29 14:58:03 +01:00
>
<label :for="'filter-lang-' + lang[0]">
2024-11-29 14:58:03 +01:00
<input
:id="'filter-lang-' + lang[0]"
2024-11-29 14:58:03 +01:00
type="checkbox"
x-model="$store.files.filters['lang_' + lang[0]]"
2024-11-29 14:58:03 +01:00
@change="$store.files.setFilter()"
/>
<span>
<span x-text="lang[0]"></span>
(<span x-text="lang[1]"></span>)
2024-11-29 14:58:03 +01:00
</span>
</label>
</div>
</template>
</div>
<template x-if="$store.files.sources.length > 1">
<div class="toots-filters-group">
<h3 class="toots-filters-group-title">Author</h3>
2024-12-09 15:20:29 +01:00
<template x-for="source in $store.files.sources">
<div
class="toots-filter checkbox"
2024-12-09 15:20:29 +01:00
:class="isFilterActive('actor_' + source.id) ? 'active' : ''"
>
2024-12-09 15:20:29 +01:00
<label :for="'filter-actor-' + source.id">
<input
2024-12-09 15:20:29 +01:00
:id="'filter-actor-' + source.id"
type="checkbox"
2024-12-09 15:20:29 +01:00
x-model="$store.files.filters['actor_' + source.id]"
@change="$store.files.setFilter()"
/>
<span>
2024-12-09 15:20:29 +01:00
<span x-text="source.actor.name"></span>
</span>
</label>
</div>
</template>
</div>
</template>
2024-11-29 14:58:03 +01:00
<div class="toots-filters-reset">
<button
2024-12-01 10:45:20 +01:00
@click="$store.files.resetFilters(true)"
2024-11-29 14:58:03 +01:00
:disabled="!$store.files.filtersActive"
>
<svg class="btn-icon" aria-hidden="true">
<use href="#reset-filters" /></svg
><span class="btn-label">Reset filters</span>
</button>
</div>
</div>
<header class="toots-header" aria-labelledby="toots-header-title">
<h2 id="toots-header-title">
<span class="count">
<span
x-text="await formatNumber($store.files.filteredToots.length)"
class="nb"
></span>
posts
</span>
<span class="order">
-
<span
x-text="await $store.files.sortAsc ? 'oldest' : 'latest'"
></span>
first
<button
@click="$store.files.toggleTootsOrder()"
class="toggle-order"
>
<svg class="btn-icon" aria-hidden="true">
<use href="#toggle-order" /></svg
><span class="btn-label">Reverse</span>
</button>
</span>
<button @click="startOver" class="load-new">
<svg class="btn-icon" aria-hidden="true">
<use href="#load-file" /></svg
><span class="btn-label">Load new file</span>
</button>
</h2>
<div class="paging">
<div class="direction-back">
<button
id="paging-btn-first"
@click="$store.files.firstPage()"
:disabled="$store.files.currentPage <= 1"
>
<svg class="btn-icon" aria-hidden="true">
<use href="#nav-first" /></svg
><span class="btn-label">First</span>
</button>
<button
id="paging-btn-prev"
@click="$store.files.prevPage()"
:disabled="$store.files.currentPage <= 1"
>
<svg class="btn-icon" aria-hidden="true">
<use href="#nav-prev" /></svg
><span class="btn-label">Prev</span>
</button>
</div>
<div class="paging-options-toggle">
<button @click="$store.ui.togglePagingOptions()">
<svg class="btn-icon" aria-hidden="true">
<use href="#options" /></svg
><span class="btn-label">Paging options</span>
</button>
</div>
<div
class="paging-options"
id="paging-options"
:class="$store.ui.pagingOptionsClass"
tabindex="-1"
>
<div class="paging-options-inner">
<label for="paging-current-page">Page</label>
<input
class="current-page"
id="paging-current-page"
type="number"
min="1"
max="$store.files.totalPages"
x-model="$store.files.currentPage"
@keyup="$store.files.checkPagingValue()"
@change="$store.files.checkPagingValue()"
onClick="this.select()"
/>
/
<span
class="total-pages"
x-text="formatNumber($store.files.totalPages)"
></span>
(
<input
class="page-size"
id="paging-page-size"
type="number"
min="1"
x-model="$store.files.pageSize"
@keyup="$store.files.checkPagingValue()"
@change="$store.files.checkPagingValue()"
onClick="this.select()"
/>
<label for="paging-page-size">posts per page)</label>
</div>
2024-11-29 17:26:57 +01:00
<div class="paging-options-reverse">
<button @click="$store.files.toggleTootsOrder()">
<svg class="btn-icon" aria-hidden="true">
<use href="#toggle-order" /></svg
><span class="btn-label">Reverse order</span>
</button>
</div>
2024-11-29 14:58:03 +01:00
</div>
<div class="direction-fwd">
<button
id="paging-btn-next"
@click="$store.files.nextPage()"
:disabled="$store.files.currentPage >= $store.files.totalPages"
>
<span class="btn-label">Next</span
><svg class="btn-icon" aria-hidden="true">
<use href="#nav-next" />
</svg>
</button>
<button
id="paging-btn-last"
@click="$store.files.lastPage()"
:disabled="$store.files.currentPage >= $store.files.totalPages"
>
<span class="btn-label">Last</span
><svg class="btn-icon" aria-hidden="true">
<use href="#nav-last" />
</svg>
</button>
</div>
</div>
</header>
<div
class="toots"
id="toots"
role="region"
aria-labelledby="toots-title"
tabindex="-1"
>
<h2 id="toots-title" class="visually-hidden">Posts</h2>
<template
x-show="$store.files.pagedToots.length"
x-for="toot in await $store.files.pagedToots"
>
<div
class="toot"
:class="['toot-type-' + contentType(toot.type).toLowerCase(), 'toot-visibility-' + toot._marl.visibility[0]]"
:style="'--actor-hue: '+ $store.files.sources[toot._marl.source].hue"
2024-11-29 14:58:03 +01:00
>
<h3 class="toot-header visually-hidden">
<span x-text="contentType(toot.type)"></span>
by <span x-text="formatAuthor(toot.actor, true)"></span>,
<span x-text="formatDateTime(toot.published)"></span>
</h3>
<template x-if="toot.type === 'Create'">
<div class="toot-pretty">
<div class="toot-summary" x-show="toot.object.summary">
<span x-text="toot.object.summary"></span>
</div>
<div
class="toot-content"
x-html="toot.object.content"
></div>
<div
class="toot-attachments"
x-show="toot.object.attachment && toot.object.attachment.length"
>
<ul>
<template
x-for="(att, index) in toot.object.attachment"
>
<li
:class="attachmentWrapperClass(att)"
:data-trigger="loadAttachedMedia(att, toot._marl.source)"
2024-11-29 14:58:03 +01:00
>
<!-- image -->
<template
x-if="attachmentIsImage(att) && $store.files.sources[toot._marl.source][att.url]"
2024-11-29 14:58:03 +01:00
>
<div class="att-wrapper">
<button
class="att-img-wrapper"
@click="$store.lightbox.open(toot, index, toot._marl.id + '-' + index)"
2024-11-29 14:58:03 +01:00
:id="toot._marl.id + '-' + index"
>
<img
alt=""
:src="`data:${att.mediaType}; base64,${await $store.files.sources[toot._marl.source][att.url].content}`"
2024-11-29 14:58:03 +01:00
:aria-labelledby="toot._marl.id + '-' + index + '-desc'"
/>
</button>
<div
class="att-description"
aria-hidden="true"
:id="toot._marl.id + '-' + index + '-desc'"
>
<span
class="desc-body"
x-text="att.name ?? 'No description provided'"
></span>
<span class="desc-source"
><strong>In archive:</strong>
<span x-text="att.url"></span
></span>
</div>
</div>
</template>
<!-- sound -->
<template
x-if="attachmentIsSound(att) && $store.files.sources[toot._marl.source][att.url]"
2024-11-29 14:58:03 +01:00
>
<div class="att-wrapper">
<audio
controls
:src="`data:${att.mediaType}; base64,${await $store.files.sources[toot._marl.source][att.url].content}`"
2024-11-29 14:58:03 +01:00
:aria-labelledby="toot._marl.id + '-' + index + '-desc'"
></audio>
<div
class="att-description"
:id="toot._marl.id + '-' + index + '-desc'"
>
<span
class="desc-body"
x-text="att.name ?? 'No description provided'"
></span>
<span class="desc-source"
><strong>In archive:</strong>
<span x-text="att.url"></span
></span>
</div>
</div>
</template>
<!-- video -->
<template
x-if="attachmentIsVideo(att) && $store.files.sources[toot._marl.source][att.url]"
2024-11-29 14:58:03 +01:00
>
<div class="att-wrapper">
<video
controls
:width="att.width"
:height="att.height"
:src="`data:${att.mediaType}; base64,${await $store.files.sources[toot._marl.source][att.url].content}`"
2024-11-29 14:58:03 +01:00
:aria-labelledby="toot._marl.id + '-' + index + '-desc'"
></video>
<div
class="att-description"
:id="toot._marl.id + '-' + index + '-desc'"
>
<span
class="desc-body"
x-text="att.name ?? 'No description provided'"
></span>
<span class="desc-source"
><strong>In archive:</strong>
<span x-text="att.url"></span
></span>
</div>
</div>
</template>
</li>
</template>
</ul>
</div>
</div>
</template>
<template
x-if="toot.type === 'Announce' && toot.object && toot.object.content"
>
<div class="toot-pretty">
<div class="toot-content">
<div
class="toot-content-inner"
x-html="toot.object.content"
></div>
</div>
</div>
</template>
<template
x-if="toot.type === 'Announce' && toot.object && !toot.object.content"
>
<div class="toot-pretty">
<div class="toot-content">
<div class="toot-content-inner">
<a :href="toot.object" x-text="toot.object"></a>
</div>
</div>
</div>
</template>
<div class="toot-infos">
<span class="type">
<span x-text="contentType(toot.type)"></span> 
</span>
<span class="author">
by
<span x-html="formatAuthor(toot.actor)"></span>
</span>
<span class="published"
><span x-text="formatDateTime(toot.published)"></span>
<span
x-show="toot.object && toot.object.updated"
class="updated"
>
Last updated
<span
x-text="formatDateTime(toot.object.updated)"
></span>
</span>
</span>
<span class="visibility">
<span x-text="toot._marl.visibility[1]"></span>
</span>
<span class="link">
<a x-show="toot.object.id" :href="toot.object.id">link</a>
</span>
</div>
<template
x-if="(toot.object.tag && toot.object.tag.length) || (toot._marl.externalLinks && toot._marl.externalLinks.length)"
>
<div class="toot-meta">
<template
x-if="toot.object.tag && toot.object.tag.filter(x => x.type === 'Mention').length"
>
<div class="toot-people">
<h4>People</h4>
<ul>
<template x-for="tag in toot.object.tag">
<li x-show="tag.type === 'Mention'">
<a :href="tag.href" x-text="tag.name"></a>
</li>
</template>
</ul>
</div>
</template>
<template
x-if="toot.object.tag && toot.object.tag.filter(x => x.type === 'Hashtag').length"
>
<div class="toot-hashtags">
<h4>Hashtags</h4>
<ul>
<template x-for="tag in toot.object.tag">
<li x-show="tag.type === 'Hashtag'">
<a :href="tag.href" x-text="tag.name"></a>
</li>
</template>
</ul>
</div>
</template>
<template x-if="toot._marl.externalLinks.length">
<div class="toot-links">
<h4>External links</h4>
<ul>
<template x-for="link in toot._marl.externalLinks">
<li>
<a :href="link.href" x-text="link.text"></a>
</li>
</template>
</ul>
</div>
</template>
</div>
</template>
<div class="toot-raw">
<details>
<summary>
<span class="summary-icon">
<svg aria-hidden="true">
<use href="#json" />
</svg>
</span>
2024-12-09 15:20:29 +01:00
<span class="summary-label" :id="'raw-' + toot._marl.id"
>Raw data</span
>
2024-11-29 14:58:03 +01:00
</summary>
<div class="details-content">
2024-12-09 15:20:29 +01:00
<textarea
x-text="formatJson(toot)"
:aria-labelledby="'raw-' + toot._marl.id"
readonly
></textarea>
2024-11-29 14:58:03 +01:00
</div>
</details>
</div>
</div>
</template>
<div
class="toots-no-results"
x-show="$store.files.pagedToots.length === 0"
>
<div x-show="$store.files.filtersActive">
<p>No results for the specified filters</p>
<p class="action">
<button
2024-12-01 10:45:20 +01:00
@click="$store.files.resetFilters(true)"
2024-11-29 14:58:03 +01:00
:disabled="!$store.files.filtersActive"
>
<svg class="btn-icon" aria-hidden="true">
<use href="#reset-filters" /></svg
><span class="btn-label">Reset filters</span>
</button>
</p>
</div>
<div x-show="!$store.files.filtersActive">
<p>No posts found in archive (?!)</p>
</div>
</div>
<div class="paging">
<div class="direction-back">
<button
@click="$store.files.firstPage('toots')"
:disabled="$store.files.currentPage <= 1"
>
<svg class="btn-icon" aria-hidden="true">
<use href="#nav-first" /></svg
><span class="btn-label">First</span>
</button>
<button
@click="$store.files.prevPage('paging-btn-prev')"
:disabled="$store.files.currentPage <= 1"
>
<svg class="btn-icon" aria-hidden="true">
<use href="#nav-prev" /></svg
><span class="btn-label">Prev</span>
</button>
</div>
<div class="direction-fwd">
<button
@click="$store.files.nextPage('paging-btn-next')"
:disabled="$store.files.currentPage >= $store.files.totalPages"
>
<span class="btn-label">Next</span
><svg class="btn-icon" aria-hidden="true">
<use href="#nav-next" />
</svg>
</button>
<button
@click="$store.files.lastPage('toots')"
:disabled="$store.files.currentPage >= $store.files.totalPages"
>
<span class="btn-label">Last</span
><svg class="btn-icon" aria-hidden="true">
<use href="#nav-last" />
</svg>
</button>
</div>
</div>
</div>
<aside
class="toots-tags mobile-menu-panel"
id="panel-tags"
aria-labelledby="toots-tags-title"
tabindex="-1"
>
<button
class="panel-close"
@click="$store.ui.menuClose()"
x-show="$store.ui.menuIsActive"
>
<svg class="btn-icon" aria-hidden="true">
<use href="#close" /></svg
><span class="visually-hidden">Close panel</span>
</button>
<h2 class="tags-title" id="toots-tags-title">Tags</h2>
<div class="tags-group">
<div class="tags-group-header">
<h3>
Hashtags
<span class="count">
(<span
x-text="formatNumber($store.files.listHashtags.length)"
></span
>)
</span>
</h3>
<div class="tags-group-filter">
<label
for="tags-group-hashtags-filter"
class="visually-hidden"
>filter hashtags</label
>
<input
id="tags-group-hashtags-filter"
type="text"
onClick="this.select()"
x-model.debounce="$store.files.tagsFilters.hashtags"
/>
</div>
</div>
<div class="tags-group-scroll">
<ul x-show="$store.files.listHashtags.length">
<template x-for="item in await $store.files.listHashtags">
<li
:class="item.name === $store.files.filters.hashtagText ? 'active' : ''"
>
<button
:id="'filter-hashtag-' + item.name.toLowerCase().slice(1)"
@click="$store.files.filterByTag('hashtagText', item.name, 'filter-hashtag-' + item.name.toLowerCase().slice(1))"
>
<div>
<span
class="visually-hidden"
x-text="item.name === $store.files.filters.hashtagText ? 'active item:' : ''"
></span>
<span class="count" x-text="`(${item.nb})`"></span>
<span class="name" x-text="item.name"></span>
</div>
</button>
</li>
</template>
</ul>
</div>
</div>
<div class="tags-group">
<div class="tags-group-header">
<h3>
Mentions
<span class="count">
(<span
x-text="formatNumber($store.files.listMentions.length)"
></span
>)
</span>
</h3>
<div class="tags-group-filter">
<label
for="tags-group-mentions-filter"
class="visually-hidden"
>filter hashtags</label
>
<input
id="tags-group-mentions-filter"
type="text"
onClick="this.select()"
x-model.debounce="$store.files.tagsFilters.mentions"
/>
</div>
</div>
<div class="tags-group-scroll">
<ul x-show="$store.files.listMentions.length">
<template x-for="item in await $store.files.listMentions">
<li
:class="item.name === $store.files.filters.mentionText ? 'active' : ''"
>
<button
:id="'filter-mention-' + item.name.toLowerCase().replaceAll('@', '_').replaceAll('.', '_')"
@click="$store.files.filterByTag('mentionText', item.name, 'filter-mention-' + item.name.toLowerCase().replaceAll('@', '_').replaceAll('.', '_'))"
>
<div>
<span
class="visually-hidden"
x-text="item.name === $store.files.filters.mentionText ? 'active item:' : ''"
></span>
<span class="count" x-text="`(${item.nb})`"></span>
<span class="name" x-text="item.name"></span>
</div>
</button>
</li>
</template>
</ul>
</div>
</div>
<div class="tags-group">
<div class="tags-group-header">
<h3>
Boosted users
<span class="count">
(<span
x-text="formatNumber($store.files.listBoostsAuthors.length)"
></span
>)
</span>
</h3>
<div class="tags-group-filter">
<label
for="tags-group-boosts-authors-filter"
class="visually-hidden"
>filter boosts users</label
>
<input
id="tags-group-boosts-authors-filter"
type="text"
onClick="this.select()"
x-model.debounce="$store.files.tagsFilters.boostsAuthors"
/>
</div>
</div>
<div class="tags-group-scroll">
<ul x-show="$store.files.listBoostsAuthors.length">
<template x-for="item in $store.files.listBoostsAuthors">
<li
:class="item.url === $store.files.filters.fullText ? 'active' : ''"
>
<button
@click="$store.files.filterByTag('fullText', item.url, 'filter-boost-author-' + item.url.toLowerCase().replaceAll('/', '_'))"
:id="'filter-boost-author-' + item.url.toLowerCase().replaceAll('/', '_')"
>
<div>
<span
class="visually-hidden"
x-text="item.url === $store.files.filters.fullText ? 'active item:' : ''"
></span>
<span class="count" x-text="`(${item.nb})`"></span>
<span class="name">
<span x-text="item.name"></span>
<span x-text="item.domain" class="domain"></span>
</span>
</div>
</button>
</li>
</template>
</ul>
</div>
</div>
</aside>
</div>
<template x-if="$store.lightbox.show && $store.lightbox.data.length">
<div
id="lightbox"
class="lightbox overlay"
tabindex="-1"
@keyup.left="$store.lightbox.showPrev()"
@keyup.right="$store.lightbox.showNext()"
@keyup.esc="$store.lightbox.close()"
>
<div class="overlay-content">
<img
:alt="$store.lightbox.data[$store.lightbox.index].name"
:src="`data:${$store.lightbox.data[$store.lightbox.index].mediaType}; base64,${$store.files.sources[$store.lightbox.source][$store.lightbox.data[$store.lightbox.index].url].content}`"
2024-11-29 14:58:03 +01:00
@click="$store.lightbox.showNext()"
/>
</div>
<div class="overlay-ui">
<div class="backdrop" @click="$store.lightbox.close()"></div>
<button
x-show="$store.lightbox.data.length > 1"
class="viewer-next"
@click="$store.lightbox.showNext()"
>
<svg class="btn-icon" aria-hidden="true">
<use href="#viewer-next" /></svg
><span class="visually-hidden">Next image</span>
</button>
<button
x-show="$store.lightbox.data.length > 1"
class="viewer-prev"
@click="$store.lightbox.showPrev()"
>
<svg class="btn-icon" aria-hidden="true">
<use href="#viewer-prev" /></svg
><span class="visually-hidden">Previous image</span>
</button>
<button class="viewer-close" @click="$store.lightbox.close()">
<svg class="btn-icon" aria-hidden="true">
<use href="#close" /></svg
><span class="visually-hidden">Close image</span>
</button>
</div>
</div>
</template>
</main>
</template>
</div>
<svg style="display: none">
<symbol viewBox="0 -960 960 960" id="reset-filters">
<path
d="m653-208-46 46q-17 17-42 17.5T522-162q-17-17-17-42.5t17-42.5l46-46q-4-11-6-23t-2-24q0-58 41-99t99-41q9 0 18 .5t17 3.5q11 4 13.5 16.5T743-439l-43 43q-11 11-11 28t11 28q11 11 28 11t28-11l43-43q8-8 20.5-5.5T836-375q3 8 3.5 17t.5 18q0 58-41 99t-99 41q-13 0-24.5-2t-22.5-6ZM480-760q-117 0-198.5 81.5T200-480q0 72 32.5 132t87.5 98v-70q0-17 11.5-28.5T360-360q17 0 28.5 11.5T400-320v160q0 17-11.5 28.5T360-120H200q-17 0-28.5-11.5T160-160q0-17 11.5-28.5T200-200h54q-62-50-98-122.5T120-480q0-75 28.5-140.5t77-114q48.5-48.5 114-77T480-840q113 0 203.5 63T814-615q6 16 0 31t-22 21q-16 6-31.5 0T739-585q-31-78-100.5-126.5T480-760Z"
/>
</symbol>
<symbol viewBox="0 -960 960 960" id="toggle-order">
<path
d="M360-440q-17 0-28.5-11.5T320-480v-247l-75 75q-11 11-27.5 11T189-652q-12-12-12-28.5t12-28.5l143-143q6-6 13-8.5t15-2.5q8 0 15 2.5t13 8.5l144 144q12 12 11.5 28T531-652q-12 11-28 11.5T475-652l-75-75v247q0 17-11.5 28.5T360-440ZM600-97q-8 0-15-2.5t-13-8.5L428-252q-12-12-11.5-28t12.5-28q12-11 28-11.5t28 11.5l75 75v-247q0-17 11.5-28.5T600-520q17 0 28.5 11.5T640-480v247l75-75q11-11 27.5-11t28.5 11q12 12 12 28.5T771-251L628-108q-6 6-13 8.5T600-97Z"
/>
</symbol>
<symbol viewBox="0 -960 960 960" id="load-file">
<path
d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h207q16 0 30.5 6t25.5 17l57 57h360q17 0 28.5 11.5T880-680q0 17-11.5 28.5T840-640H447l-80-80H160v480l79-263q8-26 29.5-41.5T316-560h516q41 0 64.5 32.5T909-457l-72 240q-8 26-29.5 41.5T760-160H160Zm84-80h516l72-240H316l-72 240Zm-84-262v-218 218Zm84 262 72-240-72 240Z"
/>
</symbol>
<symbol viewBox="0 -960 960 960" id="nav-first">
<path
d="m313-480 155 156q11 11 11.5 27.5T468-268q-11 11-28 11t-28-11L228-452q-6-6-8.5-13t-2.5-15q0-8 2.5-15t8.5-13l184-184q11-11 27.5-11.5T468-692q11 11 11 28t-11 28L313-480Zm264 0 155 156q11 11 11.5 27.5T732-268q-11 11-28 11t-28-11L492-452q-6-6-8.5-13t-2.5-15q0-8 2.5-15t8.5-13l184-184q11-11 27.5-11.5T732-692q11 11 11 28t-11 28L577-480Z"
/>
</symbol>
<symbol viewBox="0 -960 960 960" id="nav-prev">
<path
d="m432-480 156 156q11 11 11 28t-11 28q-11 11-28 11t-28-11L348-452q-6-6-8.5-13t-2.5-15q0-8 2.5-15t8.5-13l184-184q11-11 28-11t28 11q11 11 11 28t-11 28L432-480Z"
/>
</symbol>
<symbol viewBox="0 -960 960 960" id="nav-next">
<path
d="M504-480 348-636q-11-11-11-28t11-28q11-11 28-11t28 11l184 184q6 6 8.5 13t2.5 15q0 8-2.5 15t-8.5 13L404-268q-11 11-28 11t-28-11q-11-11-11-28t11-28l156-156Z"
/>
</symbol>
<symbol viewBox="0 -960 960 960" id="nav-last">
<path
d="M383-480 228-636q-11-11-11.5-27.5T228-692q11-11 28-11t28 11l184 184q6 6 8.5 13t2.5 15q0 8-2.5 15t-8.5 13L284-268q-11 11-27.5 11.5T228-268q-11-11-11-28t11-28l155-156Zm264 0L492-636q-11-11-11.5-27.5T492-692q11-11 28-11t28 11l184 184q6 6 8.5 13t2.5 15q0 8-2.5 15t-8.5 13L548-268q-11 11-27.5 11.5T492-268q-11-11-11-28t11-28l155-156Z"
/>
</symbol>
<symbol viewBox="0 -960 960 960" id="close">
<path
d="M480-424 284-228q-11 11-28 11t-28-11q-11-11-11-28t11-28l196-196-196-196q-11-11-11-28t11-28q11-11 28-11t28 11l196 196 196-196q11-11 28-11t28 11q11 11 11 28t-11 28L536-480l196 196q11 11 11 28t-11 28q-11 11-28 11t-28-11L480-424Z"
/>
</symbol>
<symbol viewBox="0 -960 960 960" id="viewer-next">
<path
d="M504-480 348-636q-11-11-11-28t11-28q11-11 28-11t28 11l184 184q6 6 8.5 13t2.5 15q0 8-2.5 15t-8.5 13L404-268q-11 11-28 11t-28-11q-11-11-11-28t11-28l156-156Z"
/>
</symbol>
<symbol viewBox="0 -960 960 960" id="viewer-prev">
<path
d="m432-480 156 156q11 11 11 28t-11 28q-11 11-28 11t-28-11L348-452q-6-6-8.5-13t-2.5-15q0-8 2.5-15t8.5-13l184-184q11-11 28-11t28 11q11 11 11 28t-11 28L432-480Z"
/>
</symbol>
<symbol viewBox="0 -960 960 960" id="menu-actor">
<path
d="M480-480q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47ZM160-240v-32q0-34 17.5-62.5T224-378q62-31 126-46.5T480-440q66 0 130 15.5T736-378q29 15 46.5 43.5T800-272v32q0 33-23.5 56.5T720-160H240q-33 0-56.5-23.5T160-240Zm80 0h480v-32q0-11-5.5-20T700-306q-54-27-109-40.5T480-360q-56 0-111 13.5T260-306q-9 5-14.5 14t-5.5 20v32Zm240-320q33 0 56.5-23.5T560-640q0-33-23.5-56.5T480-720q-33 0-56.5 23.5T400-640q0 33 23.5 56.5T480-560Zm0-80Zm0 400Z"
/>
</symbol>
<symbol viewBox="0 -960 960 960" id="menu-filters">
<path
d="M440-240q-17 0-28.5-11.5T400-280q0-17 11.5-28.5T440-320h80q17 0 28.5 11.5T560-280q0 17-11.5 28.5T520-240h-80ZM280-440q-17 0-28.5-11.5T240-480q0-17 11.5-28.5T280-520h400q17 0 28.5 11.5T720-480q0 17-11.5 28.5T680-440H280ZM160-640q-17 0-28.5-11.5T120-680q0-17 11.5-28.5T160-720h640q17 0 28.5 11.5T840-680q0 17-11.5 28.5T800-640H160Z"
/>
</symbol>
<symbol viewBox="0 -960 960 960" id="menu-tags">
<path
d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h440q19 0 36 8.5t28 23.5l180 240q16 21 16 48t-16 48L664-192q-11 15-28 23.5t-36 8.5H160Zm0-80h440l180-240-180-240H160v480Zm310-240Z"
/>
</symbol>
<symbol viewBox="0 -960 960 960" id="menu-new">
<path
d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h207q16 0 30.5 6t25.5 17l57 57h360q17 0 28.5 11.5T880-680q0 17-11.5 28.5T840-640H447l-80-80H160v480l79-263q8-26 29.5-41.5T316-560h516q41 0 64.5 32.5T909-457l-72 240q-8 26-29.5 41.5T760-160H160Zm84-80h516l72-240H316l-72 240Zm-84-262v-218 218Zm84 262 72-240-72 240Z"
/>
</symbol>
<symbol viewBox="0 -960 960 960" id="fetch-data">
<path
d="M440-474v-242q-76 14-118 73.5T280-520h-20q-58 0-99 41t-41 99q0 58 41 99t99 41h480q42 0 71-29t29-71q0-42-29-71t-71-29h-60v-80q0-48-22-89.5T600-680v-93q74 35 117 103.5T760-520q69 8 114.5 59.5T920-340q0 75-52.5 127.5T740-160H260q-91 0-155.5-63T40-377q0-78 47-139t123-78q17-72 85-137t145-65q33 0 56.5 23.5T520-716v242l36-35q11-11 27.5-11t28.5 12q11 11 11 28t-11 28L508-348q-12 12-28 12t-28-12L348-452q-11-11-11.5-27.5T348-508q11-11 27.5-11.5T404-509l36 35Zm40-44Z"
/>
</symbol>
<symbol viewBox="0 -960 960 960" id="options">
<path
d="M433-80q-27 0-46.5-18T363-142l-9-66q-13-5-24.5-12T307-235l-62 26q-25 11-50 2t-39-32l-47-82q-14-23-8-49t27-43l53-40q-1-7-1-13.5v-27q0-6.5 1-13.5l-53-40q-21-17-27-43t8-49l47-82q14-23 39-32t50 2l62 26q11-8 23-15t24-12l9-66q4-26 23.5-44t46.5-18h94q27 0 46.5 18t23.5 44l9 66q13 5 24.5 12t22.5 15l62-26q25-11 50-2t39 32l47 82q14 23 8 49t-27 43l-53 40q1 7 1 13.5v27q0 6.5-2 13.5l53 40q21 17 27 43t-8 49l-48 82q-14 23-39 32t-50-2l-60-26q-11 8-23 15t-24 12l-9 66q-4 26-23.5 44T527-80h-94Zm7-80h79l14-106q31-8 57.5-23.5T639-327l99 41 39-68-86-65q5-14 7-29.5t2-31.5q0-16-2-31.5t-7-29.5l86-65-39-68-99 42q-22-23-48.5-38.5T533-694l-13-106h-79l-14 106q-31 8-57.5 23.5T321-633l-99-41-39 68 86 64q-5 15-7 30t-2 32q0 16 2 31t7 30l-86 65 39 68 99-42q22 23 48.5 38.5T427-266l13 106Zm42-180q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Zm-2-140Z"
/>
</symbol>
<symbol viewBox="0 -960 960 960" id="json">
<path
d="M190-360h70q17 0 28.5-11.5T300-400v-200h-60v190h-40v-50h-50v60q0 17 11.5 28.5T190-360Zm177 0h60q17 0 28.5-11.5T467-400v-60q0-17-11.5-28.5T427-500h-50v-50h40v20h50v-30q0-17-11.5-28.5T427-600h-60q-17 0-28.5 11.5T327-560v60q0 17 11.5 28.5T367-460h50v50h-40v-20h-50v30q0 17 11.5 28.5T367-360Zm176-60v-120h40v120h-40Zm-10 60h60q17 0 28.5-11.5T633-400v-160q0-17-11.5-28.5T593-600h-60q-17 0-28.5 11.5T493-560v160q0 17 11.5 28.5T533-360Zm127 0h50v-105l40 105h50v-240h-50v105l-40-105h-50v240ZM120-160q-33 0-56.5-23.5T40-240v-480q0-33 23.5-56.5T120-800h720q33 0 56.5 23.5T920-720v480q0 33-23.5 56.5T840-160H120Zm0-80h720v-480H120v480Zm0 0v-480 480Z"
/>
</symbol>
<symbol viewBox="0 -960 960 960" id="question">
<path
d="M478-240q21 0 35.5-14.5T528-290q0-21-14.5-35.5T478-340q-21 0-35.5 14.5T428-290q0 21 14.5 35.5T478-240Zm-36-154h74q0-33 7.5-52t42.5-52q26-26 41-49.5t15-56.5q0-56-41-86t-97-30q-57 0-92.5 30T342-618l66 26q5-18 22.5-39t53.5-21q32 0 48 17.5t16 38.5q0 20-12 37.5T506-526q-44 39-54 59t-10 73Zm38 314q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"
/>
</symbol>
<symbol viewBox="0 -960 960 960" id="bookmark">
<path
d="M200-120v-640q0-33 23.5-56.5T280-840h400q33 0 56.5 23.5T760-760v640L480-240 200-120Zm80-122 200-86 200 86v-518H280v518Zm0-518h400-400Z"
/>
</symbol>
<symbol viewBox="0 -960 960 960" id="favorite">
<path
d="m354-287 126-76 126 77-33-144 111-96-146-13-58-136-58 135-146 13 111 97-33 143ZM233-120l65-281L80-590l288-25 112-265 112 265 288 25-218 189 65 281-247-149-247 149Zm247-350Z"
/>
</symbol>
</svg>
2024-12-01 10:45:20 +01:00
<script src="js/main.js?20241215"></script>
2024-11-29 14:58:03 +01:00
</body>
</html>