Skip to content

Commit

Permalink
feat: add cms field deeplinking (#355)
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenle authored Jun 6, 2024
1 parent d5ad5ce commit 39d3d57
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .changeset/quick-mice-sit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@blinkk/root-cms': patch
---

feat: add cms field deeplinking
2 changes: 2 additions & 0 deletions docs/templates/Divider/Divider.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export default schema.define({
label: 'Spacer',
help: 'Optional. Vertical spacing above and below the divider.',
fields: Spacer.fields,
variant: 'drawer',
drawerOptions: {collapsed: true, inline: true},
}),
],
});
29 changes: 27 additions & 2 deletions packages/root-cms/ui/components/DocEditor/DocEditor.css
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,34 @@
opacity: 0.5;
}

.DocEditor__field.deeplink-target {
padding: 16px;
background: linear-gradient(rgb(255, 248, 197), rgb(255, 248, 197));
border: 1px solid rgba(212, 167, 44, 0.4);
border-radius: 4px;
}

.DocEditor__FieldHeader__label {
font-size: 12px;
font-weight: 600;
display: flex;
gap: 8px;
}

.DocEditor__FieldHeader__label__deeplink {
color: #CED4DA;
text-decoration: none;
opacity: 0;
transition: opacity 0.3s ease;
}

.DocEditor__FieldHeader__label:hover .DocEditor__FieldHeader__label__deeplink {
opacity: 1;
}

.DocEditor__FieldHeader__label__deeplink:hover {
text-decoration: underline;
text-underline-offset: 2px;
}

.DocEditor__FieldHeader__help {
Expand Down Expand Up @@ -120,7 +145,7 @@

.DocEditor__ObjectFieldDrawer .mantine-Accordion-contentInner {
border: none;
padding: 12px 16px;
padding: 16px;
}

.DocEditor__ArrayField__items {
Expand Down Expand Up @@ -210,7 +235,7 @@
}

.DocEditor__ArrayField__item[open] .DocEditor__ArrayField__item__body {
padding: 12px;
padding: 16px;
border: 2px solid lightblue;
}

Expand Down
88 changes: 80 additions & 8 deletions packages/root-cms/ui/components/DocEditor/DocEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,14 @@ import {
IconTrash,
IconTriangleFilled,
} from '@tabler/icons-preact';
import {useEffect, useMemo, useReducer, useState} from 'preact/hooks';
import {
useContext,
useEffect,
useMemo,
useReducer,
useRef,
useState,
} from 'preact/hooks';
import {route} from 'preact-router';

import * as schema from '../../../core/schema.js';
Expand Down Expand Up @@ -58,16 +65,25 @@ import {ReferenceField} from './fields/ReferenceField.js';
import {RichTextField} from './fields/RichTextField.js';
import {SelectField} from './fields/SelectField.js';
import {StringField} from './fields/StringField.js';
import {createContext} from 'preact';

interface DocEditorProps {
docId: string;
collection: schema.Collection;
draft: UseDraftHook;
}

const DEEPLINK_CONTEXT = createContext('');

function useDeeplink(): string {
return useContext(DEEPLINK_CONTEXT);
}

export function DocEditor(props: DocEditorProps) {
const fields = props.collection.fields || [];
const {loading, controller, saveState, data} = props.draft;
const ref = useRef<HTMLDivElement>(null);
const [deeplink, setDeeplink] = useState('');

function goBack() {
const collectionId = props.docId.split('/')[0];
Expand All @@ -87,9 +103,20 @@ export function DocEditor(props: DocEditorProps) {
collection: props.collection,
});

useEffect(() => {
if (loading) {
return;
}
const url = new URL(window.location.href);
const deeplink = url.searchParams.get('deeplink');
if (deeplink) {
setDeeplink(deeplink);
}
}, [loading]);

return (
<>
<div className="DocEditor">
<DEEPLINK_CONTEXT.Provider value={deeplink}>
<div className="DocEditor" ref={ref}>
<LoadingOverlay
visible={loading}
loaderProps={{color: 'gray', size: 'xl'}}
Expand Down Expand Up @@ -193,13 +220,17 @@ export function DocEditor(props: DocEditorProps) {
))}
</div>
</div>
</>
</DEEPLINK_CONTEXT.Provider>
);
}

