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
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
import {
|
|
2
|
+
App,
|
|
3
|
+
DropdownComponent,
|
|
4
|
+
Modal,
|
|
5
|
+
Notice,
|
|
6
|
+
Setting,
|
|
7
|
+
TextAreaComponent,
|
|
8
|
+
TextComponent,
|
|
9
|
+
TFile,
|
|
10
|
+
} from 'obsidian'
|
|
11
|
+
import StrapiExporterPlugin from '../main'
|
|
12
|
+
import { uploadImageToStrapi } from '../utils/strapi-uploader'
|
|
13
|
+
import { RouteConfig } from '../types'
|
|
14
|
+
import { StructuredFieldAnalyzer } from '../services/field-analyzer'
|
|
15
|
+
import { ConfigurationGenerator } from '../services/configuration-generator'
|
|
16
|
+
|
|
17
|
+
export class Configuration {
|
|
18
|
+
private fieldAnalyzer: StructuredFieldAnalyzer
|
|
19
|
+
private configGenerator: ConfigurationGenerator
|
|
20
|
+
private readonly plugin: StrapiExporterPlugin
|
|
21
|
+
private readonly containerEl: HTMLElement
|
|
22
|
+
private readonly components: {
|
|
23
|
+
schemaInput: TextAreaComponent | null
|
|
24
|
+
schemaDescriptionInput: TextAreaComponent | null
|
|
25
|
+
contentFieldInput: TextComponent | null
|
|
26
|
+
configOutput: TextAreaComponent | null
|
|
27
|
+
languageDropdown: DropdownComponent | null
|
|
28
|
+
routeSelector: DropdownComponent | null
|
|
29
|
+
} = {
|
|
30
|
+
schemaInput: null,
|
|
31
|
+
schemaDescriptionInput: null,
|
|
32
|
+
contentFieldInput: null,
|
|
33
|
+
configOutput: null,
|
|
34
|
+
languageDropdown: null,
|
|
35
|
+
routeSelector: null,
|
|
36
|
+
}
|
|
37
|
+
private currentRouteId: string
|
|
38
|
+
private readonly app: App
|
|
39
|
+
|
|
40
|
+
constructor(plugin: StrapiExporterPlugin, containerEl: HTMLElement) {
|
|
41
|
+
this.plugin = plugin
|
|
42
|
+
this.containerEl = containerEl
|
|
43
|
+
this.app = plugin.app
|
|
44
|
+
this.currentRouteId = this.plugin.settings.routes[0]?.id || ''
|
|
45
|
+
|
|
46
|
+
// Initialiser les services avec la clé API
|
|
47
|
+
this.initializeServices()
|
|
48
|
+
|
|
49
|
+
this.components = {
|
|
50
|
+
schemaInput: null,
|
|
51
|
+
schemaDescriptionInput: null,
|
|
52
|
+
contentFieldInput: null,
|
|
53
|
+
configOutput: null,
|
|
54
|
+
languageDropdown: null,
|
|
55
|
+
routeSelector: null,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Initialize or reinitialize services with current API key
|
|
61
|
+
*/
|
|
62
|
+
private initializeServices(): void {
|
|
63
|
+
if (!this.plugin.settings.openaiApiKey) {
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.fieldAnalyzer = new StructuredFieldAnalyzer({
|
|
68
|
+
openaiApiKey: this.plugin.settings.openaiApiKey,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
this.configGenerator = new ConfigurationGenerator({
|
|
72
|
+
openaiApiKey: this.plugin.settings.openaiApiKey,
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
display(): void {
|
|
77
|
+
const { containerEl } = this
|
|
78
|
+
containerEl.empty()
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
this.createHeader()
|
|
82
|
+
this.addRouteSelector()
|
|
83
|
+
this.addSchemaConfigSection()
|
|
84
|
+
this.addContentFieldSection()
|
|
85
|
+
this.addLanguageSection()
|
|
86
|
+
this.addAutoConfigSection()
|
|
87
|
+
} catch (error) {
|
|
88
|
+
this.showError(
|
|
89
|
+
'Failed to display configuration interface' + error.message
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private createHeader(): void {
|
|
95
|
+
this.containerEl.createEl('h2', {
|
|
96
|
+
text: 'Configuration',
|
|
97
|
+
cls: 'configuration-title',
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
this.containerEl.createEl('p', {
|
|
101
|
+
text: 'Configure your Strapi export settings and schema mappings.',
|
|
102
|
+
cls: 'configuration-description',
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private addRouteSelector(): void {
|
|
107
|
+
const routeSetting = new Setting(this.containerEl)
|
|
108
|
+
.setName('Select Route')
|
|
109
|
+
.setDesc('Choose the route to configure')
|
|
110
|
+
.addDropdown(dropdown => {
|
|
111
|
+
this.components.routeSelector = dropdown
|
|
112
|
+
this.plugin.settings.routes.forEach(route => {
|
|
113
|
+
dropdown.addOption(route.id, route.name)
|
|
114
|
+
})
|
|
115
|
+
dropdown.setValue(this.currentRouteId)
|
|
116
|
+
dropdown.onChange(async value => {
|
|
117
|
+
this.currentRouteId = value
|
|
118
|
+
await this.updateConfigurationFields()
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
this.addRouteManagementButtons(routeSetting)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private addRouteManagementButtons(setting: Setting): void {
|
|
126
|
+
setting
|
|
127
|
+
.addButton(button =>
|
|
128
|
+
button
|
|
129
|
+
.setButtonText('New Route')
|
|
130
|
+
.setTooltip('Create a new route configuration')
|
|
131
|
+
.onClick(() => this.createNewRoute())
|
|
132
|
+
)
|
|
133
|
+
.addButton(button =>
|
|
134
|
+
button
|
|
135
|
+
.setButtonText('Delete')
|
|
136
|
+
.setTooltip('Delete current route')
|
|
137
|
+
.onClick(() => this.deleteCurrentRoute())
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private async createNewRoute(): Promise<void> {
|
|
142
|
+
try {
|
|
143
|
+
const newRoute: RouteConfig = {
|
|
144
|
+
// Base properties
|
|
145
|
+
id: `route-${Date.now()}`,
|
|
146
|
+
name: 'New Route',
|
|
147
|
+
|
|
148
|
+
// Strapi configuration
|
|
149
|
+
schema: '',
|
|
150
|
+
schemaDescription: '',
|
|
151
|
+
generatedConfig: '',
|
|
152
|
+
contentType: 'articles', // Default content type
|
|
153
|
+
contentField: 'content',
|
|
154
|
+
|
|
155
|
+
// UI configuration
|
|
156
|
+
icon: 'file-text', // Default icon
|
|
157
|
+
description: 'New export route',
|
|
158
|
+
subtitle: '',
|
|
159
|
+
|
|
160
|
+
// Route settings
|
|
161
|
+
url: '',
|
|
162
|
+
enabled: true,
|
|
163
|
+
language: 'en',
|
|
164
|
+
|
|
165
|
+
// Mappings and instructions
|
|
166
|
+
fieldMappings: {},
|
|
167
|
+
additionalInstructions: '',
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.plugin.settings.routes.push(newRoute)
|
|
171
|
+
await this.plugin.saveSettings()
|
|
172
|
+
this.currentRouteId = newRoute.id
|
|
173
|
+
this.display()
|
|
174
|
+
new Notice('New route created successfully')
|
|
175
|
+
} catch (error) {
|
|
176
|
+
this.showError('Failed to create new route' + error.message)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private async deleteCurrentRoute(): Promise<void> {
|
|
181
|
+
try {
|
|
182
|
+
if (this.plugin.settings.routes.length <= 1) {
|
|
183
|
+
new Notice('Cannot delete the only route')
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const routeIndex = this.plugin.settings.routes.findIndex(
|
|
188
|
+
route => route.id === this.currentRouteId
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
if (routeIndex !== -1) {
|
|
192
|
+
this.plugin.settings.routes.splice(routeIndex, 1)
|
|
193
|
+
await this.plugin.saveSettings()
|
|
194
|
+
this.currentRouteId = this.plugin.settings.routes[0].id
|
|
195
|
+
this.display()
|
|
196
|
+
new Notice('Route deleted successfully')
|
|
197
|
+
}
|
|
198
|
+
} catch (error) {
|
|
199
|
+
this.showError('Failed to delete route' + error.message)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Updates schema input handler
|
|
205
|
+
*/
|
|
206
|
+
private addSchemaConfigSection(): void {
|
|
207
|
+
try {
|
|
208
|
+
// Strapi Schema Input
|
|
209
|
+
new Setting(this.containerEl)
|
|
210
|
+
.setName('Strapi Schema')
|
|
211
|
+
.setDesc('Paste your complete Strapi schema JSON here')
|
|
212
|
+
.addTextArea(text => {
|
|
213
|
+
this.components.schemaInput = text
|
|
214
|
+
text.setValue(this.getCurrentRouteSchema()).onChange(async value => {
|
|
215
|
+
// Validate JSON format
|
|
216
|
+
JSON.parse(value)
|
|
217
|
+
await this.updateCurrentRouteConfig('schema', value)
|
|
218
|
+
})
|
|
219
|
+
text.inputEl.rows = 10
|
|
220
|
+
text.inputEl.cols = 50
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
// Schema Description Input
|
|
224
|
+
new Setting(this.containerEl)
|
|
225
|
+
.setName('Schema Description')
|
|
226
|
+
.setDesc('Provide descriptions for each field in the schema')
|
|
227
|
+
.addTextArea(text => {
|
|
228
|
+
this.components.schemaDescriptionInput = text
|
|
229
|
+
text
|
|
230
|
+
.setValue(this.getCurrentRouteSchemaDescription())
|
|
231
|
+
.onChange(async value => {
|
|
232
|
+
JSON.parse(value)
|
|
233
|
+
await this.updateCurrentRouteConfig('schemaDescription', value)
|
|
234
|
+
})
|
|
235
|
+
text.inputEl.rows = 10
|
|
236
|
+
text.inputEl.cols = 50
|
|
237
|
+
})
|
|
238
|
+
} catch (error) {
|
|
239
|
+
this.showError(
|
|
240
|
+
'Failed to set up schema configuration section' + error.message
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private addContentFieldSection(): void {
|
|
246
|
+
new Setting(this.containerEl)
|
|
247
|
+
.setName('Content Field Name')
|
|
248
|
+
.setDesc(
|
|
249
|
+
'Enter the name of the field where the main article content should be inserted'
|
|
250
|
+
)
|
|
251
|
+
.addText(text => {
|
|
252
|
+
this.components.contentFieldInput = text
|
|
253
|
+
text
|
|
254
|
+
.setValue(this.getCurrentRouteContentField())
|
|
255
|
+
.setPlaceholder('content')
|
|
256
|
+
.onChange(async value => {
|
|
257
|
+
await this.updateCurrentRouteConfig('contentField', value)
|
|
258
|
+
})
|
|
259
|
+
})
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private addLanguageSection(): void {
|
|
263
|
+
new Setting(this.containerEl)
|
|
264
|
+
.setName('Target Language')
|
|
265
|
+
.setDesc('Select the target language for the exported content')
|
|
266
|
+
.addDropdown(dropdown => {
|
|
267
|
+
this.components.languageDropdown = dropdown
|
|
268
|
+
const languages = this.getAvailableLanguages()
|
|
269
|
+
Object.entries(languages).forEach(([code, name]) => {
|
|
270
|
+
dropdown.addOption(code, name)
|
|
271
|
+
})
|
|
272
|
+
dropdown
|
|
273
|
+
.setValue(this.getCurrentRouteLanguage())
|
|
274
|
+
.onChange(async value => {
|
|
275
|
+
await this.updateCurrentRouteConfig('language', value)
|
|
276
|
+
})
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private addAutoConfigSection(): void {
|
|
281
|
+
new Setting(this.containerEl)
|
|
282
|
+
.setName('Auto-Configure')
|
|
283
|
+
.setDesc('Automatically configure fields using OpenAI (Experimental)')
|
|
284
|
+
.addButton(button => {
|
|
285
|
+
button
|
|
286
|
+
.setButtonText('Generate Configuration')
|
|
287
|
+
.setCta()
|
|
288
|
+
.onClick(() => this.generateConfiguration())
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
new Setting(this.containerEl)
|
|
292
|
+
.setName('Generated Configuration')
|
|
293
|
+
.setDesc(
|
|
294
|
+
'The generated configuration will appear here. You can edit it if needed.'
|
|
295
|
+
)
|
|
296
|
+
.addTextArea(text => {
|
|
297
|
+
this.components.configOutput = text
|
|
298
|
+
text
|
|
299
|
+
.setValue(this.getCurrentRouteGeneratedConfig())
|
|
300
|
+
.onChange(async value => {
|
|
301
|
+
await this.updateCurrentRouteConfig('generatedConfig', value)
|
|
302
|
+
})
|
|
303
|
+
text.inputEl.rows = 10
|
|
304
|
+
text.inputEl.cols = 50
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
new Setting(this.containerEl)
|
|
308
|
+
.setName('Apply Configuration')
|
|
309
|
+
.setDesc('Use this configuration for the plugin')
|
|
310
|
+
.addButton(button => {
|
|
311
|
+
button
|
|
312
|
+
.setButtonText('Apply')
|
|
313
|
+
.setCta()
|
|
314
|
+
.onClick(() => this.applyConfiguration())
|
|
315
|
+
})
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private async generateConfiguration(): Promise<void> {
|
|
319
|
+
try {
|
|
320
|
+
const currentRoute = this.plugin.settings.routes.find(
|
|
321
|
+
route => route.id === this.currentRouteId
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
if (!currentRoute) {
|
|
325
|
+
throw new Error('Current route not found')
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Validate OpenAI API key
|
|
329
|
+
if (!this.plugin.settings.openaiApiKey) {
|
|
330
|
+
throw new Error(
|
|
331
|
+
'OpenAI API key not configured. Please configure it in settings.'
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Get both schema and schema description
|
|
336
|
+
const schema = currentRoute.schema
|
|
337
|
+
const schemaDescription = currentRoute.schemaDescription
|
|
338
|
+
|
|
339
|
+
if (!schema || !schemaDescription) {
|
|
340
|
+
throw new Error('Both schema and schema description are required')
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Generate configuration using the new service
|
|
344
|
+
const config = await this.configGenerator.generateConfiguration({
|
|
345
|
+
schema,
|
|
346
|
+
schemaDescription,
|
|
347
|
+
language: currentRoute.language,
|
|
348
|
+
additionalInstructions: currentRoute.additionalInstructions,
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
if (this.components.configOutput) {
|
|
352
|
+
// Format the output configuration
|
|
353
|
+
const formattedConfig = JSON.stringify(config, null, 2)
|
|
354
|
+
|
|
355
|
+
// Update the UI and save
|
|
356
|
+
await this.updateCurrentRouteConfig('generatedConfig', formattedConfig)
|
|
357
|
+
|
|
358
|
+
this.components.configOutput.setValue(formattedConfig)
|
|
359
|
+
|
|
360
|
+
new Notice('Configuration generated successfully!')
|
|
361
|
+
}
|
|
362
|
+
} catch (error) {
|
|
363
|
+
let errorMessage = 'Failed to generate configuration'
|
|
364
|
+
|
|
365
|
+
if (error instanceof SyntaxError) {
|
|
366
|
+
errorMessage = 'Invalid JSON format in schema or description'
|
|
367
|
+
} else if (error instanceof Error) {
|
|
368
|
+
errorMessage = error.message
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
new Notice(errorMessage)
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Apply the generated configuration
|
|
377
|
+
*/
|
|
378
|
+
private async applyConfiguration(): Promise<void> {
|
|
379
|
+
try {
|
|
380
|
+
if (!this.components.configOutput) {
|
|
381
|
+
throw new Error('Configuration output not initialized')
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const configValue = this.components.configOutput.getValue()
|
|
385
|
+
|
|
386
|
+
// Validate configuration format
|
|
387
|
+
const config = JSON.parse(configValue)
|
|
388
|
+
|
|
389
|
+
const currentRoute = this.plugin.settings.routes.find(
|
|
390
|
+
route => route.id === this.currentRouteId
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
if (!currentRoute) {
|
|
394
|
+
throw new Error('Current route not found')
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Update route with new configuration
|
|
398
|
+
currentRoute.fieldMappings = config.fieldMappings || {}
|
|
399
|
+
currentRoute.additionalInstructions = config.additionalInstructions || ''
|
|
400
|
+
currentRoute.contentField = config.contentField || 'content'
|
|
401
|
+
|
|
402
|
+
await this.plugin.saveSettings()
|
|
403
|
+
new Notice('Configuration applied successfully!')
|
|
404
|
+
} catch (error) {
|
|
405
|
+
new Notice(`Failed to apply configuration: ${error.message}`)
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Utility methods
|
|
410
|
+
private getAvailableLanguages(): Record<string, string> {
|
|
411
|
+
return {
|
|
412
|
+
en: 'English',
|
|
413
|
+
fr: 'French',
|
|
414
|
+
es: 'Spanish',
|
|
415
|
+
de: 'German',
|
|
416
|
+
it: 'Italian',
|
|
417
|
+
zh: 'Chinese',
|
|
418
|
+
ja: 'Japanese',
|
|
419
|
+
ko: 'Korean',
|
|
420
|
+
pt: 'Portuguese',
|
|
421
|
+
ru: 'Russian',
|
|
422
|
+
ar: 'Arabic',
|
|
423
|
+
hi: 'Hindi',
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
private showError(message: string): void {
|
|
428
|
+
new Notice(`Configuration Error: ${message}`)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private getCurrentRouteGeneratedConfig(): string {
|
|
432
|
+
return this.getCurrentRouteConfig('generatedConfig')
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
private getCurrentRouteSchema(): string {
|
|
436
|
+
return this.getCurrentRouteConfig('schema')
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
private getCurrentRouteSchemaDescription(): string {
|
|
440
|
+
return this.getCurrentRouteConfig('schemaDescription')
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private getCurrentRouteLanguage(): string {
|
|
444
|
+
return this.getCurrentRouteConfig('language') || 'en'
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
private getCurrentRouteContentField(): string {
|
|
448
|
+
return this.getCurrentRouteConfig('contentField') || 'content'
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
private getCurrentRouteConfig(key: string): string {
|
|
452
|
+
const currentRoute = this.plugin.settings.routes.find(
|
|
453
|
+
route => route.id === this.currentRouteId
|
|
454
|
+
)
|
|
455
|
+
return currentRoute?.[key] || ''
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
private async updateCurrentRouteConfig(
|
|
459
|
+
key: string,
|
|
460
|
+
value: string
|
|
461
|
+
): Promise<void> {
|
|
462
|
+
const routeIndex = this.plugin.settings.routes.findIndex(
|
|
463
|
+
route => route.id === this.currentRouteId
|
|
464
|
+
)
|
|
465
|
+
if (routeIndex !== -1) {
|
|
466
|
+
this.plugin.settings.routes[routeIndex][key] = value
|
|
467
|
+
await this.plugin.saveSettings()
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
private async updateConfigurationFields(): Promise<void> {
|
|
472
|
+
try {
|
|
473
|
+
const currentRoute = this.plugin.settings.routes.find(
|
|
474
|
+
route => route.id === this.currentRouteId
|
|
475
|
+
)
|
|
476
|
+
if (currentRoute) {
|
|
477
|
+
if (this.components.schemaInput) {
|
|
478
|
+
this.components.schemaInput.setValue(currentRoute.schema || '')
|
|
479
|
+
}
|
|
480
|
+
if (this.components.schemaDescriptionInput) {
|
|
481
|
+
this.components.schemaDescriptionInput.setValue(
|
|
482
|
+
currentRoute.schemaDescription || ''
|
|
483
|
+
)
|
|
484
|
+
}
|
|
485
|
+
if (this.components.configOutput) {
|
|
486
|
+
this.components.configOutput.setValue(
|
|
487
|
+
currentRoute.generatedConfig || ''
|
|
488
|
+
)
|
|
489
|
+
}
|
|
490
|
+
if (this.components.languageDropdown) {
|
|
491
|
+
this.components.languageDropdown.setValue(
|
|
492
|
+
currentRoute.language || 'en'
|
|
493
|
+
)
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
} catch (error) {
|
|
497
|
+
this.showError('Failed to update configuration fields' + error.message)
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
private async identifyImageFields(
|
|
502
|
+
generatedConfig: string
|
|
503
|
+
): Promise<string[]> {
|
|
504
|
+
try {
|
|
505
|
+
const analysis = await this.fieldAnalyzer.analyzeSchema(generatedConfig)
|
|
506
|
+
return analysis.imageFields.map(field => field.fieldName)
|
|
507
|
+
} catch (error) {
|
|
508
|
+
new Notice(
|
|
509
|
+
'Error identifying image fields. Please try again.' + error.message
|
|
510
|
+
)
|
|
511
|
+
return []
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
private async openImageSelectionModal(
|
|
516
|
+
imageFields: string[],
|
|
517
|
+
currentRoute: RouteConfig
|
|
518
|
+
): Promise<void> {
|
|
519
|
+
const modal = new ImageSelectionModal(
|
|
520
|
+
this.app,
|
|
521
|
+
imageFields,
|
|
522
|
+
async selections => {
|
|
523
|
+
await this.handleImageSelections(selections, currentRoute)
|
|
524
|
+
}
|
|
525
|
+
)
|
|
526
|
+
modal.open()
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
private async handleImageSelections(
|
|
530
|
+
selections: Record<string, string>,
|
|
531
|
+
currentRoute: RouteConfig
|
|
532
|
+
): Promise<void> {
|
|
533
|
+
try {
|
|
534
|
+
for (const [field, imagePath] of Object.entries(selections)) {
|
|
535
|
+
if (imagePath) {
|
|
536
|
+
await this.processImageField(field, imagePath, currentRoute)
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
await this.plugin.saveSettings()
|
|
540
|
+
new Notice('Image configurations updated successfully')
|
|
541
|
+
} catch (error) {
|
|
542
|
+
this.showError('Failed to process image selections' + error.message)
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
private async processImageField(
|
|
547
|
+
field: string,
|
|
548
|
+
imagePath: string,
|
|
549
|
+
currentRoute: RouteConfig
|
|
550
|
+
): Promise<void> {
|
|
551
|
+
const file = this.app.vault.getAbstractFileByPath(imagePath)
|
|
552
|
+
if (!(file instanceof TFile)) {
|
|
553
|
+
throw new Error(`File not found: ${imagePath}`)
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const result = await uploadImageToStrapi(
|
|
557
|
+
file,
|
|
558
|
+
file.name,
|
|
559
|
+
this.plugin.settings,
|
|
560
|
+
this.app
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
if (result?.url) {
|
|
564
|
+
// Initialiser le fieldMapping avec les propriétés requises
|
|
565
|
+
if (!currentRoute.fieldMappings[field]) {
|
|
566
|
+
currentRoute.fieldMappings[field] = {
|
|
567
|
+
obsidianSource: 'frontmatter', // ou 'content' selon le cas
|
|
568
|
+
type: 'string',
|
|
569
|
+
format: 'url',
|
|
570
|
+
required: false,
|
|
571
|
+
transform: 'value => value', // transformation par défaut
|
|
572
|
+
validation: {
|
|
573
|
+
type: 'string',
|
|
574
|
+
pattern: '^https?://.+',
|
|
575
|
+
},
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
currentRoute.fieldMappings[field].value = result.url
|
|
580
|
+
} else {
|
|
581
|
+
throw new Error('Failed to upload image')
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
class ImageSelectionModal extends Modal {
|
|
587
|
+
private imageFields: string[]
|
|
588
|
+
private onSubmit: (selections: Record<string, string>) => void
|
|
589
|
+
private selections: Record<string, string> = {}
|
|
590
|
+
|
|
591
|
+
constructor(
|
|
592
|
+
app: App,
|
|
593
|
+
imageFields: string[],
|
|
594
|
+
onSubmit: (selections: Record<string, string>) => void
|
|
595
|
+
) {
|
|
596
|
+
super(app)
|
|
597
|
+
this.imageFields = imageFields
|
|
598
|
+
this.onSubmit = onSubmit
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
onOpen(): void {
|
|
602
|
+
const { contentEl } = this
|
|
603
|
+
|
|
604
|
+
contentEl.createEl('h2', { text: 'Select Images for Fields' })
|
|
605
|
+
|
|
606
|
+
this.imageFields.forEach(field => {
|
|
607
|
+
this.createFieldSelector(contentEl, field)
|
|
608
|
+
})
|
|
609
|
+
|
|
610
|
+
this.createButtons(contentEl)
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
private createFieldSelector(container: HTMLElement, field: string): Setting {
|
|
614
|
+
const setting = new Setting(container)
|
|
615
|
+
.setName(field)
|
|
616
|
+
.setDesc(`Select an image for ${field}`)
|
|
617
|
+
.addButton(button =>
|
|
618
|
+
button.setButtonText('Choose Image').onClick(async () => {
|
|
619
|
+
const imagePath = await this.selectImage()
|
|
620
|
+
if (imagePath) {
|
|
621
|
+
this.selections[field] = imagePath
|
|
622
|
+
button.setButtonText('Change Image')
|
|
623
|
+
new Notice(`Image selected for ${field}`)
|
|
624
|
+
}
|
|
625
|
+
})
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
return setting
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
private createButtons(container: HTMLElement): void {
|
|
632
|
+
const buttonContainer = container.createDiv('modal-button-container')
|
|
633
|
+
|
|
634
|
+
new Setting(buttonContainer)
|
|
635
|
+
.addButton(button =>
|
|
636
|
+
button
|
|
637
|
+
.setButtonText('Confirm')
|
|
638
|
+
.setCta()
|
|
639
|
+
.onClick(() => {
|
|
640
|
+
this.close()
|
|
641
|
+
this.onSubmit(this.selections)
|
|
642
|
+
})
|
|
643
|
+
)
|
|
644
|
+
.addButton(button =>
|
|
645
|
+
button.setButtonText('Cancel').onClick(() => {
|
|
646
|
+
this.close()
|
|
647
|
+
})
|
|
648
|
+
)
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
private async selectImage(): Promise<string | null> {
|
|
652
|
+
// TODO: Implement file selection using Obsidian's API
|
|
653
|
+
// This is a placeholder that should be replaced with actual file selection logic
|
|
654
|
+
return 'path/to/image.jpg'
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
onClose(): void {
|
|
658
|
+
const { contentEl } = this
|
|
659
|
+
contentEl.empty()
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
export default Configuration
|