notes-to-strapi-export-article-ai 1.0.119 → 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.
Files changed (46) hide show
  1. package/.eslintrc +30 -22
  2. package/README.md +98 -143
  3. package/images/img.png +0 -0
  4. package/images/img_1.png +0 -0
  5. package/images/img_10.png +0 -0
  6. package/images/img_11.png +0 -0
  7. package/images/img_12.png +0 -0
  8. package/images/img_13.png +0 -0
  9. package/images/img_2.png +0 -0
  10. package/images/img_3.png +0 -0
  11. package/images/img_4.png +0 -0
  12. package/images/img_5.png +0 -0
  13. package/images/img_6.png +0 -0
  14. package/images/img_7.png +0 -0
  15. package/images/img_8.png +0 -0
  16. package/images/img_9.png +0 -0
  17. package/manifest.json +2 -2
  18. package/package.json +29 -26
  19. package/src/components/APIKeys.ts +219 -0
  20. package/src/components/Configuration.ts +663 -0
  21. package/src/components/Dashboard.ts +184 -0
  22. package/src/components/ImageSelectionModal.ts +58 -0
  23. package/src/components/Routes.ts +279 -0
  24. package/src/constants.ts +22 -61
  25. package/src/main.ts +177 -34
  26. package/src/services/configuration-generator.ts +172 -0
  27. package/src/services/field-analyzer.ts +84 -0
  28. package/src/services/frontmatter.ts +329 -0
  29. package/src/services/strapi-export.ts +436 -0
  30. package/src/settings/UnifiedSettingsTab.ts +206 -0
  31. package/src/types/image.ts +27 -16
  32. package/src/types/index.ts +3 -0
  33. package/src/types/route.ts +51 -0
  34. package/src/types/settings.ts +22 -23
  35. package/src/utils/analyse-file.ts +94 -0
  36. package/src/utils/debounce.ts +34 -0
  37. package/src/utils/image-processor.ts +124 -400
  38. package/src/utils/preview-modal.ts +265 -0
  39. package/src/utils/process-file.ts +122 -0
  40. package/src/utils/strapi-uploader.ts +120 -119
  41. package/src/settings.ts +0 -404
  42. package/src/types/article.ts +0 -8
  43. package/src/utils/openai-generator.ts +0 -139
  44. package/src/utils/validators.ts +0 -8
  45. package/version-bump.mjs +0 -14
  46. package/versions.json +0 -119
