@silexlabs/silex 3.4.0 → 3.5.1-1

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 (91) hide show
  1. package/.silex.js +0 -3
  2. package/dist/client/client/config.d.ts +8 -0
  3. package/dist/client/client/grapesjs/cms/DataSource.d.ts +43 -0
  4. package/dist/client/client/grapesjs/cms/blocks.d.ts +2 -0
  5. package/dist/client/client/grapesjs/cms/buttons.d.ts +2 -0
  6. package/dist/client/client/grapesjs/cms/collection-pages.d.ts +4 -0
  7. package/dist/client/client/grapesjs/cms/config.d.ts +9 -0
  8. package/dist/client/client/grapesjs/cms/filters.d.ts +3 -0
  9. package/dist/client/client/grapesjs/cms/index.d.ts +44 -0
  10. package/dist/client/client/grapesjs/cms/liquid.d.ts +59 -0
  11. package/dist/client/client/grapesjs/cms/page-settings.d.ts +6 -0
  12. package/dist/client/client/grapesjs/cms/publication.d.ts +60 -0
  13. package/dist/client/client/grapesjs/cms/site-settings.d.ts +3 -0
  14. package/dist/client/client/grapesjs/cms/states.d.ts +8 -0
  15. package/dist/client/client/grapesjs/cms/traits.d.ts +8 -0
  16. package/dist/client/client/grapesjs/settings.d.ts +12 -3
  17. package/dist/client/client/grapesjs/view-buttons.d.ts +2 -1
  18. package/dist/client/client/utils.d.ts +1 -0
  19. package/dist/client/constants.d.ts +2 -1
  20. package/dist/client/css/admin.css +88 -14
  21. package/dist/client/index.html +2 -2
  22. package/dist/client/js/main.js +1968 -264
  23. package/dist/client/types.d.ts +3 -1
  24. package/dist/plugins/client/plugins/client/template.js +1 -1
  25. package/dist/plugins/client/plugins/client/template.js.map +1 -1
  26. package/dist/plugins/client/types.d.ts +3 -1
  27. package/dist/plugins/client/types.js +11 -8
  28. package/dist/plugins/client/types.js.map +1 -1
  29. package/dist/plugins/server/constants.d.ts +2 -1
  30. package/dist/plugins/server/constants.js +3 -2
  31. package/dist/plugins/server/constants.js.map +1 -1
  32. package/dist/plugins/server/plugins/server/FtpConnector.js +15 -20
  33. package/dist/plugins/server/plugins/server/FtpConnector.js.map +1 -1
  34. package/dist/plugins/server/plugins/server/GitlabConnector.js +34 -7
  35. package/dist/plugins/server/plugins/server/GitlabConnector.js.map +1 -1
  36. package/dist/plugins/server/server/connectors/FsStorage.js +16 -20
  37. package/dist/plugins/server/server/connectors/FsStorage.js.map +1 -1
  38. package/dist/plugins/server/server/utils/websiteDataSerialization.d.ts +4 -0
  39. package/dist/plugins/server/server/utils/websiteDataSerialization.js +21 -12
  40. package/dist/plugins/server/server/utils/websiteDataSerialization.js.map +1 -1
  41. package/dist/plugins/server/types.d.ts +3 -1
  42. package/dist/plugins/server/types.js +12 -9
  43. package/dist/plugins/server/types.js.map +1 -1
  44. package/dist/server/constants.js +3 -2
  45. package/dist/server/server/connectors/FsStorage.js +14 -18
  46. package/dist/server/server/utils/websiteDataSerialization.js +16 -11
  47. package/dist/server/types.js +6 -2
  48. package/package.json +15 -10
  49. package/src/css/_variables.scss +2 -1
  50. package/src/css/grapesjs-plugins.scss +31 -0
  51. package/src/css/grapesjs.scss +15 -0
  52. package/src/css/page-panel.scss +1 -1
  53. package/src/css/silex.scss +64 -5
  54. package/src/ts/client/config.ts +23 -0
  55. package/src/ts/client/grapesjs/breadcrumbs.ts +3 -2
  56. package/src/ts/client/grapesjs/cms/DataSource.ts +333 -0
  57. package/src/ts/client/grapesjs/cms/blocks.ts +124 -0
  58. package/src/ts/client/grapesjs/cms/buttons.ts +137 -0
  59. package/src/ts/client/grapesjs/cms/collection-pages.ts +205 -0
  60. package/src/ts/client/grapesjs/cms/config.ts +47 -0
  61. package/src/ts/client/grapesjs/cms/filters.ts +53 -0
  62. package/src/ts/client/grapesjs/cms/index.ts +130 -0
  63. package/src/ts/client/grapesjs/cms/liquid.mock.ts +181 -0
  64. package/src/ts/client/grapesjs/cms/liquid.test.ts +239 -0
  65. package/src/ts/client/grapesjs/cms/liquid.ts +325 -0
  66. package/src/ts/client/grapesjs/cms/page-settings.ts +285 -0
  67. package/src/ts/client/grapesjs/cms/publication.test.ts +300 -0
  68. package/src/ts/client/grapesjs/cms/publication.ts +708 -0
  69. package/src/ts/client/grapesjs/cms/site-settings.ts +79 -0
  70. package/src/ts/client/grapesjs/cms/states.ts +125 -0
  71. package/src/ts/client/grapesjs/cms/traits.ts +95 -0
  72. package/src/ts/client/grapesjs/flex.ts +59 -39
  73. package/src/ts/client/grapesjs/img.ts +1 -1
  74. package/src/ts/client/grapesjs/index.ts +77 -4
  75. package/src/ts/client/grapesjs/internal-links.ts +4 -2
  76. package/src/ts/client/grapesjs/page-panel.ts +8 -7
  77. package/src/ts/client/grapesjs/sectors.ts +18 -10
  78. package/src/ts/client/grapesjs/settings.ts +252 -101
  79. package/src/ts/client/grapesjs/storage.ts +6 -1
  80. package/src/ts/client/grapesjs/view-buttons.ts +39 -3
  81. package/src/ts/client/utils.ts +15 -2
  82. package/src/ts/constants.ts +2 -1
  83. package/src/ts/plugins/client/template.ts +1 -1
  84. package/src/ts/plugins/server/FtpConnector.test.ts +1 -0
  85. package/src/ts/plugins/server/FtpConnector.ts +20 -23
  86. package/src/ts/plugins/server/GitlabConnector.ts +40 -10
  87. package/src/ts/server/connectors/FsStorage.test.ts +3 -2
  88. package/src/ts/server/connectors/FsStorage.ts +21 -23
  89. package/src/ts/server/utils/websiteDataSerialization.test.ts +45 -13
  90. package/src/ts/server/utils/websiteDataSerialization.ts +23 -14
  91. package/src/ts/types.ts +6 -2
