├── src ├── types.d.ts ├── routes │ ├── models │ │ └── items.ts │ ├── Feeds.tsx │ ├── Items.tsx │ └── Settings.tsx ├── index.tsx ├── styles │ ├── Feeds.css │ ├── Settings.css │ └── Items.css ├── App.css ├── api │ ├── settings.ts │ ├── feeds.ts │ └── items.ts ├── styles.css └── App.tsx ├── .gitignore ├── src-tauri ├── build.rs ├── icons │ ├── 32x32.png │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ ├── 128x128.png │ ├── 128x128@2x.png │ ├── StoreLogo.png │ ├── Square30x30Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ └── Square310x310Logo.png ├── .gitignore ├── src │ ├── commands │ │ ├── settings.rs │ │ ├── items.rs │ │ └── feeds.rs │ ├── error.rs │ ├── models │ │ ├── database.rs │ │ └── settings.rs │ ├── fetchers │ │ ├── feeds.rs │ │ ├── items.rs │ │ └── auth.rs │ ├── main.rs │ └── worker.rs ├── tauri.conf.json └── Cargo.toml ├── tsconfig.node.json ├── biome.json ├── index.html ├── vite.config.ts ├── tsconfig.json ├── eslint.config.mjs ├── package.json ├── .github └── workflows │ ├── release.yml │ └── build.yml ├── README.md ├── LICENSE └── pnpm-lock.yaml /src/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.svg"; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | node_modules 4 | dist 5 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collie-reader/collie/HEAD/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collie-reader/collie/HEAD/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collie-reader/collie/HEAD/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collie-reader/collie/HEAD/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collie-reader/collie/HEAD/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collie-reader/collie/HEAD/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collie-reader/collie/HEAD/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | /data/ 5 | *.db 6 | -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collie-reader/collie/HEAD/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collie-reader/collie/HEAD/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collie-reader/collie/HEAD/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collie-reader/collie/HEAD/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collie-reader/collie/HEAD/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collie-reader/collie/HEAD/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collie-reader/collie/HEAD/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collie-reader/collie/HEAD/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collie-reader/collie/HEAD/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src/routes/models/items.ts: -------------------------------------------------------------------------------- 1 | export enum ItemType { 2 | INBOX = "Inbox", 3 | UNREAD = "Unread", 4 | SAVED = "Saved", 5 | FEED = "Feed", 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "node", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | /* @refresh reload */ 2 | import { render } from "solid-js/web"; 3 | import { Router } from "@solidjs/router"; 4 | 5 | import "./styles.css"; 6 | import App from "./App"; 7 | 8 | render( 9 | () => ( 10 | 11 | 12 | 13 | ), 14 | document.getElementById("root") as HTMLElement, 15 | ); 16 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/2.0.5/schema.json", 3 | "formatter": { 4 | "enabled": true, 5 | "indentStyle": "space", 6 | "indentWidth": 2, 7 | "lineWidth": 140 8 | }, 9 | "javascript": { 10 | "formatter": { 11 | "quoteStyle": "double", 12 | "semicolons": "always" 13 | } 14 | }, 15 | "linter": { 16 | "enabled": false 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Collie 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/styles/Feeds.css: -------------------------------------------------------------------------------- 1 | div.feeds-page form > input { 2 | width: 250px; 3 | } 4 | 5 | div.feeds-page form > span > input { 6 | margin-left: 0.5rem; 7 | } 8 | 9 | div.feeds-page ul.feed-list { 10 | padding: 0; 11 | list-style: none; 12 | } 13 | 14 | div.feeds-page ul.feed-list > li { 15 | margin-bottom: 0.5rem; 16 | } 17 | 18 | div.feeds-page ul.feed-list > li > .row { 19 | align-items: center; 20 | } 21 | 22 | div.feeds-page ul.feed-list > li > small { 23 | display: block; 24 | font-size: 0.8rem; 25 | margin-top: -3px; 26 | } 27 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .block { 2 | display: block; 3 | } 4 | 5 | .row { 6 | flex-direction: row; 7 | } 8 | 9 | .sep { 10 | color: var(--color-sep); 11 | } 12 | 13 | .lowp { 14 | opacity: 0.6; 15 | } 16 | 17 | div.navigation { 18 | padding-bottom: 16px; 19 | max-height: 38px; 20 | } 21 | 22 | div.navigation > h1 { 23 | font-size: 1rem; 24 | font-weight: 700; 25 | margin: 0 0.7rem 0 0; 26 | } 27 | 28 | div.navigation > * { 29 | margin-right: 0.7rem; 30 | } 31 | 32 | div.container { 33 | height: 100vh; 34 | width: 100%; 35 | } 36 | 37 | div.container h2 { 38 | margin: 0 0 1rem 0; 39 | } 40 | 41 | div.scrollable { 42 | margin-bottom: 70px; 43 | overflow: auto; 44 | } 45 | -------------------------------------------------------------------------------- /src/styles/Settings.css: -------------------------------------------------------------------------------- 1 | div.settings-page ul.setting-list { 2 | padding: 0; 3 | margin: 0; 4 | list-style: none; 5 | } 6 | 7 | div.settings-page ul.setting-list > li { 8 | margin-bottom: 0.5rem; 9 | } 10 | 11 | div.settings-page ul.setting-list > li > small { 12 | display: block; 13 | font-size: 0.8rem; 14 | margin-top: -2px; 15 | } 16 | 17 | div.settings-page label, 18 | div.settings-page button { 19 | margin-left: 5px; 20 | } 21 | 22 | div.settings-page .disabled { 23 | color: var(--color-sep); 24 | } 25 | 26 | div.settings-page ul.sublist { 27 | padding-top: 0.5rem; 28 | border-left: 2px solid var(--color-sep); 29 | } 30 | 31 | div.settings-page ul.sublist > li { 32 | padding-left: 0.5rem; 33 | } 34 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import solidPlugin from "vite-plugin-solid"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig(async () => ({ 6 | plugins: [solidPlugin()], 7 | 8 | // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` 9 | // 10 | // 1. prevent vite from obscuring rust errors 11 | clearScreen: false, 12 | // 2. tauri expects a fixed port, fail if that port is not available 13 | server: { 14 | port: 1420, 15 | strictPort: true, 16 | }, 17 | // 3. to make use of `TAURI_DEBUG` and other env variables 18 | // https://tauri.studio/v1/api/config#buildconfig.beforedevcommand 19 | envPrefix: ["VITE_", "TAURI_"], 20 | })); 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | "allowSyntheticDefaultImports": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "node", 12 | "allowImportingTsExtensions": true, 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "preserve", 17 | "jsxImportSource": "solid-js", 18 | 19 | /* Linting */ 20 | "strict": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "noFallthroughCasesInSwitch": true 24 | }, 25 | "include": ["src"], 26 | "references": [{ "path": "./tsconfig.node.json" }] 27 | } 28 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import tseslint from "typescript-eslint"; 3 | import solid from "eslint-plugin-solid/configs/typescript"; 4 | import globals from "globals"; 5 | 6 | export default tseslint.config( 7 | js.configs.recommended, 8 | ...tseslint.configs.recommended, 9 | { 10 | files: ["**/*.{ts,tsx}"], 11 | ...solid, 12 | languageOptions: { 13 | globals: { 14 | ...globals.browser, 15 | }, 16 | parserOptions: { 17 | project: "tsconfig.json", 18 | }, 19 | }, 20 | rules: { 21 | "no-shadow": "off", 22 | "@typescript-eslint/no-shadow": "error", 23 | "no-unused-vars": "off", 24 | "@typescript-eslint/no-unused-vars": "error", 25 | }, 26 | }, 27 | { 28 | ignores: ["dist/**", "node_modules/**", "src-tauri/**"], 29 | } 30 | ); 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "collie", 3 | "version": "0.2.0", 4 | "description": "A minimal RSS reader just for you", 5 | "scripts": { 6 | "start": "vite", 7 | "dev": "vite", 8 | "build": "vite build", 9 | "serve": "vite preview", 10 | "lint": "eslint src/", 11 | "format": "biome format --write src/", 12 | "tauri": "tauri" 13 | }, 14 | "license": "GPL-3.0", 15 | "dependencies": { 16 | "@solidjs/router": "^0.8.4", 17 | "@tauri-apps/api": "^1.6.0", 18 | "dayjs": "^1.11.13", 19 | "dompurify": "^3.2.5", 20 | "solid-js": "^1.9.5" 21 | }, 22 | "devDependencies": { 23 | "@biomejs/biome": "^2.0.0", 24 | "@tauri-apps/cli": "^1.6.3", 25 | "@types/dompurify": "^3.2.0", 26 | "eslint": "^9.15.0", 27 | "eslint-plugin-solid": "^0.14.5", 28 | "globals": "^15.12.0", 29 | "typescript": "^5.8.3", 30 | "typescript-eslint": "^8.15.0", 31 | "vite": "^4.5.13", 32 | "vite-plugin-solid": "^2.11.6" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src-tauri/src/commands/settings.rs: -------------------------------------------------------------------------------- 1 | use crate::models::settings::{self, Setting, SettingKey, SettingToUpdate}; 2 | use collie::repository::database::DbConnection; 3 | use tauri::State; 4 | 5 | #[tauri::command] 6 | pub fn read_all_settings(state: State<'_, DbConnection>) -> Result, String> { 7 | match settings::read_all(&state) { 8 | Ok(settings) => Ok(settings), 9 | Err(err) => Err(err.to_string()), 10 | } 11 | } 12 | 13 | #[tauri::command] 14 | pub fn read_setting(state: State<'_, DbConnection>, key: SettingKey) -> Result { 15 | match settings::read(&state, &key) { 16 | Ok(setting) => Ok(setting), 17 | Err(err) => Err(err.to_string()), 18 | } 19 | } 20 | 21 | #[tauri::command] 22 | pub fn update_setting( 23 | state: State<'_, DbConnection>, 24 | arg: SettingToUpdate, 25 | ) -> Result { 26 | match settings::update(&state, &arg) { 27 | Ok(_) => Ok("Setting updated".to_string()), 28 | Err(err) => Err(err.to_string()), 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/api/settings.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "@tauri-apps/api/tauri"; 2 | 3 | export enum SettingKey { 4 | POLLING_FREQUENCY = "PollingFrequency", 5 | NOTIFICATION = "Notification", 6 | DB_SCHEME_VERSION = "DbSchemeVersion", 7 | THEME = "Theme", 8 | ITEMS_ORDER = "ItemsOrder", 9 | PROXY = "Proxy", 10 | FETCH_OLD_ITEMS = "FetchOldItems", 11 | UPSTREAM_URL = "UpstreamUrl", 12 | UPSTREAM_ACCESS = "UpstreamAccess", 13 | UPSTREAM_SECRET = "UpstreamSecret", 14 | } 15 | 16 | export interface Setting { 17 | key: SettingKey; 18 | value: string; 19 | } 20 | 21 | export type SettingToUpdate = Setting; 22 | 23 | export async function readAllSettings(): Promise { 24 | try { 25 | return invoke("read_all_settings"); 26 | } catch { 27 | // Do nothing 28 | } 29 | 30 | return []; 31 | } 32 | 33 | export async function readSetting(key: SettingKey): Promise { 34 | try { 35 | return invoke("read_setting", { key }); 36 | } catch { 37 | // Do nothing 38 | } 39 | 40 | return null; 41 | } 42 | 43 | export async function updateSetting(arg: SettingToUpdate) { 44 | try { 45 | await invoke("update_setting", { arg: { key: arg.key, value: arg.value } }); 46 | } catch { 47 | // Do nothing 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "beforeDevCommand": "pnpm dev", 4 | "beforeBuildCommand": "pnpm build", 5 | "devPath": "http://localhost:1420", 6 | "distDir": "../dist", 7 | "withGlobalTauri": false 8 | }, 9 | "package": { 10 | "productName": "Collie", 11 | "version": "0.2.0" 12 | }, 13 | "tauri": { 14 | "allowlist": { 15 | "all": false, 16 | "notification": { 17 | "all": true 18 | }, 19 | "shell": { 20 | "all": false, 21 | "open": true 22 | }, 23 | "dialog": { 24 | "ask": true, 25 | "confirm": true 26 | }, 27 | "path": { 28 | "all": true 29 | } 30 | }, 31 | "bundle": { 32 | "active": true, 33 | "targets": "all", 34 | "identifier": "com.collie.Collie", 35 | "icon": [ 36 | "icons/32x32.png", 37 | "icons/128x128.png", 38 | "icons/128x128@2x.png", 39 | "icons/icon.icns", 40 | "icons/icon.ico" 41 | ] 42 | }, 43 | "security": { 44 | "csp": null 45 | }, 46 | "windows": [ 47 | { 48 | "fullscreen": false, 49 | "resizable": true, 50 | "title": "Collie", 51 | "width": 850, 52 | "height": 600 53 | } 54 | ] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src-tauri/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | #[derive(thiserror::Error, Debug)] 4 | pub enum Error { 5 | #[error("invalid value `{0}`")] 6 | InvalidValue(String), 7 | 8 | #[error("invalid key `{0}` for `{1}`")] 9 | InvalidEnumKey(String, String), 10 | 11 | #[error("invalid feed link `{0}`")] 12 | InvalidFeedLink(String), 13 | 14 | #[error("forbidden")] 15 | Forbidden, 16 | 17 | #[error("failed to parse syndication feed")] 18 | SyndicationParsingFailure, 19 | 20 | #[error("failed to fetch feed: {0}")] 21 | FetchFeedFailure(String), 22 | 23 | #[error("failed to fetch feed items: {0}")] 24 | FetchFeedItemsFailure(String), 25 | 26 | #[error("empty string")] 27 | EmptyString, 28 | 29 | #[error("unknown")] 30 | Unknown, 31 | 32 | #[error(transparent)] 33 | RusqliteError { 34 | #[from] 35 | source: rusqlite::Error, 36 | }, 37 | 38 | #[error(transparent)] 39 | SeaQueryError { 40 | #[from] 41 | source: sea_query::error::Error, 42 | }, 43 | 44 | #[error(transparent)] 45 | ReqwestError { 46 | #[from] 47 | source: reqwest::Error, 48 | }, 49 | 50 | #[error(transparent)] 51 | IoError { 52 | #[from] 53 | source: io::Error, 54 | }, 55 | } 56 | 57 | pub type Result = std::result::Result; 58 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "collie" 3 | version = "0.2.0" 4 | description = "A minimal RSS reader just for you" 5 | authors = ["parksb "] 6 | license = "GPL-3.0" 7 | repository = "https://github.com/parksb/collie" 8 | edition = "2021" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [build-dependencies] 13 | tauri-build = { version = "1.4", features = [] } 14 | 15 | [dependencies] 16 | collie = { version = "0.2" } 17 | tokio = { version = "1.40", features = ["full"] } 18 | tauri = { version = "1.4", features = [ "path-all", "dialog-confirm", "dialog-ask", "notification-all", "shell-open"] } 19 | serde = { version = "1.0", features = ["derive"] } 20 | serde_json = "1.0" 21 | chrono = { version = "0.4", default-features = false, features = ["clock", "serde"] } 22 | rusqlite = { version = "0.31", features = ["bundled"] } 23 | sea-query = { version = "0.31", features = ["with-chrono"] } 24 | sea-query-rusqlite = { version = "0.6", features = ["with-chrono"] } 25 | thiserror = "1.0" 26 | regex = "1.9" 27 | reqwest = { version = "0.12.7", features = ["json"] } 28 | base64 = "0.22" 29 | 30 | [dev-dependencies] 31 | pretty_assertions = "1.4" 32 | 33 | [features] 34 | # this feature is used for production builds or when `devPath` points to the filesystem 35 | # DO NOT REMOVE!! 36 | custom-protocol = ["tauri/custom-protocol"] 37 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: 'release' 2 | on: 3 | push: 4 | branches: 5 | - release 6 | 7 | jobs: 8 | release: 9 | permissions: 10 | contents: write 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | platform: [macos-latest, ubuntu-22.04, windows-latest] 15 | 16 | runs-on: ${{ matrix.platform }} 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: setup node 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: 16 23 | - name: install pnpm 24 | uses: pnpm/action-setup@v2 25 | with: 26 | version: 8 27 | - name: install Rust stable 28 | uses: dtolnay/rust-toolchain@stable 29 | - name: install dependencies (ubuntu only) 30 | if: matrix.platform == 'ubuntu-22.04' 31 | run: | 32 | sudo apt-get update 33 | sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf 34 | - name: install frontend dependencies 35 | run: pnpm install 36 | - uses: tauri-apps/tauri-action@v0 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | with: 40 | tagName: v__VERSION__ 41 | releaseName: 'Collie v__VERSION__' 42 | releaseBody: 'See the assets to download this version and install.' 43 | releaseDraft: true 44 | prerelease: false 45 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | [data-theme="dark"] { 2 | color-scheme: dark; 3 | --color-fg: #f0f0f0; 4 | --color-bg: #252526; 5 | --color-bg-highlight: #454546; 6 | --color-link: #007acc; 7 | --color-selected: #8f8f00; 8 | --color-sep: #afafaf; 9 | } 10 | 11 | [data-theme="light"] { 12 | color-scheme: light; 13 | --color-fg: #000000; 14 | --color-bg: #ffffff; 15 | --color-bg-highlight: #f0f0f0; 16 | --color-link: #005ccc; 17 | --color-selected: #fff000; 18 | --color-sep: #9f9f9f; 19 | } 20 | 21 | [data-theme="dracula"] { 22 | color-scheme: light; 23 | --color-fg: #f8f8f2; 24 | --color-bg: #282a36; 25 | --color-bg-highlight: #44475a; 26 | --color-link: #8be9fd; 27 | --color-selected: #44475a; 28 | --color-sep: #6272a4; 29 | } 30 | 31 | :root { 32 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 33 | font-size: 16px; 34 | font-synthesis: none; 35 | text-rendering: optimizeLegibility; 36 | -webkit-font-smoothing: antialiased; 37 | -moz-osx-font-smoothing: grayscale; 38 | -webkit-text-size-adjust: 100%; 39 | height: 100%; 40 | overflow: hidden; 41 | padding: 1vh 0 1vh 1vw; 42 | color: var(--color-fg); 43 | background-color: var(--color-bg); 44 | } 45 | 46 | div { 47 | display: flex; 48 | flex-direction: column; 49 | } 50 | 51 | a { 52 | color: var(--color-link); 53 | cursor: pointer; 54 | text-decoration: none; 55 | } 56 | 57 | a:hover { 58 | text-decoration: underline; 59 | } 60 | 61 | input { 62 | background-color: var(--color-bg); 63 | color: var(--color-fg); 64 | } 65 | -------------------------------------------------------------------------------- /src/api/feeds.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "@tauri-apps/api/tauri"; 2 | 3 | export enum FeedStatus { 4 | SUBSCRIBED = "Subscribed", 5 | UNSUBSCRIBED = "Unsubscribed", 6 | } 7 | 8 | export interface Feed { 9 | id: number; 10 | title: string; 11 | link: string; 12 | status: FeedStatus; 13 | checked_at: string; 14 | fetch_old_items: boolean; 15 | } 16 | 17 | export interface FeedToCreate { 18 | title: string; 19 | link: string; 20 | fetch_old_items: boolean; 21 | } 22 | 23 | export interface FeedToUpdate { 24 | id: number; 25 | title?: string | null; 26 | link?: string | null; 27 | status?: FeedStatus | null; 28 | fetch_old_items?: boolean | null; 29 | } 30 | 31 | export async function createFeed(arg: FeedToCreate) { 32 | try { 33 | await invoke("create_feed", { arg }); 34 | } catch { 35 | // Do nothing 36 | } 37 | } 38 | 39 | export async function updateFeed(arg: FeedToUpdate) { 40 | try { 41 | await invoke("update_feed", { arg }); 42 | } catch { 43 | // Do nothing 44 | } 45 | } 46 | 47 | export async function readAllFeeds(): Promise { 48 | try { 49 | return invoke("read_all_feeds"); 50 | } catch { 51 | // Do nothing 52 | } 53 | 54 | return []; 55 | } 56 | 57 | export async function readFeed(id: number): Promise { 58 | try { 59 | return invoke("read_feed", { id }); 60 | } catch { 61 | // Do nothing 62 | } 63 | 64 | return null; 65 | } 66 | 67 | export async function deleteFeed(id: number) { 68 | try { 69 | await invoke("delete_feed", { id }); 70 | } catch { 71 | // Do nothing 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src-tauri/src/models/database.rs: -------------------------------------------------------------------------------- 1 | use rusqlite::Connection; 2 | use sea_query::{ColumnDef, Iden, Query, SqliteQueryBuilder, Table, TableStatement}; 3 | use sea_query_rusqlite::RusqliteBinder; 4 | 5 | use crate::error::Result; 6 | 7 | #[derive(Iden)] 8 | pub enum Settings { 9 | Table, 10 | Key, 11 | Value, 12 | } 13 | 14 | pub fn settings_table() -> Vec { 15 | let create_stmt = Table::create() 16 | .table(Settings::Table) 17 | .if_not_exists() 18 | .col( 19 | ColumnDef::new(Settings::Key) 20 | .text() 21 | .not_null() 22 | .primary_key(), 23 | ) 24 | .col(ColumnDef::new(Settings::Value).text().not_null()) 25 | .to_owned(); 26 | 27 | vec![TableStatement::Create(create_stmt)] 28 | } 29 | 30 | pub fn insert_default_settings(db: &Connection) -> Result<()> { 31 | let _ = insert_settings(db, "db_scheme_version", "1"); 32 | let _ = insert_settings(db, "polling_frequency", "300"); 33 | let _ = insert_settings(db, "notification", "1"); 34 | let _ = insert_settings(db, "theme", "system"); 35 | let _ = insert_settings(db, "items_order", "ReceivedDateDesc"); 36 | let _ = insert_settings(db, "proxy", ""); 37 | let _ = insert_settings(db, "fetch_old_items", "1"); 38 | let _ = insert_settings(db, "upstream_url", ""); 39 | 40 | Ok(()) 41 | } 42 | 43 | fn insert_settings(db: &Connection, key: &str, value: &str) -> Result { 44 | let (insert_settings_sql, insert_settings_values) = Query::insert() 45 | .into_table(Settings::Table) 46 | .columns([Settings::Key, Settings::Value]) 47 | .values([key.into(), value.into()])? 48 | .build_rusqlite(SqliteQueryBuilder); 49 | 50 | Ok(db.execute( 51 | insert_settings_sql.as_str(), 52 | &*insert_settings_values.as_params(), 53 | )?) 54 | } 55 | -------------------------------------------------------------------------------- /src-tauri/src/fetchers/feeds.rs: -------------------------------------------------------------------------------- 1 | use collie::model::feed::{Feed, FeedToCreate, FeedToUpdate}; 2 | 3 | use super::auth::AuthClient; 4 | 5 | pub async fn create(client: &AuthClient, arg: &FeedToCreate) -> Result { 6 | let response = client.post("/feeds", arg).await?; 7 | 8 | if response.status().is_success() { 9 | Ok("New feed added".to_string()) 10 | } else { 11 | Err(format!("Failed to create feed: {}", response.status())) 12 | } 13 | } 14 | 15 | pub async fn read_all(client: &AuthClient) -> Result, String> { 16 | let response = client.get("/feeds").await?; 17 | 18 | if response.status().is_success() { 19 | response.json().await.map_err(|e| e.to_string()) 20 | } else { 21 | Err(format!("Failed to fetch feeds: {}", response.status())) 22 | } 23 | } 24 | 25 | pub async fn read(client: &AuthClient, id: i32) -> Result, String> { 26 | let response = client.get(&format!("/feeds/{}", id)).await?; 27 | 28 | if response.status().is_success() { 29 | response.json().await.map_err(|e| e.to_string()) 30 | } else { 31 | Err(format!("Failed to fetch feed: {}", response.status())) 32 | } 33 | } 34 | 35 | pub async fn update(client: &AuthClient, arg: &FeedToUpdate) -> Result { 36 | let response = client.patch(&format!("/feeds/{}", arg.id), arg).await?; 37 | 38 | if response.status().is_success() { 39 | Ok("Feed updated".to_string()) 40 | } else { 41 | Err(format!("Failed to update feed: {}", response.status())) 42 | } 43 | } 44 | 45 | pub async fn delete(client: &AuthClient, id: i32) -> Result { 46 | let response = client.delete(&format!("/feeds/{}", id)).await?; 47 | 48 | if response.status().is_success() { 49 | Ok("Feed deleted".to_string()) 50 | } else { 51 | Err(format!("Failed to delete feed: {}", response.status())) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: 'build' 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - '**' 9 | 10 | # On any PR branch, cancel active workflows if new code is pushed to the same head_ref (i.e., the same PR). 11 | # Fallback to github.run_id for non-PRs 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | build: 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | platform: [macos-latest, ubuntu-22.04, windows-latest] 22 | 23 | runs-on: ${{ matrix.platform }} 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: setup node 27 | uses: actions/setup-node@v4 28 | with: 29 | node-version: 16 30 | - name: install pnpm 31 | uses: pnpm/action-setup@v2 32 | with: 33 | version: 8 34 | - name: install Rust stable 35 | uses: dtolnay/rust-toolchain@stable 36 | - name: install dependencies (ubuntu only) 37 | if: matrix.platform == 'ubuntu-22.04' 38 | run: | 39 | sudo apt-get update 40 | sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf 41 | - name: install frontend dependencies 42 | run: pnpm install 43 | - uses: tauri-apps/tauri-action@v0 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | with: 47 | projectPath: src-tauri 48 | 49 | fmt: 50 | runs-on: ubuntu-latest 51 | name: Run cargo fmt on stable 52 | steps: 53 | - uses: actions/checkout@v4 54 | with: 55 | submodules: true 56 | - name: Install Rust stable toolchain 57 | uses: dtolnay/rust-toolchain@stable 58 | with: 59 | components: rustfmt 60 | - name: cargo fmt --check 61 | run: cargo fmt --check 62 | working-directory: src-tauri 63 | -------------------------------------------------------------------------------- /src-tauri/src/fetchers/items.rs: -------------------------------------------------------------------------------- 1 | use collie::model::item::{Item, ItemReadOption, ItemToCreate, ItemToUpdate, ItemToUpdateAll}; 2 | 3 | use super::auth::AuthClient; 4 | 5 | pub async fn create(client: &AuthClient, arg: &ItemToCreate) -> Result { 6 | let response = client.post("/items", arg).await?; 7 | 8 | if response.status().is_success() { 9 | Ok("Item created".to_string()) 10 | } else { 11 | Err(format!("Failed to create item: {}", response.status())) 12 | } 13 | } 14 | 15 | pub async fn read_all(client: &AuthClient, opt: &ItemReadOption) -> Result, String> { 16 | let response = client.get_with_json("/items", opt).await?; 17 | 18 | if response.status().is_success() { 19 | response.json().await.map_err(|e| e.to_string()) 20 | } else { 21 | Err(format!("Failed to fetch items: {}", response.status())) 22 | } 23 | } 24 | 25 | pub async fn count_all(client: &AuthClient, opt: &ItemReadOption) -> Result { 26 | let response = client.get_with_json("/items/count", opt).await?; 27 | 28 | if response.status().is_success() { 29 | response.json().await.map_err(|e| e.to_string()) 30 | } else { 31 | Err(format!("Failed to count items: {}", response.status())) 32 | } 33 | } 34 | 35 | pub async fn update(client: &AuthClient, arg: &ItemToUpdate) -> Result { 36 | let response = client.patch(&format!("/items/{}", arg.id), arg).await?; 37 | 38 | if response.status().is_success() { 39 | Ok("Item updated".to_string()) 40 | } else { 41 | Err(format!("Failed to update item: {}", response.status())) 42 | } 43 | } 44 | 45 | pub async fn update_all(client: &AuthClient, arg: &ItemToUpdateAll) -> Result { 46 | let response = client.patch("/items", arg).await?; 47 | 48 | if response.status().is_success() { 49 | Ok("Items updated".to_string()) 50 | } else { 51 | Err(format!("Failed to update items: {}", response.status())) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { lazy, onMount } from "solid-js"; 2 | import { A, Route, Routes } from "@solidjs/router"; 3 | 4 | import "./App.css"; 5 | import { ItemType } from "./routes/models/items"; 6 | import * as api from "./api/settings"; 7 | 8 | const Items = lazy(() => import("./routes/Items")); 9 | const Feeds = lazy(() => import("./routes/Feeds")); 10 | const Settings = lazy(() => import("./routes/Settings")); 11 | 12 | function App() { 13 | const setTheme = (theme: string) => { 14 | document.querySelector("html")?.setAttribute("data-theme", theme); 15 | }; 16 | 17 | onMount(async () => { 18 | const res = await api.readSetting(api.SettingKey.THEME); 19 | const theme = res?.value ?? "system"; 20 | const systemDarkTheme = window.matchMedia("(prefers-color-scheme: dark)"); 21 | 22 | switch (theme) { 23 | case "system": 24 | if (systemDarkTheme.matches) { 25 | setTheme("dark"); 26 | } else { 27 | setTheme("light"); 28 | } 29 | systemDarkTheme.addEventListener("change", (e) => (e.matches ? setTheme("dark") : setTheme("light"))); 30 | break; 31 | default: 32 | setTheme(theme); 33 | } 34 | }); 35 | 36 | return ( 37 |
38 | 46 | 47 | } /> 48 | } /> 49 | } /> 50 | 51 | } /> 52 | 53 | 54 |
55 | ); 56 | } 57 | 58 | export default App; 59 | -------------------------------------------------------------------------------- /src/styles/Items.css: -------------------------------------------------------------------------------- 1 | div.items-page div.item-list { 2 | flex: 1; 3 | } 4 | 5 | div.items-page div.item-list div.controls-container { 6 | margin-bottom: 1rem; 7 | align-items: center; 8 | } 9 | 10 | div.items-page div.item-list > ul { 11 | margin: 0; 12 | padding: 0; 13 | list-style: none; 14 | } 15 | 16 | div.items-page div.item-list > ul > li { 17 | margin-bottom: 0.5rem; 18 | } 19 | 20 | div.items-page div.item-list > ul > li.selected { 21 | background-color: var(--color-selected); 22 | } 23 | 24 | div.items-page div.item-list > ul > li .hostname { 25 | font-weight: normal; 26 | } 27 | 28 | div.items-page div.item-list > ul > li > small { 29 | display: block; 30 | font-size: 0.8rem; 31 | margin-top: -3px; 32 | } 33 | 34 | div.items-page div.item-list { 35 | padding-right: 1rem; 36 | } 37 | 38 | div.items-page div.item-viewer-handle { 39 | cursor: col-resize; 40 | margin-bottom: 70px; 41 | padding: 3px; 42 | } 43 | 44 | div.items-page div.item-viewer-handle:hover { 45 | background-color: var(--color-bg-highlight); 46 | } 47 | 48 | div.items-page div.item-viewer-handle::before { 49 | content: ""; 50 | width: 1px; 51 | height: 100%; 52 | background-color: var(--color-sep); 53 | } 54 | 55 | div.items-page div.item-viewer { 56 | padding: 0 1.5rem; 57 | overflow-wrap: break-word; 58 | font-size: 1rem; 59 | } 60 | 61 | div.items-page div.item-viewer .heading { 62 | font-size: 1.5rem; 63 | } 64 | 65 | div.items-page div.item-viewer .heading > button { 66 | font-size: 1.2rem; 67 | margin-left: 5px; 68 | } 69 | 70 | div.items-page div.item-viewer h1 { 71 | font-size: 1.5rem; 72 | } 73 | 74 | div.items-page div.item-viewer img { 75 | width: 100%; 76 | } 77 | 78 | div.items-page div.item-viewer ul { 79 | padding: 0; 80 | margin-left: 1.2rem; 81 | } 82 | 83 | div.items-page div.item-viewer blockquote { 84 | margin: 0; 85 | padding-left: 1rem; 86 | border-left: 3px solid var(--color-sep); 87 | } 88 | 89 | div.items-page div.item-viewer * { 90 | margin-top: 0; 91 | margin-bottom: 0.5rem; 92 | } 93 | 94 | div.items-page div.item-viewer li { 95 | margin-bottom: 0.2rem; 96 | } 97 | -------------------------------------------------------------------------------- /src-tauri/src/commands/items.rs: -------------------------------------------------------------------------------- 1 | use collie::{ 2 | model::item::{Item, ItemReadOption, ItemToUpdate, ItemToUpdateAll}, 3 | repository::database::DbConnection, 4 | service::item, 5 | }; 6 | use tauri::State; 7 | 8 | use crate::fetchers; 9 | use crate::fetchers::auth::AuthClient; 10 | use crate::models::settings; 11 | 12 | fn create_auth_client(state: &DbConnection, url: String) -> Result { 13 | let (access, secret) = settings::upstream_credentials(state) 14 | .ok_or_else(|| "Upstream credentials not configured".to_string())?; 15 | Ok(AuthClient::new(url, access, secret)) 16 | } 17 | 18 | #[tauri::command] 19 | pub async fn read_all_items( 20 | state: State<'_, DbConnection>, 21 | opt: ItemReadOption, 22 | ) -> Result, String> { 23 | match settings::upstream_url(&state) { 24 | Some(url) => { 25 | let client = create_auth_client(&state, url)?; 26 | fetchers::items::read_all(&client, &opt).await 27 | } 28 | None => match item::read_all(&state, &opt) { 29 | Ok(items) => Ok(items), 30 | Err(err) => Err(err.to_string()), 31 | }, 32 | } 33 | } 34 | 35 | #[tauri::command] 36 | pub async fn count_all_items( 37 | state: State<'_, DbConnection>, 38 | opt: ItemReadOption, 39 | ) -> Result { 40 | match settings::upstream_url(&state) { 41 | Some(url) => { 42 | let client = create_auth_client(&state, url)?; 43 | fetchers::items::count_all(&client, &opt).await 44 | } 45 | None => match item::count_all(&state, &opt) { 46 | Ok(count) => Ok(count), 47 | Err(err) => Err(err.to_string()), 48 | }, 49 | } 50 | } 51 | 52 | #[tauri::command] 53 | pub async fn update_item( 54 | state: State<'_, DbConnection>, 55 | arg: ItemToUpdate, 56 | ) -> Result { 57 | match settings::upstream_url(&state) { 58 | Some(url) => { 59 | let client = create_auth_client(&state, url)?; 60 | fetchers::items::update(&client, &arg).await 61 | } 62 | None => match item::update(&state, &arg) { 63 | Ok(_) => Ok("Item updated".to_string()), 64 | Err(err) => Err(err.to_string()), 65 | }, 66 | } 67 | } 68 | 69 | #[tauri::command] 70 | pub async fn update_items( 71 | state: State<'_, DbConnection>, 72 | arg: ItemToUpdateAll, 73 | ) -> Result { 74 | match settings::upstream_url(&state) { 75 | Some(url) => { 76 | let client = create_auth_client(&state, url)?; 77 | fetchers::items::update_all(&client, &arg).await 78 | } 79 | None => match item::update_all(&state, &arg) { 80 | Ok(_) => Ok("Items updated".to_string()), 81 | Err(err) => Err(err.to_string()), 82 | }, 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | // Prevents additional console window on Windows in release, DO NOT REMOVE!! 2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 3 | 4 | use models::database::insert_default_settings; 5 | use std::{ 6 | fs, 7 | path::PathBuf, 8 | sync::{Arc, Mutex}, 9 | }; 10 | use tauri::Manager; 11 | 12 | pub mod models { 13 | pub mod database; 14 | pub mod settings; 15 | } 16 | 17 | pub mod commands { 18 | pub mod feeds; 19 | pub mod items; 20 | pub mod settings; 21 | } 22 | 23 | pub mod fetchers { 24 | pub mod auth; 25 | pub mod feeds; 26 | pub mod items; 27 | } 28 | 29 | pub mod error; 30 | pub mod worker; 31 | 32 | fn main() { 33 | let _ = tauri::Builder::default() 34 | .invoke_handler(tauri::generate_handler![ 35 | commands::feeds::create_feed, 36 | commands::feeds::read_all_feeds, 37 | commands::feeds::read_feed, 38 | commands::feeds::update_feed, 39 | commands::feeds::delete_feed, 40 | commands::items::read_all_items, 41 | commands::items::count_all_items, 42 | commands::items::update_item, 43 | commands::items::update_items, 44 | commands::settings::read_all_settings, 45 | commands::settings::read_setting, 46 | commands::settings::update_setting, 47 | ]) 48 | .setup(|app| { 49 | let app_data_dir = if cfg!(dev) { 50 | PathBuf::from("data") 51 | } else { 52 | app.handle().path_resolver().app_data_dir().unwrap() 53 | }; 54 | 55 | fs::create_dir_all(&app_data_dir).unwrap(); 56 | let db_file = &app_data_dir.join("collie.db"); 57 | let db = collie::repository::database::open_connection(db_file).unwrap(); 58 | let _ = collie::repository::database::Migration::new() 59 | .table(collie::repository::database::feeds_table()) 60 | .table(collie::repository::database::items_table()) 61 | .table(crate::models::database::settings_table()) 62 | .migrate(&db); 63 | let _ = insert_default_settings(&db); 64 | 65 | let conn = Arc::new(Mutex::new(db)); 66 | app.manage(conn.clone()); 67 | worker::start(conn.clone(), app); 68 | 69 | Ok(()) 70 | }) 71 | .on_window_event(|event| { 72 | if let tauri::WindowEvent::CloseRequested { api, .. } = event.event() { 73 | hide_window(&event); 74 | api.prevent_close(); 75 | } 76 | }) 77 | .run(tauri::generate_context!("tauri.conf.json")); 78 | } 79 | 80 | #[cfg(target_os = "macos")] 81 | fn hide_window(event: &tauri::GlobalWindowEvent) { 82 | let _ = event.window().app_handle().hide(); 83 | } 84 | 85 | #[cfg(not(target_os = "macos"))] 86 | fn hide_window(event: &tauri::GlobalWindowEvent) { 87 | event.window().hide().unwrap(); 88 | } 89 | -------------------------------------------------------------------------------- /src/api/items.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "@tauri-apps/api/tauri"; 2 | 3 | export enum ItemStatus { 4 | UNREAD = "Unread", 5 | READ = "Read", 6 | } 7 | 8 | export interface ItemFeed { 9 | id: number; 10 | title: string; 11 | link: string; 12 | } 13 | 14 | export interface Item { 15 | id: number; 16 | fingerprint: string; 17 | author?: string | null; 18 | title: string; 19 | description: string; 20 | link: string; 21 | status: ItemStatus; 22 | is_saved: boolean; 23 | published_at: string; 24 | feed: ItemFeed; 25 | } 26 | 27 | export interface ItemToCreate { 28 | author?: string | null; 29 | title: string; 30 | description: string; 31 | link: string; 32 | status: ItemStatus; 33 | pulished_at: string; 34 | feed: number; 35 | } 36 | 37 | export interface ItemToUpdate { 38 | id: number; 39 | status?: ItemStatus | null; 40 | is_saved?: boolean | null; 41 | } 42 | 43 | export interface ItemToUpdateAll { 44 | status?: ItemStatus | null; 45 | is_saved?: boolean | null; 46 | option: ItemReadOption; 47 | } 48 | 49 | export enum ItemOrder { 50 | RECEIVED_DATE_DESC = "ReceivedDateDesc", 51 | PUBLISHED_DATE_DESC = "PublishedDateDesc", 52 | UNREAD_FIRST = "UnreadFirst", 53 | } 54 | 55 | export function ItemOrderfrom(x: string) { 56 | switch (x) { 57 | case "ReceivedDateDesc": 58 | return ItemOrder.RECEIVED_DATE_DESC; 59 | case "PublishedDateDesc": 60 | return ItemOrder.PUBLISHED_DATE_DESC; 61 | case "UnreadFirst": 62 | return ItemOrder.UNREAD_FIRST; 63 | default: 64 | return ItemOrder.RECEIVED_DATE_DESC; 65 | } 66 | } 67 | 68 | export interface ItemReadOption { 69 | ids?: number[] | null; 70 | feed?: number | null; 71 | status?: ItemStatus | null; 72 | is_saved?: boolean | null; 73 | order_by?: ItemOrder | null; 74 | limit?: number | null; 75 | offset?: number | null; 76 | } 77 | 78 | export async function readItems(opt: ItemReadOption): Promise { 79 | try { 80 | return invoke("read_all_items", { opt: { ...opt } }); 81 | } catch { 82 | // Do nothing 83 | } 84 | 85 | return []; 86 | } 87 | 88 | export async function countItems(opt: ItemReadOption): Promise { 89 | try { 90 | return invoke("count_all_items", { opt: { ...opt } }); 91 | } catch { 92 | // Do nothing 93 | } 94 | 95 | return 0; 96 | } 97 | 98 | export async function save(id: number) { 99 | try { 100 | await invoke("update_item", { arg: { id, is_saved: true } }); 101 | } catch { 102 | // Do nothing 103 | } 104 | } 105 | 106 | export async function unsave(id: number) { 107 | try { 108 | await invoke("update_item", { arg: { id, is_saved: false } }); 109 | } catch { 110 | // Do nothing 111 | } 112 | } 113 | 114 | export async function markAs(ids: number[], status: ItemStatus) { 115 | try { 116 | if (ids.length === 1) { 117 | await invoke("update_item", { arg: { id: ids[0], status } }); 118 | } else if (ids.length > 1) { 119 | await invoke("update_items", { arg: { opt: { ids }, status } }); 120 | } 121 | } catch { 122 | // Do nothing 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src-tauri/src/commands/feeds.rs: -------------------------------------------------------------------------------- 1 | use collie::model::feed::{Feed, FeedToCreate, FeedToUpdate}; 2 | use collie::repository::database::DbConnection; 3 | use collie::service::feed; 4 | use tauri::State; 5 | 6 | use crate::fetchers; 7 | use crate::fetchers::auth::AuthClient; 8 | use crate::models::settings; 9 | 10 | fn create_auth_client(state: &DbConnection, url: String) -> Result { 11 | let (access, secret) = settings::upstream_credentials(state) 12 | .ok_or_else(|| "Upstream credentials not configured".to_string())?; 13 | Ok(AuthClient::new(url, access, secret)) 14 | } 15 | 16 | #[tauri::command] 17 | pub async fn create_feed( 18 | state: State<'_, DbConnection>, 19 | arg: FeedToCreate, 20 | ) -> Result { 21 | match settings::upstream_url(&state) { 22 | Some(url) => { 23 | let client = create_auth_client(&state, url)?; 24 | fetchers::feeds::create(&client, &arg).await 25 | } 26 | None => match feed::create(&state, &arg, None).await { 27 | Ok(_) => Ok("New feed added".to_string()), 28 | Err(err) => Err(err.to_string()), 29 | }, 30 | } 31 | } 32 | 33 | #[tauri::command] 34 | pub async fn read_all_feeds(state: State<'_, DbConnection>) -> Result, String> { 35 | match settings::upstream_url(&state) { 36 | Some(url) => { 37 | let client = create_auth_client(&state, url)?; 38 | fetchers::feeds::read_all(&client).await 39 | } 40 | None => match feed::read_all(&state) { 41 | Ok(feeds) => Ok(feeds), 42 | Err(err) => Err(err.to_string()), 43 | }, 44 | } 45 | } 46 | 47 | #[tauri::command] 48 | pub async fn read_feed(state: State<'_, DbConnection>, id: i32) -> Result, String> { 49 | match settings::upstream_url(&state) { 50 | Some(url) => { 51 | let client = create_auth_client(&state, url)?; 52 | fetchers::feeds::read(&client, id).await 53 | } 54 | None => match feed::read(&state, id) { 55 | Ok(feed) => Ok(feed), 56 | Err(err) => Err(err.to_string()), 57 | }, 58 | } 59 | } 60 | 61 | #[tauri::command] 62 | pub async fn update_feed( 63 | state: State<'_, DbConnection>, 64 | arg: FeedToUpdate, 65 | ) -> Result { 66 | match settings::upstream_url(&state) { 67 | Some(url) => { 68 | let client = create_auth_client(&state, url)?; 69 | fetchers::feeds::update(&client, &arg).await 70 | } 71 | None => match feed::update(&state, &arg) { 72 | Ok(_) => Ok("Feed updated".to_string()), 73 | Err(err) => Err(err.to_string()), 74 | }, 75 | } 76 | } 77 | 78 | #[tauri::command] 79 | pub async fn delete_feed(state: State<'_, DbConnection>, id: i32) -> Result { 80 | match settings::upstream_url(&state) { 81 | Some(url) => { 82 | let client = create_auth_client(&state, url)?; 83 | fetchers::feeds::delete(&client, id).await 84 | } 85 | None => match feed::delete(&state, id) { 86 | Ok(_) => Ok("Feed deleted".to_string()), 87 | Err(err) => Err(err.to_string()), 88 | }, 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Collie 2 | 3 | ![](https://user-images.githubusercontent.com/6410412/263000117-70fdfe41-18cf-472a-a038-59d39d8b6e20.png) 4 | 5 | Collie is a minimal RSS feed reader application. With Collie, you can: 6 | 7 | - subscribe to multiple RSS/Atom feeds to organize your own news feed. 8 | - receive a real-time notification when a new item is added to the subscribed feed. (By default, it is checked every 5 minutes.) 9 | - and save the items to read again or later. 10 | 11 | All you need is a local machine and the Internet. No virtual machine, no cloud infrastructures, no always-on database, and no account registration with privacy information required. 12 | 13 | Collie is heavily inspired by [Miniflux](https://miniflux.app/) and [RSS app for Slack](https://gdgkr.slack.com/apps/A0F81R7U7-rss). 14 | 15 | ## Installation 16 | 17 | Download the latest release for your system from [release page](https://github.com/parksb/collie/releases). Collie provides `.exe`/`.msi` files for Windows, `.app`/`.dmg` files for macOS, and `.deb`/`.AppImage` files for Linux. 18 | 19 | On macOS, you can also install Collie via Homebrew: 20 | 21 | ``` 22 | $ brew install parksb/x/collie 23 | ``` 24 | 25 | ## Screenshots 26 | 27 | ![](https://user-images.githubusercontent.com/6410412/262967600-4273a958-cb92-427f-9ddc-19446c1b9889.png) 28 | 29 | ![](https://user-images.githubusercontent.com/6410412/262967611-1edb6675-b56c-4f28-a505-8689d1d7ede6.png) 30 | 31 | ![](https://user-images.githubusercontent.com/6410412/262967608-063e2cfd-bc82-4aa4-a159-bacda268397d.png) 32 | 33 | ## Build 34 | 35 | If you want to build Collie from source, you should get code by forking and cloning the git repository or downloading a zip file. After placing the source in your local environment, go to the project directory, and install front-end dependencies using pnpm. (If pnpm is not installed, [install pnpm](https://pnpm.io/installation) first.) 36 | 37 | ``` 38 | $ pnpm install 39 | ``` 40 | 41 | Then, run the following command to build. 42 | 43 | ``` 44 | $ pnpm tauri build 45 | ``` 46 | 47 | This command builds and installs your own Collie based on the cloned source. To develop and modify the application, learn more about [Tauri](https://tauri.app/). 48 | 49 | The following diagram illustrates the overview of the architecture of Collie. 50 | 51 | ![](https://user-images.githubusercontent.com/6410412/292761571-d1afbdc7-b507-4ee9-8616-7673a5107614.svg) 52 | 53 | If you want to share the features that you developed yourself with others, please feel free to open an issue and send a pull request on this repository. All contributions are welcome :) 54 | 55 | ## Background 56 | 57 | I've been getting tech news from HackerNews, Lobsters, etc. on Twitter (It's X now, but I'll keep calling it Twitter anyway), but many of them have been terminated due to changes in Twitter's API policy. I went from place to place: Bluesky, Mastodon, Slack, and newsletter. However, I couldn't settle anywhere. The social media services such as Bluesky and Mastodon had too many unnecessary features as news feed. Slack RSS was good to get the news in real-time, but the notifications mixed with other workspaces overwhelmed me. The newsletters gave me a lot of high-quality information, but not in real-time. 58 | 59 | Then, I remembered Miniflux, the "minimalist and opinionated feed reader" that I had used past. This is the best option for my goal, but I had to pay for the hosted version or keep running docker machine on my local computer which did not have enough resources. Additionally, I didn't need a system that maintains multi-user sessions. Eventually, I had no choice but to create my own application, and that's why I made Collie, the minimal RSS reader just for me. 60 | 61 | ## License 62 | 63 | Collie is distributed under the terms of the [GNU General Public License v3.0](LICENSE). 64 | -------------------------------------------------------------------------------- /src-tauri/src/worker.rs: -------------------------------------------------------------------------------- 1 | use chrono::DateTime; 2 | use collie::model::item::{ItemStatus, ItemToCreate}; 3 | use collie::repository::database::DbConnection; 4 | use collie::worker::Worker; 5 | use regex::Regex; 6 | use tauri::api::notification::Notification; 7 | use tauri::App; 8 | use tauri::Manager; 9 | 10 | use crate::fetchers; 11 | use crate::fetchers::auth::AuthClient; 12 | use crate::models::settings; 13 | use crate::models::settings::{SettingKey, SettingToUpdate}; 14 | 15 | #[tokio::main] 16 | pub async fn start(conn: DbConnection, app: &App) { 17 | let app_handle = app.handle(); 18 | let app_id = app.config().tauri.bundle.identifier.clone(); 19 | let upstream_url = settings::upstream_url(&conn); 20 | let upstream_credentials = settings::upstream_credentials(&conn); 21 | 22 | let worker = if upstream_url.is_some() && upstream_credentials.is_some() { 23 | None 24 | } else { 25 | Some(Worker::new(conn.clone(), proxy(&conn))) 26 | }; 27 | 28 | let auth_client = 29 | if let (Some(url), Some((access, secret))) = (&upstream_url, upstream_credentials) { 30 | Some(AuthClient::new(url.clone(), access, secret)) 31 | } else { 32 | None 33 | }; 34 | 35 | tauri::async_runtime::spawn(async move { 36 | loop { 37 | let inserted = if let Some(worker) = &worker { 38 | worker.execute().await 39 | } else if let Some(client) = &auth_client { 40 | worker_using_upstream(&conn, client).await 41 | } else { 42 | Ok(vec![]) 43 | }; 44 | 45 | match inserted { 46 | Ok(inserted) => { 47 | if !inserted.is_empty() { 48 | if notification(&conn) { 49 | notify(&app_id, &inserted); 50 | } 51 | 52 | let _ = app_handle.emit_all("feed_updated", ()); 53 | } 54 | } 55 | Err(err) => { 56 | eprintln!("Error fetching new items: {}", err); 57 | } 58 | } 59 | 60 | tokio::time::sleep(std::time::Duration::from_secs(polling_frequency(&conn))).await; 61 | } 62 | }); 63 | } 64 | 65 | async fn worker_using_upstream( 66 | conn: &DbConnection, 67 | client: &AuthClient, 68 | ) -> Result, collie::error::Error> { 69 | let last_sync_time = settings::read(conn, &SettingKey::UpstreamLastSyncTime) 70 | .ok() 71 | .and_then(|s| DateTime::parse_from_rfc3339(&s.value).ok()); 72 | 73 | let opt = collie::model::item::ItemReadOption { 74 | ids: None, 75 | feed: None, 76 | status: Some(ItemStatus::Unread), 77 | is_saved: None, 78 | order_by: None, 79 | limit: None, 80 | offset: None, 81 | }; 82 | 83 | match fetchers::items::read_all(client, &opt).await { 84 | Ok(items) => { 85 | let new_items: Vec = items 86 | .iter() 87 | .filter(|x| last_sync_time.map(|t| x.published_at > t).unwrap_or(true)) 88 | .map(|x| ItemToCreate { 89 | author: x.author.clone(), 90 | title: x.title.clone(), 91 | description: x.description.clone(), 92 | link: x.link.clone(), 93 | status: x.status.clone(), 94 | published_at: x.published_at, 95 | feed: x.feed.id, 96 | }) 97 | .collect(); 98 | 99 | if let Some(latest) = items.iter().map(|x| x.published_at).max() { 100 | let _ = settings::update( 101 | conn, 102 | &SettingToUpdate { 103 | key: SettingKey::UpstreamLastSyncTime, 104 | value: latest.to_rfc3339(), 105 | }, 106 | ); 107 | } 108 | 109 | Ok(new_items) 110 | } 111 | Err(_) => Err(collie::error::Error::BadArgument), 112 | } 113 | } 114 | 115 | fn proxy(conn: &DbConnection) -> Option { 116 | match settings::read(conn, &SettingKey::Proxy) { 117 | Ok(x) => Some(x.value), 118 | Err(_) => None, 119 | } 120 | } 121 | 122 | fn polling_frequency(conn: &DbConnection) -> u64 { 123 | settings::read(conn, &SettingKey::PollingFrequency) 124 | .map(|x| x.value) 125 | .unwrap_or("300".to_string()) 126 | .parse() 127 | .unwrap_or(300) 128 | } 129 | 130 | fn notification(conn: &DbConnection) -> bool { 131 | settings::read(conn, &SettingKey::Notification) 132 | .map(|x| x.value) 133 | .unwrap_or("1".to_string()) 134 | .parse() 135 | .unwrap_or(true) 136 | } 137 | 138 | fn notify(app_id: &str, args: &[ItemToCreate]) { 139 | if args.len() <= 3 { 140 | let html_tag_regex = Regex::new(r"<.*?>").unwrap(); 141 | for arg in args { 142 | let _ = Notification::new(app_id) 143 | .title(&arg.title) 144 | .body(html_tag_regex.replace_all(&arg.description, "")) 145 | .show(); 146 | } 147 | } else { 148 | let _ = Notification::new(app_id) 149 | .title("New items arrived") 150 | .body(format!("There are {} items to read", args.len())) 151 | .show(); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src-tauri/src/fetchers/auth.rs: -------------------------------------------------------------------------------- 1 | use base64::prelude::*; 2 | use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION}; 3 | use reqwest::{Client, Response, StatusCode}; 4 | use std::sync::Mutex; 5 | 6 | static TOKEN_CACHE: Mutex> = Mutex::new(None); 7 | 8 | pub async fn get_token(url: &str, access: &str, secret: &str) -> Result { 9 | // Check cache first 10 | if let Ok(cache) = TOKEN_CACHE.lock() { 11 | if let Some(token) = cache.as_ref() { 12 | return Ok(token.clone()); 13 | } 14 | } 15 | 16 | fetch_new_token(url, access, secret).await 17 | } 18 | 19 | async fn fetch_new_token(url: &str, access: &str, secret: &str) -> Result { 20 | let credentials = format!("{}:{}", access, secret); 21 | let encoded = BASE64_STANDARD.encode(credentials.as_bytes()); 22 | 23 | let client = Client::new(); 24 | let response = client 25 | .get(format!("{}/auth", url)) 26 | .header(AUTHORIZATION, format!("Basic {}", encoded)) 27 | .send() 28 | .await 29 | .map_err(|e| e.to_string())?; 30 | 31 | if response.status().is_success() { 32 | let token: String = response.json().await.map_err(|e| e.to_string())?; 33 | 34 | // Cache the token 35 | if let Ok(mut cache) = TOKEN_CACHE.lock() { 36 | *cache = Some(token.clone()); 37 | } 38 | 39 | Ok(token) 40 | } else { 41 | Err(format!("Authentication failed: {}", response.status())) 42 | } 43 | } 44 | 45 | pub fn clear_token_cache() { 46 | if let Ok(mut cache) = TOKEN_CACHE.lock() { 47 | *cache = None; 48 | } 49 | } 50 | 51 | pub fn create_auth_headers(token: &str) -> HeaderMap { 52 | let mut headers = HeaderMap::new(); 53 | if let Ok(value) = HeaderValue::from_str(&format!("Bearer {}", token)) { 54 | headers.insert(AUTHORIZATION, value); 55 | } 56 | headers 57 | } 58 | 59 | /// Wrapper for authenticated requests with automatic token refresh on 401 60 | pub struct AuthClient { 61 | url: String, 62 | access: String, 63 | secret: String, 64 | client: Client, 65 | } 66 | 67 | impl AuthClient { 68 | pub fn new(url: String, access: String, secret: String) -> Self { 69 | Self { 70 | url, 71 | access, 72 | secret, 73 | client: Client::new(), 74 | } 75 | } 76 | 77 | async fn get_or_refresh_token(&self, force_refresh: bool) -> Result { 78 | if force_refresh { 79 | clear_token_cache(); 80 | } 81 | get_token(&self.url, &self.access, &self.secret).await 82 | } 83 | 84 | async fn request_with_retry(&self, make_request: F) -> Result 85 | where 86 | F: Fn(Client, HeaderMap) -> Fut, 87 | Fut: std::future::Future>, 88 | { 89 | // First attempt with cached or new token 90 | let token = self.get_or_refresh_token(false).await?; 91 | let headers = create_auth_headers(&token); 92 | let response = make_request(self.client.clone(), headers) 93 | .await 94 | .map_err(|e| e.to_string())?; 95 | 96 | // If unauthorized, refresh token and retry once 97 | if response.status() == StatusCode::UNAUTHORIZED { 98 | let new_token = self.get_or_refresh_token(true).await?; 99 | let new_headers = create_auth_headers(&new_token); 100 | make_request(self.client.clone(), new_headers) 101 | .await 102 | .map_err(|e| e.to_string()) 103 | } else { 104 | Ok(response) 105 | } 106 | } 107 | 108 | pub async fn get(&self, path: &str) -> Result { 109 | let url = format!("{}{}", self.url, path); 110 | self.request_with_retry(|client, headers| { 111 | let url = url.clone(); 112 | async move { client.get(&url).headers(headers).send().await } 113 | }) 114 | .await 115 | } 116 | 117 | pub async fn get_with_json( 118 | &self, 119 | path: &str, 120 | body: &T, 121 | ) -> Result { 122 | let url = format!("{}{}", self.url, path); 123 | self.request_with_retry(|client, headers| { 124 | let url = url.clone(); 125 | let body_clone = serde_json::to_value(body).unwrap(); 126 | async move { 127 | client 128 | .get(&url) 129 | .headers(headers) 130 | .json(&body_clone) 131 | .send() 132 | .await 133 | } 134 | }) 135 | .await 136 | } 137 | 138 | pub async fn post( 139 | &self, 140 | path: &str, 141 | body: &T, 142 | ) -> Result { 143 | let url = format!("{}{}", self.url, path); 144 | self.request_with_retry(|client, headers| { 145 | let url = url.clone(); 146 | let body_clone = serde_json::to_value(body).unwrap(); 147 | async move { 148 | client 149 | .post(&url) 150 | .headers(headers) 151 | .json(&body_clone) 152 | .send() 153 | .await 154 | } 155 | }) 156 | .await 157 | } 158 | 159 | pub async fn patch( 160 | &self, 161 | path: &str, 162 | body: &T, 163 | ) -> Result { 164 | let url = format!("{}{}", self.url, path); 165 | self.request_with_retry(|client, headers| { 166 | let url = url.clone(); 167 | let body_clone = serde_json::to_value(body).unwrap(); 168 | async move { 169 | client 170 | .patch(&url) 171 | .headers(headers) 172 | .json(&body_clone) 173 | .send() 174 | .await 175 | } 176 | }) 177 | .await 178 | } 179 | 180 | pub async fn delete(&self, path: &str) -> Result { 181 | let url = format!("{}{}", self.url, path); 182 | self.request_with_retry(|client, headers| { 183 | let url = url.clone(); 184 | async move { client.delete(&url).headers(headers).send().await } 185 | }) 186 | .await 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/routes/Feeds.tsx: -------------------------------------------------------------------------------- 1 | import { confirm } from "@tauri-apps/api/dialog"; 2 | import { A, useNavigate } from "@solidjs/router"; 3 | import { createSignal, For, Match, onMount, Switch } from "solid-js"; 4 | 5 | import dayjs from "dayjs"; 6 | import relativeTime from "dayjs/plugin/relativeTime"; 7 | import utc from "dayjs/plugin/utc"; 8 | import timezone from "dayjs/plugin/timezone"; 9 | dayjs.extend(relativeTime); 10 | dayjs.extend(utc); 11 | dayjs.extend(timezone); 12 | 13 | import "../styles/Feeds.css"; 14 | import * as feedApi from "../api/feeds"; 15 | import * as settingApi from "../api/settings"; 16 | 17 | function Feeds() { 18 | const navigate = useNavigate(); 19 | 20 | const [feeds, setFeeds] = createSignal([]); 21 | const [linkToCreate, setLinkToCreate] = createSignal(""); 22 | const [idToUpdate, setIdToUpdate] = createSignal(null); 23 | const [titleToUpdate, setTitleToUpdate] = createSignal(null); 24 | const [linkToUpdate, setLinkToUpdate] = createSignal(null); 25 | const [fetchOldItems, setFetchOldItems] = createSignal(true); 26 | 27 | const createFeed = async () => { 28 | await feedApi.createFeed({ title: "auto", link: linkToCreate(), fetch_old_items: fetchOldItems() }); 29 | setFeeds(await feedApi.readAllFeeds()); 30 | setLinkToCreate(""); 31 | }; 32 | 33 | const updateFeed = async (id: number) => { 34 | await feedApi.updateFeed({ id, title: titleToUpdate(), link: linkToUpdate() }); 35 | setFeeds(await feedApi.readAllFeeds()); 36 | setIdToUpdate(null); 37 | setTitleToUpdate(null); 38 | setLinkToUpdate(null); 39 | }; 40 | 41 | const toggleFeedStatus = async (feed: feedApi.Feed) => { 42 | switch (feed.status) { 43 | case feedApi.FeedStatus.SUBSCRIBED: 44 | await feedApi.updateFeed({ id: feed.id, status: feedApi.FeedStatus.UNSUBSCRIBED }); 45 | break; 46 | case feedApi.FeedStatus.UNSUBSCRIBED: 47 | await feedApi.updateFeed({ id: feed.id, status: feedApi.FeedStatus.SUBSCRIBED }); 48 | } 49 | 50 | setFeeds(await feedApi.readAllFeeds()); 51 | }; 52 | 53 | const deleteFeed = async (feed: feedApi.Feed) => { 54 | if (await confirm(`A feed "${feed.title}" and their all items will be deleted. Are you sure?`)) { 55 | await feedApi.deleteFeed(feed.id); 56 | setFeeds(await feedApi.readAllFeeds()); 57 | } 58 | }; 59 | 60 | const enableFetchOldItems = async (value: boolean) => { 61 | const real_value = value === true ? "1" : "0"; 62 | await settingApi.updateSetting({ key: settingApi.SettingKey.FETCH_OLD_ITEMS, value: real_value }); 63 | setFetchOldItems(value); 64 | }; 65 | 66 | onMount(async () => { 67 | const res = await settingApi.readSetting(settingApi.SettingKey.FETCH_OLD_ITEMS); 68 | const value = res?.value; 69 | setFetchOldItems(value === "1" ? true : false); 70 | 71 | setFeeds(await feedApi.readAllFeeds()); 72 | }); 73 | 74 | return ( 75 |
76 |
77 |

