notes-to-strapi-export-article-ai 1.0.118 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc +30 -22
- package/README.md +98 -143
- package/images/img.png +0 -0
- package/images/img_1.png +0 -0
- package/images/img_10.png +0 -0
- package/images/img_11.png +0 -0
- package/images/img_12.png +0 -0
- package/images/img_13.png +0 -0
- package/images/img_2.png +0 -0
- package/images/img_3.png +0 -0
- package/images/img_4.png +0 -0
- package/images/img_5.png +0 -0
- package/images/img_6.png +0 -0
- package/images/img_7.png +0 -0
- package/images/img_8.png +0 -0
- package/images/img_9.png +0 -0
- package/manifest.json +2 -2
- package/package.json +29 -26
- package/src/components/APIKeys.ts +219 -0
- package/src/components/Configuration.ts +663 -0
- package/src/components/Dashboard.ts +184 -0
- package/src/components/ImageSelectionModal.ts +58 -0
- package/src/components/Routes.ts +279 -0
- package/src/constants.ts +22 -61
- package/src/main.ts +177 -34
- package/src/services/configuration-generator.ts +172 -0
- package/src/services/field-analyzer.ts +84 -0
- package/src/services/frontmatter.ts +329 -0
- package/src/services/strapi-export.ts +436 -0
- package/src/settings/UnifiedSettingsTab.ts +206 -0
- package/src/types/image.ts +27 -16
- package/src/types/index.ts +3 -0
- package/src/types/route.ts +51 -0
- package/src/types/settings.ts +22 -23
- package/src/utils/analyse-file.ts +94 -0
- package/src/utils/debounce.ts +34 -0
- package/src/utils/image-processor.ts +124 -400
- package/src/utils/preview-modal.ts +265 -0
- package/src/utils/process-file.ts +122 -0
- package/src/utils/strapi-uploader.ts +120 -119
- package/src/settings.ts +0 -404
- package/src/types/article.ts +0 -8
- package/src/utils/openai-generator.ts +0 -139
- package/src/utils/validators.ts +0 -8
- package/version-bump.mjs +0 -14
- package/versions.json +0 -118
package/.eslintrc
CHANGED
|
@@ -1,24 +1,32 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
"
|
|
6
|
-
|
|
2
|
+
"root": true,
|
|
3
|
+
"parser": "@typescript-eslint/parser",
|
|
4
|
+
"env": {
|
|
5
|
+
"node": true
|
|
6
|
+
},
|
|
7
|
+
"plugins": [
|
|
8
|
+
"@typescript-eslint"
|
|
9
|
+
],
|
|
10
|
+
"extends": [
|
|
11
|
+
"eslint:recommended",
|
|
12
|
+
"plugin:@typescript-eslint/eslint-recommended",
|
|
13
|
+
"plugin:@typescript-eslint/recommended"
|
|
14
|
+
],
|
|
15
|
+
"parserOptions": {
|
|
16
|
+
"sourceType": "module"
|
|
17
|
+
},
|
|
18
|
+
"rules": {
|
|
19
|
+
"no-unused-vars": "off",
|
|
20
|
+
"@typescript-eslint/no-unused-vars": [
|
|
21
|
+
"error",
|
|
22
|
+
{
|
|
23
|
+
"args": "none"
|
|
24
|
+
}
|
|
7
25
|
],
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
},
|
|
16
|
-
"rules": {
|
|
17
|
-
"no-unused-vars": "off",
|
|
18
|
-
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
|
|
19
|
-
"@typescript-eslint/ban-ts-comment": "off",
|
|
20
|
-
"no-prototype-builtins": "off",
|
|
21
|
-
"@typescript-eslint/no-empty-function": "off",
|
|
22
|
-
"@typescript-eslint/no-explicit-any": "off"
|
|
23
|
-
}
|
|
24
|
-
}
|
|
26
|
+
"@typescript-eslint/ban-ts-comment": "off",
|
|
27
|
+
"no-prototype-builtins": "off",
|
|
28
|
+
"@typescript-eslint/no-empty-function": "off",
|
|
29
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
30
|
+
"no-extra-semi": "off"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/README.md
CHANGED
|
@@ -6,15 +6,28 @@
|
|
|
6
6
|
|
|
7
7
|
Strapi Exporter is a game-changing Obsidian plugin that streamlines your content creation process by seamlessly exporting your notes to Strapi CMS. With its AI-powered image handling and SEO optimization features, you can take your content to the next level with just a few clicks.
|
|
8
8
|
|
|
9
|
-
## ✨ Features
|
|
9
|
+
## ✨ Key Features
|
|
10
|
+
|
|
11
|
+
- 🔄 Multiple Export Routes
|
|
12
|
+
- Configure different export paths for various content types
|
|
13
|
+
- Customizable field mappings per route
|
|
14
|
+
- Support for multiple languages
|
|
15
|
+
|
|
16
|
+
- 🤖 AI-Powered Content Processing
|
|
17
|
+
- Automatic frontmatter generation
|
|
18
|
+
- SEO optimization
|
|
19
|
+
- Content analysis and enhancement
|
|
20
|
+
|
|
21
|
+
- 📷 Advanced Image Management
|
|
22
|
+
- Automatic image upload to Strapi
|
|
23
|
+
- Support for both single images and galleries
|
|
24
|
+
- Image path management and updates
|
|
25
|
+
|
|
26
|
+
- 🔧 Flexible Configuration
|
|
27
|
+
- JSON schema-based configuration
|
|
28
|
+
- Custom field mappings
|
|
29
|
+
- Language-specific settings
|
|
10
30
|
|
|
11
|
-
- 🗒️ Automatically upload images from your notes to Strapi
|
|
12
|
-
- 🎨 Generate SEO-friendly alt text and captions for images using AI
|
|
13
|
-
- 😎 Create SEO-optimized article content based on your notes
|
|
14
|
-
- 🔧 Customize the JSON template for the article fields in Strapi
|
|
15
|
-
- ⚙️ Easy configuration for Strapi API URL, token, and content attribute name
|
|
16
|
-
- 📷 Supports both single images and image galleries
|
|
17
|
-
- 🔗 Additional API route for creating content in a different Strapi content type
|
|
18
31
|
|
|
19
32
|
## 🛠️ Installation
|
|
20
33
|
|
|
@@ -58,159 +71,101 @@ To install Strapi Exporter, follow these steps (coming soon to the Obsidian plug
|
|
|
58
71
|
## ⚙️ Configuration
|
|
59
72
|
|
|
60
73
|
To get started with Strapi Exporter, you'll need to configure the following settings:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
-
|
|
80
|
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
],
|
|
103
|
-
"subtitle": "string",
|
|
104
|
-
"type": "string",
|
|
105
|
-
"rank": "number",
|
|
106
|
-
"tags": [
|
|
107
|
-
{
|
|
108
|
-
"id": "number",
|
|
109
|
-
"name": "string"
|
|
110
|
-
}
|
|
111
|
-
],
|
|
112
|
-
"locale": "string"
|
|
113
|
-
}
|
|
114
|
-
}
|
|
74
|
+