@@ -19,8 +19,8 @@ import { Client } from 'basic-ftp'
19
19
  import { Readable } from 'stream'
20
20
  import { ConnectorFile, ConnectorFileContent, HostingConnector, StatusCallback, StorageConnector, contentToReadable, contentToString, toConnectorData, toConnectorEnum} from '../../server/connectors/connectors'
21
21
  import { requiredParam } from '../../server/utils/validation'
22
- import { WEBSITE_DATA_FILE, WEBSITE_META_DATA_FILE, WEBSITE_PAGES_FOLDER } from '../../constants'
23
- import { ConnectorType, ConnectorUser, WebsiteMeta, FileMeta, JobData, JobStatus, WebsiteId, PublicationJobData, WebsiteMetaFileContent, defaultWebsiteData, WebsiteData, ConnectorOptions } from '../../types'
22
+ import { WEBSITE_DATA_FILE, WEBSITE_META_DATA_FILE, LEGACY_WEBSITE_PAGES_FOLDER } from '../../constants'
23
+ import { ConnectorType, ConnectorUser, WebsiteMeta, FileMeta, JobData, JobStatus, WebsiteId, PublicationJobData, WebsiteMetaFileContent, EMPTY_WEBSITE, WebsiteData, ConnectorOptions } from '../../types'
24
24
  import { ServerConfig } from '../../server/config'
25
25
  import { join } from 'path'
26
26
  import { tmpdir } from 'os'
@@ -28,7 +28,7 @@ import { v4 as uuid } from 'uuid'
28
28
  import { JobManager } from '../../server/jobs'
29
29
  import { mkdtemp, rm, rmdir } from 'fs/promises'
30
30
  import { createReadStream } from 'fs'
31
- import { stringify, split, merge } from '../../server/utils/websiteDataSerialization'
31
+ import { stringify, split, merge, getPagesFolder } from '../../server/utils/websiteDataSerialization'
32
32
 
