mirror of
https://github.com/sboulema/mtg-deck.git
synced 2026-07-22 06:50:39 +00:00
Merge pull request #73 from sboulema/feature/collection_view
Feature/collection view
This commit is contained in:
commit
08953c8339
13 changed files with 522 additions and 805 deletions
2
.github/workflows/workflow.yml
vendored
2
.github/workflows/workflow.yml
vendored
|
|
@ -10,7 +10,7 @@ permissions:
|
|||
contents: read
|
||||
|
||||
env:
|
||||
version: '1.13.${{ github.run_number }}'
|
||||
version: '1.14.${{ github.run_number }}'
|
||||
nodeVersion: '22'
|
||||
|
||||
jobs:
|
||||
|
|
|
|||
31
README.md
31
README.md
|
|
@ -137,8 +137,8 @@ The following rules are validated in the section footer:
|
|||
- **Card legality** — flags banned/not legal/restricted cards
|
||||
- **Deck size** — flags when the deck is too small or too large for the format
|
||||
- **Sideboard size** — flags when the sideboard exceeds the maximum for the format
|
||||
- **Number of copies** - flags when a card is included more than the allowed times (4/1), handles cards like Hare Apparent!
|
||||
- **Commander rules** - flags when a card is not a legal commander, or cards in the deck that or outside of the color identity
|
||||
- **Number of copies** — flags when a card is included more than the allowed times (4/1), handles cards like Hare Apparent!
|
||||
- **Commander rules** — flags when a card is not a legal commander, or cards in the deck that are outside of the color identity
|
||||
|
||||
# ⚙️ Settings
|
||||
|
||||
|
|
@ -183,13 +183,11 @@ Supported show values:
|
|||
|
||||
# 📦 Collection Tracking
|
||||
|
||||
This plugin expects your collection to be stored as CSV files with the extension `.mtg.collection.csv` by default.
|
||||
This plugin tracks how many copies of each card you own by reading CSV files from a configurable folder. Cards where you don't own enough copies are highlighted in red, and a count shows owned vs. needed (e.g. `2 / 4`). A **Buylist** section is automatically shown at the bottom when you're missing cards.
|
||||
|
||||
These files are expected to be properly formed CSVs with a Name and a Count column. Column names can be configured through the settings.
|
||||
Collection CSV files must have at least two columns: one for the card name and one for the count. Column names are configurable in settings and default to `Name` and `Count`. Multiple CSV files are supported — the plugin merges all files in the configured folder and its subfolders.
|
||||
|
||||
Cards where you don't own enough copies are highlighted in red, and a count shows owned vs. needed (e.g. `2 / 4`). A **Buylist** section is automatically shown at the bottom when you're missing cards.
|
||||
|
||||
## Example CSV Files
|
||||
## Example CSV File
|
||||
|
||||
```
|
||||
Name,Count
|
||||
|
|
@ -199,17 +197,30 @@ Delver of Secrets // Insectile Aberration,8
|
|||
Ledger Shredder,5
|
||||
```
|
||||
|
||||
Note that your collection will consist of the merged result of all of your CSV files.
|
||||
## Viewing Your Collection
|
||||
|
||||
You can view your entire collection in two ways:
|
||||
|
||||
**Command** — open the command palette and run `MTG Deck: View collection`. This opens a modal with your full collection, showing counts per collection file, mana costs, and rarity indicators. A search box lets you filter by card name.
|
||||
|
||||
**Code block** — embed your collection in any note using the `mtg-collection` code block:
|
||||
|
||||
````markdown
|
||||
```mtg-collection
|
||||
```
|
||||
````
|
||||
|
||||
This renders an inline table with all your cards, per-file counts, a grand total column, and a search filter. Card data is loaded from Scryfall and a progress indicator shows loading status.
|
||||
|
||||
# 🔍 Obsidian Plugin Scorecard
|
||||
|
||||
## Network Requests
|
||||
|
||||
This plugin makes network requests to the [Scryfall API](https://scryfall.com/docs/api) to fetch card data, images, and prices. Requests are only made when rendering a decklist code block and are batched to minimize the number of calls. No user data is sent to Scryfall — only card names and set codes are used to identify cards.
|
||||
This plugin makes network requests to the [Scryfall API](https://scryfall.com/docs/api) to fetch card data, images, and prices. Requests are only made when rendering a decklist or collection code block and are batched to minimize the number of calls. Fetched card data is cached for the duration of the session. No user data is sent to Scryfall — only card names and set codes are used to identify cards.
|
||||
|
||||
## Vault Access
|
||||
|
||||
This plugin enumerates files in your vault to find collection CSV files in the configured collection folder. Only files with the `.csv` extension in the configured folder are read. No other vault files are accessed.
|
||||
This plugin enumerates files in your vault to find collection CSV files in the configured collection folder and its subfolders. Only files with the `.csv` extension are read. No other vault files are accessed.
|
||||
|
||||
## CSS
|
||||
|
||||
|
|
|
|||
104
main.ts
104
main.ts
|
|
@ -1,14 +1,20 @@
|
|||
import { App, FuzzySuggestModal, Plugin, PluginSettingTab, Setting, TFolder, Vault } from "obsidian";
|
||||
import { App, FuzzySuggestModal, Plugin, PluginSettingTab, Setting, TFolder } from "obsidian";
|
||||
import {
|
||||
DEFAULT_COLLECTION_COUNT_COLUMN,
|
||||
DEFAULT_COLLECTION_FOLDER_PATH,
|
||||
DEFAULT_COLLECTION_NAME_COLUMN,
|
||||
syncCounts,
|
||||
CardCollection,
|
||||
CardCounts,
|
||||
DEFAULT_COLLECTION_COUNT_COLUMN,
|
||||
DEFAULT_COLLECTION_FOLDER_PATH,
|
||||
DEFAULT_COLLECTION_NAME_COLUMN,
|
||||
hashCollectionContents,
|
||||
mergeCollections,
|
||||
syncCollections,
|
||||
} from "src/collection";
|
||||
import { renderDecklist } from "src/renderer";
|
||||
import { ObsidianPluginMtgSettings } from "src/settings";
|
||||
import { CardCounts } from "src/collection";
|
||||
import { parseCodeBlockOptions, applyShowOverrides } from "src/code-block-options";
|
||||
import { CollectionModal } from "src/collection-modal";
|
||||
import { renderCollection } from "src/collection-renderer";
|
||||
import { loadCache, getCache, clearCache } from "src/cache";
|
||||
|
||||
const DEFAULT_SETTINGS: ObsidianPluginMtgSettings = {
|
||||
collection: {
|
||||
|
|
@ -32,6 +38,7 @@ export default class ObsidianPluginMtg extends Plugin {
|
|||
|
||||
// This keeps a record of the collection in memory
|
||||
cardCounts: CardCounts;
|
||||
collections: CardCollection[] = [];
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
|
@ -43,17 +50,33 @@ export default class ObsidianPluginMtg extends Plugin {
|
|||
|
||||
vault.on("modify", async (file) => {
|
||||
if (file.name.endsWith(".csv")) {
|
||||
const settings = this.settings;
|
||||
const collectionFolderPath =
|
||||
settings.collection?.folderPath || "";
|
||||
if (file.parent?.path === collectionFolderPath) {
|
||||
this.cardCounts = await syncCounts(vault, settings);
|
||||
const collectionFolderPath = this.settings.collection?.folderPath || "";
|
||||
if (file.parent?.path.startsWith(collectionFolderPath)) {
|
||||
this.collections = await syncCollections(vault, this.settings);
|
||||
this.cardCounts = mergeCollections(this.collections);
|
||||
clearCache();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
this.cardCounts = await syncCounts(vault, this.settings);
|
||||
this.collections = await syncCollections(vault, this.settings);
|
||||
this.cardCounts = mergeCollections(this.collections);
|
||||
|
||||
const hash = hashCollectionContents(this.collections);
|
||||
|
||||
const savedData = await this.loadData();
|
||||
if (savedData?.collectionHash === hash && savedData?.cardDataCache) {
|
||||
loadCache(savedData.cardDataCache);
|
||||
}
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "view-collection",
|
||||
name: "View collection",
|
||||
callback: () => {
|
||||
new CollectionModal(this.app, this.collections).open();
|
||||
},
|
||||
});
|
||||
|
||||
this.registerMarkdownPostProcessor((element) => {
|
||||
|
|
@ -79,43 +102,44 @@ export default class ObsidianPluginMtg extends Plugin {
|
|||
}
|
||||
})();
|
||||
});
|
||||
|
||||
this.registerMarkdownCodeBlockProcessor(
|
||||
"mtg-collection",
|
||||
(_source, el) => {
|
||||
void renderCollection(el, this.collections);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onunload() {}
|
||||
onunload() {
|
||||
void (async () => {
|
||||
const hash = hashCollectionContents(this.collections);
|
||||
const data = await this.loadData() ?? {};
|
||||
|
||||
await this.saveData({
|
||||
...data,
|
||||
settings: this.settings,
|
||||
collectionHash: hash,
|
||||
cardDataCache: getCache(),
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
const data = await this.loadData();
|
||||
this.settings = Object.assign(
|
||||
{},
|
||||
DEFAULT_SETTINGS,
|
||||
await this.loadData() as Partial<ObsidianPluginMtgSettings>
|
||||
data?.settings as Partial<ObsidianPluginMtgSettings>
|
||||
);
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
|
||||
async renderDecklist(vault: Vault, source: string, el: HTMLElement, format: string | null = null) {
|
||||
// Sync card counts once if they haven't been already
|
||||
if (!this.cardCounts) {
|
||||
this.cardCounts = await syncCounts(vault, this.settings);
|
||||
}
|
||||
|
||||
try {
|
||||
await renderDecklist(
|
||||
el,
|
||||
source,
|
||||
this.cardCounts,
|
||||
this.settings,
|
||||
format
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
el.createDiv({
|
||||
text: String(err),
|
||||
cls: "obsidian-plugin-mtg-error",
|
||||
});
|
||||
}
|
||||
const data = await this.loadData() ?? {};
|
||||
await this.saveData({
|
||||
...data,
|
||||
settings: this.settings,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -316,6 +340,10 @@ class FolderSuggestModal extends FuzzySuggestModal<TFolder> {
|
|||
this.app.vault.getFiles().forEach((file) => {
|
||||
if (file.extension === "csv" && file.parent) {
|
||||
folders.set(file.parent.path, file.parent);
|
||||
|
||||
if (file.parent.parent) {
|
||||
folders.set(file.parent.parent.path, file.parent.parent);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "mtg-deck",
|
||||
"name": "MTG Deck",
|
||||
"version": "1.13.106",
|
||||
"version": "1.14.128",
|
||||
"minAppVersion": "1.5.7",
|
||||
"description": "Display your MTG decks and card lists in your notes.",
|
||||
"author": "Samir Boulema",
|
||||
|
|
|
|||
758
package-lock.json
generated
758
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "mtg-deck",
|
||||
"version": "1.13.106",
|
||||
"version": "1.14.123",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mtg-deck",
|
||||
"version": "1.13.106",
|
||||
"version": "1.14.123",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.3.5",
|
||||
|
|
@ -22,11 +22,12 @@
|
|||
"jest": "^30.4.2",
|
||||
"jest-environment-jsdom": "^30.4.1",
|
||||
"jsdom": "^29.1.1",
|
||||
"obsidian": "*",
|
||||
"obsidian": "latest",
|
||||
"prettier": "3.9.4",
|
||||
"ts-jest": "^29.0.0",
|
||||
"tslib": "2.8.1",
|
||||
"typescript": "^7.0.2"
|
||||
"typescript": "6.0.3",
|
||||
"typescript-eslint": "^8.63.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
|
|
@ -2513,45 +2514,6 @@
|
|||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.63.0.tgz",
|
||||
"integrity": "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.63.0",
|
||||
"@typescript-eslint/types": "^8.63.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz",
|
||||
"integrity": "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.63.0.tgz",
|
||||
|
|
@ -2577,81 +2539,6 @@
|
|||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.63.0.tgz",
|
||||
"integrity": "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.63.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.63.0",
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/visitor-keys": "8.63.0",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.63.0.tgz",
|
||||
"integrity": "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.63.0",
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/typescript-estree": "8.63.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/balanced-match": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
||||
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/brace-expansion": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
|
||||
"integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
|
||||
"version": "7.0.5",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
|
||||
|
|
@ -2662,22 +2549,6 @@
|
|||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/minimatch": {
|
||||
"version": "10.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
|
||||
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^5.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.63.0.tgz",
|
||||
|
|
@ -2703,7 +2574,7 @@
|
|||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/project-service": {
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.63.0.tgz",
|
||||
"integrity": "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==",
|
||||
|
|
@ -2725,7 +2596,25 @@
|
|||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.63.0.tgz",
|
||||
"integrity": "sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/visitor-keys": "8.63.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz",
|
||||
"integrity": "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==",
|
||||
|
|
@ -2742,7 +2631,21 @@
|
|||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": {
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.63.0.tgz",
|
||||
"integrity": "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.63.0.tgz",
|
||||
"integrity": "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==",
|
||||
|
|
@ -2770,7 +2673,7 @@
|
|||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/balanced-match": {
|
||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
||||
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
|
||||
|
|
@ -2780,7 +2683,7 @@
|
|||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/brace-expansion": {
|
||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
|
||||
"integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
|
||||
|
|
@ -2793,7 +2696,7 @@
|
|||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/minimatch": {
|
||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
|
||||
"version": "10.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
|
||||
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
|
||||
|
|
@ -2809,15 +2712,17 @@
|
|||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.63.0.tgz",
|
||||
"integrity": "sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A==",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.63.0.tgz",
|
||||
"integrity": "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.63.0",
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/visitor-keys": "8.63.0"
|
||||
"@typescript-eslint/typescript-estree": "8.63.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
|
|
@ -2825,20 +2730,10 @@
|
|||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.63.0.tgz",
|
||||
"integrity": "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
|
|
@ -2872,346 +2767,6 @@
|
|||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-aix-ppc64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz",
|
||||
"integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-darwin-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-darwin-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-freebsd-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-freebsd-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-arm": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz",
|
||||
"integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-loong64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz",
|
||||
"integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-mips64el": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz",
|
||||
"integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-ppc64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz",
|
||||
"integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-riscv64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz",
|
||||
"integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-s390x": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz",
|
||||
"integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-netbsd-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-netbsd-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-openbsd-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-openbsd-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-sunos-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-win32-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-win32-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ungap/structured-clone": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.2.tgz",
|
||||
|
|
@ -5525,136 +5080,6 @@
|
|||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-obsidianmd/node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.63.0.tgz",
|
||||
"integrity": "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.63.0",
|
||||
"@typescript-eslint/types": "^8.63.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-obsidianmd/node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz",
|
||||
"integrity": "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-obsidianmd/node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.63.0.tgz",
|
||||
"integrity": "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.63.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.63.0",
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/visitor-keys": "8.63.0",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-obsidianmd/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
|
||||
"version": "10.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
|
||||
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^5.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-obsidianmd/node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.63.0.tgz",
|
||||
"integrity": "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.63.0",
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/typescript-estree": "8.63.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-obsidianmd/node_modules/balanced-match": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
||||
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-obsidianmd/node_modules/brace-expansion": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
|
||||
"integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-obsidianmd/node_modules/doctrine": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
|
||||
|
|
@ -5938,30 +5363,6 @@
|
|||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-obsidianmd/node_modules/typescript-eslint": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.63.0.tgz",
|
||||
"integrity": "sha512-xgwXyzG4sK9ALkBxbyGkTMMOS+imnW65iPhxCQMK83KhxyoDNW7l+IDqEf9vMdoUidHpOoS967RCq4eMiTexwQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.63.0",
|
||||
"@typescript-eslint/parser": "8.63.0",
|
||||
"@typescript-eslint/typescript-estree": "8.63.0",
|
||||
"@typescript-eslint/utils": "8.63.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-obsidianmd/node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
|
|
@ -10629,38 +10030,41 @@
|
|||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz",
|
||||
"integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==",
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
|
||||
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc"
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.63.0.tgz",
|
||||
"integrity": "sha512-xgwXyzG4sK9ALkBxbyGkTMMOS+imnW65iPhxCQMK83KhxyoDNW7l+IDqEf9vMdoUidHpOoS967RCq4eMiTexwQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.63.0",
|
||||
"@typescript-eslint/parser": "8.63.0",
|
||||
"@typescript-eslint/typescript-estree": "8.63.0",
|
||||
"@typescript-eslint/utils": "8.63.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@typescript/typescript-aix-ppc64": "7.0.2",
|
||||
"@typescript/typescript-darwin-arm64": "7.0.2",
|
||||
"@typescript/typescript-darwin-x64": "7.0.2",
|
||||
"@typescript/typescript-freebsd-arm64": "7.0.2",
|
||||
"@typescript/typescript-freebsd-x64": "7.0.2",
|
||||
"@typescript/typescript-linux-arm": "7.0.2",
|
||||
"@typescript/typescript-linux-arm64": "7.0.2",
|
||||
"@typescript/typescript-linux-loong64": "7.0.2",
|
||||
"@typescript/typescript-linux-mips64el": "7.0.2",
|
||||
"@typescript/typescript-linux-ppc64": "7.0.2",
|
||||
"@typescript/typescript-linux-riscv64": "7.0.2",
|
||||
"@typescript/typescript-linux-s390x": "7.0.2",
|
||||
"@typescript/typescript-linux-x64": "7.0.2",
|
||||
"@typescript/typescript-netbsd-arm64": "7.0.2",
|
||||
"@typescript/typescript-netbsd-x64": "7.0.2",
|
||||
"@typescript/typescript-openbsd-arm64": "7.0.2",
|
||||
"@typescript/typescript-openbsd-x64": "7.0.2",
|
||||
"@typescript/typescript-sunos-x64": "7.0.2",
|
||||
"@typescript/typescript-win32-arm64": "7.0.2",
|
||||
"@typescript/typescript-win32-x64": "7.0.2"
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uglify-js": {
|
||||
|
|
|
|||
11
package.json
11
package.json
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "mtg-deck",
|
||||
"version": "1.13.106",
|
||||
"version": "1.14.128",
|
||||
"description": "Display your MTG decks and card lists in your notes.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
@ -20,24 +20,25 @@
|
|||
"author": "Samir Boulema",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.3.5",
|
||||
"@eslint/eslintrc": "^3.3.6",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@jest/globals": "^30.4.1",
|
||||
"@types/node": "^26.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.63.0",
|
||||
"@typescript-eslint/parser": "8.63.0",
|
||||
"esbuild": "0.28.1",
|
||||
"eslint": "^10.6.0",
|
||||
"eslint": "^10.7.0",
|
||||
"eslint-plugin-obsidianmd": "^0.4.1",
|
||||
"globals": "^17.7.0",
|
||||
"jest": "^30.4.2",
|
||||
"jest-environment-jsdom": "^30.4.1",
|
||||
"jsdom": "^29.1.1",
|
||||
"obsidian": "latest",
|
||||
"prettier": "3.9.4",
|
||||
"prettier": "3.9.5",
|
||||
"ts-jest": "^29.0.0",
|
||||
"tslib": "2.8.1",
|
||||
"typescript": "^7.0.2"
|
||||
"typescript": "6.0.3",
|
||||
"typescript-eslint": "^8.63.0"
|
||||
},
|
||||
"allowScripts": {
|
||||
"esbuild@0.28.1": true
|
||||
|
|
|
|||
34
src/cache.ts
Normal file
34
src/cache.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { CardData } from "./scryfall";
|
||||
|
||||
interface CachedCardData {
|
||||
data: CardData;
|
||||
}
|
||||
|
||||
let cardDataCache: Record<string, CachedCardData> = {};
|
||||
|
||||
export const loadCache = (data: Record<string, CachedCardData>): void => {
|
||||
cardDataCache = data;
|
||||
};
|
||||
|
||||
export const getCache = (): Record<string, CachedCardData> => {
|
||||
return cardDataCache;
|
||||
};
|
||||
|
||||
export const clearCache = (): void => {
|
||||
cardDataCache = {};
|
||||
};
|
||||
|
||||
export const getCachedCardData = (): Record<string, CardData> => {
|
||||
return Object.fromEntries(
|
||||
Object.entries(cardDataCache)
|
||||
.map(([key, entry]) => [key, entry.data])
|
||||
);
|
||||
};
|
||||
|
||||
export const setCachedCardData = (key: string, data: CardData): void => {
|
||||
cardDataCache[key] = { data };
|
||||
};
|
||||
|
||||
export const hasCachedCardData = (key: string): boolean => {
|
||||
return !!cardDataCache[key];
|
||||
};
|
||||
21
src/collection-modal.ts
Normal file
21
src/collection-modal.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { App, Modal } from "obsidian";
|
||||
import { CardCollection, CardCounts } from "./collection";
|
||||
import { renderCollection } from "./collection-renderer";
|
||||
|
||||
export class CollectionModal extends Modal {
|
||||
collections: CardCollection[];
|
||||
|
||||
constructor(app: App, collections: CardCollection[]) {
|
||||
super(app);
|
||||
this.collections = collections;
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
this.modalEl.addClass("collection-modal");
|
||||
void renderCollection(this.contentEl, this.collections);
|
||||
}
|
||||
|
||||
onClose() {
|
||||
this.contentEl.empty();
|
||||
}
|
||||
}
|
||||
142
src/collection-renderer.ts
Normal file
142
src/collection-renderer.ts
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
import { CardCollection, nameToId } from "./collection";
|
||||
import { CardData, fetchCardDataFromScryfallCached } from "./scryfall";
|
||||
|
||||
const renderRows = (
|
||||
tbody: HTMLElement,
|
||||
entries: string[],
|
||||
collections: CardCollection[],
|
||||
cardDataByCardId: Record<string, CardData>,
|
||||
filter: string
|
||||
) => {
|
||||
tbody.empty();
|
||||
|
||||
entries
|
||||
.filter(name => name.includes(filter.toLowerCase()))
|
||||
.forEach(name => {
|
||||
const cardInfo = cardDataByCardId[nameToId(name)];
|
||||
const row = tbody.createEl("tr");
|
||||
|
||||
// Name cell with rarity dot and hyperlink
|
||||
const nameCell = row.createEl("td", { cls: "max" });
|
||||
nameCell.createSpan({ cls: `card-rarity ${cardInfo?.rarity ?? ""}` });
|
||||
const nameEl = nameCell.createSpan({ cls: "card-name" });
|
||||
|
||||
if (cardInfo?.scryfall_uri) {
|
||||
const link = nameEl.createEl("a");
|
||||
link.href = cardInfo.scryfall_uri;
|
||||
link.textContent = cardInfo.printed_name ?? cardInfo.name ?? name;
|
||||
} else {
|
||||
nameEl.textContent = cardInfo?.printed_name ?? cardInfo?.name ?? name;
|
||||
}
|
||||
|
||||
// Mana cost cell
|
||||
const costEl = row.createEl("td").createSpan({ cls: "card-cost" });
|
||||
const manaCost = cardInfo?.mana_cost ?? cardInfo?.card_faces?.[0]?.mana_cost;
|
||||
|
||||
if (manaCost) {
|
||||
manaCost
|
||||
.split("//")
|
||||
.map(part => part.trim())
|
||||
.forEach((part, index) => {
|
||||
if (index > 0) {
|
||||
costEl.createSpan({ cls: "card-cost-divider" });
|
||||
}
|
||||
part
|
||||
.replace(/\//g, "")
|
||||
.split("{")
|
||||
.slice(1)
|
||||
.forEach(symbol => {
|
||||
costEl.createEl("img", {
|
||||
attr: {
|
||||
src: `https://svgs.scryfall.io/card-symbols/${symbol.slice(0, -1)}.svg`,
|
||||
width: 18,
|
||||
height: 18,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Per-collection count cells
|
||||
let total = 0;
|
||||
collections.forEach(c => {
|
||||
const count = c.counts[name] ?? 0;
|
||||
total += count;
|
||||
row.createEl("td", { text: count > 0 ? `${count}` : "-" });
|
||||
});
|
||||
|
||||
// Total cell
|
||||
row.createEl("td", { text: `${total}` });
|
||||
});
|
||||
};
|
||||
|
||||
export const renderCollection = async (
|
||||
root: HTMLElement,
|
||||
collections: CardCollection[]
|
||||
): Promise<void> => {
|
||||
const containerEl = root.createDiv({ cls: "decklist" });
|
||||
|
||||
const searchEl = containerEl.createEl("input", {
|
||||
attr: { type: "text", placeholder: "Search cards..." },
|
||||
cls: "collection__search",
|
||||
});
|
||||
|
||||
const table = containerEl.createEl("table");
|
||||
const thead = table.createEl("thead");
|
||||
const headerRow = thead.createEl("tr");
|
||||
headerRow.createEl("th", { text: "Name", cls: "max" });
|
||||
headerRow.createEl("th", { text: "Cost" });
|
||||
collections.forEach(c => {
|
||||
headerRow.createEl("th", { text: c.fileName });
|
||||
});
|
||||
headerRow.createEl("th", { text: "Total" });
|
||||
|
||||
const tbody = table.createEl("tbody");
|
||||
|
||||
// Build sorted list of all unique card names across all collections
|
||||
const allCardNames = Array.from(
|
||||
new Set(collections.flatMap(c => Object.keys(c.counts)))
|
||||
).sort((a, b) => a.localeCompare(b));
|
||||
|
||||
// Render skeleton immediately
|
||||
renderRows(tbody, allCardNames, collections, {}, "");
|
||||
|
||||
const loadingEl = containerEl.createDiv({ cls: "collection__loading" });
|
||||
const progressEl = loadingEl.createSpan({ text: `0 / ${allCardNames.length}` });
|
||||
loadingEl.createDiv({ cls: "collection__spinner" });
|
||||
|
||||
// Fetch card data and re-render
|
||||
const identifiers = allCardNames.map(name => ({ name }));
|
||||
const cardDataByCardId = await fetchCardDataFromScryfallCached(
|
||||
identifiers,
|
||||
(fetched, total) => {
|
||||
progressEl.textContent = `${fetched} / ${total}`;
|
||||
}
|
||||
);
|
||||
|
||||
loadingEl.remove();
|
||||
|
||||
renderRows(tbody, allCardNames, collections, cardDataByCardId, "");
|
||||
|
||||
// Footer with per-collection totals and grand total
|
||||
const tfoot = table.createEl("tfoot", { cls: "decklist__section-totals" });
|
||||
const footRow = tfoot.createEl("tr");
|
||||
footRow.createEl("td", { text: "Total", cls: "max" });
|
||||
footRow.createEl("td");
|
||||
|
||||
collections.forEach(c => {
|
||||
const total = Object.values(c.counts).reduce((acc, v) => acc + v, 0);
|
||||
footRow.createEl("td", { text: `${total}` });
|
||||
});
|
||||
|
||||
const grandTotal = collections.reduce(
|
||||
(acc, c) => acc + Object.values(c.counts).reduce((a, v) => a + v, 0),
|
||||
0
|
||||
);
|
||||
footRow.createEl("td", { text: `${grandTotal}` });
|
||||
|
||||
// Search filter
|
||||
searchEl.addEventListener("input", () => {
|
||||
renderRows(tbody, allCardNames, collections, cardDataByCardId, searchEl.value);
|
||||
});
|
||||
};
|
||||
|
|
@ -1,9 +1,16 @@
|
|||
import { TFile, Vault } from "obsidian";
|
||||
import { Vault } from "obsidian";
|
||||
import { parseCsvFile } from "./csv";
|
||||
import { ObsidianPluginMtgSettings } from "./settings";
|
||||
|
||||
export type CardCounts = Record<string, number>;
|
||||
|
||||
export interface CardCollection {
|
||||
fileName: string;
|
||||
counts: CardCounts;
|
||||
}
|
||||
|
||||
export type CollectionData = CardCollection[];
|
||||
|
||||
export const DEFAULT_COLLECTION_FOLDER_PATH = "Collections";
|
||||
export const DEFAULT_COLLECTION_NAME_COLUMN = "Name";
|
||||
export const DEFAULT_COLLECTION_COUNT_COLUMN = "Count";
|
||||
|
|
@ -55,42 +62,51 @@ export const createCardCountsMapping = (
|
|||
export const processCollectionFiles = async (
|
||||
vault: Vault,
|
||||
settings: ObsidianPluginMtgSettings
|
||||
): Promise<string[]> => {
|
||||
const folder = vault.getFolderByPath(
|
||||
settings.collection.folderPath
|
||||
);
|
||||
): Promise<CardCollection[]> => {
|
||||
const folderPath = settings.collection.folderPath;
|
||||
|
||||
if (!folder) {
|
||||
if (!folderPath) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return (
|
||||
(
|
||||
await Promise.all(
|
||||
folder.children
|
||||
.filter(
|
||||
(file): file is TFile =>
|
||||
file instanceof TFile &&
|
||||
file.extension === "csv"
|
||||
)
|
||||
.map((file) => vault.cachedRead(file).catch(() => ""))
|
||||
)
|
||||
)
|
||||
// remove unreadable files
|
||||
.filter((s) => s.length)
|
||||
const files = vault.getFiles().filter(
|
||||
file => file.extension === "csv" &&
|
||||
file.parent?.path.startsWith(folderPath)
|
||||
);
|
||||
|
||||
if (files.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return (await Promise.all(
|
||||
files.map(async file => {
|
||||
const content = await vault.cachedRead(file).catch(() => "");
|
||||
if (!content.length) return null;
|
||||
return {
|
||||
fileName: file.basename,
|
||||
counts: createCardCountsMapping([content], settings),
|
||||
};
|
||||
})
|
||||
)).filter((c): c is CardCollection => c !== null);
|
||||
};
|
||||
|
||||
export const syncCounts = async (
|
||||
export const syncCollections = async (
|
||||
vault: Vault,
|
||||
settings: ObsidianPluginMtgSettings
|
||||
): Promise<CardCounts> => {
|
||||
// Sync collection
|
||||
const collectionContents: string[] = await processCollectionFiles(
|
||||
vault,
|
||||
settings
|
||||
);
|
||||
): Promise<CardCollection[]> => {
|
||||
return processCollectionFiles(vault, settings);
|
||||
};
|
||||
|
||||
// Create consolidated collection dictionary
|
||||
return createCardCountsMapping(collectionContents, settings);
|
||||
export const hashCollectionContents = (contents: CardCollection[]): string => {
|
||||
const combined = contents.map(c => Object.entries(c.counts).join("")).join("");
|
||||
return `${contents.length}-${combined.length}`;
|
||||
};
|
||||
|
||||
export const mergeCollections = (collections: CardCollection[]): CardCounts => {
|
||||
return collections.reduce((acc, collection) => {
|
||||
Object.entries(collection.counts).forEach(([name, count]) => {
|
||||
acc[name] = (acc[name] ?? 0) + count;
|
||||
});
|
||||
return acc;
|
||||
}, {} as CardCounts);
|
||||
};
|
||||
112
src/scryfall.ts
112
src/scryfall.ts
|
|
@ -1,14 +1,15 @@
|
|||
import { nameToId } from "./collection";
|
||||
import { promiseWrappedRequest } from "./http";
|
||||
import { getCachedCardData, setCachedCardData, hasCachedCardData } from "./cache";
|
||||
|
||||
export type CardIdentifier =
|
||||
| {
|
||||
name: string;
|
||||
}
|
||||
| {
|
||||
set: string;
|
||||
collector_number: string;
|
||||
};
|
||||
| {
|
||||
name: string;
|
||||
}
|
||||
| {
|
||||
set: string;
|
||||
collector_number: string;
|
||||
};
|
||||
|
||||
export interface RequestOptions {
|
||||
url: string;
|
||||
|
|
@ -21,11 +22,10 @@ export interface RequestOptions {
|
|||
|
||||
export type Request = <T>(options: RequestOptions) => Promise<T>;
|
||||
|
||||
// This is the maximum number of cards that can be requested at the same time
|
||||
export const MAX_SCRYFALL_BATCH_SIZE = 75;
|
||||
|
||||
export interface CardFace {
|
||||
object?: string; // card_face
|
||||
object?: string;
|
||||
name?: string;
|
||||
mana_cost?: string;
|
||||
type_line?: string;
|
||||
|
|
@ -50,7 +50,7 @@ export interface CardFace {
|
|||
}
|
||||
|
||||
export interface CardData {
|
||||
object?: string; // card
|
||||
object?: string;
|
||||
id?: string;
|
||||
oracle_id?: string;
|
||||
multiverse_ids?: number[];
|
||||
|
|
@ -194,41 +194,75 @@ export const getMultipleCardData = async (
|
|||
* @returns A record of card data indexed by normalized card name
|
||||
*/
|
||||
export const fetchCardDataFromScryfall = async (
|
||||
distinctCards: CardIdentifier[]
|
||||
distinctCards: CardIdentifier[],
|
||||
onProgress?: (fetched: number, total: number) => void
|
||||
): Promise<Record<string, CardData>> => {
|
||||
const batches: CardIdentifier[][] = [];
|
||||
let currentBatch: CardIdentifier[] = [];
|
||||
batches.push(currentBatch);
|
||||
const batches: CardIdentifier[][] = [];
|
||||
let currentBatch: CardIdentifier[] = [];
|
||||
batches.push(currentBatch);
|
||||
|
||||
distinctCards.forEach((identifier: CardIdentifier) => {
|
||||
if (currentBatch.length === MAX_SCRYFALL_BATCH_SIZE) {
|
||||
batches.push(currentBatch);
|
||||
currentBatch = [];
|
||||
}
|
||||
currentBatch.push(identifier);
|
||||
});
|
||||
distinctCards.forEach((identifier: CardIdentifier) => {
|
||||
if (currentBatch.length === MAX_SCRYFALL_BATCH_SIZE) {
|
||||
batches.push(currentBatch);
|
||||
currentBatch = [];
|
||||
}
|
||||
currentBatch.push(identifier);
|
||||
});
|
||||
|
||||
batches.push(currentBatch);
|
||||
batches.push(currentBatch);
|
||||
|
||||
const cardDataInBatches: ScryfallResponse[] = await Promise.all(
|
||||
batches.map((batch) => getMultipleCardData(batch))
|
||||
);
|
||||
const cardDataByCardId: Record<string, CardData> = {};
|
||||
let fetched = 0;
|
||||
const total = distinctCards.length;
|
||||
|
||||
const cardDataByCardId: Record<string, CardData> = {};
|
||||
for (const batch of batches) {
|
||||
const result: ScryfallResponse = await getMultipleCardData(batch);
|
||||
|
||||
cardDataInBatches.forEach((batch) => {
|
||||
batch.data.forEach((card: CardData) => {
|
||||
if (card.name) {
|
||||
const cardId = nameToId(card.name);
|
||||
cardDataByCardId[cardId] = card;
|
||||
}
|
||||
|
||||
if (card.printed_name) {
|
||||
const cardId = nameToId(card.printed_name);
|
||||
cardDataByCardId[cardId] = card;
|
||||
result.data.forEach((card: CardData) => {
|
||||
if (card.name) {
|
||||
cardDataByCardId[nameToId(card.name)] = card;
|
||||
}
|
||||
});
|
||||
});
|
||||
if (card.printed_name) {
|
||||
cardDataByCardId[nameToId(card.printed_name)] = card;
|
||||
}
|
||||
});
|
||||
|
||||
return cardDataByCardId;
|
||||
fetched += batch.length;
|
||||
onProgress?.(fetched, total);
|
||||
}
|
||||
|
||||
return cardDataByCardId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches card data from the Scryfall API, using a persistent cache.
|
||||
* Only cards not already in the cache are fetched. The cache is keyed by
|
||||
* normalized card name and persists across renders for the duration of
|
||||
* the session or until the collection changes.
|
||||
*
|
||||
* @param distinctCards - List of distinct card identifiers to fetch
|
||||
* @param onProgress - Optional callback called when all cards are resolved
|
||||
* @returns A record of card data indexed by normalized card name
|
||||
*/
|
||||
export const fetchCardDataFromScryfallCached = async (
|
||||
distinctCards: CardIdentifier[],
|
||||
onProgress?: (fetched: number, total: number) => void
|
||||
): Promise<Record<string, CardData>> => {
|
||||
const uncachedCards = distinctCards.filter(identifier => {
|
||||
const key = "name" in identifier
|
||||
? identifier.name
|
||||
: `${identifier.set}-${identifier.collector_number}`;
|
||||
return !hasCachedCardData(key);
|
||||
});
|
||||
|
||||
if (uncachedCards.length > 0) {
|
||||
const freshData = await fetchCardDataFromScryfall(uncachedCards, onProgress);
|
||||
Object.entries(freshData).forEach(([key, data]) => {
|
||||
setCachedCardData(key, data);
|
||||
});
|
||||
} else {
|
||||
onProgress?.(distinctCards.length, distinctCards.length);
|
||||
}
|
||||
|
||||
return getCachedCardData();
|
||||
};
|
||||
33
styles.css
33
styles.css
|
|
@ -77,10 +77,6 @@
|
|||
width: 18px;
|
||||
}
|
||||
|
||||
.card-image {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
span.card-name {
|
||||
flex: 2;
|
||||
}
|
||||
|
|
@ -90,6 +86,11 @@ span.comment {
|
|||
font-style: italic;
|
||||
}
|
||||
|
||||
.card-image {
|
||||
border-radius: 8px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.card-image-container {
|
||||
display: none;
|
||||
position: fixed;
|
||||
|
|
@ -228,3 +229,27 @@ span.comment {
|
|||
font-size: 0.85em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.collection__search {
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.collection__loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 0;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.collection__spinner {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid var(--background-modifier-border);
|
||||
border-top-color: var(--interactive-accent);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
|
@ -27,5 +27,6 @@
|
|||
"1.11.80": "0.15.0",
|
||||
"1.12.88": "1.1.0",
|
||||
"1.13.105": "1.5.7",
|
||||
"1.13.106": "1.5.7"
|
||||
"1.13.106": "1.5.7",
|
||||
"1.14.128": "1.5.7"
|
||||
}
|
||||
Loading…
Reference in a new issue