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**
|
**What does the API do/offer**
|
||||||
A short description of what data the API offers
|
A short description of what data the API offers
|
||||||
|
|
||||||
- [ ] Is the API free to use
|
- [ ] Is the API free to use
|
||||||
- [ ] Does the API require authentication
|
- [ ] 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: ''
|
assignees: ''
|
||||||
---
|
---
|
||||||
|
|
||||||
- [ ] The Plugin is up to date
|
- [ ] The Plugin is up to date
|
||||||
- [ ] Obsidian is up to date
|
- [ ] Obsidian is up to date
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
A clear and concise description of what the bug is.
|
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**
|
**Occurs on**
|
||||||
|
|
||||||
- [ ] Windows
|
- [ ] Windows
|
||||||
- [ ] macOS
|
- [ ] macOS
|
||||||
- [ ] Linux
|
- [ ] Linux
|
||||||
- [ ] Android
|
- [ ] Android
|
||||||
- [ ] iOS
|
- [ ] iOS
|
||||||
|
|
||||||
**Plugin version**
|
**Plugin version**
|
||||||
x.x.x
|
x.x.x
|
||||||
|
|
|
||||||
88
.github/workflows/release.yml
vendored
88
.github/workflows/release.yml
vendored
|
|
@ -1,6 +1,4 @@
|
||||||
name: Build Obsidian Plugin
|
name: Create Plugin Release
|
||||||
|
|
||||||
# adapted from https://github.com/argenos/nldates-obsidian/blob/master/.github/workflows/release.yml
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|
@ -16,10 +14,23 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- name: Checkout
|
||||||
- uses: oven-sh/setup-bun@v1
|
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:
|
with:
|
||||||
bun-version: latest
|
bun-version: latest
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
id: build
|
id: build
|
||||||
run: |
|
run: |
|
||||||
|
|
@ -27,62 +38,17 @@ jobs:
|
||||||
bun run build
|
bun run build
|
||||||
mkdir ${{ env.PLUGIN_NAME }}
|
mkdir ${{ env.PLUGIN_NAME }}
|
||||||
cp main.js manifest.json styles.css ${{ 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
|
ls
|
||||||
echo "tag_name=$(git tag --sort version:refname | tail -n 1)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Create Release
|
- name: Release
|
||||||
id: create_release
|
id: release
|
||||||
uses: actions/create-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
VERSION: ${{ github.ref }}
|
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ github.ref }}
|
prerelease: ${{ steps.status.outputs.prerelease }}
|
||||||
release_name: ${{ github.ref }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
draft: false
|
files: |
|
||||||
prerelease: false
|
${{ env.PLUGIN_NAME }}-${{ github.ref_name }}.zip
|
||||||
|
main.js
|
||||||
- name: Upload zip file
|
manifest.json
|
||||||
id: upload-zip
|
styles.css
|
||||||
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
|
|
||||||
|
|
|
||||||
78
CHANGELOG.md
78
CHANGELOG.md
|
|
@ -1,66 +1,74 @@
|
||||||
# Changelog
|
# 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
|
# 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
|
# 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)
|
- 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)
|
- 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)
|
- 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)
|
- Use https for all API requests [#147](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/147) (thanks ZackBoe)
|
||||||
- Sped up multi API search
|
- Sped up multi API search
|
||||||
- Fixed unrelated APIs being searched when searching by a specific media type
|
- Fixed unrelated APIs being searched when searching by a specific media type
|
||||||
|
|
||||||
# 0.7.0
|
# 0.7.0
|
||||||
|
|
||||||
- renamed the plugin to just `Media DB`
|
- 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)
|
- 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)
|
- 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)
|
- 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)
|
- 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)
|
- 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)
|
- Added developers and publishers field to games [#122](https://github.com/mProjectsCode/obsidian-media-db-plugin/pull/122) (thanks ltctceplrm)
|
||||||
|
|
||||||
# 0.6.0
|
# 0.6.0
|
||||||
|
|
||||||
- Added manga support through Jikan
|
- Added manga support through Jikan
|
||||||
- Added book support through Open Library
|
- Added book support through Open Library
|
||||||
- Added album cover support for music releases
|
- Added album cover support for music releases
|
||||||
- Split up `producer` into `studio`, `director` and `writer` for movies and series
|
- Split up `producer` into `studio`, `director` and `writer` for movies and series
|
||||||
- fixed the preview modal not displaying the frontmatter anymore
|
- fixed the preview modal not displaying the frontmatter anymore
|
||||||
|
|
||||||
# 0.5.0
|
# 0.5.0
|
||||||
|
|
||||||
- New simple search modal, select the media type and search all applicable APIs
|
- New simple search modal, select the media type and search all applicable APIs
|
||||||
- More data for Board Games
|
- More data for Board Games
|
||||||
- Actors and Streaming Platforms for Movies and Series
|
- Actors and Streaming Platforms for Movies and Series
|
||||||
- Separate new file location for all media types
|
- Separate new file location for all media types
|
||||||
- Separate command for each media type
|
- Separate command for each media type
|
||||||
- Fix problems with closing of preview modal
|
- Fix problems with closing of preview modal
|
||||||
|
|
||||||
# 0.3.2
|
# 0.3.2
|
||||||
|
|
||||||
- Added Board Game Geek API (documentation pending)
|
- Added Board Game Geek API (documentation pending)
|
||||||
- More information in the search results
|
- More information in the search results
|
||||||
- various fixes
|
- various fixes
|
||||||
|
|
||||||
# 0.3.1
|
# 0.3.1
|
||||||
|
|
||||||
- various fixes
|
- various fixes
|
||||||
|
|
||||||
# 0.3.0
|
# 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 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
|
- 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`
|
- Fixed a bug where the note creation would fail when the metadata included a field with the values `null` or `undefined`
|
||||||
|
|
||||||
# 0.2.1
|
# 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
|
# 0.2.0
|
||||||
|
|
||||||
- Added the option to rename metadata fields through property mappings
|
- Added the option to rename metadata fields through property mappings
|
||||||
- fixed note creation falling, when the folder set in the settings did not exist
|
- 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.
|
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 1
|
||||||
- element 2
|
- element 2
|
||||||
- element 3
|
- element 3
|
||||||
- ...
|
- ...
|
||||||
```
|
```
|
||||||
- using `{{ ENUM:variable_name }}` will result in
|
- using `{{ ENUM:variable_name }}` will result in
|
||||||
```
|
```
|
||||||
element 1, element 2, element 3, ...
|
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
|
### Currently supported media types
|
||||||
|
|
||||||
- movies (including specials)
|
- movies (including specials)
|
||||||
- series (including OVAs)
|
- series (including OVAs)
|
||||||
- games
|
- games
|
||||||
- music releases
|
- music releases
|
||||||
- wiki articles
|
- wiki articles
|
||||||
- books
|
- books
|
||||||
|
|
||||||
### Currently supported APIs:
|
### Currently supported APIs:
|
||||||
|
|
||||||
| Name | Description | Supported formats | Authentification | Rate limiting | SFW filter support |
|
| 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 |
|
| [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 |
|
| [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 |
|
| [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 |
|
| [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 |
|
| [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 |
|
| [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 |
|
| [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 |
|
||||||
| [VNDB](https://vndb.org/) | The VNDB API offers metadata for visual novels | games | No | 200 requests per 5 minutes | Yes |
|
| [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
|
#### Notes
|
||||||
|
|
||||||
- [Jikan](https://jikan.moe/)
|
- [Jikan](https://jikan.moe/)
|
||||||
- sometimes the api is very slow, this is normal
|
- 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
|
- 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"
|
- e.g. instead of "Demon Slayer" you have to search "Kimetsu no Yaiba"
|
||||||
|
|
||||||
#### Search by ID
|
#### Search by ID
|
||||||
|
|
||||||
- [Jikan](https://jikan.moe/)
|
- [Jikan](https://jikan.moe/)
|
||||||
- the ID you need is the ID of the anime on [My Anime List](https://myanimelist.net)
|
- 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
|
- 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`
|
- 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/)
|
- [Jikan Manga](https://jikan.moe/)
|
||||||
- the ID you need is the ID of the manga on [My Anime List](https://myanimelist.net)
|
- 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
|
- 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`
|
- 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/)
|
- [OMDb](https://www.omdbapi.com/)
|
||||||
- the ID you need is the ID of the movie or show on [IMDb](https://www.imdb.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
|
- 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`
|
- 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/)
|
- [MusicBrainz](https://musicbrainz.org/)
|
||||||
- the id of a release is not easily accessible, you are better off just searching by title
|
- 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)
|
- [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
|
- [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/)
|
- [Steam](https://store.steampowered.com/)
|
||||||
- you can find this ID in the URL
|
- 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`
|
- 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)
|
- [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
|
- 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`
|
- 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) `
|
- 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)
|
- [Moby Games](https://www.mobygames.com)
|
||||||
- you can find this ID in the URL
|
- 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`
|
- 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/)
|
- [Giant Bomb](https://www.giantbomb.com)
|
||||||
- Located in the novel's VNDB URL path
|
- you can find this ID in the URL
|
||||||
- e.g. The ID for [Katawa Shoujo](https://vndb.org/v945) (`https://vndb.org/v945`) is `v945`
|
- 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?
|
### 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:
|
Credits go to:
|
||||||
|
|
||||||
- https://github.com/anpigon/obsidian-book-search-plugin for some inspiration and the idea to make this plugin
|
- 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/liamcain/obsidian-periodic-notes for 99% of `Suggest.ts` and `FolderSuggest.ts`
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import builtins from 'builtin-modules';
|
import builtins from 'builtin-modules';
|
||||||
import esbuild from 'esbuild';
|
import esbuild from 'esbuild';
|
||||||
import esbuildSvelte from 'esbuild-svelte';
|
import esbuildSvelte from 'esbuild-svelte';
|
||||||
import sveltePreprocess from 'svelte-preprocess';
|
import { sveltePreprocess } from 'svelte-preprocess';
|
||||||
import { getBuildBanner } from 'build/buildBanner';
|
import { getBuildBanner } from 'build/buildBanner';
|
||||||
|
|
||||||
const banner = getBuildBanner('Release Build', version => version);
|
const banner = getBuildBanner('Release Build', version => version);
|
||||||
|
|
@ -41,7 +41,7 @@ const build = await esbuild.build({
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
esbuildSvelte({
|
esbuildSvelte({
|
||||||
compilerOptions: { css: 'injected', dev: false, sveltePath: 'svelte' },
|
compilerOptions: { css: 'injected', dev: false },
|
||||||
preprocess: sveltePreprocess(),
|
preprocess: sveltePreprocess(),
|
||||||
filterWarnings: warning => {
|
filterWarnings: warning => {
|
||||||
// we don't want warnings from node modules that we can do nothing about
|
// we don't want warnings from node modules that we can do nothing about
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import esbuild from 'esbuild';
|
import esbuild from 'esbuild';
|
||||||
import copy from 'esbuild-plugin-copy-watch';
|
import copy from 'esbuild-plugin-copy-watch';
|
||||||
import esbuildSvelte from 'esbuild-svelte';
|
import esbuildSvelte from 'esbuild-svelte';
|
||||||
import sveltePreprocess from 'svelte-preprocess';
|
import { sveltePreprocess } from 'svelte-preprocess';
|
||||||
import manifest from '../../manifest.json' assert { type: 'json' };
|
import manifest from '../../manifest.json' assert { type: 'json' };
|
||||||
import { getBuildBanner } from 'build/buildBanner';
|
import { getBuildBanner } from 'build/buildBanner';
|
||||||
|
|
||||||
|
|
@ -52,7 +52,7 @@ const context = await esbuild.context({
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
esbuildSvelte({
|
esbuildSvelte({
|
||||||
compilerOptions: { css: 'injected', dev: true, sveltePath: 'svelte' },
|
compilerOptions: { css: 'injected', dev: true },
|
||||||
preprocess: sveltePreprocess(),
|
preprocess: sveltePreprocess(),
|
||||||
filterWarnings: warning => {
|
filterWarnings: warning => {
|
||||||
// we don't want warnings from node modules that we can do nothing about
|
// 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> {
|
async function runPreconditions(): Promise<void> {
|
||||||
// run preconditions
|
// run preconditions
|
||||||
await $seq(
|
await $seq(
|
||||||
[`bun run format`, `bun run lint:fix`, `bun run test`],
|
[`bun run format`, `bun run test`],
|
||||||
(cmd: string) => {
|
(cmd: string) => {
|
||||||
throw new UserError(`precondition "${cmd}" failed`);
|
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",
|
"id": "obsidian-media-db-plugin",
|
||||||
"name": "Media DB",
|
"name": "Media DB",
|
||||||
"version": "0.7.2",
|
"version": "0.8.0",
|
||||||
"minAppVersion": "1.5.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.",
|
"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",
|
"author": "Moritz Jung",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "obsidian-media-db-plugin",
|
"id": "obsidian-media-db-plugin",
|
||||||
"name": "Media DB",
|
"name": "Media DB",
|
||||||
"version": "0.7.2",
|
"version": "0.8.0",
|
||||||
"minAppVersion": "1.5.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.",
|
"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",
|
"author": "Moritz Jung",
|
||||||
|
|
|
||||||
41
package.json
41
package.json
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "obsidian-media-db-plugin",
|
"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.",
|
"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",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
@ -14,8 +14,8 @@
|
||||||
"lint": "eslint --max-warnings=0 src/**",
|
"lint": "eslint --max-warnings=0 src/**",
|
||||||
"lint:fix": "eslint --max-warnings=0 --fix src/**",
|
"lint:fix": "eslint --max-warnings=0 --fix src/**",
|
||||||
"svelte-check": "svelte-check --compiler-warnings \"unused-export-let:ignore\"",
|
"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": "bun run format:check && bun run tsc && bun run test",
|
||||||
"check:fix": "bun run format && bun run tsc && bun run lint:fix && bun run test",
|
"check:fix": "bun run format && bun run tsc && bun run test",
|
||||||
"release": "bun run automation/release.ts",
|
"release": "bun run automation/release.ts",
|
||||||
"stats": "bun run automation/stats.ts"
|
"stats": "bun run automation/stats.ts"
|
||||||
},
|
},
|
||||||
|
|
@ -25,27 +25,24 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"@lemons_dev/parsinom": "^0.0.12",
|
"@lemons_dev/parsinom": "^0.0.12",
|
||||||
"@happy-dom/global-registrator": "^14.3.6",
|
"@happy-dom/global-registrator": "^14.12.3",
|
||||||
"@tsconfig/svelte": "^5.0.3",
|
"@types/bun": "^1.1.16",
|
||||||
"@types/bun": "^1.0.10",
|
"builtin-modules": "^4.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.3.1",
|
"esbuild": "^0.24.2",
|
||||||
"@typescript-eslint/parser": "^7.3.1",
|
"esbuild-plugin-copy-watch": "^2.3.1",
|
||||||
"builtin-modules": "^3.3.0",
|
"esbuild-svelte": "^0.8.2",
|
||||||
"esbuild": "^0.20.2",
|
"eslint": "^9.18.0",
|
||||||
"esbuild-plugin-copy-watch": "^2.1.0",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"esbuild-svelte": "^0.8.0",
|
|
||||||
"eslint": "^8.57.0",
|
|
||||||
"eslint-plugin-import": "^2.29.1",
|
|
||||||
"eslint-plugin-isaacscript": "^3.12.2",
|
|
||||||
"eslint-plugin-only-warn": "^1.1.0",
|
"eslint-plugin-only-warn": "^1.1.0",
|
||||||
"obsidian": "latest",
|
"obsidian": "latest",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.4.2",
|
||||||
"prettier-plugin-svelte": "^3.2.2",
|
"prettier-plugin-svelte": "^3.3.3",
|
||||||
"string-argv": "^0.3.2",
|
"string-argv": "^0.3.2",
|
||||||
"svelte": "^4.2.12",
|
"svelte": "^5.17.5",
|
||||||
"svelte-check": "^3.6.8",
|
"svelte-check": "^4.1.4",
|
||||||
"svelte-preprocess": "^5.1.3",
|
"svelte-preprocess": "^6.0.3",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.3"
|
"typescript": "^5.7.3",
|
||||||
|
"typescript-eslint": "^8.20.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { APIModel } from './APIModel';
|
import { Notice } from 'obsidian';
|
||||||
import { MediaTypeModel } from '../models/MediaTypeModel';
|
import type { MediaTypeModel } from '../models/MediaTypeModel';
|
||||||
|
import type { APIModel } from './APIModel';
|
||||||
|
|
||||||
export class APIManager {
|
export class APIManager {
|
||||||
apis: APIModel[];
|
apis: APIModel[];
|
||||||
|
|
@ -23,7 +24,10 @@ export class APIManager {
|
||||||
try {
|
try {
|
||||||
return await api.searchByTitle(query);
|
return await api.searchByTitle(query);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
new Notice(`Error querying ${api.apiName}: ${e}`);
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
|
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -35,7 +39,7 @@ export class APIManager {
|
||||||
*
|
*
|
||||||
* @param item
|
* @param item
|
||||||
*/
|
*/
|
||||||
async queryDetailedInfo(item: MediaTypeModel): Promise<MediaTypeModel> {
|
async queryDetailedInfo(item: MediaTypeModel): Promise<MediaTypeModel | undefined> {
|
||||||
return await this.queryDetailedInfoById(item.id, item.dataSource);
|
return await this.queryDetailedInfoById(item.id, item.dataSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,22 +49,31 @@ export class APIManager {
|
||||||
* @param id
|
* @param id
|
||||||
* @param apiName
|
* @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) {
|
for (const api of this.apis) {
|
||||||
if (api.apiName === apiName) {
|
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) {
|
for (const api of this.apis) {
|
||||||
if (api.apiName === name) {
|
if (api.apiName === name) {
|
||||||
return api;
|
return api;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
registerAPI(api: APIModel): void {
|
registerAPI(api: APIModel): void {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { MediaTypeModel } from '../models/MediaTypeModel';
|
import type MediaDbPlugin from '../main';
|
||||||
import { MediaType } from '../utils/MediaType';
|
import type { MediaTypeModel } from '../models/MediaTypeModel';
|
||||||
import MediaDbPlugin from '../main';
|
import type { MediaType } from '../utils/MediaType';
|
||||||
|
|
||||||
export abstract class APIModel {
|
export abstract class APIModel {
|
||||||
apiName: string;
|
apiName!: string;
|
||||||
apiUrl: string;
|
apiUrl!: string;
|
||||||
apiDescription: string;
|
apiDescription!: string;
|
||||||
types: MediaType[];
|
types!: MediaType[];
|
||||||
plugin: MediaDbPlugin;
|
plugin!: MediaDbPlugin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function should query the api and return a list of matches. The matches should be caped at 20.
|
* 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 { 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 { MediaType } from '../../utils/MediaType';
|
||||||
|
import { APIModel } from '../APIModel';
|
||||||
|
|
||||||
export class BoardGameGeekAPI extends APIModel {
|
export class BoardGameGeekAPI extends APIModel {
|
||||||
plugin: MediaDbPlugin;
|
plugin: MediaDbPlugin;
|
||||||
|
|
@ -38,8 +38,8 @@ export class BoardGameGeekAPI extends APIModel {
|
||||||
const ret: MediaTypeModel[] = [];
|
const ret: MediaTypeModel[] = [];
|
||||||
|
|
||||||
for (const boardgame of Array.from(response.querySelectorAll('boardgame'))) {
|
for (const boardgame of Array.from(response.querySelectorAll('boardgame'))) {
|
||||||
const id = boardgame.attributes.getNamedItem('objectid')!.value;
|
const id = boardgame.attributes.getNamedItem('objectid')?.value;
|
||||||
const title = boardgame.querySelector('name[primary=true]')?.textContent ?? boardgame.querySelector('name')!.textContent!;
|
const title = boardgame.querySelector('name[primary=true]')?.textContent ?? boardgame.querySelector('name')?.textContent ?? undefined;
|
||||||
const year = boardgame.querySelector('yearpublished')?.textContent ?? '';
|
const year = boardgame.querySelector('yearpublished')?.textContent ?? '';
|
||||||
|
|
||||||
ret.push(
|
ret.push(
|
||||||
|
|
@ -49,7 +49,7 @@ export class BoardGameGeekAPI extends APIModel {
|
||||||
title,
|
title,
|
||||||
englishTitle: title,
|
englishTitle: title,
|
||||||
year,
|
year,
|
||||||
} as BoardGameModel),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,21 +72,29 @@ export class BoardGameGeekAPI extends APIModel {
|
||||||
const response = new window.DOMParser().parseFromString(data, 'text/xml');
|
const response = new window.DOMParser().parseFromString(data, 'text/xml');
|
||||||
// console.debug(response);
|
// console.debug(response);
|
||||||
|
|
||||||
const boardgame = response.querySelector('boardgame')!;
|
const boardgame = response.querySelector('boardgame');
|
||||||
const title = boardgame.querySelector('name[primary=true]')!.textContent!;
|
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 year = boardgame.querySelector('yearpublished')?.textContent ?? '';
|
||||||
const image = boardgame.querySelector('image')?.textContent ?? undefined;
|
const image = boardgame.querySelector('image')?.textContent ?? undefined;
|
||||||
const onlineRating = Number.parseFloat(boardgame.querySelector('statistics ratings average')?.textContent ?? '0');
|
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 complexityRating = Number.parseFloat(boardgame.querySelector('averageweight')?.textContent ?? '0');
|
||||||
const minPlayers = Number.parseFloat(boardgame.querySelector('minplayers')?.textContent ?? '0');
|
const minPlayers = Number.parseFloat(boardgame.querySelector('minplayers')?.textContent ?? '0');
|
||||||
const maxPlayers = Number.parseFloat(boardgame.querySelector('maxplayers')?.textContent ?? '0');
|
const maxPlayers = Number.parseFloat(boardgame.querySelector('maxplayers')?.textContent ?? '0');
|
||||||
const playtime = (boardgame.querySelector('playingtime')?.textContent ?? 'unknown') + ' minutes';
|
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({
|
return new BoardGameModel({
|
||||||
title: title,
|
title: title ?? undefined,
|
||||||
englishTitle: title,
|
englishTitle: title ?? undefined,
|
||||||
year: year === '0' ? '' : year,
|
year: year === '0' ? '' : year,
|
||||||
dataSource: this.apiName,
|
dataSource: this.apiName,
|
||||||
url: `https://boardgamegeek.com/boardgame/${id}`,
|
url: `https://boardgamegeek.com/boardgame/${id}`,
|
||||||
|
|
@ -107,6 +115,6 @@ export class BoardGameGeekAPI extends APIModel {
|
||||||
played: false,
|
played: false,
|
||||||
personalRating: 0,
|
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 type MediaDbPlugin from '../../main';
|
||||||
import { MediaTypeModel } from '../../models/MediaTypeModel';
|
import type { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||||
import { MovieModel } from '../../models/MovieModel';
|
import { MovieModel } from '../../models/MovieModel';
|
||||||
import MediaDbPlugin from '../../main';
|
|
||||||
import { SeriesModel } from '../../models/SeriesModel';
|
import { SeriesModel } from '../../models/SeriesModel';
|
||||||
import { MediaType } from '../../utils/MediaType';
|
import { MediaType } from '../../utils/MediaType';
|
||||||
|
import { APIModel } from '../APIModel';
|
||||||
|
|
||||||
export class MALAPI extends APIModel {
|
export class MALAPI extends APIModel {
|
||||||
plugin: MediaDbPlugin;
|
plugin: MediaDbPlugin;
|
||||||
|
|
@ -52,7 +52,7 @@ export class MALAPI extends APIModel {
|
||||||
year: result.year ?? result.aired?.prop?.from?.year ?? '',
|
year: result.year ?? result.aired?.prop?.from?.year ?? '',
|
||||||
dataSource: this.apiName,
|
dataSource: this.apiName,
|
||||||
id: result.mal_id,
|
id: result.mal_id,
|
||||||
} as MovieModel),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (type === 'movie' || type === 'special') {
|
if (type === 'movie' || type === 'special') {
|
||||||
|
|
@ -64,7 +64,7 @@ export class MALAPI extends APIModel {
|
||||||
year: result.year ?? result.aired?.prop?.from?.year ?? '',
|
year: result.year ?? result.aired?.prop?.from?.year ?? '',
|
||||||
dataSource: this.apiName,
|
dataSource: this.apiName,
|
||||||
id: result.mal_id,
|
id: result.mal_id,
|
||||||
} as MovieModel),
|
}),
|
||||||
);
|
);
|
||||||
} else if (type === 'series' || type === 'ova') {
|
} else if (type === 'series' || type === 'ova') {
|
||||||
ret.push(
|
ret.push(
|
||||||
|
|
@ -75,7 +75,7 @@ export class MALAPI extends APIModel {
|
||||||
year: result.year ?? result.aired?.prop?.from?.year ?? '',
|
year: result.year ?? result.aired?.prop?.from?.year ?? '',
|
||||||
dataSource: this.apiName,
|
dataSource: this.apiName,
|
||||||
id: result.mal_id,
|
id: result.mal_id,
|
||||||
} as SeriesModel),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -127,7 +127,7 @@ export class MALAPI extends APIModel {
|
||||||
lastWatched: '',
|
lastWatched: '',
|
||||||
personalRating: 0,
|
personalRating: 0,
|
||||||
},
|
},
|
||||||
} as MovieModel);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'movie' || type === 'special') {
|
if (type === 'movie' || type === 'special') {
|
||||||
|
|
@ -159,7 +159,7 @@ export class MALAPI extends APIModel {
|
||||||
lastWatched: '',
|
lastWatched: '',
|
||||||
personalRating: 0,
|
personalRating: 0,
|
||||||
},
|
},
|
||||||
} as MovieModel);
|
});
|
||||||
} else if (type === 'series' || type === 'ova') {
|
} else if (type === 'series' || type === 'ova') {
|
||||||
return new SeriesModel({
|
return new SeriesModel({
|
||||||
subType: type,
|
subType: type,
|
||||||
|
|
@ -190,9 +190,9 @@ export class MALAPI extends APIModel {
|
||||||
lastWatched: '',
|
lastWatched: '',
|
||||||
personalRating: 0,
|
personalRating: 0,
|
||||||
},
|
},
|
||||||
} as SeriesModel);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
throw new Error(`MDB | Unknown media type for id ${id}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { APIModel } from '../APIModel';
|
import type MediaDbPlugin from '../../main';
|
||||||
import { MediaTypeModel } from '../../models/MediaTypeModel';
|
|
||||||
import MediaDbPlugin from '../../main';
|
|
||||||
import { MangaModel } from '../../models/MangaModel';
|
import { MangaModel } from '../../models/MangaModel';
|
||||||
|
import type { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||||
import { MediaType } from '../../utils/MediaType';
|
import { MediaType } from '../../utils/MediaType';
|
||||||
|
import { APIModel } from '../APIModel';
|
||||||
|
|
||||||
export class MALAPIManga extends APIModel {
|
export class MALAPIManga extends APIModel {
|
||||||
plugin: MediaDbPlugin;
|
plugin: MediaDbPlugin;
|
||||||
|
|
@ -73,7 +73,7 @@ export class MALAPIManga extends APIModel {
|
||||||
lastWatched: '',
|
lastWatched: '',
|
||||||
personalRating: 0,
|
personalRating: 0,
|
||||||
},
|
},
|
||||||
} as MangaModel),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,6 +123,6 @@ export class MALAPIManga extends APIModel {
|
||||||
lastWatched: '',
|
lastWatched: '',
|
||||||
personalRating: 0,
|
personalRating: 0,
|
||||||
},
|
},
|
||||||
} as MangaModel);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { APIModel } from '../APIModel';
|
import { Notice } from 'obsidian';
|
||||||
import { MediaTypeModel } from '../../models/MediaTypeModel';
|
|
||||||
import MediaDbPlugin from '../../main';
|
|
||||||
import { GameModel } from '../../models/GameModel';
|
|
||||||
import { requestUrl } 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 { MediaType } from '../../utils/MediaType';
|
||||||
|
import { APIModel } from '../APIModel';
|
||||||
|
|
||||||
export class MobyGamesAPI extends APIModel {
|
export class MobyGamesAPI extends APIModel {
|
||||||
plugin: MediaDbPlugin;
|
plugin: MediaDbPlugin;
|
||||||
|
|
@ -22,7 +23,7 @@ export class MobyGamesAPI extends APIModel {
|
||||||
console.log(`MDB | api "${this.apiName}" queried by Title`);
|
console.log(`MDB | api "${this.apiName}" queried by Title`);
|
||||||
|
|
||||||
if (!this.plugin.settings.MobyGamesKey) {
|
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}`;
|
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,
|
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 { requestUrl } from 'obsidian';
|
||||||
|
import type MediaDbPlugin from '../../main';
|
||||||
|
import type { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||||
import { MusicReleaseModel } from '../../models/MusicReleaseModel';
|
import { MusicReleaseModel } from '../../models/MusicReleaseModel';
|
||||||
import { contactEmail, mediaDbVersion, pluginName } from '../../utils/Utils';
|
|
||||||
import { MediaType } from '../../utils/MediaType';
|
import { MediaType } from '../../utils/MediaType';
|
||||||
|
import { contactEmail, mediaDbVersion, pluginName } from '../../utils/Utils';
|
||||||
|
import { APIModel } from '../APIModel';
|
||||||
|
|
||||||
export class MusicBrainzAPI extends APIModel {
|
export class MusicBrainzAPI extends APIModel {
|
||||||
plugin: MediaDbPlugin;
|
plugin: MediaDbPlugin;
|
||||||
|
|
@ -55,7 +55,7 @@ export class MusicBrainzAPI extends APIModel {
|
||||||
|
|
||||||
artists: result['artist-credit'].map((a: any) => a.name),
|
artists: result['artist-credit'].map((a: any) => a.name),
|
||||||
subType: result['primary-type'],
|
subType: result['primary-type'],
|
||||||
} as MusicReleaseModel),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,6 +97,6 @@ export class MusicBrainzAPI extends APIModel {
|
||||||
userData: {
|
userData: {
|
||||||
personalRating: 0,
|
personalRating: 0,
|
||||||
},
|
},
|
||||||
} as MusicReleaseModel);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { APIModel } from '../APIModel';
|
import { Notice } from 'obsidian';
|
||||||
import { MediaTypeModel } from '../../models/MediaTypeModel';
|
import type MediaDbPlugin from '../../main';
|
||||||
import { MovieModel } from '../../models/MovieModel';
|
|
||||||
import MediaDbPlugin from '../../main';
|
|
||||||
import { SeriesModel } from '../../models/SeriesModel';
|
|
||||||
import { GameModel } from '../../models/GameModel';
|
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 { MediaType } from '../../utils/MediaType';
|
||||||
|
import { APIModel } from '../APIModel';
|
||||||
|
|
||||||
export class OMDbAPI extends APIModel {
|
export class OMDbAPI extends APIModel {
|
||||||
plugin: MediaDbPlugin;
|
plugin: MediaDbPlugin;
|
||||||
|
|
@ -29,7 +30,7 @@ export class OMDbAPI extends APIModel {
|
||||||
console.log(`MDB | api "${this.apiName}" queried by Title`);
|
console.log(`MDB | api "${this.apiName}" queried by Title`);
|
||||||
|
|
||||||
if (!this.plugin.settings.OMDbKey) {
|
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}`;
|
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,
|
year: result.Year,
|
||||||
dataSource: this.apiName,
|
dataSource: this.apiName,
|
||||||
id: result.imdbID,
|
id: result.imdbID,
|
||||||
} as MovieModel),
|
}),
|
||||||
);
|
);
|
||||||
} else if (type === 'series') {
|
} else if (type === 'series') {
|
||||||
ret.push(
|
ret.push(
|
||||||
|
|
@ -84,7 +85,7 @@ export class OMDbAPI extends APIModel {
|
||||||
year: result.Year,
|
year: result.Year,
|
||||||
dataSource: this.apiName,
|
dataSource: this.apiName,
|
||||||
id: result.imdbID,
|
id: result.imdbID,
|
||||||
} as SeriesModel),
|
}),
|
||||||
);
|
);
|
||||||
} else if (type === 'game') {
|
} else if (type === 'game') {
|
||||||
ret.push(
|
ret.push(
|
||||||
|
|
@ -95,7 +96,7 @@ export class OMDbAPI extends APIModel {
|
||||||
year: result.Year,
|
year: result.Year,
|
||||||
dataSource: this.apiName,
|
dataSource: this.apiName,
|
||||||
id: result.imdbID,
|
id: result.imdbID,
|
||||||
} as GameModel),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -161,7 +162,7 @@ export class OMDbAPI extends APIModel {
|
||||||
lastWatched: '',
|
lastWatched: '',
|
||||||
personalRating: 0,
|
personalRating: 0,
|
||||||
},
|
},
|
||||||
} as MovieModel);
|
});
|
||||||
} else if (type === 'series') {
|
} else if (type === 'series') {
|
||||||
return new SeriesModel({
|
return new SeriesModel({
|
||||||
type: type,
|
type: type,
|
||||||
|
|
@ -193,7 +194,7 @@ export class OMDbAPI extends APIModel {
|
||||||
lastWatched: '',
|
lastWatched: '',
|
||||||
personalRating: 0,
|
personalRating: 0,
|
||||||
},
|
},
|
||||||
} as SeriesModel);
|
});
|
||||||
} else if (type === 'game') {
|
} else if (type === 'game') {
|
||||||
return new GameModel({
|
return new GameModel({
|
||||||
type: type,
|
type: type,
|
||||||
|
|
@ -217,9 +218,9 @@ export class OMDbAPI extends APIModel {
|
||||||
played: false,
|
played: false,
|
||||||
personalRating: 0,
|
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 { BookModel } from 'src/models/BookModel';
|
||||||
|
import type MediaDbPlugin from '../../main';
|
||||||
|
import type { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||||
import { MediaType } from '../../utils/MediaType';
|
import { MediaType } from '../../utils/MediaType';
|
||||||
|
import { APIModel } from '../APIModel';
|
||||||
|
|
||||||
export class OpenLibraryAPI extends APIModel {
|
export class OpenLibraryAPI extends APIModel {
|
||||||
plugin: MediaDbPlugin;
|
plugin: MediaDbPlugin;
|
||||||
|
|
@ -42,7 +42,7 @@ export class OpenLibraryAPI extends APIModel {
|
||||||
dataSource: this.apiName,
|
dataSource: this.apiName,
|
||||||
id: result.key,
|
id: result.key,
|
||||||
author: result.author_name ?? 'unknown',
|
author: result.author_name ?? 'unknown',
|
||||||
} as BookModel),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,6 +87,6 @@ export class OpenLibraryAPI extends APIModel {
|
||||||
lastRead: '',
|
lastRead: '',
|
||||||
personalRating: 0,
|
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 { 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 { MediaType } from '../../utils/MediaType';
|
||||||
|
import { APIModel } from '../APIModel';
|
||||||
|
|
||||||
export class SteamAPI extends APIModel {
|
export class SteamAPI extends APIModel {
|
||||||
plugin: MediaDbPlugin;
|
plugin: MediaDbPlugin;
|
||||||
|
|
@ -49,7 +49,7 @@ export class SteamAPI extends APIModel {
|
||||||
year: '',
|
year: '',
|
||||||
dataSource: this.apiName,
|
dataSource: this.apiName,
|
||||||
id: result.appid,
|
id: result.appid,
|
||||||
} as GameModel),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,19 +94,19 @@ export class SteamAPI extends APIModel {
|
||||||
url: `https://store.steampowered.com/app/${result.steam_appid}`,
|
url: `https://store.steampowered.com/app/${result.steam_appid}`,
|
||||||
id: result.steam_appid,
|
id: result.steam_appid,
|
||||||
|
|
||||||
developers: result['developers'],
|
developers: result.developers,
|
||||||
publishers: result['publishers'],
|
publishers: result.publishers,
|
||||||
genres: result.genres?.map((x: any) => x.description) ?? [],
|
genres: result.genres?.map((x: any) => x.description) ?? [],
|
||||||
onlineRating: Number.parseFloat(result.metacritic?.score ?? 0),
|
onlineRating: Number.parseFloat(result.metacritic?.score ?? 0),
|
||||||
image: result.header_image ?? '',
|
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',
|
releaseDate: this.plugin.dateFormatter.format(result.release_date?.date, this.apiDateFormat) ?? 'unknown',
|
||||||
|
|
||||||
userData: {
|
userData: {
|
||||||
played: false,
|
played: false,
|
||||||
personalRating: 0,
|
personalRating: 0,
|
||||||
},
|
},
|
||||||
} as GameModel);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { APIModel } from '../APIModel';
|
import type MediaDbPlugin from '../../main';
|
||||||
import { MediaTypeModel } from '../../models/MediaTypeModel';
|
import type { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||||
import MediaDbPlugin from '../../main';
|
|
||||||
import { WikiModel } from '../../models/WikiModel';
|
import { WikiModel } from '../../models/WikiModel';
|
||||||
import { MediaType } from '../../utils/MediaType';
|
import { MediaType } from '../../utils/MediaType';
|
||||||
|
import { APIModel } from '../APIModel';
|
||||||
|
|
||||||
export class WikipediaAPI extends APIModel {
|
export class WikipediaAPI extends APIModel {
|
||||||
plugin: MediaDbPlugin;
|
plugin: MediaDbPlugin;
|
||||||
|
|
@ -42,7 +42,7 @@ export class WikipediaAPI extends APIModel {
|
||||||
year: '',
|
year: '',
|
||||||
dataSource: this.apiName,
|
dataSource: this.apiName,
|
||||||
id: result.pageid,
|
id: result.pageid,
|
||||||
} as WikiModel),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,10 +73,10 @@ export class WikipediaAPI extends APIModel {
|
||||||
id: result.pageid,
|
id: result.pageid,
|
||||||
|
|
||||||
wikiUrl: result.fullurl,
|
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,
|
length: result.length,
|
||||||
|
|
||||||
userData: {},
|
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 { 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 { APIManager } from './api/APIManager';
|
||||||
import { MediaTypeModel } from './models/MediaTypeModel';
|
import { BoardGameGeekAPI } from './api/apis/BoardGameGeekAPI';
|
||||||
import {
|
import { GiantBombAPI } from './api/apis/GiantBombAPI';
|
||||||
CreateNoteOptions,
|
|
||||||
dateTimeToString,
|
|
||||||
markdownTable,
|
|
||||||
replaceIllegalFileNameCharactersInString,
|
|
||||||
unCamelCase,
|
|
||||||
hasTemplaterPlugin,
|
|
||||||
useTemplaterPluginInFile,
|
|
||||||
} from './utils/Utils';
|
|
||||||
import { OMDbAPI } from './api/apis/OMDbAPI';
|
|
||||||
import { MALAPI } from './api/apis/MALAPI';
|
import { MALAPI } from './api/apis/MALAPI';
|
||||||
import { MALAPIManga } from './api/apis/MALAPIManga';
|
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 { 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 { VNDBAPI } from './api/apis/VNDBAPI';
|
||||||
import { PropertyMapper } from './settings/PropertyMapper';
|
|
||||||
import { MediaDbFolderImportModal } from './modals/MediaDbFolderImportModal';
|
import { MediaDbFolderImportModal } from './modals/MediaDbFolderImportModal';
|
||||||
|
import type { MediaTypeModel } from './models/MediaTypeModel';
|
||||||
|
import { PropertyMapper } from './settings/PropertyMapper';
|
||||||
import { PropertyMapping, PropertyMappingModel } from './settings/PropertyMapping';
|
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 { 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>;
|
export type Metadata = Record<string, unknown>;
|
||||||
|
|
||||||
|
|
@ -38,12 +34,12 @@ export interface MediaTypeModelObj {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class MediaDbPlugin extends Plugin {
|
export default class MediaDbPlugin extends Plugin {
|
||||||
settings: MediaDbPluginSettings;
|
settings!: MediaDbPluginSettings;
|
||||||
apiManager: APIManager;
|
apiManager!: APIManager;
|
||||||
mediaTypeManager: MediaTypeManager;
|
mediaTypeManager!: MediaTypeManager;
|
||||||
modelPropertyMapper: PropertyMapper;
|
modelPropertyMapper!: PropertyMapper;
|
||||||
modalHelper: ModalHelper;
|
modalHelper!: ModalHelper;
|
||||||
dateFormatter: DateFormatter;
|
dateFormatter!: DateFormatter;
|
||||||
|
|
||||||
frontMatterRexExpPattern: string = '^(---)\\n[\\s\\S]*?\\n---';
|
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 BoardGameGeekAPI(this));
|
||||||
this.apiManager.registerAPI(new OpenLibraryAPI(this));
|
this.apiManager.registerAPI(new OpenLibraryAPI(this));
|
||||||
this.apiManager.registerAPI(new MobyGamesAPI(this));
|
this.apiManager.registerAPI(new MobyGamesAPI(this));
|
||||||
|
this.apiManager.registerAPI(new GiantBombAPI(this));
|
||||||
this.apiManager.registerAPI(new VNDBAPI(this));
|
this.apiManager.registerAPI(new VNDBAPI(this));
|
||||||
// this.apiManager.registerAPI(new LocGovAPI(this)); // TODO: parse data
|
// this.apiManager.registerAPI(new LocGovAPI(this)); // TODO: parse data
|
||||||
|
|
||||||
|
|
@ -165,7 +162,7 @@ export default class MediaDbPlugin extends Plugin {
|
||||||
* - maybe custom link syntax
|
* - maybe custom link syntax
|
||||||
*/
|
*/
|
||||||
async createLinkWithSearchModal(): Promise<void> {
|
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);
|
return await this.apiManager.query(advancedSearchModalData.query, advancedSearchModalData.apis);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -173,7 +170,7 @@ export default class MediaDbPlugin extends Plugin {
|
||||||
return;
|
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);
|
return await this.queryDetails(selectModalData.selected);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -193,7 +190,7 @@ export default class MediaDbPlugin extends Plugin {
|
||||||
|
|
||||||
async createEntryWithSearchModal(searchModalOptions?: SearchModalOptions): Promise<void> {
|
async createEntryWithSearchModal(searchModalOptions?: SearchModalOptions): Promise<void> {
|
||||||
let types: string[] = [];
|
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;
|
types = searchModalData.types;
|
||||||
const apis = this.apiManager.apis.filter(x => x.hasTypeOverlap(searchModalData.types)).map(x => x.apiName);
|
const apis = this.apiManager.apis.filter(x => x.hasTypeOverlap(searchModalData.types)).map(x => x.apiName);
|
||||||
try {
|
try {
|
||||||
|
|
@ -214,12 +211,13 @@ export default class MediaDbPlugin extends Plugin {
|
||||||
apiSearchResults = apiSearchResults.filter(x => types.contains(x.type));
|
apiSearchResults = apiSearchResults.filter(x => types.contains(x.type));
|
||||||
|
|
||||||
let selectResults: MediaTypeModel[];
|
let selectResults: MediaTypeModel[];
|
||||||
let proceed: boolean;
|
let proceed: boolean = false;
|
||||||
|
|
||||||
while (!proceed) {
|
while (!proceed) {
|
||||||
selectResults = await this.modalHelper.openSelectModal({ elements: apiSearchResults }, async selectModalData => {
|
selectResults =
|
||||||
return await this.queryDetails(selectModalData.selected);
|
(await this.modalHelper.openSelectModal({ elements: apiSearchResults }, async selectModalData => {
|
||||||
});
|
return await this.queryDetails(selectModalData.selected);
|
||||||
|
})) ?? [];
|
||||||
if (!selectResults) {
|
if (!selectResults) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -229,11 +227,11 @@ export default class MediaDbPlugin extends Plugin {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.createMediaDbNotes(selectResults);
|
await this.createMediaDbNotes(selectResults!);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createEntryWithAdvancedSearchModal(): Promise<void> {
|
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);
|
return await this.apiManager.query(advancedSearchModalData.query, advancedSearchModalData.apis);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -243,12 +241,13 @@ export default class MediaDbPlugin extends Plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
let selectResults: MediaTypeModel[];
|
let selectResults: MediaTypeModel[];
|
||||||
let proceed: boolean;
|
let proceed: boolean = false;
|
||||||
|
|
||||||
while (!proceed) {
|
while (!proceed) {
|
||||||
selectResults = await this.modalHelper.openSelectModal({ elements: apiSearchResults }, async selectModalData => {
|
selectResults =
|
||||||
return await this.queryDetails(selectModalData.selected);
|
(await this.modalHelper.openSelectModal({ elements: apiSearchResults }, async selectModalData => {
|
||||||
});
|
return await this.queryDetails(selectModalData.selected);
|
||||||
|
})) ?? [];
|
||||||
if (!selectResults) {
|
if (!selectResults) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -258,12 +257,12 @@ export default class MediaDbPlugin extends Plugin {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.createMediaDbNotes(selectResults);
|
await this.createMediaDbNotes(selectResults!);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createEntryWithIdSearchModal(): Promise<void> {
|
async createEntryWithIdSearchModal(): Promise<void> {
|
||||||
let idSearchResult: MediaTypeModel;
|
let idSearchResult: MediaTypeModel | undefined = undefined;
|
||||||
let proceed: boolean;
|
let proceed: boolean = false;
|
||||||
|
|
||||||
while (!proceed) {
|
while (!proceed) {
|
||||||
idSearchResult = await this.modalHelper.openIdSearchModal({}, async idSearchModalData => {
|
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 });
|
await this.createMediaDbNoteFromModel(idSearchResult, { attachTemplate: true, openNote: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -290,11 +292,9 @@ export default class MediaDbPlugin extends Plugin {
|
||||||
async queryDetails(models: MediaTypeModel[]): Promise<MediaTypeModel[]> {
|
async queryDetails(models: MediaTypeModel[]): Promise<MediaTypeModel[]> {
|
||||||
const detailModels: MediaTypeModel[] = [];
|
const detailModels: MediaTypeModel[] = [];
|
||||||
for (const model of models) {
|
for (const model of models) {
|
||||||
try {
|
const res = await this.apiManager.queryDetailedInfo(model);
|
||||||
detailModels.push(await this.apiManager.queryDetailedInfo(model));
|
if (res) {
|
||||||
} catch (e) {
|
detailModels.push(res);
|
||||||
console.warn(e);
|
|
||||||
new Notice(e.toString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return detailModels;
|
return detailModels;
|
||||||
|
|
@ -319,7 +319,7 @@ export default class MediaDbPlugin extends Plugin {
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(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
|
// Updating a previous file
|
||||||
if (options.attachFile) {
|
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
|
// Use contents (below front matter) from previous file
|
||||||
fileContent = await this.app.vault.read(options.attachFile);
|
fileContent = await this.app.vault.read(options.attachFile);
|
||||||
|
|
@ -443,7 +443,7 @@ export default class MediaDbPlugin extends Plugin {
|
||||||
return { fileMetadata: fileMetadata, fileContent: fileContent };
|
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) {
|
if (!template) {
|
||||||
return { fileMetadata: fileMetadata, fileContent: fileContent };
|
return { fileMetadata: fileMetadata, fileContent: fileContent };
|
||||||
}
|
}
|
||||||
|
|
@ -486,7 +486,7 @@ export default class MediaDbPlugin extends Plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
getMetadataFromFileCache(file: TFile): Metadata {
|
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 ?? {});
|
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
|
// find and possibly create the folder set in settings or passed in folder
|
||||||
const folder = options.folder ?? this.app.vault.getAbstractFileByPath('/');
|
const folder = options.folder ?? this.app.vault.getAbstractFileByPath('/');
|
||||||
|
|
||||||
|
if (!folder || !(folder instanceof TFolder)) {
|
||||||
|
throw new Error('MDB | invalid folder');
|
||||||
|
}
|
||||||
|
|
||||||
fileName = replaceIllegalFileNameCharactersInString(fileName);
|
fileName = replaceIllegalFileNameCharactersInString(fileName);
|
||||||
const filePath = `${folder.path}/${fileName}.md`;
|
const filePath = `${folder.path}/${fileName}.md`;
|
||||||
|
|
||||||
|
|
@ -519,7 +523,7 @@ export default class MediaDbPlugin extends Plugin {
|
||||||
const activeLeaf = this.app.workspace.getUnpinnedLeaf();
|
const activeLeaf = this.app.workspace.getUnpinnedLeaf();
|
||||||
if (!activeLeaf) {
|
if (!activeLeaf) {
|
||||||
console.warn('MDB | no active leaf, not opening newly created note');
|
console.warn('MDB | no active leaf, not opening newly created note');
|
||||||
return;
|
return targetFile;
|
||||||
}
|
}
|
||||||
await activeLeaf.openFile(targetFile, { state: { mode: 'source' } });
|
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.
|
* 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> {
|
async updateActiveNote(onlyMetadata: boolean = false): Promise<void> {
|
||||||
const activeFile: TFile = this.app.workspace.getActiveFile();
|
const activeFile = this.app.workspace.getActiveFile() ?? undefined;
|
||||||
if (!activeFile) {
|
if (!activeFile) {
|
||||||
throw new Error('MDB | there is no active note');
|
throw new Error('MDB | there is no active note');
|
||||||
}
|
}
|
||||||
|
|
@ -560,9 +564,9 @@ export default class MediaDbPlugin extends Plugin {
|
||||||
// console.debug(newMediaTypeModel);
|
// console.debug(newMediaTypeModel);
|
||||||
|
|
||||||
if (onlyMetadata) {
|
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 {
|
} 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 {
|
try {
|
||||||
results = await this.apiManager.query(title, [selectedAPI]);
|
results = await this.apiManager.query(title, [selectedAPI]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
erroredFiles.push({ filePath: file.path, error: e.toString() });
|
erroredFiles.push({ filePath: file.path, error: `${e}` });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!results || results.length === 0) {
|
if (!results || results.length === 0) {
|
||||||
|
|
@ -631,7 +635,7 @@ export default class MediaDbPlugin extends Plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
const detailedResults = await this.queryDetails(selectModalResult.data.selected);
|
const detailedResults = await this.queryDetails(selectModalResult.data.selected);
|
||||||
await this.createMediaDbNotes(detailedResults, appendContent ? file : null);
|
await this.createMediaDbNotes(detailedResults, appendContent ? file : undefined);
|
||||||
|
|
||||||
selectModal.close();
|
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
|
// migrate the settings loaded from the disk to match the structure of the default settings
|
||||||
const newPropertyMappings: PropertyMappingModel[] = [];
|
const newPropertyMappings: PropertyMappingModel[] = [];
|
||||||
for (const defaultPropertyMappingModel of defaultSettings.propertyMappingModels) {
|
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 (newPropertyMappingModel === undefined) {
|
||||||
// if the propertyMappingModel exists in the default settings but not the loaded settings, add it
|
// if the propertyMappingModel exists in the default settings but not the loaded settings, add it
|
||||||
newPropertyMappings.push(defaultPropertyMappingModel);
|
newPropertyMappings.push(defaultPropertyMappingModel);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import { ButtonComponent, Modal, Notice, Setting, TextComponent, ToggleComponent } from 'obsidian';
|
import type { ButtonComponent } from 'obsidian';
|
||||||
import { MediaTypeModel } from '../models/MediaTypeModel';
|
import { Modal, Notice, Setting, TextComponent, ToggleComponent } from 'obsidian';
|
||||||
import MediaDbPlugin from '../main';
|
import type MediaDbPlugin from '../main';
|
||||||
import { ADVANCED_SEARCH_MODAL_DEFAULT_OPTIONS, AdvancedSearchModalData, AdvancedSearchModalOptions } from '../utils/ModalHelper';
|
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 {
|
export class MediaDbAdvancedSearchModal extends Modal {
|
||||||
plugin: MediaDbPlugin;
|
plugin: MediaDbPlugin;
|
||||||
|
|
@ -9,9 +11,9 @@ export class MediaDbAdvancedSearchModal extends Modal {
|
||||||
query: string;
|
query: string;
|
||||||
isBusy: boolean;
|
isBusy: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
selectedApis: { name: string; selected: boolean }[];
|
selectedApis: string[];
|
||||||
|
|
||||||
searchBtn: ButtonComponent;
|
searchBtn?: ButtonComponent;
|
||||||
|
|
||||||
submitCallback?: (res: AdvancedSearchModalData) => void;
|
submitCallback?: (res: AdvancedSearchModalData) => void;
|
||||||
closeCallback?: (err?: Error) => void;
|
closeCallback?: (err?: Error) => void;
|
||||||
|
|
@ -22,12 +24,9 @@ export class MediaDbAdvancedSearchModal extends Modal {
|
||||||
|
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.selectedApis = [];
|
this.selectedApis = [];
|
||||||
this.title = advancedSearchModalOptions.modalTitle;
|
this.title = advancedSearchModalOptions.modalTitle ?? '';
|
||||||
this.query = advancedSearchModalOptions.prefilledSearchString;
|
this.query = advancedSearchModalOptions.prefilledSearchString ?? '';
|
||||||
|
this.isBusy = false;
|
||||||
for (const api of this.plugin.apiManager.apis) {
|
|
||||||
this.selectedApis.push({ name: api.apiName, selected: advancedSearchModalOptions.preselectedAPIs.contains(api.apiName) });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setSubmitCallback(submitCallback: (res: AdvancedSearchModalData) => void): void {
|
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) {
|
if (!this.query || this.query.length < 3) {
|
||||||
new Notice('MDB | Query too short');
|
new Notice('MDB | Query too short');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const apis: string[] = this.selectedApis.filter(x => x.selected).map(x => x.name);
|
const apis: string[] = this.selectedApis;
|
||||||
|
|
||||||
if (apis.length === 0) {
|
if (apis.length === 0) {
|
||||||
new Notice('MDB | No API selected');
|
new Notice('MDB | No API selected');
|
||||||
|
|
@ -59,10 +58,10 @@ export class MediaDbAdvancedSearchModal extends Modal {
|
||||||
|
|
||||||
if (!this.isBusy) {
|
if (!this.isBusy) {
|
||||||
this.isBusy = true;
|
this.isBusy = true;
|
||||||
this.searchBtn.setDisabled(false);
|
this.searchBtn?.setDisabled(false);
|
||||||
this.searchBtn.setButtonText('Searching...');
|
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);
|
const apiToggleComponent = new ToggleComponent(apiToggleComponentWrapper);
|
||||||
apiToggleComponent.setTooltip(api.apiName);
|
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 => {
|
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);
|
apiToggleComponentWrapper.appendChild(apiToggleComponent.toggleEl);
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +127,7 @@ export class MediaDbAdvancedSearchModal extends Modal {
|
||||||
}
|
}
|
||||||
|
|
||||||
onClose(): void {
|
onClose(): void {
|
||||||
this.closeCallback();
|
this.closeCallback?.();
|
||||||
const { contentEl } = this;
|
const { contentEl } = this;
|
||||||
contentEl.empty();
|
contentEl.empty();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
import { App, ButtonComponent, DropdownComponent, Modal, Setting, TextComponent, ToggleComponent } from 'obsidian';
|
import type { App, ButtonComponent } from 'obsidian';
|
||||||
import MediaDbPlugin from '../main';
|
import { DropdownComponent, Modal, Setting, TextComponent, ToggleComponent } from 'obsidian';
|
||||||
|
import type MediaDbPlugin from '../main';
|
||||||
|
|
||||||
export class MediaDbFolderImportModal extends Modal {
|
export class MediaDbFolderImportModal extends Modal {
|
||||||
plugin: MediaDbPlugin;
|
plugin: MediaDbPlugin;
|
||||||
onSubmit: (selectedAPI: string, titleFieldName: string, appendContent: boolean) => void;
|
onSubmit: (selectedAPI: string, titleFieldName: string, appendContent: boolean) => void;
|
||||||
selectedApi: string;
|
selectedApi: string;
|
||||||
searchBtn: ButtonComponent;
|
searchBtn?: ButtonComponent;
|
||||||
titleFieldName: string;
|
titleFieldName: string;
|
||||||
appendContent: boolean;
|
appendContent: boolean;
|
||||||
|
|
||||||
|
|
@ -14,6 +15,8 @@ export class MediaDbFolderImportModal extends Modal {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.onSubmit = onSubmit;
|
this.onSubmit = onSubmit;
|
||||||
this.selectedApi = plugin.apiManager.apis[0].apiName;
|
this.selectedApi = plugin.apiManager.apis[0].apiName;
|
||||||
|
this.titleFieldName = '';
|
||||||
|
this.appendContent = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
submit(): void {
|
submit(): void {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import { ButtonComponent, DropdownComponent, Modal, Notice, Setting, TextComponent } from 'obsidian';
|
import type { ButtonComponent } from 'obsidian';
|
||||||
import { MediaTypeModel } from '../models/MediaTypeModel';
|
import { DropdownComponent, Modal, Notice, Setting, TextComponent } from 'obsidian';
|
||||||
import MediaDbPlugin from '../main';
|
import type MediaDbPlugin from '../main';
|
||||||
import { ID_SEARCH_MODAL_DEFAULT_OPTIONS, IdSearchModalData, IdSearchModalOptions } from '../utils/ModalHelper';
|
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 {
|
export class MediaDbIdSearchModal extends Modal {
|
||||||
plugin: MediaDbPlugin;
|
plugin: MediaDbPlugin;
|
||||||
|
|
@ -11,7 +13,7 @@ export class MediaDbIdSearchModal extends Modal {
|
||||||
title: string;
|
title: string;
|
||||||
selectedApi: string;
|
selectedApi: string;
|
||||||
|
|
||||||
searchBtn: ButtonComponent;
|
searchBtn?: ButtonComponent;
|
||||||
|
|
||||||
submitCallback?: (res: IdSearchModalData, err?: Error) => void;
|
submitCallback?: (res: IdSearchModalData, err?: Error) => void;
|
||||||
closeCallback?: (err?: Error) => void;
|
closeCallback?: (err?: Error) => void;
|
||||||
|
|
@ -21,8 +23,10 @@ export class MediaDbIdSearchModal extends Modal {
|
||||||
super(plugin.app);
|
super(plugin.app);
|
||||||
|
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.title = idSearchModalOptions.modalTitle;
|
this.title = idSearchModalOptions.modalTitle ?? '';
|
||||||
this.selectedApi = idSearchModalOptions.preselectedAPI || plugin.apiManager.apis[0].apiName;
|
this.selectedApi = idSearchModalOptions.preselectedAPI || plugin.apiManager.apis[0].apiName;
|
||||||
|
this.query = '';
|
||||||
|
this.isBusy = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSubmitCallback(submitCallback: (res: IdSearchModalData, err?: Error) => void): void {
|
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) {
|
if (!this.query) {
|
||||||
new Notice('MDB | no Id entered');
|
new Notice('MDB | no Id entered');
|
||||||
return;
|
return;
|
||||||
|
|
@ -52,10 +56,10 @@ export class MediaDbIdSearchModal extends Modal {
|
||||||
|
|
||||||
if (!this.isBusy) {
|
if (!this.isBusy) {
|
||||||
this.isBusy = true;
|
this.isBusy = true;
|
||||||
this.searchBtn.setDisabled(false);
|
this.searchBtn?.setDisabled(false);
|
||||||
this.searchBtn.setButtonText('Searching...');
|
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 {
|
onClose(): void {
|
||||||
this.closeCallback();
|
this.closeCallback?.();
|
||||||
const { contentEl } = this;
|
const { contentEl } = this;
|
||||||
contentEl.empty();
|
contentEl.empty();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,19 @@
|
||||||
import { ButtonComponent, Component, MarkdownRenderer, Modal, Setting } from 'obsidian';
|
import type { ButtonComponent } from 'obsidian';
|
||||||
import MediaDbPlugin from 'src/main';
|
import { Component, MarkdownRenderer, Modal, Setting } from 'obsidian';
|
||||||
import { MediaTypeModel } from 'src/models/MediaTypeModel';
|
import type MediaDbPlugin from 'src/main';
|
||||||
import { PREVIEW_MODAL_DEFAULT_OPTIONS, PreviewModalData, PreviewModalOptions } from '../utils/ModalHelper';
|
import type { MediaTypeModel } from 'src/models/MediaTypeModel';
|
||||||
import { CreateNoteOptions } from '../utils/Utils';
|
import type { PreviewModalData, PreviewModalOptions } from '../utils/ModalHelper';
|
||||||
|
import { PREVIEW_MODAL_DEFAULT_OPTIONS } from '../utils/ModalHelper';
|
||||||
|
|
||||||
export class MediaDbPreviewModal extends Modal {
|
export class MediaDbPreviewModal extends Modal {
|
||||||
plugin: MediaDbPlugin;
|
plugin: MediaDbPlugin;
|
||||||
|
|
||||||
createNoteOptions: CreateNoteOptions;
|
|
||||||
elements: MediaTypeModel[];
|
elements: MediaTypeModel[];
|
||||||
isBusy: boolean;
|
|
||||||
title: string;
|
title: string;
|
||||||
cancelButton: ButtonComponent;
|
|
||||||
submitButton: ButtonComponent;
|
|
||||||
markdownComponent: Component;
|
markdownComponent: Component;
|
||||||
|
|
||||||
submitCallback: (previewModalData: PreviewModalData) => void;
|
submitCallback?: (previewModalData: PreviewModalData) => void;
|
||||||
closeCallback: (err?: Error) => void;
|
closeCallback?: (err?: Error) => void;
|
||||||
|
|
||||||
constructor(plugin: MediaDbPlugin, previewModalOptions: PreviewModalOptions) {
|
constructor(plugin: MediaDbPlugin, previewModalOptions: PreviewModalOptions) {
|
||||||
previewModalOptions = Object.assign({}, PREVIEW_MODAL_DEFAULT_OPTIONS, previewModalOptions);
|
previewModalOptions = Object.assign({}, PREVIEW_MODAL_DEFAULT_OPTIONS, previewModalOptions);
|
||||||
|
|
@ -24,8 +21,8 @@ export class MediaDbPreviewModal extends Modal {
|
||||||
super(plugin.app);
|
super(plugin.app);
|
||||||
|
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.title = previewModalOptions.modalTitle;
|
this.title = previewModalOptions.modalTitle ?? '';
|
||||||
this.elements = previewModalOptions.elements;
|
this.elements = previewModalOptions.elements ?? [];
|
||||||
|
|
||||||
this.markdownComponent = new Component();
|
this.markdownComponent = new Component();
|
||||||
}
|
}
|
||||||
|
|
@ -70,14 +67,12 @@ export class MediaDbPreviewModal extends Modal {
|
||||||
btn.setButtonText('Cancel');
|
btn.setButtonText('Cancel');
|
||||||
btn.onClick(() => this.close());
|
btn.onClick(() => this.close());
|
||||||
btn.buttonEl.addClass('media-db-plugin-button');
|
btn.buttonEl.addClass('media-db-plugin-button');
|
||||||
this.cancelButton = btn;
|
|
||||||
});
|
});
|
||||||
bottomSettingRow.addButton(btn => {
|
bottomSettingRow.addButton(btn => {
|
||||||
btn.setButtonText('Ok');
|
btn.setButtonText('Ok');
|
||||||
btn.setCta();
|
btn.setCta();
|
||||||
btn.onClick(() => this.submitCallback({ confirmed: true }));
|
btn.onClick(() => this.submitCallback?.({ confirmed: true }));
|
||||||
btn.buttonEl.addClass('media-db-plugin-button');
|
btn.buttonEl.addClass('media-db-plugin-button');
|
||||||
this.submitButton = btn;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,6 +82,6 @@ export class MediaDbPreviewModal extends Modal {
|
||||||
|
|
||||||
onClose(): void {
|
onClose(): void {
|
||||||
this.markdownComponent.unload();
|
this.markdownComponent.unload();
|
||||||
this.closeCallback();
|
this.closeCallback?.();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
import { ButtonComponent, Modal, Notice, Setting, TextComponent, ToggleComponent } from 'obsidian';
|
import type { ButtonComponent } from 'obsidian';
|
||||||
import { MediaTypeModel } from '../models/MediaTypeModel';
|
import { Modal, Notice, Setting, TextComponent, ToggleComponent } from 'obsidian';
|
||||||
import MediaDbPlugin from '../main';
|
import type MediaDbPlugin from '../main';
|
||||||
import { SEARCH_MODAL_DEFAULT_OPTIONS, SearchModalData, SearchModalOptions } from '../utils/ModalHelper';
|
import type { MediaTypeModel } from '../models/MediaTypeModel';
|
||||||
|
import type { MediaType } from '../utils/MediaType';
|
||||||
import { MEDIA_TYPES } from '../utils/MediaTypeManager';
|
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 { unCamelCase } from '../utils/Utils';
|
||||||
import { MediaType } from '../utils/MediaType';
|
|
||||||
|
|
||||||
export class MediaDbSearchModal extends Modal {
|
export class MediaDbSearchModal extends Modal {
|
||||||
plugin: MediaDbPlugin;
|
plugin: MediaDbPlugin;
|
||||||
|
|
@ -12,9 +14,9 @@ export class MediaDbSearchModal extends Modal {
|
||||||
query: string;
|
query: string;
|
||||||
isBusy: boolean;
|
isBusy: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
selectedTypes: { name: MediaType; selected: boolean }[];
|
selectedTypes: MediaType[];
|
||||||
|
|
||||||
searchBtn: ButtonComponent;
|
searchBtn?: ButtonComponent;
|
||||||
|
|
||||||
submitCallback?: (res: SearchModalData) => void;
|
submitCallback?: (res: SearchModalData) => void;
|
||||||
closeCallback?: (err?: Error) => void;
|
closeCallback?: (err?: Error) => void;
|
||||||
|
|
@ -24,13 +26,10 @@ export class MediaDbSearchModal extends Modal {
|
||||||
super(plugin.app);
|
super(plugin.app);
|
||||||
|
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.selectedTypes = [];
|
this.selectedTypes = [...(searchModalOptions.preselectedTypes ?? [])];
|
||||||
this.title = searchModalOptions.modalTitle;
|
this.title = searchModalOptions.modalTitle ?? '';
|
||||||
this.query = searchModalOptions.prefilledSearchString;
|
this.query = searchModalOptions.prefilledSearchString ?? '';
|
||||||
|
this.isBusy = false;
|
||||||
for (const mediaType of MEDIA_TYPES) {
|
|
||||||
this.selectedTypes.push({ name: mediaType, selected: searchModalOptions.preselectedTypes.contains(mediaType) });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setSubmitCallback(submitCallback: (res: SearchModalData) => void): void {
|
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) {
|
if (!this.query || this.query.length < 3) {
|
||||||
new Notice('MDB | Query too short');
|
new Notice('MDB | Query too short');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const types: MediaType[] = this.selectedTypes.filter(x => x.selected).map(x => x.name);
|
const types: MediaType[] = this.selectedTypes;
|
||||||
|
|
||||||
if (types.length === 0) {
|
if (types.length === 0) {
|
||||||
new Notice('MDB | No Type selected');
|
new Notice('MDB | No Type selected');
|
||||||
|
|
@ -62,10 +61,10 @@ export class MediaDbSearchModal extends Modal {
|
||||||
|
|
||||||
if (!this.isBusy) {
|
if (!this.isBusy) {
|
||||||
this.isBusy = true;
|
this.isBusy = true;
|
||||||
this.searchBtn.setDisabled(false);
|
this.searchBtn?.setDisabled(false);
|
||||||
this.searchBtn.setButtonText('Searching...');
|
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 placeholder = 'Search by title';
|
||||||
const searchComponent = new TextComponent(contentEl);
|
const searchComponent = new TextComponent(contentEl);
|
||||||
let currentToggle: ToggleComponent = null;
|
let currentToggle: ToggleComponent | undefined = undefined;
|
||||||
|
|
||||||
searchComponent.inputEl.style.width = '100%';
|
searchComponent.inputEl.style.width = '100%';
|
||||||
searchComponent.setPlaceholder(placeholder);
|
searchComponent.setPlaceholder(placeholder);
|
||||||
|
|
@ -100,7 +99,7 @@ export class MediaDbSearchModal extends Modal {
|
||||||
|
|
||||||
const apiToggleComponent = new ToggleComponent(apiToggleComponentWrapper);
|
const apiToggleComponent = new ToggleComponent(apiToggleComponentWrapper);
|
||||||
apiToggleComponent.setTooltip(unCamelCase(mediaType));
|
apiToggleComponent.setTooltip(unCamelCase(mediaType));
|
||||||
apiToggleComponent.setValue(this.selectedTypes.find(x => x.name === mediaType).selected);
|
apiToggleComponent.setValue(this.selectedTypes.contains(mediaType));
|
||||||
if (apiToggleComponent.getValue()) {
|
if (apiToggleComponent.getValue()) {
|
||||||
currentToggle = apiToggleComponent;
|
currentToggle = apiToggleComponent;
|
||||||
}
|
}
|
||||||
|
|
@ -108,13 +107,13 @@ export class MediaDbSearchModal extends Modal {
|
||||||
if (value) {
|
if (value) {
|
||||||
if (currentToggle && currentToggle !== apiToggleComponent) {
|
if (currentToggle && currentToggle !== apiToggleComponent) {
|
||||||
currentToggle.setValue(false);
|
currentToggle.setValue(false);
|
||||||
this.selectedTypes.find(x => x.name === mediaType).selected = false;
|
this.selectedTypes = this.selectedTypes.filter(x => x !== mediaType);
|
||||||
}
|
}
|
||||||
currentToggle = apiToggleComponent;
|
currentToggle = apiToggleComponent;
|
||||||
this.selectedTypes.find(x => x.name === mediaType).selected = true;
|
this.selectedTypes.push(mediaType);
|
||||||
} else {
|
} else {
|
||||||
currentToggle = null;
|
currentToggle = undefined;
|
||||||
this.selectedTypes.find(x => x.name === mediaType).selected = false;
|
this.selectedTypes = this.selectedTypes.filter(x => x !== mediaType);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
apiToggleComponentWrapper.appendChild(apiToggleComponent.toggleEl);
|
apiToggleComponentWrapper.appendChild(apiToggleComponent.toggleEl);
|
||||||
|
|
@ -140,7 +139,7 @@ export class MediaDbSearchModal extends Modal {
|
||||||
}
|
}
|
||||||
|
|
||||||
onClose(): void {
|
onClose(): void {
|
||||||
this.closeCallback();
|
this.closeCallback?.();
|
||||||
const { contentEl } = this;
|
const { contentEl } = this;
|
||||||
contentEl.empty();
|
contentEl.empty();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { MediaTypeModel } from '../models/MediaTypeModel';
|
import type MediaDbPlugin from '../main';
|
||||||
import 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 { SelectModal } from './SelectModal';
|
||||||
import { SELECT_MODAL_OPTIONS_DEFAULT, SelectModalData, SelectModalOptions } from '../utils/ModalHelper';
|
|
||||||
|
|
||||||
export class MediaDbSearchResultModal extends SelectModal<MediaTypeModel> {
|
export class MediaDbSearchResultModal extends SelectModal<MediaTypeModel> {
|
||||||
plugin: MediaDbPlugin;
|
plugin: MediaDbPlugin;
|
||||||
|
|
@ -9,18 +10,18 @@ export class MediaDbSearchResultModal extends SelectModal<MediaTypeModel> {
|
||||||
busy: boolean;
|
busy: boolean;
|
||||||
sendCallback: boolean;
|
sendCallback: boolean;
|
||||||
|
|
||||||
submitCallback: (res: SelectModalData) => void;
|
submitCallback?: (res: SelectModalData) => void;
|
||||||
closeCallback: (err?: Error) => void;
|
closeCallback?: (err?: Error) => void;
|
||||||
skipCallback: () => void;
|
skipCallback?: () => void;
|
||||||
|
|
||||||
constructor(plugin: MediaDbPlugin, selectModalOptions: SelectModalOptions) {
|
constructor(plugin: MediaDbPlugin, selectModalOptions: SelectModalOptions) {
|
||||||
selectModalOptions = Object.assign({}, SELECT_MODAL_OPTIONS_DEFAULT, 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.plugin = plugin;
|
||||||
|
|
||||||
this.title = selectModalOptions.modalTitle;
|
this.title = selectModalOptions.modalTitle ?? '';
|
||||||
this.description = 'Select one or multiple search results.';
|
this.description = 'Select one or multiple search results.';
|
||||||
this.addSkipButton = selectModalOptions.skipButton;
|
this.addSkipButton = selectModalOptions.skipButton ?? false;
|
||||||
|
|
||||||
this.busy = false;
|
this.busy = false;
|
||||||
|
|
||||||
|
|
@ -50,17 +51,17 @@ export class MediaDbSearchResultModal extends SelectModal<MediaTypeModel> {
|
||||||
submit(): void {
|
submit(): void {
|
||||||
if (!this.busy) {
|
if (!this.busy) {
|
||||||
this.busy = true;
|
this.busy = true;
|
||||||
this.submitButton.setButtonText('Creating entry...');
|
this.submitButton?.setButtonText('Creating entry...');
|
||||||
this.submitCallback({ selected: this.selectModalElements.filter(x => x.isActive()).map(x => x.value) });
|
this.submitCallback?.({ selected: this.selectModalElements.filter(x => x.isActive()).map(x => x.value) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
skip(): void {
|
skip(): void {
|
||||||
this.skipButton.setButtonText('Skipping...');
|
this.skipButton?.setButtonText('Skipping...');
|
||||||
this.skipCallback();
|
this.skipCallback?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
onClose(): void {
|
onClose(): void {
|
||||||
this.closeCallback();
|
this.closeCallback?.();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { App, ButtonComponent, Modal, Setting } from 'obsidian';
|
import type { App, ButtonComponent } from 'obsidian';
|
||||||
import { SelectModalElement } from './SelectModalElement';
|
import { Modal, Setting } from 'obsidian';
|
||||||
import { mod } from '../utils/Utils';
|
import { mod } from '../utils/Utils';
|
||||||
|
import { SelectModalElement } from './SelectModalElement';
|
||||||
|
|
||||||
export abstract class SelectModal<T> extends Modal {
|
export abstract class SelectModal<T> extends Modal {
|
||||||
allowMultiSelect: boolean;
|
allowMultiSelect: boolean;
|
||||||
|
|
@ -142,7 +143,7 @@ export abstract class SelectModal<T> extends Modal {
|
||||||
}
|
}
|
||||||
|
|
||||||
// nothing is highlighted
|
// nothing is highlighted
|
||||||
this.selectModalElements.last().setHighlighted(true);
|
this.selectModalElements.last()?.setHighlighted(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
highlightDown(): void {
|
highlightDown(): void {
|
||||||
|
|
@ -154,20 +155,20 @@ export abstract class SelectModal<T> extends Modal {
|
||||||
}
|
}
|
||||||
|
|
||||||
// nothing is highlighted
|
// nothing is highlighted
|
||||||
this.selectModalElements.first().setHighlighted(true);
|
this.selectModalElements.first()?.setHighlighted(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getNextSelectModalElement(selectModalElement: SelectModalElement<T>): SelectModalElement<T> {
|
private getNextSelectModalElement(selectModalElement: SelectModalElement<T>): SelectModalElement<T> {
|
||||||
let nextId = selectModalElement.id + 1;
|
let nextId = selectModalElement.id + 1;
|
||||||
nextId = mod(nextId, this.selectModalElements.length);
|
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> {
|
private getPreviousSelectModalElement(selectModalElement: SelectModalElement<T>): SelectModalElement<T> {
|
||||||
let nextId = selectModalElement.id - 1;
|
let nextId = selectModalElement.id - 1;
|
||||||
nextId = mod(nextId, this.selectModalElements.length);
|
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> {
|
export class SelectModalElement<T> {
|
||||||
selectModal: SelectModal<T>;
|
selectModal: SelectModal<T>;
|
||||||
|
|
@ -35,6 +35,8 @@ export class SelectModalElement<T> {
|
||||||
this.element.on('mouseleave', '#' + this.getHTMLId(), () => {
|
this.element.on('mouseleave', '#' + this.getHTMLId(), () => {
|
||||||
this.setHighlighted(false);
|
this.setHighlighted(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.highlighted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
getHTMLId(): string {
|
getHTMLId(): string {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { MediaTypeModel } from './MediaTypeModel';
|
|
||||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
|
||||||
import { MediaType } from '../utils/MediaType';
|
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 {
|
export class BoardGameModel extends MediaTypeModel {
|
||||||
genres: string[];
|
genres: string[];
|
||||||
|
|
@ -19,23 +22,23 @@ export class BoardGameModel extends MediaTypeModel {
|
||||||
personalRating: number;
|
personalRating: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(obj: any = {}) {
|
constructor(obj: BoardGameData) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.genres = undefined;
|
this.genres = [];
|
||||||
this.onlineRating = undefined;
|
this.onlineRating = 0;
|
||||||
this.minPlayers = undefined;
|
this.complexityRating = 0;
|
||||||
this.maxPlayers = undefined;
|
this.minPlayers = 0;
|
||||||
this.playtime = undefined;
|
this.maxPlayers = 0;
|
||||||
this.publishers = undefined;
|
this.playtime = '';
|
||||||
this.complexityRating = undefined;
|
this.publishers = [];
|
||||||
this.image = undefined;
|
this.image = '';
|
||||||
|
|
||||||
this.released = undefined;
|
this.released = false;
|
||||||
|
|
||||||
this.userData = {
|
this.userData = {
|
||||||
played: undefined,
|
played: false,
|
||||||
personalRating: undefined,
|
personalRating: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
migrateObject(this, obj, this);
|
migrateObject(this, obj, this);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { MediaTypeModel } from './MediaTypeModel';
|
|
||||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
|
||||||
import { MediaType } from '../utils/MediaType';
|
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 {
|
export class BookModel extends MediaTypeModel {
|
||||||
author: string;
|
author: string;
|
||||||
|
|
@ -8,7 +11,6 @@ export class BookModel extends MediaTypeModel {
|
||||||
pages: number;
|
pages: number;
|
||||||
image: string;
|
image: string;
|
||||||
onlineRating: number;
|
onlineRating: number;
|
||||||
english_title: string;
|
|
||||||
isbn: number;
|
isbn: number;
|
||||||
isbn13: number;
|
isbn13: number;
|
||||||
|
|
||||||
|
|
@ -20,22 +22,23 @@ export class BookModel extends MediaTypeModel {
|
||||||
personalRating: number;
|
personalRating: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(obj: any = {}) {
|
constructor(obj: BookData) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.author = undefined;
|
this.author = '';
|
||||||
this.pages = undefined;
|
this.plot = '';
|
||||||
this.image = undefined;
|
this.pages = 0;
|
||||||
this.onlineRating = undefined;
|
this.image = '';
|
||||||
this.isbn = undefined;
|
this.onlineRating = 0;
|
||||||
this.isbn13 = undefined;
|
this.isbn = 0;
|
||||||
|
this.isbn13 = 0;
|
||||||
|
|
||||||
this.released = undefined;
|
this.released = false;
|
||||||
|
|
||||||
this.userData = {
|
this.userData = {
|
||||||
read: undefined,
|
read: false,
|
||||||
lastRead: undefined,
|
lastRead: '',
|
||||||
personalRating: undefined,
|
personalRating: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
migrateObject(this, obj, this);
|
migrateObject(this, obj, this);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { MediaTypeModel } from './MediaTypeModel';
|
|
||||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
|
||||||
import { MediaType } from '../utils/MediaType';
|
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 {
|
export class GameModel extends MediaTypeModel {
|
||||||
developers: string[];
|
developers: string[];
|
||||||
|
|
@ -17,19 +20,21 @@ export class GameModel extends MediaTypeModel {
|
||||||
personalRating: number;
|
personalRating: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(obj: any = {}) {
|
constructor(obj: GameData) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.developers = undefined;
|
this.developers = [];
|
||||||
this.publishers = undefined;
|
this.publishers = [];
|
||||||
this.genres = undefined;
|
this.genres = [];
|
||||||
this.onlineRating = undefined;
|
this.onlineRating = 0;
|
||||||
this.image = undefined;
|
this.image = '';
|
||||||
this.released = undefined;
|
|
||||||
this.releaseDate = undefined;
|
this.released = false;
|
||||||
|
this.releaseDate = '';
|
||||||
|
|
||||||
this.userData = {
|
this.userData = {
|
||||||
played: undefined,
|
played: false,
|
||||||
personalRating: undefined,
|
personalRating: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
migrateObject(this, obj, this);
|
migrateObject(this, obj, this);
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,13 @@
|
||||||
import { MediaTypeModel } from './MediaTypeModel';
|
|
||||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
|
||||||
import { MediaType } from '../utils/MediaType';
|
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 {
|
export class MangaModel extends MediaTypeModel {
|
||||||
type: string;
|
|
||||||
subType: string;
|
|
||||||
title: string;
|
|
||||||
plot: string;
|
plot: string;
|
||||||
englishTitle: string;
|
|
||||||
alternateTitles: string[];
|
alternateTitles: string[];
|
||||||
year: string;
|
|
||||||
dataSource: string;
|
|
||||||
url: string;
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
genres: string[];
|
genres: string[];
|
||||||
authors: string[];
|
authors: string[];
|
||||||
chapters: number;
|
chapters: number;
|
||||||
|
|
@ -32,27 +26,27 @@ export class MangaModel extends MediaTypeModel {
|
||||||
personalRating: number;
|
personalRating: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(obj: any = {}) {
|
constructor(obj: MangaData) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.plot = undefined;
|
this.plot = '';
|
||||||
this.genres = undefined;
|
this.alternateTitles = [];
|
||||||
this.authors = undefined;
|
this.genres = [];
|
||||||
this.alternateTitles = undefined;
|
this.authors = [];
|
||||||
this.chapters = undefined;
|
this.chapters = 0;
|
||||||
this.volumes = undefined;
|
this.volumes = 0;
|
||||||
this.onlineRating = undefined;
|
this.onlineRating = 0;
|
||||||
this.image = undefined;
|
this.image = '';
|
||||||
|
|
||||||
this.released = undefined;
|
this.released = false;
|
||||||
this.status = undefined;
|
this.status = '';
|
||||||
this.publishedFrom = undefined;
|
this.publishedFrom = '';
|
||||||
this.publishedTo = undefined;
|
this.publishedTo = '';
|
||||||
|
|
||||||
this.userData = {
|
this.userData = {
|
||||||
watched: undefined,
|
watched: false,
|
||||||
lastWatched: undefined,
|
lastWatched: '',
|
||||||
personalRating: undefined,
|
personalRating: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
migrateObject(this, obj, this);
|
migrateObject(this, obj, this);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { MediaType } from '../utils/MediaType';
|
import type { MediaType } from '../utils/MediaType';
|
||||||
|
|
||||||
export abstract class MediaTypeModel {
|
export abstract class MediaTypeModel {
|
||||||
type: string;
|
type: string;
|
||||||
|
|
@ -13,14 +13,14 @@ export abstract class MediaTypeModel {
|
||||||
userData: object;
|
userData: object;
|
||||||
|
|
||||||
protected constructor() {
|
protected constructor() {
|
||||||
this.type = undefined;
|
this.type = '';
|
||||||
this.subType = undefined;
|
this.subType = '';
|
||||||
this.title = undefined;
|
this.title = '';
|
||||||
this.englishTitle = undefined;
|
this.englishTitle = '';
|
||||||
this.year = undefined;
|
this.year = '';
|
||||||
this.dataSource = undefined;
|
this.dataSource = '';
|
||||||
this.url = undefined;
|
this.url = '';
|
||||||
this.id = undefined;
|
this.id = '';
|
||||||
this.userData = {};
|
this.userData = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { MediaTypeModel } from './MediaTypeModel';
|
|
||||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
|
||||||
import { MediaType } from '../utils/MediaType';
|
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 {
|
export class MovieModel extends MediaTypeModel {
|
||||||
plot: string;
|
plot: string;
|
||||||
|
|
@ -23,27 +26,27 @@ export class MovieModel extends MediaTypeModel {
|
||||||
personalRating: number;
|
personalRating: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(obj: any = {}) {
|
constructor(obj: MovieData) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.plot = undefined;
|
this.plot = '';
|
||||||
this.genres = undefined;
|
this.genres = [];
|
||||||
this.director = undefined;
|
this.director = [];
|
||||||
this.writer = undefined;
|
this.writer = [];
|
||||||
this.studio = undefined;
|
this.studio = [];
|
||||||
this.duration = undefined;
|
this.duration = '';
|
||||||
this.onlineRating = undefined;
|
this.onlineRating = 0;
|
||||||
this.actors = undefined;
|
this.actors = [];
|
||||||
this.image = undefined;
|
this.image = '';
|
||||||
|
|
||||||
this.released = undefined;
|
this.released = false;
|
||||||
this.streamingServices = undefined;
|
this.streamingServices = [];
|
||||||
this.premiere = undefined;
|
this.premiere = '';
|
||||||
|
|
||||||
this.userData = {
|
this.userData = {
|
||||||
watched: undefined,
|
watched: false,
|
||||||
lastWatched: undefined,
|
lastWatched: '',
|
||||||
personalRating: undefined,
|
personalRating: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
migrateObject(this, obj, this);
|
migrateObject(this, obj, this);
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,29 @@
|
||||||
import { MediaTypeModel } from './MediaTypeModel';
|
|
||||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
|
||||||
import { MediaType } from '../utils/MediaType';
|
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 {
|
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[];
|
genres: string[];
|
||||||
artists: string[];
|
artists: string[];
|
||||||
|
image: string;
|
||||||
rating: number;
|
rating: number;
|
||||||
|
|
||||||
userData: {
|
userData: {
|
||||||
personalRating: number;
|
personalRating: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(obj: any = {}) {
|
constructor(obj: MusicReleaseData) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.genres = undefined;
|
this.genres = [];
|
||||||
this.artists = undefined;
|
this.artists = [];
|
||||||
this.image = undefined;
|
this.image = '';
|
||||||
this.rating = undefined;
|
this.rating = 0;
|
||||||
this.userData = {
|
this.userData = {
|
||||||
personalRating: undefined,
|
personalRating: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
migrateObject(this, obj, this);
|
migrateObject(this, obj, this);
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,11 @@
|
||||||
import { MediaTypeModel } from './MediaTypeModel';
|
|
||||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
|
||||||
import { MediaType } from '../utils/MediaType';
|
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 {
|
export class SeriesModel extends MediaTypeModel {
|
||||||
type: string;
|
|
||||||
subType: string;
|
|
||||||
title: string;
|
|
||||||
englishTitle: string;
|
|
||||||
year: string;
|
|
||||||
dataSource: string;
|
|
||||||
url: string;
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
plot: string;
|
plot: string;
|
||||||
genres: string[];
|
genres: string[];
|
||||||
writer: string[];
|
writer: string[];
|
||||||
|
|
@ -34,29 +28,29 @@ export class SeriesModel extends MediaTypeModel {
|
||||||
personalRating: number;
|
personalRating: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(obj: any = {}) {
|
constructor(obj: SeriesData) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.plot = undefined;
|
this.plot = '';
|
||||||
this.genres = undefined;
|
this.genres = [];
|
||||||
this.writer = undefined;
|
this.writer = [];
|
||||||
this.studio = undefined;
|
this.studio = [];
|
||||||
this.episodes = undefined;
|
this.episodes = 0;
|
||||||
this.duration = undefined;
|
this.duration = '';
|
||||||
this.onlineRating = undefined;
|
this.onlineRating = 0;
|
||||||
this.actors = undefined;
|
this.actors = [];
|
||||||
this.image = undefined;
|
this.image = '';
|
||||||
|
|
||||||
this.released = undefined;
|
this.released = false;
|
||||||
this.streamingServices = undefined;
|
this.streamingServices = [];
|
||||||
this.airing = undefined;
|
this.airing = false;
|
||||||
this.airedFrom = undefined;
|
this.airedFrom = '';
|
||||||
this.airedTo = undefined;
|
this.airedTo = '';
|
||||||
|
|
||||||
this.userData = {
|
this.userData = {
|
||||||
watched: undefined,
|
watched: false,
|
||||||
lastWatched: undefined,
|
lastWatched: '',
|
||||||
personalRating: undefined,
|
personalRating: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
migrateObject(this, obj, this);
|
migrateObject(this, obj, this);
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,11 @@
|
||||||
import { MediaTypeModel } from './MediaTypeModel';
|
|
||||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
|
||||||
import { MediaType } from '../utils/MediaType';
|
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 {
|
export class WikiModel extends MediaTypeModel {
|
||||||
type: string;
|
|
||||||
subType: string;
|
|
||||||
title: string;
|
|
||||||
englishTitle: string;
|
|
||||||
year: string;
|
|
||||||
dataSource: string;
|
|
||||||
url: string;
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
wikiUrl: string;
|
wikiUrl: string;
|
||||||
lastUpdated: string;
|
lastUpdated: string;
|
||||||
length: number;
|
length: number;
|
||||||
|
|
@ -19,13 +13,13 @@ export class WikiModel extends MediaTypeModel {
|
||||||
|
|
||||||
userData: Record<string, unknown>;
|
userData: Record<string, unknown>;
|
||||||
|
|
||||||
constructor(obj: any = {}) {
|
constructor(obj: WikiData) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.wikiUrl = undefined;
|
this.wikiUrl = '';
|
||||||
this.lastUpdated = undefined;
|
this.lastUpdated = '';
|
||||||
this.length = undefined;
|
this.length = 0;
|
||||||
this.article = undefined;
|
this.article = '';
|
||||||
this.userData = {};
|
this.userData = {};
|
||||||
|
|
||||||
migrateObject(this, obj, this);
|
migrateObject(this, obj, this);
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,16 @@
|
||||||
import { setIcon } from 'obsidian';
|
import { setIcon } from 'obsidian';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
export let iconName: string = '';
|
interface Props {
|
||||||
export let iconSize: number = 20;
|
iconName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
let iconEl: HTMLElement;
|
let { iconName = '' }: Props = $props();
|
||||||
|
|
||||||
|
let iconEl: HTMLElement | undefined = $state();
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
setIcon(iconEl, iconName, iconSize);
|
setIcon(iconEl!, iconName);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { PropertyMappingOption } from './PropertyMapping';
|
import type MediaDbPlugin from '../main';
|
||||||
import { MEDIA_TYPES } from '../utils/MediaTypeManager';
|
import { MEDIA_TYPES } from '../utils/MediaTypeManager';
|
||||||
import MediaDbPlugin from '../main';
|
import { PropertyMappingOption } from './PropertyMapping';
|
||||||
|
|
||||||
export class PropertyMapper {
|
export class PropertyMapper {
|
||||||
plugin: MediaDbPlugin;
|
plugin: MediaDbPlugin;
|
||||||
|
|
@ -66,7 +66,7 @@ export class PropertyMapper {
|
||||||
return obj;
|
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> = {};
|
const originalObj: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import type { MediaType } from '../utils/MediaType';
|
||||||
import { containsOnlyLettersAndUnderscores, PropertyMappingNameConflictError, PropertyMappingValidationError } from '../utils/Utils';
|
import { containsOnlyLettersAndUnderscores, PropertyMappingNameConflictError, PropertyMappingValidationError } from '../utils/Utils';
|
||||||
import { MediaType } from '../utils/MediaType';
|
|
||||||
|
|
||||||
export enum PropertyMappingOption {
|
export enum PropertyMappingOption {
|
||||||
Default = 'default',
|
Default = 'default',
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,22 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { run } from 'svelte/legacy';
|
||||||
|
|
||||||
import { PropertyMappingModel, PropertyMappingOption, propertyMappingOptions } from './PropertyMapping';
|
import { PropertyMappingModel, PropertyMappingOption, propertyMappingOptions } from './PropertyMapping';
|
||||||
import { capitalizeFirstLetter } from '../utils/Utils';
|
import { capitalizeFirstLetter } from '../utils/Utils';
|
||||||
import Icon from './Icon.svelte';
|
import Icon from './Icon.svelte';
|
||||||
|
|
||||||
export let model: PropertyMappingModel;
|
interface Props {
|
||||||
export let save: (model: PropertyMappingModel) => void;
|
model: PropertyMappingModel;
|
||||||
|
save: (model: PropertyMappingModel) => void;
|
||||||
let validationResult: { res: boolean; err?: Error };
|
|
||||||
|
|
||||||
$: modelChanged(model);
|
|
||||||
|
|
||||||
function modelChanged(model: PropertyMappingModel) {
|
|
||||||
validationResult = model.validate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let { model, save }: Props = $props();
|
||||||
|
|
||||||
|
let validationResult: { res: boolean; err?: Error } | undefined = $state();
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
validationResult = model.validate();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="media-db-plugin-property-mappings-model-container">
|
<div class="media-db-plugin-property-mappings-model-container">
|
||||||
|
|
@ -51,7 +55,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
<button
|
<button
|
||||||
class="media-db-plugin-property-mappings-save-button {validationResult?.res ? 'mod-cta' : 'mod-muted'}"
|
class="media-db-plugin-property-mappings-save-button {validationResult?.res ? 'mod-cta' : 'mod-muted'}"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
if (model.validate().res) save(model);
|
if (model.validate().res) save(model);
|
||||||
}}
|
}}
|
||||||
>Save
|
>Save
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,12 @@
|
||||||
import { PropertyMappingModel } from './PropertyMapping';
|
import { PropertyMappingModel } from './PropertyMapping';
|
||||||
import PropertyMappingModelComponent from './PropertyMappingModelComponent.svelte';
|
import PropertyMappingModelComponent from './PropertyMappingModelComponent.svelte';
|
||||||
|
|
||||||
export let models: PropertyMappingModel[] = [];
|
interface Props {
|
||||||
export let save: (model: PropertyMappingModel) => void;
|
models?: PropertyMappingModel[];
|
||||||
|
save: (model: PropertyMappingModel) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { models = [], save }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="setting-item" style="display: flex; gap: 10px; flex-direction: column; align-items: stretch;">
|
<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 type { App } from 'obsidian';
|
||||||
|
import { Notice, PluginSettingTab, Setting } from 'obsidian';
|
||||||
import MediaDbPlugin from '../main';
|
import { mount } from 'svelte';
|
||||||
import { FolderSuggest } from './suggesters/FolderSuggest';
|
import type MediaDbPlugin from '../main';
|
||||||
import { FileSuggest } from './suggesters/FileSuggest';
|
import type { MediaTypeModel } from '../models/MediaTypeModel';
|
||||||
import PropertyMappingModelsComponent from './PropertyMappingModelsComponent.svelte';
|
|
||||||
import { PropertyMapping, PropertyMappingModel, PropertyMappingOption } from './PropertyMapping';
|
|
||||||
import { MEDIA_TYPES } from '../utils/MediaTypeManager';
|
import { MEDIA_TYPES } from '../utils/MediaTypeManager';
|
||||||
import { MediaTypeModel } from '../models/MediaTypeModel';
|
|
||||||
import { fragWithHTML } from '../utils/Utils';
|
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 {
|
export interface MediaDbPluginSettings {
|
||||||
OMDbKey: string;
|
OMDbKey: string;
|
||||||
MobyGamesKey: string;
|
MobyGamesKey: string;
|
||||||
|
GiantBombKey: string;
|
||||||
sfwFilter: boolean;
|
sfwFilter: boolean;
|
||||||
templates: boolean;
|
templates: boolean;
|
||||||
customDateFormat: string;
|
customDateFormat: string;
|
||||||
|
|
@ -78,6 +80,7 @@ export interface MediaDbPluginSettings {
|
||||||
const DEFAULT_SETTINGS: MediaDbPluginSettings = {
|
const DEFAULT_SETTINGS: MediaDbPluginSettings = {
|
||||||
OMDbKey: '',
|
OMDbKey: '',
|
||||||
MobyGamesKey: '',
|
MobyGamesKey: '',
|
||||||
|
GiantBombKey: '',
|
||||||
sfwFilter: true,
|
sfwFilter: true,
|
||||||
templates: true,
|
templates: true,
|
||||||
customDateFormat: 'L',
|
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)
|
new Setting(containerEl)
|
||||||
.setName('SFW filter')
|
.setName('SFW filter')
|
||||||
.setDesc('Only shows SFW results for APIs that offer filtering.')
|
.setDesc('Only shows SFW results for APIs that offer filtering.')
|
||||||
|
|
@ -240,7 +255,10 @@ export class MediaDbSettingTab extends PluginSettingTab {
|
||||||
.onChange(data => {
|
.onChange(data => {
|
||||||
const newDateFormat = data ? data : DEFAULT_SETTINGS.customDateFormat;
|
const newDateFormat = data ? data : DEFAULT_SETTINGS.customDateFormat;
|
||||||
this.plugin.settings.customDateFormat = newDateFormat;
|
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();
|
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.
|
Don't forget to save your changes using the save button for each individual category.
|
||||||
</p>`;
|
</p>`;
|
||||||
|
|
||||||
new PropertyMappingModelsComponent({
|
mount(PropertyMappingModelsComponent, {
|
||||||
target: this.containerEl,
|
target: this.containerEl,
|
||||||
props: {
|
props: {
|
||||||
models: this.plugin.settings.propertyMappingModels.map(x => x.copy()),
|
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 { TextInputSuggest } from './Suggest';
|
||||||
import { TAbstractFile, TFile } from 'obsidian';
|
|
||||||
|
|
||||||
export class FileSuggest extends TextInputSuggest<TFile> {
|
export class FileSuggest extends TextInputSuggest<TFile> {
|
||||||
getSuggestions(inputStr: string): TFile[] {
|
getSuggestions(inputStr: string): TFile[] {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes
|
// 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';
|
import { TextInputSuggest } from './Suggest';
|
||||||
|
|
||||||
export class FolderSuggest extends TextInputSuggest<TFolder> {
|
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
|
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes
|
||||||
|
|
||||||
import { App, ISuggestOwner, Scope } from 'obsidian';
|
import type { Instance as PopperInstance } from '@popperjs/core';
|
||||||
import { createPopper, 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';
|
import { wrapAround } from 'src/utils/Utils';
|
||||||
|
|
||||||
export class Suggest<T> {
|
export class Suggest<T> {
|
||||||
private owner: ISuggestOwner<T>;
|
private owner: ISuggestOwner<T>;
|
||||||
private values: T[];
|
private values: T[];
|
||||||
private suggestions: HTMLDivElement[];
|
private suggestions: HTMLElement[];
|
||||||
private selectedItem: number;
|
private selectedItem: number;
|
||||||
private containerEl: HTMLElement;
|
private containerEl: HTMLElement;
|
||||||
|
|
||||||
constructor(owner: ISuggestOwner<T>, containerEl: HTMLElement, scope: Scope) {
|
constructor(owner: ISuggestOwner<T>, containerEl: HTMLElement, scope: Scope) {
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.containerEl = containerEl;
|
this.containerEl = containerEl;
|
||||||
|
this.values = [];
|
||||||
|
this.suggestions = [];
|
||||||
|
this.selectedItem = 0;
|
||||||
|
|
||||||
containerEl.on('click', '.suggestion-item', this.onSuggestionClick.bind(this));
|
containerEl.on('click', '.suggestion-item', (e, el) => this.onSuggestionClick(e, el));
|
||||||
containerEl.on('mousemove', '.suggestion-item', this.onSuggestionMouseover.bind(this));
|
containerEl.on('mousemove', '.suggestion-item', (e, el) => this.onSuggestionMouseover(e, el));
|
||||||
|
|
||||||
scope.register([], 'ArrowUp', event => {
|
scope.register([], 'ArrowUp', event => {
|
||||||
if (!event.isComposing) {
|
if (!event.isComposing) {
|
||||||
this.setSelectedItem(this.selectedItem - 1, true);
|
this.setSelectedItem(this.selectedItem - 1, true);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
scope.register([], 'ArrowDown', event => {
|
scope.register([], 'ArrowDown', event => {
|
||||||
|
|
@ -30,6 +36,7 @@ export class Suggest<T> {
|
||||||
this.setSelectedItem(this.selectedItem + 1, true);
|
this.setSelectedItem(this.selectedItem + 1, true);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
scope.register([], 'Enter', event => {
|
scope.register([], 'Enter', event => {
|
||||||
|
|
@ -37,10 +44,11 @@ export class Suggest<T> {
|
||||||
this.useSelectedItem(event);
|
this.useSelectedItem(event);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return undefined;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onSuggestionClick(event: MouseEvent, el: HTMLDivElement): void {
|
onSuggestionClick(event: MouseEvent, el: HTMLElement): void {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const item = this.suggestions.indexOf(el);
|
const item = this.suggestions.indexOf(el);
|
||||||
|
|
@ -48,7 +56,7 @@ export class Suggest<T> {
|
||||||
this.useSelectedItem(event);
|
this.useSelectedItem(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSuggestionMouseover(_event: MouseEvent, el: HTMLDivElement): void {
|
onSuggestionMouseover(_event: MouseEvent, el: HTMLElement): void {
|
||||||
const item = this.suggestions.indexOf(el);
|
const item = this.suggestions.indexOf(el);
|
||||||
this.setSelectedItem(item, false);
|
this.setSelectedItem(item, false);
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +103,7 @@ export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
|
||||||
protected app: App;
|
protected app: App;
|
||||||
protected inputEl: HTMLInputElement;
|
protected inputEl: HTMLInputElement;
|
||||||
|
|
||||||
private popper: PopperInstance;
|
private popper?: PopperInstance;
|
||||||
private scope: Scope;
|
private scope: Scope;
|
||||||
private suggestEl: HTMLElement;
|
private suggestEl: HTMLElement;
|
||||||
private suggest: Suggest<T>;
|
private suggest: Suggest<T>;
|
||||||
|
|
@ -126,13 +134,13 @@ export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
|
||||||
if (suggestions.length > 0) {
|
if (suggestions.length > 0) {
|
||||||
this.suggest.setSuggestions(suggestions);
|
this.suggest.setSuggestions(suggestions);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// 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 {
|
open(container: HTMLElement, inputEl: HTMLElement): void {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// 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);
|
container.appendChild(this.suggestEl);
|
||||||
this.popper = createPopper(inputEl, this.suggestEl, {
|
this.popper = createPopper(inputEl, this.suggestEl, {
|
||||||
|
|
@ -162,7 +170,7 @@ export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
|
||||||
|
|
||||||
close(): void {
|
close(): void {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// 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.suggest.setSuggestions([]);
|
||||||
this.popper?.destroy();
|
this.popper?.destroy();
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
import { MediaDbPluginSettings } from '../settings/Settings';
|
import type { App, TAbstractFile, TFile } from 'obsidian';
|
||||||
import { MediaType } from './MediaType';
|
import { TFolder } from 'obsidian';
|
||||||
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 { BoardGameModel } from '../models/BoardGameModel';
|
import { BoardGameModel } from '../models/BoardGameModel';
|
||||||
import { BookModel } from '../models/BookModel';
|
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[] = [
|
export const MEDIA_TYPES: MediaType[] = [
|
||||||
MediaType.Movie,
|
MediaType.Movie,
|
||||||
|
|
@ -28,7 +29,11 @@ export class MediaTypeManager {
|
||||||
mediaTemplateMap: Map<MediaType, string>;
|
mediaTemplateMap: Map<MediaType, string>;
|
||||||
mediaFolderMap: 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 {
|
updateTemplates(settings: MediaDbPluginSettings): void {
|
||||||
this.mediaFileNameTemplateMap = new Map<MediaType, string>();
|
this.mediaFileNameTemplateMap = new Map<MediaType, string>();
|
||||||
|
|
@ -66,7 +71,7 @@ export class MediaTypeManager {
|
||||||
|
|
||||||
getFileName(mediaTypeModel: MediaTypeModel): string {
|
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
|
// 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> {
|
async getTemplate(mediaTypeModel: MediaTypeModel, app: App): Promise<string> {
|
||||||
|
|
@ -76,7 +81,7 @@ export class MediaTypeManager {
|
||||||
return '';
|
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.
|
// 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
|
// 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))) {
|
if (!(await app.vault.adapter.exists(folderPath))) {
|
||||||
await app.vault.createFolder(folderPath);
|
await app.vault.createFolder(folderPath);
|
||||||
}
|
}
|
||||||
const folder: TAbstractFile = app.vault.getAbstractFileByPath(folderPath);
|
const folder = app.vault.getAbstractFileByPath(folderPath);
|
||||||
|
|
||||||
if (!(folder instanceof TFolder)) {
|
if (!(folder instanceof TFolder)) {
|
||||||
throw Error(`Expected ${folder} to be instance of TFolder`);
|
throw Error(`Expected ${folder} to be instance of TFolder`);
|
||||||
|
|
@ -141,6 +146,6 @@ export class MediaTypeManager {
|
||||||
return new BookModel(obj);
|
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 { MediaDbAdvancedSearchModal } from '../modals/MediaDbAdvancedSearchModal';
|
||||||
import { MediaDbIdSearchModal } from '../modals/MediaDbIdSearchModal';
|
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 { 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 {
|
export enum ModalResultCode {
|
||||||
SUCCESS = 'SUCCESS',
|
SUCCESS = 'SUCCESS',
|
||||||
|
|
@ -15,60 +15,59 @@ export enum ModalResultCode {
|
||||||
ERROR = 'ERROR',
|
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.
|
* Object containing the data {@link ModalHelper.createSearchModal} returns.
|
||||||
* On {@link ModalResultCode.SUCCESS} this contains {@link SearchModalData}.
|
* On {@link ModalResultCode.SUCCESS} this contains {@link SearchModalData}.
|
||||||
* On {@link ModalResultCode.ERROR} this contains a reference to that error.
|
* On {@link ModalResultCode.ERROR} this contains a reference to that error.
|
||||||
*/
|
*/
|
||||||
export interface SearchModalResult {
|
export type SearchModalResult = ModalResult<SearchModalData>;
|
||||||
code: ModalResultCode.SUCCESS | ModalResultCode.CLOSE | ModalResultCode.ERROR;
|
|
||||||
data?: SearchModalData;
|
|
||||||
error?: Error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object containing the data {@link ModalHelper.createAdvancedSearchModal} returns.
|
* Object containing the data {@link ModalHelper.createAdvancedSearchModal} returns.
|
||||||
* On {@link ModalResultCode.SUCCESS} this contains {@link AdvancedSearchModalData}.
|
* On {@link ModalResultCode.SUCCESS} this contains {@link AdvancedSearchModalData}.
|
||||||
* On {@link ModalResultCode.ERROR} this contains a reference to that error.
|
* On {@link ModalResultCode.ERROR} this contains a reference to that error.
|
||||||
*/
|
*/
|
||||||
export interface AdvancedSearchModalResult {
|
export type AdvancedSearchModalResult = ModalResult<AdvancedSearchModalData>;
|
||||||
code: ModalResultCode.SUCCESS | ModalResultCode.CLOSE | ModalResultCode.ERROR;
|
|
||||||
data?: AdvancedSearchModalData;
|
|
||||||
error?: Error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object containing the data {@link ModalHelper.createIdSearchModal} returns.
|
* Object containing the data {@link ModalHelper.createIdSearchModal} returns.
|
||||||
* On {@link ModalResultCode.SUCCESS} this contains {@link IdSearchModalData}.
|
* On {@link ModalResultCode.SUCCESS} this contains {@link IdSearchModalData}.
|
||||||
* On {@link ModalResultCode.ERROR} this contains a reference to that error.
|
* On {@link ModalResultCode.ERROR} this contains a reference to that error.
|
||||||
*/
|
*/
|
||||||
export interface IdSearchModalResult {
|
export type IdSearchModalResult = ModalResult<IdSearchModalData>;
|
||||||
code: ModalResultCode.SUCCESS | ModalResultCode.CLOSE | ModalResultCode.ERROR;
|
|
||||||
data?: IdSearchModalData;
|
|
||||||
error?: Error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object containing the data {@link ModalHelper.createSelectModal} returns.
|
* Object containing the data {@link ModalHelper.createSelectModal} returns.
|
||||||
* On {@link ModalResultCode.SUCCESS} this contains {@link SelectModalData}.
|
* On {@link ModalResultCode.SUCCESS} this contains {@link SelectModalData}.
|
||||||
* On {@link ModalResultCode.ERROR} this contains a reference to that error.
|
* On {@link ModalResultCode.ERROR} this contains a reference to that error.
|
||||||
*/
|
*/
|
||||||
export interface SelectModalResult {
|
export type SelectModalResult = SkippableModalResult<SelectModalData>;
|
||||||
code: ModalResultCode.SUCCESS | ModalResultCode.CLOSE | ModalResultCode.SKIP | ModalResultCode.ERROR;
|
|
||||||
data?: SelectModalData;
|
|
||||||
error?: Error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object containing the data {@link ModalHelper.createPreviewModal} returns.
|
* Object containing the data {@link ModalHelper.createPreviewModal} returns.
|
||||||
* On {@link ModalResultCode.SUCCESS} this contains {@link PreviewModalData}.
|
* On {@link ModalResultCode.SUCCESS} this contains {@link PreviewModalData}.
|
||||||
* On {@link ModalResultCode.ERROR} this contains a reference to that error.
|
* On {@link ModalResultCode.ERROR} this contains a reference to that error.
|
||||||
*/
|
*/
|
||||||
export interface PreviewModalResult {
|
export type PreviewModalResult = ModalResult<PreviewModalData>;
|
||||||
code: ModalResultCode.SUCCESS | ModalResultCode.CLOSE | ModalResultCode.ERROR;
|
|
||||||
data?: PreviewModalData;
|
|
||||||
error?: Error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The data the search modal returns.
|
* 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
|
* @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.
|
* @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);
|
const { searchModalResult, searchModal } = await this.createSearchModal(searchModalOptions);
|
||||||
console.debug(`MDB | searchModal closed with code ${searchModalResult.code}`);
|
console.debug(`MDB | searchModal closed with code ${searchModalResult.code}`);
|
||||||
|
|
||||||
|
|
@ -271,7 +273,7 @@ export class ModalHelper {
|
||||||
return callbackRes;
|
return callbackRes;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
new Notice(e.toString());
|
new Notice(`${e}`);
|
||||||
searchModal.close();
|
searchModal.close();
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
@ -314,7 +316,7 @@ export class ModalHelper {
|
||||||
async openAdvancedSearchModal(
|
async openAdvancedSearchModal(
|
||||||
advancedSearchModalOptions: AdvancedSearchModalOptions,
|
advancedSearchModalOptions: AdvancedSearchModalOptions,
|
||||||
submitCallback: (advancedSearchModalData: AdvancedSearchModalData) => Promise<MediaTypeModel[]>,
|
submitCallback: (advancedSearchModalData: AdvancedSearchModalData) => Promise<MediaTypeModel[]>,
|
||||||
): Promise<MediaTypeModel[]> {
|
): Promise<MediaTypeModel[] | undefined> {
|
||||||
const { advancedSearchModalResult, advancedSearchModal } = await this.createAdvancedSearchModal(advancedSearchModalOptions);
|
const { advancedSearchModalResult, advancedSearchModal } = await this.createAdvancedSearchModal(advancedSearchModalOptions);
|
||||||
console.debug(`MDB | advencedSearchModal closed with code ${advancedSearchModalResult.code}`);
|
console.debug(`MDB | advencedSearchModal closed with code ${advancedSearchModalResult.code}`);
|
||||||
|
|
||||||
|
|
@ -337,7 +339,7 @@ export class ModalHelper {
|
||||||
return callbackRes;
|
return callbackRes;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
new Notice(e.toString());
|
new Notice(`${e}`);
|
||||||
advancedSearchModal.close();
|
advancedSearchModal.close();
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
@ -377,8 +379,8 @@ export class ModalHelper {
|
||||||
*/
|
*/
|
||||||
async openIdSearchModal(
|
async openIdSearchModal(
|
||||||
idSearchModalOptions: IdSearchModalOptions,
|
idSearchModalOptions: IdSearchModalOptions,
|
||||||
submitCallback: (idSearchModalData: IdSearchModalData) => Promise<MediaTypeModel>,
|
submitCallback: (idSearchModalData: IdSearchModalData) => Promise<MediaTypeModel | undefined>,
|
||||||
): Promise<MediaTypeModel> {
|
): Promise<MediaTypeModel | undefined> {
|
||||||
const { idSearchModalResult, idSearchModal } = await this.createIdSearchModal(idSearchModalOptions);
|
const { idSearchModalResult, idSearchModal } = await this.createIdSearchModal(idSearchModalOptions);
|
||||||
console.debug(`MDB | idSearchModal closed with code ${idSearchModalResult.code}`);
|
console.debug(`MDB | idSearchModal closed with code ${idSearchModalResult.code}`);
|
||||||
|
|
||||||
|
|
@ -396,12 +398,12 @@ export class ModalHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const callbackRes: MediaTypeModel = await submitCallback(idSearchModalResult.data);
|
const callbackRes = await submitCallback(idSearchModalResult.data);
|
||||||
idSearchModal.close();
|
idSearchModal.close();
|
||||||
return callbackRes;
|
return callbackRes;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
new Notice(e.toString());
|
new Notice(`${e}`);
|
||||||
idSearchModal.close();
|
idSearchModal.close();
|
||||||
return undefined;
|
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
|
* @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.
|
* @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);
|
const { selectModalResult, selectModal } = await this.createSelectModal(selectModalOptions);
|
||||||
console.debug(`MDB | selectModal closed with code ${selectModalResult.code}`);
|
console.debug(`MDB | selectModal closed with code ${selectModalResult.code}`);
|
||||||
|
|
||||||
|
|
@ -468,7 +473,7 @@ export class ModalHelper {
|
||||||
return callbackRes;
|
return callbackRes;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
new Notice(e.toString());
|
new Notice(`${e}`);
|
||||||
selectModal.close();
|
selectModal.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -500,12 +505,12 @@ export class ModalHelper {
|
||||||
console.warn(previewModalResult.error);
|
console.warn(previewModalResult.error);
|
||||||
new Notice(previewModalResult.error.toString());
|
new Notice(previewModalResult.error.toString());
|
||||||
previewModal.close();
|
previewModal.close();
|
||||||
return undefined;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (previewModalResult.code === ModalResultCode.CLOSE) {
|
if (previewModalResult.code === ModalResultCode.CLOSE) {
|
||||||
// modal is already being closed
|
// modal is already being closed
|
||||||
return undefined;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -514,9 +519,9 @@ export class ModalHelper {
|
||||||
return callbackRes;
|
return callbackRes;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
new Notice(e.toString());
|
new Notice(`${e}`);
|
||||||
previewModal.close();
|
previewModal.close();
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { MediaTypeModel } from '../models/MediaTypeModel';
|
import type { TFile, TFolder, App } from 'obsidian';
|
||||||
import { TFile, TFolder, App } from 'obsidian';
|
import type { MediaTypeModel } from '../models/MediaTypeModel';
|
||||||
|
|
||||||
export const pluginName: string = 'obsidian-media-db-plugin';
|
export const pluginName: string = 'obsidian-media-db-plugin';
|
||||||
export const contactEmail: string = 'm.projects.code@gmail.com';
|
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 {
|
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 {
|
export function replaceIllegalFileNameCharactersInString(string: string): string {
|
||||||
|
|
@ -82,7 +82,7 @@ function replaceTag(match: string, mediaTypeModel: MediaTypeModel, ignoreUndefin
|
||||||
return '{{ INVALID TEMPLATE TAG }}';
|
return '{{ INVALID TEMPLATE TAG }}';
|
||||||
}
|
}
|
||||||
|
|
||||||
function traverseMetaData(path: Array<string>, mediaTypeModel: MediaTypeModel): any {
|
function traverseMetaData(path: string[], mediaTypeModel: MediaTypeModel): any {
|
||||||
let o: any = mediaTypeModel;
|
let o: any = mediaTypeModel;
|
||||||
|
|
||||||
for (const part of path) {
|
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> {
|
export async function useTemplaterPluginInFile(app: App, file: TFile): Promise<void> {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const templater = (app as any).plugins.plugins['templater-obsidian'];
|
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);
|
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": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"target": "ES6",
|
"target": "ESNext",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
|
"strict": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"sourceMap": true,
|
||||||
"lib": ["DOM", "ESNext"],
|
"lib": ["DOM", "ESNext"],
|
||||||
"types": ["svelte"],
|
|
||||||
"allowSyntheticDefaultImports": true
|
"allowSyntheticDefaultImports": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "tests/**/*.ts"]
|
"include": ["src/**/*.ts", "tests/**/*.ts"]
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,6 @@
|
||||||
"0.1.7": "0.14.0",
|
"0.1.7": "0.14.0",
|
||||||
"0.7.0": "1.5.0",
|
"0.7.0": "1.5.0",
|
||||||
"0.7.1": "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