@@ -0,0 +1,184 @@
1
+ import { Setting, Notice } from 'obsidian'
2
+ import StrapiExporterPlugin from '../main'
3
+
4
+ interface ConfigStatus {
5
+ key: string
6
+ status: boolean
7
+ description: string
8
+ }
9
+
10
+ interface QuickLink {
11
+ name: string
12
+ description: string
13
+ targetTab: string
14
+ icon?: string
15
+ }
16
+
17
+ export class Dashboard {
18
+ private plugin: StrapiExporterPlugin
19
+ private containerEl: HTMLElement
20
+
21
+ constructor(plugin: StrapiExporterPlugin, containerEl: HTMLElement) {
22
+ this.plugin = plugin
23
+ this.containerEl = containerEl
24
+ }
25
+
26
+ display(): void {
27
+ try {
28
+ const { containerEl } = this
29
+ containerEl.empty()
30
+
31
+ this.createHeader()
32
+ this.addSummary()
33
+ this.addQuickLinks()
34
+ } catch (error) {
35
+ new Notice('Error displaying dashboard' + error.message)
36
+ }
37
+ }
38
+
39
+ private createHeader(): void {
40
+ const headerEl = this.containerEl.createEl('div', {
41
+ cls: 'dashboard-header',
42
+ })
43
+ headerEl.createEl('h2', {
44
+ text: 'Strapi Exporter Dashboard',
45
+ cls: 'dashboard-title',
46
+ })
47
+
48
+ headerEl.createEl('p', {
49
+ text: 'Overview of your Strapi export configuration and quick access to settings.',
50
+ cls: 'dashboard-description',
51
+ })
52
+ }
53
+
54
+ private addSummary(): void {
55
+ const summaryEl = this.containerEl.createEl('div', {
56
+ cls: 'dashboard-summary',
57
+ })
58
+
59
+ const configStatus = this.getConfigurationStatus()
60
+ summaryEl.createEl('h3', {
61
+ text: 'Configuration Status',
62
+ cls: 'dashboard-section-title',
63
+ })
64
+
65
+ this.createStatusList(summaryEl, configStatus)
66
+ }
67
+
68
+ private createStatusList(
69
+ container: HTMLElement,
70
+ statuses: ConfigStatus[]
71
+ ): void {
72
+ const statusList = container.createEl('ul', { cls: 'status-list' })
73
+
74
+ statuses.forEach(({ key, status, description }) => {
75
+ const listItem = statusList.createEl('li', { cls: 'status-item' })
76
+
77
+ // Status icon
78
+ listItem.createSpan({
79
+ text: status ? '✅ ' : '❌ ',
80
+ cls: `status-icon ${status ? 'status-ok' : 'status-error'}`,
81
+ })
82
+
83
+ // Status text
84
+ const textContainer = listItem.createSpan({ cls: 'status-text' })
85
+ textContainer.createSpan({ text: key, cls: 'status-key' })
86
+ textContainer.createSpan({ text: description, cls: 'status-description' })
87
+ })
88
+ }
89
+
90
+ private addQuickLinks(): void {
91
+ const quickLinksEl = this.containerEl.createEl('div', {
92
+ cls: 'dashboard-quick-links',
93
+ })
94
+
95
+ quickLinksEl.createEl('h3', {
96
+ text: 'Quick Links',
97
+ cls: 'dashboard-section-title',
98
+ })
99
+
100
+ this.quickLinks.forEach(link => {
101
+ this.createQuickLink(quickLinksEl, link)
102
+ })
103
+ }
104
+
105
+ private createQuickLink(container: HTMLElement, link: QuickLink): void {
106
+ new Setting(container)
107
+ .setName(link.name)
108
+ .setDesc(link.description)
109
+ .addButton(button => {
110
+ button
111
+ .setButtonText(`Go to ${link.name}`)
112
+ .setCta()
113
+ .onClick(() => {
114
+ this.navigateToTab(link.targetTab)
115
+ })
116
+ })
117
+ }
118
+
119
+ private navigateToTab(tab: string): void {
120
+ try {
121
+ this.plugin.settings.currentTab = tab
122
+ this.plugin.settingsTab.display()
123
+ } catch (error) {
124
+ new Notice(`Failed to navigate to ${tab}` + error.message)
125
+ }
126
+ }
127
+
128
+ private getConfigurationStatus(): ConfigStatus[] {
129
+ const { settings } = this.plugin
130
+ return [
131
+ {
132
+ key: 'Strapi URL',
133
+ status: !!settings.strapiUrl,
134
+ description: 'Connection to your Strapi instance',
135
+ },
136
+ {
137
+ key: 'Strapi API Token',
138
+ status: !!settings.strapiApiToken,
139
+ description: 'Authentication token for Strapi API',
140
+ },
141
+ {
142
+ key: 'ForVoyez API Key',
143
+ status: !!settings.forvoyezApiKey,
144
+ description: 'API key for ForVoyez integration',
145
+ },
146
+ {
147
+ key: 'OpenAI API Key',
148
+ status: !!settings.openaiApiKey,
149
+ description: 'API key for AI-powered features',
150
+ },
151
+ {
152
+ key: 'Schema Configured',
153
+ status: settings.routes.some(route => !!route.schema),
154
+ description: 'Strapi schema configuration',
155
+ },
156
+ {
157
+ key: 'Routes Configured',
158
+ status: settings.routes.length > 0,
159
+ description: 'Export route configuration',
160
+ },
161
+ ]
162
+ }
163
+
164
+ private readonly quickLinks: QuickLink[] = [
165
+ {
166
+ name: 'Configuration',
167
+ description: 'Set up your Strapi schema and field mappings',
168
+ targetTab: 'configuration',
169
+ icon: 'settings',
170
+ },
171
+ {
172
+ name: 'API Keys',
173
+ description: 'Configure your API keys',
174
+ targetTab: 'apiKeys',
175
+ icon: 'key',
176
+ },
177
+ {
178
+ name: 'Routes',
179
+ description: 'Manage your export routes',
180
+ targetTab: 'routes',
181
+ icon: 'git-branch',
182
+ },
183
+ ]
184
+ }
@@ -0,0 +1,58 @@
1
+ import { App, Modal, Setting } from 'obsidian'
2
+
3
+ export class ImageSelectionModal extends Modal {
4
+ private selectedImages: string[] = []
5
+ private onConfirm: (images: string[]) => void
6
+ private isMultiple: boolean
7
+
8
+ constructor(
9
+ app: App,
10
+ isMultiple: boolean,
11
+ onConfirm: (images: string[]) => void
12
+ ) {
13
+ super(app)
14
+ this.isMultiple = isMultiple
15
+ this.onConfirm = onConfirm
16
+ }
17
+
18
+ onOpen() {
19
+ const { contentEl } = this
20
+ contentEl.empty()
21
+
22
+ contentEl.createEl('h2', {
23
+ text: this.isMultiple ? 'Select Images' : 'Select an Image',
24
+ })
25
+
26
+ new Setting(contentEl)
27
+ .setName('Image Selection')
28
+ .setDesc(
29
+ this.isMultiple ? 'Choose one or more images' : 'Choose an image'
30
+ )
31
+ .addButton(button =>
32
+ button
33
+ .setButtonText('Select Image(s)')
34
+ .onClick(() => this.openFilePicker())
35
+ )
36
+
37
+ new Setting(contentEl).addButton(button =>
38
+ button
39
+ .setButtonText('Confirm')
40
+ .setCta()
41
+ .onClick(() => {
42
+ this.close()
43
+ this.onConfirm(this.selectedImages)
44
+ })
45
+ )
46
+ }
47
+
48
+ private async openFilePicker() {
49
+ // Implement file picking logic here
50
+ // This could use Obsidian's file suggestion API or a custom file browser
51
+ // Update this.selectedImages with the chosen file path(s)
52
+ }
53
+
54
+ onClose() {
55
+ const { contentEl } = this
56
+ contentEl.empty()
57
+ }
58
+ }
@@ -0,0 +1,279 @@
1
+ import { Notice, Setting, TextComponent } from 'obsidian'
2
+ import StrapiExporterPlugin from '../main'
3
+ import { RouteConfig } from '../types'
4
+
5
+ interface RouteField {
6
+ name: string
7
+ description: string
8
+ type: 'text' | 'toggle'
9
+ key: keyof RouteConfig
10
+ placeholder?: string
11
+ icon?: string
12
+ }
13
+
14
+ export class Routes {
15
+ private plugin: StrapiExporterPlugin
16
+ private containerEl: HTMLElement
17
+
18
+ private routeFields: RouteField[] = [
19
+ {
20
+ name: 'Name',
21
+ description: 'Enter a name for this route',
22
+ type: 'text',
23
+ key: 'name',
24
+ placeholder: 'My Export Route',
25
+ },
26
+ {
27
+ name: 'Icon',
28
+ description: 'Enter an icon name (e.g., "upload", "link", "star")',
29
+ type: 'text',
30
+ key: 'icon',
31
+ placeholder: 'upload',
32
+ },
33
+ {
34
+ name: 'URL',
35
+ description: 'Enter the Strapi API endpoint URL for this route',
36
+ type: 'text',
37
+ key: 'url',
38
+ placeholder: '/api/articles',
39
+ },
40
+ {
41
+ name: 'Subtitle',
42
+ description: 'Enter a brief subtitle for this route',
43
+ type: 'text',
44
+ key: 'subtitle',
45
+ placeholder: 'Export to articles collection',
46
+ },
47
+ ]
48
+
49
+ constructor(plugin: StrapiExporterPlugin, containerEl: HTMLElement) {
50
+ this.plugin = plugin
51
+ this.containerEl = containerEl
52
+ }
53
+
54
+ display(): void {
55
+ const { containerEl } = this
56
+ containerEl.empty()
57
+
58
+ try {
59
+ this.createHeader()
60
+ this.createRoutesList()
61
+ this.addNewRouteButton()
62
+ } catch (error) {
63
+ this.showError('Failed to display routes configuration' + error.message)
64
+ }
65
+ }
66
+
67
+ private createHeader(): void {
68
+ const headerEl = this.containerEl.createEl('div', { cls: 'routes-header' })
69
+
70
+ headerEl.createEl('h2', {
71
+ text: 'Routes Configuration',
72
+ cls: 'routes-title',
73
+ })
74
+
75
+ headerEl.createEl('p', {
76
+ text: 'Configure export routes for different content types.',
77
+ cls: 'routes-description',
78
+ })
79
+ }
80
+
81
+ private createRoutesList(): void {
82
+ const routesContainer = this.containerEl.createDiv('routes-list')
83
+
84
+ this.plugin.settings.routes.forEach((route, index) => {
85
+ this.createRouteConfigSettings(route, index, routesContainer)
86
+ })
87
+ }
88
+
89
+ private createTextComponent(
90
+ text: TextComponent,
91
+ route: RouteConfig,
92
+ field: keyof RouteConfig
93
+ ): TextComponent {
94
+ return text.setValue(route[field] as string).onChange(async value => {
95
+ try {
96
+ await this.updateRouteField(route, field, value)
97
+ } catch (error) {
98
+ this.showError(`Failed to update ${String(field)}` + error.message)
99
+ }
100
+ })
101
+ }
102
+
103
+ private createRouteConfigSettings(
104
+ route: RouteConfig,
105
+ index: number,
106
+ container: HTMLElement
107
+ ): void {
108
+ try {
109
+ const routeEl = container.createEl('div', {
110
+ cls: 'route-config',
111
+ })
112
+
113
+ // Route header with toggle
114
+ this.createRouteHeader(routeEl, route, index)
115
+
116
+ // Route fields
117
+ this.routeFields.forEach(field => {
118
+ this.createRouteField(routeEl, route, field)
119
+ })
120
+
121
+ // Delete button
122
+ this.addDeleteButton(routeEl, index)
123
+ } catch (error) {
124
+ this.showError(
125
+ `Failed to create settings for route ${index + 1}` + error.message
126
+ )
127
+ }
128
+ }
129
+
130
+ private createRouteHeader(
131
+ routeEl: HTMLElement,
132
+ route: RouteConfig,
133
+ index: number
134
+ ): void {
135
+ new Setting(routeEl)
136
+ .setName(`Route ${index + 1}: ${route.name}`)
137
+ .setDesc('Configure route settings')
138
+ .addToggle(toggle =>
139
+ toggle.setValue(route.enabled).onChange(async value => {
140
+ try {
141
+ await this.updateRouteEnabled(route, value)
142
+ } catch (error) {
143
+ this.showError('Failed to update route state' + error.message)
144
+ }
145
+ })
146
+ )
147
+ }
148
+
149
+ private createRouteField(
150
+ routeEl: HTMLElement,
151
+ route: RouteConfig,
152
+ field: RouteField
153
+ ): void {
154
+ new Setting(routeEl)
155
+ .setName(field.name)
156
+ .setDesc(field.description)
157
+ .addText(text => {
158
+ text.setPlaceholder(field.placeholder || '')
159
+ return this.createTextComponent(text, route, field.key)
160
+ })
161
+ }
162
+
163
+ private addDeleteButton(routeEl: HTMLElement, index: number): void {
164
+ new Setting(routeEl).addButton(button =>
165
+ button
166
+ .setButtonText('Delete Route')
167
+ .setWarning()
168
+ .onClick(async () => {
169
+ try {
170
+ await this.deleteRoute(index)
171
+ } catch (error) {
172
+ this.showError('Failed to delete route' + error.message)
173
+ }
174
+ })
175
+ )
176
+ }
177
+
178
+ private async updateRouteField(
179
+ route: RouteConfig,
180
+ field: keyof RouteConfig,
181
+ value: string | boolean
182
+ ): Promise<void> {
183
+ if (field === 'enabled') {
184
+ route[field] = value as boolean
185
+ } else {
186
+ const stringFields: (keyof RouteConfig)[] = [
187
+ 'id',
188
+ 'name',
189
+ 'icon',
190
+ 'url',
191
+ 'contentType',
192
+ 'contentField',
193
+ 'additionalInstructions',
194
+ 'description',
195
+ 'subtitle',
196
+ 'schema',
197
+ 'schemaDescription',
198
+ 'language',
199
+ ]
200
+
201
+ if (stringFields.includes(field)) {
202
+ ;(route[field] as string) = value as string
203
+ }
204
+ }
205
+
206
+ await this.plugin.saveSettings()
207
+
208
+ if (field === 'icon' || field === 'enabled') {
209
+ await this.plugin.debouncedUpdateRibbonIcons()
210
+ }
211
+ }
212
+
213
+ private async updateRouteEnabled(
214
+ route: RouteConfig,
215
+ value: boolean
216
+ ): Promise<void> {
217
+ route.enabled = value
218
+ await this.plugin.saveSettings()
219
+ this.plugin.updateRibbonIcons()
220
+ }
221
+
222
+ private async deleteRoute(index: number): Promise<void> {
223
+ if (this.plugin.settings.routes.length <= 1) {
224
+ new Notice('Cannot delete the only route')
225
+ return
226
+ }
227
+
228
+ this.plugin.settings.routes.splice(index, 1)
229
+ await this.plugin.saveSettings()
230
+ this.plugin.updateRibbonIcons()
231
+ this.display()
232
+ }
233
+
234
+ private addNewRouteButton(): void {
235
+ new Setting(this.containerEl)
236
+ .setName('Add New Route')
237
+ .setDesc('Create a new route configuration')
238
+ .addButton(button =>
239
+ button
240
+ .setButtonText('Add Route')
241
+ .setCta()
242
+ .onClick(async () => {
243
+ try {
244
+ await this.createNewRoute()
245
+ } catch (error) {
246
+ this.showError('Failed to create new route' + error.message)
247
+ }
248
+ })
249
+ )
250
+ }
251
+
252
+ private async createNewRoute(): Promise<void> {
253
+ const newRoute: RouteConfig = {
254
+ generatedConfig: '',
255
+ id: `route-${Date.now()}`,
256
+ name: 'New Route',
257
+ icon: 'star',
258
+ url: '',
259
+ contentType: '',
260
+ contentField: '',
261
+ additionalInstructions: '',
262
+ enabled: true,
263
+ description: '',
264
+ subtitle: '',
265
+ schema: '',
266
+ schemaDescription: '',
267
+ language: '',
268
+ fieldMappings: {},
269
+ }
270
+
271
+ this.plugin.settings.routes.push(newRoute)
272
+ await this.plugin.saveSettings()
273
+ this.display()
274
+ }
275
+
276
+ private showError(message: string): void {
277
+ new Notice(message)
278
+ }
279
+ }
package/src/constants.ts CHANGED
@@ -1,67 +1,28 @@
1
- import { StrapiExporterSettings } from './types/settings'
1
+ import { StrapiExporterSettings } from './types'
2
2
 
