maglevcms 1.8.0 → 2.0.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. checksums.yaml +4 -4
  2. data/.yarn/install-state.gz +0 -0
  3. data/README.md +4 -4
  4. data/Rakefile +2 -1
  5. data/app/components/maglev/block_component.rb +1 -1
  6. data/app/components/maglev/content/base.rb +1 -1
  7. data/app/components/maglev/content/link.rb +4 -0
  8. data/app/components/maglev/section_component.rb +2 -2
  9. data/app/controllers/concerns/maglev/fetchers_concern.rb +3 -1
  10. data/app/controllers/concerns/maglev/resource_id_concern.rb +10 -0
  11. data/app/controllers/concerns/maglev/standalone_sections_concern.rb +6 -0
  12. data/app/controllers/concerns/maglev/{ui_locale_concern.rb → user_interface_locale_concern.rb} +1 -1
  13. data/app/controllers/maglev/api/collection_items_controller.rb +7 -0
  14. data/app/controllers/maglev/api/page_clones_controller.rb +1 -2
  15. data/app/controllers/maglev/api_controller.rb +1 -1
  16. data/app/controllers/maglev/application_controller.rb +1 -5
  17. data/app/controllers/maglev/assets/active_storage_proxy_controller.rb +17 -0
  18. data/app/controllers/maglev/assets/proxy_controller.rb +21 -0
  19. data/app/controllers/maglev/editor_controller.rb +1 -1
  20. data/app/controllers/maglev/page_preview_controller.rb +3 -3
  21. data/app/frontend/admin/controllers/screenshot_controller.js +29 -1
  22. data/app/frontend/editor/assets/remixicons/clipboard-line.svg +1 -0
  23. data/app/frontend/editor/assets/remixicons/ri-draggable.svg +1 -0
  24. data/app/frontend/editor/assets/remixicons/ri-folders-line.svg +1 -0
  25. data/app/frontend/editor/assets/remixicons/ri-more-fill.svg +1 -0
  26. data/app/frontend/editor/assets/remixicons/ri-shadow-line.svg +1 -0
  27. data/app/frontend/editor/components/errors/forbidden.vue +24 -0
  28. data/app/frontend/editor/components/errors/stale-record.vue +2 -2
  29. data/app/frontend/editor/components/header-nav/device-toggler.vue +6 -3
  30. data/app/frontend/editor/components/header-nav/index.vue +13 -42
  31. data/app/frontend/editor/components/header-nav/locale-toggler/index.vue +1 -1
  32. data/app/frontend/editor/components/header-nav/page-info.vue +41 -0
  33. data/app/frontend/editor/components/header-nav/preview-button.vue +6 -1
  34. data/app/frontend/editor/components/header-nav/separator.vue +1 -1
  35. data/app/frontend/editor/components/image-library/index.vue +26 -29
  36. data/app/frontend/editor/components/kit/collection-item-input.vue +9 -0
  37. data/app/frontend/editor/components/kit/copy-paste-button.vue +26 -0
  38. data/app/frontend/editor/components/kit/dropdown.vue +5 -0
  39. data/app/frontend/editor/components/kit/icon-button.vue +24 -0
  40. data/app/frontend/editor/components/kit/icon.vue +2 -3
  41. data/app/frontend/editor/components/kit/index.js +4 -2
  42. data/app/frontend/editor/components/kit/link-input.vue +1 -1
  43. data/app/frontend/editor/components/kit/modal.vue +1 -1
  44. data/app/frontend/editor/components/kit/page-icon.vue +1 -1
  45. data/app/frontend/editor/components/kit/rich-text-input/link-buttons.vue +1 -1
  46. data/app/frontend/editor/components/kit/select-input.vue +4 -2
  47. data/app/frontend/editor/components/link-picker/actions.vue +1 -1
  48. data/app/frontend/editor/components/link-picker/index.vue +1 -1
  49. data/app/frontend/editor/components/link-picker/page.vue +2 -0
  50. data/app/frontend/editor/components/page/form/main.vue +1 -1
  51. data/app/frontend/editor/components/page/list/actions-button.vue +159 -0
  52. data/app/frontend/editor/components/page/list/index.vue +15 -24
  53. data/app/frontend/editor/components/page/list/list-item.vue +10 -61
  54. data/app/frontend/editor/components/section-highlighter/index.vue +47 -46
  55. data/app/frontend/editor/components/section-list/add-button.vue +39 -0
  56. data/app/frontend/editor/components/section-list/index.vue +30 -15
  57. data/app/frontend/editor/components/section-list/list-item.vue +23 -8
  58. data/app/frontend/editor/components/section-pane/block-list/index.vue +1 -0
  59. data/app/frontend/editor/components/section-pane/block-list/list-item.vue +9 -6
  60. data/app/frontend/editor/components/section-pane/block-tree/new-nested-block-button.vue +2 -2
  61. data/app/frontend/editor/components/section-pane/block-tree/tree-node.vue +2 -2
  62. data/app/frontend/editor/components/sidebar-nav/index.vue +102 -0
  63. data/app/frontend/editor/components/sidebar-nav/link.vue +60 -0
  64. data/app/frontend/editor/components/theme-section-list/category.vue +47 -0
  65. data/app/frontend/editor/components/theme-section-list/index.vue +7 -31
  66. data/app/frontend/editor/components/theme-section-list/list-item.vue +10 -3
  67. data/app/frontend/editor/design/components/modal.scss +1 -1
  68. data/app/frontend/editor/design/components/tooltip.scss +6 -0
  69. data/app/frontend/editor/design/transitions.scss +1 -1
  70. data/app/frontend/editor/layouts/app.vue +1 -1
  71. data/app/frontend/editor/layouts/default.vue +2 -2
  72. data/app/frontend/editor/layouts/slide-pane.vue +1 -1
  73. data/app/frontend/editor/locales/editor.ar.json +265 -0
  74. data/app/frontend/editor/locales/editor.en.json +23 -15
  75. data/app/frontend/editor/locales/editor.es.json +18 -10
  76. data/app/frontend/editor/locales/editor.fr.json +28 -13
  77. data/app/frontend/editor/locales/editor.pt-BR.json +18 -10
  78. data/app/frontend/editor/locales/index.js +3 -0
  79. data/app/frontend/editor/mixins/error-modal.js +7 -2
  80. data/app/frontend/editor/mixins/preview-transformation.js +0 -1
  81. data/app/frontend/editor/plugins/i18n.js +2 -17
  82. data/app/frontend/editor/services/__tests__/section.spec.js +6 -0
  83. data/app/frontend/editor/services/collection-item.js +8 -0
  84. data/app/frontend/editor/services/page.js +1 -1
  85. data/app/frontend/editor/services/section.js +30 -0
  86. data/app/frontend/editor/services/site.js +4 -4
  87. data/app/frontend/editor/services/theme.js +1 -0
  88. data/app/frontend/editor/spec/__mocks__/page.js +3 -1
  89. data/app/frontend/editor/spec/__mocks__/services.js +1 -0
  90. data/app/frontend/editor/store/__tests__/getters.spec.js +56 -1
  91. data/app/frontend/editor/store/actions/index.js +1 -0
  92. data/app/frontend/editor/store/actions/site.js +8 -4
  93. data/app/frontend/editor/store/getters.js +11 -0
  94. data/app/frontend/editor/views/page-preview.vue +2 -1
  95. data/app/frontend/images/favicon.svg +11 -0
  96. data/app/frontend/images/logo.svg +14 -0
  97. data/app/frontend/live-preview-client/iframe-decorator.js +17 -15
  98. data/app/frontend/live-preview-client/rails.js +1 -1
  99. data/app/helpers/maglev/application_helper.rb +7 -2
  100. data/app/helpers/maglev/editor_helper.rb +12 -5
  101. data/app/models/concerns/maglev/sections_concern.rb +4 -0
  102. data/app/models/maglev/asset.rb +13 -14
  103. data/app/models/maglev/page/path_concern.rb +23 -8
  104. data/app/models/maglev/page.rb +17 -18
  105. data/app/models/maglev/page_path.rb +18 -18
  106. data/app/models/maglev/section/setting.rb +1 -1
  107. data/app/models/maglev/section.rb +18 -9
  108. data/app/models/maglev/setting_types/link.rb +1 -1
  109. data/app/models/maglev/site.rb +13 -24
  110. data/app/services/maglev/app_container.rb +2 -2
  111. data/app/services/maglev/extract_locale.rb +3 -2
  112. data/app/services/maglev/fetch_page.rb +2 -0
  113. data/app/services/maglev/fetch_theme_layout.rb +2 -0
  114. data/app/services/maglev/get_page_fullpath.rb +1 -2
  115. data/app/services/maglev/remove_section_type.rb +50 -0
  116. data/app/services/maglev/rename_section_type.rb +57 -0
  117. data/app/services/maglev/search_pages.rb +2 -1
  118. data/app/views/layouts/maglev/admin/application.html.erb +1 -1
  119. data/app/views/maglev/admin/sections/previews/show.html.erb +16 -1
  120. data/app/views/maglev/api/collection_items/show.json.jbuilder +7 -0
  121. data/app/views/maglev/api/page_clones/create.json.jbuilder +3 -0
  122. data/app/views/maglev/editor/show.html.erb +6 -5
  123. data/config/locales/activerecord.ar.yml +13 -0
  124. data/config/locales/activerecord.en.yml +8 -2
  125. data/config/locales/activerecord.es.yml +4 -0
  126. data/config/locales/activerecord.fr.yml +6 -2
  127. data/config/locales/activerecord.pt-BR.yml +13 -0
  128. data/config/routes.rb +3 -4
  129. data/db/migrate/20200831101942_create_maglev_section_content.rb +10 -2
  130. data/db/migrate/20210819092740_switch_to_localized_page_fields.rb +13 -3
  131. data/db/migrate/20211008064437_add_locales_to_sites.rb +7 -1
  132. data/db/migrate/20211013210954_translate_section_content.rb +16 -2
  133. data/db/migrate/20211203224112_add_open_graph_tags_to_pages.rb +9 -3
  134. data/db/migrate/20220612092235_add_style_to_sites.rb +5 -1
  135. data/lib/commands/maglev/change_site_locales_command.rb +61 -0
  136. data/lib/commands/maglev/create_site_command.rb +28 -0
  137. data/lib/commands/maglev/sections/remove_command.rb +48 -0
  138. data/lib/commands/maglev/sections/rename_command.rb +49 -0
  139. data/lib/generators/maglev/templates/install/config/initializers/maglev.rb +4 -1
  140. data/lib/generators/maglev/templates/theme/app/views/theme/layout.html.erb.tt +8 -2
  141. data/lib/maglev/errors.rb +1 -0
  142. data/lib/maglev/preview_constraint.rb +5 -1
  143. data/lib/maglev/theme_filesystem_loader.rb +7 -0
  144. data/lib/maglev/version.rb +1 -1
  145. data/lib/maglev.rb +13 -3
  146. data/lib/tasks/maglev/icons.rake +123 -0
  147. data/lib/tasks/maglev/vite.rake +62 -0
  148. data/lib/tasks/maglev_tasks.rake +9 -107
  149. data/tailwind.config.js +1 -0
  150. metadata +48 -33
  151. data/app/controllers/maglev/assets_controller.rb +0 -10
  152. data/app/frontend/editor/components/kit/list-item-button.vue +0 -16
  153. data/app/frontend/editor/components/sidebar-nav.vue +0 -125
  154. data/app/frontend/editor/plugins/maglev_dummy.js +0 -2
  155. data/app/frontend/images/favicon.png +0 -0
  156. data/app/frontend/images/logo.png +0 -0
