@silexlabs/silex 3.0.0-alpha.21
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/.dockerignore +5 -0
- package/.editorconfig +14 -0
- package/.eslintrc.json +43 -0
- package/.github/FUNDING.yml +13 -0
- package/.github/workflows/docker-hub-publish.yml +36 -0
- package/.github/workflows/npm-publish.yml +19 -0
- package/.github/workflows/test.yml +26 -0
- package/.nvmrc +1 -0
- package/.silex.js +19 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/CONTRIBUTING.md +108 -0
- package/Dockerfile +15 -0
- package/LICENSE +203 -0
- package/app.json +106 -0
- package/directus.silex.js +7 -0
- package/dist/client/css/admin.css +4350 -0
- package/dist/client/index.html +9 -0
- package/dist/client/js/main.js +282 -0
- package/dist/client/js/main.js.LICENSE.txt +52 -0
- package/dist/server/config.js +50 -0
- package/dist/server/events.js +5 -0
- package/dist/server/index.js +29 -0
- package/dist/server/start.js +18 -0
- package/jest.config.js +10 -0
- package/logos/logo_by_ceubri/silex-icon-2018@200px.png +0 -0
- package/logos/logo_by_ceubri/silex-icon-2018@4x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white-transp.pdf +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white.jpg +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white.pdf +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white@0.5x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white@0.75x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white@4x.jpg +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white@4x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018.ai +2520 -6
- package/logos/logo_by_ceubri/silex-logo-2018.jpg +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018.pdf +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018@0.5x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018@0.75x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018@4x.jpg +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018@4x.png +0 -0
- package/logos/silexlabs.png +0 -0
- package/package.json +170 -0
- package/public/assets/container.png +0 -0
- package/public/assets/favicon.png +0 -0
- package/public/assets/logo-silex-small.png +0 -0
- package/public/example-config.js +31 -0
- package/public/robots.txt +2 -0
- package/readme.md +51 -0
- package/src/css/_mixins.scss +8 -0
- package/src/css/_variables.scss +33 -0
- package/src/css/dialog.scss +34 -0
- package/src/css/grapesjs.scss +44 -0
- package/src/css/index.scss +22 -0
- package/src/css/loader.scss +10 -0
- package/src/css/page-panel.scss +65 -0
- package/src/css/project-bar.scss +102 -0
- package/src/css/silex.scss +183 -0
- package/src/html/debug.pug +8 -0
- package/src/html/index.pug +39 -0
- package/src/html/templates/includes.pug +2 -0
- package/src/html/templates/workspace.pug +2 -0
- package/src/plugins/AuthPlugin.js +21 -0
- package/src/plugins/Cache.js +11 -0
- package/src/plugins/DefaultBackend.js +113 -0
- package/src/plugins/ExpressPlugin.js +30 -0
- package/src/plugins/HooksPlugin.js +58 -0
- package/src/plugins/PublishPlugin.js +69 -0
- package/src/plugins/SslPlugin.js +66 -0
- package/src/plugins/StaticPlugin.js +48 -0
- package/src/plugins/WebsitePlugin.js +149 -0
- package/src/ts/client/config.ts +21 -0
- package/src/ts/client/expose.ts +4 -0
- package/src/ts/client/grapesjs/blocks.ts +88 -0
- package/src/ts/client/grapesjs/eleventy.ts +21 -0
- package/src/ts/client/grapesjs/index.ts +255 -0
- package/src/ts/client/grapesjs/internal-links.test.ts +87 -0
- package/src/ts/client/grapesjs/internal-links.ts +210 -0
- package/src/ts/client/grapesjs/new-page-dialog.ts +73 -0
- package/src/ts/client/grapesjs/openImport.ts +59 -0
- package/src/ts/client/grapesjs/page-panel.ts +101 -0
- package/src/ts/client/grapesjs/project-bar.ts +92 -0
- package/src/ts/client/grapesjs/publish.ts +386 -0
- package/src/ts/client/grapesjs/rich-text.ts +16 -0
- package/src/ts/client/grapesjs/semantic.ts +96 -0
- package/src/ts/client/grapesjs/settings.ts +190 -0
- package/src/ts/client/grapesjs/symbolDialogs.ts +21 -0
- package/src/ts/client/grapesjs/template.ts +166 -0
- package/src/ts/client/index.ts +38 -0
- package/src/ts/client/utils.ts +9 -0
- package/src/ts/externs.ts +1 -0
- package/src/ts/page.ts +14 -0
- package/src/ts/server/cli.ts +23 -0
- package/src/ts/server/config.ts +62 -0
- package/src/ts/server/events.ts +2 -0
- package/src/ts/server/index.ts +35 -0
- package/src/ts/server/start.ts +18 -0
- package/src/ts/server/utils/BackwardCompat.ts +534 -0
- package/src/ts/types.ts +138 -0
- package/tsconfig-server-debug.json +20 -0
- package/tsconfig-server-release.json +19 -0
- package/tsconfig.jest.json +9 -0
- package/tsconfig.json +27 -0
- package/webpack.config.js +29 -0
- package/webpack.debug.config.js +8 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
const { Router } = require('express')
|
|
2
|
+
const { resolve } = require('path')
|
|
3
|
+
const formidable = require('formidable')
|
|
4
|
+
|
|
5
|
+
const { noCache } = require('./Cache')
|
|
6
|
+
|
|
7
|
+
const EVENT_READ_START = 'EVENT_READ_START'
|
|
8
|
+
const EVENT_READ_END = 'EVENT_READ_END'
|
|
9
|
+
const EVENT_WRITE_START = 'EVENT_WRITE_START'
|
|
10
|
+
const EVENT_WRITE_END = 'EVENT_WRITE_END'
|
|
11
|
+
const EVENT_ASSET_READ_START = 'EVENT_ASSET_READ_START'
|
|
12
|
+
const EVENT_ASSET_READ_END = 'EVENT_ASSET_READ_END'
|
|
13
|
+
const EVENT_ASSET_WRITE_START = 'EVENT_ASSET_WRITE_START'
|
|
14
|
+
const EVENT_ASSET_WRITE_END = 'EVENT_ASSET_WRITE_END'
|
|
15
|
+
|
|
16
|
+
module.exports = async function(config, opts = {}) {
|
|
17
|
+
// Options with defaults
|
|
18
|
+
const options = {
|
|
19
|
+
// Defaults
|
|
20
|
+
backend: 'src/plugins/DefaultBackend.js',
|
|
21
|
+
// Options
|
|
22
|
+
...opts
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Link to the desired backend
|
|
26
|
+
const backend = resolve(__dirname, '../..', options.backend)
|
|
27
|
+
const { assetsDir, assetUrl, init, writeData, readData } = require(backend)
|
|
28
|
+
|
|
29
|
+
config.on('silex:startup:start', ({app}) => {
|
|
30
|
+
// const backwardCompat = new BackwardCompat(rootUrl)
|
|
31
|
+
const router = Router()
|
|
32
|
+
|
|
33
|
+
// website specials
|
|
34
|
+
router.get('/website', readWebsite)
|
|
35
|
+
router.post('/website', writeWebsite)
|
|
36
|
+
router.get(/\/assets\/(.*)/, readAsset)
|
|
37
|
+
router.post('/assets', writeAsset)
|
|
38
|
+
|
|
39
|
+
app.use(noCache, router)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
async function readWebsite(req, res) {
|
|
43
|
+
const id = req.query.id
|
|
44
|
+
config.emit(EVENT_READ_START, { req, id })
|
|
45
|
+
try {
|
|
46
|
+
const data = await readData(id)
|
|
47
|
+
config.emit(EVENT_READ_END, { req, res, data, id })
|
|
48
|
+
res
|
|
49
|
+
.type('application/json')
|
|
50
|
+
.send(data)
|
|
51
|
+
} catch (err) {
|
|
52
|
+
if(err.code === 'ENOENT') {
|
|
53
|
+
console.error('Read file error, website does not exist', err)
|
|
54
|
+
res.status(404).json({ message: 'Read file error, website does not exist: ' + err.message, code: err.code})
|
|
55
|
+
} else {
|
|
56
|
+
console.error('Read file error', err)
|
|
57
|
+
res.status(400).json({ message: 'Read file error: ' + err.message, code: err.code})
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function fromBody(body) {
|
|
63
|
+
try {
|
|
64
|
+
return [{
|
|
65
|
+
assets: body.assets,
|
|
66
|
+
pages: body.pages,
|
|
67
|
+
files: body.files,
|
|
68
|
+
styles: body.styles,
|
|
69
|
+
settings: body.settings,
|
|
70
|
+
publication: body.publication,
|
|
71
|
+
name: body.name,
|
|
72
|
+
fonts: body.fonts,
|
|
73
|
+
symbols: body.symbols,
|
|
74
|
+
}, null]
|
|
75
|
+
} catch(err) {
|
|
76
|
+
console.error('Could not parse body data', body, err)
|
|
77
|
+
return [null, err]
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function writeWebsite(req, res) {
|
|
82
|
+
const id = req.query.id
|
|
83
|
+
await init(id)
|
|
84
|
+
|
|
85
|
+
const [data, err] = fromBody(req.body)
|
|
86
|
+
if(err) {
|
|
87
|
+
res.status(400).json({ message: 'Error writing data file, could not parse the provided body: ' + err.message, code: err.code})
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
config.emit(EVENT_WRITE_START, { req, id, data })
|
|
91
|
+
try {
|
|
92
|
+
await writeData(id, data)
|
|
93
|
+
} catch (err) {
|
|
94
|
+
console.error('Error writing data file', err)
|
|
95
|
+
res.status(500).json({ message: 'Error writing data file: ' + err.message, code: err.code})
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
config.emit(EVENT_WRITE_END, { res, req, id, data })
|
|
100
|
+
|
|
101
|
+
res.json({
|
|
102
|
+
message: 'OK',
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function readAsset(req, res) {
|
|
107
|
+
const id = req.query.id
|
|
108
|
+
const fileName = req.params[0]
|
|
109
|
+
const uploadDir = await assetsDir(id)
|
|
110
|
+
config.emit(EVENT_ASSET_READ_START, { req, id, fileName, uploadDir })
|
|
111
|
+
res.sendFile(`${uploadDir}/${fileName}`)
|
|
112
|
+
config.emit(EVENT_ASSET_READ_END, { req, id, fileName, uploadDir })
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function writeAsset(req, res) {
|
|
116
|
+
const id = req.query.id
|
|
117
|
+
// FIXME: assetsDir and assetsUrl will both download the site settings file => get both in 1 call
|
|
118
|
+
const uploadDir = await assetsDir(id)
|
|
119
|
+
|
|
120
|
+
const form = formidable({
|
|
121
|
+
uploadDir,
|
|
122
|
+
filename: (name, ext, part, _form) => `${name}${ext}`,
|
|
123
|
+
multiples: true,
|
|
124
|
+
keepExtensions: true,
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
form.parse(req, async (err, fields, files) => {
|
|
128
|
+
if (err) {
|
|
129
|
+
console.error('Error parsing upload data', err)
|
|
130
|
+
res
|
|
131
|
+
.status(400)
|
|
132
|
+
.json({ message: 'Error parsing upload data: ' + err.message, code: err.code})
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
Promise.all(
|
|
136
|
+
[].concat(files['files[]']) // may be an array or 1 element
|
|
137
|
+
.map(file => {
|
|
138
|
+
const { originalFilename, filepath } = file
|
|
139
|
+
return assetUrl(id, originalFilename)
|
|
140
|
+
})
|
|
141
|
+
)
|
|
142
|
+
.then(data => {
|
|
143
|
+
config.emit(EVENT_ASSET_WRITE_START, { req, id, uploadDir, form, data })
|
|
144
|
+
res.json({ data })
|
|
145
|
+
config.emit(EVENT_ASSET_WRITE_END, { res, req, id, uploadDir, form, data })
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Config } from '@silexlabs/silex-plugins'
|
|
2
|
+
import { getEditorConfig } from './grapesjs'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @fileoverview Silex client side config
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const id = new URL(location.href).searchParams.get('id') || 'default'
|
|
9
|
+
const rootUrl = '.'
|
|
10
|
+
|
|
11
|
+
export class SilexConfig extends Config {
|
|
12
|
+
/**
|
|
13
|
+
* debug mode
|
|
14
|
+
*/
|
|
15
|
+
debug = false
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Grapesjs config
|
|
19
|
+
*/
|
|
20
|
+
editor = getEditorConfig(id, rootUrl)
|
|
21
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as grapesjs from 'grapesjs/dist/grapes.min.js'
|
|
2
|
+
|
|
3
|
+
const pluginName = 'blocks'
|
|
4
|
+
const clsContainer = 'silex-container'
|
|
5
|
+
const clsSection = 'silex-section'
|
|
6
|
+
const clsSectionBackground = 'silex-section-background'
|
|
7
|
+
|
|
8
|
+
export const blocksPlugin = grapesjs.plugins.add(pluginName, (editor, opts) => {
|
|
9
|
+
// Make row and column classes private
|
|
10
|
+
const privateCls = [clsSection, clsSectionBackground, clsContainer]
|
|
11
|
+
.map(cls => '.' + cls)
|
|
12
|
+
editor.on(
|
|
13
|
+
'selector:add',
|
|
14
|
+
selector => privateCls.indexOf(selector.getFullName()) >= 0 && selector.set('private', 1)
|
|
15
|
+
)
|
|
16
|
+
;[
|
|
17
|
+
// {
|
|
18
|
+
// id: 'section',
|
|
19
|
+
// def: {
|
|
20
|
+
// label: 'Section',
|
|
21
|
+
// category: 'Containers',
|
|
22
|
+
// attributes: { class: 'fa fa-list-alt' },
|
|
23
|
+
// draggable: 'body',
|
|
24
|
+
// content: {
|
|
25
|
+
// tagName: 'section',
|
|
26
|
+
// styles: `
|
|
27
|
+
// .${clsSectionBackground} {
|
|
28
|
+
// display: flex;
|
|
29
|
+
// justify-content: center;
|
|
30
|
+
// }
|
|
31
|
+
// `,
|
|
32
|
+
// components: [
|
|
33
|
+
// {
|
|
34
|
+
// tagName: 'div',
|
|
35
|
+
// name: 'Section container',
|
|
36
|
+
// attributes: { class: ['website-width'] },
|
|
37
|
+
// draggable: false,
|
|
38
|
+
// removable: false,
|
|
39
|
+
// resizable: true,
|
|
40
|
+
// styles: `
|
|
41
|
+
// .${clsSection} {
|
|
42
|
+
// min-height: 100px;
|
|
43
|
+
// width: 1200px;
|
|
44
|
+
// }
|
|
45
|
+
// `,
|
|
46
|
+
// },
|
|
47
|
+
// ],
|
|
48
|
+
// },
|
|
49
|
+
// },
|
|
50
|
+
// },
|
|
51
|
+
{
|
|
52
|
+
id: 'container',
|
|
53
|
+
def: {
|
|
54
|
+
label: 'Container',
|
|
55
|
+
category: 'Basics',
|
|
56
|
+
attributes: { class: 'container-png' },
|
|
57
|
+
content: {
|
|
58
|
+
type: 'container',
|
|
59
|
+
style: {
|
|
60
|
+
'min-height': '100px',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
// {
|
|
66
|
+
// id: 'link-block',
|
|
67
|
+
// def: {
|
|
68
|
+
// category: 'Containers',
|
|
69
|
+
// label: 'Link Block',
|
|
70
|
+
// attributes: { class: 'fa fa-link' },
|
|
71
|
+
// content: {
|
|
72
|
+
// type: 'link',
|
|
73
|
+
// editable: false,
|
|
74
|
+
// droppable: true,
|
|
75
|
+
// resizable: true,
|
|
76
|
+
// style:{
|
|
77
|
+
// display: 'inline-block',
|
|
78
|
+
// padding: '5px',
|
|
79
|
+
// 'min-height': '100px',
|
|
80
|
+
// 'width': '100px'
|
|
81
|
+
// }
|
|
82
|
+
// },
|
|
83
|
+
// },
|
|
84
|
+
// },
|
|
85
|
+
]
|
|
86
|
+
.forEach(block => editor.BlockManager.add(block.id, block.def))
|
|
87
|
+
})
|
|
88
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import grapesjs from 'grapesjs/dist/grapes.min.js'
|
|
2
|
+
|
|
3
|
+
import { getPageSlug } from '../../page'
|
|
4
|
+
import { Page } from '../../types'
|
|
5
|
+
|
|
6
|
+
const pluginName = 'eleventy'
|
|
7
|
+
|
|
8
|
+
export const eleventyPlugin = grapesjs.plugins.add(pluginName, (editor, opts) => {
|
|
9
|
+
editor.on(opts.eventStart || 'publish:start', data => {
|
|
10
|
+
data.pages.forEach((page: Page, idx) => {
|
|
11
|
+
const file = data.files[idx]
|
|
12
|
+
file.css = `---
|
|
13
|
+
permalink: /css/${getPageSlug(page.name)}.css
|
|
14
|
+
---
|
|
15
|
+
${file.css}
|
|
16
|
+
`
|
|
17
|
+
file.cssPath += '.liquid'
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import grapesjs, { EditorConfig } from 'grapesjs'
|
|
2
|
+
import openImport from './openImport'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @fileoverview This is where grapes config gets created
|
|
6
|
+
* Handle plugins, options and initialization of the editor
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ////////////////////
|
|
10
|
+
// Plugins
|
|
11
|
+
// ////////////////////
|
|
12
|
+
import blocksBasicPlugin from 'grapesjs-blocks-basic'
|
|
13
|
+
import styleFilterPlugin from 'grapesjs-style-filter'
|
|
14
|
+
import formPlugin from 'grapesjs-plugin-forms'
|
|
15
|
+
import codePlugin from 'grapesjs-custom-code'
|
|
16
|
+
import uiSuggestClasses from '@silexlabs/grapesjs-ui-suggest-classes'
|
|
17
|
+
import symbolsPlugin from '@silexlabs/grapesjs-symbols/src'
|
|
18
|
+
import loadingPlugin from '@silexlabs/grapesjs-loading'
|
|
19
|
+
import fontsDialogPlugin, { cmdOpenFonts } from '@silexlabs/grapesjs-fonts'
|
|
20
|
+
import symbolDialogsPlugin, { cmdPromptAddSymbol } from './symbolDialogs'
|
|
21
|
+
|
|
22
|
+
import { pagePanelPlugin, cmdTogglePages, cmdAddPage } from './page-panel'
|
|
23
|
+
import { newPageDialog, cmdOpenNewPageDialog } from './new-page-dialog'
|
|
24
|
+
import { projectBarPlugin } from './project-bar'
|
|
25
|
+
import { settingsDialog, cmdOpenSettings } from './settings'
|
|
26
|
+
import { blocksPlugin } from './blocks'
|
|
27
|
+
import { semanticPlugin } from './semantic'
|
|
28
|
+
import { richTextPlugin } from './rich-text'
|
|
29
|
+
import { internalLinksPlugin } from './internal-links'
|
|
30
|
+
import { publishPlugin } from './publish'
|
|
31
|
+
import { templatePlugin } from './template'
|
|
32
|
+
import { eleventyPlugin } from './eleventy'
|
|
33
|
+
|
|
34
|
+
const plugins = [
|
|
35
|
+
{name: './grapesjs/project-bar', value: projectBarPlugin}, // has to be before panels and dialogs
|
|
36
|
+
{name: './grapesjs/settings', value: settingsDialog},
|
|
37
|
+
{name: '@silexlabs/grapesjs-fonts', value: fontsDialogPlugin},
|
|
38
|
+
{name: './grapesjs/new-page-dialog', value: newPageDialog},
|
|
39
|
+
{name: './grapesjs/page-panel', value: pagePanelPlugin},
|
|
40
|
+
{name: 'grapesjs-blocks-basic', value: blocksBasicPlugin},
|
|
41
|
+
{name: './grapesjs/blocks', value: blocksPlugin},
|
|
42
|
+
{name: './grapesjs/semantic', value: semanticPlugin},
|
|
43
|
+
{name: './grapesjs/rich-text', value: richTextPlugin},
|
|
44
|
+
{name: 'grapesjs-style-filter', value: styleFilterPlugin},
|
|
45
|
+
{name: 'grapesjs-plugin-forms', value: formPlugin},
|
|
46
|
+
{name: 'grapesjs-custom-code', value: codePlugin},
|
|
47
|
+
{name: './grapesjs/internal-links', value: internalLinksPlugin},
|
|
48
|
+
{name: '@silexlabs/grapesjs-ui-suggest-classes', value: uiSuggestClasses},
|
|
49
|
+
{name: './grapesjs/symbolDialogs', value: symbolDialogsPlugin},
|
|
50
|
+
{name: '@silexlabs/grapesjs-symbols', value: symbolsPlugin},
|
|
51
|
+
{name: './grapesjs/publish', value: publishPlugin},
|
|
52
|
+
{name: './grapesjs/template', value: templatePlugin},
|
|
53
|
+
{name: './grapesjs/eleventy', value: eleventyPlugin},
|
|
54
|
+
{name: '@silexlabs/grapesjs-loading', value: loadingPlugin},
|
|
55
|
+
]
|
|
56
|
+
// Check that all plugins are loaded correctly
|
|
57
|
+
plugins
|
|
58
|
+
.filter(p => !p.value)
|
|
59
|
+
.forEach(p => {
|
|
60
|
+
throw new Error(`Plugin ${p.name} could not be loaded correctly`)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
export function getPlugins() { return plugins }
|
|
64
|
+
|
|
65
|
+
// ////////////////////
|
|
66
|
+
// Config
|
|
67
|
+
// ////////////////////
|
|
68
|
+
const catBasic = 'Containers'
|
|
69
|
+
const catComponents = 'Components'
|
|
70
|
+
|
|
71
|
+
export function getEditorConfig(id: string, rootUrl: string) {
|
|
72
|
+
return {
|
|
73
|
+
container: '#gjs',
|
|
74
|
+
height: '100%',
|
|
75
|
+
showOffsets: true,
|
|
76
|
+
showDevices: true,
|
|
77
|
+
|
|
78
|
+
//pageManager: {},
|
|
79
|
+
|
|
80
|
+
layerManager: {
|
|
81
|
+
appendTo: '.layer-manager-container',
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
blockManager: {
|
|
85
|
+
appendTo: '.block-manager-container',
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
storageManager: {
|
|
89
|
+
autoload: true,
|
|
90
|
+
options: {
|
|
91
|
+
remote: {
|
|
92
|
+
urlStore: `${rootUrl}/website/?id=${id}`,
|
|
93
|
+
urlLoad: `${rootUrl}/website/?id=${id}`,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
plugins: plugins.map(p => p.value),
|
|
99
|
+
|
|
100
|
+
pluginsOpts: {
|
|
101
|
+
[blocksBasicPlugin as any]: {
|
|
102
|
+
blocks: ['text', 'image', 'video', 'map'],
|
|
103
|
+
category: catBasic,
|
|
104
|
+
//flexGrid: true,
|
|
105
|
+
},
|
|
106
|
+
[projectBarPlugin as any]: {
|
|
107
|
+
panels: [
|
|
108
|
+
{
|
|
109
|
+
id: 'dash',
|
|
110
|
+
className: 'logo',
|
|
111
|
+
attributes: { title: 'Go to your dashboard' },
|
|
112
|
+
link: '/',
|
|
113
|
+
command: 'open-dash',
|
|
114
|
+
}, {
|
|
115
|
+
id: 'block-manager-btn',
|
|
116
|
+
className: 'block-manager-btn fa fa-fw fa-plus',
|
|
117
|
+
attributes: { title: 'Blocks', containerClassName: 'block-manager-container', },
|
|
118
|
+
command: 'open-blocks',
|
|
119
|
+
}, {
|
|
120
|
+
id: 'symbols-btn',
|
|
121
|
+
className: 'symbols-btn fa fa-fw fa-diamond',
|
|
122
|
+
attributes: { title: 'Symbols', containerClassName: 'symbols-list-container', },
|
|
123
|
+
command: 'open-symbols',
|
|
124
|
+
buttons: [
|
|
125
|
+
{
|
|
126
|
+
id: 'symbol-create-button',
|
|
127
|
+
className: 'pages__add-page fa fa-plus',
|
|
128
|
+
label: 'Create symbol from selection',
|
|
129
|
+
command: cmdPromptAddSymbol,
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
}, {
|
|
133
|
+
id: 'page-panel-btn',
|
|
134
|
+
className: 'page-panel-btn fa fa-fw fa-file',
|
|
135
|
+
attributes: { title: 'Pages', containerClassName: 'page-panel-container', },
|
|
136
|
+
command: cmdTogglePages,
|
|
137
|
+
buttons: [{
|
|
138
|
+
className: 'pages__add-page fa fa-file',
|
|
139
|
+
command: cmdAddPage,
|
|
140
|
+
text: '+',
|
|
141
|
+
}],
|
|
142
|
+
}, {
|
|
143
|
+
id: 'layer-manager-btn',
|
|
144
|
+
className: 'layer-manager-btn fa fa-fw fa-list',
|
|
145
|
+
attributes: { title: 'Layers', containerClassName: 'layer-manager-container', },
|
|
146
|
+
command: 'open-layers',
|
|
147
|
+
}, {
|
|
148
|
+
id: 'font-dialog-btn',
|
|
149
|
+
className: 'font-manager-btn fa fa-fw fa-font',
|
|
150
|
+
attributes: { title: 'Fonts' },
|
|
151
|
+
command: cmdOpenFonts,
|
|
152
|
+
}, {
|
|
153
|
+
id: 'settings-dialog-btn',
|
|
154
|
+
className: 'page-panel-btn fa fa-fw fa-cog',
|
|
155
|
+
attributes: { title: 'Settings' },
|
|
156
|
+
command: cmdOpenSettings,
|
|
157
|
+
}, {
|
|
158
|
+
id: 'spacer',
|
|
159
|
+
attributes: {},
|
|
160
|
+
className: 'project-bar-spacer',
|
|
161
|
+
}, {
|
|
162
|
+
id: 'logout-button',
|
|
163
|
+
className: 'page-panel-btn fa fa-fw fa-sign-out',
|
|
164
|
+
attributes: { title: 'Sign out' },
|
|
165
|
+
command: 'relogin',
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
[publishPlugin as any]: {
|
|
170
|
+
appendTo: 'options',
|
|
171
|
+
rootUrl,
|
|
172
|
+
id,
|
|
173
|
+
},
|
|
174
|
+
[pagePanelPlugin as any]: {
|
|
175
|
+
cmdOpenNewPageDialog,
|
|
176
|
+
cmdOpenSettings,
|
|
177
|
+
appendTo: '.page-panel-container',
|
|
178
|
+
},
|
|
179
|
+
[uiSuggestClasses as any]: {
|
|
180
|
+
enableCount: false,
|
|
181
|
+
enablePerformance: false,
|
|
182
|
+
},
|
|
183
|
+
[internalLinksPlugin as any]: {
|
|
184
|
+
// FIXME: warn the user about links in error
|
|
185
|
+
onError: (errors) => console.log('Links errors:', errors),
|
|
186
|
+
},
|
|
187
|
+
[codePlugin as any]: {
|
|
188
|
+
blockLabel: 'HTML',
|
|
189
|
+
blockCustomCode: {
|
|
190
|
+
category: catComponents,
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
[symbolsPlugin as any]: {
|
|
194
|
+
appendTo: '.symbols-list-container',
|
|
195
|
+
},
|
|
196
|
+
[fontsDialogPlugin as any]: {
|
|
197
|
+
api_key: 'AIzaSyAdJTYSLPlKz4w5Iqyy-JAF2o8uQKd1FKc',
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ////////////////////
|
|
204
|
+
// Initialize editor
|
|
205
|
+
// ////////////////////
|
|
206
|
+
// Expose grapes for plugins
|
|
207
|
+
// window['grapesjs'] = grapesjs
|
|
208
|
+
|
|
209
|
+
// Keep a ref to the editor singleton
|
|
210
|
+
let editor
|
|
211
|
+
|
|
212
|
+
export function initEditor(config: EditorConfig) {
|
|
213
|
+
if(editor) throw new Error('Grapesjs editor already created')
|
|
214
|
+
|
|
215
|
+
editor = grapesjs.init(config)
|
|
216
|
+
|
|
217
|
+
// customize the editor
|
|
218
|
+
;['text']
|
|
219
|
+
.forEach(id => editor.Blocks.get(id)?.set('category', 'Basics'))
|
|
220
|
+
;['image', 'video']
|
|
221
|
+
.forEach(id => editor.Blocks.get(id)?.set('category', 'Media'))
|
|
222
|
+
;['map']
|
|
223
|
+
.forEach(id => editor.Blocks.get(id)?.set('category', 'Components'))
|
|
224
|
+
editor.Blocks.render()
|
|
225
|
+
|
|
226
|
+
editor.Commands.add('gjs-open-import-webpage', openImport(editor, {
|
|
227
|
+
modalImportLabel: '',
|
|
228
|
+
modalImportContent: 'Paste a web page HTML code here.',
|
|
229
|
+
modalImportButton: 'Import',
|
|
230
|
+
modalImportTitle: 'Import from website',
|
|
231
|
+
}))
|
|
232
|
+
|
|
233
|
+
editor.on('load', () => {
|
|
234
|
+
// // move the options panel to the sidebar
|
|
235
|
+
// const optionsEl = editor.Panels.getPanel('options').view.el
|
|
236
|
+
// editor.Panels.getPanel('project-bar-panel').view.el
|
|
237
|
+
// .appendChild(options)
|
|
238
|
+
// options.style.width = 0
|
|
239
|
+
// options.style.position = 'static'
|
|
240
|
+
// remove blocks and layers buttons from the properties
|
|
241
|
+
setTimeout(() => {
|
|
242
|
+
editor.Panels.getPanel('views').buttons.remove('open-blocks')
|
|
243
|
+
editor.Panels.getPanel('views').buttons.remove('open-layers')
|
|
244
|
+
editor.Panels.getPanel('views').view.el.firstChild.style.justifyContent = 'initial' // align left
|
|
245
|
+
editor.Panels.getPanel('options').buttons.remove('export-template')
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
// use the style filter plugin
|
|
249
|
+
editor.StyleManager.addProperty('extra',{ extend: 'filter' })
|
|
250
|
+
})
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function getEditor() {
|
|
254
|
+
return editor
|
|
255
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { internalLinksPlugin } from './internal-links'
|
|
2
|
+
import grapesjs from 'grapesjs'
|
|
3
|
+
|
|
4
|
+
let editor
|
|
5
|
+
let nextId = 1
|
|
6
|
+
function getNewId() {
|
|
7
|
+
return (nextId++).toString()
|
|
8
|
+
}
|
|
9
|
+
const comp1 = {
|
|
10
|
+
tagName: 'a',
|
|
11
|
+
content: 'link to main',
|
|
12
|
+
attributes: { href: './index.html' },
|
|
13
|
+
}
|
|
14
|
+
const comp2 = {
|
|
15
|
+
tagName: 'a',
|
|
16
|
+
content: 'link to page 1',
|
|
17
|
+
attributes: { href: './page-1.html' },
|
|
18
|
+
}
|
|
19
|
+
const comp3 = {
|
|
20
|
+
tagName: 'a',
|
|
21
|
+
content: 'link to page 2',
|
|
22
|
+
attributes: { href: './page-2.html' },
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const pageMain = {
|
|
26
|
+
id: getNewId(),
|
|
27
|
+
type: 'main',
|
|
28
|
+
component: {
|
|
29
|
+
tagName: 'div',
|
|
30
|
+
components: [comp1, comp2],
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
const page1 = {
|
|
34
|
+
id: getNewId(),
|
|
35
|
+
name: 'Page 1',
|
|
36
|
+
component: {
|
|
37
|
+
tagName: 'div',
|
|
38
|
+
components: [comp3],
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
const page2 = {
|
|
42
|
+
id: getNewId(),
|
|
43
|
+
name: 'Page 2',
|
|
44
|
+
component: {
|
|
45
|
+
tagName: 'div',
|
|
46
|
+
components: [comp1, comp2, comp3],
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
editor = grapesjs.init({
|
|
51
|
+
headless: true,
|
|
52
|
+
storageManager: { autoload: false },
|
|
53
|
+
pageManager: {
|
|
54
|
+
pages: [pageMain, page1, page2],
|
|
55
|
+
},
|
|
56
|
+
plugins: [internalLinksPlugin],
|
|
57
|
+
})
|
|
58
|
+
expect(editor).not.toBeUndefined()
|
|
59
|
+
})
|
|
60
|
+
test('init', () => {
|
|
61
|
+
expect(editor.getComponents()).toHaveLength(pageMain.component.components.length)
|
|
62
|
+
})
|
|
63
|
+
test('rename page and rewrite links in current page', () => {
|
|
64
|
+
const page = editor.Pages.getSelected()
|
|
65
|
+
expect(page.id).toBe(pageMain.id)
|
|
66
|
+
expect(page.getName()).toBeUndefined()
|
|
67
|
+
const newName = 'Home page'
|
|
68
|
+
const newPath = './home-page.html'
|
|
69
|
+
// Do not work: page.setName(newName)
|
|
70
|
+
page.set('name', newName)
|
|
71
|
+
expect(page.getName()).toBe(newName)
|
|
72
|
+
const comp1Model = page.getMainComponent().components().find(c => c.attributes.content === comp1.content)
|
|
73
|
+
expect(comp1Model).toBeTruthy()
|
|
74
|
+
expect(comp1Model.attributes.attributes.href).toBe(newPath)
|
|
75
|
+
})
|
|
76
|
+
test('rename page and rewrite links in another page', () => {
|
|
77
|
+
const page = editor.Pages.get(page2.id)
|
|
78
|
+
expect(page.get('name')).toBe(page2.name)
|
|
79
|
+
const newName = 'Page 2 new-name'
|
|
80
|
+
const newPath = './page-2-new-name.html'
|
|
81
|
+
// Do not work: page.setName(newName)
|
|
82
|
+
page.set('name', newName)
|
|
83
|
+
expect(page.get('name')).toBe(newName)
|
|
84
|
+
const comp1Model = page.getMainComponent().components().find(c => c.attributes.content === comp3.content)
|
|
85
|
+
expect(comp1Model).toBeTruthy()
|
|
86
|
+
expect(comp1Model.attributes.attributes.href).toBe(newPath)
|
|
87
|
+
})
|