Code refactor with Astro
8
.gitignore
vendored
@ -1,2 +1,10 @@
|
||||
.data
|
||||
.docs
|
||||
.astro
|
||||
node_modules
|
||||
|
||||
# unused Astro generated files
|
||||
dist/content-assets.mjs
|
||||
dist/content-modules.mjs
|
||||
dist/data-store.json
|
||||
dist/settings.json
|
||||
|
@ -1,9 +1,6 @@
|
||||
FROM nginx:latest
|
||||
|
||||
COPY index.html /usr/share/nginx/html/
|
||||
COPY css/ /usr/share/nginx/html/css/
|
||||
COPY js/ /usr/share/nginx/html/js/
|
||||
COPY img/ /usr/share/nginx/html/img/
|
||||
COPY svg/ /usr/share/nginx/html/svg/
|
||||
COPY dist/ /usr/share/nginx/html/dist/
|
||||
|
||||
EXPOSE 80
|
||||
|
@ -1,170 +0,0 @@
|
||||
@mixin theme-vars($theme: "") {
|
||||
@if $theme == "dark" {
|
||||
--bg0: #{$dark-bg0};
|
||||
--bg1: #{$dark-bg1};
|
||||
--bg2: #{$dark-bg2};
|
||||
--bg3: #{$dark-bg3};
|
||||
--bg4: #{$dark-bg4};
|
||||
|
||||
--fg0: #{$dark-fg0};
|
||||
--fg1: #{$dark-fg1};
|
||||
--fg2: #{$dark-fg2};
|
||||
--fg-inv: #{$dark-fg-inv};
|
||||
|
||||
--menu-bg: #{$dark-menu-bg};
|
||||
--menu-fg: #{$dark-menu-fg};
|
||||
--menu-fg-active: #{$dark-menu-fg-active};
|
||||
--menu-icon: #{$dark-menu-icon};
|
||||
--menu-filter-active: #{$dark-menu-filter-active};
|
||||
--panel-close: #{$dark-panel-close};
|
||||
--panel-close-hover: #{$dark-panel-close-hover};
|
||||
|
||||
--accent: #{$dark-accent};
|
||||
--accent-dark: #{$dark-accent-dark};
|
||||
--accent-light: #{$dark-accent-light};
|
||||
--accent-light2: #{$dark-accent-light2};
|
||||
--accent-light3: #{$dark-accent-light3};
|
||||
|
||||
--overlay-icon: #{$dark-overlay-icon};
|
||||
--overlay-icon-hover: #{$dark-overlay-icon-hover};
|
||||
--overlay-backdrop: #{$dark-overlay-backdrop};
|
||||
--menu-backdrop: #{$dark-menu-backdrop};
|
||||
|
||||
--bg-input: #{$dark-bg-input};
|
||||
--bg-input-hover: #{$dark-bg-input-hover};
|
||||
--bg-input-focus: #{$dark-bg-input-focus};
|
||||
|
||||
--bg-button: #{$dark-bg-button};
|
||||
--bg-button-hover: #{$dark-bg-button-hover};
|
||||
--button-svg: #{$dark-button-svg};
|
||||
--button-svg-hover: #{$dark-button-svg-hover};
|
||||
--button-svg-focus: #{$dark-button-svg-focus};
|
||||
--button-svg-active: #{$dark-button-svg-active};
|
||||
|
||||
--fg-button-focus: #{$dark-fg-button-focus};
|
||||
--fg-button-active: #{$dark-fg-button-active};
|
||||
|
||||
--posts-count: #{$dark-posts-count};
|
||||
--selection-text: #{$dark-selection-text};
|
||||
--selection-bg: #{$dark-selection-bg};
|
||||
--stripe1: #{$dark-stripe1};
|
||||
--stripe2: #{$dark-stripe2};
|
||||
--stripe-fg: #{$dark-stripe-fg};
|
||||
--private-post-bg: #{$dark-private-post-bg};
|
||||
--private-post-border: #{$dark-private-post-border};
|
||||
|
||||
.actors-wrapper {
|
||||
--actor-hue: 0;
|
||||
--actor-bg0: #{$dark-actor-bg0};
|
||||
--actor-bg0-ok: #{$dark-actor-bg0-ok};
|
||||
--actor-bg1: #{$dark-actor-bg1};
|
||||
--actor-bg1-ok: #{$dark-actor-bg1-ok};
|
||||
--actor-bg2: #{$dark-actor-bg2};
|
||||
--actor-bg2-ok: #{$dark-actor-bg2-ok};
|
||||
--actor-bg3: #{$dark-actor-bg3};
|
||||
--actor-bg3-ok: #{$dark-actor-bg3-ok};
|
||||
--actor-bg4: #{$dark-actor-bg4};
|
||||
--actor-bg4-ok: #{$dark-actor-bg4-ok};
|
||||
|
||||
--actor-fg0: #{$dark-actor-fg0};
|
||||
--actor-fg1: #{$dark-actor-fg1};
|
||||
--actor-fg1-ok: #{$dark-actor-fg1-ok};
|
||||
--actor-tabs-bg: #{$dark-actor-tabs-bg};
|
||||
--actor-accent: #{$dark-actor-accent};
|
||||
--actor-accent-ok: #{$dark-actor-accent-ok};
|
||||
--actor-accent2-ok: #{$dark-actor-accent2-ok};
|
||||
}
|
||||
.actors-tabs button {
|
||||
--actor-accent-ok: #{$dark-actor-accent-ok};
|
||||
--actor-accent2-ok: #{$dark-actor-accent2-ok};
|
||||
}
|
||||
.toot-content {
|
||||
--actor-accent-ok: #{$dark-actor-accent-ok};
|
||||
--actor-accent2-ok: #{$dark-actor-accent2-ok};
|
||||
}
|
||||
|
||||
} @else {
|
||||
|
||||
--bg0: #{$bg0};
|
||||
--bg1: #{$bg1};
|
||||
--bg2: #{$bg2};
|
||||
--bg3: #{$bg3};
|
||||
--bg4: #{$bg4};
|
||||
|
||||
--fg0: #{$fg0};
|
||||
--fg1: #{$fg1};
|
||||
--fg2: #{$fg2};
|
||||
--fg-inv: #{$fg-inv};
|
||||
|
||||
--menu-bg: #{$menu-bg};
|
||||
--menu-fg: #{$menu-fg};
|
||||
--menu-fg-active: #{$menu-fg-active};
|
||||
--menu-icon: #{$menu-icon};
|
||||
--menu-filter-active: #{$menu-filter-active};
|
||||
--panel-close: #{$panel-close};
|
||||
--panel-close-hover: #{$panel-close-hover};
|
||||
|
||||
--accent: #{$accent};
|
||||
--accent-dark: #{$accent-dark};
|
||||
--accent-light: #{$accent-light};
|
||||
--accent-light2: #{$accent-light2};
|
||||
--accent-light3: #{$accent-light3};
|
||||
|
||||
--overlay-icon: #{$overlay-icon};
|
||||
--overlay-icon-hover: #{$overlay-icon-hover};
|
||||
--overlay-backdrop: #{$overlay-backdrop};
|
||||
--menu-backdrop: #{$menu-backdrop};
|
||||
|
||||
--bg-input: #{$bg-input};
|
||||
--bg-input-hover: #{$bg-input-hover};
|
||||
--bg-input-focus: #{$bg-input-focus};
|
||||
|
||||
--bg-button: #{$bg-button};
|
||||
--bg-button-hover: #{$bg-button-hover};
|
||||
--button-svg: #{$button-svg};
|
||||
--button-svg-hover: #{$button-svg-hover};
|
||||
--button-svg-focus: #{$button-svg-focus};
|
||||
--button-svg-active: #{$button-svg-active};
|
||||
|
||||
--fg-button-focus: #{$fg-button-focus};
|
||||
--fg-button-active: #{$fg-button-active};
|
||||
|
||||
--posts-count: #{$posts-count};
|
||||
--selection-text: #{$selection-text};
|
||||
--selection-bg: #{$selection-bg};
|
||||
--stripe1: #{$stripe1};
|
||||
--stripe2: #{$stripe2};
|
||||
--stripe-fg: #{$stripe-fg};
|
||||
--private-post-bg: #{$private-post-bg};
|
||||
--private-post-border: #{$private-post-border};
|
||||
|
||||
.actors-wrapper {
|
||||
--actor-hue: 0;
|
||||
--actor-bg0: #{$actor-bg0};
|
||||
--actor-bg0-ok: #{$actor-bg0-ok};
|
||||
--actor-bg1: #{$actor-bg1};
|
||||
--actor-bg1-ok: #{$actor-bg1-ok};
|
||||
--actor-bg2: #{$actor-bg2};
|
||||
--actor-bg2-ok: #{$actor-bg2-ok};
|
||||
--actor-bg3: #{$actor-bg3};
|
||||
--actor-bg3-ok: #{$actor-bg3-ok};
|
||||
--actor-bg4: #{$actor-bg4};
|
||||
--actor-bg4-ok: #{$actor-bg4-ok};
|
||||
--actor-fg0: #{$actor-fg0};
|
||||
--actor-fg1: #{$actor-fg1};
|
||||
--actor-fg1-ok: #{$actor-fg1-ok};
|
||||
--actor-tabs-bg: #{$actor-tabs-bg};
|
||||
--actor-accent: #{$actor-accent};
|
||||
--actor-accent-ok: #{$actor-accent-ok};
|
||||
--actor-accent2-ok: #{$actor-accent2-ok};
|
||||
}
|
||||
.actors-tabs button {
|
||||
--actor-accent-ok: #{$actor-accent-ok};
|
||||
--actor-accent2-ok: #{$actor-accent2-ok};
|
||||
}
|
||||
.toot-content {
|
||||
--actor-accent-ok: #{$actor-accent-ok};
|
||||
--actor-accent2-ok: #{$actor-accent2-ok};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
@import "colors";
|
||||
@import "mixins";
|
||||
|
||||
@import "global";
|
||||
@import "layout";
|
||||
@import "welcome";
|
||||
@import "actor";
|
||||
@import "filters";
|
||||
@import "toot";
|
||||
@import "tags";
|
||||
@import "menu";
|
||||
@import "overlay";
|
48
dev/README.md
Normal file
@ -0,0 +1,48 @@
|
||||
# Astro Starter Kit: Basics
|
||||
|
||||
```sh
|
||||
npm create astro@latest -- --template basics
|
||||
```
|
||||
|
||||
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
|
||||
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics)
|
||||
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json)
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
|
||||
![just-the-basics](https://github.com/withastro/astro/assets/2244813/a0a5533c-a856-4198-8470-2d67b1d7c554)
|
||||
|
||||
## 🚀 Project Structure
|
||||
|
||||
Inside of your Astro project, you'll see the following folders and files:
|
||||
|
||||
```text
|
||||
/
|
||||
├── public/
|
||||
│ └── favicon.svg
|
||||
├── src/
|
||||
│ ├── layouts/
|
||||
│ │ └── Layout.astro
|
||||
│ └── pages/
|
||||
│ └── index.astro
|
||||
└── package.json
|
||||
```
|
||||
|
||||
To learn more about the folder structure of an Astro project, refer to [our guide on project structure](https://docs.astro.build/en/basics/project-structure/).
|
||||
|
||||
## 🧞 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :------------------------ | :----------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:4321` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
||||
|
||||
## 👀 Want to learn more?
|
||||
|
||||
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
10
dev/astro.config.mjs
Normal file
@ -0,0 +1,10 @@
|
||||
// @ts-check
|
||||
import { defineConfig } from "astro/config";
|
||||
|
||||
import relativeLinks from "astro-relative-links";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [relativeLinks()],
|
||||
outDir: "../dist",
|
||||
});
|
5870
dev/package-lock.json
generated
Normal file
18
dev/package.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "marl",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^5.1.1",
|
||||
"astro-relative-links": "^0.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"sass-embedded": "^1.83.0"
|
||||
}
|
||||
}
|
27
dev/src/components/App.astro
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
import WelcomeScreen from './screens/WelcomeScreen.astro';
|
||||
import LoadingScreen from './screens/LoadingScreen.astro';
|
||||
import MainScreen from './screens/MainScreen.astro';
|
||||
---
|
||||
|
||||
<div
|
||||
class="main-page"
|
||||
id="app"
|
||||
x-data
|
||||
:class="$store.ui.appClasses"
|
||||
x-init="$watch('$store.files.appReady', value => checkAppReady(value))"
|
||||
>
|
||||
|
||||
<template x-if="!$store.files.appReady && !$store.files.someFilesLoaded && !$store.files.loading">
|
||||
<WelcomeScreen />
|
||||
</template>
|
||||
|
||||
<template x-if="(!$store.files.appReady && $store.files.someFilesLoaded) || $store.files.loading">
|
||||
<LoadingScreen />
|
||||
</template>
|
||||
|
||||
<template x-if="$store.files.appReady">
|
||||
<MainScreen />
|
||||
</template>
|
||||
|
||||
</div>
|
47
dev/src/components/LightBox.astro
Normal file
@ -0,0 +1,47 @@
|
||||
<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}`"
|
||||
@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>
|
44
dev/src/components/MobileMenu.astro
Normal file
@ -0,0 +1,44 @@
|
||||
<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()"
|
||||
/>
|
40
dev/src/components/PagingBottom.astro
Normal file
@ -0,0 +1,40 @@
|
||||
<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>
|
38
dev/src/components/PagingOptions.astro
Normal file
@ -0,0 +1,38 @@
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
59
dev/src/components/PagingTop.astro
Normal file
@ -0,0 +1,59 @@
|
||||
---
|
||||
import PagingOptions from "./PagingOptions.astro";
|
||||
---
|
||||
|
||||
<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>
|
||||
|
||||
<PagingOptions />
|
||||
|
||||
<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>
|
102
dev/src/components/SvgSprites.astro
Normal file
@ -0,0 +1,102 @@
|
||||
<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>
|
After Width: | Height: | Size: 8.7 KiB |
145
dev/src/components/panels/ActorPanel.astro
Normal file
@ -0,0 +1,145 @@
|
||||
---
|
||||
import CloseBtn from './CloseBtn.astro'
|
||||
import ImgAvatar from './actor/ImgAvatar.astro'
|
||||
import ImgHeader from './actor/ImgHeader.astro'
|
||||
import LikesBookmarks from './actor/LikesBookmarks.astro'
|
||||
---
|
||||
|
||||
<div
|
||||
class="actor mobile-menu-panel"
|
||||
id="panel-actor"
|
||||
role="region"
|
||||
aria-labelledby="actor-title"
|
||||
tabindex="-1"
|
||||
>
|
||||
<CloseBtn />
|
||||
|
||||
<h2 class="visually-hidden" id="actor-title">Account info</h2>
|
||||
<h3 class="visually-hidden" id="actor-tabs-title">Accounts</h3>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
<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>
|
||||
|
||||
<template x-for="source in $store.files.sources">
|
||||
<div
|
||||
class="actor-panel"
|
||||
:id="'actorpanel-' + source.id"
|
||||
role="tabpanel"
|
||||
tabindex="0"
|
||||
: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">
|
||||
<ImgHeader />
|
||||
</div>
|
||||
|
||||
<div class="actor-id">
|
||||
<ImgAvatar />
|
||||
<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>
|
||||
|
||||
<div class="actor-summary" x-show="a.summary" x-html="await a.summary"></div>
|
||||
|
||||
<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>
|
||||
|
||||
<div class="actor-posts-count">
|
||||
<div class="total">
|
||||
<span class="count" x-text="formatNumber(source.outbox.totalItems)"></span>
|
||||
<span class="label">posts</span>
|
||||
</div>
|
||||
|
||||
<div class="archive">
|
||||
<span class="count" x-text="formatNumber(source.nbToots)"></span>
|
||||
<span class="label">in archive</span>
|
||||
</div>
|
||||
|
||||
<details class="comment" 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>
|
||||
</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>
|
||||
<span class="summary-label" :id="source + 'raw-data-label'"
|
||||
>Raw data <em>(actor.json)</em></span
|
||||
>
|
||||
</summary>
|
||||
<div class="details-content">
|
||||
<textarea
|
||||
x-text="formatJson(await a)"
|
||||
:aria-labelledby="source + 'raw-data-label'"
|
||||
readonly
|
||||
></textarea>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actor-likes-bookmarks">
|
||||
<LikesBookmarks type="likes" />
|
||||
<LikesBookmarks type="bookmarks" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
5
dev/src/components/panels/CloseBtn.astro
Normal file
@ -0,0 +1,5 @@
|
||||
<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>
|
130
dev/src/components/panels/FiltersPanel.astro
Normal file
@ -0,0 +1,130 @@
|
||||
---
|
||||
import CloseBtn from './CloseBtn.astro'
|
||||
import FilterText from './filters/FilterText.astro';
|
||||
import FilterCheckbox from './filters/FilterCheckbox.astro';
|
||||
---
|
||||
|
||||
<div
|
||||
class="toots-filters mobile-menu-panel"
|
||||
id="panel-filters"
|
||||
role="search"
|
||||
aria-labelledby="toots-filter-title"
|
||||
tabindex="-1"
|
||||
>
|
||||
<CloseBtn />
|
||||
|
||||
<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">
|
||||
<FilterText label="Full text" name="fullText" />
|
||||
<FilterText label="Hashtags" name="hashtagText" />
|
||||
<FilterText label="Mentions" name="mentionText" />
|
||||
<FilterText label="External links" name="externalLink" />
|
||||
<FilterText label="Summary (CW)" name="summary" />
|
||||
</div>
|
||||
|
||||
<div class="toots-filters-group">
|
||||
<FilterCheckbox label="Has been edited" name="isEdited" />
|
||||
<template x-show="$store.files.duplicates">
|
||||
<FilterCheckbox label="Non-exact duplicates" name="isDuplicate" />
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="toots-filters-group">
|
||||
<h3 class="toots-filters-group-title">Must contain</h3>
|
||||
|
||||
<FilterCheckbox label="Hashtag(s)" name="hasHashtags" />
|
||||
<FilterCheckbox label="Mention(s)" name="hasMentions" />
|
||||
<FilterCheckbox label="External link(s)" name="hasExternalLink" />
|
||||
<FilterCheckbox label="Summary (CW)" name="hasSummary" />
|
||||
</div>
|
||||
|
||||
<div class="toots-filters-group">
|
||||
<h3 class="toots-filters-group-title">Type</h3>
|
||||
|
||||
<FilterCheckbox label="Original posts (incl. replies)" name="typeOriginal" />
|
||||
<FilterCheckbox label="Boosts" name="typeBoost" />
|
||||
</div>
|
||||
<div class="toots-filters-group">
|
||||
<FilterCheckbox label='Does not start with "@"' name="noStartingAt" />
|
||||
<FilterCheckbox label="Marked as sensitive" name="isSensitive" />
|
||||
</div>
|
||||
|
||||
<div class="toots-filters-group">
|
||||
<h3 class="toots-filters-group-title">Must have attachment</h3>
|
||||
|
||||
<FilterCheckbox label="Any type" name="attachmentAny" />
|
||||
<FilterCheckbox label="Image(s)" name="attachmentImage" />
|
||||
<FilterCheckbox label="Video(s)" name="attachmentVideo" />
|
||||
<FilterCheckbox label="Sound(s)" name="attachmentSound" />
|
||||
</div>
|
||||
|
||||
<div class="toots-filters-group">
|
||||
<FilterCheckbox label="Without alt text" name="attachmentNoAltText" />
|
||||
<FilterCheckbox label="With alt text" name="attachmentWithAltText" />
|
||||
</div>
|
||||
|
||||
<div class="toots-filters-group">
|
||||
<h3 class="toots-filters-group-title">Visibility</h3>
|
||||
|
||||
<FilterCheckbox label="Public" name="visibilityPublic" />
|
||||
<FilterCheckbox label="Unlisted" name="visibilityUnlisted" />
|
||||
<FilterCheckbox label="Followers only" name="visibilityFollowers" />
|
||||
<FilterCheckbox label="Mentioned people only" name="visibilityMentioned" />
|
||||
</div>
|
||||
|
||||
<div class="toots-filters-group" x-show="$store.files.toots.length">
|
||||
<h3 class="toots-filters-group-title">Language</h3>
|
||||
<template x-for="lang in $store.files.sortedLanguages">
|
||||
|
||||
<div class="toots-filter checkbox" :class="isFilterActive('lang_' + lang[0]) ? 'active' : ''">
|
||||
<label :for="'filter-lang-' + lang[0]">
|
||||
<input
|
||||
:id="'filter-lang-' + lang[0]"
|
||||
type="checkbox"
|
||||
x-model="$store.files.filters['lang_' + lang[0]]"
|
||||
@change="$store.files.setFilter()"
|
||||
/>
|
||||
<span>
|
||||
<span x-text="lang[0]"></span>
|
||||
(<span x-text="lang[1]"></span>)
|
||||
</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>
|
||||
<template x-for="source in $store.files.sources">
|
||||
|
||||
<div class="toots-filter checkbox" :class="isFilterActive('actor_' + source.id) ? 'active' : ''">
|
||||
<label :for="'filter-actor-' + source.id">
|
||||
<input
|
||||
:id="'filter-actor-' + source.id"
|
||||
type="checkbox"
|
||||
x-model="$store.files.filters['actor_' + source.id]"
|
||||
@change="$store.files.setFilter()"
|
||||
/>
|
||||
<span>
|
||||
<span x-text="source.actor.name"></span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="toots-filters-reset">
|
||||
<button @click="$store.files.resetFilters(true)" :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>
|
31
dev/src/components/panels/HeaderPanel.astro
Normal file
@ -0,0 +1,31 @@
|
||||
---
|
||||
import PagingTop from '../PagingTop.astro';
|
||||
---
|
||||
|
||||
<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>
|
||||
|
||||
<PagingTop />
|
||||
|
||||
</header>
|
47
dev/src/components/panels/PostsPanel.astro
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
import PagingBottom from '../PagingBottom.astro';
|
||||
import Post from './posts/Post.astro';
|
||||
---
|
||||
|
||||
<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"
|
||||
>
|
||||
<Post />
|
||||
</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
|
||||
@click="$store.files.resetFilters(true)"
|
||||
: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>
|
||||
|
||||
<PagingBottom />
|
||||
|
||||
</div>
|
19
dev/src/components/panels/TagsPanel.astro
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
import CloseBtn from './CloseBtn.astro'
|
||||
import TagsGroup from './tags/TagsGroup.astro';
|
||||
---
|
||||
|
||||
<aside
|
||||
class="toots-tags mobile-menu-panel"
|
||||
id="panel-tags"
|
||||
aria-labelledby="toots-tags-title"
|
||||
tabindex="-1"
|
||||
>
|
||||
<CloseBtn />
|
||||
|
||||
<h2 class="tags-title" id="toots-tags-title">Tags</h2>
|
||||
|
||||
<TagsGroup type="hashtags" />
|
||||
<TagsGroup type="mentions" />
|
||||
<TagsGroup type="boosts" />
|
||||
</aside>
|
20
dev/src/components/panels/ToolsPanel.astro
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
import CloseBtn from './CloseBtn.astro'
|
||||
---
|
||||
|
||||
<aside
|
||||
class="panel-tools mobile-menu-panel"
|
||||
id="panel-tools"
|
||||
aria-labelledby="panel-tools-title"
|
||||
tabindex="-1"
|
||||
>
|
||||
<CloseBtn />
|
||||
|
||||
<h2 class="tools-title visually-hidden" id="panel-tools-title">Tools</h2>
|
||||
|
||||
<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>
|
||||
</aside>
|
22
dev/src/components/panels/actor/ImgAvatar.astro
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
import NoAvatar from '../../../img/no-avatar.png'
|
||||
---
|
||||
|
||||
<template x-if="source.avatar.noImg">
|
||||
<div class="actor-avatar no-avatar">
|
||||
<img :alt="a.name" src={NoAvatar.src} />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template x-if="! source.avatar.noImg">
|
||||
<button
|
||||
id="actor-avatar"
|
||||
class="actor-avatar"
|
||||
@click="$store.lightbox.openProfileImg('avatar', 'actor-avatar', source.id)"
|
||||
>
|
||||
<img
|
||||
:alt="a.name"
|
||||
:src="`data:${source.avatar.type}; base64,${source.avatar.content}`"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
22
dev/src/components/panels/actor/ImgHeader.astro
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
import NoHeader from '../../../img/no-header.png'
|
||||
---
|
||||
|
||||
<template x-if="source.header.noImg">
|
||||
<div class="actor-header no-header">
|
||||
<img alt="header" src={NoHeader.src} />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template x-if="! source.header.noImg">
|
||||
<button
|
||||
id="actor-header"
|
||||
class="actor-header"
|
||||
@click="$store.lightbox.openProfileImg('header', 'actor-header', source.id)"
|
||||
>
|
||||
<img
|
||||
alt="header"
|
||||
:src="`data:${source.header.type}; base64,${source.header.content}`"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
37
dev/src/components/panels/actor/LikesBookmarks.astro
Normal file
@ -0,0 +1,37 @@
|
||||
---
|
||||
const { type } = Astro.props;
|
||||
|
||||
let label = "Favorites";
|
||||
if (type === "bookmarks") {
|
||||
label = "Bookmarks";
|
||||
}
|
||||
---
|
||||
|
||||
<div class={`actor-${type}`}>
|
||||
<details>
|
||||
<summary>
|
||||
<span class="summary-icon">
|
||||
<svg aria-hidden="true">
|
||||
<use href="#favorite" />
|
||||
</svg>
|
||||
</span>
|
||||
<h2 class="summary-label">
|
||||
{label} (<span class="count" x-text={`source.${type}.length`}></span>)
|
||||
</h2>
|
||||
</summary>
|
||||
<div class="details-content">
|
||||
<template x-if={`source.${type}.length`}>
|
||||
<ul>
|
||||
<template x-for={`url in source.${type}`}>
|
||||
<li>
|
||||
<a :href="url" x-html="formatLikesBookmarks(url)"></a>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</template>
|
||||
<template x-if={`! source.${type}.length`}>
|
||||
<p class="no-content">... no {label?.toLowerCase()} ...</p>
|
||||
</template>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
15
dev/src/components/panels/filters/FilterCheckbox.astro
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
const { label, name } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="toots-filter checkbox" :class={`isFilterActive('${name}') ? 'active' : ''`}>
|
||||
<label for={"filter-"+name}>
|
||||
<input
|
||||
id={"filter-"+name}
|
||||
type="checkbox"
|
||||
x-model.debounce={"$store.files.filters."+name}
|
||||
@change.debounce="$store.files.setFilter()"
|
||||
/>
|
||||
<span>{label}</span>
|
||||
</label>
|
||||
</div>
|
19
dev/src/components/panels/filters/FilterText.astro
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
const { label, name } = Astro.props;
|
||||
---
|
||||
|
||||
<div
|
||||
class="toots-filter text"
|
||||
:class={"isFilterActive('"+name+"') ? 'active' : ''"}
|
||||
>
|
||||
<label for={"filter-"+name}>{label}</label>
|
||||
<div class="input-wrapper">
|
||||
<input
|
||||
id={"filter-"+name}
|
||||
type="text"
|
||||
x-model.debounce={"$store.files.filters."+name}
|
||||
@keyup.debounce="$store.files.setFilter()"
|
||||
@change.debounce="$store.files.setFilter()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
11
dev/src/components/panels/posts/AttachmentDescription.astro
Normal file
@ -0,0 +1,11 @@
|
||||
<div class="att-description">
|
||||
<span
|
||||
class="desc-body"
|
||||
x-text="att.name ?? 'No description provided'"
|
||||
:id="'att-' + toot._marl.id + '-' + index + '-desc'"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<span class="desc-source">
|
||||
<strong>In archive:</strong> <span x-text="att.url"></span>
|
||||
</span>
|
||||
</div>
|
24
dev/src/components/panels/posts/AttachmentImage.astro
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
import AttachmentDescription from './AttachmentDescription.astro'
|
||||
---
|
||||
|
||||
<template
|
||||
x-if="attachmentIsImage(att) && $store.files.sources[toot._marl.source][att.url]"
|
||||
>
|
||||
<div class="att-wrapper">
|
||||
<button
|
||||
class="att-img-wrapper"
|
||||
@click="$store.lightbox.open(toot, index, toot._marl.id + '-' + index)"
|
||||
:id="'att-' + toot._marl.id + '-' + index"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
:src="`data:${att.mediaType}; base64,${await $store.files.sources[toot._marl.source][att.url].content}`"
|
||||
:aria-labelledby="'att-' + toot._marl.id + '-' + index + '-desc'"
|
||||
/>
|
||||
</button>
|
||||
|
||||
<AttachmentDescription />
|
||||
|
||||
</div>
|
||||
</template>
|
20
dev/src/components/panels/posts/AttachmentSound.astro
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
import AttachmentDescription from './AttachmentDescription.astro'
|
||||
---
|
||||
|
||||
<template
|
||||
x-if="attachmentIsSound(att) && $store.files.sources[toot._marl.source][att.url]"
|
||||
>
|
||||
<div class="att-wrapper">
|
||||
<audio
|
||||
controls
|
||||
|
||||
|
||||
:src="`data:${att.mediaType}; base64,${await $store.files.sources[toot._marl.source][att.url].content}`"
|
||||
:aria-labelledby="'att-' + toot._marl.id + '-' + index + '-desc'"
|
||||
></audio>
|
||||
|
||||
<AttachmentDescription />
|
||||
|
||||
</div>
|
||||
</template>
|
20
dev/src/components/panels/posts/AttachmentVideo.astro
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
import AttachmentDescription from './AttachmentDescription.astro'
|
||||
---
|
||||
|
||||
<template
|
||||
x-if="attachmentIsVideo(att) && $store.files.sources[toot._marl.source][att.url]"
|
||||
>
|
||||
<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}`"
|
||||
:aria-labelledby="'att-' + toot._marl.id + '-' + index + '-desc'"
|
||||
></video>
|
||||
|
||||
<AttachmentDescription />
|
||||
|
||||
</div>
|
||||
</template>
|
150
dev/src/components/panels/posts/Post.astro
Normal file
@ -0,0 +1,150 @@
|
||||
---
|
||||
import AttachmentImage from "./AttachmentImage.astro";
|
||||
import AttachmentSound from "./AttachmentSound.astro";
|
||||
import AttachmentVideo from "./AttachmentVideo.astro";
|
||||
---
|
||||
|
||||
<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"
|
||||
>
|
||||
<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)"
|
||||
>
|
||||
<AttachmentImage />
|
||||
<AttachmentSound />
|
||||
<AttachmentVideo />
|
||||
</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>
|
||||
<span class="summary-label" :id="'raw-' + toot._marl.id">Raw data</span>
|
||||
</summary>
|
||||
<div class="details-content">
|
||||
<textarea
|
||||
x-text="formatJson(toot)"
|
||||
:aria-labelledby="'raw-' + toot._marl.id"
|
||||
readonly
|
||||
></textarea>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
10
dev/src/components/panels/tags/TagsGroup.astro
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
import TagsGroupHeader from './TagsGroupHeader.astro';
|
||||
import TagsGroupMain from './TagsGroupMain.astro';
|
||||
const { type } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="tags-group">
|
||||
<TagsGroupHeader type={type} />
|
||||
<TagsGroupMain type={type} />
|
||||
</div>
|
43
dev/src/components/panels/tags/TagsGroupHeader.astro
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
const { type } = Astro.props;
|
||||
let title, filterLabel, storeEntry, filterId;
|
||||
|
||||
switch (type) {
|
||||
case 'hashtags':
|
||||
title = "Hashtags";
|
||||
filterLabel = "filter hashtags";
|
||||
filterId = 'hashtags';
|
||||
storeEntry = "listHashtags";
|
||||
break;
|
||||
case 'mentions':
|
||||
title = "Mentions";
|
||||
filterLabel = "filter mentions";
|
||||
filterId = 'mentions';
|
||||
storeEntry = "listMentions";
|
||||
break;
|
||||
case 'boosts':
|
||||
title = "Boosted users";
|
||||
filterLabel = "filter boosted users";
|
||||
filterId = 'boostsAuthors';
|
||||
storeEntry = "listBoostsAuthors";
|
||||
break;
|
||||
}
|
||||
|
||||
---
|
||||
<div class="tags-group-header">
|
||||
<h3>
|
||||
{title}
|
||||
<span class="count">
|
||||
(<span x-text={`formatNumber($store.files.${storeEntry}.length)`}></span>)
|
||||
</span>
|
||||
</h3>
|
||||
<div class="tags-group-filter">
|
||||
<label for={`tags-group-${filterId}-filter`} class="visually-hidden">{filterLabel}</label>
|
||||
<input
|
||||
id={`tags-group-${filterId}-filter`}
|
||||
type="text"
|
||||
onclick="this.select()"
|
||||
x-model.debounce={`$store.files.tagsFilters.${filterId}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
58
dev/src/components/panels/tags/TagsGroupMain.astro
Normal file
@ -0,0 +1,58 @@
|
||||
---
|
||||
const { type } = Astro.props;
|
||||
let storeData, storeFilter, filterProp, btnId, clickCmd;
|
||||
|
||||
switch (type) {
|
||||
case 'hashtags':
|
||||
storeData = "listHashtags";
|
||||
storeFilter = "hashtagText";
|
||||
filterProp = "name";
|
||||
btnId = "hashtag";
|
||||
clickCmd = `.slice(1)`;
|
||||
break;
|
||||
case 'mentions':
|
||||
storeData = "listMentions";
|
||||
storeFilter = "mentionText";
|
||||
filterProp = "name";
|
||||
btnId = "mention";
|
||||
clickCmd = `.replaceAll('@', '_').replaceAll('.', '_')`;
|
||||
break;
|
||||
case 'boosts':
|
||||
storeData = "listBoostsAuthors";
|
||||
storeFilter = "fullText";
|
||||
filterProp = "url";
|
||||
btnId = "boost-author";
|
||||
clickCmd = `.replaceAll('/', '_')`;
|
||||
break;
|
||||
}
|
||||
|
||||
---
|
||||
<div class="tags-group-scroll">
|
||||
<ul x-show={`$store.files.${storeData}.length`}>
|
||||
<template x-for={`item in await $store.files.${storeData}`}>
|
||||
<li :class={`item.${filterProp} === $store.files.filters.${storeFilter} ? 'active' : ''`}>
|
||||
<button
|
||||
:id={`'filter-${btnId}-' + item.${filterProp}.toLowerCase()${clickCmd}`}
|
||||
@click={`$store.files.filterByTag('${storeFilter}', item.${filterProp}, 'filter-${btnId}-' + item.${filterProp}.toLowerCase()${clickCmd})`}
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
class="visually-hidden"
|
||||
x-text={`item.${filterProp} === $store.files.filters.${storeFilter} ? 'active item:' : ''`}
|
||||
></span>
|
||||
<span class="count" x-text="`(${item.nb})`"></span>
|
||||
{type !== 'boosts' && (
|
||||
<span class="name" x-text="item.name"></span>
|
||||
)}
|
||||
{type === 'boosts' && (
|
||||
<span class="name">
|
||||
<span x-text="item.name"></span>
|
||||
<span x-text="item.domain" class="domain"></span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
8
dev/src/components/screens/LoadingScreen.astro
Normal file
@ -0,0 +1,8 @@
|
||||
<main class="welcome loading-more">
|
||||
<div class="intro">
|
||||
<div class="file-loader loading">
|
||||
<div>... Loading ...</div>
|
||||
<div class="filename" x-text="$store.files.loadingName"></div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
32
dev/src/components/screens/MainScreen.astro
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
import MobileMenu from '../MobileMenu.astro';
|
||||
import LightBox from '../LightBox.astro';
|
||||
|
||||
import ActorPanel from '../panels/ActorPanel.astro';
|
||||
import FiltersPanel from '../panels/FiltersPanel.astro';
|
||||
import HeaderPanel from '../panels/HeaderPanel.astro';
|
||||
import PostsPanel from '../panels/PostsPanel.astro';
|
||||
import TagsPanel from '../panels/TagsPanel.astro';
|
||||
---
|
||||
|
||||
<main
|
||||
class="main-section"
|
||||
id="main-section"
|
||||
tabindex="-1"
|
||||
x-on:resize.window="$store.ui.checkMenuState()"
|
||||
>
|
||||
<div
|
||||
class="main-section-inner"
|
||||
id="main-section-inner"
|
||||
:class="$store.files.sources.length > 1 ? 'multiple-actors' : ''"
|
||||
>
|
||||
<MobileMenu />
|
||||
<ActorPanel />
|
||||
<FiltersPanel />
|
||||
<HeaderPanel />
|
||||
<PostsPanel />
|
||||
<TagsPanel />
|
||||
</div>
|
||||
|
||||
<LightBox />
|
||||
</main>
|
35
dev/src/components/screens/WelcomeScreen.astro
Normal file
@ -0,0 +1,35 @@
|
||||
<main class="welcome">
|
||||
<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 > Request your archive".<br />
|
||||
<strong>Please note:</strong> only ZIP files are supported (not GZ).
|
||||
</p>
|
||||
<p>
|
||||
<strong>Start by opening your archive file with MARL.</strong><br />
|
||||
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"
|
||||
accept=".zip"
|
||||
multiple
|
||||
@change="unZip(Object.values($event.target.files))"
|
||||
/>
|
||||
</div>
|
||||
<div class="about">
|
||||
<a href="https://github.com/s427/MARL">Project page (github)</a>
|
||||
</div>
|
||||
</main>
|
172
dev/src/css/_colors-mixins.scss
Normal file
@ -0,0 +1,172 @@
|
||||
@use 'colors';
|
||||
|
||||
@mixin theme-vars($theme: "") {
|
||||
@if $theme == "dark" {
|
||||
--bg0: #{colors.$dark-bg0};
|
||||
--bg1: #{colors.$dark-bg1};
|
||||
--bg2: #{colors.$dark-bg2};
|
||||
--bg3: #{colors.$dark-bg3};
|
||||
--bg4: #{colors.$dark-bg4};
|
||||
|
||||
--fg0: #{colors.$dark-fg0};
|
||||
--fg1: #{colors.$dark-fg1};
|
||||
--fg2: #{colors.$dark-fg2};
|
||||
--fg-inv: #{colors.$dark-fg-inv};
|
||||
|
||||
--menu-bg: #{colors.$dark-menu-bg};
|
||||
--menu-fg: #{colors.$dark-menu-fg};
|
||||
--menu-fg-active: #{colors.$dark-menu-fg-active};
|
||||
--menu-icon: #{colors.$dark-menu-icon};
|
||||
--menu-filter-active: #{colors.$dark-menu-filter-active};
|
||||
--panel-close: #{colors.$dark-panel-close};
|
||||
--panel-close-hover: #{colors.$dark-panel-close-hover};
|
||||
|
||||
--accent: #{colors.$dark-accent};
|
||||
--accent-dark: #{colors.$dark-accent-dark};
|
||||
--accent-light: #{colors.$dark-accent-light};
|
||||
--accent-light2: #{colors.$dark-accent-light2};
|
||||
--accent-light3: #{colors.$dark-accent-light3};
|
||||
|
||||
--overlay-icon: #{colors.$dark-overlay-icon};
|
||||
--overlay-icon-hover: #{colors.$dark-overlay-icon-hover};
|
||||
--overlay-backdrop: #{colors.$dark-overlay-backdrop};
|
||||
--menu-backdrop: #{colors.$dark-menu-backdrop};
|
||||
|
||||
--bg-input: #{colors.$dark-bg-input};
|
||||
--bg-input-hover: #{colors.$dark-bg-input-hover};
|
||||
--bg-input-focus: #{colors.$dark-bg-input-focus};
|
||||
|
||||
--bg-button: #{colors.$dark-bg-button};
|
||||
--bg-button-hover: #{colors.$dark-bg-button-hover};
|
||||
--button-svg: #{colors.$dark-button-svg};
|
||||
--button-svg-hover: #{colors.$dark-button-svg-hover};
|
||||
--button-svg-focus: #{colors.$dark-button-svg-focus};
|
||||
--button-svg-active: #{colors.$dark-button-svg-active};
|
||||
|
||||
--fg-button-focus: #{colors.$dark-fg-button-focus};
|
||||
--fg-button-active: #{colors.$dark-fg-button-active};
|
||||
|
||||
--posts-count: #{colors.$dark-posts-count};
|
||||
--selection-text: #{colors.$dark-selection-text};
|
||||
--selection-bg: #{colors.$dark-selection-bg};
|
||||
--stripe1: #{colors.$dark-stripe1};
|
||||
--stripe2: #{colors.$dark-stripe2};
|
||||
--stripe-fg: #{colors.$dark-stripe-fg};
|
||||
--private-post-bg: #{colors.$dark-private-post-bg};
|
||||
--private-post-border: #{colors.$dark-private-post-border};
|
||||
|
||||
.actors-wrapper {
|
||||
--actor-hue: 0;
|
||||
--actor-bg0: #{colors.$dark-actor-bg0};
|
||||
--actor-bg0-ok: #{colors.$dark-actor-bg0-ok};
|
||||
--actor-bg1: #{colors.$dark-actor-bg1};
|
||||
--actor-bg1-ok: #{colors.$dark-actor-bg1-ok};
|
||||
--actor-bg2: #{colors.$dark-actor-bg2};
|
||||
--actor-bg2-ok: #{colors.$dark-actor-bg2-ok};
|
||||
--actor-bg3: #{colors.$dark-actor-bg3};
|
||||
--actor-bg3-ok: #{colors.$dark-actor-bg3-ok};
|
||||
--actor-bg4: #{colors.$dark-actor-bg4};
|
||||
--actor-bg4-ok: #{colors.$dark-actor-bg4-ok};
|
||||
|
||||
--actor-fg0: #{colors.$dark-actor-fg0};
|
||||
--actor-fg1: #{colors.$dark-actor-fg1};
|
||||
--actor-fg1-ok: #{colors.$dark-actor-fg1-ok};
|
||||
--actor-tabs-bg: #{colors.$dark-actor-tabs-bg};
|
||||
--actor-accent: #{colors.$dark-actor-accent};
|
||||
--actor-accent-ok: #{colors.$dark-actor-accent-ok};
|
||||
--actor-accent2-ok: #{colors.$dark-actor-accent2-ok};
|
||||
}
|
||||
.actors-tabs button {
|
||||
--actor-accent-ok: #{colors.$dark-actor-accent-ok};
|
||||
--actor-accent2-ok: #{colors.$dark-actor-accent2-ok};
|
||||
}
|
||||
.toot-content {
|
||||
--actor-accent-ok: #{colors.$dark-actor-accent-ok};
|
||||
--actor-accent2-ok: #{colors.$dark-actor-accent2-ok};
|
||||
}
|
||||
|
||||
} @else {
|
||||
|
||||
--bg0: #{colors.$bg0};
|
||||
--bg1: #{colors.$bg1};
|
||||
--bg2: #{colors.$bg2};
|
||||
--bg3: #{colors.$bg3};
|
||||
--bg4: #{colors.$bg4};
|
||||
|
||||
--fg0: #{colors.$fg0};
|
||||
--fg1: #{colors.$fg1};
|
||||
--fg2: #{colors.$fg2};
|
||||
--fg-inv: #{colors.$fg-inv};
|
||||
|
||||
--menu-bg: #{colors.$menu-bg};
|
||||
--menu-fg: #{colors.$menu-fg};
|
||||
--menu-fg-active: #{colors.$menu-fg-active};
|
||||
--menu-icon: #{colors.$menu-icon};
|
||||
--menu-filter-active: #{colors.$menu-filter-active};
|
||||
--panel-close: #{colors.$panel-close};
|
||||
--panel-close-hover: #{colors.$panel-close-hover};
|
||||
|
||||
--accent: #{colors.$accent};
|
||||
--accent-dark: #{colors.$accent-dark};
|
||||
--accent-light: #{colors.$accent-light};
|
||||
--accent-light2: #{colors.$accent-light2};
|
||||
--accent-light3: #{colors.$accent-light3};
|
||||
|
||||
--overlay-icon: #{colors.$overlay-icon};
|
||||
--overlay-icon-hover: #{colors.$overlay-icon-hover};
|
||||
--overlay-backdrop: #{colors.$overlay-backdrop};
|
||||
--menu-backdrop: #{colors.$menu-backdrop};
|
||||
|
||||
--bg-input: #{colors.$bg-input};
|
||||
--bg-input-hover: #{colors.$bg-input-hover};
|
||||
--bg-input-focus: #{colors.$bg-input-focus};
|
||||
|
||||
--bg-button: #{colors.$bg-button};
|
||||
--bg-button-hover: #{colors.$bg-button-hover};
|
||||
--button-svg: #{colors.$button-svg};
|
||||
--button-svg-hover: #{colors.$button-svg-hover};
|
||||
--button-svg-focus: #{colors.$button-svg-focus};
|
||||
--button-svg-active: #{colors.$button-svg-active};
|
||||
|
||||
--fg-button-focus: #{colors.$fg-button-focus};
|
||||
--fg-button-active: #{colors.$fg-button-active};
|
||||
|
||||
--posts-count: #{colors.$posts-count};
|
||||
--selection-text: #{colors.$selection-text};
|
||||
--selection-bg: #{colors.$selection-bg};
|
||||
--stripe1: #{colors.$stripe1};
|
||||
--stripe2: #{colors.$stripe2};
|
||||
--stripe-fg: #{colors.$stripe-fg};
|
||||
--private-post-bg: #{colors.$private-post-bg};
|
||||
--private-post-border: #{colors.$private-post-border};
|
||||
|
||||
.actors-wrapper {
|
||||
--actor-hue: 0;
|
||||
--actor-bg0: #{colors.$actor-bg0};
|
||||
--actor-bg0-ok: #{colors.$actor-bg0-ok};
|
||||
--actor-bg1: #{colors.$actor-bg1};
|
||||
--actor-bg1-ok: #{colors.$actor-bg1-ok};
|
||||
--actor-bg2: #{colors.$actor-bg2};
|
||||
--actor-bg2-ok: #{colors.$actor-bg2-ok};
|
||||
--actor-bg3: #{colors.$actor-bg3};
|
||||
--actor-bg3-ok: #{colors.$actor-bg3-ok};
|
||||
--actor-bg4: #{colors.$actor-bg4};
|
||||
--actor-bg4-ok: #{colors.$actor-bg4-ok};
|
||||
--actor-fg0: #{colors.$actor-fg0};
|
||||
--actor-fg1: #{colors.$actor-fg1};
|
||||
--actor-fg1-ok: #{colors.$actor-fg1-ok};
|
||||
--actor-tabs-bg: #{colors.$actor-tabs-bg};
|
||||
--actor-accent: #{colors.$actor-accent};
|
||||
--actor-accent-ok: #{colors.$actor-accent-ok};
|
||||
--actor-accent2-ok: #{colors.$actor-accent2-ok};
|
||||
}
|
||||
.actors-tabs button {
|
||||
--actor-accent-ok: #{colors.$actor-accent-ok};
|
||||
--actor-accent2-ok: #{colors.$actor-accent2-ok};
|
||||
}
|
||||
.toot-content {
|
||||
--actor-accent-ok: #{colors.$actor-accent-ok};
|
||||
--actor-accent2-ok: #{colors.$actor-accent2-ok};
|
||||
}
|
||||
}
|
||||
}
|
@ -181,27 +181,3 @@ $dark-stripe-fg: $dark-fg0;
|
||||
$dark-private-post-bg: $dark-bg2;
|
||||
$dark-private-post-border: $dark-accent-dark;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@import "colors-mixins";
|
||||
|
||||
html {
|
||||
@include theme-vars();
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
color-scheme: dark;
|
||||
@include theme-vars("dark");
|
||||
}
|
||||
}
|
||||
|
||||
html.light {
|
||||
color-scheme: light;
|
||||
@include theme-vars();
|
||||
}
|
||||
|
||||
html.dark {
|
||||
color-scheme: dark;
|
||||
@include theme-vars("dark");
|
||||
}
|
@ -1,3 +1,6 @@
|
||||
@use 'mixins';
|
||||
@use 'colors-mixins';
|
||||
|
||||
/* mini-reset */
|
||||
*,
|
||||
*::before,
|
||||
@ -26,6 +29,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* color schemes */
|
||||
|
||||
html {
|
||||
@include colors-mixins.theme-vars();
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
color-scheme: dark;
|
||||
@include colors-mixins.theme-vars('dark');
|
||||
}
|
||||
}
|
||||
|
||||
html.light {
|
||||
color-scheme: light;
|
||||
@include colors-mixins.theme-vars();
|
||||
}
|
||||
|
||||
html.dark {
|
||||
color-scheme: dark;
|
||||
@include colors-mixins.theme-vars('dark');
|
||||
}
|
||||
|
||||
/* global rules */
|
||||
|
||||
body {
|
||||
@ -158,14 +182,14 @@ input {
|
||||
}
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
input[type='number'] {
|
||||
width: 6ch;
|
||||
text-align: center;
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
}
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
input[type='number']::-webkit-inner-spin-button,
|
||||
input[type='number']::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
@ -219,7 +243,7 @@ details {
|
||||
// focusable elements
|
||||
*:focus-visible,
|
||||
.tags-group button:focus-visible div,
|
||||
.toots-filter:has([type="checkbox"]:focus-visible) {
|
||||
.toots-filter:has([type='checkbox']:focus-visible) {
|
||||
text-decoration: none;
|
||||
outline: 2px solid var(--accent-light);
|
||||
outline-offset: 2px;
|
||||
@ -229,7 +253,7 @@ details {
|
||||
}
|
||||
|
||||
.visually-hidden:not(:focus):not(:active) {
|
||||
@include visually-hidden();
|
||||
@include mixins.visually-hidden();
|
||||
}
|
||||
|
||||
.nojs {
|
@ -1,3 +1,5 @@
|
||||
@use "mixins";
|
||||
|
||||
html {
|
||||
width: 100dvw;
|
||||
height: 100dvh;
|
||||
@ -100,7 +102,7 @@ html {
|
||||
.direction-fwd,
|
||||
.paging-options-toggle {
|
||||
.btn-label {
|
||||
@include visually-hidden();
|
||||
@include mixins.visually-hidden();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -194,7 +196,7 @@ html {
|
||||
.direction-fwd,
|
||||
.paging-options-toggle {
|
||||
.btn-label {
|
||||
@include visually-hidden();
|
||||
@include mixins.visually-hidden();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
@use "mixins";
|
||||
|
||||
$meta-visible: 100ch;
|
||||
|
||||
.toot {
|
||||
@ -45,7 +47,7 @@ $meta-visible: 100ch;
|
||||
var(--stripe2) 10px,
|
||||
var(--stripe2) 20px
|
||||
);
|
||||
@include box-shadow-inner();
|
||||
@include mixins.box-shadow-inner();
|
||||
border-radius: 0.5rem;
|
||||
@media (forced-colors: active) {
|
||||
outline: 1px solid Highlight;
|
||||
@ -226,7 +228,7 @@ $meta-visible: 100ch;
|
||||
padding: 1rem;
|
||||
background-color: var(--bg2);
|
||||
border-radius: 0.5rem;
|
||||
@include box-shadow-inner();
|
||||
@include mixins.box-shadow-inner();
|
||||
|
||||
& + .att-sound {
|
||||
margin-top: 1rem;
|
||||
@ -286,7 +288,7 @@ $meta-visible: 100ch;
|
||||
padding: 1rem;
|
||||
background-color: var(--bg2);
|
||||
border-radius: 0.5rem;
|
||||
@include box-shadow-inner();
|
||||
@include mixins.box-shadow-inner();
|
||||
|
||||
@media (forced-colors: active) {
|
||||
border: 1px solid CanvasText;
|
||||
@ -342,7 +344,7 @@ $meta-visible: 100ch;
|
||||
container-type: inline-size;
|
||||
background: var(--bg2);
|
||||
border-radius: 0.5rem;
|
||||
@include box-shadow-inner();
|
||||
@include mixins.box-shadow-inner();
|
||||
|
||||
& > span {
|
||||
margin-bottom: 0.25rem;
|
||||
@ -474,7 +476,7 @@ $meta-visible: 100ch;
|
||||
|
||||
details[open] {
|
||||
background-color: var(--bg2);
|
||||
@include box-shadow-inner();
|
||||
@include mixins.box-shadow-inner();
|
||||
|
||||
@media (forced-colors: active) {
|
||||
border: 1px solid CanvasText;
|
13
dev/src/css/main.scss
Normal file
@ -0,0 +1,13 @@
|
||||
@use 'colors';
|
||||
@use 'colors-mixins';
|
||||
@use 'mixins';
|
||||
|
||||
@use 'global';
|
||||
@use 'layout';
|
||||
@use 'welcome';
|
||||
@use 'actor';
|
||||
@use 'filters';
|
||||
@use 'toot';
|
||||
@use 'tags';
|
||||
@use 'menu';
|
||||
@use 'overlay';
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
26
dev/src/pages/index.astro
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
import '../css/main.scss';
|
||||
import App from '../components/App.astro';
|
||||
import SvgSprites from '../components/SvgSprites.astro';
|
||||
---
|
||||
|
||||
<!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" />
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<div class="nojs">JavaScript is required for this app to run.</div>
|
||||
</noscript>
|
||||
|
||||
<App />
|
||||
<SvgSprites />
|
||||
|
||||
<script src="/js/main.js" is:inline></script>
|
||||
</body>
|
||||
</html>
|
Before Width: | Height: | Size: 649 B After Width: | Height: | Size: 649 B |
Before Width: | Height: | Size: 1018 B After Width: | Height: | Size: 1018 B |
Before Width: | Height: | Size: 548 B After Width: | Height: | Size: 548 B |
Before Width: | Height: | Size: 400 B After Width: | Height: | Size: 400 B |
Before Width: | Height: | Size: 567 B After Width: | Height: | Size: 567 B |
Before Width: | Height: | Size: 638 B After Width: | Height: | Size: 638 B |
Before Width: | Height: | Size: 529 B After Width: | Height: | Size: 529 B |
Before Width: | Height: | Size: 593 B After Width: | Height: | Size: 593 B |
Before Width: | Height: | Size: 860 B After Width: | Height: | Size: 860 B |
Before Width: | Height: | Size: 660 B After Width: | Height: | Size: 660 B |
Before Width: | Height: | Size: 500 B After Width: | Height: | Size: 500 B |
Before Width: | Height: | Size: 649 B After Width: | Height: | Size: 649 B |
Before Width: | Height: | Size: 603 B After Width: | Height: | Size: 603 B |
Before Width: | Height: | Size: 528 B After Width: | Height: | Size: 528 B |
Before Width: | Height: | Size: 1018 B After Width: | Height: | Size: 1018 B |
Before Width: | Height: | Size: 548 B After Width: | Height: | Size: 548 B |
Before Width: | Height: | Size: 400 B After Width: | Height: | Size: 400 B |
Before Width: | Height: | Size: 567 B After Width: | Height: | Size: 567 B |
Before Width: | Height: | Size: 638 B After Width: | Height: | Size: 638 B |
Before Width: | Height: | Size: 455 B After Width: | Height: | Size: 455 B |
Before Width: | Height: | Size: 448 B After Width: | Height: | Size: 448 B |
Before Width: | Height: | Size: 593 B After Width: | Height: | Size: 593 B |
Before Width: | Height: | Size: 315 B After Width: | Height: | Size: 315 B |
Before Width: | Height: | Size: 337 B After Width: | Height: | Size: 337 B |
Before Width: | Height: | Size: 529 B After Width: | Height: | Size: 529 B |
Before Width: | Height: | Size: 593 B After Width: | Height: | Size: 593 B |
Before Width: | Height: | Size: 860 B After Width: | Height: | Size: 860 B |
Before Width: | Height: | Size: 660 B After Width: | Height: | Size: 660 B |
Before Width: | Height: | Size: 500 B After Width: | Height: | Size: 500 B |
Before Width: | Height: | Size: 649 B After Width: | Height: | Size: 649 B |
Before Width: | Height: | Size: 603 B After Width: | Height: | Size: 603 B |
Before Width: | Height: | Size: 528 B After Width: | Height: | Size: 528 B |
5
dev/tsconfig.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"include": [".astro/types.d.ts", "**/*"],
|
||||
"exclude": ["dist"]
|
||||
}
|
1
dist/_astro/index.uwcihdcd.css
vendored
Normal file
BIN
dist/_astro/no-avatar.DeffBVR2.png
vendored
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
dist/_astro/no-header.DKXzxkmt.png
vendored
Normal file
After Width: | Height: | Size: 5.1 KiB |