mirror of
https://github.com/cmorooney/obsidian-game-search-plugin.git
synced 2026-07-22 07:50:25 +00:00
initial. s/book/game/g. model RAWG Api games response and request. remove service providers and other unneeded things for this fork
This commit is contained in:
parent
0dc93919fd
commit
c3732cdbaf
28 changed files with 223 additions and 1057 deletions
35
README.ko.md
35
README.ko.md
|
|
@ -1,35 +0,0 @@
|
|||
# 옵시디언 책 검색 플러그인
|
||||
|
||||

|
||||

|
||||
[](README.md)
|
||||
|
||||
옵시디언에서 독서노트를 간단하게 생성할 수 있습니다.
|
||||
|
||||
<br>
|
||||
|
||||
## 데모 영상
|
||||
|
||||
https://user-images.githubusercontent.com/3969643/184918274-8ad24546-2e01-4288-a855-c8eeb1feca7d.mp4
|
||||
|
||||
<br>
|
||||
|
||||
## 사용 방법
|
||||
|
||||
_작성 중..._
|
||||
|
||||
<br>
|
||||
|
||||
## 블로그 글
|
||||
|
||||
- [[옵시디언] '네이버 책 검색 API'를 사용하여 독서노트 생성하기](https://anpigon.tistory.com/177)
|
||||
- [[옵시디언] 책 검색 플러그인 (Book Search Plugin v0.1.0)](https://anpigon.tistory.com/165)
|
||||
|
||||
<br>
|
||||
|
||||
## 개발 지원
|
||||
|
||||
이 플러그인 개발에 도움을 주고 싶다면, 저에게 커피 한 잔을 사주세요.
|
||||
|
||||
<a href="https://www.buymeacoffee.com/anpigon" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="60"></a>
|
||||
<a href="https://anpigon.github.io/buymeacoffee/"><img src="https://user-images.githubusercontent.com/3969643/184924261-f0224843-08fa-4bce-af70-dc5db589979f.png" height="60"></a>
|
||||
217
README.md
217
README.md
|
|
@ -1,240 +1,45 @@
|
|||
# Obsidian Book Search Plugin
|
||||
# Obsidian Game Search Plugin
|
||||
|
||||

|
||||

|
||||
[](README.ko.md)
|
||||
|
||||
Easily create book notes.
|
||||
|
||||
<br>
|
||||
|
||||
## Changelog
|
||||
|
||||
### [0.5.8](https://github.com/anpigon/obsidian-book-search-plugin/compare/0.5.8-beta.2...0.5.8) (2022-09-13)
|
||||
|
||||
### Features
|
||||
|
||||
- Add locale selection for Google searches.
|
||||
- Can use the [Templater plugin](https://github.com/SilentVoid13/Templater) with.
|
||||
- Enables [Inline scripts](https://github.com/anpigon/obsidian-book-search-plugin#inline-script) for templates.
|
||||
|
||||
<br>
|
||||
|
||||
## Demo
|
||||
|
||||
https://user-images.githubusercontent.com/3969643/184918274-8ad24546-2e01-4288-a855-c8eeb1feca7d.mp4
|
||||
|
||||
<br>
|
||||
## forked from
|
||||
https://github.com/anpigon/obsidian-book-search-plugin/
|
||||
|
||||
## Description
|
||||
|
||||
Use to query book using :
|
||||
Use to query game using :
|
||||
|
||||
- A book title, author, publisher or ISBN (10 or 13).
|
||||
- A game title
|
||||
|
||||
Use Google Books API to get the book information.
|
||||
Use RAWG Game APe to get the game information.
|
||||
|
||||
<br>
|
||||
|
||||
## How to install
|
||||
|
||||
Click the link to install the Book Search plugin: [Install Link](https://obsidian.md/plugins?id=obsidian-book-search-plugin)
|
||||
Click the link to install the Game Search plugin: [Install Link](https://github.com/CMorooney/obsidian-game-search-plugin)
|
||||
|
||||
Or, Search in the Obsidian Community plugin. And install it.
|
||||
|
||||
<img width="700" src="https://user-images.githubusercontent.com/3969643/184918934-585375a9-7b25-4905-81c8-5f092ed74991.png">
|
||||
|
||||
<br>
|
||||
|
||||
## How to use
|
||||
|
||||
### 1. Click the ribbon icon, or excute the command "Create new book note".
|
||||
### 1. Click the ribbon icon, or excute the command "Create new game note".
|
||||
|
||||
<img width="600" src="https://user-images.githubusercontent.com/3969643/161973483-ab007598-e0b8-433f-9697-75ee0ef74195.png">
|
||||
### 2. Search for game by keywords.
|
||||
|
||||
### 2. Search for books by keywords.
|
||||
|
||||
<img width="600" src="https://user-images.githubusercontent.com/3969643/161973979-51f642c9-626a-4015-a7e9-dfdbe6ec2cbc.png">
|
||||
|
||||
### 3. Select the book from the search results.
|
||||
|
||||
<img width="600" src="https://user-images.githubusercontent.com/3969643/161974310-13c3b39b-51dc-472f-b787-db64f74caf74.png">
|
||||
### 3. Select the game from the search results.
|
||||
|
||||
### 4. Voila! A note has been created.
|
||||
|
||||
<img width="600" src="https://user-images.githubusercontent.com/3969643/161974593-1b7bfe69-cb9d-47d7-a43d-1d725295a122.png">
|
||||
|
||||
<br>
|
||||
|
||||
## How to use settings
|
||||
|
||||
<img width="700" src="https://user-images.githubusercontent.com/3969643/184919550-68eff0e4-2b02-41bb-8f17-30a5354359a3.png">
|
||||
|
||||
### New file location
|
||||
|
||||
Set the folder location where the new file is created. Otherwise, a new file is created in the Obsidian Root folder.
|
||||
|
||||
### New file name
|
||||
|
||||
You can set the file name format. The default format is `{{title}} - {{author}}`.
|
||||
You can set the file name format. The default format is `{{name}} - {{published}}`.
|
||||
You can use `{{DATE}}` or `{{DATE:YYYYMMDD}}` to set a unique file name.
|
||||
|
||||
### Template file
|
||||
|
||||
You can set the template file location. There is an example template at the bottom.
|
||||
|
||||
### Service Provider
|
||||
|
||||
You can set up the services that you use to search for books. Only Google and Naver(네이버) are available now.
|
||||
To use Naver Book Search, clientId and clientSecret are required. I will explain how to get clientId and clientSecret from Naver on my blog.
|
||||
|
||||
### <strike>(Deprecated) Text to insert into front matter</strike>
|
||||
|
||||
<strike>You can add the following to the default Front Matter, or create a new Front Matter with the structure you want.</strike> Please use the template file described below.
|
||||
|
||||
### <strike>(Deprecated) Text to insert into content</strike>
|
||||
|
||||
<strike>You can add text to the content for [Dataview inline metadata](https://blacksmithgu.github.io/obsidian-dataview/data-annotation/#pages).</strike> Please use the template file described below.
|
||||
|
||||
<br>
|
||||
|
||||
## Example template
|
||||
|
||||
Please also find a definition of the variables used in this template below (see: [Template variables definitions](#template-variables-definitions)).
|
||||
|
||||
```
|
||||
---
|
||||
tag: 📚Book
|
||||
title: "{{title}}"
|
||||
author: [{{author}}]
|
||||
publisher: {{publisher}}
|
||||
publish: {{publishDate}}
|
||||
total: {{totalPage}}
|
||||
isbn: {{isbn10}} {{isbn13}}
|
||||
cover: {{coverUrl}}
|
||||
status: unread
|
||||
created: {{DATE:YYYY-MM-DD HH:mm:ss}}
|
||||
updated: {{DATE:YYYY-MM-DD HH:mm:ss}}
|
||||
---
|
||||
|
||||

|
||||
|
||||
# {{title}}
|
||||
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
## Dataview rendering
|
||||
|
||||
<img width="1024" alt="" src="https://user-images.githubusercontent.com/3969643/184546096-82ccaae6-9893-411b-aed6-a72c54f72cb2.png">
|
||||
|
||||
Here is the dataview query used in the demo
|
||||
|
||||
````
|
||||
# 📚 My Bookshelf
|
||||
|
||||
```dataview
|
||||
TABLE WITHOUT ID
|
||||
status as Status,
|
||||
rows.file.link as Book
|
||||
FROM #📚Book
|
||||
WHERE !contains(file.path, "Templates")
|
||||
GROUP BY status
|
||||
SORT status
|
||||
```
|
||||
|
||||
## List of all books
|
||||
|
||||
```dataview
|
||||
TABLE WITHOUT ID
|
||||
status as Status,
|
||||
"" as Cover,
|
||||
link(file.link, title) as Title,
|
||||
author as Author,
|
||||
join(list(publisher, publish)) as Publisher
|
||||
FROM #📚Book
|
||||
WHERE !contains(file.path, "Templates")
|
||||
SORT status DESC, file.ctime ASC
|
||||
```
|
||||
````
|
||||
|
||||
The banner at the top of the document is rendered using [Obsidian-banners](https://github.com/noatpad/obsidian-banners) plugin.
|
||||
|
||||
<br>
|
||||
|
||||
## Template variables definitions
|
||||
|
||||
Please find here a definition of the possible variables to be used in your template. Simply write `{{name}}` in your template, and replace name by the desired book data, including:
|
||||
|
||||
| name | description |
|
||||
| ----------- | ------------------------------------------------------- |
|
||||
| title | The title of the book. |
|
||||
| author | The name of the book author. It can be multiple people. |
|
||||
| category | Book category. |
|
||||
| description | Book description. |
|
||||
| publisher | The publisher of the book. |
|
||||
| totalPage | The total number of pages in the book. |
|
||||
| coverUrl | Book cover image URL. |
|
||||
| publishDate | The year the book was published. |
|
||||
| isbn10 | ISBN10 |
|
||||
| isbn13 | ISBN13 |
|
||||
|
||||
<br>
|
||||
|
||||
## Advanced
|
||||
|
||||
### Inline Script
|
||||
|
||||
#### To print out a book object:
|
||||
|
||||
````
|
||||
```json
|
||||
<%=book%>
|
||||
```
|
||||
````
|
||||
|
||||
or
|
||||
|
||||
````
|
||||
```json
|
||||
<%=JSON.stringify(book, null, 2)%>
|
||||
```
|
||||
````
|
||||
|
||||
#### When you want to list or link authors:
|
||||
|
||||
```
|
||||
---
|
||||
authors: <%=book.authors.map(author=>`\n - ${author}`).join('')%>
|
||||
---
|
||||
|
||||
authors: <%=book.authors.map(author => `[[${author}]]`).join(', ')%>
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
## License
|
||||
|
||||
[Obsidian Book Search Plugin](https://github.com/anpigon/obsidian-book-search-plugin) is licensed under the GNU AGPLv3 license. Refer to [LICENSE](https://github.com/SilentVoid13/Templater/blob/master/LICENSE.TXT) for more information.
|
||||
|
||||
<br>
|
||||
|
||||
## Contributing
|
||||
|
||||
Feel free to contribute.
|
||||
|
||||
You can create an [issue](https://github.com/anpigon/obsidian-book-search-plugin/issues) to report a bug, suggest an improvement for this plugin, ask a question, etc.
|
||||
|
||||
You can make a [pull request](https://github.com/anpigon/obsidian-book-search-plugin/pulls) to contribute to this plugin development.
|
||||
|
||||
<br>
|
||||
|
||||
## Support
|
||||
|
||||
If this plugin helped you and you wish to contribute :)
|
||||
|
||||
Buy me coffee on [buymeacoffee.com/anpigon](https://www.buymeacoffee.com/anpigon)
|
||||
|
||||
<a href="https://www.buymeacoffee.com/anpigon" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="60"></a>
|
||||
<a href="https://anpigon.github.io/buymeacoffee/"><img src="https://user-images.githubusercontent.com/3969643/184924261-f0224843-08fa-4bce-af70-dc5db589979f.png" height="60"></a>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"id": "obsidian-book-search-plugin",
|
||||
"name": "Book Search",
|
||||
"version": "0.5.8",
|
||||
"id": "obsidian-game-search-plugin",
|
||||
"name": "Game Search",
|
||||
"version": "0.1.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Helps you find books and create notes.",
|
||||
"author": "anpigon",
|
||||
"authorUrl": "https://github.com/anpigon",
|
||||
"description": "Helps you find Games and create notes.",
|
||||
"author": "Calving forked from anpigon",
|
||||
"authorUrl": "https://github.com/CMorooney/obsidian-game-search-plugin",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"id": "obsidian-book-search-plugin",
|
||||
"name": "Book Search",
|
||||
"version": "0.5.9",
|
||||
"id": "obsidian-game-search-plugin",
|
||||
"name": "Game Search",
|
||||
"version": "0.1.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Helps you find books and create notes.",
|
||||
"author": "anpigon",
|
||||
"authorUrl": "https://github.com/anpigon",
|
||||
"description": "Helps you find games and create notes.",
|
||||
"author": "Calvin forked from anpigon",
|
||||
"authorUrl": "https://github.com/CMorooney/obsidian-game-search-plugin",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
|
|
|
|||
15
package.json
15
package.json
|
|
@ -1,19 +1,20 @@
|
|||
{
|
||||
"name": "obsidian-book-search-plugin",
|
||||
"version": "0.5.9",
|
||||
"description": "This is a plugin to help you create book notes.",
|
||||
"homepage": "https://anpigon.github.io/obsidian-book-search-plugin/",
|
||||
"name": "obsidian-game-search-plugin",
|
||||
"version": "0.1.0",
|
||||
"description": "This is a plugin to help you create game notes.",
|
||||
"homepage": "https://github.com/CMorooney/obsidian-game-search-plugin",
|
||||
"main": "main.js",
|
||||
"standard-version": {
|
||||
"t": ""
|
||||
},
|
||||
"keywords": [
|
||||
"book",
|
||||
"book note",
|
||||
"game",
|
||||
"game note",
|
||||
"video games",
|
||||
"obsidian",
|
||||
"plugin"
|
||||
],
|
||||
"author": "anpigon",
|
||||
"author": "Calvin forked from anpigon",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16",
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Obsidian Book Search Plugin</title>
|
||||
<title>Obsidian Game Search Plugin</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Obsidian Book Search Plugin</h1>
|
||||
<h1>Obsidian Game Search Plugin</h1>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0; url=obsidian://show-plugin?id=obsidian-book-search-plugin">
|
||||
<title>Install Obsidian Book Search Plugin</title>
|
||||
<meta http-equiv="refresh" content="0; url=obsidian://show-plugin?id=obsidian-game-search-plugin">
|
||||
<title>Install Obsidian Game Search Plugin</title>
|
||||
</head>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
import { Book } from '@models/book.model';
|
||||
import { BookSearchPluginSettings } from '@settings/settings';
|
||||
import { ServiceProvider } from '@src/constants';
|
||||
import { requestUrl } from 'obsidian';
|
||||
import { GoogleBooksApi } from './google_books_api';
|
||||
import { NaverBooksApi } from './naver_books_api';
|
||||
|
||||
export interface BaseBooksApiImpl {
|
||||
getByQuery(query: string): Promise<Book[]>;
|
||||
}
|
||||
|
||||
export function factoryServiceProvider(settings: BookSearchPluginSettings): BaseBooksApiImpl {
|
||||
if (settings.serviceProvider === ServiceProvider.google) {
|
||||
return new GoogleBooksApi(settings.localePreference);
|
||||
}
|
||||
if (settings.serviceProvider === ServiceProvider.naver) {
|
||||
if (!settings.naverClientId || !settings.naverClientSecret) {
|
||||
// TODO: Let's create a custom error class
|
||||
throw new Error('네이버 개발자센터에서 "Client ID"와 "Client Secret"를 발급받아 설정해주세요.');
|
||||
}
|
||||
return new NaverBooksApi(settings.naverClientId, settings.naverClientSecret);
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiGet<T>(
|
||||
url: string,
|
||||
params: Record<string, string | number> = {},
|
||||
headers?: Record<string, string>,
|
||||
): Promise<T> {
|
||||
const apiURL = new URL(url);
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
apiURL.searchParams.append(key, value?.toString());
|
||||
});
|
||||
const res = await requestUrl({
|
||||
url: apiURL.href,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: '*/*',
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
...headers,
|
||||
},
|
||||
});
|
||||
return res.json as T;
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
import { Book } from '@models/book.model';
|
||||
import { GoogleBooksApi } from './google_books_api';
|
||||
import { Type, VolumeInfo } from './models/google_books_response';
|
||||
|
||||
describe('Book creation', () => {
|
||||
const volumeInfo: VolumeInfo = {
|
||||
title: 'Flow',
|
||||
subtitle: 'The Psychology of Optimal Experience',
|
||||
authors: ['Mihaly Csikszentmihalyi'],
|
||||
publisher: 'Harper Collins',
|
||||
publishedDate: '2009-10-13',
|
||||
description:
|
||||
'\u003cp\u003e“Csikszentmihalyi arrives at an insight that many of us can intuitively grasp, despite our insistent (and culturally supported) denial of this truth. That is, it is not what happens to us that determines our happiness, but the manner in which we make sense of that reality. . . . The manner in which Csikszentmihalyi integrates research on consciousness, personal psychology and spirituality is illuminating.” —Los Angeles Times Book Review\u003c/p\u003e\u003cp\u003eThe bestselling classic that holds the key to unlocking meaning, creativity, peak performance, and true happiness. \u003c/p\u003e\u003cp\u003eLegendary psychologist Mihaly Csikszentmihalyi\'s famous investigations of "optimal experience" have revealed that what makes an experience genuinely satisfying is a state of consciousness called flow. During flow, people typically experience deep enjoyment, creativity, and a total involvement with life. In this new edition of his groundbreaking classic work, Csikszentmihalyi ("the leading researcher into ‘flow states’" —Newsweek) demonstrates the ways this positive state can be controlled, not just left to chance. Flow: The Psychology of Optimal Experience teaches how, by ordering the information that enters our consciousness, we can discover true happiness, unlock our potential, and greatly improve the quality of our lives.\u003c/p\u003e',
|
||||
industryIdentifiers: [
|
||||
{
|
||||
type: Type.Isbn10,
|
||||
identifier: '0061876720',
|
||||
},
|
||||
{
|
||||
type: Type.Isbn13,
|
||||
identifier: '9780061876721',
|
||||
},
|
||||
],
|
||||
readingModes: {
|
||||
text: true,
|
||||
image: false,
|
||||
},
|
||||
pageCount: 336,
|
||||
printType: 'BOOK',
|
||||
categories: ['Psychology / Creative Ability', 'Psychology / Applied Psychology', 'Psychology / Personality'],
|
||||
averageRating: 4,
|
||||
ratingsCount: 1404,
|
||||
maturityRating: 'NOT_MATURE',
|
||||
allowAnonLogging: true,
|
||||
contentVersion: '1.4.3.0.preview.2',
|
||||
panelizationSummary: {
|
||||
containsEpubBubbles: false,
|
||||
containsImageBubbles: false,
|
||||
},
|
||||
imageLinks: {
|
||||
smallThumbnail:
|
||||
'http://books.google.com/books/content?id=QVjPsd1UukEC&printsec=frontcover&img=1&zoom=5&edge=curl&imgtk=AFLRE71MowoDIZtesyLc6kiXVlnvQznYNgPj7fIAedD1BNhyuPtLC5i3QuDwZKM_5Q-FZXaf0tMfBx2ijWUBEcVqPmYkK6ApHUMJVxsIlEP2maBsJJHIU2_De5ioR5KVAF0za48f39aA&source=gbs_api',
|
||||
thumbnail:
|
||||
'http://books.google.com/books/content?id=QVjPsd1UukEC&printsec=frontcover&img=1&zoom=1&edge=curl&imgtk=AFLRE71SvX9ABv9Ensxle-fchWlnS8UEYiU3Symajbi_0pbwhrZFP3ahRLKcK_RjWJP9pRMtbjSSK3SDbr_UXWQ8GbCnncMVCs37gYWGedBePMEbk4G9eCsiiWy22ejJIJ3H3bx53Huq&source=gbs_api',
|
||||
},
|
||||
language: 'en',
|
||||
previewLink: 'http://books.google.fr/books?id=QVjPsd1UukEC&hl=&source=gbs_api',
|
||||
infoLink: 'https://play.google.com/store/books/details?id=QVjPsd1UukEC&source=gbs_api',
|
||||
canonicalVolumeLink: 'https://play.google.com/store/books/details?id=QVjPsd1UukEC',
|
||||
};
|
||||
|
||||
const api: GoogleBooksApi = new GoogleBooksApi('default');
|
||||
const book: Book = api.createBookItem(volumeInfo);
|
||||
|
||||
it('Title', () => {
|
||||
expect(book.title).toEqual(volumeInfo.title);
|
||||
});
|
||||
|
||||
it('Subtitle', () => {
|
||||
expect(book.subtitle).toEqual(volumeInfo.subtitle);
|
||||
});
|
||||
|
||||
it('Author', () => {
|
||||
expect(book.author).toEqual(api.formatList(volumeInfo.authors));
|
||||
});
|
||||
|
||||
it('Category', () => {
|
||||
expect(book.category).toEqual(api.formatList(volumeInfo.categories));
|
||||
});
|
||||
|
||||
it('Publisher', () => {
|
||||
expect(book.publisher).toEqual(volumeInfo.publisher);
|
||||
});
|
||||
|
||||
it('Published date', () => {
|
||||
expect(book.publishDate).toEqual(volumeInfo.publishedDate);
|
||||
});
|
||||
|
||||
it('Total pages', () => {
|
||||
expect(book.totalPage).toEqual(volumeInfo.pageCount);
|
||||
});
|
||||
|
||||
it('Cover URL', () => {
|
||||
expect(book.coverUrl).toEqual(volumeInfo.imageLinks.thumbnail);
|
||||
});
|
||||
|
||||
it('Cover small URL', () => {
|
||||
expect(book.coverSmallUrl).toEqual(volumeInfo.imageLinks.smallThumbnail);
|
||||
});
|
||||
|
||||
it('Description', () => {
|
||||
expect(book.description).toEqual(volumeInfo.description);
|
||||
});
|
||||
|
||||
it('Link', () => {
|
||||
expect(book.link).toEqual(volumeInfo.canonicalVolumeLink);
|
||||
});
|
||||
|
||||
it('Preview link', () => {
|
||||
expect(book.previewLink).toEqual(volumeInfo.previewLink);
|
||||
});
|
||||
|
||||
it('ISBN 10', () => {
|
||||
expect(book.isbn10).toEqual(volumeInfo.industryIdentifiers[0].identifier);
|
||||
});
|
||||
|
||||
it('ISBN 13', () => {
|
||||
expect(book.isbn13).toEqual(volumeInfo.industryIdentifiers[1].identifier);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
import { Book } from '@models/book.model';
|
||||
import { apiGet, BaseBooksApiImpl } from '@apis/base_api';
|
||||
import { GoogleBooksResponse, VolumeInfo } from './models/google_books_response';
|
||||
|
||||
export class GoogleBooksApi implements BaseBooksApiImpl {
|
||||
constructor(private readonly localePreference: string) {}
|
||||
|
||||
async getByQuery(query: string) {
|
||||
try {
|
||||
const params = {
|
||||
q: query,
|
||||
maxResults: 40,
|
||||
printType: 'books',
|
||||
};
|
||||
const langRestrict = this.localePreference;
|
||||
if (langRestrict === 'default') {
|
||||
params['langRestrict'] = window.moment.locale();
|
||||
} else {
|
||||
params['langRestrict'] = langRestrict;
|
||||
}
|
||||
const searchResults = await apiGet<GoogleBooksResponse>('https://www.googleapis.com/books/v1/volumes', params);
|
||||
if (!searchResults?.totalItems) {
|
||||
return [];
|
||||
}
|
||||
return searchResults.items.map(({ volumeInfo }) => this.createBookItem(volumeInfo));
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
getISBN(industryIdentifiers: VolumeInfo['industryIdentifiers']) {
|
||||
return industryIdentifiers?.reduce((result, item) => {
|
||||
if (item.type == 'ISBN_10') {
|
||||
result['isbn10'] = item.identifier.trim();
|
||||
}
|
||||
if (item.type == 'ISBN_13') {
|
||||
result['isbn13'] = item.identifier.trim();
|
||||
}
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
createBookItem(item: VolumeInfo): Book {
|
||||
const book: Book = {
|
||||
title: item.title,
|
||||
subtitle: item.subtitle,
|
||||
author: this.formatList(item.authors),
|
||||
authors: item.authors,
|
||||
category: this.formatList(item.categories),
|
||||
publisher: item.publisher,
|
||||
totalPage: item.pageCount,
|
||||
coverUrl: item.imageLinks?.thumbnail ?? '',
|
||||
coverSmallUrl: item.imageLinks?.smallThumbnail ?? '',
|
||||
publishDate: item.publishedDate || '',
|
||||
description: item.description,
|
||||
link: item.canonicalVolumeLink || item.infoLink,
|
||||
previewLink: item.previewLink,
|
||||
...this.getISBN(item.industryIdentifiers),
|
||||
};
|
||||
return book;
|
||||
}
|
||||
|
||||
convertGoogleBookImageURLSize(url: string, zoom: number) {
|
||||
return url.replace(/(&zoom)=\d/, `$1=${zoom}`);
|
||||
}
|
||||
|
||||
formatList(list?: string[]) {
|
||||
if (list?.length > 1) {
|
||||
return list.map(item => `${item.trim()}`).join(', ');
|
||||
}
|
||||
return list?.[0]?.replace('N/A', '') ?? '';
|
||||
}
|
||||
}
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
export interface GoogleBooksResponse {
|
||||
kind: string;
|
||||
totalItems: number;
|
||||
items: Item[];
|
||||
}
|
||||
|
||||
export interface Item {
|
||||
kind: string;
|
||||
id: string;
|
||||
etag: string;
|
||||
selfLink: string;
|
||||
volumeInfo: VolumeInfo;
|
||||
saleInfo: SaleInfo;
|
||||
accessInfo: AccessInfo;
|
||||
searchInfo: SearchInfo;
|
||||
}
|
||||
|
||||
export interface AccessInfo {
|
||||
country: string;
|
||||
viewability: string;
|
||||
embeddable: boolean;
|
||||
publicDomain: boolean;
|
||||
textToSpeechPermission: string;
|
||||
epub: Epub;
|
||||
pdf: Epub;
|
||||
webReaderLink: string;
|
||||
accessViewStatus: string;
|
||||
quoteSharingAllowed: boolean;
|
||||
}
|
||||
|
||||
export interface Epub {
|
||||
isAvailable: boolean;
|
||||
acsTokenLink?: string;
|
||||
}
|
||||
|
||||
export interface SaleInfo {
|
||||
country: string;
|
||||
saleability: string;
|
||||
isEbook: boolean;
|
||||
listPrice?: SaleInfoListPrice;
|
||||
retailPrice?: SaleInfoListPrice;
|
||||
buyLink?: string;
|
||||
offers?: Offer[];
|
||||
}
|
||||
|
||||
export interface SaleInfoListPrice {
|
||||
amount: number;
|
||||
currencyCode: string;
|
||||
}
|
||||
|
||||
export interface Offer {
|
||||
finskyOfferType: number;
|
||||
listPrice: OfferListPrice;
|
||||
retailPrice: OfferListPrice;
|
||||
rentalDuration?: RentalDuration;
|
||||
}
|
||||
|
||||
export interface OfferListPrice {
|
||||
amountInMicros: number;
|
||||
currencyCode: string;
|
||||
}
|
||||
|
||||
export interface RentalDuration {
|
||||
unit: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface SearchInfo {
|
||||
textSnippet: string;
|
||||
}
|
||||
|
||||
export interface VolumeInfo {
|
||||
title: string;
|
||||
authors?: string[];
|
||||
publisher?: string;
|
||||
publishedDate: string;
|
||||
industryIdentifiers: IndustryIdentifier[];
|
||||
readingModes: ReadingModes;
|
||||
pageCount?: number;
|
||||
printType: string;
|
||||
categories?: string[];
|
||||
maturityRating: string;
|
||||
allowAnonLogging: boolean;
|
||||
contentVersion: string;
|
||||
panelizationSummary: PanelizationSummary;
|
||||
imageLinks: ImageLinks;
|
||||
language: string;
|
||||
previewLink: string;
|
||||
infoLink: string;
|
||||
canonicalVolumeLink: string;
|
||||
subtitle?: string;
|
||||
description?: string;
|
||||
averageRating?: number;
|
||||
ratingsCount?: number;
|
||||
}
|
||||
|
||||
export interface ImageLinks {
|
||||
smallThumbnail: string;
|
||||
thumbnail: string;
|
||||
}
|
||||
|
||||
export interface IndustryIdentifier {
|
||||
type: Type;
|
||||
identifier: string;
|
||||
}
|
||||
|
||||
export enum Type {
|
||||
Isbn10 = 'ISBN_10',
|
||||
Isbn13 = 'ISBN_13',
|
||||
Other = 'OTHER',
|
||||
}
|
||||
|
||||
export interface PanelizationSummary {
|
||||
containsEpubBubbles: boolean;
|
||||
containsImageBubbles: boolean;
|
||||
}
|
||||
|
||||
export interface ReadingModes {
|
||||
text: boolean;
|
||||
image: boolean;
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
export interface NaverBooksResponse {
|
||||
lastBuildDate: string;
|
||||
total: number;
|
||||
start: number;
|
||||
display: number;
|
||||
items: NaverBookItem[];
|
||||
}
|
||||
|
||||
export interface NaverBookItem {
|
||||
title: string;
|
||||
link: string;
|
||||
image: string;
|
||||
author: string;
|
||||
discount: string;
|
||||
publisher: string;
|
||||
pubdate: string;
|
||||
isbn: string;
|
||||
description: string;
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
import { Book } from '@models/book.model';
|
||||
import { apiGet, BaseBooksApiImpl } from './base_api';
|
||||
import { NaverBookItem, NaverBooksResponse } from './models/naver_books_response';
|
||||
|
||||
export class NaverBooksApi implements BaseBooksApiImpl {
|
||||
constructor(private readonly clientId, private readonly clientSecret: string) {}
|
||||
|
||||
async getByQuery(query: string) {
|
||||
try {
|
||||
const params = {
|
||||
query,
|
||||
display: 50,
|
||||
sort: 'sim',
|
||||
};
|
||||
const header = {
|
||||
'X-Naver-Client-Id': this.clientId,
|
||||
'X-Naver-Client-Secret': this.clientSecret,
|
||||
};
|
||||
const searchResults = await apiGet<NaverBooksResponse>(
|
||||
'https://openapi.naver.com/v1/search/book.json',
|
||||
params,
|
||||
header,
|
||||
);
|
||||
if (!searchResults?.total) {
|
||||
return [];
|
||||
}
|
||||
return searchResults.items.map(this.createBookItem);
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
createBookItem(item: NaverBookItem) {
|
||||
return {
|
||||
title: item.title,
|
||||
author: item.author,
|
||||
publisher: item.publisher,
|
||||
coverUrl: item.image,
|
||||
publishDate: item.pubdate?.slice(0, 4) || '',
|
||||
link: item.link,
|
||||
description: item.description,
|
||||
isbn: item.isbn,
|
||||
...(item.isbn?.length >= 13 ? { isbn13: item.isbn } : { isbn10: item.isbn }),
|
||||
} as Book;
|
||||
}
|
||||
}
|
||||
30
src/apis/rawg_games_api.ts
Normal file
30
src/apis/rawg_games_api.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { RAWGResponse, Game } from '@models/game.model';
|
||||
import { requestUrl } from 'obsidian';
|
||||
|
||||
export class RAWGAPI {
|
||||
constructor(private readonly key: string) {}
|
||||
|
||||
async getByQuery(query: string): Promise<Game[]> {
|
||||
try {
|
||||
const apiURL = new URL('https://api.rawg.io/api/games');
|
||||
apiURL.searchParams.append('key', this.key);
|
||||
apiURL.searchParams.append('search', query);
|
||||
|
||||
const res = await requestUrl({
|
||||
url: apiURL.href,
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
const searchResults = res.json as RAWGResponse<Game>;
|
||||
|
||||
if (!searchResults?.count) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return searchResults.results;
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
export enum ServiceProvider {
|
||||
google = 'google',
|
||||
naver = 'naver',
|
||||
}
|
||||
70
src/main.ts
70
src/main.ts
|
|
@ -1,10 +1,10 @@
|
|||
import { MarkdownView, Notice, Plugin } from 'obsidian';
|
||||
|
||||
import { BookSearchModal } from '@views/book_search_modal';
|
||||
import { BookSuggestModal } from '@views/book_suggest_modal';
|
||||
import { GameSearchModal } from '@views/game_search_modal';
|
||||
import { GameSuggestModal } from '@views/game_suggest_modal';
|
||||
import { CursorJumper } from '@utils/cursor_jumper';
|
||||
import { Book } from '@models/book.model';
|
||||
import { BookSearchSettingTab, BookSearchPluginSettings, DEFAULT_SETTINGS } from '@settings/settings';
|
||||
import { Game } from '@models/game.model';
|
||||
import { GameSearchSettingTab, GameSearchPluginSettings, DEFAULT_SETTINGS } from '@settings/settings';
|
||||
import {
|
||||
getTemplateContents,
|
||||
applyTemplateTransformations,
|
||||
|
|
@ -13,34 +13,34 @@ import {
|
|||
} from '@utils/template';
|
||||
import { replaceVariableSyntax, makeFileName, applyDefaultFrontMatter, toStringFrontMatter } from '@utils/utils';
|
||||
|
||||
export default class BookSearchPlugin extends Plugin {
|
||||
settings: BookSearchPluginSettings;
|
||||
export default class GameSearchPlugin extends Plugin {
|
||||
settings: GameSearchPluginSettings;
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
// This creates an icon in the left ribbon.
|
||||
const ribbonIconEl = this.addRibbonIcon('book', 'Create new book note', () => this.createNewBookNote());
|
||||
const ribbonIconEl = this.addRibbonIcon('game', 'Create new game note', () => this.createNewGameNote());
|
||||
// Perform additional things with the ribbon
|
||||
ribbonIconEl.addClass('obsidian-book-search-plugin-ribbon-class');
|
||||
ribbonIconEl.addClass('obsidian-game-search-plugin-ribbon-class');
|
||||
|
||||
// This adds a simple command that can be triggered anywhere
|
||||
this.addCommand({
|
||||
id: 'open-book-search-modal',
|
||||
name: 'Create new book note',
|
||||
callback: () => this.createNewBookNote(),
|
||||
id: 'open-game-search-modal',
|
||||
name: 'Create new game note',
|
||||
callback: () => this.createNewGameNote(),
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: 'open-book-search-modal-to-insert',
|
||||
id: 'open-game-search-modal-to-insert',
|
||||
name: 'Insert the metadata',
|
||||
callback: () => this.insertMetadata(),
|
||||
});
|
||||
|
||||
// This adds a settings tab so the user can configure various aspects of the plugin
|
||||
this.addSettingTab(new BookSearchSettingTab(this.app, this));
|
||||
this.addSettingTab(new GameSearchSettingTab(this.app, this));
|
||||
|
||||
console.log(`Book Search: version ${this.manifest.version} (requires obsidian ${this.manifest.minAppVersion})`);
|
||||
console.log(`Game Search: version ${this.manifest.version} (requires obsidian ${this.manifest.minAppVersion})`);
|
||||
}
|
||||
|
||||
showNotice(message: unknown) {
|
||||
|
|
@ -51,13 +51,13 @@ export default class BookSearchPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
// open modal for book search
|
||||
async searchBookMetadata(query?: string): Promise<Book> {
|
||||
const searchedBooks = await this.openBookSearchModal(query);
|
||||
return await this.openBookSuggestModal(searchedBooks);
|
||||
// open modal for game search
|
||||
async searchGameMetadata(query?: string): Promise<Game> {
|
||||
const searchedGames = await this.openGameSearchModal(query);
|
||||
return await this.openGameSuggestModal(searchedGames);
|
||||
}
|
||||
|
||||
async getRenderedContents(book: Book) {
|
||||
async getRenderedContents(game: Game) {
|
||||
const {
|
||||
templateFile,
|
||||
useDefaultFrontmatter,
|
||||
|
|
@ -68,17 +68,17 @@ export default class BookSearchPlugin extends Plugin {
|
|||
|
||||
if (templateFile) {
|
||||
const templateContents = await getTemplateContents(this.app, templateFile);
|
||||
const replacedVariable = replaceVariableSyntax(book, applyTemplateTransformations(templateContents));
|
||||
return executeInlineScriptsTemplates(book, replacedVariable);
|
||||
const replacedVariable = replaceVariableSyntax(game, applyTemplateTransformations(templateContents));
|
||||
return executeInlineScriptsTemplates(game, replacedVariable);
|
||||
}
|
||||
|
||||
let replacedVariableFrontmatter = replaceVariableSyntax(book, frontmatter); // @deprecated
|
||||
let replacedVariableFrontmatter = replaceVariableSyntax(game, frontmatter); // @deprecated
|
||||
if (useDefaultFrontmatter) {
|
||||
replacedVariableFrontmatter = toStringFrontMatter(
|
||||
applyDefaultFrontMatter(book, replacedVariableFrontmatter, defaultFrontmatterKeyType),
|
||||
applyDefaultFrontMatter(game, replacedVariableFrontmatter, defaultFrontmatterKeyType),
|
||||
);
|
||||
}
|
||||
const replacedVariableContent = replaceVariableSyntax(book, content);
|
||||
const replacedVariableContent = replaceVariableSyntax(game, content);
|
||||
|
||||
return replacedVariableFrontmatter
|
||||
? `---\n${replacedVariableFrontmatter}\n---\n${replacedVariableContent}`
|
||||
|
|
@ -94,14 +94,14 @@ export default class BookSearchPlugin extends Plugin {
|
|||
}
|
||||
|
||||
// TODO: Try using a search query on the selected text
|
||||
const book = await this.searchBookMetadata(markdownView.file.basename);
|
||||
const game = await this.searchGameMetadata(markdownView.file.basename);
|
||||
|
||||
if (!markdownView.editor) {
|
||||
console.warn('Can not find editor from the active markdown view');
|
||||
return;
|
||||
}
|
||||
|
||||
const renderedContents = await this.getRenderedContents(book);
|
||||
const renderedContents = await this.getRenderedContents(game);
|
||||
markdownView.editor.replaceRange(renderedContents, { line: 0, ch: 0 });
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
|
|
@ -109,9 +109,9 @@ export default class BookSearchPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
async createNewBookNote(): Promise<void> {
|
||||
async createNewGameNote(): Promise<void> {
|
||||
try {
|
||||
const book = await this.searchBookMetadata();
|
||||
const game = await this.searchGameMetadata();
|
||||
|
||||
// open file
|
||||
const activeLeaf = this.app.workspace.getLeaf();
|
||||
|
|
@ -120,11 +120,11 @@ export default class BookSearchPlugin extends Plugin {
|
|||
return;
|
||||
}
|
||||
|
||||
const renderedContents = await this.getRenderedContents(book);
|
||||
const renderedContents = await this.getRenderedContents(game);
|
||||
|
||||
// TODO: If the same file exists, it asks if you want to overwrite it.
|
||||
// create new File
|
||||
const fileName = makeFileName(book, this.settings.fileNameFormat);
|
||||
const fileName = makeFileName(game, this.settings.fileNameFormat);
|
||||
const filePath = `${this.settings.folder}/${fileName}`;
|
||||
const targetFile = await this.app.vault.create(filePath, renderedContents);
|
||||
|
||||
|
|
@ -143,18 +143,18 @@ export default class BookSearchPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
async openBookSearchModal(query = ''): Promise<Book[]> {
|
||||
async openGameSearchModal(query = ''): Promise<Game[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new BookSearchModal(this, query, (error, results) => {
|
||||
return new GameSearchModal(this, this.settings.rawgApiKey, query, (error, results) => {
|
||||
return error ? reject(error) : resolve(results);
|
||||
}).open();
|
||||
});
|
||||
}
|
||||
|
||||
async openBookSuggestModal(books: Book[]): Promise<Book> {
|
||||
async openGameSuggestModal(games: Game[]): Promise<Game> {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new BookSuggestModal(this.app, books, (error, selectedBook) => {
|
||||
return error ? reject(error) : resolve(selectedBook);
|
||||
return new GameSuggestModal(this.app, games, (error, selectedGame) => {
|
||||
return error ? reject(error) : resolve(selectedGame);
|
||||
}).open();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
export interface FrontMatter {
|
||||
[key: string]: string | string[];
|
||||
}
|
||||
|
||||
export interface Book {
|
||||
title: string; // 책 제목
|
||||
subtitle?: string;
|
||||
author: string; // 저자
|
||||
authors: string[];
|
||||
category?: string; // 카테고리
|
||||
publisher?: string; // 출판사
|
||||
publishDate?: string; // 출판일
|
||||
totalPage?: number | string; // 전체 페이지
|
||||
coverUrl?: string; // 커버 URL
|
||||
coverSmallUrl?: string; // 커버 URL
|
||||
coverMediumUrl?: string; // 커버 URL
|
||||
coverLargeUrl?: string; // 커버 URL
|
||||
status?: string; // 읽기 상태(읽기전, 읽는중, 읽기완료)
|
||||
startReadDate?: string; // 읽기 시작한 일시
|
||||
finishReadDate?: string; // 읽기 완료한 일시
|
||||
myRate?: number | string; //나의 평점
|
||||
bookNote?: string; //서평 기록 여부
|
||||
isbn10?: string;
|
||||
isbn13?: string;
|
||||
isbn?: string;
|
||||
link?: string;
|
||||
description?: string;
|
||||
previewLink?: string;
|
||||
}
|
||||
34
src/models/game.model.ts
Normal file
34
src/models/game.model.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export interface FrontMatter {
|
||||
[key: string]: string | string[];
|
||||
}
|
||||
|
||||
export interface RAWGResponse<T> {
|
||||
count: number;
|
||||
next: string;
|
||||
previous: string;
|
||||
results: T[];
|
||||
}
|
||||
|
||||
export interface Game {
|
||||
id: number;
|
||||
slug: string;
|
||||
name: string;
|
||||
released: string;
|
||||
tba: boolean;
|
||||
background_image: string;
|
||||
rating: number;
|
||||
rating_top: number;
|
||||
ratings: any;
|
||||
ratings_count: number;
|
||||
reviews_text_count: number;
|
||||
added: number;
|
||||
added_by_status: number;
|
||||
metacritic: number;
|
||||
playtime: number; // in hours
|
||||
suggestions_count: number;
|
||||
updated: string;
|
||||
esrb_rating?: string;
|
||||
platforms: any[];
|
||||
}
|
||||
|
|
@ -1,21 +1,19 @@
|
|||
import { App, PluginSettingTab, Setting } from 'obsidian';
|
||||
import { replaceDateInString } from '@utils/utils';
|
||||
|
||||
import BookSearchPlugin from '../main';
|
||||
import GameSearchPlugin from '../main';
|
||||
import { FileNameFormatSuggest } from './suggesters/FileNameFormatSuggester';
|
||||
import { FolderSuggest } from './suggesters/FolderSuggester';
|
||||
import { FileSuggest } from './suggesters/FileSuggester';
|
||||
import { ServiceProvider } from '@src/constants';
|
||||
import { SettingServiceProviderModal } from '@views/setting_service_provider_modal';
|
||||
|
||||
const docUrl = 'https://github.com/anpigon/obsidian-book-search-plugin';
|
||||
const docUrl = 'https://github.com/CMorooney/obsidian-game-search-plugin';
|
||||
|
||||
export enum DefaultFrontmatterKeyType {
|
||||
snakeCase = 'Snake Case',
|
||||
camelCase = 'Camel Case',
|
||||
}
|
||||
|
||||
export interface BookSearchPluginSettings {
|
||||
export interface GameSearchPluginSettings {
|
||||
folder: string; // new file location
|
||||
fileNameFormat: string; // new file name format
|
||||
frontmatter: string; // frontmatter that is inserted into the file
|
||||
|
|
@ -23,13 +21,10 @@ export interface BookSearchPluginSettings {
|
|||
useDefaultFrontmatter: boolean;
|
||||
defaultFrontmatterKeyType: DefaultFrontmatterKeyType;
|
||||
templateFile: string;
|
||||
serviceProvider: ServiceProvider;
|
||||
naverClientId: string;
|
||||
naverClientSecret: string;
|
||||
localePreference: string;
|
||||
rawgApiKey: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: BookSearchPluginSettings = {
|
||||
export const DEFAULT_SETTINGS: GameSearchPluginSettings = {
|
||||
folder: '',
|
||||
fileNameFormat: '',
|
||||
frontmatter: '',
|
||||
|
|
@ -37,14 +32,11 @@ export const DEFAULT_SETTINGS: BookSearchPluginSettings = {
|
|||
useDefaultFrontmatter: true,
|
||||
defaultFrontmatterKeyType: DefaultFrontmatterKeyType.camelCase,
|
||||
templateFile: '',
|
||||
serviceProvider: ServiceProvider.google,
|
||||
naverClientId: '',
|
||||
naverClientSecret: '',
|
||||
localePreference: 'default',
|
||||
rawgApiKey: '',
|
||||
};
|
||||
|
||||
export class BookSearchSettingTab extends PluginSettingTab {
|
||||
constructor(app: App, private plugin: BookSearchPlugin) {
|
||||
export class GameSearchSettingTab extends PluginSettingTab {
|
||||
constructor(app: App, private plugin: GameSearchPlugin) {
|
||||
super(app, plugin);
|
||||
}
|
||||
|
||||
|
|
@ -57,14 +49,14 @@ export class BookSearchSettingTab extends PluginSettingTab {
|
|||
|
||||
containerEl.empty();
|
||||
|
||||
containerEl.classList.add('book-search-plugin__settings');
|
||||
containerEl.classList.add('game-search-plugin__settings');
|
||||
|
||||
createHeader(containerEl, 'General Settings');
|
||||
|
||||
// New file location
|
||||
new Setting(containerEl)
|
||||
.setName('New file location')
|
||||
.setDesc('New book notes will be placed here.')
|
||||
.setDesc('New game notes will be placed here.')
|
||||
.addSearch(cb => {
|
||||
try {
|
||||
new FolderSuggest(this.app, cb.inputEl);
|
||||
|
|
@ -84,7 +76,7 @@ export class BookSearchSettingTab extends PluginSettingTab {
|
|||
text: replaceDateInString(this.plugin.settings.fileNameFormat) || '{{title}} - {{author}}',
|
||||
});
|
||||
new Setting(containerEl)
|
||||
.setClass('book-search-plugin__settings--new_file_name')
|
||||
.setClass('game-search-plugin__settings--new_file_name')
|
||||
.setName('New file name')
|
||||
.setDesc('Enter the file name format.')
|
||||
.addSearch(cb => {
|
||||
|
|
@ -93,18 +85,18 @@ export class BookSearchSettingTab extends PluginSettingTab {
|
|||
} catch {
|
||||
// eslint-disable
|
||||
}
|
||||
cb.setPlaceholder('Example: {{title}} - {{author}}')
|
||||
cb.setPlaceholder('Example: {{name}} - {{release}}')
|
||||
.setValue(this.plugin.settings.fileNameFormat)
|
||||
.onChange(newValue => {
|
||||
this.plugin.settings.fileNameFormat = newValue?.trim();
|
||||
this.plugin.saveSettings();
|
||||
|
||||
newFileNameHint.innerHTML = replaceDateInString(newValue) || '{{title}} - {{author}}';
|
||||
newFileNameHint.innerHTML = replaceDateInString(newValue) || '{{name}} - {{release}}';
|
||||
});
|
||||
});
|
||||
containerEl
|
||||
.createEl('div', {
|
||||
cls: ['setting-item-description', 'book-search-plugin__settings--new_file_name_hint'],
|
||||
cls: ['setting-item-description', 'game-search-plugin__settings--new_file_name_hint'],
|
||||
})
|
||||
.append(newFileNameHint);
|
||||
|
||||
|
|
@ -132,86 +124,12 @@ export class BookSearchSettingTab extends PluginSettingTab {
|
|||
});
|
||||
});
|
||||
|
||||
// Service Provider
|
||||
let serviceProviderExtraSettingButton: HTMLElement;
|
||||
// eslint-disable-next-line prefer-const
|
||||
let preferredLocaleDropdownSetting: Setting;
|
||||
const hideServiceProviderExtraSettingButton = () => {
|
||||
serviceProviderExtraSettingButton.addClass('book-search-plugin__hide');
|
||||
};
|
||||
const showServiceProviderExtraSettingButton = () => {
|
||||
serviceProviderExtraSettingButton.removeClass('book-search-plugin__hide');
|
||||
};
|
||||
const hideServiceProviderExtraSettingDropdown = () => {
|
||||
if (preferredLocaleDropdownSetting !== undefined) {
|
||||
preferredLocaleDropdownSetting.settingEl.addClass('book-search-plugin__hide');
|
||||
}
|
||||
};
|
||||
const showServiceProviderExtraSettingDropdown = () => {
|
||||
if (preferredLocaleDropdownSetting !== undefined) {
|
||||
preferredLocaleDropdownSetting.settingEl.removeClass('book-search-plugin__hide');
|
||||
}
|
||||
};
|
||||
const toggleServiceProviderExtraSettings = (serviceProvider: ServiceProvider = this.settings?.serviceProvider) => {
|
||||
if (serviceProvider === ServiceProvider.naver) {
|
||||
showServiceProviderExtraSettingButton();
|
||||
hideServiceProviderExtraSettingDropdown();
|
||||
} else {
|
||||
hideServiceProviderExtraSettingButton();
|
||||
showServiceProviderExtraSettingDropdown();
|
||||
}
|
||||
};
|
||||
new Setting(containerEl)
|
||||
.setName('Service Provider')
|
||||
.setDesc('Choose the service provider you want to use to search your books.')
|
||||
.setClass('book-search-plugin__settings--service_provider')
|
||||
.addDropdown(dropDown => {
|
||||
dropDown.addOption(ServiceProvider.google, `${ServiceProvider.google} (Global)`);
|
||||
dropDown.addOption(ServiceProvider.naver, `${ServiceProvider.naver} (Korean)`);
|
||||
dropDown.setValue(this.plugin.settings?.serviceProvider ?? ServiceProvider.google);
|
||||
dropDown.onChange(async value => {
|
||||
const newValue = value as ServiceProvider;
|
||||
toggleServiceProviderExtraSettings(newValue);
|
||||
this.settings['serviceProvider'] = newValue;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
})
|
||||
.addExtraButton(component => {
|
||||
serviceProviderExtraSettingButton = component.extraSettingsEl;
|
||||
toggleServiceProviderExtraSettings();
|
||||
component.onClick(() => {
|
||||
new SettingServiceProviderModal(this.plugin).open();
|
||||
});
|
||||
});
|
||||
|
||||
preferredLocaleDropdownSetting = new Setting(containerEl)
|
||||
.setName('Preferred locale')
|
||||
.setDesc('Sets the preferred locale to use when searching for books.')
|
||||
.addDropdown(dropDown => {
|
||||
const defaultLocale = window.moment.locale();
|
||||
dropDown.addOption(defaultLocale, `${defaultLocale} (Default Locale)`);
|
||||
window.moment.locales().forEach(locale => {
|
||||
dropDown.addOption(locale, locale);
|
||||
});
|
||||
const setValue = this.settings.localePreference;
|
||||
if (setValue === 'default') {
|
||||
dropDown.setValue(defaultLocale);
|
||||
} else {
|
||||
dropDown.setValue(setValue);
|
||||
}
|
||||
dropDown.onChange(async value => {
|
||||
const newValue = value;
|
||||
this.settings.localePreference = newValue;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
// Frontmatter Settings
|
||||
const formatterSettingsChildren: Setting[] = [];
|
||||
createFoldingHeader(containerEl, 'Frontmatter Settings', formatterSettingsChildren);
|
||||
formatterSettingsChildren.push(
|
||||
new Setting(containerEl)
|
||||
.setClass('book-search-plugin__hide')
|
||||
.setClass('game-search-plugin__hide')
|
||||
.setName('Use the default frontmatter')
|
||||
.setDesc("If you don't want the default frontmatter to be inserted, disable it.")
|
||||
.addToggle(toggle => {
|
||||
|
|
@ -222,7 +140,7 @@ export class BookSearchSettingTab extends PluginSettingTab {
|
|||
});
|
||||
}),
|
||||
new Setting(containerEl)
|
||||
.setClass('book-search-plugin__hide')
|
||||
.setClass('game-search-plugin__hide')
|
||||
.setName('Default frontmatter key type')
|
||||
.setDesc(createKeyTypeDesc())
|
||||
.addDropdown(dropDown => {
|
||||
|
|
@ -235,7 +153,7 @@ export class BookSearchSettingTab extends PluginSettingTab {
|
|||
});
|
||||
}),
|
||||
new Setting(containerEl)
|
||||
.setClass('book-search-plugin__hide')
|
||||
.setClass('game-search-plugin__hide')
|
||||
.setName('(Deprecated) Text to insert into frontmatter')
|
||||
.setDesc(createSyntaxesDescription('#text-to-insert-into-frontmatter'))
|
||||
.addTextArea(textArea => {
|
||||
|
|
@ -253,7 +171,7 @@ export class BookSearchSettingTab extends PluginSettingTab {
|
|||
createFoldingHeader(containerEl, 'Content Settings', contentSettingsChildren);
|
||||
contentSettingsChildren.push(
|
||||
new Setting(containerEl)
|
||||
.setClass('book-search-plugin__hide')
|
||||
.setClass('game-search-plugin__hide')
|
||||
.setName('(Deprecated) Text to insert into content')
|
||||
.setDesc(createSyntaxesDescription('#text-to-insert-into-content'))
|
||||
.addTextArea(textArea => {
|
||||
|
|
@ -290,7 +208,7 @@ function createFoldingHeader(containerEl: HTMLElement, title: string, formatterS
|
|||
return createHeader(containerEl, title).addToggle(toggle => {
|
||||
toggle.onChange(checked => {
|
||||
formatterSettingsChildren.forEach(({ settingEl }) => {
|
||||
settingEl.toggleClass('book-search-plugin__show', checked);
|
||||
settingEl.toggleClass('game-search-plugin__show', checked);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Book } from '@models/book.model';
|
||||
import { Game } from '@models/game.model';
|
||||
import { App, normalizePath, Notice, TFile } from 'obsidian';
|
||||
|
||||
export async function getTemplateContents(app: App, templatePath: string | undefined): Promise<string> {
|
||||
|
|
@ -43,7 +43,7 @@ export function applyTemplateTransformations(rawTemplateContents: string): strin
|
|||
);
|
||||
}
|
||||
|
||||
export function executeInlineScriptsTemplates(book: Book, text: string) {
|
||||
export function executeInlineScriptsTemplates(game: Game, text: string) {
|
||||
const commandRegex = /<%(?:=)(.+)%>/g;
|
||||
const ctor = getFunctionConstructor();
|
||||
const matchedList = [...text.matchAll(commandRegex)];
|
||||
|
|
@ -51,12 +51,12 @@ export function executeInlineScriptsTemplates(book: Book, text: string) {
|
|||
try {
|
||||
const outputs = new ctor(
|
||||
[
|
||||
'const [book] = arguments',
|
||||
'const [game] = arguments',
|
||||
`const output = ${script}`,
|
||||
'if(typeof output === "string") return output',
|
||||
'return JSON.stringify(output)',
|
||||
].join(';'),
|
||||
)(book);
|
||||
)(game);
|
||||
return result.replace(matched, outputs);
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
import { Book } from '@models/book.model';
|
||||
import * as utils from './utils';
|
||||
|
||||
jest.mock('@settings/settings', () => jest.fn());
|
||||
|
||||
describe('util.js', () => {
|
||||
const book: Book = {
|
||||
title: '코스모스',
|
||||
author: '칼 세이건',
|
||||
authors: ['칼 세이건'],
|
||||
};
|
||||
|
||||
it('replaceIllegalFileNameCharactersInString', () => {
|
||||
expect(utils.replaceIllegalFileNameCharactersInString('재레드 다이아몬드의 <대변동 : 위기, 선택, 변화>')).toBe(
|
||||
'재레드 다이아몬드의 대변동 위기 선택 변화',
|
||||
);
|
||||
});
|
||||
|
||||
it('makeFileName 1', () => {
|
||||
expect(utils.makeFileName(book)).toBe('코스모스 - 칼 세이건.md');
|
||||
});
|
||||
|
||||
it('makeFileName 2', () => {
|
||||
const newBook = {
|
||||
...book,
|
||||
author: '',
|
||||
};
|
||||
expect(utils.makeFileName(newBook)).toBe('코스모스.md');
|
||||
});
|
||||
|
||||
it('makeFileName 3', () => {
|
||||
expect(utils.makeFileName(book, '{{author}}-{{title}}')).toBe('칼 세이건-코스모스.md');
|
||||
});
|
||||
|
||||
it('makeFileName 4', () => {
|
||||
expect(utils.makeFileName(book, '{{author}}-{{title}}')).toBe('칼 세이건-코스모스.md');
|
||||
});
|
||||
|
||||
it('makeFileName 5', () => {
|
||||
const newBook = {
|
||||
...book,
|
||||
title: '코스모스 : 창백한 푸른점',
|
||||
};
|
||||
expect(utils.makeFileName(newBook, '{{title}} - {{author}}')).toBe('코스모스 창백한 푸른점 - 칼 세이건.md');
|
||||
});
|
||||
});
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { Book, FrontMatter } from '@models/book.model';
|
||||
import { Game, FrontMatter } from '@models/game.model';
|
||||
import { DefaultFrontmatterKeyType } from '@settings/settings';
|
||||
|
||||
// == Format Syntax == //
|
||||
|
|
@ -14,29 +14,29 @@ export function isISBN(str: string) {
|
|||
return /^(97(8|9))?\d{9}(\d|X)$/.test(str);
|
||||
}
|
||||
|
||||
export function makeFileName(book: Book, fileNameFormat?: string) {
|
||||
export function makeFileName(game: Game, fileNameFormat?: string) {
|
||||
let result;
|
||||
if (fileNameFormat) {
|
||||
result = replaceVariableSyntax(book, replaceDateInString(fileNameFormat));
|
||||
result = replaceVariableSyntax(game, replaceDateInString(fileNameFormat));
|
||||
} else {
|
||||
result = !book.author ? book.title : `${book.title} - ${book.author}`;
|
||||
result = !game.released ? game.name : `${game.name} - ${game.released}`;
|
||||
}
|
||||
return replaceIllegalFileNameCharactersInString(result) + '.md';
|
||||
}
|
||||
|
||||
export function changeSnakeCase(book: Book) {
|
||||
return Object.entries(book).reduce((acc, [key, value]) => {
|
||||
export function changeSnakeCase(game: Game) {
|
||||
return Object.entries(game).reduce((acc, [key, value]) => {
|
||||
acc[camelToSnakeCase(key)] = value;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
export function applyDefaultFrontMatter(
|
||||
book: Book,
|
||||
game: Game,
|
||||
frontmatter: FrontMatter | string,
|
||||
keyType: DefaultFrontmatterKeyType = DefaultFrontmatterKeyType.snakeCase,
|
||||
) {
|
||||
const frontMater = keyType === DefaultFrontmatterKeyType.camelCase ? book : changeSnakeCase(book);
|
||||
const frontMater = keyType === DefaultFrontmatterKeyType.camelCase ? game : changeSnakeCase(game);
|
||||
|
||||
const extraFrontMatter = typeof frontmatter === 'string' ? parseFrontMatter(frontmatter) : frontmatter;
|
||||
for (const key in extraFrontMatter) {
|
||||
|
|
@ -51,12 +51,12 @@ export function applyDefaultFrontMatter(
|
|||
return frontMater as object;
|
||||
}
|
||||
|
||||
export function replaceVariableSyntax(book: Book, text: string): string {
|
||||
export function replaceVariableSyntax(game: Game, text: string): string {
|
||||
if (!text?.trim()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const entries = Object.entries(book);
|
||||
const entries = Object.entries(game);
|
||||
|
||||
return entries
|
||||
.reduce((result, [key, val = '']) => {
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
import { App, SuggestModal } from 'obsidian';
|
||||
import { Book } from '@models/book.model';
|
||||
|
||||
export class BookSuggestModal extends SuggestModal<Book> {
|
||||
constructor(
|
||||
app: App,
|
||||
private readonly suggestion: Book[],
|
||||
private onChoose: (error: Error | null, result?: Book) => void,
|
||||
) {
|
||||
super(app);
|
||||
}
|
||||
|
||||
// Returns all available suggestions.
|
||||
getSuggestions(query: string): Book[] {
|
||||
return this.suggestion.filter(book => {
|
||||
const searchQuery = query?.toLowerCase();
|
||||
return (
|
||||
book.title?.toLowerCase().includes(searchQuery) ||
|
||||
book.author?.toLowerCase().includes(searchQuery) ||
|
||||
book.publisher?.toLowerCase().includes(searchQuery)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Renders each suggestion item.
|
||||
renderSuggestion(book: Book, el: HTMLElement) {
|
||||
const title = book.title;
|
||||
const publisher = book.publisher ? `, ${book.publisher}` : '';
|
||||
const publishDate = book.publishDate ? `(${book.publishDate})` : '';
|
||||
const totalPage = book.totalPage ? `, p${book.totalPage}` : '';
|
||||
const subtitle = `${book.author}${publisher}${publishDate}${totalPage}`;
|
||||
el.createEl('div', { text: title });
|
||||
el.createEl('small', { text: subtitle });
|
||||
}
|
||||
|
||||
// Perform action on the selected suggestion.
|
||||
onChooseSuggestion(book: Book) {
|
||||
this.onChoose(null, book);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +1,19 @@
|
|||
import { ButtonComponent, Modal, Setting, TextComponent, Notice } from 'obsidian';
|
||||
import { Book } from '@models/book.model';
|
||||
import { BaseBooksApiImpl, factoryServiceProvider } from '@apis/base_api';
|
||||
import BookSearchPlugin from '@src/main';
|
||||
import { RAWGAPI } from '@src/apis/rawg_games_api';
|
||||
import { Game } from '@models/game.model';
|
||||
import GameSearchPlugin from '@src/main';
|
||||
|
||||
export class BookSearchModal extends Modal {
|
||||
export class GameSearchModal extends Modal {
|
||||
private isBusy = false;
|
||||
private okBtnRef?: ButtonComponent;
|
||||
private serviceProvider: BaseBooksApiImpl;
|
||||
|
||||
constructor(
|
||||
plugin: BookSearchPlugin,
|
||||
plugin: GameSearchPlugin,
|
||||
private key: string,
|
||||
private query: string,
|
||||
private callback: (error: Error | null, result?: Book[]) => void,
|
||||
private callback: (error: Error | null, result?: Game[]) => void,
|
||||
) {
|
||||
super(plugin.app);
|
||||
this.serviceProvider = factoryServiceProvider(plugin.settings);
|
||||
}
|
||||
|
||||
setBusy(busy: boolean) {
|
||||
|
|
@ -23,7 +22,7 @@ export class BookSearchModal extends Modal {
|
|||
this.okBtnRef?.setButtonText(busy ? 'Requesting...' : 'Search');
|
||||
}
|
||||
|
||||
async searchBook() {
|
||||
async searchGame() {
|
||||
if (!this.query) {
|
||||
throw new Error('No query entered.');
|
||||
}
|
||||
|
|
@ -31,11 +30,12 @@ export class BookSearchModal extends Modal {
|
|||
if (!this.isBusy) {
|
||||
try {
|
||||
this.setBusy(true);
|
||||
const searchResults = await this.serviceProvider.getByQuery(this.query);
|
||||
const api = new RAWGAPI(this.key);
|
||||
const searchResults = await api.getByQuery(this.query);
|
||||
this.setBusy(false);
|
||||
|
||||
if (!searchResults?.length) {
|
||||
new Notice(`No results found for "${this.query}"`); // Couldn't find the book.
|
||||
new Notice(`No results found for "${this.query}"`); // Couldn't find the game.
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -49,19 +49,19 @@ export class BookSearchModal extends Modal {
|
|||
|
||||
submitEnterCallback(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter' && !event.isComposing) {
|
||||
this.searchBook();
|
||||
this.searchGame();
|
||||
}
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
|
||||
contentEl.createEl('h2', { text: 'Search Book' });
|
||||
contentEl.createEl('h2', { text: 'Search Game' });
|
||||
|
||||
contentEl.createDiv({ cls: 'book-search-plugin__search-modal--input' }, settingItem => {
|
||||
contentEl.createDiv({ cls: 'game-search-plugin__search-modal--input' }, settingItem => {
|
||||
new TextComponent(settingItem)
|
||||
.setValue(this.query)
|
||||
.setPlaceholder('Search by keyword or ISBN')
|
||||
.setPlaceholder('Search by title')
|
||||
.onChange(value => (this.query = value))
|
||||
.inputEl.addEventListener('keydown', this.submitEnterCallback.bind(this));
|
||||
});
|
||||
|
|
@ -71,7 +71,7 @@ export class BookSearchModal extends Modal {
|
|||
.setButtonText('Search')
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
this.searchBook();
|
||||
this.searchGame();
|
||||
}));
|
||||
});
|
||||
}
|
||||
30
src/views/game_suggest_modal.ts
Normal file
30
src/views/game_suggest_modal.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { App, SuggestModal } from 'obsidian';
|
||||
import { Game } from '@models/game.model';
|
||||
|
||||
export class GameSuggestModal extends SuggestModal<Game> {
|
||||
constructor(
|
||||
app: App,
|
||||
private readonly suggestion: Game[],
|
||||
private onChoose: (error: Error | null, result?: Game) => void,
|
||||
) {
|
||||
super(app);
|
||||
}
|
||||
|
||||
// Returns all available suggestions.
|
||||
getSuggestions(_query: string): Game[] {
|
||||
return this.suggestion;
|
||||
}
|
||||
|
||||
// Renders each suggestion item.
|
||||
renderSuggestion(game: Game, el: HTMLElement) {
|
||||
const title = game.name;
|
||||
const publishDate = game.released ? `(${game.released})` : '';
|
||||
el.createEl('div', { text: title });
|
||||
el.createEl('small', { text: publishDate });
|
||||
}
|
||||
|
||||
// Perform action on the selected suggestion.
|
||||
onChooseSuggestion(game: Game) {
|
||||
this.onChoose(null, game);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
import { ServiceProvider } from '@src/constants';
|
||||
import BookSearchPlugin from '@src/main';
|
||||
import { Modal, Setting } from 'obsidian';
|
||||
|
||||
export class SettingServiceProviderModal extends Modal {
|
||||
private readonly plugin: BookSearchPlugin;
|
||||
private readonly currentServiceProvider: ServiceProvider;
|
||||
|
||||
constructor(plugin: BookSearchPlugin, private callback?: () => void) {
|
||||
super(plugin.app);
|
||||
this.plugin = plugin;
|
||||
this.currentServiceProvider = plugin.settings?.serviceProvider ?? ServiceProvider.google;
|
||||
}
|
||||
|
||||
get settings() {
|
||||
return this.plugin.settings;
|
||||
}
|
||||
|
||||
async saveSetting() {
|
||||
return this.plugin.saveSettings();
|
||||
}
|
||||
|
||||
saveClientId(clientId: string) {
|
||||
if (this.currentServiceProvider === ServiceProvider.naver) {
|
||||
this.plugin.settings['naverClientId'] = clientId;
|
||||
}
|
||||
}
|
||||
|
||||
saveClientSecret(clientSecret: string) {
|
||||
if (this.currentServiceProvider === ServiceProvider.naver) {
|
||||
this.settings['naverClientSecret'] = clientSecret;
|
||||
}
|
||||
}
|
||||
|
||||
get currentClientId() {
|
||||
if (this.currentServiceProvider === ServiceProvider.naver) {
|
||||
return this.settings.naverClientId;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
get currentClientSecret() {
|
||||
if (this.currentServiceProvider === ServiceProvider.naver) {
|
||||
return this.settings.naverClientSecret;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
|
||||
contentEl.createEl('h2', { text: 'Service Provider Setting' });
|
||||
|
||||
new Setting(contentEl).setName('Client ID').addText(text => {
|
||||
text.setValue(this.currentClientId).onChange(value => this.saveClientId(value));
|
||||
});
|
||||
|
||||
new Setting(contentEl).setName('Client Secret').addText(text => {
|
||||
text.setValue(this.currentClientSecret).onChange(value => this.saveClientSecret(value));
|
||||
});
|
||||
|
||||
new Setting(contentEl).addButton(btn =>
|
||||
btn
|
||||
.setButtonText('Save')
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
await this.plugin.saveSettings();
|
||||
this.close();
|
||||
this.callback?.();
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
onClose() {
|
||||
this.contentEl.empty();
|
||||
}
|
||||
}
|
||||
16
styles.css
16
styles.css
|
|
@ -1,29 +1,29 @@
|
|||
.book-search-plugin__settings .search-input-container {
|
||||
game-search-plugin__settings .search-input-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.book-search-plugin__settings--new_file_name {
|
||||
.game-search-plugin__settings--new_file_name {
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.book-search-plugin__settings--new_file_name_hint {
|
||||
.game-search-plugin__settings--new_file_name_hint {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.book-search-plugin__settings--service_provider .dropdown {
|
||||
.game-search-plugin__settings--service_provider .dropdown {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.book-search-plugin__hide {
|
||||
.game-search-plugin__hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.book-search-plugin__show {
|
||||
.game-search-plugin__show {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.book-search-plugin__search-modal--input {
|
||||
.game-search-plugin__search-modal--input {
|
||||
padding-bottom: 18px;
|
||||
}
|
||||
.book-search-plugin__search-modal--input input {
|
||||
.game-search-plugin__search-modal--input input {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
import * as obsidian from 'obsidian';
|
||||
|
||||
export const requestUrl: typeof obsidian.requestUrl = req => {
|
||||
return fetch(req as never).then(res => res.json());
|
||||
};
|
||||
Loading…
Reference in a new issue