@@ -22,7 +22,7 @@
22
22
  }
23
23
  },
24
24
  "sidebarNav": {
25
- "addNewSectionTooltip": "Adicionar uma nova seção na parte inferior da página",
25
+ "listPagesTooltip": "Gerenciar as páginas do seu site",
26
26
  "managePageSectionsTooltip": "Reordenar / excluir as seções da página",
27
27
  "editStyleTooltip": "Alterar o estilo do seu site",
28
28
  "openImageLibraryTooltip": "Abrir a galeria de imagens",
@@ -43,14 +43,16 @@
43
43
  "title": "Lista de páginas",
44
44
  "subTitle": "Gerenciar as páginas do seu site",
45
45
  "newButton": "Nova página",
46
- "searchPlaceholder": "Digite sua pesquisa aqui...",
47
- "item": {
48
- "edit": "Editar configurações da página",
49
- "clone": "Clonar página",
50
- "hide": "Ocultar página",
51
- "show": "Mostrar página",
52
- "delete": "Excluir página"
53
- }
46
+ "searchPlaceholder": "Digite sua pesquisa aqui..."
47
+ },
48
+ "actions": {
49
+ "edit": "Editar configurações",
50
+ "clone": "Clonar",
51
+ "hide": "Ocultar",
52
+ "show": "Mostrar",
53
+ "delete": "Excluir",
54
+ "copyUrlToClipboard": "Copiar URL",
55
+ "copyUrlToClipboardSuccess": "Copiado!"
54
56
  },
