Merge branch 'master' into feat/vndb
This commit is contained in:
commit
98619fb75e
59 changed files with 927 additions and 724 deletions
|
|
@ -1,2 +0,0 @@
|
|||
npm node_modules
|
||||
build
|
||||
20
.eslintrc
20
.eslintrc
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"env": { "node": true },
|
||||
"plugins": ["@typescript-eslint", "only-warn"],
|
||||
"extends": ["eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended"],
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-inferrable-types": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": ["warn"]
|
||||
}
|
||||
}
|
||||
4
.github/ISSUE_TEMPLATE/api-request.md
vendored
4
.github/ISSUE_TEMPLATE/api-request.md
vendored
|
|
@ -15,5 +15,5 @@ A link to their API documentation
|
|||
**What does the API do/offer**
|
||||
A short description of what data the API offers
|
||||
|
||||
- [ ] Is the API free to use
|
||||
- [ ] Does the API require authentication
|
||||
- [ ] Is the API free to use
|
||||
- [ ] Does the API require authentication
|
||||
|
|
|
|||
14
.github/ISSUE_TEMPLATE/bug_report.md
vendored
14
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
|
@ -6,8 +6,8 @@ labels: bug
|
|||
assignees: ''
|
||||
---
|
||||
|
||||
- [ ] The Plugin is up to date
|
||||
- [ ] Obsidian is up to date
|
||||
- [ ] The Plugin is up to date
|
||||
- [ ] Obsidian is up to date
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
|
@ -28,11 +28,11 @@ If applicable, add screenshots to help explain your problem.
|
|||
|
||||
**Occurs on**
|
||||
|
||||
- [ ] Windows
|
||||
- [ ] macOS
|
||||
- [ ] Linux
|
||||
- [ ] Android
|
||||
- [ ] iOS
|
||||
- [ ] Windows
|
||||
- [ ] macOS
|
||||
- [ ] Linux
|
||||
- [ ] Android
|
||||
- [ ] iOS
|
||||
|
||||
**Plugin version**
|
||||
x.x.x
|
||||
|
|
|
|||
88
.github/workflows/release.yml
vendored
88
.github/workflows/release.yml
vendored
|
|
@ -1,6 +1,4 @@
|
|||
name: Build Obsidian Plugin
|
||||
|
||||
# adapted from https://github.com/argenos/nldates-obsidian/blob/master/.github/workflows/release.yml
|
||||
name: Create Plugin Release
|
||||
|
||||
on:
|
||||
push:
|
||||
|
|
@ -16,10 +14,23 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Determine prerelease status
|
||||
id: status
|
||||
run: |
|
||||
if [[ "${{ github.ref }}" == *"canary"* ]]; then
|
||||
echo "prerelease=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "prerelease=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Install Bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Build
|
||||
id: build
|
||||
run: |
|
||||
|
|
@ -27,62 +38,17 @@ jobs:
|
|||
bun run build
|
||||
mkdir ${{ env.PLUGIN_NAME }}
|
||||
cp main.js manifest.json styles.css ${{ env.PLUGIN_NAME }}
|
||||
zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }}
|
||||
zip -r ${{ env.PLUGIN_NAME }}-${{ github.ref_name }}.zip ${{ env.PLUGIN_NAME }}
|
||||
ls
|
||||
echo "tag_name=$(git tag --sort version:refname | tail -n 1)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
VERSION: ${{ github.ref }}
|
||||
- name: Release
|
||||
id: release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ github.ref }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
- name: Upload zip file
|
||||
id: upload-zip
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./${{ env.PLUGIN_NAME }}.zip
|
||||
asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Upload main.js
|
||||
id: upload-main
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./main.js
|
||||
asset_name: main.js
|
||||
asset_content_type: text/javascript
|
||||
|
||||
- name: Upload manifest.json
|
||||
id: upload-manifest
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./manifest.json
|
||||
asset_name: manifest.json
|
||||
asset_content_type: application/json
|
||||
|
||||
- name: Upload styles.css
|
||||
id: upload-css
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./styles.css
|
||||
asset_name: styles.css
|
||||
asset_content_type: text/css
|
||||
prerelease: ${{ steps.status.outputs.prerelease }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: |
|
||||
${{ env.PLUGIN_NAME }}-${{ github.ref_name }}.zip
|
||||
main.js
|
||||
manifest.json
|
||||
styles.css
|
||||
|
|
|
|||
78
CHANGELOG.md
78
CHANGELOG.md
|
|
@ -1,66 +1,74 @@
|
|||
# Changelog
|
||||
|
||||
# 0.8.0
|
||||
|
||||
- Fixed bugs when API keys for certain APIs were missing [#161](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/161) (thanks ltctceplrm)
|
||||
- Added support for other languages when remapping fields [#162](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/162) (thanks ltctceplrm)
|
||||
- Added support for the `Giant Bomb` API [#166](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/166) (thanks ltctceplrm)
|
||||
- Migration to Svelte 5
|
||||
- Some internal changes and improved error handling
|
||||
|
||||
# 0.7.2
|
||||
|
||||
- Improvements to UI text to match the Obsidian plugin guidelines [#153](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/153) (thanks kepano)
|
||||
- Improvements to UI text to match the Obsidian plugin guidelines [#153](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/153) (thanks kepano)
|
||||
|
||||
# 0.7.1
|
||||
|
||||
- Fixed mobygames result without an image crashing the search [#148](https://github.com/mProjectsCode/obsidian-media-db-plugin/issues/148) [#149](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/149) (thanks ltctceplrm)
|
||||
- Use Steam Community SearchApps for Steam search by title for fuzzy results [#146](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/146) (thanks ZackBoe)
|
||||
- Don't search APIs that don't have an API key set [#147](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/147) (thanks ZackBoe)
|
||||
- Use https for all API requests [#147](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/147) (thanks ZackBoe)
|
||||
- Sped up multi API search
|
||||
- Fixed unrelated APIs being searched when searching by a specific media type
|
||||
- Fixed mobygames result without an image crashing the search [#148](https://github.com/mProjectsCode/obsidian-media-db-plugin/issues/148) [#149](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/149) (thanks ltctceplrm)
|
||||
- Use Steam Community SearchApps for Steam search by title for fuzzy results [#146](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/146) (thanks ZackBoe)
|
||||
- Don't search APIs that don't have an API key set [#147](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/147) (thanks ZackBoe)
|
||||
- Use https for all API requests [#147](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/147) (thanks ZackBoe)
|
||||
- Sped up multi API search
|
||||
- Fixed unrelated APIs being searched when searching by a specific media type
|
||||
|
||||
# 0.7.0
|
||||
|
||||
- renamed the plugin to just `Media DB`
|
||||
- Add plot field when fetching data from OMDb [#106](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/106) (thanks onesvat)
|
||||
- Added support for Moby Games API [#131](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/131) (thanks ltctceplrm)
|
||||
- Add index operator for arrays when templating [#129](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/129) (thanks kelszo)
|
||||
- Support disabling default front matter and add support for Templater [#119](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/119) (thanks kelszo)
|
||||
- Add option to open new note in a new tab [#128](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/128) (thanks kelszo)
|
||||
- Added developers and publishers field to games [#122](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/122) (thanks ltctceplrm)
|
||||
- renamed the plugin to just `Media DB`
|
||||
- Add plot field when fetching data from OMDb [#106](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/106) (thanks onesvat)
|
||||
- Added support for Moby Games API [#131](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/131) (thanks ltctceplrm)
|
||||
- Add index operator for arrays when templating [#129](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/129) (thanks kelszo)
|
||||
- Support disabling default front matter and add support for Templater [#119](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/119) (thanks kelszo)
|
||||
- Add option to open new note in a new tab [#128](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/128) (thanks kelszo)
|
||||
- Added developers and publishers field to games [#122](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/122) (thanks ltctceplrm)
|
||||
|
||||
# 0.6.0
|
||||
|
||||
- Added manga support through Jikan
|
||||
- Added book support through Open Library
|
||||
- Added album cover support for music releases
|
||||
- Split up `producer` into `studio`, `director` and `writer` for movies and series
|
||||
- fixed the preview modal not displaying the frontmatter anymore
|
||||
- Added manga support through Jikan
|
||||
- Added book support through Open Library
|
||||
- Added album cover support for music releases
|
||||
- Split up `producer` into `studio`, `director` and `writer` for movies and series
|
||||
- fixed the preview modal not displaying the frontmatter anymore
|
||||
|
||||
# 0.5.0
|
||||
|
||||
- New simple search modal, select the media type and search all applicable APIs
|
||||
- More data for Board Games
|
||||
- Actors and Streaming Platforms for Movies and Series
|
||||
- Separate new file location for all media types
|
||||
- Separate command for each media type
|
||||
- Fix problems with closing of preview modal
|
||||
- New simple search modal, select the media type and search all applicable APIs
|
||||
- More data for Board Games
|
||||
- Actors and Streaming Platforms for Movies and Series
|
||||
- Separate new file location for all media types
|
||||
- Separate command for each media type
|
||||
- Fix problems with closing of preview modal
|
||||
|
||||
# 0.3.2
|
||||
|
||||
- Added Board Game Geek API (documentation pending)
|
||||
- More information in the search results
|
||||
- various fixes
|
||||
- Added Board Game Geek API (documentation pending)
|
||||
- More information in the search results
|
||||
- various fixes
|
||||
|
||||
# 0.3.1
|
||||
|
||||
- various fixes
|
||||
- various fixes
|
||||
|
||||
# 0.3.0
|
||||
|
||||
- Added bulk import. Import a folder of media notes as Media DB entries (thanks to [PaperOrb](https://github.com/PaperOrb) on GitHub for their input and for helping me test this feature)
|
||||
- Added a custom result select modal that allows you to select multiple results at once
|
||||
- Fixed a bug where the note creation would fail when the metadata included a field with the values `null` or `undefined`
|
||||
- Added bulk import. Import a folder of media notes as Media DB entries (thanks to [PaperOrb](https://github.com/PaperOrb) on GitHub for their input and for helping me test this feature)
|
||||
- Added a custom result select modal that allows you to select multiple results at once
|
||||
- Fixed a bug where the note creation would fail when the metadata included a field with the values `null` or `undefined`
|
||||
|
||||
# 0.2.1
|
||||
|
||||
- fixed a small bug with the initial selection of an API in the ID search modal
|
||||
- fixed a small bug with the initial selection of an API in the ID search modal
|
||||
|
||||
# 0.2.0
|
||||
|
||||
- Added the option to rename metadata fields through property mappings
|
||||
- fixed note creation falling, when the folder set in the settings did not exist
|
||||
- Added the option to rename metadata fields through property mappings
|
||||
- fixed note creation falling, when the folder set in the settings did not exist
|
||||
|
|
|
|||
110
README.md
110
README.md
|
|
@ -20,14 +20,14 @@ Note that "template tags" are surrounded with two curly braces and that the spac
|
|||
|
||||
For arrays there are two special ways of displaying them.
|
||||
|
||||
- using `{{ LIST:variable_name }}` will result in
|
||||
- using `{{ LIST:variable_name }}` will result in
|
||||
```
|
||||
- element 1
|
||||
- element 2
|
||||
- element 3
|
||||
- ...
|
||||
```
|
||||
- using `{{ ENUM:variable_name }}` will result in
|
||||
- using `{{ ENUM:variable_name }}` will result in
|
||||
```
|
||||
element 1, element 2, element 3, ...
|
||||
```
|
||||
|
|
@ -102,64 +102,68 @@ Now you select the result you want and the plugin will cast it's magic and creat
|
|||
|
||||
### Currently supported media types
|
||||
|
||||
- movies (including specials)
|
||||
- series (including OVAs)
|
||||
- games
|
||||
- music releases
|
||||
- wiki articles
|
||||
- books
|
||||
- movies (including specials)
|
||||
- series (including OVAs)
|
||||
- games
|
||||
- music releases
|
||||
- wiki articles
|
||||
- books
|
||||
|
||||
### Currently supported APIs:
|
||||
|
||||
| Name | Description | Supported formats | Authentification | Rate limiting | SFW filter support |
|
||||
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
|
||||
| [Jikan](https://jikan.moe/) | Jikan is an API that uses [My Anime List](https://myanimelist.net) and offers metadata for anime. | series, movies, specials, OVAs, manga, manwha, novels | No | 60 per minute and 3 per second | Yes |
|
||||
| [OMDb](https://www.omdbapi.com/) | OMDb is an API that offers metadata for movie, series and games. | series, movies, games | Yes, you can get a free key here [here](https://www.omdbapi.com/apikey.aspx) | 1000 per day | No |
|
||||
| [MusicBrainz](https://musicbrainz.org/) | MusicBrainz is an API that offers information about music releases. | music releases | No | 50 per second | No |
|
||||
| [Wikipedia](https://en.wikipedia.org/wiki/Main_Page) | The Wikipedia API allows access to all Wikipedia articles. | wiki articles | No | None | No |
|
||||
| [Steam](https://store.steampowered.com/) | The Steam API offers information on all steam games. | games | No | 10000 per day | No |
|
||||
| [Open Library](https://openlibrary.org) | The OpenLibrary API offers metadata for books | books | No | Cover access is rate-limited when not using CoverID or OLID by max 100 requests/IP every 5 minutes. This plugin uses OLID so there shouldn't be a rate limit. | No |
|
||||
| [Moby Games](https://www.mobygames.com) | The Moby Games API offers metadata for games for all platforms | games | Yes, by making an account [here](https://www.mobygames.com/user/register/) | API requests are limited to 360 per hour (one every ten seconds). In addition, requests should be made no more frequently than one per second. | No |
|
||||
| [VNDB](https://vndb.org/) | The VNDB API offers metadata for visual novels | games | No | 200 requests per 5 minutes | Yes |
|
||||
| Name | Description | Supported formats | Authentification | Rate limiting | SFW filter support |
|
||||
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
|
||||
| [Jikan](https://jikan.moe/) | Jikan is an API that uses [My Anime List](https://myanimelist.net) and offers metadata for anime. | series, movies, specials, OVAs, manga, manwha, novels | No | 60 per minute and 3 per second | Yes |
|
||||
| [OMDb](https://www.omdbapi.com/) | OMDb is an API that offers metadata for movie, series and games. | series, movies, games | Yes, you can get a free key here [here](https://www.omdbapi.com/apikey.aspx) | 1000 per day | No |
|
||||
| [MusicBrainz](https://musicbrainz.org/) | MusicBrainz is an API that offers information about music releases. | music releases | No | 50 per second | No |
|
||||
| [Wikipedia](https://en.wikipedia.org/wiki/Main_Page) | The Wikipedia API allows access to all Wikipedia articles. | wiki articles | No | None | No |
|
||||
| [Steam](https://store.steampowered.com/) | The Steam API offers information on all steam games. | games | No | 10000 per day | No |
|
||||
| [Open Library](https://openlibrary.org) | The OpenLibrary API offers metadata for books | books | No | Cover access is rate-limited when not using CoverID or OLID by max 100 requests/IP every 5 minutes. This plugin uses OLID so there shouldn't be a rate limit. | No |
|
||||
| [Moby Games](https://www.mobygames.com) | The Moby Games API offers metadata for games for all platforms | games | Yes, by making an account [here](https://www.mobygames.com/user/register/). NOTE: As of September 2024 the API key is no longer free so consider using Giant Bomb or steam instead | API requests are limited to 360 per hour (one every ten seconds). In addition, requests should be made no more frequently than one per second. | No |
|
||||
| [Giant Bomb](https://www.giantbomb.com) | The Giant Bomb API offers metadata for games for all platforms | games | Yes, by making an account [here](https://www.giantbomb.com/login-signup/) | API requests are limited to 200 requests per resource, per hour. In addition, they implement velocity detection to prevent malicious use. If too many requests are made per second, you may receive temporary blocks to resources. | No |
|
||||
| [VNDB](https://vndb.org/) | The VNDB API offers metadata for visual novels | games | No | 200 requests per 5 minutes | Yes |
|
||||
|
||||
#### Notes
|
||||
|
||||
- [Jikan](https://jikan.moe/)
|
||||
- sometimes the api is very slow, this is normal
|
||||
- you need to use the title the anime has on [My Anime List](https://myanimelist.net), which is in most cases the japanese title
|
||||
- e.g. instead of "Demon Slayer" you have to search "Kimetsu no Yaiba"
|
||||
- [Jikan](https://jikan.moe/)
|
||||
- sometimes the api is very slow, this is normal
|
||||
- you need to use the title the anime has on [My Anime List](https://myanimelist.net), which is in most cases the japanese title
|
||||
- e.g. instead of "Demon Slayer" you have to search "Kimetsu no Yaiba"
|
||||
|
||||
#### Search by ID
|
||||
|
||||
- [Jikan](https://jikan.moe/)
|
||||
- the ID you need is the ID of the anime on [My Anime List](https://myanimelist.net)
|
||||
- you can find this ID in the URL
|
||||
- e.g. for "Beyond the Boundary" the URL looks like this `https://myanimelist.net/anime/18153/Kyoukai_no_Kanata` so the ID is `18153`
|
||||
- [Jikan Manga](https://jikan.moe/)
|
||||
- the ID you need is the ID of the manga on [My Anime List](https://myanimelist.net)
|
||||
- you can find this ID in the URL
|
||||
- e.g. for "All You Need Is Kill" the URL looks like this `https://myanimelist.net/manga/62887/All_You_Need_Is_Kill` so the ID is `62887`
|
||||
- [OMDb](https://www.omdbapi.com/)
|
||||
- the ID you need is the ID of the movie or show on [IMDb](https://www.imdb.com)
|
||||
- you can find this ID in the URL
|
||||
- e.g. for "Rogue One" the URL looks like this `https://www.imdb.com/title/tt3748528/` so the ID is `tt3748528`
|
||||
- [MusicBrainz](https://musicbrainz.org/)
|
||||
- the id of a release is not easily accessible, you are better off just searching by title
|
||||
- [Wikipedia](https://en.wikipedia.org/wiki/Main_Page)
|
||||
- [here](https://en.wikipedia.org/wiki/Wikipedia:Finding_a_Wikidata_ID) is a guide to finding the Wikipedia ID for an article
|
||||
- [Steam](https://store.steampowered.com/)
|
||||
- you can find this ID in the URL
|
||||
- e.g. for "Factorio" the URL looks like this `https://store.steampowered.com/app/427520/Factorio/` so the ID is `427520`
|
||||
- [Open Library](https://openlibrary.org)
|
||||
- The ID you need is the "work" ID and not the "book" ID, it needs to start with `/works/`. You can find this ID in the URL
|
||||
- e.g. for "Fantastic Mr. Fox" the URL looks like this `https://openlibrary.org/works/OL45804W` so the ID is `/works/OL45804W`
|
||||
- This URL is located near the top of the page above the title, see `An edition of Fantastic Mr Fox (1970) `
|
||||
- [Moby Games](https://www.mobygames.com)
|
||||
- you can find this ID in the URL
|
||||
- e.g. for "Bioshock 2" the URL looks like this `https://www.mobygames.com/game/45089/bioshock-2/` so the ID is `45089`
|
||||
- [VNDB](https://vndb.org/)
|
||||
- Located in the novel's VNDB URL path
|
||||
- e.g. The ID for [Katawa Shoujo](https://vndb.org/v945) (`https://vndb.org/v945`) is `v945`
|
||||
- [Jikan](https://jikan.moe/)
|
||||
- the ID you need is the ID of the anime on [My Anime List](https://myanimelist.net)
|
||||
- you can find this ID in the URL
|
||||
- e.g. for "Beyond the Boundary" the URL looks like this `https://myanimelist.net/anime/18153/Kyoukai_no_Kanata` so the ID is `18153`
|
||||
- [Jikan Manga](https://jikan.moe/)
|
||||
- the ID you need is the ID of the manga on [My Anime List](https://myanimelist.net)
|
||||
- you can find this ID in the URL
|
||||
- e.g. for "All You Need Is Kill" the URL looks like this `https://myanimelist.net/manga/62887/All_You_Need_Is_Kill` so the ID is `62887`
|
||||
- [OMDb](https://www.omdbapi.com/)
|
||||
- the ID you need is the ID of the movie or show on [IMDb](https://www.imdb.com)
|
||||
- you can find this ID in the URL
|
||||
- e.g. for "Rogue One" the URL looks like this `https://www.imdb.com/title/tt3748528/` so the ID is `tt3748528`
|
||||
- [MusicBrainz](https://musicbrainz.org/)
|
||||
- the id of a release is not easily accessible, you are better off just searching by title
|
||||
- [Wikipedia](https://en.wikipedia.org/wiki/Main_Page)
|
||||
- [here](https://en.wikipedia.org/wiki/Wikipedia:Finding_a_Wikidata_ID) is a guide to finding the Wikipedia ID for an article
|
||||
- [Steam](https://store.steampowered.com/)
|
||||
- you can find this ID in the URL
|
||||
- e.g. for "Factorio" the URL looks like this `https://store.steampowered.com/app/427520/Factorio/` so the ID is `427520`
|
||||
- [Open Library](https://openlibrary.org)
|
||||
- The ID you need is the "work" ID and not the "book" ID, it needs to start with `/works/`. You can find this ID in the URL
|
||||
- e.g. for "Fantastic Mr. Fox" the URL looks like this `https://openlibrary.org/works/OL45804W` so the ID is `/works/OL45804W`
|
||||
- This URL is located near the top of the page above the title, see `An edition of Fantastic Mr Fox (1970) `
|
||||
- [Moby Games](https://www.mobygames.com)
|
||||
- you can find this ID in the URL
|
||||
- e.g. for "Bioshock 2" the URL looks like this `https://www.mobygames.com/game/45089/bioshock-2/` so the ID is `45089`
|
||||
- [Giant Bomb](https://www.giantbomb.com)
|
||||
- you can find this ID in the URL
|
||||
- e.g. for "Dota 2" the URL looks like this `https://www.giantbomb.com/dota-2/3030-32887/` so the ID is `3030-32887`
|
||||
- [VNDB](https://vndb.org/)
|
||||
- Located in the novel's VNDB URL path
|
||||
- e.g. The ID for [Katawa Shoujo](https://vndb.org/v945) (`https://vndb.org/v945`) is `v945`
|
||||
|
||||
### Problems, unexpected behavior or improvement suggestions?
|
||||
|
||||
|
|
@ -175,5 +179,5 @@ Contributions are always welcome. If you have an idea, feel free to open a featu
|
|||
|
||||
Credits go to:
|
||||
|
||||
- https://github.com/anpigon/obsidian-book-search-plugin for some inspiration and the idea to make this plugin
|
||||
- https://github.com/liamcain/obsidian-periodic-notes for 99% of `Suggest.ts` and `FolderSuggest.ts`
|
||||
- https://github.com/anpigon/obsidian-book-search-plugin for some inspiration and the idea to make this plugin
|
||||
- https://github.com/liamcain/obsidian-periodic-notes for 99% of `Suggest.ts` and `FolderSuggest.ts`
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import builtins from 'builtin-modules';
|
||||
import esbuild from 'esbuild';
|
||||
import esbuildSvelte from 'esbuild-svelte';
|
||||
import sveltePreprocess from 'svelte-preprocess';
|
||||
import { sveltePreprocess } from 'svelte-preprocess';
|
||||
import { getBuildBanner } from 'build/buildBanner';
|
||||
|
||||
const banner = getBuildBanner('Release Build', version => version);
|
||||
|
|
@ -41,7 +41,7 @@ const build = await esbuild.build({
|
|||
},
|
||||
plugins: [
|
||||
esbuildSvelte({
|
||||
compilerOptions: { css: 'injected', dev: false, sveltePath: 'svelte' },
|
||||
compilerOptions: { css: 'injected', dev: false },
|
||||
preprocess: sveltePreprocess(),
|
||||
filterWarnings: warning => {
|
||||
// we don't want warnings from node modules that we can do nothing about
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import esbuild from 'esbuild';
|
||||
import copy from 'esbuild-plugin-copy-watch';
|
||||
import esbuildSvelte from 'esbuild-svelte';
|
||||
import sveltePreprocess from 'svelte-preprocess';
|
||||
import { sveltePreprocess } from 'svelte-preprocess';
|
||||
import manifest from '../../manifest.json' assert { type: 'json' };
|
||||
import { getBuildBanner } from 'build/buildBanner';
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ const context = await esbuild.context({
|
|||
],
|
||||
}),
|
||||
esbuildSvelte({
|
||||
compilerOptions: { css: 'injected', dev: true, sveltePath: 'svelte' },
|
||||
compilerOptions: { css: 'injected', dev: true },
|
||||
preprocess: sveltePreprocess(),
|
||||
filterWarnings: warning => {
|
||||
// we don't want warnings from node modules that we can do nothing about
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { $choice as $choice, $confirm, $seq, CMD_FMT, Verboseness } from 'utils/
|
|||
async function runPreconditions(): Promise<void> {
|
||||
// run preconditions
|
||||
await $seq(
|
||||
[`bun run format`, `bun run lint:fix`, `bun run test`],
|
||||
[`bun run format`, `bun run test`],
|
||||
(cmd: string) => {
|
||||
throw new UserError(`precondition "${cmd}" failed`);
|
||||
},
|
||||
|
|
|
|||
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
54
eslint.config.mjs
Normal file
54
eslint.config.mjs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// @ts-check
|
||||
|
||||
import eslint from '@eslint/js';
|
||||
import tseslint from 'typescript-eslint';
|
||||
import only_warn from 'eslint-plugin-only-warn';
|
||||
import * as plugin_import from 'eslint-plugin-import';
|
||||
|
||||
export default tseslint.config(
|
||||
{
|
||||
ignores: ['npm/', 'node_modules/', 'exampleVault/', 'automation/', 'main.js', '*.svelte'],
|
||||
},
|
||||
{
|
||||
files: ['src/**/*.ts'],
|
||||
extends: [eslint.configs.recommended, ...tseslint.configs.recommended, ...tseslint.configs.recommendedTypeChecked, ...tseslint.configs.stylisticTypeChecked],
|
||||
languageOptions: {
|
||||
parser: tseslint.parser,
|
||||
parserOptions: {
|
||||
project: true,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
// @ts-ignore
|
||||
'only-warn': only_warn,
|
||||
import: plugin_import,
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': ['warn'],
|
||||
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{ argsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' },
|
||||
],
|
||||
'@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports', fixStyle: 'separate-type-imports' }],
|
||||
|
||||
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
|
||||
'import/order': [
|
||||
'error',
|
||||
{
|
||||
'newlines-between': 'never',
|
||||
alphabetize: { order: 'asc', orderImportKind: 'asc', caseInsensitive: true },
|
||||
},
|
||||
],
|
||||
|
||||
'@typescript-eslint/no-confusing-void-expression': ['error', { ignoreArrowShorthand: true }],
|
||||
'@typescript-eslint/restrict-template-expressions': 'off',
|
||||
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/no-inferrable-types': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': ['warn'],
|
||||
'@typescript-eslint/require-await': 'off',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "obsidian-media-db-plugin",
|
||||
"name": "Media DB",
|
||||
"version": "0.7.2",
|
||||
"version": "0.8.0",
|
||||
"minAppVersion": "1.5.0",
|
||||
"description": "A plugin that can query multiple APIs for movies, series, anime, games, music and wiki articles, and import them into your vault.",
|
||||
"author": "Moritz Jung",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "obsidian-media-db-plugin",
|
||||
"name": "Media DB",
|
||||
"version": "0.7.2",
|
||||
"version": "0.8.0",
|
||||
"minAppVersion": "1.5.0",
|
||||
"description": "A plugin that can query multiple APIs for movies, series, anime, games, music and wiki articles, and import them into your vault.",
|
||||
"author": "Moritz Jung",
|
||||
|
|
|
|||
41
package.json
41
package.json
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-media-db-plugin",
|
||||
"version": "0.7.2",
|
||||
"version": "0.8.0",
|
||||
"description": "A plugin that can query multiple APIs for movies, series, anime, games, music and wiki articles, and import them into your vault.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
@ -14,8 +14,8 @@
|
|||
"lint": "eslint --max-warnings=0 src/**",
|
||||
"lint:fix": "eslint --max-warnings=0 --fix src/**",
|
||||
"svelte-check": "svelte-check --compiler-warnings \"unused-export-let:ignore\"",
|
||||
"check": "bun run format:check && bun run tsc && bun run lint && bun run test",
|
||||
"check:fix": "bun run format && bun run tsc && bun run lint:fix && bun run test",
|
||||
"check": "bun run format:check && bun run tsc && bun run test",
|
||||
"check:fix": "bun run format && bun run tsc && bun run test",
|
||||
"release": "bun run automation/release.ts",
|
||||
"stats": "bun run automation/stats.ts"
|
||||
},
|
||||
|
|
@ -25,27 +25,24 @@
|
|||
"devDependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@lemons_dev/parsinom": "^0.0.12",
|
||||
"@happy-dom/global-registrator": "^14.3.6",
|
||||
"@tsconfig/svelte": "^5.0.3",
|
||||
"@types/bun": "^1.0.10",
|
||||
"@typescript-eslint/eslint-plugin": "^7.3.1",
|
||||
"@typescript-eslint/parser": "^7.3.1",
|
||||
"builtin-modules": "^3.3.0",
|
||||
"esbuild": "^0.20.2",
|
||||
"esbuild-plugin-copy-watch": "^2.1.0",
|
||||
"esbuild-svelte": "^0.8.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-isaacscript": "^3.12.2",
|
||||
"@happy-dom/global-registrator": "^14.12.3",
|
||||
"@types/bun": "^1.1.16",
|
||||
"builtin-modules": "^4.0.0",
|
||||
"esbuild": "^0.24.2",
|
||||
"esbuild-plugin-copy-watch": "^2.3.1",
|
||||
"esbuild-svelte": "^0.8.2",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-only-warn": "^1.1.0",
|
||||
"obsidian": "latest",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-svelte": "^3.2.2",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"string-argv": "^0.3.2",
|
||||
"svelte": "^4.2.12",
|
||||
"svelte-check": "^3.6.8",
|
||||
"svelte-preprocess": "^5.1.3",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.4.3"
|
||||
"svelte": "^5.17.5",
|
||||
"svelte-check": "^4.1.4",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.20.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { APIModel } from './APIModel';
|
||||
import { MediaTypeModel } from '../models/MediaTypeModel';
|
||||
import { Notice } from 'obsidian';
|
||||
import type { MediaTypeModel } from '../models/MediaTypeModel';
|
||||
import type { APIModel } from './APIModel';
|
||||
|
||||
export class APIManager {
|
||||
apis: APIModel[];
|
||||
|
|
@ -23,7 +24,10 @@ export class APIManager {
|
|||
try {
|
||||
return await api.searchByTitle(query);
|
||||
} catch (e) {
|
||||
new Notice(`Error querying ${api.apiName}: ${e}`);
|
||||
console.warn(e);
|
||||
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -35,7 +39,7 @@ export class APIManager {
|
|||
*
|
||||
* @param item
|
||||
*/
|
||||
async queryDetailedInfo(item: MediaTypeModel): Promise<MediaTypeModel> {
|
||||
async queryDetailedInfo(item: MediaTypeModel): Promise<MediaTypeModel | undefined> {
|
||||
return await this.queryDetailedInfoById(item.id, item.dataSource);
|
||||
}
|
||||
|
||||
|
|
@ -45,22 +49,31 @@ export class APIManager {
|
|||
* @param id
|
||||
* @param apiName
|
||||
*/
|
||||
async queryDetailedInfoById(id: string, apiName: string): Promise<MediaTypeModel> {
|
||||
async queryDetailedInfoById(id: string, apiName: string): Promise<MediaTypeModel | undefined> {
|
||||
for (const api of this.apis) {
|
||||
if (api.apiName === apiName) {
|
||||
return api.getById(id);
|
||||
try {
|
||||
return api.getById(id);
|
||||
} catch (e) {
|
||||
new Notice(`Error querying ${api.apiName}: ${e}`);
|
||||
console.warn(e);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getApiByName(name: string): APIModel {
|
||||
getApiByName(name: string): APIModel | undefined {
|
||||
for (const api of this.apis) {
|
||||
if (api.apiName === name) {
|
||||
return api;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
registerAPI(api: APIModel): void {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { MediaTypeModel } from '../models/MediaTypeModel';
|
||||
import { MediaType } from '../utils/MediaType';
|
||||
import MediaDbPlugin from '../main';
|
||||
import type MediaDbPlugin from '../main';
|
||||
import type { MediaTypeModel } from '../models/MediaTypeModel';
|
||||
import type { MediaType } from '../utils/MediaType';
|
||||
|
||||
export abstract class APIModel {
|
||||
apiName: string;
|
||||
apiUrl: string;
|
||||
apiDescription: string;
|
||||
types: MediaType[];
|
||||
plugin: MediaDbPlugin;
|
||||
apiName!: string;
|
||||
apiUrl!: string;
|
||||
apiDescription!: string;
|
||||
types!: MediaType[];
|
||||
plugin!: MediaDbPlugin;
|
||||
|
||||
/**
|
||||
* This function should query the api and return a list of matches. The matches should be caped at 20.
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { APIModel } from '../APIModel';
|
||||
import { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||
import MediaDbPlugin from '../../main';
|
||||
import { BoardGameModel } from 'src/models/BoardGameModel';
|
||||
import { requestUrl } from 'obsidian';
|
||||
import { BoardGameModel } from 'src/models/BoardGameModel';
|
||||
import type MediaDbPlugin from '../../main';
|
||||
import type { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||
import { MediaType } from '../../utils/MediaType';
|
||||
import { APIModel } from '../APIModel';
|
||||
|
||||
export class BoardGameGeekAPI extends APIModel {
|
||||
plugin: MediaDbPlugin;
|
||||
|
|
@ -38,8 +38,8 @@ export class BoardGameGeekAPI extends APIModel {
|
|||
const ret: MediaTypeModel[] = [];
|
||||
|
||||
for (const boardgame of Array.from(response.querySelectorAll('boardgame'))) {
|
||||
const id = boardgame.attributes.getNamedItem('objectid')!.value;
|
||||
const title = boardgame.querySelector('name[primary=true]')?.textContent ?? boardgame.querySelector('name')!.textContent!;
|
||||
const id = boardgame.attributes.getNamedItem('objectid')?.value;
|
||||
const title = boardgame.querySelector('name[primary=true]')?.textContent ?? boardgame.querySelector('name')?.textContent ?? undefined;
|
||||
const year = boardgame.querySelector('yearpublished')?.textContent ?? '';
|
||||
|
||||
ret.push(
|
||||
|
|
@ -49,7 +49,7 @@ export class BoardGameGeekAPI extends APIModel {
|
|||
title,
|
||||
englishTitle: title,
|
||||
year,
|
||||
} as BoardGameModel),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -72,21 +72,29 @@ export class BoardGameGeekAPI extends APIModel {
|
|||
const response = new window.DOMParser().parseFromString(data, 'text/xml');
|
||||
// console.debug(response);
|
||||
|
||||
const boardgame = response.querySelector('boardgame')!;
|
||||
const title = boardgame.querySelector('name[primary=true]')!.textContent!;
|
||||
const boardgame = response.querySelector('boardgame');
|
||||
if (!boardgame) {
|
||||
throw Error(`MDB | Received invalid data from ${this.apiName}.`);
|
||||
}
|
||||
|
||||
const title = boardgame.querySelector('name[primary=true]')?.textContent;
|
||||
const year = boardgame.querySelector('yearpublished')?.textContent ?? '';
|
||||
const image = boardgame.querySelector('image')?.textContent ?? undefined;
|
||||
const onlineRating = Number.parseFloat(boardgame.querySelector('statistics ratings average')?.textContent ?? '0');
|
||||
const genres = Array.from(boardgame.querySelectorAll('boardgamecategory')).map(n => n!.textContent!);
|
||||
const genres = Array.from(boardgame.querySelectorAll('boardgamecategory'))
|
||||
.map(n => n.textContent)
|
||||
.filter(n => n !== null);
|
||||
const complexityRating = Number.parseFloat(boardgame.querySelector('averageweight')?.textContent ?? '0');
|
||||
const minPlayers = Number.parseFloat(boardgame.querySelector('minplayers')?.textContent ?? '0');
|
||||
const maxPlayers = Number.parseFloat(boardgame.querySelector('maxplayers')?.textContent ?? '0');
|
||||
const playtime = (boardgame.querySelector('playingtime')?.textContent ?? 'unknown') + ' minutes';
|
||||
const publishers = Array.from(boardgame.querySelectorAll('boardgamepublisher')).map(n => n!.textContent!);
|
||||
const publishers = Array.from(boardgame.querySelectorAll('boardgamepublisher'))
|
||||
.map(n => n.textContent)
|
||||
.filter(n => n !== null);
|
||||
|
||||
return new BoardGameModel({
|
||||
title: title,
|
||||
englishTitle: title,
|
||||
title: title ?? undefined,
|
||||
englishTitle: title ?? undefined,
|
||||
year: year === '0' ? '' : year,
|
||||
dataSource: this.apiName,
|
||||
url: `https://boardgamegeek.com/boardgame/${id}`,
|
||||
|
|
@ -107,6 +115,6 @@ export class BoardGameGeekAPI extends APIModel {
|
|||
played: false,
|
||||
personalRating: 0,
|
||||
},
|
||||
} as BoardGameModel);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
109
src/api/apis/GiantBombAPI.ts
Normal file
109
src/api/apis/GiantBombAPI.ts
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
import { requestUrl } from 'obsidian';
|
||||
import type MediaDbPlugin from '../../main';
|
||||
import { GameModel } from '../../models/GameModel';
|
||||
import type { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||
import { MediaType } from '../../utils/MediaType';
|
||||
import { APIModel } from '../APIModel';
|
||||
|
||||
export class GiantBombAPI extends APIModel {
|
||||
plugin: MediaDbPlugin;
|
||||
apiDateFormat: string = 'YYYY-MM-DD';
|
||||
|
||||
constructor(plugin: MediaDbPlugin) {
|
||||
super();
|
||||
|
||||
this.plugin = plugin;
|
||||
this.apiName = 'GiantBombAPI';
|
||||
this.apiDescription = 'A free API for games.';
|
||||
this.apiUrl = 'https://www.giantbomb.com/api';
|
||||
this.types = [MediaType.Game];
|
||||
}
|
||||
async searchByTitle(title: string): Promise<MediaTypeModel[]> {
|
||||
console.log(`MDB | api "${this.apiName}" queried by Title`);
|
||||
|
||||
if (!this.plugin.settings.GiantBombKey) {
|
||||
throw Error(`MDB | API key for ${this.apiName} missing.`);
|
||||
}
|
||||
|
||||
const searchUrl = `${this.apiUrl}/games?api_key=${this.plugin.settings.GiantBombKey}&filter=name:${encodeURIComponent(title)}&format=json`;
|
||||
const fetchData = await requestUrl({
|
||||
url: searchUrl,
|
||||
});
|
||||
|
||||
// console.debug(fetchData);
|
||||
|
||||
if (fetchData.status === 401) {
|
||||
throw Error(`MDB | Authentication for ${this.apiName} failed. Check the API key.`);
|
||||
}
|
||||
if (fetchData.status === 429) {
|
||||
throw Error(`MDB | Too many requests for ${this.apiName}, you've exceeded your API quota.`);
|
||||
}
|
||||
if (fetchData.status !== 200) {
|
||||
throw Error(`MDB | Received status code ${fetchData.status} from ${this.apiName}.`);
|
||||
}
|
||||
|
||||
const data = await fetchData.json;
|
||||
// console.debug(data);
|
||||
const ret: MediaTypeModel[] = [];
|
||||
for (const result of data.results) {
|
||||
ret.push(
|
||||
new GameModel({
|
||||
type: MediaType.Game,
|
||||
title: result.name,
|
||||
englishTitle: result.name,
|
||||
year: new Date(result.original_release_date).getFullYear().toString(),
|
||||
dataSource: this.apiName,
|
||||
id: result.guid,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
async getById(id: string): Promise<MediaTypeModel> {
|
||||
console.log(`MDB | api "${this.apiName}" queried by ID`);
|
||||
|
||||
if (!this.plugin.settings.GiantBombKey) {
|
||||
throw Error(`MDB | API key for ${this.apiName} missing.`);
|
||||
}
|
||||
|
||||
const searchUrl = `${this.apiUrl}/game/${encodeURIComponent(id)}/?api_key=${this.plugin.settings.GiantBombKey}&format=json`;
|
||||
const fetchData = await requestUrl({
|
||||
url: searchUrl,
|
||||
});
|
||||
console.debug(fetchData);
|
||||
|
||||
if (fetchData.status !== 200) {
|
||||
throw Error(`MDB | Received status code ${fetchData.status} from ${this.apiName}.`);
|
||||
}
|
||||
|
||||
const data = await fetchData.json;
|
||||
// console.debug(data);
|
||||
const result = data.results;
|
||||
|
||||
return new GameModel({
|
||||
type: MediaType.Game,
|
||||
title: result.name,
|
||||
englishTitle: result.name,
|
||||
year: new Date(result.original_release_date).getFullYear().toString(),
|
||||
dataSource: this.apiName,
|
||||
url: result.site_detail_url,
|
||||
id: result.guid,
|
||||
developers: result.developers?.map((x: any) => x.name) ?? [],
|
||||
publishers: result.publishers?.map((x: any) => x.name) ?? [],
|
||||
genres: result.genres?.map((x: any) => x.name) ?? [],
|
||||
onlineRating: 0,
|
||||
image: result.image?.super_url ?? '',
|
||||
|
||||
released: true,
|
||||
releaseDate: result.original_release_date ?? 'unknown',
|
||||
|
||||
userData: {
|
||||
played: false,
|
||||
|
||||
personalRating: 0,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import { APIModel } from '../APIModel';
|
||||
import { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||
import type MediaDbPlugin from '../../main';
|
||||
import type { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||
import { MovieModel } from '../../models/MovieModel';
|
||||
import MediaDbPlugin from '../../main';
|
||||
import { SeriesModel } from '../../models/SeriesModel';
|
||||
import { MediaType } from '../../utils/MediaType';
|
||||
import { APIModel } from '../APIModel';
|
||||
|
||||
export class MALAPI extends APIModel {
|
||||
plugin: MediaDbPlugin;
|
||||
|
|
@ -52,7 +52,7 @@ export class MALAPI extends APIModel {
|
|||
year: result.year ?? result.aired?.prop?.from?.year ?? '',
|
||||
dataSource: this.apiName,
|
||||
id: result.mal_id,
|
||||
} as MovieModel),
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (type === 'movie' || type === 'special') {
|
||||
|
|
@ -64,7 +64,7 @@ export class MALAPI extends APIModel {
|
|||
year: result.year ?? result.aired?.prop?.from?.year ?? '',
|
||||
dataSource: this.apiName,
|
||||
id: result.mal_id,
|
||||
} as MovieModel),
|
||||
}),
|
||||
);
|
||||
} else if (type === 'series' || type === 'ova') {
|
||||
ret.push(
|
||||
|
|
@ -75,7 +75,7 @@ export class MALAPI extends APIModel {
|
|||
year: result.year ?? result.aired?.prop?.from?.year ?? '',
|
||||
dataSource: this.apiName,
|
||||
id: result.mal_id,
|
||||
} as SeriesModel),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -127,7 +127,7 @@ export class MALAPI extends APIModel {
|
|||
lastWatched: '',
|
||||
personalRating: 0,
|
||||
},
|
||||
} as MovieModel);
|
||||
});
|
||||
}
|
||||
|
||||
if (type === 'movie' || type === 'special') {
|
||||
|
|
@ -159,7 +159,7 @@ export class MALAPI extends APIModel {
|
|||
lastWatched: '',
|
||||
personalRating: 0,
|
||||
},
|
||||
} as MovieModel);
|
||||
});
|
||||
} else if (type === 'series' || type === 'ova') {
|
||||
return new SeriesModel({
|
||||
subType: type,
|
||||
|
|
@ -190,9 +190,9 @@ export class MALAPI extends APIModel {
|
|||
lastWatched: '',
|
||||
personalRating: 0,
|
||||
},
|
||||
} as SeriesModel);
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
throw new Error(`MDB | Unknown media type for id ${id}`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { APIModel } from '../APIModel';
|
||||
import { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||
import MediaDbPlugin from '../../main';
|
||||
import type MediaDbPlugin from '../../main';
|
||||
import { MangaModel } from '../../models/MangaModel';
|
||||
import type { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||
import { MediaType } from '../../utils/MediaType';
|
||||
import { APIModel } from '../APIModel';
|
||||
|
||||
export class MALAPIManga extends APIModel {
|
||||
plugin: MediaDbPlugin;
|
||||
|
|
@ -73,7 +73,7 @@ export class MALAPIManga extends APIModel {
|
|||
lastWatched: '',
|
||||
personalRating: 0,
|
||||
},
|
||||
} as MangaModel),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -123,6 +123,6 @@ export class MALAPIManga extends APIModel {
|
|||
lastWatched: '',
|
||||
personalRating: 0,
|
||||
},
|
||||
} as MangaModel);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { APIModel } from '../APIModel';
|
||||
import { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||
import MediaDbPlugin from '../../main';
|
||||
import { GameModel } from '../../models/GameModel';
|
||||
import { Notice } from 'obsidian';
|
||||
import { requestUrl } from 'obsidian';
|
||||
import type MediaDbPlugin from '../../main';
|
||||
import { GameModel } from '../../models/GameModel';
|
||||
import type { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||
import { MediaType } from '../../utils/MediaType';
|
||||
import { APIModel } from '../APIModel';
|
||||
|
||||
export class MobyGamesAPI extends APIModel {
|
||||
plugin: MediaDbPlugin;
|
||||
|
|
@ -22,7 +23,7 @@ export class MobyGamesAPI extends APIModel {
|
|||
console.log(`MDB | api "${this.apiName}" queried by Title`);
|
||||
|
||||
if (!this.plugin.settings.MobyGamesKey) {
|
||||
throw Error(`MDB | API key for ${this.apiName} missing.`);
|
||||
throw new Error(`MDB | API key for ${this.apiName} missing.`);
|
||||
}
|
||||
|
||||
const searchUrl = `${this.apiUrl}/games?title=${encodeURIComponent(title)}&api_key=${this.plugin.settings.MobyGamesKey}`;
|
||||
|
|
@ -104,6 +105,6 @@ export class MobyGamesAPI extends APIModel {
|
|||
|
||||
personalRating: 0,
|
||||
},
|
||||
} as GameModel);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { APIModel } from '../APIModel';
|
||||
import { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||
import MediaDbPlugin from '../../main';
|
||||
import { requestUrl } from 'obsidian';
|
||||
import type MediaDbPlugin from '../../main';
|
||||
import type { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||
import { MusicReleaseModel } from '../../models/MusicReleaseModel';
|
||||
import { contactEmail, mediaDbVersion, pluginName } from '../../utils/Utils';
|
||||
import { MediaType } from '../../utils/MediaType';
|
||||
import { contactEmail, mediaDbVersion, pluginName } from '../../utils/Utils';
|
||||
import { APIModel } from '../APIModel';
|
||||
|
||||
export class MusicBrainzAPI extends APIModel {
|
||||
plugin: MediaDbPlugin;
|
||||
|
|
@ -55,7 +55,7 @@ export class MusicBrainzAPI extends APIModel {
|
|||
|
||||
artists: result['artist-credit'].map((a: any) => a.name),
|
||||
subType: result['primary-type'],
|
||||
} as MusicReleaseModel),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -97,6 +97,6 @@ export class MusicBrainzAPI extends APIModel {
|
|||
userData: {
|
||||
personalRating: 0,
|
||||
},
|
||||
} as MusicReleaseModel);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { APIModel } from '../APIModel';
|
||||
import { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||
import { MovieModel } from '../../models/MovieModel';
|
||||
import MediaDbPlugin from '../../main';
|
||||
import { SeriesModel } from '../../models/SeriesModel';
|
||||
import { Notice } from 'obsidian';
|
||||
import type MediaDbPlugin from '../../main';
|
||||
import { GameModel } from '../../models/GameModel';
|
||||
import type { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||
import { MovieModel } from '../../models/MovieModel';
|
||||
import { SeriesModel } from '../../models/SeriesModel';
|
||||
import { MediaType } from '../../utils/MediaType';
|
||||
import { APIModel } from '../APIModel';
|
||||
|
||||
export class OMDbAPI extends APIModel {
|
||||
plugin: MediaDbPlugin;
|
||||
|
|
@ -29,7 +30,7 @@ export class OMDbAPI extends APIModel {
|
|||
console.log(`MDB | api "${this.apiName}" queried by Title`);
|
||||
|
||||
if (!this.plugin.settings.OMDbKey) {
|
||||
throw Error(`MDB | API key for ${this.apiName} missing.`);
|
||||
throw new Error(`MDB | API key for ${this.apiName} missing.`);
|
||||
}
|
||||
|
||||
const searchUrl = `https://www.omdbapi.com/?s=${encodeURIComponent(title)}&apikey=${this.plugin.settings.OMDbKey}`;
|
||||
|
|
@ -73,7 +74,7 @@ export class OMDbAPI extends APIModel {
|
|||
year: result.Year,
|
||||
dataSource: this.apiName,
|
||||
id: result.imdbID,
|
||||
} as MovieModel),
|
||||
}),
|
||||
);
|
||||
} else if (type === 'series') {
|
||||
ret.push(
|
||||
|
|
@ -84,7 +85,7 @@ export class OMDbAPI extends APIModel {
|
|||
year: result.Year,
|
||||
dataSource: this.apiName,
|
||||
id: result.imdbID,
|
||||
} as SeriesModel),
|
||||
}),
|
||||
);
|
||||
} else if (type === 'game') {
|
||||
ret.push(
|
||||
|
|
@ -95,7 +96,7 @@ export class OMDbAPI extends APIModel {
|
|||
year: result.Year,
|
||||
dataSource: this.apiName,
|
||||
id: result.imdbID,
|
||||
} as GameModel),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -161,7 +162,7 @@ export class OMDbAPI extends APIModel {
|
|||
lastWatched: '',
|
||||
personalRating: 0,
|
||||
},
|
||||
} as MovieModel);
|
||||
});
|
||||
} else if (type === 'series') {
|
||||
return new SeriesModel({
|
||||
type: type,
|
||||
|
|
@ -193,7 +194,7 @@ export class OMDbAPI extends APIModel {
|
|||
lastWatched: '',
|
||||
personalRating: 0,
|
||||
},
|
||||
} as SeriesModel);
|
||||
});
|
||||
} else if (type === 'game') {
|
||||
return new GameModel({
|
||||
type: type,
|
||||
|
|
@ -217,9 +218,9 @@ export class OMDbAPI extends APIModel {
|
|||
played: false,
|
||||
personalRating: 0,
|
||||
},
|
||||
} as GameModel);
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
throw new Error(`MDB | Unknown media type for id ${id}`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { APIModel } from '../APIModel';
|
||||
import { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||
import MediaDbPlugin from '../../main';
|
||||
import { BookModel } from 'src/models/BookModel';
|
||||
import type MediaDbPlugin from '../../main';
|
||||
import type { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||
import { MediaType } from '../../utils/MediaType';
|
||||
import { APIModel } from '../APIModel';
|
||||
|
||||
export class OpenLibraryAPI extends APIModel {
|
||||
plugin: MediaDbPlugin;
|
||||
|
|
@ -42,7 +42,7 @@ export class OpenLibraryAPI extends APIModel {
|
|||
dataSource: this.apiName,
|
||||
id: result.key,
|
||||
author: result.author_name ?? 'unknown',
|
||||
} as BookModel),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -87,6 +87,6 @@ export class OpenLibraryAPI extends APIModel {
|
|||
lastRead: '',
|
||||
personalRating: 0,
|
||||
},
|
||||
} as BookModel);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { APIModel } from '../APIModel';
|
||||
import { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||
import MediaDbPlugin from '../../main';
|
||||
import { GameModel } from '../../models/GameModel';
|
||||
import { requestUrl } from 'obsidian';
|
||||
import type MediaDbPlugin from '../../main';
|
||||
import { GameModel } from '../../models/GameModel';
|
||||
import type { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||
import { MediaType } from '../../utils/MediaType';
|
||||
import { APIModel } from '../APIModel';
|
||||
|
||||
export class SteamAPI extends APIModel {
|
||||
plugin: MediaDbPlugin;
|
||||
|
|
@ -49,7 +49,7 @@ export class SteamAPI extends APIModel {
|
|||
year: '',
|
||||
dataSource: this.apiName,
|
||||
id: result.appid,
|
||||
} as GameModel),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -94,19 +94,19 @@ export class SteamAPI extends APIModel {
|
|||
url: `https://store.steampowered.com/app/${result.steam_appid}`,
|
||||
id: result.steam_appid,
|
||||
|
||||
developers: result['developers'],
|
||||
publishers: result['publishers'],
|
||||
developers: result.developers,
|
||||
publishers: result.publishers,
|
||||
genres: result.genres?.map((x: any) => x.description) ?? [],
|
||||
onlineRating: Number.parseFloat(result.metacritic?.score ?? 0),
|
||||
image: result.header_image ?? '',
|
||||
|
||||
released: !result.release_date?.comming_soon,
|
||||
released: !result.release_date?.coming_soon,
|
||||
releaseDate: this.plugin.dateFormatter.format(result.release_date?.date, this.apiDateFormat) ?? 'unknown',
|
||||
|
||||
userData: {
|
||||
played: false,
|
||||
personalRating: 0,
|
||||
},
|
||||
} as GameModel);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { APIModel } from '../APIModel';
|
||||
import { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||
import MediaDbPlugin from '../../main';
|
||||
import type MediaDbPlugin from '../../main';
|
||||
import type { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||
import { WikiModel } from '../../models/WikiModel';
|
||||
import { MediaType } from '../../utils/MediaType';
|
||||
import { APIModel } from '../APIModel';
|
||||
|
||||
export class WikipediaAPI extends APIModel {
|
||||
plugin: MediaDbPlugin;
|
||||
|
|
@ -42,7 +42,7 @@ export class WikipediaAPI extends APIModel {
|
|||
year: '',
|
||||
dataSource: this.apiName,
|
||||
id: result.pageid,
|
||||
} as WikiModel),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -73,10 +73,10 @@ export class WikipediaAPI extends APIModel {
|
|||
id: result.pageid,
|
||||
|
||||
wikiUrl: result.fullurl,
|
||||
lastUpdated: this.plugin.dateFormatter.format(result.touched, this.apiDateFormat),
|
||||
lastUpdated: this.plugin.dateFormatter.format(result.touched, this.apiDateFormat) ?? undefined,
|
||||
length: result.length,
|
||||
|
||||
userData: {},
|
||||
} as WikiModel);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
122
src/main.ts
122
src/main.ts
|
|
@ -1,33 +1,29 @@
|
|||
import { MarkdownView, Notice, parseYaml, Plugin, stringifyYaml, TFile, TFolder } from 'obsidian';
|
||||
import { getDefaultSettings, MediaDbPluginSettings, MediaDbSettingTab } from './settings/Settings';
|
||||
import type { MediaType } from 'src/utils/MediaType';
|
||||
import { APIManager } from './api/APIManager';
|
||||
import { MediaTypeModel } from './models/MediaTypeModel';
|
||||
import {
|
||||
CreateNoteOptions,
|
||||
dateTimeToString,
|
||||
markdownTable,
|
||||
replaceIllegalFileNameCharactersInString,
|
||||
unCamelCase,
|
||||
hasTemplaterPlugin,
|
||||
useTemplaterPluginInFile,
|
||||
} from './utils/Utils';
|
||||
import { OMDbAPI } from './api/apis/OMDbAPI';
|
||||
import { BoardGameGeekAPI } from './api/apis/BoardGameGeekAPI';
|
||||
import { GiantBombAPI } from './api/apis/GiantBombAPI';
|
||||
import { MALAPI } from './api/apis/MALAPI';
|
||||
import { MALAPIManga } from './api/apis/MALAPIManga';
|
||||
import { WikipediaAPI } from './api/apis/WikipediaAPI';
|
||||
import { MusicBrainzAPI } from './api/apis/MusicBrainzAPI';
|
||||
import { MEDIA_TYPES, MediaTypeManager } from './utils/MediaTypeManager';
|
||||
import { SteamAPI } from './api/apis/SteamAPI';
|
||||
import { BoardGameGeekAPI } from './api/apis/BoardGameGeekAPI';
|
||||
import { OpenLibraryAPI } from './api/apis/OpenLibraryAPI';
|
||||
import { MobyGamesAPI } from './api/apis/MobyGamesAPI';
|
||||
import { MusicBrainzAPI } from './api/apis/MusicBrainzAPI';
|
||||
import { OMDbAPI } from './api/apis/OMDbAPI';
|
||||
import { OpenLibraryAPI } from './api/apis/OpenLibraryAPI';
|
||||
import { SteamAPI } from './api/apis/SteamAPI';
|
||||
import { WikipediaAPI } from './api/apis/WikipediaAPI';
|
||||
import { VNDBAPI } from './api/apis/VNDBAPI';
|
||||
import { PropertyMapper } from './settings/PropertyMapper';
|
||||
import { MediaDbFolderImportModal } from './modals/MediaDbFolderImportModal';
|
||||
import type { MediaTypeModel } from './models/MediaTypeModel';
|
||||
import { PropertyMapper } from './settings/PropertyMapper';
|
||||
import { PropertyMapping, PropertyMappingModel } from './settings/PropertyMapping';
|
||||
import { ModalHelper, ModalResultCode, SearchModalOptions } from './utils/ModalHelper';
|
||||
import type { MediaDbPluginSettings } from './settings/Settings';
|
||||
import { getDefaultSettings, MediaDbSettingTab } from './settings/Settings';
|
||||
import { DateFormatter } from './utils/DateFormatter';
|
||||
import { MediaType } from 'src/utils/MediaType';
|
||||
import { MEDIA_TYPES, MediaTypeManager } from './utils/MediaTypeManager';
|
||||
import type { SearchModalOptions } from './utils/ModalHelper';
|
||||
import { ModalHelper, ModalResultCode } from './utils/ModalHelper';
|
||||
import type { CreateNoteOptions } from './utils/Utils';
|
||||
import { dateTimeToString, markdownTable, replaceIllegalFileNameCharactersInString, unCamelCase, hasTemplaterPlugin, useTemplaterPluginInFile } from './utils/Utils';
|
||||
|
||||
export type Metadata = Record<string, unknown>;
|
||||
|
||||
|
|
@ -38,12 +34,12 @@ export interface MediaTypeModelObj {
|
|||
}
|
||||
|
||||
export default class MediaDbPlugin extends Plugin {
|
||||
settings: MediaDbPluginSettings;
|
||||
apiManager: APIManager;
|
||||
mediaTypeManager: MediaTypeManager;
|
||||
modelPropertyMapper: PropertyMapper;
|
||||
modalHelper: ModalHelper;
|
||||
dateFormatter: DateFormatter;
|
||||
settings!: MediaDbPluginSettings;
|
||||
apiManager!: APIManager;
|
||||
mediaTypeManager!: MediaTypeManager;
|
||||
modelPropertyMapper!: PropertyMapper;
|
||||
modalHelper!: ModalHelper;
|
||||
dateFormatter!: DateFormatter;
|
||||
|
||||
frontMatterRexExpPattern: string = '^(---)\\n[\\s\\S]*?\\n---';
|
||||
|
||||
|
|
@ -59,6 +55,7 @@ export default class MediaDbPlugin extends Plugin {
|
|||
this.apiManager.registerAPI(new BoardGameGeekAPI(this));
|
||||
this.apiManager.registerAPI(new OpenLibraryAPI(this));
|
||||
this.apiManager.registerAPI(new MobyGamesAPI(this));
|
||||
this.apiManager.registerAPI(new GiantBombAPI(this));
|
||||
this.apiManager.registerAPI(new VNDBAPI(this));
|
||||
// this.apiManager.registerAPI(new LocGovAPI(this)); // TODO: parse data
|
||||
|
||||
|
|
@ -165,7 +162,7 @@ export default class MediaDbPlugin extends Plugin {
|
|||
* - maybe custom link syntax
|
||||
*/
|
||||
async createLinkWithSearchModal(): Promise<void> {
|
||||
const apiSearchResults: MediaTypeModel[] = await this.modalHelper.openAdvancedSearchModal({}, async advancedSearchModalData => {
|
||||
const apiSearchResults = await this.modalHelper.openAdvancedSearchModal({}, async advancedSearchModalData => {
|
||||
return await this.apiManager.query(advancedSearchModalData.query, advancedSearchModalData.apis);
|
||||
});
|
||||
|
||||
|
|
@ -173,7 +170,7 @@ export default class MediaDbPlugin extends Plugin {
|
|||
return;
|
||||
}
|
||||
|
||||
const selectResults: MediaTypeModel[] = await this.modalHelper.openSelectModal({ elements: apiSearchResults, multiSelect: false }, async selectModalData => {
|
||||
const selectResults = await this.modalHelper.openSelectModal({ elements: apiSearchResults, multiSelect: false }, async selectModalData => {
|
||||
return await this.queryDetails(selectModalData.selected);
|
||||
});
|
||||
|
||||
|
|
@ -193,7 +190,7 @@ export default class MediaDbPlugin extends Plugin {
|
|||
|
||||
async createEntryWithSearchModal(searchModalOptions?: SearchModalOptions): Promise<void> {
|
||||
let types: string[] = [];
|
||||
let apiSearchResults: MediaTypeModel[] = await this.modalHelper.openSearchModal(searchModalOptions ?? {}, async searchModalData => {
|
||||
let apiSearchResults = await this.modalHelper.openSearchModal(searchModalOptions ?? {}, async searchModalData => {
|
||||
types = searchModalData.types;
|
||||
const apis = this.apiManager.apis.filter(x => x.hasTypeOverlap(searchModalData.types)).map(x => x.apiName);
|
||||
try {
|
||||
|
|
@ -214,12 +211,13 @@ export default class MediaDbPlugin extends Plugin {
|
|||
apiSearchResults = apiSearchResults.filter(x => types.contains(x.type));
|
||||
|
||||
let selectResults: MediaTypeModel[];
|
||||
let proceed: boolean;
|
||||
let proceed: boolean = false;
|
||||
|
||||
while (!proceed) {
|
||||
selectResults = await this.modalHelper.openSelectModal({ elements: apiSearchResults }, async selectModalData => {
|
||||
return await this.queryDetails(selectModalData.selected);
|
||||
});
|
||||
selectResults =
|
||||
(await this.modalHelper.openSelectModal({ elements: apiSearchResults }, async selectModalData => {
|
||||
return await this.queryDetails(selectModalData.selected);
|
||||
})) ?? [];
|
||||
if (!selectResults) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -229,11 +227,11 @@ export default class MediaDbPlugin extends Plugin {
|
|||
});
|
||||
}
|
||||
|
||||
await this.createMediaDbNotes(selectResults);
|
||||
await this.createMediaDbNotes(selectResults!);
|
||||
}
|
||||
|
||||
async createEntryWithAdvancedSearchModal(): Promise<void> {
|
||||
const apiSearchResults: MediaTypeModel[] = await this.modalHelper.openAdvancedSearchModal({}, async advancedSearchModalData => {
|
||||
const apiSearchResults = await this.modalHelper.openAdvancedSearchModal({}, async advancedSearchModalData => {
|
||||
return await this.apiManager.query(advancedSearchModalData.query, advancedSearchModalData.apis);
|
||||
});
|
||||
|
||||
|
|
@ -243,12 +241,13 @@ export default class MediaDbPlugin extends Plugin {
|
|||
}
|
||||
|
||||
let selectResults: MediaTypeModel[];
|
||||
let proceed: boolean;
|
||||
let proceed: boolean = false;
|
||||
|
||||
while (!proceed) {
|
||||
selectResults = await this.modalHelper.openSelectModal({ elements: apiSearchResults }, async selectModalData => {
|
||||
return await this.queryDetails(selectModalData.selected);
|
||||
});
|
||||
selectResults =
|
||||
(await this.modalHelper.openSelectModal({ elements: apiSearchResults }, async selectModalData => {
|
||||
return await this.queryDetails(selectModalData.selected);
|
||||
})) ?? [];
|
||||
if (!selectResults) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -258,12 +257,12 @@ export default class MediaDbPlugin extends Plugin {
|
|||
});
|
||||
}
|
||||
|
||||
await this.createMediaDbNotes(selectResults);
|
||||
await this.createMediaDbNotes(selectResults!);
|
||||
}
|
||||
|
||||
async createEntryWithIdSearchModal(): Promise<void> {
|
||||
let idSearchResult: MediaTypeModel;
|
||||
let proceed: boolean;
|
||||
let idSearchResult: MediaTypeModel | undefined = undefined;
|
||||
let proceed: boolean = false;
|
||||
|
||||
while (!proceed) {
|
||||
idSearchResult = await this.modalHelper.openIdSearchModal({}, async idSearchModalData => {
|
||||
|
|
@ -278,6 +277,9 @@ export default class MediaDbPlugin extends Plugin {
|
|||
});
|
||||
}
|
||||
|
||||
if (!idSearchResult) {
|
||||
return;
|
||||
}
|
||||
await this.createMediaDbNoteFromModel(idSearchResult, { attachTemplate: true, openNote: true });
|
||||
}
|
||||
|
||||
|
|
@ -290,11 +292,9 @@ export default class MediaDbPlugin extends Plugin {
|
|||
async queryDetails(models: MediaTypeModel[]): Promise<MediaTypeModel[]> {
|
||||
const detailModels: MediaTypeModel[] = [];
|
||||
for (const model of models) {
|
||||
try {
|
||||
detailModels.push(await this.apiManager.queryDetailedInfo(model));
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
new Notice(e.toString());
|
||||
const res = await this.apiManager.queryDetailedInfo(model);
|
||||
if (res) {
|
||||
detailModels.push(res);
|
||||
}
|
||||
}
|
||||
return detailModels;
|
||||
|
|
@ -319,7 +319,7 @@ export default class MediaDbPlugin extends Plugin {
|
|||
}
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
new Notice(e.toString());
|
||||
new Notice(`${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -383,7 +383,7 @@ export default class MediaDbPlugin extends Plugin {
|
|||
|
||||
// Updating a previous file
|
||||
if (options.attachFile) {
|
||||
const previousMetadata = this.app.metadataCache.getFileCache(options.attachFile).frontmatter;
|
||||
const previousMetadata = this.app.metadataCache.getFileCache(options.attachFile)?.frontmatter ?? {};
|
||||
|
||||
// Use contents (below front matter) from previous file
|
||||
fileContent = await this.app.vault.read(options.attachFile);
|
||||
|
|
@ -443,7 +443,7 @@ export default class MediaDbPlugin extends Plugin {
|
|||
return { fileMetadata: fileMetadata, fileContent: fileContent };
|
||||
}
|
||||
|
||||
async attachTemplate(fileMetadata: Metadata, fileContent: string, template: string): Promise<{ fileMetadata: Metadata; fileContent: string }> {
|
||||
async attachTemplate(fileMetadata: Metadata, fileContent: string, template: string | undefined): Promise<{ fileMetadata: Metadata; fileContent: string }> {
|
||||
if (!template) {
|
||||
return { fileMetadata: fileMetadata, fileContent: fileContent };
|
||||
}
|
||||
|
|
@ -486,7 +486,7 @@ export default class MediaDbPlugin extends Plugin {
|
|||
}
|
||||
|
||||
getMetadataFromFileCache(file: TFile): Metadata {
|
||||
const metadata: Metadata | undefined = this.app.metadataCache.getFileCache(file).frontmatter;
|
||||
const metadata: Metadata | undefined = this.app.metadataCache.getFileCache(file)?.frontmatter;
|
||||
return structuredClone(metadata ?? {});
|
||||
}
|
||||
|
||||
|
|
@ -501,6 +501,10 @@ export default class MediaDbPlugin extends Plugin {
|
|||
// find and possibly create the folder set in settings or passed in folder
|
||||
const folder = options.folder ?? this.app.vault.getAbstractFileByPath('/');
|
||||
|
||||
if (!folder || !(folder instanceof TFolder)) {
|
||||
throw new Error('MDB | invalid folder');
|
||||
}
|
||||
|
||||
fileName = replaceIllegalFileNameCharactersInString(fileName);
|
||||
const filePath = `${folder.path}/${fileName}.md`;
|
||||
|
||||
|
|
@ -519,7 +523,7 @@ export default class MediaDbPlugin extends Plugin {
|
|||
const activeLeaf = this.app.workspace.getUnpinnedLeaf();
|
||||
if (!activeLeaf) {
|
||||
console.warn('MDB | no active leaf, not opening newly created note');
|
||||
return;
|
||||
return targetFile;
|
||||
}
|
||||
await activeLeaf.openFile(targetFile, { state: { mode: 'source' } });
|
||||
}
|
||||
|
|
@ -532,7 +536,7 @@ export default class MediaDbPlugin extends Plugin {
|
|||
* Tries to read the type, id and dataSource of the active note. If successful it will query the api, delete the old note and create a new one.
|
||||
*/
|
||||
async updateActiveNote(onlyMetadata: boolean = false): Promise<void> {
|
||||
const activeFile: TFile = this.app.workspace.getActiveFile();
|
||||
const activeFile = this.app.workspace.getActiveFile() ?? undefined;
|
||||
if (!activeFile) {
|
||||
throw new Error('MDB | there is no active note');
|
||||
}
|
||||
|
|
@ -560,9 +564,9 @@ export default class MediaDbPlugin extends Plugin {
|
|||
// console.debug(newMediaTypeModel);
|
||||
|
||||
if (onlyMetadata) {
|
||||
await this.createMediaDbNoteFromModel(newMediaTypeModel, { attachFile: activeFile, folder: activeFile.parent, openNote: true });
|
||||
await this.createMediaDbNoteFromModel(newMediaTypeModel, { attachFile: activeFile, folder: activeFile.parent ?? undefined, openNote: true });
|
||||
} else {
|
||||
await this.createMediaDbNoteFromModel(newMediaTypeModel, { attachTemplate: true, folder: activeFile.parent, openNote: true });
|
||||
await this.createMediaDbNoteFromModel(newMediaTypeModel, { attachTemplate: true, folder: activeFile.parent ?? undefined, openNote: true });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -596,7 +600,7 @@ export default class MediaDbPlugin extends Plugin {
|
|||
try {
|
||||
results = await this.apiManager.query(title, [selectedAPI]);
|
||||
} catch (e) {
|
||||
erroredFiles.push({ filePath: file.path, error: e.toString() });
|
||||
erroredFiles.push({ filePath: file.path, error: `${e}` });
|
||||
continue;
|
||||
}
|
||||
if (!results || results.length === 0) {
|
||||
|
|
@ -631,7 +635,7 @@ export default class MediaDbPlugin extends Plugin {
|
|||
}
|
||||
|
||||
const detailedResults = await this.queryDetails(selectModalResult.data.selected);
|
||||
await this.createMediaDbNotes(detailedResults, appendContent ? file : null);
|
||||
await this.createMediaDbNotes(detailedResults, appendContent ? file : undefined);
|
||||
|
||||
selectModal.close();
|
||||
}
|
||||
|
|
@ -661,7 +665,7 @@ export default class MediaDbPlugin extends Plugin {
|
|||
// migrate the settings loaded from the disk to match the structure of the default settings
|
||||
const newPropertyMappings: PropertyMappingModel[] = [];
|
||||
for (const defaultPropertyMappingModel of defaultSettings.propertyMappingModels) {
|
||||
const newPropertyMappingModel: PropertyMappingModel = loadedSettings.propertyMappingModels.find(x => x.type === defaultPropertyMappingModel.type);
|
||||
const newPropertyMappingModel = loadedSettings.propertyMappingModels.find(x => x.type === defaultPropertyMappingModel.type);
|
||||
if (newPropertyMappingModel === undefined) {
|
||||
// if the propertyMappingModel exists in the default settings but not the loaded settings, add it
|
||||
newPropertyMappings.push(defaultPropertyMappingModel);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { ButtonComponent, Modal, Notice, Setting, TextComponent, ToggleComponent } from 'obsidian';
|
||||
import { MediaTypeModel } from '../models/MediaTypeModel';
|
||||
import MediaDbPlugin from '../main';
|
||||
import { ADVANCED_SEARCH_MODAL_DEFAULT_OPTIONS, AdvancedSearchModalData, AdvancedSearchModalOptions } from '../utils/ModalHelper';
|
||||
import type { ButtonComponent } from 'obsidian';
|
||||
import { Modal, Notice, Setting, TextComponent, ToggleComponent } from 'obsidian';
|
||||
import type MediaDbPlugin from '../main';
|
||||
import type { MediaTypeModel } from '../models/MediaTypeModel';
|
||||
import type { AdvancedSearchModalData, AdvancedSearchModalOptions } from '../utils/ModalHelper';
|
||||
import { ADVANCED_SEARCH_MODAL_DEFAULT_OPTIONS } from '../utils/ModalHelper';
|
||||
|
||||
export class MediaDbAdvancedSearchModal extends Modal {
|
||||
plugin: MediaDbPlugin;
|
||||
|
|
@ -9,9 +11,9 @@ export class MediaDbAdvancedSearchModal extends Modal {
|
|||
query: string;
|
||||
isBusy: boolean;
|
||||
title: string;
|
||||
selectedApis: { name: string; selected: boolean }[];
|
||||
selectedApis: string[];
|
||||
|
||||
searchBtn: ButtonComponent;
|
||||
searchBtn?: ButtonComponent;
|
||||
|
||||
submitCallback?: (res: AdvancedSearchModalData) => void;
|
||||
closeCallback?: (err?: Error) => void;
|
||||
|
|
@ -22,12 +24,9 @@ export class MediaDbAdvancedSearchModal extends Modal {
|
|||
|
||||
this.plugin = plugin;
|
||||
this.selectedApis = [];
|
||||
this.title = advancedSearchModalOptions.modalTitle;
|
||||
this.query = advancedSearchModalOptions.prefilledSearchString;
|
||||
|
||||
for (const api of this.plugin.apiManager.apis) {
|
||||
this.selectedApis.push({ name: api.apiName, selected: advancedSearchModalOptions.preselectedAPIs.contains(api.apiName) });
|
||||
}
|
||||
this.title = advancedSearchModalOptions.modalTitle ?? '';
|
||||
this.query = advancedSearchModalOptions.prefilledSearchString ?? '';
|
||||
this.isBusy = false;
|
||||
}
|
||||
|
||||
setSubmitCallback(submitCallback: (res: AdvancedSearchModalData) => void): void {
|
||||
|
|
@ -44,13 +43,13 @@ export class MediaDbAdvancedSearchModal extends Modal {
|
|||
}
|
||||
}
|
||||
|
||||
async search(): Promise<MediaTypeModel[]> {
|
||||
async search(): Promise<void> {
|
||||
if (!this.query || this.query.length < 3) {
|
||||
new Notice('MDB | Query too short');
|
||||
return;
|
||||
}
|
||||
|
||||
const apis: string[] = this.selectedApis.filter(x => x.selected).map(x => x.name);
|
||||
const apis: string[] = this.selectedApis;
|
||||
|
||||
if (apis.length === 0) {
|
||||
new Notice('MDB | No API selected');
|
||||
|
|
@ -59,10 +58,10 @@ export class MediaDbAdvancedSearchModal extends Modal {
|
|||
|
||||
if (!this.isBusy) {
|
||||
this.isBusy = true;
|
||||
this.searchBtn.setDisabled(false);
|
||||
this.searchBtn.setButtonText('Searching...');
|
||||
this.searchBtn?.setDisabled(false);
|
||||
this.searchBtn?.setButtonText('Searching...');
|
||||
|
||||
this.submitCallback({ query: this.query, apis: apis });
|
||||
this.submitCallback?.({ query: this.query, apis: apis });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -97,9 +96,13 @@ export class MediaDbAdvancedSearchModal extends Modal {
|
|||
|
||||
const apiToggleComponent = new ToggleComponent(apiToggleComponentWrapper);
|
||||
apiToggleComponent.setTooltip(api.apiName);
|
||||
apiToggleComponent.setValue(this.selectedApis.find(x => x.name === api.apiName).selected);
|
||||
apiToggleComponent.setValue(this.selectedApis.some(x => x === api.apiName));
|
||||
apiToggleComponent.onChange(value => {
|
||||
this.selectedApis.find(x => x.name === api.apiName).selected = value;
|
||||
if (value) {
|
||||
this.selectedApis.push(api.apiName);
|
||||
} else {
|
||||
this.selectedApis = this.selectedApis.filter(x => x !== api.apiName);
|
||||
}
|
||||
});
|
||||
apiToggleComponentWrapper.appendChild(apiToggleComponent.toggleEl);
|
||||
}
|
||||
|
|
@ -124,7 +127,7 @@ export class MediaDbAdvancedSearchModal extends Modal {
|
|||
}
|
||||
|
||||
onClose(): void {
|
||||
this.closeCallback();
|
||||
this.closeCallback?.();
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import { App, ButtonComponent, DropdownComponent, Modal, Setting, TextComponent, ToggleComponent } from 'obsidian';
|
||||
import MediaDbPlugin from '../main';
|
||||
import type { App, ButtonComponent } from 'obsidian';
|
||||
import { DropdownComponent, Modal, Setting, TextComponent, ToggleComponent } from 'obsidian';
|
||||
import type MediaDbPlugin from '../main';
|
||||
|
||||
export class MediaDbFolderImportModal extends Modal {
|
||||
plugin: MediaDbPlugin;
|
||||
onSubmit: (selectedAPI: string, titleFieldName: string, appendContent: boolean) => void;
|
||||
selectedApi: string;
|
||||
searchBtn: ButtonComponent;
|
||||
searchBtn?: ButtonComponent;
|
||||
titleFieldName: string;
|
||||
appendContent: boolean;
|
||||
|
||||
|
|
@ -14,6 +15,8 @@ export class MediaDbFolderImportModal extends Modal {
|
|||
this.plugin = plugin;
|
||||
this.onSubmit = onSubmit;
|
||||
this.selectedApi = plugin.apiManager.apis[0].apiName;
|
||||
this.titleFieldName = '';
|
||||
this.appendContent = false;
|
||||
}
|
||||
|
||||
submit(): void {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { ButtonComponent, DropdownComponent, Modal, Notice, Setting, TextComponent } from 'obsidian';
|
||||
import { MediaTypeModel } from '../models/MediaTypeModel';
|
||||
import MediaDbPlugin from '../main';
|
||||
import { ID_SEARCH_MODAL_DEFAULT_OPTIONS, IdSearchModalData, IdSearchModalOptions } from '../utils/ModalHelper';
|
||||
import type { ButtonComponent } from 'obsidian';
|
||||
import { DropdownComponent, Modal, Notice, Setting, TextComponent } from 'obsidian';
|
||||
import type MediaDbPlugin from '../main';
|
||||
import type { MediaTypeModel } from '../models/MediaTypeModel';
|
||||
import type { IdSearchModalData, IdSearchModalOptions } from '../utils/ModalHelper';
|
||||
import { ID_SEARCH_MODAL_DEFAULT_OPTIONS } from '../utils/ModalHelper';
|
||||
|
||||
export class MediaDbIdSearchModal extends Modal {
|
||||
plugin: MediaDbPlugin;
|
||||
|
|
@ -11,7 +13,7 @@ export class MediaDbIdSearchModal extends Modal {
|
|||
title: string;
|
||||
selectedApi: string;
|
||||
|
||||
searchBtn: ButtonComponent;
|
||||
searchBtn?: ButtonComponent;
|
||||
|
||||
submitCallback?: (res: IdSearchModalData, err?: Error) => void;
|
||||
closeCallback?: (err?: Error) => void;
|
||||
|
|
@ -21,8 +23,10 @@ export class MediaDbIdSearchModal extends Modal {
|
|||
super(plugin.app);
|
||||
|
||||
this.plugin = plugin;
|
||||
this.title = idSearchModalOptions.modalTitle;
|
||||
this.title = idSearchModalOptions.modalTitle ?? '';
|
||||
this.selectedApi = idSearchModalOptions.preselectedAPI || plugin.apiManager.apis[0].apiName;
|
||||
this.query = '';
|
||||
this.isBusy = false;
|
||||
}
|
||||
|
||||
setSubmitCallback(submitCallback: (res: IdSearchModalData, err?: Error) => void): void {
|
||||
|
|
@ -39,7 +43,7 @@ export class MediaDbIdSearchModal extends Modal {
|
|||
}
|
||||
}
|
||||
|
||||
async search(): Promise<MediaTypeModel> {
|
||||
async search(): Promise<void> {
|
||||
if (!this.query) {
|
||||
new Notice('MDB | no Id entered');
|
||||
return;
|
||||
|
|
@ -52,10 +56,10 @@ export class MediaDbIdSearchModal extends Modal {
|
|||
|
||||
if (!this.isBusy) {
|
||||
this.isBusy = true;
|
||||
this.searchBtn.setDisabled(false);
|
||||
this.searchBtn.setButtonText('Searching...');
|
||||
this.searchBtn?.setDisabled(false);
|
||||
this.searchBtn?.setButtonText('Searching...');
|
||||
|
||||
this.submitCallback({ query: this.query, api: this.selectedApi });
|
||||
this.submitCallback?.({ query: this.query, api: this.selectedApi });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +113,7 @@ export class MediaDbIdSearchModal extends Modal {
|
|||
}
|
||||
|
||||
onClose(): void {
|
||||
this.closeCallback();
|
||||
this.closeCallback?.();
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,19 @@
|
|||
import { ButtonComponent, Component, MarkdownRenderer, Modal, Setting } from 'obsidian';
|
||||
import MediaDbPlugin from 'src/main';
|
||||
import { MediaTypeModel } from 'src/models/MediaTypeModel';
|
||||
import { PREVIEW_MODAL_DEFAULT_OPTIONS, PreviewModalData, PreviewModalOptions } from '../utils/ModalHelper';
|
||||
import { CreateNoteOptions } from '../utils/Utils';
|
||||
import type { ButtonComponent } from 'obsidian';
|
||||
import { Component, MarkdownRenderer, Modal, Setting } from 'obsidian';
|
||||
import type MediaDbPlugin from 'src/main';
|
||||
import type { MediaTypeModel } from 'src/models/MediaTypeModel';
|
||||
import type { PreviewModalData, PreviewModalOptions } from '../utils/ModalHelper';
|
||||
import { PREVIEW_MODAL_DEFAULT_OPTIONS } from '../utils/ModalHelper';
|
||||
|
||||
export class MediaDbPreviewModal extends Modal {
|
||||
plugin: MediaDbPlugin;
|
||||
|
||||
createNoteOptions: CreateNoteOptions;
|
||||
elements: MediaTypeModel[];
|
||||
isBusy: boolean;
|
||||
title: string;
|
||||
cancelButton: ButtonComponent;
|
||||
submitButton: ButtonComponent;
|
||||
markdownComponent: Component;
|
||||
|
||||
submitCallback: (previewModalData: PreviewModalData) => void;
|
||||
closeCallback: (err?: Error) => void;
|
||||
submitCallback?: (previewModalData: PreviewModalData) => void;
|
||||
closeCallback?: (err?: Error) => void;
|
||||
|
||||
constructor(plugin: MediaDbPlugin, previewModalOptions: PreviewModalOptions) {
|
||||
previewModalOptions = Object.assign({}, PREVIEW_MODAL_DEFAULT_OPTIONS, previewModalOptions);
|
||||
|
|
@ -24,8 +21,8 @@ export class MediaDbPreviewModal extends Modal {
|
|||
super(plugin.app);
|
||||
|
||||
this.plugin = plugin;
|
||||
this.title = previewModalOptions.modalTitle;
|
||||
this.elements = previewModalOptions.elements;
|
||||
this.title = previewModalOptions.modalTitle ?? '';
|
||||
this.elements = previewModalOptions.elements ?? [];
|
||||
|
||||
this.markdownComponent = new Component();
|
||||
}
|
||||
|
|
@ -70,14 +67,12 @@ export class MediaDbPreviewModal extends Modal {
|
|||
btn.setButtonText('Cancel');
|
||||
btn.onClick(() => this.close());
|
||||
btn.buttonEl.addClass('media-db-plugin-button');
|
||||
this.cancelButton = btn;
|
||||
});
|
||||
bottomSettingRow.addButton(btn => {
|
||||
btn.setButtonText('Ok');
|
||||
btn.setCta();
|
||||
btn.onClick(() => this.submitCallback({ confirmed: true }));
|
||||
btn.onClick(() => this.submitCallback?.({ confirmed: true }));
|
||||
btn.buttonEl.addClass('media-db-plugin-button');
|
||||
this.submitButton = btn;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -87,6 +82,6 @@ export class MediaDbPreviewModal extends Modal {
|
|||
|
||||
onClose(): void {
|
||||
this.markdownComponent.unload();
|
||||
this.closeCallback();
|
||||
this.closeCallback?.();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import { ButtonComponent, Modal, Notice, Setting, TextComponent, ToggleComponent } from 'obsidian';
|
||||
import { MediaTypeModel } from '../models/MediaTypeModel';
|
||||
import MediaDbPlugin from '../main';
|
||||
import { SEARCH_MODAL_DEFAULT_OPTIONS, SearchModalData, SearchModalOptions } from '../utils/ModalHelper';
|
||||
import type { ButtonComponent } from 'obsidian';
|
||||
import { Modal, Notice, Setting, TextComponent, ToggleComponent } from 'obsidian';
|
||||
import type MediaDbPlugin from '../main';
|
||||
import type { MediaTypeModel } from '../models/MediaTypeModel';
|
||||
import type { MediaType } from '../utils/MediaType';
|
||||
import { MEDIA_TYPES } from '../utils/MediaTypeManager';
|
||||
import type { SearchModalData, SearchModalOptions } from '../utils/ModalHelper';
|
||||
import { SEARCH_MODAL_DEFAULT_OPTIONS } from '../utils/ModalHelper';
|
||||
import { unCamelCase } from '../utils/Utils';
|
||||
import { MediaType } from '../utils/MediaType';
|
||||
|
||||
export class MediaDbSearchModal extends Modal {
|
||||
plugin: MediaDbPlugin;
|
||||
|
|
@ -12,9 +14,9 @@ export class MediaDbSearchModal extends Modal {
|
|||
query: string;
|
||||
isBusy: boolean;
|
||||
title: string;
|
||||
selectedTypes: { name: MediaType; selected: boolean }[];
|
||||
selectedTypes: MediaType[];
|
||||
|
||||
searchBtn: ButtonComponent;
|
||||
searchBtn?: ButtonComponent;
|
||||
|
||||
submitCallback?: (res: SearchModalData) => void;
|
||||
closeCallback?: (err?: Error) => void;
|
||||
|
|
@ -24,13 +26,10 @@ export class MediaDbSearchModal extends Modal {
|
|||
super(plugin.app);
|
||||
|
||||
this.plugin = plugin;
|
||||
this.selectedTypes = [];
|
||||
this.title = searchModalOptions.modalTitle;
|
||||
this.query = searchModalOptions.prefilledSearchString;
|
||||
|
||||
for (const mediaType of MEDIA_TYPES) {
|
||||
this.selectedTypes.push({ name: mediaType, selected: searchModalOptions.preselectedTypes.contains(mediaType) });
|
||||
}
|
||||
this.selectedTypes = [...(searchModalOptions.preselectedTypes ?? [])];
|
||||
this.title = searchModalOptions.modalTitle ?? '';
|
||||
this.query = searchModalOptions.prefilledSearchString ?? '';
|
||||
this.isBusy = false;
|
||||
}
|
||||
|
||||
setSubmitCallback(submitCallback: (res: SearchModalData) => void): void {
|
||||
|
|
@ -47,13 +46,13 @@ export class MediaDbSearchModal extends Modal {
|
|||
}
|
||||
}
|
||||
|
||||
async search(): Promise<MediaTypeModel[]> {
|
||||
async search(): Promise<void> {
|
||||
if (!this.query || this.query.length < 3) {
|
||||
new Notice('MDB | Query too short');
|
||||
return;
|
||||
}
|
||||
|
||||
const types: MediaType[] = this.selectedTypes.filter(x => x.selected).map(x => x.name);
|
||||
const types: MediaType[] = this.selectedTypes;
|
||||
|
||||
if (types.length === 0) {
|
||||
new Notice('MDB | No Type selected');
|
||||
|
|
@ -62,10 +61,10 @@ export class MediaDbSearchModal extends Modal {
|
|||
|
||||
if (!this.isBusy) {
|
||||
this.isBusy = true;
|
||||
this.searchBtn.setDisabled(false);
|
||||
this.searchBtn.setButtonText('Searching...');
|
||||
this.searchBtn?.setDisabled(false);
|
||||
this.searchBtn?.setButtonText('Searching...');
|
||||
|
||||
this.submitCallback({ query: this.query, types: types });
|
||||
this.submitCallback?.({ query: this.query, types: types });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +75,7 @@ export class MediaDbSearchModal extends Modal {
|
|||
|
||||
const placeholder = 'Search by title';
|
||||
const searchComponent = new TextComponent(contentEl);
|
||||
let currentToggle: ToggleComponent = null;
|
||||
let currentToggle: ToggleComponent | undefined = undefined;
|
||||
|
||||
searchComponent.inputEl.style.width = '100%';
|
||||
searchComponent.setPlaceholder(placeholder);
|
||||
|
|
@ -100,7 +99,7 @@ export class MediaDbSearchModal extends Modal {
|
|||
|
||||
const apiToggleComponent = new ToggleComponent(apiToggleComponentWrapper);
|
||||
apiToggleComponent.setTooltip(unCamelCase(mediaType));
|
||||
apiToggleComponent.setValue(this.selectedTypes.find(x => x.name === mediaType).selected);
|
||||
apiToggleComponent.setValue(this.selectedTypes.contains(mediaType));
|
||||
if (apiToggleComponent.getValue()) {
|
||||
currentToggle = apiToggleComponent;
|
||||
}
|
||||
|
|
@ -108,13 +107,13 @@ export class MediaDbSearchModal extends Modal {
|
|||
if (value) {
|
||||
if (currentToggle && currentToggle !== apiToggleComponent) {
|
||||
currentToggle.setValue(false);
|
||||
this.selectedTypes.find(x => x.name === mediaType).selected = false;
|
||||
this.selectedTypes = this.selectedTypes.filter(x => x !== mediaType);
|
||||
}
|
||||
currentToggle = apiToggleComponent;
|
||||
this.selectedTypes.find(x => x.name === mediaType).selected = true;
|
||||
this.selectedTypes.push(mediaType);
|
||||
} else {
|
||||
currentToggle = null;
|
||||
this.selectedTypes.find(x => x.name === mediaType).selected = false;
|
||||
currentToggle = undefined;
|
||||
this.selectedTypes = this.selectedTypes.filter(x => x !== mediaType);
|
||||
}
|
||||
});
|
||||
apiToggleComponentWrapper.appendChild(apiToggleComponent.toggleEl);
|
||||
|
|
@ -140,7 +139,7 @@ export class MediaDbSearchModal extends Modal {
|
|||
}
|
||||
|
||||
onClose(): void {
|
||||
this.closeCallback();
|
||||
this.closeCallback?.();
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { MediaTypeModel } from '../models/MediaTypeModel';
|
||||
import MediaDbPlugin from '../main';
|
||||
import type MediaDbPlugin from '../main';
|
||||
import type { MediaTypeModel } from '../models/MediaTypeModel';
|
||||
import type { SelectModalData, SelectModalOptions } from '../utils/ModalHelper';
|
||||
import { SELECT_MODAL_OPTIONS_DEFAULT } from '../utils/ModalHelper';
|
||||
import { SelectModal } from './SelectModal';
|
||||
import { SELECT_MODAL_OPTIONS_DEFAULT, SelectModalData, SelectModalOptions } from '../utils/ModalHelper';
|
||||
|
||||
export class MediaDbSearchResultModal extends SelectModal<MediaTypeModel> {
|
||||
plugin: MediaDbPlugin;
|
||||
|
|
@ -9,18 +10,18 @@ export class MediaDbSearchResultModal extends SelectModal<MediaTypeModel> {
|
|||
busy: boolean;
|
||||
sendCallback: boolean;
|
||||
|
||||
submitCallback: (res: SelectModalData) => void;
|
||||
closeCallback: (err?: Error) => void;
|
||||
skipCallback: () => void;
|
||||
submitCallback?: (res: SelectModalData) => void;
|
||||
closeCallback?: (err?: Error) => void;
|
||||
skipCallback?: () => void;
|
||||
|
||||
constructor(plugin: MediaDbPlugin, selectModalOptions: SelectModalOptions) {
|
||||
selectModalOptions = Object.assign({}, SELECT_MODAL_OPTIONS_DEFAULT, selectModalOptions);
|
||||
super(plugin.app, selectModalOptions.elements, selectModalOptions.multiSelect);
|
||||
super(plugin.app, selectModalOptions.elements ?? [], selectModalOptions.multiSelect);
|
||||
this.plugin = plugin;
|
||||
|
||||
this.title = selectModalOptions.modalTitle;
|
||||
this.title = selectModalOptions.modalTitle ?? '';
|
||||
this.description = 'Select one or multiple search results.';
|
||||
this.addSkipButton = selectModalOptions.skipButton;
|
||||
this.addSkipButton = selectModalOptions.skipButton ?? false;
|
||||
|
||||
this.busy = false;
|
||||
|
||||
|
|
@ -50,17 +51,17 @@ export class MediaDbSearchResultModal extends SelectModal<MediaTypeModel> {
|
|||
submit(): void {
|
||||
if (!this.busy) {
|
||||
this.busy = true;
|
||||
this.submitButton.setButtonText('Creating entry...');
|
||||
this.submitCallback({ selected: this.selectModalElements.filter(x => x.isActive()).map(x => x.value) });
|
||||
this.submitButton?.setButtonText('Creating entry...');
|
||||
this.submitCallback?.({ selected: this.selectModalElements.filter(x => x.isActive()).map(x => x.value) });
|
||||
}
|
||||
}
|
||||
|
||||
skip(): void {
|
||||
this.skipButton.setButtonText('Skipping...');
|
||||
this.skipCallback();
|
||||
this.skipButton?.setButtonText('Skipping...');
|
||||
this.skipCallback?.();
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
this.closeCallback();
|
||||
this.closeCallback?.();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { App, ButtonComponent, Modal, Setting } from 'obsidian';
|
||||
import { SelectModalElement } from './SelectModalElement';
|
||||
import type { App, ButtonComponent } from 'obsidian';
|
||||
import { Modal, Setting } from 'obsidian';
|
||||
import { mod } from '../utils/Utils';
|
||||
import { SelectModalElement } from './SelectModalElement';
|
||||
|
||||
export abstract class SelectModal<T> extends Modal {
|
||||
allowMultiSelect: boolean;
|
||||
|
|
@ -142,7 +143,7 @@ export abstract class SelectModal<T> extends Modal {
|
|||
}
|
||||
|
||||
// nothing is highlighted
|
||||
this.selectModalElements.last().setHighlighted(true);
|
||||
this.selectModalElements.last()?.setHighlighted(true);
|
||||
}
|
||||
|
||||
highlightDown(): void {
|
||||
|
|
@ -154,20 +155,20 @@ export abstract class SelectModal<T> extends Modal {
|
|||
}
|
||||
|
||||
// nothing is highlighted
|
||||
this.selectModalElements.first().setHighlighted(true);
|
||||
this.selectModalElements.first()?.setHighlighted(true);
|
||||
}
|
||||
|
||||
private getNextSelectModalElement(selectModalElement: SelectModalElement<T>): SelectModalElement<T> {
|
||||
let nextId = selectModalElement.id + 1;
|
||||
nextId = mod(nextId, this.selectModalElements.length);
|
||||
|
||||
return this.selectModalElements.filter(x => x.id === nextId).first();
|
||||
return this.selectModalElements.find(x => x.id === nextId)!;
|
||||
}
|
||||
|
||||
private getPreviousSelectModalElement(selectModalElement: SelectModalElement<T>): SelectModalElement<T> {
|
||||
let nextId = selectModalElement.id - 1;
|
||||
nextId = mod(nextId, this.selectModalElements.length);
|
||||
|
||||
return this.selectModalElements.filter(x => x.id === nextId).first();
|
||||
return this.selectModalElements.find(x => x.id === nextId)!;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { SelectModal } from './SelectModal';
|
||||
import type { SelectModal } from './SelectModal';
|
||||
|
||||
export class SelectModalElement<T> {
|
||||
selectModal: SelectModal<T>;
|
||||
|
|
@ -35,6 +35,8 @@ export class SelectModalElement<T> {
|
|||
this.element.on('mouseleave', '#' + this.getHTMLId(), () => {
|
||||
this.setHighlighted(false);
|
||||
});
|
||||
|
||||
this.highlighted = false;
|
||||
}
|
||||
|
||||
getHTMLId(): string {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { MediaTypeModel } from './MediaTypeModel';
|
||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
||||
import { MediaType } from '../utils/MediaType';
|
||||
import type { ModelToData } from '../utils/Utils';
|
||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
||||
import { MediaTypeModel } from './MediaTypeModel';
|
||||
|
||||
export type BoardGameData = ModelToData<BoardGameModel>;
|
||||
|
||||
export class BoardGameModel extends MediaTypeModel {
|
||||
genres: string[];
|
||||
|
|
@ -19,23 +22,23 @@ export class BoardGameModel extends MediaTypeModel {
|
|||
personalRating: number;
|
||||
};
|
||||
|
||||
constructor(obj: any = {}) {
|
||||
constructor(obj: BoardGameData) {
|
||||
super();
|
||||
|
||||
this.genres = undefined;
|
||||
this.onlineRating = undefined;
|
||||
this.minPlayers = undefined;
|
||||
this.maxPlayers = undefined;
|
||||
this.playtime = undefined;
|
||||
this.publishers = undefined;
|
||||
this.complexityRating = undefined;
|
||||
this.image = undefined;
|
||||
this.genres = [];
|
||||
this.onlineRating = 0;
|
||||
this.complexityRating = 0;
|
||||
this.minPlayers = 0;
|
||||
this.maxPlayers = 0;
|
||||
this.playtime = '';
|
||||
this.publishers = [];
|
||||
this.image = '';
|
||||
|
||||
this.released = undefined;
|
||||
this.released = false;
|
||||
|
||||
this.userData = {
|
||||
played: undefined,
|
||||
personalRating: undefined,
|
||||
played: false,
|
||||
personalRating: 0,
|
||||
};
|
||||
|
||||
migrateObject(this, obj, this);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { MediaTypeModel } from './MediaTypeModel';
|
||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
||||
import { MediaType } from '../utils/MediaType';
|
||||
import type { ModelToData } from '../utils/Utils';
|
||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
||||
import { MediaTypeModel } from './MediaTypeModel';
|
||||
|
||||
export type BookData = ModelToData<BookModel>;
|
||||
|
||||
export class BookModel extends MediaTypeModel {
|
||||
author: string;
|
||||
|
|
@ -8,7 +11,6 @@ export class BookModel extends MediaTypeModel {
|
|||
pages: number;
|
||||
image: string;
|
||||
onlineRating: number;
|
||||
english_title: string;
|
||||
isbn: number;
|
||||
isbn13: number;
|
||||
|
||||
|
|
@ -20,22 +22,23 @@ export class BookModel extends MediaTypeModel {
|
|||
personalRating: number;
|
||||
};
|
||||
|
||||
constructor(obj: any = {}) {
|
||||
constructor(obj: BookData) {
|
||||
super();
|
||||
|
||||
this.author = undefined;
|
||||
this.pages = undefined;
|
||||
this.image = undefined;
|
||||
this.onlineRating = undefined;
|
||||
this.isbn = undefined;
|
||||
this.isbn13 = undefined;
|
||||
this.author = '';
|
||||
this.plot = '';
|
||||
this.pages = 0;
|
||||
this.image = '';
|
||||
this.onlineRating = 0;
|
||||
this.isbn = 0;
|
||||
this.isbn13 = 0;
|
||||
|
||||
this.released = undefined;
|
||||
this.released = false;
|
||||
|
||||
this.userData = {
|
||||
read: undefined,
|
||||
lastRead: undefined,
|
||||
personalRating: undefined,
|
||||
read: false,
|
||||
lastRead: '',
|
||||
personalRating: 0,
|
||||
};
|
||||
|
||||
migrateObject(this, obj, this);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { MediaTypeModel } from './MediaTypeModel';
|
||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
||||
import { MediaType } from '../utils/MediaType';
|
||||
import type { ModelToData } from '../utils/Utils';
|
||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
||||
import { MediaTypeModel } from './MediaTypeModel';
|
||||
|
||||
export type GameData = ModelToData<GameModel>;
|
||||
|
||||
export class GameModel extends MediaTypeModel {
|
||||
developers: string[];
|
||||
|
|
@ -17,19 +20,21 @@ export class GameModel extends MediaTypeModel {
|
|||
personalRating: number;
|
||||
};
|
||||
|
||||
constructor(obj: any = {}) {
|
||||
constructor(obj: GameData) {
|
||||
super();
|
||||
|
||||
this.developers = undefined;
|
||||
this.publishers = undefined;
|
||||
this.genres = undefined;
|
||||
this.onlineRating = undefined;
|
||||
this.image = undefined;
|
||||
this.released = undefined;
|
||||
this.releaseDate = undefined;
|
||||
this.developers = [];
|
||||
this.publishers = [];
|
||||
this.genres = [];
|
||||
this.onlineRating = 0;
|
||||
this.image = '';
|
||||
|
||||
this.released = false;
|
||||
this.releaseDate = '';
|
||||
|
||||
this.userData = {
|
||||
played: undefined,
|
||||
personalRating: undefined,
|
||||
played: false,
|
||||
personalRating: 0,
|
||||
};
|
||||
|
||||
migrateObject(this, obj, this);
|
||||
|
|
|
|||
|
|
@ -1,19 +1,13 @@
|
|||
import { MediaTypeModel } from './MediaTypeModel';
|
||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
||||
import { MediaType } from '../utils/MediaType';
|
||||
import type { ModelToData } from '../utils/Utils';
|
||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
||||
import { MediaTypeModel } from './MediaTypeModel';
|
||||
|
||||
export type MangaData = ModelToData<MangaModel>;
|
||||
|
||||
export class MangaModel extends MediaTypeModel {
|
||||
type: string;
|
||||
subType: string;
|
||||
title: string;
|
||||
plot: string;
|
||||
englishTitle: string;
|
||||
alternateTitles: string[];
|
||||
year: string;
|
||||
dataSource: string;
|
||||
url: string;
|
||||
id: string;
|
||||
|
||||
genres: string[];
|
||||
authors: string[];
|
||||
chapters: number;
|
||||
|
|
@ -32,27 +26,27 @@ export class MangaModel extends MediaTypeModel {
|
|||
personalRating: number;
|
||||
};
|
||||
|
||||
constructor(obj: any = {}) {
|
||||
constructor(obj: MangaData) {
|
||||
super();
|
||||
|
||||
this.plot = undefined;
|
||||
this.genres = undefined;
|
||||
this.authors = undefined;
|
||||
this.alternateTitles = undefined;
|
||||
this.chapters = undefined;
|
||||
this.volumes = undefined;
|
||||
this.onlineRating = undefined;
|
||||
this.image = undefined;
|
||||
this.plot = '';
|
||||
this.alternateTitles = [];
|
||||
this.genres = [];
|
||||
this.authors = [];
|
||||
this.chapters = 0;
|
||||
this.volumes = 0;
|
||||
this.onlineRating = 0;
|
||||
this.image = '';
|
||||
|
||||
this.released = undefined;
|
||||
this.status = undefined;
|
||||
this.publishedFrom = undefined;
|
||||
this.publishedTo = undefined;
|
||||
this.released = false;
|
||||
this.status = '';
|
||||
this.publishedFrom = '';
|
||||
this.publishedTo = '';
|
||||
|
||||
this.userData = {
|
||||
watched: undefined,
|
||||
lastWatched: undefined,
|
||||
personalRating: undefined,
|
||||
watched: false,
|
||||
lastWatched: '',
|
||||
personalRating: 0,
|
||||
};
|
||||
|
||||
migrateObject(this, obj, this);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { MediaType } from '../utils/MediaType';
|
||||
import type { MediaType } from '../utils/MediaType';
|
||||
|
||||
export abstract class MediaTypeModel {
|
||||
type: string;
|
||||
|
|
@ -13,14 +13,14 @@ export abstract class MediaTypeModel {
|
|||
userData: object;
|
||||
|
||||
protected constructor() {
|
||||
this.type = undefined;
|
||||
this.subType = undefined;
|
||||
this.title = undefined;
|
||||
this.englishTitle = undefined;
|
||||
this.year = undefined;
|
||||
this.dataSource = undefined;
|
||||
this.url = undefined;
|
||||
this.id = undefined;
|
||||
this.type = '';
|
||||
this.subType = '';
|
||||
this.title = '';
|
||||
this.englishTitle = '';
|
||||
this.year = '';
|
||||
this.dataSource = '';
|
||||
this.url = '';
|
||||
this.id = '';
|
||||
this.userData = {};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { MediaTypeModel } from './MediaTypeModel';
|
||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
||||
import { MediaType } from '../utils/MediaType';
|
||||
import type { ModelToData } from '../utils/Utils';
|
||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
||||
import { MediaTypeModel } from './MediaTypeModel';
|
||||
|
||||
export type MovieData = ModelToData<MovieModel>;
|
||||
|
||||
export class MovieModel extends MediaTypeModel {
|
||||
plot: string;
|
||||
|
|
@ -23,27 +26,27 @@ export class MovieModel extends MediaTypeModel {
|
|||
personalRating: number;
|
||||
};
|
||||
|
||||
constructor(obj: any = {}) {
|
||||
constructor(obj: MovieData) {
|
||||
super();
|
||||
|
||||
this.plot = undefined;
|
||||
this.genres = undefined;
|
||||
this.director = undefined;
|
||||
this.writer = undefined;
|
||||
this.studio = undefined;
|
||||
this.duration = undefined;
|
||||
this.onlineRating = undefined;
|
||||
this.actors = undefined;
|
||||
this.image = undefined;
|
||||
this.plot = '';
|
||||
this.genres = [];
|
||||
this.director = [];
|
||||
this.writer = [];
|
||||
this.studio = [];
|
||||
this.duration = '';
|
||||
this.onlineRating = 0;
|
||||
this.actors = [];
|
||||
this.image = '';
|
||||
|
||||
this.released = undefined;
|
||||
this.streamingServices = undefined;
|
||||
this.premiere = undefined;
|
||||
this.released = false;
|
||||
this.streamingServices = [];
|
||||
this.premiere = '';
|
||||
|
||||
this.userData = {
|
||||
watched: undefined,
|
||||
lastWatched: undefined,
|
||||
personalRating: undefined,
|
||||
watched: false,
|
||||
lastWatched: '',
|
||||
personalRating: 0,
|
||||
};
|
||||
|
||||
migrateObject(this, obj, this);
|
||||
|
|
|
|||
|
|
@ -1,35 +1,29 @@
|
|||
import { MediaTypeModel } from './MediaTypeModel';
|
||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
||||
import { MediaType } from '../utils/MediaType';
|
||||
import type { ModelToData } from '../utils/Utils';
|
||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
||||
import { MediaTypeModel } from './MediaTypeModel';
|
||||
|
||||
export type MusicReleaseData = ModelToData<MusicReleaseModel>;
|
||||
|
||||
export class MusicReleaseModel extends MediaTypeModel {
|
||||
type: string;
|
||||
subType: string;
|
||||
title: string;
|
||||
englishTitle: string;
|
||||
year: string;
|
||||
dataSource: string;
|
||||
url: string;
|
||||
id: string;
|
||||
image: string;
|
||||
|
||||
genres: string[];
|
||||
artists: string[];
|
||||
image: string;
|
||||
rating: number;
|
||||
|
||||
userData: {
|
||||
personalRating: number;
|
||||
};
|
||||
|
||||
constructor(obj: any = {}) {
|
||||
constructor(obj: MusicReleaseData) {
|
||||
super();
|
||||
|
||||
this.genres = undefined;
|
||||
this.artists = undefined;
|
||||
this.image = undefined;
|
||||
this.rating = undefined;
|
||||
this.genres = [];
|
||||
this.artists = [];
|
||||
this.image = '';
|
||||
this.rating = 0;
|
||||
this.userData = {
|
||||
personalRating: undefined,
|
||||
personalRating: 0,
|
||||
};
|
||||
|
||||
migrateObject(this, obj, this);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,11 @@
|
|||
import { MediaTypeModel } from './MediaTypeModel';
|
||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
||||
import { MediaType } from '../utils/MediaType';
|
||||
import type { ModelToData } from '../utils/Utils';
|
||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
||||
import { MediaTypeModel } from './MediaTypeModel';
|
||||
|
||||
export type SeriesData = ModelToData<SeriesModel>;
|
||||
|
||||
export class SeriesModel extends MediaTypeModel {
|
||||
type: string;
|
||||
subType: string;
|
||||
title: string;
|
||||
englishTitle: string;
|
||||
year: string;
|
||||
dataSource: string;
|
||||
url: string;
|
||||
id: string;
|
||||
|
||||
plot: string;
|
||||
genres: string[];
|
||||
writer: string[];
|
||||
|
|
@ -34,29 +28,29 @@ export class SeriesModel extends MediaTypeModel {
|
|||
personalRating: number;
|
||||
};
|
||||
|
||||
constructor(obj: any = {}) {
|
||||
constructor(obj: SeriesData) {
|
||||
super();
|
||||
|
||||
this.plot = undefined;
|
||||
this.genres = undefined;
|
||||
this.writer = undefined;
|
||||
this.studio = undefined;
|
||||
this.episodes = undefined;
|
||||
this.duration = undefined;
|
||||
this.onlineRating = undefined;
|
||||
this.actors = undefined;
|
||||
this.image = undefined;
|
||||
this.plot = '';
|
||||
this.genres = [];
|
||||
this.writer = [];
|
||||
this.studio = [];
|
||||
this.episodes = 0;
|
||||
this.duration = '';
|
||||
this.onlineRating = 0;
|
||||
this.actors = [];
|
||||
this.image = '';
|
||||
|
||||
this.released = undefined;
|
||||
this.streamingServices = undefined;
|
||||
this.airing = undefined;
|
||||
this.airedFrom = undefined;
|
||||
this.airedTo = undefined;
|
||||
this.released = false;
|
||||
this.streamingServices = [];
|
||||
this.airing = false;
|
||||
this.airedFrom = '';
|
||||
this.airedTo = '';
|
||||
|
||||
this.userData = {
|
||||
watched: undefined,
|
||||
lastWatched: undefined,
|
||||
personalRating: undefined,
|
||||
watched: false,
|
||||
lastWatched: '',
|
||||
personalRating: 0,
|
||||
};
|
||||
|
||||
migrateObject(this, obj, this);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,11 @@
|
|||
import { MediaTypeModel } from './MediaTypeModel';
|
||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
||||
import { MediaType } from '../utils/MediaType';
|
||||
import type { ModelToData } from '../utils/Utils';
|
||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
||||
import { MediaTypeModel } from './MediaTypeModel';
|
||||
|
||||
export type WikiData = ModelToData<WikiModel>;
|
||||
|
||||
export class WikiModel extends MediaTypeModel {
|
||||
type: string;
|
||||
subType: string;
|
||||
title: string;
|
||||
englishTitle: string;
|
||||
year: string;
|
||||
dataSource: string;
|
||||
url: string;
|
||||
id: string;
|
||||
|
||||
wikiUrl: string;
|
||||
lastUpdated: string;
|
||||
length: number;
|
||||
|
|
@ -19,13 +13,13 @@ export class WikiModel extends MediaTypeModel {
|
|||
|
||||
userData: Record<string, unknown>;
|
||||
|
||||
constructor(obj: any = {}) {
|
||||
constructor(obj: WikiData) {
|
||||
super();
|
||||
|
||||
this.wikiUrl = undefined;
|
||||
this.lastUpdated = undefined;
|
||||
this.length = undefined;
|
||||
this.article = undefined;
|
||||
this.wikiUrl = '';
|
||||
this.lastUpdated = '';
|
||||
this.length = 0;
|
||||
this.article = '';
|
||||
this.userData = {};
|
||||
|
||||
migrateObject(this, obj, this);
|
||||
|
|
|
|||
|
|
@ -4,13 +4,16 @@
|
|||
import { setIcon } from 'obsidian';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let iconName: string = '';
|
||||
export let iconSize: number = 20;
|
||||
interface Props {
|
||||
iconName?: string;
|
||||
}
|
||||
|
||||
let iconEl: HTMLElement;
|
||||
let { iconName = '' }: Props = $props();
|
||||
|
||||
let iconEl: HTMLElement | undefined = $state();
|
||||
|
||||
onMount(() => {
|
||||
setIcon(iconEl, iconName, iconSize);
|
||||
setIcon(iconEl!, iconName);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { PropertyMappingOption } from './PropertyMapping';
|
||||
import type MediaDbPlugin from '../main';
|
||||
import { MEDIA_TYPES } from '../utils/MediaTypeManager';
|
||||
import MediaDbPlugin from '../main';
|
||||
import { PropertyMappingOption } from './PropertyMapping';
|
||||
|
||||
export class PropertyMapper {
|
||||
plugin: MediaDbPlugin;
|
||||
|
|
@ -66,7 +66,7 @@ export class PropertyMapper {
|
|||
return obj;
|
||||
}
|
||||
|
||||
const propertyMappings = this.plugin.settings.propertyMappingModels.find(x => x.type === obj.type).properties;
|
||||
const propertyMappings = this.plugin.settings.propertyMappingModels.find(x => x.type === obj.type)?.properties ?? [];
|
||||
|
||||
const originalObj: Record<string, unknown> = {};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { MediaType } from '../utils/MediaType';
|
||||
import { containsOnlyLettersAndUnderscores, PropertyMappingNameConflictError, PropertyMappingValidationError } from '../utils/Utils';
|
||||
import { MediaType } from '../utils/MediaType';
|
||||
|
||||
export enum PropertyMappingOption {
|
||||
Default = 'default',
|
||||
|
|
|
|||
|
|
@ -1,18 +1,22 @@
|
|||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import { PropertyMappingModel, PropertyMappingOption, propertyMappingOptions } from './PropertyMapping';
|
||||
import { capitalizeFirstLetter } from '../utils/Utils';
|
||||
import Icon from './Icon.svelte';
|
||||
|
||||
export let model: PropertyMappingModel;
|
||||
export let save: (model: PropertyMappingModel) => void;
|
||||
|
||||
let validationResult: { res: boolean; err?: Error };
|
||||
|
||||
$: modelChanged(model);
|
||||
|
||||
function modelChanged(model: PropertyMappingModel) {
|
||||
validationResult = model.validate();
|
||||
interface Props {
|
||||
model: PropertyMappingModel;
|
||||
save: (model: PropertyMappingModel) => void;
|
||||
}
|
||||
|
||||
let { model, save }: Props = $props();
|
||||
|
||||
let validationResult: { res: boolean; err?: Error } | undefined = $state();
|
||||
|
||||
$effect(() => {
|
||||
validationResult = model.validate();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="media-db-plugin-property-mappings-model-container">
|
||||
|
|
@ -51,7 +55,7 @@
|
|||
{/if}
|
||||
<button
|
||||
class="media-db-plugin-property-mappings-save-button {validationResult?.res ? 'mod-cta' : 'mod-muted'}"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
if (model.validate().res) save(model);
|
||||
}}
|
||||
>Save
|
||||
|
|
|
|||
|
|
@ -2,8 +2,12 @@
|
|||
import { PropertyMappingModel } from './PropertyMapping';
|
||||
import PropertyMappingModelComponent from './PropertyMappingModelComponent.svelte';
|
||||
|
||||
export let models: PropertyMappingModel[] = [];
|
||||
export let save: (model: PropertyMappingModel) => void;
|
||||
interface Props {
|
||||
models?: PropertyMappingModel[];
|
||||
save: (model: PropertyMappingModel) => void;
|
||||
}
|
||||
|
||||
let { models = [], save }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="setting-item" style="display: flex; gap: 10px; flex-direction: column; align-items: stretch;">
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
import { App, Notice, PluginSettingTab, Setting } from 'obsidian';
|
||||
|
||||
import MediaDbPlugin from '../main';
|
||||
import { FolderSuggest } from './suggesters/FolderSuggest';
|
||||
import { FileSuggest } from './suggesters/FileSuggest';
|
||||
import PropertyMappingModelsComponent from './PropertyMappingModelsComponent.svelte';
|
||||
import { PropertyMapping, PropertyMappingModel, PropertyMappingOption } from './PropertyMapping';
|
||||
import type { App } from 'obsidian';
|
||||
import { Notice, PluginSettingTab, Setting } from 'obsidian';
|
||||
import { mount } from 'svelte';
|
||||
import type MediaDbPlugin from '../main';
|
||||
import type { MediaTypeModel } from '../models/MediaTypeModel';
|
||||
import { MEDIA_TYPES } from '../utils/MediaTypeManager';
|
||||
import { MediaTypeModel } from '../models/MediaTypeModel';
|
||||
import { fragWithHTML } from '../utils/Utils';
|
||||
import { PropertyMapping, PropertyMappingModel, PropertyMappingOption } from './PropertyMapping';
|
||||
import PropertyMappingModelsComponent from './PropertyMappingModelsComponent.svelte';
|
||||
import { FileSuggest } from './suggesters/FileSuggest';
|
||||
import { FolderSuggest } from './suggesters/FolderSuggest';
|
||||
|
||||
export interface MediaDbPluginSettings {
|
||||
OMDbKey: string;
|
||||
MobyGamesKey: string;
|
||||
GiantBombKey: string;
|
||||
sfwFilter: boolean;
|
||||
templates: boolean;
|
||||
customDateFormat: string;
|
||||
|
|
@ -78,6 +80,7 @@ export interface MediaDbPluginSettings {
|
|||
const DEFAULT_SETTINGS: MediaDbPluginSettings = {
|
||||
OMDbKey: '',
|
||||
MobyGamesKey: '',
|
||||
GiantBombKey: '',
|
||||
sfwFilter: true,
|
||||
templates: true,
|
||||
customDateFormat: 'L',
|
||||
|
|
@ -203,6 +206,18 @@ export class MediaDbSettingTab extends PluginSettingTab {
|
|||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Giant Bomb Key')
|
||||
.setDesc('API key for "www.giantbomb.com".')
|
||||
.addText(cb => {
|
||||
cb.setPlaceholder('API key')
|
||||
.setValue(this.plugin.settings.GiantBombKey)
|
||||
.onChange(data => {
|
||||
this.plugin.settings.GiantBombKey = data;
|
||||
void this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('SFW filter')
|
||||
.setDesc('Only shows SFW results for APIs that offer filtering.')
|
||||
|
|
@ -240,7 +255,10 @@ export class MediaDbSettingTab extends PluginSettingTab {
|
|||
.onChange(data => {
|
||||
const newDateFormat = data ? data : DEFAULT_SETTINGS.customDateFormat;
|
||||
this.plugin.settings.customDateFormat = newDateFormat;
|
||||
document.getElementById('media-db-dateformat-preview').textContent = this.plugin.dateFormatter.getPreview(newDateFormat); // update preview
|
||||
const previewEl = document.getElementById('media-db-dateformat-preview');
|
||||
if (previewEl) {
|
||||
previewEl.textContent = this.plugin.dateFormatter.getPreview(newDateFormat); // update preview
|
||||
}
|
||||
void this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
|
@ -675,7 +693,7 @@ export class MediaDbSettingTab extends PluginSettingTab {
|
|||
Don't forget to save your changes using the save button for each individual category.
|
||||
</p>`;
|
||||
|
||||
new PropertyMappingModelsComponent({
|
||||
mount(PropertyMappingModelsComponent, {
|
||||
target: this.containerEl,
|
||||
props: {
|
||||
models: this.plugin.settings.propertyMappingModels.map(x => x.copy()),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { TAbstractFile } from 'obsidian';
|
||||
import { TFile } from 'obsidian';
|
||||
import { TextInputSuggest } from './Suggest';
|
||||
import { TAbstractFile, TFile } from 'obsidian';
|
||||
|
||||
export class FileSuggest extends TextInputSuggest<TFile> {
|
||||
getSuggestions(inputStr: string): TFile[] {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes
|
||||
|
||||
import { TAbstractFile, TFolder } from 'obsidian';
|
||||
import type { TAbstractFile } from 'obsidian';
|
||||
import { TFolder } from 'obsidian';
|
||||
import { TextInputSuggest } from './Suggest';
|
||||
|
||||
export class FolderSuggest extends TextInputSuggest<TFolder> {
|
||||
|
|
|
|||
|
|
@ -1,28 +1,34 @@
|
|||
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes
|
||||
|
||||
import { App, ISuggestOwner, Scope } from 'obsidian';
|
||||
import { createPopper, Instance as PopperInstance } from '@popperjs/core';
|
||||
import type { Instance as PopperInstance } from '@popperjs/core';
|
||||
import { createPopper } from '@popperjs/core';
|
||||
import type { App, ISuggestOwner } from 'obsidian';
|
||||
import { Scope } from 'obsidian';
|
||||
import { wrapAround } from 'src/utils/Utils';
|
||||
|
||||
export class Suggest<T> {
|
||||
private owner: ISuggestOwner<T>;
|
||||
private values: T[];
|
||||
private suggestions: HTMLDivElement[];
|
||||
private suggestions: HTMLElement[];
|
||||
private selectedItem: number;
|
||||
private containerEl: HTMLElement;
|
||||
|
||||
constructor(owner: ISuggestOwner<T>, containerEl: HTMLElement, scope: Scope) {
|
||||
this.owner = owner;
|
||||
this.containerEl = containerEl;
|
||||
this.values = [];
|
||||
this.suggestions = [];
|
||||
this.selectedItem = 0;
|
||||
|
||||
containerEl.on('click', '.suggestion-item', this.onSuggestionClick.bind(this));
|
||||
containerEl.on('mousemove', '.suggestion-item', this.onSuggestionMouseover.bind(this));
|
||||
containerEl.on('click', '.suggestion-item', (e, el) => this.onSuggestionClick(e, el));
|
||||
containerEl.on('mousemove', '.suggestion-item', (e, el) => this.onSuggestionMouseover(e, el));
|
||||
|
||||
scope.register([], 'ArrowUp', event => {
|
||||
if (!event.isComposing) {
|
||||
this.setSelectedItem(this.selectedItem - 1, true);
|
||||
return false;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
scope.register([], 'ArrowDown', event => {
|
||||
|
|
@ -30,6 +36,7 @@ export class Suggest<T> {
|
|||
this.setSelectedItem(this.selectedItem + 1, true);
|
||||
return false;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
scope.register([], 'Enter', event => {
|
||||
|
|
@ -37,10 +44,11 @@ export class Suggest<T> {
|
|||
this.useSelectedItem(event);
|
||||
return false;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
onSuggestionClick(event: MouseEvent, el: HTMLDivElement): void {
|
||||
onSuggestionClick(event: MouseEvent, el: HTMLElement): void {
|
||||
event.preventDefault();
|
||||
|
||||
const item = this.suggestions.indexOf(el);
|
||||
|
|
@ -48,7 +56,7 @@ export class Suggest<T> {
|
|||
this.useSelectedItem(event);
|
||||
}
|
||||
|
||||
onSuggestionMouseover(_event: MouseEvent, el: HTMLDivElement): void {
|
||||
onSuggestionMouseover(_event: MouseEvent, el: HTMLElement): void {
|
||||
const item = this.suggestions.indexOf(el);
|
||||
this.setSelectedItem(item, false);
|
||||
}
|
||||
|
|
@ -95,7 +103,7 @@ export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
|
|||
protected app: App;
|
||||
protected inputEl: HTMLInputElement;
|
||||
|
||||
private popper: PopperInstance;
|
||||
private popper?: PopperInstance;
|
||||
private scope: Scope;
|
||||
private suggestEl: HTMLElement;
|
||||
private suggest: Suggest<T>;
|
||||
|
|
@ -126,13 +134,13 @@ export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
|
|||
if (suggestions.length > 0) {
|
||||
this.suggest.setSuggestions(suggestions);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
this.open((<any>this.app).dom.appContainerEl, this.inputEl);
|
||||
this.open((this.app as any).dom.appContainerEl, this.inputEl);
|
||||
}
|
||||
}
|
||||
|
||||
open(container: HTMLElement, inputEl: HTMLElement): void {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(<any>this.app).keymap.pushScope(this.scope);
|
||||
(this.app as any).keymap.pushScope(this.scope);
|
||||
|
||||
container.appendChild(this.suggestEl);
|
||||
this.popper = createPopper(inputEl, this.suggestEl, {
|
||||
|
|
@ -162,7 +170,7 @@ export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
|
|||
|
||||
close(): void {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(<any>this.app).keymap.popScope(this.scope);
|
||||
(this.app as any).keymap.popScope(this.scope);
|
||||
|
||||
this.suggest.setSuggestions([]);
|
||||
this.popper?.destroy();
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
import { MediaDbPluginSettings } from '../settings/Settings';
|
||||
import { MediaType } from './MediaType';
|
||||
import { MediaTypeModel } from '../models/MediaTypeModel';
|
||||
import { replaceTags } from './Utils';
|
||||
import { App, TAbstractFile, TFile, TFolder } from 'obsidian';
|
||||
import { MovieModel } from '../models/MovieModel';
|
||||
import { SeriesModel } from '../models/SeriesModel';
|
||||
import { MangaModel } from '../models/MangaModel';
|
||||
import { GameModel } from '../models/GameModel';
|
||||
import { WikiModel } from '../models/WikiModel';
|
||||
import { MusicReleaseModel } from '../models/MusicReleaseModel';
|
||||
import type { App, TAbstractFile, TFile } from 'obsidian';
|
||||
import { TFolder } from 'obsidian';
|
||||
import { BoardGameModel } from '../models/BoardGameModel';
|
||||
import { BookModel } from '../models/BookModel';
|
||||
import { GameModel } from '../models/GameModel';
|
||||
import { MangaModel } from '../models/MangaModel';
|
||||
import type { MediaTypeModel } from '../models/MediaTypeModel';
|
||||
import { MovieModel } from '../models/MovieModel';
|
||||
import { MusicReleaseModel } from '../models/MusicReleaseModel';
|
||||
import { SeriesModel } from '../models/SeriesModel';
|
||||
import { WikiModel } from '../models/WikiModel';
|
||||
import type { MediaDbPluginSettings } from '../settings/Settings';
|
||||
import { MediaType } from './MediaType';
|
||||
import { replaceTags } from './Utils';
|
||||
|
||||
export const MEDIA_TYPES: MediaType[] = [
|
||||
MediaType.Movie,
|
||||
|
|
@ -28,7 +29,11 @@ export class MediaTypeManager {
|
|||
mediaTemplateMap: Map<MediaType, string>;
|
||||
mediaFolderMap: Map<MediaType, string>;
|
||||
|
||||
constructor() {}
|
||||
constructor() {
|
||||
this.mediaFileNameTemplateMap = new Map<MediaType, string>();
|
||||
this.mediaTemplateMap = new Map<MediaType, string>();
|
||||
this.mediaFolderMap = new Map<MediaType, string>();
|
||||
}
|
||||
|
||||
updateTemplates(settings: MediaDbPluginSettings): void {
|
||||
this.mediaFileNameTemplateMap = new Map<MediaType, string>();
|
||||
|
|
@ -66,7 +71,7 @@ export class MediaTypeManager {
|
|||
|
||||
getFileName(mediaTypeModel: MediaTypeModel): string {
|
||||
// Ignore undefined tags since some search APIs do not return all properties in the model and produce clean file names even if errors occur
|
||||
return replaceTags(this.mediaFileNameTemplateMap.get(mediaTypeModel.getMediaType()), mediaTypeModel, true);
|
||||
return replaceTags(this.mediaFileNameTemplateMap.get(mediaTypeModel.getMediaType())!, mediaTypeModel, true);
|
||||
}
|
||||
|
||||
async getTemplate(mediaTypeModel: MediaTypeModel, app: App): Promise<string> {
|
||||
|
|
@ -76,7 +81,7 @@ export class MediaTypeManager {
|
|||
return '';
|
||||
}
|
||||
|
||||
let templateFile = app.vault.getAbstractFileByPath(templateFilePath);
|
||||
let templateFile = app.vault.getAbstractFileByPath(templateFilePath) ?? undefined;
|
||||
|
||||
// WARNING: This was previously selected by filename, but that could lead to collisions and unwanted effects.
|
||||
// This now falls back to the previous method if no file is found
|
||||
|
|
@ -107,7 +112,7 @@ export class MediaTypeManager {
|
|||
if (!(await app.vault.adapter.exists(folderPath))) {
|
||||
await app.vault.createFolder(folderPath);
|
||||
}
|
||||
const folder: TAbstractFile = app.vault.getAbstractFileByPath(folderPath);
|
||||
const folder = app.vault.getAbstractFileByPath(folderPath);
|
||||
|
||||
if (!(folder instanceof TFolder)) {
|
||||
throw Error(`Expected ${folder} to be instance of TFolder`);
|
||||
|
|
@ -141,6 +146,6 @@ export class MediaTypeManager {
|
|||
return new BookModel(obj);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
throw new Error(`Unknown media type: ${mediaType}`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { Notice } from 'obsidian';
|
||||
import { MediaDbPreviewModal } from 'src/modals/MediaDbPreviewModal';
|
||||
import type MediaDbPlugin from '../main';
|
||||
import { MediaDbAdvancedSearchModal } from '../modals/MediaDbAdvancedSearchModal';
|
||||
import { MediaDbIdSearchModal } from '../modals/MediaDbIdSearchModal';
|
||||
import { MediaTypeModel } from '../models/MediaTypeModel';
|
||||
import { MediaDbSearchResultModal } from '../modals/MediaDbSearchResultModal';
|
||||
import { Notice } from 'obsidian';
|
||||
import MediaDbPlugin from '../main';
|
||||
import { MediaDbPreviewModal } from 'src/modals/MediaDbPreviewModal';
|
||||
import { MediaDbSearchModal } from '../modals/MediaDbSearchModal';
|
||||
import { MediaType } from './MediaType';
|
||||
import { MediaDbSearchResultModal } from '../modals/MediaDbSearchResultModal';
|
||||
import type { MediaTypeModel } from '../models/MediaTypeModel';
|
||||
import type { MediaType } from './MediaType';
|
||||
|
||||
export enum ModalResultCode {
|
||||
SUCCESS = 'SUCCESS',
|
||||
|
|
@ -15,60 +15,59 @@ export enum ModalResultCode {
|
|||
ERROR = 'ERROR',
|
||||
}
|
||||
|
||||
type ModalResult<T> =
|
||||
| {
|
||||
code: ModalResultCode.CLOSE;
|
||||
}
|
||||
| {
|
||||
code: ModalResultCode.ERROR;
|
||||
error: Error;
|
||||
}
|
||||
| {
|
||||
code: ModalResultCode.SUCCESS;
|
||||
data: T;
|
||||
};
|
||||
|
||||
type SkippableModalResult<T> =
|
||||
| ModalResult<T>
|
||||
| {
|
||||
code: ModalResultCode.SKIP;
|
||||
};
|
||||
|
||||
/**
|
||||
* Object containing the data {@link ModalHelper.createSearchModal} returns.
|
||||
* On {@link ModalResultCode.SUCCESS} this contains {@link SearchModalData}.
|
||||
* On {@link ModalResultCode.ERROR} this contains a reference to that error.
|
||||
*/
|
||||
export interface SearchModalResult {
|
||||
code: ModalResultCode.SUCCESS | ModalResultCode.CLOSE | ModalResultCode.ERROR;
|
||||
data?: SearchModalData;
|
||||
error?: Error;
|
||||
}
|
||||
export type SearchModalResult = ModalResult<SearchModalData>;
|
||||
|
||||
/**
|
||||
* Object containing the data {@link ModalHelper.createAdvancedSearchModal} returns.
|
||||
* On {@link ModalResultCode.SUCCESS} this contains {@link AdvancedSearchModalData}.
|
||||
* On {@link ModalResultCode.ERROR} this contains a reference to that error.
|
||||
*/
|
||||
export interface AdvancedSearchModalResult {
|
||||
code: ModalResultCode.SUCCESS | ModalResultCode.CLOSE | ModalResultCode.ERROR;
|
||||
data?: AdvancedSearchModalData;
|
||||
error?: Error;
|
||||
}
|
||||
export type AdvancedSearchModalResult = ModalResult<AdvancedSearchModalData>;
|
||||
|
||||
/**
|
||||
* Object containing the data {@link ModalHelper.createIdSearchModal} returns.
|
||||
* On {@link ModalResultCode.SUCCESS} this contains {@link IdSearchModalData}.
|
||||
* On {@link ModalResultCode.ERROR} this contains a reference to that error.
|
||||
*/
|
||||
export interface IdSearchModalResult {
|
||||
code: ModalResultCode.SUCCESS | ModalResultCode.CLOSE | ModalResultCode.ERROR;
|
||||
data?: IdSearchModalData;
|
||||
error?: Error;
|
||||
}
|
||||
export type IdSearchModalResult = ModalResult<IdSearchModalData>;
|
||||
|
||||
/**
|
||||
* Object containing the data {@link ModalHelper.createSelectModal} returns.
|
||||
* On {@link ModalResultCode.SUCCESS} this contains {@link SelectModalData}.
|
||||
* On {@link ModalResultCode.ERROR} this contains a reference to that error.
|
||||
*/
|
||||
export interface SelectModalResult {
|
||||
code: ModalResultCode.SUCCESS | ModalResultCode.CLOSE | ModalResultCode.SKIP | ModalResultCode.ERROR;
|
||||
data?: SelectModalData;
|
||||
error?: Error;
|
||||
}
|
||||
export type SelectModalResult = SkippableModalResult<SelectModalData>;
|
||||
|
||||
/**
|
||||
* Object containing the data {@link ModalHelper.createPreviewModal} returns.
|
||||
* On {@link ModalResultCode.SUCCESS} this contains {@link PreviewModalData}.
|
||||
* On {@link ModalResultCode.ERROR} this contains a reference to that error.
|
||||
*/
|
||||
export interface PreviewModalResult {
|
||||
code: ModalResultCode.SUCCESS | ModalResultCode.CLOSE | ModalResultCode.ERROR;
|
||||
data?: PreviewModalData;
|
||||
error?: Error;
|
||||
}
|
||||
export type PreviewModalResult = ModalResult<PreviewModalData>;
|
||||
|
||||
/**
|
||||
* The data the search modal returns.
|
||||
|
|
@ -248,7 +247,10 @@ export class ModalHelper {
|
|||
* @param submitCallback the callback that gets executed after the modal has been submitted, but after it has been closed
|
||||
* @returns the user input or nothing and a reference to the modal.
|
||||
*/
|
||||
async openSearchModal(searchModalOptions: SearchModalOptions, submitCallback: (searchModalData: SearchModalData) => Promise<MediaTypeModel[]>): Promise<MediaTypeModel[]> {
|
||||
async openSearchModal(
|
||||
searchModalOptions: SearchModalOptions,
|
||||
submitCallback: (searchModalData: SearchModalData) => Promise<MediaTypeModel[]>,
|
||||
): Promise<MediaTypeModel[] | undefined> {
|
||||
const { searchModalResult, searchModal } = await this.createSearchModal(searchModalOptions);
|
||||
console.debug(`MDB | searchModal closed with code ${searchModalResult.code}`);
|
||||
|
||||
|
|
@ -271,7 +273,7 @@ export class ModalHelper {
|
|||
return callbackRes;
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
new Notice(e.toString());
|
||||
new Notice(`${e}`);
|
||||
searchModal.close();
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -314,7 +316,7 @@ export class ModalHelper {
|
|||
async openAdvancedSearchModal(
|
||||
advancedSearchModalOptions: AdvancedSearchModalOptions,
|
||||
submitCallback: (advancedSearchModalData: AdvancedSearchModalData) => Promise<MediaTypeModel[]>,
|
||||
): Promise<MediaTypeModel[]> {
|
||||
): Promise<MediaTypeModel[] | undefined> {
|
||||
const { advancedSearchModalResult, advancedSearchModal } = await this.createAdvancedSearchModal(advancedSearchModalOptions);
|
||||
console.debug(`MDB | advencedSearchModal closed with code ${advancedSearchModalResult.code}`);
|
||||
|
||||
|
|
@ -337,7 +339,7 @@ export class ModalHelper {
|
|||
return callbackRes;
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
new Notice(e.toString());
|
||||
new Notice(`${e}`);
|
||||
advancedSearchModal.close();
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -377,8 +379,8 @@ export class ModalHelper {
|
|||
*/
|
||||
async openIdSearchModal(
|
||||
idSearchModalOptions: IdSearchModalOptions,
|
||||
submitCallback: (idSearchModalData: IdSearchModalData) => Promise<MediaTypeModel>,
|
||||
): Promise<MediaTypeModel> {
|
||||
submitCallback: (idSearchModalData: IdSearchModalData) => Promise<MediaTypeModel | undefined>,
|
||||
): Promise<MediaTypeModel | undefined> {
|
||||
const { idSearchModalResult, idSearchModal } = await this.createIdSearchModal(idSearchModalOptions);
|
||||
console.debug(`MDB | idSearchModal closed with code ${idSearchModalResult.code}`);
|
||||
|
||||
|
|
@ -396,12 +398,12 @@ export class ModalHelper {
|
|||
}
|
||||
|
||||
try {
|
||||
const callbackRes: MediaTypeModel = await submitCallback(idSearchModalResult.data);
|
||||
const callbackRes = await submitCallback(idSearchModalResult.data);
|
||||
idSearchModal.close();
|
||||
return callbackRes;
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
new Notice(e.toString());
|
||||
new Notice(`${e}`);
|
||||
idSearchModal.close();
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -440,7 +442,10 @@ export class ModalHelper {
|
|||
* @param submitCallback the callback that gets executed after the modal has been submitted, but before it has been closed
|
||||
* @returns the user input or nothing and a reference to the modal.
|
||||
*/
|
||||
async openSelectModal(selectModalOptions: SelectModalOptions, submitCallback: (selectModalData: SelectModalData) => Promise<MediaTypeModel[]>): Promise<MediaTypeModel[]> {
|
||||
async openSelectModal(
|
||||
selectModalOptions: SelectModalOptions,
|
||||
submitCallback: (selectModalData: SelectModalData) => Promise<MediaTypeModel[]>,
|
||||
): Promise<MediaTypeModel[] | undefined> {
|
||||
const { selectModalResult, selectModal } = await this.createSelectModal(selectModalOptions);
|
||||
console.debug(`MDB | selectModal closed with code ${selectModalResult.code}`);
|
||||
|
||||
|
|
@ -468,7 +473,7 @@ export class ModalHelper {
|
|||
return callbackRes;
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
new Notice(e.toString());
|
||||
new Notice(`${e}`);
|
||||
selectModal.close();
|
||||
return;
|
||||
}
|
||||
|
|
@ -500,12 +505,12 @@ export class ModalHelper {
|
|||
console.warn(previewModalResult.error);
|
||||
new Notice(previewModalResult.error.toString());
|
||||
previewModal.close();
|
||||
return undefined;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (previewModalResult.code === ModalResultCode.CLOSE) {
|
||||
// modal is already being closed
|
||||
return undefined;
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -514,9 +519,9 @@ export class ModalHelper {
|
|||
return callbackRes;
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
new Notice(e.toString());
|
||||
new Notice(`${e}`);
|
||||
previewModal.close();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { MediaTypeModel } from '../models/MediaTypeModel';
|
||||
import { TFile, TFolder, App } from 'obsidian';
|
||||
import type { TFile, TFolder, App } from 'obsidian';
|
||||
import type { MediaTypeModel } from '../models/MediaTypeModel';
|
||||
|
||||
export const pluginName: string = 'obsidian-media-db-plugin';
|
||||
export const contactEmail: string = 'm.projects.code@gmail.com';
|
||||
|
|
@ -15,7 +15,7 @@ export function wrapAround(value: number, size: number): number {
|
|||
}
|
||||
|
||||
export function containsOnlyLettersAndUnderscores(str: string): boolean {
|
||||
return /^[a-zA-Z_]+$/.test(str);
|
||||
return /^[\p{Letter}\p{M}_]+$/u.test(str);
|
||||
}
|
||||
|
||||
export function replaceIllegalFileNameCharactersInString(string: string): string {
|
||||
|
|
@ -82,7 +82,7 @@ function replaceTag(match: string, mediaTypeModel: MediaTypeModel, ignoreUndefin
|
|||
return '{{ INVALID TEMPLATE TAG }}';
|
||||
}
|
||||
|
||||
function traverseMetaData(path: Array<string>, mediaTypeModel: MediaTypeModel): any {
|
||||
function traverseMetaData(path: string[], mediaTypeModel: MediaTypeModel): any {
|
||||
let o: any = mediaTypeModel;
|
||||
|
||||
for (const part of path) {
|
||||
|
|
@ -226,7 +226,11 @@ export function hasTemplaterPlugin(app: App): boolean {
|
|||
export async function useTemplaterPluginInFile(app: App, file: TFile): Promise<void> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const templater = (app as any).plugins.plugins['templater-obsidian'];
|
||||
if (templater && !templater?.settings['trigger_on_file_creation']) {
|
||||
if (templater && !templater?.settings.trigger_on_file_creation) {
|
||||
await templater.templater.overwrite_file_commands(file);
|
||||
}
|
||||
}
|
||||
|
||||
export type ModelToData<T> = {
|
||||
[K in keyof T as T[K] extends Function ? never : K]?: T[K];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,14 +2,22 @@
|
|||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"target": "ES6",
|
||||
"target": "ESNext",
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"noImplicitAny": true,
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitReturns": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"isolatedModules": true,
|
||||
"skipLibCheck": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"resolveJsonModule": true,
|
||||
"moduleDetection": "force",
|
||||
"sourceMap": true,
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"types": ["svelte"],
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "tests/**/*.ts"]
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@
|
|||
"0.1.7": "0.14.0",
|
||||
"0.7.0": "1.5.0",
|
||||
"0.7.1": "1.5.0",
|
||||
"0.7.2": "1.5.0"
|
||||
"0.7.2": "1.5.0",
|
||||
"0.8.0": "1.5.0"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue