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.
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
package/src/settings.ts DELETED
@@ -1,404 +0,0 @@
1
- import { App, PluginSettingTab, Setting } from 'obsidian'
2
- import StrapiExporterPlugin from './main'
3
- import { validateJsonTemplate } from './utils/validators'
4
-
5
- export class StrapiExporterSettingTab extends PluginSettingTab {
6
- plugin: StrapiExporterPlugin
7
-
8
- constructor(app: App, plugin: StrapiExporterPlugin) {
9
- super(app, plugin)
10
- this.plugin = plugin
11
- }
12
-
13
- display(): void {
14
- const { containerEl } = this
15
- containerEl.empty()
16
-
17
- new Setting(containerEl)
18
- .setName('Strapi URL')
19
- .setDesc('Enter your Strapi instance URL')
20
- .addText(text =>
21
- text
22
- .setPlaceholder('https://your-strapi-url')
23
- .setValue(this.plugin.settings.strapiUrl)
24
- .onChange(async value => {
25
- this.plugin.settings.strapiUrl = value
26
- await this.plugin.saveSettings()
27
- })
28
- )
29
-
30
- new Setting(containerEl)
31
- .setName('Strapi API token')
32
- .setDesc('Enter your Strapi API token')
33
- .addText(text =>
34
- text
35
- .setPlaceholder('Enter your token')
36
- .setValue(this.plugin.settings.strapiApiToken)
37
- .onChange(async value => {
38
- this.plugin.settings.strapiApiToken = value
39
- await this.plugin.saveSettings()
40
- })
41
- )
42
-
43
- new Setting(containerEl)
44
- .setName('OpenAI API key')
45
- .setDesc('Enter your OpenAI API key for GPT-3')
46
- .addText(text =>
47
- text
48
- .setPlaceholder('Enter your OpenAI API key')
49
- .setValue(this.plugin.settings.openaiApiKey)
50
- .onChange(async value => {
51
- this.plugin.settings.openaiApiKey = value
52
- await this.plugin.saveSettings()
53
- })
54
- )
55
-
56
- new Setting(containerEl)
57
- .setName('Additional prompt')
58
- .setDesc(
59
- 'Enter an optional additional prompt to customize the article content generation'
60
- )
61
- .addTextArea(text =>
62
- text
63
- .setPlaceholder('Enter your additional prompt here...')
64
- .setValue(this.plugin.settings.additionalPrompt)
65
- .onChange(async value => {
66
- this.plugin.settings.additionalPrompt = value
67
- await this.plugin.saveSettings()
68
- })
69
- )
70
-
71
- new Setting(containerEl).setName('Strapi settings - Call 1').setHeading()
72
-
73
- new Setting(containerEl)
74
- .setName('Strapi article create URL')
75
- .setDesc('Enter the URL to create articles in Strapi')
76
- .addText(text =>
77
- text
78
- .setPlaceholder('https://your-strapi-url/api/articles')
79
- .setValue(this.plugin.settings.strapiArticleCreateUrl)
80
- .onChange(async value => {
81
- this.plugin.settings.strapiArticleCreateUrl = value
82
- await this.plugin.saveSettings()
83
- })
84
- )
85
-
86
- new Setting(containerEl)
87
- .setName('JSON template')
88
- .setDesc('Enter the JSON template for the fields needed')
89
- .addTextArea(text =>
90
- text
91
- .setPlaceholder('Enter your JSON template')
92
- .setValue(this.plugin.settings.jsonTemplate)
93
- .onChange(async value => {
94
- if (validateJsonTemplate(value)) {
95
- this.plugin.settings.jsonTemplate = value
96
- await this.plugin.saveSettings()
97
- }
98
- })
99
- )
100
-
101
- new Setting(containerEl)
102
- .setName('JSON template description')
103
- .setDesc('Enter the description for each field in the JSON template')
104
- .addTextArea(text =>
105
- text
106
- .setPlaceholder('Enter the field descriptions')
107
- .setValue(this.plugin.settings.jsonTemplateDescription)
108
- .onChange(async value => {
109
- this.plugin.settings.jsonTemplateDescription = value
110
- await this.plugin.saveSettings()
111
- })
112
- )
113
-
114
- new Setting(containerEl)
115
- .setName('Strapi content attribute name')
116
- .setDesc('Enter the attribute name for the content field in Strapi')
117
- .addText(text =>
118
- text
119
- .setPlaceholder('content')
120
- .setValue(this.plugin.settings.strapiContentAttributeName)
121
- .onChange(async value => {
122
- this.plugin.settings.strapiContentAttributeName = value
123
- await this.plugin.saveSettings()
124
- })
125
- )
126
-
127
- new Setting(containerEl).setName('Main image settings').setHeading()
128
-
129
- new Setting(containerEl)
130
- .setName('Enable main image')
131
- .setDesc('Toggle the main image')
132
- .addToggle(toggle =>
133
- toggle
134
- .setValue(this.plugin.settings.mainButtonImageEnabled)
135
- .onChange(async value => {
136
- this.plugin.settings.mainButtonImageEnabled = value
137
- await this.plugin.saveSettings()
138
- this.display()
139
- })
140
- )
141
-
142
- if (this.plugin.settings.mainButtonImageEnabled) {
143
- containerEl.createEl('p', {
144
- text: 'For the plugin to detect images and galleries, ensure the following folder structure:',
145
- })
146
-
147
- containerEl.createEl('ul', {
148
- text: '- Article file (e.g., article.md)',
149
- })
150
- containerEl.createEl('ul', {
151
- text: '- Main image folder (name: image)',
152
- })
153
-
154
- containerEl.createEl('p', {
155
- text: 'The plugin will detect images in the main image (for this api call)',
156
- })
157
-
158
- new Setting(containerEl)
159
- .setName('Main image full path property')
160
- .setDesc(
161
- 'Enter the full path property for the main image in the final call'
162
- )
163
- .addText(text =>
164
- text
165
- .setPlaceholder('data.attributes.image')
166
- .setValue(this.plugin.settings.mainImageFullPathProperty)
167
- .onChange(async value => {
168
- this.plugin.settings.mainImageFullPathProperty = value
169
- await this.plugin.saveSettings()
170
- })
171
- )
172
- }
173
-
174
- new Setting(containerEl).setName('Main gallery settings').setHeading()
175
-
176
- new Setting(containerEl)
177
- .setName('Enable main gallery')
178
- .setDesc('Toggle the main gallery')
179
- .addToggle(toggle =>
180
- toggle
181
- .setValue(this.plugin.settings.mainButtonGalleryEnabled)
182
- .onChange(async value => {
183
- this.plugin.settings.mainButtonGalleryEnabled = value
184
- await this.plugin.saveSettings()
185
- this.display()
186
- })
187
- )
188
-
189
- if (this.plugin.settings.mainButtonGalleryEnabled) {
190
- containerEl.createEl('p', {
191
- text: 'For the plugin to detect galleries, ensure the following folder structure:',
192
- })
193
-
194
- containerEl.createEl('ul', {
195
- text: '- Article file (e.g., article.md)',
196
- })
197
- containerEl.createEl('ul', {
198
- text: '- Main gallery folder (name: gallery)',
199
- })
200
-
201
- containerEl.createEl('p', {
202
- text: 'The plugin will detect images in the main gallery folders. (for this api call)',
203
- })
204
-
205
- new Setting(containerEl)
206
- .setName('Main gallery full path property')
207
- .setDesc(
208
- 'Enter the full path property for the main gallery in the final call'
209
- )
210
- .addText(text =>
211
- text
212
- .setPlaceholder('data.attributes.gallery')
213
- .setValue(this.plugin.settings.mainGalleryFullPathProperty)
214
- .onChange(async value => {
215
- this.plugin.settings.mainGalleryFullPathProperty = value
216
- await this.plugin.saveSettings()
217
- })
218
- )
219
- }
220
-
221
- new Setting(containerEl)
222
- .setName('Strapi settings - Call 2 - Additional call')
223
- .setHeading()
224
-
225
- containerEl.createEl('p', {
226
- text: `(Be careful, when enabling this feature, you'll need to restart Obsidian to see the additional button in the ribbon menu.)`,
227
- })
228
-
229
- new Setting(containerEl)
230
- .setName('Enable additional call API')
231
- .setDesc(
232
- 'Toggle the additional Call API, and display a new icon in the ribbon menu'
233
- )
234
- .addToggle(toggle =>
235
- toggle
236
- .setValue(this.plugin.settings.enableAdditionalApiCall)
237
- .onChange(async value => {
238
- this.plugin.settings.enableAdditionalApiCall = value
239
- await this.plugin.saveSettings()
240
- this.display()
241
- })
242
- )
243
-
244
- if (this.plugin.settings.enableAdditionalApiCall) {
245
- new Setting(containerEl)
246
- .setName('Additional API URL')
247
- .setDesc('Enter the URL to create content for the additional API')
248
- .addText(text =>
249
- text
250
- .setPlaceholder('https://your-strapi-url/api/additional-content')
251
- .setValue(this.plugin.settings.additionalUrl)
252
- .onChange(async value => {
253
- this.plugin.settings.additionalUrl = value
254
- await this.plugin.saveSettings()
255
- })
256
- )
257
-
258
- new Setting(containerEl)
259
- .setName('Additional JSON template')
260
- .setDesc(
261
- 'Enter the JSON template for the fields needed for the additional api'
262
- )
263
- .addTextArea(text =>
264
- text
265
- .setPlaceholder('Enter your JSON template')
266
- .setValue(this.plugin.settings.additionalJsonTemplate)
267
- .onChange(async value => {
268
- if (validateJsonTemplate(value)) {
269
- this.plugin.settings.additionalJsonTemplate = value
270
- await this.plugin.saveSettings()
271
- }
272
- })
273
- )
274
-
275
- new Setting(containerEl)
276
- .setName('Additional API JSON template description')
277
- .setDesc(
278
- 'Enter the description for each field in the additional API JSON template'
279
- )
280
- .addTextArea(text =>
281
- text
282
- .setPlaceholder('Enter the field descriptions')
283
- .setValue(this.plugin.settings.additionalJsonTemplateDescription)
284
- .onChange(async value => {
285
- this.plugin.settings.additionalJsonTemplateDescription = value
286
- await this.plugin.saveSettings()
287
- })
288
- )
289
-
290
- new Setting(containerEl)
291
- .setName('Additional API content attribute name')
292
- .setDesc(
293
- 'Enter the attribute name for the content field for the additional API'
294
- )
295
- .addText(text =>
296
- text
297
- .setPlaceholder('content')
298
- .setValue(this.plugin.settings.additionalContentAttributeName)
299
- .onChange(async value => {
300
- this.plugin.settings.additionalContentAttributeName = value
301
- await this.plugin.saveSettings()
302
- })
303
- )
304
-
305
- new Setting(containerEl)
306
- .setName('Additional call API image settings')
307
- .setHeading()
308
-
309
- new Setting(containerEl)
310
- .setName('Enable additional call API image')
311
- .setDesc('Toggle the additional Call API image')
312
- .addToggle(toggle =>
313
- toggle
314
- .setValue(this.plugin.settings.additionalButtonImageEnabled)
315
- .onChange(async value => {
316
- this.plugin.settings.additionalButtonImageEnabled = value
317
- await this.plugin.saveSettings()
318
- this.display()
319
- })
320
- )
321
-
322
- if (this.plugin.settings.additionalButtonImageEnabled) {
323
- containerEl.createEl('p', {
324
- text: 'For the plugin to detect images and galleries, ensure the following folder structure:',
325
- })
326
-
327
- containerEl.createEl('ul', {
328
- text: '- Article file (e.g., article.md)',
329
- })
330
- containerEl.createEl('ul', {
331
- text: '- Main image folder (name: image)',
332
- })
333
-
334
- containerEl.createEl('p', {
335
- text: 'The plugin will detect images in the main image (for this api call)',
336
- })
337
-
338
- new Setting(containerEl)
339
- .setName('Additional call API image full path property')
340
- .setDesc(
341
- 'Enter the full path property for the additional Call API image in the final call'
342
- )
343
- .addText(text =>
344
- text
345
- .setPlaceholder('image_presentation')
346
- .setValue(this.plugin.settings.additionalImageFullPathProperty)
347
- .onChange(async value => {
348
- this.plugin.settings.additionalImageFullPathProperty = value
349
- await this.plugin.saveSettings()
350
- })
351
- )
352
- }
353
-
354
- new Setting(containerEl)
355
- .setName('Additional call API gallery settings')
356
- .setHeading()
357
-
358
- new Setting(containerEl)
359
- .setName('Enable additional call API gallery')
360
- .setDesc('Toggle the additional Call API gallery')
361
- .addToggle(toggle =>
362
- toggle
363
- .setValue(this.plugin.settings.additionalButtonGalleryEnabled)
364
- .onChange(async value => {
365
- this.plugin.settings.additionalButtonGalleryEnabled = value
366
- await this.plugin.saveSettings()
367
- this.display()
368
- })
369
- )
370
-
371
- if (this.plugin.settings.additionalButtonGalleryEnabled) {
372
- containerEl.createEl('p', {
373
- text: 'For the plugin to detect galleries, ensure the following folder structure:',
374
- })
375
-
376
- containerEl.createEl('ul', {
377
- text: '- Article file (e.g., article.md)',
378
- })
379
- containerEl.createEl('ul', {
380
- text: '- Main gallery folder (name: gallery)',
381
- })
382
-
383
- containerEl.createEl('p', {
384
- text: 'The plugin will detect images in the main gallery folders. (for this api call)',
385
- })
386
-
387
- new Setting(containerEl)
388
- .setName('Additional call API gallery full path property')
389
- .setDesc(
390
- 'Enter the full path property for the additional Call API gallery in the final call'
391
- )
392
- .addText(text =>
393
- text
394
- .setPlaceholder('galery')
395
- .setValue(this.plugin.settings.additionalGalleryFullPathProperty)
396
- .onChange(async value => {
397
- this.plugin.settings.additionalGalleryFullPathProperty = value
398
- await this.plugin.saveSettings()
399
- })
400
- )
401
- }
402
- }
403
- }
404
- }
@@ -1,8 +0,0 @@
1
- /**
2
- * Article
3
- */
4
- export interface ArticleContent {
5
- data: {
6
- [key: string]: any
7
- }
8
- }
@@ -1,139 +0,0 @@
1
- import { OpenAI } from 'openai'
2
- import { StrapiExporterSettings } from '../types/settings'
3
- import { ArticleContent } from '../types/article'
4
- import { Notice } from 'obsidian'
5
-
6
- /**
7
- * Generate article content using OpenAI
8
- * @param content
9
- * @param openai
10
- * @param settings
11
- * @param useAdditionalCallAPI
12
- */
13
- export async function generateArticleContent(
14
- content: string,
15
- openai: OpenAI,
16
- settings: StrapiExporterSettings,
17
- useAdditionalCallAPI = false
18
- ): Promise<ArticleContent> {
19
- let jsonTemplate: any
20
- let jsonTemplateDescription: any
21
- let contentAttributeName: string
22
-
23
- if (useAdditionalCallAPI) {
24
- jsonTemplate = JSON.parse(settings.additionalJsonTemplate)
25
- jsonTemplateDescription = JSON.parse(
26
- settings.additionalJsonTemplateDescription
27
- )
28
- contentAttributeName = settings.additionalContentAttributeName
29
- } else {
30
- jsonTemplate = JSON.parse(settings.jsonTemplate)
31
- jsonTemplateDescription = JSON.parse(settings.jsonTemplateDescription)
32
- contentAttributeName = settings.strapiContentAttributeName
33
- }
34
-
35
- const articlePrompt = `You are an SEO expert. Generate an article based on the following template and field descriptions:
36
-
37
- Template:
38
- ${JSON.stringify(jsonTemplate, null, 2)}
39
-
40
- Field Descriptions:
41
- ${JSON.stringify(jsonTemplateDescription, null, 2)}
42
-
43
- The main content of the article should be based on the following text and all the keywords around the domain of the text:
44
- ----- CONTENT -----
45
- ${content.substring(0, 500)}
46
- ----- END CONTENT -----
47
-
48
- Please provide the generated article content as a JSON object following the given template structure.
49
-
50
- ${settings.additionalPrompt ? `Additional Prompt: ${settings.additionalPrompt}` : ''}`
51
-
52
- const completion = await openai.chat.completions.create({
53
- model: 'gpt-3.5-turbo-0125',
54
- messages: [
55
- {
56
- role: 'user',
57
- content: articlePrompt,
58
- },
59
- ],
60
- max_tokens: 2000,
61
- n: 1,
62
- stop: null,
63
- })
64
-
65
- let articleContent = JSON.parse(completion.choices[0].message.content ?? '{}')
66
- articleContent = {
67
- data: {
68
- ...articleContent.data,
69
- [contentAttributeName]: content,
70
- },
71
- }
72
-
73
- return articleContent
74
- }
75
-
76
- /**
77
- * Get the description of the image using OpenAI
78
- * @param imageBlob
79
- * @param openai
80
- */
81
- export const getImageDescription = async (imageBlob: Blob, openai: OpenAI) => {
82
- // Get the image description using the OpenAI API (using gpt 4 vision preview model)
83
- // @ts-ignore
84
- const response = await openai.chat.completions.create({
85
- model: 'gpt-4-vision-preview',
86
- messages: [
87
- {
88
- role: 'user',
89
- content: [
90
- {
91
- type: 'text',
92
- text: `What's in this image? make it simple, i just want the context and an idea(think about alt text)`,
93
- },
94
- {
95
- type: 'image_url',
96
- // Encode imageBlob as base64
97
- // @ts-ignore
98
- image_url: `data:image/png;base64,${btoa(
99
- new Uint8Array(await imageBlob.arrayBuffer()).reduce(
100
- (data, byte) => data + String.fromCharCode(byte),
101
- ''
102
- )
103
- )}`,
104
- },
105
- ],
106
- },
107
- ],
108
- })
109
-
110
- new Notice(response.choices[0].message.content ?? 'no response content...')
111
- new Notice(
112
- `prompt_tokens: ${response.usage?.prompt_tokens} // completion_tokens: ${response.usage?.completion_tokens} // total_tokens: ${response.usage?.total_tokens}`
113
- )
114
-
115
- // gpt-3.5-turbo-0125
116
- // Generate alt text, caption, and title for the image, based on the description of the image
117
- const completion = await openai.chat.completions.create({
118
- model: 'gpt-3.5-turbo-0125',
119
- messages: [
120
- {
121
- role: 'user',
122
- content: `You are an SEO expert and you are writing alt text, caption, and title for this image. The description of the image is: ${response.choices[0].message.content}.
123
- Give me a title (name) for this image, an SEO-friendly alternative text, and a caption for this image.
124
- Generate this information and respond with a JSON object using the following fields: name, alternativeText, caption.
125
- Use this JSON template: {"name": "string", "alternativeText": "string", "caption": "string"}.`,
126
- },
127
- ],
128
- max_tokens: 750,
129
- n: 1,
130
- stop: null,
131
- })
132
-
133
- new Notice(completion.choices[0].message.content ?? 'no response content...')
134
- new Notice(
135
- `prompt_tokens: ${completion.usage?.prompt_tokens} // completion_tokens: ${completion.usage?.completion_tokens} // total_tokens: ${completion.usage?.total_tokens}`
136
- )
137
-
138
- return JSON.parse(completion.choices[0].message.content?.trim() || '{}')
139
- }
@@ -1,8 +0,0 @@
1
- export const validateJsonTemplate = (jsonString: string): boolean => {
2
- try {
3
- JSON.parse(jsonString)
4
- return true
5
- } catch (error) {
6
- return false
7
- }
8
- }
package/version-bump.mjs DELETED
@@ -1,14 +0,0 @@
1
- import { readFileSync, writeFileSync } from "fs";
2
-
3
- const targetVersion = process.env.npm_package_version;
4
-
5
- // read minAppVersion from manifest.json and bump version to target version
6
- let manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
7
- const { minAppVersion } = manifest;
8
- manifest.version = targetVersion;
9
- writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t"));
10
-
11
- // update versions.json with target version and minAppVersion from manifest.json
12
- let versions = JSON.parse(readFileSync("versions.json", "utf8"));
13
- versions[targetVersion] = minAppVersion;
14
- writeFileSync("versions.json", JSON.stringify(versions, null, "\t"));
package/versions.json DELETED
@@ -1,119 +0,0 @@
1
- {
2
- "1.0.2": "1.0.2",
3
- "1.0.4": "1.5.0",
4
- "1.0.5": "1.5.0",
5
- "1.0.6": "1.5.0",
6
- "1.0.7": "1.5.0",
7
- "1.0.8": "1.5.0",
8
- "1.0.9": "1.5.0",
9
- "1.0.10": "1.5.0",
10
- "1.0.11": "1.5.0",
11
- "1.0.12": "1.5.0",
12
- "1.0.13": "1.5.0",
13
- "1.0.14": "1.5.0",
14
- "1.0.15": "1.5.0",
15
- "1.0.16": "1.5.0",
16
- "1.0.17": "1.5.0",
17
- "1.0.18": "1.5.0",
18
- "1.0.19": "1.5.0",
19
- "1.0.20": "1.5.0",
20
- "1.0.21": "1.5.0",
21
- "1.0.22": "1.5.0",
22
- "1.0.23": "1.5.0",
23
- "1.0.24": "1.5.0",
24
- "1.0.25": "1.5.0",
25
- "1.0.26": "1.5.0",
26
- "1.0.27": "1.5.0",
27
- "1.0.28": "1.5.0",
28
- "1.0.29": "1.5.0",
29
- "1.0.30": "1.5.0",
30
- "1.0.31": "1.5.0",
31
- "1.0.32": "1.5.0",
32
- "1.0.33": "1.5.0",
33
- "1.0.34": "1.5.0",
34
- "1.0.35": "1.5.0",
35
- "1.0.36": "1.5.0",
36
- "1.0.37": "1.5.0",
37
- "1.0.38": "1.5.0",
38
- "1.0.39": "1.5.0",
39
- "1.0.40": "1.5.0",
40
- "1.0.41": "1.5.0",
41
- "1.0.42": "1.5.0",
42
- "1.0.43": "1.5.0",
43
- "1.0.44": "1.5.0",
44
- "1.0.45": "1.5.0",
45
- "1.0.46": "1.5.0",
46
- "1.0.47": "1.5.0",
47
- "1.0.48": "1.5.0",
48
- "1.0.49": "1.5.0",
49
- "1.0.50": "1.5.0",
50
- "1.0.51": "1.5.0",
51
- "1.0.52": "1.5.0",
52
- "1.0.53": "1.5.0",
53
- "1.0.54": "1.5.0",
54
- "1.0.55": "1.5.0",
55
- "1.0.56": "1.5.0",
56
- "1.0.57": "1.5.0",
57
- "1.0.58": "1.5.0",
58
- "1.0.59": "1.5.0",
59
- "1.0.60": "1.5.0",
60
- "1.0.61": "1.5.0",
61
- "1.0.62": "1.5.0",
62
- "1.0.63": "1.5.0",
63
- "1.0.64": "1.5.0",
64
- "1.0.65": "1.5.0",
65
- "1.0.66": "1.5.0",
66
- "1.0.67": "1.5.0",
67
- "1.0.68": "1.5.0",
68
- "1.0.69": "1.5.0",
69
- "1.0.70": "1.5.0",
70
- "1.0.71": "1.5.0",
71
- "1.0.72": "1.5.0",
72
- "1.0.73": "1.5.0",
73
- "1.0.74": "1.5.0",
74
- "1.0.75": "1.5.0",
75
- "1.0.76": "1.5.0",
76
- "1.0.77": "1.5.0",
77
- "1.0.78": "1.5.0",
78
- "1.0.79": "1.5.0",
79
- "1.0.80": "1.5.0",
80
- "1.0.81": "1.5.0",
81
- "1.0.82": "1.5.0",
82
- "1.0.83": "1.5.0",
83
- "1.0.84": "1.5.0",
84
- "1.0.85": "1.5.0",
85
- "1.0.86": "1.5.0",
86
- "1.0.87": "1.5.0",
87
- "1.0.88": "1.5.0",
88
- "1.0.89": "1.5.0",
89
- "1.0.90": "1.5.0",
90
- "1.0.91": "1.5.0",
91
- "1.0.92": "1.5.0",
92
- "1.0.93": "1.5.0",
93
- "1.0.94": "1.5.0",
94
- "1.0.95": "1.5.0",
95
- "1.0.96": "1.5.0",
96
- "1.0.97": "1.5.0",
97
- "1.0.98": "1.5.0",
98
- "1.0.99": "1.5.0",
99
- "1.0.100": "1.5.0",
100
- "1.0.101": "1.5.0",
101
- "1.0.102": "1.5.0",
102
- "1.0.103": "1.5.0",
103
- "1.0.104": "1.5.0",
104
- "1.0.105": "1.5.0",
105
- "1.0.106": "1.5.0",
106
- "1.0.107": "1.5.0",
107
- "1.0.108": "1.5.0",
108
- "1.0.109": "1.5.0",
109
- "1.0.110": "1.5.0",
110
- "1.0.111": "1.5.0",
111
- "1.0.112": "1.5.0",
112
- "1.0.113": "1.5.0",
113
- "1.0.114": "1.5.0",
114
- "1.0.115": "1.5.0",
115
- "1.0.116": "1.5.0",
116
- "1.0.117": "1.5.0",
117
- "1.0.118": "1.5.0",
118
- "1.0.119": "1.5.0"
119
- }