@silexlabs/silex 3.0.0-alpha.152 → 3.0.0-alpha.154

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.
@@ -58,6 +58,132 @@
58
58
  margin-left: 10px;
59
59
  }
60
60
 
61
+ // publication-ui
62
+ #publish-dialog {
63
+ header {
64
+ display: flex;
65
+ align-items: center;
66
+ justify-content: space-between;
67
+ padding-top: 10px;
68
+ padding-bottom: 10px;
69
+ font-size: 0.95rem;
70
+ transition: 200ms;
71
+
72
+ svg {
73
+ width: 1.4rem;
74
+ vertical-align: middle;
75
+ margin-right: 5px;
76
+ }
77
+
78
+ button {
79
+ border: solid 2px #484848;
80
+ border-radius: 50px;
81
+ padding: 4px 9px;
82
+ background-color: #444;
83
+ color: $secondaryColor;
84
+ }
85
+
86
+ .connector {
87
+ color: $quaternaryColor;
88
+ }
89
+ }
90
+
91
+ main {
92
+ max-height: none;
93
+
94
+ .buttons {
95
+ display: grid;
96
+ grid-template-columns: 1fr;
97
+ grid-template-rows: auto;
98
+ grid-row-gap: 0.5rem;
99
+ transition: 200ms;
100
+ }
101
+
102
+ svg {
103
+ height: 0.9rem;
104
+ margin-left: 5px;
105
+ }
106
+
107
+ h3 {
108
+ margin: 0;
109
+ }
110
+
111
+ p {
112
+ margin-top: 0;
113
+ }
114
+
115
+ details {
116
+ border-radius: 5px;
117
+ border: solid #222 2px;
118
+ background-color: #222;
119
+ margin: 10px 0;
120
+ padding: 10px;
121
+ word-break: break-word;
122
+ overflow-y: auto;
123
+ max-height: 200px;
124
+ position: relative;
125
+
126
+ pre {
127
+ word-break: break-word;
128
+ }
129
+
130
+ summary {
131
+ font-size: 0.95rem;
132
+ opacity: 0.7;
133
+ }
134
+
135
+ .logs {
136
+ font-family: monospace;
137
+
138
+ * {
139
+ margin: 0;
140
+ padding-top: 3px;
141
+ padding-bottom: 3px;
142
+ border-radius: 5px;
143
+ list-style: '-';
144
+
145
+ &:hover {
146
+ transition: 200ms;
147
+ background-color: rgba(255, 255, 255, 0.05);
148
+ }
149
+ }
150
+ }
151
+
152
+ &:hover {
153
+ transition: 200ms;
154
+ border-color: $lighterPrimaryColor;
155
+ background-color: $darkerPrimaryColor;
156
+ }
157
+
158
+ * {
159
+ transition: 200ms;
160
+ }
161
+ }
162
+
163
+ details[open] summary {
164
+ margin-bottom: 10px;
165
+ }
166
+
167
+ .silex-button {
168
+ padding: 10px;
169
+ width: 100%;
170
+ transition: 200ms;
171
+
172
+ &:hover {
173
+ filter: brightness(1.2);
174
+ }
175
+
176
+ &:active {
177
+ filter: brightness(0.8);
178
+ }
179
+ }
180
+
181
+ .silex-button--primary {
182
+ background-color: $tertiaryColor;
183
+ }
184
+ }
185
+ }
186
+
61
187
  // Adapting the layout of the suggestion list
62
188
  .gjs-suggest {
63
189
  max-height: 30vh;
@@ -141,7 +141,14 @@ export async function api<ReqQuery, ReqBody, ResBody>(route: ApiRoute | string,
141
141
  })
142
142
 
143
143
  if (!response.ok) {
144
- const json = await response.json()
144
+ let json
145
+ try {
146
+ json = await response.json()
147
+ } catch (err) {
148
+ // If the response is not JSON, throw a generic error
149
+ // This is the case when Silex backend has an uncatched error, e.g. expressjs 413 PayloadTooLargeError: request entity too large
150
+ throw new ApiError(response.statusText, response.status)
151
+ }
145
152
  throw new ApiError(json.message, response.status)
146
153
  }
147
154
 
