Allow in-preview editing of paragraphs and headings
This commit is contained in:
parent
c6304ca321
commit
b522726afc
|
@ -589,6 +589,7 @@
|
||||||
"icon": "TextParagraph",
|
"icon": "TextParagraph",
|
||||||
"illegalChildren": ["section"],
|
"illegalChildren": ["section"],
|
||||||
"showSettingsBar": true,
|
"showSettingsBar": true,
|
||||||
|
"editable": true,
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -695,6 +696,7 @@
|
||||||
"description": "A component for displaying heading text",
|
"description": "A component for displaying heading text",
|
||||||
"illegalChildren": ["section"],
|
"illegalChildren": ["section"],
|
||||||
"showSettingsBar": true,
|
"showSettingsBar": true,
|
||||||
|
"editable": true,
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
|
|
@ -60,21 +60,32 @@
|
||||||
$: instanceKey = JSON.stringify(rawProps)
|
$: instanceKey = JSON.stringify(rawProps)
|
||||||
$: updateComponentProps(rawProps, instanceKey, $context)
|
$: updateComponentProps(rawProps, instanceKey, $context)
|
||||||
$: selected =
|
$: selected =
|
||||||
$builderStore.inBuilder &&
|
$builderStore.inBuilder && $builderStore.selectedComponentId === id
|
||||||
$builderStore.selectedComponentId === instance._id
|
|
||||||
$: inSelectedPath = $builderStore.selectedComponentPath?.includes(id)
|
$: inSelectedPath = $builderStore.selectedComponentPath?.includes(id)
|
||||||
$: evaluateConditions(enrichedSettings?._conditions)
|
$: evaluateConditions(enrichedSettings?._conditions)
|
||||||
$: componentSettings = { ...enrichedSettings, ...conditionalSettings }
|
$: componentSettings = { ...enrichedSettings, ...conditionalSettings }
|
||||||
$: renderKey = `${propsHash}-${emptyState}`
|
$: renderKey = `${propsHash}-${emptyState}`
|
||||||
|
$: editable = definition.editable
|
||||||
|
$: editing = editable && selected && $builderStore.editMode
|
||||||
|
$: draggable = interactive && !isLayout && !isScreen && !editing
|
||||||
|
$: droppable = interactive && !isLayout && !isScreen
|
||||||
|
|
||||||
// Update component context
|
// Update component context
|
||||||
$: componentStore.set({
|
$: componentStore.set({
|
||||||
id,
|
id,
|
||||||
children: children.length,
|
children: children.length,
|
||||||
styles: { ...instance._styles, id, empty: emptyState, interactive },
|
styles: {
|
||||||
|
...instance._styles,
|
||||||
|
id,
|
||||||
|
empty: emptyState,
|
||||||
|
interactive,
|
||||||
|
draggable,
|
||||||
|
editable,
|
||||||
|
},
|
||||||
empty: emptyState,
|
empty: emptyState,
|
||||||
selected,
|
selected,
|
||||||
name,
|
name,
|
||||||
|
editing,
|
||||||
})
|
})
|
||||||
|
|
||||||
const getRawProps = instance => {
|
const getRawProps = instance => {
|
||||||
|
@ -171,10 +182,6 @@
|
||||||
conditionalSettings = result.settingUpdates
|
conditionalSettings = result.settingUpdates
|
||||||
visible = nextVisible
|
visible = nextVisible
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drag and drop helper tags
|
|
||||||
$: draggable = interactive && !isLayout && !isScreen
|
|
||||||
$: droppable = interactive && !isLayout && !isScreen
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#key renderKey}
|
{#key renderKey}
|
||||||
|
@ -187,6 +194,7 @@
|
||||||
class:droppable
|
class:droppable
|
||||||
class:empty
|
class:empty
|
||||||
class:interactive
|
class:interactive
|
||||||
|
class:editing
|
||||||
data-id={id}
|
data-id={id}
|
||||||
data-name={name}
|
data-name={name}
|
||||||
>
|
>
|
||||||
|
@ -213,4 +221,7 @@
|
||||||
.draggable :global(*:hover) {
|
.draggable :global(*:hover) {
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
}
|
}
|
||||||
|
.editing :global(*:hover) {
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -36,21 +36,34 @@
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert contenteditable HTML to text and save
|
||||||
|
const updateText = e => {
|
||||||
|
const html = e.target.innerHTML
|
||||||
|
const sanitized = html
|
||||||
|
.replace(/<\/div><div>/gi, "\n")
|
||||||
|
.replace(/<div>/gi, "")
|
||||||
|
.replace(/<\/div>/gi, "")
|
||||||
|
.replace(/<br>/gi, "")
|
||||||
|
builderStore.actions.updateProp("text", sanitized)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1
|
<div
|
||||||
|
contenteditable={$component.editing}
|
||||||
use:styleable={styles}
|
use:styleable={styles}
|
||||||
class:placeholder
|
class:placeholder
|
||||||
class:bold
|
class:bold
|
||||||
class:italic
|
class:italic
|
||||||
class:underline
|
class:underline
|
||||||
class="spectrum-Heading {sizeClass} {alignClass}"
|
class="spectrum-Heading {sizeClass} {alignClass}"
|
||||||
|
on:blur={$component.editing ? updateText : null}
|
||||||
>
|
>
|
||||||
{componentText}
|
{componentText}
|
||||||
</h1>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
h1 {
|
div {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,21 +35,34 @@
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert contenteditable HTML to text and save
|
||||||
|
const updateText = e => {
|
||||||
|
const html = e.target.innerHTML
|
||||||
|
const sanitized = html
|
||||||
|
.replace(/<\/div><div>/gi, "\n")
|
||||||
|
.replace(/<div>/gi, "")
|
||||||
|
.replace(/<\/div>/gi, "")
|
||||||
|
.replace(/<br>/gi, "")
|
||||||
|
builderStore.actions.updateProp("text", sanitized)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<p
|
<div
|
||||||
|
contenteditable={$component.editing}
|
||||||
use:styleable={styles}
|
use:styleable={styles}
|
||||||
class:placeholder
|
class:placeholder
|
||||||
class:bold
|
class:bold
|
||||||
class:italic
|
class:italic
|
||||||
class:underline
|
class:underline
|
||||||
class="spectrum-Body {sizeClass} {alignClass}"
|
class="spectrum-Body {sizeClass} {alignClass}"
|
||||||
|
on:blur={$component.editing ? updateText : null}
|
||||||
>
|
>
|
||||||
{componentText}
|
{componentText}
|
||||||
</p>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
p {
|
div {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
|
@ -17,10 +17,9 @@
|
||||||
|
|
||||||
<div
|
<div
|
||||||
in:fade={{
|
in:fade={{
|
||||||
delay: transition ? 50 : 0,
|
delay: transition ? 130 : 0,
|
||||||
duration: transition ? 130 : 0,
|
duration: transition ? 130 : 0,
|
||||||
}}
|
}}
|
||||||
out:fade={{ duration: transition ? 130 : 0 }}
|
|
||||||
class="indicator"
|
class="indicator"
|
||||||
class:flipped
|
class:flipped
|
||||||
class:line
|
class:line
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
<script>
|
<script>
|
||||||
import { builderStore } from "stores"
|
import { builderStore } from "stores"
|
||||||
import IndicatorSet from "./IndicatorSet.svelte"
|
import IndicatorSet from "./IndicatorSet.svelte"
|
||||||
|
|
||||||
|
$: color = $builderStore.editMode
|
||||||
|
? "var(--spectrum-global-color-static-green-500)"
|
||||||
|
: "var(--spectrum-global-color-static-blue-600)"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<IndicatorSet
|
<IndicatorSet
|
||||||
componentId={$builderStore.selectedComponentId}
|
componentId={$builderStore.selectedComponentId}
|
||||||
color="var(--spectrum-global-color-static-blue-600)"
|
{color}
|
||||||
zIndex="910"
|
zIndex="910"
|
||||||
transition
|
transition
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { writable, derived } from "svelte/store"
|
import { writable, derived, get } from "svelte/store"
|
||||||
import Manifest from "manifest.json"
|
import Manifest from "manifest.json"
|
||||||
import { findComponentById, findComponentPathById } from "../utils/components"
|
import { findComponentById, findComponentPathById } from "../utils/components"
|
||||||
import { pingEndUser } from "../api"
|
import { pingEndUser } from "../api"
|
||||||
|
@ -18,6 +18,7 @@ const createBuilderStore = () => {
|
||||||
layout: null,
|
layout: null,
|
||||||
screen: null,
|
screen: null,
|
||||||
selectedComponentId: null,
|
selectedComponentId: null,
|
||||||
|
editMode: false,
|
||||||
previewId: null,
|
previewId: null,
|
||||||
previewType: null,
|
previewType: null,
|
||||||
selectedPath: [],
|
selectedPath: [],
|
||||||
|
@ -54,6 +55,10 @@ const createBuilderStore = () => {
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
selectComponent: id => {
|
selectComponent: id => {
|
||||||
|
if (id === get(writableStore).selectedComponentId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writableStore.update(state => ({ ...state, editMode: false }))
|
||||||
dispatchEvent("select-component", { id })
|
dispatchEvent("select-component", { id })
|
||||||
},
|
},
|
||||||
updateProp: (prop, value) => {
|
updateProp: (prop, value) => {
|
||||||
|
@ -69,10 +74,7 @@ const createBuilderStore = () => {
|
||||||
pingEndUser()
|
pingEndUser()
|
||||||
},
|
},
|
||||||
setSelectedPath: path => {
|
setSelectedPath: path => {
|
||||||
writableStore.update(state => {
|
writableStore.update(state => ({ ...state, selectedPath: path }))
|
||||||
state.selectedPath = path
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
moveComponent: (componentId, destinationComponentId, mode) => {
|
moveComponent: (componentId, destinationComponentId, mode) => {
|
||||||
dispatchEvent("move-component", {
|
dispatchEvent("move-component", {
|
||||||
|
@ -82,10 +84,10 @@ const createBuilderStore = () => {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setDragging: dragging => {
|
setDragging: dragging => {
|
||||||
writableStore.update(state => {
|
writableStore.update(state => ({ ...state, isDragging: dragging }))
|
||||||
state.isDragging = dragging
|
},
|
||||||
return state
|
setEditMode: enabled => {
|
||||||
})
|
writableStore.update(state => ({ ...state, editMode: enabled }))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -22,12 +22,7 @@ export const styleable = (node, styles = {}) => {
|
||||||
let applyNormalStyles
|
let applyNormalStyles
|
||||||
let applyHoverStyles
|
let applyHoverStyles
|
||||||
let selectComponent
|
let selectComponent
|
||||||
|
let editComponent
|
||||||
// Allow dragging if required
|
|
||||||
const parent = node.closest(".component")
|
|
||||||
if (parent && parent.classList.contains("draggable")) {
|
|
||||||
node.setAttribute("draggable", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates event listeners and applies initial styles
|
// Creates event listeners and applies initial styles
|
||||||
const setupStyles = (newStyles = {}) => {
|
const setupStyles = (newStyles = {}) => {
|
||||||
|
@ -46,6 +41,9 @@ export const styleable = (node, styles = {}) => {
|
||||||
...(newStyles.hover || {}),
|
...(newStyles.hover || {}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allow dragging if required
|
||||||
|
node.setAttribute("draggable", !!styles.draggable)
|
||||||
|
|
||||||
// Applies a style string to a DOM node
|
// Applies a style string to a DOM node
|
||||||
const applyStyles = styleString => {
|
const applyStyles = styleString => {
|
||||||
node.style = styleString
|
node.style = styleString
|
||||||
|
@ -72,13 +70,25 @@ export const styleable = (node, styles = {}) => {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handler to start editing a component (if applicable) when double
|
||||||
|
// clicking in the builder preview
|
||||||
|
editComponent = event => {
|
||||||
|
if (newStyles.interactive && newStyles.editable) {
|
||||||
|
builderStore.actions.setEditMode(true)
|
||||||
|
}
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Add listeners to toggle hover styles
|
// Add listeners to toggle hover styles
|
||||||
node.addEventListener("mouseover", applyHoverStyles)
|
node.addEventListener("mouseover", applyHoverStyles)
|
||||||
node.addEventListener("mouseout", applyNormalStyles)
|
node.addEventListener("mouseout", applyNormalStyles)
|
||||||
|
|
||||||
// Add builder preview click listener
|
// Add builder preview listeners
|
||||||
if (get(builderStore).inBuilder) {
|
if (get(builderStore).inBuilder) {
|
||||||
node.addEventListener("click", selectComponent, false)
|
node.addEventListener("click", selectComponent, false)
|
||||||
|
node.addEventListener("dblclick", editComponent, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply initial normal styles
|
// Apply initial normal styles
|
||||||
|
@ -90,9 +100,10 @@ export const styleable = (node, styles = {}) => {
|
||||||
node.removeEventListener("mouseover", applyHoverStyles)
|
node.removeEventListener("mouseover", applyHoverStyles)
|
||||||
node.removeEventListener("mouseout", applyNormalStyles)
|
node.removeEventListener("mouseout", applyNormalStyles)
|
||||||
|
|
||||||
// Remove builder preview click listener
|
// Remove builder preview listeners
|
||||||
if (get(builderStore).inBuilder) {
|
if (get(builderStore).inBuilder) {
|
||||||
node.removeEventListener("click", selectComponent)
|
node.removeEventListener("click", selectComponent)
|
||||||
|
node.removeEventListener("dblclick", editComponent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue