AT protocol bookmarking platforms in obsidian

Compare changes

Choose any two refs to compare.

+9244 -2
+10
.editorconfig
··· 1 + # top-most EditorConfig file 2 + root = true 3 + 4 + [*] 5 + charset = utf-8 6 + end_of_line = lf 7 + insert_final_newline = true 8 + indent_style = tab 9 + indent_size = 4 10 + tab_width = 4
+26
.github/workflows/lint.yml
··· 1 + name: Bun build 2 + 3 + on: 4 + push: 5 + branches: ["**"] 6 + pull_request: 7 + branches: ["**"] 8 + 9 + jobs: 10 + build: 11 + runs-on: ubuntu-latest 12 + 13 + strategy: 14 + matrix: 15 + bun-version: [latest] 16 + 17 + steps: 18 + - uses: actions/checkout@v4 19 + - name: Setup Bun ${{ matrix.bun-version }} 20 + uses: oven-sh/setup-bun@v2 21 + with: 22 + bun-version: ${{ matrix.bun-version }} 23 + - run: bun install --frozen-lockfile 24 + - run: bun run build --if-present 25 + - run: bun run lint 26 +
+37
.github/workflows/release.yml
··· 1 + name: Release Obsidian Plugin 2 + 3 + on: 4 + push: 5 + tags: 6 + - "*" 7 + 8 + permissions: 9 + contents: write 10 + 11 + jobs: 12 + build: 13 + runs-on: ubuntu-latest 14 + 15 + steps: 16 + - uses: actions/checkout@v4 17 + 18 + - name: Setup Bun 19 + uses: oven-sh/setup-bun@v2 20 + with: 21 + bun-version: latest 22 + 23 + - name: Install dependencies 24 + run: bun install --frozen-lockfile 25 + 26 + - name: Build plugin 27 + run: bun run build 28 + 29 + - name: Create release 30 + env: 31 + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 + run: | 33 + tag="${{ github.ref_name }}" 34 + gh release create "$tag" \ 35 + --title="$tag" \ 36 + --draft \ 37 + main.js manifest.json styles.css
+22
.gitignore
··· 1 + # vscode 2 + .vscode 3 + 4 + # Intellij 5 + *.iml 6 + .idea 7 + 8 + # npm 9 + node_modules 10 + 11 + # Don't include the compiled main.js file in the repo. 12 + # They should be uploaded to GitHub releases instead. 13 + main.js 14 + 15 + # Exclude sourcemaps 16 + *.map 17 + 18 + # obsidian 19 + data.json 20 + 21 + # Exclude macOS Finder (System Explorer) View States 22 + .DS_Store
+1
.npmrc
··· 1 + tag-version-prefix=""
+251
AGENTS.md
··· 1 + # Obsidian community plugin 2 + 3 + ## Project overview 4 + 5 + - Target: Obsidian Community Plugin (TypeScript โ†’ bundled JavaScript). 6 + - Entry point: `main.ts` compiled to `main.js` and loaded by Obsidian. 7 + - Required release artifacts: `main.js`, `manifest.json`, and optional `styles.css`. 8 + 9 + ## Environment & tooling 10 + 11 + - Node.js: use current LTS (Node 18+ recommended). 12 + - **Package manager: npm** (required for this sample - `package.json` defines npm scripts and dependencies). 13 + - **Bundler: esbuild** (required for this sample - `esbuild.config.mjs` and build scripts depend on it). Alternative bundlers like Rollup or webpack are acceptable for other projects if they bundle all external dependencies into `main.js`. 14 + - Types: `obsidian` type definitions. 15 + 16 + **Note**: This sample project has specific technical dependencies on npm and esbuild. If you're creating a plugin from scratch, you can choose different tools, but you'll need to replace the build configuration accordingly. 17 + 18 + ### Install 19 + 20 + ```bash 21 + npm install 22 + ``` 23 + 24 + ### Dev (watch) 25 + 26 + ```bash 27 + npm run dev 28 + ``` 29 + 30 + ### Production build 31 + 32 + ```bash 33 + npm run build 34 + ``` 35 + 36 + ## Linting 37 + 38 + - To use eslint install eslint from terminal: `npm install -g eslint` 39 + - To use eslint to analyze this project use this command: `eslint main.ts` 40 + - eslint will then create a report with suggestions for code improvement by file and line number. 41 + - If your source code is in a folder, such as `src`, you can use eslint with this command to analyze all files in that folder: `eslint ./src/` 42 + 43 + ## File & folder conventions 44 + 45 + - **Organize code into multiple files**: Split functionality across separate modules rather than putting everything in `main.ts`. 46 + - Source lives in `src/`. Keep `main.ts` small and focused on plugin lifecycle (loading, unloading, registering commands). 47 + - **Example file structure**: 48 + ``` 49 + src/ 50 + main.ts # Plugin entry point, lifecycle management 51 + settings.ts # Settings interface and defaults 52 + commands/ # Command implementations 53 + command1.ts 54 + command2.ts 55 + ui/ # UI components, modals, views 56 + modal.ts 57 + view.ts 58 + utils/ # Utility functions, helpers 59 + helpers.ts 60 + constants.ts 61 + types.ts # TypeScript interfaces and types 62 + ``` 63 + - **Do not commit build artifacts**: Never commit `node_modules/`, `main.js`, or other generated files to version control. 64 + - Keep the plugin small. Avoid large dependencies. Prefer browser-compatible packages. 65 + - Generated output should be placed at the plugin root or `dist/` depending on your build setup. Release artifacts must end up at the top level of the plugin folder in the vault (`main.js`, `manifest.json`, `styles.css`). 66 + 67 + ## Manifest rules (`manifest.json`) 68 + 69 + - Must include (non-exhaustive): 70 + - `id` (plugin ID; for local dev it should match the folder name) 71 + - `name` 72 + - `version` (Semantic Versioning `x.y.z`) 73 + - `minAppVersion` 74 + - `description` 75 + - `isDesktopOnly` (boolean) 76 + - Optional: `author`, `authorUrl`, `fundingUrl` (string or map) 77 + - Never change `id` after release. Treat it as stable API. 78 + - Keep `minAppVersion` accurate when using newer APIs. 79 + - Canonical requirements are coded here: https://github.com/obsidianmd/obsidian-releases/blob/master/.github/workflows/validate-plugin-entry.yml 80 + 81 + ## Testing 82 + 83 + - Manual install for testing: copy `main.js`, `manifest.json`, `styles.css` (if any) to: 84 + ``` 85 + <Vault>/.obsidian/plugins/<plugin-id>/ 86 + ``` 87 + - Reload Obsidian and enable the plugin in **Settings โ†’ Community plugins**. 88 + 89 + ## Commands & settings 90 + 91 + - Any user-facing commands should be added via `this.addCommand(...)`. 92 + - If the plugin has configuration, provide a settings tab and sensible defaults. 93 + - Persist settings using `this.loadData()` / `this.saveData()`. 94 + - Use stable command IDs; avoid renaming once released. 95 + 96 + ## Versioning & releases 97 + 98 + - Bump `version` in `manifest.json` (SemVer) and update `versions.json` to map plugin version โ†’ minimum app version. 99 + - Create a GitHub release whose tag exactly matches `manifest.json`'s `version`. Do not use a leading `v`. 100 + - Attach `manifest.json`, `main.js`, and `styles.css` (if present) to the release as individual assets. 101 + - After the initial release, follow the process to add/update your plugin in the community catalog as required. 102 + 103 + ## Security, privacy, and compliance 104 + 105 + Follow Obsidian's **Developer Policies** and **Plugin Guidelines**. In particular: 106 + 107 + - Default to local/offline operation. Only make network requests when essential to the feature. 108 + - No hidden telemetry. If you collect optional analytics or call third-party services, require explicit opt-in and document clearly in `README.md` and in settings. 109 + - Never execute remote code, fetch and eval scripts, or auto-update plugin code outside of normal releases. 110 + - Minimize scope: read/write only what's necessary inside the vault. Do not access files outside the vault. 111 + - Clearly disclose any external services used, data sent, and risks. 112 + - Respect user privacy. Do not collect vault contents, filenames, or personal information unless absolutely necessary and explicitly consented. 113 + - Avoid deceptive patterns, ads, or spammy notifications. 114 + - Register and clean up all DOM, app, and interval listeners using the provided `register*` helpers so the plugin unloads safely. 115 + 116 + ## UX & copy guidelines (for UI text, commands, settings) 117 + 118 + - Prefer sentence case for headings, buttons, and titles. 119 + - Use clear, action-oriented imperatives in step-by-step copy. 120 + - Use **bold** to indicate literal UI labels. Prefer "select" for interactions. 121 + - Use arrow notation for navigation: **Settings โ†’ Community plugins**. 122 + - Keep in-app strings short, consistent, and free of jargon. 123 + 124 + ## Performance 125 + 126 + - Keep startup light. Defer heavy work until needed. 127 + - Avoid long-running tasks during `onload`; use lazy initialization. 128 + - Batch disk access and avoid excessive vault scans. 129 + - Debounce/throttle expensive operations in response to file system events. 130 + 131 + ## Coding conventions 132 + 133 + - TypeScript with `"strict": true` preferred. 134 + - **Keep `main.ts` minimal**: Focus only on plugin lifecycle (onload, onunload, addCommand calls). Delegate all feature logic to separate modules. 135 + - **Split large files**: If any file exceeds ~200-300 lines, consider breaking it into smaller, focused modules. 136 + - **Use clear module boundaries**: Each file should have a single, well-defined responsibility. 137 + - Bundle everything into `main.js` (no unbundled runtime deps). 138 + - Avoid Node/Electron APIs if you want mobile compatibility; set `isDesktopOnly` accordingly. 139 + - Prefer `async/await` over promise chains; handle errors gracefully. 140 + 141 + ## Mobile 142 + 143 + - Where feasible, test on iOS and Android. 144 + - Don't assume desktop-only behavior unless `isDesktopOnly` is `true`. 145 + - Avoid large in-memory structures; be mindful of memory and storage constraints. 146 + 147 + ## Agent do/don't 148 + 149 + **Do** 150 + - Add commands with stable IDs (don't rename once released). 151 + - Provide defaults and validation in settings. 152 + - Write idempotent code paths so reload/unload doesn't leak listeners or intervals. 153 + - Use `this.register*` helpers for everything that needs cleanup. 154 + 155 + **Don't** 156 + - Introduce network calls without an obvious user-facing reason and documentation. 157 + - Ship features that require cloud services without clear disclosure and explicit opt-in. 158 + - Store or transmit vault contents unless essential and consented. 159 + 160 + ## Common tasks 161 + 162 + ### Organize code across multiple files 163 + 164 + **main.ts** (minimal, lifecycle only): 165 + ```ts 166 + import { Plugin } from "obsidian"; 167 + import { MySettings, DEFAULT_SETTINGS } from "./settings"; 168 + import { registerCommands } from "./commands"; 169 + 170 + export default class MyPlugin extends Plugin { 171 + settings: MySettings; 172 + 173 + async onload() { 174 + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 175 + registerCommands(this); 176 + } 177 + } 178 + ``` 179 + 180 + **settings.ts**: 181 + ```ts 182 + export interface MySettings { 183 + enabled: boolean; 184 + apiKey: string; 185 + } 186 + 187 + export const DEFAULT_SETTINGS: MySettings = { 188 + enabled: true, 189 + apiKey: "", 190 + }; 191 + ``` 192 + 193 + **commands/index.ts**: 194 + ```ts 195 + import { Plugin } from "obsidian"; 196 + import { doSomething } from "./my-command"; 197 + 198 + export function registerCommands(plugin: Plugin) { 199 + plugin.addCommand({ 200 + id: "do-something", 201 + name: "Do something", 202 + callback: () => doSomething(plugin), 203 + }); 204 + } 205 + ``` 206 + 207 + ### Add a command 208 + 209 + ```ts 210 + this.addCommand({ 211 + id: "your-command-id", 212 + name: "Do the thing", 213 + callback: () => this.doTheThing(), 214 + }); 215 + ``` 216 + 217 + ### Persist settings 218 + 219 + ```ts 220 + interface MySettings { enabled: boolean } 221 + const DEFAULT_SETTINGS: MySettings = { enabled: true }; 222 + 223 + async onload() { 224 + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 225 + await this.saveData(this.settings); 226 + } 227 + ``` 228 + 229 + ### Register listeners safely 230 + 231 + ```ts 232 + this.registerEvent(this.app.workspace.on("file-open", f => { /* ... */ })); 233 + this.registerDomEvent(window, "resize", () => { /* ... */ }); 234 + this.registerInterval(window.setInterval(() => { /* ... */ }, 1000)); 235 + ``` 236 + 237 + ## Troubleshooting 238 + 239 + - Plugin doesn't load after build: ensure `main.js` and `manifest.json` are at the top level of the plugin folder under `<Vault>/.obsidian/plugins/<plugin-id>/`. 240 + - Build issues: if `main.js` is missing, run `npm run build` or `npm run dev` to compile your TypeScript source code. 241 + - Commands not appearing: verify `addCommand` runs after `onload` and IDs are unique. 242 + - Settings not persisting: ensure `loadData`/`saveData` are awaited and you re-render the UI after changes. 243 + - Mobile-only issues: confirm you're not using desktop-only APIs; check `isDesktopOnly` and adjust. 244 + 245 + ## References 246 + 247 + - Obsidian sample plugin: https://github.com/obsidianmd/obsidian-sample-plugin 248 + - API documentation: https://docs.obsidian.md 249 + - Developer policies: https://docs.obsidian.md/Developer+policies 250 + - Plugin guidelines: https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines 251 + - Style guide: https://help.obsidian.md/style-guide
+21
LICENSE
··· 1 + MIT License 2 + 3 + Copyright (c) 2025 treethought 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is 10 + furnished to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + SOFTWARE.
+40 -2
README.md
··· 1 - # ATmark (archived) 1 + # ATmark 2 2 3 3 Obsidian plugin for AT Protocol bookmarking platforms. 4 4 5 - This project has been renamed and moved to [obsidian-atmosphere](https://tangled.org/treethought.xyz/obsidian-atmosphere) 5 + 6 + ## Supported platforms 7 + 8 + - **Semble** (`network.cosmik.*`) - Collections and cards 9 + - **Bookmarks** (`community.lexicon.bookmarks.*`) - Community bookmarks lexicon with tag filtering (supports kipclip tags) 10 + - **margin.at** (`at.margin.*`) - Bookmarks with collections and tags support 11 + 12 + ![](/preview.png) 13 + ![](/preview-sidebar.png) 14 + 15 + ## Installation 16 + 17 + Install via [BRAT](https://github.com/TfTHacker/obsidian42-brat): 18 + 19 + 1. Install the BRAT plugin from Community Plugins 20 + 2. Open BRAT settings 21 + 3. Click "Add Beta plugin" 22 + 4. Enter the GitHub URL: `https://github.com/treethought/obsidian-atmark` 23 + 5. Enable the plugin in Community Plugins 24 + 25 + ## Getting Started 26 + 27 + ### Authentication 28 + 29 + 1. Open Settings > ATmark 30 + 2. Enter your AT Protocol handle or DID 31 + 3. Create an app password in your AT Protocol client (Bluesky: Settings > Privacy and security > App passwords) 32 + 4. Enter the app password in the plugin settings 33 + 5. Save settings 34 + 35 + The plugin will automatically connect using your credentials. 36 + 37 + ### Opening the View 38 + 39 + Open the command palette (Ctrl/Cmd + P) and search for "ATmark: Open view". The view will show your bookmarks from all supported platforms. 40 + 41 + ## Network Use 42 + 43 + This plugin connects to AT Protocol services to fetch and manage your bookmarks.
+884
bun.lock
··· 1 + { 2 + "lockfileVersion": 1, 3 + "configVersion": 1, 4 + "workspaces": { 5 + "": { 6 + "name": "obsidian-atmark", 7 + "dependencies": { 8 + "@atcute/atproto": "^3.1.10", 9 + "@atcute/bluesky": "^3.2.15", 10 + "@atcute/client": "^4.2.1", 11 + "@atcute/identity-resolver": "^1.2.2", 12 + "@atcute/leaflet": "^1.0.17", 13 + "@atcute/oauth-browser-client": "^2.0.3", 14 + "@atcute/pckt": "^0.1.5", 15 + "@atcute/standard-site": "^1.0.0", 16 + "obsidian": "latest", 17 + "remark-parse": "^11.0.0", 18 + "remark-stringify": "^11.0.0", 19 + "unified": "^11.0.5", 20 + }, 21 + "devDependencies": { 22 + "@atcute/lex-cli": "^2.5.3", 23 + "@eslint/js": "9.30.1", 24 + "@types/node": "^16.11.6", 25 + "esbuild": "0.25.5", 26 + "eslint-plugin-obsidianmd": "0.1.9", 27 + "globals": "14.0.0", 28 + "jiti": "2.6.1", 29 + "tslib": "2.4.0", 30 + "typescript": "^5.8.3", 31 + "typescript-eslint": "8.35.1", 32 + }, 33 + }, 34 + }, 35 + "packages": { 36 + "@atcute/atproto": ["@atcute/atproto@3.1.10", "", { "dependencies": { "@atcute/lexicons": "^1.2.6" } }, "sha512-+GKZpOc0PJcdWMQEkTfg/rSNDAAHxmAUGBl60g2az15etqJn5WaUPNGFE2sB7hKpwi5Ue2h/L0OacINcE/JDDQ=="], 37 + 38 + "@atcute/bluesky": ["@atcute/bluesky@3.2.16", "", { "dependencies": { "@atcute/atproto": "^3.1.10", "@atcute/lexicons": "^1.2.7" } }, "sha512-phFAJNE+SCkIbCcgzjFxntS2KpGvzkLw0JA9qKIXlueF4wNreEt/D5HjnB5eRR9pV1/kcD94II9f7ZAwarf0lQ=="], 39 + 40 + "@atcute/car": ["@atcute/car@5.1.0", "", { "dependencies": { "@atcute/cbor": "^2.3.0", "@atcute/cid": "^2.4.0", "@atcute/uint8array": "^1.0.6", "@atcute/varint": "^1.0.3" } }, "sha512-W9axHVrwIkZJaeN/VQ1LyyU3b95wHBjQnrREouxygvmvWd1lmjFvF8Si8b0AG0VkWJ6h7h6qhOeUATynBzBFIg=="], 41 + 42 + "@atcute/cbor": ["@atcute/cbor@2.3.0", "", { "dependencies": { "@atcute/cid": "^2.4.0", "@atcute/multibase": "^1.1.6", "@atcute/uint8array": "^1.0.6" } }, "sha512-7G2AndkfYzIXMBOBqUPUWP6oIJJm77KY5nYzS4Mr5NNxnmnrBrXEQqp+seCE3X5TV8FUSWQK5YRTU87uPjafMQ=="], 43 + 44 + "@atcute/cid": ["@atcute/cid@2.4.0", "", { "dependencies": { "@atcute/multibase": "^1.1.6", "@atcute/uint8array": "^1.0.6" } }, "sha512-6+5u9MpUrgSRQ94z7vaIX4BYk8fYr2KXUBS+rrr2NhlPy8xam8nbTlmd3hvBbtpSwShbhRAE4tA5Ab7eYUp2Yw=="], 45 + 46 + "@atcute/client": ["@atcute/client@4.2.1", "", { "dependencies": { "@atcute/identity": "^1.1.3", "@atcute/lexicons": "^1.2.6" } }, "sha512-ZBFM2pW075JtgGFu5g7HHZBecrClhlcNH8GVP9Zz1aViWR+cjjBsTpeE63rJs+FCOHFYlirUyo5L8SGZ4kMINw=="], 47 + 48 + "@atcute/crypto": ["@atcute/crypto@2.3.0", "", { "dependencies": { "@atcute/multibase": "^1.1.6", "@atcute/uint8array": "^1.0.6", "@noble/secp256k1": "^3.0.0" } }, "sha512-w5pkJKCjbNMQu+F4JRHbR3ROQyhi1wbn+GSC6WDQamcYHkZmEZk1/eoI354bIQOOfkEM6aFLv718iskrkon4GQ=="], 49 + 50 + "@atcute/identity": ["@atcute/identity@1.1.3", "", { "dependencies": { "@atcute/lexicons": "^1.2.4", "@badrap/valita": "^0.4.6" } }, "sha512-oIqPoI8TwWeQxvcLmFEZLdN2XdWcaLVtlm8pNk0E72As9HNzzD9pwKPrLr3rmTLRIoULPPFmq9iFNsTeCIU9ng=="], 51 + 52 + "@atcute/identity-resolver": ["@atcute/identity-resolver@1.2.2", "", { "dependencies": { "@atcute/lexicons": "^1.2.6", "@atcute/util-fetch": "^1.0.5", "@badrap/valita": "^0.4.6" }, "peerDependencies": { "@atcute/identity": "^1.0.0" } }, "sha512-eUh/UH4bFvuXS0X7epYCeJC/kj4rbBXfSRumLEH4smMVwNOgTo7cL/0Srty+P/qVPoZEyXdfEbS0PHJyzoXmHw=="], 53 + 54 + "@atcute/leaflet": ["@atcute/leaflet@1.0.17", "", { "dependencies": { "@atcute/atproto": "^3.1.10", "@atcute/lexicons": "^1.2.7" } }, "sha512-aCykf/vCRY19B8S42VEoyIvUR2CuOsH/RQcDF8u8rHZvmGfc4qgcAAxriHxiVrlinLNhxzhzuliq2avVxHiv8g=="], 55 + 56 + "@atcute/lex-cli": ["@atcute/lex-cli@2.5.3", "", { "dependencies": { "@atcute/identity": "^1.1.3", "@atcute/identity-resolver": "^1.2.2", "@atcute/lexicon-doc": "^2.1.0", "@atcute/lexicon-resolver": "^0.1.6", "@atcute/lexicons": "^1.2.7", "@badrap/valita": "^0.4.6", "@optique/core": "^0.6.10", "@optique/run": "^0.6.10", "picocolors": "^1.1.1", "prettier": "^3.7.4" }, "bin": { "lex-cli": "cli.mjs" } }, "sha512-829rvezMOfRkJQRKvupNT8TWT/YYffJ2QsB80D9aPjkXSogrETZA7xZcPaMZBXg+mJaVbLO9S4ThPQmlF0L4UQ=="], 57 + 58 + "@atcute/lexicon-doc": ["@atcute/lexicon-doc@2.1.0", "", { "dependencies": { "@atcute/identity": "^1.1.3", "@atcute/lexicons": "^1.2.7", "@atcute/uint8array": "^1.1.0", "@atcute/util-text": "^1.1.0", "@badrap/valita": "^0.4.6" } }, "sha512-I8BNFKUP2VvEv2tNzZ10b0TKtQWJHQ/CLUh29N1HRDUImah+OS7Z7wGBFleF7e1JnMzcWDhgZCvA6JaJOScBuA=="], 59 + 60 + "@atcute/lexicon-resolver": ["@atcute/lexicon-resolver@0.1.6", "", { "dependencies": { "@atcute/crypto": "^2.3.0", "@atcute/lexicon-doc": "^2.0.6", "@atcute/lexicons": "^1.2.6", "@atcute/repo": "^0.1.1", "@atcute/util-fetch": "^1.0.5", "@badrap/valita": "^0.4.6" }, "peerDependencies": { "@atcute/identity": "^1.1.0", "@atcute/identity-resolver": "^1.1.3" } }, "sha512-wJC/ChmpP7k+ywpOd07CMvioXjIGaFpF3bDwXLi/086LYjSWHOvtW6pyC+mqP5wLhjyH2hn4wmi77Buew1l1aw=="], 61 + 62 + "@atcute/lexicons": ["@atcute/lexicons@1.2.7", "", { "dependencies": { "@atcute/uint8array": "^1.1.0", "@atcute/util-text": "^1.1.0", "@standard-schema/spec": "^1.1.0", "esm-env": "^1.2.2" } }, "sha512-gCvkSMI1F1zx7xXa59iPiSKMH3L5Hga6iurGqQjaQbE2V/np/2QuDqQzt96TNbWfaFAXE9f9oY+0z3ljf/bweA=="], 63 + 64 + "@atcute/mst": ["@atcute/mst@0.1.2", "", { "dependencies": { "@atcute/cbor": "^2.3.0", "@atcute/cid": "^2.4.0", "@atcute/uint8array": "^1.0.6" } }, "sha512-Oz5CZTjqauEJLT9B+zkoy/mjl216DrjCxJFrguRV3N+1NkIbCfAcSRf3UDSNjfzDzBkJvC1WjA/3oQkm83duPg=="], 65 + 66 + "@atcute/multibase": ["@atcute/multibase@1.1.7", "", { "dependencies": { "@atcute/uint8array": "^1.1.0" } }, "sha512-YmWds7U52b7Qri0xNfGeqSOvgyNfHR8Yy/NNDQx4d5TkCX2fHJIo0pXquEhCyMNAwKt53uH5yQDswy4TNP1Zhw=="], 67 + 68 + "@atcute/oauth-browser-client": ["@atcute/oauth-browser-client@2.0.3", "", { "dependencies": { "@atcute/client": "^4.1.1", "@atcute/identity-resolver": "^1.2.0", "@atcute/lexicons": "^1.2.5", "@atcute/multibase": "^1.1.6", "@atcute/uint8array": "^1.0.6", "nanoid": "^5.1.6" } }, "sha512-rzUjwhjE4LRRKdQnCFQag/zXRZMEAB1hhBoLfnoQuHwWbmDUCL7fzwC3jRhDPp3om8XaYNDj8a/iqRip0wRqoQ=="], 69 + 70 + "@atcute/pckt": ["@atcute/pckt@0.1.5", "", { "dependencies": { "@atcute/atproto": "^3.1.10", "@atcute/lexicons": "^1.2.7" } }, "sha512-vvQ/jrSvCsipH7DoAZYDHMWQcStJWg1Jt+GBPGyCmYJylb5ZZTrRg6Hy1rEik5WbLUxKdK5SLXSq0/EYwhqOww=="], 71 + 72 + "@atcute/repo": ["@atcute/repo@0.1.1", "", { "dependencies": { "@atcute/car": "^5.0.0", "@atcute/cbor": "^2.2.8", "@atcute/cid": "^2.2.6", "@atcute/crypto": "^2.3.0", "@atcute/lexicons": "^1.2.5", "@atcute/mst": "^0.1.0", "@atcute/uint8array": "^1.0.6" } }, "sha512-P5aWjt3bvcquUkUmGPslF0naAfLGRHse5Qdz9/RJYrFuoH0iiEMyRnW6M+3ksOe20GPsMnbq71WbzzFkRFPBtg=="], 73 + 74 + "@atcute/standard-site": ["@atcute/standard-site@1.0.0", "", { "dependencies": { "@atcute/atproto": "^3.1.10", "@atcute/lexicons": "^1.2.7" } }, "sha512-szUuE/+9XDsYUSNaWgG/+Lqd0goyKUevP5UYV3CYkDgs10Vxv/waAXer25zWv20Gs2jFzRh+eFY64q8bSvfiQA=="], 75 + 76 + "@atcute/uint8array": ["@atcute/uint8array@1.1.0", "", {}, "sha512-JtHXIVW6LPU9FMWp7SgE4HbUs3uV2WdfkK/2RWdEGjr4EgMV50P3FdU6fPeGlTfDNBJVYMIsuD2wwaKRPV/Aqg=="], 77 + 78 + "@atcute/util-fetch": ["@atcute/util-fetch@1.0.5", "", { "dependencies": { "@badrap/valita": "^0.4.6" } }, "sha512-qjHj01BGxjSjIFdPiAjSARnodJIIyKxnCMMEcXMESo9TAyND6XZQqrie5fia+LlYWVXdpsTds8uFQwc9jdKTig=="], 79 + 80 + "@atcute/util-text": ["@atcute/util-text@1.1.0", "", { "dependencies": { "unicode-segmenter": "^0.14.5" } }, "sha512-34G9KD5Z9f7oEdFpZOmqrMnU86p8ne6LlxJowfZzKNszRcl1GH+FtEPh3N1woelJT2SkPXMK2anwT8DESTluwA=="], 81 + 82 + "@atcute/varint": ["@atcute/varint@1.0.3", "", {}, "sha512-fdvMPyBB+McDT+Ai5e9RwEbwYV4yjZ60S2Dn5PTjGqUyxvoCH1z42viuheDZRUDkmfQehXJTZ5az7dSozVNtog=="], 83 + 84 + "@badrap/valita": ["@badrap/valita@0.4.6", "", {}, "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg=="], 85 + 86 + "@codemirror/state": ["@codemirror/state@6.5.0", "", { "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw=="], 87 + 88 + "@codemirror/view": ["@codemirror/view@6.38.6", "", { "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw=="], 89 + 90 + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA=="], 91 + 92 + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.5", "", { "os": "android", "cpu": "arm" }, "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA=="], 93 + 94 + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.5", "", { "os": "android", "cpu": "arm64" }, "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg=="], 95 + 96 + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.5", "", { "os": "android", "cpu": "x64" }, "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw=="], 97 + 98 + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ=="], 99 + 100 + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ=="], 101 + 102 + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw=="], 103 + 104 + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw=="], 105 + 106 + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.5", "", { "os": "linux", "cpu": "arm" }, "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw=="], 107 + 108 + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg=="], 109 + 110 + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA=="], 111 + 112 + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.5", "", { "os": "linux", "cpu": "none" }, "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg=="], 113 + 114 + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.5", "", { "os": "linux", "cpu": "none" }, "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg=="], 115 + 116 + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ=="], 117 + 118 + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.5", "", { "os": "linux", "cpu": "none" }, "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA=="], 119 + 120 + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ=="], 121 + 122 + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.5", "", { "os": "linux", "cpu": "x64" }, "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw=="], 123 + 124 + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.5", "", { "os": "none", "cpu": "arm64" }, "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw=="], 125 + 126 + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.5", "", { "os": "none", "cpu": "x64" }, "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ=="], 127 + 128 + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.5", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw=="], 129 + 130 + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg=="], 131 + 132 + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA=="], 133 + 134 + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw=="], 135 + 136 + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ=="], 137 + 138 + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.5", "", { "os": "win32", "cpu": "x64" }, "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g=="], 139 + 140 + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], 141 + 142 + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], 143 + 144 + "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="], 145 + 146 + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], 147 + 148 + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], 149 + 150 + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="], 151 + 152 + "@eslint/js": ["@eslint/js@9.30.1", "", {}, "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg=="], 153 + 154 + "@eslint/json": ["@eslint/json@0.14.0", "", { "dependencies": { "@eslint/core": "^0.17.0", "@eslint/plugin-kit": "^0.4.1", "@humanwhocodes/momoa": "^3.3.10", "natural-compare": "^1.4.0" } }, "sha512-rvR/EZtvUG3p9uqrSmcDJPYSH7atmWr0RnFWN6m917MAPx82+zQgPUmDu0whPFG6XTyM0vB/hR6c1Q63OaYtCQ=="], 155 + 156 + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], 157 + 158 + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], 159 + 160 + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], 161 + 162 + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], 163 + 164 + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], 165 + 166 + "@humanwhocodes/momoa": ["@humanwhocodes/momoa@3.3.10", "", {}, "sha512-KWiFQpSAqEIyrTXko3hFNLeQvSK8zXlJQzhhxsyVn58WFRYXST99b3Nqnu+ttOtjds2Pl2grUHGpe2NzhPynuQ=="], 167 + 168 + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], 169 + 170 + "@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="], 171 + 172 + "@microsoft/eslint-plugin-sdl": ["@microsoft/eslint-plugin-sdl@1.1.0", "", { "dependencies": { "eslint-plugin-n": "17.10.3", "eslint-plugin-react": "7.37.3", "eslint-plugin-security": "1.4.0" }, "peerDependencies": { "eslint": "^9" } }, "sha512-dxdNHOemLnBhfY3eByrujX9KyLigcNtW8sU+axzWv5nLGcsSBeKW2YYyTpfPo1hV8YPOmIGnfA4fZHyKVtWqBQ=="], 173 + 174 + "@noble/secp256k1": ["@noble/secp256k1@3.0.0", "", {}, "sha512-NJBaR352KyIvj3t6sgT/+7xrNyF9Xk9QlLSIqUGVUYlsnDTAUqY8LOmwpcgEx4AMJXRITQ5XEVHD+mMaPfr3mg=="], 175 + 176 + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], 177 + 178 + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], 179 + 180 + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], 181 + 182 + "@optique/core": ["@optique/core@0.6.11", "", {}, "sha512-GVLFihzBA1j78NFlkU5N1Lu0jRqET0k6Z66WK8VQKG/a3cxmCInVGSKMIdQG8i6pgC8wD5OizF6Y3QMztmhAxg=="], 183 + 184 + "@optique/run": ["@optique/run@0.6.11", "", { "dependencies": { "@optique/core": "0.6.11" } }, "sha512-tsXBEygGSzNpFK2gjsRlXBn7FiScUeLFWIZNpoAZ8iG85Km0/3K9xgqlQAXoQ+uEZBe4XplnzyCDvmEgbyNT8w=="], 185 + 186 + "@pkgr/core": ["@pkgr/core@0.1.2", "", {}, "sha512-fdDH1LSGfZdTH2sxdpVMw31BanV28K/Gry0cVFxaNP77neJSkd82mM8ErPNYs9e+0O7SdHBLTDzDgwUuy18RnQ=="], 187 + 188 + "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], 189 + 190 + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], 191 + 192 + "@types/codemirror": ["@types/codemirror@5.60.8", "", { "dependencies": { "@types/tern": "*" } }, "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw=="], 193 + 194 + "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], 195 + 196 + "@types/eslint": ["@types/eslint@8.56.2", "", { "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, "sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw=="], 197 + 198 + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], 199 + 200 + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], 201 + 202 + "@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="], 203 + 204 + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], 205 + 206 + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], 207 + 208 + "@types/node": ["@types/node@16.18.126", "", {}, "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw=="], 209 + 210 + "@types/tern": ["@types/tern@0.23.9", "", { "dependencies": { "@types/estree": "*" } }, "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw=="], 211 + 212 + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], 213 + 214 + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.35.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.35.1", "@typescript-eslint/type-utils": "8.35.1", "@typescript-eslint/utils": "8.35.1", "@typescript-eslint/visitor-keys": "8.35.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.35.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg=="], 215 + 216 + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.35.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.35.1", "@typescript-eslint/types": "8.35.1", "@typescript-eslint/typescript-estree": "8.35.1", "@typescript-eslint/visitor-keys": "8.35.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w=="], 217 + 218 + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.35.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.35.1", "@typescript-eslint/types": "^8.35.1", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q=="], 219 + 220 + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.35.1", "", { "dependencies": { "@typescript-eslint/types": "8.35.1", "@typescript-eslint/visitor-keys": "8.35.1" } }, "sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg=="], 221 + 222 + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.35.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ=="], 223 + 224 + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.35.1", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.35.1", "@typescript-eslint/utils": "8.35.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ=="], 225 + 226 + "@typescript-eslint/types": ["@typescript-eslint/types@8.35.1", "", {}, "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ=="], 227 + 228 + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.35.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.35.1", "@typescript-eslint/tsconfig-utils": "8.35.1", "@typescript-eslint/types": "8.35.1", "@typescript-eslint/visitor-keys": "8.35.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g=="], 229 + 230 + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.35.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.35.1", "@typescript-eslint/types": "8.35.1", "@typescript-eslint/typescript-estree": "8.35.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ=="], 231 + 232 + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.35.1", "", { "dependencies": { "@typescript-eslint/types": "8.35.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw=="], 233 + 234 + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], 235 + 236 + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], 237 + 238 + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], 239 + 240 + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], 241 + 242 + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], 243 + 244 + "array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="], 245 + 246 + "array-includes": ["array-includes@3.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.0", "es-object-atoms": "^1.1.1", "get-intrinsic": "^1.3.0", "is-string": "^1.1.1", "math-intrinsics": "^1.1.0" } }, "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ=="], 247 + 248 + "array.prototype.findlast": ["array.prototype.findlast@1.2.5", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ=="], 249 + 250 + "array.prototype.findlastindex": ["array.prototype.findlastindex@1.2.6", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-shim-unscopables": "^1.1.0" } }, "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ=="], 251 + 252 + "array.prototype.flat": ["array.prototype.flat@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg=="], 253 + 254 + "array.prototype.flatmap": ["array.prototype.flatmap@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg=="], 255 + 256 + "array.prototype.tosorted": ["array.prototype.tosorted@1.1.4", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3", "es-errors": "^1.3.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA=="], 257 + 258 + "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], 259 + 260 + "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], 261 + 262 + "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], 263 + 264 + "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], 265 + 266 + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], 267 + 268 + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], 269 + 270 + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], 271 + 272 + "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], 273 + 274 + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], 275 + 276 + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], 277 + 278 + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], 279 + 280 + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], 281 + 282 + "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], 283 + 284 + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], 285 + 286 + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 287 + 288 + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], 289 + 290 + "crelt": ["crelt@1.0.6", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="], 291 + 292 + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], 293 + 294 + "data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="], 295 + 296 + "data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="], 297 + 298 + "data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="], 299 + 300 + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], 301 + 302 + "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], 303 + 304 + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], 305 + 306 + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], 307 + 308 + "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], 309 + 310 + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], 311 + 312 + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], 313 + 314 + "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], 315 + 316 + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], 317 + 318 + "empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="], 319 + 320 + "enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="], 321 + 322 + "es-abstract": ["es-abstract@1.24.1", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw=="], 323 + 324 + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], 325 + 326 + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], 327 + 328 + "es-iterator-helpers": ["es-iterator-helpers@1.2.2", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.1", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "safe-array-concat": "^1.1.3" } }, "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w=="], 329 + 330 + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], 331 + 332 + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], 333 + 334 + "es-shim-unscopables": ["es-shim-unscopables@1.1.0", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw=="], 335 + 336 + "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], 337 + 338 + "esbuild": ["esbuild@0.25.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.5", "@esbuild/android-arm": "0.25.5", "@esbuild/android-arm64": "0.25.5", "@esbuild/android-x64": "0.25.5", "@esbuild/darwin-arm64": "0.25.5", "@esbuild/darwin-x64": "0.25.5", "@esbuild/freebsd-arm64": "0.25.5", "@esbuild/freebsd-x64": "0.25.5", "@esbuild/linux-arm": "0.25.5", "@esbuild/linux-arm64": "0.25.5", "@esbuild/linux-ia32": "0.25.5", "@esbuild/linux-loong64": "0.25.5", "@esbuild/linux-mips64el": "0.25.5", "@esbuild/linux-ppc64": "0.25.5", "@esbuild/linux-riscv64": "0.25.5", "@esbuild/linux-s390x": "0.25.5", "@esbuild/linux-x64": "0.25.5", "@esbuild/netbsd-arm64": "0.25.5", "@esbuild/netbsd-x64": "0.25.5", "@esbuild/openbsd-arm64": "0.25.5", "@esbuild/openbsd-x64": "0.25.5", "@esbuild/sunos-x64": "0.25.5", "@esbuild/win32-arm64": "0.25.5", "@esbuild/win32-ia32": "0.25.5", "@esbuild/win32-x64": "0.25.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ=="], 339 + 340 + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], 341 + 342 + "eslint": ["eslint@9.39.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw=="], 343 + 344 + "eslint-compat-utils": ["eslint-compat-utils@0.5.1", "", { "dependencies": { "semver": "^7.5.4" }, "peerDependencies": { "eslint": ">=6.0.0" } }, "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q=="], 345 + 346 + "eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.9", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", "resolve": "^1.22.4" } }, "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g=="], 347 + 348 + "eslint-module-utils": ["eslint-module-utils@2.12.1", "", { "dependencies": { "debug": "^3.2.7" } }, "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw=="], 349 + 350 + "eslint-plugin-depend": ["eslint-plugin-depend@1.3.1", "", { "dependencies": { "empathic": "^2.0.0", "module-replacements": "^2.8.0", "semver": "^7.6.3" } }, "sha512-1uo2rFAr9vzNrCYdp7IBZRB54LiyVxfaIso0R6/QV3t6Dax6DTbW/EV2Hktf0f4UtmGHK8UyzJWI382pwW04jw=="], 351 + 352 + "eslint-plugin-es-x": ["eslint-plugin-es-x@7.8.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.1.2", "@eslint-community/regexpp": "^4.11.0", "eslint-compat-utils": "^0.5.1" }, "peerDependencies": { "eslint": ">=8" } }, "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ=="], 353 + 354 + "eslint-plugin-import": ["eslint-plugin-import@2.32.0", "", { "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", "array.prototype.findlastindex": "^1.2.6", "array.prototype.flat": "^1.3.3", "array.prototype.flatmap": "^1.3.3", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", "eslint-module-utils": "^2.12.1", "hasown": "^2.0.2", "is-core-module": "^2.16.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", "object.values": "^1.2.1", "semver": "^6.3.1", "string.prototype.trimend": "^1.0.9", "tsconfig-paths": "^3.15.0" }, "peerDependencies": { "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA=="], 355 + 356 + "eslint-plugin-json-schema-validator": ["eslint-plugin-json-schema-validator@5.1.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.3.0", "ajv": "^8.0.0", "debug": "^4.3.1", "eslint-compat-utils": "^0.5.0", "json-schema-migrate": "^2.0.0", "jsonc-eslint-parser": "^2.0.0", "minimatch": "^8.0.0", "synckit": "^0.9.0", "toml-eslint-parser": "^0.9.0", "tunnel-agent": "^0.6.0", "yaml-eslint-parser": "^1.0.0" }, "peerDependencies": { "eslint": ">=6.0.0" } }, "sha512-ZmVyxRIjm58oqe2kTuy90PpmZPrrKvOjRPXKzq8WCgRgAkidCgm5X8domL2KSfadZ3QFAmifMgGTcVNhZ5ez2g=="], 357 + 358 + "eslint-plugin-n": ["eslint-plugin-n@17.10.3", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "enhanced-resolve": "^5.17.0", "eslint-plugin-es-x": "^7.5.0", "get-tsconfig": "^4.7.0", "globals": "^15.8.0", "ignore": "^5.2.4", "minimatch": "^9.0.5", "semver": "^7.5.3" }, "peerDependencies": { "eslint": ">=8.23.0" } }, "sha512-ySZBfKe49nQZWR1yFaA0v/GsH6Fgp8ah6XV0WDz6CN8WO0ek4McMzb7A2xnf4DCYV43frjCygvb9f/wx7UUxRw=="], 359 + 360 + "eslint-plugin-obsidianmd": ["eslint-plugin-obsidianmd@0.1.9", "", { "dependencies": { "@microsoft/eslint-plugin-sdl": "^1.1.0", "@types/eslint": "8.56.2", "@types/node": "20.12.12", "eslint": ">=9.0.0 <10.0.0", "eslint-plugin-depend": "1.3.1", "eslint-plugin-import": "^2.31.0", "eslint-plugin-json-schema-validator": "5.1.0", "eslint-plugin-security": "2.1.1", "globals": "14.0.0", "obsidian": "1.8.7", "typescript": "5.4.5" }, "peerDependencies": { "@eslint/js": "^9.30.1", "@eslint/json": "0.14.0", "typescript-eslint": "^8.35.1" }, "bin": { "eslint-plugin-obsidian": "dist/lib/index.js" } }, "sha512-/gyo5vky3Y7re4BtT/8MQbHU5Wes4o6VRqas3YmXE7aTCnMsdV0kfzV1GDXJN9Hrsc9UQPoeKUMiapKL0aGE4g=="], 361 + 362 + "eslint-plugin-react": ["eslint-plugin-react@7.37.3", "", { "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.8", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "sha512-DomWuTQPFYZwF/7c9W2fkKkStqZmBd3uugfqBYLdkZ3Hii23WzZuOLUskGxB8qkSKqftxEeGL1TB2kMhrce0jA=="], 363 + 364 + "eslint-plugin-security": ["eslint-plugin-security@2.1.1", "", { "dependencies": { "safe-regex": "^2.1.1" } }, "sha512-7cspIGj7WTfR3EhaILzAPcfCo5R9FbeWvbgsPYWivSurTBKW88VQxtP3c4aWMG9Hz/GfJlJVdXEJ3c8LqS+u2w=="], 365 + 366 + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], 367 + 368 + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], 369 + 370 + "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], 371 + 372 + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], 373 + 374 + "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], 375 + 376 + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], 377 + 378 + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], 379 + 380 + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], 381 + 382 + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], 383 + 384 + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], 385 + 386 + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], 387 + 388 + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], 389 + 390 + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], 391 + 392 + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], 393 + 394 + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], 395 + 396 + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], 397 + 398 + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], 399 + 400 + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], 401 + 402 + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], 403 + 404 + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], 405 + 406 + "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], 407 + 408 + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], 409 + 410 + "function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="], 411 + 412 + "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="], 413 + 414 + "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], 415 + 416 + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], 417 + 418 + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], 419 + 420 + "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="], 421 + 422 + "get-tsconfig": ["get-tsconfig@4.13.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w=="], 423 + 424 + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], 425 + 426 + "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], 427 + 428 + "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], 429 + 430 + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], 431 + 432 + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], 433 + 434 + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], 435 + 436 + "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], 437 + 438 + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], 439 + 440 + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], 441 + 442 + "has-proto": ["has-proto@1.2.0", "", { "dependencies": { "dunder-proto": "^1.0.0" } }, "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ=="], 443 + 444 + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], 445 + 446 + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], 447 + 448 + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], 449 + 450 + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], 451 + 452 + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], 453 + 454 + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], 455 + 456 + "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], 457 + 458 + "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="], 459 + 460 + "is-async-function": ["is-async-function@2.1.1", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="], 461 + 462 + "is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="], 463 + 464 + "is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="], 465 + 466 + "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], 467 + 468 + "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], 469 + 470 + "is-data-view": ["is-data-view@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" } }, "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw=="], 471 + 472 + "is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="], 473 + 474 + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], 475 + 476 + "is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="], 477 + 478 + "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="], 479 + 480 + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], 481 + 482 + "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], 483 + 484 + "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="], 485 + 486 + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], 487 + 488 + "is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="], 489 + 490 + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], 491 + 492 + "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], 493 + 494 + "is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="], 495 + 496 + "is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="], 497 + 498 + "is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="], 499 + 500 + "is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="], 501 + 502 + "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], 503 + 504 + "is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="], 505 + 506 + "is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="], 507 + 508 + "is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="], 509 + 510 + "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], 511 + 512 + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], 513 + 514 + "iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="], 515 + 516 + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], 517 + 518 + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], 519 + 520 + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], 521 + 522 + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], 523 + 524 + "json-schema-migrate": ["json-schema-migrate@2.0.0", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-r38SVTtojDRp4eD6WsCqiE0eNDt4v1WalBXb9cyZYw9ai5cGtBwzRNWjHzJl38w6TxFkXAIA7h+fyX3tnrAFhQ=="], 525 + 526 + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], 527 + 528 + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], 529 + 530 + "json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], 531 + 532 + "jsonc-eslint-parser": ["jsonc-eslint-parser@2.4.2", "", { "dependencies": { "acorn": "^8.5.0", "eslint-visitor-keys": "^3.0.0", "espree": "^9.0.0", "semver": "^7.3.5" } }, "sha512-1e4qoRgnn448pRuMvKGsFFymUCquZV0mpGgOyIKNgD3JVDTsVJyRBGH/Fm0tBb8WsWGgmB1mDe6/yJMQM37DUA=="], 533 + 534 + "jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="], 535 + 536 + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], 537 + 538 + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], 539 + 540 + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], 541 + 542 + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], 543 + 544 + "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], 545 + 546 + "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], 547 + 548 + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], 549 + 550 + "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], 551 + 552 + "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="], 553 + 554 + "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="], 555 + 556 + "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], 557 + 558 + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], 559 + 560 + "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], 561 + 562 + "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], 563 + 564 + "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], 565 + 566 + "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], 567 + 568 + "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="], 569 + 570 + "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], 571 + 572 + "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], 573 + 574 + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], 575 + 576 + "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], 577 + 578 + "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], 579 + 580 + "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], 581 + 582 + "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], 583 + 584 + "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="], 585 + 586 + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], 587 + 588 + "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], 589 + 590 + "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], 591 + 592 + "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], 593 + 594 + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], 595 + 596 + "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], 597 + 598 + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], 599 + 600 + "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], 601 + 602 + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], 603 + 604 + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], 605 + 606 + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], 607 + 608 + "module-replacements": ["module-replacements@2.11.0", "", {}, "sha512-j5sNQm3VCpQQ7nTqGeOZtoJtV3uKERgCBm9QRhmGRiXiqkf7iRFOkfxdJRZWLkqYY8PNf4cDQF/WfXUYLENrRA=="], 609 + 610 + "moment": ["moment@2.29.4", "", {}, "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="], 611 + 612 + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 613 + 614 + "nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], 615 + 616 + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], 617 + 618 + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], 619 + 620 + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], 621 + 622 + "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], 623 + 624 + "object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="], 625 + 626 + "object.entries": ["object.entries@1.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-object-atoms": "^1.1.1" } }, "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw=="], 627 + 628 + "object.fromentries": ["object.fromentries@2.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0" } }, "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ=="], 629 + 630 + "object.groupby": ["object.groupby@1.0.3", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2" } }, "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ=="], 631 + 632 + "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="], 633 + 634 + "obsidian": ["obsidian@1.11.4", "", { "dependencies": { "@types/codemirror": "5.60.8", "moment": "2.29.4" }, "peerDependencies": { "@codemirror/state": "6.5.0", "@codemirror/view": "6.38.6" } }, "sha512-n0KD3S+VndgaByrEtEe8NELy0ya6/s+KZ7OcxA6xOm5NN4thxKpQjo6eqEudHEvfGCeT/TYToAKJzitQ1I3XTg=="], 635 + 636 + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], 637 + 638 + "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], 639 + 640 + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], 641 + 642 + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], 643 + 644 + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], 645 + 646 + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], 647 + 648 + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], 649 + 650 + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], 651 + 652 + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 653 + 654 + "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], 655 + 656 + "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], 657 + 658 + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], 659 + 660 + "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], 661 + 662 + "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], 663 + 664 + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], 665 + 666 + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], 667 + 668 + "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], 669 + 670 + "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], 671 + 672 + "regexp-tree": ["regexp-tree@0.1.27", "", { "bin": { "regexp-tree": "bin/regexp-tree" } }, "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA=="], 673 + 674 + "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="], 675 + 676 + "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], 677 + 678 + "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], 679 + 680 + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], 681 + 682 + "resolve": ["resolve@2.0.0-next.5", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA=="], 683 + 684 + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], 685 + 686 + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], 687 + 688 + "ret": ["ret@0.1.15", "", {}, "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="], 689 + 690 + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], 691 + 692 + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], 693 + 694 + "safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="], 695 + 696 + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], 697 + 698 + "safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="], 699 + 700 + "safe-regex": ["safe-regex@2.1.1", "", { "dependencies": { "regexp-tree": "~0.1.1" } }, "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A=="], 701 + 702 + "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], 703 + 704 + "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], 705 + 706 + "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], 707 + 708 + "set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="], 709 + 710 + "set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="], 711 + 712 + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], 713 + 714 + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], 715 + 716 + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], 717 + 718 + "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], 719 + 720 + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], 721 + 722 + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], 723 + 724 + "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], 725 + 726 + "string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="], 727 + 728 + "string.prototype.repeat": ["string.prototype.repeat@1.0.0", "", { "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" } }, "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w=="], 729 + 730 + "string.prototype.trim": ["string.prototype.trim@1.2.10", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-object-atoms": "^1.0.0", "has-property-descriptors": "^1.0.2" } }, "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA=="], 731 + 732 + "string.prototype.trimend": ["string.prototype.trimend@1.0.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ=="], 733 + 734 + "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="], 735 + 736 + "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], 737 + 738 + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], 739 + 740 + "style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="], 741 + 742 + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], 743 + 744 + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], 745 + 746 + "synckit": ["synckit@0.9.3", "", { "dependencies": { "@pkgr/core": "^0.1.0", "tslib": "^2.6.2" } }, "sha512-JJoOEKTfL1urb1mDoEblhD9NhEbWmq9jHEMEnxoC4ujUaZ4itA8vKgwkFAyNClgxplLi9tsUKX+EduK0p/l7sg=="], 747 + 748 + "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], 749 + 750 + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], 751 + 752 + "toml-eslint-parser": ["toml-eslint-parser@0.9.3", "", { "dependencies": { "eslint-visitor-keys": "^3.0.0" } }, "sha512-moYoCvkNUAPCxSW9jmHmRElhm4tVJpHL8ItC/+uYD0EpPSFXbck7yREz9tNdJVTSpHVod8+HoipcpbQ0oE6gsw=="], 753 + 754 + "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], 755 + 756 + "ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="], 757 + 758 + "tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="], 759 + 760 + "tslib": ["tslib@2.4.0", "", {}, "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="], 761 + 762 + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], 763 + 764 + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], 765 + 766 + "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], 767 + 768 + "typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="], 769 + 770 + "typed-array-byte-offset": ["typed-array-byte-offset@1.0.4", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.15", "reflect.getprototypeof": "^1.0.9" } }, "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ=="], 771 + 772 + "typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="], 773 + 774 + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], 775 + 776 + "typescript-eslint": ["typescript-eslint@8.35.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.35.1", "@typescript-eslint/parser": "8.35.1", "@typescript-eslint/utils": "8.35.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-xslJjFzhOmHYQzSB/QTeASAHbjmxOGEP6Coh93TXmUBFQoJ1VU35UHIDmG06Jd6taf3wqqC1ntBnCMeymy5Ovw=="], 777 + 778 + "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], 779 + 780 + "undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], 781 + 782 + "unicode-segmenter": ["unicode-segmenter@0.14.5", "", {}, "sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g=="], 783 + 784 + "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], 785 + 786 + "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], 787 + 788 + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], 789 + 790 + "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], 791 + 792 + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], 793 + 794 + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], 795 + 796 + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], 797 + 798 + "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], 799 + 800 + "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="], 801 + 802 + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], 803 + 804 + "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="], 805 + 806 + "which-builtin-type": ["which-builtin-type@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.1.0", "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", "which-typed-array": "^1.1.16" } }, "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q=="], 807 + 808 + "which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="], 809 + 810 + "which-typed-array": ["which-typed-array@1.1.20", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg=="], 811 + 812 + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], 813 + 814 + "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], 815 + 816 + "yaml-eslint-parser": ["yaml-eslint-parser@1.3.2", "", { "dependencies": { "eslint-visitor-keys": "^3.0.0", "yaml": "^2.0.0" } }, "sha512-odxVsHAkZYYglR30aPYRY4nUGJnoJ2y1ww2HDvZALo0BDETv9kWbi16J52eHs+PWRNmF4ub6nZqfVOeesOvntg=="], 817 + 818 + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], 819 + 820 + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], 821 + 822 + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], 823 + 824 + "@microsoft/eslint-plugin-sdl/eslint-plugin-security": ["eslint-plugin-security@1.4.0", "", { "dependencies": { "safe-regex": "^1.1.0" } }, "sha512-xlS7P2PLMXeqfhyf3NpqbvbnW04kN8M9NtmhpR3XGyOvt/vNKS7XPXT5EDbwKW9vCjWH4PpfQvgD/+JgN0VJKA=="], 825 + 826 + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], 827 + 828 + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], 829 + 830 + "eslint/@eslint/js": ["@eslint/js@9.39.2", "", {}, "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA=="], 831 + 832 + "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], 833 + 834 + "eslint-import-resolver-node/resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], 835 + 836 + "eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], 837 + 838 + "eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], 839 + 840 + "eslint-plugin-import/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 841 + 842 + "eslint-plugin-json-schema-validator/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], 843 + 844 + "eslint-plugin-json-schema-validator/minimatch": ["minimatch@8.0.4", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA=="], 845 + 846 + "eslint-plugin-n/globals": ["globals@15.15.0", "", {}, "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg=="], 847 + 848 + "eslint-plugin-n/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], 849 + 850 + "eslint-plugin-obsidianmd/@types/node": ["@types/node@20.12.12", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw=="], 851 + 852 + "eslint-plugin-obsidianmd/obsidian": ["obsidian@1.8.7", "", { "dependencies": { "@types/codemirror": "5.60.8", "moment": "2.29.4" }, "peerDependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" } }, "sha512-h4bWwNFAGRXlMlMAzdEiIM2ppTGlrh7uGOJS6w4gClrsjc+ei/3YAtU2VdFUlCiPuTHpY4aBpFJJW75S1Tl/JA=="], 853 + 854 + "eslint-plugin-obsidianmd/typescript": ["typescript@5.4.5", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ=="], 855 + 856 + "eslint-plugin-react/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 857 + 858 + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], 859 + 860 + "json-schema-migrate/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], 861 + 862 + "jsonc-eslint-parser/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], 863 + 864 + "jsonc-eslint-parser/espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="], 865 + 866 + "synckit/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 867 + 868 + "toml-eslint-parser/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], 869 + 870 + "yaml-eslint-parser/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], 871 + 872 + "@microsoft/eslint-plugin-sdl/eslint-plugin-security/safe-regex": ["safe-regex@1.1.0", "", { "dependencies": { "ret": "~0.1.10" } }, "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg=="], 873 + 874 + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], 875 + 876 + "eslint-plugin-json-schema-validator/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], 877 + 878 + "eslint-plugin-json-schema-validator/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], 879 + 880 + "eslint-plugin-n/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], 881 + 882 + "json-schema-migrate/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], 883 + } 884 + }
+49
esbuild.config.mjs
··· 1 + import esbuild from "esbuild"; 2 + import process from "process"; 3 + import { builtinModules } from 'node:module'; 4 + 5 + const banner = 6 + `/* 7 + THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 8 + if you want to view the source, please visit the github repository of this plugin 9 + */ 10 + `; 11 + 12 + const prod = (process.argv[2] === "production"); 13 + 14 + const context = await esbuild.context({ 15 + banner: { 16 + js: banner, 17 + }, 18 + entryPoints: ["src/main.ts"], 19 + bundle: true, 20 + external: [ 21 + "obsidian", 22 + "electron", 23 + "@codemirror/autocomplete", 24 + "@codemirror/collab", 25 + "@codemirror/commands", 26 + "@codemirror/language", 27 + "@codemirror/lint", 28 + "@codemirror/search", 29 + "@codemirror/state", 30 + "@codemirror/view", 31 + "@lezer/common", 32 + "@lezer/highlight", 33 + "@lezer/lr", 34 + ...builtinModules], 35 + format: "cjs", 36 + target: "es2018", 37 + logLevel: "info", 38 + sourcemap: prod ? false : "inline", 39 + treeShaking: true, 40 + outfile: "main.js", 41 + minify: prod, 42 + }); 43 + 44 + if (prod) { 45 + await context.rebuild(); 46 + process.exit(0); 47 + } else { 48 + await context.watch(); 49 + }
+36
eslint.config.mts
··· 1 + import tseslint from 'typescript-eslint'; 2 + import obsidianmd from "eslint-plugin-obsidianmd"; 3 + import globals from "globals"; 4 + import { globalIgnores } from "eslint/config"; 5 + 6 + export default tseslint.config( 7 + { 8 + languageOptions: { 9 + globals: { 10 + ...globals.browser, 11 + }, 12 + parserOptions: { 13 + projectService: { 14 + allowDefaultProject: [ 15 + 'eslint.config.js', 16 + 'manifest.json' 17 + ] 18 + }, 19 + tsconfigRootDir: import.meta.dirname, 20 + extraFileExtensions: ['.json'] 21 + }, 22 + }, 23 + }, 24 + ...obsidianmd.configs.recommended, 25 + globalIgnores([ 26 + "node_modules", 27 + "dist", 28 + "esbuild.config.mjs", 29 + "eslint.config.js", 30 + "version-bump.mjs", 31 + "versions.json", 32 + "main.js", 33 + "lex.config.js", 34 + "src/lexicons", 35 + ]), 36 + );
+44
lex.config.js
··· 1 + // file: lex.config.js 2 + import { defineLexiconConfig } from '@atcute/lex-cli'; 3 + 4 + export default defineLexiconConfig({ 5 + files: ['lexicons/**/*.json'], 6 + outdir: 'src/lexicons/', 7 + pull: { 8 + outdir: 'lexicons/', 9 + clean: false, 10 + sources: [ 11 + { 12 + type: 'git', 13 + remote: 'https://github.com/cosmik-network/semble.git', 14 + ref: 'main', 15 + pattern: ['src/modules/atproto/infrastructure/lexicons/**/*.json'], 16 + }, 17 + { 18 + type: 'git', 19 + remote: 'https://tangled.org/margin.at/margin.git', 20 + ref: 'main', 21 + pattern: ['lexicons/**/*.json'], 22 + }, 23 + // { 24 + // type: 'git', 25 + // remote: 'https://tangled.org/leaflet.pub/leaflet.git', 26 + // ref: 'main', 27 + // pattern: ['lexicons/**/*.json'], 28 + // }, 29 + // { 30 + // type: 'atproto', 31 + // mode: 'nsids', 32 + // nsids: [ 33 + // 'site.standard.publication', 34 + // 'site.standard.document', 35 + // 'site.standard.theme.basic', 36 + // 'site.standard.theme.color', 37 + // 'pub.leaflet.document', 38 + // 'pub.leaflet.canvas', 39 + // 40 + // ], 41 + // }, 42 + ], 43 + }, 44 + });
+8
lexicons/README.md
··· 1 + # lexicon sources 2 + 3 + this directory contains lexicon documents pulled from the following sources: 4 + 5 + - https://github.com/cosmik-network/semble.git (ref: main) 6 + - commit: 0c1ee36649c20c0713a0ad0fd20209c0154b8550 7 + - https://tangled.org/margin.at/margin.git (ref: main) 8 + - commit: ac7f76cb2ce7d27087825178d95e8d267b524d88
+273
lexicons/at/margin/annotation.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "at.margin.annotation", 4 + "revision": 2, 5 + "description": "W3C Web Annotation Data Model compliant annotation record for ATProto", 6 + "defs": { 7 + "main": { 8 + "type": "record", 9 + "description": "A W3C-compliant web annotation stored on the AT Protocol", 10 + "key": "tid", 11 + "record": { 12 + "type": "object", 13 + "required": ["target", "createdAt"], 14 + "properties": { 15 + "motivation": { 16 + "type": "string", 17 + "description": "W3C motivation for the annotation", 18 + "knownValues": [ 19 + "commenting", 20 + "highlighting", 21 + "bookmarking", 22 + "tagging", 23 + "describing", 24 + "linking", 25 + "replying", 26 + "editing", 27 + "questioning", 28 + "assessing" 29 + ] 30 + }, 31 + "body": { 32 + "type": "ref", 33 + "ref": "#body", 34 + "description": "The annotation content (text or reference)" 35 + }, 36 + "target": { 37 + "type": "ref", 38 + "ref": "#target", 39 + "description": "The resource being annotated with optional selector" 40 + }, 41 + "tags": { 42 + "type": "array", 43 + "description": "Tags for categorization", 44 + "items": { 45 + "type": "string", 46 + "maxLength": 64, 47 + "maxGraphemes": 32 48 + }, 49 + "maxLength": 10 50 + }, 51 + "createdAt": { 52 + "type": "string", 53 + "format": "datetime" 54 + } 55 + } 56 + } 57 + }, 58 + "body": { 59 + "type": "object", 60 + "description": "Annotation body - the content of the annotation", 61 + "properties": { 62 + "value": { 63 + "type": "string", 64 + "maxLength": 10000, 65 + "maxGraphemes": 3000, 66 + "description": "Text content of the annotation" 67 + }, 68 + "format": { 69 + "type": "string", 70 + "description": "MIME type of the body content", 71 + "default": "text/plain" 72 + }, 73 + "language": { 74 + "type": "string", 75 + "description": "BCP47 language tag" 76 + }, 77 + "uri": { 78 + "type": "string", 79 + "format": "uri", 80 + "description": "Reference to external body content" 81 + } 82 + } 83 + }, 84 + "target": { 85 + "type": "object", 86 + "description": "W3C SpecificResource - the target with optional selector", 87 + "required": ["source"], 88 + "properties": { 89 + "source": { 90 + "type": "string", 91 + "format": "uri", 92 + "description": "The URL being annotated" 93 + }, 94 + "sourceHash": { 95 + "type": "string", 96 + "description": "SHA256 hash of normalized URL for indexing" 97 + }, 98 + "title": { 99 + "type": "string", 100 + "maxLength": 500, 101 + "description": "Page title at time of annotation" 102 + }, 103 + "selector": { 104 + "type": "union", 105 + "description": "Selector to identify the specific segment", 106 + "refs": [ 107 + "#textQuoteSelector", 108 + "#textPositionSelector", 109 + "#cssSelector", 110 + "#xpathSelector", 111 + "#fragmentSelector", 112 + "#rangeSelector" 113 + ] 114 + }, 115 + "state": { 116 + "type": "ref", 117 + "ref": "#timeState", 118 + "description": "State of the resource at annotation time" 119 + } 120 + } 121 + }, 122 + "textQuoteSelector": { 123 + "type": "object", 124 + "description": "W3C TextQuoteSelector - select text by quoting it with context", 125 + "required": ["exact"], 126 + "properties": { 127 + "type": { 128 + "type": "string", 129 + "const": "TextQuoteSelector" 130 + }, 131 + "exact": { 132 + "type": "string", 133 + "maxLength": 5000, 134 + "maxGraphemes": 1500, 135 + "description": "The exact text to match" 136 + }, 137 + "prefix": { 138 + "type": "string", 139 + "maxLength": 500, 140 + "maxGraphemes": 150, 141 + "description": "Text immediately before the selection" 142 + }, 143 + "suffix": { 144 + "type": "string", 145 + "maxLength": 500, 146 + "maxGraphemes": 150, 147 + "description": "Text immediately after the selection" 148 + } 149 + } 150 + }, 151 + "textPositionSelector": { 152 + "type": "object", 153 + "description": "W3C TextPositionSelector - select by character offsets", 154 + "required": ["start", "end"], 155 + "properties": { 156 + "type": { 157 + "type": "string", 158 + "const": "TextPositionSelector" 159 + }, 160 + "start": { 161 + "type": "integer", 162 + "minimum": 0, 163 + "description": "Starting character position (0-indexed, inclusive)" 164 + }, 165 + "end": { 166 + "type": "integer", 167 + "minimum": 0, 168 + "description": "Ending character position (exclusive)" 169 + } 170 + } 171 + }, 172 + "cssSelector": { 173 + "type": "object", 174 + "description": "W3C CssSelector - select DOM elements by CSS selector", 175 + "required": ["value"], 176 + "properties": { 177 + "type": { 178 + "type": "string", 179 + "const": "CssSelector" 180 + }, 181 + "value": { 182 + "type": "string", 183 + "maxLength": 2000, 184 + "description": "CSS selector string" 185 + } 186 + } 187 + }, 188 + "xpathSelector": { 189 + "type": "object", 190 + "description": "W3C XPathSelector - select by XPath expression", 191 + "required": ["value"], 192 + "properties": { 193 + "type": { 194 + "type": "string", 195 + "const": "XPathSelector" 196 + }, 197 + "value": { 198 + "type": "string", 199 + "maxLength": 2000, 200 + "description": "XPath expression" 201 + } 202 + } 203 + }, 204 + "fragmentSelector": { 205 + "type": "object", 206 + "description": "W3C FragmentSelector - select by URI fragment", 207 + "required": ["value"], 208 + "properties": { 209 + "type": { 210 + "type": "string", 211 + "const": "FragmentSelector" 212 + }, 213 + "value": { 214 + "type": "string", 215 + "maxLength": 1000, 216 + "description": "Fragment identifier value" 217 + }, 218 + "conformsTo": { 219 + "type": "string", 220 + "format": "uri", 221 + "description": "Specification the fragment conforms to" 222 + } 223 + } 224 + }, 225 + "rangeSelector": { 226 + "type": "object", 227 + "description": "W3C RangeSelector - select range between two selectors", 228 + "required": ["startSelector", "endSelector"], 229 + "properties": { 230 + "type": { 231 + "type": "string", 232 + "const": "RangeSelector" 233 + }, 234 + "startSelector": { 235 + "type": "union", 236 + "description": "Selector for range start", 237 + "refs": [ 238 + "#textQuoteSelector", 239 + "#textPositionSelector", 240 + "#cssSelector", 241 + "#xpathSelector" 242 + ] 243 + }, 244 + "endSelector": { 245 + "type": "union", 246 + "description": "Selector for range end", 247 + "refs": [ 248 + "#textQuoteSelector", 249 + "#textPositionSelector", 250 + "#cssSelector", 251 + "#xpathSelector" 252 + ] 253 + } 254 + } 255 + }, 256 + "timeState": { 257 + "type": "object", 258 + "description": "W3C TimeState - record when content was captured", 259 + "properties": { 260 + "sourceDate": { 261 + "type": "string", 262 + "format": "datetime", 263 + "description": "When the source was accessed" 264 + }, 265 + "cached": { 266 + "type": "string", 267 + "format": "uri", 268 + "description": "URL to cached/archived version" 269 + } 270 + } 271 + } 272 + } 273 + }
+30
lexicons/at/margin/authFull.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "at.margin.authFull", 4 + "defs": { 5 + "main": { 6 + "type": "permission-set", 7 + "title": "Margin", 8 + "title:langs": {}, 9 + "detail": "Full access to Margin features including annotations, highlights, bookmarks, and collections.", 10 + "detail:langs": {}, 11 + "permissions": [ 12 + { 13 + "type": "permission", 14 + "resource": "repo", 15 + "action": ["create", "update", "delete"], 16 + "collection": [ 17 + "at.margin.annotation", 18 + "at.margin.highlight", 19 + "at.margin.bookmark", 20 + "at.margin.reply", 21 + "at.margin.like", 22 + "at.margin.collection", 23 + "at.margin.collectionItem", 24 + "at.margin.profile" 25 + ] 26 + } 27 + ] 28 + } 29 + } 30 + }
+52
lexicons/at/margin/bookmark.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "at.margin.bookmark", 4 + "description": "A bookmark record - save URL for later", 5 + "defs": { 6 + "main": { 7 + "type": "record", 8 + "description": "A bookmarked URL (motivation: bookmarking)", 9 + "key": "tid", 10 + "record": { 11 + "type": "object", 12 + "required": ["source", "createdAt"], 13 + "properties": { 14 + "source": { 15 + "type": "string", 16 + "format": "uri", 17 + "description": "The bookmarked URL" 18 + }, 19 + "sourceHash": { 20 + "type": "string", 21 + "description": "SHA256 hash of normalized URL for indexing" 22 + }, 23 + "title": { 24 + "type": "string", 25 + "maxLength": 500, 26 + "description": "Page title" 27 + }, 28 + "description": { 29 + "type": "string", 30 + "maxLength": 1000, 31 + "maxGraphemes": 300, 32 + "description": "Optional description/note" 33 + }, 34 + "tags": { 35 + "type": "array", 36 + "description": "Tags for categorization", 37 + "items": { 38 + "type": "string", 39 + "maxLength": 64, 40 + "maxGraphemes": 32 41 + }, 42 + "maxLength": 10 43 + }, 44 + "createdAt": { 45 + "type": "string", 46 + "format": "datetime" 47 + } 48 + } 49 + } 50 + } 51 + } 52 + }
+40
lexicons/at/margin/collection.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "at.margin.collection", 4 + "description": "A collection of annotations (like a folder or notebook)", 5 + "defs": { 6 + "main": { 7 + "type": "record", 8 + "description": "A named collection for organizing annotations", 9 + "key": "tid", 10 + "record": { 11 + "type": "object", 12 + "required": ["name", "createdAt"], 13 + "properties": { 14 + "name": { 15 + "type": "string", 16 + "maxLength": 100, 17 + "maxGraphemes": 50, 18 + "description": "Collection name" 19 + }, 20 + "description": { 21 + "type": "string", 22 + "maxLength": 500, 23 + "maxGraphemes": 150, 24 + "description": "Collection description" 25 + }, 26 + "icon": { 27 + "type": "string", 28 + "maxLength": 100, 29 + "maxGraphemes": 100, 30 + "description": "Emoji icon or icon identifier for the collection" 31 + }, 32 + "createdAt": { 33 + "type": "string", 34 + "format": "datetime" 35 + } 36 + } 37 + } 38 + } 39 + } 40 + }
+37
lexicons/at/margin/collectionItem.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "at.margin.collectionItem", 4 + "description": "An item in a collection (links annotation to collection)", 5 + "defs": { 6 + "main": { 7 + "type": "record", 8 + "description": "Associates an annotation with a collection", 9 + "key": "tid", 10 + "record": { 11 + "type": "object", 12 + "required": ["collection", "annotation", "createdAt"], 13 + "properties": { 14 + "collection": { 15 + "type": "string", 16 + "format": "at-uri", 17 + "description": "AT URI of the collection" 18 + }, 19 + "annotation": { 20 + "type": "string", 21 + "format": "at-uri", 22 + "description": "AT URI of the annotation, highlight, or bookmark" 23 + }, 24 + "position": { 25 + "type": "integer", 26 + "minimum": 0, 27 + "description": "Sort order within the collection" 28 + }, 29 + "createdAt": { 30 + "type": "string", 31 + "format": "datetime" 32 + } 33 + } 34 + } 35 + } 36 + } 37 + }
+42
lexicons/at/margin/highlight.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "at.margin.highlight", 4 + "description": "A lightweight highlight record - annotation without body text", 5 + "defs": { 6 + "main": { 7 + "type": "record", 8 + "description": "A highlight on a web page (motivation: highlighting)", 9 + "key": "tid", 10 + "record": { 11 + "type": "object", 12 + "required": ["target", "createdAt"], 13 + "properties": { 14 + "target": { 15 + "type": "ref", 16 + "ref": "at.margin.annotation#target", 17 + "description": "The resource and segment being highlighted" 18 + }, 19 + "color": { 20 + "type": "string", 21 + "description": "Highlight color (hex or named)", 22 + "maxLength": 20 23 + }, 24 + "tags": { 25 + "type": "array", 26 + "description": "Tags for categorization", 27 + "items": { 28 + "type": "string", 29 + "maxLength": 64, 30 + "maxGraphemes": 32 31 + }, 32 + "maxLength": 10 33 + }, 34 + "createdAt": { 35 + "type": "string", 36 + "format": "datetime" 37 + } 38 + } 39 + } 40 + } 41 + } 42 + }
+40
lexicons/at/margin/like.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "at.margin.like", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A like on an annotation or reply", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["subject", "createdAt"], 12 + "properties": { 13 + "subject": { 14 + "type": "ref", 15 + "ref": "#subjectRef", 16 + "description": "Reference to the annotation or reply being liked" 17 + }, 18 + "createdAt": { 19 + "type": "string", 20 + "format": "datetime" 21 + } 22 + } 23 + } 24 + }, 25 + "subjectRef": { 26 + "type": "object", 27 + "required": ["uri", "cid"], 28 + "properties": { 29 + "uri": { 30 + "type": "string", 31 + "format": "at-uri" 32 + }, 33 + "cid": { 34 + "type": "string", 35 + "format": "cid" 36 + } 37 + } 38 + } 39 + } 40 + }
+51
lexicons/at/margin/profile.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "at.margin.profile", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A profile for a user on the Margin network.", 8 + "key": "literal:self", 9 + "record": { 10 + "type": "object", 11 + "required": ["createdAt"], 12 + "properties": { 13 + "displayName": { 14 + "type": "string", 15 + "maxLength": 640, 16 + "description": "Display name for the user." 17 + }, 18 + "avatar": { 19 + "type": "blob", 20 + "accept": ["image/png", "image/jpeg"], 21 + "maxSize": 1000000, 22 + "description": "User avatar image." 23 + }, 24 + "bio": { 25 + "type": "string", 26 + "maxLength": 5000, 27 + "description": "User biography or description." 28 + }, 29 + "website": { 30 + "type": "string", 31 + "maxLength": 1000, 32 + "description": "User website URL." 33 + }, 34 + "links": { 35 + "type": "array", 36 + "description": "List of other relevant links (e.g. GitHub, Bluesky, etc).", 37 + "items": { 38 + "type": "string", 39 + "maxLength": 1000 40 + }, 41 + "maxLength": 20 42 + }, 43 + "createdAt": { 44 + "type": "string", 45 + "format": "datetime" 46 + } 47 + } 48 + } 49 + } 50 + } 51 + }
+59
lexicons/at/margin/reply.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "at.margin.reply", 4 + "revision": 2, 5 + "description": "A reply to an annotation or another reply", 6 + "defs": { 7 + "main": { 8 + "type": "record", 9 + "description": "A reply to an annotation (motivation: replying)", 10 + "key": "tid", 11 + "record": { 12 + "type": "object", 13 + "required": ["parent", "root", "text", "createdAt"], 14 + "properties": { 15 + "parent": { 16 + "type": "ref", 17 + "ref": "#replyRef", 18 + "description": "Reference to the parent annotation or reply" 19 + }, 20 + "root": { 21 + "type": "ref", 22 + "ref": "#replyRef", 23 + "description": "Reference to the root annotation of the thread" 24 + }, 25 + "text": { 26 + "type": "string", 27 + "maxLength": 10000, 28 + "maxGraphemes": 3000, 29 + "description": "Reply text content" 30 + }, 31 + "format": { 32 + "type": "string", 33 + "description": "MIME type of the text content", 34 + "default": "text/plain" 35 + }, 36 + "createdAt": { 37 + "type": "string", 38 + "format": "datetime" 39 + } 40 + } 41 + } 42 + }, 43 + "replyRef": { 44 + "type": "object", 45 + "description": "Strong reference to an annotation or reply", 46 + "required": ["uri", "cid"], 47 + "properties": { 48 + "uri": { 49 + "type": "string", 50 + "format": "at-uri" 51 + }, 52 + "cid": { 53 + "type": "string", 54 + "format": "cid" 55 + } 56 + } 57 + } 58 + } 59 + }
+21
lexicons/com/atproto/repo/strongRef.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.strongRef", 4 + "description": "A URI with a content-hash fingerprint.", 5 + "defs": { 6 + "main": { 7 + "type": "object", 8 + "required": ["uri", "cid"], 9 + "properties": { 10 + "cid": { 11 + "type": "string", 12 + "format": "cid" 13 + }, 14 + "uri": { 15 + "type": "string", 16 + "format": "at-uri" 17 + } 18 + } 19 + } 20 + } 21 + }
+131
lexicons/network/cosmik/card.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "network.cosmik.card", 4 + "description": "A single record type for all cards.", 5 + "defs": { 6 + "main": { 7 + "type": "record", 8 + "description": "A record representing a card with content.", 9 + "key": "tid", 10 + "record": { 11 + "type": "object", 12 + "required": ["type", "content"], 13 + "properties": { 14 + "type": { 15 + "type": "string", 16 + "description": "The type of card", 17 + "knownValues": ["URL", "NOTE"] 18 + }, 19 + "content": { 20 + "type": "union", 21 + "description": "The specific content of the card, determined by the card type.", 22 + "refs": ["#urlContent", "#noteContent"] 23 + }, 24 + "url": { 25 + "type": "string", 26 + "format": "uri", 27 + "description": "Optional URL associated with the card. Required for URL cards, optional for NOTE cards." 28 + }, 29 + "parentCard": { 30 + "type": "ref", 31 + "description": "Optional strong reference to a parent card (for NOTE cards).", 32 + "ref": "com.atproto.repo.strongRef" 33 + }, 34 + "createdAt": { 35 + "type": "string", 36 + "format": "datetime", 37 + "description": "Timestamp when this card was created (usually set by PDS)." 38 + }, 39 + "originalCard": { 40 + "type": "ref", 41 + "description": "Optional strong reference to the original card (for NOTE cards).", 42 + "ref": "com.atproto.repo.strongRef" 43 + }, 44 + "provenance": { 45 + "type": "ref", 46 + "description": "Optional provenance information for this card.", 47 + "ref": "network.cosmik.defs#provenance" 48 + } 49 + } 50 + } 51 + }, 52 + "urlContent": { 53 + "type": "object", 54 + "description": "Content structure for a URL card.", 55 + "required": ["url"], 56 + "properties": { 57 + "url": { 58 + "type": "string", 59 + "format": "uri", 60 + "description": "The URL being saved" 61 + }, 62 + "metadata": { 63 + "type": "ref", 64 + "ref": "#urlMetadata", 65 + "description": "Optional metadata about the URL" 66 + } 67 + } 68 + }, 69 + "noteContent": { 70 + "type": "object", 71 + "description": "Content structure for a note card.", 72 + "required": ["text"], 73 + "properties": { 74 + "text": { 75 + "type": "string", 76 + "description": "The note text content", 77 + "maxLength": 10000 78 + } 79 + } 80 + }, 81 + "urlMetadata": { 82 + "type": "object", 83 + "description": "Metadata about a URL.", 84 + "properties": { 85 + "title": { 86 + "type": "string", 87 + "description": "Title of the page" 88 + }, 89 + "description": { 90 + "type": "string", 91 + "description": "Description of the page" 92 + }, 93 + "author": { 94 + "type": "string", 95 + "description": "Author of the content" 96 + }, 97 + "publishedDate": { 98 + "type": "string", 99 + "format": "datetime", 100 + "description": "When the content was published" 101 + }, 102 + "siteName": { 103 + "type": "string", 104 + "description": "Name of the site" 105 + }, 106 + "imageUrl": { 107 + "type": "string", 108 + "format": "uri", 109 + "description": "URL of a representative image" 110 + }, 111 + "type": { 112 + "type": "string", 113 + "description": "Type of content (e.g., 'video', 'article')" 114 + }, 115 + "retrievedAt": { 116 + "type": "string", 117 + "format": "datetime", 118 + "description": "When the metadata was retrieved" 119 + }, 120 + "doi": { 121 + "type": "string", 122 + "description": "Digital Object Identifier (DOI) for academic content" 123 + }, 124 + "isbn": { 125 + "type": "string", 126 + "description": "International Standard Book Number (ISBN) for books" 127 + } 128 + } 129 + } 130 + } 131 + }
+51
lexicons/network/cosmik/collection.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "network.cosmik.collection", 4 + "description": "A single record type for collections of cards.", 5 + "defs": { 6 + "main": { 7 + "type": "record", 8 + "description": "A record representing a collection of cards.", 9 + "key": "tid", 10 + "record": { 11 + "type": "object", 12 + "required": ["name", "accessType"], 13 + "properties": { 14 + "name": { 15 + "type": "string", 16 + "description": "Name of the collection", 17 + "maxLength": 100 18 + }, 19 + "description": { 20 + "type": "string", 21 + "description": "Description of the collection", 22 + "maxLength": 500 23 + }, 24 + "accessType": { 25 + "type": "string", 26 + "description": "Access control for the collection", 27 + "knownValues": ["OPEN", "CLOSED"] 28 + }, 29 + "collaborators": { 30 + "type": "array", 31 + "description": "List of collaborator DIDs who can add cards to closed collections", 32 + "items": { 33 + "type": "string", 34 + "description": "DID of a collaborator" 35 + } 36 + }, 37 + "createdAt": { 38 + "type": "string", 39 + "format": "datetime", 40 + "description": "Timestamp when this collection was created (usually set by PDS)." 41 + }, 42 + "updatedAt": { 43 + "type": "string", 44 + "format": "datetime", 45 + "description": "Timestamp when this collection was last updated." 46 + } 47 + } 48 + } 49 + } 50 + } 51 + }
+52
lexicons/network/cosmik/collectionLink.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "network.cosmik.collectionLink", 4 + "description": "A record that links a card to a collection.", 5 + "defs": { 6 + "main": { 7 + "type": "record", 8 + "description": "A record representing the relationship between a card and a collection.", 9 + "key": "tid", 10 + "record": { 11 + "type": "object", 12 + "required": ["collection", "card", "addedBy", "addedAt"], 13 + "properties": { 14 + "collection": { 15 + "type": "ref", 16 + "description": "Strong reference to the collection record.", 17 + "ref": "com.atproto.repo.strongRef" 18 + }, 19 + "card": { 20 + "type": "ref", 21 + "description": "Strong reference to the card record in the users library.", 22 + "ref": "com.atproto.repo.strongRef" 23 + }, 24 + "originalCard": { 25 + "type": "ref", 26 + "description": "Strong reference to the original card record (may be in another library).", 27 + "ref": "com.atproto.repo.strongRef" 28 + }, 29 + "addedBy": { 30 + "type": "string", 31 + "description": "DID of the user who added the card to the collection" 32 + }, 33 + "addedAt": { 34 + "type": "string", 35 + "format": "datetime", 36 + "description": "Timestamp when the card was added to the collection." 37 + }, 38 + "createdAt": { 39 + "type": "string", 40 + "format": "datetime", 41 + "description": "Timestamp when this link record was created (usually set by PDS)." 42 + }, 43 + "provenance": { 44 + "type": "ref", 45 + "description": "Optional provenance information for this link.", 46 + "ref": "network.cosmik.defs#provenance" 47 + } 48 + } 49 + } 50 + } 51 + } 52 + }
+18
lexicons/network/cosmik/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "network.cosmik.defs", 4 + "description": "Common definitions for annotation types and references", 5 + "defs": { 6 + "provenance": { 7 + "type": "object", 8 + "description": "Represents the provenance or source of a record.", 9 + "properties": { 10 + "via": { 11 + "type": "ref", 12 + "description": "Strong reference to the card that led to this record.", 13 + "ref": "com.atproto.repo.strongRef" 14 + } 15 + } 16 + } 17 + } 18 + }
+10
manifest.json
··· 1 + { 2 + "id": "atmark", 3 + "name": "ATmark", 4 + "version": "0.1.9", 5 + "minAppVersion": "0.15.0", 6 + "description": "View and manage AT Protocol bookmarks.", 7 + "author": "treethought", 8 + "authorUrl": "https://github.com/treethought", 9 + "isDesktopOnly": false 10 + }
+41
package.json
··· 1 + { 2 + "name": "obsidian-atmark", 3 + "version": "0.1.9", 4 + "description": "View and manage AT Protocol bookmarks.", 5 + "main": "main.js", 6 + "type": "module", 7 + "scripts": { 8 + "dev": "node esbuild.config.mjs", 9 + "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", 10 + "version": "node version-bump.mjs && git add manifest.json versions.json", 11 + "lint": "eslint ." 12 + }, 13 + "keywords": [], 14 + "license": "MIT", 15 + "devDependencies": { 16 + "@eslint/js": "9.30.1", 17 + "@types/node": "^16.11.6", 18 + "esbuild": "0.25.5", 19 + "eslint-plugin-obsidianmd": "0.1.9", 20 + "globals": "14.0.0", 21 + "jiti": "2.6.1", 22 + "tslib": "2.4.0", 23 + "typescript": "^5.8.3", 24 + "typescript-eslint": "8.35.1", 25 + "@atcute/lex-cli": "^2.5.3" 26 + }, 27 + "dependencies": { 28 + "@atcute/atproto": "^3.1.10", 29 + "@atcute/bluesky": "^3.2.15", 30 + "@atcute/client": "^4.2.1", 31 + "@atcute/identity-resolver": "^1.2.2", 32 + "@atcute/leaflet": "^1.0.17", 33 + "@atcute/oauth-browser-client": "^2.0.3", 34 + "@atcute/pckt": "^0.1.5", 35 + "@atcute/standard-site": "^1.0.0", 36 + "obsidian": "latest", 37 + "remark-parse": "^11.0.0", 38 + "remark-stringify": "^11.0.0", 39 + "unified": "^11.0.5" 40 + } 41 + }
preview-sidebar.png

This is a binary file and will not be displayed.

preview.png

This is a binary file and will not be displayed.

+188
src/commands/publishDocument.ts
··· 1 + import { Notice, TFile } from "obsidian"; 2 + import type ATmarkPlugin from "../main"; 3 + import { createDocument, putDocument, getPublication, markdownToLeafletContent, stripMarkdown, markdownToPcktContent, buildDocumentUrl } from "../lib"; 4 + import { PublicationSelection, SelectPublicationModal } from "../components/selectPublicationModal"; 5 + import { type ResourceUri, } from "@atcute/lexicons"; 6 + import { SiteStandardDocument, SiteStandardPublication } from "@atcute/standard-site"; 7 + import { PubLeafletContent } from "@atcute/leaflet"; 8 + import { BlogPcktContent } from "@atcute/pckt"; 9 + 10 + export async function publishFileAsDocument(plugin: ATmarkPlugin) { 11 + const file = plugin.app.workspace.getActiveFile(); 12 + if (!file) { 13 + new Notice("No active file to publish."); 14 + return; 15 + } 16 + 17 + if (!plugin.client.loggedIn) { 18 + new Notice("Must login to publish document."); 19 + return; 20 + } 21 + 22 + 23 + try { 24 + let { record, docUri } = await buildDocumentRecord(plugin, file); 25 + let newUri = await createOrUpdateDocument(plugin, record, docUri); 26 + 27 + // pubUrl is at:// record uri or https:// for loose document 28 + // fetch pub if at:// so we can get the url 29 + // otherwise just use the url as is 30 + if (record.site.startsWith("https://")) { 31 + const documentUrl = buildDocumentUrl(record.site, newUri, record); 32 + await updateFrontMatter(plugin, file, newUri, record, documentUrl); 33 + return; 34 + } 35 + const pub = await getPublication(plugin.client, record.site as ResourceUri); 36 + const documentUrl = buildDocumentUrl(pub.value.url, newUri, record); 37 + 38 + await updateFrontMatter(plugin, file, newUri, record, documentUrl); 39 + } catch (error) { 40 + const message = error instanceof Error ? error.message : String(error); 41 + new Notice(`Error publishing document: ${message}`); 42 + console.error("Publish document error:", error); 43 + } 44 + } 45 + 46 + async function updateFrontMatter( 47 + plugin: ATmarkPlugin, 48 + file: TFile, 49 + docUri: ResourceUri, 50 + record: SiteStandardDocument.Main, 51 + documentUrl?: string 52 + ) { 53 + await plugin.app.fileManager.processFrontMatter(file, (fm: Record<string, unknown>) => { 54 + fm["atDocument"] = docUri; 55 + fm["atPublication"] = record.site; 56 + fm["publishedAt"] = record.publishedAt; 57 + fm["updatedAt"] = new Date().toISOString(); 58 + fm["title"] = record.title; 59 + if (documentUrl) { 60 + fm["url"] = documentUrl; 61 + } 62 + if (record.description) { 63 + fm["description"] = record.description; 64 + } 65 + if (record.path) { 66 + fm["path"] = record.path; 67 + } 68 + if (record.tags) { 69 + fm["tags"] = record.tags; 70 + } 71 + if (documentUrl) { 72 + fm["url"] = documentUrl; 73 + } 74 + }); 75 + } 76 + 77 + 78 + async function buildDocumentRecord(plugin: ATmarkPlugin, file: TFile): Promise<{ record: SiteStandardDocument.Main; docUri?: ResourceUri }> { 79 + const full = await plugin.app.vault.read(file); 80 + 81 + let fm: Record<string, unknown> | null = null; 82 + await plugin.app.fileManager.processFrontMatter(file, (fmm: Record<string, unknown>) => { 83 + fm = fmm; 84 + }); 85 + let content = full.replace(/---\n[\s\S]*?\n---\n/, '').trim(); 86 + 87 + 88 + let docUri: ResourceUri | undefined; 89 + let pubUri: ResourceUri | undefined; 90 + let description: string | undefined; 91 + let title: string | undefined; 92 + let path: string | undefined; 93 + let tags: string[] | undefined; 94 + let publishedAt: string | undefined; 95 + if (fm) { 96 + pubUri = fm["atPublication"]; 97 + docUri = fm["atDocument"] as ResourceUri; 98 + description = fm["description"]; 99 + title = fm["title"]; 100 + tags = fm["tags"] && Array.isArray(fm["tags"]) ? fm["tags"] : undefined; 101 + publishedAt = fm["publishedAt"]; // Preserve existing if updating 102 + } 103 + 104 + if (!title) { 105 + title = file.basename; 106 + } 107 + 108 + let pub: SiteStandardPublication.Main | null = null; 109 + if (!pubUri) { 110 + const sel = await selectPublication(plugin); 111 + pubUri = sel.uri; 112 + pub = sel.publication; 113 + } else { 114 + const pubData = await getPublication(plugin.client, pubUri); 115 + pub = pubData.value; 116 + } 117 + 118 + if (!pubUri) { 119 + throw new Error("Missing publication URI."); 120 + } 121 + 122 + // TODO: determine which lexicon to use for rich content 123 + // for now just check url 124 + let textContent = stripMarkdown(content); 125 + 126 + let richContent: PubLeafletContent.Main | BlogPcktContent.Main | null = null; 127 + if (pub?.url.contains("leaflet.pub")) { 128 + richContent = markdownToLeafletContent(content) 129 + } else if (pub?.url.contains("pckt.blog")) { 130 + richContent = markdownToPcktContent(content) 131 + } 132 + 133 + let record = { 134 + $type: "site.standard.document", 135 + title: title, 136 + site: pubUri, 137 + publishedAt: publishedAt || "", 138 + description: description, 139 + path: path, 140 + tags: tags, 141 + textContent, 142 + content: richContent ?? undefined, 143 + } as SiteStandardDocument.Main; 144 + return { record, docUri }; 145 + }; 146 + 147 + async function selectPublication(plugin: ATmarkPlugin): Promise<PublicationSelection> { 148 + return new Promise<PublicationSelection>((resolve, reject) => { 149 + let selected = false; 150 + const modal = new SelectPublicationModal(plugin, (selection) => { 151 + selected = true; 152 + resolve(selection); 153 + }); 154 + 155 + // Override close to reject if nothing selected 156 + const originalClose = modal.close.bind(modal); 157 + modal.close = () => { 158 + originalClose(); 159 + if (!selected) { 160 + reject(new Error("Publication not selected")); 161 + } 162 + }; 163 + 164 + modal.open(); 165 + }); 166 + } 167 + 168 + 169 + async function createOrUpdateDocument( 170 + plugin: ATmarkPlugin, 171 + doc: SiteStandardDocument.Main, 172 + existingUri?: ResourceUri, 173 + ) { 174 + if (!plugin.client) { 175 + throw new Error("Client not initialized"); 176 + } 177 + 178 + const response = existingUri 179 + ? await putDocument(plugin.client, plugin.settings.identifier, existingUri, doc) 180 + : await createDocument(plugin.client, plugin.settings.identifier, doc); 181 + 182 + if (!response.ok) { 183 + throw new Error(`Failed to publish: ${response.status}`); 184 + } 185 + 186 + new Notice(`Published ${doc.title}!`); 187 + return response.data.uri; 188 + }
+142
src/components/cardDetailModal.ts
··· 1 + import { Modal, Notice, setIcon } from "obsidian"; 2 + import type ATmarkPlugin from "../main"; 3 + import { createNoteCard, deleteRecord } from "../lib"; 4 + import type { ATmarkItem } from "../sources/types"; 5 + 6 + export class CardDetailModal extends Modal { 7 + plugin: ATmarkPlugin; 8 + item: ATmarkItem; 9 + onSuccess?: () => void; 10 + noteInput: HTMLTextAreaElement | null = null; 11 + 12 + constructor(plugin: ATmarkPlugin, item: ATmarkItem, onSuccess?: () => void) { 13 + super(plugin.app); 14 + this.plugin = plugin; 15 + this.item = item; 16 + this.onSuccess = onSuccess; 17 + } 18 + 19 + onOpen() { 20 + const { contentEl } = this; 21 + contentEl.empty(); 22 + contentEl.addClass("atmark-detail-modal"); 23 + 24 + const header = contentEl.createEl("div", { cls: "atmark-detail-header" }); 25 + const source = this.item.getSource(); 26 + header.createEl("span", { 27 + text: source, 28 + cls: `atmark-badge atmark-badge-source atmark-badge-${source}`, 29 + }); 30 + 31 + this.item.renderDetail(contentEl); 32 + 33 + // semble 34 + if (this.item.canAddNotes() && this.item.getAttachedNotes) { 35 + this.renderNotesSection(contentEl); 36 + } 37 + 38 + if (this.item.canAddNotes()) { 39 + this.renderAddNoteForm(contentEl); 40 + } 41 + 42 + const footer = contentEl.createEl("div", { cls: "atmark-detail-footer" }); 43 + footer.createEl("span", { 44 + text: `Created ${new Date(this.item.getCreatedAt()).toLocaleDateString()}`, 45 + cls: "atmark-detail-date", 46 + }); 47 + } 48 + 49 + private renderNotesSection(contentEl: HTMLElement) { 50 + const notes = this.item.getAttachedNotes?.(); 51 + if (!notes || notes.length === 0) return; 52 + 53 + const notesSection = contentEl.createEl("div", { cls: "atmark-semble-detail-notes-section" }); 54 + notesSection.createEl("h3", { text: "Notes", cls: "atmark-detail-section-title" }); 55 + 56 + for (const note of notes) { 57 + const noteEl = notesSection.createEl("div", { cls: "atmark-semble-detail-note" }); 58 + 59 + const noteContent = noteEl.createEl("div", { cls: "atmark-semble-detail-note-content" }); 60 + const noteIcon = noteContent.createEl("span", { cls: "atmark-semble-detail-note-icon" }); 61 + setIcon(noteIcon, "message-square"); 62 + noteContent.createEl("p", { text: note.text, cls: "atmark-semble-detail-note-text" }); 63 + 64 + const deleteBtn = noteEl.createEl("button", { cls: "atmark-semble-note-delete-btn" }); 65 + setIcon(deleteBtn, "trash-2"); 66 + deleteBtn.addEventListener("click", () => { 67 + void this.handleDeleteNote(note.uri); 68 + }); 69 + } 70 + } 71 + 72 + private renderAddNoteForm(contentEl: HTMLElement) { 73 + const formSection = contentEl.createEl("div", { cls: "atmark-semble-detail-add-note" }); 74 + formSection.createEl("h3", { text: "Add a note", cls: "atmark-detail-section-title" }); 75 + 76 + const form = formSection.createEl("div", { cls: "atmark-semble-add-note-form" }); 77 + 78 + this.noteInput = form.createEl("textarea", { 79 + cls: "atmark-textarea atmark-semble-note-input", 80 + attr: { placeholder: "Write a note about this item..." }, 81 + }); 82 + 83 + const addBtn = form.createEl("button", { text: "Add note", cls: "atmark-btn atmark-btn-primary" }); 84 + addBtn.addEventListener("click", () => { void this.handleAddNote(); }); 85 + } 86 + 87 + private async handleAddNote() { 88 + if (!this.plugin.client || !this.noteInput) return; 89 + 90 + const text = this.noteInput.value.trim(); 91 + if (!text) { 92 + new Notice("Please enter a note"); 93 + return; 94 + } 95 + 96 + try { 97 + await createNoteCard( 98 + this.plugin.client, 99 + this.plugin.settings.identifier, 100 + text, 101 + { uri: this.item.getUri(), cid: this.item.getCid() } 102 + ); 103 + 104 + new Notice("Note added"); 105 + this.close(); 106 + this.onSuccess?.(); 107 + } catch (err) { 108 + const message = err instanceof Error ? err.message : String(err); 109 + new Notice(`Failed to add note: ${message}`); 110 + } 111 + } 112 + 113 + private async handleDeleteNote(noteUri: string) { 114 + if (!this.plugin.client) return; 115 + 116 + const rkey = noteUri.split("/").pop(); 117 + if (!rkey) { 118 + new Notice("Invalid note uri"); 119 + return; 120 + } 121 + 122 + try { 123 + await deleteRecord( 124 + this.plugin.client, 125 + this.plugin.settings.identifier, 126 + "network.cosmik.card", 127 + rkey 128 + ); 129 + 130 + new Notice("Note deleted"); 131 + this.close(); 132 + this.onSuccess?.(); 133 + } catch (err) { 134 + const message = err instanceof Error ? err.message : String(err); 135 + new Notice(`Failed to delete note: ${message}`); 136 + } 137 + } 138 + 139 + onClose() { 140 + this.contentEl.empty(); 141 + } 142 + }
+103
src/components/createCollectionModal.ts
··· 1 + import { Modal, Notice } from "obsidian"; 2 + import type ATmarkPlugin from "../main"; 3 + import { createCollection } from "../lib"; 4 + 5 + export class CreateCollectionModal extends Modal { 6 + plugin: ATmarkPlugin; 7 + onSuccess?: () => void; 8 + 9 + constructor(plugin: ATmarkPlugin, onSuccess?: () => void) { 10 + super(plugin.app); 11 + this.plugin = plugin; 12 + this.onSuccess = onSuccess; 13 + } 14 + 15 + onOpen() { 16 + const { contentEl } = this; 17 + contentEl.empty(); 18 + contentEl.addClass("atmark-modal"); 19 + 20 + contentEl.createEl("h2", { text: "New collection" }); 21 + 22 + if (!this.plugin.client) { 23 + contentEl.createEl("p", { text: "Not connected." }); 24 + return; 25 + } 26 + 27 + const form = contentEl.createEl("form", { cls: "atmark-form" }); 28 + 29 + const nameGroup = form.createEl("div", { cls: "atmark-form-group" }); 30 + nameGroup.createEl("label", { text: "Name", attr: { for: "collection-name" } }); 31 + const nameInput = nameGroup.createEl("input", { 32 + type: "text", 33 + cls: "atmark-input", 34 + attr: { id: "collection-name", placeholder: "Collection name", required: "true" }, 35 + }); 36 + 37 + const descGroup = form.createEl("div", { cls: "atmark-form-group" }); 38 + descGroup.createEl("label", { text: "Description", attr: { for: "collection-desc" } }); 39 + const descInput = descGroup.createEl("textarea", { 40 + cls: "atmark-textarea", 41 + attr: { id: "collection-desc", placeholder: "Optional description", rows: "3" }, 42 + }); 43 + 44 + const actions = form.createEl("div", { cls: "atmark-modal-actions" }); 45 + 46 + const cancelBtn = actions.createEl("button", { 47 + text: "Cancel", 48 + cls: "atmark-btn atmark-btn-secondary", 49 + type: "button", 50 + }); 51 + cancelBtn.addEventListener("click", () => this.close()); 52 + 53 + const createBtn = actions.createEl("button", { 54 + text: "Create", 55 + cls: "atmark-btn atmark-btn-primary", 56 + type: "submit", 57 + }); 58 + 59 + form.addEventListener("submit", (e) => { 60 + e.preventDefault(); 61 + void this.handleSubmit(nameInput, descInput, createBtn); 62 + }); 63 + 64 + nameInput.focus(); 65 + } 66 + 67 + private async handleSubmit( 68 + nameInput: HTMLInputElement, 69 + descInput: HTMLTextAreaElement, 70 + createBtn: HTMLButtonElement 71 + ) { 72 + const name = nameInput.value.trim(); 73 + if (!name) { 74 + new Notice("Please enter a collection name"); 75 + return; 76 + } 77 + 78 + createBtn.disabled = true; 79 + createBtn.textContent = "Creating..."; 80 + 81 + try { 82 + await createCollection( 83 + this.plugin.client, 84 + this.plugin.settings.identifier, 85 + name, 86 + descInput.value.trim() 87 + ); 88 + 89 + new Notice(`Created collection "${name}"`); 90 + this.close(); 91 + this.onSuccess?.(); 92 + } catch (err) { 93 + const message = err instanceof Error ? err.message : String(err); 94 + new Notice(`Failed to create collection: ${message}`); 95 + createBtn.disabled = false; 96 + createBtn.textContent = "Create"; 97 + } 98 + } 99 + 100 + onClose() { 101 + this.contentEl.empty(); 102 + } 103 + }
+113
src/components/createMarginCollectionModal.ts
··· 1 + import { Modal, Notice } from "obsidian"; 2 + import type ATmarkPlugin from "../main"; 3 + import { createMarginCollection } from "../lib"; 4 + 5 + export class CreateMarginCollectionModal extends Modal { 6 + plugin: ATmarkPlugin; 7 + onSuccess?: () => void; 8 + 9 + constructor(plugin: ATmarkPlugin, onSuccess?: () => void) { 10 + super(plugin.app); 11 + this.plugin = plugin; 12 + this.onSuccess = onSuccess; 13 + } 14 + 15 + onOpen() { 16 + const { contentEl } = this; 17 + contentEl.empty(); 18 + contentEl.addClass("atmark-modal"); 19 + 20 + contentEl.createEl("h2", { text: "New margin collection" }); 21 + 22 + if (!this.plugin.client) { 23 + // contentEl.createEl("p", { text: "Not Logged In. Please Login Using Settings." }); 24 + return; 25 + } 26 + 27 + const form = contentEl.createEl("form", { cls: "atmark-form" }); 28 + 29 + const nameGroup = form.createEl("div", { cls: "atmark-form-group" }); 30 + nameGroup.createEl("label", { text: "Name", attr: { for: "collection-name" } }); 31 + const nameInput = nameGroup.createEl("input", { 32 + type: "text", 33 + cls: "atmark-input", 34 + attr: { id: "collection-name", placeholder: "Collection name", required: "true" }, 35 + }); 36 + 37 + const iconGroup = form.createEl("div", { cls: "atmark-form-group" }); 38 + iconGroup.createEl("label", { text: "Icon (optional)", attr: { for: "collection-icon" } }); 39 + const iconInput = iconGroup.createEl("input", { 40 + type: "text", 41 + cls: "atmark-input", 42 + attr: { id: "collection-icon" }, 43 + }); 44 + 45 + const descGroup = form.createEl("div", { cls: "atmark-form-group" }); 46 + descGroup.createEl("label", { text: "Description", attr: { for: "collection-desc" } }); 47 + const descInput = descGroup.createEl("textarea", { 48 + cls: "atmark-textarea", 49 + attr: { id: "collection-desc", placeholder: "Optional description", rows: "3" }, 50 + }); 51 + 52 + const actions = form.createEl("div", { cls: "atmark-modal-actions" }); 53 + 54 + const cancelBtn = actions.createEl("button", { 55 + text: "Cancel", 56 + cls: "atmark-btn atmark-btn-secondary", 57 + type: "button", 58 + }); 59 + cancelBtn.addEventListener("click", () => this.close()); 60 + 61 + const createBtn = actions.createEl("button", { 62 + text: "Create", 63 + cls: "atmark-btn atmark-btn-primary", 64 + type: "submit", 65 + }); 66 + 67 + form.addEventListener("submit", (e) => { 68 + e.preventDefault(); 69 + void this.handleSubmit(nameInput, iconInput, descInput, createBtn); 70 + }); 71 + 72 + nameInput.focus(); 73 + } 74 + 75 + private async handleSubmit( 76 + nameInput: HTMLInputElement, 77 + iconInput: HTMLInputElement, 78 + descInput: HTMLTextAreaElement, 79 + createBtn: HTMLButtonElement 80 + ) { 81 + const name = nameInput.value.trim(); 82 + if (!name) { 83 + new Notice("Please enter a collection name"); 84 + return; 85 + } 86 + 87 + createBtn.disabled = true; 88 + createBtn.textContent = "Creating..."; 89 + 90 + try { 91 + await createMarginCollection( 92 + this.plugin.client, 93 + this.plugin.settings.identifier, 94 + name, 95 + descInput.value.trim() || undefined, 96 + iconInput.value.trim() || undefined 97 + ); 98 + 99 + new Notice(`Created collection "${name}"`); 100 + this.close(); 101 + this.onSuccess?.(); 102 + } catch (err) { 103 + const message = err instanceof Error ? err.message : String(err); 104 + new Notice(`Failed to create collection: ${message}`); 105 + createBtn.disabled = false; 106 + createBtn.textContent = "Create"; 107 + } 108 + } 109 + 110 + onClose() { 111 + this.contentEl.empty(); 112 + } 113 + }
+94
src/components/createTagModal.ts
··· 1 + import { Modal, Notice } from "obsidian"; 2 + import type ATmarkPlugin from "../main"; 3 + import { createTag } from "../lib"; 4 + 5 + export class CreateTagModal extends Modal { 6 + plugin: ATmarkPlugin; 7 + onSuccess?: () => void; 8 + 9 + constructor(plugin: ATmarkPlugin, onSuccess?: () => void) { 10 + super(plugin.app); 11 + this.plugin = plugin; 12 + this.onSuccess = onSuccess; 13 + } 14 + 15 + onOpen() { 16 + const { contentEl } = this; 17 + contentEl.empty(); 18 + contentEl.addClass("atmark-modal"); 19 + 20 + contentEl.createEl("h2", { text: "New tag" }); 21 + 22 + if (!this.plugin.client) { 23 + contentEl.createEl("p", { text: "Not connected." }); 24 + return; 25 + } 26 + 27 + const form = contentEl.createEl("form", { cls: "atmark-form" }); 28 + 29 + const tagGroup = form.createEl("div", { cls: "atmark-form-group" }); 30 + tagGroup.createEl("label", { text: "Tag", attr: { for: "tag-value" } }); 31 + const tagInput = tagGroup.createEl("input", { 32 + type: "text", 33 + cls: "atmark-input", 34 + attr: { id: "tag-value", placeholder: "Tag name", required: "true" }, 35 + }); 36 + 37 + const actions = form.createEl("div", { cls: "atmark-modal-actions" }); 38 + 39 + const cancelBtn = actions.createEl("button", { 40 + text: "Cancel", 41 + cls: "atmark-btn atmark-btn-secondary", 42 + type: "button", 43 + }); 44 + cancelBtn.addEventListener("click", () => this.close()); 45 + 46 + const createBtn = actions.createEl("button", { 47 + text: "Create", 48 + cls: "atmark-btn atmark-btn-primary", 49 + type: "submit", 50 + }); 51 + 52 + form.addEventListener("submit", (e) => { 53 + e.preventDefault(); 54 + void this.handleSubmit(tagInput, createBtn); 55 + }); 56 + 57 + tagInput.focus(); 58 + } 59 + 60 + private async handleSubmit( 61 + tagInput: HTMLInputElement, 62 + createBtn: HTMLButtonElement 63 + ) { 64 + const value = tagInput.value.trim(); 65 + if (!value) { 66 + new Notice("Please enter a tag name"); 67 + return; 68 + } 69 + 70 + createBtn.disabled = true; 71 + createBtn.textContent = "Creating..."; 72 + 73 + try { 74 + await createTag( 75 + this.plugin.client, 76 + this.plugin.settings.identifier, 77 + value 78 + ); 79 + 80 + new Notice(`Created tag "${value}"`); 81 + this.close(); 82 + this.onSuccess?.(); 83 + } catch (err) { 84 + const message = err instanceof Error ? err.message : String(err); 85 + new Notice(`Failed to create tag: ${message}`); 86 + createBtn.disabled = false; 87 + createBtn.textContent = "Create"; 88 + } 89 + } 90 + 91 + onClose() { 92 + this.contentEl.empty(); 93 + } 94 + }
+238
src/components/editBookmarkModal.ts
··· 1 + import { Modal, Notice } from "obsidian"; 2 + import type { Record } from "@atcute/atproto/types/repo/listRecords"; 3 + import type { Main as Bookmark } from "../lexicons/types/community/lexicon/bookmarks/bookmark"; 4 + import type ATmarkPlugin from "../main"; 5 + import { putRecord, deleteRecord, getBookmarks } from "../lib"; 6 + 7 + type BookmarkRecord = Record & { value: Bookmark }; 8 + 9 + interface TagState { 10 + tag: string; 11 + isSelected: boolean; 12 + } 13 + 14 + export class EditBookmarkModal extends Modal { 15 + plugin: ATmarkPlugin; 16 + record: BookmarkRecord; 17 + onSuccess?: () => void; 18 + tagStates: TagState[] = []; 19 + newTagInput: HTMLInputElement | null = null; 20 + 21 + constructor(plugin: ATmarkPlugin, record: BookmarkRecord, onSuccess?: () => void) { 22 + super(plugin.app); 23 + this.plugin = plugin; 24 + this.record = record; 25 + this.onSuccess = onSuccess; 26 + } 27 + 28 + async onOpen() { 29 + const { contentEl } = this; 30 + contentEl.empty(); 31 + contentEl.addClass("atmark-modal"); 32 + 33 + contentEl.createEl("h2", { text: "Edit bookmark" }); 34 + 35 + if (!this.plugin.client) { 36 + contentEl.createEl("p", { text: "Not connected." }); 37 + return; 38 + } 39 + 40 + const loading = contentEl.createEl("p", { text: "Loading..." }); 41 + 42 + try { 43 + const bookmarksResp = await getBookmarks(this.plugin.client, this.plugin.settings.identifier); 44 + loading.remove(); 45 + 46 + const bookmarks = (bookmarksResp.ok ? bookmarksResp.data.records : []) as unknown as BookmarkRecord[]; 47 + 48 + const allTags = new Set<string>(); 49 + for (const bookmark of bookmarks) { 50 + if (bookmark.value.tags) { 51 + for (const tag of bookmark.value.tags) { 52 + allTags.add(tag); 53 + } 54 + } 55 + } 56 + 57 + const currentTags = new Set(this.record.value.tags || []); 58 + this.tagStates = Array.from(allTags).sort().map(tag => ({ 59 + tag, 60 + isSelected: currentTags.has(tag), 61 + })); 62 + 63 + this.renderForm(contentEl); 64 + } catch (err) { 65 + loading.remove(); 66 + const message = err instanceof Error ? err.message : String(err); 67 + contentEl.createEl("p", { text: `Error: ${message}`, cls: "atmark-error" }); 68 + } 69 + } 70 + 71 + private renderForm(contentEl: HTMLElement) { 72 + const form = contentEl.createEl("div", { cls: "atmark-form" }); 73 + 74 + const tagsGroup = form.createEl("div", { cls: "atmark-form-group" }); 75 + tagsGroup.createEl("label", { text: "Tags" }); 76 + 77 + const tagsList = tagsGroup.createEl("div", { cls: "atmark-tag-list" }); 78 + for (const state of this.tagStates) { 79 + this.addTagChip(tagsList, state); 80 + } 81 + 82 + const newTagRow = tagsGroup.createEl("div", { cls: "atmark-tag-row" }); 83 + this.newTagInput = newTagRow.createEl("input", { 84 + type: "text", 85 + cls: "atmark-input", 86 + attr: { placeholder: "Add new tag..." } 87 + }); 88 + const addBtn = newTagRow.createEl("button", { 89 + text: "Add", 90 + cls: "atmark-btn atmark-btn-secondary", 91 + attr: { type: "button" } 92 + }); 93 + addBtn.addEventListener("click", () => { 94 + const value = this.newTagInput?.value.trim(); 95 + if (value && !this.tagStates.some(s => s.tag === value)) { 96 + const newState = { tag: value, isSelected: true }; 97 + this.tagStates.push(newState); 98 + this.addTagChip(tagsList, newState); 99 + if (this.newTagInput) this.newTagInput.value = ""; 100 + } 101 + }); 102 + 103 + const actions = contentEl.createEl("div", { cls: "atmark-modal-actions" }); 104 + 105 + const deleteBtn = actions.createEl("button", { 106 + text: "Delete", 107 + cls: "atmark-btn atmark-btn-danger" 108 + }); 109 + deleteBtn.addEventListener("click", () => { this.confirmDelete(contentEl); }); 110 + 111 + actions.createEl("div", { cls: "atmark-spacer" }); 112 + 113 + const cancelBtn = actions.createEl("button", { 114 + text: "Cancel", 115 + cls: "atmark-btn atmark-btn-secondary" 116 + }); 117 + cancelBtn.addEventListener("click", () => { this.close(); }); 118 + 119 + const saveBtn = actions.createEl("button", { 120 + text: "Save", 121 + cls: "atmark-btn atmark-btn-primary" 122 + }); 123 + saveBtn.addEventListener("click", () => { void this.saveChanges(); }); 124 + } 125 + 126 + private addTagChip(container: HTMLElement, state: TagState) { 127 + const item = container.createEl("label", { cls: "atmark-tag-item" }); 128 + const checkbox = item.createEl("input", { type: "checkbox" }); 129 + checkbox.checked = state.isSelected; 130 + checkbox.addEventListener("change", () => { 131 + state.isSelected = checkbox.checked; 132 + }); 133 + item.createEl("span", { text: state.tag }); 134 + } 135 + 136 + private confirmDelete(contentEl: HTMLElement) { 137 + contentEl.empty(); 138 + contentEl.createEl("h2", { text: "Delete bookmark" }); 139 + contentEl.createEl("p", { text: "Delete this bookmark?", cls: "atmark-warning-text" }); 140 + 141 + const actions = contentEl.createEl("div", { cls: "atmark-modal-actions" }); 142 + 143 + const cancelBtn = actions.createEl("button", { 144 + text: "Cancel", 145 + cls: "atmark-btn atmark-btn-secondary" 146 + }); 147 + cancelBtn.addEventListener("click", () => { 148 + void this.onOpen(); 149 + }); 150 + 151 + const confirmBtn = actions.createEl("button", { 152 + text: "Delete", 153 + cls: "atmark-btn atmark-btn-danger" 154 + }); 155 + confirmBtn.addEventListener("click", () => { void this.deleteBookmark(); }); 156 + } 157 + 158 + private async deleteBookmark() { 159 + if (!this.plugin.client) return; 160 + 161 + const { contentEl } = this; 162 + contentEl.empty(); 163 + contentEl.createEl("p", { text: "Deleting bookmark..." }); 164 + 165 + try { 166 + const rkey = this.record.uri.split("/").pop(); 167 + if (!rkey) { 168 + contentEl.empty(); 169 + contentEl.createEl("p", { text: "Invalid bookmark uri.", cls: "atmark-error" }); 170 + return; 171 + } 172 + 173 + await deleteRecord( 174 + this.plugin.client, 175 + this.plugin.settings.identifier, 176 + "community.lexicon.bookmarks.bookmark", 177 + rkey 178 + ); 179 + 180 + new Notice("Bookmark deleted"); 181 + this.close(); 182 + this.onSuccess?.(); 183 + } catch (err) { 184 + contentEl.empty(); 185 + const message = err instanceof Error ? err.message : String(err); 186 + contentEl.createEl("p", { text: `Failed to delete: ${message}`, cls: "atmark-error" }); 187 + } 188 + } 189 + 190 + private async saveChanges() { 191 + if (!this.plugin.client) return; 192 + 193 + const { contentEl } = this; 194 + contentEl.empty(); 195 + contentEl.createEl("p", { text: "Saving changes..." }); 196 + 197 + try { 198 + const selectedTags = this.tagStates.filter(s => s.isSelected).map(s => s.tag); 199 + const newTag = this.newTagInput?.value.trim(); 200 + if (newTag && !selectedTags.includes(newTag)) { 201 + selectedTags.push(newTag); 202 + } 203 + const tags = [...new Set(selectedTags)]; 204 + 205 + const rkey = this.record.uri.split("/").pop(); 206 + if (!rkey) { 207 + contentEl.empty(); 208 + contentEl.createEl("p", { text: "Invalid bookmark uri.", cls: "atmark-error" }); 209 + return; 210 + } 211 + 212 + const updatedRecord: Bookmark = { 213 + ...this.record.value, 214 + tags, 215 + }; 216 + 217 + await putRecord( 218 + this.plugin.client, 219 + this.plugin.settings.identifier, 220 + "community.lexicon.bookmarks.bookmark", 221 + rkey, 222 + updatedRecord 223 + ); 224 + 225 + new Notice("Tags updated"); 226 + this.close(); 227 + this.onSuccess?.(); 228 + } catch (err) { 229 + contentEl.empty(); 230 + const message = err instanceof Error ? err.message : String(err); 231 + contentEl.createEl("p", { text: `Failed to save: ${message}`, cls: "atmark-error" }); 232 + } 233 + } 234 + 235 + onClose() { 236 + this.contentEl.empty(); 237 + } 238 + }
+258
src/components/editCardModal.ts
··· 1 + import { Modal, Notice } from "obsidian"; 2 + import type ATmarkPlugin from "../main"; 3 + import { getCollections, getCollectionLinks, createCollectionLink, getRecord, deleteRecord } from "../lib"; 4 + import type { Main as Collection } from "../lexicons/types/network/cosmik/collection"; 5 + import type { Main as CollectionLink } from "../lexicons/types/network/cosmik/collectionLink"; 6 + 7 + interface CollectionRecord { 8 + uri: string; 9 + cid: string; 10 + value: Collection; 11 + } 12 + 13 + interface CollectionLinkRecord { 14 + uri: string; 15 + value: CollectionLink; 16 + } 17 + 18 + interface CollectionState { 19 + collection: CollectionRecord; 20 + isSelected: boolean; 21 + wasSelected: boolean; // Original state to track changes 22 + linkUri?: string; // URI of existing link (for deletion) 23 + } 24 + 25 + export class EditCardModal extends Modal { 26 + plugin: ATmarkPlugin; 27 + cardUri: string; 28 + cardCid: string; 29 + onSuccess?: () => void; 30 + collectionStates: CollectionState[] = []; 31 + 32 + constructor(plugin: ATmarkPlugin, cardUri: string, cardCid: string, onSuccess?: () => void) { 33 + super(plugin.app); 34 + this.plugin = plugin; 35 + this.cardUri = cardUri; 36 + this.cardCid = cardCid; 37 + this.onSuccess = onSuccess; 38 + } 39 + 40 + async onOpen() { 41 + const { contentEl } = this; 42 + contentEl.empty(); 43 + contentEl.addClass("atmark-modal"); 44 + 45 + contentEl.createEl("h2", { text: "Edit collections" }); 46 + 47 + if (!this.plugin.client) { 48 + contentEl.createEl("p", { text: "Not connected." }); 49 + return; 50 + } 51 + 52 + const loading = contentEl.createEl("p", { text: "Loading..." }); 53 + 54 + try { 55 + const [collectionsResp, linksResp] = await Promise.all([ 56 + getCollections(this.plugin.client, this.plugin.settings.identifier), 57 + getCollectionLinks(this.plugin.client, this.plugin.settings.identifier), 58 + ]); 59 + 60 + loading.remove(); 61 + 62 + if (!collectionsResp.ok) { 63 + contentEl.createEl("p", { text: "Failed to load collections.", cls: "atmark-error" }); 64 + return; 65 + } 66 + 67 + const collections = collectionsResp.data.records as unknown as CollectionRecord[]; 68 + const links = (linksResp.ok ? linksResp.data.records : []) as unknown as CollectionLinkRecord[]; 69 + 70 + if (collections.length === 0) { 71 + contentEl.createEl("p", { text: "No collections found. Create a collection first." }); 72 + return; 73 + } 74 + 75 + const cardLinks = links.filter(link => link.value.card.uri === this.cardUri); 76 + const linkedCollectionUris = new Map<string, string>(); 77 + for (const link of cardLinks) { 78 + linkedCollectionUris.set(link.value.collection.uri, link.uri); 79 + } 80 + 81 + this.collectionStates = collections.map(collection => ({ 82 + collection, 83 + isSelected: linkedCollectionUris.has(collection.uri), 84 + wasSelected: linkedCollectionUris.has(collection.uri), 85 + linkUri: linkedCollectionUris.get(collection.uri), 86 + })); 87 + 88 + this.renderCollectionList(contentEl); 89 + } catch (err) { 90 + loading.remove(); 91 + const message = err instanceof Error ? err.message : String(err); 92 + contentEl.createEl("p", { text: `Error: ${message}`, cls: "atmark-error" }); 93 + } 94 + } 95 + 96 + private renderCollectionList(contentEl: HTMLElement) { 97 + const list = contentEl.createEl("div", { cls: "atmark-collection-list" }); 98 + 99 + for (const state of this.collectionStates) { 100 + const item = list.createEl("label", { cls: "atmark-collection-item" }); 101 + 102 + const checkbox = item.createEl("input", { type: "checkbox", cls: "atmark-collection-checkbox" }); 103 + checkbox.checked = state.isSelected; 104 + checkbox.addEventListener("change", () => { 105 + state.isSelected = checkbox.checked; 106 + this.updateSaveButton(); 107 + }); 108 + 109 + const info = item.createEl("div", { cls: "atmark-collection-item-info" }); 110 + info.createEl("span", { text: state.collection.value.name, cls: "atmark-collection-item-name" }); 111 + if (state.collection.value.description) { 112 + info.createEl("span", { text: state.collection.value.description, cls: "atmark-collection-item-desc" }); 113 + } 114 + } 115 + 116 + const actions = contentEl.createEl("div", { cls: "atmark-modal-actions" }); 117 + 118 + const deleteBtn = actions.createEl("button", { text: "Delete", cls: "atmark-btn atmark-btn-danger" }); 119 + deleteBtn.addEventListener("click", () => { this.confirmDelete(contentEl); }); 120 + 121 + actions.createEl("div", { cls: "atmark-spacer" }); 122 + 123 + const cancelBtn = actions.createEl("button", { text: "Cancel", cls: "atmark-btn atmark-btn-secondary" }); 124 + cancelBtn.addEventListener("click", () => { this.close(); }); 125 + 126 + const saveBtn = actions.createEl("button", { text: "Save", cls: "atmark-btn atmark-btn-primary" }); 127 + saveBtn.id = "atmark-save-btn"; 128 + saveBtn.disabled = true; 129 + saveBtn.addEventListener("click", () => { void this.saveChanges(); }); 130 + } 131 + 132 + private confirmDelete(contentEl: HTMLElement) { 133 + contentEl.empty(); 134 + contentEl.createEl("h2", { text: "Delete card" }); 135 + contentEl.createEl("p", { text: "Delete this card?", cls: "atmark-warning-text" }); 136 + 137 + const actions = contentEl.createEl("div", { cls: "atmark-modal-actions" }); 138 + 139 + const cancelBtn = actions.createEl("button", { text: "Cancel", cls: "atmark-btn atmark-btn-secondary" }); 140 + cancelBtn.addEventListener("click", () => { 141 + void this.onOpen(); 142 + }); 143 + 144 + const confirmBtn = actions.createEl("button", { text: "Delete", cls: "atmark-btn atmark-btn-danger" }); 145 + confirmBtn.addEventListener("click", () => { void this.deleteCard(); }); 146 + } 147 + 148 + private async deleteCard() { 149 + if (!this.plugin.client) return; 150 + 151 + const { contentEl } = this; 152 + contentEl.empty(); 153 + contentEl.createEl("p", { text: "Deleting card..." }); 154 + 155 + try { 156 + const rkey = this.cardUri.split("/").pop(); 157 + if (!rkey) { 158 + contentEl.empty(); 159 + contentEl.createEl("p", { text: "Invalid card uri.", cls: "atmark-error" }); 160 + return; 161 + } 162 + 163 + await deleteRecord( 164 + this.plugin.client, 165 + this.plugin.settings.identifier, 166 + "network.cosmik.card", 167 + rkey 168 + ); 169 + 170 + new Notice("Card deleted"); 171 + this.close(); 172 + this.onSuccess?.(); 173 + } catch (err) { 174 + contentEl.empty(); 175 + const message = err instanceof Error ? err.message : String(err); 176 + contentEl.createEl("p", { text: `Failed to delete: ${message}`, cls: "atmark-error" }); 177 + } 178 + } 179 + 180 + private updateSaveButton() { 181 + const saveBtn = document.getElementById("atmark-save-btn") as HTMLButtonElement | null; 182 + if (!saveBtn) return; 183 + 184 + const hasChanges = this.collectionStates.some(s => s.isSelected !== s.wasSelected); 185 + saveBtn.disabled = !hasChanges; 186 + } 187 + 188 + private async saveChanges() { 189 + if (!this.plugin.client) return; 190 + 191 + const { contentEl } = this; 192 + contentEl.empty(); 193 + contentEl.createEl("p", { text: "Saving changes..." }); 194 + 195 + try { 196 + const toAdd = this.collectionStates.filter(s => s.isSelected && !s.wasSelected); 197 + const toRemove = this.collectionStates.filter(s => !s.isSelected && s.wasSelected); 198 + 199 + for (const state of toRemove) { 200 + if (state.linkUri) { 201 + const rkey = state.linkUri.split("/").pop(); 202 + if (rkey) { 203 + await deleteRecord( 204 + this.plugin.client, 205 + this.plugin.settings.identifier, 206 + "network.cosmik.collectionLink", 207 + rkey 208 + ); 209 + } 210 + } 211 + } 212 + 213 + for (const state of toAdd) { 214 + const collectionRkey = state.collection.uri.split("/").pop(); 215 + if (!collectionRkey) continue; 216 + 217 + const collectionResp = await getRecord( 218 + this.plugin.client, 219 + this.plugin.settings.identifier, 220 + "network.cosmik.collection", 221 + collectionRkey 222 + ); 223 + 224 + if (!collectionResp.ok || !collectionResp.data.cid) continue; 225 + 226 + await createCollectionLink( 227 + this.plugin.client, 228 + this.plugin.settings.identifier, 229 + this.cardUri, 230 + this.cardCid, 231 + state.collection.uri, 232 + String(collectionResp.data.cid) 233 + ); 234 + } 235 + 236 + const addedCount = toAdd.length; 237 + const removedCount = toRemove.length; 238 + const messages: string[] = []; 239 + if (addedCount > 0) messages.push(`Added to ${addedCount} collection${addedCount > 1 ? "s" : ""}`); 240 + if (removedCount > 0) messages.push(`Removed from ${removedCount} collection${removedCount > 1 ? "s" : ""}`); 241 + 242 + if (messages.length > 0) { 243 + new Notice(messages.join(". ")); 244 + } 245 + 246 + this.close(); 247 + this.onSuccess?.(); 248 + } catch (err) { 249 + contentEl.empty(); 250 + const message = err instanceof Error ? err.message : String(err); 251 + contentEl.createEl("p", { text: `Failed to save: ${message}`, cls: "atmark-error" }); 252 + } 253 + } 254 + 255 + onClose() { 256 + this.contentEl.empty(); 257 + } 258 + }
+331
src/components/editMarginBookmarkModal.ts
··· 1 + import { Modal, Notice } from "obsidian"; 2 + import type { Record } from "@atcute/atproto/types/repo/listRecords"; 3 + import type { Main as MarginBookmark } from "../lexicons/types/at/margin/bookmark"; 4 + import type { Main as MarginCollection } from "../lexicons/types/at/margin/collection"; 5 + import type { Main as MarginCollectionItem } from "../lexicons/types/at/margin/collectionItem"; 6 + import type ATmarkPlugin from "../main"; 7 + import { putRecord, deleteRecord, getMarginCollections, getMarginCollectionItems, createMarginCollectionItem, getMarginBookmarks } from "../lib"; 8 + 9 + type MarginBookmarkRecord = Record & { value: MarginBookmark }; 10 + type MarginCollectionRecord = Record & { value: MarginCollection }; 11 + type MarginCollectionItemRecord = Record & { value: MarginCollectionItem }; 12 + 13 + interface CollectionState { 14 + collection: MarginCollectionRecord; 15 + isSelected: boolean; 16 + wasSelected: boolean; 17 + linkUri?: string; 18 + } 19 + 20 + interface TagState { 21 + tag: string; 22 + isSelected: boolean; 23 + } 24 + 25 + export class EditMarginBookmarkModal extends Modal { 26 + plugin: ATmarkPlugin; 27 + record: MarginBookmarkRecord; 28 + onSuccess?: () => void; 29 + tagStates: TagState[] = []; 30 + newTagInput: HTMLInputElement | null = null; 31 + collectionStates: CollectionState[] = []; 32 + 33 + constructor(plugin: ATmarkPlugin, record: MarginBookmarkRecord, onSuccess?: () => void) { 34 + super(plugin.app); 35 + this.plugin = plugin; 36 + this.record = record; 37 + this.onSuccess = onSuccess; 38 + } 39 + 40 + async onOpen() { 41 + const { contentEl } = this; 42 + contentEl.empty(); 43 + contentEl.addClass("atmark-modal"); 44 + 45 + contentEl.createEl("h2", { text: "Edit margin bookmark" }); 46 + 47 + if (!this.plugin.client) { 48 + contentEl.createEl("p", { text: "Not connected." }); 49 + return; 50 + } 51 + 52 + const loading = contentEl.createEl("p", { text: "Loading..." }); 53 + 54 + try { 55 + const [collectionsResp, itemsResp, bookmarksResp] = await Promise.all([ 56 + getMarginCollections(this.plugin.client, this.plugin.settings.identifier), 57 + getMarginCollectionItems(this.plugin.client, this.plugin.settings.identifier), 58 + getMarginBookmarks(this.plugin.client, this.plugin.settings.identifier), 59 + ]); 60 + 61 + loading.remove(); 62 + 63 + const collections = (collectionsResp.ok ? collectionsResp.data.records : []) as unknown as MarginCollectionRecord[]; 64 + const items = (itemsResp.ok ? itemsResp.data.records : []) as unknown as MarginCollectionItemRecord[]; 65 + const bookmarks = (bookmarksResp.ok ? bookmarksResp.data.records : []) as unknown as MarginBookmarkRecord[]; 66 + 67 + const bookmarkLinks = items.filter(item => item.value.annotation === this.record.uri); 68 + const linkedCollectionUris = new Map<string, string>(); 69 + for (const link of bookmarkLinks) { 70 + linkedCollectionUris.set(link.value.collection, link.uri); 71 + } 72 + 73 + this.collectionStates = collections.map(collection => ({ 74 + collection, 75 + isSelected: linkedCollectionUris.has(collection.uri), 76 + wasSelected: linkedCollectionUris.has(collection.uri), 77 + linkUri: linkedCollectionUris.get(collection.uri), 78 + })); 79 + 80 + const allTags = new Set<string>(); 81 + for (const bookmark of bookmarks) { 82 + if (bookmark.value.tags) { 83 + for (const tag of bookmark.value.tags) { 84 + allTags.add(tag); 85 + } 86 + } 87 + } 88 + 89 + const currentTags = new Set(this.record.value.tags || []); 90 + this.tagStates = Array.from(allTags).sort().map(tag => ({ 91 + tag, 92 + isSelected: currentTags.has(tag), 93 + })); 94 + 95 + this.renderForm(contentEl); 96 + } catch (err) { 97 + loading.remove(); 98 + const message = err instanceof Error ? err.message : String(err); 99 + contentEl.createEl("p", { text: `Error: ${message}`, cls: "atmark-error" }); 100 + } 101 + } 102 + 103 + private renderForm(contentEl: HTMLElement) { 104 + const form = contentEl.createEl("div", { cls: "atmark-form" }); 105 + 106 + const tagsGroup = form.createEl("div", { cls: "atmark-form-group" }); 107 + tagsGroup.createEl("label", { text: "Tags" }); 108 + 109 + const tagsList = tagsGroup.createEl("div", { cls: "atmark-tag-list" }); 110 + for (const state of this.tagStates) { 111 + this.addTagChip(tagsList, state); 112 + } 113 + 114 + const newTagRow = tagsGroup.createEl("div", { cls: "atmark-tag-row" }); 115 + this.newTagInput = newTagRow.createEl("input", { 116 + type: "text", 117 + cls: "atmark-input", 118 + attr: { placeholder: "Add new tag..." } 119 + }); 120 + const addBtn = newTagRow.createEl("button", { 121 + text: "Add", 122 + cls: "atmark-btn atmark-btn-secondary", 123 + attr: { type: "button" } 124 + }); 125 + addBtn.addEventListener("click", () => { 126 + const value = this.newTagInput?.value.trim(); 127 + if (value && !this.tagStates.some(s => s.tag === value)) { 128 + const newState = { tag: value, isSelected: true }; 129 + this.tagStates.push(newState); 130 + this.addTagChip(tagsList, newState); 131 + if (this.newTagInput) this.newTagInput.value = ""; 132 + } 133 + }); 134 + 135 + if (this.collectionStates.length > 0) { 136 + const collectionsGroup = form.createEl("div", { cls: "atmark-form-group" }); 137 + collectionsGroup.createEl("label", { text: "Collections" }); 138 + 139 + const collectionsList = collectionsGroup.createEl("div", { cls: "atmark-collection-list" }); 140 + 141 + for (const state of this.collectionStates) { 142 + const item = collectionsList.createEl("label", { cls: "atmark-collection-item" }); 143 + 144 + const checkbox = item.createEl("input", { type: "checkbox", cls: "atmark-collection-checkbox" }); 145 + checkbox.checked = state.isSelected; 146 + checkbox.addEventListener("change", () => { 147 + state.isSelected = checkbox.checked; 148 + }); 149 + 150 + const info = item.createEl("div", { cls: "atmark-collection-item-info" }); 151 + info.createEl("span", { text: state.collection.value.name, cls: "atmark-collection-item-name" }); 152 + if (state.collection.value.description) { 153 + info.createEl("span", { text: state.collection.value.description, cls: "atmark-collection-item-desc" }); 154 + } 155 + } 156 + } 157 + 158 + const actions = contentEl.createEl("div", { cls: "atmark-modal-actions" }); 159 + 160 + const deleteBtn = actions.createEl("button", { 161 + text: "Delete", 162 + cls: "atmark-btn atmark-btn-danger" 163 + }); 164 + deleteBtn.addEventListener("click", () => { this.confirmDelete(contentEl); }); 165 + 166 + actions.createEl("div", { cls: "atmark-spacer" }); 167 + 168 + const cancelBtn = actions.createEl("button", { 169 + text: "Cancel", 170 + cls: "atmark-btn atmark-btn-secondary" 171 + }); 172 + cancelBtn.addEventListener("click", () => { this.close(); }); 173 + 174 + const saveBtn = actions.createEl("button", { 175 + text: "Save", 176 + cls: "atmark-btn atmark-btn-primary" 177 + }); 178 + saveBtn.addEventListener("click", () => { void this.saveChanges(); }); 179 + } 180 + 181 + private addTagChip(container: HTMLElement, state: TagState) { 182 + const item = container.createEl("label", { cls: "atmark-tag-item" }); 183 + const checkbox = item.createEl("input", { type: "checkbox" }); 184 + checkbox.checked = state.isSelected; 185 + checkbox.addEventListener("change", () => { 186 + state.isSelected = checkbox.checked; 187 + }); 188 + item.createEl("span", { text: state.tag }); 189 + } 190 + 191 + private confirmDelete(contentEl: HTMLElement) { 192 + contentEl.empty(); 193 + contentEl.createEl("h2", { text: "Delete bookmark" }); 194 + contentEl.createEl("p", { text: "Delete this bookmark?", cls: "atmark-warning-text" }); 195 + 196 + const actions = contentEl.createEl("div", { cls: "atmark-modal-actions" }); 197 + 198 + const cancelBtn = actions.createEl("button", { 199 + text: "Cancel", 200 + cls: "atmark-btn atmark-btn-secondary" 201 + }); 202 + cancelBtn.addEventListener("click", () => { 203 + void this.onOpen(); 204 + }); 205 + 206 + const confirmBtn = actions.createEl("button", { 207 + text: "Delete", 208 + cls: "atmark-btn atmark-btn-danger" 209 + }); 210 + confirmBtn.addEventListener("click", () => { void this.deleteBookmark(); }); 211 + } 212 + 213 + private async deleteBookmark() { 214 + if (!this.plugin.client) return; 215 + 216 + const { contentEl } = this; 217 + contentEl.empty(); 218 + contentEl.createEl("p", { text: "Deleting bookmark..." }); 219 + 220 + try { 221 + const rkey = this.record.uri.split("/").pop(); 222 + if (!rkey) { 223 + contentEl.empty(); 224 + contentEl.createEl("p", { text: "Invalid bookmark uri.", cls: "atmark-error" }); 225 + return; 226 + } 227 + 228 + await deleteRecord( 229 + this.plugin.client, 230 + this.plugin.settings.identifier, 231 + "at.margin.bookmark", 232 + rkey 233 + ); 234 + 235 + new Notice("Bookmark deleted"); 236 + this.close(); 237 + this.onSuccess?.(); 238 + } catch (err) { 239 + contentEl.empty(); 240 + const message = err instanceof Error ? err.message : String(err); 241 + contentEl.createEl("p", { text: `Failed to delete: ${message}`, cls: "atmark-error" }); 242 + } 243 + } 244 + 245 + private async saveChanges() { 246 + if (!this.plugin.client) return; 247 + 248 + const { contentEl } = this; 249 + contentEl.empty(); 250 + contentEl.createEl("p", { text: "Saving changes..." }); 251 + 252 + try { 253 + const selectedTags = this.tagStates.filter(s => s.isSelected).map(s => s.tag); 254 + const newTag = this.newTagInput?.value.trim(); 255 + if (newTag && !selectedTags.includes(newTag)) { 256 + selectedTags.push(newTag); 257 + } 258 + const tags = [...new Set(selectedTags)]; 259 + 260 + const rkey = this.record.uri.split("/").pop(); 261 + if (!rkey) { 262 + contentEl.empty(); 263 + contentEl.createEl("p", { text: "Invalid bookmark uri.", cls: "atmark-error" }); 264 + return; 265 + } 266 + 267 + const updatedRecord: MarginBookmark = { 268 + ...this.record.value, 269 + tags, 270 + }; 271 + 272 + await putRecord( 273 + this.plugin.client, 274 + this.plugin.settings.identifier, 275 + "at.margin.bookmark", 276 + rkey, 277 + updatedRecord 278 + ); 279 + 280 + const collectionsToAdd = this.collectionStates.filter(s => s.isSelected && !s.wasSelected); 281 + const collectionsToRemove = this.collectionStates.filter(s => !s.isSelected && s.wasSelected); 282 + 283 + for (const state of collectionsToRemove) { 284 + if (state.linkUri) { 285 + const linkRkey = state.linkUri.split("/").pop(); 286 + if (linkRkey) { 287 + await deleteRecord( 288 + this.plugin.client, 289 + this.plugin.settings.identifier, 290 + "at.margin.collectionItem", 291 + linkRkey 292 + ); 293 + } 294 + } 295 + } 296 + 297 + for (const state of collectionsToAdd) { 298 + await createMarginCollectionItem( 299 + this.plugin.client, 300 + this.plugin.settings.identifier, 301 + this.record.uri, 302 + state.collection.uri 303 + ); 304 + } 305 + 306 + const messages: string[] = []; 307 + if (tags.length !== (this.record.value.tags?.length || 0) || 308 + !tags.every(t => this.record.value.tags?.includes(t))) { 309 + messages.push("Tags updated"); 310 + } 311 + if (collectionsToAdd.length > 0) { 312 + messages.push(`Added to ${collectionsToAdd.length} collection${collectionsToAdd.length > 1 ? "s" : ""}`); 313 + } 314 + if (collectionsToRemove.length > 0) { 315 + messages.push(`Removed from ${collectionsToRemove.length} collection${collectionsToRemove.length > 1 ? "s" : ""}`); 316 + } 317 + 318 + new Notice(messages.length > 0 ? messages.join(". ") : "Saved"); 319 + this.close(); 320 + this.onSuccess?.(); 321 + } catch (err) { 322 + contentEl.empty(); 323 + const message = err instanceof Error ? err.message : String(err); 324 + contentEl.createEl("p", { text: `Failed to save: ${message}`, cls: "atmark-error" }); 325 + } 326 + } 327 + 328 + onClose() { 329 + this.contentEl.empty(); 330 + } 331 + }
+72
src/components/profileIcon.ts
··· 1 + import type { Client } from "@atcute/client"; 2 + import { getProfile } from "../lib"; 3 + 4 + export interface ProfileData { 5 + did: string; 6 + handle: string; 7 + displayName?: string; 8 + avatar?: string; 9 + } 10 + 11 + export async function fetchProfileData(client: Client, actor: string): Promise<ProfileData | null> { 12 + try { 13 + const resp = await getProfile(client, actor); 14 + if (!resp.ok) return null; 15 + 16 + return { 17 + did: resp.data.did, 18 + handle: resp.data.handle, 19 + displayName: resp.data.displayName, 20 + avatar: resp.data.avatar, 21 + }; 22 + } catch (e) { 23 + console.error("Failed to fetch profile:", e); 24 + return null; 25 + } 26 + } 27 + 28 + export function renderProfileIcon( 29 + container: HTMLElement, 30 + profile: ProfileData | null, 31 + onClick?: () => void 32 + ): HTMLElement { 33 + const wrapper = container.createEl("div", { cls: "atmark-profile-icon" }); 34 + 35 + if (!profile) { 36 + // Fallback when no profile data 37 + const placeholder = wrapper.createEl("div", { cls: "atmark-avatar-placeholder" }); 38 + placeholder.createEl("span", { text: "?" }); 39 + return wrapper; 40 + } 41 + 42 + const avatarBtn = wrapper.createEl("button", { cls: "atmark-avatar-btn" }); 43 + 44 + if (profile.avatar) { 45 + const img = avatarBtn.createEl("img", { cls: "atmark-avatar-img" }); 46 + img.src = profile.avatar; 47 + img.alt = profile.displayName || profile.handle; 48 + } else { 49 + // Fallback initials 50 + const initials = (profile.displayName || profile.handle) 51 + .split(" ") 52 + .map(w => w[0]) 53 + .slice(0, 2) 54 + .join("") 55 + .toUpperCase(); 56 + avatarBtn.createEl("span", { text: initials, cls: "atmark-avatar-initials" }); 57 + } 58 + 59 + const info = wrapper.createEl("div", { cls: "atmark-profile-info" }); 60 + 61 + if (profile.displayName) { 62 + info.createEl("span", { text: profile.displayName, cls: "atmark-profile-name" }); 63 + } 64 + 65 + info.createEl("span", { text: `@${profile.handle}`, cls: "atmark-profile-handle" }); 66 + 67 + if (onClick) { 68 + avatarBtn.addEventListener("click", onClick); 69 + } 70 + 71 + return wrapper; 72 + }
+90
src/components/selectPublicationModal.ts
··· 1 + import { Modal } from "obsidian"; 2 + import type ATmarkPlugin from "../main"; 3 + import { getPublications } from "../lib"; 4 + import { SiteStandardPublication } from "@atcute/standard-site"; 5 + import type { ResourceUri } from "@atcute/lexicons"; 6 + 7 + export type PublicationSelection = { 8 + uri: ResourceUri; 9 + publication: SiteStandardPublication.Main; 10 + }; 11 + 12 + export class SelectPublicationModal extends Modal { 13 + plugin: ATmarkPlugin; 14 + onSelect: (selection: PublicationSelection) => void; 15 + 16 + constructor(plugin: ATmarkPlugin, onSelect: (selection: PublicationSelection) => void) { 17 + super(plugin.app); 18 + this.plugin = plugin; 19 + this.onSelect = onSelect; 20 + } 21 + 22 + async onOpen() { 23 + const { contentEl } = this; 24 + contentEl.empty(); 25 + contentEl.addClass("atmark-modal"); 26 + 27 + contentEl.createEl("h2", { text: "Select publication" }); 28 + 29 + if (!this.plugin.client) { 30 + contentEl.createEl("p", { text: "Not logged in", cls: "atmark-error" }); 31 + return; 32 + } 33 + 34 + const loading = contentEl.createEl("p", { text: "Loading publications..." }); 35 + 36 + try { 37 + const response = await getPublications(this.plugin.client, this.plugin.settings.identifier); 38 + loading.remove(); 39 + 40 + let pubs = response.records 41 + 42 + if (pubs.length === 0) { 43 + contentEl.createEl("p", { text: "No publications found. Create one first." }); 44 + return; 45 + } 46 + 47 + // Create a list of publications 48 + const listContainer = contentEl.createEl("div", { cls: "atmark-collection-list" }); 49 + 50 + for (const pub of pubs) { 51 + const item = listContainer.createEl("div", { cls: "atmark-collection-item" }); 52 + 53 + const publication = pub.value; 54 + 55 + const info = item.createEl("div", { cls: "atmark-collection-item-info" }); 56 + info.createEl("div", { text: publication.name, cls: "atmark-collection-item-name" }); 57 + 58 + if (publication.description) { 59 + info.createEl("div", { 60 + text: publication.description, 61 + cls: "atmark-collection-item-desc" 62 + }); 63 + } 64 + 65 + info.createEl("div", { 66 + text: publication.url, 67 + cls: "atmark-collection-item-desc" 68 + }); 69 + 70 + item.addEventListener("click", () => { 71 + this.onSelect({ 72 + uri: pub.uri, 73 + publication, 74 + }); 75 + this.close(); 76 + }); 77 + } 78 + 79 + } catch (error) { 80 + loading.remove(); 81 + const message = error instanceof Error ? error.message : String(error); 82 + contentEl.createEl("p", { text: `Error: ${message}`, cls: "atmark-error" }); 83 + } 84 + } 85 + 86 + onClose() { 87 + const { contentEl } = this; 88 + contentEl.empty(); 89 + } 90 + }
+4
src/env.d.ts
··· 1 + /// <reference types="@atcute/bluesky" /> 2 + /// <reference types="@atcute/atproto" /> 3 + /// <reference types="@atcute/standard-site" /> 4 + /// <reference types="@atcute/leaflet" />
+13
src/lexicons/index.ts
··· 1 + export * as AtMarginAnnotation from "./types/at/margin/annotation.js"; 2 + export * as AtMarginBookmark from "./types/at/margin/bookmark.js"; 3 + export * as AtMarginCollection from "./types/at/margin/collection.js"; 4 + export * as AtMarginCollectionItem from "./types/at/margin/collectionItem.js"; 5 + export * as AtMarginHighlight from "./types/at/margin/highlight.js"; 6 + export * as AtMarginLike from "./types/at/margin/like.js"; 7 + export * as AtMarginProfile from "./types/at/margin/profile.js"; 8 + export * as AtMarginReply from "./types/at/margin/reply.js"; 9 + export * as ComAtprotoRepoStrongRef from "./types/com/atproto/repo/strongRef.js"; 10 + export * as NetworkCosmikCard from "./types/network/cosmik/card.js"; 11 + export * as NetworkCosmikCollection from "./types/network/cosmik/collection.js"; 12 + export * as NetworkCosmikCollectionLink from "./types/network/cosmik/collectionLink.js"; 13 + export * as NetworkCosmikDefs from "./types/network/cosmik/defs.js";
+330
src/lexicons/types/at/margin/annotation.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + 5 + const _bodySchema = /*#__PURE__*/ v.object({ 6 + $type: /*#__PURE__*/ v.optional( 7 + /*#__PURE__*/ v.literal("at.margin.annotation#body"), 8 + ), 9 + /** 10 + * MIME type of the body content 11 + * @default "text/plain" 12 + */ 13 + format: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string(), "text/plain"), 14 + /** 15 + * BCP47 language tag 16 + */ 17 + language: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 18 + /** 19 + * Reference to external body content 20 + */ 21 + uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.genericUriString()), 22 + /** 23 + * Text content of the annotation 24 + * @maxLength 10000 25 + * @maxGraphemes 3000 26 + */ 27 + value: /*#__PURE__*/ v.optional( 28 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 29 + /*#__PURE__*/ v.stringLength(0, 10000), 30 + /*#__PURE__*/ v.stringGraphemes(0, 3000), 31 + ]), 32 + ), 33 + }); 34 + const _cssSelectorSchema = /*#__PURE__*/ v.object({ 35 + $type: /*#__PURE__*/ v.optional( 36 + /*#__PURE__*/ v.literal("at.margin.annotation#cssSelector"), 37 + ), 38 + type: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.literal("CssSelector")), 39 + /** 40 + * CSS selector string 41 + * @maxLength 2000 42 + */ 43 + value: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 44 + /*#__PURE__*/ v.stringLength(0, 2000), 45 + ]), 46 + }); 47 + const _fragmentSelectorSchema = /*#__PURE__*/ v.object({ 48 + $type: /*#__PURE__*/ v.optional( 49 + /*#__PURE__*/ v.literal("at.margin.annotation#fragmentSelector"), 50 + ), 51 + /** 52 + * Specification the fragment conforms to 53 + */ 54 + conformsTo: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.genericUriString()), 55 + type: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.literal("FragmentSelector")), 56 + /** 57 + * Fragment identifier value 58 + * @maxLength 1000 59 + */ 60 + value: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 61 + /*#__PURE__*/ v.stringLength(0, 1000), 62 + ]), 63 + }); 64 + const _mainSchema = /*#__PURE__*/ v.record( 65 + /*#__PURE__*/ v.tidString(), 66 + /*#__PURE__*/ v.object({ 67 + $type: /*#__PURE__*/ v.literal("at.margin.annotation"), 68 + /** 69 + * The annotation content (text or reference) 70 + */ 71 + get body() { 72 + return /*#__PURE__*/ v.optional(bodySchema); 73 + }, 74 + createdAt: /*#__PURE__*/ v.datetimeString(), 75 + /** 76 + * W3C motivation for the annotation 77 + */ 78 + motivation: /*#__PURE__*/ v.optional( 79 + /*#__PURE__*/ v.string< 80 + | "assessing" 81 + | "bookmarking" 82 + | "commenting" 83 + | "describing" 84 + | "editing" 85 + | "highlighting" 86 + | "linking" 87 + | "questioning" 88 + | "replying" 89 + | "tagging" 90 + | (string & {}) 91 + >(), 92 + ), 93 + /** 94 + * Tags for categorization 95 + * @maxLength 10 96 + */ 97 + tags: /*#__PURE__*/ v.optional( 98 + /*#__PURE__*/ v.constrain( 99 + /*#__PURE__*/ v.array( 100 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 101 + /*#__PURE__*/ v.stringLength(0, 64), 102 + /*#__PURE__*/ v.stringGraphemes(0, 32), 103 + ]), 104 + ), 105 + [/*#__PURE__*/ v.arrayLength(0, 10)], 106 + ), 107 + ), 108 + /** 109 + * The resource being annotated with optional selector 110 + */ 111 + get target() { 112 + return targetSchema; 113 + }, 114 + }), 115 + ); 116 + const _rangeSelectorSchema = /*#__PURE__*/ v.object({ 117 + $type: /*#__PURE__*/ v.optional( 118 + /*#__PURE__*/ v.literal("at.margin.annotation#rangeSelector"), 119 + ), 120 + /** 121 + * Selector for range end 122 + */ 123 + get endSelector() { 124 + return /*#__PURE__*/ v.variant([ 125 + cssSelectorSchema, 126 + textPositionSelectorSchema, 127 + textQuoteSelectorSchema, 128 + xpathSelectorSchema, 129 + ]); 130 + }, 131 + /** 132 + * Selector for range start 133 + */ 134 + get startSelector() { 135 + return /*#__PURE__*/ v.variant([ 136 + cssSelectorSchema, 137 + textPositionSelectorSchema, 138 + textQuoteSelectorSchema, 139 + xpathSelectorSchema, 140 + ]); 141 + }, 142 + type: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.literal("RangeSelector")), 143 + }); 144 + const _targetSchema = /*#__PURE__*/ v.object({ 145 + $type: /*#__PURE__*/ v.optional( 146 + /*#__PURE__*/ v.literal("at.margin.annotation#target"), 147 + ), 148 + /** 149 + * Selector to identify the specific segment 150 + */ 151 + get selector() { 152 + return /*#__PURE__*/ v.optional( 153 + /*#__PURE__*/ v.variant([ 154 + cssSelectorSchema, 155 + fragmentSelectorSchema, 156 + rangeSelectorSchema, 157 + textPositionSelectorSchema, 158 + textQuoteSelectorSchema, 159 + xpathSelectorSchema, 160 + ]), 161 + ); 162 + }, 163 + /** 164 + * The URL being annotated 165 + */ 166 + source: /*#__PURE__*/ v.genericUriString(), 167 + /** 168 + * SHA256 hash of normalized URL for indexing 169 + */ 170 + sourceHash: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 171 + /** 172 + * State of the resource at annotation time 173 + */ 174 + get state() { 175 + return /*#__PURE__*/ v.optional(timeStateSchema); 176 + }, 177 + /** 178 + * Page title at time of annotation 179 + * @maxLength 500 180 + */ 181 + title: /*#__PURE__*/ v.optional( 182 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 183 + /*#__PURE__*/ v.stringLength(0, 500), 184 + ]), 185 + ), 186 + }); 187 + const _textPositionSelectorSchema = /*#__PURE__*/ v.object({ 188 + $type: /*#__PURE__*/ v.optional( 189 + /*#__PURE__*/ v.literal("at.margin.annotation#textPositionSelector"), 190 + ), 191 + /** 192 + * Ending character position (exclusive) 193 + * @minimum 0 194 + */ 195 + end: /*#__PURE__*/ v.integer(), 196 + /** 197 + * Starting character position (0-indexed, inclusive) 198 + * @minimum 0 199 + */ 200 + start: /*#__PURE__*/ v.integer(), 201 + type: /*#__PURE__*/ v.optional( 202 + /*#__PURE__*/ v.literal("TextPositionSelector"), 203 + ), 204 + }); 205 + const _textQuoteSelectorSchema = /*#__PURE__*/ v.object({ 206 + $type: /*#__PURE__*/ v.optional( 207 + /*#__PURE__*/ v.literal("at.margin.annotation#textQuoteSelector"), 208 + ), 209 + /** 210 + * The exact text to match 211 + * @maxLength 5000 212 + * @maxGraphemes 1500 213 + */ 214 + exact: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 215 + /*#__PURE__*/ v.stringLength(0, 5000), 216 + /*#__PURE__*/ v.stringGraphemes(0, 1500), 217 + ]), 218 + /** 219 + * Text immediately before the selection 220 + * @maxLength 500 221 + * @maxGraphemes 150 222 + */ 223 + prefix: /*#__PURE__*/ v.optional( 224 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 225 + /*#__PURE__*/ v.stringLength(0, 500), 226 + /*#__PURE__*/ v.stringGraphemes(0, 150), 227 + ]), 228 + ), 229 + /** 230 + * Text immediately after the selection 231 + * @maxLength 500 232 + * @maxGraphemes 150 233 + */ 234 + suffix: /*#__PURE__*/ v.optional( 235 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 236 + /*#__PURE__*/ v.stringLength(0, 500), 237 + /*#__PURE__*/ v.stringGraphemes(0, 150), 238 + ]), 239 + ), 240 + type: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.literal("TextQuoteSelector")), 241 + }); 242 + const _timeStateSchema = /*#__PURE__*/ v.object({ 243 + $type: /*#__PURE__*/ v.optional( 244 + /*#__PURE__*/ v.literal("at.margin.annotation#timeState"), 245 + ), 246 + /** 247 + * URL to cached/archived version 248 + */ 249 + cached: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.genericUriString()), 250 + /** 251 + * When the source was accessed 252 + */ 253 + sourceDate: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 254 + }); 255 + const _xpathSelectorSchema = /*#__PURE__*/ v.object({ 256 + $type: /*#__PURE__*/ v.optional( 257 + /*#__PURE__*/ v.literal("at.margin.annotation#xpathSelector"), 258 + ), 259 + type: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.literal("XPathSelector")), 260 + /** 261 + * XPath expression 262 + * @maxLength 2000 263 + */ 264 + value: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 265 + /*#__PURE__*/ v.stringLength(0, 2000), 266 + ]), 267 + }); 268 + 269 + type body$schematype = typeof _bodySchema; 270 + type cssSelector$schematype = typeof _cssSelectorSchema; 271 + type fragmentSelector$schematype = typeof _fragmentSelectorSchema; 272 + type main$schematype = typeof _mainSchema; 273 + type rangeSelector$schematype = typeof _rangeSelectorSchema; 274 + type target$schematype = typeof _targetSchema; 275 + type textPositionSelector$schematype = typeof _textPositionSelectorSchema; 276 + type textQuoteSelector$schematype = typeof _textQuoteSelectorSchema; 277 + type timeState$schematype = typeof _timeStateSchema; 278 + type xpathSelector$schematype = typeof _xpathSelectorSchema; 279 + 280 + export interface bodySchema extends body$schematype {} 281 + export interface cssSelectorSchema extends cssSelector$schematype {} 282 + export interface fragmentSelectorSchema extends fragmentSelector$schematype {} 283 + export interface mainSchema extends main$schematype {} 284 + export interface rangeSelectorSchema extends rangeSelector$schematype {} 285 + export interface targetSchema extends target$schematype {} 286 + export interface textPositionSelectorSchema extends textPositionSelector$schematype {} 287 + export interface textQuoteSelectorSchema extends textQuoteSelector$schematype {} 288 + export interface timeStateSchema extends timeState$schematype {} 289 + export interface xpathSelectorSchema extends xpathSelector$schematype {} 290 + 291 + export const bodySchema = _bodySchema as bodySchema; 292 + export const cssSelectorSchema = _cssSelectorSchema as cssSelectorSchema; 293 + export const fragmentSelectorSchema = 294 + _fragmentSelectorSchema as fragmentSelectorSchema; 295 + export const mainSchema = _mainSchema as mainSchema; 296 + export const rangeSelectorSchema = _rangeSelectorSchema as rangeSelectorSchema; 297 + export const targetSchema = _targetSchema as targetSchema; 298 + export const textPositionSelectorSchema = 299 + _textPositionSelectorSchema as textPositionSelectorSchema; 300 + export const textQuoteSelectorSchema = 301 + _textQuoteSelectorSchema as textQuoteSelectorSchema; 302 + export const timeStateSchema = _timeStateSchema as timeStateSchema; 303 + export const xpathSelectorSchema = _xpathSelectorSchema as xpathSelectorSchema; 304 + 305 + export interface Body extends v.InferInput<typeof bodySchema> {} 306 + export interface CssSelector extends v.InferInput<typeof cssSelectorSchema> {} 307 + export interface FragmentSelector extends v.InferInput< 308 + typeof fragmentSelectorSchema 309 + > {} 310 + export interface Main extends v.InferInput<typeof mainSchema> {} 311 + export interface RangeSelector extends v.InferInput< 312 + typeof rangeSelectorSchema 313 + > {} 314 + export interface Target extends v.InferInput<typeof targetSchema> {} 315 + export interface TextPositionSelector extends v.InferInput< 316 + typeof textPositionSelectorSchema 317 + > {} 318 + export interface TextQuoteSelector extends v.InferInput< 319 + typeof textQuoteSelectorSchema 320 + > {} 321 + export interface TimeState extends v.InferInput<typeof timeStateSchema> {} 322 + export interface XpathSelector extends v.InferInput< 323 + typeof xpathSelectorSchema 324 + > {} 325 + 326 + declare module "@atcute/lexicons/ambient" { 327 + interface Records { 328 + "at.margin.annotation": mainSchema; 329 + } 330 + }
+68
src/lexicons/types/at/margin/bookmark.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + 5 + const _mainSchema = /*#__PURE__*/ v.record( 6 + /*#__PURE__*/ v.tidString(), 7 + /*#__PURE__*/ v.object({ 8 + $type: /*#__PURE__*/ v.literal("at.margin.bookmark"), 9 + createdAt: /*#__PURE__*/ v.datetimeString(), 10 + /** 11 + * Optional description/note 12 + * @maxLength 1000 13 + * @maxGraphemes 300 14 + */ 15 + description: /*#__PURE__*/ v.optional( 16 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 17 + /*#__PURE__*/ v.stringLength(0, 1000), 18 + /*#__PURE__*/ v.stringGraphemes(0, 300), 19 + ]), 20 + ), 21 + /** 22 + * The bookmarked URL 23 + */ 24 + source: /*#__PURE__*/ v.genericUriString(), 25 + /** 26 + * SHA256 hash of normalized URL for indexing 27 + */ 28 + sourceHash: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 29 + /** 30 + * Tags for categorization 31 + * @maxLength 10 32 + */ 33 + tags: /*#__PURE__*/ v.optional( 34 + /*#__PURE__*/ v.constrain( 35 + /*#__PURE__*/ v.array( 36 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 37 + /*#__PURE__*/ v.stringLength(0, 64), 38 + /*#__PURE__*/ v.stringGraphemes(0, 32), 39 + ]), 40 + ), 41 + [/*#__PURE__*/ v.arrayLength(0, 10)], 42 + ), 43 + ), 44 + /** 45 + * Page title 46 + * @maxLength 500 47 + */ 48 + title: /*#__PURE__*/ v.optional( 49 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 50 + /*#__PURE__*/ v.stringLength(0, 500), 51 + ]), 52 + ), 53 + }), 54 + ); 55 + 56 + type main$schematype = typeof _mainSchema; 57 + 58 + export interface mainSchema extends main$schematype {} 59 + 60 + export const mainSchema = _mainSchema as mainSchema; 61 + 62 + export interface Main extends v.InferInput<typeof mainSchema> {} 63 + 64 + declare module "@atcute/lexicons/ambient" { 65 + interface Records { 66 + "at.margin.bookmark": mainSchema; 67 + } 68 + }
+56
src/lexicons/types/at/margin/collection.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + 5 + const _mainSchema = /*#__PURE__*/ v.record( 6 + /*#__PURE__*/ v.tidString(), 7 + /*#__PURE__*/ v.object({ 8 + $type: /*#__PURE__*/ v.literal("at.margin.collection"), 9 + createdAt: /*#__PURE__*/ v.datetimeString(), 10 + /** 11 + * Collection description 12 + * @maxLength 500 13 + * @maxGraphemes 150 14 + */ 15 + description: /*#__PURE__*/ v.optional( 16 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 17 + /*#__PURE__*/ v.stringLength(0, 500), 18 + /*#__PURE__*/ v.stringGraphemes(0, 150), 19 + ]), 20 + ), 21 + /** 22 + * Emoji icon or icon identifier for the collection 23 + * @maxLength 100 24 + * @maxGraphemes 100 25 + */ 26 + icon: /*#__PURE__*/ v.optional( 27 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 28 + /*#__PURE__*/ v.stringLength(0, 100), 29 + /*#__PURE__*/ v.stringGraphemes(0, 100), 30 + ]), 31 + ), 32 + /** 33 + * Collection name 34 + * @maxLength 100 35 + * @maxGraphemes 50 36 + */ 37 + name: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 38 + /*#__PURE__*/ v.stringLength(0, 100), 39 + /*#__PURE__*/ v.stringGraphemes(0, 50), 40 + ]), 41 + }), 42 + ); 43 + 44 + type main$schematype = typeof _mainSchema; 45 + 46 + export interface mainSchema extends main$schematype {} 47 + 48 + export const mainSchema = _mainSchema as mainSchema; 49 + 50 + export interface Main extends v.InferInput<typeof mainSchema> {} 51 + 52 + declare module "@atcute/lexicons/ambient" { 53 + interface Records { 54 + "at.margin.collection": mainSchema; 55 + } 56 + }
+38
src/lexicons/types/at/margin/collectionItem.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + 5 + const _mainSchema = /*#__PURE__*/ v.record( 6 + /*#__PURE__*/ v.tidString(), 7 + /*#__PURE__*/ v.object({ 8 + $type: /*#__PURE__*/ v.literal("at.margin.collectionItem"), 9 + /** 10 + * AT URI of the annotation, highlight, or bookmark 11 + */ 12 + annotation: /*#__PURE__*/ v.resourceUriString(), 13 + /** 14 + * AT URI of the collection 15 + */ 16 + collection: /*#__PURE__*/ v.resourceUriString(), 17 + createdAt: /*#__PURE__*/ v.datetimeString(), 18 + /** 19 + * Sort order within the collection 20 + * @minimum 0 21 + */ 22 + position: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 23 + }), 24 + ); 25 + 26 + type main$schematype = typeof _mainSchema; 27 + 28 + export interface mainSchema extends main$schematype {} 29 + 30 + export const mainSchema = _mainSchema as mainSchema; 31 + 32 + export interface Main extends v.InferInput<typeof mainSchema> {} 33 + 34 + declare module "@atcute/lexicons/ambient" { 35 + interface Records { 36 + "at.margin.collectionItem": mainSchema; 37 + } 38 + }
+56
src/lexicons/types/at/margin/highlight.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + import * as AtMarginAnnotation from "./annotation.js"; 5 + 6 + const _mainSchema = /*#__PURE__*/ v.record( 7 + /*#__PURE__*/ v.tidString(), 8 + /*#__PURE__*/ v.object({ 9 + $type: /*#__PURE__*/ v.literal("at.margin.highlight"), 10 + /** 11 + * Highlight color (hex or named) 12 + * @maxLength 20 13 + */ 14 + color: /*#__PURE__*/ v.optional( 15 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 16 + /*#__PURE__*/ v.stringLength(0, 20), 17 + ]), 18 + ), 19 + createdAt: /*#__PURE__*/ v.datetimeString(), 20 + /** 21 + * Tags for categorization 22 + * @maxLength 10 23 + */ 24 + tags: /*#__PURE__*/ v.optional( 25 + /*#__PURE__*/ v.constrain( 26 + /*#__PURE__*/ v.array( 27 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 28 + /*#__PURE__*/ v.stringLength(0, 64), 29 + /*#__PURE__*/ v.stringGraphemes(0, 32), 30 + ]), 31 + ), 32 + [/*#__PURE__*/ v.arrayLength(0, 10)], 33 + ), 34 + ), 35 + /** 36 + * The resource and segment being highlighted 37 + */ 38 + get target() { 39 + return AtMarginAnnotation.targetSchema; 40 + }, 41 + }), 42 + ); 43 + 44 + type main$schematype = typeof _mainSchema; 45 + 46 + export interface mainSchema extends main$schematype {} 47 + 48 + export const mainSchema = _mainSchema as mainSchema; 49 + 50 + export interface Main extends v.InferInput<typeof mainSchema> {} 51 + 52 + declare module "@atcute/lexicons/ambient" { 53 + interface Records { 54 + "at.margin.highlight": mainSchema; 55 + } 56 + }
+42
src/lexicons/types/at/margin/like.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + 5 + const _mainSchema = /*#__PURE__*/ v.record( 6 + /*#__PURE__*/ v.tidString(), 7 + /*#__PURE__*/ v.object({ 8 + $type: /*#__PURE__*/ v.literal("at.margin.like"), 9 + createdAt: /*#__PURE__*/ v.datetimeString(), 10 + /** 11 + * Reference to the annotation or reply being liked 12 + */ 13 + get subject() { 14 + return subjectRefSchema; 15 + }, 16 + }), 17 + ); 18 + const _subjectRefSchema = /*#__PURE__*/ v.object({ 19 + $type: /*#__PURE__*/ v.optional( 20 + /*#__PURE__*/ v.literal("at.margin.like#subjectRef"), 21 + ), 22 + cid: /*#__PURE__*/ v.cidString(), 23 + uri: /*#__PURE__*/ v.resourceUriString(), 24 + }); 25 + 26 + type main$schematype = typeof _mainSchema; 27 + type subjectRef$schematype = typeof _subjectRefSchema; 28 + 29 + export interface mainSchema extends main$schematype {} 30 + export interface subjectRefSchema extends subjectRef$schematype {} 31 + 32 + export const mainSchema = _mainSchema as mainSchema; 33 + export const subjectRefSchema = _subjectRefSchema as subjectRefSchema; 34 + 35 + export interface Main extends v.InferInput<typeof mainSchema> {} 36 + export interface SubjectRef extends v.InferInput<typeof subjectRefSchema> {} 37 + 38 + declare module "@atcute/lexicons/ambient" { 39 + interface Records { 40 + "at.margin.like": mainSchema; 41 + } 42 + }
+72
src/lexicons/types/at/margin/profile.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + 5 + const _mainSchema = /*#__PURE__*/ v.record( 6 + /*#__PURE__*/ v.literal("self"), 7 + /*#__PURE__*/ v.object({ 8 + $type: /*#__PURE__*/ v.literal("at.margin.profile"), 9 + /** 10 + * User avatar image. 11 + * @accept image/png, image/jpeg 12 + * @maxSize 1000000 13 + */ 14 + avatar: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.blob()), 15 + /** 16 + * User biography or description. 17 + * @maxLength 5000 18 + */ 19 + bio: /*#__PURE__*/ v.optional( 20 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 21 + /*#__PURE__*/ v.stringLength(0, 5000), 22 + ]), 23 + ), 24 + createdAt: /*#__PURE__*/ v.datetimeString(), 25 + /** 26 + * Display name for the user. 27 + * @maxLength 640 28 + */ 29 + displayName: /*#__PURE__*/ v.optional( 30 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 31 + /*#__PURE__*/ v.stringLength(0, 640), 32 + ]), 33 + ), 34 + /** 35 + * List of other relevant links (e.g. GitHub, Bluesky, etc). 36 + * @maxLength 20 37 + */ 38 + links: /*#__PURE__*/ v.optional( 39 + /*#__PURE__*/ v.constrain( 40 + /*#__PURE__*/ v.array( 41 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 42 + /*#__PURE__*/ v.stringLength(0, 1000), 43 + ]), 44 + ), 45 + [/*#__PURE__*/ v.arrayLength(0, 20)], 46 + ), 47 + ), 48 + /** 49 + * User website URL. 50 + * @maxLength 1000 51 + */ 52 + website: /*#__PURE__*/ v.optional( 53 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 54 + /*#__PURE__*/ v.stringLength(0, 1000), 55 + ]), 56 + ), 57 + }), 58 + ); 59 + 60 + type main$schematype = typeof _mainSchema; 61 + 62 + export interface mainSchema extends main$schematype {} 63 + 64 + export const mainSchema = _mainSchema as mainSchema; 65 + 66 + export interface Main extends v.InferInput<typeof mainSchema> {} 67 + 68 + declare module "@atcute/lexicons/ambient" { 69 + interface Records { 70 + "at.margin.profile": mainSchema; 71 + } 72 + }
+62
src/lexicons/types/at/margin/reply.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + 5 + const _mainSchema = /*#__PURE__*/ v.record( 6 + /*#__PURE__*/ v.tidString(), 7 + /*#__PURE__*/ v.object({ 8 + $type: /*#__PURE__*/ v.literal("at.margin.reply"), 9 + createdAt: /*#__PURE__*/ v.datetimeString(), 10 + /** 11 + * MIME type of the text content 12 + * @default "text/plain" 13 + */ 14 + format: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string(), "text/plain"), 15 + /** 16 + * Reference to the parent annotation or reply 17 + */ 18 + get parent() { 19 + return replyRefSchema; 20 + }, 21 + /** 22 + * Reference to the root annotation of the thread 23 + */ 24 + get root() { 25 + return replyRefSchema; 26 + }, 27 + /** 28 + * Reply text content 29 + * @maxLength 10000 30 + * @maxGraphemes 3000 31 + */ 32 + text: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 33 + /*#__PURE__*/ v.stringLength(0, 10000), 34 + /*#__PURE__*/ v.stringGraphemes(0, 3000), 35 + ]), 36 + }), 37 + ); 38 + const _replyRefSchema = /*#__PURE__*/ v.object({ 39 + $type: /*#__PURE__*/ v.optional( 40 + /*#__PURE__*/ v.literal("at.margin.reply#replyRef"), 41 + ), 42 + cid: /*#__PURE__*/ v.cidString(), 43 + uri: /*#__PURE__*/ v.resourceUriString(), 44 + }); 45 + 46 + type main$schematype = typeof _mainSchema; 47 + type replyRef$schematype = typeof _replyRefSchema; 48 + 49 + export interface mainSchema extends main$schematype {} 50 + export interface replyRefSchema extends replyRef$schematype {} 51 + 52 + export const mainSchema = _mainSchema as mainSchema; 53 + export const replyRefSchema = _replyRefSchema as replyRefSchema; 54 + 55 + export interface Main extends v.InferInput<typeof mainSchema> {} 56 + export interface ReplyRef extends v.InferInput<typeof replyRefSchema> {} 57 + 58 + declare module "@atcute/lexicons/ambient" { 59 + interface Records { 60 + "at.margin.reply": mainSchema; 61 + } 62 + }
+18
src/lexicons/types/com/atproto/repo/strongRef.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + 4 + const _mainSchema = /*#__PURE__*/ v.object({ 5 + $type: /*#__PURE__*/ v.optional( 6 + /*#__PURE__*/ v.literal("com.atproto.repo.strongRef"), 7 + ), 8 + cid: /*#__PURE__*/ v.cidString(), 9 + uri: /*#__PURE__*/ v.resourceUriString(), 10 + }); 11 + 12 + type main$schematype = typeof _mainSchema; 13 + 14 + export interface mainSchema extends main$schematype {} 15 + 16 + export const mainSchema = _mainSchema as mainSchema; 17 + 18 + export interface Main extends v.InferInput<typeof mainSchema> {}
+19
src/lexicons/types/community/lexicon/bookmarks/bookmark.ts
··· 1 + /** 2 + * community.lexicon.bookmarks.bookmark 3 + */ 4 + 5 + export interface Main { 6 + $type?: "community.lexicon.bookmarks.bookmark"; 7 + subject: string; 8 + title?: string; 9 + description?: string; 10 + tags?: string[]; 11 + enriched?: { 12 + title?: string; 13 + description?: string; 14 + image?: string; 15 + thumb?: string; 16 + siteName?: string; 17 + }; 18 + createdAt: string; 19 + }
+146
src/lexicons/types/network/cosmik/card.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + import * as ComAtprotoRepoStrongRef from "../../com/atproto/repo/strongRef.js"; 5 + import * as NetworkCosmikDefs from "./defs.js"; 6 + 7 + const _mainSchema = /*#__PURE__*/ v.record( 8 + /*#__PURE__*/ v.tidString(), 9 + /*#__PURE__*/ v.object({ 10 + $type: /*#__PURE__*/ v.literal("network.cosmik.card"), 11 + /** 12 + * The specific content of the card, determined by the card type. 13 + */ 14 + get content() { 15 + return /*#__PURE__*/ v.variant([noteContentSchema, urlContentSchema]); 16 + }, 17 + /** 18 + * Timestamp when this card was created (usually set by PDS). 19 + */ 20 + createdAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 21 + /** 22 + * Optional strong reference to the original card (for NOTE cards). 23 + */ 24 + get originalCard() { 25 + return /*#__PURE__*/ v.optional(ComAtprotoRepoStrongRef.mainSchema); 26 + }, 27 + /** 28 + * Optional strong reference to a parent card (for NOTE cards). 29 + */ 30 + get parentCard() { 31 + return /*#__PURE__*/ v.optional(ComAtprotoRepoStrongRef.mainSchema); 32 + }, 33 + /** 34 + * Optional provenance information for this card. 35 + */ 36 + get provenance() { 37 + return /*#__PURE__*/ v.optional(NetworkCosmikDefs.provenanceSchema); 38 + }, 39 + /** 40 + * The type of card 41 + */ 42 + type: /*#__PURE__*/ v.string<"NOTE" | "URL" | (string & {})>(), 43 + /** 44 + * Optional URL associated with the card. Required for URL cards, optional for NOTE cards. 45 + */ 46 + url: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.genericUriString()), 47 + }), 48 + ); 49 + const _noteContentSchema = /*#__PURE__*/ v.object({ 50 + $type: /*#__PURE__*/ v.optional( 51 + /*#__PURE__*/ v.literal("network.cosmik.card#noteContent"), 52 + ), 53 + /** 54 + * The note text content 55 + * @maxLength 10000 56 + */ 57 + text: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 58 + /*#__PURE__*/ v.stringLength(0, 10000), 59 + ]), 60 + }); 61 + const _urlContentSchema = /*#__PURE__*/ v.object({ 62 + $type: /*#__PURE__*/ v.optional( 63 + /*#__PURE__*/ v.literal("network.cosmik.card#urlContent"), 64 + ), 65 + /** 66 + * Optional metadata about the URL 67 + */ 68 + get metadata() { 69 + return /*#__PURE__*/ v.optional(urlMetadataSchema); 70 + }, 71 + /** 72 + * The URL being saved 73 + */ 74 + url: /*#__PURE__*/ v.genericUriString(), 75 + }); 76 + const _urlMetadataSchema = /*#__PURE__*/ v.object({ 77 + $type: /*#__PURE__*/ v.optional( 78 + /*#__PURE__*/ v.literal("network.cosmik.card#urlMetadata"), 79 + ), 80 + /** 81 + * Author of the content 82 + */ 83 + author: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 84 + /** 85 + * Description of the page 86 + */ 87 + description: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 88 + /** 89 + * Digital Object Identifier (DOI) for academic content 90 + */ 91 + doi: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 92 + /** 93 + * URL of a representative image 94 + */ 95 + imageUrl: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.genericUriString()), 96 + /** 97 + * International Standard Book Number (ISBN) for books 98 + */ 99 + isbn: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 100 + /** 101 + * When the content was published 102 + */ 103 + publishedDate: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 104 + /** 105 + * When the metadata was retrieved 106 + */ 107 + retrievedAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 108 + /** 109 + * Name of the site 110 + */ 111 + siteName: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 112 + /** 113 + * Title of the page 114 + */ 115 + title: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 116 + /** 117 + * Type of content (e.g., 'video', 'article') 118 + */ 119 + type: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 120 + }); 121 + 122 + type main$schematype = typeof _mainSchema; 123 + type noteContent$schematype = typeof _noteContentSchema; 124 + type urlContent$schematype = typeof _urlContentSchema; 125 + type urlMetadata$schematype = typeof _urlMetadataSchema; 126 + 127 + export interface mainSchema extends main$schematype {} 128 + export interface noteContentSchema extends noteContent$schematype {} 129 + export interface urlContentSchema extends urlContent$schematype {} 130 + export interface urlMetadataSchema extends urlMetadata$schematype {} 131 + 132 + export const mainSchema = _mainSchema as mainSchema; 133 + export const noteContentSchema = _noteContentSchema as noteContentSchema; 134 + export const urlContentSchema = _urlContentSchema as urlContentSchema; 135 + export const urlMetadataSchema = _urlMetadataSchema as urlMetadataSchema; 136 + 137 + export interface Main extends v.InferInput<typeof mainSchema> {} 138 + export interface NoteContent extends v.InferInput<typeof noteContentSchema> {} 139 + export interface UrlContent extends v.InferInput<typeof urlContentSchema> {} 140 + export interface UrlMetadata extends v.InferInput<typeof urlMetadataSchema> {} 141 + 142 + declare module "@atcute/lexicons/ambient" { 143 + interface Records { 144 + "network.cosmik.card": mainSchema; 145 + } 146 + }
+58
src/lexicons/types/network/cosmik/collection.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + 5 + const _mainSchema = /*#__PURE__*/ v.record( 6 + /*#__PURE__*/ v.tidString(), 7 + /*#__PURE__*/ v.object({ 8 + $type: /*#__PURE__*/ v.literal("network.cosmik.collection"), 9 + /** 10 + * Access control for the collection 11 + */ 12 + accessType: /*#__PURE__*/ v.string<"CLOSED" | "OPEN" | (string & {})>(), 13 + /** 14 + * List of collaborator DIDs who can add cards to closed collections 15 + */ 16 + collaborators: /*#__PURE__*/ v.optional( 17 + /*#__PURE__*/ v.array(/*#__PURE__*/ v.string()), 18 + ), 19 + /** 20 + * Timestamp when this collection was created (usually set by PDS). 21 + */ 22 + createdAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 23 + /** 24 + * Description of the collection 25 + * @maxLength 500 26 + */ 27 + description: /*#__PURE__*/ v.optional( 28 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 29 + /*#__PURE__*/ v.stringLength(0, 500), 30 + ]), 31 + ), 32 + /** 33 + * Name of the collection 34 + * @maxLength 100 35 + */ 36 + name: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 37 + /*#__PURE__*/ v.stringLength(0, 100), 38 + ]), 39 + /** 40 + * Timestamp when this collection was last updated. 41 + */ 42 + updatedAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 43 + }), 44 + ); 45 + 46 + type main$schematype = typeof _mainSchema; 47 + 48 + export interface mainSchema extends main$schematype {} 49 + 50 + export const mainSchema = _mainSchema as mainSchema; 51 + 52 + export interface Main extends v.InferInput<typeof mainSchema> {} 53 + 54 + declare module "@atcute/lexicons/ambient" { 55 + interface Records { 56 + "network.cosmik.collection": mainSchema; 57 + } 58 + }
+62
src/lexicons/types/network/cosmik/collectionLink.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + import * as ComAtprotoRepoStrongRef from "../../com/atproto/repo/strongRef.js"; 5 + import * as NetworkCosmikDefs from "./defs.js"; 6 + 7 + const _mainSchema = /*#__PURE__*/ v.record( 8 + /*#__PURE__*/ v.tidString(), 9 + /*#__PURE__*/ v.object({ 10 + $type: /*#__PURE__*/ v.literal("network.cosmik.collectionLink"), 11 + /** 12 + * Timestamp when the card was added to the collection. 13 + */ 14 + addedAt: /*#__PURE__*/ v.datetimeString(), 15 + /** 16 + * DID of the user who added the card to the collection 17 + */ 18 + addedBy: /*#__PURE__*/ v.string(), 19 + /** 20 + * Strong reference to the card record in the users library. 21 + */ 22 + get card() { 23 + return ComAtprotoRepoStrongRef.mainSchema; 24 + }, 25 + /** 26 + * Strong reference to the collection record. 27 + */ 28 + get collection() { 29 + return ComAtprotoRepoStrongRef.mainSchema; 30 + }, 31 + /** 32 + * Timestamp when this link record was created (usually set by PDS). 33 + */ 34 + createdAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 35 + /** 36 + * Strong reference to the original card record (may be in another library). 37 + */ 38 + get originalCard() { 39 + return /*#__PURE__*/ v.optional(ComAtprotoRepoStrongRef.mainSchema); 40 + }, 41 + /** 42 + * Optional provenance information for this link. 43 + */ 44 + get provenance() { 45 + return /*#__PURE__*/ v.optional(NetworkCosmikDefs.provenanceSchema); 46 + }, 47 + }), 48 + ); 49 + 50 + type main$schematype = typeof _mainSchema; 51 + 52 + export interface mainSchema extends main$schematype {} 53 + 54 + export const mainSchema = _mainSchema as mainSchema; 55 + 56 + export interface Main extends v.InferInput<typeof mainSchema> {} 57 + 58 + declare module "@atcute/lexicons/ambient" { 59 + interface Records { 60 + "network.cosmik.collectionLink": mainSchema; 61 + } 62 + }
+23
src/lexicons/types/network/cosmik/defs.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import * as ComAtprotoRepoStrongRef from "../../com/atproto/repo/strongRef.js"; 4 + 5 + const _provenanceSchema = /*#__PURE__*/ v.object({ 6 + $type: /*#__PURE__*/ v.optional( 7 + /*#__PURE__*/ v.literal("network.cosmik.defs#provenance"), 8 + ), 9 + /** 10 + * Strong reference to the card that led to this record. 11 + */ 12 + get via() { 13 + return /*#__PURE__*/ v.optional(ComAtprotoRepoStrongRef.mainSchema); 14 + }, 15 + }); 16 + 17 + type provenance$schematype = typeof _provenanceSchema; 18 + 19 + export interface provenanceSchema extends provenance$schematype {} 20 + 21 + export const provenanceSchema = _provenanceSchema as provenanceSchema; 22 + 23 + export interface Provenance extends v.InferInput<typeof provenanceSchema> {}
+39
src/lib/atproto.ts
··· 1 + import type { Client } from "@atcute/client"; 2 + import type { ActorIdentifier, Nsid } from "@atcute/lexicons"; 3 + 4 + export async function getRecord(client: Client, repo: string, collection: string, rkey: string) { 5 + return await client.get("com.atproto.repo.getRecord", { 6 + params: { 7 + repo: repo as ActorIdentifier, 8 + collection: collection as Nsid, 9 + rkey, 10 + }, 11 + }); 12 + } 13 + 14 + export async function deleteRecord(client: Client, repo: string, collection: string, rkey: string) { 15 + return await client.post("com.atproto.repo.deleteRecord", { 16 + input: { 17 + repo: repo as ActorIdentifier, 18 + collection: collection as Nsid, 19 + rkey, 20 + }, 21 + }); 22 + } 23 + 24 + export async function putRecord<T = unknown>(client: Client, repo: string, collection: string, rkey: string, record: T) { 25 + return await client.post("com.atproto.repo.putRecord", { 26 + input: { 27 + repo: repo as ActorIdentifier, 28 + collection: collection as Nsid, 29 + rkey, 30 + record: record as unknown as { [key: string]: unknown }, 31 + }, 32 + }); 33 + } 34 + 35 + export async function getProfile(client: Client, actor: string) { 36 + return await client.get("app.bsky.actor.getProfile", { 37 + params: { actor: actor as ActorIdentifier }, 38 + }); 39 + }
+60
src/lib/bookmarks/community.ts
··· 1 + import type { Client } from "@atcute/client"; 2 + import type { ActorIdentifier, Nsid } from "@atcute/lexicons"; 3 + 4 + export async function getBookmarks(client: Client, repo: string) { 5 + return await client.get("com.atproto.repo.listRecords", { 6 + params: { 7 + repo: repo as ActorIdentifier, 8 + collection: "community.lexicon.bookmarks.bookmark" as Nsid, 9 + limit: 100, 10 + }, 11 + }); 12 + } 13 + 14 + export async function createBookmark( 15 + client: Client, 16 + repo: string, 17 + subject: string, 18 + title?: string, 19 + description?: string, 20 + tags?: string[] 21 + ) { 22 + return await client.post("com.atproto.repo.createRecord", { 23 + input: { 24 + repo: repo as ActorIdentifier, 25 + collection: "community.lexicon.bookmarks.bookmark" as Nsid, 26 + record: { 27 + $type: "community.lexicon.bookmarks.bookmark", 28 + subject, 29 + title, 30 + description, 31 + tags, 32 + createdAt: new Date().toISOString(), 33 + }, 34 + }, 35 + }); 36 + } 37 + 38 + export async function getTags(client: Client, repo: string) { 39 + return await client.get("com.atproto.repo.listRecords", { 40 + params: { 41 + repo: repo as ActorIdentifier, 42 + collection: "com.kipclip.tag" as Nsid, 43 + limit: 100, 44 + }, 45 + }); 46 + } 47 + 48 + export async function createTag(client: Client, repo: string, value: string) { 49 + return await client.post("com.atproto.repo.createRecord", { 50 + input: { 51 + repo: repo as ActorIdentifier, 52 + collection: "com.kipclip.tag" as Nsid, 53 + record: { 54 + $type: "com.kipclip.tag", 55 + value, 56 + createdAt: new Date().toISOString(), 57 + }, 58 + }, 59 + }); 60 + }
+124
src/lib/bookmarks/cosmik.ts
··· 1 + import type { Client } from "@atcute/client"; 2 + import type { ActorIdentifier, Nsid } from "@atcute/lexicons"; 3 + 4 + export async function getSembleCollections(client: Client, repo: string) { 5 + return await client.get("com.atproto.repo.listRecords", { 6 + params: { 7 + repo: repo as ActorIdentifier, 8 + collection: "network.cosmik.collection" as Nsid, 9 + limit: 100, 10 + }, 11 + }); 12 + } 13 + 14 + export async function createSembleCollection(client: Client, repo: string, name: string, description: string) { 15 + return await client.post("com.atproto.repo.createRecord", { 16 + input: { 17 + repo: repo as ActorIdentifier, 18 + collection: "network.cosmik.collection" as Nsid, 19 + validate: false, 20 + record: { 21 + $type: "network.cosmik.collection", 22 + name, 23 + description, 24 + accessType: "CLOSED", 25 + createdAt: new Date().toISOString(), 26 + }, 27 + }, 28 + }); 29 + } 30 + 31 + export async function getSembleCards(client: Client, repo: string) { 32 + return await client.get("com.atproto.repo.listRecords", { 33 + params: { 34 + repo: repo as ActorIdentifier, 35 + collection: "network.cosmik.card" as Nsid, 36 + limit: 100, 37 + }, 38 + }); 39 + } 40 + 41 + export async function createSembleNote(client: Client, repo: string, text: string, parentCard?: { uri: string; cid: string }) { 42 + return await client.post("com.atproto.repo.createRecord", { 43 + input: { 44 + repo: repo as ActorIdentifier, 45 + collection: "network.cosmik.card" as Nsid, 46 + record: { 47 + $type: "network.cosmik.card", 48 + type: "NOTE", 49 + content: { 50 + $type: "network.cosmik.card#noteContent", 51 + text, 52 + }, 53 + // Only set parentCard as per Semble documentation 54 + parentCard: parentCard ? { uri: parentCard.uri, cid: parentCard.cid } : undefined, 55 + createdAt: new Date().toISOString(), 56 + }, 57 + }, 58 + }); 59 + } 60 + 61 + export async function createSembleUrlCard(client: Client, repo: string, url: string, metadata?: { 62 + title?: string; 63 + description?: string; 64 + imageUrl?: string; 65 + siteName?: string; 66 + }) { 67 + return await client.post("com.atproto.repo.createRecord", { 68 + input: { 69 + repo: repo as ActorIdentifier, 70 + collection: "network.cosmik.card" as Nsid, 71 + record: { 72 + $type: "network.cosmik.card", 73 + type: "URL", 74 + url, 75 + content: { 76 + $type: "network.cosmik.card#urlContent", 77 + url, 78 + metadata: metadata ? { $type: "network.cosmik.card#urlMetadata", ...metadata } : undefined, 79 + }, 80 + createdAt: new Date().toISOString(), 81 + }, 82 + }, 83 + }); 84 + } 85 + 86 + export async function getSembleCollectionLinks(client: Client, repo: string) { 87 + return await client.get("com.atproto.repo.listRecords", { 88 + params: { 89 + repo: repo as ActorIdentifier, 90 + collection: "network.cosmik.collectionLink" as Nsid, 91 + limit: 100, 92 + }, 93 + }); 94 + } 95 + 96 + export async function createSembleCollectionLink( 97 + client: Client, 98 + repo: string, 99 + cardUri: string, 100 + cardCid: string, 101 + collectionUri: string, 102 + collectionCid: string 103 + ) { 104 + return await client.post("com.atproto.repo.createRecord", { 105 + input: { 106 + repo: repo as ActorIdentifier, 107 + collection: "network.cosmik.collectionLink" as Nsid, 108 + record: { 109 + $type: "network.cosmik.collectionLink", 110 + card: { 111 + uri: cardUri, 112 + cid: cardCid, 113 + }, 114 + collection: { 115 + uri: collectionUri, 116 + cid: collectionCid, 117 + }, 118 + addedAt: new Date().toISOString(), 119 + addedBy: repo, 120 + createdAt: new Date().toISOString(), 121 + }, 122 + }, 123 + }); 124 + }
+100
src/lib/bookmarks/margin.ts
··· 1 + import type { Client } from "@atcute/client"; 2 + import type { ActorIdentifier, Nsid } from "@atcute/lexicons"; 3 + 4 + export async function getMarginBookmarks(client: Client, repo: string) { 5 + return await client.get("com.atproto.repo.listRecords", { 6 + params: { 7 + repo: repo as ActorIdentifier, 8 + collection: "at.margin.bookmark" as Nsid, 9 + limit: 100, 10 + }, 11 + }); 12 + } 13 + 14 + export async function createMarginBookmark( 15 + client: Client, 16 + repo: string, 17 + source: string, 18 + title?: string, 19 + description?: string, 20 + tags?: string[] 21 + ) { 22 + return await client.post("com.atproto.repo.createRecord", { 23 + input: { 24 + repo: repo as ActorIdentifier, 25 + collection: "at.margin.bookmark" as Nsid, 26 + record: { 27 + $type: "at.margin.bookmark", 28 + source, 29 + title, 30 + description, 31 + tags, 32 + createdAt: new Date().toISOString(), 33 + }, 34 + }, 35 + }); 36 + } 37 + 38 + export async function getMarginCollections(client: Client, repo: string) { 39 + return await client.get("com.atproto.repo.listRecords", { 40 + params: { 41 + repo: repo as ActorIdentifier, 42 + collection: "at.margin.collection" as Nsid, 43 + limit: 100, 44 + }, 45 + }); 46 + } 47 + 48 + export async function getMarginCollectionItems(client: Client, repo: string) { 49 + return await client.get("com.atproto.repo.listRecords", { 50 + params: { 51 + repo: repo as ActorIdentifier, 52 + collection: "at.margin.collectionItem" as Nsid, 53 + limit: 100, 54 + }, 55 + }); 56 + } 57 + 58 + export async function createMarginCollection( 59 + client: Client, 60 + repo: string, 61 + name: string, 62 + description?: string, 63 + icon?: string 64 + ) { 65 + return await client.post("com.atproto.repo.createRecord", { 66 + input: { 67 + repo: repo as ActorIdentifier, 68 + collection: "at.margin.collection" as Nsid, 69 + record: { 70 + $type: "at.margin.collection", 71 + name, 72 + description, 73 + icon, 74 + createdAt: new Date().toISOString(), 75 + }, 76 + }, 77 + }); 78 + } 79 + 80 + export async function createMarginCollectionItem( 81 + client: Client, 82 + repo: string, 83 + annotationUri: string, 84 + collectionUri: string, 85 + position?: number 86 + ) { 87 + return await client.post("com.atproto.repo.createRecord", { 88 + input: { 89 + repo: repo as ActorIdentifier, 90 + collection: "at.margin.collectionItem" as Nsid, 91 + record: { 92 + $type: "at.margin.collectionItem", 93 + annotation: annotationUri, 94 + collection: collectionUri, 95 + position, 96 + createdAt: new Date().toISOString(), 97 + }, 98 + }, 99 + }); 100 + }
+138
src/lib/client.ts
··· 1 + import { Client, CredentialManager, FetchHandlerObject, simpleFetchHandler } from "@atcute/client"; 2 + import { resolveActor } from "./identity"; 3 + import { isActorIdentifier } from "@atcute/lexicons/syntax"; 4 + import { ResolvedActor } from "@atcute/identity-resolver"; 5 + 6 + const DEFAULT_SERVICE = "https://bsky.social"; 7 + 8 + export interface Credentials { 9 + identifier: string; 10 + password: string; 11 + } 12 + 13 + export class ATClient extends Client { 14 + hh: Handler; 15 + 16 + constructor(creds?: Credentials) { 17 + const handler = new Handler(creds); 18 + super({ handler }); 19 + this.hh = handler; 20 + } 21 + 22 + get loggedIn(): boolean { 23 + return !!this.hh.cm.session?.did; 24 + } 25 + get session() { 26 + return this.hh.cm.session; 27 + } 28 + 29 + getActor(identifier: string): Promise<ResolvedActor> { 30 + return this.hh.getActor(identifier); 31 + } 32 + } 33 + 34 + export class Handler implements FetchHandlerObject { 35 + creds?: Credentials; 36 + cm: CredentialManager; 37 + cache: Cache 38 + 39 + constructor(creds?: Credentials) { 40 + this.creds = creds; 41 + this.cache = new Cache(5 * 60 * 1000); // 5 minutes TTL 42 + this.cm = new CredentialManager({ service: DEFAULT_SERVICE }); 43 + } 44 + 45 + async getActor(identifier: string): Promise<ResolvedActor> { 46 + const key = `actor:${identifier}`; 47 + const cached = this.cache.get<ResolvedActor>(key); 48 + if (cached) { 49 + return cached; 50 + } 51 + if (isActorIdentifier(identifier)) { 52 + const res = await resolveActor(identifier); 53 + this.cache.set(key, res); 54 + return res; 55 + } else { 56 + throw new Error("Invalid actor identifier: " + JSON.stringify(identifier)); 57 + } 58 + } 59 + 60 + async getPDS(pathname: string): Promise<string | null> { 61 + const url = new URL(pathname, "https://placeholder") 62 + const repo = url.searchParams.get("repo"); 63 + if (!repo) { 64 + return null 65 + } 66 + const own = (repo === this.cm.session?.handle || repo === this.cm.session?.did); 67 + if (!own) { 68 + const actor = await this.getActor(repo); 69 + return actor.pds 70 + } 71 + return null 72 + } 73 + 74 + async handle(pathname: string, init: RequestInit): Promise<Response> { 75 + if (this.creds && !this.cm.session?.did) { 76 + await this.cm.login(this.creds); 77 + if (this.cm.session?.did) { 78 + void this.getActor(this.cm.session?.did) 79 + } 80 + } 81 + 82 + const cacheKey = `${init?.method || "GET"}:${pathname}`; 83 + if (init?.method?.toLowerCase() === "get") { 84 + const cached = this.cache.get<Response>(cacheKey); 85 + if (cached) { 86 + return cached.clone(); 87 + } 88 + } 89 + 90 + let resp: Response; 91 + const pds = await this.getPDS(pathname); 92 + if (pds) { 93 + const sfh = simpleFetchHandler({ service: pds }); 94 + resp = await sfh(pathname, init); 95 + } else { 96 + resp = await this.cm.handle(pathname, init); 97 + } 98 + 99 + if (init?.method?.toLowerCase() === "get" && resp.ok) { 100 + this.cache.set(cacheKey, resp.clone()); 101 + } 102 + return resp; 103 + } 104 + } 105 + 106 + class CacheEntry<T> { 107 + value: T; 108 + timestamp: number; 109 + constructor(value: T) { 110 + this.value = value; 111 + this.timestamp = Date.now(); 112 + } 113 + } 114 + 115 + class Cache { 116 + #store = new Map<string, CacheEntry<unknown>>(); 117 + #ttl: number; 118 + 119 + constructor(ttlMillis: number) { 120 + this.#ttl = ttlMillis; 121 + } 122 + 123 + get<T>(key: string): T | undefined { 124 + const entry = this.#store.get(key); 125 + if (entry) { 126 + if (Date.now() - entry.timestamp < this.#ttl) { 127 + return entry.value as T; 128 + } else { 129 + this.#store.delete(key); 130 + } 131 + } 132 + return undefined; 133 + } 134 + 135 + set<T>(key: string, value: T): void { 136 + this.#store.set(key, new CacheEntry(value)); 137 + } 138 + }
+118
src/lib/clipper.ts
··· 1 + import { ATRecord, buildDocumentUrl } from "lib"; 2 + import { Main as Document } from "@atcute/standard-site/types/document"; 3 + import { Main as Publication } from "@atcute/standard-site/types/publication"; 4 + import { is, parseResourceUri } from "@atcute/lexicons"; 5 + import { Notice, TFile } from "obsidian"; 6 + import ATmarkPlugin from "main"; 7 + import { leafletContentToMarkdown } from "./markdown/leaflet"; 8 + import { pcktContentToMarkdown } from "./markdown/pckt"; 9 + import { ResolvedActor } from "@atcute/identity-resolver"; 10 + import { PubLeafletContent } from "@atcute/leaflet"; 11 + import { BlogPcktContent } from "@atcute/pckt"; 12 + 13 + 14 + function bskyLink(handle: string) { 15 + return `https://bsky.app/profile/${handle}`; 16 + } 17 + 18 + export class Clipper { 19 + plugin: ATmarkPlugin; 20 + 21 + constructor(plugin: ATmarkPlugin) { 22 + this.plugin = plugin; 23 + } 24 + 25 + safeFilePath(title: string, clipDir: string) { 26 + const safeTitle = title.replace(/[/\\?%*:|"<>]/g, "-").substring(0, 50); 27 + return `${clipDir}/${safeTitle}.md`; 28 + } 29 + 30 + existsInClipDir(doc: ATRecord<Document>) { 31 + const vault = this.plugin.app.vault; 32 + const clipDir = this.plugin.settings.clipDir 33 + 34 + 35 + const filePath = this.safeFilePath(doc.value.title, clipDir); 36 + const file = vault.getAbstractFileByPath(filePath); 37 + return file !== null; 38 + } 39 + 40 + 41 + async writeFrontmatter(file: TFile, doc: ATRecord<Document>, pub: ATRecord<Publication>) { 42 + let actor: ResolvedActor | null = null; 43 + const repoParsed = parseResourceUri(doc.uri); 44 + if (repoParsed.ok) { 45 + actor = await this.plugin.client.getActor(repoParsed.value.repo); 46 + } 47 + // Add frontmatter using Obsidian's processFrontMatter 48 + await this.plugin.app.fileManager.processFrontMatter(file, (fm: Record<string, unknown>) => { 49 + fm["title"] = doc.value.title; 50 + if (actor && actor.handle) { 51 + fm["author"] = `[${actor.handle}](${bskyLink(actor.handle)})`; 52 + } 53 + fm["aturi"] = doc.uri; 54 + 55 + let docUrl = ""; 56 + 57 + // pubUrl is at:// record uri or https:// for loose document 58 + // fetch pub if at:// so we can get the url 59 + // otherwise just use the url as is 60 + if (doc.value.site.startsWith("https://")) { 61 + docUrl = buildDocumentUrl(doc.value.site, doc.uri, doc.value); 62 + } else { 63 + docUrl = buildDocumentUrl(pub.value.url, doc.uri, doc.value); 64 + 65 + } 66 + if (docUrl) { 67 + fm["url"] = docUrl; 68 + } 69 + }); 70 + } 71 + 72 + async clipDocument(doc: ATRecord<Document>, pub: ATRecord<Publication>) { 73 + const vault = this.plugin.app.vault; 74 + const clipDir = this.plugin.settings.clipDir 75 + 76 + const parsed = parseResourceUri(pub.uri); 77 + if (!parsed.ok) { 78 + throw new Error(`Invalid publication URI: ${pub.uri}`); 79 + } 80 + if (!vault.getAbstractFileByPath(clipDir)) { 81 + await vault.createFolder(clipDir); 82 + } 83 + const filePath = this.safeFilePath(doc.value.title, clipDir); 84 + 85 + let content = `# ${doc.value.title}\n\n`; 86 + 87 + if (doc.value.description) { 88 + content += `> ${doc.value.description}\n\n`; 89 + } 90 + 91 + content += `---\n\n`; 92 + 93 + let bodyContent = ""; 94 + if (doc.value.content) { 95 + if (is(PubLeafletContent.mainSchema, doc.value.content)) { 96 + bodyContent = leafletContentToMarkdown(doc.value.content); 97 + } else if (is(BlogPcktContent.mainSchema, doc.value.content)) { 98 + bodyContent = pcktContentToMarkdown(doc.value.content); 99 + } 100 + } 101 + 102 + if (!bodyContent && doc.value.textContent) { 103 + bodyContent = doc.value.textContent; 104 + } 105 + 106 + content += bodyContent; 107 + 108 + const file = await vault.create(filePath, content); 109 + await this.writeFrontmatter(file, doc, pub); 110 + 111 + 112 + const leaf = this.plugin.app.workspace.getLeaf(false); 113 + await leaf.openFile(file); 114 + 115 + new Notice(`Clipped document to ${filePath}`); 116 + } 117 + } 118 +
+38
src/lib/identity.ts
··· 1 + import type { ActorIdentifier } from "@atcute/lexicons"; 2 + import { 3 + CompositeHandleResolver, 4 + DohJsonHandleResolver, 5 + LocalActorResolver, 6 + WellKnownHandleResolver, 7 + } from '@atcute/identity-resolver'; 8 + 9 + import { 10 + CompositeDidDocumentResolver, 11 + PlcDidDocumentResolver, 12 + WebDidDocumentResolver, 13 + } from '@atcute/identity-resolver'; 14 + 15 + const handleResolver = new CompositeHandleResolver({ 16 + methods: { 17 + dns: new DohJsonHandleResolver({ dohUrl: 'https://mozilla.cloudflare-dns.com/dns-query' }), 18 + http: new WellKnownHandleResolver(), 19 + }, 20 + }); 21 + 22 + const didResolver = new CompositeDidDocumentResolver({ 23 + methods: { 24 + plc: new PlcDidDocumentResolver(), 25 + web: new WebDidDocumentResolver(), 26 + }, 27 + }); 28 + 29 + const actorResolver = new LocalActorResolver({ 30 + handleResolver, 31 + didDocumentResolver: didResolver, 32 + }); 33 + 34 + export async function resolveActor(identifier: ActorIdentifier) { 35 + return actorResolver.resolve(identifier); 36 + } 37 + 38 +
+46
src/lib/markdown/index.ts
··· 1 + import { unified } from "unified"; 2 + import remarkParse from "remark-parse"; 3 + import type { Root, RootContent } from "mdast"; 4 + 5 + export function parseMarkdown(markdown: string): Root { 6 + return unified().use(remarkParse).parse(markdown); 7 + } 8 + 9 + export function extractText(node: RootContent | Root): string { 10 + if (node.type === "text") { 11 + return node.value; 12 + } 13 + 14 + if (node.type === "inlineCode") { 15 + return node.value; 16 + } 17 + 18 + if ("children" in node && Array.isArray(node.children)) { 19 + return node.children.map(extractText).join(""); 20 + } 21 + 22 + if ("value" in node && typeof node.value === "string") { 23 + return node.value; 24 + } 25 + 26 + return ""; 27 + } 28 + 29 + /** 30 + * Strip markdown formatting to plain text 31 + * Used for the textContent field in standard.site documents 32 + */ 33 + export function stripMarkdown(markdown: string): string { 34 + const tree = parseMarkdown(markdown); 35 + return tree.children.map(extractText).join("\n\n").trim(); 36 + } 37 + 38 + export function cleanPlaintext(text: string): string { 39 + return text.trim(); 40 + } 41 + 42 + export type { Root, RootContent }; 43 + 44 + export { markdownToPcktContent, pcktContentToMarkdown } from "./pckt"; 45 + export { markdownToLeafletContent, leafletContentToMarkdown } from "./leaflet"; 46 +
+191
src/lib/markdown/leaflet.ts
··· 1 + import type { RootContent, Root } from "mdast"; 2 + import { unified } from "unified"; 3 + import remarkStringify from "remark-stringify"; 4 + import { 5 + PubLeafletBlocksUnorderedList, 6 + PubLeafletContent, 7 + PubLeafletPagesLinearDocument, 8 + } from "@atcute/leaflet"; 9 + import { parseMarkdown, extractText, cleanPlaintext } from "../markdown"; 10 + 11 + export function markdownToLeafletContent(markdown: string): PubLeafletContent.Main { 12 + const tree = parseMarkdown(markdown); 13 + const blocks: PubLeafletPagesLinearDocument.Block[] = []; 14 + 15 + for (const node of tree.children) { 16 + const block = convertNodeToBlock(node); 17 + if (block) { 18 + blocks.push(block); 19 + } 20 + } 21 + 22 + return { 23 + $type: "pub.leaflet.content", 24 + pages: [{ 25 + $type: "pub.leaflet.pages.linearDocument", 26 + blocks, 27 + }], 28 + }; 29 + } 30 + 31 + function convertNodeToBlock(node: RootContent): PubLeafletPagesLinearDocument.Block | null { 32 + switch (node.type) { 33 + case "heading": 34 + return { 35 + block: { 36 + $type: "pub.leaflet.blocks.header", 37 + level: node.depth, 38 + plaintext: extractText(node), 39 + }, 40 + alignment: "pub.leaflet.pages.linearDocument#textAlignLeft", 41 + }; 42 + 43 + case "paragraph": 44 + return { 45 + block: { 46 + $type: "pub.leaflet.blocks.text", 47 + plaintext: extractText(node), 48 + textSize: "default", 49 + }, 50 + alignment: "pub.leaflet.pages.linearDocument#textAlignLeft", 51 + }; 52 + 53 + case "list": { 54 + const listItems: PubLeafletBlocksUnorderedList.ListItem[] = node.children.map((item) => ({ 55 + $type: "pub.leaflet.blocks.unorderedList#listItem", 56 + content: { 57 + $type: "pub.leaflet.blocks.text", 58 + plaintext: extractText(item), 59 + textSize: "default", 60 + }, 61 + })); 62 + 63 + return { 64 + block: { 65 + $type: "pub.leaflet.blocks.unorderedList", 66 + children: listItems, 67 + }, 68 + alignment: "pub.leaflet.pages.linearDocument#textAlignLeft", 69 + }; 70 + } 71 + 72 + case "code": 73 + return { 74 + block: { 75 + $type: "pub.leaflet.blocks.code", 76 + plaintext: node.value, 77 + language: node.lang || undefined, 78 + }, 79 + alignment: "pub.leaflet.pages.linearDocument#textAlignLeft", 80 + }; 81 + 82 + case "thematicBreak": 83 + return { 84 + block: { 85 + $type: "pub.leaflet.blocks.horizontalRule", 86 + }, 87 + alignment: "pub.leaflet.pages.linearDocument#textAlignLeft", 88 + }; 89 + 90 + case "blockquote": 91 + return { 92 + block: { 93 + $type: "pub.leaflet.blocks.blockquote", 94 + plaintext: extractText(node), 95 + }, 96 + alignment: "pub.leaflet.pages.linearDocument#textAlignLeft", 97 + }; 98 + 99 + default: 100 + return null; 101 + } 102 + } 103 + 104 + export function leafletContentToMarkdown(content: PubLeafletContent.Main): string { 105 + const mdastNodes: RootContent[] = []; 106 + 107 + for (const page of content.pages) { 108 + if (page.$type !== "pub.leaflet.pages.linearDocument") { 109 + continue; 110 + } 111 + 112 + for (const item of page.blocks) { 113 + const block = item.block; 114 + const node = leafletBlockToMdast(block); 115 + if (node) { 116 + mdastNodes.push(node); 117 + } 118 + } 119 + } 120 + 121 + const root: Root = { 122 + type: "root", 123 + children: mdastNodes, 124 + }; 125 + 126 + return unified().use(remarkStringify).stringify(root); 127 + } 128 + 129 + // Extract the union type of all possible leaflet blocks from the Block interface 130 + type LeafletBlockType = PubLeafletPagesLinearDocument.Block['block']; 131 + 132 + function leafletBlockToMdast(block: LeafletBlockType): RootContent | null { 133 + switch (block.$type) { 134 + case "pub.leaflet.blocks.header": 135 + return { 136 + type: "heading", 137 + depth: block.level as 1 | 2 | 3 | 4 | 5 | 6, 138 + children: [{ type: "text", value: cleanPlaintext(block.plaintext) }], 139 + }; 140 + 141 + case "pub.leaflet.blocks.text": 142 + return { 143 + type: "paragraph", 144 + children: [{ type: "text", value: cleanPlaintext(block.plaintext) }], 145 + }; 146 + 147 + case "pub.leaflet.blocks.unorderedList": 148 + return { 149 + type: "list", 150 + ordered: false, 151 + spread: false, 152 + children: block.children.map((item: PubLeafletBlocksUnorderedList.ListItem) => { 153 + // Extract plaintext from the content, which can be Header, Image, or Text 154 + const plaintext = 'plaintext' in item.content ? cleanPlaintext(item.content.plaintext) : ''; 155 + return { 156 + type: "listItem", 157 + spread: false, 158 + children: [{ 159 + type: "paragraph", 160 + children: [{ type: "text", value: plaintext }], 161 + }], 162 + }; 163 + }), 164 + }; 165 + 166 + case "pub.leaflet.blocks.code": 167 + return { 168 + type: "code", 169 + lang: block.language || null, 170 + meta: null, 171 + value: block.plaintext, // Keep code blocks as-is to preserve formatting 172 + }; 173 + 174 + case "pub.leaflet.blocks.horizontalRule": 175 + return { 176 + type: "thematicBreak", 177 + }; 178 + 179 + case "pub.leaflet.blocks.blockquote": 180 + return { 181 + type: "blockquote", 182 + children: [{ 183 + type: "paragraph", 184 + children: [{ type: "text", value: cleanPlaintext(block.plaintext) }], 185 + }], 186 + }; 187 + 188 + default: 189 + return null; 190 + } 191 + }
+223
src/lib/markdown/pckt.ts
··· 1 + import type { RootContent, Root } from "mdast"; 2 + import { unified } from "unified"; 3 + import remarkStringify from "remark-stringify"; 4 + import { 5 + BlogPcktBlockListItem, 6 + BlogPcktBlockText, 7 + BlogPcktBlockHeading, 8 + BlogPcktBlockCodeBlock, 9 + BlogPcktBlockBulletList, 10 + BlogPcktBlockOrderedList, 11 + BlogPcktBlockHorizontalRule, 12 + BlogPcktBlockBlockquote, 13 + BlogPcktContent, 14 + } from "@atcute/pckt"; 15 + import { parseMarkdown, extractText, cleanPlaintext } from "../markdown"; 16 + 17 + type PcktBlock = 18 + | BlogPcktBlockText.Main 19 + | BlogPcktBlockHeading.Main 20 + | BlogPcktBlockCodeBlock.Main 21 + | BlogPcktBlockBulletList.Main 22 + | BlogPcktBlockOrderedList.Main 23 + | BlogPcktBlockHorizontalRule.Main 24 + | BlogPcktBlockBlockquote.Main; 25 + 26 + export function markdownToPcktContent(markdown: string): BlogPcktContent.Main { 27 + const tree = parseMarkdown(markdown); 28 + const items: PcktBlock[] = []; 29 + 30 + for (const node of tree.children) { 31 + const block = convertNodeToBlock(node); 32 + if (block) { 33 + items.push(block); 34 + } 35 + } 36 + 37 + return { 38 + $type: "blog.pckt.content", 39 + items, 40 + } as BlogPcktContent.Main; 41 + } 42 + 43 + function convertNodeToBlock(node: RootContent): PcktBlock | null { 44 + switch (node.type) { 45 + case "heading": { 46 + const block: BlogPcktBlockHeading.Main = { 47 + $type: "blog.pckt.block.heading", 48 + level: node.depth, 49 + plaintext: extractText(node), 50 + }; 51 + return block; 52 + } 53 + 54 + case "paragraph": { 55 + const block: BlogPcktBlockText.Main = { 56 + $type: "blog.pckt.block.text", 57 + plaintext: extractText(node), 58 + }; 59 + return block; 60 + } 61 + 62 + case "list": { 63 + const listItems: BlogPcktBlockListItem.Main[] = node.children.map((item) => ({ 64 + $type: "blog.pckt.block.listItem", 65 + content: [{ 66 + $type: "blog.pckt.block.text", 67 + plaintext: extractText(item), 68 + }], 69 + })); 70 + 71 + if (node.ordered) { 72 + const block: BlogPcktBlockOrderedList.Main = { 73 + $type: "blog.pckt.block.orderedList", 74 + content: listItems, 75 + }; 76 + return block; 77 + } else { 78 + const block: BlogPcktBlockBulletList.Main = { 79 + $type: "blog.pckt.block.bulletList", 80 + content: listItems, 81 + }; 82 + return block; 83 + } 84 + } 85 + 86 + case "code": { 87 + const block: BlogPcktBlockCodeBlock.Main = { 88 + $type: "blog.pckt.block.codeBlock", 89 + plaintext: node.value, 90 + language: node.lang || undefined, 91 + }; 92 + return block; 93 + } 94 + 95 + case "thematicBreak": { 96 + const block: BlogPcktBlockHorizontalRule.Main = { 97 + $type: "blog.pckt.block.horizontalRule", 98 + }; 99 + return block; 100 + } 101 + 102 + case "blockquote": { 103 + const block: BlogPcktBlockBlockquote.Main = { 104 + $type: "blog.pckt.block.blockquote", 105 + content: [{ 106 + $type: "blog.pckt.block.text", 107 + plaintext: extractText(node), 108 + }], 109 + }; 110 + return block; 111 + } 112 + 113 + default: 114 + return null; 115 + } 116 + } 117 + 118 + /** 119 + * Convert pckt content to markdown string 120 + */ 121 + export function pcktContentToMarkdown(content: BlogPcktContent.Main): string { 122 + const mdastNodes: RootContent[] = []; 123 + 124 + for (const block of content.items) { 125 + const node = pcktBlockToMdast(block); 126 + if (node) { 127 + mdastNodes.push(node); 128 + } 129 + } 130 + 131 + const root: Root = { 132 + type: "root", 133 + children: mdastNodes, 134 + }; 135 + 136 + return unified().use(remarkStringify).stringify(root); 137 + } 138 + 139 + function pcktBlockToMdast(block: PcktBlock): RootContent | null { 140 + switch (block.$type) { 141 + case "blog.pckt.block.heading": 142 + return { 143 + type: "heading", 144 + depth: block.level as 1 | 2 | 3 | 4 | 5 | 6, 145 + children: [{ type: "text", value: cleanPlaintext(block.plaintext) }], 146 + }; 147 + 148 + case "blog.pckt.block.text": 149 + return { 150 + type: "paragraph", 151 + children: [{ type: "text", value: cleanPlaintext(block.plaintext) }], 152 + }; 153 + 154 + case "blog.pckt.block.bulletList": 155 + return { 156 + type: "list", 157 + ordered: false, 158 + spread: false, 159 + children: block.content.map((item: BlogPcktBlockListItem.Main) => { 160 + const text = item.content 161 + .map((c) => ('plaintext' in c ? cleanPlaintext(c.plaintext) : '')) 162 + .join(" "); 163 + return { 164 + type: "listItem", 165 + spread: false, 166 + children: [{ 167 + type: "paragraph", 168 + children: [{ type: "text", value: text }], 169 + }], 170 + }; 171 + }), 172 + }; 173 + 174 + case "blog.pckt.block.orderedList": 175 + return { 176 + type: "list", 177 + ordered: true, 178 + spread: false, 179 + children: block.content.map((item: BlogPcktBlockListItem.Main) => { 180 + const text = item.content 181 + .map((c) => ('plaintext' in c ? cleanPlaintext(c.plaintext) : '')) 182 + .join(" "); 183 + return { 184 + type: "listItem", 185 + spread: false, 186 + children: [{ 187 + type: "paragraph", 188 + children: [{ type: "text", value: text }], 189 + }], 190 + }; 191 + }), 192 + }; 193 + 194 + case "blog.pckt.block.codeBlock": 195 + return { 196 + type: "code", 197 + lang: block.language || null, 198 + meta: null, 199 + value: block.plaintext, 200 + }; 201 + 202 + case "blog.pckt.block.horizontalRule": 203 + return { 204 + type: "thematicBreak", 205 + }; 206 + 207 + case "blog.pckt.block.blockquote": { 208 + const text = block.content 209 + .map((c: BlogPcktBlockText.Main) => cleanPlaintext(c.plaintext)) 210 + .join("\n"); 211 + return { 212 + type: "blockquote", 213 + children: [{ 214 + type: "paragraph", 215 + children: [{ type: "text", value: text }], 216 + }], 217 + }; 218 + } 219 + 220 + default: 221 + return null; 222 + } 223 + }
+214
src/lib/standardsite/index.ts
··· 1 + import { ok, type Client } from "@atcute/client"; 2 + import type { ActorIdentifier, GenericUri, Nsid, ResourceUri } from "@atcute/lexicons"; 3 + import { parse, parseResourceUri } from "@atcute/lexicons"; 4 + import { ComAtprotoRepoCreateRecord, ComAtprotoRepoGetRecord, ComAtprotoRepoListRecords, ComAtprotoRepoPutRecord } from "@atcute/atproto"; 5 + import { Main as Document } from "@atcute/standard-site/types/document"; 6 + import { Main as Publication } from "@atcute/standard-site/types/publication"; 7 + import { Main as Subscription } from "@atcute/standard-site/types/graph/subscription"; 8 + 9 + import { ATRecord } from "lib"; 10 + import { SiteStandardDocument, SiteStandardGraphSubscription, SiteStandardPublication } from "@atcute/standard-site"; 11 + 12 + export function buildDocumentUrl(pubUrl: string, docUri: string, record: SiteStandardDocument.Main): string { 13 + const baseUrl = pubUrl.replace(/\/$/, ''); 14 + 15 + // leaflet does not use path, url just uses rkey 16 + if (record.path === undefined || record.path === '') { 17 + const parsed = parseResourceUri(docUri) 18 + if (parsed.ok) { 19 + return `${baseUrl}/${parsed.value.rkey}`; 20 + } 21 + return "" 22 + } 23 + 24 + return `${baseUrl}/${record.path}` 25 + } 26 + 27 + 28 + export async function getPublicationDocuments(client: Client, repo: string, pubUri: ResourceUri) { 29 + const response = await ok(client.call(ComAtprotoRepoListRecords, { 30 + params: { 31 + repo: repo as ActorIdentifier, 32 + collection: "site.standard.document" as Nsid, 33 + limit: 100, 34 + }, 35 + })); 36 + 37 + const pubDocs = response.records.filter(record => { 38 + const parsed = parse(SiteStandardDocument.mainSchema, record.value); 39 + return parsed.site === pubUri; 40 + }); 41 + 42 + return { 43 + ...response, 44 + records: pubDocs.map(record => ({ 45 + ...record, 46 + value: parse(SiteStandardDocument.mainSchema, record.value), 47 + })) as ATRecord<Document>[], 48 + }; 49 + }; 50 + 51 + export async function createDocument( 52 + client: Client, 53 + repo: string, 54 + record: Document 55 + ) { 56 + const now = new Date().toISOString(); 57 + if (!record.publishedAt) { 58 + record.publishedAt = now; 59 + } 60 + record.updatedAt = now; 61 + 62 + return await client.call(ComAtprotoRepoCreateRecord, { 63 + input: { 64 + repo: repo as ActorIdentifier, 65 + collection: "site.standard.document" as Nsid, 66 + validate: false, 67 + record, 68 + }, 69 + }); 70 + } 71 + 72 + export async function putDocument( 73 + client: Client, 74 + repo: string, 75 + uri: ResourceUri, 76 + record: Document, 77 + ) { 78 + const now = new Date().toISOString(); 79 + record.updatedAt = now; 80 + 81 + if (!record.publishedAt) { 82 + record.publishedAt = now; 83 + } 84 + 85 + const parsed = parseResourceUri(uri); 86 + if (!parsed.ok) { 87 + throw new Error(`Invalid URI: ${uri}`); 88 + } 89 + if (!parsed.value.rkey) { 90 + throw new Error(`URI does not contain rkey: ${uri}`); 91 + } 92 + 93 + return await client.call(ComAtprotoRepoPutRecord, { 94 + input: { 95 + repo: repo as ActorIdentifier, 96 + collection: "site.standard.document" as Nsid, 97 + validate: false, 98 + record, 99 + rkey: parsed.value.rkey, 100 + }, 101 + }); 102 + } 103 + 104 + export async function getPublications(client: Client, repo: string) { 105 + const response = await ok(client.call(ComAtprotoRepoListRecords, { 106 + params: { 107 + repo: repo as ActorIdentifier, 108 + collection: "site.standard.publication" as Nsid, 109 + limit: 100, 110 + }, 111 + })); 112 + const records: ATRecord<Publication>[] = response.records.map(record => ({ 113 + ...record, 114 + value: parse(SiteStandardPublication.mainSchema, record.value), 115 + })); 116 + 117 + return { 118 + ...response, 119 + records, 120 + } 121 + } 122 + 123 + 124 + export async function getPublication(client: Client, uri: ResourceUri): Promise<ATRecord<Publication>> { 125 + const parsed = parseResourceUri(uri); 126 + if (!parsed.ok) { 127 + throw new Error(`Invalid URI: ${uri}`); 128 + } 129 + const resp = await ok(client.call(ComAtprotoRepoGetRecord, { 130 + params: { 131 + repo: parsed.value.repo, 132 + collection: parsed.value.collection as Nsid, 133 + rkey: parsed.value.rkey!, 134 + }, 135 + })); 136 + 137 + return { 138 + uri: resp.uri, 139 + cid: resp.cid!, 140 + value: parse(SiteStandardPublication.mainSchema, resp.value) 141 + }; 142 + } 143 + 144 + 145 + export async function createPublication( 146 + client: Client, 147 + repo: string, 148 + name: string, 149 + url: GenericUri, 150 + options?: { 151 + description?: string; 152 + icon?: Blob; 153 + showInDiscover?: boolean; 154 + } 155 + ) { 156 + const record: Publication = { 157 + $type: "site.standard.publication", 158 + name, 159 + url, 160 + description: options?.description, 161 + // icon: options?.icon, 162 + preferences: options?.showInDiscover !== undefined 163 + ? { 164 + $type: "site.standard.publication#preferences", 165 + showInDiscover: options.showInDiscover, 166 + } 167 + : undefined, 168 + }; 169 + 170 + return await client.post("com.atproto.repo.createRecord", { 171 + input: { 172 + repo: repo as ActorIdentifier, 173 + collection: "site.standard.publication" as Nsid, 174 + validate: false, 175 + record, 176 + }, 177 + }); 178 + } 179 + 180 + export async function getSubscriptions(client: Client, repo: string) { 181 + const response = await ok(client.call(ComAtprotoRepoListRecords, { 182 + params: { 183 + repo: repo as ActorIdentifier, 184 + collection: "site.standard.graph.subscription" as Nsid, 185 + limit: 100, 186 + }, 187 + })); 188 + const records: ATRecord<Subscription>[] = response.records.map(record => ({ 189 + ...record, 190 + value: parse(SiteStandardGraphSubscription.mainSchema, record.value), 191 + })); 192 + 193 + return { 194 + ...response, 195 + records, 196 + } 197 + } 198 + 199 + export async function getSubscribedPublications(client: Client, repo: string): Promise<ATRecord<Publication>[]> { 200 + const subsResp = await getSubscriptions(client, repo); 201 + const pubUris = subsResp.records.map(sub => sub.value.publication); 202 + 203 + let pubs: ATRecord<Publication>[] = []; 204 + for (const uri of pubUris) { 205 + try { 206 + const pubResp = await getPublication(client, uri); 207 + pubs.push(pubResp); 208 + } catch (e) { 209 + console.warn(`Failed to fetch publication at ${uri}:`, e); 210 + } 211 + } 212 + return pubs; 213 + } 214 +
+43
src/lib.ts
··· 1 + import { Record } from "@atcute/atproto/types/repo/listRecords"; 2 + 3 + export { getRecord, deleteRecord, putRecord, getProfile } from "./lib/atproto"; 4 + 5 + export { 6 + getSembleCollections as getCollections, 7 + createSembleCollection as createCollection, 8 + getSembleCards as getCards, 9 + createSembleNote as createNoteCard, 10 + createSembleUrlCard as createUrlCard, 11 + getSembleCollectionLinks as getCollectionLinks, 12 + createSembleCollectionLink as createCollectionLink, 13 + } from "./lib/bookmarks/cosmik"; 14 + 15 + export { getBookmarks, createBookmark, getTags, createTag } from "./lib/bookmarks/community"; 16 + 17 + export { 18 + getMarginBookmarks, 19 + createMarginBookmark, 20 + getMarginCollections, 21 + getMarginCollectionItems, 22 + createMarginCollection, 23 + createMarginCollectionItem, 24 + } from "./lib/bookmarks/margin"; 25 + 26 + export { 27 + getPublicationDocuments, 28 + createDocument, 29 + putDocument, 30 + getPublication, 31 + getPublications, 32 + getSubscribedPublications, 33 + createPublication, 34 + buildDocumentUrl 35 + } from "./lib/standardsite"; 36 + 37 + export { 38 + stripMarkdown, 39 + markdownToLeafletContent, 40 + markdownToPcktContent, 41 + } from "./lib/markdown"; 42 + 43 + export type ATRecord<T> = Record & { value: T };
+111
src/main.ts
··· 1 + import { Plugin, WorkspaceLeaf } from "obsidian"; 2 + import { DEFAULT_SETTINGS, AtProtoSettings, SettingTab } from "./settings"; 3 + import { ATmarkView, VIEW_TYPE_ATMARK } from "./views/atmark"; 4 + import { publishFileAsDocument } from "./commands/publishDocument"; 5 + import { StandardFeedView, VIEW_STANDARD_FEED } from "views/standardfeed"; 6 + import { ATClient } from "lib/client"; 7 + import { Clipper } from "lib/clipper"; 8 + 9 + export default class ATmarkPlugin extends Plugin { 10 + settings: AtProtoSettings = DEFAULT_SETTINGS; 11 + client: ATClient; 12 + clipper: Clipper; 13 + 14 + async onload() { 15 + await this.loadSettings(); 16 + 17 + const creds = { 18 + identifier: this.settings.identifier, 19 + password: this.settings.appPassword, 20 + }; 21 + this.client = new ATClient(creds); 22 + this.clipper = new Clipper(this); 23 + 24 + this.registerView(VIEW_TYPE_ATMARK, (leaf) => { 25 + return new ATmarkView(leaf, this); 26 + }); 27 + 28 + this.registerView(VIEW_STANDARD_FEED, (leaf) => { 29 + return new StandardFeedView(leaf, this); 30 + }); 31 + 32 + // included name of the plugin, which contains the acronym "AT" 33 + // eslint-disable-next-line obsidianmd/ui/sentence-case 34 + this.addRibbonIcon("layers", "ATmark bookmarks", () => { 35 + void this.activateView(VIEW_TYPE_ATMARK); 36 + }); 37 + 38 + // included name of the plugin, which contains the acronym "AT" 39 + // eslint-disable-next-line obsidianmd/ui/sentence-case 40 + this.addRibbonIcon("rss", "ATmark feed", () => { 41 + void this.activateView(VIEW_STANDARD_FEED); 42 + }); 43 + 44 + this.addCommand({ 45 + id: "view", 46 + name: "Open view", 47 + callback: () => { void this.activateView(VIEW_TYPE_ATMARK); }, 48 + }); 49 + 50 + // this.addCommand({ 51 + // id: "standard-site-view", 52 + // name: "View publications", 53 + // callback: () => { void this.activateView(VIEW_TYPE_STANDARD_SITE); }, 54 + // }); 55 + 56 + this.addCommand({ 57 + id: "standard-site-publich-document", 58 + name: "Publish document", 59 + editorCheckCallback: (checking: boolean,) => { 60 + const file = this.app.workspace.getActiveFile(); 61 + 62 + if (file) { 63 + if (!checking) { 64 + void publishFileAsDocument(this) 65 + } 66 + 67 + return true 68 + } 69 + 70 + return false; 71 + }, 72 + }); 73 + 74 + this.addSettingTab(new SettingTab(this.app, this)); 75 + } 76 + 77 + 78 + 79 + async activateView(v: string) { 80 + const { workspace } = this.app; 81 + 82 + let leaf: WorkspaceLeaf | null = null; 83 + const leaves = workspace.getLeavesOfType(v); 84 + 85 + if (leaves.length > 0) { 86 + // A leaf with our view already exists, use that 87 + leaf = leaves[0] as WorkspaceLeaf; 88 + void workspace.revealLeaf(leaf); 89 + return; 90 + } 91 + 92 + // Our view could not be found in the workspace, create a new leaf 93 + leaf = workspace.getMostRecentLeaf() 94 + await leaf?.setViewState({ type: v, active: true }); 95 + 96 + // "Reveal" the leaf in case it is in a collapsed sidebar 97 + if (leaf) { 98 + void workspace.revealLeaf(leaf); 99 + } 100 + } 101 + 102 + async loadSettings() { 103 + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData() as Partial<AtProtoSettings>); 104 + } 105 + 106 + async saveSettings() { 107 + await this.saveData(this.settings); 108 + } 109 + 110 + onunload() { } 111 + }
+65
src/settings.ts
··· 1 + import { App, PluginSettingTab, Setting } from "obsidian"; 2 + import type ATmarkPlugin from "./main"; 3 + 4 + export interface AtProtoSettings { 5 + identifier: string; 6 + appPassword: string; 7 + clipDir: string; 8 + } 9 + 10 + export const DEFAULT_SETTINGS: AtProtoSettings = { 11 + identifier: "", 12 + appPassword: "", 13 + clipDir: "AtmosphereClips", 14 + }; 15 + 16 + export class SettingTab extends PluginSettingTab { 17 + plugin: ATmarkPlugin; 18 + 19 + constructor(app: App, plugin: ATmarkPlugin) { 20 + super(app, plugin); 21 + this.plugin = plugin; 22 + } 23 + 24 + display(): void { 25 + const { containerEl } = this; 26 + containerEl.empty(); 27 + 28 + new Setting(containerEl) 29 + .setName("Handle") 30 + .setDesc("Your bluesky handle or identifier (e.g., user.bsky.social)") 31 + // .setDesc("user.bsky.social") 32 + .addText((text) => 33 + text 34 + .setValue(this.plugin.settings.identifier) 35 + .onChange(async (value) => { 36 + this.plugin.settings.identifier = value; 37 + await this.plugin.saveSettings(); 38 + }) 39 + ); 40 + 41 + new Setting(containerEl) 42 + .setName("App password") 43 + .setDesc("Create one at https://bsky.app/settings/app-passwords") 44 + .addText((text) => { 45 + text.inputEl.type = "password"; 46 + text 47 + .setValue(this.plugin.settings.appPassword) 48 + .onChange(async (value) => { 49 + this.plugin.settings.appPassword = value; 50 + await this.plugin.saveSettings(); 51 + }); 52 + }); 53 + new Setting(containerEl) 54 + .setName("Clip directory") 55 + .setDesc("Directory in your vault to save clips (will be created if it doesn't exist)") 56 + .addText((text) => 57 + text 58 + .setValue(this.plugin.settings.clipDir) 59 + .onChange(async (value) => { 60 + this.plugin.settings.clipDir = value; 61 + await this.plugin.saveSettings(); 62 + }) 63 + ); 64 + } 65 + }
+233
src/sources/bookmark.ts
··· 1 + import type { Client } from "@atcute/client"; 2 + import type { Record } from "@atcute/atproto/types/repo/listRecords"; 3 + import { setIcon } from "obsidian"; 4 + import type ATmarkPlugin from "../main"; 5 + import { getBookmarks } from "../lib"; 6 + import type { ATmarkItem, DataSource, SourceFilter } from "./types"; 7 + import { EditBookmarkModal } from "../components/editBookmarkModal"; 8 + import { CreateTagModal } from "../components/createTagModal"; 9 + import type { Main as Bookmark } from "../lexicons/types/community/lexicon/bookmarks/bookmark"; 10 + 11 + type BookmarkRecord = Record & { value: Bookmark }; 12 + 13 + class BookmarkItem implements ATmarkItem { 14 + private record: BookmarkRecord; 15 + private plugin: ATmarkPlugin; 16 + 17 + constructor(record: BookmarkRecord, plugin: ATmarkPlugin) { 18 + this.record = record; 19 + this.plugin = plugin; 20 + } 21 + 22 + getUri(): string { 23 + return this.record.uri; 24 + } 25 + 26 + getCid(): string { 27 + return this.record.cid; 28 + } 29 + 30 + getCreatedAt(): string { 31 + return this.record.value.createdAt; 32 + } 33 + 34 + getSource(): "bookmark" { 35 + return "bookmark"; 36 + } 37 + 38 + canAddNotes(): boolean { 39 + return false; 40 + } 41 + 42 + canEdit(): boolean { 43 + return true; 44 + } 45 + 46 + openEditModal(onSuccess?: () => void): void { 47 + new EditBookmarkModal(this.plugin, this.record, onSuccess).open(); 48 + } 49 + 50 + render(container: HTMLElement): void { 51 + const el = container.createEl("div", { cls: "atmark-item-content" }); 52 + const bookmark = this.record.value; 53 + const enriched = bookmark.enriched; 54 + 55 + if (bookmark.tags && bookmark.tags.length > 0) { 56 + const tagsContainer = el.createEl("div", { cls: "atmark-item-tags" }); 57 + for (const tag of bookmark.tags) { 58 + tagsContainer.createEl("span", { text: tag, cls: "atmark-tag" }); 59 + } 60 + } 61 + 62 + const title = enriched?.title || bookmark.title; 63 + if (title) { 64 + el.createEl("div", { text: title, cls: "atmark-item-title" }); 65 + } 66 + 67 + const imageUrl = enriched?.image || enriched?.thumb; 68 + if (imageUrl) { 69 + const img = el.createEl("img", { cls: "atmark-item-image" }); 70 + img.src = imageUrl; 71 + img.alt = title || "Image"; 72 + } 73 + 74 + const description = enriched?.description || bookmark.description; 75 + if (description) { 76 + const desc = description.length > 200 77 + ? description.slice(0, 200) + "โ€ฆ" 78 + : description; 79 + el.createEl("p", { text: desc, cls: "atmark-item-desc" }); 80 + } 81 + 82 + if (enriched?.siteName) { 83 + el.createEl("span", { text: enriched.siteName, cls: "atmark-item-site" }); 84 + } 85 + 86 + const link = el.createEl("a", { 87 + text: bookmark.subject, 88 + href: bookmark.subject, 89 + cls: "atmark-item-url", 90 + }); 91 + link.setAttr("target", "_blank"); 92 + } 93 + 94 + renderDetail(container: HTMLElement): void { 95 + const body = container.createEl("div", { cls: "atmark-detail-body" }); 96 + const bookmark = this.record.value; 97 + const enriched = bookmark.enriched; 98 + 99 + const title = enriched?.title || bookmark.title; 100 + if (title) { 101 + body.createEl("h2", { text: title, cls: "atmark-detail-title" }); 102 + } 103 + 104 + const imageUrl = enriched?.image || enriched?.thumb; 105 + if (imageUrl) { 106 + const img = body.createEl("img", { cls: "atmark-detail-image" }); 107 + img.src = imageUrl; 108 + img.alt = title || "Image"; 109 + } 110 + 111 + const description = enriched?.description || bookmark.description; 112 + if (description) { 113 + body.createEl("p", { text: description, cls: "atmark-detail-description" }); 114 + } 115 + 116 + if (enriched?.siteName) { 117 + const metaGrid = body.createEl("div", { cls: "atmark-detail-meta" }); 118 + const item = metaGrid.createEl("div", { cls: "atmark-detail-meta-item" }); 119 + item.createEl("span", { text: "Site", cls: "atmark-detail-meta-label" }); 120 + item.createEl("span", { text: enriched.siteName, cls: "atmark-detail-meta-value" }); 121 + } 122 + 123 + const linkWrapper = body.createEl("div", { cls: "atmark-detail-link-wrapper" }); 124 + const link = linkWrapper.createEl("a", { 125 + text: bookmark.subject, 126 + href: bookmark.subject, 127 + cls: "atmark-detail-link", 128 + }); 129 + link.setAttr("target", "_blank"); 130 + 131 + if (bookmark.tags && bookmark.tags.length > 0) { 132 + const tagsSection = container.createEl("div", { cls: "atmark-item-tags-section" }); 133 + tagsSection.createEl("h3", { text: "Tags", cls: "atmark-detail-section-title" }); 134 + const tagsContainer = tagsSection.createEl("div", { cls: "atmark-item-tags" }); 135 + for (const tag of bookmark.tags) { 136 + tagsContainer.createEl("span", { text: tag, cls: "atmark-tag" }); 137 + } 138 + } 139 + } 140 + 141 + getTags() { 142 + return this.record.value.tags || []; 143 + } 144 + 145 + getRecord() { 146 + return this.record; 147 + } 148 + } 149 + 150 + export class BookmarkSource implements DataSource { 151 + readonly name = "bookmark" as const; 152 + private client: Client; 153 + private repo: string; 154 + 155 + constructor(client: Client, repo: string) { 156 + this.client = client; 157 + this.repo = repo; 158 + } 159 + 160 + async fetchItems(filters: SourceFilter[], plugin: ATmarkPlugin): Promise<ATmarkItem[]> { 161 + const bookmarksResp = await getBookmarks(this.client, this.repo); 162 + if (!bookmarksResp.ok) return []; 163 + 164 + let bookmarks = bookmarksResp.data.records as BookmarkRecord[]; 165 + 166 + const tagFilter = filters.find(f => f.type === "bookmarkTag"); 167 + if (tagFilter && tagFilter.value) { 168 + bookmarks = bookmarks.filter((record: BookmarkRecord) => 169 + record.value.tags?.includes(tagFilter.value) 170 + ); 171 + } 172 + 173 + return bookmarks.map((record: BookmarkRecord) => new BookmarkItem(record, plugin)); 174 + } 175 + 176 + async getAvailableFilters(): Promise<SourceFilter[]> { 177 + const bookmarksResp = await getBookmarks(this.client, this.repo); 178 + if (!bookmarksResp.ok) return []; 179 + 180 + const tagSet = new Set<string>(); 181 + const records = bookmarksResp.data.records as BookmarkRecord[]; 182 + for (const record of records) { 183 + if (record.value.tags) { 184 + for (const tag of record.value.tags) { 185 + tagSet.add(tag); 186 + } 187 + } 188 + } 189 + 190 + return Array.from(tagSet).map(tag => ({ 191 + type: "bookmarkTag", 192 + value: tag, 193 + label: tag, 194 + })); 195 + } 196 + 197 + renderFilterUI(container: HTMLElement, activeFilters: Map<string, SourceFilter>, onChange: () => void, plugin: ATmarkPlugin): void { 198 + const section = container.createEl("div", { cls: "atmark-filter-section" }); 199 + 200 + const titleRow = section.createEl("div", { cls: "atmark-filter-title-row" }); 201 + titleRow.createEl("h3", { text: "Tags", cls: "atmark-filter-title" }); 202 + 203 + const createBtn = titleRow.createEl("button", { cls: "atmark-filter-create-btn" }); 204 + setIcon(createBtn, "plus"); 205 + createBtn.addEventListener("click", () => { 206 + new CreateTagModal(plugin, onChange).open(); 207 + }); 208 + 209 + const chips = section.createEl("div", { cls: "atmark-filter-chips" }); 210 + 211 + const allChip = chips.createEl("button", { 212 + text: "All", 213 + cls: `atmark-chip ${!activeFilters.has("bookmarkTag") ? "atmark-chip-active" : ""}`, 214 + }); 215 + allChip.addEventListener("click", () => { 216 + activeFilters.delete("bookmarkTag"); 217 + onChange(); 218 + }); 219 + 220 + void this.getAvailableFilters().then(tags => { 221 + for (const tag of tags) { 222 + const chip = chips.createEl("button", { 223 + text: tag.label, 224 + cls: `atmark-chip ${activeFilters.get("bookmarkTag")?.value === tag.value ? "atmark-chip-active" : ""}`, 225 + }); 226 + chip.addEventListener("click", () => { 227 + activeFilters.set("bookmarkTag", tag); 228 + onChange(); 229 + }); 230 + } 231 + }); 232 + } 233 + }
+304
src/sources/margin.ts
··· 1 + import type { Client } from "@atcute/client"; 2 + import type { Record } from "@atcute/atproto/types/repo/listRecords"; 3 + import { setIcon } from "obsidian"; 4 + import type ATmarkPlugin from "../main"; 5 + import { getMarginBookmarks, getMarginCollections, getMarginCollectionItems } from "../lib"; 6 + import type { ATmarkItem, DataSource, SourceFilter } from "./types"; 7 + import type { Main as MarginBookmark } from "../lexicons/types/at/margin/bookmark"; 8 + import type { Main as MarginCollection } from "../lexicons/types/at/margin/collection"; 9 + import type { Main as MarginCollectionItem } from "../lexicons/types/at/margin/collectionItem"; 10 + import { EditMarginBookmarkModal } from "../components/editMarginBookmarkModal"; 11 + import { CreateMarginCollectionModal } from "../components/createMarginCollectionModal"; 12 + 13 + type MarginBookmarkRecord = Record & { value: MarginBookmark }; 14 + type MarginCollectionRecord = Record & { value: MarginCollection }; 15 + type MarginCollectionItemRecord = Record & { value: MarginCollectionItem }; 16 + 17 + class MarginItem implements ATmarkItem { 18 + private record: MarginBookmarkRecord; 19 + private plugin: ATmarkPlugin; 20 + private collections: Array<{ uri: string; name: string }>; 21 + 22 + constructor(record: MarginBookmarkRecord, collections: Array<{ uri: string; name: string }>, plugin: ATmarkPlugin) { 23 + this.record = record; 24 + this.collections = collections; 25 + this.plugin = plugin; 26 + } 27 + 28 + getUri(): string { 29 + return this.record.uri; 30 + } 31 + 32 + getCid(): string { 33 + return this.record.cid; 34 + } 35 + 36 + getCreatedAt(): string { 37 + return this.record.value.createdAt; 38 + } 39 + 40 + getSource(): "margin" { 41 + return "margin"; 42 + } 43 + 44 + canAddNotes(): boolean { 45 + return false; 46 + } 47 + 48 + canEdit(): boolean { 49 + return true; 50 + } 51 + 52 + openEditModal(onSuccess?: () => void): void { 53 + new EditMarginBookmarkModal(this.plugin, this.record, onSuccess).open(); 54 + } 55 + 56 + render(container: HTMLElement): void { 57 + const el = container.createEl("div", { cls: "atmark-item-content" }); 58 + const bookmark = this.record.value; 59 + 60 + if (this.collections.length > 0) { 61 + const collectionsContainer = el.createEl("div", { cls: "atmark-item-collections" }); 62 + for (const collection of this.collections) { 63 + collectionsContainer.createEl("span", { text: collection.name, cls: "atmark-collection" }); 64 + } 65 + } 66 + 67 + if (bookmark.tags && bookmark.tags.length > 0) { 68 + const tagsContainer = el.createEl("div", { cls: "atmark-item-tags" }); 69 + for (const tag of bookmark.tags) { 70 + tagsContainer.createEl("span", { text: tag, cls: "atmark-tag" }); 71 + } 72 + } 73 + 74 + if (bookmark.title) { 75 + el.createEl("div", { text: bookmark.title, cls: "atmark-item-title" }); 76 + } 77 + 78 + if (bookmark.description) { 79 + const desc = bookmark.description.length > 200 80 + ? bookmark.description.slice(0, 200) + "โ€ฆ" 81 + : bookmark.description; 82 + el.createEl("p", { text: desc, cls: "atmark-item-desc" }); 83 + } 84 + 85 + const link = el.createEl("a", { 86 + text: bookmark.source, 87 + href: bookmark.source, 88 + cls: "atmark-item-url", 89 + }); 90 + link.setAttr("target", "_blank"); 91 + } 92 + 93 + renderDetail(container: HTMLElement): void { 94 + const body = container.createEl("div", { cls: "atmark-detail-body" }); 95 + const bookmark = this.record.value; 96 + 97 + if (bookmark.title) { 98 + body.createEl("h2", { text: bookmark.title, cls: "atmark-detail-title" }); 99 + } 100 + 101 + if (bookmark.description) { 102 + body.createEl("p", { text: bookmark.description, cls: "atmark-detail-description" }); 103 + } 104 + 105 + const linkWrapper = body.createEl("div", { cls: "atmark-detail-link-wrapper" }); 106 + const link = linkWrapper.createEl("a", { 107 + text: bookmark.source, 108 + href: bookmark.source, 109 + cls: "atmark-detail-link", 110 + }); 111 + link.setAttr("target", "_blank"); 112 + 113 + if (this.collections.length > 0) { 114 + const collectionsSection = container.createEl("div", { cls: "atmark-item-collections-section" }); 115 + collectionsSection.createEl("h3", { text: "Collections", cls: "atmark-detail-section-title" }); 116 + const collectionsContainer = collectionsSection.createEl("div", { cls: "atmark-item-collections" }); 117 + for (const collection of this.collections) { 118 + collectionsContainer.createEl("span", { text: collection.name, cls: "atmark-collection" }); 119 + } 120 + } 121 + 122 + if (bookmark.tags && bookmark.tags.length > 0) { 123 + const tagsSection = container.createEl("div", { cls: "atmark-item-tags-section" }); 124 + tagsSection.createEl("h3", { text: "Tags", cls: "atmark-detail-section-title" }); 125 + const tagsContainer = tagsSection.createEl("div", { cls: "atmark-item-tags" }); 126 + for (const tag of bookmark.tags) { 127 + tagsContainer.createEl("span", { text: tag, cls: "atmark-tag" }); 128 + } 129 + } 130 + } 131 + 132 + getTags() { 133 + return this.record.value.tags || []; 134 + } 135 + 136 + getRecord() { 137 + return this.record; 138 + } 139 + } 140 + 141 + export class MarginSource implements DataSource { 142 + readonly name = "margin" as const; 143 + private client: Client; 144 + private repo: string; 145 + 146 + constructor(client: Client, repo: string) { 147 + this.client = client; 148 + this.repo = repo; 149 + } 150 + 151 + async fetchItems(filters: SourceFilter[], plugin: ATmarkPlugin): Promise<ATmarkItem[]> { 152 + const bookmarksResp = await getMarginBookmarks(this.client, this.repo); 153 + if (!bookmarksResp.ok) return []; 154 + 155 + let bookmarks = bookmarksResp.data.records as MarginBookmarkRecord[]; 156 + 157 + // Build collections map (bookmark URI -> collection info) 158 + const collectionsMap = new Map<string, Array<{ uri: string; name: string }>>(); 159 + const collectionsResp = await getMarginCollections(this.client, this.repo); 160 + const itemsResp = await getMarginCollectionItems(this.client, this.repo); 161 + 162 + if (collectionsResp.ok && itemsResp.ok) { 163 + const collections = collectionsResp.data.records as MarginCollectionRecord[]; 164 + const collectionNameMap = new Map<string, string>(); 165 + for (const collection of collections) { 166 + collectionNameMap.set(collection.uri, collection.value.name); 167 + } 168 + 169 + const items = itemsResp.data.records as MarginCollectionItemRecord[]; 170 + for (const item of items) { 171 + const bookmarkUri = item.value.annotation; 172 + const collectionUri = item.value.collection; 173 + const collectionName = collectionNameMap.get(collectionUri); 174 + 175 + if (collectionName) { 176 + const existing = collectionsMap.get(bookmarkUri) || []; 177 + existing.push({ uri: collectionUri, name: collectionName }); 178 + collectionsMap.set(bookmarkUri, existing); 179 + } 180 + } 181 + } 182 + 183 + const collectionFilter = filters.find(f => f.type === "marginCollection"); 184 + if (collectionFilter && collectionFilter.value) { 185 + if (itemsResp.ok) { 186 + const items = itemsResp.data.records as MarginCollectionItemRecord[]; 187 + const filteredItems = items.filter((item: MarginCollectionItemRecord) => 188 + item.value.collection === collectionFilter.value 189 + ); 190 + const bookmarkUris = new Set(filteredItems.map((item: MarginCollectionItemRecord) => item.value.annotation)); 191 + bookmarks = bookmarks.filter((bookmark: MarginBookmarkRecord) => bookmarkUris.has(bookmark.uri)); 192 + } 193 + } 194 + 195 + const tagFilter = filters.find(f => f.type === "marginTag"); 196 + if (tagFilter && tagFilter.value) { 197 + bookmarks = bookmarks.filter((record: MarginBookmarkRecord) => 198 + record.value.tags?.includes(tagFilter.value) 199 + ); 200 + } 201 + 202 + return bookmarks.map((record: MarginBookmarkRecord) => 203 + new MarginItem(record, collectionsMap.get(record.uri) || [], plugin) 204 + ); 205 + } 206 + 207 + async getAvailableFilters(): Promise<SourceFilter[]> { 208 + const filters: SourceFilter[] = []; 209 + 210 + const collectionsResp = await getMarginCollections(this.client, this.repo); 211 + if (collectionsResp.ok) { 212 + const collections = collectionsResp.data.records as MarginCollectionRecord[]; 213 + filters.push(...collections.map((c: MarginCollectionRecord) => ({ 214 + type: "marginCollection", 215 + value: c.uri, 216 + label: c.value.name, 217 + }))); 218 + } 219 + 220 + const bookmarksResp = await getMarginBookmarks(this.client, this.repo); 221 + if (bookmarksResp.ok) { 222 + const tagSet = new Set<string>(); 223 + const records = bookmarksResp.data.records as MarginBookmarkRecord[]; 224 + for (const record of records) { 225 + if (record.value.tags) { 226 + for (const tag of record.value.tags) { 227 + tagSet.add(tag); 228 + } 229 + } 230 + } 231 + filters.push(...Array.from(tagSet).map(tag => ({ 232 + type: "marginTag", 233 + value: tag, 234 + label: tag, 235 + }))); 236 + } 237 + 238 + return filters; 239 + } 240 + 241 + renderFilterUI(container: HTMLElement, activeFilters: Map<string, SourceFilter>, onChange: () => void, plugin: ATmarkPlugin): void { 242 + const collectionsSection = container.createEl("div", { cls: "atmark-filter-section" }); 243 + 244 + const collectionsTitleRow = collectionsSection.createEl("div", { cls: "atmark-filter-title-row" }); 245 + collectionsTitleRow.createEl("h3", { text: "Collections", cls: "atmark-filter-title" }); 246 + 247 + const createCollectionBtn = collectionsTitleRow.createEl("button", { cls: "atmark-filter-create-btn" }); 248 + setIcon(createCollectionBtn, "plus"); 249 + createCollectionBtn.addEventListener("click", () => { 250 + new CreateMarginCollectionModal(plugin, onChange).open(); 251 + }); 252 + 253 + const collectionsChips = collectionsSection.createEl("div", { cls: "atmark-filter-chips" }); 254 + 255 + const allCollectionsChip = collectionsChips.createEl("button", { 256 + text: "All", 257 + cls: `atmark-chip ${!activeFilters.has("marginCollection") ? "atmark-chip-active" : ""}`, 258 + }); 259 + allCollectionsChip.addEventListener("click", () => { 260 + activeFilters.delete("marginCollection"); 261 + onChange(); 262 + }); 263 + 264 + const tagsSection = container.createEl("div", { cls: "atmark-filter-section" }); 265 + 266 + const tagsTitleRow = tagsSection.createEl("div", { cls: "atmark-filter-title-row" }); 267 + tagsTitleRow.createEl("h3", { text: "Tags", cls: "atmark-filter-title" }); 268 + 269 + const tagsChips = tagsSection.createEl("div", { cls: "atmark-filter-chips" }); 270 + 271 + const allTagsChip = tagsChips.createEl("button", { 272 + text: "All", 273 + cls: `atmark-chip ${!activeFilters.has("marginTag") ? "atmark-chip-active" : ""}`, 274 + }); 275 + allTagsChip.addEventListener("click", () => { 276 + activeFilters.delete("marginTag"); 277 + onChange(); 278 + }); 279 + 280 + void this.getAvailableFilters().then(filters => { 281 + for (const filter of filters) { 282 + if (filter.type === "marginCollection") { 283 + const chip = collectionsChips.createEl("button", { 284 + text: filter.label, 285 + cls: `atmark-chip ${activeFilters.get("marginCollection")?.value === filter.value ? "atmark-chip-active" : ""}`, 286 + }); 287 + chip.addEventListener("click", () => { 288 + activeFilters.set("marginCollection", filter); 289 + onChange(); 290 + }); 291 + } else if (filter.type === "marginTag") { 292 + const chip = tagsChips.createEl("button", { 293 + text: filter.label, 294 + cls: `atmark-chip ${activeFilters.get("marginTag")?.value === filter.value ? "atmark-chip-active" : ""}`, 295 + }); 296 + chip.addEventListener("click", () => { 297 + activeFilters.set("marginTag", filter); 298 + onChange(); 299 + }); 300 + } 301 + } 302 + }); 303 + } 304 + }
+256
src/sources/semble.ts
··· 1 + import type { Client } from "@atcute/client"; 2 + import type { Record } from "@atcute/atproto/types/repo/listRecords"; 3 + import { setIcon } from "obsidian"; 4 + import type ATmarkPlugin from "../main"; 5 + import { getCards, getCollections, getCollectionLinks } from "../lib"; 6 + import type { Main as Card, NoteContent, UrlContent } from "../lexicons/types/network/cosmik/card"; 7 + import type { Main as Collection } from "../lexicons/types/network/cosmik/collection"; 8 + import type { Main as CollectionLink } from "../lexicons/types/network/cosmik/collectionLink"; 9 + import type { ATmarkItem, DataSource, SourceFilter } from "./types"; 10 + import { EditCardModal } from "../components/editCardModal"; 11 + import { CreateCollectionModal } from "../components/createCollectionModal"; 12 + 13 + type CardRecord = Record & { value: Card }; 14 + type CollectionRecord = Record & { value: Collection }; 15 + type CollectionLinkRecord = Record & { value: CollectionLink }; 16 + 17 + class SembleItem implements ATmarkItem { 18 + private record: CardRecord; 19 + private attachedNotes: Array<{ uri: string; text: string }>; 20 + private plugin: ATmarkPlugin; 21 + 22 + constructor(record: CardRecord, attachedNotes: Array<{ uri: string; text: string }>, plugin: ATmarkPlugin) { 23 + this.record = record; 24 + this.attachedNotes = attachedNotes; 25 + this.plugin = plugin; 26 + } 27 + 28 + getUri(): string { 29 + return this.record.uri; 30 + } 31 + 32 + getCid(): string { 33 + return this.record.cid; 34 + } 35 + 36 + getCreatedAt(): string { 37 + return this.record.value.createdAt || new Date().toISOString(); 38 + } 39 + 40 + getSource(): "semble" { 41 + return "semble"; 42 + } 43 + 44 + canAddNotes(): boolean { 45 + return true; 46 + } 47 + 48 + canEdit(): boolean { 49 + return true; 50 + } 51 + 52 + openEditModal(onSuccess?: () => void): void { 53 + new EditCardModal(this.plugin, this.record.uri, this.record.cid, onSuccess).open(); 54 + } 55 + 56 + render(container: HTMLElement): void { 57 + const el = container.createEl("div", { cls: "atmark-item-content" }); 58 + 59 + const card = this.record.value; 60 + 61 + if (card.type === "NOTE") { 62 + const content = card.content as NoteContent; 63 + el.createEl("p", { text: content.text, cls: "atmark-semble-card-text" }); 64 + } else if (card.type === "URL") { 65 + const content = card.content as UrlContent; 66 + const meta = content.metadata; 67 + 68 + if (meta?.title) { 69 + el.createEl("div", { text: meta.title, cls: "atmark-item-title" }); 70 + } 71 + 72 + if (meta?.imageUrl) { 73 + const img = el.createEl("img", { cls: "atmark-item-image" }); 74 + img.src = meta.imageUrl; 75 + img.alt = meta.title || "Image"; 76 + } 77 + 78 + if (meta?.description) { 79 + const desc = meta.description.length > 200 80 + ? meta.description.slice(0, 200) + "โ€ฆ" 81 + : meta.description; 82 + el.createEl("p", { text: desc, cls: "atmark-item-desc" }); 83 + } 84 + 85 + if (meta?.siteName) { 86 + el.createEl("span", { text: meta.siteName, cls: "atmark-item-site" }); 87 + } 88 + 89 + const link = el.createEl("a", { 90 + text: content.url, 91 + href: content.url, 92 + cls: "atmark-item-url", 93 + }); 94 + link.setAttr("target", "_blank"); 95 + } 96 + } 97 + 98 + renderDetail(container: HTMLElement): void { 99 + const body = container.createEl("div", { cls: "atmark-detail-body" }); 100 + const card = this.record.value; 101 + 102 + if (card.type === "NOTE") { 103 + const content = card.content as NoteContent; 104 + body.createEl("p", { text: content.text, cls: "atmark-semble-detail-text" }); 105 + } else if (card.type === "URL") { 106 + const content = card.content as UrlContent; 107 + const meta = content.metadata; 108 + 109 + if (meta?.title) { 110 + body.createEl("h2", { text: meta.title, cls: "atmark-detail-title" }); 111 + } 112 + 113 + if (meta?.imageUrl) { 114 + const img = body.createEl("img", { cls: "atmark-detail-image" }); 115 + img.src = meta.imageUrl; 116 + img.alt = meta.title || "Image"; 117 + } 118 + 119 + if (meta?.description) { 120 + body.createEl("p", { text: meta.description, cls: "atmark-detail-description" }); 121 + } 122 + 123 + if (meta?.siteName) { 124 + const metaGrid = body.createEl("div", { cls: "atmark-detail-meta" }); 125 + const item = metaGrid.createEl("div", { cls: "atmark-detail-meta-item" }); 126 + item.createEl("span", { text: "Site", cls: "atmark-detail-meta-label" }); 127 + item.createEl("span", { text: meta.siteName, cls: "atmark-detail-meta-value" }); 128 + } 129 + 130 + const linkWrapper = body.createEl("div", { cls: "atmark-detail-link-wrapper" }); 131 + const link = linkWrapper.createEl("a", { 132 + text: content.url, 133 + href: content.url, 134 + cls: "atmark-detail-link", 135 + }); 136 + link.setAttr("target", "_blank"); 137 + } 138 + 139 + } 140 + 141 + getAttachedNotes() { 142 + return this.attachedNotes; 143 + } 144 + 145 + getRecord() { 146 + return this.record; 147 + } 148 + } 149 + 150 + export class SembleSource implements DataSource { 151 + readonly name = "semble" as const; 152 + private client: Client; 153 + private repo: string; 154 + 155 + constructor(client: Client, repo: string) { 156 + this.client = client; 157 + this.repo = repo; 158 + } 159 + 160 + async fetchItems(filters: SourceFilter[], plugin: ATmarkPlugin): Promise<ATmarkItem[]> { 161 + const cardsResp = await getCards(this.client, this.repo); 162 + if (!cardsResp.ok) return []; 163 + 164 + const allSembleCards = cardsResp.data.records as CardRecord[]; 165 + 166 + const notesMap = new Map<string, Array<{ uri: string; text: string }>>(); 167 + for (const record of allSembleCards) { 168 + if (record.value.type === "NOTE") { 169 + const parentUri = record.value.parentCard?.uri; 170 + if (parentUri) { 171 + const noteContent = record.value.content as NoteContent; 172 + const existing = notesMap.get(parentUri) || []; 173 + existing.push({ uri: record.uri, text: noteContent.text }); 174 + notesMap.set(parentUri, existing); 175 + } 176 + } 177 + } 178 + 179 + // Filter out NOTE cards that are attached to other cards 180 + let sembleCards = allSembleCards.filter((record: CardRecord) => { 181 + if (record.value.type === "NOTE") { 182 + const hasParent = record.value.parentCard?.uri; 183 + return !hasParent; 184 + } 185 + return true; 186 + }); 187 + 188 + const collectionFilter = filters.find(f => f.type === "sembleCollection"); 189 + if (collectionFilter && collectionFilter.value) { 190 + const linksResp = await getCollectionLinks(this.client, this.repo); 191 + if (linksResp.ok) { 192 + const links = linksResp.data.records as CollectionLinkRecord[]; 193 + const filteredLinks = links.filter((link: CollectionLinkRecord) => 194 + link.value.collection.uri === collectionFilter.value 195 + ); 196 + const cardUris = new Set(filteredLinks.map((link: CollectionLinkRecord) => link.value.card.uri)); 197 + sembleCards = sembleCards.filter((card: CardRecord) => cardUris.has(card.uri)); 198 + } 199 + } 200 + 201 + return sembleCards.map((record: CardRecord) => 202 + new SembleItem(record, notesMap.get(record.uri) || [], plugin) 203 + ); 204 + } 205 + 206 + async getAvailableFilters(): Promise<SourceFilter[]> { 207 + const collectionsResp = await getCollections(this.client, this.repo); 208 + if (!collectionsResp.ok) return []; 209 + 210 + const collections = collectionsResp.data.records as CollectionRecord[]; 211 + return collections.map((c: CollectionRecord) => ({ 212 + type: "sembleCollection", 213 + value: c.uri, 214 + label: c.value.name, 215 + })); 216 + } 217 + 218 + renderFilterUI(container: HTMLElement, activeFilters: Map<string, SourceFilter>, onChange: () => void, plugin: ATmarkPlugin): void { 219 + const section = container.createEl("div", { cls: "atmark-filter-section" }); 220 + 221 + const titleRow = section.createEl("div", { cls: "atmark-filter-title-row" }); 222 + titleRow.createEl("h3", { text: "Semble collections", cls: "atmark-filter-title" }); 223 + 224 + const createBtn = titleRow.createEl("button", { cls: "atmark-filter-create-btn" }); 225 + setIcon(createBtn, "plus"); 226 + createBtn.addEventListener("click", () => { 227 + new CreateCollectionModal(plugin, onChange).open(); 228 + }); 229 + 230 + const chips = section.createEl("div", { cls: "atmark-filter-chips" }); 231 + 232 + const allChip = chips.createEl("button", { 233 + text: "All", 234 + cls: `atmark-chip ${!activeFilters.has("sembleCollection") ? "atmark-chip-active" : ""}`, 235 + }); 236 + allChip.addEventListener("click", () => { 237 + activeFilters.delete("sembleCollection"); 238 + onChange(); 239 + }); 240 + 241 + // Get collections synchronously - note: this is a limitation 242 + // In a real app, we'd want to cache these or handle async properly 243 + void this.getAvailableFilters().then(collections => { 244 + for (const collection of collections) { 245 + const chip = chips.createEl("button", { 246 + text: collection.label, 247 + cls: `atmark-chip ${activeFilters.get("sembleCollection")?.value === collection.value ? "atmark-chip-active" : ""}`, 248 + }); 249 + chip.addEventListener("click", () => { 250 + activeFilters.set("sembleCollection", collection); 251 + onChange(); 252 + }); 253 + } 254 + }); 255 + } 256 + }
+27
src/sources/types.ts
··· 1 + import type ATmarkPlugin from "../main"; 2 + 3 + export interface ATmarkItem { 4 + render(container: HTMLElement): void; 5 + renderDetail(container: HTMLElement): void; 6 + canAddNotes(): boolean; 7 + canEdit(): boolean; 8 + openEditModal(onSuccess?: () => void): void; 9 + getUri(): string; 10 + getCid(): string; 11 + getCreatedAt(): string; 12 + getSource(): "semble" | "bookmark" | "margin"; 13 + getAttachedNotes?(): Array<{ uri: string; text: string }>; 14 + } 15 + 16 + export interface SourceFilter { 17 + type: string; 18 + value: string; 19 + label?: string; 20 + } 21 + 22 + export interface DataSource { 23 + readonly name: "semble" | "bookmark" | "margin"; 24 + fetchItems(filters: SourceFilter[], plugin: ATmarkPlugin): Promise<ATmarkItem[]>; 25 + getAvailableFilters(): Promise<SourceFilter[]>; 26 + renderFilterUI(container: HTMLElement, activeFilters: Map<string, SourceFilter>, onChange: () => void, plugin: ATmarkPlugin): void; 27 + }
+199
src/views/atmark.ts
··· 1 + import { ItemView, WorkspaceLeaf, setIcon } from "obsidian"; 2 + import type ATmarkPlugin from "../main"; 3 + import { CardDetailModal } from "../components/cardDetailModal"; 4 + import type { ATmarkItem, DataSource, SourceFilter } from "../sources/types"; 5 + import { SembleSource } from "../sources/semble"; 6 + import { BookmarkSource } from "../sources/bookmark"; 7 + import { MarginSource } from "../sources/margin"; 8 + 9 + export const VIEW_TYPE_ATMARK = "atmark-view"; 10 + 11 + type SourceType = "semble" | "bookmark" | "margin"; 12 + 13 + export class ATmarkView extends ItemView { 14 + plugin: ATmarkPlugin; 15 + activeSource: SourceType = "semble"; 16 + sources: Map<SourceType, { source: DataSource; filters: Map<string, SourceFilter> }> = new Map(); 17 + 18 + constructor(leaf: WorkspaceLeaf, plugin: ATmarkPlugin) { 19 + super(leaf); 20 + this.plugin = plugin; 21 + } 22 + 23 + initSources() { 24 + if (this.plugin.settings.identifier) { 25 + const repo = this.plugin.settings.identifier; 26 + this.sources.set("semble", { 27 + source: new SembleSource(this.plugin.client, repo), 28 + filters: new Map() 29 + }); 30 + this.sources.set("bookmark", { 31 + source: new BookmarkSource(this.plugin.client, repo), 32 + filters: new Map() 33 + }); 34 + this.sources.set("margin", { 35 + source: new MarginSource(this.plugin.client, repo), 36 + filters: new Map() 37 + }); 38 + } 39 + 40 + } 41 + 42 + getViewType() { 43 + return VIEW_TYPE_ATMARK; 44 + } 45 + 46 + getDisplayText() { 47 + // This is the name of the plugin, which contains the acronym "AT" 48 + // eslint-disable-next-line obsidianmd/ui/sentence-case 49 + return "ATmark"; 50 + } 51 + 52 + getIcon() { 53 + return "layers"; 54 + } 55 + 56 + async onOpen() { 57 + this.initSources(); 58 + await this.render(); 59 + } 60 + 61 + async fetchItems(): Promise<ATmarkItem[]> { 62 + if (!this.plugin.client) return []; 63 + 64 + const sourceData = this.sources.get(this.activeSource); 65 + if (!sourceData) return []; 66 + 67 + const filters = Array.from(sourceData.filters.values()); 68 + return await sourceData.source.fetchItems(filters, this.plugin); 69 + } 70 + 71 + async render() { 72 + const container = this.contentEl; 73 + container.empty(); 74 + container.addClass("atmark-view"); 75 + 76 + this.renderHeader(container); 77 + 78 + const loading = container.createEl("p", { text: "Loading..." }); 79 + 80 + try { 81 + const items = await this.fetchItems(); 82 + loading.remove(); 83 + 84 + 85 + if (items.length === 0) { 86 + container.createEl("p", { text: "No items found." }); 87 + return; 88 + } 89 + 90 + const grid = container.createEl("div", { cls: "atmark-grid" }); 91 + for (const item of items) { 92 + try { 93 + this.renderItem(grid, item); 94 + } catch (err) { 95 + const message = err instanceof Error ? err.message : String(err); 96 + console.error(`Failed to render item ${item.getUri()}: ${message}`); 97 + } 98 + } 99 + } catch (err) { 100 + loading.remove(); 101 + const message = err instanceof Error ? err.message : String(err); 102 + container.createEl("p", { text: `Failed to load: ${message}`, cls: "atmark-error" }); 103 + } 104 + } 105 + 106 + private renderHeader(container: HTMLElement) { 107 + const header = container.createEl("div", { cls: "atmark-header" }); 108 + 109 + const sourceSelector = header.createEl("div", { cls: "atmark-source-selector" }); 110 + const sources: SourceType[] = ["semble", "margin", "bookmark"]; 111 + 112 + for (const source of sources) { 113 + const label = sourceSelector.createEl("label", { cls: "atmark-source-option" }); 114 + 115 + const radio = label.createEl("input", { 116 + type: "radio", 117 + cls: "atmark-source-radio", 118 + }); 119 + radio.name = "atmark-source"; 120 + radio.checked = this.activeSource === source; 121 + radio.addEventListener("change", () => { 122 + this.activeSource = source; 123 + void this.render(); 124 + }); 125 + 126 + label.createEl("span", { 127 + text: source.charAt(0).toUpperCase() + source.slice(1), 128 + cls: "atmark-source-text", 129 + }); 130 + } 131 + 132 + const filtersContainer = container.createEl("div", { cls: "atmark-filters" }); 133 + const sourceData = this.sources.get(this.activeSource); 134 + if (sourceData) { 135 + sourceData.source.renderFilterUI( 136 + filtersContainer, 137 + sourceData.filters, 138 + () => void this.render(), 139 + this.plugin 140 + ); 141 + } 142 + } 143 + 144 + private renderItem(container: HTMLElement, item: ATmarkItem) { 145 + const el = container.createEl("div", { cls: "atmark-item" }); 146 + 147 + el.addEventListener("click", (e) => { 148 + // Don't open detail if clicking the edit button 149 + if ((e.target as HTMLElement).closest(".atmark-item-edit-btn")) { 150 + return; 151 + } 152 + new CardDetailModal(this.plugin, item, () => { 153 + void this.render(); 154 + }).open(); 155 + }); 156 + 157 + const header = el.createEl("div", { cls: "atmark-item-header" }); 158 + const source = item.getSource(); 159 + header.createEl("span", { 160 + text: source, 161 + cls: `atmark-badge atmark-badge-${source}`, 162 + }); 163 + 164 + if (item.canEdit()) { 165 + const editBtn = header.createEl("button", { 166 + cls: "atmark-item-edit-btn", 167 + }); 168 + setIcon(editBtn, "more-vertical"); 169 + editBtn.addEventListener("click", (e) => { 170 + e.stopPropagation(); 171 + item.openEditModal(() => { 172 + void this.render(); 173 + }); 174 + }); 175 + } 176 + 177 + item.render(el); 178 + 179 + const footer = el.createEl("div", { cls: "atmark-item-footer" }); 180 + footer.createEl("span", { 181 + text: new Date(item.getCreatedAt()).toLocaleDateString(), 182 + cls: "atmark-date", 183 + }); 184 + 185 + // Show note indicator for items with attached notes (semble cards) 186 + const notes = item.getAttachedNotes?.(); 187 + if (notes && notes.length > 0) { 188 + const noteIndicator = footer.createEl("div", { cls: "atmark-note-indicator" }); 189 + const icon = noteIndicator.createEl("span", { cls: "atmark-note-icon" }); 190 + setIcon(icon, "message-square"); 191 + noteIndicator.createEl("span", { 192 + text: `${notes.length} note${notes.length > 1 ? 's' : ''}`, 193 + cls: "atmark-note-count" 194 + }); 195 + } 196 + } 197 + 198 + async onClose() { } 199 + }
+236
src/views/standardfeed.ts
··· 1 + import { getSubscribedPublications } from "lib/standardsite"; 2 + import ATmarkPlugin from "main"; 3 + import { ItemView, Notice, WorkspaceLeaf, setIcon } from "obsidian"; 4 + import { Main as Document } from "@atcute/standard-site/types/document"; 5 + import { Main as Publication } from "@atcute/standard-site/types/publication"; 6 + import { ATRecord } from "lib"; 7 + import { parseResourceUri } from "@atcute/lexicons"; 8 + import { getPublicationDocuments } from "lib/standardsite"; 9 + 10 + export const VIEW_STANDARD_FEED = "standard-site-feed"; 11 + 12 + export class StandardFeedView extends ItemView { 13 + plugin: ATmarkPlugin; 14 + 15 + constructor(leaf: WorkspaceLeaf, plugin: ATmarkPlugin) { 16 + super(leaf); 17 + this.plugin = plugin; 18 + } 19 + 20 + getViewType() { 21 + return VIEW_STANDARD_FEED; 22 + } 23 + 24 + getDisplayText() { 25 + return "Feed"; 26 + } 27 + 28 + getIcon() { 29 + return "rss"; 30 + } 31 + 32 + async onOpen() { 33 + await this.render(); 34 + } 35 + 36 + async render() { 37 + const container = this.contentEl; 38 + container.empty(); 39 + container.addClass("standard-site-view"); 40 + this.renderHeader(container); 41 + 42 + 43 + const loading = container.createEl("p", { text: "Loading feed..." }); 44 + try { 45 + const pubs = await getSubscribedPublications(this.plugin.client, this.plugin.settings.identifier); 46 + loading.remove(); 47 + 48 + if (pubs.length === 0) { 49 + container.createEl("p", { text: "No subscriptions found" }); 50 + return; 51 + } 52 + 53 + const list = container.createEl("div", { cls: "standard-site-list" }); 54 + 55 + for (const pub of pubs) { 56 + void this.renderPublicationCard(list, pub); 57 + } 58 + } catch (error) { 59 + const message = error instanceof Error ? error.message : String(error); 60 + console.error("Failed to load feed:", error); 61 + container.createEl("p", { text: `Failed to load feed: ${message}`, cls: "standard-site-error" }); 62 + } finally { 63 + loading.remove(); 64 + } 65 + } 66 + 67 + private async renderPublicationCard(container: HTMLElement, pub: ATRecord<Publication>) { 68 + const card = container.createEl("div", { cls: "standard-site-publication" }); 69 + 70 + const header = card.createEl("div", { cls: "standard-site-publication-header" }); 71 + header.createEl("h3", { 72 + text: pub.value.name, 73 + cls: "standard-site-publication-name" 74 + }); 75 + const extLink = header.createEl("span", { cls: "clickable standard-site-publication-external" }); 76 + setIcon(extLink, "external-link"); 77 + extLink.addEventListener("click", (e) => { 78 + e.stopPropagation(); 79 + window.open(pub.value.url, "_blank"); 80 + }); 81 + 82 + const body = card.createEl("div", { cls: "standard-site-publication-body" }); 83 + 84 + const handleEl = body.createEl("span", { cls: "standard-site-author-handle", text: "..." }); 85 + const parsed = parseResourceUri(pub.uri); 86 + if (parsed.ok) { 87 + this.plugin.client.getActor(parsed.value.repo).then(actor => { 88 + if (actor?.handle) { 89 + handleEl.setText(`@${actor.handle}`); 90 + } else { 91 + handleEl.setText(""); 92 + } 93 + }).catch(() => { 94 + handleEl.setText(""); 95 + }); 96 + } 97 + 98 + const urlLine = body.createEl("div", { cls: "standard-site-publication-url" }); 99 + const link = urlLine.createEl("a", { text: pub.value.url, href: pub.value.url }); 100 + link.setAttr("target", "_blank"); 101 + 102 + if (pub.value.description) { 103 + body.createEl("p", { 104 + text: pub.value.description, 105 + cls: "standard-site-publication-description" 106 + }); 107 + } 108 + 109 + card.addClass("clickable"); 110 + card.addEventListener("click", (e) => { 111 + if ((e.target as HTMLElement).tagName !== "A") { 112 + void this.renderPublicationDocuments(pub); 113 + } 114 + }); 115 + } 116 + 117 + private async renderPublicationDocuments(pub: ATRecord<Publication>) { 118 + const container = this.contentEl; 119 + container.empty(); 120 + container.addClass("standard-site-view"); 121 + 122 + const header = container.createEl("div", { cls: "standard-site-header" }); 123 + const backBtn = header.createEl("span", { text: "Back", cls: "clickable standard-site-back" }); 124 + setIcon(backBtn, "arrow-left"); 125 + backBtn.addEventListener("click", () => { 126 + void this.render(); 127 + }); 128 + 129 + const titleGroup = header.createEl("div", { cls: "standard-site-title-group" }); 130 + titleGroup.createEl("h2", { text: pub.value.name }); 131 + const handleEl = titleGroup.createEl("span", { cls: "standard-site-author-handle", text: "..." }); 132 + 133 + const parsed = parseResourceUri(pub.uri); 134 + if (!parsed.ok) { 135 + // This is the name of the plugin, which contains the acronym "AT" 136 + // eslint-disable-next-line obsidianmd/ui/sentence-case 137 + container.createEl("p", { text: "Failed to parse publication URI." }); 138 + console.error("Failed to parse publication URI:", parsed.error); 139 + 140 + return; 141 + } 142 + 143 + // Fetch actor handle asynchronously without blocking document load 144 + this.plugin.client.getActor(parsed.value.repo).then(actor => { 145 + if (actor?.handle) { 146 + handleEl.setText(`@${actor.handle}`); 147 + } else { 148 + handleEl.setText(""); 149 + } 150 + }).catch(() => { 151 + handleEl.setText(""); 152 + }); 153 + 154 + const loading = container.createEl("p", { text: "Loading documents..." }); 155 + 156 + try { 157 + const docsResp = await getPublicationDocuments(this.plugin.client, parsed.value.repo, pub.uri); 158 + loading.remove(); 159 + 160 + if (docsResp.records.length === 0) { 161 + container.createEl("p", { text: "No documents found for this publication." }); 162 + return; 163 + } 164 + 165 + const list = container.createEl("div", { cls: "standard-site-list" }); 166 + for (const doc of docsResp.records) { 167 + this.renderDocumentCard(list, doc, pub); 168 + } 169 + } catch (error) { 170 + loading.remove(); 171 + const message = error instanceof Error ? error.message : String(error); 172 + container.createEl("p", { text: `Failed to load documents: ${message}`, cls: "standard-site-error" }); 173 + } 174 + } 175 + 176 + 177 + private renderDocumentCard(container: HTMLElement, doc: ATRecord<Document>, pub: ATRecord<Publication>) { 178 + const card = container.createEl("div", { cls: "standard-site-document" }); 179 + 180 + const header = card.createEl("div", { cls: "standard-site-document-header" }); 181 + header.createEl("h3", { text: doc.value.title, cls: "standard-site-document-title" }); 182 + 183 + let clipIcon = "book-open"; 184 + if (this.plugin.clipper.existsInClipDir(doc)) { 185 + clipIcon = "book-open-check"; 186 + } 187 + const clipBtn = header.createEl("span", { cls: "clickable standard-site-document-clip" }); 188 + setIcon(clipBtn, clipIcon); 189 + clipBtn.addEventListener("click", (e) => { 190 + e.stopPropagation(); 191 + try { 192 + void this.plugin.clipper.clipDocument(doc, pub); 193 + } catch (error) { 194 + const message = error instanceof Error ? error.message : String(error); 195 + new Notice(`Failed to clip document: ${message}`); 196 + console.error("Failed to clip document:", error); 197 + } 198 + }) 199 + 200 + 201 + if (doc.value.path) { 202 + const extLink = header.createEl("span", { cls: "clickable standard-site-document-external" }); 203 + setIcon(extLink, "external-link"); 204 + const baseUrl = pub.value.url.replace(/\/+$/, ""); 205 + const path = doc.value.path.startsWith("/") ? doc.value.path : `/${doc.value.path}`; 206 + extLink.addEventListener("click", (e) => { 207 + e.stopPropagation(); 208 + window.open(`${baseUrl}${path}`, "_blank"); 209 + }); 210 + } 211 + 212 + const body = card.createEl("div", { cls: "standard-site-document-body" }); 213 + 214 + if (doc.value.description) { 215 + body.createEl("p", { text: doc.value.description, cls: "standard-site-document-description" }); 216 + } 217 + 218 + if (doc.value.tags && doc.value.tags.length > 0) { 219 + const tags = body.createEl("div", { cls: "standard-site-document-tags" }); 220 + for (const tag of doc.value.tags) { 221 + tags.createEl("span", { text: tag, cls: "standard-site-document-tag" }); 222 + } 223 + } 224 + 225 + if (doc.value.publishedAt) { 226 + const footer = card.createEl("div", { cls: "standard-site-document-footer" }); 227 + const date = new Date(doc.value.publishedAt).toLocaleDateString(); 228 + footer.createEl("span", { text: date, cls: "standard-site-document-date" }); 229 + } 230 + } 231 + 232 + renderHeader(container: HTMLElement) { 233 + const header = container.createEl("div", { cls: "standard-site-header" }); 234 + header.createEl("h2", { text: "Subscriptions" }); 235 + } 236 + }
+1356
styles.css
··· 1 + /* ATmark View */ 2 + .atmark-view { 3 + padding: 20px; 4 + } 5 + 6 + .atmark-header { 7 + margin-bottom: 24px; 8 + padding-bottom: 16px; 9 + border-bottom: 1px solid var(--background-modifier-border); 10 + } 11 + 12 + .atmark-source-selector { 13 + display: flex; 14 + align-items: center; 15 + justify-content: center; 16 + gap: 0; 17 + margin-bottom: 12px; 18 + border-bottom: 1px solid var(--background-modifier-border); 19 + position: relative; 20 + } 21 + 22 + .atmark-source-option { 23 + display: flex; 24 + align-items: center; 25 + justify-content: center; 26 + gap: 4px; 27 + padding: 10px 20px; 28 + cursor: pointer; 29 + user-select: none; 30 + border: none; 31 + background: transparent; 32 + transition: all 0.15s ease; 33 + position: relative; 34 + margin-bottom: -1px; 35 + } 36 + 37 + .atmark-source-option::after { 38 + content: ""; 39 + position: absolute; 40 + bottom: 0; 41 + left: 0; 42 + right: 0; 43 + height: 2px; 44 + background: transparent; 45 + transition: background 0.15s ease; 46 + } 47 + 48 + .atmark-source-option:hover { 49 + background: var(--background-modifier-hover); 50 + } 51 + 52 + .atmark-source-option:has(input:checked)::after { 53 + background: var(--interactive-accent); 54 + } 55 + 56 + .atmark-source-option:has(input:checked) .atmark-source-text { 57 + color: var(--interactive-accent); 58 + font-weight: var(--font-semibold); 59 + } 60 + 61 + .atmark-source-radio { 62 + display: none; 63 + } 64 + 65 + .atmark-source-text { 66 + font-size: var(--font-ui-small); 67 + font-weight: var(--font-medium); 68 + color: var(--text-muted); 69 + } 70 + 71 + .atmark-filters { 72 + display: flex; 73 + flex-direction: column; 74 + gap: 16px; 75 + margin-bottom: 16px; 76 + } 77 + 78 + .atmark-filter-section { 79 + display: flex; 80 + flex-direction: column; 81 + gap: 6px; 82 + } 83 + 84 + .atmark-filter-title-row { 85 + display: flex; 86 + align-items: center; 87 + gap: 6px; 88 + margin-bottom: 2px; 89 + } 90 + 91 + .atmark-filter-title { 92 + margin: 0; 93 + font-size: var(--font-smallest); 94 + font-weight: var(--font-semibold); 95 + color: var(--text-faint); 96 + text-transform: uppercase; 97 + letter-spacing: 0.05em; 98 + } 99 + 100 + .atmark-filter-create-btn { 101 + display: flex; 102 + align-items: center; 103 + justify-content: center; 104 + width: 18px; 105 + height: 18px; 106 + padding: 0; 107 + background: transparent; 108 + border: none; 109 + border-radius: var(--radius-s); 110 + cursor: pointer; 111 + color: var(--text-faint); 112 + transition: all 0.15s ease; 113 + opacity: 0.7; 114 + } 115 + 116 + .atmark-filter-create-btn:hover { 117 + background: var(--background-modifier-hover); 118 + color: var(--interactive-accent); 119 + opacity: 1; 120 + } 121 + 122 + .atmark-filter-create-btn svg { 123 + width: 12px; 124 + height: 12px; 125 + } 126 + 127 + .atmark-filter-chips { 128 + display: flex; 129 + flex-wrap: wrap; 130 + gap: 6px; 131 + align-items: center; 132 + } 133 + 134 + .atmark-chip { 135 + padding: 3px 10px; 136 + border-radius: var(--radius-m); 137 + border: none; 138 + background: var(--background-modifier-border); 139 + color: var(--text-muted); 140 + font-size: var(--font-smallest); 141 + font-weight: var(--font-medium); 142 + cursor: pointer; 143 + transition: all 0.12s ease; 144 + white-space: nowrap; 145 + } 146 + 147 + .atmark-chip:hover { 148 + background: var(--background-modifier-border-hover); 149 + color: var(--text-normal); 150 + transform: translateY(-1px); 151 + } 152 + 153 + .atmark-chip-active { 154 + background: var(--interactive-accent); 155 + color: var(--text-on-accent); 156 + font-weight: var(--font-semibold); 157 + } 158 + 159 + .atmark-chip-active:hover { 160 + background: var(--interactive-accent-hover); 161 + transform: translateY(-1px); 162 + } 163 + 164 + .atmark-grid { 165 + display: grid; 166 + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); 167 + gap: 16px; 168 + padding: 8px 0; 169 + } 170 + 171 + .atmark-item { 172 + background: var(--background-secondary); 173 + border: 1px solid var(--background-modifier-border); 174 + border-radius: var(--radius-m); 175 + padding: 16px; 176 + display: flex; 177 + flex-direction: column; 178 + transition: box-shadow 0.15s ease, border-color 0.15s ease; 179 + cursor: pointer; 180 + } 181 + 182 + .atmark-item:hover { 183 + box-shadow: var(--shadow-s); 184 + border-color: var(--background-modifier-border-hover); 185 + } 186 + 187 + .atmark-item-header { 188 + display: flex; 189 + justify-content: space-between; 190 + align-items: flex-start; 191 + gap: 8px; 192 + } 193 + 194 + .atmark-item-edit-btn { 195 + display: flex; 196 + align-items: center; 197 + justify-content: center; 198 + width: 24px; 199 + height: 24px; 200 + padding: 0; 201 + margin-left: auto; 202 + background: transparent; 203 + border: none; 204 + border-radius: var(--radius-s); 205 + cursor: pointer; 206 + color: var(--text-faint); 207 + opacity: 0.6; 208 + transition: all 0.15s ease; 209 + } 210 + 211 + .atmark-item:hover .atmark-item-edit-btn { 212 + opacity: 1; 213 + } 214 + 215 + .atmark-item-edit-btn:hover { 216 + background: var(--background-modifier-hover); 217 + color: var(--text-normal); 218 + opacity: 1; 219 + } 220 + 221 + .atmark-item-edit-btn svg { 222 + width: 14px; 223 + height: 14px; 224 + } 225 + 226 + .atmark-badge { 227 + font-size: 10px; 228 + padding: 3px 8px; 229 + border-radius: 12px; 230 + text-transform: capitalize; 231 + font-weight: var(--font-normal); 232 + flex-shrink: 0; 233 + letter-spacing: 0.3px; 234 + } 235 + 236 + .atmark-badge-semble { 237 + background: color-mix(in srgb, var(--color-orange) 15%, transparent); 238 + color: var(--color-orange); 239 + border: 1px solid color-mix(in srgb, var(--color-orange) 30%, transparent); 240 + } 241 + 242 + .atmark-badge-bookmark { 243 + background: color-mix(in srgb, var(--color-cyan) 15%, transparent); 244 + color: var(--color-cyan); 245 + border: 1px solid color-mix(in srgb, var(--color-cyan) 30%, transparent); 246 + } 247 + 248 + .atmark-badge-margin { 249 + background: color-mix(in srgb, var(--color-purple) 15%, transparent); 250 + color: var(--color-purple); 251 + border: 1px solid color-mix(in srgb, var(--color-purple) 30%, transparent); 252 + } 253 + 254 + .atmark-item-footer { 255 + display: flex; 256 + justify-content: space-between; 257 + font-size: var(--font-smallest); 258 + color: var(--text-faint); 259 + margin-top: auto; 260 + padding-top: 8px; 261 + border-top: 1px solid var(--background-modifier-border); 262 + } 263 + 264 + .atmark-date { 265 + font-size: var(--font-smallest); 266 + color: var(--text-faint); 267 + } 268 + 269 + .atmark-error { 270 + color: var(--text-error); 271 + } 272 + 273 + 274 + /* Item Content (shared between sources) */ 275 + .atmark-item-content { 276 + display: flex; 277 + flex-direction: column; 278 + gap: 12px; 279 + } 280 + 281 + .atmark-item-title { 282 + font-weight: var(--font-semibold); 283 + font-size: 1em; 284 + color: var(--text-normal); 285 + display: -webkit-box; 286 + -webkit-line-clamp: 2; 287 + -webkit-box-orient: vertical; 288 + overflow: hidden; 289 + line-height: 1.4; 290 + margin-bottom: 4px; 291 + } 292 + 293 + .atmark-item-image { 294 + width: 100%; 295 + max-height: 120px; 296 + object-fit: cover; 297 + border-radius: var(--radius-s); 298 + } 299 + 300 + .atmark-item-desc { 301 + color: var(--text-muted); 302 + font-size: var(--font-small); 303 + margin: 0; 304 + display: -webkit-box; 305 + -webkit-line-clamp: 2; 306 + -webkit-box-orient: vertical; 307 + overflow: hidden; 308 + line-height: 1.5; 309 + } 310 + 311 + .atmark-item-site { 312 + font-size: var(--font-smallest); 313 + color: var(--text-faint); 314 + margin-bottom: 2px; 315 + } 316 + 317 + .atmark-item-url { 318 + font-size: var(--font-smallest); 319 + color: var(--text-accent); 320 + text-decoration: none; 321 + word-break: break-all; 322 + display: -webkit-box; 323 + -webkit-line-clamp: 1; 324 + -webkit-box-orient: vertical; 325 + overflow: hidden; 326 + } 327 + 328 + .atmark-item-url:hover { 329 + text-decoration: underline; 330 + } 331 + 332 + .atmark-item-tags { 333 + display: flex; 334 + flex-wrap: wrap; 335 + gap: 6px; 336 + margin-bottom: 8px; 337 + } 338 + 339 + .atmark-tag { 340 + font-size: var(--font-smallest); 341 + padding: 2px 8px; 342 + border-radius: var(--radius-s); 343 + background: var(--background-modifier-border); 344 + color: var(--text-muted); 345 + border: 1px solid var(--background-modifier-border-hover); 346 + } 347 + 348 + .atmark-item-collections { 349 + display: flex; 350 + flex-wrap: wrap; 351 + gap: 6px; 352 + margin-bottom: 8px; 353 + } 354 + 355 + .atmark-collection { 356 + font-size: var(--font-smallest); 357 + padding: 2px 8px; 358 + border-radius: var(--radius-s); 359 + background: color-mix(in srgb, var(--color-purple) 10%, transparent); 360 + color: var(--color-purple); 361 + border: 1px solid color-mix(in srgb, var(--color-purple) 30%, transparent); 362 + } 363 + 364 + .atmark-item-collections-section { 365 + margin-top: 20px; 366 + padding-top: 20px; 367 + border-top: 1px solid var(--background-modifier-border); 368 + } 369 + 370 + .atmark-item-tags-section { 371 + margin-top: 20px; 372 + padding-top: 20px; 373 + border-top: 1px solid var(--background-modifier-border); 374 + } 375 + 376 + /* Note indicator for cards with attached notes */ 377 + .atmark-note-indicator { 378 + display: flex; 379 + align-items: center; 380 + gap: 4px; 381 + font-size: var(--font-smallest); 382 + color: var(--text-muted); 383 + } 384 + 385 + .atmark-note-icon { 386 + display: flex; 387 + align-items: center; 388 + color: var(--text-muted); 389 + } 390 + 391 + .atmark-note-icon svg { 392 + width: 12px; 393 + height: 12px; 394 + } 395 + 396 + .atmark-note-count { 397 + font-size: var(--font-smallest); 398 + } 399 + 400 + /* Detail Modal (shared between sources) */ 401 + .atmark-detail-body { 402 + display: flex; 403 + flex-direction: column; 404 + gap: 16px; 405 + } 406 + 407 + .atmark-detail-title { 408 + margin: 0; 409 + font-size: var(--h2-size); 410 + font-weight: var(--font-semibold); 411 + color: var(--text-normal); 412 + line-height: 1.3; 413 + } 414 + 415 + .atmark-detail-image { 416 + max-width: 100%; 417 + max-height: 200px; 418 + object-fit: contain; 419 + border-radius: var(--radius-m); 420 + } 421 + 422 + .atmark-detail-description { 423 + margin: 0; 424 + color: var(--text-normal); 425 + line-height: var(--line-height-normal); 426 + } 427 + 428 + .atmark-detail-meta { 429 + display: grid; 430 + grid-template-columns: repeat(2, 1fr); 431 + gap: 12px; 432 + padding: 16px; 433 + background: var(--background-secondary); 434 + border-radius: var(--radius-m); 435 + } 436 + 437 + .atmark-detail-meta-item { 438 + display: flex; 439 + flex-direction: column; 440 + gap: 2px; 441 + } 442 + 443 + .atmark-detail-meta-label { 444 + font-size: var(--font-smallest); 445 + color: var(--text-faint); 446 + text-transform: uppercase; 447 + letter-spacing: 0.5px; 448 + } 449 + 450 + .atmark-detail-meta-value { 451 + font-size: var(--font-small); 452 + color: var(--text-normal); 453 + } 454 + 455 + .atmark-detail-link-wrapper { 456 + padding-top: 8px; 457 + } 458 + 459 + .atmark-detail-link { 460 + font-size: var(--font-small); 461 + color: var(--text-accent); 462 + text-decoration: none; 463 + word-break: break-all; 464 + } 465 + 466 + .atmark-detail-link:hover { 467 + text-decoration: underline; 468 + } 469 + 470 + .atmark-detail-section-title { 471 + margin: 0 0 12px 0; 472 + font-size: var(--font-small); 473 + font-weight: var(--font-semibold); 474 + color: var(--text-muted); 475 + text-transform: uppercase; 476 + letter-spacing: 0.5px; 477 + } 478 + 479 + /* Modals and Forms (shared) */ 480 + .atmark-modal { 481 + padding: 16px; 482 + } 483 + 484 + .atmark-modal h2 { 485 + margin: 0 0 16px 0; 486 + font-size: var(--h2-size); 487 + font-weight: var(--font-semibold); 488 + color: var(--text-normal); 489 + } 490 + 491 + .atmark-form { 492 + display: flex; 493 + flex-direction: column; 494 + gap: 16px; 495 + } 496 + 497 + .atmark-form-group { 498 + display: flex; 499 + flex-direction: column; 500 + gap: 6px; 501 + } 502 + 503 + .atmark-form-group label { 504 + font-size: var(--font-small); 505 + font-weight: var(--font-medium); 506 + color: var(--text-normal); 507 + } 508 + 509 + .atmark-input, 510 + .atmark-textarea { 511 + padding: 8px 12px; 512 + background: var(--background-primary); 513 + border: 1px solid var(--background-modifier-border); 514 + border-radius: var(--radius-s); 515 + color: var(--text-normal); 516 + font-size: var(--font-ui-medium); 517 + font-family: inherit; 518 + transition: border-color 0.15s ease; 519 + } 520 + 521 + .atmark-input:focus, 522 + .atmark-textarea:focus { 523 + outline: none; 524 + border-color: var(--interactive-accent); 525 + box-shadow: 0 0 0 2px var(--background-modifier-border-focus); 526 + } 527 + 528 + .atmark-input::placeholder, 529 + .atmark-textarea::placeholder { 530 + color: var(--text-faint); 531 + } 532 + 533 + .atmark-textarea { 534 + resize: vertical; 535 + min-height: 60px; 536 + } 537 + 538 + .atmark-modal-actions { 539 + display: flex; 540 + align-items: center; 541 + gap: 8px; 542 + padding-top: 16px; 543 + border-top: 1px solid var(--background-modifier-border); 544 + } 545 + 546 + .atmark-spacer { 547 + flex: 1; 548 + } 549 + 550 + .atmark-btn { 551 + padding: 8px 16px; 552 + border-radius: var(--radius-s); 553 + font-size: var(--font-small); 554 + font-weight: var(--font-medium); 555 + cursor: pointer; 556 + transition: all 0.15s ease; 557 + } 558 + 559 + .atmark-btn:disabled { 560 + opacity: 0.5; 561 + cursor: not-allowed; 562 + } 563 + 564 + .atmark-btn-secondary { 565 + background: var(--background-secondary); 566 + border: 1px solid var(--background-modifier-border); 567 + color: var(--text-normal); 568 + } 569 + 570 + .atmark-btn-secondary:hover:not(:disabled) { 571 + background: var(--background-modifier-hover); 572 + } 573 + 574 + .atmark-btn-primary { 575 + background: var(--interactive-accent); 576 + border: 1px solid var(--interactive-accent); 577 + color: var(--text-on-accent); 578 + } 579 + 580 + .atmark-btn-primary:hover:not(:disabled) { 581 + background: var(--interactive-accent-hover); 582 + } 583 + 584 + .atmark-btn-danger { 585 + background: color-mix(in srgb, var(--color-red) 15%, transparent); 586 + border: none; 587 + color: var(--color-red); 588 + } 589 + 590 + .atmark-btn-danger:hover:not(:disabled) { 591 + background: color-mix(in srgb, var(--color-red) 25%, transparent); 592 + } 593 + 594 + .atmark-warning-text { 595 + color: var(--text-muted); 596 + margin-bottom: 16px; 597 + } 598 + 599 + .atmark-tags-container { 600 + display: flex; 601 + flex-direction: column; 602 + gap: 8px; 603 + margin-bottom: 8px; 604 + } 605 + 606 + .atmark-tag-row { 607 + display: flex; 608 + align-items: center; 609 + gap: 8px; 610 + } 611 + 612 + .atmark-tag-row .atmark-input { 613 + flex: 1; 614 + } 615 + 616 + .atmark-tag-remove-btn { 617 + width: 32px; 618 + height: 32px; 619 + padding: 0; 620 + font-size: 20px; 621 + line-height: 1; 622 + flex-shrink: 0; 623 + } 624 + 625 + 626 + .atmark-collection-list { 627 + display: flex; 628 + flex-direction: column; 629 + gap: 8px; 630 + max-height: 200px; 631 + overflow-y: auto; 632 + } 633 + 634 + .atmark-collection-item { 635 + display: flex; 636 + align-items: center; 637 + gap: 12px; 638 + padding: 10px 12px; 639 + background: var(--background-secondary); 640 + border: 1px solid var(--background-modifier-border); 641 + border-radius: var(--radius-m); 642 + cursor: pointer; 643 + transition: all 0.15s ease; 644 + } 645 + 646 + .atmark-collection-item:hover { 647 + background: var(--background-modifier-hover); 648 + border-color: var(--background-modifier-border-hover); 649 + } 650 + 651 + .atmark-collection-checkbox { 652 + width: 18px; 653 + height: 18px; 654 + margin: 0; 655 + cursor: pointer; 656 + accent-color: var(--interactive-accent); 657 + } 658 + 659 + .atmark-collection-item-info { 660 + display: flex; 661 + flex-direction: column; 662 + gap: 2px; 663 + flex: 1; 664 + } 665 + 666 + .atmark-collection-item-name { 667 + font-weight: var(--font-medium); 668 + color: var(--text-normal); 669 + } 670 + 671 + .atmark-collection-item-desc { 672 + font-size: var(--font-small); 673 + color: var(--text-muted); 674 + } 675 + 676 + .atmark-tag-list { 677 + display: flex; 678 + flex-wrap: wrap; 679 + gap: 6px; 680 + margin-bottom: 8px; 681 + } 682 + 683 + .atmark-tag-item { 684 + display: flex; 685 + align-items: center; 686 + padding: 4px 12px; 687 + background: var(--background-modifier-border); 688 + border-radius: var(--radius-m); 689 + cursor: pointer; 690 + transition: all 0.15s ease; 691 + font-size: var(--font-small); 692 + color: var(--text-muted); 693 + } 694 + 695 + .atmark-tag-item:hover { 696 + background: var(--background-modifier-border-hover); 697 + color: var(--text-normal); 698 + } 699 + 700 + .atmark-tag-item:has(input:checked) { 701 + background: var(--interactive-accent); 702 + color: var(--text-on-accent); 703 + } 704 + 705 + .atmark-tag-item input { 706 + display: none; 707 + } 708 + 709 + /* Semble-specific styles (for NOTE cards and attached notes) */ 710 + .atmark-semble-card-note { 711 + margin: 0; 712 + padding: 8px 12px; 713 + background: var(--background-primary); 714 + border-left: 3px solid var(--color-accent); 715 + border-radius: var(--radius-s); 716 + font-size: var(--font-small); 717 + font-style: italic; 718 + color: var(--text-muted); 719 + white-space: pre-wrap; 720 + line-height: var(--line-height-normal); 721 + } 722 + 723 + .atmark-semble-card-text { 724 + margin: 0; 725 + line-height: 1.5; 726 + color: var(--text-normal); 727 + display: -webkit-box; 728 + -webkit-line-clamp: 5; 729 + -webkit-box-orient: vertical; 730 + overflow: hidden; 731 + } 732 + 733 + .atmark-semble-detail-text { 734 + margin: 0; 735 + white-space: pre-wrap; 736 + line-height: var(--line-height-normal); 737 + color: var(--text-normal); 738 + font-size: 1.1em; 739 + } 740 + 741 + .atmark-semble-detail-notes-section { 742 + margin-top: 20px; 743 + padding-top: 20px; 744 + border-top: 1px solid var(--background-modifier-border); 745 + } 746 + 747 + .atmark-semble-detail-note { 748 + display: flex; 749 + align-items: flex-start; 750 + justify-content: space-between; 751 + gap: 12px; 752 + padding: 12px 16px; 753 + background: var(--background-secondary); 754 + border-left: 3px solid var(--color-accent); 755 + border-radius: var(--radius-s); 756 + margin-bottom: 8px; 757 + } 758 + 759 + .atmark-semble-detail-note-content { 760 + display: flex; 761 + gap: 12px; 762 + flex: 1; 763 + min-width: 0; 764 + } 765 + 766 + .atmark-semble-detail-note-icon { 767 + flex-shrink: 0; 768 + color: var(--color-accent); 769 + } 770 + 771 + .atmark-semble-detail-note-icon svg { 772 + width: 16px; 773 + height: 16px; 774 + } 775 + 776 + .atmark-semble-detail-note-text { 777 + margin: 0; 778 + color: var(--text-normal); 779 + line-height: var(--line-height-normal); 780 + white-space: pre-wrap; 781 + } 782 + 783 + /* Card type badges */ 784 + .atmark-semble-badge-note { 785 + background: var(--color-accent); 786 + color: var(--text-on-accent); 787 + } 788 + 789 + .atmark-semble-badge-url { 790 + background: color-mix(in srgb, var(--color-purple) 80%, var(--background-primary)); 791 + color: var(--text-on-accent); 792 + } 793 + 794 + .atmark-badge-source { 795 + font-size: var(--font-smallest); 796 + opacity: 0.8; 797 + } 798 + 799 + .atmark-semble-badge-semble { 800 + background: color-mix(in srgb, var(--color-green) 80%, var(--background-primary)); 801 + color: var(--text-on-accent); 802 + } 803 + 804 + /* Profile Icon */ 805 + .atmark-profile-icon { 806 + display: flex; 807 + align-items: center; 808 + gap: 6px; 809 + padding: 10px 12px; 810 + margin-bottom: -1px; 811 + margin-left: auto; 812 + position: absolute; 813 + right: 0; 814 + background: transparent; 815 + transition: background 0.15s ease; 816 + } 817 + 818 + .atmark-profile-icon:hover { 819 + background: var(--background-modifier-hover); 820 + } 821 + 822 + .atmark-avatar-btn { 823 + display: flex; 824 + align-items: center; 825 + justify-content: center; 826 + width: 24px; 827 + height: 24px; 828 + padding: 0; 829 + background: var(--background-secondary); 830 + border: 1px solid var(--background-modifier-border); 831 + border-radius: 50%; 832 + cursor: pointer; 833 + overflow: hidden; 834 + transition: opacity 0.15s ease; 835 + } 836 + 837 + .atmark-avatar-btn:hover { 838 + opacity: 0.8; 839 + } 840 + 841 + .atmark-avatar-img { 842 + width: 100%; 843 + height: 100%; 844 + object-fit: cover; 845 + border-radius: 50%; 846 + } 847 + 848 + .atmark-avatar-initials { 849 + font-size: var(--font-smallest); 850 + font-weight: var(--font-semibold); 851 + color: var(--text-muted); 852 + } 853 + 854 + .atmark-avatar-placeholder { 855 + display: flex; 856 + align-items: center; 857 + justify-content: center; 858 + width: 24px; 859 + height: 24px; 860 + background: var(--background-secondary); 861 + border: 1px solid var(--background-modifier-border); 862 + border-radius: 50%; 863 + color: var(--text-faint); 864 + font-size: var(--font-smallest); 865 + } 866 + 867 + .atmark-profile-info { 868 + display: flex; 869 + flex-direction: column; 870 + align-items: flex-end; 871 + gap: 1px; 872 + } 873 + 874 + .atmark-profile-name { 875 + font-size: var(--font-ui-small); 876 + font-weight: var(--font-medium); 877 + color: var(--text-muted); 878 + line-height: 1.2; 879 + } 880 + 881 + .atmark-profile-handle { 882 + font-size: var(--font-smallest); 883 + color: var(--text-faint); 884 + line-height: 1.2; 885 + } 886 + 887 + /* Generic Card Detail Modal (used for all sources) */ 888 + .atmark-detail-modal { 889 + padding: 20px; 890 + max-width: 600px; 891 + } 892 + 893 + .atmark-detail-header { 894 + margin-bottom: 16px; 895 + } 896 + 897 + .atmark-detail-footer { 898 + margin-top: 20px; 899 + padding-top: 16px; 900 + border-top: 1px solid var(--background-modifier-border); 901 + } 902 + 903 + .atmark-detail-date { 904 + font-size: var(--font-small); 905 + color: var(--text-faint); 906 + } 907 + 908 + /* Semble-specific Add Note Form */ 909 + .atmark-semble-detail-add-note { 910 + margin-top: 20px; 911 + padding-top: 20px; 912 + border-top: 1px solid var(--background-modifier-border); 913 + } 914 + 915 + .atmark-semble-add-note-form { 916 + display: flex; 917 + flex-direction: column; 918 + gap: 12px; 919 + } 920 + 921 + .atmark-semble-note-input { 922 + min-height: 80px; 923 + resize: vertical; 924 + } 925 + 926 + .atmark-semble-note-delete-btn { 927 + display: flex; 928 + align-items: center; 929 + justify-content: center; 930 + width: 28px; 931 + height: 28px; 932 + padding: 0; 933 + flex-shrink: 0; 934 + background: transparent; 935 + border: none; 936 + border-radius: var(--radius-s); 937 + cursor: pointer; 938 + color: var(--text-faint); 939 + opacity: 0.6; 940 + transition: all 0.15s ease; 941 + } 942 + 943 + .atmark-semble-note-delete-btn:hover { 944 + background: color-mix(in srgb, var(--color-red) 15%, transparent); 945 + color: var(--color-red); 946 + opacity: 1; 947 + } 948 + 949 + .atmark-semble-note-delete-btn svg { 950 + width: 14px; 951 + height: 14px; 952 + } 953 + 954 + /* Responsive styles */ 955 + @media (max-width: 600px) { 956 + .atmark-view { 957 + padding: 12px; 958 + } 959 + 960 + .atmark-header { 961 + margin-bottom: 16px; 962 + padding-bottom: 12px; 963 + } 964 + 965 + .atmark-profile-icon { 966 + display: none; 967 + } 968 + 969 + .atmark-source-option { 970 + padding: 8px 16px; 971 + font-size: var(--font-ui-small); 972 + } 973 + 974 + .atmark-source-text { 975 + font-size: var(--font-ui-small); 976 + } 977 + 978 + .atmark-source-selector { 979 + justify-content: center; 980 + } 981 + 982 + .atmark-grid { 983 + grid-template-columns: 1fr; 984 + gap: 12px; 985 + } 986 + 987 + .standard-site-list { 988 + grid-template-columns: 1fr; 989 + gap: 12px; 990 + } 991 + 992 + .atmark-filter-section { 993 + margin-bottom: 12px; 994 + } 995 + } 996 + 997 + /* Hide profile in narrow sidebar widths (but not mobile) */ 998 + @media (max-width: 400px) { 999 + .atmark-profile-icon { 1000 + display: none; 1001 + } 1002 + } 1003 + 1004 + .is-mobile .atmark-profile-icon { 1005 + display: none; 1006 + } 1007 + 1008 + .is-mobile .atmark-source-option { 1009 + padding: 8px 16px; 1010 + font-size: var(--font-ui-small); 1011 + } 1012 + 1013 + .is-mobile .atmark-source-text { 1014 + font-size: var(--font-ui-small); 1015 + } 1016 + 1017 + .is-mobile .atmark-source-selector { 1018 + justify-content: center; 1019 + } 1020 + 1021 + /* Standard Site View */ 1022 + .standard-site-view { 1023 + padding: 20px; 1024 + } 1025 + 1026 + .standard-site-header { 1027 + display: flex; 1028 + align-items: center; 1029 + justify-content: space-between; 1030 + margin-bottom: 24px; 1031 + padding-bottom: 16px; 1032 + border-bottom: 1px solid var(--background-modifier-border); 1033 + } 1034 + 1035 + .standard-site-header h2 { 1036 + margin: 0; 1037 + font-size: var(--h2-size); 1038 + font-weight: var(--font-semibold); 1039 + color: var(--text-normal); 1040 + } 1041 + 1042 + .standard-site-create-btn { 1043 + display: flex; 1044 + align-items: center; 1045 + justify-content: center; 1046 + padding: 8px 16px; 1047 + gap: 6px; 1048 + background: var(--interactive-accent); 1049 + border: none; 1050 + border-radius: var(--radius-s); 1051 + cursor: pointer; 1052 + color: var(--text-on-accent); 1053 + font-weight: var(--font-medium); 1054 + transition: all 0.15s ease; 1055 + } 1056 + 1057 + .standard-site-create-btn:hover { 1058 + background: var(--interactive-accent-hover); 1059 + } 1060 + 1061 + .standard-site-create-btn svg { 1062 + width: 16px; 1063 + height: 16px; 1064 + } 1065 + 1066 + .standard-site-list { 1067 + display: grid; 1068 + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); 1069 + gap: 16px; 1070 + padding: 8px 0; 1071 + } 1072 + 1073 + .standard-site-publication { 1074 + background: var(--background-secondary); 1075 + border: 1px solid var(--background-modifier-border); 1076 + border-radius: var(--radius-m); 1077 + padding: 16px; 1078 + display: flex; 1079 + flex-direction: column; 1080 + transition: box-shadow 0.15s ease, border-color 0.15s ease; 1081 + } 1082 + 1083 + .standard-site-publication.clickable { 1084 + cursor: pointer; 1085 + } 1086 + 1087 + .standard-site-publication:hover { 1088 + box-shadow: var(--shadow-s); 1089 + border-color: var(--background-modifier-border-hover); 1090 + } 1091 + 1092 + .standard-site-publication-header { 1093 + display: flex; 1094 + align-items: center; 1095 + justify-content: space-between; 1096 + gap: 12px; 1097 + margin-bottom: 12px; 1098 + } 1099 + 1100 + .standard-site-publication-name { 1101 + margin: 0; 1102 + font-size: var(--h3-size); 1103 + font-weight: var(--font-semibold); 1104 + color: var(--text-normal); 1105 + flex: 1; 1106 + } 1107 + 1108 + .standard-site-publication-actions { 1109 + display: flex; 1110 + gap: 6px; 1111 + } 1112 + 1113 + .standard-site-action-btn { 1114 + display: flex; 1115 + align-items: center; 1116 + justify-content: center; 1117 + width: 32px; 1118 + height: 32px; 1119 + padding: 0; 1120 + background: transparent; 1121 + border: none; 1122 + border-radius: var(--radius-s); 1123 + cursor: pointer; 1124 + color: var(--text-faint); 1125 + transition: all 0.15s ease; 1126 + } 1127 + 1128 + .standard-site-action-btn:hover { 1129 + background: var(--background-modifier-hover); 1130 + color: var(--text-normal); 1131 + } 1132 + 1133 + .standard-site-action-btn svg { 1134 + width: 16px; 1135 + height: 16px; 1136 + } 1137 + 1138 + .standard-site-publication-body { 1139 + display: flex; 1140 + flex-direction: column; 1141 + gap: 12px; 1142 + } 1143 + 1144 + .standard-site-publication-url { 1145 + display: flex; 1146 + align-items: center; 1147 + gap: 6px; 1148 + } 1149 + 1150 + .standard-site-publication-url a { 1151 + color: var(--text-accent); 1152 + text-decoration: none; 1153 + font-size: var(--font-small); 1154 + } 1155 + 1156 + .standard-site-publication-url a:hover { 1157 + text-decoration: underline; 1158 + } 1159 + 1160 + .standard-site-publication-description { 1161 + margin: 0; 1162 + color: var(--text-muted); 1163 + line-height: var(--line-height-normal); 1164 + font-size: var(--font-small); 1165 + } 1166 + 1167 + .standard-site-publication-footer { 1168 + display: flex; 1169 + flex-wrap: wrap; 1170 + gap: 12px; 1171 + align-items: center; 1172 + margin-top: 12px; 1173 + padding-top: 12px; 1174 + border-top: 1px solid var(--background-modifier-border); 1175 + } 1176 + 1177 + .standard-site-publication-uri { 1178 + display: flex; 1179 + align-items: center; 1180 + gap: 4px; 1181 + font-size: var(--font-smallest); 1182 + color: var(--text-faint); 1183 + } 1184 + 1185 + .standard-site-label { 1186 + font-weight: var(--font-medium); 1187 + } 1188 + 1189 + .standard-site-publication-uri code { 1190 + font-size: var(--font-smallest); 1191 + background: var(--background-primary); 1192 + padding: 2px 6px; 1193 + border-radius: var(--radius-s); 1194 + color: var(--text-muted); 1195 + } 1196 + 1197 + .standard-site-badge { 1198 + font-size: var(--font-smallest); 1199 + padding: 3px 8px; 1200 + border-radius: var(--radius-s); 1201 + background: color-mix(in srgb, var(--color-orange) 15%, transparent); 1202 + color: var(--color-orange); 1203 + border: 1px solid color-mix(in srgb, var(--color-orange) 30%, transparent); 1204 + font-weight: var(--font-medium); 1205 + } 1206 + 1207 + .standard-site-error { 1208 + color: var(--text-error); 1209 + } 1210 + 1211 + /* Standard Site Documents */ 1212 + .standard-site-document { 1213 + background: var(--background-secondary); 1214 + border: 1px solid var(--background-modifier-border); 1215 + border-radius: var(--radius-m); 1216 + padding: 16px; 1217 + display: flex; 1218 + flex-direction: column; 1219 + transition: box-shadow 0.15s ease, border-color 0.15s ease; 1220 + } 1221 + 1222 + .standard-site-document:hover { 1223 + box-shadow: var(--shadow-s); 1224 + border-color: var(--background-modifier-border-hover); 1225 + } 1226 + 1227 + .standard-site-document-header { 1228 + display: flex; 1229 + align-items: flex-start; 1230 + justify-content: space-between; 1231 + gap: 8px; 1232 + margin-bottom: 8px; 1233 + } 1234 + 1235 + .standard-site-document-title { 1236 + margin: 0; 1237 + font-size: var(--h3-size); 1238 + font-weight: var(--font-semibold); 1239 + color: var(--text-normal); 1240 + flex: 1; 1241 + line-height: 1.3; 1242 + } 1243 + 1244 + .standard-site-document-external { 1245 + display: flex; 1246 + align-items: center; 1247 + justify-content: center; 1248 + flex-shrink: 0; 1249 + width: 24px; 1250 + height: 24px; 1251 + border-radius: var(--radius-s); 1252 + color: var(--text-faint); 1253 + transition: all 0.15s ease; 1254 + } 1255 + 1256 + .standard-site-document-external:hover { 1257 + background: var(--background-modifier-hover); 1258 + color: var(--text-normal); 1259 + } 1260 + 1261 + .standard-site-document-external svg { 1262 + width: 14px; 1263 + height: 14px; 1264 + } 1265 + 1266 + .standard-site-document-body { 1267 + display: flex; 1268 + flex-direction: column; 1269 + gap: 8px; 1270 + } 1271 + 1272 + .standard-site-document-description { 1273 + margin: 0; 1274 + color: var(--text-muted); 1275 + font-size: var(--font-small); 1276 + line-height: var(--line-height-normal); 1277 + display: -webkit-box; 1278 + -webkit-line-clamp: 3; 1279 + -webkit-box-orient: vertical; 1280 + overflow: hidden; 1281 + } 1282 + 1283 + .standard-site-document-tags { 1284 + display: flex; 1285 + flex-wrap: wrap; 1286 + gap: 6px; 1287 + } 1288 + 1289 + .standard-site-document-tag { 1290 + font-size: var(--font-smallest); 1291 + padding: 2px 8px; 1292 + border-radius: var(--radius-s); 1293 + background: var(--background-modifier-border); 1294 + color: var(--text-muted); 1295 + border: 1px solid var(--background-modifier-border-hover); 1296 + } 1297 + 1298 + .standard-site-document-footer { 1299 + display: flex; 1300 + align-items: center; 1301 + margin-top: 12px; 1302 + padding-top: 8px; 1303 + border-top: 1px solid var(--background-modifier-border); 1304 + } 1305 + 1306 + .standard-site-document-date { 1307 + font-size: var(--font-smallest); 1308 + color: var(--text-faint); 1309 + } 1310 + 1311 + .standard-site-title-group { 1312 + display: flex; 1313 + flex-direction: column; 1314 + flex: 1; 1315 + min-width: 0; 1316 + } 1317 + 1318 + .standard-site-author-handle { 1319 + font-size: var(--font-small); 1320 + color: var(--text-muted); 1321 + } 1322 + 1323 + .standard-site-back { 1324 + font-size: var(--font-small); 1325 + color: var(--text-muted); 1326 + padding: 4px 8px; 1327 + border-radius: var(--radius-s); 1328 + transition: all 0.15s ease; 1329 + } 1330 + 1331 + .standard-site-back:hover { 1332 + background: var(--background-modifier-hover); 1333 + color: var(--text-normal); 1334 + } 1335 + 1336 + .standard-site-publication-external { 1337 + display: flex; 1338 + align-items: center; 1339 + justify-content: center; 1340 + flex-shrink: 0; 1341 + width: 24px; 1342 + height: 24px; 1343 + border-radius: var(--radius-s); 1344 + color: var(--text-faint); 1345 + transition: all 0.15s ease; 1346 + } 1347 + 1348 + .standard-site-publication-external:hover { 1349 + background: var(--background-modifier-hover); 1350 + color: var(--text-normal); 1351 + } 1352 + 1353 + .standard-site-publication-external svg { 1354 + width: 14px; 1355 + height: 14px; 1356 + }
+30
tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "baseUrl": "src", 4 + "inlineSourceMap": true, 5 + "inlineSources": true, 6 + "module": "ESNext", 7 + "target": "ES6", 8 + "allowJs": true, 9 + "noImplicitAny": true, 10 + "noImplicitThis": true, 11 + "noImplicitReturns": true, 12 + "moduleResolution": "bundler", 13 + "importHelpers": true, 14 + "noUncheckedIndexedAccess": true, 15 + "isolatedModules": true, 16 + "strictNullChecks": true, 17 + "strictBindCallApply": true, 18 + "allowSyntheticDefaultImports": true, 19 + "useUnknownInCatchVariables": true, 20 + "lib": [ 21 + "DOM", 22 + "ES5", 23 + "ES6", 24 + "ES7" 25 + ] 26 + }, 27 + "include": [ 28 + "src/**/*.ts" 29 + ] 30 + }
+17
version-bump.mjs
··· 1 + import { readFileSync, writeFileSync } from "fs"; 2 + 3 + const targetVersion = process.env.npm_package_version; 4 + 5 + // read minAppVersion from manifest.json and bump version to target version 6 + const manifest = JSON.parse(readFileSync("manifest.json", "utf8")); 7 + const { minAppVersion } = manifest; 8 + manifest.version = targetVersion; 9 + writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); 10 + 11 + // update versions.json with target version and minAppVersion from manifest.json 12 + // but only if the target version is not already in versions.json 13 + const versions = JSON.parse(readFileSync('versions.json', 'utf8')); 14 + if (!Object.values(versions).includes(minAppVersion)) { 15 + versions[targetVersion] = minAppVersion; 16 + writeFileSync('versions.json', JSON.stringify(versions, null, '\t')); 17 + }
+3
versions.json
··· 1 + { 2 + "1.0.0": "0.15.0" 3 + }