mirror of
https://github.com/s427/MARL.git
synced 2025-01-31 03:24:48 +01:00
WIP
This commit is contained in:
parent
83fea0f787
commit
35e1bd9e37
@ -1,4 +1,16 @@
|
||||
.actor {
|
||||
position: relative;
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
width: 2rem;
|
||||
box-shadow: -1.5rem 0 2rem -2rem #000 inset;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--actor-fg0);
|
||||
}
|
||||
@ -30,10 +42,11 @@
|
||||
}
|
||||
|
||||
.actors-wrapper {
|
||||
height: 100dvh;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
background-color: var(--actor-tabs-bg);
|
||||
}
|
||||
|
||||
.actor-panel {
|
||||
@ -281,48 +294,44 @@
|
||||
.multiple-actors {
|
||||
.actors-tabs {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
align-self: flex-start;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
padding: 1rem 1rem 0;
|
||||
// background: var(--actor-bg0);
|
||||
// background: var(--actor-bg0-ok);
|
||||
background-color: hsl(var(--actor-hue), 50%, 80%);
|
||||
background-color: var(--actor-bg3-ok);
|
||||
padding: 0.75rem 1rem;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 -0.75rem 0.75rem -0.75rem rgba(0, 0, 0, 0.75) inset;
|
||||
user-select: none;
|
||||
|
||||
button {
|
||||
all: unset;
|
||||
padding: 0.35rem 0.7rem 0.3rem;
|
||||
padding: 0.35rem 0.7rem 0.4rem;
|
||||
font-size: 0.85em;
|
||||
font-family: inherit;
|
||||
border-radius: 0.25rem 0.25rem 0 0;
|
||||
color: var(--fg1);
|
||||
background: #fff; // ### VAR
|
||||
border-radius: 0.25rem;
|
||||
color: #fff;
|
||||
background: var(--actor-accent2-ok, transparent);
|
||||
cursor: pointer;
|
||||
opacity: 0.65;
|
||||
|
||||
opacity: 0.85;
|
||||
span {
|
||||
display: inline-block;
|
||||
padding-bottom: 0.1rem;
|
||||
border-bottom: 0.2rem solid transparent;
|
||||
border-color: hsl(var(--actor-hue), 50%, 80%);
|
||||
border-color: $actor-accent-light-ok;
|
||||
// border-color: var(--actor-fg1-ok);
|
||||
padding: 0 0 0.3rem;
|
||||
border-bottom: 0.1rem solid transparent;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.85;
|
||||
opacity: 1;
|
||||
}
|
||||
&[aria-selected="true"] {
|
||||
opacity: 1;
|
||||
box-shadow: none;
|
||||
span {
|
||||
border-color: #fff;
|
||||
}
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: 2px solid hsl(var(--actor-hue), 50%, 80%);
|
||||
outline: 2px solid $actor-accent-light-ok;
|
||||
// outline: 2px solid var(--actor-fg1-ok);
|
||||
outline: 2px solid var(--actor-accent-ok, var(--actor-accent));
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
@ -330,38 +339,44 @@
|
||||
|
||||
// color variations
|
||||
|
||||
.actor {
|
||||
*:focus-visible {
|
||||
outline-color: var(--actor-accent-ok, var(--accent-light));
|
||||
}
|
||||
}
|
||||
|
||||
.actor summary svg {
|
||||
fill: var(--actor-fg1-ok);
|
||||
fill: var(--actor-fg1-ok, var(--actor-fg1));
|
||||
}
|
||||
.actor-panel {
|
||||
color: var(--actor-fg1-ok);
|
||||
background: var(--actor-bg3-ok);
|
||||
color: var(--actor-fg1-ok, var(--actor-fg1));
|
||||
background: var(--actor-bg3-ok, var(--actor-bg3));
|
||||
}
|
||||
.actor-summary {
|
||||
background: var(--actor-bg4-ok);
|
||||
background: var(--actor-bg4-ok, var(--actor-bg4));
|
||||
background: radial-gradient(
|
||||
circle at 200% 150%,
|
||||
var(--actor-bg3-ok),
|
||||
var(--actor-bg4-ok)
|
||||
var(--actor-bg3-ok, var(--actor-bg3)),
|
||||
var(--actor-bg4-ok, var(--actor-bg4))
|
||||
);
|
||||
}
|
||||
.actor-infos {
|
||||
dd {
|
||||
border-left-color: var(--actor-bg4-ok);
|
||||
border-left-color: var(--actor-bg4-ok, var(--actor-bg4));
|
||||
}
|
||||
@container (width < 340px) {
|
||||
dl {
|
||||
border-bottom-color: var(--actor-bg4-ok);
|
||||
border-bottom-color: var(--actor-bg4-ok, var(--actor-bg4));
|
||||
}
|
||||
}
|
||||
}
|
||||
.actor-raw {
|
||||
background: var(--actor-bg2-ok);
|
||||
background: var(--actor-bg2-ok, var(--actor-bg2));
|
||||
}
|
||||
.actor-likes-bookmarks {
|
||||
background: var(--actor-bg0-ok);
|
||||
background: var(--actor-bg0-ok, var(--actor-bg0));
|
||||
}
|
||||
.actor-likes {
|
||||
background: var(--actor-bg1-ok);
|
||||
background: var(--actor-bg1-ok, var(--actor-bg1));
|
||||
}
|
||||
}
|
||||
|
@ -5,38 +5,44 @@ $hue: 30; // === 59.17 in oklch
|
||||
/* global background/text colors */
|
||||
/*********************************/
|
||||
|
||||
$bg0: #fff; // -> post content
|
||||
$bg1: hsl($hue, 10%, 95%); // -> post
|
||||
$bg2: hsl($hue, 15%, 90%); // -> post attachment/meta/raw
|
||||
$bg3: hsl($hue, 17.5%, 87%); // -> posts header
|
||||
$bg4: hsl($hue, 20%, 84%); // -> filters/tags panels
|
||||
$bg0: #fff; // post content
|
||||
$bg1: hsl($hue, 10%, 95%); // post
|
||||
$bg2: hsl($hue, 12%, 90%); // post attachment/meta/raw
|
||||
$bg3: hsl($hue, 15%, 87%); // posts header
|
||||
$bg4: hsl($hue, 17.5%, 84%); // filters/tags panels
|
||||
|
||||
$fg0: hsl($hue, 0%, 0%); // -> post content
|
||||
$fg1: hsl($hue, 10%, 20%); // -> body text; some SVG icons (panel close)
|
||||
$fg2: hsl($hue, 10%, 30%); // -> headings (h1, h2...)
|
||||
$fg0: #000; // post content
|
||||
$fg1: hsl($hue, 10%, 20%); // body text; some SVG icons (panel close)
|
||||
$fg2: hsl($hue, 10%, 30%); // headings (h1, h2...)
|
||||
|
||||
$fg-inv: #fff; // -> misc highlighted text
|
||||
$fg-inv: #fff; // misc highlighted text
|
||||
|
||||
/***************/
|
||||
/* actor panel */
|
||||
/***************/
|
||||
|
||||
$actor-bg0: hsl($hue, 37.5%, 11%); // -> bookmarks
|
||||
$actor-bg1: hsl($hue, 32.5%, 15%); // -> favorites
|
||||
$actor-bg2: hsl($hue, 25%, 20%); // -> actor raw data
|
||||
$actor-bg3: hsl($hue, 25%, 25%); // -> actor panel
|
||||
$actor-bg4: hsl($hue, 22.5%, 40%); // -> actor summary
|
||||
$actor-bg0: hsl($hue, 37.5%, 11%); // bookmarks
|
||||
$actor-bg1: hsl($hue, 32.5%, 15%); // favorites
|
||||
$actor-bg2: hsl($hue, 25%, 20%); // actor raw data; tabs bg
|
||||
$actor-bg3: hsl($hue, 25%, 25%); // actor panel
|
||||
$actor-bg4: hsl($hue, 22.5%, 40%); // actor summary
|
||||
|
||||
$actor-fg0: #fff;
|
||||
$actor-fg1: hsl($hue, 40%, 80%); // -> actor texts
|
||||
$actor-fg1: hsl($hue, 40%, 80%); // actor texts
|
||||
$actor-tabs-bg: hsl($hue, 12%, 17%);
|
||||
|
||||
// OKLCH variants
|
||||
$actor-bg0-ok: oklch(11% 37.5% var(--actor-hue));
|
||||
$actor-bg1-ok: oklch(15% 32.5% var(--actor-hue));
|
||||
$actor-bg2-ok: oklch(20% 25% var(--actor-hue));
|
||||
$actor-bg3-ok: oklch(25% 25% var(--actor-hue));
|
||||
$actor-bg4-ok: oklch(40% 22.5% var(--actor-hue));
|
||||
$actor-fg1-ok: oklch(92% 10% var(--actor-hue));
|
||||
// OKLCH variants (multiple archives opened)
|
||||
$actor-bg0-ok: oklch(21% 18% var(--actor-hue)); // bookmarks; tabs bg
|
||||
$actor-bg1-ok: oklch(24% 16% var(--actor-hue)); // favorites
|
||||
$actor-bg2-ok: oklch(27% 14% var(--actor-hue)); // actor raw data
|
||||
$actor-bg3-ok: oklch(30% 12% var(--actor-hue)); // actor panel
|
||||
$actor-bg4-ok: oklch(40% 10% var(--actor-hue)); // actor summary
|
||||
$actor-fg1-ok: oklch(92% 8% var(--actor-hue)); // actor texts
|
||||
|
||||
$actor-accent: hsl($hue, 75%, 50%);
|
||||
$actor-accent-ok: oklch(80% 25% var(--actor-hue));
|
||||
$actor-accent2-ok: oklch(60% 20% var(--actor-hue));
|
||||
// -> outline; color code on post
|
||||
|
||||
/***************/
|
||||
/* mobile menu */
|
||||
@ -50,7 +56,7 @@ $menu-filter-active: hsl($hue, 75%, 50%); // accent-light2
|
||||
|
||||
$menu-backdrop: rgba(0, 0, 0, 0.5);
|
||||
|
||||
$panel-close: hsl($hue, 40%, 80%); // fg1
|
||||
$panel-close: hsl($hue, 10%, 20%); // fg1
|
||||
$panel-close-hover: hsl($hue, 75%, 38%); // accent-light
|
||||
|
||||
/**********/
|
||||
@ -162,6 +168,16 @@ html {
|
||||
--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};
|
||||
}
|
||||
|
||||
$actor-accent-light-ok: oklch(80% 15% var(--actor-hue));
|
||||
|
@ -211,7 +211,6 @@ details {
|
||||
|
||||
// focusable elements
|
||||
*:focus-visible,
|
||||
// .att-img-wrapper:hover,
|
||||
.tags-group button:focus-visible div,
|
||||
.toots-filter:has([type="checkbox"]:focus-visible) {
|
||||
text-decoration: none;
|
||||
|
@ -153,6 +153,10 @@ $panel-width: 400px;
|
||||
width: min(#{$panel-width}, calc(100dvw - #{$menu-width}));
|
||||
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
|
||||
|
||||
&.actor::before {
|
||||
content: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
transition: left 0.2s ease-out;
|
||||
transition-property: left, box-shadow;
|
||||
@ -179,6 +183,10 @@ $panel-width: 400px;
|
||||
@media (forced-colors: active) {
|
||||
color: buttonText;
|
||||
}
|
||||
|
||||
.actor & {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-menu {
|
||||
|
@ -100,7 +100,6 @@ $meta-visible: 100ch;
|
||||
|
||||
.multiple-actors .toot-content {
|
||||
position: relative;
|
||||
// overflow: hidden;
|
||||
padding-left: 1.6rem;
|
||||
&::before {
|
||||
content: "";
|
||||
@ -111,8 +110,8 @@ $meta-visible: 100ch;
|
||||
z-index: 1;
|
||||
width: 0.25rem;
|
||||
border-radius: 0.25rem;
|
||||
background-color: hsl(var(--actor-hue), 50%, 80%);
|
||||
background-color: $actor-accent-light-ok;
|
||||
background-color: red;
|
||||
background-color: var(--actor-accent-ok, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
108
index.html
108
index.html
@ -7,7 +7,7 @@
|
||||
<link rel="icon" href="data:," />
|
||||
<base target="_blank" />
|
||||
|
||||
<link rel="stylesheet" href="css/main.css?20241204" />
|
||||
<link rel="stylesheet" href="css/main.css?20241209" />
|
||||
<script>
|
||||
const isFileProtocol = window.location.protocol === "file:";
|
||||
const scripts = [
|
||||
@ -228,63 +228,61 @@
|
||||
x-text="$store.files.sources[source].actor.name"
|
||||
></span>
|
||||
</button>
|
||||
<!-- :tabindex="source === $store.ui.actorPanel ? '0': '-1'" -->
|
||||
</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="actor in $store.files.actors">
|
||||
<template x-for="source in $store.files.sources">
|
||||
<div
|
||||
class="actor-panel"
|
||||
:id="'actorpanel-' + actor.source"
|
||||
:id="'actorpanel-' + source.id"
|
||||
role="tabpanel"
|
||||
tabindex="0"
|
||||
:aria-labelledby="'actortab-' + actor.source"
|
||||
x-data="{a: $store.files.sources[actor.source].actor}"
|
||||
x-show="$store.ui.actorPanel === actor.source"
|
||||
: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">
|
||||
<template
|
||||
x-if="$store.files.sources[actor.source].header.noImg"
|
||||
>
|
||||
<template x-if="source.header.noImg">
|
||||
<img alt="header" src="img/no-header.png" />
|
||||
</template>
|
||||
<template
|
||||
x-if="! $store.files.sources[actor.source].header.noImg"
|
||||
>
|
||||
<template x-if="! source.header.noImg">
|
||||
<button
|
||||
id="actor-header"
|
||||
@click="$store.lightbox.openProfileImg('header', 'actor-header', actor.source)"
|
||||
@click="$store.lightbox.openProfileImg('header', 'actor-header', source.id)"
|
||||
>
|
||||
<img
|
||||
alt="header"
|
||||
:src="`data:${$store.files.sources[actor.source].header.type}; base64,${$store.files.sources[actor.source].header.content}`"
|
||||
:src="`data:${source.header.type}; base64,${source.header.content}`"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="actor-id">
|
||||
<template
|
||||
x-if="$store.files.sources[actor.source].avatar.noImg"
|
||||
>
|
||||
<template x-if="source.avatar.noImg">
|
||||
<div class="actor-avatar no-avatar">
|
||||
<img :alt="a.name" src="img/no-avatar.png" />
|
||||
</div>
|
||||
</template>
|
||||
<template
|
||||
x-if="! $store.files.sources[actor.source].avatar.noImg"
|
||||
>
|
||||
<template x-if="! source.avatar.noImg">
|
||||
<button
|
||||
id="actor-avatar"
|
||||
class="actor-avatar"
|
||||
@click="$store.lightbox.openProfileImg('avatar', 'actor-avatar', actor.source)"
|
||||
@click="$store.lightbox.openProfileImg('avatar', 'actor-avatar', source.id)"
|
||||
>
|
||||
<img
|
||||
:alt="a.name"
|
||||
class="actor-avatar"
|
||||
:src="`data:${$store.files.sources[actor.source].avatar.type}; base64,${$store.files.sources[actor.source].avatar.content}`"
|
||||
:src="`data:${source.avatar.type}; base64,${source.avatar.content}`"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
@ -317,7 +315,7 @@
|
||||
<div class="total">
|
||||
<span
|
||||
class="count"
|
||||
x-text="formatNumber($store.files.sources[actor.source].outbox.totalItems)"
|
||||
x-text="formatNumber(source.outbox.totalItems)"
|
||||
></span>
|
||||
<span class="label">posts</span>
|
||||
</div>
|
||||
@ -325,14 +323,14 @@
|
||||
<div class="archive">
|
||||
<span
|
||||
class="count"
|
||||
x-text="formatNumber($store.files.sources[actor.source].nbToots)"
|
||||
x-text="formatNumber(source.nbToots)"
|
||||
></span>
|
||||
<span class="label">in archive</span>
|
||||
</div>
|
||||
|
||||
<details
|
||||
class="comment"
|
||||
x-show="$store.files.sources[actor.source].nbToots != $store.files.sources[actor.source].outbox.totalItems"
|
||||
x-show="source.nbToots != source.outbox.totalItems"
|
||||
>
|
||||
<summary>
|
||||
<span class="summary-icon">
|
||||
@ -390,28 +388,22 @@
|
||||
<h2 class="summary-label">
|
||||
Favorites (<span
|
||||
class="count"
|
||||
x-text="$store.files.sources[actor.source].likes.length"
|
||||
x-text="source.likes.length"
|
||||
></span
|
||||
>)
|
||||
</h2>
|
||||
</summary>
|
||||
<div class="details-content">
|
||||
<template
|
||||
x-if="$store.files.sources[actor.source].likes.length"
|
||||
>
|
||||
<template x-if="source.likes.length">
|
||||
<ul>
|
||||
<template
|
||||
x-for="url in $store.files.sources[actor.source].likes"
|
||||
>
|
||||
<template x-for="url in source.likes">
|
||||
<li>
|
||||
<a :href="url" x-text="url"></a>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</template>
|
||||
<template
|
||||
x-if="! $store.files.sources[actor.source].likes.length"
|
||||
>
|
||||
<template x-if="! source.likes.length">
|
||||
<p class="no-content">... no favorites ...</p>
|
||||
</template>
|
||||
</div>
|
||||
@ -428,28 +420,22 @@
|
||||
<h2 class="summary-label">
|
||||
Bookmarks (<span
|
||||
class="count"
|
||||
x-text="$store.files.sources[actor.source].bookmarks.length"
|
||||
x-text="source.bookmarks.length"
|
||||
></span
|
||||
>)
|
||||
</h2>
|
||||
</summary>
|
||||
<div class="details-content">
|
||||
<template
|
||||
x-if="$store.files.sources[actor.source].bookmarks.length"
|
||||
>
|
||||
<template x-if="source.bookmarks.length">
|
||||
<ul>
|
||||
<template
|
||||
x-for="url in $store.files.sources[actor.source].bookmarks"
|
||||
>
|
||||
<template x-for="url in source.bookmarks">
|
||||
<li>
|
||||
<a :href="url" x-text="url"></a>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</template>
|
||||
<template
|
||||
x-if="! $store.files.sources[actor.source].bookmarks.length"
|
||||
>
|
||||
<template x-if="! source.bookmarks.length">
|
||||
<p class="no-content">... no bookmarks ...</p>
|
||||
</template>
|
||||
</div>
|
||||
@ -845,7 +831,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toots-filters-group">
|
||||
<div
|
||||
class="toots-filters-group"
|
||||
x-show="$store.files.toots.length"
|
||||
>
|
||||
<h3 class="toots-filters-group-title">Language</h3>
|
||||
<template x-for="(nb, lang) in $store.files.languages">
|
||||
<div
|
||||
@ -871,21 +860,20 @@
|
||||
<template x-if="$store.files.sources.length > 1">
|
||||
<div class="toots-filters-group">
|
||||
<h3 class="toots-filters-group-title">Author</h3>
|
||||
<template x-for="(actor, name) in $store.files.actors">
|
||||
<template x-for="source in $store.files.sources">
|
||||
<div
|
||||
class="toots-filter checkbox"
|
||||
:class="isFilterActive('actor_' + actor.source) ? 'active' : ''"
|
||||
:class="isFilterActive('actor_' + source.id) ? 'active' : ''"
|
||||
>
|
||||
<label :for="'filter-actor-' + actor.source">
|
||||
<label :for="'filter-actor-' + source.id">
|
||||
<input
|
||||
:id="'filter-actor-' + actor.source"
|
||||
:id="'filter-actor-' + source.id"
|
||||
type="checkbox"
|
||||
x-model="$store.files.filters['actor_' + actor.source]"
|
||||
x-model="$store.files.filters['actor_' + source.id]"
|
||||
@change="$store.files.setFilter()"
|
||||
/>
|
||||
<span>
|
||||
<span x-text="name"></span>
|
||||
(<span x-text="actor.nb"></span>)
|
||||
<span x-text="source.actor.name"></span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
@ -1291,10 +1279,16 @@
|
||||
<use href="#json" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class="summary-label">Raw data</span>
|
||||
<span class="summary-label" :id="'raw-' + toot._marl.id"
|
||||
>Raw data</span
|
||||
>
|
||||
</summary>
|
||||
<div class="details-content">
|
||||
<textarea x-text="formatJson(toot)" readonly></textarea>
|
||||
<textarea
|
||||
x-text="formatJson(toot)"
|
||||
:aria-labelledby="'raw-' + toot._marl.id"
|
||||
readonly
|
||||
></textarea>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
@ -1692,6 +1686,6 @@
|
||||
</symbol>
|
||||
</svg>
|
||||
|
||||
<script src="js/main.js?20241204"></script>
|
||||
<script src="js/main.js?20241209"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
204
js/main.js
204
js/main.js
@ -47,7 +47,6 @@ const filesStore = {
|
||||
this.loading = false;
|
||||
this.someFilesLoaded = false;
|
||||
|
||||
this.actors = {};
|
||||
this.languages = {};
|
||||
this.boostsAuthors = [];
|
||||
|
||||
@ -416,13 +415,10 @@ const filesStore = {
|
||||
}
|
||||
}
|
||||
|
||||
for (const actor in this.actors) {
|
||||
const source = this.actors[actor].source;
|
||||
if (
|
||||
f.hasOwnProperty("actor_" + source) &&
|
||||
f["actor_" + source] === false
|
||||
) {
|
||||
if (t._marl.source === source) {
|
||||
for (const source of this.sources) {
|
||||
const id = source.id;
|
||||
if (f.hasOwnProperty("actor_" + id) && f["actor_" + id] === false) {
|
||||
if (t._marl.source === id) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -532,9 +528,6 @@ const filesStore = {
|
||||
if (this.sources.length === 0) {
|
||||
return false;
|
||||
}
|
||||
// if (this.someFilesLoaded) {
|
||||
// return true;
|
||||
// }
|
||||
|
||||
let r = true;
|
||||
for (let i = 0; i < this.sources.length; i++) {
|
||||
@ -823,6 +816,15 @@ const uiStore = {
|
||||
|
||||
// utils
|
||||
|
||||
function resetStores() {
|
||||
Alpine.store("files").resetState();
|
||||
Alpine.store("lightbox").resetState();
|
||||
Alpine.store("ui").resetState();
|
||||
|
||||
Alpine.store("userPrefs").load("sortAsc");
|
||||
Alpine.store("userPrefs").load("pageSize");
|
||||
}
|
||||
|
||||
function unZip(files) {
|
||||
const firstLoad = Alpine.store("files").sources.length === 0;
|
||||
if (firstLoad) {
|
||||
@ -849,6 +851,7 @@ function unZip(files) {
|
||||
let index = Alpine.store("files").sources.length;
|
||||
|
||||
Alpine.store("files").sources[index] = {
|
||||
id: index,
|
||||
fileInfos: {
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
@ -886,15 +889,6 @@ function unZip(files) {
|
||||
setHueForSources();
|
||||
}
|
||||
|
||||
function resetStores() {
|
||||
Alpine.store("files").resetState();
|
||||
Alpine.store("lightbox").resetState();
|
||||
Alpine.store("ui").resetState();
|
||||
|
||||
Alpine.store("userPrefs").load("sortAsc");
|
||||
Alpine.store("userPrefs").load("pageSize");
|
||||
}
|
||||
|
||||
function loadJsonFile(name, index) {
|
||||
const content = Alpine.store("files").sources[index]._raw;
|
||||
|
||||
@ -936,92 +930,88 @@ function loadJsonFile(name, index) {
|
||||
}
|
||||
|
||||
function buildTootsInfos() {
|
||||
let infos = Alpine.store("files").toots.reduce(
|
||||
(accu, toot) => {
|
||||
const name = formatAuthor(toot.actor, true).toLowerCase();
|
||||
if (!accu.actors[name]) {
|
||||
accu.actors[name] = {
|
||||
nb: 1,
|
||||
source: toot._marl.source,
|
||||
};
|
||||
} else {
|
||||
accu.actors[name].nb++;
|
||||
}
|
||||
|
||||
if (toot.type === "Create") {
|
||||
const map = toot.object.contentMap;
|
||||
for (let lang in map) {
|
||||
if (!accu.langs[lang]) {
|
||||
accu.langs[lang] = 1;
|
||||
} else {
|
||||
accu.langs[lang]++;
|
||||
}
|
||||
}
|
||||
} else if (toot.type === "Announce") {
|
||||
// since Mastodon doesn't allow (yet?) cross-origin requests to
|
||||
// retrieve post data (for boosts), we try to at least extract the
|
||||
// user names for all the boosts contained in the archive
|
||||
|
||||
// [ISSUE] "object" value is a string most of the times, but
|
||||
// sometimes it's a complex object similar to type "Create"
|
||||
if (typeof toot.object === "object" && toot.object !== null) {
|
||||
// let's ignore this case for now...
|
||||
// [TODO], but not clear how it should be handled
|
||||
} else if (toot.object) {
|
||||
// if it's not an object and it has a value, then it's simply a
|
||||
// url (string) pointing to the original (boosted) post.
|
||||
// [ISSUE] URL format not always consistent... (esp. in the case
|
||||
// of non-Mastodon instances) - e.g:
|
||||
// https://craftopi.art/objects/[...]
|
||||
// https://firefish.city/notes/[...]
|
||||
// https://bsky.brid.gy/convert/ap/at://did:plc:[...]/app.bsky.feed.post/[...]
|
||||
// -> the user name is not always present in URL
|
||||
const url = toot.object.split("/");
|
||||
let name;
|
||||
let user;
|
||||
let domain;
|
||||
if (url.length > 2) {
|
||||
domain = url[2];
|
||||
|
||||
if (
|
||||
url[0] === "https:" &&
|
||||
url[3] === "users" &&
|
||||
url[5] === "statuses"
|
||||
) {
|
||||
// Mastodon URL format -> user name
|
||||
name = url[4];
|
||||
user = `https://${url[2]}/users/${url[4]}/`;
|
||||
} else {
|
||||
// other URL format -> domain name
|
||||
name = `? ${url[2]}`;
|
||||
user = `https://${url[2]}/`;
|
||||
}
|
||||
|
||||
if (!accu.boosts[name]) {
|
||||
accu.boosts[name] = {
|
||||
nb: 1,
|
||||
name: name,
|
||||
url: user,
|
||||
domain: domain,
|
||||
};
|
||||
} else {
|
||||
accu.boosts[name].nb++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return accu;
|
||||
},
|
||||
{ langs: {}, actors: {}, boosts: {} }
|
||||
);
|
||||
|
||||
let langs = {};
|
||||
let boosts = [];
|
||||
for (var key in infos.boosts) {
|
||||
boosts.push(infos.boosts[key]);
|
||||
|
||||
if (Alpine.store("files").toots.length > 0) {
|
||||
let infos = Alpine.store("files").toots.reduce(
|
||||
(accu, toot) => {
|
||||
if (toot.type === "Create") {
|
||||
const map = toot.object.contentMap;
|
||||
for (let lang in map) {
|
||||
if (!accu.langs[lang]) {
|
||||
accu.langs[lang] = 1;
|
||||
} else {
|
||||
accu.langs[lang]++;
|
||||
}
|
||||
}
|
||||
} else if (toot.type === "Announce") {
|
||||
// since Mastodon doesn't allow (yet?) cross-origin requests to
|
||||
// retrieve post data (for boosts), we try to at least extract the
|
||||
// user names for all the boosts contained in the archive
|
||||
|
||||
// [ISSUE] "object" value is a string most of the times, but
|
||||
// sometimes it's a complex object similar to type "Create"
|
||||
if (typeof toot.object === "object" && toot.object !== null) {
|
||||
// let's ignore this case for now...
|
||||
// [TODO], but not clear how it should be handled
|
||||
} else if (toot.object) {
|
||||
// if it's not an object and it has a value, then it's simply a
|
||||
// url (string) pointing to the original (boosted) post.
|
||||
// [ISSUE] URL format not always consistent... (esp. in the case
|
||||
// of non-Mastodon instances) - e.g:
|
||||
// https://craftopi.art/objects/[...]
|
||||
// https://firefish.city/notes/[...]
|
||||
// https://bsky.brid.gy/convert/ap/at://did:plc:[...]/app.bsky.feed.post/[...]
|
||||
// -> the user name is not always present in URL
|
||||
const url = toot.object.split("/");
|
||||
let name;
|
||||
let user;
|
||||
let domain;
|
||||
if (url.length > 2) {
|
||||
domain = url[2];
|
||||
|
||||
if (
|
||||
url[0] === "https:" &&
|
||||
url[3] === "users" &&
|
||||
url[5] === "statuses"
|
||||
) {
|
||||
// Mastodon URL format -> user name
|
||||
name = url[4];
|
||||
user = `https://${url[2]}/users/${url[4]}/`;
|
||||
} else {
|
||||
// other URL format -> domain name
|
||||
name = `? ${url[2]}`;
|
||||
user = `https://${url[2]}/`;
|
||||
}
|
||||
|
||||
if (!accu.boosts[name]) {
|
||||
accu.boosts[name] = {
|
||||
nb: 1,
|
||||
name: name,
|
||||
url: user,
|
||||
domain: domain,
|
||||
};
|
||||
} else {
|
||||
accu.boosts[name].nb++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return accu;
|
||||
},
|
||||
{ langs: {}, boosts: {} }
|
||||
);
|
||||
|
||||
langs = infos.langs;
|
||||
|
||||
boosts = [];
|
||||
for (var key in infos.boosts) {
|
||||
boosts.push(infos.boosts[key]);
|
||||
}
|
||||
}
|
||||
|
||||
Alpine.store("files").actors = infos.actors;
|
||||
Alpine.store("files").languages = infos.langs;
|
||||
Alpine.store("files").languages = langs;
|
||||
Alpine.store("files").boostsAuthors = boosts;
|
||||
}
|
||||
|
||||
@ -1030,10 +1020,8 @@ function buildDynamicFilters() {
|
||||
Alpine.store("files").filtersDefault["lang_" + lang] = true;
|
||||
}
|
||||
|
||||
for (const actor in Alpine.store("files").actors) {
|
||||
Alpine.store("files").filtersDefault[
|
||||
"actor_" + Alpine.store("files").actors[actor].source
|
||||
] = true;
|
||||
for (const source of Alpine.store("files").sources) {
|
||||
Alpine.store("files").filtersDefault["actor_" + source.id] = true;
|
||||
}
|
||||
|
||||
Alpine.store("files").resetFilters(false);
|
||||
@ -1115,7 +1103,7 @@ function loadActorImages(index) {
|
||||
|
||||
function setHueForSources() {
|
||||
const nbSources = Alpine.store("files").sources.length;
|
||||
const hueStart = Math.round(Math.random() * 360);
|
||||
const hueStart = Math.round(Math.random() * 360); // MARL accent: 59.17
|
||||
const hueSpacing = Math.round(360 / nbSources);
|
||||
|
||||
for (let i = 0; i < nbSources; i++) {
|
||||
|
16
readme.md
16
readme.md
@ -45,6 +45,10 @@ No need to unpack your archive. Just drag'n'drop your file in the MARL window, a
|
||||
|
||||
Everything takes place in the browser, with no communication with any server (once the page has been loaded). Your archive data is _not_ sent to any server. No analytics are included either.
|
||||
|
||||
### Support for multiple archives
|
||||
|
||||
You can open multiple archive files at once (or add some more after via drag'n'drop) and MARL will show you all their posts in a single chronological list, as well as all the profiles in a tabbed interface. Each profile is automatically color-coded. A new group of filters allow you to filter posts by author.
|
||||
|
||||
### Filters
|
||||
|
||||
Many filters allow you to quickly find a subset of posts in your archive:
|
||||
@ -112,3 +116,15 @@ This is a personal, non-official project. I am not associated with the Mastodon
|
||||
You can reach me via github or on Mastodon:
|
||||
Github: https://github.com/s427
|
||||
Mastodon: https://lou.lt/@s427
|
||||
|
||||
## Version history
|
||||
|
||||
- v. 1.2
|
||||
- [NEW] support for multiple archive files. Notes:
|
||||
- you can select multiple files from the open dialog;
|
||||
- if one or several files are already loaded, you can drag and drop more files anywhere on the app window in order for MARL to load those new files and add them to the ones already loaded;
|
||||
- for now the "Load new file" button still assumes you want to start over (blank slate); this will probably be changed in a future version.
|
||||
- v. 1.1
|
||||
- [NEW] paging preferences (page size and posts order) are automatically saved to the browser and restored on app load if present
|
||||
- various bug fixes and improvements
|
||||
- v. 1.0 - Initial release
|
||||
|
Loading…
x
Reference in New Issue
Block a user