├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── release.yml ├── .gitignore ├── .npmrc ├── ImagesModal.tsx ├── LICENSE ├── Loading.tsx ├── ModalWrapper.tsx ├── NoResult.tsx ├── README.md ├── SettingTab.ts ├── demo.webm ├── esbuild.config.mjs ├── fetchers ├── constants.ts ├── index.ts ├── local.ts ├── pexels.ts ├── pixabay.ts └── unsplash.ts ├── interface.d.ts ├── main.ts ├── manifest.json ├── meta-updater.ts ├── package.json ├── pexels.d.ts ├── pixabay-logo.svg ├── pixabay.d.ts ├── styles.css ├── tsconfig.json ├── unsplash.d.ts ├── utils.ts ├── version-bump.mjs ├── versions.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 2 10 | tab_width = 2 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | npm node_modules 2 | build -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "env": { "node": true }, 5 | "plugins": [ 6 | "@typescript-eslint" 7 | ], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended" 12 | ], 13 | "parserOptions": { 14 | "sourceType": "module" 15 | }, 16 | "rules": { 17 | "no-unused-vars": "off", 18 | "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], 19 | "@typescript-eslint/ban-ts-comment": "off", 20 | "no-prototype-builtins": "off", 21 | "@typescript-eslint/no-empty-function": "off" 22 | } 23 | } -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Obsidian plugin 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | env: 9 | PLUGIN_NAME: insert-unsplash-image 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Use Node.js 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: "14.x" 21 | 22 | - name: Build 23 | id: build 24 | run: | 25 | npm install 26 | npm run build 27 | mkdir ${{ env.PLUGIN_NAME }} 28 | cp main.js manifest.json styles.css ${{ env.PLUGIN_NAME }} 29 | zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }} 30 | ls 31 | echo "name=tag_name::$(git tag --sort version:refname | tail -n 1)" >> "$GITHUB_OUTPUT" 32 | 33 | - name: Create Release 34 | id: create_release 35 | uses: actions/create-release@v1 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | VERSION: ${{ github.ref }} 39 | with: 40 | tag_name: ${{ github.ref }} 41 | release_name: ${{ github.ref }} 42 | draft: false 43 | prerelease: false 44 | 45 | - name: Upload zip file 46 | id: upload-zip 47 | uses: actions/upload-release-asset@v1 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | with: 51 | upload_url: ${{ steps.create_release.outputs.upload_url }} 52 | asset_path: ./${{ env.PLUGIN_NAME }}.zip 53 | asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip 54 | asset_content_type: application/zip 55 | 56 | - name: Upload main.js 57 | id: upload-main 58 | uses: actions/upload-release-asset@v1 59 | env: 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | with: 62 | upload_url: ${{ steps.create_release.outputs.upload_url }} 63 | asset_path: ./main.js 64 | asset_name: main.js 65 | asset_content_type: text/javascript 66 | 67 | - name: Upload manifest.json 68 | id: upload-manifest 69 | uses: actions/upload-release-asset@v1 70 | env: 71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 72 | with: 73 | upload_url: ${{ steps.create_release.outputs.upload_url }} 74 | asset_path: ./manifest.json 75 | asset_name: manifest.json 76 | asset_content_type: application/json 77 | 78 | - name: Upload styles.css 79 | id: upload-css 80 | uses: actions/upload-release-asset@v1 81 | env: 82 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 83 | with: 84 | upload_url: ${{ steps.create_release.outputs.upload_url }} 85 | asset_path: ./styles.css 86 | asset_name: styles.css 87 | asset_content_type: text/css 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | .vscode 3 | 4 | # Intellij 5 | *.iml 6 | .idea 7 | 8 | # npm 9 | node_modules 10 | 11 | # Don't include the compiled main.js file in the repo. 12 | # They should be uploaded to GitHub releases instead. 13 | main.js 14 | 15 | # Exclude sourcemaps 16 | *.map 17 | 18 | # obsidian 19 | data.json 20 | 21 | # Exclude macOS Finder (System Explorer) View States 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix="" -------------------------------------------------------------------------------- /ImagesModal.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { getFetcher } from "fetchers"; 3 | import { 4 | Fetcher, 5 | Image, 6 | imageProviders, 7 | imageSizes, 8 | imageSizesMapping, 9 | providerMapping, 10 | } from "fetchers/constants"; 11 | import { ChangeEvent, useCallback, useEffect, useRef, useState } from "react"; 12 | import { Notice } from "obsidian"; 13 | 14 | import { debounce } from "./utils"; 15 | import Loading from "./Loading"; 16 | import NoResult from "./NoResult"; 17 | import { ImageProvider, ImageSize, PluginSettings } from "SettingTab"; 18 | 19 | interface Props { 20 | fetcher: Fetcher; 21 | onFetcherChange: (fetcher: Fetcher) => void; 22 | settings: PluginSettings; 23 | onSelect: (image: Image) => void; 24 | } 25 | 26 | const ImagesModal = ({ 27 | fetcher: defaultFetcher, 28 | onFetcherChange, 29 | settings, 30 | onSelect, 31 | }: Props) => { 32 | const [fetcher, setFetcher] = useState(defaultFetcher); 33 | const [query, setQuery] = useState(""); 34 | const [images, setImages] = useState([]); 35 | const ref = useRef(null); 36 | const selectedImageRef = useRef(null); 37 | const [selectedImage, setSelectedImage] = useState(0); 38 | const [error, setError] = useState(); 39 | 40 | const [loading, setLoading] = useState(false); 41 | 42 | const fetchImages = async (query: string) => { 43 | try { 44 | const images = await fetcher.searchImages(query); 45 | setImages(images); 46 | setSelectedImage(0); 47 | } catch (e) { 48 | setError(e.message); 49 | console.error(e); 50 | new Notice("Something went wrong, please contact the plugin author."); 51 | } 52 | setLoading(false); 53 | }; 54 | 55 | const debouncedFetchImages = useCallback(debounce(fetchImages, 1000), [ 56 | fetcher, 57 | ]); 58 | 59 | const onQueryChange = async (query: string) => { 60 | setQuery(query); 61 | debouncedFetchImages(query); 62 | }; 63 | 64 | const onInputChange = async (e: ChangeEvent) => { 65 | setLoading(true); 66 | setError(undefined); 67 | onQueryChange(e.target.value); 68 | }; 69 | 70 | const onProviderChange = async (provider: ImageProvider) => { 71 | setLoading(true); 72 | setError(undefined); 73 | const newFetcher = getFetcher( 74 | { ...settings, imageProvider: provider, imageSize: fetcher.imageSize }, 75 | fetcher.vault, 76 | ); 77 | setFetcher(newFetcher); 78 | onFetcherChange(newFetcher); 79 | }; 80 | 81 | const onProviderSelectorChange = async ( 82 | e: ChangeEvent, 83 | ) => { 84 | const provider = e.target.value as ImageProvider; 85 | await onProviderChange(provider); 86 | }; 87 | 88 | const onImageSizeChange = async (quality: ImageSize) => { 89 | setLoading(true); 90 | setError(undefined); 91 | const newFetcher = getFetcher( 92 | { 93 | ...settings, 94 | imageSize: quality, 95 | imageProvider: fetcher.imageProvider, 96 | }, 97 | fetcher.vault, 98 | ); 99 | setFetcher(newFetcher); 100 | onFetcherChange(newFetcher); 101 | }; 102 | 103 | const onImageSizeSelectorChange = async ( 104 | e: ChangeEvent, 105 | ) => { 106 | const quality = e.target.value as ImageSize; 107 | await onImageSizeChange(quality); 108 | }; 109 | 110 | const onPrevBtnClick = () => { 111 | setLoading(true); 112 | fetcher.prevPage(); 113 | fetchImages(query); 114 | }; 115 | const onNextBtnClick = () => { 116 | setLoading(true); 117 | fetcher.nextPage(); 118 | fetchImages(query); 119 | }; 120 | 121 | const handleKeyDown = useCallback( 122 | (e: KeyboardEvent) => { 123 | if (e.ctrlKey && e.key === "n") { 124 | setSelectedImage((prev) => (prev + 1 >= images.length ? 0 : prev + 1)); 125 | } else if (e.ctrlKey && e.key === "p") { 126 | setSelectedImage((prev) => 127 | prev - 1 < 0 ? images.length - 1 : prev - 1, 128 | ); 129 | } else if (e.ctrlKey && e.key === "u") { 130 | let index = imageProviders.indexOf(fetcher.imageProvider) + 1; 131 | if (index >= imageProviders.length) { 132 | index = 0; 133 | } 134 | onProviderChange(imageProviders[index]); 135 | } else if (e.ctrlKey && e.key === "i") { 136 | let index = imageSizes.indexOf(fetcher.imageSize) + 1; 137 | if (index >= imageSizes.length) { 138 | index = 0; 139 | } 140 | onImageSizeChange(imageSizes[index]); 141 | } else if (e.key === "Enter") { 142 | e.preventDefault(); 143 | onSelect(images[selectedImage]); 144 | } 145 | }, 146 | [images, selectedImage, fetcher.imageProvider], 147 | ); 148 | 149 | useEffect(() => { 150 | const element = ref.current; 151 | if (element) { 152 | element.addEventListener("keydown", handleKeyDown); 153 | } 154 | 155 | return () => { 156 | const element = ref.current; 157 | if (element) { 158 | element.removeEventListener("keydown", handleKeyDown); 159 | } 160 | }; 161 | }, [handleKeyDown, ref.current]); 162 | 163 | useEffect(() => { 164 | if (selectedImageRef.current) { 165 | selectedImageRef.current.scrollIntoView({ block: "nearest" }); 166 | } 167 | }, [selectedImageRef.current]); 168 | 169 | useEffect(() => { 170 | if (fetcher) { 171 | onQueryChange(query); 172 | } 173 | }, [fetcher]); 174 | 175 | const hasPagination = fetcher.hasPrevPage() || fetcher.hasNextPage(); 176 | 177 | return ( 178 |
179 |
180 | 186 | 197 | 208 |
209 | {loading && } 210 | {query !== "" && !loading && fetcher.noResult() && } 211 | {error} 212 |
213 |
214 | {images.map((image, index) => ( 215 |
onSelect(image)} 217 | onMouseMove={() => setSelectedImage(index)} 218 | key={image.url} 219 | className={`query-result${ 220 | selectedImage === index ? " is-selected" : "" 221 | }`} 222 | ref={index === selectedImage ? selectedImageRef : null} 223 | > 224 | 225 |
226 | ))} 227 |
228 | {hasPagination && ( 229 |
230 |
231 | {fetcher.hasPrevPage() && ( 232 | 240 | )} 241 |
242 |
243 | {fetcher.hasNextPage() && ( 244 | 252 | )} 253 |
254 |
255 | )} 256 | {fetcher.imageProvider === ImageProvider.pixabay && ( 257 | 301 | )} 302 |
303 |
304 | ); 305 | }; 306 | 307 | export { ImagesModal }; 308 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ray Hao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Loading.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export default function Loading() { 4 | return ( 5 |
6 |
7 |
8 | ) 9 | } 10 | 11 | -------------------------------------------------------------------------------- /ModalWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { upsert } from "meta-updater"; 2 | import { App, Editor, Notice, Modal } from "obsidian"; 3 | import * as React from "react"; 4 | import * as ReactDOM from "react-dom"; 5 | import { createRoot } from "react-dom/client"; 6 | 7 | import { getFetcher } from "./fetchers"; 8 | import { Fetcher, Image } from "./fetchers/constants"; 9 | import { ImagesModal } from "./ImagesModal"; 10 | import { PluginSettings } from "./SettingTab"; 11 | 12 | export enum InsertPlace { 13 | default = "default", 14 | frontmatter = "frontmatter", 15 | } 16 | 17 | export class ModalWrapper extends Modal { 18 | editor: Editor; 19 | fetcher: ReturnType; 20 | settings: PluginSettings; 21 | insertPlace: InsertPlace; 22 | 23 | constructor( 24 | app: App, 25 | editor: Editor, 26 | settings: PluginSettings, 27 | insertPlace: InsertPlace = InsertPlace.default, 28 | ) { 29 | super(app); 30 | this.editor = editor; 31 | this.settings = { 32 | ...settings, 33 | useMarkdownLinks: app.vault.config.useMarkdownLinks, 34 | }; 35 | this.fetcher = getFetcher(this.settings, app.vault); 36 | this.insertPlace = insertPlace; 37 | this.containerEl.addClass("image-inserter-container"); 38 | } 39 | 40 | onFetcherChange(fetcher: Fetcher) { 41 | this.fetcher = fetcher; 42 | } 43 | 44 | async onOpen() { 45 | const { contentEl } = this; 46 | 47 | const root = createRoot(contentEl); 48 | root.render( 49 | 50 | 56 | , 57 | ); 58 | } 59 | 60 | onClose() { 61 | const { containerEl } = this; 62 | ReactDOM.unmountComponentAtNode(containerEl); 63 | containerEl.empty(); 64 | } 65 | 66 | async createFile(name: string, ext: string, binary: ArrayBuffer) { 67 | const file = this.app.workspace.getActiveFile(); 68 | if (file === null) { 69 | throw "No active file"; 70 | } 71 | const filePath = await this.app.vault.getAvailablePathForAttachments( 72 | name, 73 | ext, 74 | file, 75 | ); 76 | 77 | this.app.vault.createBinary(filePath, binary); 78 | } 79 | 80 | async onChooseSuggestion(item: Image) { 81 | try { 82 | if (this.insertPlace === InsertPlace.default) { 83 | const imageTag = await this.fetcher.downloadAndInsertImage( 84 | item, 85 | this.createFile.bind(this), 86 | ); 87 | this.editor.replaceSelection(imageTag); 88 | } 89 | if (this.insertPlace === InsertPlace.frontmatter) { 90 | const { url: imageTag, referral } = 91 | await this.fetcher.downloadAndGetUri( 92 | item, 93 | this.createFile.bind(this), 94 | ); 95 | const file = this.app.workspace.getActiveFile(); 96 | if (file) { 97 | const updatedContent = await upsert( 98 | this.app, 99 | file, 100 | this.settings.frontmatter.key, 101 | `"${this.settings.frontmatter.valueFormat.replace( 102 | "{image-url}", 103 | imageTag, 104 | )}"`, 105 | ); 106 | await this.app.vault.modify( 107 | file, 108 | this.settings.frontmatter.appendReferral 109 | ? [updatedContent, referral].join("\n") 110 | : updatedContent, 111 | ); 112 | } 113 | } 114 | 115 | this.close(); 116 | } catch (e) { 117 | console.error(e); 118 | new Notice("Something went wrong, please contact the plugin author."); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /NoResult.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export default function NoResult() { 4 | return ( 5 |
6 | OPS! Nothing Found 7 |
8 | ) 9 | } 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Obsidian Image Inserter 2 | 3 | This plugin helps users easily search and insert images to editors from one or more image sources. 4 | 5 | # Features 6 | 7 | - Search images from Unsplash / Pixabay / Pexels (Will support more sources in the future) 8 | - Insert image locally (download it into your resource folder), or remotely (only insert the image url) 9 | - Insert image with preferred quality 10 | - Set default image size, support only width or width and height 11 | - Set preferred image orientation when searching 12 | - Insert image to frontmatter with customized key and value format 13 | 14 | # Support Image Sources 15 | 16 | - [Unsplash](https://unsplash.com) Unsplash provides over 3 million free high-resolution images. 17 | - [Pixabay](https://pixabay.com) Over 2.8 million+ high quality stock images, videos and music shared by our talented community. 18 | - [Pexels](https://pexels.com) The best free stock photos, royalty free images & videos shared by creators. 19 | 20 | # Usage 21 | 22 | ### Insert image to current place 23 | 24 | [demo.webm](https://user-images.githubusercontent.com/5436425/194984473-506249c2-b3ed-4c3d-835b-494f43c7684a.webm) 25 | 26 | When editing a note and want to insert an image from Unsplash: 27 | 1. Open command palette (⌘+P / Ctrl+P). 28 | 2. Search the command "Image Inserter: Insert Image". 29 | 3. Click on the command, then a modal opened. 30 | 4. Input some words describe image you'd like to insert, e.g. robot. 31 | 5. Wait a second, there will show several images from Unsplash / Pixabay / Pexels. 32 | 6. Click an image you'd like to insert. 33 | 7. The image should be inserted into your note now. 34 | 35 | ### Insert image to frontmatter 36 | 37 | About [front matter](https://help.obsidian.md/Advanced+topics/YAML+front+matter). 38 | Some plugins or markdown driven site generators read from front matter as metadata and maybe extract the header image or cover image from the metadata. 39 | Our insert image to frontmatter feature should be useful in those cases. 40 | 41 | If you want to insert an image to frontmatter: 42 | 1. Set the frontmatter key and value format in the plugin setting tab. 43 | 2. Open a markdown file you want to edit 44 | 3. Open command palette (⌘+P / Ctrl+P). 45 | 4. Search the command "Image Inserter: Insert Image in Frontmatter" and click. 46 | 5. Search and click an image you want to insert in the opened modal. 47 | 6. The image URL should be inserted into your file's frontmatter with the set key and value format. 48 | 49 | # Tip 50 | 51 | You can also set a hotkey for the "Image Inserter: Insert Image" and "Image Inserter: Insert Image in Frontmatter" command which allows you activate the command without open command palette and search it. [Custom hotkeys](https://help.obsidian.md/Customization/Custom+hotkeys) 52 | 53 | ## Key Bindings 54 | 55 | In the image searching modal, there's several convience shortcut key bindings. 56 | `Ctrl + n` -> select next image 57 | `Ctrl + p` -> select previous image 58 | `Ctrl + u` -> switch image provider 59 | `Ctrl + i` -> switch image quality 60 | 61 | # Installation 62 | 63 | ### From the Community Plugins list (Recommend) 64 | 65 | Search for "Image Inserter" in Obsidian's community plugins browser. 66 | Enable the plugin in your Obsidian settings (find "Image Inserter" under "Community plugins"). 67 | 68 | ### Manually installing the plugin 69 | 70 | Copy over main.js, styles.css, manifest.json to your vault VaultFolder/.obsidian/plugins/insert-unsplash-image/. 71 | 72 | # Notes 73 | 74 | ### Unsplash API Proxy 75 | 76 | Between your editor and Unsplash API, there is an HTTP proxy that runs on my own server. This means all your search input will send to my own server first. Please don't use this plugin if you can't accept it. 77 | The proxy server is necessary because Unsplash API requires a client ID when fetching data from it and Unsplash requires the developer to keep it secret. 78 | 79 | #### Self host proxy 80 | 81 | If you want to host the proxy server by yourself, please refer to the [proxy repo](https://github.com/cloudy9101/obsidian-image-inserter-proxy). 82 | After setup the proxy server, you can set the proxy server address on the plugin's settings tab. 83 | 84 | ### Frontmatter value format 85 | 86 | You can set a customized value format for inserting in frontmatter. 87 | The default format will be "{image-url}". 88 | ``` 89 | # if the key is "image" and the value format is "{image-url}", it will be 90 | --- 91 | image: "https://some-random-image-url.com" 92 | --- 93 | 94 | # if the key is "banner" and the value format is "![[{image-url}]], it will be 95 | --- 96 | banner: "![[https://some-random-image-url.com]]" 97 | --- 98 | ``` 99 | 100 | # Development 101 | 102 | Clone the repository, run `npm install` to install the dependencies, and run `npm run dev` to compile the plugin and watch file changes. 103 | 104 | See https://github.com/obsidianmd/obsidian-api for Obsidian's API documentation. 105 | 106 | Issues and PRs welcome. 107 | 108 | # Thanks 109 | 110 | - [@javiavid](https://github.com/javiavid) 111 | - [@vovech](https://github.com/vovech) 112 | - [@engage-results](https://github.com/engage-results) 113 | - [@Bluemoondragon07](https://github.com/Bluemoondragon07) 114 | - [@mxro](https://github.com/mxro) 115 | - [@Explosion-Scratch](https://github.com/Explosion-Scratch) 116 | 117 | # License 118 | 119 | This plugin's code and documentation is released under the [MIT License](./LICENSE). 120 | -------------------------------------------------------------------------------- /SettingTab.ts: -------------------------------------------------------------------------------- 1 | import InsertUpsplashImagePlugin from "./main"; 2 | import { App, PluginSettingTab, Setting } from "obsidian"; 3 | import { imageSizesMapping, providerMapping } from "fetchers/constants"; 4 | 5 | export enum InsertMode { 6 | remote = "remote", 7 | local = "local", 8 | } 9 | 10 | export enum Orientation { 11 | notSpecified = "not_specified", 12 | landscape = "landscape", 13 | portrait = "portrait", 14 | squarish = "squarish", 15 | } 16 | 17 | export enum ImageProvider { 18 | unsplash = "unsplash", 19 | pixabay = "pixabay", 20 | pexels = "pexels", 21 | local = "local", 22 | } 23 | 24 | export enum ImageSize { 25 | raw = "raw", 26 | large = "large", 27 | medium = "medium", 28 | small = "small", 29 | } 30 | 31 | export interface PluginSettings { 32 | insertMode: InsertMode; 33 | orientation: Orientation; 34 | insertSize: string; 35 | imageSize: ImageSize; 36 | frontmatter: { 37 | key: string; 38 | valueFormat: string; 39 | appendReferral: boolean; 40 | }; 41 | imageProvider: ImageProvider; 42 | proxyServer: string; 43 | pixabayApiKey: string; 44 | insertBackLink: boolean; 45 | pexelsApiKey: string; 46 | useMarkdownLinks: boolean; 47 | insertReferral: boolean; 48 | } 49 | 50 | export const DEFAULT_SETTINGS = { 51 | insertMode: InsertMode.remote, 52 | orientation: Orientation.landscape, 53 | insertSize: "", 54 | imageSize: ImageSize.large, 55 | frontmatter: { 56 | key: "image", 57 | valueFormat: "{image-url}", 58 | appendReferral: false, 59 | }, 60 | imageProvider: ImageProvider.unsplash, 61 | proxyServer: "", 62 | pixabayApiKey: "", 63 | insertBackLink: false, 64 | pexelsApiKey: "", 65 | insertReferral: true, 66 | }; 67 | 68 | export class SettingTab extends PluginSettingTab { 69 | plugin: InsertUpsplashImagePlugin; 70 | 71 | constructor(app: App, plugin: InsertUpsplashImagePlugin) { 72 | super(app, plugin); 73 | this.plugin = plugin; 74 | } 75 | 76 | display(): void { 77 | const { containerEl } = this; 78 | 79 | containerEl.empty(); 80 | 81 | new Setting(containerEl) 82 | .setName("Insert Mode") 83 | .setDesc( 84 | "Should the image be insert remotely (with Unsplash url) or locally (download into attachments folder).", 85 | ) 86 | .addDropdown((dropdown) => 87 | dropdown 88 | .addOption(InsertMode.remote, "Remote") 89 | .addOption(InsertMode.local, "Local") 90 | .setValue(this.plugin.settings.insertMode) 91 | .onChange(async (value) => { 92 | if (value === InsertMode.remote || value === InsertMode.local) { 93 | this.plugin.settings.insertMode = value; 94 | await this.plugin.saveSettings(); 95 | } 96 | }), 97 | ); 98 | 99 | new Setting(containerEl) 100 | .setName("Orientation") 101 | .setDesc("Search images with the specified orientation.") 102 | .addDropdown((dropdown) => 103 | dropdown 104 | .addOption(Orientation.notSpecified, "Not Specified") 105 | .addOption(Orientation.landscape, "Landscape") 106 | .addOption(Orientation.portrait, "Portrait") 107 | .addOption(Orientation.squarish, "Squarish") 108 | .setValue(this.plugin.settings.orientation) 109 | .onChange(async (value) => { 110 | if ( 111 | value === Orientation.notSpecified || 112 | value === Orientation.landscape || 113 | value === Orientation.portrait || 114 | value === Orientation.squarish 115 | ) { 116 | this.plugin.settings.orientation = value; 117 | await this.plugin.saveSettings(); 118 | } 119 | }), 120 | ); 121 | 122 | new Setting(containerEl) 123 | .setName("Insert Size") 124 | .setDesc( 125 | 'Set the size of the image when inserting. Format could be only the width "200" or the width and height "200x100".', 126 | ) 127 | .addText((text) => { 128 | text 129 | .setValue(this.plugin.settings.insertSize) 130 | .onChange(async (value) => { 131 | this.plugin.settings.insertSize = value; 132 | await this.plugin.saveSettings(); 133 | }); 134 | }); 135 | 136 | new Setting(containerEl) 137 | .setName("Default Image Size") 138 | .setDesc("Set the default preferred image size from image providers.") 139 | .addDropdown((dropdown) => { 140 | dropdown 141 | .addOptions({ 142 | [ImageSize.raw]: imageSizesMapping[ImageSize.raw], 143 | [ImageSize.large]: imageSizesMapping[ImageSize.large], 144 | [ImageSize.medium]: imageSizesMapping[ImageSize.medium], 145 | [ImageSize.small]: imageSizesMapping[ImageSize.small], 146 | }) 147 | .setValue(this.plugin.settings.imageSize) 148 | .onChange(async (value: ImageSize) => { 149 | this.plugin.settings.imageSize = value; 150 | await this.plugin.saveSettings(); 151 | }); 152 | }); 153 | 154 | new Setting(containerEl) 155 | .setName("Insert referral") 156 | .setDesc("Insert the reference text, eg. Photo by ...") 157 | .addToggle((toggle) => { 158 | toggle 159 | .setValue(this.plugin.settings.insertReferral) 160 | .onChange(async (value: boolean) => { 161 | this.plugin.settings.insertReferral = value; 162 | await this.plugin.saveSettings(); 163 | }); 164 | }); 165 | 166 | new Setting(containerEl) 167 | .setName("Insert backlink") 168 | .setDesc( 169 | "Insert a backlink (image HTML location on Provider website) in front of the reference text, eg. Backlink | Photo by ...", 170 | ) 171 | .addToggle((toggle) => { 172 | toggle 173 | .setValue(this.plugin.settings.insertBackLink) 174 | .onChange(async (value: boolean) => { 175 | this.plugin.settings.insertBackLink = value; 176 | await this.plugin.saveSettings(); 177 | }); 178 | }); 179 | 180 | containerEl.createEl("h1", { text: "Frontmatter" }); 181 | new Setting(containerEl) 182 | .setName("Insert to Frontmatter Key") 183 | .setDesc("The key used when insert to frontmatter.") 184 | .addText((text) => { 185 | text 186 | .setPlaceholder("image") 187 | .setValue(this.plugin.settings.frontmatter.key) 188 | .onChange(async (value) => { 189 | this.plugin.settings.frontmatter.key = value; 190 | await this.plugin.saveSettings(); 191 | }); 192 | }); 193 | 194 | new Setting(containerEl) 195 | .setName("Insert to Frontmatter Value Format") 196 | .setDesc( 197 | "The value format used when insert to frontmatter. {image-url} will be replaced by the image url. Please visit https://github.com/cloudy9101/obsidian-image-inserter#frontmatter-value-format for more details.", 198 | ) 199 | .addText((text) => { 200 | text 201 | .setPlaceholder("{image-url}") 202 | .setValue(this.plugin.settings.frontmatter.valueFormat) 203 | .onChange(async (value) => { 204 | this.plugin.settings.frontmatter.valueFormat = value; 205 | await this.plugin.saveSettings(); 206 | }); 207 | }); 208 | 209 | new Setting(containerEl) 210 | .setName("Append image referral at end of the file.") 211 | .setDesc("Will append image referral at end of the file if set to true") 212 | .addToggle((toggle) => { 213 | toggle 214 | .setValue(this.plugin.settings.frontmatter.appendReferral) 215 | .onChange(async (value) => { 216 | this.plugin.settings.frontmatter.appendReferral = value; 217 | await this.plugin.saveSettings(); 218 | }); 219 | }); 220 | 221 | containerEl.createEl("h1", { text: "Image Provider" }); 222 | new Setting(containerEl) 223 | .setName("Default Provider") 224 | .addDropdown((dropdown) => { 225 | dropdown 226 | .addOptions({ 227 | [ImageProvider.unsplash]: providerMapping[ImageProvider.unsplash], 228 | [ImageProvider.pixabay]: providerMapping[ImageProvider.pixabay], 229 | [ImageProvider.pexels]: providerMapping[ImageProvider.pexels], 230 | [ImageProvider.local]: providerMapping[ImageProvider.local], 231 | }) 232 | .setValue(this.plugin.settings.imageProvider) 233 | .onChange(async (value: ImageProvider) => { 234 | this.plugin.settings.imageProvider = value; 235 | await this.plugin.saveSettings(); 236 | }); 237 | }); 238 | 239 | new Setting(containerEl) 240 | .setName("Unsplash Proxy Server") 241 | .setDesc( 242 | "Use a self host proxy server. Leave it empty if you don't want host proxy server by yourself.", 243 | ) 244 | .addText((text) => { 245 | text 246 | .setPlaceholder("https://self-host-proxy.com/") 247 | .setValue(this.plugin.settings.proxyServer) 248 | .onChange(async (value) => { 249 | this.plugin.settings.proxyServer = value; 250 | await this.plugin.saveSettings(); 251 | }); 252 | }); 253 | 254 | new Setting(containerEl) 255 | .setName("Pixabay API key") 256 | .setDesc( 257 | "API key can be found on https://pixabay.com/api/docs/ after logging in.", 258 | ) 259 | .addText((text) => { 260 | text 261 | .setPlaceholder("Your API key") 262 | .setValue(this.plugin.settings.pixabayApiKey) 263 | .onChange(async (value) => { 264 | this.plugin.settings.pixabayApiKey = value; 265 | await this.plugin.saveSettings(); 266 | }); 267 | }); 268 | 269 | new Setting(containerEl) 270 | .setName("Pexels API key") 271 | .setDesc( 272 | "API key can be created on https://www.pexels.com/api/new/ after logging in.", 273 | ) 274 | .addText((text) => { 275 | text 276 | .setPlaceholder("Your API key") 277 | .setValue(this.plugin.settings.pexelsApiKey) 278 | .onChange(async (value) => { 279 | this.plugin.settings.pexelsApiKey = value; 280 | await this.plugin.saveSettings(); 281 | }); 282 | }); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /demo.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudy9101/obsidian-image-inserter/e3cf985c49b34b9ff2f52eec706f976e433d9af5/demo.webm -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild"; 2 | import process from "process"; 3 | import builtins from 'builtin-modules' 4 | 5 | const banner = 6 | `/* 7 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 8 | if you want to view the source, please visit the github repository of this plugin 9 | */ 10 | `; 11 | 12 | const prod = (process.argv[2] === 'production'); 13 | 14 | esbuild.build({ 15 | banner: { 16 | js: banner, 17 | }, 18 | entryPoints: ['main.ts'], 19 | bundle: true, 20 | external: [ 21 | 'obsidian', 22 | 'electron', 23 | '@codemirror/autocomplete', 24 | '@codemirror/collab', 25 | '@codemirror/commands', 26 | '@codemirror/language', 27 | '@codemirror/lint', 28 | '@codemirror/search', 29 | '@codemirror/state', 30 | '@codemirror/view', 31 | '@lezer/common', 32 | '@lezer/highlight', 33 | '@lezer/lr', 34 | ...builtins], 35 | format: 'cjs', 36 | watch: !prod, 37 | target: 'es2018', 38 | logLevel: "info", 39 | sourcemap: prod ? false : 'inline', 40 | treeShaking: true, 41 | outfile: 'main.js', 42 | }).catch(() => process.exit(1)); 43 | -------------------------------------------------------------------------------- /fetchers/constants.ts: -------------------------------------------------------------------------------- 1 | import { Vault } from "obsidian"; 2 | import { ImageProvider, ImageSize } from "SettingTab"; 3 | 4 | export const APP_NAME = encodeURIComponent("Obsidian Image Inserter Plugin"); 5 | export const UTM = `utm_source=${APP_NAME}&utm_medium=referral`; 6 | export const PER_PAGE = "30"; 7 | 8 | export const providerMapping = { 9 | [ImageProvider.unsplash]: "Unsplash", 10 | [ImageProvider.pixabay]: "Pixabay", 11 | [ImageProvider.pexels]: "Pexels", 12 | [ImageProvider.local]: "Local", 13 | }; 14 | 15 | export const imageProviders = [ 16 | ImageProvider.unsplash, 17 | ImageProvider.pixabay, 18 | ImageProvider.pexels, 19 | ImageProvider.local, 20 | ]; 21 | 22 | export const imageSizes = [ 23 | ImageSize.raw, 24 | ImageSize.large, 25 | ImageSize.medium, 26 | ImageSize.small, 27 | ]; 28 | 29 | export const imageSizesMapping = { 30 | [ImageSize.raw]: "Raw", 31 | [ImageSize.large]: "Large", 32 | [ImageSize.medium]: "Medium", 33 | [ImageSize.small]: "Small", 34 | }; 35 | 36 | export interface Image { 37 | desc?: string; 38 | thumb: string; 39 | url: string; 40 | downloadLocationUrl?: string; 41 | pageUrl: string; 42 | username: string; 43 | userUrl: string; 44 | } 45 | 46 | export interface Fetcher { 47 | vault: Vault; 48 | imageProvider: ImageProvider; 49 | imageSize: ImageSize; 50 | noResult: () => boolean; 51 | hasPrevPage: () => boolean; 52 | hasNextPage: () => boolean; 53 | prevPage: () => void; 54 | nextPage: () => void; 55 | searchImages: (query: string) => Promise; 56 | touchDownloadLocation?: (url: string) => Promise; 57 | downloadImage: (url: string) => Promise; 58 | downloadAndInsertImage: ( 59 | image: Image, 60 | createFile: (name: string, ext: string, binary: ArrayBuffer) => void, 61 | ) => Promise; 62 | downloadAndGetUri: ( 63 | image: Image, 64 | createFile: (name: string, ext: string, binary: ArrayBuffer) => void, 65 | ) => Promise<{ url: string; referral: string }>; 66 | } 67 | 68 | export const IMAGE_EXTS = [ 69 | "avif", 70 | "bmp", 71 | "gif", 72 | "jpeg", 73 | "jpg", 74 | "png", 75 | "svg", 76 | "webp", 77 | ]; 78 | -------------------------------------------------------------------------------- /fetchers/index.ts: -------------------------------------------------------------------------------- 1 | import { pexels } from "./pexels"; 2 | import { pixabay } from "./pixabay"; 3 | import { unsplash } from "./unsplash"; 4 | import { ImageProvider, PluginSettings } from "SettingTab"; 5 | import { Fetcher } from "./constants"; 6 | import { Vault } from "obsidian"; 7 | import { local } from "./local"; 8 | 9 | export const getFetcher = (settings: PluginSettings, vault: Vault): Fetcher => { 10 | const { imageProvider } = settings; 11 | 12 | switch (imageProvider) { 13 | case ImageProvider.local: 14 | return local(settings, vault); 15 | case ImageProvider.pexels: 16 | return pexels(settings, vault); 17 | case ImageProvider.pixabay: 18 | return pixabay(settings, vault); 19 | default: 20 | return unsplash(settings, vault); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /fetchers/local.ts: -------------------------------------------------------------------------------- 1 | import { Vault } from "obsidian"; 2 | import { PluginSettings } from "SettingTab"; 3 | import { PER_PAGE, Image, IMAGE_EXTS } from "./constants"; 4 | 5 | export const local = (settings: PluginSettings, vault: Vault) => { 6 | const startPage = 1; 7 | let curPage = startPage; 8 | let totalPage = 0; 9 | 10 | const { insertSize, imageSize, imageProvider } = settings; 11 | 12 | return { 13 | vault, 14 | imageProvider, 15 | imageSize, 16 | noResult() { 17 | return totalPage <= 0; 18 | }, 19 | hasPrevPage() { 20 | return !this.noResult() && curPage > startPage; 21 | }, 22 | hasNextPage() { 23 | return !this.noResult() && curPage < totalPage; 24 | }, 25 | prevPage() { 26 | this.hasPrevPage() && (curPage -= 1); 27 | }, 28 | nextPage() { 29 | this.hasNextPage() && (curPage += 1); 30 | }, 31 | async searchImages(query: string): Promise { 32 | const images = vault 33 | .getFiles() 34 | .filter( 35 | (file) => 36 | IMAGE_EXTS.includes(file.extension) && 37 | file.name.toLowerCase().includes(query.toLowerCase()), 38 | ); 39 | const perPage = parseInt(PER_PAGE); 40 | totalPage = Math.ceil(images.length / perPage) + 1; 41 | return images 42 | .slice((curPage - 1) * perPage, curPage * perPage) 43 | .map(function (item) { 44 | return { 45 | desc: item.name.replace(new RegExp(/\n/g), " "), 46 | thumb: vault.getResourcePath(item), 47 | url: vault.getResourcePath(item), 48 | pageUrl: "Local", 49 | username: "Local", 50 | userUrl: "Local", 51 | }; 52 | }); 53 | }, 54 | async downloadImage(_: string): Promise { 55 | return new ArrayBuffer(0); 56 | }, 57 | async downloadAndInsertImage( 58 | image: Image, 59 | _: (name: string, ext: string, binary: ArrayBuffer) => void, 60 | ): Promise { 61 | const url = image.url; 62 | 63 | const imageSize = insertSize === "" ? "" : `|${insertSize}`; 64 | const nameText = `![${image.desc}${imageSize}]`; 65 | const urlText = `(${url})`; 66 | 67 | return `${nameText}${urlText}`; 68 | }, 69 | async downloadAndGetUri( 70 | image: Image, 71 | _: (name: string, ext: string, binary: ArrayBuffer) => void, 72 | ): Promise<{ url: string; referral: string }> { 73 | const url = image.url; 74 | 75 | return { url, referral: "" }; 76 | }, 77 | }; 78 | }; 79 | -------------------------------------------------------------------------------- /fetchers/pexels.ts: -------------------------------------------------------------------------------- 1 | import { requestUrl, moment, Vault } from "obsidian"; 2 | import { ImageSize, InsertMode, Orientation, PluginSettings } from "SettingTab"; 3 | import { randomImgName } from "utils"; 4 | import { PER_PAGE, Image } from "./constants"; 5 | 6 | const orientationMapping = { 7 | [Orientation.landscape]: "landscape", 8 | [Orientation.portrait]: "portrait", 9 | [Orientation.squarish]: "square", 10 | [Orientation.notSpecified]: "", 11 | }; 12 | 13 | const imageSizeMapping: Record = { 14 | [ImageSize.raw]: "original", 15 | [ImageSize.large]: "large", 16 | [ImageSize.medium]: "medium", 17 | [ImageSize.small]: "small", 18 | }; 19 | 20 | export const pexels = (settings: PluginSettings, vault: Vault) => { 21 | const startPage = 1; 22 | let curPage = startPage; 23 | let totalPage = 0; 24 | 25 | const { 26 | orientation, 27 | insertMode, 28 | insertSize, 29 | imageSize, 30 | imageProvider, 31 | pexelsApiKey, 32 | useMarkdownLinks, 33 | } = settings; 34 | 35 | return { 36 | vault, 37 | imageProvider, 38 | imageSize, 39 | noResult() { 40 | return totalPage <= 0; 41 | }, 42 | hasPrevPage() { 43 | return !this.noResult() && curPage > startPage; 44 | }, 45 | hasNextPage() { 46 | return !this.noResult() && curPage < totalPage; 47 | }, 48 | prevPage() { 49 | this.hasPrevPage() && (curPage -= 1); 50 | }, 51 | nextPage() { 52 | this.hasNextPage() && (curPage += 1); 53 | }, 54 | async searchImages(query: string): Promise { 55 | if (!query || query === "") { 56 | return []; 57 | } 58 | 59 | const url = new URL("https://api.pexels.com/v1/search"); 60 | url.searchParams.set("query", query); 61 | if (orientation != "not_specified") { 62 | url.searchParams.set("orientation", orientationMapping[orientation]); 63 | } 64 | url.searchParams.set("page", `${curPage}`); 65 | url.searchParams.set("per_page", PER_PAGE); 66 | const res = await requestUrl({ 67 | url: url.toString(), 68 | headers: { Authorization: pexelsApiKey }, 69 | }); 70 | const data: Pexels.RootObject = res.json; 71 | totalPage = data.total_results / parseInt(PER_PAGE); 72 | return data.photos.map(function (item) { 73 | return { 74 | thumb: item.src.small, 75 | url: item.src[imageSizeMapping[imageSize]], 76 | pageUrl: item.url, 77 | userUrl: item.photographer_url, 78 | username: item.photographer, 79 | }; 80 | }); 81 | }, 82 | async downloadImage(url: string): Promise { 83 | const res = await requestUrl({ url }); 84 | return res.arrayBuffer; 85 | }, 86 | async downloadAndInsertImage( 87 | image: Image, 88 | createFile: (name: string, ext: string, binary: ArrayBuffer) => void, 89 | ): Promise { 90 | const url = image.url; 91 | 92 | const imageSize = insertSize === "" ? "" : `|${insertSize}`; 93 | let nameText = `![${randomImgName()}${imageSize}]`; 94 | let urlText = `(${url})`; 95 | const backlink = 96 | settings.insertBackLink && image.pageUrl 97 | ? `[Backlink](${image.pageUrl}) | ` 98 | : ""; 99 | const referral = settings.insertReferral 100 | ? `\n*${backlink}Photo by [${image.username}](${image.userUrl}) on [Pexels](https://pexels.com/)*\n` 101 | : ""; 102 | 103 | if (insertMode === InsertMode.local) { 104 | const imageName = `Inserted image ${moment().format("YYYYMMDDHHmmss")}`; 105 | const ext = "png"; 106 | const arrayBuf = await this.downloadImage(url); 107 | createFile(imageName, ext, arrayBuf); 108 | nameText = useMarkdownLinks 109 | ? `![${insertSize}](${encodeURIComponent(imageName)}.${ext})` 110 | : `![[${imageName}.${ext}${imageSize}]]`; 111 | urlText = ""; 112 | } 113 | 114 | return `${nameText}${urlText}${referral}`; 115 | }, 116 | async downloadAndGetUri( 117 | image: Image, 118 | createFile: (name: string, ext: string, binary: ArrayBuffer) => void, 119 | ): Promise<{ url: string; referral: string }> { 120 | const url = image.url; 121 | const backlink = 122 | settings.insertBackLink && image.pageUrl 123 | ? `[Backlink](${image.pageUrl}) | ` 124 | : ""; 125 | const referral = settings.insertReferral 126 | ? `\n*${backlink}Photo by [${image.username}](${image.userUrl}) on [Pixabay](https://pixabay.com/)*\n` 127 | : ""; 128 | 129 | if (insertMode === InsertMode.local) { 130 | const imageName = `Inserted image ${moment().format("YYYYMMDDHHmmss")}`; 131 | const ext = "png"; 132 | const arrayBuf = await this.downloadImage(url); 133 | createFile(imageName, ext, arrayBuf); 134 | return { url: `${imageName}.${ext}`, referral }; 135 | } 136 | 137 | return { url, referral }; 138 | }, 139 | }; 140 | }; 141 | -------------------------------------------------------------------------------- /fetchers/pixabay.ts: -------------------------------------------------------------------------------- 1 | import { moment, requestUrl, Vault } from "obsidian"; 2 | import { ImageSize, InsertMode, Orientation, PluginSettings } from "SettingTab"; 3 | import { randomImgName } from "utils"; 4 | import { PER_PAGE, Image } from "./constants"; 5 | 6 | const orientationMapping = { 7 | [Orientation.landscape]: "horizontal", 8 | [Orientation.portrait]: "vertical", 9 | [Orientation.squarish]: "", 10 | [Orientation.notSpecified]: "", 11 | }; 12 | 13 | const getImageUrl = (item: Pixabay.Hit, quality: ImageSize) => { 14 | switch (quality) { 15 | case ImageSize.raw: 16 | return item.imageURL || item.fullHDURL || item.largeImageURL; 17 | case ImageSize.large: 18 | return item.fullHDURL || item.largeImageURL; 19 | case ImageSize.medium: 20 | return item.webformatURL; 21 | case ImageSize.small: 22 | return item.previewURL; 23 | default: 24 | return item.webformatURL; 25 | } 26 | }; 27 | 28 | export const pixabay = (settings: PluginSettings, vault: Vault) => { 29 | const startPage = 1; 30 | let curPage = startPage; 31 | let totalPage = 0; 32 | 33 | const { 34 | orientation, 35 | insertMode, 36 | insertSize, 37 | imageSize, 38 | imageProvider, 39 | pixabayApiKey, 40 | useMarkdownLinks, 41 | } = settings; 42 | 43 | return { 44 | vault, 45 | imageProvider, 46 | imageSize, 47 | noResult() { 48 | return totalPage <= 0; 49 | }, 50 | hasPrevPage() { 51 | return !this.noResult() && curPage > startPage; 52 | }, 53 | hasNextPage() { 54 | return !this.noResult() && curPage < totalPage; 55 | }, 56 | prevPage() { 57 | this.hasPrevPage() && (curPage -= 1); 58 | }, 59 | nextPage() { 60 | this.hasNextPage() && (curPage += 1); 61 | }, 62 | async searchImages(query: string): Promise { 63 | if (!query || query === "") { 64 | return []; 65 | } 66 | 67 | const url = new URL("https://pixabay.com/api/"); 68 | url.searchParams.set("key", pixabayApiKey); 69 | url.searchParams.set("q", query); 70 | if (orientation != "not_specified") { 71 | url.searchParams.set("orientation", orientationMapping[orientation]); 72 | } 73 | url.searchParams.set("page", `${curPage}`); 74 | url.searchParams.set("per_page", PER_PAGE); 75 | const res = await requestUrl({ url: url.toString() }); 76 | const data: Pixabay.RootObject = res.json; 77 | totalPage = data.total / parseInt(PER_PAGE); 78 | return data.hits.map(function (item) { 79 | return { 80 | thumb: item.previewURL, 81 | url: getImageUrl(item, imageSize), 82 | pageUrl: item.pageURL, 83 | userUrl: `https://pixabay.com/users/${item.user}-${item.user_id}`, 84 | username: item.user, 85 | }; 86 | }); 87 | }, 88 | async downloadImage(url: string): Promise { 89 | const res = await requestUrl({ url }); 90 | return res.arrayBuffer; 91 | }, 92 | async downloadAndInsertImage( 93 | image: Image, 94 | createFile: (name: string, ext: string, binary: ArrayBuffer) => void, 95 | ): Promise { 96 | const url = image.url; 97 | 98 | const imageSize = insertSize === "" ? "" : `|${insertSize}`; 99 | let nameText = `![${randomImgName()}${imageSize}]`; 100 | let urlText = `(${url})`; 101 | const backlink = 102 | settings.insertBackLink && image.pageUrl 103 | ? `[Backlink](${image.pageUrl}) | ` 104 | : ""; 105 | const referral = settings.insertReferral 106 | ? `\n*${backlink}Photo by [${image.username}](${image.userUrl}) on [Pixabay](https://pixabay.com/)*\n` 107 | : ""; 108 | 109 | if (insertMode === InsertMode.local) { 110 | const imageName = `Inserted image ${moment().format("YYYYMMDDHHmmss")}`; 111 | const ext = "png"; 112 | const arrayBuf = await this.downloadImage(url); 113 | createFile(imageName, ext, arrayBuf); 114 | nameText = useMarkdownLinks 115 | ? `![${insertSize}](${encodeURIComponent(imageName)}.${ext})` 116 | : `![[${imageName}.${ext}${imageSize}]]`; 117 | urlText = ""; 118 | } 119 | 120 | return `${nameText}${urlText}${referral}`; 121 | }, 122 | async downloadAndGetUri( 123 | image: Image, 124 | createFile: (name: string, ext: string, binary: ArrayBuffer) => void, 125 | ): Promise<{ url: string; referral: string }> { 126 | const url = image.url; 127 | const backlink = 128 | settings.insertBackLink && image.pageUrl 129 | ? `[Backlink](${image.pageUrl}) | ` 130 | : ""; 131 | const referral = settings.insertReferral 132 | ? `\n*${backlink}Photo by [${image.username}](${image.userUrl}) on [Pixabay](https://pixabay.com/)*\n` 133 | : ""; 134 | 135 | if (insertMode === InsertMode.local) { 136 | const imageName = `Inserted image ${moment().format("YYYYMMDDHHmmss")}`; 137 | const ext = "png"; 138 | const arrayBuf = await this.downloadImage(url); 139 | createFile(imageName, ext, arrayBuf); 140 | return { url: `${imageName}.${ext}`, referral }; 141 | } 142 | 143 | return { url, referral }; 144 | }, 145 | }; 146 | }; 147 | -------------------------------------------------------------------------------- /fetchers/unsplash.ts: -------------------------------------------------------------------------------- 1 | import { requestUrl, moment, Vault } from "obsidian"; 2 | import { ImageSize, InsertMode, PluginSettings } from "SettingTab"; 3 | import { randomImgName, validUrl } from "utils"; 4 | import { PER_PAGE, Image } from "./constants"; 5 | 6 | const DEFAULT_PROXY_SERVER = "https://insert-unsplash-image.cloudy9101.com/"; 7 | const APP_NAME = encodeURIComponent("Obsidian Image Inserter Plugin"); 8 | const UTM = `utm_source=${APP_NAME}&utm_medium=referral`; 9 | 10 | const imageSizeMapping: Record = { 11 | [ImageSize.raw]: "raw", 12 | [ImageSize.large]: "regular", 13 | [ImageSize.medium]: "small", 14 | [ImageSize.small]: "thumb", 15 | }; 16 | 17 | export const unsplash = (settings: PluginSettings, vault: Vault) => { 18 | const startPage = 1; 19 | let curPage = startPage; 20 | let totalPage = 0; 21 | 22 | const { 23 | orientation, 24 | insertMode, 25 | insertSize, 26 | imageSize, 27 | imageProvider, 28 | useMarkdownLinks, 29 | } = settings; 30 | 31 | let proxyServer = DEFAULT_PROXY_SERVER; 32 | if (validUrl(settings.proxyServer)) { 33 | proxyServer = settings.proxyServer; 34 | } 35 | 36 | return { 37 | vault, 38 | imageProvider, 39 | imageSize, 40 | noResult() { 41 | return totalPage <= 0; 42 | }, 43 | hasPrevPage() { 44 | return !this.noResult() && curPage > startPage; 45 | }, 46 | hasNextPage() { 47 | return !this.noResult() && curPage < totalPage; 48 | }, 49 | prevPage() { 50 | this.hasPrevPage() && (curPage -= 1); 51 | }, 52 | nextPage() { 53 | this.hasNextPage() && (curPage += 1); 54 | }, 55 | async searchImages(query: string): Promise { 56 | if (!query || query === "") { 57 | return []; 58 | } 59 | 60 | const url = new URL("/search/photos", proxyServer); 61 | url.searchParams.set("query", query); 62 | if (orientation != "not_specified") { 63 | url.searchParams.set("orientation", orientation); 64 | } 65 | url.searchParams.set("page", `${curPage}`); 66 | url.searchParams.set("per_page", PER_PAGE); 67 | const res = await requestUrl({ url: url.toString() }); 68 | const data: Unsplash.RootObject = res.json; 69 | totalPage = data.total_pages; 70 | return data.results.map(function (item) { 71 | return { 72 | desc: (item.description || item.alt_description || "").replace( 73 | new RegExp(/\n/g), 74 | " ", 75 | ), 76 | thumb: item.urls.thumb, 77 | url: item.urls[imageSizeMapping[imageSize]], 78 | downloadLocationUrl: item.links.download_location, 79 | pageUrl: item.links.html, 80 | username: item.user.name, 81 | userUrl: `https://unsplash.com/@${item.user.username}?${UTM}`, 82 | }; 83 | }); 84 | }, 85 | async touchDownloadLocation(url: string): Promise { 86 | await requestUrl({ 87 | url: url.replace("https://api.unsplash.com", proxyServer), 88 | }); 89 | }, 90 | async downloadImage(url: string): Promise { 91 | const res = await requestUrl({ url }); 92 | return res.arrayBuffer; 93 | }, 94 | async downloadAndInsertImage( 95 | image: Image, 96 | createFile: (name: string, ext: string, binary: ArrayBuffer) => void, 97 | ): Promise { 98 | this.touchDownloadLocation(image.downloadLocationUrl); 99 | const url = image.url; 100 | 101 | const imageSize = insertSize === "" ? "" : `|${insertSize}`; 102 | let nameText = `![${image.desc || randomImgName()}${imageSize}]`; 103 | let urlText = `(${url})`; 104 | const backlink = 105 | settings.insertBackLink && image.pageUrl 106 | ? `[Backlink](${image.pageUrl}) | ` 107 | : ""; 108 | const referral = settings.insertReferral 109 | ? `\n*${backlink}Photo by [${image.username}](${image.userUrl}) on [Unsplash](https://unsplash.com/?${UTM})*\n` 110 | : ""; 111 | 112 | if (insertMode === InsertMode.local) { 113 | const imageName = `Inserted image ${moment().format("YYYYMMDDHHmmss")}`; 114 | const ext = "png"; 115 | const arrayBuf = await this.downloadImage(url); 116 | createFile(imageName, ext, arrayBuf); 117 | nameText = useMarkdownLinks 118 | ? `![${insertSize}](${encodeURIComponent(imageName)}.${ext})` 119 | : `![[${imageName}.${ext}${imageSize}]]`; 120 | urlText = ""; 121 | } 122 | 123 | return `${nameText}${urlText}${referral}`; 124 | }, 125 | async downloadAndGetUri( 126 | image: Image, 127 | createFile: (name: string, ext: string, binary: ArrayBuffer) => void, 128 | ): Promise<{ url: string; referral: string }> { 129 | this.touchDownloadLocation(image.downloadLocationUrl); 130 | const url = image.url; 131 | const backlink = 132 | settings.insertBackLink && image.pageUrl 133 | ? `[Backlink](${image.pageUrl}) | ` 134 | : ""; 135 | const referral = settings.insertReferral 136 | ? `\n*${backlink}Photo by [${image.username}](${image.userUrl}) on [Unsplash](https://unsplash.com/?${UTM})*\n` 137 | : ""; 138 | 139 | if (insertMode === InsertMode.local) { 140 | const imageName = `Inserted image ${moment().format("YYYYMMDDHHmmss")}`; 141 | const ext = "png"; 142 | const arrayBuf = await this.downloadImage(url); 143 | createFile(imageName, ext, arrayBuf); 144 | return { url: `${imageName}.${ext}`, referral }; 145 | } 146 | 147 | return { url, referral }; 148 | }, 149 | }; 150 | }; 151 | -------------------------------------------------------------------------------- /interface.d.ts: -------------------------------------------------------------------------------- 1 | import { TFile } from "obsidian"; 2 | 3 | declare module 'obsidian' { 4 | interface Vault { 5 | getAvailablePathForAttachments: ( 6 | fileName: string, 7 | extension?: string, 8 | currentFile?: TFile 9 | ) => Promise; 10 | config: { 11 | attachmentFolderPath: string; 12 | useMarkdownLinks: boolean; 13 | }; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | import { Editor, Plugin } from 'obsidian'; 2 | 3 | import { SettingTab, PluginSettings, DEFAULT_SETTINGS } from './SettingTab'; 4 | import { InsertPlace, ModalWrapper } from './ModalWrapper'; 5 | 6 | export default class InsertUnsplashImage extends Plugin { 7 | settings: PluginSettings; 8 | 9 | async onload() { 10 | this.loadSettings() 11 | this.addSettingTab(new SettingTab(this.app, this)); 12 | 13 | this.addCommand({ 14 | id: 'insert', 15 | name: 'Insert Image', 16 | editorCallback: (editor: Editor) => { 17 | new ModalWrapper(this.app, editor, this.settings).open(); 18 | } 19 | }); 20 | 21 | this.addCommand({ 22 | id: 'insert-in-frontmatter', 23 | name: 'Insert Image in Frontmatter', 24 | editorCallback: (editor: Editor) => { 25 | new ModalWrapper(this.app, editor, this.settings, InsertPlace.frontmatter).open(); 26 | } 27 | }); 28 | } 29 | 30 | onunload() {} 31 | 32 | async loadSettings() { 33 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 34 | } 35 | 36 | async saveSettings() { 37 | await this.saveData(this.settings); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "insert-unsplash-image", 3 | "name": "Image Inserter", 4 | "version": "0.8.3", 5 | "minAppVersion": "0.15.0", 6 | "description": "This plugin helps users easily search and insert images to editors from Unsplash / Pixabay / Pexels.", 7 | "author": "Ray Hao", 8 | "authorUrl": "https://github.com/cloudy9101", 9 | "isDesktopOnly": false 10 | } -------------------------------------------------------------------------------- /meta-updater.ts: -------------------------------------------------------------------------------- 1 | import { App, TFile } from "obsidian"; 2 | 3 | export const upsert = async (app: App, file: TFile, key: string, value: string) => { 4 | const content = await app.vault.read(file) 5 | if (!content.startsWith("---")) { 6 | return ["---", `${key}: ${value}`, "---", content].join("\n") 7 | } 8 | 9 | const secondTripleDashPos = content.indexOf("---", 3) 10 | const frontmatter = content.substring(0, secondTripleDashPos + 3) 11 | const rest = content.substring(secondTripleDashPos + 3) 12 | 13 | let keyExists = false 14 | const updatedFrontmatterLines = frontmatter.split("\n").map((line) => { 15 | if (line.startsWith(`${key}:`)) { 16 | keyExists = true 17 | return `${key}: ${value}` 18 | } 19 | return line 20 | }) 21 | const lastLine = updatedFrontmatterLines.pop() 22 | if (!keyExists) { 23 | updatedFrontmatterLines.push(`${key}: ${value}`) 24 | } 25 | if (lastLine) { 26 | updatedFrontmatterLines.push(lastLine) 27 | } 28 | const updatedFrontmatter = updatedFrontmatterLines.join("\n") 29 | 30 | return [updatedFrontmatter, rest].join("\n") 31 | } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-sample-plugin", 3 | "version": "0.8.3", 4 | "description": "This is a sample plugin for Obsidian (https://obsidian.md)", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "node esbuild.config.mjs", 8 | "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", 9 | "version": "node version-bump.mjs && git add manifest.json versions.json" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "@types/node": "^16.11.6", 16 | "@types/react": "^18.0.21", 17 | "@types/react-dom": "^18.0.6", 18 | "@typescript-eslint/eslint-plugin": "5.29.0", 19 | "@typescript-eslint/parser": "5.29.0", 20 | "builtin-modules": "3.3.0", 21 | "esbuild": "0.14.47", 22 | "eslint": "^8.25.0", 23 | "obsidian": "latest", 24 | "tslib": "2.4.0", 25 | "typescript": "4.7.4" 26 | }, 27 | "dependencies": { 28 | "react": "^18.2.0", 29 | "react-dom": "^18.2.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /pexels.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace Pexels { 2 | export interface RootObject { 3 | total_results: number; 4 | page: number; 5 | per_page: number; 6 | photos: Photo[]; 7 | next_page: string; 8 | } 9 | 10 | export interface Photo { 11 | id: number; 12 | width: number; 13 | height: number; 14 | url: string; 15 | photographer: string; 16 | photographer_url: string; 17 | photographer_id: number; 18 | avg_color: string; 19 | src: Src; 20 | liked: boolean; 21 | alt: string; 22 | } 23 | 24 | export interface Src { 25 | original: string; 26 | large2x: string; 27 | large: string; 28 | medium: string; 29 | small: string; 30 | portrait: string; 31 | landscape: string; 32 | tiny: string; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pixabay-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /pixabay.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace Pixabay { 2 | export interface RootObject { 3 | total: number; 4 | totalHits: number; 5 | hits: Hit[]; 6 | } 7 | 8 | export interface Hit { 9 | id: number; 10 | pageURL: string; 11 | type: string; 12 | tags: string; 13 | previewURL: string; 14 | previewWidth: number; 15 | previewHeight: number; 16 | webformatURL: string; 17 | webformatWidth: number; 18 | webformatHeight: number; 19 | largeImageURL: string; 20 | fullHDURL: string; 21 | imageURL: string; 22 | imageWidth: number; 23 | imageHeight: number; 24 | imageSize: number; 25 | views: number; 26 | downloads: number; 27 | likes: number; 28 | comments: number; 29 | user_id: number; 30 | user: string; 31 | userImageURL: string; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This CSS file will be included with your plugin, and 4 | available in the app when your plugin is enabled. 5 | 6 | If your plugin does not need CSS, delete this file. 7 | 8 | */ 9 | 10 | .image-inserter-container .modal-close-button { 11 | display: none; 12 | } 13 | 14 | .image-inserter-container .modal-content { 15 | overflow-y: hidden; 16 | } 17 | 18 | .image-inserter-container .container { 19 | z-index: 1; 20 | position: relative; 21 | max-width: 100%; 22 | } 23 | 24 | .image-inserter-container .container .input-group { 25 | display: flex; 26 | } 27 | 28 | .image-inserter-container .container .query-input { 29 | flex-grow: 1; 30 | } 31 | 32 | .image-inserter-container .container .selector { 33 | margin-left: 8px; 34 | } 35 | 36 | .image-inserter-container .container .scroll-area { 37 | overflow-x: hidden; 38 | margin-top: 6px; 39 | width: 100%; 40 | max-height: 70vh; 41 | } 42 | 43 | .image-inserter-container .container .scroll-area.loading { 44 | display: none; 45 | } 46 | 47 | .image-inserter-container .container .scroll-area .images-list { 48 | display: grid; 49 | grid-template-columns: repeat(1, 1fr); 50 | } 51 | 52 | @media only screen and (min-width: 600px) { 53 | .image-inserter-container .container { 54 | padding: 10px; 55 | } 56 | 57 | .image-inserter-container .container { 58 | max-width: 80vw; 59 | } 60 | 61 | .image-inserter-container .container .scroll-area .images-list { 62 | grid-template-columns: repeat(2, 1fr); 63 | } 64 | } 65 | 66 | @media only screen and (min-width: 768px) { 67 | .image-inserter-container .container .scroll-area .images-list { 68 | grid-template-columns: repeat(3, 1fr); 69 | } 70 | } 71 | 72 | @media only screen and (min-width: 1200px) { 73 | .image-inserter-container .container .scroll-area .images-list { 74 | grid-template-columns: repeat(4, 1fr); 75 | } 76 | } 77 | 78 | .image-inserter-container .container .scroll-area .images-list .query-result { 79 | padding: 5px 10px; 80 | cursor: pointer; 81 | border-radius: 6px; 82 | } 83 | 84 | .image-inserter-container .container .scroll-area .images-list .query-result img { 85 | width: 100%; 86 | height: 100%; 87 | object-fit: contain; 88 | box-shadow: none; 89 | background-color: inherit; 90 | } 91 | 92 | .image-inserter-container .container .scroll-area .images-list .query-result.is-selected { 93 | background-color: var(--background-tertiary); 94 | box-shadow: 0 1px 3px rgb(0 0 0 / 12%), 0 1px 2px rgb(0 0 0 / 24%); 95 | } 96 | 97 | .image-inserter-container .container .scroll-area .pagination { 98 | width: 100%; 99 | margin-top: 24px; 100 | margin-bottom: 24px; 101 | display: flex; 102 | justify-content: space-between; 103 | } 104 | 105 | .image-inserter-container .container .scroll-area .pagination .btn { 106 | cursor: pointer; 107 | } 108 | 109 | .image-inserter-container .container .pixabay-logo { 110 | text-align: right; 111 | margin-top: 20px; 112 | } 113 | 114 | /* Loading Animation */ 115 | .image-inserter-container .loading-container { 116 | display: flex; 117 | justify-content: center; 118 | } 119 | .image-inserter-container .loading-container .lds-heart { 120 | display: inline-block; 121 | position: relative; 122 | width: 80px; 123 | height: 80px; 124 | transform: rotate(45deg); 125 | transform-origin: 40px 40px; 126 | } 127 | 128 | .image-inserter-container .loading-container .lds-heart div { 129 | top: 32px; 130 | left: 32px; 131 | position: absolute; 132 | width: 32px; 133 | height: 32px; 134 | background: var(--background-modifier-success); 135 | animation: lds-heart 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1); 136 | } 137 | .image-inserter-container .loading-container .lds-heart div:after, 138 | .image-inserter-container .loading-container .lds-heart div:before { 139 | content: " "; 140 | position: absolute; 141 | display: block; 142 | width: 32px; 143 | height: 32px; 144 | background: var(--background-modifier-success); 145 | } 146 | 147 | .image-inserter-container .loading-container .lds-heart div:before { 148 | left: -24px; 149 | border-radius: 50% 0 0 50%; 150 | } 151 | 152 | .image-inserter-container .loading-container .lds-heart div:after { 153 | top: -24px; 154 | border-radius: 50% 50% 0 0; 155 | } 156 | 157 | @keyframes lds-heart { 158 | 0% { 159 | transform: scale(0.95); 160 | } 161 | 5% { 162 | transform: scale(1.1); 163 | } 164 | 39% { 165 | transform: scale(0.85); 166 | } 167 | 45% { 168 | transform: scale(1); 169 | } 170 | 60% { 171 | transform: scale(0.95); 172 | } 173 | 100% { 174 | transform: scale(0.9); 175 | } 176 | } 177 | 178 | /* Loading Animation */ 179 | .image-inserter-container .no-result-container { 180 | display: flex; 181 | justify-content: center; 182 | } 183 | 184 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "ES6", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "isolatedModules": true, 13 | "strictNullChecks": true, 14 | "lib": [ 15 | "DOM", 16 | "ES5", 17 | "ES6", 18 | "ES7" 19 | ], 20 | "jsx": "react-jsx" 21 | }, 22 | "include": [ 23 | "**/*.ts", 24 | "node_modules/obsidian/obsidian.d.ts" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /unsplash.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace Unsplash { 2 | 3 | export interface ProfileImage { 4 | small: string; 5 | medium: string; 6 | large: string; 7 | } 8 | 9 | export interface Links { 10 | self: string; 11 | html: string; 12 | photos: string; 13 | likes: string; 14 | portfolio: string; 15 | } 16 | 17 | export interface User { 18 | id: string; 19 | username: string; 20 | name: string; 21 | portfolio_url: string; 22 | bio: string; 23 | location: string; 24 | total_likes: number; 25 | total_photos: number; 26 | total_collections: number; 27 | instagram_username: string; 28 | twitter_username: string; 29 | profile_image: ProfileImage; 30 | links: Links; 31 | } 32 | 33 | export interface CurrentUserCollection { 34 | id: number; 35 | title: string; 36 | published_at: Date; 37 | last_collected_at: Date; 38 | updated_at: Date; 39 | cover_photo?: unknown; 40 | user?: unknown; 41 | } 42 | 43 | export interface Urls { 44 | raw: string; 45 | full: string; 46 | regular: string; 47 | small: string; 48 | thumb: string; 49 | } 50 | 51 | export interface Links2 { 52 | self: string; 53 | html: string; 54 | download: string; 55 | download_location: string; 56 | } 57 | 58 | export interface Photo { 59 | id: string; 60 | created_at: Date; 61 | updated_at: Date; 62 | width: number; 63 | height: number; 64 | color: string; 65 | blur_hash: string; 66 | likes: number; 67 | liked_by_user: boolean; 68 | description: string; 69 | alt_description: string; 70 | user: User; 71 | current_user_collections: CurrentUserCollection[]; 72 | urls: Urls; 73 | links: Links2; 74 | } 75 | 76 | export interface RootObject { 77 | results: Photo[]; 78 | total: number; 79 | total_pages: number; 80 | } 81 | 82 | } 83 | 84 | -------------------------------------------------------------------------------- /utils.ts: -------------------------------------------------------------------------------- 1 | import { moment } from 'obsidian' 2 | 3 | export function debounce(fn: (query: string) => Promise, delay: number): (query: string) => void { 4 | let timer: ReturnType; 5 | 6 | return function (...params) { 7 | const args = params 8 | clearTimeout(timer); 9 | timer = setTimeout(() => { 10 | fn.apply(this, args); 11 | }, delay); 12 | }; 13 | } 14 | 15 | export function validUrl(url: string) { 16 | try { 17 | return Boolean(new URL(url)); 18 | } catch(e){ 19 | return false; 20 | } 21 | } 22 | 23 | export const randomImgName = () => { 24 | return `img-${moment().format("YYYYMMDDHHmmss")}` 25 | } 26 | 27 | -------------------------------------------------------------------------------- /version-bump.mjs: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from "fs"; 2 | 3 | const targetVersion = process.env.npm_package_version; 4 | 5 | // read minAppVersion from manifest.json and bump version to target version 6 | let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); 7 | const { minAppVersion } = manifest; 8 | manifest.version = targetVersion; 9 | writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); 10 | 11 | // update versions.json with target version and minAppVersion from manifest.json 12 | let versions = JSON.parse(readFileSync("versions.json", "utf8")); 13 | versions[targetVersion] = minAppVersion; 14 | writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); 15 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "0.1.0": "0.15.0", 3 | "0.2.0": "0.15.0", 4 | "0.3.0": "0.15.0", 5 | "0.4.0": "0.15.0", 6 | "0.5.0": "0.15.0", 7 | "0.5.1": "0.15.0", 8 | "0.5.2": "0.15.0", 9 | "0.5.3": "0.15.0", 10 | "0.6.0": "0.15.0", 11 | "0.6.1": "0.15.0", 12 | "0.6.2": "0.15.0", 13 | "0.7.0": "0.15.0", 14 | "0.8.0": "0.15.0", 15 | "0.8.2": "0.15.0", 16 | "0.8.3": "0.15.0" 17 | } -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@aashutoshrathi/word-wrap@^1.2.3": 6 | version "1.2.6" 7 | resolved "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz" 8 | integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== 9 | 10 | "@eslint-community/eslint-utils@^4.2.0": 11 | version "4.4.0" 12 | resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz" 13 | integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== 14 | dependencies: 15 | eslint-visitor-keys "^3.3.0" 16 | 17 | "@eslint-community/regexpp@^4.6.1": 18 | version "4.10.0" 19 | resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz" 20 | integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== 21 | 22 | "@eslint/eslintrc@^2.1.4": 23 | version "2.1.4" 24 | resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz" 25 | integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== 26 | dependencies: 27 | ajv "^6.12.4" 28 | debug "^4.3.2" 29 | espree "^9.6.0" 30 | globals "^13.19.0" 31 | ignore "^5.2.0" 32 | import-fresh "^3.2.1" 33 | js-yaml "^4.1.0" 34 | minimatch "^3.1.2" 35 | strip-json-comments "^3.1.1" 36 | 37 | "@eslint/js@8.57.0": 38 | version "8.57.0" 39 | resolved "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz" 40 | integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== 41 | 42 | "@humanwhocodes/config-array@^0.11.14": 43 | version "0.11.14" 44 | resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz" 45 | integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== 46 | dependencies: 47 | "@humanwhocodes/object-schema" "^2.0.2" 48 | debug "^4.3.1" 49 | minimatch "^3.0.5" 50 | 51 | "@humanwhocodes/module-importer@^1.0.1": 52 | version "1.0.1" 53 | resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz" 54 | integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== 55 | 56 | "@humanwhocodes/object-schema@^2.0.2": 57 | version "2.0.2" 58 | resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz" 59 | integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== 60 | 61 | "@nodelib/fs.scandir@2.1.5": 62 | version "2.1.5" 63 | resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" 64 | integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== 65 | dependencies: 66 | "@nodelib/fs.stat" "2.0.5" 67 | run-parallel "^1.1.9" 68 | 69 | "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": 70 | version "2.0.5" 71 | resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" 72 | integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== 73 | 74 | "@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": 75 | version "1.2.8" 76 | resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" 77 | integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== 78 | dependencies: 79 | "@nodelib/fs.scandir" "2.1.5" 80 | fastq "^1.6.0" 81 | 82 | "@types/codemirror@0.0.108": 83 | version "0.0.108" 84 | resolved "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.108.tgz" 85 | integrity sha512-3FGFcus0P7C2UOGCNUVENqObEb4SFk+S8Dnxq7K6aIsLVs/vDtlangl3PEO0ykaKXyK56swVF6Nho7VsA44uhw== 86 | dependencies: 87 | "@types/tern" "*" 88 | 89 | "@types/estree@*": 90 | version "1.0.0" 91 | resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz" 92 | integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== 93 | 94 | "@types/json-schema@^7.0.9": 95 | version "7.0.11" 96 | resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz" 97 | integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== 98 | 99 | "@types/node@^16.11.6": 100 | version "16.11.56" 101 | resolved "https://registry.npmjs.org/@types/node/-/node-16.11.56.tgz" 102 | integrity sha512-aFcUkv7EddxxOa/9f74DINReQ/celqH8DiB3fRYgVDM2Xm5QJL8sl80QKuAnGvwAsMn+H3IFA6WCrQh1CY7m1A== 103 | 104 | "@types/prop-types@*": 105 | version "15.7.12" 106 | resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz" 107 | integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== 108 | 109 | "@types/react-dom@^18.0.6": 110 | version "18.2.22" 111 | resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.22.tgz" 112 | integrity sha512-fHkBXPeNtfvri6gdsMYyW+dW7RXFo6Ad09nLFK0VQWR7yGLai/Cyvyj696gbwYvBnhGtevUG9cET0pmUbMtoPQ== 113 | dependencies: 114 | "@types/react" "*" 115 | 116 | "@types/react@*", "@types/react@^18.0.21": 117 | version "18.2.72" 118 | resolved "https://registry.npmjs.org/@types/react/-/react-18.2.72.tgz" 119 | integrity sha512-/e7GWxGzXQF7OJAua7UAYqYi/4VpXEfbGtmYQcAQwP3SjjjAXfybTf/JK5S+SaetB/ChXl8Y2g1hCsj7jDXxcg== 120 | dependencies: 121 | "@types/prop-types" "*" 122 | csstype "^3.0.2" 123 | 124 | "@types/tern@*": 125 | version "0.23.4" 126 | resolved "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz" 127 | integrity sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg== 128 | dependencies: 129 | "@types/estree" "*" 130 | 131 | "@typescript-eslint/eslint-plugin@5.29.0": 132 | version "5.29.0" 133 | resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.29.0.tgz" 134 | integrity sha512-kgTsISt9pM53yRFQmLZ4npj99yGl3x3Pl7z4eA66OuTzAGC4bQB5H5fuLwPnqTKU3yyrrg4MIhjF17UYnL4c0w== 135 | dependencies: 136 | "@typescript-eslint/scope-manager" "5.29.0" 137 | "@typescript-eslint/type-utils" "5.29.0" 138 | "@typescript-eslint/utils" "5.29.0" 139 | debug "^4.3.4" 140 | functional-red-black-tree "^1.0.1" 141 | ignore "^5.2.0" 142 | regexpp "^3.2.0" 143 | semver "^7.3.7" 144 | tsutils "^3.21.0" 145 | 146 | "@typescript-eslint/parser@5.29.0": 147 | version "5.29.0" 148 | resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.29.0.tgz" 149 | integrity sha512-ruKWTv+x0OOxbzIw9nW5oWlUopvP/IQDjB5ZqmTglLIoDTctLlAJpAQFpNPJP/ZI7hTT9sARBosEfaKbcFuECw== 150 | dependencies: 151 | "@typescript-eslint/scope-manager" "5.29.0" 152 | "@typescript-eslint/types" "5.29.0" 153 | "@typescript-eslint/typescript-estree" "5.29.0" 154 | debug "^4.3.4" 155 | 156 | "@typescript-eslint/scope-manager@5.29.0": 157 | version "5.29.0" 158 | resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.29.0.tgz" 159 | integrity sha512-etbXUT0FygFi2ihcxDZjz21LtC+Eps9V2xVx09zFoN44RRHPrkMflidGMI+2dUs821zR1tDS6Oc9IXxIjOUZwA== 160 | dependencies: 161 | "@typescript-eslint/types" "5.29.0" 162 | "@typescript-eslint/visitor-keys" "5.29.0" 163 | 164 | "@typescript-eslint/type-utils@5.29.0": 165 | version "5.29.0" 166 | resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.29.0.tgz" 167 | integrity sha512-JK6bAaaiJozbox3K220VRfCzLa9n0ib/J+FHIwnaV3Enw/TO267qe0pM1b1QrrEuy6xun374XEAsRlA86JJnyg== 168 | dependencies: 169 | "@typescript-eslint/utils" "5.29.0" 170 | debug "^4.3.4" 171 | tsutils "^3.21.0" 172 | 173 | "@typescript-eslint/types@5.29.0": 174 | version "5.29.0" 175 | resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.29.0.tgz" 176 | integrity sha512-X99VbqvAXOMdVyfFmksMy3u8p8yoRGITgU1joBJPzeYa0rhdf5ok9S56/itRoUSh99fiDoMtarSIJXo7H/SnOg== 177 | 178 | "@typescript-eslint/typescript-estree@5.29.0": 179 | version "5.29.0" 180 | resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.29.0.tgz" 181 | integrity sha512-mQvSUJ/JjGBdvo+1LwC+GY2XmSYjK1nAaVw2emp/E61wEVYEyibRHCqm1I1vEKbXCpUKuW4G7u9ZCaZhJbLoNQ== 182 | dependencies: 183 | "@typescript-eslint/types" "5.29.0" 184 | "@typescript-eslint/visitor-keys" "5.29.0" 185 | debug "^4.3.4" 186 | globby "^11.1.0" 187 | is-glob "^4.0.3" 188 | semver "^7.3.7" 189 | tsutils "^3.21.0" 190 | 191 | "@typescript-eslint/utils@5.29.0": 192 | version "5.29.0" 193 | resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.29.0.tgz" 194 | integrity sha512-3Eos6uP1nyLOBayc/VUdKZikV90HahXE5Dx9L5YlSd/7ylQPXhLk1BYb29SDgnBnTp+jmSZUU0QxUiyHgW4p7A== 195 | dependencies: 196 | "@types/json-schema" "^7.0.9" 197 | "@typescript-eslint/scope-manager" "5.29.0" 198 | "@typescript-eslint/types" "5.29.0" 199 | "@typescript-eslint/typescript-estree" "5.29.0" 200 | eslint-scope "^5.1.1" 201 | eslint-utils "^3.0.0" 202 | 203 | "@typescript-eslint/visitor-keys@5.29.0": 204 | version "5.29.0" 205 | resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.29.0.tgz" 206 | integrity sha512-Hpb/mCWsjILvikMQoZIE3voc9wtQcS0A9FUw3h8bhr9UxBdtI/tw1ZDZUOXHXLOVMedKCH5NxyzATwnU78bWCQ== 207 | dependencies: 208 | "@typescript-eslint/types" "5.29.0" 209 | eslint-visitor-keys "^3.3.0" 210 | 211 | "@ungap/structured-clone@^1.2.0": 212 | version "1.2.0" 213 | resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz" 214 | integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== 215 | 216 | acorn-jsx@^5.3.2: 217 | version "5.3.2" 218 | resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" 219 | integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== 220 | 221 | acorn@^8.9.0: 222 | version "8.11.3" 223 | resolved "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz" 224 | integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== 225 | 226 | ajv@^6.12.4: 227 | version "6.12.6" 228 | resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" 229 | integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== 230 | dependencies: 231 | fast-deep-equal "^3.1.1" 232 | fast-json-stable-stringify "^2.0.0" 233 | json-schema-traverse "^0.4.1" 234 | uri-js "^4.2.2" 235 | 236 | ansi-regex@^5.0.1: 237 | version "5.0.1" 238 | resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" 239 | integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== 240 | 241 | ansi-styles@^4.1.0: 242 | version "4.3.0" 243 | resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" 244 | integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== 245 | dependencies: 246 | color-convert "^2.0.1" 247 | 248 | argparse@^2.0.1: 249 | version "2.0.1" 250 | resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" 251 | integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== 252 | 253 | array-union@^2.1.0: 254 | version "2.1.0" 255 | resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" 256 | integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== 257 | 258 | balanced-match@^1.0.0: 259 | version "1.0.2" 260 | resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" 261 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 262 | 263 | brace-expansion@^1.1.7: 264 | version "1.1.11" 265 | resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" 266 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 267 | dependencies: 268 | balanced-match "^1.0.0" 269 | concat-map "0.0.1" 270 | 271 | braces@^3.0.2: 272 | version "3.0.2" 273 | resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" 274 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 275 | dependencies: 276 | fill-range "^7.0.1" 277 | 278 | builtin-modules@3.3.0: 279 | version "3.3.0" 280 | resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz" 281 | integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== 282 | 283 | callsites@^3.0.0: 284 | version "3.1.0" 285 | resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" 286 | integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== 287 | 288 | chalk@^4.0.0: 289 | version "4.1.2" 290 | resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" 291 | integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== 292 | dependencies: 293 | ansi-styles "^4.1.0" 294 | supports-color "^7.1.0" 295 | 296 | color-convert@^2.0.1: 297 | version "2.0.1" 298 | resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" 299 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 300 | dependencies: 301 | color-name "~1.1.4" 302 | 303 | color-name@~1.1.4: 304 | version "1.1.4" 305 | resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" 306 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 307 | 308 | concat-map@0.0.1: 309 | version "0.0.1" 310 | resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" 311 | integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== 312 | 313 | cross-spawn@^7.0.2: 314 | version "7.0.3" 315 | resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" 316 | integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== 317 | dependencies: 318 | path-key "^3.1.0" 319 | shebang-command "^2.0.0" 320 | which "^2.0.1" 321 | 322 | csstype@^3.0.2: 323 | version "3.1.3" 324 | resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz" 325 | integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== 326 | 327 | debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: 328 | version "4.3.4" 329 | resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" 330 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== 331 | dependencies: 332 | ms "2.1.2" 333 | 334 | deep-is@^0.1.3: 335 | version "0.1.4" 336 | resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" 337 | integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== 338 | 339 | dir-glob@^3.0.1: 340 | version "3.0.1" 341 | resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" 342 | integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== 343 | dependencies: 344 | path-type "^4.0.0" 345 | 346 | doctrine@^3.0.0: 347 | version "3.0.0" 348 | resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" 349 | integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== 350 | dependencies: 351 | esutils "^2.0.2" 352 | 353 | esbuild-android-64@0.14.47: 354 | version "0.14.47" 355 | resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.47.tgz#ef95b42c67bcf4268c869153fa3ad1466c4cea6b" 356 | integrity sha512-R13Bd9+tqLVFndncMHssZrPWe6/0Kpv2/dt4aA69soX4PRxlzsVpCvoJeFE8sOEoeVEiBkI0myjlkDodXlHa0g== 357 | 358 | esbuild-android-arm64@0.14.47: 359 | version "0.14.47" 360 | resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.47.tgz#4ebd7ce9fb250b4695faa3ee46fd3b0754ecd9e6" 361 | integrity sha512-OkwOjj7ts4lBp/TL6hdd8HftIzOy/pdtbrNA4+0oVWgGG64HrdVzAF5gxtJufAPOsEjkyh1oIYvKAUinKKQRSQ== 362 | 363 | esbuild-darwin-64@0.14.47: 364 | version "0.14.47" 365 | resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.47.tgz#e0da6c244f497192f951807f003f6a423ed23188" 366 | integrity sha512-R6oaW0y5/u6Eccti/TS6c/2c1xYTb1izwK3gajJwi4vIfNs1s8B1dQzI1UiC9T61YovOQVuePDcfqHLT3mUZJA== 367 | 368 | esbuild-darwin-arm64@0.14.47: 369 | version "0.14.47" 370 | resolved "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.47.tgz" 371 | integrity sha512-seCmearlQyvdvM/noz1L9+qblC5vcBrhUaOoLEDDoLInF/VQ9IkobGiLlyTPYP5dW1YD4LXhtBgOyevoIHGGnw== 372 | 373 | esbuild-freebsd-64@0.14.47: 374 | version "0.14.47" 375 | resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.47.tgz#8da6a14c095b29c01fc8087a16cb7906debc2d67" 376 | integrity sha512-ZH8K2Q8/Ux5kXXvQMDsJcxvkIwut69KVrYQhza/ptkW50DC089bCVrJZZ3sKzIoOx+YPTrmsZvqeZERjyYrlvQ== 377 | 378 | esbuild-freebsd-arm64@0.14.47: 379 | version "0.14.47" 380 | resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.47.tgz#ad31f9c92817ff8f33fd253af7ab5122dc1b83f6" 381 | integrity sha512-ZJMQAJQsIOhn3XTm7MPQfCzEu5b9STNC+s90zMWe2afy9EwnHV7Ov7ohEMv2lyWlc2pjqLW8QJnz2r0KZmeAEQ== 382 | 383 | esbuild-linux-32@0.14.47: 384 | version "0.14.47" 385 | resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.47.tgz#de085e4db2e692ea30c71208ccc23fdcf5196c58" 386 | integrity sha512-FxZOCKoEDPRYvq300lsWCTv1kcHgiiZfNrPtEhFAiqD7QZaXrad8LxyJ8fXGcWzIFzRiYZVtB3ttvITBvAFhKw== 387 | 388 | esbuild-linux-64@0.14.47: 389 | version "0.14.47" 390 | resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.47.tgz#2a9321bbccb01f01b04cebfcfccbabeba3658ba1" 391 | integrity sha512-nFNOk9vWVfvWYF9YNYksZptgQAdstnDCMtR6m42l5Wfugbzu11VpMCY9XrD4yFxvPo9zmzcoUL/88y0lfJZJJw== 392 | 393 | esbuild-linux-arm64@0.14.47: 394 | version "0.14.47" 395 | resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.47.tgz#b9da7b6fc4b0ca7a13363a0c5b7bb927e4bc535a" 396 | integrity sha512-ywfme6HVrhWcevzmsufjd4iT3PxTfCX9HOdxA7Hd+/ZM23Y9nXeb+vG6AyA6jgq/JovkcqRHcL9XwRNpWG6XRw== 397 | 398 | esbuild-linux-arm@0.14.47: 399 | version "0.14.47" 400 | resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.47.tgz#56fec2a09b9561c337059d4af53625142aded853" 401 | integrity sha512-ZGE1Bqg/gPRXrBpgpvH81tQHpiaGxa8c9Rx/XOylkIl2ypLuOcawXEAo8ls+5DFCcRGt/o3sV+PzpAFZobOsmA== 402 | 403 | esbuild-linux-mips64le@0.14.47: 404 | version "0.14.47" 405 | resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.47.tgz#9db21561f8f22ed79ef2aedb7bbef082b46cf823" 406 | integrity sha512-mg3D8YndZ1LvUiEdDYR3OsmeyAew4MA/dvaEJxvyygahWmpv1SlEEnhEZlhPokjsUMfRagzsEF/d/2XF+kTQGg== 407 | 408 | esbuild-linux-ppc64le@0.14.47: 409 | version "0.14.47" 410 | resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.47.tgz#dc3a3da321222b11e96e50efafec9d2de408198b" 411 | integrity sha512-WER+f3+szmnZiWoK6AsrTKGoJoErG2LlauSmk73LEZFQ/iWC+KhhDsOkn1xBUpzXWsxN9THmQFltLoaFEH8F8w== 412 | 413 | esbuild-linux-riscv64@0.14.47: 414 | version "0.14.47" 415 | resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.47.tgz#9bd6dcd3dca6c0357084ecd06e1d2d4bf105335f" 416 | integrity sha512-1fI6bP3A3rvI9BsaaXbMoaOjLE3lVkJtLxsgLHqlBhLlBVY7UqffWBvkrX/9zfPhhVMd9ZRFiaqXnB1T7BsL2g== 417 | 418 | esbuild-linux-s390x@0.14.47: 419 | version "0.14.47" 420 | resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.47.tgz#a458af939b52f2cd32fc561410d441a51f69d41f" 421 | integrity sha512-eZrWzy0xFAhki1CWRGnhsHVz7IlSKX6yT2tj2Eg8lhAwlRE5E96Hsb0M1mPSE1dHGpt1QVwwVivXIAacF/G6mw== 422 | 423 | esbuild-netbsd-64@0.14.47: 424 | version "0.14.47" 425 | resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.47.tgz#6388e785d7e7e4420cb01348d7483ab511b16aa8" 426 | integrity sha512-Qjdjr+KQQVH5Q2Q1r6HBYswFTToPpss3gqCiSw2Fpq/ua8+eXSQyAMG+UvULPqXceOwpnPo4smyZyHdlkcPppQ== 427 | 428 | esbuild-openbsd-64@0.14.47: 429 | version "0.14.47" 430 | resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.47.tgz#309af806db561aa886c445344d1aacab850dbdc5" 431 | integrity sha512-QpgN8ofL7B9z8g5zZqJE+eFvD1LehRlxr25PBkjyyasakm4599iroUpaj96rdqRlO2ShuyqwJdr+oNqWwTUmQw== 432 | 433 | esbuild-sunos-64@0.14.47: 434 | version "0.14.47" 435 | resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.47.tgz#3f19612dcdb89ba6c65283a7ff6e16f8afbf8aaa" 436 | integrity sha512-uOeSgLUwukLioAJOiGYm3kNl+1wJjgJA8R671GYgcPgCx7QR73zfvYqXFFcIO93/nBdIbt5hd8RItqbbf3HtAQ== 437 | 438 | esbuild-windows-32@0.14.47: 439 | version "0.14.47" 440 | resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.47.tgz#a92d279c8458d5dc319abcfeb30aa49e8f2e6f7f" 441 | integrity sha512-H0fWsLTp2WBfKLBgwYT4OTfFly4Im/8B5f3ojDv1Kx//kiubVY0IQunP2Koc/fr/0wI7hj3IiBDbSrmKlrNgLQ== 442 | 443 | esbuild-windows-64@0.14.47: 444 | version "0.14.47" 445 | resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.47.tgz#2564c3fcf0c23d701edb71af8c52d3be4cec5f8a" 446 | integrity sha512-/Pk5jIEH34T68r8PweKRi77W49KwanZ8X6lr3vDAtOlH5EumPE4pBHqkCUdELanvsT14yMXLQ/C/8XPi1pAtkQ== 447 | 448 | esbuild-windows-arm64@0.14.47: 449 | version "0.14.47" 450 | resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.47.tgz#86d9db1a22d83360f726ac5fba41c2f625db6878" 451 | integrity sha512-HFSW2lnp62fl86/qPQlqw6asIwCnEsEoNIL1h2uVMgakddf+vUuMcCbtUY1i8sst7KkgHrVKCJQB33YhhOweCQ== 452 | 453 | esbuild@0.14.47: 454 | version "0.14.47" 455 | resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.14.47.tgz" 456 | integrity sha512-wI4ZiIfFxpkuxB8ju4MHrGwGLyp1+awEHAHVpx6w7a+1pmYIq8T9FGEVVwFo0iFierDoMj++Xq69GXWYn2EiwA== 457 | optionalDependencies: 458 | esbuild-android-64 "0.14.47" 459 | esbuild-android-arm64 "0.14.47" 460 | esbuild-darwin-64 "0.14.47" 461 | esbuild-darwin-arm64 "0.14.47" 462 | esbuild-freebsd-64 "0.14.47" 463 | esbuild-freebsd-arm64 "0.14.47" 464 | esbuild-linux-32 "0.14.47" 465 | esbuild-linux-64 "0.14.47" 466 | esbuild-linux-arm "0.14.47" 467 | esbuild-linux-arm64 "0.14.47" 468 | esbuild-linux-mips64le "0.14.47" 469 | esbuild-linux-ppc64le "0.14.47" 470 | esbuild-linux-riscv64 "0.14.47" 471 | esbuild-linux-s390x "0.14.47" 472 | esbuild-netbsd-64 "0.14.47" 473 | esbuild-openbsd-64 "0.14.47" 474 | esbuild-sunos-64 "0.14.47" 475 | esbuild-windows-32 "0.14.47" 476 | esbuild-windows-64 "0.14.47" 477 | esbuild-windows-arm64 "0.14.47" 478 | 479 | escape-string-regexp@^4.0.0: 480 | version "4.0.0" 481 | resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" 482 | integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== 483 | 484 | eslint-scope@^5.1.1: 485 | version "5.1.1" 486 | resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" 487 | integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== 488 | dependencies: 489 | esrecurse "^4.3.0" 490 | estraverse "^4.1.1" 491 | 492 | eslint-scope@^7.2.2: 493 | version "7.2.2" 494 | resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz" 495 | integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== 496 | dependencies: 497 | esrecurse "^4.3.0" 498 | estraverse "^5.2.0" 499 | 500 | eslint-utils@^3.0.0: 501 | version "3.0.0" 502 | resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz" 503 | integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== 504 | dependencies: 505 | eslint-visitor-keys "^2.0.0" 506 | 507 | eslint-visitor-keys@^2.0.0: 508 | version "2.1.0" 509 | resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz" 510 | integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== 511 | 512 | eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: 513 | version "3.4.3" 514 | resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" 515 | integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== 516 | 517 | eslint@^8.25.0: 518 | version "8.57.0" 519 | resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz" 520 | integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== 521 | dependencies: 522 | "@eslint-community/eslint-utils" "^4.2.0" 523 | "@eslint-community/regexpp" "^4.6.1" 524 | "@eslint/eslintrc" "^2.1.4" 525 | "@eslint/js" "8.57.0" 526 | "@humanwhocodes/config-array" "^0.11.14" 527 | "@humanwhocodes/module-importer" "^1.0.1" 528 | "@nodelib/fs.walk" "^1.2.8" 529 | "@ungap/structured-clone" "^1.2.0" 530 | ajv "^6.12.4" 531 | chalk "^4.0.0" 532 | cross-spawn "^7.0.2" 533 | debug "^4.3.2" 534 | doctrine "^3.0.0" 535 | escape-string-regexp "^4.0.0" 536 | eslint-scope "^7.2.2" 537 | eslint-visitor-keys "^3.4.3" 538 | espree "^9.6.1" 539 | esquery "^1.4.2" 540 | esutils "^2.0.2" 541 | fast-deep-equal "^3.1.3" 542 | file-entry-cache "^6.0.1" 543 | find-up "^5.0.0" 544 | glob-parent "^6.0.2" 545 | globals "^13.19.0" 546 | graphemer "^1.4.0" 547 | ignore "^5.2.0" 548 | imurmurhash "^0.1.4" 549 | is-glob "^4.0.0" 550 | is-path-inside "^3.0.3" 551 | js-yaml "^4.1.0" 552 | json-stable-stringify-without-jsonify "^1.0.1" 553 | levn "^0.4.1" 554 | lodash.merge "^4.6.2" 555 | minimatch "^3.1.2" 556 | natural-compare "^1.4.0" 557 | optionator "^0.9.3" 558 | strip-ansi "^6.0.1" 559 | text-table "^0.2.0" 560 | 561 | espree@^9.6.0, espree@^9.6.1: 562 | version "9.6.1" 563 | resolved "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz" 564 | integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== 565 | dependencies: 566 | acorn "^8.9.0" 567 | acorn-jsx "^5.3.2" 568 | eslint-visitor-keys "^3.4.1" 569 | 570 | esquery@^1.4.2: 571 | version "1.5.0" 572 | resolved "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz" 573 | integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== 574 | dependencies: 575 | estraverse "^5.1.0" 576 | 577 | esrecurse@^4.3.0: 578 | version "4.3.0" 579 | resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" 580 | integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== 581 | dependencies: 582 | estraverse "^5.2.0" 583 | 584 | estraverse@^4.1.1: 585 | version "4.3.0" 586 | resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" 587 | integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== 588 | 589 | estraverse@^5.1.0, estraverse@^5.2.0: 590 | version "5.3.0" 591 | resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" 592 | integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== 593 | 594 | esutils@^2.0.2: 595 | version "2.0.3" 596 | resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" 597 | integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== 598 | 599 | fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: 600 | version "3.1.3" 601 | resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" 602 | integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== 603 | 604 | fast-glob@^3.2.9: 605 | version "3.2.11" 606 | resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz" 607 | integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== 608 | dependencies: 609 | "@nodelib/fs.stat" "^2.0.2" 610 | "@nodelib/fs.walk" "^1.2.3" 611 | glob-parent "^5.1.2" 612 | merge2 "^1.3.0" 613 | micromatch "^4.0.4" 614 | 615 | fast-json-stable-stringify@^2.0.0: 616 | version "2.1.0" 617 | resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" 618 | integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== 619 | 620 | fast-levenshtein@^2.0.6: 621 | version "2.0.6" 622 | resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" 623 | integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== 624 | 625 | fastq@^1.6.0: 626 | version "1.13.0" 627 | resolved "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz" 628 | integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== 629 | dependencies: 630 | reusify "^1.0.4" 631 | 632 | file-entry-cache@^6.0.1: 633 | version "6.0.1" 634 | resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" 635 | integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== 636 | dependencies: 637 | flat-cache "^3.0.4" 638 | 639 | fill-range@^7.0.1: 640 | version "7.0.1" 641 | resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" 642 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 643 | dependencies: 644 | to-regex-range "^5.0.1" 645 | 646 | find-up@^5.0.0: 647 | version "5.0.0" 648 | resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" 649 | integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== 650 | dependencies: 651 | locate-path "^6.0.0" 652 | path-exists "^4.0.0" 653 | 654 | flat-cache@^3.0.4: 655 | version "3.0.4" 656 | resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz" 657 | integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== 658 | dependencies: 659 | flatted "^3.1.0" 660 | rimraf "^3.0.2" 661 | 662 | flatted@^3.1.0: 663 | version "3.2.7" 664 | resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz" 665 | integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== 666 | 667 | fs.realpath@^1.0.0: 668 | version "1.0.0" 669 | resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" 670 | integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== 671 | 672 | functional-red-black-tree@^1.0.1: 673 | version "1.0.1" 674 | resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz" 675 | integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== 676 | 677 | glob-parent@^5.1.2: 678 | version "5.1.2" 679 | resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" 680 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 681 | dependencies: 682 | is-glob "^4.0.1" 683 | 684 | glob-parent@^6.0.2: 685 | version "6.0.2" 686 | resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" 687 | integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== 688 | dependencies: 689 | is-glob "^4.0.3" 690 | 691 | glob@^7.1.3: 692 | version "7.2.3" 693 | resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" 694 | integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== 695 | dependencies: 696 | fs.realpath "^1.0.0" 697 | inflight "^1.0.4" 698 | inherits "2" 699 | minimatch "^3.1.1" 700 | once "^1.3.0" 701 | path-is-absolute "^1.0.0" 702 | 703 | globals@^13.19.0: 704 | version "13.24.0" 705 | resolved "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz" 706 | integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== 707 | dependencies: 708 | type-fest "^0.20.2" 709 | 710 | globby@^11.1.0: 711 | version "11.1.0" 712 | resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" 713 | integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== 714 | dependencies: 715 | array-union "^2.1.0" 716 | dir-glob "^3.0.1" 717 | fast-glob "^3.2.9" 718 | ignore "^5.2.0" 719 | merge2 "^1.4.1" 720 | slash "^3.0.0" 721 | 722 | graphemer@^1.4.0: 723 | version "1.4.0" 724 | resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" 725 | integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== 726 | 727 | has-flag@^4.0.0: 728 | version "4.0.0" 729 | resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" 730 | integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== 731 | 732 | ignore@^5.2.0: 733 | version "5.2.0" 734 | resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz" 735 | integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== 736 | 737 | import-fresh@^3.2.1: 738 | version "3.3.0" 739 | resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" 740 | integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== 741 | dependencies: 742 | parent-module "^1.0.0" 743 | resolve-from "^4.0.0" 744 | 745 | imurmurhash@^0.1.4: 746 | version "0.1.4" 747 | resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" 748 | integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== 749 | 750 | inflight@^1.0.4: 751 | version "1.0.6" 752 | resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" 753 | integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== 754 | dependencies: 755 | once "^1.3.0" 756 | wrappy "1" 757 | 758 | inherits@2: 759 | version "2.0.4" 760 | resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" 761 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 762 | 763 | is-extglob@^2.1.1: 764 | version "2.1.1" 765 | resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" 766 | integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== 767 | 768 | is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: 769 | version "4.0.3" 770 | resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" 771 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 772 | dependencies: 773 | is-extglob "^2.1.1" 774 | 775 | is-number@^7.0.0: 776 | version "7.0.0" 777 | resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" 778 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 779 | 780 | is-path-inside@^3.0.3: 781 | version "3.0.3" 782 | resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz" 783 | integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== 784 | 785 | isexe@^2.0.0: 786 | version "2.0.0" 787 | resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" 788 | integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== 789 | 790 | "js-tokens@^3.0.0 || ^4.0.0": 791 | version "4.0.0" 792 | resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" 793 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 794 | 795 | js-yaml@^4.1.0: 796 | version "4.1.0" 797 | resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" 798 | integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== 799 | dependencies: 800 | argparse "^2.0.1" 801 | 802 | json-schema-traverse@^0.4.1: 803 | version "0.4.1" 804 | resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" 805 | integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== 806 | 807 | json-stable-stringify-without-jsonify@^1.0.1: 808 | version "1.0.1" 809 | resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" 810 | integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== 811 | 812 | levn@^0.4.1: 813 | version "0.4.1" 814 | resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" 815 | integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== 816 | dependencies: 817 | prelude-ls "^1.2.1" 818 | type-check "~0.4.0" 819 | 820 | locate-path@^6.0.0: 821 | version "6.0.0" 822 | resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" 823 | integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== 824 | dependencies: 825 | p-locate "^5.0.0" 826 | 827 | lodash.merge@^4.6.2: 828 | version "4.6.2" 829 | resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" 830 | integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== 831 | 832 | loose-envify@^1.1.0: 833 | version "1.4.0" 834 | resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" 835 | integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== 836 | dependencies: 837 | js-tokens "^3.0.0 || ^4.0.0" 838 | 839 | lru-cache@^6.0.0: 840 | version "6.0.0" 841 | resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" 842 | integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== 843 | dependencies: 844 | yallist "^4.0.0" 845 | 846 | merge2@^1.3.0, merge2@^1.4.1: 847 | version "1.4.1" 848 | resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" 849 | integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== 850 | 851 | micromatch@^4.0.4: 852 | version "4.0.5" 853 | resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" 854 | integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== 855 | dependencies: 856 | braces "^3.0.2" 857 | picomatch "^2.3.1" 858 | 859 | minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: 860 | version "3.1.2" 861 | resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" 862 | integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== 863 | dependencies: 864 | brace-expansion "^1.1.7" 865 | 866 | moment@2.29.4: 867 | version "2.29.4" 868 | resolved "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz" 869 | integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== 870 | 871 | ms@2.1.2: 872 | version "2.1.2" 873 | resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" 874 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 875 | 876 | natural-compare@^1.4.0: 877 | version "1.4.0" 878 | resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" 879 | integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== 880 | 881 | obsidian@latest: 882 | version "0.16.0" 883 | resolved "https://registry.npmjs.org/obsidian/-/obsidian-0.16.0.tgz" 884 | integrity sha512-KnQu1CntLz/EqA50W0zwlCqMgLbvMMfW2nmNQV4aMPW/aSYyjmnRMEwO0rAThQGhJPabDm2okVUSeXLctC/aMA== 885 | dependencies: 886 | "@types/codemirror" "0.0.108" 887 | moment "2.29.4" 888 | 889 | once@^1.3.0: 890 | version "1.4.0" 891 | resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" 892 | integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== 893 | dependencies: 894 | wrappy "1" 895 | 896 | optionator@^0.9.3: 897 | version "0.9.3" 898 | resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz" 899 | integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== 900 | dependencies: 901 | "@aashutoshrathi/word-wrap" "^1.2.3" 902 | deep-is "^0.1.3" 903 | fast-levenshtein "^2.0.6" 904 | levn "^0.4.1" 905 | prelude-ls "^1.2.1" 906 | type-check "^0.4.0" 907 | 908 | p-limit@^3.0.2: 909 | version "3.1.0" 910 | resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" 911 | integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== 912 | dependencies: 913 | yocto-queue "^0.1.0" 914 | 915 | p-locate@^5.0.0: 916 | version "5.0.0" 917 | resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" 918 | integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== 919 | dependencies: 920 | p-limit "^3.0.2" 921 | 922 | parent-module@^1.0.0: 923 | version "1.0.1" 924 | resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" 925 | integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== 926 | dependencies: 927 | callsites "^3.0.0" 928 | 929 | path-exists@^4.0.0: 930 | version "4.0.0" 931 | resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" 932 | integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== 933 | 934 | path-is-absolute@^1.0.0: 935 | version "1.0.1" 936 | resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" 937 | integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== 938 | 939 | path-key@^3.1.0: 940 | version "3.1.1" 941 | resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" 942 | integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== 943 | 944 | path-type@^4.0.0: 945 | version "4.0.0" 946 | resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" 947 | integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== 948 | 949 | picomatch@^2.3.1: 950 | version "2.3.1" 951 | resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" 952 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 953 | 954 | prelude-ls@^1.2.1: 955 | version "1.2.1" 956 | resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" 957 | integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== 958 | 959 | punycode@^2.1.0: 960 | version "2.3.1" 961 | resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" 962 | integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== 963 | 964 | queue-microtask@^1.2.2: 965 | version "1.2.3" 966 | resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" 967 | integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== 968 | 969 | react-dom@^18.2.0: 970 | version "18.2.0" 971 | resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz" 972 | integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== 973 | dependencies: 974 | loose-envify "^1.1.0" 975 | scheduler "^0.23.0" 976 | 977 | react@^18.2.0: 978 | version "18.2.0" 979 | resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz" 980 | integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== 981 | dependencies: 982 | loose-envify "^1.1.0" 983 | 984 | regexpp@^3.2.0: 985 | version "3.2.0" 986 | resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz" 987 | integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== 988 | 989 | resolve-from@^4.0.0: 990 | version "4.0.0" 991 | resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" 992 | integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== 993 | 994 | reusify@^1.0.4: 995 | version "1.0.4" 996 | resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" 997 | integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== 998 | 999 | rimraf@^3.0.2: 1000 | version "3.0.2" 1001 | resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" 1002 | integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== 1003 | dependencies: 1004 | glob "^7.1.3" 1005 | 1006 | run-parallel@^1.1.9: 1007 | version "1.2.0" 1008 | resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" 1009 | integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== 1010 | dependencies: 1011 | queue-microtask "^1.2.2" 1012 | 1013 | scheduler@^0.23.0: 1014 | version "0.23.0" 1015 | resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz" 1016 | integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== 1017 | dependencies: 1018 | loose-envify "^1.1.0" 1019 | 1020 | semver@^7.3.7: 1021 | version "7.3.7" 1022 | resolved "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz" 1023 | integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== 1024 | dependencies: 1025 | lru-cache "^6.0.0" 1026 | 1027 | shebang-command@^2.0.0: 1028 | version "2.0.0" 1029 | resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" 1030 | integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== 1031 | dependencies: 1032 | shebang-regex "^3.0.0" 1033 | 1034 | shebang-regex@^3.0.0: 1035 | version "3.0.0" 1036 | resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" 1037 | integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== 1038 | 1039 | slash@^3.0.0: 1040 | version "3.0.0" 1041 | resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" 1042 | integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== 1043 | 1044 | strip-ansi@^6.0.1: 1045 | version "6.0.1" 1046 | resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" 1047 | integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== 1048 | dependencies: 1049 | ansi-regex "^5.0.1" 1050 | 1051 | strip-json-comments@^3.1.1: 1052 | version "3.1.1" 1053 | resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" 1054 | integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== 1055 | 1056 | supports-color@^7.1.0: 1057 | version "7.2.0" 1058 | resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" 1059 | integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== 1060 | dependencies: 1061 | has-flag "^4.0.0" 1062 | 1063 | text-table@^0.2.0: 1064 | version "0.2.0" 1065 | resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" 1066 | integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== 1067 | 1068 | to-regex-range@^5.0.1: 1069 | version "5.0.1" 1070 | resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" 1071 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 1072 | dependencies: 1073 | is-number "^7.0.0" 1074 | 1075 | tslib@2.4.0: 1076 | version "2.4.0" 1077 | resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz" 1078 | integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== 1079 | 1080 | tslib@^1.8.1: 1081 | version "1.14.1" 1082 | resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" 1083 | integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== 1084 | 1085 | tsutils@^3.21.0: 1086 | version "3.21.0" 1087 | resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz" 1088 | integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== 1089 | dependencies: 1090 | tslib "^1.8.1" 1091 | 1092 | type-check@^0.4.0, type-check@~0.4.0: 1093 | version "0.4.0" 1094 | resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" 1095 | integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== 1096 | dependencies: 1097 | prelude-ls "^1.2.1" 1098 | 1099 | type-fest@^0.20.2: 1100 | version "0.20.2" 1101 | resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" 1102 | integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== 1103 | 1104 | typescript@4.7.4: 1105 | version "4.7.4" 1106 | resolved "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz" 1107 | integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== 1108 | 1109 | uri-js@^4.2.2: 1110 | version "4.4.1" 1111 | resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" 1112 | integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== 1113 | dependencies: 1114 | punycode "^2.1.0" 1115 | 1116 | which@^2.0.1: 1117 | version "2.0.2" 1118 | resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" 1119 | integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== 1120 | dependencies: 1121 | isexe "^2.0.0" 1122 | 1123 | wrappy@1: 1124 | version "1.0.2" 1125 | resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" 1126 | integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== 1127 | 1128 | yallist@^4.0.0: 1129 | version "4.0.0" 1130 | resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" 1131 | integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== 1132 | 1133 | yocto-queue@^0.1.0: 1134 | version "0.1.0" 1135 | resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" 1136 | integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== 1137 | --------------------------------------------------------------------------------