maglevcms 1.1.6 → 1.2.0
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.
- checksums.yaml +4 -4
- data/app/components/maglev/content/image.rb +2 -0
- data/app/controllers/concerns/maglev/fetchers_concern.rb +1 -0
- data/app/controllers/concerns/maglev/standalone_sections_concern.rb +6 -0
- data/app/controllers/maglev/sitemap_controller.rb +31 -0
- data/app/frontend/editor/assets/remixicons/ri-palette-line.svg +1 -0
- data/app/frontend/editor/components/dynamic-form/dynamic-input.vue +2 -1
- data/app/frontend/editor/components/header-nav/publish-button.vue +1 -1
- data/app/frontend/editor/components/kit/collection-item-input.vue +5 -2
- data/app/frontend/editor/components/kit/color-input/core-input.vue +85 -0
- data/app/frontend/editor/components/kit/color-input/preset-button.vue +57 -0
- data/app/frontend/editor/components/kit/color-input/preset-dropdown.vue +46 -0
- data/app/frontend/editor/components/kit/color-input.vue +43 -0
- data/app/frontend/editor/components/kit/dropdown.vue +1 -0
- data/app/frontend/editor/components/kit/index.js +2 -2
- data/app/frontend/editor/components/kit/submit-button.vue +3 -3
- data/app/frontend/editor/components/kit/tabs.vue +2 -1
- data/app/frontend/editor/components/kit/text-input.vue +2 -2
- data/app/frontend/editor/misc/utils.js +32 -2
- data/app/frontend/editor/services/api.js +2 -2
- data/app/frontend/editor/spec/__mocks__/page.js +36 -36
- data/app/frontend/editor/spec/__mocks__/section.js +29 -29
- data/app/helpers/maglev/editor_helper.rb +1 -1
- data/app/helpers/maglev/page_preview_helper.rb +5 -1
- data/app/helpers/maglev/sitemap_helper.rb +13 -0
- data/app/models/concerns/maglev/sections_concern.rb +10 -0
- data/app/models/maglev/page/path_concern.rb +8 -1
- data/app/models/maglev/section/setting.rb +5 -0
- data/app/models/maglev/site/locales_concern.rb +8 -0
- data/app/services/concerns/maglev/get_page_sections/transform_collection_item_concern.rb +8 -1
- data/app/services/maglev/app_container.rb +2 -0
- data/app/services/maglev/fetch_collection_items.rb +6 -2
- data/app/services/maglev/persist_page.rb +23 -4
- data/app/services/maglev/setup_pages.rb +41 -5
- data/app/views/maglev/api/pages/_show.json.jbuilder +1 -2
- data/app/views/maglev/sitemap/index.xml.builder +21 -0
- data/config/routes.rb +1 -0
- data/db/migrate/20210830085101_create_maglev_page_paths.rb +1 -1
- data/db/migrate/20210906102712_add_canonical_to_pages.rb +1 -1
- data/db/migrate/20211008064437_add_locales_to_sites.rb +1 -1
- data/db/migrate/20211013210954_translate_section_content.rb +1 -1
- data/db/migrate/20211101205001_add_lock_version_to_maglev_pages.rb +1 -1
- data/db/migrate/20211116161121_better_page_path_canonical_indices.rb +1 -1
- data/db/migrate/20211124101005_fix_page_path_indices.rb +1 -1
- data/db/migrate/20211203224112_add_open_graph_tags_to_pages.rb +1 -1
- data/db/migrate/20220612092235_add_style_to_sites.rb +1 -1
- data/lib/generators/maglev/install_generator.rb +3 -2
- data/lib/generators/maglev/section_generator.rb +2 -1
- data/lib/generators/maglev/templates/section/app/theme/sections/%category%/%file_name%.yml.tt +13 -9
- data/lib/maglev/engine.rb +8 -2
- data/lib/maglev/version.rb +1 -1
- data/package.json +3 -3
- data/yarn.lock +799 -565
- metadata +14 -7
- data/app/frontend/editor/components/kit/color-picker.vue +0 -81
- /data/app/frontend/editor/assets/remixicons/{check-line.svg → ri-check-line.svg} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b23017be4bbbf4d06d99cf0f94ede1136e0e0ea343e0fbbef94edb6dc4d112d4
|
4
|
+
data.tar.gz: b6e5d107a2ea7a18091ffdd084a3fed631d0726fe845e0ac0f7100f1fc3e2b60
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1b579c11945ca3c28bb48f59d7efc0ea70a6d1608b7275cd507c3044cf30446bf3a342163ad9c468f0f3cf690386be137401e34f6426bbc016b4306af2d701ff
|
7
|
+
data.tar.gz: d4257dfeb8ef1a210fc9147325e4cae5517c4a5f676b7c27637353573ee9c4ba315283edc4b0c97253d06b6327ba48b4fbf25199b4247f21913482b17f53777a
|
@@ -15,6 +15,8 @@ module Maglev
|
|
15
15
|
private
|
16
16
|
|
17
17
|
def fetch_maglev_site_scoped_sections
|
18
|
+
return if within_maglev_engine?
|
19
|
+
|
18
20
|
fetch_maglev_site
|
19
21
|
fetch_maglev_theme
|
20
22
|
fetch_maglev_dummy_page
|
@@ -24,5 +26,9 @@ module Maglev
|
|
24
26
|
def fetch_maglev_dummy_page
|
25
27
|
@fetch_maglev_page = ::Maglev::Page.new(title: 'DummyPage', sections: fetch_maglev_site.sections)
|
26
28
|
end
|
29
|
+
|
30
|
+
def within_maglev_engine?
|
31
|
+
controller_path.starts_with?('maglev/')
|
32
|
+
end
|
27
33
|
end
|
28
34
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Maglev
|
4
|
+
class SitemapController < ApplicationController
|
5
|
+
include Maglev::FetchersConcern
|
6
|
+
include Maglev::ServicesConcern
|
7
|
+
include Maglev::ContentLocaleConcern
|
8
|
+
|
9
|
+
before_action :verify_request_format!
|
10
|
+
before_action :fetch_maglev_site
|
11
|
+
|
12
|
+
def index
|
13
|
+
@host = request.protocol + fetch_host
|
14
|
+
@pages = fetch_pages
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def fetch_host
|
20
|
+
request.headers['HTTP_X_MAGLEV_HOST'] || request.host
|
21
|
+
end
|
22
|
+
|
23
|
+
def fetch_pages
|
24
|
+
Maglev::Page.all.visible
|
25
|
+
end
|
26
|
+
|
27
|
+
def verify_request_format!
|
28
|
+
raise ActionController::UnknownFormat, 'Sitemap is only rendered as XML' if request.format != 'xml'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2C17.5222 2 22 5.97778 22 10.8889C22 13.9556 19.5111 16.4444 16.4444 16.4444H14.4778C13.5556 16.4444 12.8111 17.1889 12.8111 18.1111C12.8111 18.5333 12.9778 18.9222 13.2333 19.2111C13.5 19.5111 13.6667 19.9 13.6667 20.3333C13.6667 21.2556 12.9 22 12 22C6.47778 22 2 17.5222 2 12C2 6.47778 6.47778 2 12 2ZM10.8111 18.1111C10.8111 16.0843 12.451 14.4444 14.4778 14.4444H16.4444C18.4065 14.4444 20 12.851 20 10.8889C20 7.1392 16.4677 4 12 4C7.58235 4 4 7.58235 4 12C4 16.19 7.2226 19.6285 11.324 19.9718C10.9948 19.4168 10.8111 18.7761 10.8111 18.1111ZM7.5 12C6.67157 12 6 11.3284 6 10.5C6 9.67157 6.67157 9 7.5 9C8.32843 9 9 9.67157 9 10.5C9 11.3284 8.32843 12 7.5 12ZM16.5 12C15.6716 12 15 11.3284 15 10.5C15 9.67157 15.6716 9 16.5 9C17.3284 9 18 9.67157 18 10.5C18 11.3284 17.3284 12 16.5 12ZM12 9C11.1716 9 10.5 8.32843 10.5 7.5C10.5 6.67157 11.1716 6 12 6C12.8284 6 13.5 6.67157 13.5 7.5C13.5 8.32843 12.8284 9 12 9Z"></path></svg>
|
@@ -62,7 +62,7 @@
|
|
62
62
|
v-model="inputValue"
|
63
63
|
v-if="setting.type == 'link'"
|
64
64
|
/>
|
65
|
-
<color-
|
65
|
+
<color-input
|
66
66
|
:label="setting.label"
|
67
67
|
:name="setting.id"
|
68
68
|
v-model="inputValue"
|
@@ -80,6 +80,7 @@
|
|
80
80
|
:label="setting.label"
|
81
81
|
:name="setting.id"
|
82
82
|
v-model="inputValue"
|
83
|
+
:collection-id="options.collectionId"
|
83
84
|
v-if="setting.type == 'collection_item'"
|
84
85
|
/>
|
85
86
|
</div>
|
@@ -10,7 +10,7 @@
|
|
10
10
|
:searchEnabled="true"
|
11
11
|
:searchPlaceholder="$t(`collectionItemInput.select.searchPlaceholder`)"
|
12
12
|
:emptyLabel="$t(`collectionItemInput.select.emptyLabel`)"
|
13
|
-
:fetchList="(q) => services.collectionItem.findAll(
|
13
|
+
:fetchList="(q) => services.collectionItem.findAll(collectionId, { q })"
|
14
14
|
:clearEnabled="true"
|
15
15
|
buttonClass="h-10"
|
16
16
|
v-model="selectedCollectionItem"
|
@@ -42,18 +42,21 @@
|
|
42
42
|
</template>
|
43
43
|
|
44
44
|
<script>
|
45
|
+
import { camelizeKeys } from '@/misc/utils'
|
46
|
+
|
45
47
|
export default {
|
46
48
|
name: 'CollectionItemInput',
|
47
49
|
props: {
|
48
50
|
label: { type: String, default: 'Label' },
|
49
51
|
name: { type: String, default: 'image' },
|
50
52
|
value: { default: () => null },
|
53
|
+
collectionId: { type: String },
|
51
54
|
isFocused: { type: Boolean, default: false },
|
52
55
|
},
|
53
56
|
computed: {
|
54
57
|
selectedCollectionItem: {
|
55
58
|
get() {
|
56
|
-
return this.value
|
59
|
+
return this.value === 'any' ? null : camelizeKeys(this.value)
|
57
60
|
},
|
58
61
|
set(collectionItem) {
|
59
62
|
this.$emit('input', collectionItem ? { ...collectionItem } : null)
|
@@ -0,0 +1,85 @@
|
|
1
|
+
<template>
|
2
|
+
<div class="relative">
|
3
|
+
<div
|
4
|
+
class="absolute left-3 top-2 w-6 h-6 rounded-sm border border-gray-200"
|
5
|
+
:class="{ 'bg-checkerboard': isTransparent }"
|
6
|
+
:style="{ 'background-color': styleRgbaColor }"></div>
|
7
|
+
|
8
|
+
<span class="absolute left-11 top-1.5 font-bold text-gray-900 text-lg">#</span>
|
9
|
+
|
10
|
+
<input
|
11
|
+
type="text"
|
12
|
+
:value="inputColor"
|
13
|
+
@input="updateInput"
|
14
|
+
class="py-2 pl-14 rounded bg-gray-100 text-gray-800 focus:outline-none focus:ring placeholder-gray-500 font-normal"
|
15
|
+
:class="{
|
16
|
+
'pr-8': hasPresets,
|
17
|
+
'pr-2': !hasPresets
|
18
|
+
}"
|
19
|
+
autocomplete="off"
|
20
|
+
minlength="4"
|
21
|
+
maxlength="8"
|
22
|
+
size="7"
|
23
|
+
/>
|
24
|
+
|
25
|
+
<preset-dropdown
|
26
|
+
v-model="updatableValue"
|
27
|
+
:presets="presets"
|
28
|
+
v-if="hasPresets"
|
29
|
+
/>
|
30
|
+
</div>
|
31
|
+
</template>
|
32
|
+
|
33
|
+
<script>
|
34
|
+
import PresetDropdown from './preset-dropdown.vue'
|
35
|
+
|
36
|
+
import { colorVariableToHex, colorVariableToRgb } from '@/misc/utils'
|
37
|
+
|
38
|
+
export default {
|
39
|
+
name: 'CoreInput',
|
40
|
+
components: { PresetDropdown },
|
41
|
+
props: {
|
42
|
+
value: { type: String },
|
43
|
+
presets: {
|
44
|
+
type: Array,
|
45
|
+
default: () => [],
|
46
|
+
},
|
47
|
+
},
|
48
|
+
computed: {
|
49
|
+
inputColor() {
|
50
|
+
return this.hexColor?.replaceAll('#', '')
|
51
|
+
},
|
52
|
+
hexColor() {
|
53
|
+
return colorVariableToHex(this.value)
|
54
|
+
},
|
55
|
+
rgbColor() {
|
56
|
+
return colorVariableToRgb(this.value)
|
57
|
+
},
|
58
|
+
styleRgbaColor() {
|
59
|
+
const color = this.isTransparent ? { r: 255, g: 255, b: 255 } : (this.rgbColor || { r: 0, g: 0, b: 0 })
|
60
|
+
return `rgba(${color.r}, ${color.g}, ${color.b}, 1)`
|
61
|
+
},
|
62
|
+
isTransparent() {
|
63
|
+
return this.value === ''
|
64
|
+
},
|
65
|
+
hasPresets() {
|
66
|
+
return this.presets && this.presets.length > 0
|
67
|
+
},
|
68
|
+
updatableValue: {
|
69
|
+
get() {
|
70
|
+
return this.value
|
71
|
+
},
|
72
|
+
set(color) {
|
73
|
+
this.$emit('input', color)
|
74
|
+
},
|
75
|
+
},
|
76
|
+
},
|
77
|
+
methods: {
|
78
|
+
updateInput(event) {
|
79
|
+
var value = event.target.value
|
80
|
+
if (value.length > 0) value = `#${value}`
|
81
|
+
this.$emit('input', value)
|
82
|
+
}
|
83
|
+
}
|
84
|
+
}
|
85
|
+
</script>
|
@@ -0,0 +1,57 @@
|
|
1
|
+
<template>
|
2
|
+
<div
|
3
|
+
class="self-center cursor-pointer p-0.5 rounded-sm"
|
4
|
+
@click="selectPreset"
|
5
|
+
>
|
6
|
+
<div
|
7
|
+
class="flex items-center justify-center w-6 h-6 rounded-sm transition transform duration-200 ease-in-out hover:scale-110 select-none text-white"
|
8
|
+
:class="{
|
9
|
+
'border border-gray-300 text-gray-800': isWhite,
|
10
|
+
'bg-checkerboard': isTransparent,
|
11
|
+
}"
|
12
|
+
:style="{ 'background-color': hexColor }"
|
13
|
+
>
|
14
|
+
<icon name="ri-check-line" size="1rem" v-if="selected" />
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
</template>
|
18
|
+
|
19
|
+
<script>
|
20
|
+
import { hexToRgb, colorVariableToHex, colorVariableToRgb } from '@/misc/utils'
|
21
|
+
|
22
|
+
export default {
|
23
|
+
name: 'PresetButton',
|
24
|
+
props: {
|
25
|
+
preset: { type: String },
|
26
|
+
value: { type: String },
|
27
|
+
},
|
28
|
+
computed: {
|
29
|
+
selected() {
|
30
|
+
return !!this.value && this.value.trim().toLowerCase() === this.hexColor
|
31
|
+
},
|
32
|
+
hexColor() {
|
33
|
+
return colorVariableToHex(this.preset)
|
34
|
+
},
|
35
|
+
rgbColor() {
|
36
|
+
return colorVariableToRgb(this.preset)
|
37
|
+
},
|
38
|
+
isWhite() {
|
39
|
+
if (!this.rgbColor) return true
|
40
|
+
return this.rgbColor.r === 255 && this.rgbColor.g === 255 && this.rgbColor.b === 255
|
41
|
+
},
|
42
|
+
isTransparent() {
|
43
|
+
return this.hexColor === ''
|
44
|
+
},
|
45
|
+
borderColor() {
|
46
|
+
if (!this.selected) return 'transparent'
|
47
|
+
let color = this.isWhite ? { r: 0, g: 0, b: 0 } : this.rgbColor
|
48
|
+
return `rgba(${color.r}, ${color.g}, ${color.b}, 0.40)`
|
49
|
+
}
|
50
|
+
},
|
51
|
+
methods: {
|
52
|
+
selectPreset() {
|
53
|
+
this.$emit('input', this.hexColor)
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
</script>
|
@@ -0,0 +1,46 @@
|
|
1
|
+
<template>
|
2
|
+
<dropdown v-on="$listeners" class="absolute top-1.5 right-1">
|
3
|
+
<template v-slot:button>
|
4
|
+
<button
|
5
|
+
class="px-1 py-1 hover:text-gray-900 focus:outline-none transition-colors duration-200"
|
6
|
+
>
|
7
|
+
<icon name="ri-palette-line" size="1.25rem" />
|
8
|
+
</button>
|
9
|
+
</template>
|
10
|
+
<template v-slot:content>
|
11
|
+
<div class="flex flex-wrap gap-1">
|
12
|
+
<preset-button
|
13
|
+
v-for="preset in presets"
|
14
|
+
v-model="updatableValue"
|
15
|
+
:preset="preset"
|
16
|
+
:key="preset" />
|
17
|
+
</div>
|
18
|
+
</template>
|
19
|
+
</dropdown>
|
20
|
+
</template>
|
21
|
+
|
22
|
+
<script>
|
23
|
+
import PresetButton from './preset-button.vue'
|
24
|
+
|
25
|
+
export default {
|
26
|
+
name: 'PresetDropdown',
|
27
|
+
components: { PresetButton },
|
28
|
+
props: {
|
29
|
+
value: { type: String },
|
30
|
+
presets: {
|
31
|
+
type: Array,
|
32
|
+
default: () => [],
|
33
|
+
},
|
34
|
+
},
|
35
|
+
computed: {
|
36
|
+
updatableValue: {
|
37
|
+
get() {
|
38
|
+
return this.value
|
39
|
+
},
|
40
|
+
set(color) {
|
41
|
+
this.$emit('input', color)
|
42
|
+
},
|
43
|
+
},
|
44
|
+
}
|
45
|
+
}
|
46
|
+
</script>
|
@@ -0,0 +1,43 @@
|
|
1
|
+
<template>
|
2
|
+
<div class="pt-1">
|
3
|
+
<label
|
4
|
+
class="flex items-center justify-between font-semibold text-gray-800"
|
5
|
+
:for="name"
|
6
|
+
v-if="showLabel"
|
7
|
+
>
|
8
|
+
<span>{{ label }}</span>
|
9
|
+
<core-input :presets="presets" v-model="updatableValue" />
|
10
|
+
</label>
|
11
|
+
</div>
|
12
|
+
</template>
|
13
|
+
|
14
|
+
<script>
|
15
|
+
import { hexToRgb } from '@/misc/utils'
|
16
|
+
|
17
|
+
import CoreInput from '@/components/kit/color-input/core-input.vue'
|
18
|
+
|
19
|
+
export default {
|
20
|
+
name: 'ColorInput',
|
21
|
+
components: { CoreInput },
|
22
|
+
props: {
|
23
|
+
label: { type: String, default: 'Label' },
|
24
|
+
name: { type: String, default: 'color' },
|
25
|
+
presets: {
|
26
|
+
type: Array,
|
27
|
+
default: () => [],
|
28
|
+
},
|
29
|
+
value: { type: String },
|
30
|
+
showLabel: { type: Boolean, default: true }
|
31
|
+
},
|
32
|
+
computed: {
|
33
|
+
updatableValue: {
|
34
|
+
get() {
|
35
|
+
return this.value
|
36
|
+
},
|
37
|
+
set(color) {
|
38
|
+
this.$emit('input', color)
|
39
|
+
},
|
40
|
+
},
|
41
|
+
},
|
42
|
+
}
|
43
|
+
</script>
|
@@ -19,7 +19,7 @@ import CheckboxInput from './checkbox-input.vue'
|
|
19
19
|
import SearchInput from './search-input.vue'
|
20
20
|
import Pagination from './pagination/index.vue'
|
21
21
|
import PageIcon from './page-icon.vue'
|
22
|
-
import
|
22
|
+
import ColorInput from './color-input.vue'
|
23
23
|
import SimpleSelect from './simple-select.vue'
|
24
24
|
import CollectionItemInput from './collection-item-input.vue'
|
25
25
|
import ListItemButton from './list-item-button.vue'
|
@@ -44,7 +44,7 @@ Vue.component('checkbox-input', CheckboxInput)
|
|
44
44
|
Vue.component('search-input', SearchInput)
|
45
45
|
Vue.component('pagination', Pagination)
|
46
46
|
Vue.component('page-icon', PageIcon)
|
47
|
-
Vue.component('color-
|
47
|
+
Vue.component('color-input', ColorInput)
|
48
48
|
Vue.component('simple-select', SimpleSelect)
|
49
49
|
Vue.component('collection-item-input', CollectionItemInput)
|
50
50
|
Vue.component('list-item-button', ListItemButton)
|
@@ -8,17 +8,17 @@
|
|
8
8
|
<span v-if="isDefaultState" data-button-label>{{ defaultLabel }}</span>
|
9
9
|
|
10
10
|
<span v-if="isInProgressState" class="flex items-center justify-center">
|
11
|
-
<icon
|
11
|
+
<icon name="ri-loader-4-line" spin color="#fff" key="progress" />
|
12
12
|
<span class="ml-2" data-button-label>{{ inProgressLabel }}</span>
|
13
13
|
</span>
|
14
14
|
|
15
15
|
<span v-if="isSuccessState" class="flex items-center justify-center">
|
16
|
-
<icon
|
16
|
+
<icon name="ri-check-line" color="#fff" key="success" />
|
17
17
|
<span class="ml-2" data-button-label>{{ successLabel }}</span>
|
18
18
|
</span>
|
19
19
|
|
20
20
|
<span v-if="isFailState" class="flex items-center justify-center">
|
21
|
-
<icon
|
21
|
+
<icon name="ri-alert-line" color="#fff" key="fail" />
|
22
22
|
<span class="ml-2" data-button-label>{{ failLabel }}</span>
|
23
23
|
</span>
|
24
24
|
</button>
|
@@ -8,8 +8,9 @@
|
|
8
8
|
v-for="(tab, index) in tabs"
|
9
9
|
:key="`tab-${index}`"
|
10
10
|
type="button"
|
11
|
-
class="
|
11
|
+
class=" py-1 pb-0 px-4 block hover:text-editor-primary focus:outline-none border-b-2 z-10"
|
12
12
|
:class="{
|
13
|
+
'text-gray-500 border-transparent': index !== currentIndex,
|
13
14
|
'text-editor-primary font-medium border-editor-primary':
|
14
15
|
index === currentIndex,
|
15
16
|
}"
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<template>
|
2
|
-
<div>
|
2
|
+
<div class="space-y-1">
|
3
3
|
<label
|
4
4
|
class="block font-semibold text-gray-800"
|
5
5
|
:for="name"
|
@@ -20,7 +20,7 @@
|
|
20
20
|
:placeholder="placeholder"
|
21
21
|
@blur="blur()"
|
22
22
|
@input="updateInput"
|
23
|
-
class="block w-full
|
23
|
+
class="block w-full py-2 px-3 rounded bg-gray-100 text-gray-800 focus:outline-none focus:ring placeholder-gray-500"
|
24
24
|
autocomplete="off"
|
25
25
|
ref="input"
|
26
26
|
/>
|
@@ -8,11 +8,28 @@ export const isBlank = (object) => {
|
|
8
8
|
}
|
9
9
|
|
10
10
|
export const camelize = (str) => {
|
11
|
+
if (!str.includes('_')) return str
|
11
12
|
return str
|
12
13
|
.toLowerCase()
|
13
14
|
.replace(/[^a-zA-Z0-9]+(.)/g, (m, chr) => chr.toUpperCase())
|
14
15
|
}
|
15
16
|
|
17
|
+
export const camelizeKeys = (obj) => {
|
18
|
+
if (Array.isArray(obj)) {
|
19
|
+
return obj.map(v => camelizeKeys(v));
|
20
|
+
} else if (obj != null && obj.constructor === Object) {
|
21
|
+
return Object.keys(obj).reduce(
|
22
|
+
(result, key) => ({
|
23
|
+
...result,
|
24
|
+
[camelize(key)]: camelizeKeys(obj[key]),
|
25
|
+
}),
|
26
|
+
{},
|
27
|
+
);
|
28
|
+
}
|
29
|
+
return obj;
|
30
|
+
}
|
31
|
+
|
32
|
+
|
16
33
|
export const numberToHumanSize = (size, i18n) => {
|
17
34
|
if (isBlank(size)) return null
|
18
35
|
|
@@ -94,16 +111,29 @@ export const omitEmpty = (obj) =>
|
|
94
111
|
Object.keys(obj).forEach((key) => obj[key] === undefined && delete obj[key])
|
95
112
|
|
96
113
|
export const hexToRgb = (hex) => {
|
97
|
-
|
114
|
+
if (!hex) return null
|
115
|
+
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i.exec(hex.trim())
|
98
116
|
return result
|
99
117
|
? {
|
100
118
|
r: parseInt(result[1], 16),
|
101
119
|
g: parseInt(result[2], 16),
|
102
|
-
b: parseInt(result[3], 16),
|
120
|
+
b: result[3] ? parseInt(result[3], 16) : 0,
|
103
121
|
}
|
104
122
|
: null
|
105
123
|
}
|
106
124
|
|
125
|
+
export const colorVariableToHex = (variable) => {
|
126
|
+
if (!variable) return null
|
127
|
+
const color = (variable.startsWith('--')
|
128
|
+
? getComputedStyle(document.body).getPropertyValue(variable)
|
129
|
+
: variable).trim().toLowerCase()
|
130
|
+
return color === 'transparent' ? '' : color
|
131
|
+
}
|
132
|
+
|
133
|
+
export const colorVariableToRgb = (variable) => {
|
134
|
+
return hexToRgb(colorVariableToHex(variable))
|
135
|
+
}
|
136
|
+
|
107
137
|
// Static pages have absolute path ("/something") but regular pages have no leading slash
|
108
138
|
export const formatPath = (path) => {
|
109
139
|
return path[0] === '/' ? path : `/${path}`
|
@@ -18,10 +18,10 @@ const api = axios.create({
|
|
18
18
|
Accept: 'application/json',
|
19
19
|
},
|
20
20
|
transformResponse(data) {
|
21
|
-
//
|
21
|
+
// for "security" (even if the Rails API already camelcases the JSON),
|
22
|
+
// we also camelcase the response in the Editor.
|
22
23
|
// console.log('[DEBUG] API response', data)
|
23
24
|
return data ? camelcaseObjectDeep(JSON.parse(data)) : {}
|
24
|
-
// return data ? JSON.parse(data) : {} // LEGACY
|
25
25
|
},
|
26
26
|
})
|
27
27
|
|