@@ -115,7 +115,7 @@ export class ClientConfig extends Config {
115
115
  * @see https://fontlay.com/
116
116
  * @default Google Fonts
117
117
  */
118
- fontsServerUrl = 'https://www.googleapis.com'
118
+ fontsServerUrl = 'https://fonts.googleapis.com'
119
119
 
120
120
  /**
121
121
  * Init GrapesJS config which depend on the config file properties
@@ -298,6 +298,11 @@ export class PublicationManager {
298
298
  data: data as ApiPublicationPublishBody,
299
299
  options: this.settings.options,
300
300
  })
301
+
302
+ // in gitlab pages situation, getUrl from gitlabHostingConnector gives publication url obtained with Gitlab API pages
303
+ console.info('Gitlab url: ', url)
304
+ // could be used in an future UI
305
+
301
306
  this.job = job
302
307
  this.status = jobStatusToPublicationStatus(this.job.status)
303
308
  this.trackProgress()
@@ -21,6 +21,8 @@ import { unsafeHTML } from 'lit-html/directives/unsafe-html.js'
21
21
  import { cmdPublicationLogin, cmdPublicationLogout, cmdPublicationStart, PublicationStatus, PublishableEditor } from './PublicationManager'
22
22
  import { ConnectorData, ConnectorType, PublicationJobData, PublicationSettings } from '../../types'
23
23
  import { connectorList } from '../api'
24
+ import { defaultKms } from './keymaps'
25
+ import { titleCase } from '../utils'
24
26
 
25
27
  /**
26
28
  * @fileoverview define the publication dialog
@@ -36,6 +38,12 @@ import { connectorList } from '../api'
36
38
  // Constants
37
39
  export const cmdPublish = 'publish-open-dialog'
38
40
 
41
+ // **
42
+ // Semantic Icons
43
+ const svgConnector = '<svg width="100%" height="100%" viewBox="0 0 44 23" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;"><g><g><path d="M2,11.427c0,0 3.846,-2.655 7.376,0c3.531,2.656 7.45,0 7.45,0" style="fill:none;stroke:#a291ff;stroke-width:4px;"/></g><path d="M32.165,6.424l9.156,0" style="fill:none;stroke:#a291ff;stroke-width:4px;"/><path d="M32.165,2l0,18.854" style="fill:none;stroke:#a291ff;stroke-width:4px;"/><path d="M32.165,16.43l9.156,0" style="fill:none;stroke:#a291ff;stroke-width:4px;"/><path d="M26.253,20.854c-5.203,0 -9.427,-4.224 -9.427,-9.427c-0,-5.203 4.224,-9.427 9.427,-9.427l5.912,-0l0,18.854l-5.912,0Z" style="fill:none;stroke:#a291ff;stroke-width:4px;"/></g></svg>'
44
+ const svgSuccess = '<svg viewBox="0 0 40 28" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;"><path d="M3,12.969l11.868,11.869l21.838,-21.838" style="fill:none;stroke:#37bf76;stroke-width:6px;"/></svg>'
45
+ const svgError = '<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;"><g><path d="M3,28.915l25.915,-25.915" style="fill:none;stroke:#cf4443;stroke-width:6px;"/><path d="M28.915,28.915l-25.915,-25.915" style="fill:none;stroke:#cf4443;stroke-width:6px;"/></g></svg>'
46
+
39
47
  // **
40
48
  // Types
41
49
  export type PublicationDialogOptions = {
@@ -132,7 +140,7 @@ export class PublicationUi {
132
140
  id: 'publish-button',
133
141
  className: 'silex-button--size publish-button',
134
142
  command: cmdPublish,
135
- attributes: { title: 'Publish' },
143
+ attributes: { title: `Publish [${titleCase(defaultKms.kmOpenPublish.keys, '+')}]` },
136
144
  label: '<span class="fa-solid fa-upload"></span><span class="silex-button--small">Publish</span>',
137
145
  })
138
146
  })
@@ -152,6 +160,8 @@ export class PublicationUi {
152
160
  `, this.el)
153
161
  if (this.isOpen) {
154
162
  this.el.classList.remove('silex-dialog-hide')
163
+ const primaryBtn = this.el.querySelector('.silex-button--primary') as HTMLElement
164
+ if (primaryBtn) primaryBtn.focus()
155
165
  } else {
156
166
  this.el.classList.add('silex-dialog-hide')
157
167
  }
@@ -162,26 +172,39 @@ export class PublicationUi {
162
172
  async renderOpenDialog(job: PublicationJobData, status: PublicationStatus): Promise<TemplateResult> {
163
173
  return html`
164
174
  <header>
165
- <h3>Publication</h3>
175
+ <span>${unsafeHTML(svgConnector)} Connected to <span class="connector">${this.settings.connector.displayName}</span></span>
176
+ ${this.settings.connector.disableLogout ? '' : html`
177
+ <button
178
+ class="silex-button silex-button--secondary"
179
+ id="publish-button--secondary"
180
+ @click=${() => this.editor.Commands.run(cmdPublicationLogout)}
181
+ >Disconnect
182
+ </button>`}
166
183
  </header>
167
184
  <main>
168
185
  ${this.isPending(status) ? html`
169
- <p>Publication in progress</p>
186
+ <p>Publication in progress...</p>
170
187
  ` : ''}
171
188
  ${this.isReady(status) ? html`
172
- <p>You are connected to ${this.settings.connector.displayName}</p><p>Click on the publish button to publish your website</p>
189
+ <p>Click on the button below to publish your website.</p>
173
190
  ${this.settings.options && Object.entries(this.settings.options).length && html`<p>Publication options: <ul>${ Object.entries(this.settings.options).map(([key, value]) => html`<li>${key}: ${value}</li>`) }</ul></p>`}
174
191
  ` : ''}
175
192
  ${this.isSuccess(status) ? html`
176
- <p>Publication success</p>
193
+ <h3 class="status">Publication success ${unsafeHTML(svgSuccess)}</h3>
177
194
  ${this.settings.options?.websiteUrl ? html`<p><a href="${this.settings.options.websiteUrl}" target="_blank">Click here to view the published website</a></p>` : ''}
178
195
  ` : ''}
179
196
  ${this.isError(status) || this.isLoggedOut(status) ? html`
180
- <p>Publication error</p>
181
- <div>${unsafeHTML(this.errorMessage)}</div>
197
+ <h3 class="status">Publication error ${unsafeHTML(svgError)}</h3>
198
+ <details open>
199
+ <summary>Details</summary>
200
+ ${unsafeHTML(this.errorMessage)}
201
+ </details>
182
202
  ` : ''}
183
203
  ${job?.message ? html`
184
- <p>${unsafeHTML(job.message)}</p>
204
+ <details open>
205
+ <summary>Details</summary>
206
+ ${unsafeHTML(job.message)}
207
+ </details>
185
208
  ` : ''}
186
209
  ${this.isPending(status) ? html`
187
210
  <progress
@@ -190,17 +213,13 @@ export class PublicationUi {
190
213
  ></progress>
191
214
  ` : ''}
192
215
  ${job?.logs?.length && job.logs[0].length ? html`
193
- <br><details>
216
+ <details>
194
217
  <summary>Logs</summary>
195
- <pre style="
196
- max-width: 100%;
197
- font-size: x-small;
198
- " >${unsafeHTML(cleanupLogEntry(job.logs))}
199
- </pre>
218
+ <div class="logs">${unsafeHTML(cleanupLogEntry(job.logs))}</div>
200
219
  </details>
201
220
  ` : ''}
202
221
  ${job?.errors?.length && job.errors[0].length ? html`
203
- <br><details>
222
+ <details>
204
223
  <summary>Errors</summary>
205
224
  <pre style="
206
225
  max-width: 100%;
@@ -210,8 +229,6 @@ export class PublicationUi {
210
229
  </pre>
211
230
  </details>
212
231
  ` : ''}
213
- </main>
214
- <footer>
215
232
  ${this.isPending(status) || this.isLoggedOut(status) ? '' : html`
216
233
  <button
217
234
  class="silex-button silex-button--primary"
@@ -219,19 +236,16 @@ export class PublicationUi {
219
236
  @click=${() => this.editor.Commands.run(cmdPublicationStart)}
220
237
  >Publish</button>
221
238
  `}
239
+ </main>
240
+ <footer>
222
241
  ${this.isLoggedOut(status) ? html`
223
242
  <button
224
243
  class="silex-button silex-button--primary"
225
244
  id="publish-button--primary"
226
245
  @click=${() => this.editor.Commands.run(cmdPublicationLogin, this.settings.connector)}
227
246
  >Connect</button>
228
- `: this.settings.connector.disableLogout ? '' : html`
229
- <button
230
- class="silex-button silex-button--secondary"
231
- id="publish-button--secondary"
232
- @click=${() => this.editor.Commands.run(cmdPublicationLogout)}
233
- >Disconnect</button>
234
- `}
247
+ `: ''}
248
+ <a href="https://docs.silex.me/en/user/publish" target="_blank">Help</a>
235
249
  <button
236
250
  class="silex-button silex-button--secondary"
237
251
  id="publish-button--secondary"
@@ -256,20 +270,23 @@ export class PublicationUi {
256
270
  //}
257
271
  return html`
258
272
  <main>
259
- <p>You need to connect a hosting connector to publish your website</p>
273
+ <p>You need to select a hosting connector to publish your website.</p>
260
274
  ${this.isError(status) || this.isLoggedOut(status) ? html`
261
275
  <p>Login error</p>
262
276
  <div>${unsafeHTML(this.errorMessage)}</div>
263
277
  ` : ''}
264
- ${hostingConnectors.map(connector => html`
278
+ <div class="buttons">
279
+ ${hostingConnectors.map(connector => html`
265
280
  <button
266
- class="silex-button silex-button--primary"
281
+ class="silex-button"
267
282
  id="publish-button--primary"
268
283
  @click=${() => this.editor.Commands.run(cmdPublicationLogin, connector)}
269
284
  >${connector.displayName}</button>
270
285
  `)}
286
+ </div>
271
287
  </main>
272
288
  <footer>
289
+ <a href="https://docs.silex.me/en/user/publish" target="_blank">Help</a>
273
290
  <button
274
291
  class="silex-button silex-button--secondary"
275
292
  id="publish-button--secondary"
@@ -288,6 +305,7 @@ export class PublicationUi {
288
305
  <p>Something went wrong: ${err.message}</p>
289
306
  </main>
290
307
  <footer>
308
+ <a href="https://docs.silex.me/en/user/publish" target="_blank">Help</a>
291
309
  <button
292
310
  class="silex-button silex-button--secondary"
293
311
  id="publish-button--secondary"
@@ -57,11 +57,13 @@ import { blocksPlugin } from './blocks'
57
57
  import { semanticPlugin } from './semantic'
58
58
  import { orderedList, richTextPlugin, unorderedList } from './rich-text'
59
59
  import { internalLinksPlugin } from './internal-links'
60
+ import { defaultKms, keymapsPlugin } from './keymaps'
60
61
  import publicationManagerPlugin, { PublicationManagerOptions } from './PublicationManager'
61
62
  import ViewButtons from './view-buttons'
62
63
  import { storagePlugin } from './storage'
63
64
  import { API_PATH, API_WEBSITE_ASSETS_WRITE, API_WEBSITE_PATH } from '../../constants'
64
65
  import { ClientConfig } from '../config'
66
+ import { titleCase } from '../utils'
65
67
 
66
68
  const plugins = [
67
69
  {name: './project-bar', value: projectBarPlugin}, // has to be before panels and dialogs
@@ -79,6 +81,7 @@ const plugins = [
79
81
  {name: 'grapesjs-plugin-forms', value: formPlugin},
80
82
  {name: 'grapesjs-custom-code', value: codePlugin},
81
83
  {name: './internal-links', value: internalLinksPlugin},
84
+ {name: './keymaps', value: keymapsPlugin},
82
85
  {name: '@silexlabs/grapesjs-ui-suggest-classes', value: uiSuggestClasses},
83
86
  {name: '@silexlabs/grapesjs-filter-styles', value: filterStyles},
84
87
  {name: './symbolDialogs', value: symbolDialogsPlugin},
@@ -111,6 +114,12 @@ const QUATERNARY_COLOR = '#A291FF'
111
114
  const DARKER_PRIMARY_COLOR = '#363636'
112
115
  const LIGHTER_PRIMARY_COLOR = '#575757'
113
116
 
117
+ // Commands
118
+ export const cmdToggleLayers = 'open-layers'
119
+ export const cmdToggleBlocks = 'open-blocks'
120
+ export const cmdToggleSymbols = 'open-symbols'
121
+ export const cmdToggleNotifications = 'open-notifications'
122
+
114
123
  // ////////////////////
115
124
  // Config
116
125
  // ////////////////////
@@ -195,13 +204,15 @@ export function getEditorConfig(config: ClientConfig): EditorConfig {
195
204
  }, {
196
205
  id: 'block-manager-btn',
197
206
  className: 'block-manager-btn fa fa-fw fa-plus',
198
- attributes: { title: 'Blocks', containerClassName: 'block-manager-container', },
199
- command: 'open-blocks',
207
+ name: 'Blocks',
208
+ attributes: { title: `Blocks [${titleCase(defaultKms.kmBlocks.keys, '+')}]`, containerClassName: 'block-manager-container', },
209
+ command: cmdToggleBlocks,
200
210
  }, {
201
211
  id: 'symbols-btn',
202
212
  className: 'symbols-btn fa-regular fa-gem',
203
- attributes: { title: 'Symbols', containerClassName: 'symbols-list-container', },
204
- command: 'open-symbols',
213
+ name: 'Symbols',
214
+ attributes: { title: `Symbols [${titleCase(defaultKms.kmSymbols.keys, '+')}]`, containerClassName: 'symbols-list-container', },
215
+ command: cmdToggleSymbols,
205
216
  buttons: [
206
217
  {
207
218
  id: 'symbol-create-button',
@@ -213,7 +224,8 @@ export function getEditorConfig(config: ClientConfig): EditorConfig {
213
224
  }, {
214
225
  id: 'page-panel-btn',
215
226
  className: 'page-panel-btn fa fa-fw fa-file',
216
- attributes: { title: 'Pages', containerClassName: 'page-panel-container', },
227
+ name: 'Pages',
228
+ attributes: { title: `Pages [${titleCase(defaultKms.kmPages.keys, '+')}]`, containerClassName: 'page-panel-container', },
217
229
  command: cmdTogglePages,
218
230
  buttons: [{
219
231
  className: 'gjs-pn-btn',
@@ -223,19 +235,22 @@ export function getEditorConfig(config: ClientConfig): EditorConfig {
223
235
  }, {
224
236
  id: 'layer-manager-btn',
225
237
  className: 'layer-manager-btn fa-solid fa-layer-group',
226
- attributes: { title: 'Layers', containerClassName: 'layer-manager-container', },
227
- command: 'open-layers',
238
+ name: 'Layers',
239
+ attributes: { title: `Layers [${titleCase(defaultKms.kmLayers.keys, '+')}]`, containerClassName: 'layer-manager-container', },
240
+ command: cmdToggleLayers,
228
241
  }, {
229
242
  id: 'font-dialog-btn',
230
243
  className: 'font-manager-btn fa-solid fa-font',
231
- attributes: { title: 'Fonts' },
244
+ name: 'Fonts',
245
+ attributes: { title: `Fonts [${titleCase(defaultKms.kmOpenFonts.keys, '+')}]` },
232
246
  command: () => {
233
247
  editor.runCommand(cmdOpenFonts)
234
248
  },
235
249
  }, {
236
250
  id: 'settings-dialog-btn',
237
251
  className: 'page-panel-btn fa-solid fa-gears',
238
- attributes: { title: 'Settings' },
252
+ name: 'Settings',
253
+ attributes: { title: `Settings [${titleCase(defaultKms.kmOpenSettings.keys, '+')}]` },
239
254
  command: cmdOpenSettings,
240
255
  }, {
241
256
  id: 'spacer',
@@ -244,8 +259,9 @@ export function getEditorConfig(config: ClientConfig): EditorConfig {
244
259
  }, {
245
260
  id: 'notifications-btn',
246
261
  className: 'notifications-btn fa-regular fa-bell',
247
- attributes: { title: 'Notifications', containerClassName: 'notifications-container', },
248
- command: 'open-notifications',
262
+ name: 'Notifications',
263
+ attributes: { title: `Notifications [${titleCase(defaultKms.kmNotifications.keys, '+')}]`, containerClassName: 'notifications-container', },
264
+ command: cmdToggleNotifications,
249
265
  buttons: [{
250
266
  className: 'gjs-pn-btn',
251
267
  command: 'notifications:clear',
@@ -289,6 +305,13 @@ export function getEditorConfig(config: ClientConfig): EditorConfig {
289
305
  [filterStyles]: {
290
306
  appendBefore: '.gjs-sm-sectors',
291
307
  },
308
+ [internalLinksPlugin.toString()]: {
309
+ // FIXME: warn the user about links in error
310
+ onError: (errors) => console.warn('Links errors:', errors),
311
+ },
312
+ [keymapsPlugin.toString()]: {
313
+ disableKeymaps: false,
314
+ },
292
315
  [codePlugin.toString()]: {
293
316
  blockLabel: 'HTML',
294
317
  blockCustomCode: {
@@ -384,8 +407,8 @@ export async function initEditor(config: EditorConfig) {
384
407
 
385
408
  // Remove blocks and layers buttons from the properties
386
409
  // This is because in Silex they are on the left
387
- views.buttons.remove('open-blocks')
388
- views.buttons.remove('open-layers')
410
+ views.buttons.remove(cmdToggleBlocks)
411
+ views.buttons.remove(cmdToggleLayers)
389
412
 
390
413
  // Remove useless buttons
391
414
  editor.Panels.getPanel('options').buttons.remove('export-template')
@@ -0,0 +1,155 @@
1
+ import {Editor, PluginOptions} from 'grapesjs'
2
+ import {cmdPublish} from './PublicationUi'
3
+ import {cmdOpenFonts} from '@silexlabs/grapesjs-fonts'
4
+ import {cmdToggleBlocks, cmdToggleLayers, cmdToggleNotifications, cmdToggleSymbols} from './index'
5
+ import {cmdTogglePages} from './page-panel'
6
+ import {cmdOpenSettings} from './settings'
7
+ import {isTextOrInputField, selectBody} from '../utils'
8
+ import {PublishableEditor} from './PublicationManager'
9
+
10
+ // Utility functions
11
+
12
+ function getPanelCommandIds (): string[] {
13
+ return [
14
+ cmdToggleBlocks,
15
+ cmdToggleLayers,
16
+ cmdToggleNotifications,
17
+ cmdToggleSymbols,
18
+ cmdTogglePages,
19
+ cmdOpenSettings,
20
+ cmdOpenFonts
21
+ ]
22
+ }
23
+
24
+ function toggleCommand (editor: Editor, name: string): void {
25
+ const cmd = editor.Commands
26
+
27
+ if (!cmd.isActive(name)) {
28
+ resetPanel(editor)
29
+ cmd.run(name)
30
+ } else {
31
+ cmd.stop(name)
32
+ }
33
+ }
34
+
35
+ function resetPanel(editor: Editor): void {
36
+ getPanelCommandIds().forEach(p => editor.Commands.stop(p))
37
+ }
38
+
39
+ /**
40
+ * Escapes the current context in this order : modal, Publish dialog, left panel.
41
+ * If none of these are open, it selects the body.
42
+ * @param editor The editor.
43
+ */
44
+ function escapeContext(editor: Editor): void {
45
+ const publishDialog = (editor as PublishableEditor).PublicationManager.dialog
46
+
47
+ if (editor.Modal.isOpen()) {
48
+ editor.Modal.close()
49
+ } else if (publishDialog && publishDialog.isOpen) {
50
+ publishDialog.closeDialog()
51
+ } else if (getPanelCommandIds().some(cmd => editor.Commands.isActive(cmd))) {
52
+ resetPanel(editor)
53
+ } else {
54
+ selectBody(editor)
55
+ }
56
+ }
57
+
58
+ // Constants
59
+
60
+ export const cmdSelectBody = 'select-body'
61
+ export let prefixKey = 'shift'
62
+
63
+ export const defaultKms = {
64
+ kmOpenSettings: {
65
+ id: 'general:open-settings',
66
+ keys: 'alt+s',
67
+ handler: editor => toggleCommand(editor, cmdOpenSettings)
68
+ },
69
+ kmOpenPublish: {
70
+ id: 'general:open-publish',
71
+ keys: 'alt+p',
72
+ handler: editor => toggleCommand(editor, cmdPublish)
73
+ },
74
+ kmOpenFonts: {
75
+ id: 'general:open-fonts',
76
+ keys: 'alt+f',
77
+ handler: editor => toggleCommand(editor, cmdOpenFonts)
78
+ },
79
+ kmPreviewMode: {
80
+ id: 'general:preview-mode',
81
+ keys: 'tab',
82
+ handler: editor => toggleCommand(editor, 'preview'),
83
+ options: {prevent: true}
84
+ },
85
+ kmLayers: {
86
+ id: 'panels:layers',
87
+ keys: prefixKey + '+l',
88
+ handler: editor => toggleCommand(editor, cmdToggleLayers)
89
+ },
90
+ kmBlocks: {
91
+ id: 'panels:blocks',
92
+ keys: prefixKey + '+a',
93
+ handler: editor => toggleCommand(editor, cmdToggleBlocks)
94
+ },
95
+ kmNotifications: {
96
+ id: 'panels:notifications',
97
+ keys: prefixKey + '+n',
98
+ handler: editor => toggleCommand(editor, cmdToggleNotifications)
99
+ },
100
+ kmPages: {
101
+ id: 'panels:pages',
102
+ keys: prefixKey + '+p',
103
+ handler: editor => toggleCommand(editor, cmdTogglePages)
104
+ },
105
+ kmSymbols: {
106
+ id: 'panels:symbols',
107
+ keys: prefixKey + '+s',
108
+ handler: editor => toggleCommand(editor, cmdToggleSymbols)
109
+ },
110
+ kmClosePanel: {
111
+ id: 'panels:close-panel',
112
+ keys: 'escape',
113
+ handler: escapeContext
114
+ },
115
+ kmSelectBody: {
116
+ id: 'workflow:select-body',
117
+ keys: prefixKey + '+b',
118
+ handler: cmdSelectBody
119
+ }
120
+ }
121
+
122
+ // Main part
123
+
124
+ export function keymapsPlugin(editor: Editor, opts: PluginOptions): void {
125
+ // Commands
126
+ editor.Commands.add(cmdSelectBody, selectBody)
127
+
128
+ if (opts.disableKeymaps) return
129
+ if (opts.prefixKey) prefixKey = opts.prefixKey
130
+
131
+ const km = editor.Keymaps
132
+
133
+ // Default keymaps
134
+ for (const keymap in defaultKms) {
135
+ km.add(defaultKms[keymap].id, defaultKms[keymap].keys, defaultKms[keymap].handler, defaultKms[keymap].options)
136
+ }
137
+
138
+ // Handling the Escape keymap during text edition
139
+ document.addEventListener('keydown', event => {
140
+ if (event.key.toLowerCase() === defaultKms.kmClosePanel.keys) {
141
+ const target = event.target as HTMLElement | null
142
+ const rte = editor.getEditing()
143
+
144
+ if (rte) { // If in Rich Text edition...
145
+ // TODO: Close the Rich Text edition and un-focus the text field
146
+ } else if (target) { // If target exists...
147
+ if (target.tagName === 'INPUT' && target.getAttribute('type') === 'submit') { // If it's a submit button...
148
+ escapeContext(editor)
149
+ } else if (isTextOrInputField(target)) { // If it's a text field...
150
+ target.blur()
151
+ }
152
+ }
153
+ }
154
+ })
155
+ }
@@ -25,6 +25,7 @@ export interface PanelObject {
25
25
  command: string | ((editor: Editor) => void)
26
26
  text: string
27
27
  className: string
28
+ name?: string
28
29
  attributes: {
29
30
  title?: string
30
31
  containerClassName?: string
@@ -41,13 +42,13 @@ export const projectBarPlugin = (editor, opts) => {
41
42
  // create the panels container for all panels in grapesjs
42
43
  const containerPanel = editor.Panels.addPanel({
43
44
  id: containerPanelId,
44
- visible : false,
45
+ visible: false,
45
46
  })
46
47
  // create the project bar panel in grapesjs
47
48
  editor.Panels.addPanel({
48
49
  id: PROJECT_BAR_PANEL_ID,
49
50
  buttons: opts.panels,
50
- visible : true,
51
+ visible: true,
51
52
  })
52
53
  // add the panels to the container
53
54
  opts.panels.map(panel => addButton(editor, panel))
@@ -79,10 +80,11 @@ export function addButton(editor: Editor, panel: PanelObject) {
79
80
  if(panel.attributes.containerClassName) {
80
81
  el.classList.add('project-bar__panel', panel.attributes.containerClassName, 'gjs-hidden')
81
82
  // add header
82
- if(panel.attributes.title) {
83
+ const title = panel.name ?? panel.attributes.title
84
+ if(title) {
83
85
  render(html`
84
86
  <header class="project-bar__panel-header">
85
- <h3 class="project-bar__panel-header-title">${ panel.attributes.title }</h3>
87
+ <h3 class="project-bar__panel-header-title">${ title }</h3>
86
88
  ${ panel.buttons?.map(button => {
87
89
  return html`
88
90
  <div