33
33
  /**
34
34
  * @fileoverview FTP connector for Silex
@@ -443,7 +443,7 @@ export default class FtpConnector implements StorageConnector<FtpSession> {
443
443
  await this.mkdir(ftp, assetsPath)
444
444
  // create website data file
445
445
  const websiteDataPath = join(this.rootPath(session), id, WEBSITE_DATA_FILE)
446
- await this.write(ftp, websiteDataPath, stringify(defaultWebsiteData))
446
+ await this.write(ftp, websiteDataPath, stringify(EMPTY_WEBSITE))
447
447
  // create website meta data file
448
448
  const websiteMetaDataPath = join(this.rootPath(session), id, WEBSITE_META_DATA_FILE)
449
449
  await this.write(ftp, websiteMetaDataPath, stringify({}))
@@ -477,22 +477,16 @@ export default class FtpConnector implements StorageConnector<FtpSession> {
477
477
 
478
478
  // Check if this is the new split format by trying to parse
479
479
  const parsedData = JSON.parse(websiteDataContent)
480
- if (parsedData.fileFormatVersion === '1.0.0') {
481
- // Use the merge function to reconstruct website data
482
- const pageLoader = async (pagePath: string): Promise<string> => {
483
- const fullPath = join(storageRootPath, websiteId, pagePath)
484
- const pageStream = await this.read(ftp, fullPath)
485
- return await contentToString(pageStream)
486
- }
487
-
488
- const result = await merge(websiteDataContent, pageLoader)
489
- this.closeClient(ftp)
490
- return result
491
- } else {
492
- // Legacy format, return as is
493
- this.closeClient(ftp)
494
- return parsedData
480
+ // Use the merge function to reconstruct website data
481
+ const pageLoader = async (pagePath: string): Promise<string> => {
482
+ const fullPath = join(storageRootPath, websiteId, pagePath)
483
+ const pageStream = await this.read(ftp, fullPath)
484
+ return await contentToString(pageStream)
495
485
  }
486
+
487
+ const result = await merge(websiteDataContent, pageLoader)
488
+ this.closeClient(ftp)
489
+ return result
496
490
  } catch (error) {
497
491
  this.closeClient(ftp)
498
492
  throw error
@@ -506,17 +500,20 @@ export default class FtpConnector implements StorageConnector<FtpSession> {
506
500
  // Use the split function to create separate files for pages
507
501
  const filesToWrite = split(data)
508
502
 
503
+ // Get the pages folder path from website data
504
+ const pagesFolder = getPagesFolder(data)
505
+
509
506
  // **
510
507
  // Delete pages that are not in the new website data
511
- const pagesPath = join(storageRootPath, websiteId, WEBSITE_PAGES_FOLDER)
508
+ const pagesPath = join(storageRootPath, websiteId, pagesFolder)
512
509
  try {
513
510
  const existingPageFiles = await this.readdir(ftp, pagesPath)
514
511
  const newPageFiles = new Set(
515
512
  filesToWrite
516
- .filter(f => f.path.startsWith(WEBSITE_PAGES_FOLDER))
517
- .map(f => f.path.replace(`${WEBSITE_PAGES_FOLDER}/`, ''))
513
+ .filter(f => f.path.startsWith(pagesFolder))
514
+ .map(f => f.path.replace(`${pagesFolder}/`, ''))
518
515
  )
519
-
516
+
520
517
  for (const existingFile of existingPageFiles) {
521
518
  if (existingFile.name.endsWith('.json') && !newPageFiles.has(existingFile.name)) {
522
519
  await this.unlink(ftp, join(pagesPath, existingFile.name))
@@ -15,10 +15,10 @@
15
15
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
16
  */
17
17
 
18
- import { API_CONNECTOR_LOGIN_CALLBACK, API_CONNECTOR_PATH, API_PATH, WEBSITE_DATA_FILE, WEBSITE_PAGES_FOLDER } from '../../constants'
18
+ import { API_CONNECTOR_LOGIN_CALLBACK, API_CONNECTOR_PATH, API_PATH, WEBSITE_DATA_FILE, LEGACY_WEBSITE_PAGES_FOLDER, WEBSITE_PAGES_FOLDER } from '../../constants'
19
19
  import { ServerConfig } from '../../server/config'
20
20
  import { ConnectorFile, ConnectorFileContent, StatusCallback, StorageConnector, contentToBuffer, contentToString, toConnectorData } from '../../server/connectors/connectors'
