refactor edit and edges

This commit is contained in:
2024-03-25 21:26:21 +02:00
parent e74e1e7956
commit 02224eb580
83 changed files with 3569 additions and 818 deletions
+32
View File
@@ -0,0 +1,32 @@
<script>
import {onMount} from "svelte";
import offcanvas from "bootstrap/js/src/offcanvas.js";
export let title = "";
let isHidden = true;
let offcanvasEl;
let offcanvasInstance;
export function show() {
offcanvasInstance.show();
}
onMount(()=>{
offcanvasInstance = new offcanvas(offcanvasEl);
});
export function hide(e) {
e.preventDefault();
offcanvasInstance.hide();
}
</script>
<div bind:this={offcanvasEl} class="offcanvas offcanvas-end" data-bs-backdrop="static" tabindex="-1"
aria-labelledby="offcanvasEditContent">
<div class="offcanvas-header">
<h5 class="offcanvas-title">{title}</h5>
<button type="button" on:click={hide} class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body" style="overflow: auto">
<slot/>
</div>
</div>
+11 -12
View File
@@ -1,11 +1,10 @@
<script>
import {getContext} from "svelte";
import Preview from "../files/Preview.svelte";
import {selectRecord} from "./functions/recordSelect.js";
import Preview from "../newPreview/Preview.svelte";
const channel = getContext("channel");
export let schema;
export let records;
export let isWritable;
export let selected = [];
@@ -17,42 +16,42 @@
</script>
<div class="row" style="max-width:1000px">
{#each records as record (record.id)}
{#each records as queryRecord (queryRecord.record.id)}
<div class="col-6 col-md-4">
<div
class="file-wrapper rounded p-2 mb-4 bg-light"
class:selected={selected.includes(record)}
class:selected={selected.includes(queryRecord)}
>
{#if isWritable}
<div class="form-check">
<input
on:change={() => select(record)}
on:change={() => select(queryRecord.record)}
class="form-check-input "
type="checkbox"
checked={selected.find(
(r) => r.id === record.id
(r) => r.id === queryRecord.record.id
)}
value={record}
value={queryRecord}
/>
</div>
{/if}
<div class="d-flex justify-content-center">
<Preview {record} size="medium"/>
<Preview record={queryRecord.record} />
</div>
<a
href="{channel.lucentUrl}/records/{record.id}"
title={record._file.path}
href="{channel.lucentUrl}/records/{queryRecord.record.id}"
title={queryRecord.record._file.path}
class="d-block text-center overflow-hidden text-nowrap my-2 "
style="
text-overflow: ellipsis;
font-size: 13px;
color: #333;
">{record._file.path}</a
">{queryRecord.record._file.path}</a
>
<span
class="lx-small-text text-muted d-block text-center"
>{record._file.mime}</span
>{queryRecord.record._file.mime}</span
>
</div>
</div>
+3 -3
View File
@@ -73,7 +73,7 @@
{#if isWritable}
<div class="form-check">
<input
on:change={() => select(queryRecord)}
on:change={() => select(queryRecord.record)}
class="form-check-input "
type="checkbox"
checked={selected.find(
@@ -89,11 +89,11 @@
class="me-2 text-decoration-none text-dark fs-6"
href="{channel.lucentUrl}/records/{queryRecord.record.id}"
target={inModal ? "_blank" : "_self"}
title={previewTitle(queryRecord)}
title={previewTitle(queryRecord.record)}
data-bs-toggle="tooltip" data-bs-placement="left"
>
{previewTitle(queryRecord)}
{previewTitle(queryRecord.record)}
</a>
</div>
<div>
+4 -4
View File
@@ -1,9 +1,9 @@
export function sortByField(from, to, edges, fieldName) {
export function sortByField(from, to, queryRecords, fieldName) {
if (from === to) {
return edges;
return queryRecords;
}
let edgesTosort = edges?.filter((ed) => ed.field === fieldName && ed.depth === 1 ) ?? [];
let remainingEdge = edges?.filter((ed) => !(ed.field === fieldName && ed.depth === 1)) ?? [];
let edgesTosort = queryRecords?.filter((qr) => qr.edge.field === fieldName && qr.edge.depth === 1 ) ?? [];
let remainingEdge = queryRecords?.filter((qr) => !(qr.edge.field === fieldName && qr.edge.depth === 1)) ?? [];
edgesTosort = array_move(edgesTosort,from, to);
return [...remainingEdge, ...edgesTosort];
+2 -2
View File
@@ -4,7 +4,7 @@
export let sortableClass = "";
// export let handle;
export let isTable = false;
export let sortableInstance;
export let sortableInstance = null;
const dispatch = createEventDispatcher();
let sortableContainer;
@@ -18,7 +18,7 @@
easing: "cubic-bezier(1, 0, 0, 1)",
onUpdate: function (/**Event*/ evt) {
// reorder(evt.oldIndex,evt.newIndex);
console.log(evt)
// console.log(evt)
dispatch("update", {
source: evt.oldIndex,
target: evt.newIndex,
+53 -49
View File
@@ -1,61 +1,65 @@
<script>
import { getContext, createEventDispatcher } from "svelte";
import PreviewEdge from "./preview/PreviewEdge.svelte";
import PreviewRecord from "./preview/PreviewRecord.svelte";
import Icon from "../common/Icon.svelte";
const dispatch = createEventDispatcher();
import File from "./includes/File.svelte";
import {previewTitle} from "../records/Preview.js";
import {getContext} from "svelte";
import Status from "../records/Status.svelte";
const channel = getContext("channel");
export let classes = "";
export let hasDelete = false;
export let record
export let edge
export let graph
export let field
let schema = channel.schemas.find((aschema) => aschema.name === record.schema);
export let record;
function remove(e) {
e.preventDefault();
dispatch("remove", record.id);
export let edge = null;
let schema = channel.schemas.find(s => s.name === record.schema);
let types = ["inline", "card"];
export let type = "inline";
if (!types.includes(type)) {
console.error("unknown preview type")
}
export let editable = false;
</script>
<!-- Preview Edge-->
<div
class="card mb-2 bg-light {classes}"
style="border-color:{schema.color ?? '#ccc'}; border-width: 1px;"
>
<div class="card-body d-flex flex-md-column">
{#if field.data}
<PreviewEdge {record} {edge} {graph} {field} {classes}/>
{:else}
<PreviewRecord {record} {graph} {classes} />
{/if}
</div>
{#if hasDelete}
<div class="position-absolute end-0" style="top:5px">
<button
class="trash-button text-dark btn btn-sm btn-link"
on:click={remove}
>
<Icon icon="trash-can"/>
</button>
</div>
<div class="preview-card">
{#if edge?.data}
<div class="preview-card-edge">Edge Data</div>
{/if}
<div class="d-flex column-gap-3">
{#if record._file}
<div>
<File {record}/>
</div>
{/if}
<div class="d-flex flex-md-column " style="line-height: 22px">
<span class="">{previewTitle(record)}</span>
<span class="d-flex gap-1 text-muted">
{#if record.status === "draft"}
<Status status={record.status}/>
{/if}
{schema.label}
</span>
</div>
</div>
</div>
<style>
.card .trash-button {
display: none;
.preview-card {
position: relative
}
.card:hover .trash-button {
display: block;
.preview-card-edge {
position: absolute;
top: -28px;
background: #fff;
padding: 0px 5px;
border: 1px solid #ccc;
border-radius: 7px;
font-size: 14px;
}
</style>
</style>
<!--{#if record._file && type === "inline"}-->
<!--&lt;!&ndash; <FilePreviewInline {record} {edge} {editable}/>&ndash;&gt;-->
<!--{:else if record._file && type === "card"}-->
<!-- <FilePreviewCard {record} {edge} {editable}/>-->
<!--{:else if type === "inline"}-->
<!--&lt;!&ndash; <DocPreviewCard {record} {edge} {editable}/>&ndash;&gt;-->
<!--{:else if type === "card"}-->
<!--&lt;!&ndash; <DocPreviewCard {record} {edge} {editable}/>&ndash;&gt;-->
<!--{/if}-->
@@ -0,0 +1,60 @@
<script>
import {imgurl} from "../../files/imageserver.js";
import {getContext} from "svelte";
import Icon from "../../common/Icon.svelte";
const channel = getContext("channel");
export let record;
export let size = "tiny";
let imageSide;
let fileSide;
let fontSize;
if (size === "large") {
imageSide = 256;
fileSide = 32;
fontSize = "20";
} else if (size === "medium") {
imageSide = 128;
fileSide = 12;
fontSize = "17";
} else if (size === "small") {
imageSide = 64;
fileSide = 12;
fontSize = "15";
} else if (size === "tiny") {
imageSide = 42;
fileSide = 12;
fontSize = "13";
}
</script>
{#if record}
{#if record._file.mime.startsWith("image")}
<!-- href={imgurl(record)} -->
<a
href="{channel.lucentUrl}/records/{record.id}"
title={record._file.path}
class="d-flex align-items-center justify-content-center "
style="width:{imageSide}px;height:{imageSide}px"
>
<img
class="rounded w-100"
src={imgurl(record)}
alt={record._file.path}
/>
</a>
{:else}
<a
href="{channel.lucentUrl}/records/{record.id}"
title={record._file.path}
class="btn btn-outline-primary btn-sm d-flex align-items-center justify-content-center"
style="width:{imageSide}px;height:{imageSide}px"
>
<Icon icon="file" width={fileSide} height={fileSide}/>
<span class="ms-2" style="font-size:{fontSize}px"
>.{record._file.path.split(".").pop()}</span
>
</a>
{/if}
{/if}
@@ -1,30 +0,0 @@
<script>
import PreviewRecord from "./PreviewRecord.svelte";
import {previewTitle} from "../../records/Preview.js";
import {getContext} from "svelte";
import EdgeModal from "../../edges/EdgeModal.svelte";
const channel = getContext("channel");
export let record
export let edge
export let graph
export let classes
export let field
let schema = channel.schemas.find((aschema) => aschema.name === record.schema);
let cardTitle = previewTitle({data:edge.data,schema:field.data}, graph);
let modal;
function edit(e){
e.preventDefault();
modal.open();
}
</script>
<button class="btn btn-primary btn-sm" on:click={edit}>{cardTitle}</button>
<PreviewRecord {record} {graph} {classes} />
<EdgeModal bind:this={modal} />
<style>
</style>
@@ -1,55 +0,0 @@
<script>
import Status from "../../records/Status.svelte";
import Preview from "../../files/Preview.svelte";
import {getContext} from "svelte";
import {previewTitle} from "../../records/Preview.js";
const channel = getContext("channel");
export let classes
export let record
export let graph
let schema = channel.schemas.find((aschema) => aschema.name === record.schema);
let cardTitle = previewTitle(record, graph);
</script>
<div
class="card mb-2 bg-light {classes}"
style="border-color:{schema.color ?? '#ccc'}; border-width: 1px;"
>
<div class="card-body d-flex">
{#if schema.type === "files"}
<div style="max-width:94px;margin-right:15px">
<Preview {record} size="small"/>
</div>
{/if}
<div class="overflow-hidden">
<a
class="title-link m-0 fs-5 text-decoration-none text-dark d-block"
href="{channel.lucentUrl}/records/{record.id}"
title={cardTitle}
>
{cardTitle}
</a>
<small class="text-muted">
{schema.label}
</small>
<small class="text-muted">
{#if record.status === "draft"}
<Status status={record.status}/>
{/if}
</small>
</div>
</div>
</div>
<style>
.title-link {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
</style>
+2 -20
View File
@@ -1,7 +1,5 @@
<script>
export let schema;
export let isCreateMode;
export let active = "";
let tabs = schema.groups?.map((group) => {
@@ -11,28 +9,12 @@
label: "Main",
name: "",
};
let graphTab = {
label: "Graph",
name: "_graph",
};
if (isCreateMode) {
tabs = [mainTab, ...tabs];
} else {
tabs = [mainTab, ...tabs, graphTab];
}
function showGraph(e) {
e.preventDefault();
active = "_graph";
}
tabs = [mainTab, ...tabs];
function changeTab(e, tabName) {
e.preventDefault();
if (tabName == "_graph") {
showGraph(e);
} else {
active = tabName;
}
active = tabName;
}
</script>
+34 -156
View File
@@ -1,112 +1,43 @@
<script>
import {afterUpdate, getContext, onMount} from "svelte";
import {isEqual} from "lodash";
import {getContext} from "svelte";
import Manager from "./Manager.svelte";
import EditHeader from "./EditHeader.svelte"
import StatusSelect from "./StatusSelect.svelte"
import FilePreview from "./FilePreview.svelte"
import ContentTabs from "./ContentTabs.svelte"
import FormField from "./FormField.svelte"
import Graph from "./Graph.svelte"
import Info from "./Info.svelte"
import ErrorAlert from "../common/ErrorAlert.svelte"
import Form from "./form/Form.svelte";
import axios from "axios";
const channel = getContext("channel");
export let schema;
export let record;
export let graph = {
records: [],
edges: []
};
export let graph = [];
export let recordHistory;
export let isCreateMode;
export let isWritable = false;
export let users;
let originalContent;
let activeContentTab = "";
$: hasUnsavedData = false;
// export let isWritable = false;
// export let users;
$: validationErrors = null;
$: errorMessage = validationErrors
? `Record submission failed. ${
Object.entries(validationErrors).length
} error(s)`
: null;
let activeFields = schema.fields.filter(
(f) => f.name !== "id"
);
onMount(() => {
setOriginalContent();
});
function setOriginalContent() {
originalContent = {
data: JSON.parse(JSON.stringify(record.data)),
schema: record.schema,
status: record.status,
_sys: JSON.parse(JSON.stringify(record._sys)),
edges: JSON.parse(JSON.stringify(graph)),
};
}
afterUpdate(() => {
hasUnsavedData = checkUnsavedData();
});
function beforeUnload(e) {
// Cancel the event as stated by the standard.
// e.preventDefault();
// console.log(hasUnsavedData);
if (hasUnsavedData) {
return (e.returnValue =
"You have unsaved changes. Are you sure you want to exit?");
}
// Chrome requires returnValue to be set.
// e.returnValue = "";
delete e["returnValue"];
// more compatibility
// return true;
return "...";
}
function checkUnsavedData() {
if (isCreateMode) {
return false;
}
return !isEqual(originalContent, {
data: record.data,
schema: record.schema,
status: record.status,
_sys: record._sys,
edges: graph,
});
}
let form;
function save(e) {
e.preventDefault();
console.log("SAVE: Attempt");
validationErrors = null;
errorMessage = "";
return new Promise(function (resolve, reject) {
if (!hasUnsavedData && !isCreateMode) {
resolve(null);
return;
}
if (!record) {
resolve(null);
return;
}
// remove trashed edges
graph.edges = graph.edges?.filter((edge) => !edge._isTrashed && edge.source === record.id);
let replaceEdges = graph
.map((queryRecord) => queryRecord.edge)
.filter((edge) => !edge._isTrashed && edge.source === record.id);
axios
.post(channel.lucentUrl + "/records", {
record: record,
edges: graph.edges,
schemaName: record.schema,
updateEdges: true,
id: record.id,
data: record.data,
edges: replaceEdges,
status: record.status,
isCreateMode: isCreateMode,
})
.then(function (response) {
@@ -115,15 +46,15 @@
if (isCreateMode) {
window.location.href = channel.lucentUrl + "/records/" + record.id;
} else {
record = response.data.rootRecords[0] ?? null;
record = response.data.record ?? null;
if (!record) {
// means trashed
hasUnsavedData = false;
window.location = channel.lucentUrl;
return;
}
graph = response.data;
setOriginalContent();
form.setOriginalData();
graph = response.data.graph;
}
resolve(null);
@@ -145,77 +76,24 @@
}
</script>
<svelte:window on:beforeunload={beforeUnload}/>
<div class="wrapper-normal transparent">
<Manager managerRecords={recordHistory} {graph}/>
<EditHeader {schema} {record} {isCreateMode} {graph} bind:activeContentTab/>
{#if !["_graph", "_info"].includes(activeContentTab) && isWritable}
<div class="shadow-lg record-status-bar">
<div
class="d-flex mt-3 mb-3 align-items-center justify-content-center"
>
<StatusSelect bind:status={record.status} {record} {schema}/>
{#if isCreateMode}
<button
class="ms-2 btn btn-primary btn-spinner"
on:click={save}
>
<span
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
/>
Create
</button>
{:else if hasUnsavedData}
<button
type="button"
class="ms-2 btn btn-primary btn-spinner"
on:click={save}
>
<span
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
/>
Save
</button>
{/if}
</div>
</div>
{/if}
<ErrorAlert message={errorMessage}/>
<FilePreview {record} {schema}/>
<div class=" mt-4" style="margin-bottom:150px">
<ContentTabs
{schema}
{isCreateMode}
bind:active={activeContentTab}
/>
{#if !["_graph", "_info"].includes(activeContentTab)}
<FilePreview {record} {schema}/>
<!-- <fieldset disabled="disabled"> -->
{#each activeFields as field (field.name)}
{#if activeContentTab === field.group}
<FormField
bind:data={record.data}
bind:graph={graph}
{field}
{schema}
{record}
{validationErrors}
{isCreateMode}
/>
{/if}
{/each}
<!-- </fieldset> -->
{:else if activeContentTab === "_graph"}
<Graph {graph} {record}/>
{:else if activeContentTab === "_info"}
<Info {record} {graph} {users} {schema}/>
{/if}
<Form
bind:this={form}
data={record.data}
status={record.status}
{graph}
{schema}
{record}
{isCreateMode}
on:save={save}
/>
<!-- <Graph {graph} {record}/>-->
<!-- <Info {record} {graph} {users} {schema}/>-->
</div>
</div>
+26 -22
View File
@@ -2,18 +2,18 @@
import {getContext} from "svelte";
import Icon from "../common/Icon.svelte";
import {previewTitle} from "./Preview";
import axios from "axios";
const channel = getContext("channel");
export let schema;
export let graph;
export let record;
export let title;
export let isCreateMode;
export let activeContentTab;
function clone(e) {
e.preventDefault();
axios.post(channel.lucentUrl + "/records/clone/" + record.id).then(response => {
window.location = channel.lucentUrl + "/records/" + response.data.id;
window.location.href = channel.lucentUrl + "/records/" + response.data.id;
}).catch(error => {
});
@@ -22,25 +22,29 @@
<h3 class="header-normal mt-5 mb-0">
<a
class="text-muted d-block text-decoration-none fs-6 mb-1"
href="{channel.lucentUrl}/content/{schema.name}"
class="text-muted d-block text-decoration-none fs-6 mb-1"
href="{channel.lucentUrl}/content/{schema.name}"
>{schema.label.toUpperCase()}</a
>
<span class="text-dark d-block">
{#if !isCreateMode}
{previewTitle( record, graph)}
{#if record}
{previewTitle(record)}
{:else}
{ title}
{/if}
{:else}
New Record
{/if}
</span>
{#if !isCreateMode}
{#if !isCreateMode && !!record}
<div class="dropdown d-inline-block">
<button
class="btn btn-link btn-sm"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
class="btn btn-link btn-sm"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<Icon icon="ellipsis"/>
</button>
@@ -48,25 +52,25 @@
<h6 class="dropdown-header">Record Actions</h6>
<a
class="dropdown-item"
href="{channel.lucentUrl}/records/new?schema={schema.name}"
class="dropdown-item"
href="{channel.lucentUrl}/records/new?schema={schema.name}"
>Create new</a
>
{#if !isCreateMode}
<a
class="dropdown-item"
on:click={clone}
href={channel.lucentUrl}
class="dropdown-item"
on:click={clone}
href={channel.lucentUrl}
>
Clone
</a>
{/if}
<a
on:click|preventDefault={(e) =>
(activeContentTab = "_info")}
class="dropdown-item"
href="{channel.lucentUrl}">Revisions</a
>
<!-- <a-->
<!-- on:click|preventDefault={(e) =>-->
<!-- (activeContentTab = "_info")}-->
<!-- class="dropdown-item"-->
<!-- href="{channel.lucentUrl}">Revisions</a-->
<!-- >-->
</div>
</div>
{/if}
+18 -28
View File
@@ -1,24 +1,23 @@
<script>
import Text from "./elements/Text.svelte";
import Slug from "./elements/Slug.svelte";
import Text from "./form/fields/Text.svelte";
import Slug from "./form/fields/Slug.svelte";
import Reference from "./elements/Reference.svelte";
import ReferenceInline from "./elements/ReferenceInline.svelte";
import Block from "./block/Block.svelte";
import Color from "./elements/Color.svelte";
import Checkbox from "./elements/Checkbox.svelte";
import Number from "./elements/Number.svelte";
import Url from "./elements/Url.svelte";
import Date from "./elements/Date.svelte";
import UUID from "./elements/UUID.svelte";
import File from "./elements/File.svelte";
import Textarea from "./elements/Textarea.svelte";
import Datetime from "./elements/Datetime.svelte";
import RichEditor from "./elements/RichEditor.svelte";
import Json from "./elements/JSON.svelte";
import Markdown from "./elements/Markdown.svelte";
import FieldHeader from "./elements/FieldHeader.svelte";
import Color from "./form/fields/Color.svelte";
import Checkbox from "./form/fields/Checkbox.svelte";
import Number from "./form/fields/Number.svelte";
import Date from "./form/fields/Date.svelte";
import UUID from "./form/fields/UUID.svelte";
import File from "./form/references/File.svelte";
import Textarea from "./form/fields/Textarea.svelte";
import Datetime from "./form/fields/Datetime.svelte";
import RichEditor from "./form/fields/RichEditor.svelte";
import Json from "./form/fields/JSON.svelte";
import Markdown from "./form/fields/Markdown.svelte";
import FieldHeader from "./form/FieldHeader.svelte";
import ReferenceTable from "./elements/ReferenceTable.svelte";
import ReferenceTags from "./elements/ReferenceTags.svelte";
import ReferenceTags from "./form/references/ReferenceTags.svelte";
const formElements = {
text: Text,
@@ -28,7 +27,6 @@
color: Color,
checkbox: Checkbox,
number: Number,
url: Url,
date: Date,
datetime: Datetime,
uuid: UUID,
@@ -48,7 +46,7 @@
</script>
<div class="card editor-field">
<FieldHeader {schema} {field} {id}/>
<FieldHeader {field} {id}/>
{#if field.info.name === "reference" && field.layout === "inline"}
<ReferenceInline
bind:graph
@@ -72,16 +70,8 @@
{field}
{validationErrors}
/>
{:else if field.info.name === "reference"}
<Reference
bind:graph
{id}
{record}
{field}
{validationErrors}
/>
{:else if field.info.name === "file"}
<File bind:graph {record} {field} {validationErrors}/>
{:else if ["reference","file"].includes(field.info.name)}
<File bind:graph {record} {field} />
{:else if field.info.name === "block"}
<Block
bind:graph
+1 -1
View File
@@ -5,7 +5,7 @@
import FormField from "./FormField.svelte";
import FilePreview from "./FilePreview.svelte";
import ContentTabs from "./ContentTabs.svelte";
import StatusSelect from "./StatusSelect.svelte";
import StatusSelect from "./form/StatusSelect.svelte";
import ErrorAlert from "../common/ErrorAlert.svelte";
const channel = getContext("channel");
+4 -19
View File
@@ -4,31 +4,17 @@ import {getContext} from "svelte";
export function previewTitle(record) {
const channel = getContext("channel");
let schema = channel.schemas.find((aSchema) => aSchema.name === record.record?.schema);
let schema = channel.schemas.find((aSchema) => aSchema.name === record?.schema);
if (!schema?.titleTemplate) {
return noTemplate(schema, record.record);
return noTemplate(schema, record);
}
let recordData = record.record.data;
let template = Mustache.parse(schema.titleTemplate);
let referencePreviews = template
.filter(segment => segment[0] === "name") // keep only template tags
.map((segment) => segment[1]) // map to fieldNames
.filter(fieldName => { // keep only references
let schemaField = schema.fields.find(f => f.name === fieldName)
return schemaField?.info.name === "reference";
}).reduce((carry, fieldName) => { // map to records
let child = record._children[fieldName].find(c => c.record.id === record.record.id);
carry[field] = previewTitle(child);
return carry;
}, {});
recordData = {...recordData, ...referencePreviews}
let render = Mustache.render(schema.titleTemplate, recordData);
let render = Mustache.render(schema.titleTemplate, record.data);
if (!render || render === "") {
return noTemplate(schema, record.record);
return noTemplate(schema, record);
}
return stripHtml(render.slice(0, 300));
@@ -42,7 +28,6 @@ function noTemplate(schema, record) {
let title = stripHtml(
record?.data[schema.fields.filter((f) => f.info.name === "text")[0]?.name]
).slice(0, 300);
if (title === "") {
return "Untitled";
}
+57 -33
View File
@@ -1,62 +1,84 @@
<script>
import Icon from "../common/Icon.svelte";
import { getContext, createEventDispatcher } from "svelte";
import Preview from "../files/Preview.svelte";
import { previewTitle } from "./Preview";
import {getContext, createEventDispatcher} from "svelte";
import {previewTitle} from "./Preview";
import Status from "./Status.svelte";
import Preview from "../newPreview/Preview.svelte";
import EdgeData from "./form/references/EdgeData.svelte";
const dispatch = createEventDispatcher();
const channel = getContext("channel");
export let graph;
export let record;
export let field;
export let edge;
export let editable = false;
export let classes = "";
export let hasDelete = false;
let edgeData;
let schema = channel.schemas.find((aschema) => aschema.name === record.schema);
let cardTitle = previewTitle(record, graph);
let cardTitle = previewTitle(record);
function remove(e) {
e.preventDefault();
dispatch("remove", record.id);
}
function edit(e) {
e.preventDefault();
edgeData.openEdit();
}
</script>
<div
class="card mb-2 bg-light {classes}"
style="border-color:{schema.color ?? '#ccc'}; border-width: 1px;"
class="card mb-2 bg-light {classes}"
style="border-color:{schema.color ?? '#ccc'}; border-width: 1px;"
>
<div class="card-body d-flex">
{#if schema.type === "files"}
<div style="max-width:94px;margin-right:15px">
<Preview {record} size="small" />
</div>
<div class="card-body">
<Preview {record} type="card"/>
{#if editable}
<EdgeData bind:this={edgeData} {field} {edge}/>
{/if}
<div class="overflow-hidden">
<a
class="title-link m-0 fs-5 text-decoration-none text-dark d-block"
href="{channel.lucentUrl}/records/{record.id}"
title={cardTitle}
>
{cardTitle}
</a>
<small class="text-muted">
{schema.label}
</small>
<small class="text-muted">
{#if record.status === "draft"}
<Status status={record.status} />
{/if}
</small>
</div>
<!-- <div class="overflow-hidden">-->
<!-- <a-->
<!-- class="title-link m-0 fs-5 text-decoration-none text-dark d-block"-->
<!-- href="{channel.lucentUrl}/records/{record.id}"-->
<!-- title={cardTitle}-->
<!-- >-->
<!-- {cardTitle}-->
<!-- </a>-->
<!-- <small class="text-muted">-->
<!-- {schema.label}-->
<!-- </small>-->
<!-- <small class="text-muted">-->
<!-- {#if record.status === "draft"}-->
<!-- <Status status={record.status} />-->
<!-- {/if}-->
<!-- </small>-->
<!-- </div>-->
</div>
{#if hasDelete}
<div class="position-absolute end-0" style="top:5px">
<div class="position-absolute d-flex end-0" style="top:5px">
{#if editable}
<button
class="trash-button text-dark btn btn-sm btn-link"
on:click={edit}
>
<Icon icon="pencil"/>
</button>
{/if}
<button
class="trash-button text-dark btn btn-sm btn-link"
on:click={remove}
><Icon icon="trash-can" />
class="trash-button text-dark btn btn-sm btn-link"
on:click={remove}
>
<Icon icon="trash-can"/>
</button>
</div>
{/if}
</div>
@@ -66,9 +88,11 @@
.card .trash-button {
display: none;
}
.card:hover .trash-button {
display: block;
}
.title-link {
overflow: hidden;
white-space: nowrap;
@@ -1,57 +0,0 @@
<script>
import {getContext} from "svelte";
import {getStatus, getStatusList} from "./StatusText";
const channel = getContext("channel");
export let status = "draft";
export let record;
export let schema;
let dropdown;
$: currentStatus = getStatus(status);
const statusList = Object.values(getStatusList());
function updateStatus(e, statusValue) {
// e.preventDefault();
status = statusValue;
dropdown.click();
}
</script>
<!-- Example split danger button -->
<div class="d-flex justify-content-between">
<div class="btn-group dropup">
<button type="button" class="btn btn-{currentStatus.bg}"
>{currentStatus.text}</button
>
<button
bind:this={dropdown}
type="button"
class="btn btn-{currentStatus.bg} dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
<div class="dropdown-header">Change status to</div>
{#each statusList as astatus}
{#if astatus.value !== status}
<button
type="button"
class="dropdown-item my-2 rounded w-100 bg-{astatus.bg} text-{astatus.color}"
on:click={(e) => updateStatus(e, astatus.value)}
>
{astatus.text}
</button>
{/if}
{/each}
</div>
</div>
{#if channel.previewTarget}
<a href="{channel.previewTargetUrl}?schema={schema.name}&id={record.id}" target="_blank" class="btn btn-info ms-3">
Preview
</a>
{/if}
</div>
@@ -4,8 +4,8 @@
import {sortByField} from "../../../edges/sortEdges";
import ReferenceInlineButtons from "../../elements/ReferenceInlineButtons.svelte"
import Sortable from "../../../libs/Sortable.svelte";
import {insertEdges} from "../../elements/reference";
import BrowseModal from "../../elements/BrowseModal.svelte";
import {insertEdges} from "../../form/references/reference.js";
import BrowseModal from "../../form/references/BrowseModal.svelte";
const channel = getContext("channel");
@@ -4,7 +4,7 @@
import {sortByField} from "../../../edges/sortEdges";
import ReferenceInlineButtons from "../../elements/ReferenceInlineButtons.svelte"
import Sortable from "../../../libs/Sortable.svelte";
import {insertEdges} from "../../elements/reference";
import {insertEdges} from "../../form/references/reference.js";
const channel = getContext("channel");
+34
View File
@@ -0,0 +1,34 @@
import { deepEqual } from 'fast-equals';
export function isEqual(obj1, obj2) {
return deepEqual(obj1, obj2);
// if (obj1 === obj2) return true;
//
// if (Array.isArray(obj1) && Array.isArray(obj2)) {
//
// if(obj1.length !== obj2.length) return false;
//
// return obj1.every((elem, index) => {
// return isEqual(elem, obj2[index]);
// })
//
//
// }
//
// if(typeof obj1 === "object" && typeof obj2 === "object" && obj1 !== null && obj2 !== null) {
// if(Array.isArray(obj1) || Array.isArray(obj2)) return false;
//
// const keys1 = Object.keys(obj1)
// const keys2 = Object.keys(obj2)
//
// if(keys1.length !== keys2.length || !keys1.every(key => keys2.includes(key))) return false;
//
// for(let key in obj1) {
// if (!isEqual(obj1[key], obj2[key])) { return false; }
// }
//
// return true;
//
// }
//
// return false;
}
@@ -1,122 +0,0 @@
<script>
import {getContext} from "svelte";
import {uniqBy} from "lodash";
import {sortByField} from "../../edges/sortEdges";
import PreviewCard from "../PreviewCard.svelte";
import Sortable from "../../libs/Sortable.svelte";
import BrowseModal from "./BrowseModal.svelte";
import Preview from "../../newPreview/Preview.svelte";
const channel = getContext("channel");
export let field;
export let record;
export let graph
let browseModal;
$: currentReferences = graph._children[field.name] ?? [];
let collections = channel.schemas.filter((aschema) =>
field.collections.includes(aschema.name)
);
function removeReference(e) {
e.preventDefault();
// graph.edges = graph.edges.filter(
// (edge) => !(edge.target === e.detail && edge.field === field.name)
// );
}
function openBrowseModal(e, schema) {
e.preventDefault();
browseModal.open(schema);
}
async function reorder(e) {
// graph.edges = await sortByField(e.detail.source, e.detail.target, graph.edges, field.name);
}
function insert(e) {
e.preventDefault();
browseModal.close();
const recordsToInsert = e.detail.records;
console.log({recordsToInsert})
const action = e.detail.action;
let newEdges = recordsToInsert.map((r) => {
return {
target: r.id,
source: record.id,
sourceSchema: record.schema,
targetSchema: r.schema,
field: field.name,
rank: ""
};
});
let replacedEdges = graph.edges ?? [];
if (action === "replace") {
replacedEdges = replacedEdges.filter((e) => e.field !== field.name);
}
// graph.records = uniqBy([...graph.records, ...recordsToInsert], (r) => r.id);
// graph.edges = uniqBy([...replacedEdges, ...newEdges], (e) => e.target + e.field);
}
</script>
<div class="mb-0">
{#if field.collections.length === 1}
<button
class="btn btn-outline-primary"
on:click={(e) => openBrowseModal(e, collections[0].name)}
>
Browse
</button>
{:else}
<div class="dropdown d-inline-block">
<button
class="btn btn-outline-primary btn-sm"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
Browse
</button>
<ul class="dropdown-menu">
{#each collections as collection}
<li>
<!-- {`${channelurl}/content/${collection.name}?parent=${record.id}&parentfield=${field.name}`} -->
<a
class="dropdown-item"
on:click={(e) =>
openBrowseModal(e, collection.name)}
href="/">{collection.label}</a
>
</li>
{/each}
</ul>
</div>
{/if}
</div>
{#if currentReferences.length > 0}
<Sortable sortableClass="row row-cols-3 mt-3" on:update={reorder}>
{#each currentReferences as reference (reference.record.id)}
<div class="col mb-3">
<PreviewCard
classes="h-100"
record={reference.record}
hasDelete={true}
on:remove={removeReference}
/>
<!-- <Preview-->
<!-- classes="h-100"-->
<!-- record={reference.record}-->
<!-- edge={reference.edge}-->
<!-- hasDelete={true}-->
<!-- {field}-->
<!-- on:remove={removeReference}-->
<!-- />-->
</div>
{/each}
</Sortable>
{/if}
<BrowseModal bind:this={browseModal} on:insert={insert}/>
@@ -1,8 +1,8 @@
<script>
import {getContext} from "svelte";
import {insertEdges} from "./reference";
import {insertEdges} from "../form/references/reference.js";
import PreviewCard from "../PreviewCard.svelte";
import {getErrorMessage} from "./errorMessage";
import {getErrorMessage} from "../form/errorMessage.js";
import {sortByField} from "../../edges/sortEdges";
import ReferenceInlineButtons from "./ReferenceInlineButtons.svelte";
import Sortable from "../../libs/Sortable.svelte";
@@ -2,7 +2,7 @@
import {getContext} from "svelte";
import {uniqBy} from "lodash";
import PreviewCardInline from "../PreviewCardInline.svelte";
import {getErrorMessage} from "./errorMessage";
import {getErrorMessage} from "../form/errorMessage.js";
import {sortByField} from "../../edges/sortEdges";
import ReferenceInlineButtons from "./ReferenceInlineButtons.svelte";
import {flip} from "svelte/animate";
@@ -2,7 +2,7 @@
import {createEventDispatcher, getContext} from "svelte";
import Icon from "../../common/Icon.svelte";
import InlineEdit from "../InlineEdit.svelte";
import BrowseModal from "./BrowseModal.svelte";
import BrowseModal from "../form/references/BrowseModal.svelte";
const dispatch = createEventDispatcher();
// export let field;
@@ -1,13 +1,13 @@
<script>
import {getContext} from "svelte";
import {previewTitle} from "../Preview";
import {getErrorMessage} from "./errorMessage";
import {getErrorMessage} from "../form/errorMessage.js";
import {sortByField} from "../../edges/sortEdges";
import ReferenceInlineButtons from "./ReferenceInlineButtons.svelte";
import Sortable from "../../libs/Sortable.svelte";
import RenderField from "../../content/RenderField.svelte";
import Icon from "../../common/Icon.svelte";
import {insertEdges} from "./reference.js";
import {insertEdges} from "../form/references/reference.js";
const channel = getContext("channel");
export let field;
@@ -1,30 +0,0 @@
<script>
import { uniqueId } from "lodash";
import { getContext } from "svelte";
const channelurl = getContext("channelurl");
export let field;
export let value;
export let schema;
let id = uniqueId();
</script>
<div class="mb-0">
<div class="d-flex justify-content-between">
<label for={id} class="form-label">{field.label}</label>
<a
class="text-decoration-none"
href="{channelurl}/schemas/{schema.name}/fields/edit/{field.name}"
><code class="text-primary opacity-50">{field.name}</code></a
>
</div>
<input
type="url"
{id}
class="form-control"
bind:value
placeholder="https://www.example.com"
/>
{#if field.help}
<small class=" text-primary opacity-50">{field.help}</small>
{/if}
</div>
@@ -1,24 +0,0 @@
import {uniqBy} from "lodash";
export function insertEdges(graph, sourceRecord, targetRecords, fieldName, action = "") {
let newEdges = targetRecords.map((r) => {
return {
target: r.id,
source: sourceRecord.id,
sourceSchema: sourceRecord.schema,
targetSchema: r.schema,
field: fieldName,
depth: 1,
rank: ""
};
});
let replacedEdges = graph.edges;
if (action === "replace") {
replacedEdges = replacedEdges.filter((edge) => edge.field !== field.name);
}
graph.records = uniqBy([...graph.records, ...targetRecords], (r) => r.id);
graph.edges = uniqBy([...replacedEdges, ...newEdges], (edge) => edge.source + edge.target + edge.field + edge.depth);
return graph;
}
+122
View File
@@ -0,0 +1,122 @@
<script>
import {afterUpdate, createEventDispatcher, onMount} from "svelte";
import {isEqual} from "../deepEquality.js";
import ContentTabs from "../ContentTabs.svelte"
import ErrorAlert from "../../common/ErrorAlert.svelte"
import FormField from "./FormField.svelte";
import ReferenceField from "./ReferenceField.svelte";
import SaveButtons from "./SaveButtons.svelte";
import EditHeader from "../EditHeader.svelte";
const dispatch = createEventDispatcher();
function save(){
dispatch("save");
}
export let title = null;
export let schema;
export let record;
export let data;
export let status = null;
export let graph = [];
export let isCreateMode;
let originalContent;
let activeContentTab = "";
$: hasUnsavedData = false;
$: validationErrors = null;
$: errorMessage = validationErrors
? `Record submission failed. ${
Object.entries(validationErrors).length
} error(s)`
: null;
export function setOriginalData(){
originalContent = {
data: JSON.parse(JSON.stringify(data)),
status: status,
edges: JSON.parse(JSON.stringify(graph.map(r => r.edge))),
};
hasUnsavedData = checkUnsavedData();
}
onMount(()=>{
setOriginalData()
})
afterUpdate(() => {
hasUnsavedData = checkUnsavedData();
});
function beforeUnload(e) {
// Cancel the event as stated by the standard.
// e.preventDefault();
// console.log(hasUnsavedData);
if (hasUnsavedData) {
return (e.returnValue =
"You have unsaved changes. Are you sure you want to exit?");
}
// Chrome requires returnValue to be set.
// e.returnValue = "";
delete e["returnValue"];
// more compatibility
// return true;
return "...";
}
function checkUnsavedData() {
if (isCreateMode) {
return false;
}
return !isEqual(originalContent, {
data: data,
status: status,
edges: graph.map(r => r.edge),
});
}
</script>
<svelte:window on:beforeunload={beforeUnload}/>
<div >
<EditHeader {schema} {record} {isCreateMode} {title}/>
<div class=" mt-4" style="margin-bottom:150px">
<SaveButtons on:save={save} status={status} {hasUnsavedData} />
<ErrorAlert message={errorMessage}/>
<ContentTabs
{schema}
bind:active={activeContentTab}
/>
<!-- <fieldset disabled="disabled"> -->
{#each schema.fields as field (field.name)}
{#if activeContentTab === field.group}
{#if ["reference", "file"].includes(field.info.name)}
<ReferenceField
bind:graph={graph}
{field}
{record}
/>
{:else}
<FormField
bind:data={data}
{field}
{validationErrors}
{isCreateMode}
/>
{/if}
{/if}
{/each}
<!-- </fieldset> -->
</div>
</div>
@@ -0,0 +1,50 @@
<script>
import Text from "./fields/Text.svelte";
import Slug from "./fields/Slug.svelte";
import Color from "./fields/Color.svelte";
import Checkbox from "./fields/Checkbox.svelte";
import Number from "./fields/Number.svelte";
import Date from "./fields/Date.svelte";
import UUID from "./fields/UUID.svelte";
import Textarea from "./fields/Textarea.svelte";
import Datetime from "./fields/Datetime.svelte";
import RichEditor from "./fields/RichEditor.svelte";
import Json from "./fields/JSON.svelte";
import Markdown from "./fields/Markdown.svelte";
import FieldHeader from "./FieldHeader.svelte";
const formElements = {
text: Text,
slug: Slug,
textarea: Textarea,
rich: RichEditor,
color: Color,
checkbox: Checkbox,
number: Number,
date: Date,
datetime: Datetime,
uuid: UUID,
json: Json,
markdown: Markdown,
};
export let field;
export let data;
export let validationErrors;
export let isCreateMode;
let formElement = formElements[field.info.name];
const uniqueId = `field-${field.name}-id`;
</script>
<div class="card editor-field">
<FieldHeader {field} id={uniqueId}/>
<svelte:component
this={formElement}
bind:value={data[field.name]}
{field}
{validationErrors}
{isCreateMode}
id={uniqueId}
/>
</div>
@@ -0,0 +1,17 @@
<script>
import File from "./references/Reference.svelte";
import FieldHeader from "./FieldHeader.svelte";
export let field;
export let record;
export let graph;
// export let validationErrors;
// export let isCreateMode;
const id = `field-${field.name}-${record.id}`;
</script>
<div class="card editor-field">
<FieldHeader {field} {id}/>
<File bind:graph {record} {field} />
</div>
@@ -0,0 +1,47 @@
<script>
import StatusSelect from "./StatusSelect.svelte"
import {createEventDispatcher} from "svelte";
export let status;
export let isCreateMode;
export let hasUnsavedData;
const dispatch = createEventDispatcher();
function save(){
dispatch("save");
}
</script>
<div class="record-status-bar">
<div
class="d-flex mt-3 mb-3 align-items-center justify-content-center"
>
<StatusSelect bind:status={status}/>
{#if isCreateMode}
<button
class="ms-2 btn btn-primary btn-spinner"
on:click={save}
>
<span
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
/>
Create
</button>
{:else if hasUnsavedData}
<button
type="button"
class="ms-2 btn btn-primary btn-spinner"
on:click={save}
>
<span
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
/>
Save
</button>
{/if}
</div>
</div>
@@ -0,0 +1,47 @@
<script>
import {getStatus, getStatusList} from "../StatusText.js";
export let status = "draft";
let dropdown;
$: currentStatus = getStatus(status);
const statusList = Object.values(getStatusList());
function updateStatus(e, statusValue) {
// e.preventDefault();
status = statusValue;
dropdown.click();
}
</script>
{#if status}
<!-- Example split danger button -->
<div class="d-flex justify-content-between">
<div class="btn-group dropup">
<button type="button" class="btn btn-{currentStatus.bg}"
>{currentStatus.text}</button
>
<button
bind:this={dropdown}
type="button"
class="btn btn-{currentStatus.bg} dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
<div class="dropdown-header">Change status to</div>
{#each statusList as astatus}
{#if astatus.value !== status}
<button
type="button"
class="dropdown-item my-2 rounded w-100 bg-{astatus.bg} text-{astatus.color}"
on:click={(e) => updateStatus(e, astatus.value)}
>
{astatus.text}
</button>
{/if}
{/each}
</div>
</div>
</div>
{/if}
@@ -1,5 +1,5 @@
<script>
import { getErrorMessage } from "./errorMessage";
import { getErrorMessage } from "../form.js";
export let id;
export let field;
export let value;
@@ -1,5 +1,5 @@
<script>
import { getErrorMessage } from "./errorMessage";
import { getErrorMessage } from "../form.js";
export let field;
export let value;
export let isCreateMode;
@@ -1,7 +1,7 @@
<script>
import {getContext} from "svelte";
import {debounce} from "lodash";
import {previewTitle} from "../Preview";
import {previewTitle} from "../../Preview.js";
const channel = getContext("channel");
export let field;
@@ -4,8 +4,8 @@
import flatpickr from "flatpickr";
import "flatpickr/dist/flatpickr.css";
import "flatpickr/dist/themes/light.css";
import {getErrorMessage} from "./errorMessage";
import Icon from "../../common/Icon.svelte";
import { getErrorMessage } from "../form.js";
import Icon from "../../../common/Icon.svelte";
export let field;
export let value;
@@ -4,8 +4,8 @@
import flatpickr from "flatpickr";
import "flatpickr/dist/flatpickr.css";
import "flatpickr/dist/themes/light.css";
import {getErrorMessage} from "./errorMessage";
import Icon from "../../common/Icon.svelte";
import { getErrorMessage } from "../form.js";
import Icon from "../../../common/Icon.svelte";
export let field;
export let value;
@@ -1,6 +1,6 @@
<script>
import Codemirror from "../../libs/Codemirror.svelte";
import { getErrorMessage } from "./errorMessage";
import Codemirror from "../../../libs/Codemirror.svelte";
import { getErrorMessage } from "../form.js";
export let value;
@@ -1,6 +1,6 @@
<script>
import Codemirror from "../../libs/CodemirrorMarkdown.svelte";
import { getErrorMessage } from "./errorMessage";
import Codemirror from "../../../libs/CodemirrorMarkdown.svelte";
import { getErrorMessage } from "../form.js";
export let value;
@@ -1,6 +1,6 @@
<script>
import Datalist from "./Datalist.svelte";
import {getErrorMessage} from "./errorMessage";
import { getErrorMessage } from "../form.js";
export let field;
export let value;
@@ -1,6 +1,6 @@
<script>
import Tinymce from "../../libs/Tinymce.svelte";
import {getErrorMessage} from "./errorMessage";
import Tinymce from "../../../libs/Tinymce.svelte";
import { getErrorMessage } from "../form.js";
export let value;
export let field;
@@ -1,5 +1,5 @@
<script>
import { getErrorMessage } from "./errorMessage";
import { getErrorMessage } from "../form.js";
export let field;
export let value;
export let isCreateMode;
@@ -1,7 +1,7 @@
<script>
import Datalist from "./Datalist.svelte";
import Selectlist from "./Selectlist.svelte";
import {getErrorMessage} from "./errorMessage";
import { getErrorMessage } from "../form.js";
export let field;
export let value;
@@ -1,6 +1,6 @@
<script>
import { onMount } from "svelte";
import { getErrorMessage } from "./errorMessage";
import { getErrorMessage } from "../form.js";
export let field;
export let value;
export let isCreateMode;
@@ -1,8 +1,8 @@
<script>
import { v4 as uuidv4 } from "uuid";
import { getContext } from "svelte";
import Icon from "../../common/Icon.svelte";
import { getErrorMessage } from "./errorMessage";
import Icon from "../../../common/Icon.svelte";
import { getErrorMessage } from "../form.js";
const channelurl = getContext("channelurl");
export let validationErrors;
$: errorMessage = getErrorMessage(validationErrors, field.name);
@@ -1,6 +1,6 @@
<script>
import {createEventDispatcher, getContext} from "svelte";
import Index from "../../content/Index.svelte";
import Index from "../../../content/Index.svelte";
const dispatch = createEventDispatcher();
const channel = getContext("channel");
@@ -0,0 +1,36 @@
<script>
import {getContext} from "svelte";
import Form from "../Form.svelte";
import OffCanvas from "../../../common/OffCanvas.svelte";
export let field;
export let edge;
let form;
let offCanvas;
const channel = getContext("channel");
let schema = channel.schemas.find(s => s.name === field.data);
export function openEdit() {
offCanvas.show();
}
function save(e){
e.preventDefault();
console.log("yo")
}
</script>
<OffCanvas bind:this={offCanvas}>
<Form
bind:this={form}
data={edge.data}
{schema}
isCreateMode={false}
on:save={save}
/>
</OffCanvas>
@@ -0,0 +1,98 @@
<script>
import {getContext} from "svelte";
import PreviewCard from "../../PreviewCard.svelte";
import Sortable from "../../../libs/Sortable.svelte";
import BrowseModal from "./BrowseModal.svelte";
import {insertEdges} from "./reference.js";
import {sortByField} from "../../../edges/sortEdges.js";
const channel = getContext("channel");
export let field;
export let record;
export let graph;
let browseModal;
$: currentReferences = graph.filter((queryRecord)=> field.name === queryRecord.edge.field) ?? [];
let collections = channel.schemas.filter((aschema) =>
field.collections.includes(aschema.name)
);
function removeReference(e) {
e.preventDefault();
graph = graph.filter(
(queryRecord) => !(queryRecord.edge.target === e.detail && queryRecord.edge.field === field.name)
);
}
function openBrowseModal(e, schema) {
e.preventDefault();
browseModal.open(schema);
}
async function reorder(e) {
graph = await sortByField(e.detail.source, e.detail.target, graph, field.name);
}
function insert(e) {
e.preventDefault();
browseModal.close();
const recordsToInsert = e.detail.records;
const action = e.detail.action;
graph = insertEdges(graph, record, recordsToInsert, field.name, action);
}
</script>
<div class="mb-0">
{#if field.collections.length === 1}
<button
class="btn btn-outline-primary"
on:click={(e) => openBrowseModal(e, collections[0].name)}
>
Browse
</button>
{:else}
<div class="dropdown d-inline-block">
<button
class="btn btn-outline-primary btn-sm"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
Browse
</button>
<ul class="dropdown-menu">
{#each collections as collection}
<li>
<!-- {`${channelurl}/content/${collection.name}?parent=${record.id}&parentfield=${field.name}`} -->
<a
class="dropdown-item"
on:click={(e) =>
openBrowseModal(e, collection.name)}
href="/">{collection.label}</a
>
</li>
{/each}
</ul>
</div>
{/if}
</div>
{#if currentReferences.length > 0}
<Sortable sortableClass="mt-3" on:update={reorder}>
{#each currentReferences as reference (reference.record.id)}
<div class="mb-1">
<PreviewCard
record={reference.record}
hasDelete={true}
editable={!!field?.data}
{field}
edge={reference.edge}
on:remove={removeReference}
/>
</div>
{/each}
</Sortable>
{/if}
<BrowseModal bind:this={browseModal} on:insert={insert}/>
@@ -1,14 +1,14 @@
<script>
import {getContext} from "svelte";
import {uniqBy, debounce} from "lodash";
import {previewTitle} from "../Preview";
import {getErrorMessage} from "./errorMessage";
import {sortByField} from "../../edges/sortEdges";
import ReferenceInlineButtons from "./ReferenceInlineButtons.svelte";
import Sortable from "../../libs/Sortable.svelte";
import RenderField from "../../content/RenderField.svelte";
import Icon from "../../common/Icon.svelte";
import Datalist from "./Datalist.svelte";
import {previewTitle} from "../../Preview.js";
import {getErrorMessage} from "../errorMessage.js";
import {sortByField} from "../../../edges/sortEdges.js";
import ReferenceInlineButtons from "../../elements/ReferenceInlineButtons.svelte";
import Sortable from "../../../libs/Sortable.svelte";
import RenderField from "../../../content/RenderField.svelte";
import Icon from "../../../common/Icon.svelte";
import Datalist from "../fields/Datalist.svelte";
import {insertEdges} from "./reference.js";
const channel = getContext("channel");
@@ -0,0 +1,23 @@
import {uniqBy} from "lodash";
export function insertEdges(existingRecords, sourceRecord, targetRecords, fieldName, action = "") {
let newQueryRecords = targetRecords.map((r) => {
return {
record: r,
edge: {
target: r.id,
source: sourceRecord.id,
sourceSchema: sourceRecord.schema,
targetSchema: r.schema,
field: fieldName,
rank: ""
}
};
});
if (action === "replace") {
existingRecords = existingRecords.filter(queryRecord => queryRecord.edge.field !== fieldName)
}
existingRecords = [...existingRecords ?? [], ...newQueryRecords];
return uniqBy(existingRecords, (r) => r.record.id);
}