BREAKING CHANGE: complete rebuild of plugin(02182025)
11
README.es.md
|
|
@ -5,8 +5,8 @@
|
|||
[](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">
|
||||
|
||||
[](README.md)
|
||||
[](README.zh.md)
|
||||
[](README.md)
|
||||
[](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
|
||||
|
||||
|
|
|
|||
30
README.md
|
|
@ -5,13 +5,11 @@
|
|||
[](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">
|
||||
|
||||
[](README.es.md)
|
||||
[](README.zh.md)
|
||||
[](README.es.md)
|
||||
[](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.
|
||||
|
||||

|
||||
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/)
|
||||
|
||||

|
||||
### [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>
|
||||
|
||||
|
|
|
|||
19
README.zh.md
|
|
@ -5,15 +5,13 @@
|
|||
[](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">
|
||||
|
||||
[](README.es.md)
|
||||
[](README.es.md)
|
||||
[](README.md)
|
||||
|
||||
|
||||
这个[Obsidian](https://obsidian.md/)插件能让用户直接在笔记中进行 HTTP 请求,并在代码块、模式窗口中显示响应,或直接将其粘贴到活动文档中。
|
||||
|
||||
|
||||

|
||||
|
||||
## 🚀 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/)
|
||||
|
||||

|
||||
|
||||
## ✅ 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
|
|
@ -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
|
Before Width: | Height: | Size: 901 KiB |
BIN
config_img.png
|
Before Width: | Height: | Size: 137 KiB |
|
|
@ -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: 
|
||||
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`."
|
||||
~~~
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
Before Width: | Height: | Size: 103 KiB |
|
|
@ -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:  <br> **Name:** {} <br> **Type:** {}
|
||||
render
|
||||
url: https://mapi.mobilelegends.com/hero/detail?id=1
|
||||
show: $.data[cover_picture,name,type]
|
||||
format:  <br> <strong>Name:</strong> {} <br> <strong>Type:</strong> {}
|
||||
```
|
||||
~~~
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
```
|
||||
~~~
|
||||
|
||||
|
|
|
|||
|
|
@ -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: 
|
||||
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`."
|
||||
~~~
|
||||
|
|
@ -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)
|
||||
~~~
|
||||
|
|
@ -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.
|
||||
|
Before Width: | Height: | Size: 103 KiB |
|
|
@ -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:  <br> **Name:** {} <br> **Type:** {}
|
||||
render
|
||||
show: $.data[cover_picture,name,type]
|
||||
```
|
||||
~~~
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
```
|
||||
~~~
|
||||
|
||||
|
|
|
|||
|
|
@ -6,5 +6,4 @@
|
|||
|
||||
> [](es/index.md)
|
||||
> [](en/index.md)
|
||||
|
||||

|
||||
> [](zh/index.md)
|
||||
|
|
|
|||
|
|
@ -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: 
|
||||
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` 的表格。"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 名称来删除它。
|
||||
|
Before Width: | Height: | Size: 103 KiB |
|
|
@ -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:  <br> **Name:** {} <br> **Type:** {}
|
||||
render
|
||||
show: $.data[cover_picture,name,type]
|
||||
```
|
||||
~~~
|
||||
|
||||

|
||||
|
||||
## 从 [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
|
||||
```
|
||||
~~~
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
BIN
showcase_1.gif
|
Before Width: | Height: | Size: 1.5 MiB |
BIN
showcase_2.gif
|
Before Width: | Height: | Size: 756 KiB |
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
879
src/main.ts
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)',
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||