Feeds

78 |
{ 81 | e.preventDefault(); 82 | createFeed(); 83 | }} 84 | > 85 | setLinkToCreate(e.currentTarget.value)} /> 86 | 87 | 88 | enableFetchOldItems(e.currentTarget.checked)} 94 | /> 95 | 98 | 99 |
100 |
    101 | 102 | {(feed: feedApi.Feed) => ( 103 |
  • 104 |
    105 | 106 | 107 | 108 | 109 | {feed.title} 110 | 111 | 112 | 113 | 114 | setTitleToUpdate(e.currentTarget.value)} /> 115 | setLinkToUpdate(e.currentTarget.value)} /> 116 | 117 | 118 | 119 |
    120 | 121 | Last checked at 122 | {dayjs(feed.checked_at).fromNow()} 123 | | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | | 142 | 143 | 144 |
  • 145 | )} 146 |
    147 |
148 |
149 |
150 | ); 151 | } 152 | 153 | export default Feeds; 154 | -------------------------------------------------------------------------------- /src-tauri/src/models/settings.rs: -------------------------------------------------------------------------------- 1 | use collie::repository::database::DbConnection; 2 | use core::fmt; 3 | use rusqlite::Row; 4 | use sea_query::{Expr, OnConflict, Query, SqliteQueryBuilder}; 5 | use sea_query_rusqlite::RusqliteBinder; 6 | use serde::{Deserialize, Serialize}; 7 | use std::ops::Deref; 8 | use std::{ 9 | fmt::{Display, Formatter}, 10 | str::FromStr, 11 | }; 12 | 13 | use crate::error::{Error, Result}; 14 | 15 | use super::database::Settings; 16 | 17 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 18 | pub enum SettingKey { 19 | PollingFrequency, // seconds 20 | Notification, 21 | DbSchemeVersion, 22 | Theme, 23 | ItemsOrder, 24 | Proxy, 25 | FetchOldItems, 26 | UpstreamUrl, 27 | UpstreamAccess, 28 | UpstreamSecret, 29 | UpstreamLastSyncTime, 30 | } 31 | 32 | impl Display for SettingKey { 33 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 34 | match self { 35 | Self::PollingFrequency => write!(f, "polling_frequency"), 36 | Self::Notification => write!(f, "notification"), 37 | Self::DbSchemeVersion => write!(f, "db_scheme_version"), 38 | Self::Theme => write!(f, "theme"), 39 | Self::ItemsOrder => write!(f, "items_order"), 40 | Self::Proxy => write!(f, "proxy"), 41 | Self::FetchOldItems => write!(f, "fetch_old_items"), 42 | Self::UpstreamUrl => write!(f, "upstream_url"), 43 | Self::UpstreamAccess => write!(f, "upstream_access"), 44 | Self::UpstreamSecret => write!(f, "upstream_secret"), 45 | Self::UpstreamLastSyncTime => write!(f, "upstream_last_sync_time"), 46 | } 47 | } 48 | } 49 | 50 | impl FromStr for SettingKey { 51 | type Err = Error; 52 | 53 | fn from_str(x: &str) -> std::result::Result { 54 | match x { 55 | "polling_frequency" => Ok(Self::PollingFrequency), 56 | "notification" => Ok(Self::Notification), 57 | "db_scheme_version" => Ok(Self::DbSchemeVersion), 58 | "theme" => Ok(Self::Theme), 59 | "items_order" => Ok(Self::ItemsOrder), 60 | "proxy" => Ok(Self::Proxy), 61 | "fetch_old_items" => Ok(Self::FetchOldItems), 62 | "upstream_url" => Ok(Self::UpstreamUrl), 63 | "upstream_access" => Ok(Self::UpstreamAccess), 64 | "upstream_secret" => Ok(Self::UpstreamSecret), 65 | "upstream_last_sync_time" => Ok(Self::UpstreamLastSyncTime), 66 | _ => Err(Error::InvalidEnumKey( 67 | x.to_string(), 68 | "SettingKey".to_string(), 69 | )), 70 | } 71 | } 72 | } 73 | 74 | #[derive(Serialize, Debug)] 75 | pub struct Setting { 76 | pub key: SettingKey, 77 | pub value: String, 78 | } 79 | 80 | impl From<&Row<'_>> for Setting { 81 | fn from(row: &Row) -> Self { 82 | Self { 83 | key: SettingKey::from_str(&row.get_unwrap::<&str, String>("key")).unwrap(), 84 | value: row.get_unwrap("value"), 85 | } 86 | } 87 | } 88 | 89 | #[derive(Deserialize)] 90 | pub struct SettingToUpdate { 91 | pub key: SettingKey, 92 | pub value: String, 93 | } 94 | 95 | pub fn read_all(conn: &DbConnection) -> Result> { 96 | let (sql, values) = Query::select() 97 | .columns([Settings::Key, Settings::Value]) 98 | .from(Settings::Table) 99 | .build_rusqlite(SqliteQueryBuilder); 100 | 101 | let db = conn.lock().unwrap(); 102 | let mut stmt = db.prepare(sql.as_str())?; 103 | let rows = stmt.query_map(&*values.as_params(), |x| Ok(Setting::from(x)))?; 104 | 105 | Ok(rows 106 | .map(std::result::Result::unwrap) 107 | .collect::>()) 108 | } 109 | 110 | pub fn read(conn: &DbConnection, key: &SettingKey) -> Result { 111 | let (sql, values) = Query::select() 112 | .columns([Settings::Key, Settings::Value]) 113 | .from(Settings::Table) 114 | .and_where(Expr::col(Settings::Key).eq(key.to_string())) 115 | .limit(1) 116 | .build_rusqlite(SqliteQueryBuilder); 117 | 118 | let db = conn.lock().unwrap(); 119 | let mut stmt = db.prepare(sql.as_str())?; 120 | let mut rows = stmt.query(&*values.as_params())?; 121 | match rows.next()? { 122 | None => Err(Error::Unknown), 123 | row => Ok(row.map(Setting::from).unwrap()), 124 | } 125 | } 126 | 127 | pub fn update(conn: &DbConnection, arg: &SettingToUpdate) -> Result { 128 | match arg.key { 129 | SettingKey::PollingFrequency => { 130 | if arg.value.parse::().map(|x| x < 30).unwrap_or(false) { 131 | return Err(Error::Unknown); 132 | } 133 | } 134 | SettingKey::Notification | SettingKey::FetchOldItems => { 135 | if arg.value.parse::().unwrap_or(false) { 136 | return Err(Error::Unknown); 137 | } 138 | } 139 | SettingKey::Proxy => { 140 | if arg 141 | .value 142 | .parse::() 143 | .map(|x| reqwest::Proxy::http(x.deref())) 144 | .map(|_| false) 145 | .unwrap_or(true) 146 | { 147 | return Err(Error::Unknown); 148 | } 149 | } 150 | SettingKey::DbSchemeVersion => return Err(Error::Forbidden), 151 | _ => {} 152 | } 153 | 154 | let (sql, values) = Query::insert() 155 | .into_table(Settings::Table) 156 | .columns([Settings::Key, Settings::Value]) 157 | .values_panic([arg.key.to_string().into(), arg.value.clone().into()]) 158 | .on_conflict( 159 | OnConflict::column(Settings::Key) 160 | .update_column(Settings::Value) 161 | .to_owned(), 162 | ) 163 | .build_rusqlite(SqliteQueryBuilder); 164 | 165 | let db = conn.lock().unwrap(); 166 | Ok(db.execute(sql.as_str(), &*values.as_params())?) 167 | } 168 | 169 | pub fn upstream_url(conn: &DbConnection) -> Option { 170 | match read(conn, &SettingKey::UpstreamUrl) { 171 | Ok(x) if !x.value.is_empty() => Some(x.value), 172 | _ => None, 173 | } 174 | } 175 | 176 | pub fn upstream_credentials(conn: &DbConnection) -> Option<(String, String)> { 177 | let access = match read(conn, &SettingKey::UpstreamAccess) { 178 | Ok(x) if !x.value.is_empty() => x.value, 179 | _ => return None, 180 | }; 181 | let secret = match read(conn, &SettingKey::UpstreamSecret) { 182 | Ok(x) if !x.value.is_empty() => x.value, 183 | _ => return None, 184 | }; 185 | Some((access, secret)) 186 | } 187 | -------------------------------------------------------------------------------- /src/routes/Items.tsx: -------------------------------------------------------------------------------- 1 | import { listen } from "@tauri-apps/api/event"; 2 | import { A, useParams } from "@solidjs/router"; 3 | import { createSignal, For, Match, onMount, Show, Switch } from "solid-js"; 4 | import DOMPurify from "dompurify"; 5 | 6 | import dayjs from "dayjs"; 7 | import relativeTime from "dayjs/plugin/relativeTime"; 8 | import utc from "dayjs/plugin/utc"; 9 | import timezone from "dayjs/plugin/timezone"; 10 | dayjs.extend(relativeTime); 11 | dayjs.extend(utc); 12 | dayjs.extend(timezone); 13 | 14 | import "../styles/Items.css"; 15 | import * as api from "../api/items"; 16 | import * as feedApi from "../api/feeds"; 17 | import * as settingApi from "../api/settings"; 18 | import { ItemType } from "./models/items"; 19 | 20 | interface Props { 21 | type: ItemType; 22 | } 23 | 24 | function Items(props: Props) { 25 | const LIMIT = 50; 26 | const params = useParams(); 27 | 28 | const [offset, setOffset] = createSignal(0); 29 | const [opt, setOpt] = createSignal({}); 30 | const [feed, setFeed] = createSignal(null); 31 | const [items, setItems] = createSignal([]); 32 | const [selectedItem, setSelectedItem] = createSignal(null); 33 | const [count, setCount] = createSignal(0); 34 | const [viewerBasis, setViewerBasis] = createSignal(200); 35 | 36 | const loadItems = async () => { 37 | const [fetchedCount, fetchedItems] = await Promise.all([api.countItems(opt()), api.readItems(opt())]); 38 | 39 | setCount(fetchedCount); 40 | setItems(fetchedItems); 41 | }; 42 | 43 | const loadPage = async (newOffset: number) => { 44 | setOffset(newOffset); 45 | setOpt({ ...opt(), offset: offset() }); 46 | window.scroll(0, 0); 47 | await loadItems(); 48 | }; 49 | 50 | const toggleSave = async (item: api.Item) => { 51 | if (item.is_saved) { 52 | await api.unsave(item.id); 53 | } else { 54 | await api.save(item.id); 55 | } 56 | 57 | await loadItems(); 58 | }; 59 | 60 | const markAs = async (targets: api.Item[], status: api.ItemStatus) => { 61 | const ids = targets.filter((x) => x.status !== status).map((x) => x.id); 62 | if (ids.length) { 63 | await api.markAs(ids, status); 64 | if (props.type === ItemType.UNREAD) { 65 | setItems(items().map((x) => (ids.includes(x.id) ? { ...x, status: status } : x))); 66 | setCount(await api.countItems(opt())); 67 | } else { 68 | await loadItems(); 69 | } 70 | } 71 | }; 72 | 73 | const changeOrder = async (order: api.ItemOrder) => { 74 | setOpt({ ...opt(), order_by: order }); 75 | await Promise.all([settingApi.updateSetting({ key: settingApi.SettingKey.ITEMS_ORDER, value: order }), loadItems()]); 76 | }; 77 | 78 | const selectItem = (item: api.Item) => { 79 | setSelectedItem(item); 80 | markAs([item], api.ItemStatus.READ); 81 | 82 | // Resize the view of the panel based on the user's screen size instead of a hard coded value 83 | const resize = (e: MouseEvent) => { 84 | e.preventDefault(); 85 | const basis = document.documentElement.clientWidth - e.clientX - 60; 86 | 87 | // Note: Can only resize up to 75% of the screen for all screen size 88 | const checkMaxSize = Math.floor(document.documentElement.clientWidth * 0.75); 89 | 90 | // The global minimum size stays at 100px. 91 | // However, larger screen can now resize beyond the previously set upper limit 92 | if (basis >= 100 && basis < checkMaxSize) { 93 | setViewerBasis(basis); 94 | } 95 | }; 96 | 97 | document.querySelector(".item-viewer-handle")?.addEventListener("mousedown", () => { 98 | document.addEventListener("mousemove", resize, false); 99 | document.addEventListener( 100 | "mouseup", 101 | () => { 102 | document.removeEventListener("mousemove", resize, false); 103 | }, 104 | false, 105 | ); 106 | }); 107 | }; 108 | 109 | onMount(async () => { 110 | const res = await settingApi.readSetting(settingApi.SettingKey.ITEMS_ORDER); 111 | const order = res?.value ?? api.ItemOrder.RECEIVED_DATE_DESC; 112 | 113 | const initialOpt = { order_by: api.ItemOrderfrom(order), offset: 0, limit: LIMIT }; 114 | switch (props.type) { 115 | case ItemType.INBOX: 116 | setOpt(initialOpt); 117 | break; 118 | case ItemType.UNREAD: 119 | setOpt({ ...initialOpt, status: api.ItemStatus.UNREAD }); 120 | break; 121 | case ItemType.SAVED: 122 | setOpt({ ...initialOpt, is_saved: true }); 123 | break; 124 | case ItemType.FEED: 125 | setOpt({ ...initialOpt, feed: Number(params.id) }); 126 | } 127 | 128 | if (props.type === ItemType.FEED) { 129 | const [fetchedFeed] = await Promise.all([feedApi.readFeed(Number(params.id)), loadItems()]); 130 | 131 | setFeed(fetchedFeed); 132 | } else { 133 | await loadItems(); 134 | } 135 | }); 136 | 137 | listen("feed_updated", async () => loadItems()); 138 | 139 | return ( 140 |
141 |
142 | {`${props.type.valueOf()} (${count()})`}}> 143 | 144 |