|
|
75
|
+
|
|
76
|
+
### 1. Go in "API Keys" tab
|
|
77
|
+
- (required) Add your Strapi API URL
|
|
78
|
+
It's the link to your Strapi API, for example: `https://api.yourdomain.com`
|
|
79
|
+

|
|
80
|
+
- (required) Add your Strapi API Token
|
|
81
|
+

|
|
82
|
+
(settings -> api tokens -> create new API Token)
|
|
83
|
+
then, give it the right permissions (at least, upload, and create permissions on the content type you want to use)
|
|
84
|
+
(or just use a full-access token [in Token type configuration])
|
|
85
|
+
- (required) Add your OpenAI API Token
|
|
86
|
+

|
|
87
|
+
you need to create an account on OpenAI, and create an API key, then add it here
|
|
88
|
+
- (optional to improve the results) Add your ForVoyez API Token
|
|
89
|
+
|
|
90
|
+
### 2. Go in "Routes" tab
|
|
91
|
+
- Add a new route
|
|
92
|
+
- Give it a name, icon, url, subtitle
|
|
93
|
+
- for example,
|
|
94
|
+
- name : articles
|
|
95
|
+
- icon : bird
|
|
96
|
+
- url : /api/articles
|
|
97
|
+
- subtitle : Create articles
|
|
98
|
+
- You can add multiple routes, for example, one for articles, one for pages, one for categories, etc.
|
|
99
|
+

