@silexlabs/silex 3.5.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.
- package/.silex.js +0 -3
- package/dist/client/client/config.d.ts +8 -0
- package/dist/client/client/grapesjs/cms/DataSource.d.ts +43 -0
- package/dist/client/client/grapesjs/cms/blocks.d.ts +2 -0
- package/dist/client/client/grapesjs/cms/buttons.d.ts +2 -0
- package/dist/client/client/grapesjs/cms/collection-pages.d.ts +4 -0
- package/dist/client/client/grapesjs/cms/config.d.ts +9 -0
- package/dist/client/client/grapesjs/cms/filters.d.ts +3 -0
- package/dist/client/client/grapesjs/cms/index.d.ts +44 -0
- package/dist/client/client/grapesjs/cms/liquid.d.ts +59 -0
- package/dist/client/client/grapesjs/cms/page-settings.d.ts +6 -0
- package/dist/client/client/grapesjs/cms/publication.d.ts +60 -0
- package/dist/client/client/grapesjs/cms/site-settings.d.ts +3 -0
- package/dist/client/client/grapesjs/cms/states.d.ts +8 -0
- package/dist/client/client/grapesjs/cms/traits.d.ts +8 -0
- package/dist/client/client/grapesjs/settings.d.ts +12 -3
- package/dist/client/client/grapesjs/view-buttons.d.ts +2 -1
- package/dist/client/client/utils.d.ts +1 -0
- package/dist/client/constants.d.ts +2 -1
- package/dist/client/css/admin.css +88 -14
- package/dist/client/index.html +2 -2
- package/dist/client/js/main.js +1968 -264
- package/dist/client/types.d.ts +3 -1
- package/dist/plugins/client/plugins/client/template.js +1 -1
- package/dist/plugins/client/plugins/client/template.js.map +1 -1
- package/dist/plugins/client/types.d.ts +3 -1
- package/dist/plugins/client/types.js +11 -8
- package/dist/plugins/client/types.js.map +1 -1
- package/dist/plugins/server/constants.d.ts +2 -1
- package/dist/plugins/server/constants.js +3 -2
- package/dist/plugins/server/constants.js.map +1 -1
- package/dist/plugins/server/plugins/server/FtpConnector.js +15 -20
- package/dist/plugins/server/plugins/server/FtpConnector.js.map +1 -1
- package/dist/plugins/server/plugins/server/GitlabConnector.js +34 -7
- package/dist/plugins/server/plugins/server/GitlabConnector.js.map +1 -1
- package/dist/plugins/server/server/connectors/FsStorage.js +16 -20
- package/dist/plugins/server/server/connectors/FsStorage.js.map +1 -1
- package/dist/plugins/server/server/utils/websiteDataSerialization.d.ts +4 -0
- package/dist/plugins/server/server/utils/websiteDataSerialization.js +21 -12
- package/dist/plugins/server/server/utils/websiteDataSerialization.js.map +1 -1
- package/dist/plugins/server/types.d.ts +3 -1
- package/dist/plugins/server/types.js +12 -9
- package/dist/plugins/server/types.js.map +1 -1
- package/dist/server/constants.js +3 -2
- package/dist/server/server/connectors/FsStorage.js +14 -18
- package/dist/server/server/utils/websiteDataSerialization.js +16 -11
- package/dist/server/types.js +6 -2
- package/package.json +14 -9
- package/src/css/_variables.scss +2 -1
- package/src/css/grapesjs-plugins.scss +31 -0
- package/src/css/grapesjs.scss +15 -0
- package/src/css/page-panel.scss +1 -1
- package/src/css/silex.scss +64 -5
- package/src/ts/client/config.ts +23 -0
- package/src/ts/client/grapesjs/breadcrumbs.ts +3 -2
- package/src/ts/client/grapesjs/cms/DataSource.ts +333 -0
- package/src/ts/client/grapesjs/cms/blocks.ts +124 -0
- package/src/ts/client/grapesjs/cms/buttons.ts +137 -0
- package/src/ts/client/grapesjs/cms/collection-pages.ts +205 -0
- package/src/ts/client/grapesjs/cms/config.ts +47 -0
- package/src/ts/client/grapesjs/cms/filters.ts +53 -0
- package/src/ts/client/grapesjs/cms/index.ts +130 -0
- package/src/ts/client/grapesjs/cms/liquid.mock.ts +181 -0
- package/src/ts/client/grapesjs/cms/liquid.test.ts +239 -0
- package/src/ts/client/grapesjs/cms/liquid.ts +325 -0
- package/src/ts/client/grapesjs/cms/page-settings.ts +285 -0
- package/src/ts/client/grapesjs/cms/publication.test.ts +300 -0
- package/src/ts/client/grapesjs/cms/publication.ts +708 -0
- package/src/ts/client/grapesjs/cms/site-settings.ts +79 -0
- package/src/ts/client/grapesjs/cms/states.ts +125 -0
- package/src/ts/client/grapesjs/cms/traits.ts +95 -0
- package/src/ts/client/grapesjs/flex.ts +59 -39
- package/src/ts/client/grapesjs/img.ts +1 -1
- package/src/ts/client/grapesjs/index.ts +77 -4
- package/src/ts/client/grapesjs/internal-links.ts +4 -2
- package/src/ts/client/grapesjs/page-panel.ts +8 -7
- package/src/ts/client/grapesjs/sectors.ts +18 -10
- package/src/ts/client/grapesjs/settings.ts +252 -101
- package/src/ts/client/grapesjs/storage.ts +6 -1
- package/src/ts/client/grapesjs/view-buttons.ts +39 -3
- package/src/ts/client/utils.ts +15 -2
- package/src/ts/constants.ts +2 -1
- package/src/ts/plugins/client/template.ts +1 -1
- package/src/ts/plugins/server/FtpConnector.test.ts +1 -0
- package/src/ts/plugins/server/FtpConnector.ts +20 -23
- package/src/ts/plugins/server/GitlabConnector.ts +40 -10
- package/src/ts/server/connectors/FsStorage.test.ts +3 -2
- package/src/ts/server/connectors/FsStorage.ts +21 -23
- package/src/ts/server/utils/websiteDataSerialization.test.ts +45 -13
- package/src/ts/server/utils/websiteDataSerialization.ts +23 -14
- 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,
|
|
23
|
-
import { ConnectorType, ConnectorUser, WebsiteMeta, FileMeta, JobData, JobStatus, WebsiteId, PublicationJobData, WebsiteMetaFileContent,
|
|
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(
|
|
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
|
-
|
|
481
|
-
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
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,
|
|
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(
|
|
517
|
-
.map(f => f.path.replace(`${
|
|
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(
|
|
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
|
-
//
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
839
|
+
const pathsToWrite = filesToWrite.map(f => f.path)
|
|
810
840
|
for (const filePath of existingFiles.keys()) {
|
|
811
|
-
if (!
|
|
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
|
-
|
|
53
|
-
expect(parsed).
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
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(
|
|
217
|
+
const hasPageFiles = filesToWrite.some(f => f.path.startsWith(pagesFolder))
|
|
220
218
|
if (hasPageFiles) {
|
|
221
|
-
await fs.mkdir(join(websitePath,
|
|
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,
|
|
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(
|
|
232
|
-
.map(f => f.path.replace(`${
|
|
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,
|
|
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
|
-
...
|
|
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('
|
|
36
|
-
expect(files[1].path).toBe('
|
|
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 {
|
|
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 = `${
|
|
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
|
|
78
|
+
const websiteDataLinkedPages = {
|
|
74
79
|
...websiteData,
|
|
75
|
-
|
|
80
|
+
pagesFolder,
|
|
76
81
|
pages,
|
|
77
82
|
}
|
|
78
83
|
|
|
79
84
|
files.push({
|
|
80
85
|
path: WEBSITE_DATA_FILE,
|
|
81
|
-
content: stringify(
|
|
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
|
|
96
|
-
const { fileFormatVersion, ...websiteData } = websiteDataOb
|
|
100
|
+
const websiteData = JSON.parse(websiteDataContent) as any
|
|
97
101
|
|
|
98
|
-
//
|
|
99
|
-
|
|
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 = `${
|
|
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
|
|
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,
|