BREAKING CHANGE: complete rebuild of plugin(02182025)

This commit is contained in:
rooyca 2025-02-18 08:40:33 -05:00
parent c44975b95d
commit 37c7dca7e3
35 changed files with 747 additions and 1789 deletions

View file

@ -5,8 +5,8 @@
[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/rooyca/obsidian-api-request?logo=github&color=ee8449&style=flat-square)](https://github.com/rooyca/obsidian-api-request/releases/latest)
<img alt="GitHub Release" src="https://img.shields.io/github/downloads/rooyca/obsidian-api-request/total?logo=github&&color=ee8449&style=flat-square">
[![Inglés](https://img.shields.io/badge/Inglés-8A2BE2)](README.md)
[![Chino](https://img.shields.io/badge/Chino-8A2BE2)](README.zh.md)
[![English](https://img.shields.io/badge/English-8A2BE2)](README.md)
[![中文](https://img.shields.io/badge/中文-8A2BE2)](README.zh.md)
Este plugin para [Obsidian](https://obsidian.md/) permite a los usuarios realizar solicitudes HTTP desde sus notas y mostrar la respuesta en un bloque de código, una ventana modal o pegarla directamente en su documento actual.
@ -34,14 +34,9 @@ Este plugin se puede instalar desde Obsidian.
- [x] Añadir más tipos de solicitudes (POST, PUT, DELETE)
- [x] Añadir soporte para autenticación
- [x] Añadir personalización para la salida modal
- [x] Guardar la respuesta en un archivo
- [x] Eliminar uno a uno del localStorage
- [x] Traducir la documentación
- [ ] Consultas inline de la respuesta de una solicitud
- [ ] Solicitudes predefinidas
- [ ] GUI para bloques de código
- [ ] Añadir la bandera `render` a los bloques de código
- [ ] Traducir la documentación
## ❤️ Patrocinadores

View file

@ -5,13 +5,11 @@
[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/rooyca/obsidian-api-request?logo=github&color=ee8449&style=flat-square)](https://github.com/rooyca/obsidian-api-request/releases/latest)
<img alt="GitHub Release" src="https://img.shields.io/github/downloads/rooyca/obsidian-api-request/total?logo=github&&color=ee8449&style=flat-square">
[![Spanish](https://img.shields.io/badge/Spanish-8A2BE2)](README.es.md)
[![中文](https://img.shields.io/badge/Chinese-8A2BE2)](README.zh.md)
[![Español](https://img.shields.io/badge/Español-8A2BE2)](README.es.md)
[![中文](https://img.shields.io/badge/中文-8A2BE2)](README.zh.md)
This [Obsidian](https://obsidian.md/) plugin enables users to make HTTP requests directly within their notes and display the response in a code-block, modal window, or paste it into their active document.
![req_img](showcase_1.gif)
This [Obsidian](https://obsidian.md/) plugin enables users to make API requests directly within their notes and display the response in a code-block.
## 🚀 Installation
@ -27,24 +25,18 @@ The plugin can be installed from within Obsidian.
## 🛠️ Usage
### [Read the docs](https://rooyca.github.io/obsidian-api-request/)
![showcase](showcase_2.gif)
### [Read documentation here](https://rooyca.github.io/obsidian-api-request/)
## ✅ To-do
- [x] Add more request types (POST, PUT, DELETE)
- [x] Add support for authentication
- [x] Add customization for modal output
- [x] Save response to a file
- [x] Remove one by one from localStorage
- [x] Translate documentation
- [x] Add `render` flag to code-blocks
- [ ] Inline query from response
- [ ] Predefined requests
- [ ] GUI for code-blocks
> Check all changes on [TODOS-v2.md](TODOS-v2.md)
## ❤️ Sponsors
- [ ] Translate (& update) documentation
- [x] Data re-usage (`{{ls.UUID>JSONPath}}` syntax, where `ls` stands for `localStorage`)
- [x] Support for comments using `#` or `//` syntax
- [ ] Inline query from response
- [ ] Add tests (!!!)
- [ ] Re-implement `repeat` flag (repeat requests X times or every X seconds)
<a href="https://github.com/tlwt"><img src="https://github.com/tlwt.png" width="40px" /></a>

View file

@ -5,15 +5,13 @@
[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/rooyca/obsidian-api-request?logo=github&color=ee8449&style=flat-square)](https://github.com/rooyca/obsidian-api-request/releases/latest)
<img alt="GitHub Release" src="https://img.shields.io/github/downloads/rooyca/obsidian-api-request/total?logo=github&&color=ee8449&style=flat-square">
[![Spanish](https://img.shields.io/badge/Spanish-8A2BE2)](README.es.md)
[![Español](https://img.shields.io/badge/Español-8A2BE2)](README.es.md)
[![English](https://img.shields.io/badge/English-8A2BE2)](README.md)
这个[Obsidian](https://obsidian.md/)插件能让用户直接在笔记中进行 HTTP 请求,并在代码块、模式窗口中显示响应,或直接将其粘贴到活动文档中。
![req_img](showcase_1.gif)
## 🚀 Installation
The plugin can be installed from within Obsidian.
@ -30,18 +28,15 @@ The plugin can be installed from within Obsidian.
### [Read the docs](https://rooyca.github.io/obsidian-api-request/)
![showcase](showcase_2.gif)
## ✅ To-do
- [x] Add more request types (POST, PUT, DELETE)
- [x] Add support for authentication
- [x] Add customization for modal output
- [x] Save response to a file
- [ ] Translate documentation
- [x] Data re-usage (`{{ls.UUID>JSONPath}}` syntax, where `ls` stands for `localStorage`)
- [x] Support for comments using `#` or `//` syntax
- [ ] Inline query from response
- [ ] Predefined requests
- [ ] GUI for code-blocks
- [x] Remove one by one from localStorage
- [ ] Add tests (!!!)
- [ ] Re-implement `repeat` flag (repeat requests X times or every X seconds)
- [ ] Re-implement `properties` flag (specifies the frontmatter properties to update with the response)
## ❤️ Sponsors

35
TODOS-v2.md Normal file
View file

@ -0,0 +1,35 @@
# APIRequest v2: Feature Overview
## Features to Retain
- [x] Support for standard HTTP methods: `GET, POST, PUT, DELETE`
- [x] Headers and Body configuration
- [x] Fix quotation marks to support formats beyond double quotes
- [x] Customizable response output using the `jsonpath-plus` library
- [x] Display strings when arrays contain a single element
- [ ] Properties configuration (pending implementation)
- [ ] Support for repeated requests (e.g., at fixed intervals or multiple iterations)
- [x] Single-request execution
- [x] Save responses to a file
- [x] Automatically save responses upon each code block execution
- [x] Integration with `frontmatter` variables using `{{this.VARNAME}}` syntax
- [x] Global variables (Key/Value pairs) managed via settings
## Features to Remove
- [x] Modals (removed for streamlined functionality)
- [x] `res-type` flag (removed)
- [x] `notify-if` flag (removed to reduce complexity)
- [x] `render` flag (removed to focus on core functionality)
- [x] `maketable` (removed to simplify output)
- [x] `format` flag (removed for streamlined functionality)
- [x] Support for non-JSON formats (e.g., MD, XML) — JSON remains the primary focus
## Features to Add
- [ ] `hide` flag ~do we actually want this?~
- [x] `auto-update` flag
- [x] Unique request identifiers (`req-uuid`) (save it in local storage and use it for subsequent requests unless user use the flag `auto-update`)
- [-] Inline queries (rendered exclusively in "read mode")
- [x] Data re-usage (`{{ls.UUID>JSONPath}}` syntax, where `ls` stands for `localStorage`)
- [x] Support for comments using `#` or `//` syntax

BIN
apir.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 901 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

View file

@ -1,10 +1,10 @@
# 👨🏻‍💻 Codeblocks
The `codeblock` is a versatile block that can be used to write code in different languages. In this case, we will use it to make requests.
The `codeblock` is a versatile block that can be used to write code in different languages. In this case, we will use it to make API requests.
## 🏳️ Flags
Flags are the way to specify the parameters of our request and also the format in which we want our response.
Flags are the way to specify the parameters of our request.
| Flag | Default |
| ------------| ---------|
@ -13,23 +13,20 @@ Flags are the way to specify the parameters of our request and also the format i
| [body](#body) | |
| [headers](#headers) | |
| [show](#show) | ALL |
| [format](#format) | `{}` |
| [req-id](#req-id) | req-general |
| [req-uuid](#req-uuid) | req-general |
| [disabled](#disabled) | |
| [req-repeat](#req-repeat) | 1t@1s |
| [notify-if](#notify-if) | |
| [save-to](#save-to) | |
| [save-as](#save-as) | |
| [auto-update](#auto-update) | |
| [format](#format) | |
| [properties](#properties) | |
| [render](#render)| false |
| [res-type](#res-type)| |
| [maketable](#maketable)| |
### url
Is the only **required** flag. It specifies the endpoint of the request. Variables defined in the `frontmatter` can be used.
Is the only **required** flag. It specifies the endpoint of the request.
~~~markdown
```req
# this is just a comment
url: https://jsonplaceholder.typicode.com/users/{{this.id}}
```
~~~
@ -39,7 +36,7 @@ url: https://jsonplaceholder.typicode.com/users/{{this.id}}
### method
Specifies the request method. The default value is `GET` and the available values are:
Specifies the request method. The default value is `GET` and the available methods are:
- GET
- POST
@ -55,21 +52,21 @@ method: post
### body
Specifies the body of the request. The default value is an empty object. The data should be in JSON format with double quotes separating the keys and values with a colon and space. Variables defined in the `frontmatter` can be used.
Specifies the body of the request. The default value is an empty object. The data should be in JSON format, separating key and value with a colon plus space (`, `).
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/posts
method: post
body: {"title": {{this.title}}, "body": "bar", "userId": 1}
body: {"title": {{this.filename}}, "body": "bar", "userId": 1}
```
~~~
!!! note "Where `{{this.title}}` is a variable (`title`) defined in the frontmatter."
!!! note "Where `{{this.filename}}` is the name of the working file."
### headers
Specifies the headers of the request. The default value is an empty object. The data should be in JSON format with double quotes separating the keys and values with a colon and space. Variables defined in the `frontmatter` can be used.
Specifies the headers of the request. The default value is an empty object. The data should be in JSON format, separating key and value with a colon plus space (`, `).
~~~markdown
```req
@ -81,117 +78,90 @@ headers: {"Content-type": "application/json; charset=UTF-8"}
### show
Specifies the response data to display. Accessing nested objects is done using a right arrow `->`. The default value is `ALL`.
Specifies the response data to display. See [JSONPath examples](https://github.com/JSONPath-Plus/JSONPath?tab=readme-ov-file#syntax-through-examples), or try the online tool by [jsonpath-plus](https://jsonpath-plus.github.io/JSONPath/demo/).
~~~markdown
```req
url: https://api.chess.com/pub/player/hikaru/stats
show: chess_daily -> last -> rating
show: $['chess_daily']['last']['rating']
```
~~~
Multiple outputs can be displayed by separating them with a comma.
Multiple outputs can be displayed by using `[]`.
~~~markdown
```req
url: https://api.chess.com/pub/player/hikaru/stats
show: chess_daily -> last -> rating, chess_daily -> best -> rating
format: <p>Last game: {}</p> <strong>Best game: {}</strong>
render
show: $.chess_daily[last,best].rating
```
~~~
Looping over an array is also possible using `{..}`. The following example retrieves the city from all users.
Looping over an array is also possible. The following example retrieves the city from all users.
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/users
show: {..} -> address -> city
show: $..address.city
```
~~~
Looping over a specified number of elements of the array is also possible using `{n..n}`.
Looping over a specified number of elements of the array is also possible.
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/users
show: {0..2} -> address -> city
show: $..[:3].address.city
```
~~~
It's also possible to loop over a specified range of indexes of the array using `{n-n-n}`.
It's also possible to loop over a specified range of indexes of the array.
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/users
show: {0-2-1} -> address -> city
show: $..[3,2,6].address.city
```
~~~
You can access the last element using `{-1}`...
You can access the last element using `(@.length-1)` or just `[-1:]`.
~~~markdown
```req
url:https://api.modrinth.com/v2/project/distanthorizons
show: game_versions -> {-1}
url: https://api.modrinth.com/v2/project/distanthorizons
show: $.game_versions[(@.length-1)]
```
~~~
... or get the length of the array using `{len}`.
~~~markdown
```req
url:https://api.modrinth.com/v2/project/distanthorizons
show: game_versions -> {len}
```
~~~
To access multiple elements at the same time when using `{..}` use `&` to separate the keys and use `.` to access the values.
To access multiple elements at the same time.
~~~markdown
```req
url: http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=rooyca&api_key=API_KEY&format=json&limit=4
show: recenttracks -> track -> {..} -> name & artist.#text & streamable
maketable: name, artist, stream
show: $..recenttracks.track[0:][streamable,name,artist]
```
~~~
### format
Specifies the format in which the response should be displayed. The default value is `{}`. It can be any string (including `markdown` and `html`). If more than one output is specified, more then one format should be specified, otherwise, the same format will be applied to all outputs.
### req-uuid
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/posts/1
show: title, body
format: <h1>{}</h1> <p>{}</p>
render
```
~~~
!!! note "In this example, first `{}` will be replaced by the title, and second `{}` will be replaced by the body."
### req-id
Specifies the id of the request. The default value is `req-general`. This is useful when we want to store the response in `localStorage` and use it in other blocks or notes.
Specifies the unique identifier of the request. This is useful when we want to store the response in `localStorage` and use it in other blocks or notes.
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/users/1
show: name
req-id: name
show: $.name
req-uuid: name
```
~~~
Stored responses can be accessed using the `req-id` with the `disabled` flag (which won't trigger a new request).
Stored responses can be accessed using the `req-uuid` (which won't trigger a new request).
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/users/1
req-id: name
disabled
req-uuid: name
```
~~~
@ -199,11 +169,11 @@ Responses can also be accessed using [dataview](https://blacksmithgu.github.io/d
~~~markdown
```dataview
dv.paragraph(localStorage.getItem("req-name"))
dv.paragraph(localStorage.getItem("req-UUID"))
```
~~~
!!! info "Is mandatory to use `req-` before whatever you defined in `req-id` flag."
!!! info "Is mandatory to use `req-` before whatever you defined in `req-uuid` flag."
To remove responses from localStorage, run:
@ -213,112 +183,69 @@ localStorage.removeItem("req-name")
```
~~~
To remove all responses, go to settings and click on the `Clear ID's` button.
To remove responses, go to settings and click over the response you want to delete.
### disabled
Disables the request. If a `req-id` is specified, APIR will check for the response in `localStorage`. If it's not found, it will make a new request and store it. After that, APIR will use the stored response.
Disables the request. The codeblock won't be executed.
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/users/1
show: name
req-id: name
show: $.name
req-uuid: name
disabled
```
~~~
### req-repeat
!!! warning "This only works with JSON responses"
Specifies the number of times the request should be repeated and the interval between each repetition. The default value is `1@1` (read as `X time(s) every X second(s)`).
~~~markdown
```req
url: api.coincap.io/v2/rates/bitcoin
req-repeat: 5@5
render
```
~~~
### notify-if
!!! warning "This only works with JSON responses"
Specifies the condition to trigger a notification. Can be used to monitor a specific value. The path syntax used to access nested objects varies from the `show` flag, here dots are used instead of arrows and not spaces are allowed in the path.
~~~markdown
```req
url: api.coincap.io/v2/rates/bitcoin
req-repeat: 5@5
notify-if: data.rateUsd < 69889
render
```
~~~
!!! note "In the example above, a notification will be triggered everytime the value of `data.rateUsd` is less than `69889`."
### save-to
### save-as
Specifies the path to save the response. It'll save the entire response. A file extension is required. It won't create directories.
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/posts/1
save-to: posts/1.json
save-as: posts/1.json
```
~~~
### properties
## auto-update
If present, the codeblock will automatically update the response every time is possible. This is only needed when using the flag `req-uuid`, because the default behavior of the codeblock is to run every time the note is loaded.
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/posts/1
req-uuid: firstPost
auto-update
save-as: posts/1.json
```
~~~
## format
Specifies the format in which the response should be displayed. It can be any string (including `html`). If more than one output is specified, more then one format should be specified, otherwise it'd just render the first output.
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/posts/1
show: $.[title,body]
format: <h1>{}</h1> <p>{}</p>
```
~~~
!!! note "In this example, first `{}` will be replaced with the *title*, and second `{}` will be replaced with the *body*."
## properties
!!! warning "To use this flag you need a JSON response and the `show` flag"
Specifies the frontmatter properties to update with the response. The data should be strings separated by commas. To set internal links use the `[[..]]` syntax.
Specifies the frontmatter properties to update with the response. The data should be strings separated by commas. To set internal links use the this `[[..]]` syntax.
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/posts/1
show: id, title
show: $.[id,title]
properties: id, title
```
~~~
### render
If present the response will be rendered as HTML. It's useful when the response is an image or a table. The HTML is sanitized to prevent XSS attacks.
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/photos/1
show: url
format: ![img]({})
render
```
~~~
## res-type
Specifies the type of the response. If this flag is not present the plugin will try to guess the type based on the response content-type. This could be used *as an optional fallback feature*.
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/posts/1
res-type: json
```
~~~
## maketable
Converts the response into a table. It's useful when the response is an array of objects. This flags expects a list of titles separated by commas.
~~~markdown
```req
url: http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=rooyca&api_key=API_KEY&format=json&limit=4
show: recenttracks -> track -> {..} -> name & artist.#text & streamable
maketable: name, artist, stream
```
~~~
!!! note "In the example above, the response will be converted into a table with the titles `name`, `artist`, and `stream`."
~~~

View file

@ -1,24 +1,20 @@
# 🔎 Overview
APIRequest (APIR) is a plugin for the note taking app [Obsidian](https://obsidian.md/) that allows you to make requests to apis or other sources and display the response in your notes.
APIRequest (APIR) is a plugin for the note taking app [Obsidian](https://obsidian.md/) that allows you to make requests to apis display the response in your notes.
## 🔥 Features
- Perform HTTP requests using various methods such as `GET`, `POST`, `PUT`, and `DELETE`.
- Receive responses in different formats including JSON, HTML, and Markdown.
- Utilize variables from the front-matter within code blocks.
- Perform requests using various methods such as `GET`, `POST`, `PUT`, and `DELETE`.
- Utilize variables from the `front-matter`, global variables or even reuse responses from another codeblocks.
- Save responses in the `localStorage` for convenient access and reuse.
- Disable code blocks as needed to optimize performance.
- Repeat requests multiple times at specified intervals, facilitating automated tasks or continuous data retrieval without manual intervention.
- Receive notifications when specific values meet predefined conditions, enabling proactive monitoring and alerting.
- Define shortcuts for executing requests, enhancing efficiency and user experience by enabling quick access to frequently used requests.
- Display specific values from responses, providing granular control over the presentation of data.
## ⚡ How to use
### 👨🏻‍💻 Code-block
To use it, create a code-block with the language set to `req`. Inside the code-block, you can specify `url`, `method`, `body`, `headers`, `format`, etc. See the [available flags](codeblocks.md#flags) for more information.
To use it, create a code-block with the language set to `req`. Inside the code-block, you can specify `url`, `method`, `body`, `headers`, etc. See the [available flags](codeblocks.md#flags) for more information.
~~~markdown
```req
@ -26,21 +22,9 @@ url: https://my-json-server.typicode.com/typicode/demo/comments
method: post
body: {"id":1}
headers: {"Accept": "application/json"}
show: id
format: <h1>{}</h1>
req-id: id-persona
show: $.id
req-uuid: IDpersona
disabled
```
~~~
### 🛠️ Settings (don't have all functionalities yet)
!!! info "All parameters can be defined in settings."
Press `Ctrl+P` and search for `APIR`. There are two options:
1. Show response in modal
2. Paste response in current document (at current line)
[More information](settings.md)

View file

@ -1,46 +0,0 @@
# 🛠️ Settings
Using this method is NOT recommended. It is better to use code blocks. This method is very limited and does not support all the features of the code block method.
## ❓ Why use this method
This method is useful when you want to make a quick request or you need to reuse the same request multiple times. You just need to fill the fields, press `Ctrl+P`, search for `APIR` and select the way you want to display the response (Show a modal or paste it into the document).
Another advantage is that you can save the request and assign a `shortcut` to it. This way you can make the request just by pressing the keys you assigned.
## 🏳️ Flags
### name
Specifies the name of the request.
### url (required)
Specifies the URL of the request.
### method
- GET
- POST
- PUT
- DELETE
### body
Specifies the body of the request.
### headers
Specifies the headers of the request.
### what to display
Specifies the output you want.
### Add
Save the above fields into a new APIR (you can access all APIRs by pressing `Ctrl+P` and searching for `APIR`).
### Clear
Remove all IDs stored in `localStorage`. You could also click the APIR name to delete it.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

View file

@ -2,54 +2,23 @@
A collection of use cases for this plugin. **If you have a one, please share it with us.**
## Render Markdown
~~~makdown
```req
url: https://raw.githubusercontent.com/Rooyca/Rooyca/main/README.md
```
~~~
## Check BITCOIN (or any crypto) price
~~~makdown
```req
url: api.coincap.io/v2/rates/bitcoin
show: data -> rateUsd
url: https://api.coincap.io/v2/rates/bitcoin
show: $.data.rateUsd
```
~~~
> 64992.8972508856324769
If we want to repeat this request 100 times every 5 seconds, we can do it like this:
~~~makdown
```req
url: api.coincap.io/v2/rates/bitcoin
show: data -> rateUsd
req-repeat: 100@5
```
~~~
If we want to take this one step further and get notified when the price goes above 65000, we can do it like this:
~~~makdown
```req
url: api.coincap.io/v2/rates/bitcoin
show: data -> rateUsd
req-repeat: 100@5
notify-if: data.rateUsd > 65000
```
~~~
!!! warning "Noted the use of `data.rateUsd` instead of `data -> rateUsd`"
## Get the weather
~~~makdown
```req
url: api.openweathermap.org/data/2.5/weather?q=<CITY>&appid=YOUR_API_KEY
show: main -> temp
url: https://api.openweathermap.org/data/2.5/weather?q=<CITY>&appid=YOUR_API_KEY
show: $.main.temp
```
~~~
@ -58,7 +27,7 @@ show: main -> temp
~~~makdown
```req
url: https://api.themoviedb.org/3/search/movie?query={{this.title}}&api_key=YOUR_API_KEY
show: results -> {..} -> title
show: $.results[0:].title
```
~~~
@ -66,27 +35,23 @@ show: results -> {..} -> title
## Render data
~~~makdown
~~~markdown
```req
url: https://mapi.mobilelegends.com/hero/detail?id={{this.file.name}}
show: data -> cover_picture, data -> name, data -> type
format: ![img]({}) <br> **Name:** {} <br> **Type:** {}
render
url: https://mapi.mobilelegends.com/hero/detail?id=1
show: $.data[cover_picture,name,type]
format: ![img]({}) <br> <strong>Name:</strong> {} <br> <strong>Type:</strong> {}
```
~~~
![data-rendering](./data-rendering.jpg)
## Get TODOS from [todoist](https://todoist.com/)
~~~makdown
```req
url: https://api.todoist.com/rest/v2/tasks
headers: {"Authorization": "Bearer YOUR_TOKEN"}
show: {..} -> content
show: $..content
format: - [ ] {}
req-id: todos
render
```
~~~

View file

@ -13,23 +13,17 @@ Las banderas son la forma de especificar los parámetros de nuestra solicitud y
| [body](#body) | |
| [headers](#headers) | |
| [show](#show) | ALL |
| [format](#format) | `{}` |
| [req-id](#req-id) | req-general |
| [req-uuid](#req-uuid) | |
| [disabled](#disabled) | |
| [req-repeat](#req-repeat) | 1t@1s |
| [notify-if](#notify-if) | |
| [save-to](#save-to) | |
| [properties](#properties) | |
| [render](#render) | false |
| [res-type](#res-type) | |
| [maketable](#maketable)| |
| [save-as](#save-as) | |
### url
Es la única bandera **obligatoria**. Especifica la URL de la solicitud. Se pueden utilizar variables definidas en el `frontmatter`.
~~~markdown
```req
```req
# un comentario
url: https://jsonplaceholder.typicode.com/users/{{this.id}}
```
~~~
@ -55,7 +49,7 @@ method: post
### body
Especifica el cuerpo de la solicitud. El valor predeterminado es un objeto vacío. Los datos deben estar en formato JSON con comillas dobles separando las claves y valores con dos puntos y espacio. Se pueden utilizar variables definidas en el `frontmatter`.
Especifica el cuerpo de la solicitud. El valor predeterminado es un objeto vacío. Los datos deben estar en formato JSON separando las claves y valores con dos puntos y espacio. Se pueden utilizar variables definidas en el `frontmatter`.
~~~markdown
```req
@ -69,7 +63,7 @@ body: {"title": {{this.title}}, "body": "bar", "userId": 1}
### headers
Especifica los encabezados de la solicitud. El valor predeterminado es un objeto vacío. Los datos deben estar en formato JSON con comillas dobles separando las claves y valores con dos puntos y espacio. Se pueden utilizar variables definidas en el `frontmatter`.
Especifica los encabezados de la solicitud. El valor predeterminado es un objeto vacío. Los datos deben estar en formato JSON separando las claves y valores con dos puntos y espacio. Se pueden utilizar variables definidas en el `frontmatter`.
~~~markdown
```req
@ -81,115 +75,88 @@ headers: {"Content-type": "application/json; charset=UTF-8"}
### show
Especifica los datos de respuesta que se van a mostrar. Para acceder a objetos anidados, se utiliza una flecha derecha `->`. El valor predeterminado es `ALL`.
Especifica los datos de respuesta que se van a mostrar. Ver [ejemplos de JSONPath](https://github.com/JSONPath-Plus/JSONPath?tab=readme-ov-file#syntax-through-examples), o prueba la herramienta online de [jsonpath-plus](https://jsonpath-plus.github.io/JSONPath/demo/).
~~~markdown
```req
url: https://api.chess.com/pub/player/hikaru/stats
show: chess_daily -> last -> rating
show: $['chess_daily']['last']['rating']
```
~~~
Se pueden mostrar múltiples salidas separándolas con coma.
Se pueden mostrar múltiples resultados usando `[]`.
~~~markdown
```req
url: https://api.chess.com/pub/player/hikaru/stats
show: chess_daily -> last -> rating, chess_daily -> best -> rating
format: <p>Último juego: {}</p> <strong>Mejor juego: {}</strong>
render
show: $.chess_daily[last,best].rating
```
~~~
También es posible iterar sobre un arreglo usando `{..}`. El siguiente ejemplo muestra la ciudad de todos los usuarios.
También es posible iterar sobre un arreglo. El siguiente ejemplo muestra la ciudad de todos los usuarios.
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/users
show: {..} -> address -> city
show: $..address.city
```
~~~
También es posible iterar sobre un número especificado de elementos del arreglo usando `{n..n}`.
También es posible iterar sobre un número especificado de elementos del arreglo.
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/users
show: {0..2} -> address -> city
show: $..[:3].address.city
```
~~~
También es posible iterar sobre un rango especificado de índices del arreglo usando `{n-n-n}`.
También es posible iterar sobre un rango especificado de índices del arreglo.
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/users
show: {0-2-1} -> address -> city
show: $..[3,2,6].address.city
```
~~~
Puedes acceder al último elemento usando `{-1}`...
Puedes acceder al último elemento usando `(@.length-1)` o simplemente `[-1:]`.
~~~markdown
```req
url:https://api.modrinth.com/v2/project/distanthorizons
show: game_versions -> {-1}
url: https://api.modrinth.com/v2/project/distanthorizons
show: $.game_versions[(@.length-1)]
```
~~~
... o obtener la cantidad de elementos usando `{len}`.
~~~markdown
```req
url:https://api.modrinth.com/v2/project/distanthorizons
show: game_versions -> {len}
```
~~~
To access multiple elements at the same time when using `{..}` use `&` to separate the keys and use `.` to access the values.
Para acceder a multiples resultados podemos usar:
~~~markdown
```req
url: http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=rooyca&api_key=API_KEY&format=json&limit=4
show: recenttracks -> track -> {..} -> name & artist.#text & streamable
maketable: name, artist, stream
show: $..recenttracks.track[0:][streamable,name,artist]
```
~~~
### format
### req-uuid
Especifica el formato en el que se debe mostrar la respuesta. El valor predeterminado es `{}`. Puede ser cualquier cadena (incluyendo `markdown` y `html`). Si se especifican más de una salida, se deben especificar más formatos, de lo contrario, se aplicará el mismo formato para todas las salidas.
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/posts/1
show: title, body
format: <h1>{}</h1> <p>{}</p>
render
```
~~~
!!! note "En este ejemplo, primero `{}` será reemplazado por el título, y segundo `{}` será reemplazado por el cuerpo."
### req-id
Especifica el ID con la que se almacenará la solicitud. El valor predeterminado es `req-general`. Esto es útil cuando queremos almacenar la respuesta en `localStorage` y usarla en otros bloques o notas.
Especifica el ID con la que se almacenará la solicitud. Esto es útil cuando queremos almacenar la respuesta en `localStorage` y usarla en otros bloques o notas.
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/users/1
show: name
req-id: name
show: $.name
req-uuid: name
```
~~~
Las respuestas almacenadas se pueden ver usando el `req-id` con la bandera `disabled` (que no activará una nueva solicitud).
Las respuestas almacenadas se pueden ver usando el `req-uuid` con la bandera `disabled` (que no activará una nueva solicitud).
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/users/1
req-id: name
req-uuid: name
disabled
```
~~~
@ -202,7 +169,7 @@ dv.paragraph(localStorage.getItem("req-name"))
```
~~~
!!! info "Es obligatorio usar `req-` antes de lo que sea que hayas definido en la bandera `req-id`."
!!! info "Es obligatorio usar `req-` antes de lo que sea que hayas definido en la bandera `req-uuid`."
Para eliminar respuestas de localStorage, ejecuta:
@ -212,112 +179,41 @@ localStorage.removeItem("req-name")
```
~~~
Para eliminar todas las respuestas, ve a configuraciones y haz clic en el botón `Clear`.
Para eliminar todas las respuestas, ve a configuraciones y haz clic sobre la respuesta que quieras eliminar.
### disabled
Deshabilita la solicitud. Si se especifica un `req-id`, APIR buscará la respuesta en `localStorage`. Si no se encuentra, realizará una nueva solicitud y la almacenará. Después de eso se usará la respuesta recién almacenada.
Deshabilita la solicitud. El codeblock no se ejecutará.
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/users/1
show: name
req-id: name
show: $.name
req-uuid: name
disabled
```
~~~
### req-repeat
!!! warning "Esto solo funciona con respuestas de tipo JSON"
Especifica la cantidad de veces que se debe repetir la solicitud y el intervalo entre cada repetición. El valor predeterminado es `1@1` (leído como `X veces cada X segundo(s)`).
~~~markdown
```req
url: api.coincap.io/v2/rates/bitcoin
req-repeat: 5@5
render
```
~~~
### notify-if
!!! warning "Esto solo funciona con respuestas de tipo JSON"
Especifica la condición para activar una notificación. Puede usarse para monitorear un valor específico. La sintaxis de ruta utilizada para acceder a objetos anidados varía respecto a la bandera `show`, aquí se usan puntos en lugar de flechas y no se permiten espacios en la ruta.
~~~markdown
```req
url: api.coincap.io/v2/rates/bitcoin
req-repeat: 5@5
notify-if: data.rateUsd < 69889
render
```
~~~
!!! note "En el ejemplo anterior, se activará una notificación cada vez que el valor de `data.rateUsd` sea menor que `69889`."
### save-to
### save-as
Especifica la ruta para guardar la respuesta. Guardará toda la respuesta. Se requiere una extensión de archivo. No creará directorios.
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/posts/1
save-to: posts/1.json
save-as: posts/1.json
```
~~~
### properties
### auto-update
!!! warning "Para usar esta bandera necesitas una respuesta de tipo JSON y la bandera `show`"
Especifica las propiedades del frontmatter que se actualizarán con la respuesta. Los datos deben ser cadenas separadas por comas. Para establecer enlaces internos, usa la sintaxis `[[..]]`.
El codeblock se actualizará de manera automatica cada que sea posible. Esto solo es necesario cuando la bandera `req-uuid` está precente, porque el comportamiento predeterminado del codeblock es ejecutarse cada vez que se carga la nota.
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/posts/1
show: id, title
properties: id, title
req-uuid: firstPost
auto-update
save-as: posts/1.json
```
~~~
### render
Si se especifica, la respuesta se renderizará. El valor predeterminado es `false`. Se puede usar para mostrar imágenes, tablas, etc. La respuesta se saneará antes de renderizarla.
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/photos/1
show: url
format: ![img]({})
render
```
~~~
## res-type
Espefica el tipo de respuesta. Si esta bandera no está presente, el plugin intentará adivinar el tipo basado en el tipo de contenido de la respuesta. Esto podría usarse *como una característica opcional de respaldo*.
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/posts/1
res-type: json
```
~~~
## maketable
Convierte la respuesta en una tabla. Es útil cuando la respuesta es un array de objetos. Esta opción espera una lista de títulos separados por comas.
~~~markdown
```req
url: http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=rooyca&api_key=API_KEY&format=json&limit=4
show: recenttracks -> track -> {..} -> name & artist.#text & streamable
maketable: name, artist, stream
```
~~~
!!! note "En el ejemplo anterior, la respuesta se convertirá en una tabla con los títulos `name`, `artist` y `stream`."
~~~

View file

@ -5,20 +5,16 @@ APIRequest (APIR) es un plugin para [Obsidian](https://obsidian.md/) que te perm
## 🔥 Características
- Realiza solicitudes HTTP utilizando varios métodos como `GET`, `POST`, `PUT` y `DELETE`.
- Recibe respuestas en diferentes formatos incluyendo JSON, HTML y Markdown.
- Utiliza variables del front-matter dentro de bloques de código.
- Guarda respuestas en `localStorage` para un acceso y reutilización convenientes.
- Desactiva bloques de código según sea necesario para optimizar el rendimiento.
- Repite solicitudes múltiples veces a intervalos especificados, facilitando tareas automatizadas o la recuperación continua de datos sin intervención manual.
- Recibe notificaciones cuando los valores específicos cumplen condiciones predefinidas, permitiendo monitoreo proactivo y alertas.
- Define combinaciones de teclas para ejecutar solicitudes, mejorando la eficiencia y la experiencia del usuario al permitir acceso rápido a solicitudes frecuentemente utilizadas.
- Muestra valores específicos de las respuestas, proporcionando un control detallado sobre la presentación de datos.
## ⚡ Cómo usar
### 👨🏻‍💻 Bloque de código
Para usarlo, crea un bloque de código con el lenguaje establecido en `req`. Dentro del bloque de código, puedes especificar `url`, `method`, `body`, `headers`, `format`, etc. Consulta las [banderas disponibles](codeblocks.md#flags) para más información.
Para usarlo, crea un bloque de código con el lenguaje establecido en `req`. Dentro del bloque de código, puedes especificar `url`, `method`, `body`, `headers`, etc. Consulta las [banderas disponibles](codeblocks.md#flags) para más información.
~~~markdown
```req
@ -26,20 +22,8 @@ url: https://my-json-server.typicode.com/typicode/demo/comments
method: post
body: {"id":1}
headers: {"Accept": "application/json"}
show: id
format: <h1>{}</h1>
req-id: id-persona
show: $.id
req-uuid: id-persona
disabled
```
~~~
### 🛠️ Configuraciones (no todas las funcionalidades están disponibles)
!!! info "Todos los parámetros se pueden definir en la configuración."
Presiona `Ctrl+P` y busca `APIR`. Hay dos opciones:
1. Mostrar respuesta en modal
2. Pegar respuesta en el documento actual (en la línea actual)
[Más información](settings.md)
~~~

View file

@ -1,46 +0,0 @@
# 🛠️ Configuraciones
No se recomienda utilizar este método. Es mejor usar bloques de código. Este método es muy limitado y no soporta todas las características del método de bloques de código.
## ❓ ¿Por qué usar este método?
Este método es útil cuando deseas hacer una solicitud rápida o necesitas reutilizar la misma solicitud varias veces. Solo necesitas llenar los campos, presionar `Ctrl+P`, buscar `APIR` y seleccionar la forma en que deseas mostrar la respuesta (Mostrar en un modal o pegarla en el documento).
Otra ventaja es que puedes guardar la solicitud y asignarle un `atajo`. De esta manera, puedes hacer la solicitud simplemente presionando las teclas que hayas asignado.
## 🏳️ Banderas
### name
Especifica el nombre de la solicitud.
### url (requerido)
Especifica la URL de la solicitud.
### method
- GET
- POST
- PUT
- DELETE
### body
Especifica el cuerpo de la solicitud.
### headers
Especifica los encabezados de la solicitud.
### what to display
Especifica la salida deseada.
### Add
Guarda los campos anteriores en un nuevo APIR (puedes acceder a todos los APIRs presionando `Ctrl+P` y buscando `APIR`).
### Clear
Elimina todos los IDs almacenados en `localStorage`. También puedes dar click en el nombre del APIR para eliminarlo.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

View file

@ -3,55 +3,23 @@
A collection of use cases for this plugin. **If you have a one, please share it with us.**
Una colección ejemplos de uso para este plugin. **Si tienes uno, por favor compártelo con nosotros.**
## Renderizar Markdown
~~~makdown
```req
url: https://raw.githubusercontent.com/Rooyca/Rooyca/main/README.md
```
~~~
## Ver el precio de BITCOIN (o cualquier criptomoneda)
~~~makdown
```req
url: api.coincap.io/v2/rates/bitcoin
show: data -> rateUsd
url: https://api.coincap.io/v2/rates/bitcoin
show: $.data.rateUsd
```
~~~
> 64992.8972508856324769
Si queremos repetir esta solicitud 100 veces cada 5 segundos, podemos hacerlo de la siguiente manera:
~~~makdown
```req
url: api.coincap.io/v2/rates/bitcoin
show: data -> rateUsd
req-repeat: 100@5
```
~~~
Si deseamos llevar esto un paso más allá y recibir una notificación cuando el precio supere los 65000, podemos hacerlo de la siguiente manera:
~~~makdown
```req
url: api.coincap.io/v2/rates/bitcoin
show: data -> rateUsd
req-repeat: 100@5
notify-if: data.rateUsd > 65000
```
~~~
!!! warning "Ten presente el uso de `data.rateUsd` en lugar de `data -> rateUsd`"
## Obtener el clima
~~~makdown
```req
url: api.openweathermap.org/data/2.5/weather?q=<CITY>&appid=YOUR_API_KEY
show: main -> temp
url: https://api.openweathermap.org/data/2.5/weather?q=<CITY>&appid=YOUR_API_KEY
show: $.main.temp
```
~~~
@ -60,35 +28,29 @@ show: main -> temp
~~~makdown
```req
url: https://api.themoviedb.org/3/search/movie?query={{this.title}}&api_key=YOUR_API_KEY
show: results -> {..} -> title
show: $.results[0:].title
```
~~~
!!! info "Nota el uso de `{{this.title}}`. Esta es una característica que te permite pasar propiedades del front-matter."
## Renderizar datos
## Obtener más de un resultado
~~~makdown
```req
url: https://mapi.mobilelegends.com/hero/detail?id={{this.file.name}}
show: data -> cover_picture, data -> name, data -> type
format: ![img]({}) <br> **Name:** {} <br> **Type:** {}
render
show: $.data[cover_picture,name,type]
```
~~~
![data-rendering](./data-rendering.jpg)
## Obtener Tareas de [todoist](https://todoist.com/)
~~~makdown
```req
url: https://api.todoist.com/rest/v2/tasks
headers: {"Authorization": "Bearer YOUR_TOKEN"}
show: {..} -> content
format: - [ ] {}
req-id: todos
render
show: $..content
req-uuid: todos
```
~~~

View file

@ -6,5 +6,4 @@
> [![Español](https://img.shields.io/badge/Español-8A2BE2)](es/index.md)
> [![English](https://img.shields.io/badge/English-8A2BE2)](en/index.md)
![req_img](https://raw.githubusercontent.com/Rooyca/obsidian-api-request/master/showcase_1.gif)
> [![中文](https://img.shields.io/badge/中文-8A2BE2)](zh/index.md)

View file

@ -13,16 +13,9 @@ Flag是指定请求参数以及我们想要的响应格式的方式。
| [body](#body) | |
| [headers](#headers) | |
| [show](#show) | ALL |
| [format](#format) | `{}` |
| [req-id](#req-id) | req-general |
| [disabled](#disabled) | |
| [req-repeat](#req-repeat) | 1t@1s |
| [notify-if](#notify-if) | |
| [save-to](#save-to) | |
| [properties](#properties) | |
| [render](#render)| false |
| [res-type](#res-type)| |
| [maketable](#maketable)| |
| [save-as](#save-as) | |
### url
@ -155,21 +148,6 @@ maketable: name, artist, stream
```
~~~
### format
指定响应应以何种格式显示。默认值为 `{}`。它可以是任何字符串(包括 `markdown``html`)。如果指定了多个输出,则应指定多种格式,否则,所有输出将应用相同的格式。
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/posts/1
show: title, body
format: <h1>{}</h1> <p>{}</p>
render
```
~~~
!!! warning "在此示例中,第一个 `{}` 将被标题替换,第二个 `{}` 将被正文替换。"
### req-id
指定请求的 ID。默认值为 `req-general`。当我们想要将响应存储在 `localStorage` 中并在其他块或注释中使用它时,这很有用。
@ -225,96 +203,13 @@ disabled
```
~~~
### req-repeat
!!! warning "这仅适用于 JSON 响应"
指定应重复请求的次数以及每次重复之间的间隔。默认值为 `1@1`(读作 `X time(s) every X second(s)` (每 X 秒 X 次))。
~~~markdown
```req
url: api.coincap.io/v2/rates/bitcoin
req-repeat: 5@5
render
```
~~~
### notify-if
!!! warning "这仅适用于 JSON 响应"
指定触发通知的条件。可用于监视特定值。用于访问嵌套对象的路径语法与 `show` 标志不同,这里使用点代替箭头,并且路径中不允许有空格。
~~~markdown
```req
url: api.coincap.io/v2/rates/bitcoin
req-repeat: 5@5
notify-if: data.rateUsd < 69889
render
```
~~~
!!! note “在上面的例子中,每次 `data.rateUsd` 的值小于 `69889` 时都会触发通知。”
### save-to
### save-as
指定保存响应的路径。它将保存整个响应。需要文件扩展名。它不会创建目录。
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/posts/1
save-to: posts/1.json
save-as: posts/1.json
```
~~~
### properties
!!! warning "要使用此标志,您需要 JSON 响应和 `show` 标志"
指定要使用响应更新的前置内容属性。数据应该是用逗号分隔的字符串。要设置内部链接,请使用 `[[..]]` 语法。
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/posts/1
show: id, title
properties: id, title
```
~~~
### render
如果存在,响应将呈现为 HTML。当响应是图像或表格时它很有用。HTML 经过清理以防止 XSS 攻击。
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/photos/1
show: url
format: ![img]({})
render
```
~~~
## res-type
指定响应的类型。如果不存在此标志,插件将尝试根据响应内容类型猜测类型。这可以用作*可选的后备功能*。
~~~markdown
```req
url: https://jsonplaceholder.typicode.com/posts/1
res-type: json
```
~~~
## maketable
将响应转换为表格。当响应是对象数组时,它很有用。此标志需要以逗号分隔的标题列表。
~~~markdown
```req
url: http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=rooyca&api_key=API_KEY&format=json&limit=4
show: recenttracks -> track -> {..} -> name & artist.#text & streamable
maketable: name, artist, stream
```
~~~
!!! note "在上面的示例中,响应将转换为带有标题 `name`, `artist`, 和 `stream` 的表格。"

View file

@ -5,20 +5,16 @@ APIRequest (APIR) 是笔记应用 [Obsidian](https://obsidian.md/) 的一个插
## 🔥 功能
- 使用各种方法执行 HTTP 请求,例如 `GET`、`POST`、`PUT` 和 `DELETE`
- 接收不同格式的响应,包括 JSON、HTML 和 Markdown。
- 在代码块内利用前言中的变量。
- 将响应保存在 `localStorage` 中,以方便访问和重用。
- 根据需要禁用代码块以优化性能。
- 以指定的间隔多次重复请求,促进自动化任务或连续数据检索而无需人工干预。
- 当特定值满足预定义条件时接收通知,从而实现主动监控和警报。
- 定义执行请求的快捷方式,通过快速访问常用请求来提高效率和用户体验。
- 显示响应中的特定值,提供对数据呈现的精细控制。
## ⚡ 如何使用
### 👨🏻‍💻 代码块
要使用它,请创建一个代码块,并将语言设置为 `req`。在代码块内,您可以指定`url`、`method`、`body`、`headers`、`format`等。有关更多信息,请参阅[可用标志](codeblocks.md#flags)。
要使用它,请创建一个代码块,并将语言设置为 `req`。在代码块内,您可以指定`url`、`method`、`body`、`headers` 等。有关更多信息,请参阅[可用标志](codeblocks.md#flags)。
~~~markdown
```req
@ -26,20 +22,8 @@ url: https://my-json-server.typicode.com/typicode/demo/comments
method: post
body: {"id":1}
headers: {"Accept": "application/json"}
show: id
format: <h1>{}</h1>
req-id: id-persona
show: $.id
req-uuid: id-persona
disabled
```
~~~
### 🛠️ 设置(尚未拥有所有功能)
!!! info "所有参数都可以在设置中定义"
`Ctrl+P` 并搜索 `APIR`。有两个选项:
1. 在模态中显示响应
2. 将响应粘贴到当前文档中(在当前行)
[更多信息](settings.md)

View file

@ -1,46 +0,0 @@
# 🛠️ 设置
不推荐使用这种方法。最好使用代码块。这种方法非常有限,不支持代码块方法的所有功能。
## ❓ Why use this method
当您想要快速发出请求或需要多次重复使用同一请求时,此方法非常有用。您只需填写字段,按 `Ctrl+P`,搜索 `APIR`,然后选择您想要显示响应的方式(显示模式或将其粘贴到文档中)。
另一个优点是您可以保存请求并为其分配 `快捷方式`。这样,您只需按下您分配的键即可发出请求。
## 🏳️ 标志
### name
指定请求的名称
### url (必须)
指定请求的 URL
### method
- GET
- POST
- PUT
- DELETE
### body
指定请求的正文
### headers
指定请求的标头
### what to display
指定您想要的输出
### Add
将上述字段保存到新的 APIR 中(您可以通过按 `Ctrl+P` 并搜索 `APIR` 来访问所有 APIR
### Clear
删除存储在 `localStorage` 中的所有 ID。您也可以单击 APIR 名称来删除它。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

View file

@ -2,54 +2,24 @@
此插件的用例集合。 **如果您有,请与我们分享。**
## 渲染 Markdown
~~~makdown
```req
url: https://raw.githubusercontent.com/Rooyca/Rooyca/main/README.md
```
~~~
## 检查比特币(或任何加密货币)价格
~~~makdown
```req
url: api.coincap.io/v2/rates/bitcoin
show: data -> rateUsd
url: https://api.coincap.io/v2/rates/bitcoin
show: $.data.rateUsd
```
~~~
> 64992.8972508856324769
如果我们想每 5 秒重复此请求 100 次,我们可以这样做:
~~~makdown
```req
url: api.coincap.io/v2/rates/bitcoin
show: data -> rateUsd
req-repeat: 100@5
```
~~~
如果我们想更进一步,在价格超过 65000 时收到通知,我们可以这样做:
~~~makdown
```req
url: api.coincap.io/v2/rates/bitcoin
show: data -> rateUsd
req-repeat: 100@5
notify-if: data.rateUsd > 65000
```
~~~
!!! warning "请注意使用 `data.rateUsd` 而不是 `data -> rateUsd`"
## 获取天气
~~~makdown
```req
url: api.openweathermap.org/data/2.5/weather?q=<CITY>&appid=YOUR_API_KEY
show: main -> temp
url: https://api.openweathermap.org/data/2.5/weather?q=<CITY>&appid=YOUR_API_KEY
show: $.main.temp
```
~~~
@ -58,35 +28,30 @@ show: main -> temp
~~~makdown
```req
url: https://api.themoviedb.org/3/search/movie?query={{this.title}}&api_key=YOUR_API_KEY
show: results -> {..} -> title
show: $.results[0:].title
```
~~~
!!! info "请注意使用 `{{this.title}}`。此功能允许您传递前置属性。"
## 渲染数据
## 获得多个结果
~~~makdown
```req
url: https://mapi.mobilelegends.com/hero/detail?id={{this.file.name}}
show: data -> cover_picture, data -> name, data -> type
format: ![img]({}) <br> **Name:** {} <br> **Type:** {}
render
show: $.data[cover_picture,name,type]
```
~~~
![data-rendering](./data-rendering.jpg)
## 从 [todoist](https://todoist.com/) 获取 TODOS
~~~makdown
```req
url: https://api.todoist.com/rest/v2/tasks
headers: {"Authorization": "Bearer YOUR_TOKEN"}
show: {..} -> content
show: $..content
format: - [ ] {}
req-id: todos
render
```
~~~

View file

@ -26,11 +26,14 @@ nav:
- Overview: en/index.md
- Usage:
- codeblocks: en/codeblocks.md
- settings: en/settings.md
- usecase: en/usecase
- Español:
- Inicio: es/index.md
- Uso:
- bloques de código: es/codeblocks.md
- configuraciones: es/settings.md
- ejemplos de uso: es/usecase
- 中文:
- 概述: zh/index.md
- 用法:
- 代码块: zh/codeblocks.md
- 用例: zh/usecase

View file

@ -1,7 +1,7 @@
{
"id": "api-request",
"name": "APIRequest",
"version": "1.4.5",
"version": "2.0.0",
"minAppVersion": "0.15.0",
"description": "Fetch data from APIs or other sources. Responses in JSON, MD or HTML directly in your notes.",
"author": "rooyca",

58
package-lock.json generated
View file

@ -1,13 +1,16 @@
{
"name": "api-request",
"version": "1.4.5",
"version": "2.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "api-request",
"version": "1.4.4",
"version": "1.4.5",
"license": "MIT",
"dependencies": {
"jsonpath-plus": "^10.2.0"
},
"devDependencies": {
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "^5.59.2",
@ -494,6 +497,30 @@
"dev": true,
"peer": true
},
"node_modules/@jsep-plugin/assignment": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz",
"integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==",
"license": "MIT",
"engines": {
"node": ">= 10.16.0"
},
"peerDependencies": {
"jsep": "^0.4.0||^1.0.0"
}
},
"node_modules/@jsep-plugin/regex": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz",
"integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==",
"license": "MIT",
"engines": {
"node": ">= 10.16.0"
},
"peerDependencies": {
"jsep": "^0.4.0||^1.0.0"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -1733,6 +1760,15 @@
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/jsep": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz",
"integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==",
"license": "MIT",
"engines": {
"node": ">= 10.16.0"
}
},
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@ -1754,6 +1790,24 @@
"dev": true,
"peer": true
},
"node_modules/jsonpath-plus": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.2.0.tgz",
"integrity": "sha512-T9V+8iNYKFL2n2rF+w02LBOT2JjDnTjioaNFrxRy0Bv1y/hNsqR/EBK7Ojy2ythRHwmz2cRIls+9JitQGZC/sw==",
"license": "MIT",
"dependencies": {
"@jsep-plugin/assignment": "^1.3.0",
"@jsep-plugin/regex": "^1.0.4",
"jsep": "^1.4.0"
},
"bin": {
"jsonpath": "bin/jsonpath-cli.js",
"jsonpath-plus": "bin/jsonpath-cli.js"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",

View file

@ -1,6 +1,6 @@
{
"name": "api-request",
"version": "1.4.5",
"version": "2.0.0",
"description": "Fetch data from APIs or other sources. Responses in JSON, MD or HTML directly in your notes.",
"main": "main.js",
"scripts": {
@ -20,5 +20,8 @@
"obsidian": "latest",
"tslib": "2.4.0",
"typescript": "4.7.4"
},
"dependencies": {
"jsonpath-plus": "^10.2.0"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 756 KiB

View file

@ -1,120 +0,0 @@
// JavaScript HTML Sanitizer v2.0.2, (c) Alexander Yumashev, Jitbit Software.
// homepage https://github.com/jitbit/HtmlSanitizer
// License: MIT https://github.com/jitbit/HtmlSanitizer/blob/master/LICENSE
// ADAPTED BY: rooyca (2024-06-16)
'use strict';
class HtmlSanitizer {
constructor() {
this._tagWhitelist = {
'A': true, 'ABBR': true, 'B': true, 'BLOCKQUOTE': true, 'BODY': true, 'BR': true, 'CENTER': true, 'CODE': true, 'DD': true, 'DIV': true, 'DL': true, 'DT': true, 'EM': true, 'FONT': true,
'H1': true, 'H2': true, 'H3': true, 'H4': true, 'H5': true, 'H6': true, 'HR': true, 'I': true, 'IMG': true, 'LABEL': true, 'LI': true, 'OL': true, 'P': true, 'PRE': true,
'SMALL': true, 'SOURCE': true, 'SPAN': true, 'STRONG': true, 'SUB': true, 'SUP': true, 'TABLE': true, 'TBODY': true, 'TR': true, 'TD': true, 'TH': true, 'THEAD': true, 'UL': true, 'U': true, 'VIDEO': true
};
this._contentTagWhiteList = { 'FORM': true, 'GOOGLE-SHEETS-HTML-ORIGIN': true }; // tags that will be converted to DIVs
this._attributeWhitelist = { 'align': true, 'color': true, 'controls': true, 'height': true, 'href': true, 'id': true, 'src': true, 'style': true, 'target': true, 'title': true, 'type': true, 'width': true };
this._cssWhitelist = { 'background-color': true, 'color': true, 'font-size': true, 'font-weight': true, 'text-align': true, 'text-decoration': true, 'width': true };
this._schemaWhiteList = ['http:', 'https:', 'data:', 'm-files:', 'file:', 'ftp:', 'mailto:', 'pw:']; // which "protocols" are allowed in "href", "src" etc
this._uriAttributes = { 'href': true, 'action': true };
this._parser = new DOMParser();
}
SanitizeHtml(input, extraSelector) {
input = input.trim();
if (input === "") return ""; // to save performance
// firefox "bogus node" workaround for wysiwyg's
if (input === "<br>") return "";
if (input.indexOf("<body") === -1) input = "<body>" + input + "</body>"; // add "body" otherwise some tags are skipped, like <style>
let doc = this._parser.parseFromString(input, "text/html");
// DOM clobbering check (damn you firefox)
if (doc.body.tagName !== 'BODY') doc.body.remove();
if (typeof doc.createElement !== 'function') doc.createElement.remove();
const makeSanitizedCopy = (node) => {
let newNode;
if (node.nodeType === Node.TEXT_NODE) {
newNode = node.cloneNode(true);
} else if (node.nodeType === Node.ELEMENT_NODE && (this._tagWhitelist[node.tagName] || this._contentTagWhiteList[node.tagName] || (extraSelector && node.matches(extraSelector)))) { // is tag allowed?
if (this._contentTagWhiteList[node.tagName])
newNode = doc.createElement('DIV'); // convert to DIV
else
newNode = doc.createElement(node.tagName);
for (let i = 0; i < node.attributes.length; i++) {
let attr = node.attributes[i];
if (this._attributeWhitelist[attr.name]) {
if (attr.name === "style") {
for (let s = 0; s < node.style.length; s++) {
let styleName = node.style[s];
if (this._cssWhitelist[styleName])
newNode.style.setProperty(styleName, node.style.getPropertyValue(styleName));
}
}
else {
if (this._uriAttributes[attr.name]) { // if this is a "uri" attribute, that can have "javascript:" or something
if (attr.value.indexOf(":") > -1 && !this.startsWithAny(attr.value, this._schemaWhiteList))
continue;
}
newNode.setAttribute(attr.name, attr.value);
}
}
}
for (let i = 0; i < node.childNodes.length; i++) {
let subCopy = makeSanitizedCopy(node.childNodes[i]);
newNode.appendChild(subCopy, false);
}
// remove useless empty spans (lots of those when pasting from MS Outlook)
if ((newNode.tagName === "SPAN" || newNode.tagName === "B" || newNode.tagName === "I" || newNode.tagName === "U") && newNode.innerHTML.trim() === "") {
return doc.createDocumentFragment();
}
} else {
newNode = doc.createDocumentFragment();
}
return newNode;
};
let resultElement = makeSanitizedCopy(doc.body);
return resultElement.innerHTML
.replace(/<br[^>]*>(\S)/g, "<br>\n$1")
.replace(/div><div/g, "div>\n<div"); // replace is just for cleaner code
}
startsWithAny(str, substrings) {
for (let i = 0; i < substrings.length; i++) {
if (str.indexOf(substrings[i]) === 0) {
return true;
}
}
return false;
}
get AllowedTags() {
return this._tagWhitelist;
}
get AllowedAttributes() {
return this._attributeWhitelist;
}
get AllowedCssStyles() {
return this._cssWhitelist;
}
get AllowedSchemas() {
return this._schemaWhiteList;
}
}
export const sanitizer = new HtmlSanitizer();
export default HtmlSanitizer;

View file

@ -1,10 +1,3 @@
import { Notice, requestUrl, Editor } from 'obsidian';
// Saves the response to the localStorage
export function saveToID(reqID: string, reqText: string) {
localStorage.setItem(reqID, reqText);
}
// Adds the copy button to the code block
// Take from: https://github.com/jdbrice/obsidian-code-block-copy/
export function addBtnCopy(el: HTMLElement, copyThis: string) {
@ -20,66 +13,4 @@ export function addBtnCopy(el: HTMLElement, copyThis: string) {
btnCopy.innerText = 'Error';
});
});
}
// When more than one {} is defined in "format"
// it will loop through the responses and replace the {} with the respective value
export function replaceOrder(stri: string, val) {
let index = 0;
let replaced = stri.replace(/{}/g, function () {
return val[index++];
});
while (val.length > index) {
if (val[index] === undefined) break;
replaced += "\n" + stri.replace(/{}/g, val[index++]);
}
return replaced;
}
// Check if the user wants a nested response
// In other words, if "->" is present in "show"
export function nestedValue(data, key: string) {
const keySplit: string[] = key.split("->").map((item) => item.trim());
let value = data.json;
for (let i = 0; i < keySplit.length; i++) {
if (value === undefined) {
return undefined;
}
value = value[keySplit[i]];
}
if (typeof value === "object" && !Array.isArray(value)) {
value = JSON.stringify(value, null, 2);
}
return value;
}
// Paste the response to the editor
export function toDocument(requestOptions: object, DataResponse: string, editor: Editor) {
requestUrl(requestOptions)
.then((data) => {
if (DataResponse !== "") {
const DataResponseArray = DataResponse.split(",");
for (let i = 0; i < DataResponseArray.length; i++) {
const key = DataResponseArray[i].trim();
let value = JSON.stringify(data.json[key]);
if (key.includes("->")) {
value = nestedValue(data, key);
value = JSON.stringify(value);
}
editor.replaceSelection(value);
}
} else {
editor.replaceSelection("<div>\n" + `${data.text}\n` + "</div>\n");
}
})
.catch((error: Error) => {
console.error(error);
new Notice("Error: " + error.message);
});
}

View file

@ -1,75 +0,0 @@
// Pretty simple markdown parser
export class MarkdownParser {
parse(text) {
// Headers
text = text.replace(/^(#{1,6})\s*(.*)$/gm, (match, p1, p2) => `<h${p1.length}>${p2}</h${p1.length}>`);
// Todo Items
text = text.replace(/^- \[ \](.*)$/gm, '<li class="todo-li"><input type="checkbox">$1</li>');
text = text.replace(/^- \[x\](.*)$/gm, '<li class="todo-li"><input type="checkbox" checked>$1</li>');
// Unordered Lists
text = text.replace(/^\s*-\s*(.*)$/gm, '<li>$1</li>');
//text = text.replace(/^\s*<li>(.*?)<\/li>\s*$/gm, '<ul>$1</ul>');
// Ordered Lists
//text = text.replace(/^\s*\d+\.\s*(.*)$/gm, '<li>$1</li>');
//text = text.replace(/^\s*<li>(.*?)<\/li>\s*$/gm, '<ol>$1</ol>');
// Bold
text = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
// Italic
text = text.replace(/\*(.*?)\*/g, '<em>$1</em>');
// Images
text = text.replace(/!\[([^\]]+)\]\(([^)]+)\)/g, '<img src="$2" alt="$1">');
// Links
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
// Blockquotes
text = text.replace(/^\s*>\s*(.*)$/gm, '<blockquote>$1</blockquote>');
// Code blocks
text = text.replace(/`([^`]+)`/g, '<code>$1</code>');
// Markdown Tables
text = this.mdTables(text);
return text;
}
mdTables(text) {
let table = '';
let regCheckPipe = /(\|)/gi;
text = text.trim();
if (text.match(regCheckPipe)) {
let rows = text.split('\n');
let header = rows.shift();
let headerCells = header.split('|').map(cell => cell.trim()).filter(cell => cell);
let tableStart = '<table>\n<thead>\n';
let tableEnd = '</thead>\n';
let bodyStart = '<tbody>\n';
let bodyEnd = '</tbody>\n';
let rowStart = '<tr>\n';
let rowEnd = '</tr>\n';
let headerRow = rowStart + headerCells.map(cell => `<th>${cell}</th>`).join('\n') + rowEnd;
let bodyRows = rows.map(row => {
let cells = row.split('|').map(cell => cell.trim()).filter(cell => cell);
return rowStart + cells.map(cell => `<td>${cell}</td>`).join('\n') + rowEnd;
}).join('\n');
table += tableStart + headerRow + tableEnd + bodyStart + bodyRows + bodyEnd;
table = table.replace(/<td>[-]+<\/td>/g, '');
return table;
} else {
return text;
}
}
}

View file

@ -1,67 +1,141 @@
// ⚠️ MESSY CODE AHEAD ⚠️
// PROCEDE UNDER YOUR OWN RISK
// DON'T JUDGE ME... TOO MUCH
// ---------------------------------------------
// == TODO ==
// CLEAN UP THIS MESS
// ---------------------------------------------
import { App, Editor, MarkdownView, Modal, Plugin, Notice, requestUrl, debounce } from 'obsidian';
import { readFrontmatter, parseFrontmatter } from 'src/functions/frontmatterUtils';
import { MarkdownParser } from 'src/functions/mdparse';
import { saveToID, addBtnCopy, replaceOrder, nestedValue, toDocument } from 'src/functions/general';
import {
num_braces_regx,
num_hyphen_regx,
nums_rex,
in_braces_regx,
varname_regx,
no_varname_regx,
key_regx
} from 'src/functions/regx';
import { sanitizer } from 'src/functions/HtmlSanitizer';
import APRSettings from 'src/settings/settingsTab';
import { LoadAPIRSettings, DEFAULT_SETTINGS } from 'src/settings/settingsData';
const parser = new MarkdownParser();
import {
MarkdownView,
Plugin,
Notice,
requestUrl,
debounce,
} from "obsidian";
import {
readFrontmatter,
parseFrontmatter,
} from "src/functions/frontmatterUtils";
import { addBtnCopy } from "src/functions/general";
import { varname_regx, no_varname_regx, key_regx } from "src/functions/regx";
import APRSettings from "src/settings/settingsTab";
import { JSONPath } from "jsonpath-plus";
import { LoadAPIRSettings, DEFAULT_SETTINGS } from "src/settings/settingsData";
// Get global variables (defined in settings)
export function checkGlobalValue(value: string, settings: LoadAPIRSettings) {
const match = value.match(key_regx);
if (match) {
for (let i = 0; i < match.length; i++) {
const key = match[i].replace(/{{|}}/g, "");
value = value.replace(match[i], settings.KeyValueCodeblocks.find((obj) => obj.key === key)?.value || "");
value = value.replace(
match[i],
settings.KeyValueCodeblocks.find((obj) => obj.key === key)
?.value || "",
);
}
}
return value;
}
// Get data from localStorage using this syntax: {{ls.UUID>JSONPath}}
// where `ls` stands for `localStorage`
export function checkLocalStorage(value: string) {
const match = value.match(key_regx);
// Checks if the frontmatter is present in the request property
// If it is, it will replace the variable (this.VAR) with the frontmatter value
export function checkFrontmatter(req_prop: string, settings: LoadAPIRSettings) {
if (match) {
for (let i = 0; i < match.length; i++) {
const key = match[i].replace(/{{|}}/g, "");
let uuid = key.split(">")[0];
const jsonPath = key.split(">")[1];
uuid = uuid.split(".")[1]
const data = localStorage.getItem(uuid);
if (data) {
const parsedData = JSON.parse(data);
const output = JSONPath({ path: jsonPath, json: parsedData });
value = value.replace(match[i], output);
}
}
}
return value;
}
// parse headers and body to valid JSON
export function parseToValidJson(input, type) {
const trimmedInput = input.trim();
if (!trimmedInput) {
return null;
}
try {
// Replace single quotes with double quotes and ensure keys/values are properly quoted
const formattedInput = trimmedInput
.replace(/"/g, "") // Remove all quotes
.replace(/'/g, '"') // Replace single quotes with double quotes
.replace(
/\s*([^,{"]+):\s*([^,"}]+)/g,
'"$1":"$2"',
); // Add double quotes around unquoted keys and values
return JSON.parse(formattedInput);
} catch (e) {
throw new Error(
`Invalid ${type} format. Details: ${e.message}`,
);
}
}
// format the output to be displayed
export function formatOutput(output: string): string {
// If output is Array
if (Array.isArray(output)) {
// If it's an Array of one element, format that element
if (output.length === 1) {
return formatOutput(output[0]);
}
// If it's an array of multiples elements, use resistivity
return output
.map((item) => formatOutput(item))
.join(", ");
}
// If output it's an object, convert it to string
if (typeof output === "object" && output !== null) {
return JSON.stringify(output, null, 2);
}
// Any other case, convert it to string
return String(output ?? "");
}
// Check if the value has variables and replace them
export function checkVariables(req_prop: string, settings: LoadAPIRSettings) {
// search value in localStorage
req_prop = checkLocalStorage(req_prop);
// search value globally
req_prop = checkGlobalValue(req_prop, settings);
const match = req_prop.match(varname_regx);
if (match) {
for (let i = 0; i < match.length; i++) {
const var_name = match[i].replace(no_varname_regx, "");
// if {{this.file.name}} return filename
if (var_name == "file.name") {
req_prop = req_prop.replace(match[i], this.app.workspace.getActiveFile().basename);
req_prop = req_prop.replace(
match[i],
this.app.workspace.getActiveFile().basename,
);
continue;
}
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
const activeView =
this.app.workspace.getActiveViewOfType(MarkdownView);
const markdownContent = activeView.editor.getValue();
try {
const frontmatterData = parseFrontmatter(readFrontmatter(markdownContent));
req_prop = req_prop.replace(match[i], frontmatterData[var_name] || "");
const frontmatterData = parseFrontmatter(
readFrontmatter(markdownContent),
);
req_prop = req_prop.replace(
match[i],
frontmatterData[var_name] || "",
);
} catch (e) {
console.error(e.message);
new Notice("Error: " + e.message);
@ -76,542 +150,349 @@ export default class MainAPIR extends Plugin {
settings: LoadAPIRSettings;
async onload() {
console.log('loading APIR');
console.log("loading APIR");
await this.loadSettings();
async function updateStatusBar() {
const statusbar = document.getElementsByClassName("status-bar-item plugin-api-request");
const statusbar = document.getElementsByClassName(
"status-bar-item plugin-api-request",
);
while (statusbar[0]) {
statusbar[0].parentNode?.removeChild(statusbar[0]);
}
// count the number of code-blocks
const markdownContent = this.app.workspace.getActiveViewOfType(MarkdownView)?.getViewData();
const markdownContent = this.app.workspace
.getActiveViewOfType(MarkdownView)
?.getViewData();
const codeBlocks = markdownContent.match(/```req/g)?.length || 0;
if (codeBlocks > 0) {
const item = this.addStatusBarItem();
const statusText = this.settings.countBlocksText.replace("%d", codeBlocks.toString());
const statusText = this.settings.countBlocksText.replace(
"%d",
codeBlocks.toString(),
);
item.createEl("span", { text: statusText });
}
}
this.registerEvent(this.app.workspace.on('file-open', debounce(updateStatusBar.bind(this), 300)));
this.registerEvent(this.app.workspace.on('editor-change', updateStatusBar.bind(this)));
this.addCommand({
id: 'show-response-in-modal',
name: 'Show response in Modal',
callback: () => {
new ShowOutputModal(this.app, this.settings.URL, this.settings.MethodRequest, this.settings.DataRequest, this.settings.HeaderRequest, this.settings.DataResponse).open();
}
});
// count number of codeblocks on "file-open" and "changes to the file"
this.registerEvent(
this.app.workspace.on(
"file-open",
debounce(updateStatusBar.bind(this), 300),
),
);
this.registerEvent(
this.app.workspace.on("editor-change", updateStatusBar.bind(this)),
);
try {
this.registerMarkdownCodeBlockProcessor("req", async (source, el) => {
const sourceLines = source.split("\n");
let [URL, show, saveTo, reqID, resType, maketable] = [String(), String(), String(), String(), String(), String()];
let [notifyIf, properties] = [[String()], [String()]];
// 'format' is not and empty objet, is a string that will be replaced by the response
let [method, format] = ["GET", "{}"];
let [headers, body, reqRepeat] = [Object(), Object(), { "times": 1, "every": 1000 }];
const allowedMethods = ["GET", "POST", "PUT", "DELETE"];
let render = false;
this.registerMarkdownCodeBlockProcessor(
"req",
async (source, el) => {
// split the content by lines
const sourceLines = source.split("\n");
// create variables
let [URL, show, saveTo] = [String(), String(), String()];
let properties = [String()];
let uuid;
let autoUpdate = false;
let method = "GET";
let format = String();
let [headers, body] = [Object(), Object()];
const allowedMethods = ["GET", "POST", "PUT", "DELETE"];
let response_disabled;
for (const line of sourceLines) {
// convert line to lowercase
// this way we can check for the keywords without worrying about the case
const lowercaseLine = line.toLowerCase();
for (const line of sourceLines) {
const lowercaseLine = line.toLowerCase();
if (lowercaseLine.includes("method:")) {
method = line.replace(/method:/i, "").toUpperCase();
method = method.trim();
if (!allowedMethods.includes(method)) {
el.createEl("strong", { text: `Error: Method ${method} not supported` });
// comments
if (
lowercaseLine.startsWith("#") ||
lowercaseLine.startsWith("//")
) {
continue;
// return if request is disabled
} else if (lowercaseLine.startsWith("disabled")) {
el.createEl("strong", {
text: this.settings.DisabledReq,
});
return;
}
} else if (lowercaseLine.includes("notify-if:")) {
const tempNotifyIf = line.replace(/notify-if:/i, "");
notifyIf = tempNotifyIf.trim().split(" ");
} else if (lowercaseLine.includes("req-repeat:")) {
const repeat_values: string[] = line.replace(/req-repeat:/i, "").trim().split("@");
// Function to check if a string is a valid integer
const isValidNumber = (value: string): boolean => {
return /^\d+$/.test(value);
};
// get the method and check if is a valid method
} else if (lowercaseLine.startsWith("method:")) {
method = line.replace(/method:/i, "").toUpperCase().trim();
if (!allowedMethods.includes(method)) {
el.createEl("strong", {
text: `Error: Method ${method} not supported`,
});
return;
}
// Check if there are two values and they are valid numbers
if (repeat_values.length === 2 && isValidNumber(repeat_values[0]) && isValidNumber(repeat_values[1])) {
reqRepeat = { "times": parseInt(repeat_values[0]), "every": parseInt(repeat_values[1]) * 1000 };
} else {
el.createEl("strong", { text: "Error: req-repeat format is not valid (use T@S)" });
return;
}
// get the url and *return* if is null
} else if (lowercaseLine.startsWith("url:")) {
URL =
checkVariables(
line.replace(/url:/i, "").trim(),
this.settings,
) ?? "";
if (!URL) {
el.createEl("strong", {
text: "Error: URL not found",
});
return;
}
// extract data using jsonpath-plus (https://www.npmjs.com/package/jsonpath-plus)
} else if (lowercaseLine.startsWith("show:")) {
show =
checkVariables(
line.replace(/show: /i, "").trim(),
this.settings,
) ?? "";
if (!show) {
el.createEl("strong", {
text: "Error: show value is empty",
});
return;
}
// get headers. They can use double, single quotes or none
} else if (lowercaseLine.startsWith("headers:")) {
const tempHeaders =
checkVariables(
line.replace("headers:", "").trim(),
this.settings,
) ?? "";
} else if (lowercaseLine.includes("url:")) {
URL = checkFrontmatter(line.replace(/url:/i, "").trim(), this.settings) ?? "";
if (!URL) {
el.createEl("strong", { text: "Error: URL not found" });
return;
}
if (URL && !URL.startsWith("http")) {
URL = "https://" + URL;
}
} else if (lowercaseLine.includes("show:")) {
show = checkFrontmatter(line.replace(/show:/i, "").trim(), this.settings) ?? "";
if (!show) {
el.createEl("strong", { text: "Error: show value is empty" });
return;
}
} else if (lowercaseLine.includes("headers:")) {
const tempHeaders = checkFrontmatter(line.replace(/headers:/i, ""), this.settings) ?? "";
if (tempHeaders) {
try {
headers = JSON.parse(tempHeaders);
headers = parseToValidJson(
tempHeaders,
"headers",
);
} catch (e) {
el.createEl("strong", { text: "Error: Headers format is not valid" });
el.createEl("strong", { text: e.message });
return;
}
}
} else if (lowercaseLine.includes("body:")) {
body = checkFrontmatter(line.replace(/body:/i, ""), this.settings);
} else if (lowercaseLine.includes("format:")) {
format = line.replace(/format:/i, "");
if (!format.includes("{}")) {
el.createEl("strong", { text: "Error: Use {} to show response in the document." });
return;
}
} else if (lowercaseLine.includes("req-id:")) {
reqID = line.replace(/id:/i, "").trim();
if (sourceLines.includes("disabled")) {
const idExists = localStorage.getItem(reqID);
if (idExists) {
response_disabled = parser.parse(idExists);
} else {
sourceLines.splice(sourceLines.indexOf("disabled"), 1);
// get body. They can use double, single quotes or none
} else if (lowercaseLine.startsWith("body:")) {
const tempBody =
checkVariables(
line.replace("body:", "").trim(),
this.settings,
) ?? "";
try {
body = parseToValidJson(tempBody, "body");
} catch (e) {
el.createEl("strong", { text: e.message });
return;
}
// save the entire JSON to a file. (filename and extension are needed)
} else if (lowercaseLine.startsWith("save-as:")) {
saveTo = line.replace(/save-as:/i, "").trim();
if (!saveTo) {
el.createEl("strong", {
text: "Error: save-as is empty. Please provide a filename with extension",
});
return;
}
} else if (lowercaseLine.startsWith("req-uuid:")) {
uuid = line.replace(/req-uuid:/i, "").trim();
if (!uuid) {
el.createEl("strong", {
text: "Error: req-uuid is empty. Please provide a unique identifier",
});
return;
}
uuid = `req-${uuid}`
} else if (lowercaseLine.startsWith("auto-update")) {
autoUpdate = true;
} else if (lowercaseLine.startsWith("format:")) {
format = line.replace(/format:/i, "").trim();
} else if (lowercaseLine.startsWith("properties:")) {
properties = line
.replace(/properties:/i, "")
.trim()
.split(",");
}
} else if (lowercaseLine.includes("save-to:")) {
saveTo = line.replace(/save-to:/i, "").trim();
if (!saveTo) {
el.createEl("strong", { text: "Error: save-to value is empty. Please provide a filename with extension" });
return;
}
} else if (lowercaseLine.includes("properties:")) {
// remove all spaces and split by comma
properties = line.replace(/properties:/i, "").replace(/\s/g, "").split(",");
} else if (lowercaseLine.includes("res-type:")) {
resType = line.replace(/res-type:/i, "").trim();
} else if (lowercaseLine.includes("maketable:")) {
maketable = line.replace(/maketable:/i, "").trim();
}
}
if (sourceLines.includes("render")) {
render = true;
};
let responseData;
let responseDataText;
for (let i = 0; i < reqRepeat.times; i++) {
try {
let responseData;
if (!response_disabled) {
responseData = await requestUrl({ url: URL, method, headers, body });
} else {
responseData = { json: JSON.parse(response_disabled) };
// Check if the response is cached in localStorage
if (uuid && !autoUpdate) {
const cachedResponse = localStorage.getItem(uuid);
if (cachedResponse) {
responseData = JSON.parse(cachedResponse);
const temp_uuid = uuid.split("req-")[1]
new Notice(`Using cached data with UUID: ${temp_uuid}`);
}
}
// If no cached data or auto-update is requested, make a new request
if (!responseData || autoUpdate) {
try {
// Check if the response is not JSON
if (!responseData.headers["content-type"].includes("json") && resType !== "json") {
try {
el.innerHTML = parser.parse(sanitizer.SanitizeHtml(responseData.text));
} catch (e) {
new Notice("Error: " + e.message);
el.innerHTML = "<pre>" + sanitizer.SanitizeHtml(responseData.text) + "</pre>";
}
const response = await requestUrl({
url: URL,
method,
headers,
body,
});
responseData = await response.json;
responseDataText = response.text;
if (reqID) saveToID(reqID, responseData.text);
addBtnCopy(el, responseData.text);
return;
// Cache the response in localStorage if req-uuid is provided
if (uuid) {
localStorage.setItem(
uuid,
JSON.stringify(responseData),
);
}
} catch (e) {
console.error(e.message);
new Notice("Error: " + e.message);
responseData = `Error: ${e.message}`;
}
}
// Save to a file
if (saveTo) {
try {
await this.app.vault.create(saveTo, responseData.text);
new Notice("Saved to: " + saveTo);
} catch (e) {
console.error(e.message);
new Notice("Error: " + e.message);
}
}
let output = responseData;
if (notifyIf) {
const jsonPath = notifyIf[0];
const symbol = notifyIf[1];
const value = notifyIf[2]
const int_value = parseInt(value);
if (show) {
// Use JSONPath to filter the output based on the `show` path
output = JSONPath({ path: show, json: output });
const jsonPathValue = jsonPath.split(".").reduce((acc, cv) => acc[cv], responseData.json);
const lastValue = jsonPath.split(".").pop();
if (symbol === ">" && jsonPathValue > int_value) {
new Notice("APIR: " + lastValue + " is greater than " + int_value);
} else if (symbol === "<" && jsonPathValue < int_value) {
new Notice("APIR: " + lastValue + " is less than " + int_value);
} else if (symbol === "=" && jsonPathValue === value) {
new Notice("APIR: " + lastValue + " is equal to " + value);
} else if (symbol === ">=" && jsonPathValue >= int_value) {
new Notice("APIR: " + lastValue + " is greater than or equal to " + int_value);
} else if (symbol === "<=" && jsonPathValue <= int_value) {
new Notice("APIR: " + lastValue + " is less than or equal to " + int_value);
}
}
if (properties.length > 0 && properties[0] !== '') {
// Format the output and split it into an array
const stringOutput = formatOutput(output);
const splitOutput = stringOutput.split(",");
if (!show) {
if (properties.length > 0 && properties[0] !== '') {
el.createEl("strong", { text: "Error: Properties are not allowed without SHOW" });
// Get the active Markdown view and its associated file
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!activeView?.file) {
console.error("No active Markdown view or file found.");
return;
}
el.innerHTML = "<pre>" + JSON.stringify(responseData.json, null, 2) + "</pre>";
if (reqID) saveToID(reqID, el.innerText);
addBtnCopy(el, el.innerText);
} else {
const checkBracesRegex = show.match(in_braces_regx);
if (checkBracesRegex) {
if (show.includes(",")) {
el.createEl("strong", { text: "Error: comma is not allowed when using {}" });
return;
}
const file: TFile = activeView.file;
let temp_show = "";
let range: number[] = [];
// Function to update the frontmatter
const updateFrontmatter = async (propertyName: string, value: string) => {
// Handle wikilink formatting if the property name contains [[...]]
const match = propertyName.match(/\[\[(.*?)\]\]/);
const cleanPropertyName = match ? match[1] : propertyName;
try {
const rangeMatch = show.match(nums_rex);
if (rangeMatch) {
range = rangeMatch.map(Number);
if (range[0] > range[1]) {
el.createEl("strong", { text: "Error: range is not valid" });
return;
}
}
} catch (e) {
console.error(e.message);
}
const numberBracesRegex = show.match(num_braces_regx);
if (!numberBracesRegex) {
if (Array.isArray(responseData.json)) {
for (let i = 0; i < responseData.json.length; i++) {
temp_show += show.replace(in_braces_regx, i.toString()) + ", ";
}
show = temp_show;
} else {
const parts = show.split('->').map(part => part.trim());
const processNestedObject = (obj: any, parts: string[]): string => {
let result = "";
const traverse = (current: any, idx: number) => {
if (idx >= parts.length) {
if (typeof current === "object") {
current = JSON.stringify(current, null, 2);
}
if (typeof current === 'string') current = encodeURIComponent(current);
result += current + ", ";
return;
}
const part = parts[idx];
if (part === "{..}") {
if (Array.isArray(current)) {
current.forEach((item, i) => {
traverse(item, idx + 1);
});
} else {
el.createEl("strong", { text: "Error: {..} used on non-array element" });
}
} else if (part == "{-1}") {
result = current[current.length - 1]
} else if (part == "{len}") {
result = current.length.toString()
} else if (part === "{gk}") {
Object.keys(current).forEach((key) => {
traverse(key, idx + 1);
});
} else {
const nextParts = part.split('&').map(p => p.trim());
if (nextParts.length > 1) {
nextParts.forEach(p => {
const subParts = p.split('.').map(sp => sp.trim());
let subCurrent = current;
subParts.forEach((sp, subIdx) => {
if (subCurrent && subCurrent.hasOwnProperty(sp)) {
if (subIdx === subParts.length - 1) {
traverse(subCurrent[sp], idx + 1);
} else {
subCurrent = subCurrent[sp];
}
} else {
el.createEl("strong", { text: `Error: property ${sp} does not exist on current object` });
}
});
});
} else {
const subParts = part.split('.').map(sp => sp.trim());
let subCurrent = current;
subParts.forEach((sp, subIdx) => {
if (subCurrent && subCurrent.hasOwnProperty(sp)) {
if (subIdx === subParts.length - 1) {
traverse(subCurrent[sp], idx + 1);
} else {
subCurrent = subCurrent[sp];
}
} else {
el.createEl("strong", { text: `Error: property ${sp} does not exist on current object` });
}
});
}
}
};
traverse(obj, 0);
return result;
};
temp_show = processNestedObject(responseData.json, parts);
show = temp_show;
}
} else {
for (let i: number = range[0]; i <= range[1]; i++) {
temp_show += show.replace(numberBracesRegex[0], i.toString()) + ", ";
}
show = temp_show;
}
if (show.match(num_hyphen_regx)) {
show = show.replace(in_braces_regx, "-");
for (let i = 0; i < range.length; i++) {
temp_show += show.replace("-", range[i].toString()) + ", ";
}
show = temp_show;
}
}
// adding properties to frontmatter
if (properties.length > 0 && properties[0] !== '') {
const showArray = show.split(",");
const propertiesArray = properties;
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
const file: TFile = activeView!.file;
await Promise.all(showArray.map(async (key, index) => {
const trimmedKey = key.trim();
let val = "";
if (trimmedKey.includes("->")) {
val = nestedValue(responseData, trimmedKey);
} else if (responseData.json && responseData.json[trimmedKey]) {
val = responseData.json[trimmedKey];
}
let propertyName = propertiesArray[index].trim();
if (propertyName) {
const match = propertyName.match(/\[\[(.*?)\]\]/);
if (match) propertyName = match[1];
await this.app.fileManager.processFrontMatter(file, (existingFrontmatter) => {
if (typeof val === "object") {
Object.keys(val).forEach((key) => {
if (match) {
val[key] = "[[" + val[key].toString() + "]]";
} else {
val[key] = val[key].toString();
}
});
}
if (match && typeof val !== "object") {
val = "[[" + val + "]]"
}
existingFrontmatter[propertyName] = val;
});
}
}));
return;
}
const trimAndProcessKey = (key: string) => {
const trimmedKey = key.trim();
return trimmedKey.includes("->")
? nestedValue(responseData, trimmedKey)
: JSON.stringify(responseData.json[trimmedKey]) || trimmedKey;
// Update the frontmatter
await this.app.fileManager.processFrontMatter(file, (existingFrontmatter) => {
existingFrontmatter[cleanPropertyName] = match ? `[[${value}]]` : value;
});
};
const values = show.split(",")
.map(trimAndProcessKey)
.filter((key) => key !== "");
let replacedText = replaceOrder(format, values);
// If there's only one property, assign the entire splitOutput to that property
if (properties.length === 1) {
const propertyName = properties[0]?.trim();
if (replacedText === 'undefined') {
show = show.trim();
if (show[show.length - 1] === ',') show = show.slice(0, -1);
if (!show.includes("->")) replacedText = show;
// Skip if the property name is empty
if (!propertyName) return;
// Update the frontmatter
await updateFrontmatter(propertyName, stringOutput);
} else {
// If there are multiple properties, iterate over them
for (let index = 0; index < properties.length; index++) {
const propertyName = properties[index]?.trim();
// Skip if the property name is empty
if (!propertyName) continue;
// Extract the value from the output
const valueOutput = splitOutput[index] || "";
// Update the frontmatter
await updateFrontmatter(propertyName, valueOutput);
}
}
if (maketable) {
const titles = maketable.split(",");
const table = el.createEl("table");
const thead = table.createEl("thead");
const tbody = table.createEl("tbody");
const trHead = thead.createEl("tr");
// Create table headers
titles.forEach((title) => {
const th = trHead.createEl("th");
th.createEl("strong", { text: title.trim() });
});
// Create table body rows
let trBody = tbody.createEl("tr");
values.forEach((value, index) => {
if (index % titles.length === 0 && index !== 0) {
trBody = tbody.createEl("tr"); // Create a new row after every set of columns
}
const td = trBody.createEl("td");
td.createEl("strong", { text: decodeURIComponent(value) });
});
return;
}
replacedText = decodeURIComponent(replacedText);
!render ? el.createEl("pre", { text: replacedText }) : el.innerHTML = parser.parse(sanitizer.SanitizeHtml(replacedText));
// check if reqID doesnt already exists on localStorage
const idExists = localStorage.getItem(reqID);
if (!idExists) {
if (reqID) saveToID(reqID, JSON.stringify(responseData));
}
addBtnCopy(el, replacedText);
}
} catch (error) {
console.error(error);
el.createEl("strong", { text: "Error: " + error.message });
new Notice("Error: " + error.message);
}
await sleep(reqRepeat.every);
}
return;
});
const formattedOutput = formatOutput(output);
// if a *format* is defined in the codeblock
// render the response, else just *return* the response as String
if (format) {
const parts = formattedOutput.split(",");
el.innerHTML = format.replace(/{}/g, () => parts.shift() || "");
} else {
el.createEl("pre", { text: formattedOutput });
}
// add a button to copy the output
addBtnCopy(el, formattedOutput);
// Save to a file
if (saveTo) {
try {
// try to create the file. It'll fail if already exists
await this.app.vault.create(
saveTo,
responseDataText,
);
new Notice("Saved to: " + saveTo);
} catch (e) {
// try to modify the file
const file =
this.app.vault.getAbstractFileByPath(saveTo);
await this.app.vault.modify(file, responseDataText);
new Notice("File modified");
}
}
},
);
} catch (e) {
console.error(e.message);
new Notice("Error: " + e.message);
}
this.addCommand({
id: 'response-in-document',
name: 'Paste response in current document',
editorCallback: (editor: Editor) => {
const set = this.settings;
const requestOptions = {
url: set.URL,
method: set.MethodRequest,
headers: JSON.parse(set.HeaderRequest),
...(set.MethodRequest !== "GET" && { body: set.DataRequest })
};
toDocument(requestOptions, set.DataResponse, editor);
}
});
// TODO
// Make *Inline queries* using responses from codeblocks
for (let i = 0; i < this.settings.URLs.length; i++) {
this.addCommand({
id: 'response-in-document-' + this.settings.URLs[i]["Name"],
name: 'Response for api: ' + this.settings.URLs[i]["Name"],
editorCallback: (editor: Editor) => {
const rea = this.settings.URLs[i];
toDocument(rea, this.settings.URLs[i]["DataResponse"], editor);
}
});
}
//this.registerMarkdownPostProcessor(async (element, context) => {
// const codeblocks = element.findAll("code");
// for (const codeblock of codeblocks) {
// const text = codeblock.innerText.trim();
//
// const inlineMatches = text.match(/{{(.*?)}}/g);
// if (inlineMatches) {
// inlineMatches.forEach((match) => {
// const varName = match.replace(/{{|}}/g, "").trim();
// const value = localStorage.getItem(varName);
// element.innerHTML = element.innerHTML.replace(match, value);
// });
// }
//}});
this.addSettingTab(new APRSettings(this.app, this));
}
onunload() {
console.log('unloading APIR');
// Clean up localStorage
// Object.keys(localStorage).forEach(key => {
// if (key.startsWith("req-")) {
// localStorage.removeItem(key);
// }
// });
console.log("unloading APIR");
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
this.settings = Object.assign(
{},
DEFAULT_SETTINGS,
await this.loadData(),
);
}
async saveSettings() {
await this.saveData(this.settings);
}
}
class ShowOutputModal extends Modal {
constructor(app: App, URL: string, MethodRequest: string, DataRequest: string, HeaderRequest: string, DataResponse: string) {
super(app);
this.props = {
URL,
MethodRequest,
DataRequest,
HeaderRequest,
DataResponse,
};
}
onOpen() {
const { contentEl } = this;
const { URL, MethodRequest, DataRequest, HeaderRequest, DataResponse } = this.props;
const handleError = (error: Error) => {
console.error(error);
new Notice("Error: " + error.message);
};
const parseAndCreate = (data: object) => (key: string) => {
const value = DataResponse.includes("->") ? nestedValue(data, key) : data[key];
contentEl.createEl('b', { text: key + " : " + JSON.stringify(value, null, 2) });
};
const requestOptions = {
url: URL,
method: MethodRequest,
headers: JSON.parse(HeaderRequest),
...(MethodRequest !== "GET" && { body: DataRequest })
};
requestUrl(requestOptions)
.then((data) => {
if (DataResponse !== "") {
const DataResponseArray = DataResponse.split(",");
DataResponseArray.forEach(parseAndCreate(data));
} else {
contentEl.createEl('b', { text: JSON.stringify(data.json, null, 2) });
}
})
.catch(handleError);
}
onClose() {
const { contentEl } = this;
contentEl.empty();
}
}

View file

@ -1,11 +1,4 @@
export interface LoadAPIRSettings {
URL: string;
MethodRequest: string;
DataRequest: string;
HeaderRequest: string;
DataResponse: string;
URLs: object[];
Name: string;
DisabledReq: string;
Key: string;
Value: string;
@ -14,16 +7,9 @@ export interface LoadAPIRSettings {
}
export const DEFAULT_SETTINGS: LoadAPIRSettings = {
URL: 'https://jsonplaceholder.typicode.com/todos/1',
MethodRequest: 'GET',
DataRequest: '',
HeaderRequest: '{"Content-Type": "application/json"}',
DataResponse: '',
URLs: [],
Name: '',
DisabledReq: 'This request is disabled',
DisabledReq: '>> Disabled <<',
Key: '',
Value: '',
KeyValueCodeblocks: [],
countBlocksText: 'Count blocks: %d',
countBlocksText: '🇦 🇷(%d)',
}

View file

@ -1,6 +1,5 @@
import { PluginSettingTab, Setting, Notice } from 'obsidian';
import MainAPIR from 'src/main';
import { toDocument } from 'src/functions/general';
import { App, PluginSettingTab, Setting } from "obsidian";
import MainAPIR from "src/main";
export default class APRSettings extends PluginSettingTab {
plugin: MainAPIR;
@ -14,212 +13,99 @@ export default class APRSettings extends PluginSettingTab {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl('h2', { text: "APIR Settings" })
const newApir = containerEl.createEl('details');
newApir.createEl('summary', { text: 'Click to Add Request ↴', cls: 'summary-text' });
new Setting(newApir)
.setName('Name')
.setDesc('Name of request')
.addText(text => text
.setPlaceholder('Name')
.setValue(this.plugin.settings.Name)
.onChange(async (value) => {
if (value !== "") {
this.plugin.settings.Name = value;
await this.plugin.saveSettings();
}
}));
new Setting(newApir)
.setName('URL')
.setDesc('Endpoint to fetch data from')
.addText(text => text
.setPlaceholder('URL')
.setValue(this.plugin.settings.URL)
.onChange(async (value) => {
this.plugin.settings.URL = value;
await this.plugin.saveSettings();
}));
new Setting(newApir)
.setName('Method')
.setDesc("Select the desired method")
.addDropdown(dropDown => {
dropDown.addOption("GET", "GET");
dropDown.addOption("POST", "POST");
dropDown.addOption("POST", "PUT");
dropDown.addOption("DELETE", "DELETE");
dropDown.setValue(this.plugin.settings.MethodRequest)
dropDown.onChange(async value => {
this.plugin.settings.MethodRequest = value;
await this.plugin.saveSettings();
});
});
new Setting(newApir)
.setName('Body')
.setDesc("Data to send in the body")
.addTextArea(text => text
.setPlaceholder('{"data":"data"}')
.setValue(this.plugin.settings.DataRequest)
.onChange(async (value) => {
this.plugin.settings.DataRequest = value;
await this.plugin.saveSettings();
}));
new Setting(newApir)
.setName('Headers')
.setDesc("The headers of the request")
.addTextArea(text => text
.setPlaceholder('{"Content-Type": "application/json"}')
.setValue(this.plugin.settings.HeaderRequest)
.onChange(async (value) => {
this.plugin.settings.HeaderRequest = value;
await this.plugin.saveSettings();
}));
new Setting(newApir)
.setName('What to display')
.setDesc("Write the name of the variables you want to show (spaced by comma)")
.addText(text => text
.setPlaceholder('varname')
.setValue(this.plugin.settings.DataResponse)
.onChange(async (value) => {
this.plugin.settings.DataResponse = value;
await this.plugin.saveSettings();
}));
new Setting(newApir)
.addButton(button => {
button.setClass('btn-add-apir');
button.setButtonText('ADD').onClick(async () => {
const { Name } = this.plugin.settings;
if (Name === "") {
new Notice("Name is empty");
return;
}
const { URL, MethodRequest, DataResponse, DataRequest, HeaderRequest } = this.plugin.settings;
const { URLs } = this.plugin.settings;
URLs.push({
'url': URL,
'Name': Name,
'method': MethodRequest,
'body': DataRequest,
'headers': HeaderRequest,
'DataResponse': DataResponse
});
await this.plugin.saveSettings();
this.display();
this.plugin.addCommand({
id: 'response-in-document-' + Name,
name: 'Response for api: ' + Name,
editorCallback: (editor: Editor) => {
const rea = URLs[URLs.length - 1];
toDocument(rea, rea.DataResponse, editor);
}
});
});
});
containerEl.createEl('hr');
containerEl.createEl('h2', { text: 'Codeblock Settings' });
containerEl.createEl("h2", { text: "Codeblocks" });
new Setting(containerEl)
.setName('Text when request is Disabled')
.setName("Text when request is Disabled")
.setDesc("What to show when a request is disabled")
.addText(text => text
.setPlaceholder('This request is disabled')
.setValue(this.plugin.settings.DisabledReq)
.onChange(async (value) => {
this.plugin.settings.DisabledReq = value;
await this.plugin.saveSettings();
}));
.addText((text) =>
text
.setPlaceholder("This request is disabled")
.setValue(this.plugin.settings.DisabledReq)
.onChange(async (value) => {
this.plugin.settings.DisabledReq = value;
await this.plugin.saveSettings();
}),
);
new Setting(containerEl)
.setName('Status-bar text')
.setDesc("Text to display in the status bar when there are code blocks (use %d to show the number of blocks)")
.addText(text => text
.setPlaceholder('Count blocks: %d')
.setValue(this.plugin.settings.countBlocksText)
.onChange(async (value) => {
if (!value.includes("%d")) value = "🗲 %d";
this.plugin.settings.countBlocksText = value;
await this.plugin.saveSettings();
}));
.setName("Status-bar text")
.setDesc(
"Text to display in the status bar when there are code blocks (use %d to show the number of blocks)",
)
.addText((text) =>
text
.setPlaceholder("Count blocks: %d")
.setValue(this.plugin.settings.countBlocksText)
.onChange(async (value) => {
if (!value.includes("%d")) value = "🗲 %d";
this.plugin.settings.countBlocksText = value;
await this.plugin.saveSettings();
}),
);
const codeblock = containerEl.createEl('details');
codeblock.createEl('summary', { text: 'Click to Add Key/Value ↴', cls: 'summary-text' });
new Setting(codeblock)
.setName('Key')
.setDesc('Key of the codeblock')
.addText(text => text
.setPlaceholder('key')
containerEl.createEl("h2", { text: "Global variables" });
new Setting(containerEl).setName("Key").addText((text) =>
text
.setPlaceholder("key")
.setValue(this.plugin.settings.Key)
.onChange(async (value) => {
this.plugin.settings.Key = value;
await this.plugin.saveSettings();
}));
new Setting(codeblock)
.setName('Value')
.setDesc('Value of the codeblock')
.addText(text => text
.setPlaceholder('value')
}),
);
new Setting(containerEl).setName("Value").addText((text) =>
text
.setPlaceholder("value")
.setValue(this.plugin.settings.Value)
.onChange(async (value) => {
this.plugin.settings.Value = value;
await this.plugin.saveSettings();
}));
new Setting(codeblock)
.addButton(button => {
button.setClass('btn-add-codeblock');
button.setButtonText('ADD').onClick(async () => {
const { Key, Value } = this.plugin.settings;
const { KeyValueCodeblocks } = this.plugin.settings;
KeyValueCodeblocks.push({ 'key': Key, 'value': Value });
await this.plugin.saveSettings();
this.display();
});
}),
);
new Setting(containerEl).addButton((button) => {
button.setClass("btn-add-codeblock");
button.setButtonText("Add Variable").onClick(async () => {
const { Key, Value } = this.plugin.settings;
const { KeyValueCodeblocks } = this.plugin.settings;
KeyValueCodeblocks.push({ key: Key, value: Value });
await this.plugin.saveSettings();
this.display();
});
});
containerEl.createEl('hr');
containerEl.createEl('h2', { text: 'Manage Requests' });
this.displayInfoApirs(containerEl);
// new Setting(containerEl)
// .addButton(button => {
// button.setClass('btn-clear-apir');
// button.setButtonText("Clear localStorage").onClick(async () => {
// Object.keys(localStorage).forEach(key => {
// if (key.startsWith("req-")) {
// localStorage.removeItem(key);
// }
// });
// this.display();
// });
// });
containerEl.createEl('hr');
containerEl.createEl('h2', { text: 'Manage Key/Values' });
containerEl.createEl("h2", { text: "Manage Global Variables" });
this.displayKeyValues();
containerEl.createEl("h2", { text: "Saved API Requests" });
this.displayInfoApirs();
}
displayKeyValues() {
const { containerEl } = this;
const { KeyValueCodeblocks } = this.plugin.settings;
const ct = containerEl.createEl('div', { cls: 'cocontainer full-width' });
const ct = containerEl.createEl("div", {
cls: "cocontainer full-width",
});
if (KeyValueCodeblocks.length > 0) {
const tableContainer = ct.createEl('div', { cls: 'table-container full-width' });
const table = tableContainer.createEl('table', { cls: 'api-table full-width' });
const thead = table.createEl('thead');
const headerRow = thead.createEl('tr');
headerRow.createEl('th', { text: 'Key' });
headerRow.createEl('th', { text: 'Value' });
const tableContainer = ct.createEl("div", {
cls: "table-container full-width",
});
const tbody = table.createEl('tbody');
const table = tableContainer.createEl("table", {
cls: "api-table full-width",
});
const thead = table.createEl("thead");
const headerRow = thead.createEl("tr");
headerRow.createEl("th", { text: "Key" });
headerRow.createEl("th", { text: "Value" });
const tbody = table.createEl("tbody");
KeyValueCodeblocks.forEach((u) => {
const row = tbody.createEl('tr');
const row = tbody.createEl("tr");
row.createEl('td', { text: u.key });
row.createEl('td', { text: u.value });
row.createEl("td", { text: u.key });
row.createEl("td", { text: u.value });
row.addEventListener('click', async () => {
row.addEventListener("click", async () => {
const index = KeyValueCodeblocks.indexOf(u);
KeyValueCodeblocks.splice(index, 1);
await this.plugin.saveSettings();
@ -229,51 +115,24 @@ export default class APRSettings extends PluginSettingTab {
}
}
displayInfoApirs(containerEl: HTMLElement) {
// Render URL table
const { URLs } = this.plugin.settings;
const ct = containerEl.createEl('div', { cls: 'cocontainer' });
if (URLs.length > 0) {
const tableContainer = ct.createEl('div', { cls: 'table-container' });
const table = tableContainer.createEl('table', { cls: 'api-table' });
const thead = table.createEl('thead');
const headerRow = thead.createEl('tr');
headerRow.createEl('th', { text: 'Name' });
headerRow.createEl('th', { text: 'URL' });
const tbody = table.createEl('tbody');
URLs.forEach((u) => {
const row = tbody.createEl('tr');
row.createEl('td', { text: u.Name });
const urlCell = row.createEl('td');
urlCell.createEl('a', { text: u.url.length > 50 ? u.url.substring(0, 50) + "..." : u.url, cls: 'api-url' });
urlCell.addEventListener('click', async () => {
const index = URLs.indexOf(u);
URLs.splice(index, 1);
await this.plugin.saveSettings();
this.display();
});
});
}
displayInfoApirs() {
const { containerEl } = this;
const ct = containerEl.createEl("div", { cls: "cocontainer" });
// Render localStorage table
containerEl.createEl('div', { cls: 'table-container full-width' });
const table = ct.createEl('table', { cls: 'api-table full-width' });
const thead = table.createEl('thead');
const headerRow = thead.createEl('tr');
const hr = headerRow.createEl('th', { text: 'ID' });
containerEl.createEl("div", { cls: "table-container full-width" });
const table = ct.createEl("table", { cls: "api-table full-width" });
const thead = table.createEl("thead");
const headerRow = thead.createEl("tr");
const hr = headerRow.createEl("th", { text: "ID" });
const tbody = table.createEl('tbody');
Object.keys(localStorage).forEach(key => {
const tbody = table.createEl("tbody");
Object.keys(localStorage).forEach((key) => {
if (key.startsWith("req-")) {
const row = tbody.createEl('tr');
const idCell = row.createEl('td', { text: key });
const row = tbody.createEl("tr");
const idCell = row.createEl("td", { text: key });
idCell.addEventListener('click', async () => {
idCell.addEventListener("click", async () => {
localStorage.removeItem(key);
this.display();
});
@ -281,8 +140,7 @@ export default class APRSettings extends PluginSettingTab {
});
// if table is empty
if (tbody.children.length === 0) {
hr.innerText = 'No response saved';
hr.innerText = "No requests saved";
}
}
}
}

View file

@ -55,13 +55,10 @@
color: black;
}
/* I want to display the line over the entire row */
.api-table tr:hover :not(th) {
background-color: #F3A0A0;
color: black;
cursor: pointer;
text-decoration: line-through;
}
.api-table tr:hover a {