55
57
  "new": {
56
58
  "title": "Criar uma nova página",
@@ -97,7 +99,8 @@
97
99
  },
98
100
  "listPane": {
99
101
  "title": "Organizar seções",
100
- "empty": "Não há seções."
102
+ "empty": "Não há seções.",
103
+ "addButton": "Adicionar uma nova seção"
101
104
  }
102
105
  },
103
106
  "sectionPane": {
@@ -236,6 +239,11 @@
236
239
  "title": "Pedimos desculpas 🙇",
237
240
  "message": "Não conseguimos salvar suas alterações porque outra pessoa atualizou o conteúdo no intervalo de tempo.",
238
241
  "button": "Por favor, recarregue a página"
242
+ },
243
+ "forbidden": {
244
+ "title": "Pedimos desculpas 🙇",
245
+ "message": "Você não tem permissão para realizar esta ação. Consulte seu administrador.",
246
+ "button": "Fechar"
239
247
  }
240
248
  },
241
249
  "support": {
@@ -2,9 +2,11 @@ import EditorEN from './editor.en.json'
2
2
  import EditorES from './editor.es.json'
3
3
  import EditorFR from './editor.fr.json'
4
4
  import EditorPTBR from './editor.pt-BR.json'
5
+ import EditorAR from './editor.ar.json'
5
6
  import { deepMerge } from '@/misc/utils'
6
7
 
7
8
  const overriddenEN = window.customTranslations?.en ?? {}
9
+ const overriddenAR = window.customTranslations?.ar ?? {}
8
10
  const overriddenES = window.customTranslations?.es ?? {}
9
11
  const overriddenFR = window.customTranslations?.fr ?? {}
10
12
  const overriddenPTBR =
@@ -17,4 +19,5 @@ export default {
17
19
  es: deepMerge(EditorES, overriddenES),
18
20
  fr: deepMerge(EditorFR, overriddenFR),
19
21
  'pt-BR': deepMerge(EditorPTBR, overriddenPTBR),
22
+ ar: deepMerge(EditorAR, overriddenAR),
20
23
  }
@@ -1,4 +1,5 @@
1
- import StaleRecord from '@/components/errors/stale-record.vue'
1
+ import StaleRecordModal from '@/components/errors/stale-record.vue'
2
+ import ForbiddenModal from '@/components/errors/forbidden.vue'
2
3
 
3
4
  export default {
4
5
  methods: {
@@ -7,9 +8,13 @@ export default {
7
8
 
8
9
  switch (errorType) {
9
10
  case 'staleRecord':
10
- ModalComponent = StaleRecord
11
+ ModalComponent = StaleRecordModal
12
+ break
13
+ case 'forbidden':
14
+ ModalComponent = ForbiddenModal
11
15
  break
12
16
  default:
17
+ console.warn("Unknown errorType:", errorType)
13
18
  return // unknown error type
14
19
  }
15
20
 
@@ -9,7 +9,6 @@ export default {
9
9
  previewPaneMaxWidth() {
10
10
  const sectionPaneWidth =
11
11
  document.querySelector('.slide-pane')?.offsetWidth || 0
12
- console.log('previewPaneMaxWidth', this.windowWidth, sectionPaneWidth)
13
12
  return this.windowWidth - sectionPaneWidth
14
13
  },
15
14
  previewScaleRatio() {
@@ -4,24 +4,9 @@ import messages from '@/locales'
4
4
 
5
5
  Vue.use(VueI18n)
6
6
 
7
- const AVAILABLE_LOCALES = ['en', 'fr']
8
- var locale = 'en'
9
-
10
- if (document.documentElement.lang) {
11
- // fetch the local from the HTML tag
12
- locale = document.documentElement.lang
13
- } else {
14
- // try to fetch the browser locale
15
- const language = navigator.languages[0]
16
- if (language) {
17
- locale = language.split('-')[0]
18
- if (AVAILABLE_LOCALES.indexOf(locale) === -1) locale = null
19
- }
20
- }
21
-
22
7
  const i18n = new VueI18n({
23
- locale,
24
- fallbackLocale: AVAILABLE_LOCALES[0],
8
+ locale: window.uiLocale,
9
+ fallbackLocale: 'en',
25
10
  messages,
26
11
  })
27
12
 
@@ -30,4 +30,10 @@ describe('SectionService', () => {
30
30
  ])
31
31
  })
32
32
  })
33
+ describe('#getSectionLabel', () => {
34
+ it('returns the label of the section', () => {
35
+ const label = service.getSectionLabel(simpleContentSection, theme.sections[0])
36
+ expect(label).toEqual('preTitle')
37
+ })
38
+ })
33
39
  })