21
- import { ApiError, ConnectorType, ConnectorUser, WebsiteData, WebsiteId, WebsiteMeta, WebsiteMetaFileContent, JobStatus } from '../../types'
21
+ import { ApiError, ConnectorType, ConnectorUser, WebsiteData, WebsiteId, WebsiteMeta, WebsiteMetaFileContent, JobStatus, EMPTY_WEBSITE } from '../../types'
22
22
  import fetch from 'node-fetch'
23
23
  import crypto, { createHash } from 'crypto'
24
24
  import { join } from 'path'
@@ -27,7 +27,7 @@ import { getPageSlug } from '../../page'
27
27
  import e from 'express'
28
28
  import { fork } from 'child_process'
29
29
  import { Page } from 'grapesjs'
30
- import { stringify, split, merge } from '../../server/utils/websiteDataSerialization'
30
+ import { stringify, split, merge, getPagesFolder } from '../../server/utils/websiteDataSerialization'
31
31
 
32
32
  /**
33
33
  * Gitlab connector
@@ -750,7 +750,7 @@ export default class GitlabConnector implements StorageConnector {
750
750
  name: this.options.repoPrefix + websiteMeta.name,
751
751
  }
752
752
  }) as any
753
- await this.createFile(session, project.id, WEBSITE_DATA_FILE, stringify({} as WebsiteData))
753
+ await this.createFile(session, project.id, WEBSITE_DATA_FILE, stringify(EMPTY_WEBSITE))
754
754
  //await this.createFile(session, project.id, WEBSITE_META_DATA_FILE, JSON.stringify(websiteMeta))
755
755
  //await this.updateWebsite(session, project.id, {} as WebsiteData)
756
756
  //await this.setWebsiteMeta(session, project.id, websiteMeta)
@@ -766,14 +766,44 @@ export default class GitlabConnector implements StorageConnector {
766
766
  const batchActions: GitlabAction[] = []
767
767
 
768
768
  // **
769
- // List existing files in the pages folder
769
+ // Handle the legacy sites that have no pagesFolder
770
+ // We want them to use "pages/" by default despite their files being in "src/" for now
771
+ let isLegacySite = !websiteData.pagesFolder
772
+
773
+ // **
774
+ // Backward compatibility case
775
+ // The second time a legacy site is saved, the new `pages/` folder has been created
776
+ // but the front end still doesn't have the `pagesFolder` key
777
+ // This will be the case until the front end reloads
778
+ if (isLegacySite) {
779
+ const rootFiles = await this.ls({
780
+ session,
781
+ websiteId,
782
+ path: WEBSITE_PAGES_FOLDER,
783
+ recursive: false,
784
+ })
785
+ if (rootFiles.size) {
786
+ // Here the new `pages/` folder has been created already
787
+ isLegacySite = false
788
+ websiteData.pagesFolder = WEBSITE_PAGES_FOLDER
789
+ }
790
+ }
791
+
792
+ // **
793
+ // List existing files in the OLD pages folder (to detect files to delete)
770
794
  const existingFiles = await this.ls({
771
795
  session,
772
796
  websiteId,
773
- path: WEBSITE_PAGES_FOLDER,
797
+ path: getPagesFolder(websiteData),
774
798
  recursive: false,
775
799
  })
776
800
 
801
+ // **
802
+ // Force pagesFolder to 'pages' for writing if not defined
803
+ if (isLegacySite) {
804
+ websiteData.pagesFolder = WEBSITE_PAGES_FOLDER
805
+ }
806
+
777
807
  // **
778
808
  // Use the common split function to create files
779
809
  const filesToWrite = split(websiteData)
@@ -787,7 +817,7 @@ export default class GitlabConnector implements StorageConnector {
787
817
 
788
818
  const existingSha = existingFiles.get(filePath) || (filePath === WEBSITE_DATA_FILE ? 'always-update' : undefined)
789
819
 
790
- if (existingSha && existingSha !== 'always-update') {
820
+ if (existingSha) {
791
821
  if (existingSha !== newSha) {
792
822
  batchActions.push({
793
823
  action: 'update',
@@ -797,7 +827,7 @@ export default class GitlabConnector implements StorageConnector {
797
827
  } // else: skip unchanged file
798
828
  } else {
799
829
  batchActions.push({
800
- action: file.path === WEBSITE_DATA_FILE ? 'update' : 'create',
830
+ action: 'create',
801
831
  file_path: filePath,
802
832
  content,
803
833
  })
@@ -806,9 +836,9 @@ export default class GitlabConnector implements StorageConnector {
806
836
 
807
837
  // **
808
838
  // Delete pages that are not in the new website data
809
- const newPageFiles = new Set(filesToWrite.filter(f => f.path.startsWith(WEBSITE_PAGES_FOLDER)).map(f => f.path))
839
+ const pathsToWrite = filesToWrite.map(f => f.path)
810
840
  for (const filePath of existingFiles.keys()) {
811
- if (!newPageFiles.has(filePath)) {
841
+ if (!pathsToWrite.includes(filePath)) {
812
842
  batchActions.push({
813
843
  action: 'delete',
814
844
  file_path: filePath,
@@ -4,6 +4,7 @@ import { FsStorage } from './FsStorage'
4
4
  import { readFileSync, rmdirSync, statSync } from 'fs'
5
5
  import { join } from 'path'
6
6
  import { WEBSITE_DATA_FILE } from '../../constants'
7
+ import { EMPTY_WEBSITE } from '../../types'
7
8
 
8
9
  const storageRootPath = '/tmp/silex-tests'
9
10
  const assetsFolder = 'assets'
@@ -49,7 +50,7 @@ describe('FsStorage website', () => {
49
50
  expect(() => statSync(join(storageRootPath, id, WEBSITE_DATA_FILE))).not.toThrow()
50
51
  const content = readFileSync(join(storageRootPath, id, WEBSITE_DATA_FILE), 'utf8')
51
52
  const parsed = JSON.parse(content)
52
- expect(parsed).toHaveProperty('fileFormatVersion', '1.0.0')
53
- expect(parsed).toHaveProperty('pages', [])
53
+ console.log({parsed, content, EMPTY_WEBSITE})
54
+ expect(parsed).toEqual(EMPTY_WEBSITE)
54
55
  })
55
56
  })
@@ -19,15 +19,15 @@ import fs from 'fs/promises'
19
19
  import { createWriteStream } from 'fs'
20
20
  import { ConnectorFile, StorageConnector, StatusCallback, ConnectorSession, toConnectorData, ConnectorFileContent} from './connectors'
21
21
  import { dirname, join } from 'path'
22
- import { ConnectorUser, WebsiteMeta, JobStatus, WebsiteId, ConnectorType, WebsiteMetaFileContent, WebsiteData, defaultWebsiteData, ConnectorOptions } from '../../types'
22
+ import { ConnectorUser, WebsiteMeta, JobStatus, WebsiteId, ConnectorType, WebsiteMetaFileContent, WebsiteData, EMPTY_WEBSITE, ConnectorOptions } from '../../types'
23
23
  import { userInfo } from 'os'
24
24
  import { requiredParam } from '../utils/validation'
25
25
  import { ServerConfig } from '../config'
26
- import { DEFAULT_WEBSITE_ID, WEBSITE_DATA_FILE, WEBSITE_META_DATA_FILE, WEBSITE_PAGES_FOLDER } from '../../constants'
26
+ import { DEFAULT_WEBSITE_ID, WEBSITE_DATA_FILE, WEBSITE_META_DATA_FILE, LEGACY_WEBSITE_PAGES_FOLDER } from '../../constants'
27
27
  import { Readable } from 'stream'
28
28
  import { v4 as uuid } from 'uuid'
29
29
  import { fileURLToPath } from 'url'
30
- import { stringify, split, merge } from '../utils/websiteDataSerialization'
30
+ import { stringify, split, merge, getPagesFolder } from '../utils/websiteDataSerialization'
31
31
 
32
32
  // Variables needed for jest tests
33
33
  if(!globalThis.__dirname) {
@@ -88,7 +88,7 @@ export class FsStorage implements StorageConnector<FsSession> {
88
88
  const id = DEFAULT_WEBSITE_ID
89
89
  await fs.mkdir(join(this.options.path, id, this.options.assetsFolder), { recursive: true })
90
90
  await this.setWebsiteMeta({}, id, { name: 'Default website', connectorUserSettings: {} })
91
- await this.updateWebsite({}, id, defaultWebsiteData)
91
+ await this.updateWebsite({}, id, EMPTY_WEBSITE)
92
92
  console.info(`> [FsStorage] Created ${id} in ${this.options.path}`)
93
93
  }
94
94
  }
@@ -177,7 +177,7 @@ export class FsStorage implements StorageConnector<FsSession> {
177
177
  const id = uuid()
178
178
  await fs.mkdir(join(this.options.path, id, this.options.assetsFolder), { recursive: true })
179
179
  await this.setWebsiteMeta(session, id, meta)
180
- await this.updateWebsite(session, id, defaultWebsiteData)
180
+ await this.updateWebsite(session, id, EMPTY_WEBSITE)
181
181
  return id
182
182
  }
183
183
 
@@ -190,19 +190,14 @@ export class FsStorage implements StorageConnector<FsSession> {
190
190
 
191
191
  // Check if this is the new split format
192
192
  const parsedData = JSON.parse(websiteDataContent)
193
- if (parsedData.fileFormatVersion === '1.0.0') {
194
- // Use the merge function to reconstruct website data
195
- const pageLoader = async (pagePath: string): Promise<string> => {
196
- const fullPath = join(this.options.path, id, pagePath)
197
- const pageContent = await fs.readFile(fullPath)
198
- return pageContent.toString()
199
- }
200
-
201
- return await merge(websiteDataContent, pageLoader)
202
- } else {
203
- // Legacy format, return as is
204
- return parsedData
193
+ // Use the merge function to reconstruct website data
194
+ const pageLoader = async (pagePath: string): Promise<string> => {
195
+ const fullPath = join(this.options.path, id, pagePath)
196
+ const pageContent = await fs.readFile(fullPath)
197
+ return pageContent.toString()
205
198
  }
199
+
200
+ return await merge(websiteDataContent, pageLoader)
206
201
  }
207
202
 
208
203
  async updateWebsite(session: FsSession, websiteId: WebsiteId, data: WebsiteData): Promise<void> {
@@ -212,26 +207,29 @@ export class FsStorage implements StorageConnector<FsSession> {
212
207
  // Use the split function to create separate files for pages
213
208
  const filesToWrite = split(data)
214
209
 
210
+ // Get the pages folder path from website data
211
+ const pagesFolder = getPagesFolder(data)
212
+
215
213
  // Ensure the website directory exists
216
214
  await fs.mkdir(websitePath, { recursive: true })
217
215
 
218
216
  // Ensure the pages directory exists if we have page files
219
- const hasPageFiles = filesToWrite.some(f => f.path.startsWith(WEBSITE_PAGES_FOLDER))
217
+ const hasPageFiles = filesToWrite.some(f => f.path.startsWith(pagesFolder))
220
218
  if (hasPageFiles) {
221
- await fs.mkdir(join(websitePath, WEBSITE_PAGES_FOLDER), { recursive: true })
219
+ await fs.mkdir(join(websitePath, pagesFolder), { recursive: true })
222
220
  }
223
221
 
224
222
  // **
225
223
  // Delete pages that are not in the new website data
226
- const pagesPath = join(websitePath, WEBSITE_PAGES_FOLDER)
224
+ const pagesPath = join(websitePath, pagesFolder)
227
225
  try {
228
226
  const existingPageFiles = await fs.readdir(pagesPath)
229
227
  const newPageFiles = new Set(
230
228
  filesToWrite
231
- .filter(f => f.path.startsWith(WEBSITE_PAGES_FOLDER))
232
- .map(f => f.path.replace(`${WEBSITE_PAGES_FOLDER}/`, ''))
229
+ .filter(f => f.path.startsWith(pagesFolder))
230
+ .map(f => f.path.replace(`${pagesFolder}/`, ''))
233
231
  )
234
-
232
+
235
233
  for (const existingFile of existingPageFiles) {
236
234
  if (existingFile.endsWith('.json') && !newPageFiles.has(existingFile)) {
237
235
  await fs.unlink(join(pagesPath, existingFile))
@@ -1,7 +1,7 @@
1
- import { stringify, split, merge } from './websiteDataSerialization'
1
+ import { stringify, split, merge, getPagesFolder } from './websiteDataSerialization'
2
2
  import { expect, it, describe } from '@jest/globals'
3
- import { WebsiteData, defaultWebsiteData } from '../../types'
4
- import { WEBSITE_DATA_FILE } from '../../constants'
3
+ import { WebsiteData, EMPTY_WEBSITE } from '../../types'
4
+ import { WEBSITE_DATA_FILE, WEBSITE_PAGES_FOLDER } from '../../constants'
5
5
 
6
6
  describe('websiteDataSerialization', () => {
7
7
  describe('stringify', () => {
@@ -22,22 +22,21 @@ describe('websiteDataSerialization', () => {
22
22
  describe('split', () => {
23
23
  it('should split website data into separate files', () => {
24
24
  const websiteData: WebsiteData = {
25
- ...defaultWebsiteData,
25
+ ...EMPTY_WEBSITE,
26
26
  pages: [
27
27
  { id: 'page1', getName: () => 'Home' } as any,
28
28
  { id: 'page2', getName: () => 'About' } as any
29
29
  ]
30
30
  }
31
-
31
+
32
32
  const files = split(websiteData)
33
-
33
+
34
34
  expect(files).toHaveLength(3)
35
- expect(files[0].path).toBe('src/home-page1.json')
36
- expect(files[1].path).toBe('src/about-page2.json')
35
+ expect(files[0].path).toBe('pages/home-page1.json')
36
+ expect(files[1].path).toBe('pages/about-page2.json')
37
37
  expect(files[2].path).toBe(WEBSITE_DATA_FILE)
38
-
38
+
39
39
  const mainFile = JSON.parse(files[2].content)
40
- expect(mainFile.fileFormatVersion).toBe('1.0.0')
41
40
  expect(mainFile.pages[0].isFile).toBe(true)
42
41
  })
43
42
  })
@@ -48,12 +47,45 @@ describe('websiteDataSerialization', () => {
48
47
  "settings": {},
49
48
  "pages": [{ "id": "page1" }]
50
49
  }`
51
-
50
+
52
51
  const pageLoader = async () => { throw new Error('Should not be called') }
53
52
  const result = await merge(legacyContent, pageLoader)
54
-
53
+
55
54
  expect(result.pages).toHaveLength(1)
56
55
  expect(result.pages[0].id).toBe('page1')
57
56
  })
58
57
  })
59
- })
58
+
59
+ describe('getPagesFolder', () => {
60
+ it('should return default folder when pagesFolder is not set', () => {
61
+ const websiteData = { ...EMPTY_WEBSITE }
62
+ const result = getPagesFolder(websiteData)
63
+ expect(result).toBe(WEBSITE_PAGES_FOLDER)
64
+ })
65
+
66
+ it('should return custom folder when pagesFolder is set', () => {
67
+ const websiteData = { ...EMPTY_WEBSITE, pagesFolder: 'custom-pages' }
68
+ const result = getPagesFolder(websiteData)
69
+ expect(result).toBe('custom-pages')
70
+ })
71
+ })
72
+
73
+ describe('split with custom pages folder', () => {
74
+ it('should use custom pages folder in file paths', () => {
75
+ const websiteData = {
76
+ ...EMPTY_WEBSITE,
77
+ pagesFolder: 'custom-pages',
78
+ pages: [{
79
+ id: 'page1',
80
+ getName: () => 'Home'
81
+ } as any]
82
+ }
83
+
84
+ const files = split(websiteData)
85
+ const pageFile = files.find(f => f.path.includes('page1'))
86
+
87
+ expect(pageFile).toBeDefined()
88
+ expect(pageFile!.path).toMatch(/^custom-pages\/.*\.json$/)
89
+ })
90
+ })
91
+ })
@@ -15,10 +15,10 @@
15
15
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
16
  */