3
- /**
4
- * The default settings for the plugin
5
- */
6
3
  export const DEFAULT_STRAPI_EXPORTER_SETTINGS: StrapiExporterSettings = {
7
4
  strapiUrl: '',
8
5
  strapiApiToken: '',
6
+ forvoyezApiKey: '',
9
7
  openaiApiKey: '',
10
- jsonTemplate: `{
11
- "data": {
12
- "title": "string",
13
- "seo_title": "string",
14
- "seo_description": "string",
15
- "slug": "string",
16
- "excerpt": "string",
17
- "links": [
18
- {
19
- "id": "number",
20
- "label": "string",
21
- "url": "string"
22
- }
23
- ],
24
- "subtitle": "string",
25
- "type": "string",
26
- "rank": "number",
27
- "tags": [
28
- {
29
- "id": "number",
30
- "name": "string"
31
- }
32
- ],
33
- "locale": "string"
34
- }
35
- }`,
36
- jsonTemplateDescription: `{
37
- "data": {
38
- "title": "Title of the item, as a short string",
39
- "seo_title": "SEO optimized title, as a short string",
40
- "seo_description": "SEO optimized description, as a short string",
41
- "slug": "URL-friendly string derived from the title",
42
- "excerpt": "A short preview or snippet from the content",
43
- "links": "Array of related links with ID, label, and URL",
44
- "subtitle": "Subtitle or secondary title, as a short string",
45
- "type": "Category or type of the item, as a short string",
46
- "rank": "Numerical ranking or order priority, as a number",
47
- "tags": "Array of associated tags with ID and name",
48
- "locale": "Locale or language code, as a short string"
49
- }
50
- }`,
51
- strapiArticleCreateUrl: '',
52
- strapiContentAttributeName: '',
53
- additionalPrompt: '',
54
- enableAdditionalApiCall: false,
55
- additionalJsonTemplate: '',
56
- additionalJsonTemplateDescription: '',
57
- additionalUrl: '',
58
- additionalContentAttributeName: '',
59
- mainButtonImageEnabled: false,
60
- mainButtonGalleryEnabled: false,
61
- additionalButtonImageEnabled: false,
62
- additionalButtonGalleryEnabled: false,
63
- mainImageFullPathProperty: '',
64
- mainGalleryFullPathProperty: '',
65
- additionalImageFullPathProperty: '',
66
- additionalGalleryFullPathProperty: '',
8
+ currentTab: 'dashboard',
9
+ routes: [
10
+ {
11
+ id: 'default-route',
12
+ name: 'Default Route',
13
+ icon: 'upload',
14
+ url: '',
15
+ contentType: 'articles',
16
+ enabled: true,
17
+ fieldMappings: {},
18
+ description: '',
19
+ subtitle: '',
20
+ schema: '',
21
+ schemaDescription: '',
22
+ language: '',
23
+ contentField: '',
24
+ additionalInstructions: '',
25
+ generatedConfig: '',
26
+ },
27
+ ],
67
28
  }