145 | history.back()}>← 146 | {`${feed() ? feed()?.title : "Feed"} (${count()})`} 147 |

148 |
149 |
150 |
151 | 152 | 163 | x.status == api.ItemStatus.UNREAD)}> 164 | 165 | 166 |
167 | 203 | LIMIT}> 204 |
205 | 1}> 206 | 207 | 208 | 0}> 209 | 210 | 211 | 212 | 213 | 214 | x.status == api.ItemStatus.UNREAD)}> 215 | 216 | 217 |
218 |
219 |
220 | 221 |
222 |
223 |

224 | {selectedItem()?.title} 225 | 226 |

227 |
228 |
229 | 230 |
231 | ); 232 | } 233 | 234 | export default Items; 235 | -------------------------------------------------------------------------------- /src/routes/Settings.tsx: -------------------------------------------------------------------------------- 1 | import { getVersion } from "@tauri-apps/api/app"; 2 | import { appDataDir } from "@tauri-apps/api/path"; 3 | 4 | import { createSignal, For, Match, onMount, Show, Switch } from "solid-js"; 5 | 6 | import "../styles/Settings.css"; 7 | import * as api from "../api/settings"; 8 | 9 | function Settings() { 10 | const [version, setVersion] = createSignal(""); 11 | const [latestVersion, setLatestVersion] = createSignal(""); 12 | const [dataDir, setDataDir] = createSignal(""); 13 | 14 | const [settings, setSettings] = createSignal([]); 15 | const [newSettings, setNewSettings] = createSignal<{ [key in api.SettingKey]: string }>({ 16 | [api.SettingKey.POLLING_FREQUENCY]: "", 17 | [api.SettingKey.NOTIFICATION]: "", 18 | [api.SettingKey.DB_SCHEME_VERSION]: "", 19 | [api.SettingKey.THEME]: "", 20 | [api.SettingKey.ITEMS_ORDER]: "", 21 | [api.SettingKey.PROXY]: "", 22 | [api.SettingKey.FETCH_OLD_ITEMS]: "", 23 | [api.SettingKey.UPSTREAM_URL]: "", 24 | [api.SettingKey.UPSTREAM_ACCESS]: "", 25 | [api.SettingKey.UPSTREAM_SECRET]: "", 26 | }); 27 | 28 | const keyToText = (key: api.SettingKey) => { 29 | switch (key) { 30 | case api.SettingKey.POLLING_FREQUENCY: 31 | return "Polling frequency"; 32 | case api.SettingKey.NOTIFICATION: 33 | return "Notification"; 34 | case api.SettingKey.THEME: 35 | return "Theme"; 36 | case api.SettingKey.PROXY: 37 | return "Proxy"; 38 | case api.SettingKey.UPSTREAM_URL: 39 | return "Upstream URL"; 40 | default: 41 | return ""; 42 | } 43 | }; 44 | 45 | const load = async () => { 46 | setSettings(await api.readAllSettings()); 47 | }; 48 | 49 | const validate = (key: api.SettingKey, value: string) => { 50 | switch (key) { 51 | case api.SettingKey.POLLING_FREQUENCY: 52 | if (Number(value) < 30) return false; 53 | break; 54 | case api.SettingKey.NOTIFICATION: 55 | if (value !== "1" && value !== "0") return false; 56 | } 57 | 58 | return true; 59 | }; 60 | 61 | const update = async (key: api.SettingKey, value: string) => { 62 | switch (key) { 63 | case api.SettingKey.POLLING_FREQUENCY: 64 | if (!validate(key, value)) { 65 | setNewSettings({ ...newSettings(), [api.SettingKey.POLLING_FREQUENCY]: "30" }); 66 | return; 67 | } 68 | } 69 | 70 | await api.updateSetting({ key, value }); 71 | await load(); 72 | }; 73 | 74 | const SaveButton = (setting: api.Setting, afterUpdate: () => void = () => {}) => ( 75 | 76 | 84 | 85 | ); 86 | 87 | onMount(async () => { 88 | const fetchLatestVersion = async (): Promise => { 89 | const res = await fetch("https://api.github.com/repos/parksb/collie/releases/latest"); 90 | return (await res.json())["tag_name"]; 91 | }; 92 | 93 | const [fetchedVersion, fetchedLatestVersion, fetchedDataDir] = await Promise.all([ 94 | getVersion(), 95 | fetchLatestVersion(), 96 | appDataDir(), 97 | load(), 98 | ]); 99 | 100 | setVersion(fetchedVersion); 101 | setLatestVersion(fetchedLatestVersion); 102 | setDataDir(fetchedDataDir); 103 | 104 | const newSettingsPlaceholder = newSettings(); 105 | settings().forEach((setting: api.Setting) => { 106 | newSettingsPlaceholder[setting.key] = setting.value; 107 | }); 108 | setNewSettings({ ...newSettings, ...newSettingsPlaceholder }); 109 | }); 110 | 111 | return ( 112 |
113 |

Settings

114 |
    115 | 116 | {(setting) => ( 117 |
  • 118 | 119 | 120 | 121 | {keyToText(setting.key)}: Check all feeds every 122 | 123 | setNewSettings({ ...newSettings(), [setting.key]: e.currentTarget.value })} 128 | />{" "} 129 | seconds. 130 | {SaveButton(setting)} 131 | The seconds cannot be less than 30. A feed that update too quickly may overwhelm you. 132 | x.key == api.SettingKey.UPSTREAM_URL)?.value}> 133 | This option sets the polling frequency for the given upstream, not the feeds. 134 | 135 | 136 | 137 | 138 | {keyToText(setting.key)}: Do you want to be notified when new items are arrived? 139 | 140 | 151 | 162 | {SaveButton(setting)} 163 | 164 | 165 | 166 | {keyToText(setting.key)}:{" "} 167 | 168 | 182 | {SaveButton(setting, () => location.reload())} 183 | 184 | 185 | 186 | {keyToText(setting.key)}:{" "} 187 | 188 | 192 | setNewSettings({ 193 | ...newSettings(), 194 | [setting.key]: e.currentTarget.value, 195 | }) 196 | } 197 | /> 198 | {SaveButton(setting)} 199 | 200 | 201 | 202 | {keyToText(setting.key)}:{" "} 203 | 204 | 208 | setNewSettings({ 209 | ...newSettings(), 210 | [setting.key]: e.currentTarget.value, 211 | }) 212 | } 213 | /> 214 | {SaveButton(setting)} 215 | Enter the URL to get data(items, feeds, etc.) from the upstream. 216 | 217 | Locally stored data will not be deleted, so if you want to revert to using local data, clear the upstream URL. 218 | 219 | 220 |
      221 |
    • 222 | 223 | Access Key:{" "} 224 | 225 | 229 | setNewSettings({ 230 | ...newSettings(), 231 | [api.SettingKey.UPSTREAM_ACCESS]: e.currentTarget.value, 232 | }) 233 | } 234 | /> 235 | {(() => { 236 | const accessSetting = settings().find((s) => s.key === api.SettingKey.UPSTREAM_ACCESS); 237 | const currentValue = accessSetting?.value ?? ""; 238 | return SaveButton({ key: api.SettingKey.UPSTREAM_ACCESS, value: currentValue }); 239 | })()} 240 |
    • 241 |
    • 242 | 243 | Secret Key:{" "} 244 | 245 | 249 | setNewSettings({ 250 | ...newSettings(), 251 | [api.SettingKey.UPSTREAM_SECRET]: e.currentTarget.value, 252 | }) 253 | } 254 | /> 255 | {(() => { 256 | const secretSetting = settings().find((s) => s.key === api.SettingKey.UPSTREAM_SECRET); 257 | const currentValue = secretSetting?.value ?? ""; 258 | return SaveButton({ key: api.SettingKey.UPSTREAM_SECRET, value: currentValue }); 259 | })()} 260 |
    • 261 |
    262 |
    263 |
    264 |
    265 |
  • 266 | )} 267 |
    268 |
  • 269 | Current version: v{version()} 270 | 271 | Latest version:{" "} 272 | 273 | {latestVersion()} 274 | 275 | 276 |
  • 277 |
  • x.key == api.SettingKey.UPSTREAM_URL)?.value ? "disabled" : undefined}> 278 | Data directory: {dataDir()} 279 | x.key == api.SettingKey.UPSTREAM_URL)?.value}> 280 | Using upstream, so only settings are stored in the local database. 281 | 282 |
  • 283 |