|
|
100
|
+
|
|
101
|
+
### 3. Go in "Configuration" tab
|
|
102
|
+
- For each route, you can configure the fields you want to use, and the schema needed for the fields
|
|
103
|
+
in Strapi, go in Documentation -> Open Documentation
|
|
104
|
+

|
|
105
|
+
You will see the openapi schema, you can use it to create the schema for the fields you want to use
|
|
106
|
+

|
|
107
|
+
You need to check the POST method, and the schema needed for the fields you want to use
|
|
108
|
+

|
|
109
|
+
Copy the whole schema in the Strapi schema section in the obsidian plugin
|
|
110
|
+

|
|
111
|
+
|
|
112
|
+
Then, you need to describe the content you want to use, and you want to upload at the end,
|
|
113
|
+
in schema description, for exemple for me :
|
|
114
|
+
in Strapi Schema
|
|
115
115
|
```
|
|
116
|
-
|
|
117
|
-
- then, you can copy this JSON template in the settings of the plugin
|
|
118
|
-
- and copy that, to describe each field in the other JSON description setting
|
|
119
|
-
- **JSON Template Description**: A description for each field in the JSON template to help GPT-3 understand the structure. Follow the same schema as the JSON template to provide descriptions for each field.
|
|
120
|
-
|
|
121
|
-
```json
|
|
122
116
|
{
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
"id": "<Unique identifier for the link, as a number>",
|
|
132
|
-
"label": "<Display text for the link, as a short string>",
|
|
133
|
-
"url": "<URL the link points to, as a string>"
|
|
134
|
-
}
|
|
135
|
-
],
|
|
136
|
-
"subtitle": "<Subtitle or secondary title, as a short string>",
|
|
137
|
-
"type": "<Category or type of the item, as a short string>",
|
|
138
|
-
"rank": "<Numerical ranking or order priority, as a number>",
|
|
139
|
-
"tags": [
|
|
140
|
-
{
|
|
141
|
-
"id": "<Unique identifier for the tag, as a number>",
|
|
142
|
-
"name": "<Name of the tag, as a short string>"
|
|
143
|
-
}
|
|
144
|
-
],
|
|
145
|
-
"locale": "<Locale or language code, as a short string>"
|
|
146
|
-
}
|
|
147
|
-
}
|
|
117
|
+
"data": {
|
|
118
|
+
"title": "string",
|
|
119
|
+
"content": "string",
|
|
120
|
+
"seo_title": "string",
|
|
121
|
+
"seo_description": "string",
|
|
122
|
+
"slug": "string",
|
|
123
|
+
...
|
|
124
|
+
}
|
|
148
125
|
```
|
|
149
|
-
|
|
126
|
+
In Strapi Description (just describe with your words the fields, to help the AI to understand what you want) :
|
|
150
127
|
```
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
128
|
+
{
|
|
129
|
+
"data": {
|
|
130
|
+
"title": "the title of the article",
|
|
131
|
+
"content": "the content normal of the article",
|
|
132
|
+
"seo_title": "the seo optimized version of the title",
|
|
133
|
+
"seo_description": "a summary of the content, seo optimized, for the seo optimized description",
|
|
134
|
+
"slug": "the-slug-for-the-access-of-the-article",
|
|
135
|
+
...
|
|
136
|
+
}
|
|
158
137
|
```
|
|
138
|
+

|
|
159
139
|
|
|
160
|
-
-
|
|
161
|
-
|
|
162
|
-
- it should look like this: `https://{api_url}/documentation/v1.0.0`
|
|
163
|
-
- then, go to the article creation, and copy the URL
|
|
164
|
-
- **Strapi Content Attribute Name**: The attribute name for the content field in Strapi (e.g., `content`).
|
|
165
|
-
- to get the attribute name, you can go to the documentation of your Strapi API, and copy the attribute name of the article creation, (it need to be the name of the attribute that contain the main content of the article, for me it's "content", but it can be different for you)
|
|
166
|
-
- **Additional Prompt** (optional): Additional prompt to provide context for GPT-3 when generating content. You can use this field to specify additional information or instructions for the AI model. Like your langage, the tone of the article, etc.
|
|
167
|
-
|
|
168
|
-
### Image Settings (main), optional
|
|
169
|
-
|
|
170
|
-
- **Enable Additional Call API Image**: Toggle this option to enable the image feature for the additional API route. The plugin will look for an image in the `image` folder next to your article file and upload it to Strapi.
|
|
171
|
-
- **Additional Call API Image Full Path Property**: Specify the full path to the image property for the additional API route in your Strapi API (e.g., `image_presentation`).
|
|
140
|
+
- And now, in the content field name, you need to write the main content attribute name, for example, in my case, it's "content"
|
|
141
|
+
it because we want to avoid the AI to rewrite the content field. (it's already written by us)
|
|
172
142
|
|
|
173
|
-
|
|
174
|
-
- **Additional Call API Gallery Full Path Property**: Specify the full path to the gallery property for the additional API route in your Strapi API (e.g., `gallery`).
|
|
175
|
-
-
|
|
143
|
+

|
|
176
144
|
|
|
177
|
-
|
|
145
|
+
- Select the target Language.
|
|
146
|
+

|
|
178
147
|
|
|
179
|
-
|
|
148
|
+
- click on "Generate Configuration", and wait the configuration to be generated
|
|
149
|
+
- check if it's coherent with what you want, and click on "Apply"
|
|
180
150
|
|
|
181
|
-
|
|
182
|
-
- **Additional JSON Template**: Enter the JSON template for the fields needed for the additional API route.
|
|
183
|
-
- **Additional API JSON Template Description**: Enter the description for each field in the additional API JSON template.
|
|
184
|
-
- **Additional API URL**: Enter the URL to create content for the additional API route (e.g., `https://your-strapi-url/api/additional-content`).
|
|
185
|
-
- **Additional API Content Attribute Name**: Enter the attribute name for the content field for the additional API route.
|
|
151
|
+

|
|
186
152
|
|
|
187
|
-
|
|
153
|
+
- Repeat the process for each route you want to use, (you can select an another route with the dropdown menu [select route])
|
|
154
|
+
- Reload the plugin (ctrl + r) to apply the changes
|
|
188
155
|
|
|
189
|
-
- **Enable Additional Call API Image**: Toggle this option to enable the image feature for the additional API route. The plugin will look for an image in the `image` folder next to your article file and upload it to Strapi.
|
|
190
|
-
- **Additional Call API Image Full Path Property**: Specify the full path to the image property for the additional API route in your Strapi API (e.g., `image_presentation`).
|
|
191
|
-
|
|
192
|
-
- **Enable Additional Call API Gallery**: Toggle this option to enable the gallery feature for the additional API route. The plugin will look for images in the `gallery` folder next to your article file and upload them to Strapi.
|
|
193
|
-
- **Additional Call API Gallery Full Path Property**: Specify the full path to the gallery property for the additional API route in your Strapi API (e.g., `gallery`).
|
|
194
156
|
|
|
195
157
|
## 🚀 Usage
|
|
196
158
|
|
|
159
|
+
YOU NEED TO HAVE FINISHED THE CONFIGURATION BEFORE USING THE PLUGIN
|
|
160
|
+
|
|
197
161
|
1. Open a Markdown file in Obsidian.
|
|
198
162
|
2. Click on the plugin's ribbon icon to start the magic.
|
|
199
|
-
|
|
200
|
-
3.
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
- 🌐 Publishing the article to Strapi with the generated content and images
|
|
205
|
-
4. Enjoy your freshly exported article in Strapi!
|
|
206
|
-
|
|
207
|
-
For the plugin to detect images and galleries, ensure the following folder structure:
|
|
208
|
-
|
|
209
|
-
- Article file (e.g., `article.md`)
|
|
210
|
-
- Main image folder (name: `image`)
|
|
211
|
-
- Main gallery folder (name: `gallery`)
|
|
163
|
+

|
|
164
|
+
3. if you haven't any frontmatter, click on "Generate" to generate the frontmatter
|
|
165
|
+
4. you can change the frontmatter if you want, directly in the file
|
|
166
|
+
5. Check if the frontmatter is correct, and click on "Confirm & Export"
|
|
167
|
+
6. Enjoy your freshly exported article in Strapi!
|
|
212
168
|
|
|
213
|
-
The plugin will detect images in the `image` and `gallery` folders and upload them to Strapi.
|
|
214
169
|
|
|
215
170
|
## 🤝 Contributing
|
|
216
171
|
|
package/images/img.png
CHANGED
|
Binary file
|
package/images/img_1.png
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/images/img_2.png
CHANGED
|
Binary file
|
package/images/img_3.png
CHANGED
|
Binary file
|
package/images/img_4.png
CHANGED
|
Binary file
|
package/images/img_5.png
CHANGED
|
Binary file
|
package/images/img_6.png
CHANGED
|
Binary file
|
package/images/img_7.png
CHANGED
|
Binary file
|
package/images/img_8.png
CHANGED
|
Binary file
|
package/images/img_9.png
CHANGED
|
Binary file
|
package/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "notes-to-strapi-export-article-ai",
|
|
3
3
|
"name": "Strapi Exporter AI",
|
|
4
|
-
"version": "
|
|
5
|
-
"minAppVersion": "1.
|
|
4
|
+
"version": "3.0.0",
|
|
5
|
+
"minAppVersion": "1.7.0",
|
|
6
6
|
"description": "Effortlessly export your notes to Strapi CMS with AI-powered handling and SEO optimization.",
|
|
7
7
|
"author": "Cinquin Andy",
|
|
8
8
|
"authorUrl": "https://andy.cinquin.com",
|
package/package.json
CHANGED
|
@@ -1,28 +1,31 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
2
|
+
"name": "notes-to-strapi-export-article-ai",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "Effortlessly export your Obsidian notes to Strapi CMS with AI-powered image handling and SEO optimization. Replace all the images in your notes by uploaded images in Strapi, and add SEO metadata to uploaded images.",
|
|
5
|
+
"main": "main.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "node esbuild.config.mjs",
|
|
8
|
+
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
|
9
|
+
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [],
|
|
12
|
+
"author": "Cinquin Andy",
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@types/node": "22.9.0",
|
|
15
|
+
"@typescript-eslint/eslint-plugin": "8.13.0",
|
|
16
|
+
"@typescript-eslint/parser": "8.13.0",
|
|
17
|
+
"builtin-modules": "4.0.0",
|
|
18
|
+
"esbuild": "0.24.0",
|
|
19
|
+
"obsidian": "latest",
|
|
20
|
+
"prettier": "3.3.3",
|
|
21
|
+
"tslib": "2.8.1",
|
|
22
|
+
"typescript": "5.6.3"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@ai-sdk/openai": "0.0.72",
|
|
26
|
+
"ai": "3.4.33",
|
|
27
|
+
"js-yaml": "4.1.0",
|
|
28
|
+
"openai": "4.71.1",
|
|
29
|
+
"process": "0.11.10"
|
|
30
|
+
}
|
|
28
31
|
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { Setting, Notice } from 'obsidian'
|
|
2
|
+
import StrapiExporterPlugin from '../main'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Represents a field validator for API keys
|
|
6
|
+
*/
|
|
7
|
+
interface APIKeyValidator {
|
|
8
|
+
validate: (key: string) => boolean
|
|
9
|
+
pattern: RegExp
|
|
10
|
+
message: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Class for managing API key settings in Obsidian
|
|
15
|
+
*/
|
|
16
|
+
export class APIKeys {
|
|
17
|
+
private readonly validators: Record<string, APIKeyValidator>
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
private plugin: StrapiExporterPlugin,
|
|
21
|
+
private containerEl: HTMLElement
|
|
22
|
+
) {
|
|
23
|
+
this.validators = this.initializeValidators()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Initializes field validators
|
|
28
|
+
*/
|
|
29
|
+
private initializeValidators(): Record<string, APIKeyValidator> {
|
|
30
|
+
return {
|
|
31
|
+
strapiUrl: {
|
|
32
|
+
validate: (url: string) => /^https?:\/\/.+/.test(url),
|
|
33
|
+
pattern: /^https?:\/\/.+/,
|
|
34
|
+
message: 'Must be a valid URL starting with http:// or https://',
|
|
35
|
+
},
|
|
36
|
+
strapiApiToken: {
|
|
37
|
+
validate: (token: string) => token.length >= 32,
|
|
38
|
+
pattern: /.{32,}/,
|
|
39
|
+
message: 'Must be at least 32 characters long',
|
|
40
|
+
},
|
|
41
|
+
openaiApiKey: {
|
|
42
|
+
validate: (key: string) => /^sk-[A-Za-z0-9]{32,}$/.test(key),
|
|
43
|
+
pattern: /^sk-[A-Za-z0-9]{32,}$/,
|
|
44
|
+
message: 'Must start with "sk-" followed by at least 32 characters',
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
display(): void {
|
|
50
|
+
const { containerEl } = this
|
|
51
|
+
containerEl.empty()
|
|
52
|
+
|
|
53
|
+
this.createHeader()
|
|
54
|
+
this.addStrapiSettings()
|
|
55
|
+
this.addForVoyezSettings()
|
|
56
|
+
this.addOpenAISettings()
|
|
57
|
+
this.addValidationButton()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private createHeader(): void {
|
|
61
|
+
const header = this.containerEl.createEl('div', { cls: 'api-keys-header' })
|
|
62
|
+
|
|
63
|
+
header.createEl('h2', { text: 'API Keys Configuration' })
|
|
64
|
+
header.createEl('p', {
|
|
65
|
+
text: 'Configure your API keys for various services. All keys are stored locally and encrypted.',
|
|
66
|
+
cls: 'api-keys-description',
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private addStrapiSettings(): void {
|
|
71
|
+
// Strapi URL Setting
|
|
72
|
+
this.createSettingField({
|
|
73
|
+
name: 'Strapi URL',
|
|
74
|
+
desc: 'Enter your Strapi instance URL (e.g. https://your-strapi-url)',
|
|
75
|
+
placeholder: 'https://your-strapi-url',
|
|
76
|
+
value: this.plugin.settings.strapiUrl,
|
|
77
|
+
settingKey: 'strapiUrl',
|
|
78
|
+
type: 'url',
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// Strapi API Token Setting
|
|
82
|
+
this.createSettingField({
|
|
83
|
+
name: 'Strapi API Token',
|
|
84
|
+
desc: 'Enter your Strapi API token',
|
|
85
|
+
placeholder: 'Enter your token',
|
|
86
|
+
value: this.plugin.settings.strapiApiToken,
|
|
87
|
+
settingKey: 'strapiApiToken',
|
|
88
|
+
type: 'password',
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private addForVoyezSettings(): void {
|
|
93
|
+
this.createSettingField({
|
|
94
|
+
name: 'ForVoyez API Key',
|
|
95
|
+
desc: 'Enter your ForVoyez API key',
|
|
96
|
+
placeholder: 'Enter your ForVoyez API key',
|
|
97
|
+
value: this.plugin.settings.forvoyezApiKey,
|
|
98
|
+
settingKey: 'forvoyezApiKey',
|
|
99
|
+
type: 'password',
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private addOpenAISettings(): void {
|
|
104
|
+
this.createSettingField({
|
|
105
|
+
name: 'OpenAI API Key',
|
|
106
|
+
desc: 'Enter your OpenAI API key',
|
|
107
|
+
placeholder: 'Enter your OpenAI API key',
|
|
108
|
+
value: this.plugin.settings.openaiApiKey,
|
|
109
|
+
settingKey: 'openaiApiKey',
|
|
110
|
+
type: 'password',
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private createSettingField({
|
|
115
|
+
name,
|
|
116
|
+
desc,
|
|
117
|
+
placeholder,
|
|
118
|
+
value,
|
|
119
|
+
settingKey,
|
|
120
|
+
type,
|
|
121
|
+
}: {
|
|
122
|
+
name: string
|
|
123
|
+
desc: string
|
|
124
|
+
placeholder: string
|
|
125
|
+
value: string
|
|
126
|
+
settingKey: string
|
|
127
|
+
type: 'text' | 'password' | 'url'
|
|
128
|
+
}): void {
|
|
129
|
+
const setting = new Setting(this.containerEl).setName(name).setDesc(desc)
|
|
130
|
+
|
|
131
|
+
// Add validation message element first
|
|
132
|
+
const validationEl = setting.settingEl.createDiv('validation-message')
|
|
133
|
+
validationEl.style.display = 'none'
|
|
134
|
+
|
|
135
|
+
// Create text component
|
|
136
|
+
setting.addText(text => {
|
|
137
|
+
// Configure text component
|
|
138
|
+
text
|
|
139
|
+
.setPlaceholder(placeholder)
|
|
140
|
+
.setValue(value)
|
|
141
|
+
.onChange(async newValue => {
|
|
142
|
+
try {
|
|
143
|
+
await this.updateSetting(settingKey, newValue)
|
|
144
|
+
} catch (error) {
|
|
145
|
+
new Notice(`Failed to update ${name}: ${error.message}`)
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
// Handle password type
|
|
150
|
+
if (type === 'password') {
|
|
151
|
+
text.inputEl.type = 'password'
|
|
152
|
+
text.inputEl.classList.add('api-key-input')
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Add blur event listener directly
|
|
156
|
+
text.inputEl.addEventListener('blur', () => {
|
|
157
|
+
this.validateField(settingKey, text.getValue(), validationEl)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
return text
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private async updateSetting(key: string, value: string): Promise<void> {
|
|
165
|
+
// Validate if we have a validator for this key
|
|
166
|
+
if (this.validators[key]) {
|
|
167
|
+
const isValid = this.validators[key].validate(value)
|
|
168
|
+
if (!isValid) {
|
|
169
|
+
throw new Error(this.validators[key].message)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Update the setting
|
|
174
|
+
this.plugin.settings[key] = value
|
|
175
|
+
await this.plugin.saveSettings()
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private validateField(
|
|
179
|
+
key: string,
|
|
180
|
+
value: string,
|
|
181
|
+
validationEl: HTMLElement
|
|
182
|
+
): void {
|
|
183
|
+
const validator = this.validators[key]
|
|
184
|
+
if (validator) {
|
|
185
|
+
const isValid = validator.validate(value)
|
|
186
|
+
validationEl.style.display = isValid ? 'none' : 'block'
|
|
187
|
+
validationEl.setText(isValid ? '' : validator.message)
|
|
188
|
+
validationEl.classList.toggle('invalid', !isValid)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private addValidationButton(): void {
|
|
193
|
+
new Setting(this.containerEl).addButton(button =>
|
|
194
|
+
button
|
|
195
|
+
.setButtonText('Validate All Keys')
|
|
196
|
+
.setCta()
|
|
197
|
+
.onClick(async () => {
|
|
198
|
+
try {
|
|
199
|
+
await this.validateAllKeys()
|
|
200
|
+
} catch (error) {
|
|
201
|
+
new Notice(`Validation failed: ${error.message}`)
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private async validateAllKeys(): Promise<void> {
|
|
208
|
+
const validations = Object.entries(this.validators).map(
|
|
209
|
+
async ([key, validator]) => {
|
|
210
|
+
const value = this.plugin.settings[key]
|
|
211
|
+
if (!validator.validate(value)) {
|
|
212
|
+
throw new Error(`Invalid ${key}: ${validator.message}`)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
await Promise.all(validations)
|
|
218
|
+
}
|
|
219
|
+
}
|