alinea
Version:
Headless git-based CMS
402 lines (398 loc) • 14.3 kB
JavaScript
import {
CSS,
DndContext,
DragOverlay,
KeyboardSensor,
LayoutMeasuringStrategy,
PointerSensor,
SortableContext,
closestCenter,
defaultAnimateLayoutChanges,
defaultDropAnimation,
sortableKeyboardCoordinates,
useSensor,
useSensors,
useSortable,
verticalListSortingStrategy
} from "../../chunks/chunk-TPUTTEHF.js";
import {
dist_default
} from "../../chunks/chunk-A5O3N2GS.js";
import "../../chunks/chunk-NZLE2WMY.js";
// src/field/link/LinkField.view.tsx
import { Entry } from "alinea/core/Entry";
import { Reference } from "alinea/core/Reference";
import { ListRow } from "alinea/core/shape/ListShape";
import { entries } from "alinea/core/util/Objects";
import { FormRow } from "alinea/dashboard/atoms/FormAtoms";
import { InputForm } from "alinea/dashboard/editor/InputForm";
import { useField } from "alinea/dashboard/editor/UseField";
import { useGraph } from "alinea/dashboard/hook/UseGraph";
import { useLocale } from "alinea/dashboard/hook/UseLocale";
import { useNav } from "alinea/dashboard/hook/UseNav";
import { SuspenseBoundary } from "alinea/dashboard/util/SuspenseBoundary";
import { Create } from "alinea/dashboard/view/Create";
import { IconButton } from "alinea/dashboard/view/IconButton";
import { InputLabel } from "alinea/dashboard/view/InputLabel";
import { EntryReference } from "alinea/picker/entry/EntryReference";
import { UrlReference } from "alinea/picker/url/UrlPicker";
import { TextLabel } from "alinea/ui";
import { IcRoundClose } from "alinea/ui/icons/IcRoundClose";
import IcRoundDragHandle from "alinea/ui/icons/IcRoundDragHandle";
import { IcRoundEdit } from "alinea/ui/icons/IcRoundEdit";
import { IcRoundLink } from "alinea/ui/icons/IcRoundLink";
import { IcRoundOpenInNew } from "alinea/ui/icons/IcRoundOpenInNew";
import { Sink } from "alinea/ui/Sink";
import {
Suspense,
useState
} from "react";
// src/field/link/LinkField.module.scss
var LinkField_module_default = {
"root": "alinea-LinkField",
"root-inner": "alinea-LinkField-inner",
"rootInner": "alinea-LinkField-inner",
"row": "alinea-LinkField-row",
"is-dragging": "alinea-LinkField-is-dragging",
"isDragging": "alinea-LinkField-is-dragging",
"is-overlay": "alinea-LinkField-is-overlay",
"isOverlay": "alinea-LinkField-is-overlay",
"row-header": "alinea-LinkField-row-header",
"rowHeader": "alinea-LinkField-row-header",
"row-staticHandle": "alinea-LinkField-row-staticHandle",
"rowStaticHandle": "alinea-LinkField-row-staticHandle",
"create": "alinea-LinkField-create"
};
// src/field/link/LinkField.view.tsx
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
var styles = dist_default(LinkField_module_default);
function SingleLinkInput({ field }) {
const { options, value, mutator, error } = useField(field);
const { readOnly } = options;
const [pickFrom, setPickFrom] = useState();
const picker = pickFrom ? options.pickers[pickFrom] : void 0;
function handleConfirm(link) {
if (readOnly) return;
const selected = link[0];
if (!pickFrom || !picker || !selected) return;
if (selected[Reference.type] !== pickFrom) return;
mutator.replace(selected);
setPickFrom(void 0);
}
const PickerView = picker && picker.view;
return /* @__PURE__ */ jsxs(Fragment, { children: [
PickerView && /* @__PURE__ */ jsx(SuspenseBoundary, { name: "picker", children: /* @__PURE__ */ jsx(
PickerView,
{
type: pickFrom,
options: picker.options,
selection: [value],
onConfirm: handleConfirm,
onCancel: () => setPickFrom(void 0)
}
) }),
/* @__PURE__ */ jsx(InputLabel, { ...options, error, icon: IcRoundLink, children: /* @__PURE__ */ jsx("div", { className: styles.root(), children: /* @__PURE__ */ jsx("div", { className: styles.root.inner(), children: /* @__PURE__ */ jsx(Sink.Root, { children: value && options.pickers[value[Reference.type]] ? /* @__PURE__ */ jsx(
LinkInputRow,
{
readOnly,
field,
rowId: value[Reference.id],
fields: options.pickers[value[Reference.type]].fields,
picker: options.pickers[value[Reference.type]],
reference: value,
onRemove: () => mutator.replace(void 0),
onEdit: () => setPickFrom(value[Reference.type])
}
) : /* @__PURE__ */ jsx("div", { className: styles.create(), children: /* @__PURE__ */ jsx(Create.Root, { disabled: readOnly, children: entries(options.pickers).map(([name, picker2]) => {
return /* @__PURE__ */ jsx(
Create.Button,
{
onClick: () => {
if (readOnly) return;
setPickFrom(name);
},
children: /* @__PURE__ */ jsx(TextLabel, { label: picker2.label })
},
name
);
}) }) }) }) }) }) })
] });
}
var layoutMeasuringConfig = {
strategy: LayoutMeasuringStrategy.Always
};
function MultipleLinksInput({ field }) {
const { options, value, mutator, error } = useField(field);
const { readOnly } = options;
const [pickFrom, setPickFrom] = useState();
const picker = pickFrom ? options.pickers[pickFrom[Reference.type]] : void 0;
function handleConfirm(links) {
if (!pickFrom || !picker || !links) return;
const seen = /* @__PURE__ */ new Set();
for (const link of links) {
if (link[ListRow.type] !== pickFrom[Reference.type]) continue;
seen.add(link[ListRow.id]);
const index = value.findIndex((v) => v[ListRow.id] === link[ListRow.id]);
if (index > -1) mutator.replace(link[ListRow.id], link);
else mutator.push(link);
}
if (picker.handlesMultiple)
for (const link of value) {
if (link[ListRow.type] !== pickFrom[Reference.type]) continue;
if (seen.has(link[ListRow.id])) continue;
mutator.remove(link[ListRow.id]);
}
setPickFrom(void 0);
}
const ids = value.map((row) => row[ListRow.id]);
const [dragging, setDragging] = useState(null);
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates
})
);
function handleDragStart(event) {
const { active } = event;
setDragging(value.find((row) => row[ListRow.id] === active.id) || null);
}
function handleDragEnd(event) {
const { active, over } = event;
if (!over || active.id === over.id) return;
if (!readOnly) mutator.move(ids.indexOf(active.id), ids.indexOf(over.id));
setDragging(null);
}
const showLinkPicker = options.max ? value.length < options.max : true;
const PickerView = picker && picker.view;
return /* @__PURE__ */ jsxs(Fragment, { children: [
pickFrom && PickerView && /* @__PURE__ */ jsx(SuspenseBoundary, { name: "picker", children: /* @__PURE__ */ jsx(
PickerView,
{
type: pickFrom[Reference.type],
options: picker.options,
selection: value.filter((ref) => {
if (ref[ListRow.id] === pickFrom[Reference.id]) return true;
if (picker.handlesMultiple)
return ref[ListRow.type] === pickFrom[Reference.type];
return false;
}),
onConfirm: handleConfirm,
onCancel: () => setPickFrom(void 0)
}
) }),
/* @__PURE__ */ jsx(
DndContext,
{
sensors,
collisionDetection: closestCenter,
onDragStart: handleDragStart,
onDragEnd: handleDragEnd,
layoutMeasuring: layoutMeasuringConfig,
children: /* @__PURE__ */ jsx(InputLabel, { ...options, error, icon: IcRoundLink, children: /* @__PURE__ */ jsx("div", { className: styles.root(), children: /* @__PURE__ */ jsxs("div", { className: styles.root.inner(), children: [
/* @__PURE__ */ jsx(
SortableContext,
{
items: ids,
strategy: verticalListSortingStrategy,
children: /* @__PURE__ */ jsxs(Sink.Root, { children: [
value.map((reference) => {
if (!options.pickers[reference[ListRow.type]]) return null;
const picker2 = options.pickers[reference[ListRow.type]];
const type = picker2.fields;
return /* @__PURE__ */ jsx(
LinkInputRowSortable,
{
rowId: reference[ListRow.id],
field,
fields: type,
picker: picker2,
reference,
onRemove: () => mutator.remove(reference[ListRow.id]),
onEdit: () => setPickFrom(reference),
isSortable: options.max !== 1,
readOnly,
multiple: true
},
reference[ListRow.id]
);
}),
showLinkPicker && /* @__PURE__ */ jsx("div", { className: styles.create(), children: /* @__PURE__ */ jsx(Create.Root, { disabled: readOnly, children: entries(options.pickers).map(([name, picker2]) => {
return /* @__PURE__ */ jsx(
Create.Button,
{
onClick: () => setPickFrom({ [Reference.type]: name }),
children: /* @__PURE__ */ jsx(TextLabel, { label: picker2.label })
},
name
);
}) }) })
] })
}
),
/* @__PURE__ */ jsx(
DragOverlay,
{
dropAnimation: {
...defaultDropAnimation,
dragSourceOpacity: 0.5
},
children: dragging && options.pickers[dragging[Reference.type]] ? /* @__PURE__ */ jsx(
LinkInputRow,
{
field,
rowId: dragging._id,
fields: options.pickers[dragging[Reference.type]].fields,
picker: options.pickers[dragging[Reference.type]],
reference: dragging,
onRemove: () => mutator.remove(dragging._id),
onEdit: () => setPickFrom(dragging),
isDragOverlay: true,
isSortable: options.max !== 1,
readOnly,
multiple: true
}
) : null
}
)
] }) }) })
}
)
] });
}
function animateLayoutChanges(args) {
const { isSorting, wasSorting } = args;
if (isSorting || wasSorting) return defaultAnimateLayoutChanges(args);
return true;
}
function LinkInputRowSortable(props) {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
animateLayoutChanges,
id: props.reference._id
});
const style = {
transform: CSS.Transform.toString(transform),
transition: transition || void 0
};
return /* @__PURE__ */ jsx(
LinkInputRow,
{
...props,
rootRef: setNodeRef,
style,
handle: listeners,
...attributes,
isDragging
}
);
}
function LinkInputRow({
field,
rowId,
picker,
fields,
reference,
onEdit,
onRemove,
handle,
rootRef,
isDragging,
isDragOverlay,
isSortable,
readOnly,
multiple,
...rest
}) {
const onView = useReferenceViewer();
const RowView = picker.viewRow;
const inner = /* @__PURE__ */ jsxs(
"div",
{
className: styles.row({
dragging: isDragging,
overlay: isDragOverlay
}),
ref: rootRef,
...rest,
children: [
/* @__PURE__ */ jsxs(Sink.Header, { children: [
/* @__PURE__ */ jsx(Sink.Options, { children: isSortable ? /* @__PURE__ */ jsx(
IconButton,
{
icon: IcRoundDragHandle,
...handle,
style: { cursor: handle ? "grab" : "grabbing" },
title: "Drag and drop to reorder"
}
) : /* @__PURE__ */ jsx("div", { className: styles.row.staticHandle(), children: /* @__PURE__ */ jsx(IcRoundLink, {}) }) }),
/* @__PURE__ */ jsx("div", { style: { flexGrow: 1, minWidth: 0 }, children: /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(RowView, { reference }) }) }),
!readOnly && /* @__PURE__ */ jsxs(Sink.Options, { children: [
/* @__PURE__ */ jsx(
IconButton,
{
icon: IcRoundOpenInNew,
onClick: () => onView(reference),
title: reference?._type === "image" ? "Open media file in new tab" : "Open link in new tab"
}
),
/* @__PURE__ */ jsx(
IconButton,
{
icon: IcRoundEdit,
onClick: onEdit,
title: reference?._type === "image" ? "Change image" : "Edit link"
}
),
/* @__PURE__ */ jsx(
IconButton,
{
icon: IcRoundClose,
onClick: onRemove,
title: reference?._type === "image" ? "Delete image" : "Delete link"
}
)
] })
] }),
fields && /* @__PURE__ */ jsx(Sink.Content, { children: /* @__PURE__ */ jsx(InputForm, { type: fields }) })
]
}
);
if (!fields) return inner;
return /* @__PURE__ */ jsx(
FormRow,
{
field,
type: fields,
readOnly,
rowId: multiple ? rowId : void 0,
children: inner
}
);
}
function useReferenceViewer() {
const nav = useNav();
const locale = useLocale();
const graph = useGraph();
return (reference) => {
if (UrlReference.isUrl(reference)) {
window.open(reference[UrlReference.url], "_blank");
} else if (EntryReference.isEntryReference(reference)) {
graph.first({
id: reference[EntryReference.entry],
locale: reference[Reference.type] === "entry" ? locale : null,
select: {
id: Entry.id,
workspace: Entry.workspace,
root: Entry.root
},
status: "preferDraft"
}).then((entry) => {
if (!entry) return;
window.open(`#${nav.entry(entry)}`, "_blank");
});
}
};
}
export {
MultipleLinksInput,
SingleLinkInput
};