284 |
285 | ); 286 | } 287 | 288 | export default Settings; 289 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | Collie 635 | Copyright (C) 2023 Simon Park 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Collie Copyright (C) 2023 Simon Park 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | '@solidjs/router': 12 | specifier: ^0.8.4 13 | version: 0.8.4(solid-js@1.9.5) 14 | '@tauri-apps/api': 15 | specifier: ^1.6.0 16 | version: 1.6.0 17 | dayjs: 18 | specifier: ^1.11.13 19 | version: 1.11.13 20 | dompurify: 21 | specifier: ^3.2.5 22 | version: 3.2.5 23 | solid-js: 24 | specifier: ^1.9.5 25 | version: 1.9.5 26 | devDependencies: 27 | '@biomejs/biome': 28 | specifier: ^2.0.0 29 | version: 2.3.5 30 | '@tauri-apps/cli': 31 | specifier: ^1.6.3 32 | version: 1.6.3 33 | '@types/dompurify': 34 | specifier: ^3.2.0 35 | version: 3.2.0 36 | eslint: 37 | specifier: ^9.15.0 38 | version: 9.39.1 39 | eslint-plugin-solid: 40 | specifier: ^0.14.5 41 | version: 0.14.5(eslint@9.39.1)(typescript@5.8.3) 42 | globals: 43 | specifier: ^15.12.0 44 | version: 15.15.0 45 | typescript: 46 | specifier: ^5.8.3 47 | version: 5.8.3 48 | typescript-eslint: 49 | specifier: ^8.15.0 50 | version: 8.46.4(eslint@9.39.1)(typescript@5.8.3) 51 | vite: 52 | specifier: ^4.5.13 53 | version: 4.5.13 54 | vite-plugin-solid: 55 | specifier: ^2.11.6 56 | version: 2.11.6(solid-js@1.9.5)(vite@4.5.13) 57 | 58 | packages: 59 | 60 | '@ampproject/remapping@2.3.0': 61 | resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} 62 | engines: {node: '>=6.0.0'} 63 | 64 | '@babel/code-frame@7.26.2': 65 | resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} 66 | engines: {node: '>=6.9.0'} 67 | 68 | '@babel/compat-data@7.26.8': 69 | resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} 70 | engines: {node: '>=6.9.0'} 71 | 72 | '@babel/core@7.26.10': 73 | resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==} 74 | engines: {node: '>=6.9.0'} 75 | 76 | '@babel/generator@7.27.0': 77 | resolution: {integrity: sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==} 78 | engines: {node: '>=6.9.0'} 79 | 80 | '@babel/helper-compilation-targets@7.27.0': 81 | resolution: {integrity: sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==} 82 | engines: {node: '>=6.9.0'} 83 | 84 | '@babel/helper-module-imports@7.18.6': 85 | resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} 86 | engines: {node: '>=6.9.0'} 87 | 88 | '@babel/helper-module-imports@7.25.9': 89 | resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} 90 | engines: {node: '>=6.9.0'} 91 | 92 | '@babel/helper-module-transforms@7.26.0': 93 | resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} 94 | engines: {node: '>=6.9.0'} 95 | peerDependencies: 96 | '@babel/core': ^7.0.0 97 | 98 | '@babel/helper-plugin-utils@7.26.5': 99 | resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} 100 | engines: {node: '>=6.9.0'} 101 | 102 | '@babel/helper-string-parser@7.25.9': 103 | resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} 104 | engines: {node: '>=6.9.0'} 105 | 106 | '@babel/helper-validator-identifier@7.25.9': 107 | resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} 108 | engines: {node: '>=6.9.0'} 109 | 110 | '@babel/helper-validator-option@7.25.9': 111 | resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} 112 | engines: {node: '>=6.9.0'} 113 | 114 | '@babel/helpers@7.27.0': 115 | resolution: {integrity: sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==} 116 | engines: {node: '>=6.9.0'} 117 | 118 | '@babel/parser@7.27.0': 119 | resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==} 120 | engines: {node: '>=6.0.0'} 121 | hasBin: true 122 | 123 | '@babel/plugin-syntax-jsx@7.25.9': 124 | resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} 125 | engines: {node: '>=6.9.0'} 126 | peerDependencies: 127 | '@babel/core': ^7.0.0-0 128 | 129 | '@babel/template@7.27.0': 130 | resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==} 131 | engines: {node: '>=6.9.0'} 132 | 133 | '@babel/traverse@7.27.0': 134 | resolution: {integrity: sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==} 135 | engines: {node: '>=6.9.0'} 136 | 137 | '@babel/types@7.27.0': 138 | resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} 139 | engines: {node: '>=6.9.0'} 140 | 141 | '@biomejs/biome@2.3.5': 142 | resolution: {integrity: sha512-HvLhNlIlBIbAV77VysRIBEwp55oM/QAjQEin74QQX9Xb259/XP/D5AGGnZMOyF1el4zcvlNYYR3AyTMUV3ILhg==} 143 | engines: {node: '>=14.21.3'} 144 | hasBin: true 145 | 146 | '@biomejs/cli-darwin-arm64@2.3.5': 147 | resolution: {integrity: sha512-fLdTur8cJU33HxHUUsii3GLx/TR0BsfQx8FkeqIiW33cGMtUD56fAtrh+2Fx1uhiCsVZlFh6iLKUU3pniZREQw==} 148 | engines: {node: '>=14.21.3'} 149 | cpu: [arm64] 150 | os: [darwin] 151 | 152 | '@biomejs/cli-darwin-x64@2.3.5': 153 | resolution: {integrity: sha512-qpT8XDqeUlzrOW8zb4k3tjhT7rmvVRumhi2657I2aGcY4B+Ft5fNwDdZGACzn8zj7/K1fdWjgwYE3i2mSZ+vOA==} 154 | engines: {node: '>=14.21.3'} 155 | cpu: [x64] 156 | os: [darwin] 157 | 158 | '@biomejs/cli-linux-arm64-musl@2.3.5': 159 | resolution: {integrity: sha512-eGUG7+hcLgGnMNl1KHVZUYxahYAhC462jF/wQolqu4qso2MSk32Q+QrpN7eN4jAHAg7FUMIo897muIhK4hXhqg==} 160 | engines: {node: '>=14.21.3'} 161 | cpu: [arm64] 162 | os: [linux] 163 | 164 | '@biomejs/cli-linux-arm64@2.3.5': 165 | resolution: {integrity: sha512-u/pybjTBPGBHB66ku4pK1gj+Dxgx7/+Z0jAriZISPX1ocTO8aHh8x8e7Kb1rB4Ms0nA/SzjtNOVJ4exVavQBCw==} 166 | engines: {node: '>=14.21.3'} 167 | cpu: [arm64] 168 | os: [linux] 169 | 170 | '@biomejs/cli-linux-x64-musl@2.3.5': 171 | resolution: {integrity: sha512-awVuycTPpVTH/+WDVnEEYSf6nbCBHf/4wB3lquwT7puhNg8R4XvonWNZzUsfHZrCkjkLhFH/vCZK5jHatD9FEg==} 172 | engines: {node: '>=14.21.3'} 173 | cpu: [x64] 174 | os: [linux] 175 | 176 | '@biomejs/cli-linux-x64@2.3.5': 177 | resolution: {integrity: sha512-XrIVi9YAW6ye0CGQ+yax0gLfx+BFOtKaNX74n+xHWla6Cl6huUmcKNO7HPx7BiKnJUzrxXY1qYlm7xMvi08X4g==} 178 | engines: {node: '>=14.21.3'} 179 | cpu: [x64] 180 | os: [linux] 181 | 182 | '@biomejs/cli-win32-arm64@2.3.5': 183 | resolution: {integrity: sha512-DlBiMlBZZ9eIq4H7RimDSGsYcOtfOIfZOaI5CqsWiSlbTfqbPVfWtCf92wNzx8GNMbu1s7/g3ZZESr6+GwM/SA==} 184 | engines: {node: '>=14.21.3'} 185 | cpu: [arm64] 186 | os: [win32] 187 | 188 | '@biomejs/cli-win32-x64@2.3.5': 189 | resolution: {integrity: sha512-nUmR8gb6yvrKhtRgzwo/gDimPwnO5a4sCydf8ZS2kHIJhEmSmk+STsusr1LHTuM//wXppBawvSQi2xFXJCdgKQ==} 190 | engines: {node: '>=14.21.3'} 191 | cpu: [x64] 192 | os: [win32] 193 | 194 | '@esbuild/android-arm64@0.18.20': 195 | resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} 196 | engines: {node: '>=12'} 197 | cpu: [arm64] 198 | os: [android] 199 | 200 | '@esbuild/android-arm@0.18.20': 201 | resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} 202 | engines: {node: '>=12'} 203 | cpu: [arm] 204 | os: [android] 205 | 206 | '@esbuild/android-x64@0.18.20': 207 | resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} 208 | engines: {node: '>=12'} 209 | cpu: [x64] 210 | os: [android] 211 | 212 | '@esbuild/darwin-arm64@0.18.20': 213 | resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} 214 | engines: {node: '>=12'} 215 | cpu: [arm64] 216 | os: [darwin] 217 | 218 | '@esbuild/darwin-x64@0.18.20': 219 | resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} 220 | engines: {node: '>=12'} 221 | cpu: [x64] 222 | os: [darwin] 223 | 224 | '@esbuild/freebsd-arm64@0.18.20': 225 | resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} 226 | engines: {node: '>=12'} 227 | cpu: [arm64] 228 | os: [freebsd] 229 | 230 | '@esbuild/freebsd-x64@0.18.20': 231 | resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} 232 | engines: {node: '>=12'} 233 | cpu: [x64] 234 | os: [freebsd] 235 | 236 | '@esbuild/linux-arm64@0.18.20': 237 | resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} 238 | engines: {node: '>=12'} 239 | cpu: [arm64] 240 | os: [linux] 241 | 242 | '@esbuild/linux-arm@0.18.20': 243 | resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} 244 | engines: {node: '>=12'} 245 | cpu: [arm] 246 | os: [linux] 247 | 248 | '@esbuild/linux-ia32@0.18.20': 249 | resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} 250 | engines: {node: '>=12'} 251 | cpu: [ia32] 252 | os: [linux] 253 | 254 | '@esbuild/linux-loong64@0.18.20': 255 | resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} 256 | engines: {node: '>=12'} 257 | cpu: [loong64] 258 | os: [linux] 259 | 260 | '@esbuild/linux-mips64el@0.18.20': 261 | resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} 262 | engines: {node: '>=12'} 263 | cpu: [mips64el] 264 | os: [linux] 265 | 266 | '@esbuild/linux-ppc64@0.18.20': 267 | resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} 268 | engines: {node: '>=12'} 269 | cpu: [ppc64] 270 | os: [linux] 271 | 272 | '@esbuild/linux-riscv64@0.18.20': 273 | resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} 274 | engines: {node: '>=12'} 275 | cpu: [riscv64] 276 | os: [linux] 277 | 278 | '@esbuild/linux-s390x@0.18.20': 279 | resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} 280 | engines: {node: '>=12'} 281 | cpu: [s390x] 282 | os: [linux] 283 | 284 | '@esbuild/linux-x64@0.18.20': 285 | resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} 286 | engines: {node: '>=12'} 287 | cpu: [x64] 288 | os: [linux] 289 | 290 | '@esbuild/netbsd-x64@0.18.20': 291 | resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} 292 | engines: {node: '>=12'} 293 | cpu: [x64] 294 | os: [netbsd] 295 | 296 | '@esbuild/openbsd-x64@0.18.20': 297 | resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} 298 | engines: {node: '>=12'} 299 | cpu: [x64] 300 | os: [openbsd] 301 | 302 | '@esbuild/sunos-x64@0.18.20': 303 | resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} 304 | engines: {node: '>=12'} 305 | cpu: [x64] 306 | os: [sunos] 307 | 308 | '@esbuild/win32-arm64@0.18.20': 309 | resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} 310 | engines: {node: '>=12'} 311 | cpu: [arm64] 312 | os: [win32] 313 | 314 | '@esbuild/win32-ia32@0.18.20': 315 | resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} 316 | engines: {node: '>=12'} 317 | cpu: [ia32] 318 | os: [win32] 319 | 320 | '@esbuild/win32-x64@0.18.20': 321 | resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} 322 | engines: {node: '>=12'} 323 | cpu: [x64] 324 | os: [win32] 325 | 326 | '@eslint-community/eslint-utils@4.9.0': 327 | resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} 328 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 329 | peerDependencies: 330 | eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 331 | 332 | '@eslint-community/regexpp@4.12.1': 333 | resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} 334 | engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 335 | 336 | '@eslint/config-array@0.21.1': 337 | resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} 338 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 339 | 340 | '@eslint/config-helpers@0.4.2': 341 | resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} 342 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 343 | 344 | '@eslint/core@0.17.0': 345 | resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} 346 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 347 | 348 | '@eslint/eslintrc@3.3.1': 349 | resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} 350 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 351 | 352 | '@eslint/js@9.39.1': 353 | resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} 354 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 355 | 356 | '@eslint/object-schema@2.1.7': 357 | resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} 358 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 359 | 360 | '@eslint/plugin-kit@0.4.1': 361 | resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} 362 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 363 | 364 | '@humanfs/core@0.19.1': 365 | resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} 366 | engines: {node: '>=18.18.0'} 367 | 368 | '@humanfs/node@0.16.7': 369 | resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} 370 | engines: {node: '>=18.18.0'} 371 | 372 | '@humanwhocodes/module-importer@1.0.1': 373 | resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} 374 | engines: {node: '>=12.22'} 375 | 376 | '@humanwhocodes/retry@0.4.3': 377 | resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} 378 | engines: {node: '>=18.18'} 379 | 380 | '@jridgewell/gen-mapping@0.3.8': 381 | resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} 382 | engines: {node: '>=6.0.0'} 383 | 384 | '@jridgewell/resolve-uri@3.1.2': 385 | resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 386 | engines: {node: '>=6.0.0'} 387 | 388 | '@jridgewell/set-array@1.2.1': 389 | resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} 390 | engines: {node: '>=6.0.0'} 391 | 392 | '@jridgewell/sourcemap-codec@1.5.0': 393 | resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 394 | 395 | '@jridgewell/trace-mapping@0.3.25': 396 | resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} 397 | 398 | '@nodelib/fs.scandir@2.1.5': 399 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 400 | engines: {node: '>= 8'} 401 | 402 | '@nodelib/fs.stat@2.0.5': 403 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 404 | engines: {node: '>= 8'} 405 | 406 | '@nodelib/fs.walk@1.2.8': 407 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 408 | engines: {node: '>= 8'} 409 | 410 | '@solidjs/router@0.8.4': 411 | resolution: {integrity: sha512-Gi/WVoVseGMKS1DBdT3pNAMgOzEOp6Q3dpgNd2mW9GUEnVocPmtyBjDvXwN6m7tjSGsqqfqJFXk7bm1hxabSRw==} 412 | peerDependencies: 413 | solid-js: ^1.5.3 414 | 415 | '@tauri-apps/api@1.6.0': 416 | resolution: {integrity: sha512-rqI++FWClU5I2UBp4HXFvl+sBWkdigBkxnpJDQUWttNyG7IZP4FwQGhTNL5EOw0vI8i6eSAJ5frLqO7n7jbJdg==} 417 | engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'} 418 | 419 | '@tauri-apps/cli-darwin-arm64@1.6.3': 420 | resolution: {integrity: sha512-fQN6IYSL8bG4NvkdKE4sAGF4dF/QqqQq4hOAU+t8ksOzHJr0hUlJYfncFeJYutr/MMkdF7hYKadSb0j5EE9r0A==} 421 | engines: {node: '>= 10'} 422 | cpu: [arm64] 423 | os: [darwin] 424 | 425 | '@tauri-apps/cli-darwin-x64@1.6.3': 426 | resolution: {integrity: sha512-1yTXZzLajKAYINJOJhZfmMhCzweHSgKQ3bEgJSn6t+1vFkOgY8Yx4oFgWcybrrWI5J1ZLZAl47+LPOY81dLcyA==} 427 | engines: {node: '>= 10'} 428 | cpu: [x64] 429 | os: [darwin] 430 | 431 | '@tauri-apps/cli-linux-arm-gnueabihf@1.6.3': 432 | resolution: {integrity: sha512-CjTEr9r9xgjcvos09AQw8QMRPuH152B1jvlZt4PfAsyJNPFigzuwed5/SF7XAd8bFikA7zArP4UT12RdBxrx7w==} 433 | engines: {node: '>= 10'} 434 | cpu: [arm] 435 | os: [linux] 436 | 437 | '@tauri-apps/cli-linux-arm64-gnu@1.6.3': 438 | resolution: {integrity: sha512-G9EUUS4M8M/Jz1UKZqvJmQQCKOzgTb8/0jZKvfBuGfh5AjFBu8LHvlFpwkKVm1l4951Xg4ulUp6P9Q7WRJ9XSA==} 439 | engines: {node: '>= 10'} 440 | cpu: [arm64] 441 | os: [linux] 442 | 443 | '@tauri-apps/cli-linux-arm64-musl@1.6.3': 444 | resolution: {integrity: sha512-MuBTHJyNpZRbPVG8IZBN8+Zs7aKqwD22tkWVBcL1yOGL4zNNTJlkfL+zs5qxRnHlUsn6YAlbW/5HKocfpxVwBw==} 445 | engines: {node: '>= 10'} 446 | cpu: [arm64] 447 | os: [linux] 448 | 449 | '@tauri-apps/cli-linux-x64-gnu@1.6.3': 450 | resolution: {integrity: sha512-Uvi7M+NK3tAjCZEY1WGel+dFlzJmqcvu3KND+nqa22762NFmOuBIZ4KJR/IQHfpEYqKFNUhJfCGnpUDfiC3Oxg==} 451 | engines: {node: '>= 10'} 452 | cpu: [x64] 453 | os: [linux] 454 | 455 | '@tauri-apps/cli-linux-x64-musl@1.6.3': 456 | resolution: {integrity: sha512-rc6B342C0ra8VezB/OJom9j/N+9oW4VRA4qMxS2f4bHY2B/z3J9NPOe6GOILeg4v/CV62ojkLsC3/K/CeF3fqQ==} 457 | engines: {node: '>= 10'} 458 | cpu: [x64] 459 | os: [linux] 460 | 461 | '@tauri-apps/cli-win32-arm64-msvc@1.6.3': 462 | resolution: {integrity: sha512-cSH2qOBYuYC4UVIFtrc1YsGfc5tfYrotoHrpTvRjUGu0VywvmyNk82+ZsHEnWZ2UHmu3l3lXIGRqSWveLln0xg==} 463 | engines: {node: '>= 10'} 464 | cpu: [arm64] 465 | os: [win32] 466 | 467 | '@tauri-apps/cli-win32-ia32-msvc@1.6.3': 468 | resolution: {integrity: sha512-T8V6SJQqE4PSWmYBl0ChQVmS6AR2hXFHURH2DwAhgSGSQ6uBXgwlYFcfIeQpBQA727K2Eq8X2hGfvmoySyHMRw==} 469 | engines: {node: '>= 10'} 470 | cpu: [ia32] 471 | os: [win32] 472 | 473 | '@tauri-apps/cli-win32-x64-msvc@1.6.3': 474 | resolution: {integrity: sha512-HUkWZ+lYHI/Gjkh2QjHD/OBDpqLVmvjZGpLK9losur1Eg974Jip6k+vsoTUxQBCBDfj30eDBct9E1FvXOspWeg==} 475 | engines: {node: '>= 10'} 476 | cpu: [x64] 477 | os: [win32] 478 | 479 | '@tauri-apps/cli@1.6.3': 480 | resolution: {integrity: sha512-q46umd6QLRKDd4Gg6WyZBGa2fWvk0pbeUA5vFomm4uOs1/17LIciHv2iQ4UD+2Yv5H7AO8YiE1t50V0POiEGEw==} 481 | engines: {node: '>= 10'} 482 | hasBin: true 483 | 484 | '@types/babel__core@7.20.5': 485 | resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} 486 | 487 | '@types/babel__generator@7.27.0': 488 | resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} 489 | 490 | '@types/babel__template@7.4.4': 491 | resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} 492 | 493 | '@types/babel__traverse@7.20.7': 494 | resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} 495 | 496 | '@types/dompurify@3.2.0': 497 | resolution: {integrity: sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg==} 498 | deprecated: This is a stub types definition. dompurify provides its own type definitions, so you do not need this installed. 499 | 500 | '@types/estree@1.0.8': 501 | resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} 502 | 503 | '@types/json-schema@7.0.15': 504 | resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} 505 | 506 | '@types/trusted-types@2.0.7': 507 | resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} 508 | 509 | '@typescript-eslint/eslint-plugin@8.46.4': 510 | resolution: {integrity: sha512-R48VhmTJqplNyDxCyqqVkFSZIx1qX6PzwqgcXn1olLrzxcSBDlOsbtcnQuQhNtnNiJ4Xe5gREI1foajYaYU2Vg==} 511 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 512 | peerDependencies: 513 | '@typescript-eslint/parser': ^8.46.4 514 | eslint: ^8.57.0 || ^9.0.0 515 | typescript: '>=4.8.4 <6.0.0' 516 | 517 | '@typescript-eslint/parser@8.46.4': 518 | resolution: {integrity: sha512-tK3GPFWbirvNgsNKto+UmB/cRtn6TZfyw0D6IKrW55n6Vbs7KJoZtI//kpTKzE/DUmmnAFD8/Ca46s7Obs92/w==} 519 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 520 | peerDependencies: 521 | eslint: ^8.57.0 || ^9.0.0 522 | typescript: '>=4.8.4 <6.0.0' 523 | 524 | '@typescript-eslint/project-service@8.46.4': 525 | resolution: {integrity: sha512-nPiRSKuvtTN+no/2N1kt2tUh/HoFzeEgOm9fQ6XQk4/ApGqjx0zFIIaLJ6wooR1HIoozvj2j6vTi/1fgAz7UYQ==} 526 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 527 | peerDependencies: 528 | typescript: '>=4.8.4 <6.0.0' 529 | 530 | '@typescript-eslint/scope-manager@8.46.4': 531 | resolution: {integrity: sha512-tMDbLGXb1wC+McN1M6QeDx7P7c0UWO5z9CXqp7J8E+xGcJuUuevWKxuG8j41FoweS3+L41SkyKKkia16jpX7CA==} 532 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 533 | 534 | '@typescript-eslint/tsconfig-utils@8.46.4': 535 | resolution: {integrity: sha512-+/XqaZPIAk6Cjg7NWgSGe27X4zMGqrFqZ8atJsX3CWxH/jACqWnrWI68h7nHQld0y+k9eTTjb9r+KU4twLoo9A==} 536 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 537 | peerDependencies: 538 | typescript: '>=4.8.4 <6.0.0' 539 | 540 | '@typescript-eslint/type-utils@8.46.4': 541 | resolution: {integrity: sha512-V4QC8h3fdT5Wro6vANk6eojqfbv5bpwHuMsBcJUJkqs2z5XnYhJzyz9Y02eUmF9u3PgXEUiOt4w4KHR3P+z0PQ==} 542 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 543 | peerDependencies: 544 | eslint: ^8.57.0 || ^9.0.0 545 | typescript: '>=4.8.4 <6.0.0' 546 | 547 | '@typescript-eslint/types@8.46.4': 548 | resolution: {integrity: sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w==} 549 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 550 | 551 | '@typescript-eslint/typescript-estree@8.46.4': 552 | resolution: {integrity: sha512-7oV2qEOr1d4NWNmpXLR35LvCfOkTNymY9oyW+lUHkmCno7aOmIf/hMaydnJBUTBMRCOGZh8YjkFOc8dadEoNGA==} 553 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 554 | peerDependencies: 555 | typescript: '>=4.8.4 <6.0.0' 556 | 557 | '@typescript-eslint/utils@8.46.4': 558 | resolution: {integrity: sha512-AbSv11fklGXV6T28dp2Me04Uw90R2iJ30g2bgLz529Koehrmkbs1r7paFqr1vPCZi7hHwYxYtxfyQMRC8QaVSg==} 559 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 560 | peerDependencies: 561 | eslint: ^8.57.0 || ^9.0.0 562 | typescript: '>=4.8.4 <6.0.0' 563 | 564 | '@typescript-eslint/visitor-keys@8.46.4': 565 | resolution: {integrity: sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw==} 566 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 567 | 568 | acorn-jsx@5.3.2: 569 | resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} 570 | peerDependencies: 571 | acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 572 | 573 | acorn@8.15.0: 574 | resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} 575 | engines: {node: '>=0.4.0'} 576 | hasBin: true 577 | 578 | ajv@6.12.6: 579 | resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 580 | 581 | ansi-styles@4.3.0: 582 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 583 | engines: {node: '>=8'} 584 | 585 | argparse@2.0.1: 586 | resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 587 | 588 | babel-plugin-jsx-dom-expressions@0.39.8: 589 | resolution: {integrity: sha512-/MVOIIjonylDXnrWmG23ZX82m9mtKATsVHB7zYlPfDR9Vdd/NBE48if+wv27bSkBtyO7EPMUlcUc4J63QwuACQ==} 590 | peerDependencies: 591 | '@babel/core': ^7.20.12 592 | 593 | babel-preset-solid@1.9.5: 594 | resolution: {integrity: sha512-85I3osODJ1LvZbv8wFozROV1vXq32BubqHXAGu73A//TRs3NLI1OFP83AQBUTSQHwgZQmARjHlJciym3we+V+w==} 595 | peerDependencies: 596 | '@babel/core': ^7.0.0 597 | 598 | balanced-match@1.0.2: 599 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 600 | 601 | brace-expansion@1.1.11: 602 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 603 | 604 | brace-expansion@2.0.1: 605 | resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 606 | 607 | braces@3.0.3: 608 | resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 609 | engines: {node: '>=8'} 610 | 611 | browserslist@4.24.4: 612 | resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} 613 | engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} 614 | hasBin: true 615 | 616 | callsites@3.1.0: 617 | resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 618 | engines: {node: '>=6'} 619 | 620 | caniuse-lite@1.0.30001715: 621 | resolution: {integrity: sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==} 622 | 623 | chalk@4.1.2: 624 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 625 | engines: {node: '>=10'} 626 | 627 | color-convert@2.0.1: 628 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 629 | engines: {node: '>=7.0.0'} 630 | 631 | color-name@1.1.4: 632 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 633 | 634 | concat-map@0.0.1: 635 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 636 | 637 | convert-source-map@2.0.0: 638 | resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} 639 | 640 | cross-spawn@7.0.6: 641 | resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 642 | engines: {node: '>= 8'} 643 | 644 | csstype@3.1.3: 645 | resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} 646 | 647 | dayjs@1.11.13: 648 | resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} 649 | 650 | debug@4.4.0: 651 | resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} 652 | engines: {node: '>=6.0'} 653 | peerDependencies: 654 | supports-color: '*' 655 | peerDependenciesMeta: 656 | supports-color: 657 | optional: true 658 | 659 | deep-is@0.1.4: 660 | resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 661 | 662 | dompurify@3.2.5: 663 | resolution: {integrity: sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==} 664 | 665 | electron-to-chromium@1.5.144: 666 | resolution: {integrity: sha512-eJIaMRKeAzxfBSxtjYnoIAw/tdD6VIH6tHBZepZnAbE3Gyqqs5mGN87DvcldPUbVkIljTK8pY0CMcUljP64lfQ==} 667 | 668 | entities@6.0.0: 669 | resolution: {integrity: sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==} 670 | engines: {node: '>=0.12'} 671 | 672 | esbuild@0.18.20: 673 | resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} 674 | engines: {node: '>=12'} 675 | hasBin: true 676 | 677 | escalade@3.2.0: 678 | resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} 679 | engines: {node: '>=6'} 680 | 681 | escape-string-regexp@4.0.0: 682 | resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 683 | engines: {node: '>=10'} 684 | 685 | eslint-plugin-solid@0.14.5: 686 | resolution: {integrity: sha512-nfuYK09ah5aJG/oEN6P1qziy1zLgW4PDWe75VNPi4CEFYk1x2AEqwFeQfEPR7gNn0F2jOeqKhx2E+5oNCOBYWQ==} 687 | engines: {node: '>=18.0.0'} 688 | peerDependencies: 689 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 690 | typescript: '>=4.8.4' 691 | 692 | eslint-scope@8.4.0: 693 | resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} 694 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 695 | 696 | eslint-visitor-keys@3.4.3: 697 | resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} 698 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 699 | 700 | eslint-visitor-keys@4.2.1: 701 | resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} 702 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 703 | 704 | eslint@9.39.1: 705 | resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} 706 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 707 | hasBin: true 708 | peerDependencies: 709 | jiti: '*' 710 | peerDependenciesMeta: 711 | jiti: 712 | optional: true 713 | 714 | espree@10.4.0: 715 | resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} 716 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 717 | 718 | esquery@1.6.0: 719 | resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} 720 | engines: {node: '>=0.10'} 721 | 722 | esrecurse@4.3.0: 723 | resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} 724 | engines: {node: '>=4.0'} 725 | 726 | estraverse@5.3.0: 727 | resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} 728 | engines: {node: '>=4.0'} 729 | 730 | esutils@2.0.3: 731 | resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} 732 | engines: {node: '>=0.10.0'} 733 | 734 | fast-deep-equal@3.1.3: 735 | resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 736 | 737 | fast-glob@3.3.3: 738 | resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} 739 | engines: {node: '>=8.6.0'} 740 | 741 | fast-json-stable-stringify@2.1.0: 742 | resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 743 | 744 | fast-levenshtein@2.0.6: 745 | resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 746 | 747 | fastq@1.19.1: 748 | resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} 749 | 750 | file-entry-cache@8.0.0: 751 | resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} 752 | engines: {node: '>=16.0.0'} 753 | 754 | fill-range@7.1.1: 755 | resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 756 | engines: {node: '>=8'} 757 | 758 | find-up@5.0.0: 759 | resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 760 | engines: {node: '>=10'} 761 | 762 | flat-cache@4.0.1: 763 | resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} 764 | engines: {node: '>=16'} 765 | 766 | flatted@3.3.3: 767 | resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} 768 | 769 | fsevents@2.3.3: 770 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 771 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 772 | os: [darwin] 773 | 774 | gensync@1.0.0-beta.2: 775 | resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} 776 | engines: {node: '>=6.9.0'} 777 | 778 | glob-parent@5.1.2: 779 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 780 | engines: {node: '>= 6'} 781 | 782 | glob-parent@6.0.2: 783 | resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 784 | engines: {node: '>=10.13.0'} 785 | 786 | globals@11.12.0: 787 | resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} 788 | engines: {node: '>=4'} 789 | 790 | globals@14.0.0: 791 | resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} 792 | engines: {node: '>=18'} 793 | 794 | globals@15.15.0: 795 | resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} 796 | engines: {node: '>=18'} 797 | 798 | graphemer@1.4.0: 799 | resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} 800 | 801 | has-flag@4.0.0: 802 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 803 | engines: {node: '>=8'} 804 | 805 | html-entities@2.3.3: 806 | resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==} 807 | 808 | html-tags@3.3.1: 809 | resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} 810 | engines: {node: '>=8'} 811 | 812 | ignore@5.3.2: 813 | resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} 814 | engines: {node: '>= 4'} 815 | 816 | ignore@7.0.5: 817 | resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} 818 | engines: {node: '>= 4'} 819 | 820 | import-fresh@3.3.1: 821 | resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} 822 | engines: {node: '>=6'} 823 | 824 | imurmurhash@0.1.4: 825 | resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 826 | engines: {node: '>=0.8.19'} 827 | 828 | inline-style-parser@0.2.6: 829 | resolution: {integrity: sha512-gtGXVaBdl5mAes3rPcMedEBm12ibjt1kDMFfheul1wUAOVEJW60voNdMVzVkfLN06O7ZaD/rxhfKgtlgtTbMjg==} 830 | 831 | is-extglob@2.1.1: 832 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 833 | engines: {node: '>=0.10.0'} 834 | 835 | is-glob@4.0.3: 836 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 837 | engines: {node: '>=0.10.0'} 838 | 839 | is-html@2.0.0: 840 | resolution: {integrity: sha512-S+OpgB5i7wzIue/YSE5hg0e5ZYfG3hhpNh9KGl6ayJ38p7ED6wxQLd1TV91xHpcTvw90KMJ9EwN3F/iNflHBVg==} 841 | engines: {node: '>=8'} 842 | 843 | is-number@7.0.0: 844 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 845 | engines: {node: '>=0.12.0'} 846 | 847 | is-what@4.1.16: 848 | resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} 849 | engines: {node: '>=12.13'} 850 | 851 | isexe@2.0.0: 852 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 853 | 854 | js-tokens@4.0.0: 855 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 856 | 857 | js-yaml@4.1.0: 858 | resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} 859 | hasBin: true 860 | 861 | jsesc@3.1.0: 862 | resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} 863 | engines: {node: '>=6'} 864 | hasBin: true 865 | 866 | json-buffer@3.0.1: 867 | resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} 868 | 869 | json-schema-traverse@0.4.1: 870 | resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} 871 | 872 | json-stable-stringify-without-jsonify@1.0.1: 873 | resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} 874 | 875 | json5@2.2.3: 876 | resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} 877 | engines: {node: '>=6'} 878 | hasBin: true 879 | 880 | kebab-case@1.0.2: 881 | resolution: {integrity: sha512-7n6wXq4gNgBELfDCpzKc+mRrZFs7D+wgfF5WRFLNAr4DA/qtr9Js8uOAVAfHhuLMfAcQ0pRKqbpjx+TcJVdE1Q==} 882 | 883 | keyv@4.5.4: 884 | resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} 885 | 886 | known-css-properties@0.30.0: 887 | resolution: {integrity: sha512-VSWXYUnsPu9+WYKkfmJyLKtIvaRJi1kXUqVmBACORXZQxT5oZDsoZ2vQP+bQFDnWtpI/4eq3MLoRMjI2fnLzTQ==} 888 | 889 | levn@0.4.1: 890 | resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} 891 | engines: {node: '>= 0.8.0'} 892 | 893 | locate-path@6.0.0: 894 | resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 895 | engines: {node: '>=10'} 896 | 897 | lodash.merge@4.6.2: 898 | resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 899 | 900 | lru-cache@5.1.1: 901 | resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} 902 | 903 | merge-anything@5.1.7: 904 | resolution: {integrity: sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==} 905 | engines: {node: '>=12.13'} 906 | 907 | merge2@1.4.1: 908 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 909 | engines: {node: '>= 8'} 910 | 911 | micromatch@4.0.8: 912 | resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} 913 | engines: {node: '>=8.6'} 914 | 915 | minimatch@3.1.2: 916 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 917 | 918 | minimatch@9.0.5: 919 | resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 920 | engines: {node: '>=16 || 14 >=14.17'} 921 | 922 | ms@2.1.3: 923 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 924 | 925 | nanoid@3.3.11: 926 | resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} 927 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 928 | hasBin: true 929 | 930 | natural-compare@1.4.0: 931 | resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 932 | 933 | node-releases@2.0.19: 934 | resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} 935 | 936 | optionator@0.9.4: 937 | resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} 938 | engines: {node: '>= 0.8.0'} 939 | 940 | p-limit@3.1.0: 941 | resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 942 | engines: {node: '>=10'} 943 | 944 | p-locate@5.0.0: 945 | resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 946 | engines: {node: '>=10'} 947 | 948 | parent-module@1.0.1: 949 | resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 950 | engines: {node: '>=6'} 951 | 952 | parse5@7.3.0: 953 | resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} 954 | 955 | path-exists@4.0.0: 956 | resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 957 | engines: {node: '>=8'} 958 | 959 | path-key@3.1.1: 960 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 961 | engines: {node: '>=8'} 962 | 963 | picocolors@1.1.1: 964 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 965 | 966 | picomatch@2.3.1: 967 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 968 | engines: {node: '>=8.6'} 969 | 970 | postcss@8.5.3: 971 | resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} 972 | engines: {node: ^10 || ^12 || >=14} 973 | 974 | prelude-ls@1.2.1: 975 | resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 976 | engines: {node: '>= 0.8.0'} 977 | 978 | punycode@2.3.1: 979 | resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 980 | engines: {node: '>=6'} 981 | 982 | queue-microtask@1.2.3: 983 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 984 | 985 | resolve-from@4.0.0: 986 | resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 987 | engines: {node: '>=4'} 988 | 989 | reusify@1.1.0: 990 | resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} 991 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 992 | 993 | rollup@3.29.5: 994 | resolution: {integrity: sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==} 995 | engines: {node: '>=14.18.0', npm: '>=8.0.0'} 996 | hasBin: true 997 | 998 | run-parallel@1.2.0: 999 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 1000 | 1001 | semver@6.3.1: 1002 | resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} 1003 | hasBin: true 1004 | 1005 | semver@7.7.1: 1006 | resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} 1007 | engines: {node: '>=10'} 1008 | hasBin: true 1009 | 1010 | seroval-plugins@1.2.1: 1011 | resolution: {integrity: sha512-H5vs53+39+x4Udwp4J5rNZfgFuA+Lt+uU+09w1gYBVWomtAl98B+E9w7yC05Xc81/HgLvJdlyqJbU0fJCKCmdw==} 1012 | engines: {node: '>=10'} 1013 | peerDependencies: 1014 | seroval: ^1.0 1015 | 1016 | seroval@1.2.1: 1017 | resolution: {integrity: sha512-yBxFFs3zmkvKNmR0pFSU//rIsYjuX418TnlDmc2weaq5XFDqDIV/NOMPBoLrbxjLH42p4UzRuXHryXh9dYcKcw==} 1018 | engines: {node: '>=10'} 1019 | 1020 | shebang-command@2.0.0: 1021 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 1022 | engines: {node: '>=8'} 1023 | 1024 | shebang-regex@3.0.0: 1025 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1026 | engines: {node: '>=8'} 1027 | 1028 | solid-js@1.9.5: 1029 | resolution: {integrity: sha512-ogI3DaFcyn6UhYhrgcyRAMbu/buBJitYQASZz5WzfQVPP10RD2AbCoRZ517psnezrasyCbWzIxZ6kVqet768xw==} 1030 | 1031 | solid-refresh@0.6.3: 1032 | resolution: {integrity: sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==} 1033 | peerDependencies: 1034 | solid-js: ^1.3 1035 | 1036 | source-map-js@1.2.1: 1037 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 1038 | engines: {node: '>=0.10.0'} 1039 | 1040 | strip-json-comments@3.1.1: 1041 | resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 1042 | engines: {node: '>=8'} 1043 | 1044 | style-to-object@1.0.13: 1045 | resolution: {integrity: sha512-/vyYeGGA3Z3Hno6J9dg8pf8+ooUzImdokbc6mV2PHRDBMw4x0j1ltxeujUYmpH66311kwxZjMPVFOi97IoIx1w==} 1046 | 1047 | supports-color@7.2.0: 1048 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 1049 | engines: {node: '>=8'} 1050 | 1051 | to-regex-range@5.0.1: 1052 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1053 | engines: {node: '>=8.0'} 1054 | 1055 | ts-api-utils@2.1.0: 1056 | resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} 1057 | engines: {node: '>=18.12'} 1058 | peerDependencies: 1059 | typescript: '>=4.8.4' 1060 | 1061 | type-check@0.4.0: 1062 | resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 1063 | engines: {node: '>= 0.8.0'} 1064 | 1065 | typescript-eslint@8.46.4: 1066 | resolution: {integrity: sha512-KALyxkpYV5Ix7UhvjTwJXZv76VWsHG+NjNlt/z+a17SOQSiOcBdUXdbJdyXi7RPxrBFECtFOiPwUJQusJuCqrg==} 1067 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1068 | peerDependencies: 1069 | eslint: ^8.57.0 || ^9.0.0 1070 | typescript: '>=4.8.4 <6.0.0' 1071 | 1072 | typescript@5.8.3: 1073 | resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} 1074 | engines: {node: '>=14.17'} 1075 | hasBin: true 1076 | 1077 | update-browserslist-db@1.1.3: 1078 | resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} 1079 | hasBin: true 1080 | peerDependencies: 1081 | browserslist: '>= 4.21.0' 1082 | 1083 | uri-js@4.4.1: 1084 | resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 1085 | 1086 | validate-html-nesting@1.2.2: 1087 | resolution: {integrity: sha512-hGdgQozCsQJMyfK5urgFcWEqsSSrK63Awe0t/IMR0bZ0QMtnuaiHzThW81guu3qx9abLi99NEuiaN6P9gVYsNg==} 1088 | 1089 | vite-plugin-solid@2.11.6: 1090 | resolution: {integrity: sha512-Sl5CTqJTGyEeOsmdH6BOgalIZlwH3t4/y0RQuFLMGnvWMBvxb4+lq7x3BSiAw6etf0QexfNJW7HSOO/Qf7pigg==} 1091 | peerDependencies: 1092 | '@testing-library/jest-dom': ^5.16.6 || ^5.17.0 || ^6.* 1093 | solid-js: ^1.7.2 1094 | vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 1095 | peerDependenciesMeta: 1096 | '@testing-library/jest-dom': 1097 | optional: true 1098 | 1099 | vite@4.5.13: 1100 | resolution: {integrity: sha512-Hgp8IF/yZDzKsN1hQWOuQZbrKiaFsbQud+07jJ8h9m9PaHWkpvZ5u55Xw5yYjWRXwRQ4jwFlJvY7T7FUJG9MCA==} 1101 | engines: {node: ^14.18.0 || >=16.0.0} 1102 | hasBin: true 1103 | peerDependencies: 1104 | '@types/node': '>= 14' 1105 | less: '*' 1106 | lightningcss: ^1.21.0 1107 | sass: '*' 1108 | stylus: '*' 1109 | sugarss: '*' 1110 | terser: ^5.4.0 1111 | peerDependenciesMeta: 1112 | '@types/node': 1113 | optional: true 1114 | less: 1115 | optional: true 1116 | lightningcss: 1117 | optional: true 1118 | sass: 1119 | optional: true 1120 | stylus: 1121 | optional: true 1122 | sugarss: 1123 | optional: true 1124 | terser: 1125 | optional: true 1126 | 1127 | vitefu@1.0.6: 1128 | resolution: {integrity: sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==} 1129 | peerDependencies: 1130 | vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 1131 | peerDependenciesMeta: 1132 | vite: 1133 | optional: true 1134 | 1135 | which@2.0.2: 1136 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1137 | engines: {node: '>= 8'} 1138 | hasBin: true 1139 | 1140 | word-wrap@1.2.5: 1141 | resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} 1142 | engines: {node: '>=0.10.0'} 1143 | 1144 | yallist@3.1.1: 1145 | resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} 1146 | 1147 | yocto-queue@0.1.0: 1148 | resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 1149 | engines: {node: '>=10'} 1150 | 1151 | snapshots: 1152 | 1153 | '@ampproject/remapping@2.3.0': 1154 | dependencies: 1155 | '@jridgewell/gen-mapping': 0.3.8 1156 | '@jridgewell/trace-mapping': 0.3.25 1157 | 1158 | '@babel/code-frame@7.26.2': 1159 | dependencies: 1160 | '@babel/helper-validator-identifier': 7.25.9 1161 | js-tokens: 4.0.0 1162 | picocolors: 1.1.1 1163 | 1164 | '@babel/compat-data@7.26.8': {} 1165 | 1166 | '@babel/core@7.26.10': 1167 | dependencies: 1168 | '@ampproject/remapping': 2.3.0 1169 | '@babel/code-frame': 7.26.2 1170 | '@babel/generator': 7.27.0 1171 | '@babel/helper-compilation-targets': 7.27.0 1172 | '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) 1173 | '@babel/helpers': 7.27.0 1174 | '@babel/parser': 7.27.0 1175 | '@babel/template': 7.27.0 1176 | '@babel/traverse': 7.27.0 1177 | '@babel/types': 7.27.0 1178 | convert-source-map: 2.0.0 1179 | debug: 4.4.0 1180 | gensync: 1.0.0-beta.2 1181 | json5: 2.2.3 1182 | semver: 6.3.1 1183 | transitivePeerDependencies: 1184 | - supports-color 1185 | 1186 | '@babel/generator@7.27.0': 1187 | dependencies: 1188 | '@babel/parser': 7.27.0 1189 | '@babel/types': 7.27.0 1190 | '@jridgewell/gen-mapping': 0.3.8 1191 | '@jridgewell/trace-mapping': 0.3.25 1192 | jsesc: 3.1.0 1193 | 1194 | '@babel/helper-compilation-targets@7.27.0': 1195 | dependencies: 1196 | '@babel/compat-data': 7.26.8 1197 | '@babel/helper-validator-option': 7.25.9 1198 | browserslist: 4.24.4 1199 | lru-cache: 5.1.1 1200 | semver: 6.3.1 1201 | 1202 | '@babel/helper-module-imports@7.18.6': 1203 | dependencies: 1204 | '@babel/types': 7.27.0 1205 | 1206 | '@babel/helper-module-imports@7.25.9': 1207 | dependencies: 1208 | '@babel/traverse': 7.27.0 1209 | '@babel/types': 7.27.0 1210 | transitivePeerDependencies: 1211 | - supports-color 1212 | 1213 | '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10)': 1214 | dependencies: 1215 | '@babel/core': 7.26.10 1216 | '@babel/helper-module-imports': 7.25.9 1217 | '@babel/helper-validator-identifier': 7.25.9 1218 | '@babel/traverse': 7.27.0 1219 | transitivePeerDependencies: 1220 | - supports-color 1221 | 1222 | '@babel/helper-plugin-utils@7.26.5': {} 1223 | 1224 | '@babel/helper-string-parser@7.25.9': {} 1225 | 1226 | '@babel/helper-validator-identifier@7.25.9': {} 1227 | 1228 | '@babel/helper-validator-option@7.25.9': {} 1229 | 1230 | '@babel/helpers@7.27.0': 1231 | dependencies: 1232 | '@babel/template': 7.27.0 1233 | '@babel/types': 7.27.0 1234 | 1235 | '@babel/parser@7.27.0': 1236 | dependencies: 1237 | '@babel/types': 7.27.0 1238 | 1239 | '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.10)': 1240 | dependencies: 1241 | '@babel/core': 7.26.10 1242 | '@babel/helper-plugin-utils': 7.26.5 1243 | 1244 | '@babel/template@7.27.0': 1245 | dependencies: 1246 | '@babel/code-frame': 7.26.2 1247 | '@babel/parser': 7.27.0 1248 | '@babel/types': 7.27.0 1249 | 1250 | '@babel/traverse@7.27.0': 1251 | dependencies: 1252 | '@babel/code-frame': 7.26.2 1253 | '@babel/generator': 7.27.0 1254 | '@babel/parser': 7.27.0 1255 | '@babel/template': 7.27.0 1256 | '@babel/types': 7.27.0 1257 | debug: 4.4.0 1258 | globals: 11.12.0 1259 | transitivePeerDependencies: 1260 | - supports-color 1261 | 1262 | '@babel/types@7.27.0': 1263 | dependencies: 1264 | '@babel/helper-string-parser': 7.25.9 1265 | '@babel/helper-validator-identifier': 7.25.9 1266 | 1267 | '@biomejs/biome@2.3.5': 1268 | optionalDependencies: 1269 | '@biomejs/cli-darwin-arm64': 2.3.5 1270 | '@biomejs/cli-darwin-x64': 2.3.5 1271 | '@biomejs/cli-linux-arm64': 2.3.5 1272 | '@biomejs/cli-linux-arm64-musl': 2.3.5 1273 | '@biomejs/cli-linux-x64': 2.3.5 1274 | '@biomejs/cli-linux-x64-musl': 2.3.5 1275 | '@biomejs/cli-win32-arm64': 2.3.5 1276 | '@biomejs/cli-win32-x64': 2.3.5 1277 | 1278 | '@biomejs/cli-darwin-arm64@2.3.5': 1279 | optional: true 1280 | 1281 | '@biomejs/cli-darwin-x64@2.3.5': 1282 | optional: true 1283 | 1284 | '@biomejs/cli-linux-arm64-musl@2.3.5': 1285 | optional: true 1286 | 1287 | '@biomejs/cli-linux-arm64@2.3.5': 1288 | optional: true 1289 | 1290 | '@biomejs/cli-linux-x64-musl@2.3.5': 1291 | optional: true 1292 | 1293 | '@biomejs/cli-linux-x64@2.3.5': 1294 | optional: true 1295 | 1296 | '@biomejs/cli-win32-arm64@2.3.5': 1297 | optional: true 1298 | 1299 | '@biomejs/cli-win32-x64@2.3.5': 1300 | optional: true 1301 | 1302 | '@esbuild/android-arm64@0.18.20': 1303 | optional: true 1304 | 1305 | '@esbuild/android-arm@0.18.20': 1306 | optional: true 1307 | 1308 | '@esbuild/android-x64@0.18.20': 1309 | optional: true 1310 | 1311 | '@esbuild/darwin-arm64@0.18.20': 1312 | optional: true 1313 | 1314 | '@esbuild/darwin-x64@0.18.20': 1315 | optional: true 1316 | 1317 | '@esbuild/freebsd-arm64@0.18.20': 1318 | optional: true 1319 | 1320 | '@esbuild/freebsd-x64@0.18.20': 1321 | optional: true 1322 | 1323 | '@esbuild/linux-arm64@0.18.20': 1324 | optional: true 1325 | 1326 | '@esbuild/linux-arm@0.18.20': 1327 | optional: true 1328 | 1329 | '@esbuild/linux-ia32@0.18.20': 1330 | optional: true 1331 | 1332 | '@esbuild/linux-loong64@0.18.20': 1333 | optional: true 1334 | 1335 | '@esbuild/linux-mips64el@0.18.20': 1336 | optional: true 1337 | 1338 | '@esbuild/linux-ppc64@0.18.20': 1339 | optional: true 1340 | 1341 | '@esbuild/linux-riscv64@0.18.20': 1342 | optional: true 1343 | 1344 | '@esbuild/linux-s390x@0.18.20': 1345 | optional: true 1346 | 1347 | '@esbuild/linux-x64@0.18.20': 1348 | optional: true 1349 | 1350 | '@esbuild/netbsd-x64@0.18.20': 1351 | optional: true 1352 | 1353 | '@esbuild/openbsd-x64@0.18.20': 1354 | optional: true 1355 | 1356 | '@esbuild/sunos-x64@0.18.20': 1357 | optional: true 1358 | 1359 | '@esbuild/win32-arm64@0.18.20': 1360 | optional: true 1361 | 1362 | '@esbuild/win32-ia32@0.18.20': 1363 | optional: true 1364 | 1365 | '@esbuild/win32-x64@0.18.20': 1366 | optional: true 1367 | 1368 | '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1)': 1369 | dependencies: 1370 | eslint: 9.39.1 1371 | eslint-visitor-keys: 3.4.3 1372 | 1373 | '@eslint-community/regexpp@4.12.1': {} 1374 | 1375 | '@eslint/config-array@0.21.1': 1376 | dependencies: 1377 | '@eslint/object-schema': 2.1.7 1378 | debug: 4.4.0 1379 | minimatch: 3.1.2 1380 | transitivePeerDependencies: 1381 | - supports-color 1382 | 1383 | '@eslint/config-helpers@0.4.2': 1384 | dependencies: 1385 | '@eslint/core': 0.17.0 1386 | 1387 | '@eslint/core@0.17.0': 1388 | dependencies: 1389 | '@types/json-schema': 7.0.15 1390 | 1391 | '@eslint/eslintrc@3.3.1': 1392 | dependencies: 1393 | ajv: 6.12.6 1394 | debug: 4.4.0 1395 | espree: 10.4.0 1396 | globals: 14.0.0 1397 | ignore: 5.3.2 1398 | import-fresh: 3.3.1 1399 | js-yaml: 4.1.0 1400 | minimatch: 3.1.2 1401 | strip-json-comments: 3.1.1 1402 | transitivePeerDependencies: 1403 | - supports-color 1404 | 1405 | '@eslint/js@9.39.1': {} 1406 | 1407 | '@eslint/object-schema@2.1.7': {} 1408 | 1409 | '@eslint/plugin-kit@0.4.1': 1410 | dependencies: 1411 | '@eslint/core': 0.17.0 1412 | levn: 0.4.1 1413 | 1414 | '@humanfs/core@0.19.1': {} 1415 | 1416 | '@humanfs/node@0.16.7': 1417 | dependencies: 1418 | '@humanfs/core': 0.19.1 1419 | '@humanwhocodes/retry': 0.4.3 1420 | 1421 | '@humanwhocodes/module-importer@1.0.1': {} 1422 | 1423 | '@humanwhocodes/retry@0.4.3': {} 1424 | 1425 | '@jridgewell/gen-mapping@0.3.8': 1426 | dependencies: 1427 | '@jridgewell/set-array': 1.2.1 1428 | '@jridgewell/sourcemap-codec': 1.5.0 1429 | '@jridgewell/trace-mapping': 0.3.25 1430 | 1431 | '@jridgewell/resolve-uri@3.1.2': {} 1432 | 1433 | '@jridgewell/set-array@1.2.1': {} 1434 | 1435 | '@jridgewell/sourcemap-codec@1.5.0': {} 1436 | 1437 | '@jridgewell/trace-mapping@0.3.25': 1438 | dependencies: 1439 | '@jridgewell/resolve-uri': 3.1.2 1440 | '@jridgewell/sourcemap-codec': 1.5.0 1441 | 1442 | '@nodelib/fs.scandir@2.1.5': 1443 | dependencies: 1444 | '@nodelib/fs.stat': 2.0.5 1445 | run-parallel: 1.2.0 1446 | 1447 | '@nodelib/fs.stat@2.0.5': {} 1448 | 1449 | '@nodelib/fs.walk@1.2.8': 1450 | dependencies: 1451 | '@nodelib/fs.scandir': 2.1.5 1452 | fastq: 1.19.1 1453 | 1454 | '@solidjs/router@0.8.4(solid-js@1.9.5)': 1455 | dependencies: 1456 | solid-js: 1.9.5 1457 | 1458 | '@tauri-apps/api@1.6.0': {} 1459 | 1460 | '@tauri-apps/cli-darwin-arm64@1.6.3': 1461 | optional: true 1462 | 1463 | '@tauri-apps/cli-darwin-x64@1.6.3': 1464 | optional: true 1465 | 1466 | '@tauri-apps/cli-linux-arm-gnueabihf@1.6.3': 1467 | optional: true 1468 | 1469 | '@tauri-apps/cli-linux-arm64-gnu@1.6.3': 1470 | optional: true 1471 | 1472 | '@tauri-apps/cli-linux-arm64-musl@1.6.3': 1473 | optional: true 1474 | 1475 | '@tauri-apps/cli-linux-x64-gnu@1.6.3': 1476 | optional: true 1477 | 1478 | '@tauri-apps/cli-linux-x64-musl@1.6.3': 1479 | optional: true 1480 | 1481 | '@tauri-apps/cli-win32-arm64-msvc@1.6.3': 1482 | optional: true 1483 | 1484 | '@tauri-apps/cli-win32-ia32-msvc@1.6.3': 1485 | optional: true 1486 | 1487 | '@tauri-apps/cli-win32-x64-msvc@1.6.3': 1488 | optional: true 1489 | 1490 | '@tauri-apps/cli@1.6.3': 1491 | dependencies: 1492 | semver: 7.7.1 1493 | optionalDependencies: 1494 | '@tauri-apps/cli-darwin-arm64': 1.6.3 1495 | '@tauri-apps/cli-darwin-x64': 1.6.3 1496 | '@tauri-apps/cli-linux-arm-gnueabihf': 1.6.3 1497 | '@tauri-apps/cli-linux-arm64-gnu': 1.6.3 1498 | '@tauri-apps/cli-linux-arm64-musl': 1.6.3 1499 | '@tauri-apps/cli-linux-x64-gnu': 1.6.3 1500 | '@tauri-apps/cli-linux-x64-musl': 1.6.3 1501 | '@tauri-apps/cli-win32-arm64-msvc': 1.6.3 1502 | '@tauri-apps/cli-win32-ia32-msvc': 1.6.3 1503 | '@tauri-apps/cli-win32-x64-msvc': 1.6.3 1504 | 1505 | '@types/babel__core@7.20.5': 1506 | dependencies: 1507 | '@babel/parser': 7.27.0 1508 | '@babel/types': 7.27.0 1509 | '@types/babel__generator': 7.27.0 1510 | '@types/babel__template': 7.4.4 1511 | '@types/babel__traverse': 7.20.7 1512 | 1513 | '@types/babel__generator@7.27.0': 1514 | dependencies: 1515 | '@babel/types': 7.27.0 1516 | 1517 | '@types/babel__template@7.4.4': 1518 | dependencies: 1519 | '@babel/parser': 7.27.0 1520 | '@babel/types': 7.27.0 1521 | 1522 | '@types/babel__traverse@7.20.7': 1523 | dependencies: 1524 | '@babel/types': 7.27.0 1525 | 1526 | '@types/dompurify@3.2.0': 1527 | dependencies: 1528 | dompurify: 3.2.5 1529 | 1530 | '@types/estree@1.0.8': {} 1531 | 1532 | '@types/json-schema@7.0.15': {} 1533 | 1534 | '@types/trusted-types@2.0.7': 1535 | optional: true 1536 | 1537 | '@typescript-eslint/eslint-plugin@8.46.4(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.8.3))(eslint@9.39.1)(typescript@5.8.3)': 1538 | dependencies: 1539 | '@eslint-community/regexpp': 4.12.1 1540 | '@typescript-eslint/parser': 8.46.4(eslint@9.39.1)(typescript@5.8.3) 1541 | '@typescript-eslint/scope-manager': 8.46.4 1542 | '@typescript-eslint/type-utils': 8.46.4(eslint@9.39.1)(typescript@5.8.3) 1543 | '@typescript-eslint/utils': 8.46.4(eslint@9.39.1)(typescript@5.8.3) 1544 | '@typescript-eslint/visitor-keys': 8.46.4 1545 | eslint: 9.39.1 1546 | graphemer: 1.4.0 1547 | ignore: 7.0.5 1548 | natural-compare: 1.4.0 1549 | ts-api-utils: 2.1.0(typescript@5.8.3) 1550 | typescript: 5.8.3 1551 | transitivePeerDependencies: 1552 | - supports-color 1553 | 1554 | '@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.8.3)': 1555 | dependencies: 1556 | '@typescript-eslint/scope-manager': 8.46.4 1557 | '@typescript-eslint/types': 8.46.4 1558 | '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.8.3) 1559 | '@typescript-eslint/visitor-keys': 8.46.4 1560 | debug: 4.4.0 1561 | eslint: 9.39.1 1562 | typescript: 5.8.3 1563 | transitivePeerDependencies: 1564 | - supports-color 1565 | 1566 | '@typescript-eslint/project-service@8.46.4(typescript@5.8.3)': 1567 | dependencies: 1568 | '@typescript-eslint/tsconfig-utils': 8.46.4(typescript@5.8.3) 1569 | '@typescript-eslint/types': 8.46.4 1570 | debug: 4.4.0 1571 | typescript: 5.8.3 1572 | transitivePeerDependencies: 1573 | - supports-color 1574 | 1575 | '@typescript-eslint/scope-manager@8.46.4': 1576 | dependencies: 1577 | '@typescript-eslint/types': 8.46.4 1578 | '@typescript-eslint/visitor-keys': 8.46.4 1579 | 1580 | '@typescript-eslint/tsconfig-utils@8.46.4(typescript@5.8.3)': 1581 | dependencies: 1582 | typescript: 5.8.3 1583 | 1584 | '@typescript-eslint/type-utils@8.46.4(eslint@9.39.1)(typescript@5.8.3)': 1585 | dependencies: 1586 | '@typescript-eslint/types': 8.46.4 1587 | '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.8.3) 1588 | '@typescript-eslint/utils': 8.46.4(eslint@9.39.1)(typescript@5.8.3) 1589 | debug: 4.4.0 1590 | eslint: 9.39.1 1591 | ts-api-utils: 2.1.0(typescript@5.8.3) 1592 | typescript: 5.8.3 1593 | transitivePeerDependencies: 1594 | - supports-color 1595 | 1596 | '@typescript-eslint/types@8.46.4': {} 1597 | 1598 | '@typescript-eslint/typescript-estree@8.46.4(typescript@5.8.3)': 1599 | dependencies: 1600 | '@typescript-eslint/project-service': 8.46.4(typescript@5.8.3) 1601 | '@typescript-eslint/tsconfig-utils': 8.46.4(typescript@5.8.3) 1602 | '@typescript-eslint/types': 8.46.4 1603 | '@typescript-eslint/visitor-keys': 8.46.4 1604 | debug: 4.4.0 1605 | fast-glob: 3.3.3 1606 | is-glob: 4.0.3 1607 | minimatch: 9.0.5 1608 | semver: 7.7.1 1609 | ts-api-utils: 2.1.0(typescript@5.8.3) 1610 | typescript: 5.8.3 1611 | transitivePeerDependencies: 1612 | - supports-color 1613 | 1614 | '@typescript-eslint/utils@8.46.4(eslint@9.39.1)(typescript@5.8.3)': 1615 | dependencies: 1616 | '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) 1617 | '@typescript-eslint/scope-manager': 8.46.4 1618 | '@typescript-eslint/types': 8.46.4 1619 | '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.8.3) 1620 | eslint: 9.39.1 1621 | typescript: 5.8.3 1622 | transitivePeerDependencies: 1623 | - supports-color 1624 | 1625 | '@typescript-eslint/visitor-keys@8.46.4': 1626 | dependencies: 1627 | '@typescript-eslint/types': 8.46.4 1628 | eslint-visitor-keys: 4.2.1 1629 | 1630 | acorn-jsx@5.3.2(acorn@8.15.0): 1631 | dependencies: 1632 | acorn: 8.15.0 1633 | 1634 | acorn@8.15.0: {} 1635 | 1636 | ajv@6.12.6: 1637 | dependencies: 1638 | fast-deep-equal: 3.1.3 1639 | fast-json-stable-stringify: 2.1.0 1640 | json-schema-traverse: 0.4.1 1641 | uri-js: 4.4.1 1642 | 1643 | ansi-styles@4.3.0: 1644 | dependencies: 1645 | color-convert: 2.0.1 1646 | 1647 | argparse@2.0.1: {} 1648 | 1649 | babel-plugin-jsx-dom-expressions@0.39.8(@babel/core@7.26.10): 1650 | dependencies: 1651 | '@babel/core': 7.26.10 1652 | '@babel/helper-module-imports': 7.18.6 1653 | '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.10) 1654 | '@babel/types': 7.27.0 1655 | html-entities: 2.3.3 1656 | parse5: 7.3.0 1657 | validate-html-nesting: 1.2.2 1658 | 1659 | babel-preset-solid@1.9.5(@babel/core@7.26.10): 1660 | dependencies: 1661 | '@babel/core': 7.26.10 1662 | babel-plugin-jsx-dom-expressions: 0.39.8(@babel/core@7.26.10) 1663 | 1664 | balanced-match@1.0.2: {} 1665 | 1666 | brace-expansion@1.1.11: 1667 | dependencies: 1668 | balanced-match: 1.0.2 1669 | concat-map: 0.0.1 1670 | 1671 | brace-expansion@2.0.1: 1672 | dependencies: 1673 | balanced-match: 1.0.2 1674 | 1675 | braces@3.0.3: 1676 | dependencies: 1677 | fill-range: 7.1.1 1678 | 1679 | browserslist@4.24.4: 1680 | dependencies: 1681 | caniuse-lite: 1.0.30001715 1682 | electron-to-chromium: 1.5.144 1683 | node-releases: 2.0.19 1684 | update-browserslist-db: 1.1.3(browserslist@4.24.4) 1685 | 1686 | callsites@3.1.0: {} 1687 | 1688 | caniuse-lite@1.0.30001715: {} 1689 | 1690 | chalk@4.1.2: 1691 | dependencies: 1692 | ansi-styles: 4.3.0 1693 | supports-color: 7.2.0 1694 | 1695 | color-convert@2.0.1: 1696 | dependencies: 1697 | color-name: 1.1.4 1698 | 1699 | color-name@1.1.4: {} 1700 | 1701 | concat-map@0.0.1: {} 1702 | 1703 | convert-source-map@2.0.0: {} 1704 | 1705 | cross-spawn@7.0.6: 1706 | dependencies: 1707 | path-key: 3.1.1 1708 | shebang-command: 2.0.0 1709 | which: 2.0.2 1710 | 1711 | csstype@3.1.3: {} 1712 | 1713 | dayjs@1.11.13: {} 1714 | 1715 | debug@4.4.0: 1716 | dependencies: 1717 | ms: 2.1.3 1718 | 1719 | deep-is@0.1.4: {} 1720 | 1721 | dompurify@3.2.5: 1722 | optionalDependencies: 1723 | '@types/trusted-types': 2.0.7 1724 | 1725 | electron-to-chromium@1.5.144: {} 1726 | 1727 | entities@6.0.0: {} 1728 | 1729 | esbuild@0.18.20: 1730 | optionalDependencies: 1731 | '@esbuild/android-arm': 0.18.20 1732 | '@esbuild/android-arm64': 0.18.20 1733 | '@esbuild/android-x64': 0.18.20 1734 | '@esbuild/darwin-arm64': 0.18.20 1735 | '@esbuild/darwin-x64': 0.18.20 1736 | '@esbuild/freebsd-arm64': 0.18.20 1737 | '@esbuild/freebsd-x64': 0.18.20 1738 | '@esbuild/linux-arm': 0.18.20 1739 | '@esbuild/linux-arm64': 0.18.20 1740 | '@esbuild/linux-ia32': 0.18.20 1741 | '@esbuild/linux-loong64': 0.18.20 1742 | '@esbuild/linux-mips64el': 0.18.20 1743 | '@esbuild/linux-ppc64': 0.18.20 1744 | '@esbuild/linux-riscv64': 0.18.20 1745 | '@esbuild/linux-s390x': 0.18.20 1746 | '@esbuild/linux-x64': 0.18.20 1747 | '@esbuild/netbsd-x64': 0.18.20 1748 | '@esbuild/openbsd-x64': 0.18.20 1749 | '@esbuild/sunos-x64': 0.18.20 1750 | '@esbuild/win32-arm64': 0.18.20 1751 | '@esbuild/win32-ia32': 0.18.20 1752 | '@esbuild/win32-x64': 0.18.20 1753 | 1754 | escalade@3.2.0: {} 1755 | 1756 | escape-string-regexp@4.0.0: {} 1757 | 1758 | eslint-plugin-solid@0.14.5(eslint@9.39.1)(typescript@5.8.3): 1759 | dependencies: 1760 | '@typescript-eslint/utils': 8.46.4(eslint@9.39.1)(typescript@5.8.3) 1761 | eslint: 9.39.1 1762 | estraverse: 5.3.0 1763 | is-html: 2.0.0 1764 | kebab-case: 1.0.2 1765 | known-css-properties: 0.30.0 1766 | style-to-object: 1.0.13 1767 | typescript: 5.8.3 1768 | transitivePeerDependencies: 1769 | - supports-color 1770 | 1771 | eslint-scope@8.4.0: 1772 | dependencies: 1773 | esrecurse: 4.3.0 1774 | estraverse: 5.3.0 1775 | 1776 | eslint-visitor-keys@3.4.3: {} 1777 | 1778 | eslint-visitor-keys@4.2.1: {} 1779 | 1780 | eslint@9.39.1: 1781 | dependencies: 1782 | '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) 1783 | '@eslint-community/regexpp': 4.12.1 1784 | '@eslint/config-array': 0.21.1 1785 | '@eslint/config-helpers': 0.4.2 1786 | '@eslint/core': 0.17.0 1787 | '@eslint/eslintrc': 3.3.1 1788 | '@eslint/js': 9.39.1 1789 | '@eslint/plugin-kit': 0.4.1 1790 | '@humanfs/node': 0.16.7 1791 | '@humanwhocodes/module-importer': 1.0.1 1792 | '@humanwhocodes/retry': 0.4.3 1793 | '@types/estree': 1.0.8 1794 | ajv: 6.12.6 1795 | chalk: 4.1.2 1796 | cross-spawn: 7.0.6 1797 | debug: 4.4.0 1798 | escape-string-regexp: 4.0.0 1799 | eslint-scope: 8.4.0 1800 | eslint-visitor-keys: 4.2.1 1801 | espree: 10.4.0 1802 | esquery: 1.6.0 1803 | esutils: 2.0.3 1804 | fast-deep-equal: 3.1.3 1805 | file-entry-cache: 8.0.0 1806 | find-up: 5.0.0 1807 | glob-parent: 6.0.2 1808 | ignore: 5.3.2 1809 | imurmurhash: 0.1.4 1810 | is-glob: 4.0.3 1811 | json-stable-stringify-without-jsonify: 1.0.1 1812 | lodash.merge: 4.6.2 1813 | minimatch: 3.1.2 1814 | natural-compare: 1.4.0 1815 | optionator: 0.9.4 1816 | transitivePeerDependencies: 1817 | - supports-color 1818 | 1819 | espree@10.4.0: 1820 | dependencies: 1821 | acorn: 8.15.0 1822 | acorn-jsx: 5.3.2(acorn@8.15.0) 1823 | eslint-visitor-keys: 4.2.1 1824 | 1825 | esquery@1.6.0: 1826 | dependencies: 1827 | estraverse: 5.3.0 1828 | 1829 | esrecurse@4.3.0: 1830 | dependencies: 1831 | estraverse: 5.3.0 1832 | 1833 | estraverse@5.3.0: {} 1834 | 1835 | esutils@2.0.3: {} 1836 | 1837 | fast-deep-equal@3.1.3: {} 1838 | 1839 | fast-glob@3.3.3: 1840 | dependencies: 1841 | '@nodelib/fs.stat': 2.0.5 1842 | '@nodelib/fs.walk': 1.2.8 1843 | glob-parent: 5.1.2 1844 | merge2: 1.4.1 1845 | micromatch: 4.0.8 1846 | 1847 | fast-json-stable-stringify@2.1.0: {} 1848 | 1849 | fast-levenshtein@2.0.6: {} 1850 | 1851 | fastq@1.19.1: 1852 | dependencies: 1853 | reusify: 1.1.0 1854 | 1855 | file-entry-cache@8.0.0: 1856 | dependencies: 1857 | flat-cache: 4.0.1 1858 | 1859 | fill-range@7.1.1: 1860 | dependencies: 1861 | to-regex-range: 5.0.1 1862 | 1863 | find-up@5.0.0: 1864 | dependencies: 1865 | locate-path: 6.0.0 1866 | path-exists: 4.0.0 1867 | 1868 | flat-cache@4.0.1: 1869 | dependencies: 1870 | flatted: 3.3.3 1871 | keyv: 4.5.4 1872 | 1873 | flatted@3.3.3: {} 1874 | 1875 | fsevents@2.3.3: 1876 | optional: true 1877 | 1878 | gensync@1.0.0-beta.2: {} 1879 | 1880 | glob-parent@5.1.2: 1881 | dependencies: 1882 | is-glob: 4.0.3 1883 | 1884 | glob-parent@6.0.2: 1885 | dependencies: 1886 | is-glob: 4.0.3 1887 | 1888 | globals@11.12.0: {} 1889 | 1890 | globals@14.0.0: {} 1891 | 1892 | globals@15.15.0: {} 1893 | 1894 | graphemer@1.4.0: {} 1895 | 1896 | has-flag@4.0.0: {} 1897 | 1898 | html-entities@2.3.3: {} 1899 | 1900 | html-tags@3.3.1: {} 1901 | 1902 | ignore@5.3.2: {} 1903 | 1904 | ignore@7.0.5: {} 1905 | 1906 | import-fresh@3.3.1: 1907 | dependencies: 1908 | parent-module: 1.0.1 1909 | resolve-from: 4.0.0 1910 | 1911 | imurmurhash@0.1.4: {} 1912 | 1913 | inline-style-parser@0.2.6: {} 1914 | 1915 | is-extglob@2.1.1: {} 1916 | 1917 | is-glob@4.0.3: 1918 | dependencies: 1919 | is-extglob: 2.1.1 1920 | 1921 | is-html@2.0.0: 1922 | dependencies: 1923 | html-tags: 3.3.1 1924 | 1925 | is-number@7.0.0: {} 1926 | 1927 | is-what@4.1.16: {} 1928 | 1929 | isexe@2.0.0: {} 1930 | 1931 | js-tokens@4.0.0: {} 1932 | 1933 | js-yaml@4.1.0: 1934 | dependencies: 1935 | argparse: 2.0.1 1936 | 1937 | jsesc@3.1.0: {} 1938 | 1939 | json-buffer@3.0.1: {} 1940 | 1941 | json-schema-traverse@0.4.1: {} 1942 | 1943 | json-stable-stringify-without-jsonify@1.0.1: {} 1944 | 1945 | json5@2.2.3: {} 1946 | 1947 | kebab-case@1.0.2: {} 1948 | 1949 | keyv@4.5.4: 1950 | dependencies: 1951 | json-buffer: 3.0.1 1952 | 1953 | known-css-properties@0.30.0: {} 1954 | 1955 | levn@0.4.1: 1956 | dependencies: 1957 | prelude-ls: 1.2.1 1958 | type-check: 0.4.0 1959 | 1960 | locate-path@6.0.0: 1961 | dependencies: 1962 | p-locate: 5.0.0 1963 | 1964 | lodash.merge@4.6.2: {} 1965 | 1966 | lru-cache@5.1.1: 1967 | dependencies: 1968 | yallist: 3.1.1 1969 | 1970 | merge-anything@5.1.7: 1971 | dependencies: 1972 | is-what: 4.1.16 1973 | 1974 | merge2@1.4.1: {} 1975 | 1976 | micromatch@4.0.8: 1977 | dependencies: 1978 | braces: 3.0.3 1979 | picomatch: 2.3.1 1980 | 1981 | minimatch@3.1.2: 1982 | dependencies: 1983 | brace-expansion: 1.1.11 1984 | 1985 | minimatch@9.0.5: 1986 | dependencies: 1987 | brace-expansion: 2.0.1 1988 | 1989 | ms@2.1.3: {} 1990 | 1991 | nanoid@3.3.11: {} 1992 | 1993 | natural-compare@1.4.0: {} 1994 | 1995 | node-releases@2.0.19: {} 1996 | 1997 | optionator@0.9.4: 1998 | dependencies: 1999 | deep-is: 0.1.4 2000 | fast-levenshtein: 2.0.6 2001 | levn: 0.4.1 2002 | prelude-ls: 1.2.1 2003 | type-check: 0.4.0 2004 | word-wrap: 1.2.5 2005 | 2006 | p-limit@3.1.0: 2007 | dependencies: 2008 | yocto-queue: 0.1.0 2009 | 2010 | p-locate@5.0.0: 2011 | dependencies: 2012 | p-limit: 3.1.0 2013 | 2014 | parent-module@1.0.1: 2015 | dependencies: 2016 | callsites: 3.1.0 2017 | 2018 | parse5@7.3.0: 2019 | dependencies: 2020 | entities: 6.0.0 2021 | 2022 | path-exists@4.0.0: {} 2023 | 2024 | path-key@3.1.1: {} 2025 | 2026 | picocolors@1.1.1: {} 2027 | 2028 | picomatch@2.3.1: {} 2029 | 2030 | postcss@8.5.3: 2031 | dependencies: 2032 | nanoid: 3.3.11 2033 | picocolors: 1.1.1 2034 | source-map-js: 1.2.1 2035 | 2036 | prelude-ls@1.2.1: {} 2037 | 2038 | punycode@2.3.1: {} 2039 | 2040 | queue-microtask@1.2.3: {} 2041 | 2042 | resolve-from@4.0.0: {} 2043 | 2044 | reusify@1.1.0: {} 2045 | 2046 | rollup@3.29.5: 2047 | optionalDependencies: 2048 | fsevents: 2.3.3 2049 | 2050 | run-parallel@1.2.0: 2051 | dependencies: 2052 | queue-microtask: 1.2.3 2053 | 2054 | semver@6.3.1: {} 2055 | 2056 | semver@7.7.1: {} 2057 | 2058 | seroval-plugins@1.2.1(seroval@1.2.1): 2059 | dependencies: 2060 | seroval: 1.2.1 2061 | 2062 | seroval@1.2.1: {} 2063 | 2064 | shebang-command@2.0.0: 2065 | dependencies: 2066 | shebang-regex: 3.0.0 2067 | 2068 | shebang-regex@3.0.0: {} 2069 | 2070 | solid-js@1.9.5: 2071 | dependencies: 2072 | csstype: 3.1.3 2073 | seroval: 1.2.1 2074 | seroval-plugins: 1.2.1(seroval@1.2.1) 2075 | 2076 | solid-refresh@0.6.3(solid-js@1.9.5): 2077 | dependencies: 2078 | '@babel/generator': 7.27.0 2079 | '@babel/helper-module-imports': 7.25.9 2080 | '@babel/types': 7.27.0 2081 | solid-js: 1.9.5 2082 | transitivePeerDependencies: 2083 | - supports-color 2084 | 2085 | source-map-js@1.2.1: {} 2086 | 2087 | strip-json-comments@3.1.1: {} 2088 | 2089 | style-to-object@1.0.13: 2090 | dependencies: 2091 | inline-style-parser: 0.2.6 2092 | 2093 | supports-color@7.2.0: 2094 | dependencies: 2095 | has-flag: 4.0.0 2096 | 2097 | to-regex-range@5.0.1: 2098 | dependencies: 2099 | is-number: 7.0.0 2100 | 2101 | ts-api-utils@2.1.0(typescript@5.8.3): 2102 | dependencies: 2103 | typescript: 5.8.3 2104 | 2105 | type-check@0.4.0: 2106 | dependencies: 2107 | prelude-ls: 1.2.1 2108 | 2109 | typescript-eslint@8.46.4(eslint@9.39.1)(typescript@5.8.3): 2110 | dependencies: 2111 | '@typescript-eslint/eslint-plugin': 8.46.4(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.8.3))(eslint@9.39.1)(typescript@5.8.3) 2112 | '@typescript-eslint/parser': 8.46.4(eslint@9.39.1)(typescript@5.8.3) 2113 | '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.8.3) 2114 | '@typescript-eslint/utils': 8.46.4(eslint@9.39.1)(typescript@5.8.3) 2115 | eslint: 9.39.1 2116 | typescript: 5.8.3 2117 | transitivePeerDependencies: 2118 | - supports-color 2119 | 2120 | typescript@5.8.3: {} 2121 | 2122 | update-browserslist-db@1.1.3(browserslist@4.24.4): 2123 | dependencies: 2124 | browserslist: 4.24.4 2125 | escalade: 3.2.0 2126 | picocolors: 1.1.1 2127 | 2128 | uri-js@4.4.1: 2129 | dependencies: 2130 | punycode: 2.3.1 2131 | 2132 | validate-html-nesting@1.2.2: {} 2133 | 2134 | vite-plugin-solid@2.11.6(solid-js@1.9.5)(vite@4.5.13): 2135 | dependencies: 2136 | '@babel/core': 7.26.10 2137 | '@types/babel__core': 7.20.5 2138 | babel-preset-solid: 1.9.5(@babel/core@7.26.10) 2139 | merge-anything: 5.1.7 2140 | solid-js: 1.9.5 2141 | solid-refresh: 0.6.3(solid-js@1.9.5) 2142 | vite: 4.5.13 2143 | vitefu: 1.0.6(vite@4.5.13) 2144 | transitivePeerDependencies: 2145 | - supports-color 2146 | 2147 | vite@4.5.13: 2148 | dependencies: 2149 | esbuild: 0.18.20 2150 | postcss: 8.5.3 2151 | rollup: 3.29.5 2152 | optionalDependencies: 2153 | fsevents: 2.3.3 2154 | 2155 | vitefu@1.0.6(vite@4.5.13): 2156 | optionalDependencies: 2157 | vite: 4.5.13 2158 | 2159 | which@2.0.2: 2160 | dependencies: 2161 | isexe: 2.0.0 2162 | 2163 | word-wrap@1.2.5: {} 2164 | 2165 | yallist@3.1.1: {} 2166 | 2167 | yocto-queue@0.1.0: {} 2168 | --------------------------------------------------------------------------------