@silexlabs/silex 3.5.0 → 3.5.1-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.silex.js +0 -3
- package/dist/client/client/config.d.ts +8 -0
- package/dist/client/client/grapesjs/PublicationManager.d.ts +43 -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 +117 -19
- package/dist/client/index.html +2 -2
- package/dist/client/js/main.js +1974 -267
- 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 +16 -11
- package/src/css/_variables.scss +2 -1
- package/src/css/grapesjs-plugins.scss +31 -0
- package/src/css/grapesjs.scss +20 -0
- package/src/css/page-panel.scss +25 -6
- package/src/css/silex.scss +64 -5
- package/src/ts/client/config.ts +23 -0
- package/src/ts/client/grapesjs/PublicationManager.ts +159 -1
- 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 +163 -0
- package/src/ts/client/grapesjs/cms/collection-pages.ts +214 -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/css-props.ts +15 -16
- 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 +126 -11
- package/src/ts/client/grapesjs/sectors.ts +18 -10
- package/src/ts/client/grapesjs/semantic.ts +4 -0
- package/src/ts/client/grapesjs/settings.ts +252 -101
- package/src/ts/client/grapesjs/storage.ts +12 -3
- 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
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Expression,
|
|
3
|
+
Filter, Property, State, StateId, Token, getPersistantId, getStateVariableName, toExpression,
|
|
4
|
+
FIXED_TOKEN_ID,
|
|
5
|
+
UnariOperator,
|
|
6
|
+
getExpressionResultType,
|
|
7
|
+
BinaryOperator,
|
|
8
|
+
} from '@silexlabs/grapesjs-data-source'
|
|
9
|
+
import { Component } from 'grapesjs'
|
|
10
|
+
import { EleventyDataSourceId } from './DataSource'
|
|
11
|
+
|
|
12
|
+
export interface BinaryCondition {
|
|
13
|
+
operator: BinaryOperator,
|
|
14
|
+
expression: Expression,
|
|
15
|
+
expression2: Expression,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface UnaryCondition {
|
|
19
|
+
operator: UnariOperator,
|
|
20
|
+
expression: Expression,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type Condition = BinaryCondition | UnaryCondition
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Generate liquid instructions which echo the value of an expression
|
|
27
|
+
*/
|
|
28
|
+
export function echoBlock(component: Component, expression: Expression): string {
|
|
29
|
+
if (expression.length === 0) throw new Error('Expression is empty')
|
|
30
|
+
if (expression.length === 1 && expression[0].type === 'property' && expression[0].fieldId === FIXED_TOKEN_ID) {
|
|
31
|
+
return expression[0].options?.value as string ?? ''
|
|
32
|
+
}
|
|
33
|
+
const statements = getLiquidBlock(component, expression)
|
|
34
|
+
return `{% liquid
|
|
35
|
+
${statements
|
|
36
|
+
.map(({ liquid }) => liquid)
|
|
37
|
+
.join('\n\t')
|
|
38
|
+
}
|
|
39
|
+
echo ${statements[statements.length - 1].variableName}
|
|
40
|
+
%}`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Generate liquid instructions which echo the value of an expression, on 1 line
|
|
45
|
+
*/
|
|
46
|
+
export function echoBlock1line(component: Component, expression: Expression): string {
|
|
47
|
+
if (expression.length === 0) throw new Error('Expression is empty')
|
|
48
|
+
if (expression.length === 1 && expression[0].type === 'property' && expression[0].fieldId === FIXED_TOKEN_ID) {
|
|
49
|
+
return expression[0].options?.value as string ?? ''
|
|
50
|
+
}
|
|
51
|
+
const statements = getLiquidBlock(component, expression)
|
|
52
|
+
return `{% ${statements
|
|
53
|
+
.map(({ liquid }) => liquid)
|
|
54
|
+
.join(' %}{% ')
|
|
55
|
+
} %}{{ ${statements[statements.length - 1].variableName} }}`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Generate liquid instructions which define a variable for later use
|
|
60
|
+
* This is used for components states
|
|
61
|
+
*/
|
|
62
|
+
export function assignBlock(stateId: StateId, component: Component, expression: Expression): string {
|
|
63
|
+
if (expression.length === 0) throw new Error('Expression is empty')
|
|
64
|
+
const statements = getLiquidBlock(component, expression)
|
|
65
|
+
const persistantId = getPersistantId(component)
|
|
66
|
+
if (!persistantId) throw new Error('This component has no persistant ID')
|
|
67
|
+
return `{% liquid
|
|
68
|
+
${statements
|
|
69
|
+
.map(({ liquid }) => liquid)
|
|
70
|
+
.join('\n\t')
|
|
71
|
+
}
|
|
72
|
+
assign ${getStateVariableName(persistantId, stateId)} = ${statements[statements.length - 1].variableName}
|
|
73
|
+
%}`
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Generate liquid instructions which start and end a loop over the provided expression
|
|
78
|
+
* This is used for components states
|
|
79
|
+
*/
|
|
80
|
+
export function loopBlock(component: Component, expression: Expression): [start: string, end: string] {
|
|
81
|
+
if (expression.length === 0) throw new Error('Expression is empty')
|
|
82
|
+
// Check data to loop over
|
|
83
|
+
const field = getExpressionResultType(expression, component)
|
|
84
|
+
if (!field) throw new Error(`Expression ${expression.map(token => token.label).join(' -> ')} is invalid`)
|
|
85
|
+
if (field.kind !== 'list') throw new Error(`Provided property needs to be a list in order to loop, not a ${field.kind}`)
|
|
86
|
+
const statements = getLiquidBlock(component, expression)
|
|
87
|
+
const loopDataVariableName = statements[statements.length - 1].variableName
|
|
88
|
+
const persistantId = getPersistantId(component)
|
|
89
|
+
if (!persistantId) {
|
|
90
|
+
console.error('Component', component, 'has no persistant ID. Persistant ID is required to get component states.')
|
|
91
|
+
throw new Error('This component has no persistant ID')
|
|
92
|
+
}
|
|
93
|
+
return [`{% liquid
|
|
94
|
+
${statements
|
|
95
|
+
.map(({ liquid }) => liquid)
|
|
96
|
+
.join('\n\t')
|
|
97
|
+
}
|
|
98
|
+
%}
|
|
99
|
+
{% for ${getStateVariableName(persistantId, '__data')} in ${loopDataVariableName} %}
|
|
100
|
+
`, '{% endfor %}']
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Generate liquid instructions which define a variable for later use
|
|
105
|
+
* This is used for components states
|
|
106
|
+
*/
|
|
107
|
+
export function ifBlock(component: Component, condition: Condition): [start: string, end: string] {
|
|
108
|
+
// Check the first expression
|
|
109
|
+
if (condition.expression.length === 0) throw new Error('If block expression is empty')
|
|
110
|
+
|
|
111
|
+
// Check the operator
|
|
112
|
+
const unary = Object.values(UnariOperator).includes(condition.operator as UnariOperator) ? condition as UnaryCondition : null
|
|
113
|
+
const binary = Object.values(BinaryOperator).includes(condition.operator as BinaryOperator) ? condition as BinaryCondition : null
|
|
114
|
+
if (!unary && !binary) throw new Error(`If block operator is invalid: ${condition.operator}`)
|
|
115
|
+
|
|
116
|
+
// Check the second expression
|
|
117
|
+
if (binary && binary.expression2.length === 0) return ['', '']
|
|
118
|
+
|
|
119
|
+
// Get liquid for the first expression
|
|
120
|
+
const statements = getLiquidBlock(component, condition.expression)
|
|
121
|
+
const lastVariableName = statements[statements.length - 1].variableName
|
|
122
|
+
|
|
123
|
+
// Get liquid for the second
|
|
124
|
+
let lastVariableName2 = ''
|
|
125
|
+
if (binary) {
|
|
126
|
+
statements.push(...getLiquidBlock(component, binary.expression2))
|
|
127
|
+
lastVariableName2 = statements[statements.length - 1].variableName
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Get liquid for the whole if block
|
|
131
|
+
return [`{% liquid
|
|
132
|
+
${statements
|
|
133
|
+
.map(({ liquid }) => liquid)
|
|
134
|
+
.join('\n\t')
|
|
135
|
+
}
|
|
136
|
+
%}
|
|
137
|
+
{% if ${unary ? getUnaryOp(lastVariableName, unary.operator) : getBinaryOp(lastVariableName, lastVariableName2, binary!.operator)} %}
|
|
138
|
+
`, '{% endif %}']
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function getUnaryOp(variableName: string, operator: UnariOperator): string {
|
|
142
|
+
switch (operator) {
|
|
143
|
+
case UnariOperator.TRUTHY: return `${variableName} and ${variableName} != blank and ${variableName} != empty`
|
|
144
|
+
case UnariOperator.FALSY: return `not ${variableName}`
|
|
145
|
+
case UnariOperator.EMPTY_ARR: return `${variableName}.size == 0`
|
|
146
|
+
case UnariOperator.NOT_EMPTY_ARR: return `${variableName}.size > 0`
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function getBinaryOp(variableName: string, variableName2: string, operator: BinaryOperator): string {
|
|
151
|
+
switch (operator) {
|
|
152
|
+
case BinaryOperator.EQUAL: return `${variableName} == ${variableName2}`
|
|
153
|
+
case BinaryOperator.NOT_EQUAL: return `${variableName} != ${variableName2}`
|
|
154
|
+
case BinaryOperator.GREATER_THAN: return `${variableName} > ${variableName2}`
|
|
155
|
+
case BinaryOperator.LESS_THAN: return `${variableName} < ${variableName2}`
|
|
156
|
+
case BinaryOperator.GREATER_THAN_OR_EQUAL: return `${variableName} >= ${variableName2}`
|
|
157
|
+
case BinaryOperator.LESS_THAN_OR_EQUAL: return `${variableName} <= ${variableName2}`
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let numNextVar = 0
|
|
162
|
+
/**
|
|
163
|
+
* Pagination data has no filter and no states in it
|
|
164
|
+
*/
|
|
165
|
+
export function getPaginationData(expression: Property[]): string {
|
|
166
|
+
const statement = getLiquidStatementProperties(expression)
|
|
167
|
+
const firstToken = expression[0]
|
|
168
|
+
if (firstToken) {
|
|
169
|
+
if (!firstToken.dataSourceId || firstToken.dataSourceId === EleventyDataSourceId) {
|
|
170
|
+
return statement
|
|
171
|
+
}
|
|
172
|
+
return `${firstToken.dataSourceId}.${statement}`
|
|
173
|
+
} else {
|
|
174
|
+
return ''
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Convert an expression to liquid code
|
|
180
|
+
*/
|
|
181
|
+
export function getLiquidBlock(component: Component, expression: Expression): { variableName: string, liquid: string }[] {
|
|
182
|
+
if (expression.length === 0) return []
|
|
183
|
+
const result = [] as { variableName: string, liquid: string }[]
|
|
184
|
+
const firstToken = expression[0]
|
|
185
|
+
let lastVariableName = ''
|
|
186
|
+
if (firstToken.type === 'filter') throw new Error('Expression cannot start with a filter')
|
|
187
|
+
if (firstToken.type === 'property' && firstToken.dataSourceId && firstToken.dataSourceId !== 'eleventy') {
|
|
188
|
+
lastVariableName = firstToken.dataSourceId as string
|
|
189
|
+
}
|
|
190
|
+
const rest = [...expression]
|
|
191
|
+
while (rest.length) {
|
|
192
|
+
// Move all tokens until the first filter
|
|
193
|
+
const firstFilterIndex = rest.findIndex(token => token.type === 'filter')
|
|
194
|
+
const variableExpression = firstFilterIndex === -1 ? rest.splice(0) : rest.splice(0, firstFilterIndex)
|
|
195
|
+
// Add all the filters until a property again
|
|
196
|
+
const firstNonFilterIndex = rest.findIndex(token => token.type !== 'filter')
|
|
197
|
+
const filterExpression = firstNonFilterIndex === -1 ? rest.splice(0) : rest.splice(0, firstNonFilterIndex)
|
|
198
|
+
const variableName = getNextVariableName(component, numNextVar++)
|
|
199
|
+
const statement = getLiquidStatement(variableExpression.concat(filterExpression), variableName, lastVariableName)
|
|
200
|
+
lastVariableName = variableName
|
|
201
|
+
result.push({
|
|
202
|
+
variableName,
|
|
203
|
+
liquid: statement,
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
return result
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function getNextVariableName(component: Component, numNextVar: number): string {
|
|
210
|
+
return `var_${component.ccid}_${numNextVar}`
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Get the liquid assign statement for the expression
|
|
215
|
+
* The expression must
|
|
216
|
+
* - start with a property or state
|
|
217
|
+
* - once it has a filter it canot have a property again
|
|
218
|
+
* - state can only be the first token
|
|
219
|
+
*
|
|
220
|
+
* Example of return value: `countries.continent.countries | first.name`
|
|
221
|
+
*/
|
|
222
|
+
export function getLiquidStatement(expression: Expression, variableName: string, lastVariableName: string = ''): string {
|
|
223
|
+
if (expression.length === 0) throw new Error('Expression cannot be empty')
|
|
224
|
+
// Split expression in 2: properties and filters
|
|
225
|
+
const firstFilterIndex = expression.findIndex(token => token.type === 'filter')
|
|
226
|
+
if (firstFilterIndex === 0) throw new Error('Expression cannot start with a filter')
|
|
227
|
+
const properties = (firstFilterIndex < 0 ? expression : expression.slice(0, firstFilterIndex)) as (Property | State)[]
|
|
228
|
+
const filters = firstFilterIndex > 0 ? expression.slice(firstFilterIndex) as Filter[] : []
|
|
229
|
+
// Check that no properties or state come after filter
|
|
230
|
+
if (filters.find(token => token.type !== 'filter')) {
|
|
231
|
+
throw new Error('A filter cannot be followed by a property or state')
|
|
232
|
+
}
|
|
233
|
+
// Start with the assign statement
|
|
234
|
+
return `assign ${variableName} = ${lastVariableName ? `${lastVariableName}.` : ''
|
|
235
|
+
}${
|
|
236
|
+
// Add all the properties
|
|
237
|
+
getLiquidStatementProperties(properties)
|
|
238
|
+
}${
|
|
239
|
+
// Add all the filters
|
|
240
|
+
getLiquidStatementFilters(filters)
|
|
241
|
+
}`
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function getLiquidStatementProperties(properties: (Property | State)[]): string {
|
|
245
|
+
return properties.map((token, index) => {
|
|
246
|
+
switch (token.type) {
|
|
247
|
+
case 'state': {
|
|
248
|
+
if (index !== 0) throw new Error('State can only be the first token in an expression')
|
|
249
|
+
return getStateVariableName(token.componentId, token.storedStateId)
|
|
250
|
+
}
|
|
251
|
+
case 'property': {
|
|
252
|
+
if (token.fieldId === FIXED_TOKEN_ID) {
|
|
253
|
+
return `"${token.options?.value ?? ''}"`
|
|
254
|
+
}
|
|
255
|
+
return token.fieldId
|
|
256
|
+
}
|
|
257
|
+
default: {
|
|
258
|
+
throw new Error(`Only state or property can be used in an expression, got ${(token as Token).type}`)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
})
|
|
262
|
+
.join('.')
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function getLiquidStatementFilters(filters: Filter[]): string {
|
|
266
|
+
if (!filters.length) return ''
|
|
267
|
+
return ' | ' + filters.map(token => {
|
|
268
|
+
const options = token.options ? Object.entries(token.options)
|
|
269
|
+
// Order the filter's options by the order they appear in the filter's optionsKeys
|
|
270
|
+
.map(([key, value]) => ({
|
|
271
|
+
key,
|
|
272
|
+
value: value,
|
|
273
|
+
order: token.optionsKeys?.indexOf(key),
|
|
274
|
+
}))
|
|
275
|
+
.sort((a, b) => {
|
|
276
|
+
if (a.order === undefined && b.order === undefined) return 0
|
|
277
|
+
if (a.order === undefined) return 1
|
|
278
|
+
if (b.order === undefined) return -1
|
|
279
|
+
return a.order - b.order
|
|
280
|
+
})
|
|
281
|
+
// Convert the options to liquid
|
|
282
|
+
.map(({ key, value }) => handleFilterOption(token, key, value as string)) : []
|
|
283
|
+
return `${token.filterName ?? token.id}${options.length ? `: ${options.join(', ')}` : ''}`
|
|
284
|
+
})
|
|
285
|
+
.join(' | ')
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Quote a string for liquid
|
|
290
|
+
* Check that the string is not already quoted
|
|
291
|
+
* Escape existing quotes
|
|
292
|
+
*/
|
|
293
|
+
function quote(value: string): string {
|
|
294
|
+
if (value.startsWith('"') && value.endsWith('"')) return value
|
|
295
|
+
return `"${value.replace(/"/g, '\\"')}"`
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function handleFilterOption(filter: Filter, key: string, value: string): string {
|
|
299
|
+
try {
|
|
300
|
+
const expression = toExpression(value)
|
|
301
|
+
if (expression) {
|
|
302
|
+
const result = expression.map(token => {
|
|
303
|
+
switch (token.type) {
|
|
304
|
+
case 'property': {
|
|
305
|
+
if (token.fieldId === FIXED_TOKEN_ID) {
|
|
306
|
+
return `"${token.options?.value ?? ''}"`
|
|
307
|
+
}
|
|
308
|
+
return token.fieldId
|
|
309
|
+
}
|
|
310
|
+
case 'state': {
|
|
311
|
+
return getStateVariableName(token.componentId, token.storedStateId)
|
|
312
|
+
}
|
|
313
|
+
case 'filter': {
|
|
314
|
+
throw new Error('Filter cannot be used in a filter option')
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
})
|
|
318
|
+
.join('.')
|
|
319
|
+
return filter.quotedOptions?.includes(key) ? quote(result) : result
|
|
320
|
+
}
|
|
321
|
+
} catch {
|
|
322
|
+
// Ignore
|
|
323
|
+
}
|
|
324
|
+
return filter.quotedOptions?.includes(key) ? quote(value) : value
|
|
325
|
+
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { removeState, setState, toExpression } from '@silexlabs/grapesjs-data-source'
|
|
2
|
+
import { CMS_SETTINGS_SECTION_ID, EleventyPluginOptions, Silex11tyPluginWebsiteSettings } from './index'
|
|
3
|
+
import { html, TemplateResult } from 'lit-html'
|
|
4
|
+
import { Page, Editor, Component } from 'grapesjs'
|
|
5
|
+
import { WebsiteSettings } from '../../../types'
|
|
6
|
+
import { ClientEvent } from '../../events'
|
|
7
|
+
import { cmdAddSection } from '../settings'
|
|
8
|
+
import { createRef, ref } from 'lit-html/directives/ref.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Main function to add the settings to the page
|
|
12
|
+
*/
|
|
13
|
+
export default function(editor: Editor, opts: EleventyPluginOptions): void {
|
|
14
|
+
if (!opts.enable11ty) return // Do not add the settings if 11ty is disabled
|
|
15
|
+
|
|
16
|
+
editor.on(ClientEvent.SETTINGS_SAVE_START, handleSettingsSaveStart(editor))
|
|
17
|
+
editor.on(ClientEvent.SETTINGS_CLOSE, handleSettingsClose(editor))
|
|
18
|
+
|
|
19
|
+
editor.runCommand(cmdAddSection, {
|
|
20
|
+
section: {
|
|
21
|
+
id: CMS_SETTINGS_SECTION_ID,
|
|
22
|
+
label: 'CMS',
|
|
23
|
+
render: (settings: WebsiteSettings, page: Backbone.Model) => {
|
|
24
|
+
currentPage = page as Page
|
|
25
|
+
return renderSettingsSection(settings, editor, currentPage)
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
siteOrPage: 'page',
|
|
29
|
+
position: 'last',
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Store original settings for revert functionality
|
|
34
|
+
let originalSettings: Silex11tyPluginWebsiteSettings | null = null
|
|
35
|
+
let currentPage: Page | undefined
|
|
36
|
+
|
|
37
|
+
function handleSettingsSaveStart(editor: Editor) {
|
|
38
|
+
return (page: Page) => {
|
|
39
|
+
updateBodyStates(editor, page)
|
|
40
|
+
currentPage = null
|
|
41
|
+
originalSettings = null
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function handleSettingsClose(editor: Editor) {
|
|
46
|
+
return () => {
|
|
47
|
+
if (currentPage && originalSettings) {
|
|
48
|
+
const currentSettings = currentPage.get('settings') as Silex11tyPluginWebsiteSettings || {}
|
|
49
|
+
// Revert the whole settings object if it was changed
|
|
50
|
+
if (JSON.stringify(currentSettings) !== JSON.stringify(originalSettings)) {
|
|
51
|
+
currentPage.set('settings', originalSettings)
|
|
52
|
+
updateBodyStates(editor, currentPage)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
currentPage = null
|
|
56
|
+
originalSettings = null
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function updateTemporarySettings(editor: Editor, page: Page, pageDataEditor: HTMLInputElement | undefined): void {
|
|
61
|
+
if (!pageDataEditor) return
|
|
62
|
+
originalSettings = page.get('settings') as WebsiteSettings
|
|
63
|
+
const eleventyPageData = pageDataEditor.value || ''
|
|
64
|
+
page.set('settings', {
|
|
65
|
+
...originalSettings,
|
|
66
|
+
eleventyPageData,
|
|
67
|
+
} as Silex11tyPluginWebsiteSettings)
|
|
68
|
+
updateBodyStates(editor, page)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Set the state on the body component
|
|
73
|
+
* This is only useful to build the GraphQL query
|
|
74
|
+
*/
|
|
75
|
+
function stateOnBody(editor: Editor, value: string, name: string, body: Component): void {
|
|
76
|
+
if (value) {
|
|
77
|
+
const expression = toExpression(value)
|
|
78
|
+
if (expression) {
|
|
79
|
+
setState(body, name, {
|
|
80
|
+
label: name,
|
|
81
|
+
hidden: true,
|
|
82
|
+
expression,
|
|
83
|
+
})
|
|
84
|
+
} else {
|
|
85
|
+
removeState(body, name)
|
|
86
|
+
editor.runCommand('notifications:add', {
|
|
87
|
+
type: 'error',
|
|
88
|
+
message: `Invalid JSON for ${name}`,
|
|
89
|
+
group: 'Errors in your settings',
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
removeState(body, name)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Set the state on the body component
|
|
99
|
+
* This is only useful to build the GraphQL query
|
|
100
|
+
*/
|
|
101
|
+
const bodyStateKeys: (keyof Silex11tyPluginWebsiteSettings)[] = [
|
|
102
|
+
'eleventySeoTitle',
|
|
103
|
+
'eleventySeoDescription',
|
|
104
|
+
'eleventyFavicon',
|
|
105
|
+
'eleventyOGImage',
|
|
106
|
+
'eleventyOGTitle',
|
|
107
|
+
'eleventyOGDescription',
|
|
108
|
+
'eleventyPageData',
|
|
109
|
+
'eleventyPermalink',
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
function updateBodyStates(editor: Editor, page: Page): void {
|
|
113
|
+
if (page) {
|
|
114
|
+
const settings = page?.get('settings') as Silex11tyPluginWebsiteSettings | undefined
|
|
115
|
+
if (settings) {
|
|
116
|
+
const body = page.getMainComponent()
|
|
117
|
+
for (const key of bodyStateKeys) {
|
|
118
|
+
stateOnBody(editor, (settings as Record<string, string>)[key], key, body)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Render the settings form
|
|
126
|
+
*/
|
|
127
|
+
function renderSettingsSection(settings: Silex11tyPluginWebsiteSettings, editor: Editor, page: Page): TemplateResult {
|
|
128
|
+
const body = page.getMainComponent()
|
|
129
|
+
const pageDataEditor = createRef<HTMLInputElement>()
|
|
130
|
+
setTimeout(() => {
|
|
131
|
+
// Update all input fields with their corresponding values from settings
|
|
132
|
+
// This is needed because we change things without refreshing the DOM in JS
|
|
133
|
+
;(document.querySelectorAll(`
|
|
134
|
+
#settings-${CMS_SETTINGS_SECTION_ID} input,
|
|
135
|
+
#settings-${CMS_SETTINGS_SECTION_ID} state-editor
|
|
136
|
+
`) as NodeListOf<HTMLInputElement>)
|
|
137
|
+
.forEach((input: HTMLInputElement) => {
|
|
138
|
+
const value = settings[input.name]
|
|
139
|
+
if (input.type === 'checkbox') {
|
|
140
|
+
input.checked = !!value
|
|
141
|
+
} else if (typeof value !== 'undefined') {
|
|
142
|
+
input.value = value
|
|
143
|
+
} else {
|
|
144
|
+
input.value = ''
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
return html`
|
|
149
|
+
<style>
|
|
150
|
+
.silex-warning {
|
|
151
|
+
margin-top: 10px
|
|
152
|
+
padding: 10px
|
|
153
|
+
background-color: var(--ds-primary)
|
|
154
|
+
border-color: var(--ds-button-color)
|
|
155
|
+
color: #721c24
|
|
156
|
+
border: 1px solid transparent
|
|
157
|
+
border-radius: .25rem
|
|
158
|
+
}
|
|
159
|
+
</style>
|
|
160
|
+
<div id="settings-${CMS_SETTINGS_SECTION_ID}" class="silex-hideable silex-hidden">
|
|
161
|
+
<div class="gjs-sm-sector-title">Silex CMS - Page settings</div>
|
|
162
|
+
<div class="silex-help">
|
|
163
|
+
The "Silex CMS" feature integrates <a target="_blank" href="https://www.11ty.dev/">11ty</a> static site generator and your favorite headless CMS with Silex.
|
|
164
|
+
<br>Read the <a target="_blank" href="https://docs.silex.me/en/user/cms">documentation</a> to learn more.
|
|
165
|
+
</div>
|
|
166
|
+
<div class="silex-form__group col2">
|
|
167
|
+
<label class="silex-form__element">
|
|
168
|
+
<h3>Create pages from data</h3>
|
|
169
|
+
<p class="silex-help">Pagination allows you to iterate over data and create multiple webiste pages from a single page in Silex. </p>
|
|
170
|
+
<state-editor
|
|
171
|
+
${ref(pageDataEditor)}
|
|
172
|
+
id="eleventyPageData"
|
|
173
|
+
name="eleventyPageData"
|
|
174
|
+
value=${settings.eleventyPageData ?? ''}
|
|
175
|
+
.editor=${editor}
|
|
176
|
+
.selected=${body}
|
|
177
|
+
@change=${() => updateTemporarySettings(editor, page, pageDataEditor.value)}
|
|
178
|
+
no-states
|
|
179
|
+
no-filters
|
|
180
|
+
>
|
|
181
|
+
<label slot="label">Data</label>
|
|
182
|
+
</state-editor>
|
|
183
|
+
<label class="silex-form__element">Size
|
|
184
|
+
<input type="number" name="eleventyPageSize" .value=${settings.eleventyPageSize ?? 1}/>
|
|
185
|
+
</label>
|
|
186
|
+
<label class="silex-form__element">Reverse
|
|
187
|
+
<input type="checkbox" name="eleventyPageReverse" ?checked=${!!settings.eleventyPageReverse}/>
|
|
188
|
+
</label>
|
|
189
|
+
<state-editor
|
|
190
|
+
id="eleventyPermalink"
|
|
191
|
+
name="eleventyPermalink"
|
|
192
|
+
value=${settings.eleventyPermalink ?? ''}
|
|
193
|
+
.editor=${editor}
|
|
194
|
+
.selected=${body}
|
|
195
|
+
>
|
|
196
|
+
<label slot="label">Permalink</label>
|
|
197
|
+
</state-editor>
|
|
198
|
+
<label class="silex-form__element">Available languages
|
|
199
|
+
<p class="silex-help">Silex can duplicate this page for each language and generate a different URL for each language.</p>
|
|
200
|
+
<p class="silex-help">Provide a comma separated list of languages. For example: <code>en,fr</code>. An empty value will deactivate this feature.</p>
|
|
201
|
+
<input type="text" name="silexLanguagesList" .value=${settings.silexLanguagesList ?? ''}/>
|
|
202
|
+
</label>
|
|
203
|
+
</label>
|
|
204
|
+
<label class="silex-form__element">
|
|
205
|
+
<h3>Navigation Plugin</h3>
|
|
206
|
+
<p class="silex-help">This 11ty plugin enables infinite-depth hierarchical navigation in Eleventy projects. Supports breadcrumbs too! <a target="_blank" href="https://www.11ty.dev/docs/plugins/navigation/">Read more about the Navigation Plugin</a>.</p>
|
|
207
|
+
<label class="silex-form__element">Key
|
|
208
|
+
<input type="text" name="eleventyNavigationKey" .value=${settings.eleventyNavigationKey ?? ''}/>
|
|
209
|
+
</label>
|
|
210
|
+
<label class="silex-form__element">Title
|
|
211
|
+
<input type="text" name="eleventyNavigationTitle" .value=${settings.eleventyNavigationTitle ?? ''}/>
|
|
212
|
+
</label>
|
|
213
|
+
<label class="silex-form__element">Order
|
|
214
|
+
<input type="number" name="eleventyNavigationOrder" .value=${settings.eleventyNavigationOrder ?? ''}/>
|
|
215
|
+
</label>
|
|
216
|
+
<label class="silex-form__element">Parent
|
|
217
|
+
<input type="text" name="eleventyNavigationParent" .value=${settings.eleventyNavigationParent ?? ''}/>
|
|
218
|
+
</label>
|
|
219
|
+
<label class="silex-form__element">URL
|
|
220
|
+
<input type="text" name="eleventyNavigationUrl" .value=${settings.eleventyNavigationUrl ?? ''}/>
|
|
221
|
+
</label>
|
|
222
|
+
</label>
|
|
223
|
+
<label class="silex-form__element">
|
|
224
|
+
<h3>SEO</h3>
|
|
225
|
+
<state-editor
|
|
226
|
+
id="eleventySeoTitle"
|
|
227
|
+
name="eleventySeoTitle"
|
|
228
|
+
.value=${settings.eleventySeoTitle ?? ''}
|
|
229
|
+
.editor=${editor}
|
|
230
|
+
.selected=${body}
|
|
231
|
+
>
|
|
232
|
+
<label slot="label">Title</label>
|
|
233
|
+
</state-editor>
|
|
234
|
+
<state-editor
|
|
235
|
+
id="eleventySeoDescription"
|
|
236
|
+
name="eleventySeoDescription"
|
|
237
|
+
.value=${settings.eleventySeoDescription ?? ''}
|
|
238
|
+
.editor=${editor}
|
|
239
|
+
.selected=${body}
|
|
240
|
+
>
|
|
241
|
+
<label slot="label">Description</label>
|
|
242
|
+
</state-editor>
|
|
243
|
+
<state-editor
|
|
244
|
+
id="eleventyFavicon"
|
|
245
|
+
name="eleventyFavicon"
|
|
246
|
+
.value=${settings.eleventyFavicon ?? ''}
|
|
247
|
+
.editor=${editor}
|
|
248
|
+
.selected=${body}
|
|
249
|
+
>
|
|
250
|
+
<label slot="label">Favicon</label>
|
|
251
|
+
</state-editor>
|
|
252
|
+
</label>
|
|
253
|
+
<label class="silex-form__element">
|
|
254
|
+
<h3>Social</h3>
|
|
255
|
+
<state-editor
|
|
256
|
+
id="eleventyOGImage"
|
|
257
|
+
name="eleventyOGImage"
|
|
258
|
+
.value=${settings.eleventyOGImage ?? ''}
|
|
259
|
+
.editor=${editor}
|
|
260
|
+
.selected=${body}
|
|
261
|
+
>
|
|
262
|
+
<label slot="label">OG Image</label>
|
|
263
|
+
</state-editor>
|
|
264
|
+
<state-editor
|
|
265
|
+
id="eleventyOGTitle"
|
|
266
|
+
name="eleventyOGTitle"
|
|
267
|
+
.value=${settings.eleventyOGTitle ?? ''}
|
|
268
|
+
.editor=${editor}
|
|
269
|
+
.selected=${body}
|
|
270
|
+
>
|
|
271
|
+
<label slot="label">OG Title</label>
|
|
272
|
+
</state-editor>
|
|
273
|
+
<state-editor
|
|
274
|
+
id="eleventyOGDescription"
|
|
275
|
+
name="eleventyOGDescription"
|
|
276
|
+
.value=${settings.eleventyOGDescription ?? ''}
|
|
277
|
+
.editor=${editor}
|
|
278
|
+
.selected=${body}
|
|
279
|
+
>
|
|
280
|
+
<label slot="label">OG Description</label>
|
|
281
|
+
</state-editor>
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
`
|
|
285
|
+
}
|