DocEditor.Field = (props: FieldProps) => {
// const [targeted, setTargeted] = useState(false);
const field = props.field;
const level = props.level ?? 0;
const deeplink = useDeeplink();
const targeted = deeplink === props.deepKey;
const ref = useRef<HTMLDivElement>(null);

let showFieldHeader = !props.hideHeader && !field.hideLabel;
// The "drawer" variant shows the header within the accordion button.
Expand All @@ -211,18 +242,27 @@ DocEditor.Field = (props: FieldProps) => {
}
}

useEffect(() => {
if (targeted) {
scrollToDeeplink(ref.current!);
}
}, [targeted]);

return (
<div
className={joinClassNames(
'DocEditor__field',
field.deprecated && 'DocEditor__field--deprecated'
field.deprecated && 'DocEditor__field--deprecated',
targeted && 'deeplink-target'
)}
data-type={field.type}
data-level={level}
data-key={props.deepKey}
id={props.deepKey}
ref={ref}
>
{showFieldHeader && (
<DocEditor.FieldHeader
deepKey={props.deepKey}
label={field.label || field.id}
help={field.help}
deprecated={field.deprecated}
Expand Down Expand Up @@ -264,18 +304,34 @@ DocEditor.Field = (props: FieldProps) => {
};

DocEditor.FieldHeader = (props: {
deepKey?: string;
label?: string;
help?: string;
deprecated?: boolean;
}) => {
function deeplinkUrl() {
const url = new URL(window.location.href);
url.searchParams.set('deeplink', props.deepKey!);
return url.toString();
}
return (
<div className="DocEditor__FieldHeader">
{props.deprecated ? (
<div className="DocEditor__FieldHeader__label">
DEPRECATED: {props.label}
</div>
) : (
<div className="DocEditor__FieldHeader__label">{props.label}</div>
<div className="DocEditor__FieldHeader__label">
<span>{props.label}</span>
{props.deepKey && (
<a
className="DocEditor__FieldHeader__label__deeplink"
href={deeplinkUrl()}
>
#
</a>
)}
</div>
)}
{props.help && (
<div className="DocEditor__FieldHeader__help">{props.help}</div>
Expand Down Expand Up @@ -328,6 +384,7 @@ DocEditor.ObjectFieldDrawer = (props: FieldProps) => {
<Accordion.Item
label={
<DocEditor.FieldHeader
deepKey={props.deepKey}
label={field.label || field.id}
help={field.help}
/>
Expand Down Expand Up @@ -573,6 +630,7 @@ DocEditor.ArrayField = (props: FieldProps) => {
const draft = props.draft;
const field = props.field as schema.ArrayField;
const [value, dispatch] = useReducer(arrayReducer, {_array: []});
const deeplink = useDeeplink();

const data = value ?? {};
const order = data._array || [];
Expand Down Expand Up @@ -669,6 +727,12 @@ DocEditor.ArrayField = (props: FieldProps) => {
});
};

function itemInDeeplink(itemKey: string) {
return Boolean(
deeplink && deeplink.startsWith(`${props.deepKey}.${itemKey}`)
);
}

return (
<div className="DocEditor__ArrayField">
<div className="DocEditor__ArrayField__items">
Expand All @@ -679,7 +743,7 @@ DocEditor.ArrayField = (props: FieldProps) => {
<details
className="DocEditor__ArrayField__item"
key={key}
open={newlyAdded.includes(key)}
open={newlyAdded.includes(key) || itemInDeeplink(key)}
>
<summary className="DocEditor__ArrayField__item__header">
<div className="DocEditor__ArrayField__item__header__icon">
Expand Down Expand Up @@ -898,3 +962,11 @@ function arrayPreview(

return `item ${index}`;
}

function scrollToDeeplink(deeplinkEl: HTMLElement) {
const parent = document.querySelector('.DocumentPage__side');
if (parent) {
const offsetTop = deeplinkEl.offsetTop;
parent.scroll({top: offsetTop, behavior: 'smooth'});
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.RichTextEditor {
border: 1px solid #ced4da;
background: #fff;
padding: 4px 10px;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/root-cms/ui/pages/DocumentPage/DocumentPage.css
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
}

.DocumentPage__side__editor {
padding: 16px 12px 160px;
padding: 16px 12px 400px;
}

.DocumentPage__main {
Expand Down

0 comments on commit 39d3d57

Please sign in to comment.