@@ -9,4 +9,12 @@ export default (api) => ({
9
9
  .get(`/collections/${collectionId}`, options)
10
10
  .then(({ data }) => data)
11
11
  },
12
+ findOne: (collectionId, id) => {
13
+ console.log(
14
+ `[CollectionItem] Fetching the item ${id} of ${collectionId}`,
15
+ )
16
+ return api
17
+ .get(`/collections/${collectionId}/${id}`)
18
+ .then(({ data }) => data)
19
+ },
12
20
  })
@@ -89,7 +89,7 @@ export default (api) => ({
89
89
 
90
90
  clone: (id) => {
91
91
  console.log('[PageService] Cloning page #', id)
92
- return api.post(`/pages/${id}/clones`, {})
92
+ return api.post(`/pages/${id}/clones`, {}).then(({ data }) => data)
93
93
  },
94
94
 
95
95
  destroy: (id) => {
@@ -146,6 +146,36 @@ const buildDefaultBlocks = (definition) => {
146
146
  return blocks
147
147
  }
148
148
 
149
+ export const getSectionLabel = (section, definition) => {
150
+ let label = null
151
+
152
+ definition.settings.some((setting) => {
153
+ const value = section.settings.find(
154
+ (contentSetting) => contentSetting.id === setting.id,
155
+ )?.value
156
+
157
+ if (value === undefined) return false
158
+
159
+ switch (setting.type) {
160
+ case 'text':
161
+ const doc = new DOMParser().parseFromString(value.replace(/<br\/?>/g, ' '), 'text/html')
162
+ label = doc.body.textContent
163
+ break
164
+ case 'link':
165
+ if (!isBlank(value?.text)) label = value.text
166
+ break
167
+ case 'collection_item':
168
+ if (!isBlank(value?.label)) label = value.label
169
+ break
170
+ default:
171
+ break
172
+ }
173
+
174
+ return !!label
175
+ })
176
+ return label
177
+ }
178
+
149
179
  export const getBlockLabel = (block, definition, index) => {
150
180
  let label, image
151
181
  definition.settings.forEach((setting) => {
@@ -12,10 +12,10 @@ export default (api) => ({
12
12
  return response.headers['lock-version']
13
13
  })
14
14
  },
15
- publish() {
16
- return api.post(`/publication`).then(({ data }) => data)
15
+ publish({ pageId }) {
16
+ return api.post(`/publication`, { page_id: pageId }).then(({ data }) => data)
17
17
  },
18
- getLastPublication() {
19
- return api.get(`/publication`).then(({ data }) => data)
18
+ getLastPublication({ pageId }) {
19
+ return api.get('/publication', { params: { page_id: pageId }}).then(({ data }) => data)
20
20
  },
21
21
  })
@@ -1,5 +1,6 @@
1
1
  export const buildCategories = (theme) => {
2
2
  return theme.sectionCategories.map((category) => ({
3
+ id: category.id,
3
4
  name: category.name,
4
5
  children: theme.sections
5
6
  .filter((section) => section.category == category.id)
@@ -204,6 +204,7 @@ export const page = {
204
204
  ogDescription: null,
205
205
  ogImageUrl: null,
206
206
  previewUrl: '/maglev/preview',
207
+ liveUrl: '/',
207
208
  sectionNames: [
208
209
  { id: 'GrYZW-VP', name: 'Navbar 01' },
209
210
  { id: '8hKSujtd', name: 'Content #1' },
@@ -418,13 +419,14 @@ export const normalizedPage = {
418
419
  ogDescription: null,
419
420
  ogImageUrl: null,
420
421
  previewUrl: '/maglev/preview',
422
+ liveUrl: '/',
421
423
  sectionNames: [
422
424
  { id: 'GrYZW-VP', name: 'Navbar 01' },
423
425
  { id: '8hKSujtd', name: 'Content #1' },
424
426
  { id: 'xM6f-kyh', name: 'List #1' },
425
427
  ],
426
428
  lockVersion: 1,
427
- translated: true,
429
+ translated: true,
428
430
  },
429
431
  },
430
432
  },
@@ -39,6 +39,7 @@ const sectionService = {
39
39
  build: vi.fn(),
40
40
  getSettings: vi.fn(),
41
41
  buildDefaultBlock: vi.fn(),
42
+ getSectionLabel: vi.fn(),
42
43
  }
43
44
 
44
45
  const blockService = {
@@ -1,5 +1,5 @@
1
1
  import Vuex from 'vuex'
2
- import { vi } from 'vitest'
2
+ import { describe, vi } from 'vitest'
3
3
  import { createLocalVue } from '@vue/test-utils'
4
4
  import defaultState from '@/store/default-state'
5
5
  import buildGetters from '@/store/getters'
@@ -29,6 +29,58 @@ describe('Getters', () => {
29
29
  store.commit('SET_SITE', site)
30
30
  })
31
31
 
32
+ describe('#currentPagePath', () => {
33
+ let freshNormalizedPage = null
34
+ beforeEach(() => {
35
+ freshNormalizedPage = structuredClone(normalizedPage)
36
+ mockedServices.page.normalize = vi.fn(() => freshNormalizedPage)
37
+ })
38
+ describe('Given this is the home page', () => {
39
+ it('returns the path of the page', () => {
40
+ store.commit('SET_PAGE', page)
41
+ expect(store.getters.currentPagePath).toStrictEqual('/index')
42
+ })
43
+ })
44
+ describe('Given this is a random page', () => {
45
+ it('returns the path of the page', () => {
46
+ freshNormalizedPage.entities.page['1'].path = '/bonjour'
47
+ freshNormalizedPage.entities.page['1'].liveUrl = '/fr/bonjour'
48
+ store.commit('SET_PAGE', page)
49
+ expect(store.getters.currentPagePath).toStrictEqual('/fr/bonjour')
50
+ })
51
+ })
52
+ describe('Given the liveUrl contains the domain name', () => {
53
+ it('returns the path of the page', () => {
54
+ freshNormalizedPage.entities.page['1'].liveUrl = 'https://example.com:8080/fr'
55
+ freshNormalizedPage.entities.page['1'].path = 'index'
56
+ store.commit('SET_PAGE', page)
57
+ expect(store.getters.currentPagePath).toStrictEqual('/fr/index')
58
+ })
59
+ })
60
+ })
61
+
62
+ describe('#currentPageUrl', () => {
63
+ let freshNormalizedPage = null
64
+ beforeEach(() => {
65
+ freshNormalizedPage = structuredClone(normalizedPage)
66
+ mockedServices.page.normalize = vi.fn(() => freshNormalizedPage)
67
+ })
68
+ describe('Given the page live URL is not prefixed with the base URL', () => {
69
+ it('returns the url of the page', () => {
70
+ freshNormalizedPage.entities.page['1'].liveUrl = '/hello-world'
71
+ store.commit('SET_PAGE', page)
72
+ expect(store.getters.currentPageUrl).toStrictEqual('http://localhost:3000/hello-world')
73
+ })
74
+ })
75
+ describe('Given the page live URL is prefixed with the base URL', () => {
76
+ it('returns the url of the page', () => {
77
+ freshNormalizedPage.entities.page['1'].liveUrl = 'https://example.com:8080/fr'
78
+ store.commit('SET_PAGE', page)
79
+ expect(store.getters.currentPageUrl).toStrictEqual('https://example.com:8080/fr')
80
+ })
81
+ })
82
+ })
83
+
32
84
  describe('#content', () => {
33
85
  it('returns the content of the sections for the page', () => {
34
86
  mockedServices.page.denormalize = vi.fn(() => page)
@@ -83,18 +135,21 @@ describe('Getters', () => {
83
135
  id: 'GrYZW-VP',
84
136
  type: 'navbar_01',
85
137
  name: 'Navbar 01',
138
+ label: undefined,
86
139
  viewportFixedPosition: false,
87
140
  },
88
141
  {
89
142
  id: '8hKSujtd',
90
143
  type: 'content_01',
91
144
  name: 'Content #1',
145
+ label: undefined,
92
146
  viewportFixedPosition: false,
93
147
  },
94
148
  {
95
149
  id: 'xM6f-kyh',
96
150
  type: 'list_01',
97
151
  name: 'List #1',
152
+ label: undefined,
98
153
  viewportFixedPosition: false,
99
154
  },
100
155
  ])
@@ -6,6 +6,7 @@ import buildSectionBlockActions from './section-block'
6
6
  export default (services) => ({
7
7
  setDevice({ commit }, value) {
8
8
  commit('SET_DEVICE', value)
9
+ commit('SET_HOVERED_SECTION', null)
9
10
  },
10
11
  setTheme({ commit }, theme) {
11
12
  commit('SET_THEME', theme)
@@ -7,15 +7,19 @@ export default (services) => ({
7
7
  commit('SET_STYLE', style)
8
8
  })
9
9
  },
10
- loadPublishButtonState({ commit }) {
10
+ loadPublishButtonState({ state, commit }) {
11
11
  services.site
12
- .getLastPublication()
12
+ .getLastPublication({ pageId: state.page.id })
13
13
  .then((data) => commit('SET_PUBLISH_BUTTON_STATE', data))
14
14
  },
15
- async publishSite({ commit }) {
15
+ async publishSite({ state, commit }) {
16
16
  services.site
17
- .publish()
17
+ .publish({ pageId: state.page.id })
18
18
  .then((data) => commit('SET_PUBLISH_BUTTON_STATE', data))
19
+ .catch(({ response: { status } }) => {
20
+ console.log('[Maglev] could not publish the page', status)
21
+ if (status === 403) commit('OPEN_ERROR_MODAL', 'forbidden')
22
+ })
19
23
  },
20
24
  pollLastPublication({ dispatch }) {
21
25
  dispatch('loadPublishButtonState')
@@ -1,4 +1,14 @@
1
+ import { isBlank } from '@/misc/utils'
2
+
1
3
  export default (services) => ({
4
+ currentPagePath: ({ page }) => {
5
+ const nakedPath = page.liveUrl.startsWith('http') ? new URL(page.liveUrl).pathname : page.liveUrl
6
+ return page.path === 'index' ? `${nakedPath}/index`.replace('//', '/') : nakedPath
7
+ },
8
+ currentPageUrl: ({ page }) => {
9
+ if (page.liveUrl.startsWith('http')) return page.liveUrl
10
+ return new URL(page.liveUrl, location.origin).toString()
11
+ },
2
12
  sectionList: (
3
13
  { page, sections, sectionBlocks },
4
14
  { sectionDefinition: getSectiondefinition },
@@ -14,6 +24,7 @@ export default (services) => ({
14
24
  id: sectionContent.id,
15
25
  type: sectionContent['type'],
16
26
  name: sectionDefinition.name,
27
+ label: services.section.getSectionLabel(sectionContent, sectionDefinition),
17
28
  viewportFixedPosition: !!sectionDefinition.viewportFixedPosition,
18
29
  }
19
30
  })
@@ -19,6 +19,7 @@
19
19
  v-if="currentPage"
20
20
  >
21
21
  <div
22
+ id="iframe-wrapper"
22
23
  class="transition-all duration-100 ease-in-out"
23
24
  :class="{ [deviceClass]: true, hidden: isPageEmpty }"
24
25
  :style="{ opacity: previewReady ? 1 : 0 }"
@@ -64,7 +65,7 @@
64
65
  >
65
66
  <template v-slot:icon>
66
67
  <uikit-icon
67
- name="add-box-line"
68
+ name="ri-stack-line"
68
69
  size="1.5rem"
69
70
  class="mx-1 text-black"
70
71
  />
@@ -0,0 +1,11 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <ns0:svg height="314px" version="1.1" viewBox="0 0 464 314" width="464px" xmlns:ns0="http://www.w3.org/2000/svg">
3
+ <ns0:defs>
4
+
5
+ </ns0:defs>
6
+ <ns0:g fill="none" fill-rule="evenodd" id="Page-1" stroke="none" stroke-width="1">
7
+ <ns0:g fill="#000000" id="maglev-3">
8
+ <ns0:path d="M454.3,164.08 L419.43,88.31 C394.87,34.93 341.49,0.74 282.74,0.74 L103.98,0.74 C92.95,0.74 84.01,9.68 84.01,20.71 C84.01,31.74 92.95,40.68 103.98,40.68 L229.29,40.68 C229.29,40.68 219.11,64.98 219.11,64.98 C212.3,81.22 196.41,91.79 178.8,91.79 L20.74,91.79 C9.71,91.79 0.77,100.73 0.77,111.76 C0.77,122.79 9.71,131.73 20.74,131.73 L191.14,131.73 C191.14,131.73 180.96,156.04 180.96,156.04 C174.15,172.28 158.26,182.85 140.65,182.85 L52.82,182.85 C41.79,182.85 32.85,191.79 32.85,202.82 C32.85,213.85 41.79,222.79 52.82,222.79 L414.89,222.79 C418.73,222.79 421.53,226.58 420.25,230.19 C417.74,237.29 413.97,244.05 408.92,250.15 C396.11,265.63 376.56,273.93 356.46,273.93 L126,273.93 C114.97,273.93 106.03,282.87 106.03,293.9 C106.03,304.93 114.97,313.87 126,313.87 L355.88,313.87 C390.23,313.87 423.3,298.4 443.55,270.65 C466.4,239.34 470.31,198.94 454.28,164.09 L454.3,164.08 Z" id="Path"/>
9
+ </ns0:g>
10
+ </ns0:g>
11
+ </ns0:svg>
@@ -0,0 +1,14 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="464px" height="314px" viewBox="0 0 464 314" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3
+ <defs>
4
+ <linearGradient x1="0.00215870369%" y1="49.9984032%" x2="100.004107%" y2="49.9984032%" id="linearGradient-1">
5
+ <stop stop-color="#0061FF" offset="0%"></stop>
6
+ <stop stop-color="#EC1FDA" offset="100%"></stop>
7
+ </linearGradient>
8
+ </defs>
9
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
10
+ <g id="maglev-3" fill="url(#linearGradient-1)">
11
+ <path d="M454.3,164.08 L419.43,88.31 C394.87,34.93 341.49,0.74 282.74,0.74 L103.98,0.74 C92.95,0.74 84.01,9.68 84.01,20.71 C84.01,31.74 92.95,40.68 103.98,40.68 L229.29,40.68 C229.29,40.68 219.11,64.98 219.11,64.98 C212.3,81.22 196.41,91.79 178.8,91.79 L20.74,91.79 C9.71,91.79 0.77,100.73 0.77,111.76 C0.77,122.79 9.71,131.73 20.74,131.73 L191.14,131.73 C191.14,131.73 180.96,156.04 180.96,156.04 C174.15,172.28 158.26,182.85 140.65,182.85 L52.82,182.85 C41.79,182.85 32.85,191.79 32.85,202.82 C32.85,213.85 41.79,222.79 52.82,222.79 L414.89,222.79 C418.73,222.79 421.53,226.58 420.25,230.19 C417.74,237.29 413.97,244.05 408.92,250.15 C396.11,265.63 376.56,273.93 356.46,273.93 L126,273.93 C114.97,273.93 106.03,282.87 106.03,293.9 C106.03,304.93 114.97,313.87 126,313.87 L355.88,313.87 C390.23,313.87 423.3,298.4 443.55,270.65 C466.4,239.34 470.31,198.94 454.28,164.09 L454.3,164.08 Z" id="Path"></path>
12
+ </g>
13
+ </g>
14
+ </svg>
@@ -53,6 +53,18 @@ export const start = (config) => {
53
53
 
54
54
  // click on links
55
55
  disableLinks(previewDocument)
56
+
57
+ // Only works on Google Chrome
58
+ selectHoveredSectionAtStartup(previewDocument, config.stickySectionIds)
59
+ }
60
+
61
+ const selectHoveredSectionAtStartup = (previewDocument, stickySectionIds) => {
62
+ setTimeout(() => {
63
+ const section = previewDocument.querySelector('[data-maglev-section-id]:hover')
64
+
65
+ if (section)
66
+ onSectionHovered(previewDocument, section, stickySectionIds)
67
+ }, 200)
56
68
  }
57
69
 
58
70
  const listen = (previewDocument, eventType, handler) => {
@@ -96,27 +108,17 @@ const listen = (previewDocument, eventType, handler) => {
96
108
  }
97
109
 
98
110
  const listenScrolling = (previewDocument) => {
99
- let mouseX = 0
100
- let mouseY = 0
101
-
102
- addEventListener(previewDocument, 'mousemove', (e) => {
103
- mouseX = e.clientX
104
- mouseY = e.clientY
105
- })
106
-
107
- const debouncedScrollNotifier = debounce(() => {
108
- const el = previewDocument
109
- .elementFromPoint(mouseX, mouseY)
110
- ?.closest('[data-maglev-section-id]')
111
-
111
+ const scrollNotifier = () => {
112
+ const el = previewDocument.querySelector('[data-maglev-section-id]:hover')
112
113
  if (el) postMessage('scroll', { boundingRect: el.getBoundingClientRect() })
113
- }, 10)
114
+ }
114
115
 
115
- addEventListener(previewDocument, 'scroll', debouncedScrollNotifier)
116
+ addEventListener(previewDocument, 'scroll', scrollNotifier)
116
117
  }
117
118
 
118
119
  const onSectionHovered = (previewDocument, el, stickySectionIds) => {
119
120
  const sectionId = el.dataset.maglevSectionId
121
+
120
122
  if (hoveredSectionId !== sectionId) {
121
123
  postMessage('section:hover', {
122
124
  sectionId,
@@ -166,7 +166,7 @@ const updatePreviewDocument = async (content, section, insertAt) => {
166
166
  targetElement = previewDocument.querySelector(selector)
167
167
  }
168
168
 
169
- runScripts(targetElement)
169
+ runScripts(sourceElement)
170
170
 
171
171
  targetElement.scrollIntoView(true)
172
172
  }
@@ -17,8 +17,13 @@ module Maglev
17
17
 
18
18
  entries = maglev_asset_manifest.resolve_entries(*%w[live-preview-rails-client], type: :javascript)
19
19
 
20
- javascript_include_tag(*entries.fetch(:scripts).flatten.uniq, crossorigin: 'anonymous', type: 'module',
21
- defer: true)
20
+ javascript_include_tag(
21
+ *entries.fetch(:scripts).flatten.uniq,
22
+ crossorigin: 'anonymous',
23
+ type: 'module',
24
+ defer: true,
25
+ nonce: true
26
+ )
22
27
  end
23
28
 
24
29
  def maglev_asset_manifest
@@ -20,7 +20,14 @@ module Maglev
20
20
  end
21
21
 
22
22
  def editor_window_title
23
- maglev_config.title.presence || 'Maglev - EDITOR'
23
+ case maglev_config.title
24
+ when nil
25
+ 'Maglev - EDITOR'
26
+ when String
27
+ maglev_config.title
28
+ when Proc
29
+ instance_exec(maglev_site, &maglev_config.title)
30
+ end
24
31
  end
25
32
 
26
33
  def editor_primary_hex_color
@@ -45,9 +52,9 @@ module Maglev
45
52
  def editor_logo_url
46
53
  case maglev_config.logo
47
54
  when nil
48
- editor_asset_path(nil, 'logo.png')
55
+ editor_asset_path(nil, 'logo.svg')
49
56
  when String
50
- editor_asset_path(maglev_config.logo, 'logo.png')
57
+ editor_asset_path(maglev_config.logo, 'logo.svg')
51
58
  when Proc
52
59
  instance_exec(maglev_site, &maglev_config.logo)
53
60
  end
@@ -56,9 +63,9 @@ module Maglev
56
63
  def editor_favicon_url
57
64
  case maglev_config.favicon
58
65
  when nil
59
- editor_asset_path(nil, 'favicon.png')
66
+ editor_asset_path(nil, 'favicon.svg')
60
67
  when String
61
- editor_asset_path(maglev_config.favicon, 'favicon.png')
68
+ editor_asset_path(maglev_config.favicon, 'favicon.svg')
62
69
  when Proc
63
70
  instance_exec(maglev_site, &maglev_config.favicon)
64
71
  end
@@ -21,6 +21,10 @@ module Maglev::SectionsConcern
21
21
  end
22
22
  end
23
23
 
24
+ def find_sections_by_type(type)
25
+ sections.select { |section| section['type'] == type }
26
+ end
27
+
24
28
  private
25
29
 
26
30
  def prepare_section(theme, section)
@@ -1,5 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # == Schema Information
4
+ #
5
+ # Table name: maglev_assets
6
+ #
7
+ # id :bigint not null, primary key
8
+ # byte_size :integer
9
+ # content_type :string
10
+ # filename :string
11
+ # height :integer
12
+ # width :integer
13
+ # created_at :datetime not null
14
+ # updated_at :datetime not null
15
+ #
3
16
  module Maglev
4
17
  class Asset < ApplicationRecord
5
18
  include ::Maglev.uploader
@@ -26,17 +39,3 @@ module Maglev
26
39
  end
27
40
  end
28
41
  end
29
-
30
- # == Schema Information
31
- #
32
- # Table name: maglev_assets
33
- #
34
- # id :bigint not null, primary key
35
- # byte_size :integer
36
- # content_type :string
37
- # filename :string
38
- # height :integer
39
- # width :integer
40
- # created_at :datetime not null
41
- # updated_at :datetime not null
42
- #