├── .gitignore ├── .nojekyll ├── .prettierignore ├── Dockerfile ├── LICENSE ├── dev ├── README.md ├── astro.config.mjs ├── package-lock.json ├── package.json ├── public │ └── js │ │ ├── MARL │ │ ├── init.js │ │ ├── libs.js │ │ ├── loadData.js │ │ ├── stores.js │ │ └── utils.js │ │ ├── config.default.js │ │ ├── i18n │ │ ├── _langs.js │ │ ├── en.js │ │ ├── es.js │ │ └── fr.js │ │ └── libs │ │ ├── alpinejs-i18n.min.js │ │ ├── alpinejs.min.js │ │ └── jszip.min.js ├── src │ ├── appVersion.js │ ├── components │ │ ├── App.astro │ │ ├── LightBox.astro │ │ ├── MobileMenu.astro │ │ ├── PagingBottom.astro │ │ ├── PagingOptions.astro │ │ ├── PagingTop.astro │ │ ├── SvgSprites.astro │ │ ├── panels │ │ │ ├── ActorPanel.astro │ │ │ ├── CloseBtn.astro │ │ │ ├── ConversationPanel.astro │ │ │ ├── FiltersPanel.astro │ │ │ ├── HeaderPanel.astro │ │ │ ├── PostsPanel.astro │ │ │ ├── TagsPanel.astro │ │ │ ├── ToolsPanel.astro │ │ │ ├── actor │ │ │ │ ├── ImgAvatar.astro │ │ │ │ ├── ImgHeader.astro │ │ │ │ └── LikesBookmarks.astro │ │ │ ├── filters │ │ │ │ ├── FilterActivity.astro │ │ │ │ ├── FilterCheckbox.astro │ │ │ │ ├── FilterDate.astro │ │ │ │ ├── FilterText.astro │ │ │ │ └── FilterTime.astro │ │ │ ├── posts │ │ │ │ ├── AttachmentDescription.astro │ │ │ │ ├── AttachmentImage.astro │ │ │ │ ├── AttachmentSound.astro │ │ │ │ ├── AttachmentVideo.astro │ │ │ │ ├── HasReply.astro │ │ │ │ ├── Poll.astro │ │ │ │ ├── Post.astro │ │ │ │ ├── PostAnnounce.astro │ │ │ │ ├── PostCreate.astro │ │ │ │ ├── PostInfos.astro │ │ │ │ ├── PostRaw.astro │ │ │ │ └── PostTags.astro │ │ │ ├── tags │ │ │ │ ├── TagsGroup.astro │ │ │ │ ├── TagsGroupHeader.astro │ │ │ │ └── TagsGroupMain.astro │ │ │ └── tools │ │ │ │ ├── About.astro │ │ │ │ ├── AppLog.astro │ │ │ │ ├── AppSettings.astro │ │ │ │ ├── ManageFiles.astro │ │ │ │ └── RemoteInfos.astro │ │ └── screens │ │ │ ├── LoadingScreen.astro │ │ │ ├── MainScreen.astro │ │ │ └── WelcomeScreen.astro │ ├── css │ │ ├── _actor.scss │ │ ├── _colors-mixins.scss │ │ ├── _colors.scss │ │ ├── _conversation.scss │ │ ├── _filters.scss │ │ ├── _forms.scss │ │ ├── _global.scss │ │ ├── _layout.scss │ │ ├── _menu.scss │ │ ├── _mixins.scss │ │ ├── _overlay.scss │ │ ├── _post.scss │ │ ├── _tags.scss │ │ ├── _tools.scss │ │ ├── _welcome.scss │ │ └── main.scss │ ├── img │ │ ├── no-avatar-dark.png │ │ ├── no-avatar.png │ │ ├── no-header-dark.png │ │ └── no-header.png │ ├── pages │ │ └── index.astro │ └── svg │ │ ├── dark │ │ ├── activity-like.svg │ │ ├── activity-share.svg │ │ ├── at.svg │ │ ├── calendar.svg │ │ ├── checkbox-checked.svg │ │ ├── checkbox-unchecked.svg │ │ ├── hashtag.svg │ │ ├── link.svg │ │ ├── open-in-new.svg │ │ ├── person.svg │ │ ├── type-boost.svg │ │ ├── type-post.svg │ │ ├── visibility-followers.svg │ │ ├── visibility-mentioned.svg │ │ ├── visibility-public.svg │ │ └── visibility-unlisted.svg │ │ └── light │ │ ├── activity-like.svg │ │ ├── activity-share.svg │ │ ├── calendar.svg │ │ ├── checkbox-checked.svg │ │ ├── checkbox-unchecked.svg │ │ ├── hashtag.svg │ │ ├── link.svg │ │ ├── menu-filters.svg │ │ ├── menu-new.svg │ │ ├── menu-profile.svg │ │ ├── menu-tags.svg │ │ ├── no-alt-text.svg │ │ ├── open-in-new.svg │ │ ├── person.svg │ │ ├── type-boost.svg │ │ ├── type-post.svg │ │ ├── visibility-followers.svg │ │ ├── visibility-mentioned.svg │ │ ├── visibility-public.svg │ │ └── visibility-unlisted.svg └── tsconfig.json ├── dist ├── _astro │ ├── index.Bc9gEahN.css │ ├── no-avatar-dark.DVWNt87i.png │ ├── no-avatar.DeffBVR2.png │ ├── no-header-dark.BP11rozj.png │ └── no-header.DKXzxkmt.png ├── index.html └── js │ ├── MARL │ ├── init.js │ ├── libs.js │ ├── loadData.js │ ├── stores.js │ └── utils.js │ ├── config.default.js │ ├── i18n │ ├── _langs.js │ ├── en.js │ ├── es.js │ └── fr.js │ └── libs │ ├── alpinejs-i18n.min.js │ ├── alpinejs.min.js │ └── jszip.min.js ├── index.html ├── readme.md ├── screenshots ├── screenshot-dark-theme.png ├── screenshot-multiple-archives.png └── screenshot-simpler-layout.png ├── server-mode.md ├── tools └── outbox-cleanup │ ├── commands │ └── cleanup.js │ ├── index.js │ ├── package-lock.json │ ├── package.json │ └── readme.md └── webxdc ├── .gitignore ├── Makefile ├── icon.png ├── icon.svg ├── manifest.toml └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | .data 2 | .docs 3 | .astro 4 | node_modules 5 | example-data 6 | 7 | # config file 8 | dev/public/js/config.js 9 | dist/js/config.js 10 | 11 | # unused Astro generated files 12 | dist/content-assets.mjs 13 | dist/content-modules.mjs 14 | dist/data-store.json 15 | dist/settings.json 16 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | _colors*.scss 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:latest 2 | 3 | COPY index.html /usr/share/nginx/html/ 4 | COPY dist/ /usr/share/nginx/html/dist/ 5 | 6 | EXPOSE 80 7 | -------------------------------------------------------------------------------- /dev/README.md: -------------------------------------------------------------------------------- 1 | # Astro Starter Kit: Basics 2 | 3 | ```sh 4 | npm create astro@latest -- --template basics 5 | ``` 6 | 7 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics) 8 | [![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics) 9 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json) 10 | 11 | > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! 12 | 13 | ![just-the-basics](https://github.com/withastro/astro/assets/2244813/a0a5533c-a856-4198-8470-2d67b1d7c554) 14 | 15 | ## 🚀 Project Structure 16 | 17 | Inside of your Astro project, you'll see the following folders and files: 18 | 19 | ```text 20 | / 21 | ├── public/ 22 | │ └── favicon.svg 23 | ├── src/ 24 | │ ├── layouts/ 25 | │ │ └── Layout.astro 26 | │ └── pages/ 27 | │ └── index.astro 28 | └── package.json 29 | ``` 30 | 31 | 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/). 32 | 33 | ## 🧞 Commands 34 | 35 | All commands are run from the root of the project, from a terminal: 36 | 37 | | Command | Action | 38 | | :------------------------ | :----------------------------------------------- | 39 | | `npm install` | Installs dependencies | 40 | | `npm run dev` | Starts local dev server at `localhost:4321` | 41 | | `npm run build` | Build your production site to `./dist/` | 42 | | `npm run preview` | Preview your build locally, before deploying | 43 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | 44 | | `npm run astro -- --help` | Get help using the Astro CLI | 45 | 46 | ## 👀 Want to learn more? 47 | 48 | Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). 49 | -------------------------------------------------------------------------------- /dev/astro.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { defineConfig } from "astro/config"; 3 | 4 | import relativeLinks from "astro-relative-links"; 5 | 6 | import compress from "astro-compress"; 7 | 8 | // https://astro.build/config 9 | export default defineConfig({ 10 | integrations: [ 11 | relativeLinks(), 12 | compress({ 13 | Exclude: ["alpinejs-i18n.min.js", "alpinejs.min.js", "jszip.min.js", "config.js", "config.default.js"], 14 | }), 15 | ], 16 | outDir: "../dist", 17 | }); 18 | -------------------------------------------------------------------------------- /dev/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marl", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "astro dev", 7 | "build": "astro build", 8 | "preview": "astro preview", 9 | "astro": "astro" 10 | }, 11 | "dependencies": { 12 | "astro": "^5.7.0", 13 | "astro-compress": "^2.3.6", 14 | "astro-relative-links": "^0.4.2" 15 | }, 16 | "devDependencies": { 17 | "sass-embedded": "^1.83.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /dev/public/js/MARL/init.js: -------------------------------------------------------------------------------- 1 | // Note: Alpine plug-ins must be inserted BEFORE alpinejs 2 | loadScript("alpine-i18n"); 3 | loadScript("alpine"); 4 | 5 | document.addEventListener("alpine:init", () => { 6 | // create and init stores 7 | Alpine.store("files", filesStore); 8 | Alpine.store("lightbox", lightboxStore); 9 | Alpine.store("ui", uiStore); 10 | Alpine.store("userPrefs", userPrefsStore); 11 | 12 | const salutations = [ 13 | "Hi!", 14 | "Hiya!", 15 | "Hello there!", 16 | "Good day!", 17 | "Hullo!", 18 | "Buongiorno!", 19 | "Guten Tag!", 20 | "Bonjour!", 21 | "Oh hey!", 22 | ]; 23 | marlConsole(`MARL loaded. ${salutations[Math.floor(Math.random() * salutations.length)]} 😊`); 24 | 25 | checkMobileLayout(); 26 | setMarlMode(); 27 | loadCustomPrefs(); 28 | resetStores(); 29 | 30 | if (serverMode()) { 31 | initServerMode(); 32 | } else { 33 | initLocalMode(); 34 | } 35 | }); 36 | 37 | // i18n 38 | 39 | document.addEventListener("alpine-i18n:ready", function () { 40 | AlpineI18n.create("en", appStrings); 41 | AlpineI18n.fallbackLocale = "en"; 42 | setLang(); 43 | }); 44 | -------------------------------------------------------------------------------- /dev/public/js/MARL/libs.js: -------------------------------------------------------------------------------- 1 | const isFileProtocol = window.location.protocol === "file:"; 2 | 3 | const scripts = { 4 | jszip: { 5 | src: "js/libs/jszip.min.js", 6 | integrity: "sha512-XMVd28F1oH/O71fzwBnV7HucLxVwtxf26XV8P4wPk26EDxuGZ91N8bsOttmnomcCD3CS5ZMRL50H0GgOHvegtg==", 7 | crossorigin: "anonymous", 8 | defer: false, 9 | }, 10 | "alpine-i18n": { 11 | src: "js/libs/alpinejs-i18n.min.js", 12 | integrity: "sha256-o204NcFyHPFzboSC51fufMqFe2KJdQfSCl8AlvSZO/E=", 13 | crossorigin: "anonymous", 14 | defer: true, 15 | }, 16 | alpine: { 17 | src: "js/libs/alpinejs.min.js", 18 | integrity: "sha512-FUaEyIgi9bspXaH6hUadCwBLxKwdH7CW24riiOqA5p8hTNR/RCLv9UpAILKwqs2AN5WtKB52CqbiePBei3qjKg==", 19 | crossorigin: "anonymous", 20 | defer: true, 21 | }, 22 | }; 23 | 24 | function loadScript(scriptName) { 25 | const s = scripts[scriptName]; 26 | 27 | const script = document.createElement("script"); 28 | script.src = s.src; 29 | if (!isFileProtocol) { 30 | script.integrity = s.integrity; 31 | script.crossOrigin = s.crossorigin; 32 | } 33 | if (s.defer) { 34 | script.defer = true; 35 | } 36 | document.head.appendChild(script); 37 | } 38 | -------------------------------------------------------------------------------- /dev/public/js/config.default.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This file is used to determine whether MARL operates in LOCAL or SERVER mode. 4 | For more information, please read the "server-mode.md" documentation file. 5 | 6 | Important: please rename this file "config.js" if you want to use it. 7 | 8 | 1. local mode 9 | ============= 10 | 11 | If the "servers" constant (below) is absent or empty, (or if this file is removed or renamed) MARL will work in LOCAL mode. 12 | Default value: 13 | 14 | const servers = []; 15 | 16 | 2. server mode 17 | ============== 18 | 19 | Use the "servers" constant below to indicate the path(s) where MARL can find your archive(s). 20 | 21 | If a path is specified, MARL will automatically switch to server mode. 22 | 23 | Relative paths are authorized. They should be relative to MARL (index.html), not to this config file. 24 | 25 | Example: 26 | 27 | const servers = [ 28 | "../data/archive1/", 29 | "../data/archive2/", 30 | "https://my-other-server.net/some-other-path/", 31 | ]; 32 | 33 | Each archive file must be already unpacked on the server. The "actor.json" and "outbox.json" files should be stored directly under the specified path. 34 | 35 | You may specify as many archives as you want. You can store them wherever you want. In case the archive is stored on a different server from MARL itself, make sure that the server has CORS enabled. 36 | 37 | */ 38 | 39 | const servers = []; 40 | 41 | /* 42 | 43 | You can use the following object to change the default options for the app (those are the options found in the tools panel). 44 | Note: Users will still be able to change those options for themselves. 45 | 46 | const customPrefs = {}; 47 | 48 | You can use the following properties. All properties are optional: 49 | 50 | - lang: the language for the UI 51 | possible values: "en" (default), "fr", "es" 52 | note: if no option is set, the app will attempt to use the same language as the user's browser (if available for the app) 53 | 54 | - theme: the theme for the app 55 | possible values: "light" (default), "dark" 56 | note: if no option is set, the app will follow the user preference (set in their browser or OS) 57 | 58 | - sortAsc: the order of the posts (chronological or not); true means "oldest posts first" 59 | possible values: true (default), false 60 | 61 | - pageSize: the number of posts per page 62 | possibles values: any number above 0; default is 10; be mindful that large values may slow down the app significantly 63 | 64 | - combinePanels: (on large screens) combine the panels into one sidebar 65 | possible values: true, false (default) 66 | 67 | - defaultPanel: if "combinePanels" is active (true), which panel is displayed upon loading the app 68 | possible values: "actor", "filters", "tags", "tools" 69 | note: if "combinePanels" is active and if this option is not set, no panel will be initially displayed 70 | 71 | - simplifyPostsDisplay: hide some technical or redundant elements in posts display 72 | possible values: true, false (default) 73 | 74 | The folloging options are specific to server mode and not present in the tools panel: 75 | 76 | - showActorJson: whether or not to display the full JSON data in the actor panel 77 | possible values: true, false (default) 78 | 79 | - showPostsJson: whether or not to display the full JSON data related to each post 80 | possible values: true, false (default) 81 | note: JSON data for posts is always hidden in "simplify post display" mode 82 | 83 | Example: 84 | 85 | const customPrefs = { 86 | pageSize: 5, 87 | combinePanels: true, 88 | defaultPanel: "actor", 89 | simplifyPostsDisplay: true, 90 | }; 91 | 92 | */ 93 | -------------------------------------------------------------------------------- /dev/public/js/i18n/_langs.js: -------------------------------------------------------------------------------- 1 | const appLangs = { 2 | en: "English", 3 | fr: "Français", 4 | es: "Español", 5 | }; 6 | 7 | let appStrings = {}; 8 | -------------------------------------------------------------------------------- /dev/public/js/i18n/en.js: -------------------------------------------------------------------------------- 1 | appStrings["en"] = { 2 | welcome: { 3 | title: "Welcome to {appName}", 4 | p1: `MARL allows you to explore the content of your Mastodon archive file in a 5 | user-friendly interface. Everything takes place in the browser: your archive stays 6 | strictly on your computer; none of its data is sent to any server.`, 7 | p2: `You can request your Mastodon archive by logging into your account on the web, 8 | then visiting "Preferences > Import and export > Request your archive".
9 | Please note: only ZIP files are supported (not GZ).`, 10 | p3: `Start by opening your archive file with MARL.
11 | You can drag and drop it anywhere on this page, or 12 | {labelStart}click here to select it{labelEnd}.`, 13 | }, 14 | misc: { 15 | loading: "Loading", 16 | criticalFailure: "Critical failure", 17 | closePanelBtn: "Close panel", 18 | }, 19 | menu: { 20 | profile: "Profile", 21 | filters: "Filters", 22 | tags: "Tags", 23 | tools: "Tools", 24 | filtersActive: "some filters are active", 25 | }, 26 | lightbox: { 27 | next: "Next image", 28 | prev: "Previous image", 29 | close: "Close image", 30 | }, 31 | actor: { 32 | accountInfo: "Account info", 33 | accounts: "Accounts", 34 | noAvatarImage: "No avatar image", 35 | noHeaderImage: "No header image", 36 | headerImage: "Header", 37 | memberSince: "Member since", 38 | countPosts: "posts", 39 | countInArchive: "in archive", 40 | countDiffWhy: "Why are those two numbers different?", 41 | countDiffExplanation: `Posts that are not directly hosted on your instance are kept 42 | in a cache by your instance for a given time, after what they are deleted from that 43 | cache. Posts that are not in your instance cache any more are not included in your 44 | archive. This affects boosts, likes, and bookmarks.`, 45 | rawData: "Raw data {fileName}", 46 | likes: "Favorites", 47 | likesEmpty: "no favorites", 48 | bookmarks: "Bookmarks", 49 | bookmarksEmpty: "no bookmarks", 50 | }, 51 | filters: { 52 | panelTitle: "Filter posts", 53 | panelNotice: `The list of posts will be automatically updated based on the active 54 | filters below.`, 55 | fullText: "Full text", 56 | hashtagText: "Hashtags", 57 | mentionText: "Mentions", 58 | externalLink: "External links", 59 | summary: "Summary (CW)", 60 | isDuplicate: "Non-exact duplicates", 61 | 62 | mustContain: "Must contain", 63 | hasHashtags: "Hashtag(s)", 64 | hasMentions: "Mention(s)", 65 | hasPoll: "Poll", 66 | hasExternalLink: "External link(s)", 67 | hasSummary: "Summary (CW)", 68 | 69 | activities: "Activities", 70 | hasLikes: "likes or more", 71 | hasShares: "shares or more", 72 | 73 | dateTime: "Date & time", 74 | afterDate: "or later", 75 | beforeDate: "or earlier", 76 | afterTime: "or later", 77 | beforeTime: "or earlier", 78 | 79 | type: "Type", 80 | typeOriginal: "Original posts (incl. replies)", 81 | typeBoost: "Boosts", 82 | startingAt: 'Starts with "@"', 83 | noStartingAt: 'Does not start with "@"', 84 | isInConversation: "Is part of a thread", 85 | isSensitive: "Marked as sensitive", 86 | isEdited: "Has been edited", 87 | 88 | mustHaveAttachement: "Must have attachment", 89 | attachmentAny: "Any type", 90 | attachmentImage: "Image(s)", 91 | attachmentVideo: "Video(s)", 92 | attachmentSound: "Sound(s)", 93 | attachmentNoAltText: "Without alt text", 94 | attachmentWithAltText: "With alt text", 95 | 96 | visibility: "Visibility", 97 | visibilityPublic: "Public", 98 | visibilityUnlisted: "Quiet public (Unlisted)", 99 | visibilityFollowers: "Followers only", 100 | visibilityMentioned: "Mentioned people only", 101 | 102 | language: "Language", 103 | author: "Author", 104 | 105 | showResults: "Show results", 106 | resetFilters: "Reset filters", 107 | }, 108 | header: { 109 | countLabel: "posts", 110 | oldestFirst: "oldest first", 111 | latestFirst: "latest first", 112 | }, 113 | paging: { 114 | first: "First", 115 | prev: "Prev", 116 | next: "Next", 117 | last: "Last", 118 | page: "Page", 119 | }, 120 | posts: { 121 | panelTitle: "Posts", 122 | noResults: "No results for the specified filters", 123 | noPostsError: "No posts found in archive", 124 | }, 125 | post: { 126 | showConversation: "Show thread", 127 | by: "by", 128 | lastUpdated: "Last updated", 129 | linkToPost: "Link", 130 | pollType: "Poll type:", 131 | pollTypeOne: "One choice only", 132 | pollTypeAny: "Multiple choices", 133 | pollVotersCount: "Total voters:", 134 | pollVotersCountVotes: "({nb} votes)", 135 | pollVotersCountAlt: "{nb} voters", 136 | pollEndTime: "End time:", 137 | pollStatus: "Status:", 138 | pollStatusClosed: "Closed", 139 | pollStatusRunning: "Running", 140 | attachmentNoAlt: "No description provided", 141 | attachmentInArchive: "In archive:", 142 | attachmentNotFound: "⚠️ Content not found in archive", 143 | like1: "1 like", 144 | like2: "{nb} likes", 145 | share1: "1 share", 146 | share2: "{nb} shares", 147 | people: "People", 148 | hashtags: "Hashtags", 149 | extLinks: "External links", 150 | rawData: "Raw data", 151 | }, 152 | conversation: { 153 | panelTitle: "Thread", 154 | panelTitleNbPosts: "({nb} posts in archive)", 155 | closePanel: "Close thread", 156 | helpSummary: "Why is this thread broken or incomplete?", 157 | helpContent: 158 | "MARL can only show you posts that are present in your archive. Posts written by other people are typically not included in an archive. If posts are missing in a thread, it's impossible for MARL to reconnect the broken parts. In other words, what you will mostly see here are posts where you reply to yourself (aka threads 🧵).", 159 | }, 160 | tags: { 161 | panelTitle: "Tags", 162 | hashtags: "Hashtags", 163 | mentions: "Mentions", 164 | boosts: "Boosted users", 165 | hashtagsFilter: "Filter hashtags", 166 | mentionsFilter: "Filter mentions", 167 | boostsFilter: "Filter boosted users", 168 | resetFilter: "reset filter", 169 | }, 170 | tools: { 171 | panelTitle: "Tools", 172 | appSettings: "App settings", 173 | selectLanguage: "Select language", 174 | useDarkTheme: "Use dark theme", 175 | useLightTheme: "Use light theme", 176 | panelsSettings: "Panels", 177 | combinePanels: "Combine panels", 178 | combinePanelsDesc: "(Wide screens only) Combine all panels into a single sidebar with vertical tabs", 179 | defaultPanel: "Default panel", 180 | defaultPanelDesc: "Which panel is shown by default when opening the app (if the panels are combined)", 181 | lastPanel: "Last panel used", 182 | postsSettings: "Posts", 183 | postsPerPage: "Number of posts per page:", 184 | chronologicalOrder: "Chronological order", 185 | chronologicalOrderDesc: "Uncheck to sort posts in reverse chronological order (most recent first)", 186 | simplifyPostsDisplay: "Simplify posts display", 187 | simplifyPostsDisplayDesc: "Hide some technical or redundant elements", 188 | loadedFiles: "Loaded files", 189 | loadedRemotes: "Loaded archives", 190 | addAnother: "Add another archive", 191 | addAnotherTip: 192 | "Tip: You can open multiple archives at once.
You can also drag and drop your archive files anywhere on this window.", 193 | startOver: "Start over", 194 | startOverConfirm: "Discard current data and load a new archive file?", 195 | appLog: "App log", 196 | projectPage: `Project page (github)`, 197 | }, 198 | }; 199 | -------------------------------------------------------------------------------- /dev/public/js/i18n/es.js: -------------------------------------------------------------------------------- 1 | appStrings["es"] = { 2 | welcome: { 3 | title: "Bienvenido a {appName}", 4 | p1: `MARL le permite explorar el contenido de su archivo Mastodon en un 5 | interfaz fácil de usar. Todo tiene lugar en el navegador: su archivo permanece 6 | estrictamente en su ordenador; ninguno de sus datos se envía a ningún servidor.`, 7 | p2: `Puede solicitar su archivo de Mastodon accediendo a su cuenta en la web, 8 | y visitando «Preferencias > Importar y exportar > Solicitar su archivo»..
9 | Nota: sólo se admiten archivos ZIP (no GZ).`, 10 | p3: `Empieza abriendo tu archivo con MARL.
11 | Puede arrastrarlo y soltarlo en cualquier lugar de esta página, o 12 | {labelStart}haga clic aquí para seleccionarlo{labelEnd}.`, 13 | }, 14 | misc: { 15 | loading: "Cargando", 16 | criticalFailure: "fallo crítico", 17 | closePanelBtn: "Cerrar panel", 18 | }, 19 | menu: { 20 | profile: "Perfil", 21 | filters: "Filtros", 22 | tags: "Etiquetas", 23 | tools: "Herramientas", 24 | filtersActive: "algunos filtros están activos", 25 | }, 26 | lightbox: { 27 | next: "Imagen siguiente", 28 | prev: "imagen previa", 29 | close: "Cerrar imagen", 30 | }, 31 | actor: { 32 | accountInfo: "Info de cuenta", 33 | accounts: "Cuentas", 34 | noAvatarImage: "Sin avatar", 35 | noHeaderImage: "Sin imagen de cabecera", 36 | headerImage: "Cabecera", 37 | memberSince: "Miembro desde", 38 | countPosts: "publicaciones", 39 | countInArchive: "en el archivo", 40 | countDiffWhy: "¿Por qué son estos dos números diferentes?", 41 | countDiffExplanation: `Los mensajes que no se alojan directamente en su instancia se mantienen 42 | en una caché durante un tiempo determinado, después de lo cual se eliminan. 43 | Los mensajes que ya no están en la caché de tu instancia no se incluyen en tu archivo. 44 | Esto afecta a los impulsos, favoritos y marcadores.`, 45 | rawData: "Datos en bruto {fileName}", 46 | likes: "Favoritos", 47 | likesEmpty: "sin favoritos", 48 | bookmarks: "Marcadores", 49 | bookmarksEmpty: "sin marcadores", 50 | }, 51 | filters: { 52 | panelTitle: "Filtrar publicaciones", 53 | panelNotice: `La lista de entradas se actualizará automáticamente en función 54 | de los siguientes filtros activos.`, 55 | fullText: "Texto completo", 56 | hashtagText: "Etiquetas", 57 | mentionText: "Menciones", 58 | externalLink: "Enlaces externos", 59 | summary: "Aviso de contenido", 60 | isDuplicate: "Duplicado no-exacto", 61 | 62 | mustContain: "debe contener", 63 | hasHashtags: "Etiquetas(s)", 64 | hasMentions: "Mencion(es)", 65 | hasPoll: "Encuesta", 66 | hasExternalLink: "Enlace(s) externo(s)", 67 | hasSummary: "Aviso de contenido", 68 | 69 | activities: "Actividades", 70 | hasLikes: "favoritos o más", 71 | hasShares: "impulsos o más", 72 | 73 | dateTime: "Fecha & hora", 74 | afterDate: "o después", 75 | beforeDate: "o antes", 76 | afterTime: "o después", 77 | beforeTime: "o antes", 78 | 79 | type: "Tipo", 80 | typeOriginal: "Publicación original (incl. respuestas)", 81 | typeBoost: "Impulsos", 82 | startingAt: 'Empieza por "@"', 83 | noStartingAt: 'No empieza por "@"', 84 | isInConversation: "Es parte de un hilo", 85 | isSensitive: "Marcado como sensible", 86 | isEdited: "Editado", 87 | 88 | mustHaveAttachement: "Debe tener adjunto", 89 | attachmentAny: "Cualquier tipo", 90 | attachmentImage: "Imagen(es)", 91 | attachmentVideo: "Vídeo(s)", 92 | attachmentSound: "Sonido(s)", 93 | attachmentNoAltText: "Sin texto alt", 94 | attachmentWithAltText: "Con texto alt", 95 | 96 | visibility: "Visibilidad", 97 | visibilityPublic: "Público", 98 | visibilityUnlisted: "Silencioso (no listado)", 99 | visibilityFollowers: "Sólo seguidores", 100 | visibilityMentioned: "Sólo cuentas mencionadas", 101 | 102 | language: "Idioma", 103 | author: "Autor", 104 | 105 | showResults: "Mostrar resultados", 106 | resetFilters: "Reiniciar filtros", 107 | }, 108 | header: { 109 | countLabel: "publicaciones", 110 | oldestFirst: "desde más antiguo", 111 | latestFirst: "desde más reciente", 112 | }, 113 | paging: { 114 | first: "Inicio", 115 | prev: "Anterior", 116 | next: "Siguiente", 117 | last: "Fin", 118 | page: "Página", 119 | }, 120 | posts: { 121 | panelTitle: "Publicaciones", 122 | noResults: "Sin resultados para los filtros indicados", 123 | noPostsError: "No se encontraron publicaciones en el archivo", 124 | }, 125 | post: { 126 | showConversation: "Mostrar hilo", 127 | by: "por", 128 | lastUpdated: "Última modificación", 129 | linkToPost: "Enlace", 130 | pollType: "Tipo de encuesta:", 131 | pollTypeOne: "Opción única", 132 | pollTypeAny: "Opciones múltiples", 133 | pollVotersCount: "Votantes totales:", 134 | pollVotersCountVotes: "({nb} votos)", 135 | pollVotersCountAlt: "{nb} votantes", 136 | pollEndTime: "Termina:", 137 | pollStatus: "Status:", 138 | pollStatusClosed: "Cerrada", 139 | pollStatusRunning: "En curso", 140 | attachmentNoAlt: "Sin descripción", 141 | attachmentInArchive: "En el archivo:", 142 | attachmentNotFound: "⚠️ Contenido no encontrado en el archivo", 143 | like1: "1 favorito", 144 | like2: "{nb} favoritos", 145 | share1: "1 impulso", 146 | share2: "{nb} impulsos", 147 | people: "Gente", 148 | hashtags: "Etiquetas", 149 | extLinks: "Enlaces externos", 150 | rawData: "Datos en bruto", 151 | }, 152 | conversation: { 153 | panelTitle: "Hilo", 154 | panelTitleNbPosts: "({nb} publicaciones en archivo)", 155 | closePanel: "Cerrar hilo", 156 | helpSummary: "¿Porque está roto o incompleto este hilo?", 157 | helpContent: 158 | "MARL sólo puede mostrarte los mensajes que están presentes en tu archivo. Los mensajes escritos por otras personas normalmente no se incluyen en un archivo. Si faltan mensajes en un hilo, es imposible para MARL reconectar las partes rotas. En otras palabras, lo que verás aquí son mensajes en los que te respondes a ti mismo (es decir, hilos 🧵).", 159 | }, 160 | tags: { 161 | panelTitle: "Etiquetas", 162 | hashtags: "Etiquetas", 163 | mentions: "Menciones", 164 | boosts: "Usuarios impulsados", 165 | hashtagsFilter: "Filtrar etiquetas", 166 | mentionsFilter: "Filtrar menciones", 167 | boostsFilter: "Filtrar usuarios impulsados", 168 | resetFilter: "reiniciar filtro", 169 | }, 170 | tools: { 171 | panelTitle: "Herramientas", 172 | appSettings: "Ajustes de aplicación", 173 | selectLanguage: "Seleccionar idioma", 174 | useDarkTheme: "Usar tema oscuro", 175 | useLightTheme: "Usar tema claro", 176 | panelsSettings: "Paneles", 177 | combinePanels: "Combinar paneles", 178 | combinePanelsDesc: 179 | "(Sólo pantalla anchas) Combinar todos los paneles en una una barra lateral única con pestañas verticales", 180 | defaultPanel: "Panel por defecto", 181 | defaultPanelDesc: "Qué panel se muestra por defecto al abrir la aplicación (si hay paneles combinados)", 182 | lastPanel: "Último panel usado", 183 | postsSettings: "Publicaciones", 184 | postsPerPage: "Número de publicaciones por página:", 185 | chronologicalOrder: "Orden cronológico", 186 | chronologicalOrderDesc: 187 | "Desmarca para ordenar las publicaciones en orden cronológico inverso (más recientes primero)", 188 | simplifyPostsDisplay: "Simplificar visualización de publicaciones", 189 | simplifyPostsDisplayDesc: "Ocultar algunos elementos técnicos o redundantes", 190 | loadedFiles: "Archivos cargados", 191 | loadedRemotes: "Archivos cargados", 192 | addAnother: "Añadir otro archivo", 193 | addAnotherTip: 194 | "Consejo: Puede abrir varios archivos al mismo tiempo.
También puede arrastrar y soltar sus ficheros comprimidos en cualquier lugar de esta ventana.", 195 | startOver: "Reiniciar", 196 | startOverConfirm: "¿Descartar los datos actuales y cargar un nuevo archivo?", 197 | appLog: "Registro de la aplicación", 198 | projectPage: `Página del proyecto (github)`, 199 | }, 200 | }; 201 | -------------------------------------------------------------------------------- /dev/public/js/i18n/fr.js: -------------------------------------------------------------------------------- 1 | appStrings["fr"] = { 2 | welcome: { 3 | title: "Bienvenue dans {appName}", 4 | p1: `MARL vous permet d'explorer le contenu de votre archive Mastodon dans une 5 | interface simple d'utilisation. Tout se passe dans votre navigateur : votre archive 6 | reste sur votre appareil; aucune donnée n'est envoyée à aucun serveur.`, 7 | p2: `Vous pouvez demander votre archive Mastodon en vous identifiant avec votre compte 8 | sur le web, puis en visitant "Préférences >Import et export >Demandez vos archives".
9 | Important : seuls les fichiers ZIP sont acceptés (pas les fichiers GZ).`, 10 | p3: `Commencez par ouvrir votre archive avec MARL.
11 | Vous pouvez la glisser-déposer n'importe où sur cette page, ou 12 | {labelStart}cliquer ici pour la sélectionner{labelEnd}.`, 13 | }, 14 | misc: { 15 | loading: "Chargement", 16 | criticalFailure: "Erreur fatale", 17 | closePanelBtn: "Fermer le panneau", 18 | }, 19 | menu: { 20 | profile: "Profil", 21 | filters: "Filtres", 22 | tags: "Tags", 23 | tools: "Outils", 24 | filtersActive: "certains filtres sont actifs", 25 | }, 26 | lightbox: { 27 | next: "Image suivante", 28 | prev: "Image précédente", 29 | close: "Fermer l'image", 30 | }, 31 | actor: { 32 | accountInfo: "Infos du compte", 33 | accounts: "Comptes", 34 | noAvatarImage: "Pas d'avatar", 35 | noHeaderImage: "pas d'image d'en-tête", 36 | headerImage: "En-tête", 37 | memberSince: "Membre depuis", 38 | countPosts: "posts", 39 | countInArchive: "dans l'archive", 40 | countDiffWhy: "Pourquoi ces deux nombres sont-ils différents ?", 41 | countDiffExplanation: `Les posts qui ne sont pas hébergés directement sur votre instance 42 | sont gardés en cache par celle-ci pour une durée limitée, après quoi ils sont supprimés 43 | de ce cache. Les posts qui ne sont plus présents dans le cache de votre instance ne sont 44 | pas inclus dans votre archive. Cela concerne les partages, les favoris et les marque-pages.`, 45 | rawData: "Données brutes {fileName}", 46 | likes: "Favoris", 47 | likesEmpty: "aucun favori", 48 | bookmarks: "Marque-pages", 49 | bookmarksEmpty: "aucun marque-page", 50 | }, 51 | filters: { 52 | panelTitle: "Filtrer les posts", 53 | panelNotice: `La liste des posts sera automatiquement mise à jour en fonction des filtres 54 | activés ci-dessous.`, 55 | fullText: "Partout", 56 | hashtagText: "Hashtags", 57 | mentionText: "Mentions", 58 | externalLink: "Liens externes", 59 | summary: "Avertissement de contenu", 60 | isDuplicate: "Doublons imparfaits", 61 | 62 | mustContain: "Doit contenir", 63 | hasHashtags: "Hashtag(s)", 64 | hasMentions: "Mention(s)", 65 | hasPoll: "Sondage", 66 | hasExternalLink: "Lien(s) externe(s)", 67 | hasSummary: "Avertissement de contenu", 68 | 69 | activities: "Activités", 70 | hasLikes: "favoris ou plus", 71 | hasShares: "partages ou plus", 72 | 73 | dateTime: "Date & heure", 74 | afterDate: "ou plus tard", 75 | beforeDate: "ou plus tôt", 76 | afterTime: "ou plus tard", 77 | beforeTime: "ou plus tôt", 78 | 79 | type: "Type", 80 | typeOriginal: "Posts originaux (y.c. réponses)", 81 | typeBoost: "Partages", 82 | startingAt: 'Commence par "@"', 83 | noStartingAt: 'Ne commence pas par "@"', 84 | isInConversation: "Fait partie d'un fil", 85 | isSensitive: "Marqué comme sensible", 86 | isEdited: "A été modifié", 87 | 88 | mustHaveAttachement: "Doit avoir un fichier joint", 89 | attachmentAny: "N'importe quel type", 90 | attachmentImage: "Image(s)", 91 | attachmentVideo: "Vidéo(s)", 92 | attachmentSound: "Son(s)", 93 | attachmentNoAltText: "Sans description alternative", 94 | attachmentWithAltText: "Avec description alternative", 95 | 96 | visibility: "Confidentialité", 97 | visibilityPublic: "Public", 98 | visibilityUnlisted: "Public discret", 99 | visibilityFollowers: "Abonnés", 100 | visibilityMentioned: "Personnes spécifiques", 101 | 102 | language: "Langue", 103 | author: "Auteur", 104 | 105 | showResults: "Afficher les résultats", 106 | resetFilters: "Réinitialiser les filtres", 107 | }, 108 | header: { 109 | countLabel: "posts", 110 | oldestFirst: "les plus anciens d'abord", 111 | latestFirst: "les plus récents d'abord", 112 | }, 113 | paging: { 114 | first: "Première", 115 | prev: "Précédente", 116 | next: "Suivante", 117 | last: "Dernière", 118 | page: "Page", 119 | }, 120 | posts: { 121 | panelTitle: "Posts", 122 | noResults: "Pas de résultats pour les filtres spécifiés", 123 | noPostsError: "Aucun post trouvé dans l'archive", 124 | }, 125 | post: { 126 | showConversation: "Voir le fil", 127 | by: "par", 128 | lastUpdated: "Dernière modification", 129 | linkToPost: "Lien", 130 | pollType: "Type de sondage :", 131 | pollTypeOne: "Choix unique", 132 | pollTypeAny: "Choix multiples", 133 | pollVotersCount: "Nombre de votants :", 134 | pollVotersCountVotes: "({nb} votes)", 135 | pollVotersCountAlt: "{nb} votants", 136 | pollEndTime: "Date de fin :", 137 | pollStatus: "État :", 138 | pollStatusClosed: "Terminé", 139 | pollStatusRunning: "En cours", 140 | attachmentNoAlt: "Aucune description fournie", 141 | attachmentInArchive: "Dans l'archive :", 142 | attachmentNotFound: "⚠️ Média introuvable à l'emplacement indiqué", 143 | like1: "1 favori", 144 | like2: "{nb} favoris", 145 | share1: "1 partage", 146 | share2: "{nb} partages", 147 | people: "Personnes", 148 | hashtags: "Hashtags", 149 | extLinks: "Liens externes", 150 | rawData: "Données brutes", 151 | }, 152 | conversation: { 153 | panelTitle: "Fil", 154 | panelTitleNbPosts: "({nb} posts dans l'archive)", 155 | closePanel: "Fermer le fil", 156 | helpSummary: "Pourquoi ce fil est-il cassé ou incomplet ?", 157 | helpContent: 158 | "MARL peut seulement afficher les posts qui sont présents dans votre archive. De manière générale, les posts rédigés par d'autres personnes ne font pas partie du contenu d'une archive. Si un fil est cassé du fait de l'absence d'un post, il est impossible pour MARL de reconnecter les fragments. En d'autres termes, ce que vous verrez ici, ce sont principalement des réponses à vous-même (autrement dit, des fils ou threads 🧵).", 159 | }, 160 | tags: { 161 | panelTitle: "Tags", 162 | hashtags: "Hashtags", 163 | mentions: "Mentions", 164 | boosts: "Utilisateurs partagés", 165 | hashtagsFilter: "Filtrer les hashtags", 166 | mentionsFilter: "Filtrer les mentions", 167 | boostsFilter: "Filter utilisateurs partagés", 168 | resetFilter: "réinitialiser le filtre", 169 | }, 170 | tools: { 171 | panelTitle: "Outils", 172 | appSettings: "Réglages de l'app", 173 | selectLanguage: "Choisir la langue", 174 | useDarkTheme: "Utiliser le thème sombre", 175 | useLightTheme: "Utiliser le thème clair", 176 | panelsSettings: "Panneaux", 177 | combinePanels: "Combiner les panneaux", 178 | combinePanelsDesc: 179 | "(Sur les écrans larges) Combiner les différents panneaux en une seule barre latérale avec onglets verticaux", 180 | defaultPanel: "Panneau par défaut", 181 | defaultPanelDesc: "Quel panneau est affiché par défaut au chargement de l'app (si les panneaux sont combinés)", 182 | lastPanel: "Dernier panneau utilisé", 183 | postsSettings: "Posts", 184 | postsPerPage: "Nombre de posts page page :", 185 | chronologicalOrder: "Ordre chronologique", 186 | chronologicalOrderDesc: 187 | "Décocher pour trier les posts dans l'ordre chronologique inverse (les plus récents en premier)", 188 | simplifyPostsDisplay: "Simplifier l'affichage des posts", 189 | simplifyPostsDisplayDesc: "Cacher certains éléments techniques ou redondants", 190 | loadedFiles: "Fichiers chargés", 191 | loadedRemotes: "Archives chargées", 192 | addAnother: "Ajouter une autre archive", 193 | addAnotherTip: 194 | "Astuce: Vous pouvez ouvrir plusieurs archives en même temps.
Vous pouvez aussi glisser-déposer vos fichiers d'archive n'importe où dans cette fenêtre.", 195 | startOver: "Recommencer", 196 | startOverConfirm: "Repartir de zéro et charger un nouveau fichier ?", 197 | appLog: "Journal", 198 | projectPage: `Page du project (github)`, 199 | }, 200 | }; 201 | -------------------------------------------------------------------------------- /dev/public/js/libs/alpinejs-i18n.min.js: -------------------------------------------------------------------------------- 1 | (()=>{var c=new Event("alpine-i18n:locale-change"),o=new Event("alpine-i18n:ready"),r={version:"2.5.2",set locale(e){this.checkLocale(e),this.currentLocale=e,document.dispatchEvent(c)},get locale(){return this.currentLocale},currentLocale:"",messages:{},fallbackLocale:"",options:{},create(e,i,n){this.messages=i,this.checkLocale(e),this.locale=e,this.options=n},checkLocale(e){if(!Object.keys(this.messages).includes(e))throw new Error(`Alpine I18n: The locale ${e} does not exist.`)},t(e,i){let n="";try{n=e.split(".").reduce((t,l)=>t[l],this.messages[this.locale])}catch{}if(!n&&this.fallbackLocale.length)n=e.split(".").reduce((t,l)=>t[l],this.messages[this.fallbackLocale]);else if(!n)return this.options?.debug?`???${e}`:e;for(let t in i)if(n&&n.replaceAll&&Object.prototype.hasOwnProperty.call(i,t)){let l=i[t],a=new RegExp("{s*("+t+")s*}","g");n=n.replaceAll(a,l)}return this.options?.debug?`[${n}]`:n}};function s(e){window.AlpineI18n=e.reactive(r),document.dispatchEvent(o),e.magic("locale",i=>n=>{if(!n)return window.AlpineI18n.locale;window.AlpineI18n.locale=n}),e.magic("t",i=>(n,t)=>window.AlpineI18n.t(n,t))}document.addEventListener("alpine:initializing",()=>{s(window.Alpine)});})(); 2 | -------------------------------------------------------------------------------- /dev/src/appVersion.js: -------------------------------------------------------------------------------- 1 | const appVersion = "2.10.1"; 2 | 3 | export default appVersion; 4 | -------------------------------------------------------------------------------- /dev/src/components/App.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import WelcomeScreen from './screens/WelcomeScreen.astro'; 3 | import LoadingScreen from './screens/LoadingScreen.astro'; 4 | import MainScreen from './screens/MainScreen.astro'; 5 | --- 6 | 7 |
14 | 15 | 18 | 19 | 22 | 23 | 26 | 27 |
28 | -------------------------------------------------------------------------------- /dev/src/components/LightBox.astro: -------------------------------------------------------------------------------- 1 | 60 | -------------------------------------------------------------------------------- /dev/src/components/MobileMenu.astro: -------------------------------------------------------------------------------- 1 |
2 | 39 |
40 | 41 |