notes-to-strapi-export-article-ai 1.0.119 → 3.0.2
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 -119
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { App, Modal, Notice, Setting } from 'obsidian'
|
|
2
|
+
import { AnalyzedContent } from '../types'
|
|
3
|
+
import { FrontmatterGenerator } from '../services/frontmatter'
|
|
4
|
+
import StrapiExporterPlugin from '../main'
|
|
5
|
+
|
|
6
|
+
export class PreviewModal extends Modal {
|
|
7
|
+
private content: AnalyzedContent
|
|
8
|
+
private plugin: StrapiExporterPlugin
|
|
9
|
+
private onConfirm: () => void
|
|
10
|
+
private onCancel: () => void
|
|
11
|
+
private frontmatterGenerator: FrontmatterGenerator
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
app: App,
|
|
15
|
+
content: AnalyzedContent,
|
|
16
|
+
plugin: StrapiExporterPlugin,
|
|
17
|
+
onConfirm: () => void,
|
|
18
|
+
onCancel?: () => void
|
|
19
|
+
) {
|
|
20
|
+
super(app)
|
|
21
|
+
this.plugin = plugin
|
|
22
|
+
this.content = content
|
|
23
|
+
this.onConfirm = onConfirm
|
|
24
|
+
this.onCancel = onCancel || (() => {})
|
|
25
|
+
|
|
26
|
+
// Initialize with plugin instance
|
|
27
|
+
this.frontmatterGenerator = new FrontmatterGenerator(this.plugin)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
onOpen() {
|
|
31
|
+
const { contentEl } = this
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
this.createHeader(contentEl)
|
|
35
|
+
this.createGenerateButton(contentEl)
|
|
36
|
+
this.createPreviewContainer(contentEl)
|
|
37
|
+
this.createButtons(contentEl)
|
|
38
|
+
this.addStyles()
|
|
39
|
+
} catch (error) {
|
|
40
|
+
this.showError('Failed to render preview' + error.message)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private createHeader(container: HTMLElement) {
|
|
45
|
+
container.createEl('h2', {
|
|
46
|
+
text: 'Content Preview',
|
|
47
|
+
cls: 'preview-modal-title',
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
container.createEl('p', {
|
|
51
|
+
text: 'Review or generate frontmatter before exporting to Strapi.',
|
|
52
|
+
cls: 'preview-modal-description',
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private createGenerateButton(container: HTMLElement) {
|
|
57
|
+
new Setting(container)
|
|
58
|
+
.setName('Generate Metadata')
|
|
59
|
+
.setDesc('Use AI to generate frontmatter metadata for your content')
|
|
60
|
+
.addButton(button =>
|
|
61
|
+
button
|
|
62
|
+
.setButtonText('Generate')
|
|
63
|
+
.setCta()
|
|
64
|
+
.onClick(async () => {
|
|
65
|
+
try {
|
|
66
|
+
await this.generateFrontmatter()
|
|
67
|
+
} catch (error) {
|
|
68
|
+
new Notice(`Failed to generate metadata: ${error.message}`)
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private async generateFrontmatter() {
|
|
75
|
+
new Notice('Generating frontmatter...')
|
|
76
|
+
|
|
77
|
+
// Get the active file
|
|
78
|
+
const file = this.app.workspace.getActiveFile()
|
|
79
|
+
if (!file) {
|
|
80
|
+
throw new Error('No active file')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Generate frontmatter
|
|
84
|
+
const updatedContent =
|
|
85
|
+
await this.frontmatterGenerator.updateContentFrontmatter(file, this.app)
|
|
86
|
+
|
|
87
|
+
// Update content object
|
|
88
|
+
this.content.content = updatedContent
|
|
89
|
+
|
|
90
|
+
// Update preview
|
|
91
|
+
this.updatePreview()
|
|
92
|
+
await this.app.vault.modify(file, updatedContent)
|
|
93
|
+
|
|
94
|
+
new Notice('Frontmatter generated successfully!')
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private createPreviewContainer(container: HTMLElement) {
|
|
98
|
+
const previewContainer = container.createDiv('preview-container')
|
|
99
|
+
this.createContentSections(previewContainer)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private updatePreview() {
|
|
103
|
+
const previewContainer = this.contentEl.querySelector('.preview-container')
|
|
104
|
+
if (previewContainer) {
|
|
105
|
+
previewContainer.empty()
|
|
106
|
+
this.createContentSections(previewContainer)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private createContentSections(container: Element) {
|
|
111
|
+
// Main content section
|
|
112
|
+
if (this.content.content) {
|
|
113
|
+
const contentSection = this.createSection(
|
|
114
|
+
container.createDiv(),
|
|
115
|
+
'Main Content',
|
|
116
|
+
this.content.content,
|
|
117
|
+
'content-section'
|
|
118
|
+
)
|
|
119
|
+
contentSection.addClass('main-content')
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
createSection(
|
|
124
|
+
container: HTMLElement,
|
|
125
|
+
title: string,
|
|
126
|
+
content: any,
|
|
127
|
+
className: string
|
|
128
|
+
): HTMLElement {
|
|
129
|
+
const section = container.createDiv(className)
|
|
130
|
+
section.createEl('h3', { text: title })
|
|
131
|
+
|
|
132
|
+
const previewEl = section.createEl('pre')
|
|
133
|
+
previewEl.setText(
|
|
134
|
+
typeof content === 'string' ? content : JSON.stringify(content, null, 2)
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return section
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private createButtons(container: HTMLElement) {
|
|
141
|
+
const buttonContainer = container.createDiv('button-container')
|
|
142
|
+
|
|
143
|
+
// Confirm button
|
|
144
|
+
new Setting(buttonContainer).addButton(button => {
|
|
145
|
+
button
|
|
146
|
+
.setButtonText('Confirm & Export')
|
|
147
|
+
.setCta()
|
|
148
|
+
.onClick(() => {
|
|
149
|
+
this.close()
|
|
150
|
+
this.onConfirm()
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// Cancel button
|
|
155
|
+
new Setting(buttonContainer).addButton(button => {
|
|
156
|
+
button.setButtonText('Cancel').onClick(() => {
|
|
157
|
+
this.close()
|
|
158
|
+
this.onCancel()
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private addStyles() {
|
|
164
|
+
document.body.addClass('preview-modal-open')
|
|
165
|
+
|
|
166
|
+
const styles = `
|
|
167
|
+
.preview-modal-title {
|
|
168
|
+
margin-bottom: 1em;
|
|
169
|
+
padding-bottom: 0.5em;
|
|
170
|
+
border-bottom: 1px solid var(--background-modifier-border);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.preview-modal-description {
|
|
174
|
+
margin-bottom: 1.5em;
|
|
175
|
+
color: var(--text-muted);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.preview-container {
|
|
179
|
+
max-height: 60vh;
|
|
180
|
+
overflow-y: auto;
|
|
181
|
+
margin-bottom: 1.5em;
|
|
182
|
+
padding: 1em;
|
|
183
|
+
border: 1px solid var(--background-modifier-border);
|
|
184
|
+
border-radius: 4px;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.content-section,
|
|
188
|
+
.metadata-section {
|
|
189
|
+
margin-bottom: 1.5em;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.content-section h3,
|
|
193
|
+
.metadata-section h3 {
|
|
194
|
+
margin-bottom: 0.5em;
|
|
195
|
+
color: var(--text-normal);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.content-section pre,
|
|
199
|
+
.metadata-section pre {
|
|
200
|
+
padding: 1em;
|
|
201
|
+
background-color: var(--background-primary-alt);
|
|
202
|
+
border-radius: 4px;
|
|
203
|
+
overflow-x: auto;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.button-container {
|
|
207
|
+
display: flex;
|
|
208
|
+
justify-content: flex-end;
|
|
209
|
+
gap: 1em;
|
|
210
|
+
margin-top: 1em;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.button-container .setting-item {
|
|
214
|
+
align-items: end;
|
|
215
|
+
border: none;
|
|
216
|
+
}
|
|
217
|
+
`
|
|
218
|
+
|
|
219
|
+
document.head.createEl('style', {
|
|
220
|
+
attr: { type: 'text/css' },
|
|
221
|
+
text: styles,
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private showError(message: string) {
|
|
226
|
+
const { contentEl } = this
|
|
227
|
+
contentEl.empty()
|
|
228
|
+
|
|
229
|
+
contentEl.createEl('h2', {
|
|
230
|
+
text: 'Error',
|
|
231
|
+
cls: 'preview-modal-error-title',
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
contentEl.createEl('p', {
|
|
235
|
+
text: message,
|
|
236
|
+
cls: 'preview-modal-error-message',
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
onClose() {
|
|
241
|
+
const { contentEl } = this
|
|
242
|
+
contentEl.empty()
|
|
243
|
+
document.body.removeClass('preview-modal-open')
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function showPreviewToUser(
|
|
248
|
+
app: App,
|
|
249
|
+
content: AnalyzedContent,
|
|
250
|
+
plugin: StrapiExporterPlugin
|
|
251
|
+
): Promise<boolean> {
|
|
252
|
+
return new Promise(resolve => {
|
|
253
|
+
new PreviewModal(
|
|
254
|
+
app,
|
|
255
|
+
content,
|
|
256
|
+
plugin,
|
|
257
|
+
() => {
|
|
258
|
+
resolve(true)
|
|
259
|
+
},
|
|
260
|
+
() => {
|
|
261
|
+
resolve(false)
|
|
262
|
+
}
|
|
263
|
+
).open()
|
|
264
|
+
})
|
|
265
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { App, TFile } from 'obsidian'
|
|
2
|
+
import { StrapiExporterSettings, AnalyzedContent } from '../types'
|
|
3
|
+
import { uploadImageToStrapi } from './strapi-uploader'
|
|
4
|
+
|
|
5
|
+
interface ImageMatch {
|
|
6
|
+
fullMatch: string
|
|
7
|
+
altText: string
|
|
8
|
+
imagePath: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Process images in content fields and upload them to Strapi
|
|
13
|
+
*/
|
|
14
|
+
export async function processImages(
|
|
15
|
+
content: AnalyzedContent,
|
|
16
|
+
app: App,
|
|
17
|
+
settings: StrapiExporterSettings
|
|
18
|
+
): Promise<AnalyzedContent> {
|
|
19
|
+
const processedContent = { ...content }
|
|
20
|
+
|
|
21
|
+
for (const [key, value] of Object.entries(processedContent)) {
|
|
22
|
+
if (typeof value === 'string') {
|
|
23
|
+
processedContent[key] = await processImageLinks(value, app, settings)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return processedContent
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Process image links in content string
|
|
32
|
+
*/
|
|
33
|
+
async function processImageLinks(
|
|
34
|
+
content: string,
|
|
35
|
+
app: App,
|
|
36
|
+
settings: StrapiExporterSettings
|
|
37
|
+
): Promise<string> {
|
|
38
|
+
const imageMatches = extractImageMatches(content)
|
|
39
|
+
|
|
40
|
+
let processedContent = content
|
|
41
|
+
|
|
42
|
+
for (const match of imageMatches) {
|
|
43
|
+
const replacedContent = await processImageMatch(
|
|
44
|
+
match,
|
|
45
|
+
processedContent,
|
|
46
|
+
app,
|
|
47
|
+
settings
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if (replacedContent !== processedContent) {
|
|
51
|
+
processedContent = replacedContent
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return processedContent
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Extract image matches from content
|
|
60
|
+
*/
|
|
61
|
+
function extractImageMatches(content: string): ImageMatch[] {
|
|
62
|
+
const matches: ImageMatch[] = []
|
|
63
|
+
const imageRegex = /!\[([^\]]*)\]\(([^)]+)\)/g
|
|
64
|
+
let match
|
|
65
|
+
|
|
66
|
+
while ((match = imageRegex.exec(content)) !== null) {
|
|
67
|
+
matches.push({
|
|
68
|
+
fullMatch: match[0],
|
|
69
|
+
altText: match[1],
|
|
70
|
+
imagePath: match[2],
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return matches
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Process individual image match
|
|
79
|
+
*/
|
|
80
|
+
async function processImageMatch(
|
|
81
|
+
match: ImageMatch,
|
|
82
|
+
content: string,
|
|
83
|
+
app: App,
|
|
84
|
+
settings: StrapiExporterSettings
|
|
85
|
+
): Promise<string> {
|
|
86
|
+
if (isExternalUrl(match.imagePath)) {
|
|
87
|
+
return content
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const file = app.vault.getAbstractFileByPath(match.imagePath)
|
|
91
|
+
if (!(file instanceof TFile)) {
|
|
92
|
+
return content
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const uploadedImage = await uploadImageToStrapi(
|
|
96
|
+
file,
|
|
97
|
+
file.name,
|
|
98
|
+
settings,
|
|
99
|
+
app,
|
|
100
|
+
{
|
|
101
|
+
alternativeText: match.altText || file.basename,
|
|
102
|
+
caption: match.altText || file.basename,
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if (uploadedImage?.url) {
|
|
107
|
+
const newContent = content.replace(
|
|
108
|
+
match.fullMatch,
|
|
109
|
+
``
|
|
110
|
+
)
|
|
111
|
+
return newContent
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return content
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Check if URL is external
|
|
119
|
+
*/
|
|
120
|
+
function isExternalUrl(url: string): boolean {
|
|
121
|
+
return url.startsWith('http://') || url.startsWith('https://')
|
|
122
|
+
}
|
|
@@ -1,137 +1,138 @@
|
|
|
1
|
-
import { Notice } from 'obsidian'
|
|
2
|
-
import { StrapiExporterSettings } from '../types
|
|
3
|
-
|
|
1
|
+
import { App, Notice, TAbstractFile, TFile } from 'obsidian'
|
|
2
|
+
import { StrapiExporterSettings, ImageDescription } from '../types'
|
|
3
|
+
|
|
4
|
+
interface UploadResponse {
|
|
5
|
+
url: string
|
|
6
|
+
id: number
|
|
7
|
+
name: string
|
|
8
|
+
alternativeText?: string
|
|
9
|
+
caption?: string
|
|
10
|
+
}
|
|
4
11
|
|
|
5
12
|
/**
|
|
6
|
-
* Upload
|
|
7
|
-
* @param imageDescriptions
|
|
8
|
-
* @param settings
|
|
9
|
-
* @param app
|
|
10
|
-
* @param imageFolderPath
|
|
13
|
+
* Upload single image to Strapi
|
|
11
14
|
*/
|
|
12
|
-
export async function
|
|
13
|
-
|
|
15
|
+
export async function uploadImageToStrapi(
|
|
16
|
+
imageData: string | TFile,
|
|
17
|
+
fileName: string,
|
|
14
18
|
settings: StrapiExporterSettings,
|
|
15
|
-
app:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
app: App,
|
|
20
|
+
additionalMetadata?: {
|
|
21
|
+
alternativeText?: string
|
|
22
|
+
caption?: string
|
|
23
|
+
}
|
|
24
|
+
): Promise<ImageDescription | null> {
|
|
25
|
+
// Validate settings
|
|
26
|
+
validateStrapiSettings(settings)
|
|
27
|
+
|
|
28
|
+
// Get file
|
|
29
|
+
const file = await getFileFromImageData(imageData, fileName, app)
|
|
30
|
+
if (!file) {
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Prepare form data
|
|
35
|
+
const formData = await prepareFormData(file, fileName, additionalMetadata)
|
|
36
|
+
|
|
37
|
+
// Upload to Strapi
|
|
38
|
+
const uploadResult = await performStrapiUpload(formData, settings)
|
|
39
|
+
|
|
40
|
+
if (uploadResult) {
|
|
41
|
+
return createImageDescription(uploadResult, fileName, additionalMetadata)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
// Helper functions
|
|
47
|
+
|
|
48
|
+
async function getFileFromImageData(
|
|
49
|
+
imageData: string | TFile,
|
|
50
|
+
fileName: string,
|
|
51
|
+
app: App
|
|
52
|
+
): Promise<TFile | null> {
|
|
53
|
+
let file: TAbstractFile | null = null
|
|
54
|
+
if (typeof imageData === 'string') {
|
|
55
|
+
file = app.vault.getAbstractFileByPath(imageData)
|
|
56
|
+
} else if (imageData instanceof TFile) {
|
|
57
|
+
file = imageData
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!(file instanceof TFile)) {
|
|
61
|
+
new Notice(`Failed to find file: ${fileName}`)
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return file
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function prepareFormData(
|
|
69
|
+
file: TFile,
|
|
70
|
+
fileName: string,
|
|
71
|
+
metadata?: { alternativeText?: string; caption?: string }
|
|
72
|
+
): Promise<FormData> {
|
|
73
|
+
const formData = new FormData()
|
|
74
|
+
const arrayBuffer = await file.vault.readBinary(file)
|
|
75
|
+
const blob = new Blob([arrayBuffer], { type: `image/${file.extension}` })
|
|
76
|
+
formData.append('files', blob, fileName)
|
|
77
|
+
|
|
78
|
+
if (metadata) {
|
|
24
79
|
formData.append(
|
|
25
80
|
'fileInfo',
|
|
26
81
|
JSON.stringify({
|
|
27
|
-
name:
|
|
28
|
-
alternativeText:
|
|
29
|
-
caption:
|
|
82
|
+
name: fileName,
|
|
83
|
+
alternativeText: metadata.alternativeText,
|
|
84
|
+
caption: metadata.caption,
|
|
30
85
|
})
|
|
31
86
|
)
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
const response = await fetch(`${settings.strapiUrl}/api/upload`, {
|
|
35
|
-
method: 'POST',
|
|
36
|
-
headers: {
|
|
37
|
-
Authorization: `Bearer ${settings.strapiApiToken}`,
|
|
38
|
-
},
|
|
39
|
-
body: formData,
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
if (response.ok) {
|
|
43
|
-
const data = await response.json()
|
|
44
|
-
uploadedImages[imageDescription.name] = {
|
|
45
|
-
url: data[0].url,
|
|
46
|
-
data: data[0],
|
|
47
|
-
id: data[0].id,
|
|
48
|
-
}
|
|
49
|
-
} else {
|
|
50
|
-
const errorData = await response.json()
|
|
51
|
-
new Notice(
|
|
52
|
-
`Failed to upload image: ${imageDescription.name}. Error: ${errorData.error.message}`
|
|
53
|
-
)
|
|
54
|
-
}
|
|
55
|
-
} catch (error) {
|
|
56
|
-
new Notice(
|
|
57
|
-
`Error uploading image: ${imageDescription.name}. Error: ${error.message}`
|
|
58
|
-
)
|
|
59
|
-
}
|
|
60
87
|
}
|
|
61
88
|
|
|
62
|
-
|
|
63
|
-
// Save metadata to a file only if there are uploaded images
|
|
64
|
-
if (Object.keys(uploadedImages).length > 0) {
|
|
65
|
-
const metadataFile = `${imageFolderPath}/metadata.json`
|
|
66
|
-
await app.vault.adapter.write(
|
|
67
|
-
metadataFile,
|
|
68
|
-
JSON.stringify(uploadedImages)
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return uploadedImages
|
|
89
|
+
return formData
|
|
73
90
|
}
|
|
74
91
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
galleryFolderPath: string = ''
|
|
87
|
-
): Promise<number[]> {
|
|
88
|
-
const uploadedImageIds: number[] = []
|
|
89
|
-
const uploadedImages: { [key: string]: { url: string; data: any; id: any } } =
|
|
90
|
-
{}
|
|
91
|
-
|
|
92
|
-
for (const imageBlob of imageBlobs) {
|
|
93
|
-
const formData = new FormData()
|
|
94
|
-
formData.append('files', imageBlob.blob, imageBlob.name)
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
const response = await fetch(`${settings.strapiUrl}/api/upload`, {
|
|
98
|
-
method: 'POST',
|
|
99
|
-
headers: {
|
|
100
|
-
Authorization: `Bearer ${settings.strapiApiToken}`,
|
|
101
|
-
},
|
|
102
|
-
body: formData,
|
|
103
|
-
})
|
|
92
|
+
async function performStrapiUpload(
|
|
93
|
+
formData: FormData,
|
|
94
|
+
settings: StrapiExporterSettings
|
|
95
|
+
): Promise<UploadResponse | null> {
|
|
96
|
+
const response = await fetch(`${settings.strapiUrl}/api/upload`, {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers: {
|
|
99
|
+
Authorization: `Bearer ${settings.strapiApiToken}`,
|
|
100
|
+
},
|
|
101
|
+
body: formData,
|
|
102
|
+
})
|
|
104
103
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
url: data[0].url,
|
|
109
|
-
id: data[0].id,
|
|
110
|
-
data: data[0],
|
|
111
|
-
}
|
|
112
|
-
} else {
|
|
113
|
-
const errorData = await response.json()
|
|
114
|
-
new Notice(
|
|
115
|
-
`Failed to upload gallery image: ${imageBlob.name}. Error: ${errorData.error.message}`
|
|
116
|
-
)
|
|
117
|
-
}
|
|
118
|
-
} catch (error) {
|
|
119
|
-
new Notice(
|
|
120
|
-
`Error uploading gallery image: ${imageBlob.name}. Error: ${error.message}`
|
|
121
|
-
)
|
|
122
|
-
}
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
const errorData = await response.json()
|
|
106
|
+
throw new Error(errorData.error.message)
|
|
123
107
|
}
|
|
124
108
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const metadataFile = `${galleryFolderPath}/metadata.json`
|
|
129
|
-
await app.vault.adapter.write(
|
|
130
|
-
metadataFile,
|
|
131
|
-
JSON.stringify(uploadedImages)
|
|
132
|
-
)
|
|
133
|
-
}
|
|
134
|
-
}
|
|
109
|
+
const data = await response.json()
|
|
110
|
+
return data[0]
|
|
111
|
+
}
|
|
135
112
|
|
|
136
|
-
|
|
113
|
+
function validateStrapiSettings(settings: StrapiExporterSettings): void {
|
|
114
|
+
if (!settings.strapiUrl) {
|
|
115
|
+
throw new Error('Strapi URL is not configured')
|
|
116
|
+
}
|
|
117
|
+
if (!settings.strapiApiToken) {
|
|
118
|
+
throw new Error('Strapi API token is not configured')
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function createImageDescription(
|
|
122
|
+
uploadResult: UploadResponse,
|
|
123
|
+
fileName: string,
|
|
124
|
+
metadata?: { alternativeText?: string; caption?: string }
|
|
125
|
+
): ImageDescription {
|
|
126
|
+
return {
|
|
127
|
+
url: uploadResult.url,
|
|
128
|
+
name: fileName,
|
|
129
|
+
path: uploadResult.url,
|
|
130
|
+
id: uploadResult.id,
|
|
131
|
+
description: {
|
|
132
|
+
name: uploadResult.name,
|
|
133
|
+
alternativeText:
|
|
134
|
+
uploadResult.alternativeText || metadata?.alternativeText || '',
|
|
135
|
+
caption: uploadResult.caption || metadata?.caption || '',
|
|
136
|
+
},
|
|
137
|
+
}
|
|
137
138
|
}
|