17
17
 
18
- import { WebsiteData } from '../../types'
18
+ import { EMPTY_PAGES, WebsiteData } from '../../types'
19
19
  import { Page } from 'grapesjs'
20
20
  import { getPageSlug } from '../../page'
21
- import { WEBSITE_PAGES_FOLDER, WEBSITE_DATA_FILE } from '../../constants'
21
+ import { LEGACY_WEBSITE_PAGES_FOLDER, WEBSITE_DATA_FILE } from '../../constants'
22
22
 
23
23
  /**
24
24
  * Serialize JSON with stable formatting for git
@@ -50,12 +50,17 @@ function sortKeys(key: string, value: any): any {
50
50
  export function split(websiteData: WebsiteData): Array<{path: string, content: string}> {
51
51
  const files: Array<{path: string, content: string}> = []
52
52
 
53
+ // Use custom pages folder if specified, otherwise use default
54
+ const pagesFolder = websiteData.pagesFolder || LEGACY_WEBSITE_PAGES_FOLDER
55
+
53
56
  // Split pages into separate files
54
57
  const pages = websiteData.pages?.map((page: Page) => {
55
58
  const pageName = getPageName(page)
56
59
  const slug = getPageSlug(pageName)
57
60
  const fileName = `${slug}-${page.id}.json`
58
- const filePath = `${WEBSITE_PAGES_FOLDER}/${fileName}`
61
+ const filePath = `${pagesFolder}/${fileName}`
62
+
63
+ if(!page.id) return page // It is the {} from EMPTY_PAGES, this happens only with unit tests
59
64
 
60
65
  files.push({
61
66
  path: filePath,
@@ -67,18 +72,18 @@ export function split(websiteData: WebsiteData): Array<{path: string, content: s
67
72
  id: page.id,
68
73
  isFile: true,
69
74
  }
70
- }) || []
75
+ }) || EMPTY_PAGES
71
76
 
72
77
  // Create main website data file without pages content
73
- const websiteDataWithoutPages = {
78
+ const websiteDataLinkedPages = {
74
79
  ...websiteData,
75
- fileFormatVersion: '1.0.0',
80
+ pagesFolder,
76
81
  pages,
77
82
  }
78
83
 
79
84
  files.push({
80
85
  path: WEBSITE_DATA_FILE,
81
- content: stringify(websiteDataWithoutPages)
86
+ content: stringify(websiteDataLinkedPages)
82
87
  })
83
88
 
84
89
  return files
@@ -92,13 +97,10 @@ export async function merge(
92
97
  websiteDataContent: string,
93
98
  pageLoader: (pagePath: string) => Promise<string>
94
99
  ): Promise<WebsiteData> {
95
- const websiteDataOb = JSON.parse(websiteDataContent) as any
96
- const { fileFormatVersion, ...websiteData } = websiteDataOb
100
+ const websiteData = JSON.parse(websiteDataContent) as any
97
101
 
98
- // Check file format version
99
- if (fileFormatVersion && fileFormatVersion !== '1.0.0') {
100
- console.warn('Website data file format version mismatch', fileFormatVersion, '!=', '1.0.0')
101
- }
102
+ // Use custom pages folder if specified, otherwise use default
103
+ const pagesFolder = websiteData.pagesFolder || LEGACY_WEBSITE_PAGES_FOLDER
102
104
 
103
105
  // Handle legacy format (no split pages) or empty pages
104
106
  if (!websiteData.pages || websiteData.pages.length === 0) {
@@ -116,7 +118,7 @@ export async function merge(
116
118
  const pageName = pageRef.name
117
119
  const slug = getPageSlug(pageName)
118
120
  const fileName = `${slug}-${pageRef.id}.json`
119
- const filePath = `${WEBSITE_PAGES_FOLDER}/${fileName}`
121
+ const filePath = `${pagesFolder}/${fileName}`
120
122
 
121
123
  try {
122
124
  const pageContent = await pageLoader(filePath)
@@ -135,6 +137,13 @@ export async function merge(
135
137
  } as WebsiteData
136
138
  }
137
139
 
140
+ /**
141
+ * Get the pages folder path for a website, using custom path or default
142
+ */
143
+ export function getPagesFolder(websiteData: WebsiteData): string {
144
+ return websiteData.pagesFolder || LEGACY_WEBSITE_PAGES_FOLDER
145
+ }
146
+
138
147
  /**
139
148
  * Get page name from page object
140
149
  */
package/src/ts/types.ts CHANGED
@@ -89,7 +89,9 @@ export type ApiConnectorUserResponse = ConnectorUser
89
89
 
90
90
  // **
91
91
  // Website API
92
- export const defaultWebsiteData: WebsiteData = {
92
+ export const EMPTY_PAGES = [{}] as Page[] // This is what grapesjs understands, it will create an empty page
93
+
94
+ export const EMPTY_WEBSITE: WebsiteData = {
93
95
  //pages: [],
94
96
  //assets: [],
95
97
  //styles: [],
@@ -97,15 +99,17 @@ export const defaultWebsiteData: WebsiteData = {
97
99
  //fonts: [],
98
100
  //symbols: [],
99
101
  //publication: {},
102
+ pages: EMPTY_PAGES,
103
+ pagesFolder: 'pages',
100
104
  } as WebsiteData
101
105
 
102
-
103
106
  export interface WebsiteData {
104
107
  pages: Page[],
105
108
  assets: Asset[],
106
109
  styles: Style[],
107
110
  //name: string,
108
111
  settings: WebsiteSettings,
112
+ pagesFolder: string,
109
113
  fonts: Font[],
110
114
  symbols: symbol[],
111
115
  publication: PublicationSettings,