removed lodash and axios

This commit is contained in:
2026-05-07 22:50:02 +03:00
parent daa4b268a6
commit a04cdd753d
24 changed files with 2191 additions and 4844 deletions
+71
View File
@@ -83,3 +83,74 @@ export function apiGet(url, options = {}) {
},
}).then((r) => r.json());
}
export function isEqual(db, ed) {
let isObject = (x) =>
typeof x === "object" && !Array.isArray(x) && x !== null;
let isArray = (x) => x?.constructor === Array;
let isEmpty = (x) => x === null || x === undefined || x == [];
const db_value = db ?? null;
const ed_value = ed ?? null;
if (isObject(db_value)) {
let keys = Object.keys(db_value);
return keys.reduce((acc, k) => {
if (acc === false) {
return false;
}
return isEqual(db_value?.[k], ed_value?.[k]);
}, true);
}
if (isArray(db_value)) {
return db_value.reduce((c, v, i) => {
if (c === false) {
return false;
}
return isEqual(v, ed_value?.[i]);
}, true);
}
if (isEmpty(db_value) && isEmpty(ed_value)) {
return true;
}
if (db_value == ed_value) {
return true;
}
return false;
// const ok = Object.keys,
// tx = typeof x,
// ty = typeof y;
// return x && y && tx === "object" && tx === ty
// ? ok(x).length === ok(y).length &&
// ok(x).every((key) => isEqual(x[key], y[key]))
// : x === y;
}
export function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
export function arrayUnique(array) {
return array.filter((value, index) => array.indexOf(value) === index);
}
export function arrayUniqueBy(items, uniqueBy) {
const ids = new Set(items.map((item) => item[uniqueBy]));
return [...ids].map((id) => items.find((i) => i[uniqueBy] === id));
}
export function arrayUniqueCb(items, aFilter) {
const cache = new Set();
return items.filter((item) => {
const cacheValue = aFilter(item);
if (cache.has(cacheValue)) return false;
cache.add(cacheValue);
return true;
});
}
-2
View File
@@ -25,10 +25,8 @@
// export let layout;
export let channel;
export let axios;
export let readableSchemas;
setContext("axios", axios);
setContext("channel", channel);
setContext(
"readableSchemas",
+28 -28
View File
@@ -2,8 +2,9 @@
import ErrorAlert from "../common/ErrorAlert.svelte";
import SpinnerButton from "../common/SpinnerButton.svelte";
import Avatar from "./Avatar.svelte";
import {getContext} from "svelte";
import { getContext } from "svelte";
import SuccessAlert from "../common/SuccessAlert.svelte";
import { apiPost } from "../../helpers";
const user = getContext("user");
const channel = getContext("channel");
@@ -16,16 +17,15 @@
e.preventDefault();
errorMessage = "";
axios
.post(channel.lucentUrl + "/account/update-name", {
name: name,
})
apiPost(channel.lucentUrl + "/account/update-name", {
name: name,
})
.then((response) => {
successAlert.show();
})
.catch((error) => {
errorMessage = error.response?.data.error;
console.log({errorMessage});
console.log({ errorMessage });
});
}
@@ -33,55 +33,55 @@
e.preventDefault();
errorMessage = "";
axios
.post(channel.lucentUrl + "/account/update-email", {
email: email,
})
apiPost(channel.lucentUrl + "/account/update-email", {
email: email,
})
.then((response) => {
successAlert.show();
})
.catch((error) => {
errorMessage = error.response?.data.error;
console.log({errorMessage});
console.log({ errorMessage });
});
}
</script>
<div class="wrapper-tiny">
<ErrorAlert message={errorMessage}/>
<ErrorAlert message={errorMessage} />
<SuccessAlert bind:this={successAlert} />
<h3 class="header-small mb-5">
<Avatar name={user.name}/>
<Avatar name={user.name} />
</h3>
<form on:submit={saveName}>
<div class="input-group mb-5">
<input
type="text"
bind:value={name}
class="form-control mb-3"
placeholder="Name"
required
type="text"
bind:value={name}
class="form-control mb-3"
placeholder="Name"
required
/>
<SpinnerButton label="Update Name"/>
<SpinnerButton label="Update Name" />
</div>
</form>
<form on:submit={saveEmail}>
<div class="input-group mb-5">
<input
type="email"
bind:value={email}
class="form-control mb-3"
placeholder="Email"
required
type="email"
bind:value={email}
class="form-control mb-3"
placeholder="Email"
required
/>
<SpinnerButton label="Update Email"/>
<SpinnerButton label="Update Email" />
</div>
</form>
<div class="list-group">
<a class="list-group-item list-group-item-action" href="{ channel.lucentUrl }/logout">Logout from this
device</a>
<a
class="list-group-item list-group-item-action"
href="{channel.lucentUrl}/logout">Logout from this device</a
>
</div>
</div>
+17 -19
View File
@@ -1,7 +1,8 @@
<script>
import { apiPost } from "../../helpers";
import ErrorAlert from "../common/ErrorAlert.svelte";
import SpinnerButton from "../common/SpinnerButton.svelte";
import {getContext} from "svelte";
import { getContext } from "svelte";
const channel = getContext("channel");
let name = "";
@@ -12,48 +13,45 @@
e.preventDefault();
errorMessage = "";
axios
.post(channel.lucentUrl + "/register", {
name: name,
email: email,
})
apiPost(channel.lucentUrl + "/register", {
name: name,
email: email,
})
.then(() => {
window.location = channel.lucentUrl + "/login";
})
.catch((error) => {
errorMessage = error.response?.data.error;
console.log({errorMessage});
console.log({ errorMessage });
});
}
</script>
<div class="wrapper-tiny">
<ErrorAlert message={errorMessage}/>
<ErrorAlert message={errorMessage} />
<form on:submit={register}>
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input
type="text"
bind:value={name}
class="form-control"
id="name"
type="text"
bind:value={name}
class="form-control"
id="name"
/>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email address</label>
<input
type="email"
bind:value={email}
class="form-control"
id="email"
type="email"
bind:value={email}
class="form-control"
id="email"
/>
</div>
<div class="text-center mt-5 d-block">
<SpinnerButton label="Register"/>
<SpinnerButton label="Register" />
</div>
</form>
</div>
+139
View File
@@ -0,0 +1,139 @@
/* eslint-disable no-undefined,no-param-reassign,no-shadow */
/**
* Throttle execution of a function. Especially useful for rate limiting
* execution of handlers on events like resize and scroll.
*
* @param {number} delay - A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher)
* are most useful.
* @param {Function} callback - A function to be executed after delay milliseconds. The `this` context and all arguments are passed through,
* as-is, to `callback` when the throttled-function is executed.
* @param {object} [options] - An object to configure options.
* @param {boolean} [options.noTrailing] - Optional, defaults to false. If noTrailing is true, callback will only execute every `delay` milliseconds
* while the throttled-function is being called. If noTrailing is false or unspecified, callback will be executed
* one final time after the last throttled-function call. (After the throttled-function has not been called for
* `delay` milliseconds, the internal counter is reset).
* @param {boolean} [options.noLeading] - Optional, defaults to false. If noLeading is false, the first throttled-function call will execute callback
* immediately. If noLeading is true, the first the callback execution will be skipped. It should be noted that
* callback will never executed if both noLeading = true and noTrailing = true.
* @param {boolean} [options.debounceMode] - If `debounceMode` is true (at begin), schedule `clear` to execute after `delay` ms. If `debounceMode` is
* false (at end), schedule `callback` to execute after `delay` ms.
*
* @returns {Function} A new, throttled, function.
*/
export function throttle(delay, callback, options) {
const {
noTrailing = false,
noLeading = false,
debounceMode = undefined,
} = options || {};
/*
* After wrapper has stopped being called, this timeout ensures that
* `callback` is executed at the proper times in `throttle` and `end`
* debounce modes.
*/
let timeoutID;
let cancelled = false;
// Keep track of the last time `callback` was executed.
let lastExec = 0;
// Function to clear existing timeout
function clearExistingTimeout() {
if (timeoutID) {
clearTimeout(timeoutID);
}
}
// Function to cancel next exec
function cancel(options) {
const { upcomingOnly = false } = options || {};
clearExistingTimeout();
cancelled = !upcomingOnly;
}
/*
* The `wrapper` function encapsulates all of the throttling / debouncing
* functionality and when executed will limit the rate at which `callback`
* is executed.
*/
function wrapper(...arguments_) {
let self = this;
let elapsed = Date.now() - lastExec;
if (cancelled) {
return;
}
// Execute `callback` and update the `lastExec` timestamp.
function exec() {
lastExec = Date.now();
callback.apply(self, arguments_);
}
/*
* If `debounceMode` is true (at begin) this is used to clear the flag
* to allow future `callback` executions.
*/
function clear() {
timeoutID = undefined;
}
if (!noLeading && debounceMode && !timeoutID) {
/*
* Since `wrapper` is being called for the first time and
* `debounceMode` is true (at begin), execute `callback`
* and noLeading != true.
*/
exec();
}
clearExistingTimeout();
if (debounceMode === undefined && elapsed > delay) {
if (noLeading) {
/*
* In throttle mode with noLeading, if `delay` time has
* been exceeded, update `lastExec` and schedule `callback`
* to execute after `delay` ms.
*/
lastExec = Date.now();
if (!noTrailing) {
timeoutID = setTimeout(debounceMode ? clear : exec, delay);
}
} else {
/*
* In throttle mode without noLeading, if `delay` time has been exceeded, execute
* `callback`.
*/
exec();
}
} else if (noTrailing !== true) {
/*
* In trailing throttle mode, since `delay` time has not been
* exceeded, schedule `callback` to execute `delay` ms after most
* recent execution.
*
* If `debounceMode` is true (at begin), schedule `clear` to execute
* after `delay` ms.
*
* If `debounceMode` is false (at end), schedule `callback` to
* execute after `delay` ms.
*/
timeoutID = setTimeout(
debounceMode ? clear : exec,
debounceMode === undefined ? delay - elapsed : delay,
);
}
}
wrapper.cancel = cancel;
// Return the wrapper function.
return wrapper;
}
export function debounce(delay, callback, options) {
const { atBegin = false } = options || {};
return throttle(delay, callback, { debounceMode: atBegin !== false });
}
+26 -28
View File
@@ -1,7 +1,6 @@
<script>
import {getContext, onMount} from "svelte";
import axios from "axios";
import { getContext, onMount } from "svelte";
import { apiPost } from "../../helpers";
const channel = getContext("channel");
export let title;
export let command;
@@ -12,59 +11,57 @@
let inProgress = false;
function connect() {
const eventSource = new EventSource(channel.lucentUrl + "/command-report-source/" + command.signature );
const eventSource = new EventSource(
channel.lucentUrl + "/command-report-source/" + command.signature,
);
eventSource.onmessage = function (event) {
inProgress = true;
const data = JSON.parse(event.data);
date = data.date;
logs = data.logs;
anchorEl.scrollIntoView()
}
anchorEl.scrollIntoView();
};
eventSource.onerror = (e) => {
console.log(e)
console.log(e);
eventSource.close();
inProgress = false;
}
};
}
function buildWebsite(e) {
e.preventDefault();
inProgress = true;
axios.post(channel.lucentUrl + "/command/" + command.signature).then(response => {
connect()
})
apiPost(channel.lucentUrl + "/command/" + command.signature).then(
(response) => {
connect();
},
);
}
onMount(() => {
connect()
})
connect();
});
</script>
<div class="common-wrapper">
<div class="lx-card mt-5">
<h3 class="header-small mb-5">{title}</h3>
<button on:click={buildWebsite} class="button primary mb-3" disabled={inProgress}>Start
<button
on:click={buildWebsite}
class="button primary mb-3"
disabled={inProgress}
>Start
</button>
<div class="mb-3">
{#if inProgress}
<span class="badge text-bg-warning">
Action in progress
</span>
<span class="badge text-bg-warning"> Action in progress </span>
{/if}
{#if !inProgress && logs}
<span class="badge text-bg-info">
Action completed
</span>
<span class="badge text-bg-info"> Action completed </span>
{/if}
</div>
<pre class="logs">{logs}
@@ -72,12 +69,13 @@
</pre>
</div>
</div>
<style>
.logs{
.logs {
max-height: 70vh;
overflow: scroll;
background: var(--p90);
color: var(--p10);
padding: 10px;
}
</style>
</style>
@@ -1,7 +1,7 @@
<script>
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
import { range } from "lodash";
import NavItem from "./NavItem.svelte";
export let inModal;
export let modalUrl;
@@ -11,7 +11,11 @@
$: totalPages = Math.ceil(total / limit);
$: currentPage = Math.ceil((skip - 1) / limit) + 1;
const range = (start, end, step = 1) =>
Array.from(
{ length: Math.ceil((end - start) / step) },
(_, i) => start + i * step,
);
$: pageRange = range(currentPage - 3, currentPage + 4).filter((i) => {
return i > 0 && i <= totalPages;
});
@@ -1,6 +1,6 @@
<script>
import { createEventDispatcher, getContext } from "svelte";
import { debounce } from "lodash";
import { apiGet, debounce } from "../../../helpers";
const channel = getContext("channel");
const dispatch = createEventDispatcher();
@@ -12,17 +12,16 @@
$: searchOptions = [];
const updateResults = debounce((e) => {
axios
.get(channel.lucentUrl + "/records/suggestions", {
params: {
schema: field.collections[0],
field: "search",
value: search,
ui: "search",
},
})
apiGet(channel.lucentUrl + "/records/suggestions", {
params: {
schema: field.collections[0],
field: "search",
value: search,
ui: "search",
},
})
.then((response) => {
searchOptions = response.data;
searchOptions = response;
})
.catch((error) => {
searchOptions = [];
-129
View File
@@ -1,129 +0,0 @@
<script>
import {onDestroy, onMount} from "svelte";
import tinymce from "tinymce/tinymce";
import "tinymce/models/dom";
import "tinymce/icons/default";
import "tinymce/themes/silver";
import "tinymce/skins/ui/oxide/skin.css";
import contentUiSkinCss from "tinymce/skins/ui/oxide/content.css?inline";
import customcss from "./tinymce.css?inline";
import "tinymce/plugins/link";
import "tinymce/plugins/code";
import "tinymce/plugins/image";
import "tinymce/plugins/table";
import "tinymce/plugins/codesample";
import "tinymce/plugins/media";
import "tinymce/plugins/lists";
import "tinymce/plugins/autoresize";
import "tinymce/plugins/wordcount";
export let value = "";
export let additionalConfig = {};
let lastVal = "";
let textareaEl;
let activeEditor;
let editorWrapper;
const plugins = [
"autoresize",
"code",
"image",
"table",
"codesample",
"link",
"lists",
"media",
"wordcount",
];
const toolbar =
"bold italic underline strikethrough removeformat | link | subscript superscript bullist numlist media image codesample table code wordcount blockquote indent outdent blocks";
onDestroy(() => {
if (activeEditor) {
activeEditor.destroy();
}
});
onMount(() => {
const config = {
target: textareaEl,
toolbar_mode: "sliding",
toolbar_sticky: true,
skin: false,
content_css: false,
content_style: contentUiSkinCss.toString() + customcss.toString(),
branding: false,
inline: false,
plugins: plugins,
contextmenu: false,
menubar: false,
statusbar: false,
entity_encoding: "raw",
convert_urls: false,
toolbar: toolbar,
image_caption: true,
relative_urls: false,
browser_spellcheck: true,
max_height: 600,
// media_poster: false,
setup: function (editor) {
activeEditor = editor;
editor.on("init", function (e) {
editor.setContent(value ?? "");
});
// editor.on("blur", function (e) {
// let content = setImageDimensions(editor.getContent());
// dispatch("editorBlur", content);
// editorWrapper.classList.remove("editorFocus");
// // return false;
// });
// editor.on("focus", function (e) {
// editorWrapper.classList.add("editorFocus");
// // return false;
// });
editor.on("change input undo redo", function (e) {
lastVal = editor.getContent();
if (lastVal !== value) {
value = lastVal;
}
});
},
};
tinymce.init({...config, ...additionalConfig});
});
export function insertMedia(info){
activeEditor.execCommand('InsertHTML', false, info.html);
}
</script>
<div bind:this={editorWrapper} class="tox-wrapper">
<div class="form-control" bind:this={textareaEl}>
{@html value}
</div>
</div>
<style>
:global(.tox:not(.tox-tinymce-inline) .tox-editor-header) {
background-color: #fff;
border-bottom: 1px solid #ced4da;
box-shadow: none;
padding: 4px 0;
transition: box-shadow 0.5s;
}
:global(.tox-tinymce) {
border: 1px solid #ced4da;
}
</style>
-37
View File
@@ -1,37 +0,0 @@
.mce-content-body .img {
max-width: 100%;
height: auto;
}
.mce-content-body{
font-size: 16px;
line-height: 20px;
}
.mce-content-body p{
margin-bottom: 14px;
&:last-child{
margin-bottom: 0;
}
}
.mce-content-body ul {
padding: 0 0 0 16px;
list-style: none outside none;
}
.mce-content-body li::before {
content: "—";
opacity: .5;
font-size: 12px;
padding-right: 6px;
vertical-align: 10%;
}
.mce-content-body li {
list-style: none;
padding: 0;
}
+35 -43
View File
@@ -3,9 +3,8 @@
import SuccessAlert from "../common/SuccessAlert.svelte";
import SpinnerButton from "../common/SpinnerButton.svelte";
import MemberSettingsCard from "./MemberSettingsCard.svelte";
import {getContext} from "svelte";
import axios from "axios";
import { getContext } from "svelte";
import { apiPost } from "../../helpers";
const channel = getContext("channel");
export let users;
@@ -23,15 +22,14 @@
function invite(newName, newEmail, newRole) {
errorMessage = "";
axios
.post(channel.lucentUrl + "/members/invite", {
name: newName,
email: newEmail,
roles: [newRole],
})
apiPost(channel.lucentUrl + "/members/invite", {
name: newName,
email: newEmail,
roles: [newRole],
})
.then((response) => {
successAlert.show("User was invited");
users = [...users, response.data.user];
users = [...users, response.user];
name = null;
email = null;
role = null;
@@ -45,14 +43,13 @@
e.preventDefault();
errorMessage = "";
axios
.post(channel.lucentUrl + "/members/update", {
id: e.detail.user,
roles: e.detail.roles,
})
apiPost(channel.lucentUrl + "/members/update", {
id: e.detail.user,
roles: e.detail.roles,
})
.then((response) => {
successAlert.show("Users updated");
users = response.data.users;
users = response.users;
})
.catch((error) => {
errorMessage = error.response?.data?.error ?? "";
@@ -63,50 +60,45 @@
<div class="common-wrapper">
<div class="lx-card mt-5">
<h3 class="header-small mb-5">Invite people</h3>
<ErrorAlert message={errorMessage}/>
<SuccessAlert bind:this={successAlert}/>
<ErrorAlert message={errorMessage} />
<SuccessAlert bind:this={successAlert} />
<form on:submit={submitInvite}>
<div class="mb-3">
<label for="inviteeName" class="form-label"
>Invitee Name</label
>
<label for="inviteeName" class="form-label">Invitee Name</label>
<input
type="text"
bind:value={name}
class="form-control"
id="inviteeName"
placeholder="Member name"
required
type="text"
bind:value={name}
class="form-control"
id="inviteeName"
placeholder="Member name"
required
/>
</div>
<div class="mb-3">
<label for="inviteeEmail" class="form-label"
>Invitee Email Address</label
>Invitee Email Address</label
>
<input
type="email"
bind:value={email}
class="form-control"
id="inviteeEmail"
placeholder="Member email"
required
type="email"
bind:value={email}
class="form-control"
id="inviteeEmail"
placeholder="Member email"
required
/>
</div>
<div class="me-3">
<select bind:value={role}>
{#each channel.roles.filter((r) => r !== "removed") as arole}
<option
value={arole}
>{arole}</option>
<option value={arole}>{arole}</option>
{/each}
</select>
</div>
<div class="mt-5 d-block text-center">
<SpinnerButton label="Invite"/>
<SpinnerButton label="Invite" />
</div>
</form>
</div>
@@ -115,10 +107,10 @@
<h3 class="header-small mb-5 mt-5">Members</h3>
{#each users as user}
<MemberSettingsCard
member={user}
roles={channel.roles}
on:update={update}
on:reinvite={(e) => invite(e.detail.email, e.detail.role)}
member={user}
roles={channel.roles}
on:update={update}
on:reinvite={(e) => invite(e.detail.email, e.detail.role)}
/>
{/each}
</div>
+8 -10
View File
@@ -1,7 +1,5 @@
<script>
import { afterUpdate, getContext, onMount } from "svelte";
import { isEqual } from "lodash";
import axios from "axios";
import EditHeader from "./header/EditHeader.svelte";
import ContentTabs from "./header/ContentTabs.svelte";
import FormField from "./FormField.svelte";
@@ -9,6 +7,7 @@
import Info from "./Info.svelte";
import ErrorAlert from "../common/ErrorAlert.svelte";
import Title from "./header/Title.svelte";
import { apiPost, isEqual } from "../../helpers";
const channel = getContext("channel");
@@ -97,12 +96,11 @@
graph.edges = graph.edges?.filter(
(edge) => !edge._isTrashed && edge.source === record.id,
);
axios
.post(channel.lucentUrl + "/records", {
record: record,
edges: graph.edges,
isCreateMode: isCreateMode,
})
apiPost(channel.lucentUrl + "/records", {
record: record,
edges: graph.edges,
isCreateMode: isCreateMode,
})
.then(function (response) {
console.log("SAVE: SAVED");
@@ -110,14 +108,14 @@
window.location =
channel.lucentUrl + "/records/" + record.id;
} else {
record = response.data.records[0] ?? null;
record = response.records[0] ?? null;
if (!record) {
// means trashed
hasUnsavedData = false;
window.location = channel.lucentUrl;
return;
}
graph = response.data;
graph = response;
setOriginalContent();
}
+2 -2
View File
@@ -1,8 +1,8 @@
<script>
import { friendlyDate } from "../../helpers";
import { friendlyDate, isEqual } from "../../helpers";
import Avatar from "../account/Avatar.svelte";
import { usernameById } from "../account/users";
import { isEqual } from "lodash";
import Icon from "../common/Icon.svelte";
import RevisionCell from "./revisions/RevisionCell.svelte";
import { getContext } from "svelte";
+8 -10
View File
@@ -6,14 +6,13 @@
onMount,
} from "svelte";
import { isEqual } from "lodash";
import FormField from "./FormField.svelte";
import FilePreview from "./FilePreview.svelte";
import ContentTabs from "./header/ContentTabs.svelte";
import ErrorAlert from "../common/ErrorAlert.svelte";
import EditHeader from "./header/EditHeader.svelte";
import axios from "axios";
import Title from "./header/Title.svelte";
import { apiPost, isEqual } from "../../helpers";
const channel = getContext("channel");
const dispatch = createEventDispatcher();
@@ -118,17 +117,16 @@
(edge) => !edge._isTrashed && edge.source === record.id,
) ?? [];
axios
.post(channel.lucentUrl + "/records", {
record: record,
edges: graph.edges,
isCreateMode: isCreateMode,
})
apiPost(channel.lucentUrl + "/records", {
record: record,
edges: graph.edges,
isCreateMode: isCreateMode,
})
.then(function (response) {
console.log("SAVE: SAVED INLINE");
record = response.data.records[0];
graph = response.data;
record = response.records[0];
graph = response;
if (!isCreateMode) {
setOriginalContent();
}
@@ -1,12 +1,11 @@
<script>
import {getContext} from "svelte";
import {insertEdges} from "./reference";
import {getErrorMessage} from "./errorMessage";
import {sortByField} from "../../edges/sortEdges";
import { getContext } from "svelte";
import { insertEdges } from "./reference";
import { getErrorMessage } from "./errorMessage";
import { sortByField } from "../../edges/sortEdges";
import ReferenceInlineButtons from "./ReferenceInlineButtons.svelte";
import Sortable from "../../libs/Sortable.svelte";
import PreviewReference from "../previews/PreviewReference.svelte";
import axios from "axios";
const channel = getContext("channel");
export let record;
@@ -15,27 +14,36 @@
export let validationErrors;
$: errorMessage = getErrorMessage(validationErrors, field.name);
$: references = graph.edges
.filter((edge) => edge.field === field.name)
.map((edge) => {
return graph.records.find((increc) => increc.id === edge.target && record.id === edge.source);
}).filter((rec) => (rec?.id ? true : false)) ?? [];
$: references =
graph.edges
.filter((edge) => edge.field === field.name)
.map((edge) => {
return graph.records.find(
(increc) =>
increc.id === edge.target && record.id === edge.source,
);
})
.filter((rec) => (rec?.id ? true : false)) ?? [];
let collections = channel.schemas.filter((aschema) =>
field.collections.includes(aschema.name)
field.collections.includes(aschema.name),
);
function removeReference(e) {
e.preventDefault();
graph.edges = graph.edges.filter(
(edge) => !(edge.target === e.detail && edge.field === field.name)
(edge) => !(edge.target === e.detail && edge.field === field.name),
);
}
function reorder(e) {
graph.edges = sortByField(e.detail.source, e.detail.target, graph.edges, field.name, references);
graph.edges = sortByField(
e.detail.source,
e.detail.target,
graph.edges,
field.name,
references,
);
}
function insert(e) {
@@ -49,9 +57,14 @@
// }).then(function (response) {
// graph = response.data.graph;
// })
graph = insertEdges(graph, record, e.detail.records, field.name, e.detail.action);
graph = insertEdges(
graph,
record,
e.detail.records,
field.name,
e.detail.action,
);
}
</script>
{#if errorMessage}
@@ -61,10 +74,10 @@
{/if}
<div class="inline-card-wrapper">
<ReferenceInlineButtons
recordId={null}
schemas={collections}
on:insert={insert}
on:save={insert}
recordId={null}
schemas={collections}
on:insert={insert}
on:save={insert}
/>
</div>
{#if references.length > 0}
@@ -72,10 +85,10 @@
{#each references as reference (reference.id)}
<div>
<PreviewReference
{graph}
record={reference}
hasDelete={true}
on:remove={removeReference}
{graph}
record={reference}
hasDelete={true}
on:remove={removeReference}
/>
</div>
{/each}
@@ -1,11 +1,11 @@
<script>
import {createEventDispatcher, getContext} from "svelte";
import { createEventDispatcher, getContext } from "svelte";
import Icon from "../../common/Icon.svelte";
import InlineEdit from "../InlineEdit.svelte";
import Dialog from "../../dialog/Dialog.svelte";
import DialogRecord from "../../dialog/DialogRecord.svelte";
import axios from "axios";
import Dropdown from "../../common/Dropdown.svelte";
import { apiGet } from "../../../helpers";
const dispatch = createEventDispatcher();
const channel = getContext("channel");
@@ -24,7 +24,7 @@
e.preventDefault();
console.log("Save inline");
inLineCreateRecord = null;
dialogRecord.close()
dialogRecord.close();
dispatch("save", {
records: e.detail.records,
after: recordId,
@@ -44,11 +44,10 @@
function createInlineReference(e, schemaUId) {
e.preventDefault();
inLineCreateRecord = null;
axios
.get(channel.lucentUrl + "/records/newInline?schema=" + schemaUId)
apiGet(channel.lucentUrl + "/records/newInline?schema=" + schemaUId)
.then((response) => {
inLineCreateRecord = response.data;
dialogRecord.open()
inLineCreateRecord = response;
dialogRecord.open();
})
.catch((error) => {
console.log(error);
@@ -57,60 +56,53 @@
</script>
{#if schemas.length > 1}
<div
style="display: flex;align-items: center;gap:4px"
>
<div style="display: flex;align-items: center;gap:4px">
<Dropdown>
<div slot="button">New</div>
{#each schemas as schema}
<button
class=" button"
on:click={(e) =>
createInlineReference(e, schema.name)}
>{schema.label}
class=" button"
on:click={(e) => createInlineReference(e, schema.name)}
>{schema.label}
</button>
{/each}
</Dropdown>
<Dropdown>
<div slot="button"> <Icon icon="magnifying-glass"/></div>
<div slot="button"><Icon icon="magnifying-glass" /></div>
{#each schemas as schema}
<button
class="button"
on:click={(e) => openBrowseModal(e, schema.name)}
>{schema.label}
class="button"
on:click={(e) => openBrowseModal(e, schema.name)}
>{schema.label}
</button>
{/each}
</Dropdown>
</div>
{:else}
<div style="display:flex;align-items: center;gap: 4px">
<button
class="button"
on:click={(e) => createInlineReference(e, schemas[0].name)}
>New
class="button"
on:click={(e) => createInlineReference(e, schemas[0].name)}
>New
</button>
<button
class="button"
on:click={(e) => openBrowseModal(e, schemas[0].name)}
>
<Icon icon="magnifying-glass"/>
</button
class="button"
on:click={(e) => openBrowseModal(e, schemas[0].name)}
>
<Icon icon="magnifying-glass" />
</button>
</div>
{/if}
<DialogRecord bind:this={dialogRecord}>
{#if inLineCreateRecord}
<InlineEdit
{...inLineCreateRecord}
isCreateMode={true}
on:cancel={(e) => (inLineCreateRecord = null)}
on:inlinesaved={save}
{...inLineCreateRecord}
isCreateMode={true}
on:cancel={(e) => (inLineCreateRecord = null)}
on:inlinesaved={save}
/>
{/if}
</DialogRecord>
<Dialog bind:this={browseModal} on:insert={insert}/>
<Dialog bind:this={browseModal} on:insert={insert} />
@@ -1,6 +1,5 @@
<script>
import { getContext } from "svelte";
import { debounce } from "lodash";
import { getErrorMessage } from "./errorMessage";
import { insertEdges } from "./reference.js";
import Icon from "../../common/Icon.svelte";
@@ -1,7 +1,6 @@
<script>
import Tinymce from "../../libs/Tinymce.svelte";
import RichEditorFiles from "./RichEditorFiles.svelte";
import {getErrorMessage} from "./errorMessage";
import { getErrorMessage } from "./errorMessage";
import Trix from "../../libs/Trix.svelte";
export let value;
@@ -18,29 +17,24 @@
readonly: field.readonly && !isCreateMode,
};
function insertMedia(e){
editor.insertMedia(e.detail)
function insertMedia(e) {
editor.insertMedia(e.detail);
}
</script>
<div class="mb-0">
<Trix {field} bind:this={editor} bind:value></Trix>
<!-- <Tinymce bind:this={editor} bind:value {additionalConfig}/>-->
<!-- <Tinymce bind:this={editor} bind:value {additionalConfig}/>-->
{#if field.collections.length > 0}
<RichEditorFiles
bind:graph
{record}
{field}
{validationErrors}
on:editor-insert={insertMedia}
>
</RichEditorFiles>
bind:graph
{record}
{field}
{validationErrors}
on:editor-insert={insertMedia}
></RichEditorFiles>
{/if}
{#if errorMessage}
<div class="invalid-feedback d-block">
{errorMessage}
+8 -10
View File
@@ -1,5 +1,4 @@
<script>
import { v4 as uuidv4 } from "uuid";
import { getContext } from "svelte";
import Icon from "../../common/Icon.svelte";
import { getErrorMessage } from "./errorMessage";
@@ -14,12 +13,11 @@
function generateId(e) {
e.preventDefault();
value = uuidv4();
value = self.crypto.randomUUID();
}
</script>
<div class="mb-0">
<div class="d-flex justify-content-between">
<input
type="text"
@@ -31,13 +29,13 @@
{readonly}
/>
{#if !readonly}
<button
class="btn btn-primary ms-2"
title="Generate a new UUIDv4"
on:click={generateId}
>
<Icon icon="dice" />
</button>
<button
class="btn btn-primary ms-2"
title="Generate a new UUIDv4"
on:click={generateId}
>
<Icon icon="dice" />
</button>
{/if}
</div>
+2 -3
View File
@@ -1,11 +1,10 @@
<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();
let id = self.crypto.randomUUID();
</script>
<div class="mb-0">
@@ -25,6 +24,6 @@
placeholder="https://www.example.com"
/>
{#if field.help}
<small class=" text-primary opacity-50">{field.help}</small>
<small class=" text-primary opacity-50">{field.help}</small>
{/if}
</div>
+32 -20
View File
@@ -1,24 +1,36 @@
import {uniqBy} from "lodash";
import { arrayUniqueCb } from "../../../helpers";
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: ""
};
});
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);
}
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;
graph.records = arrayUniqueCb(
[...graph.records, ...targetRecords],
(r) => r.id,
);
graph.edges = arrayUniqueCb(
[...replacedEdges, ...newEdges],
(edge) => edge.source + edge.target + edge.field + edge.depth,
);
return graph;
}
+1695 -4376
View File
File diff suppressed because it is too large Load Diff
+27 -32
View File
@@ -1,34 +1,29 @@
{
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"devDependencies": {
"@codemirror/commands": "^6.6.0",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-markdown": "^6.2.5",
"@codemirror/state": "^6.4.1",
"@sveltejs/vite-plugin-svelte": "^3.1.1",
"axios": "^1.7.4",
"codemirror": "^6.0.1",
"date-fns": "^3.6.0",
"flatpickr": "^4.6.13",
"fuse.js": "^7.0.0",
"htmx.org": "^2.0.1",
"install": "^0.13.0",
"laravel-vite-plugin": "^1.0.5",
"lodash": "^4.17.21",
"mustache": "^4.2.0",
"npm": "^10.8.2",
"postcss": "8.4.31",
"sass": "^1.77.8",
"sortablejs": "^1.15.2",
"svelte": "^4.2.18",
"tinymce": "^6.8.4",
"trix": "^2.1.5",
"uuid": "^10.0.0",
"vite": "5.2.6"
}
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"devDependencies": {
"@codemirror/commands": "^6.6.0",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-markdown": "^6.2.5",
"@codemirror/state": "^6.4.1",
"@sveltejs/vite-plugin-svelte": "^3.1.1",
"codemirror": "^6.0.1",
"date-fns": "^3.6.0",
"flatpickr": "^4.6.13",
"fuse.js": "^7.0.0",
"htmx.org": "^2.0.1",
"install": "^0.13.0",
"laravel-vite-plugin": "^1.0.5",
"mustache": "^4.2.0",
"postcss": "8.4.31",
"sass": "^1.77.8",
"sortablejs": "^1.15.2",
"svelte": "^4.2.18",
"trix": "^2.1.5",
"vite": "5.2.6"
}
}
+3 -6
View File
@@ -7,11 +7,8 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Lucent\Channel\ChannelService;
use Lucent\File\FileService;
use Lucent\File\ImageService;
use Lucent\Query\Query;
use Lucent\Record\InputData\RecordInputData;
use Lucent\Record\RecordService;
use Lucent\Record\Status;
use function Lucent\Response\fail;
use function Lucent\Response\ok;
@@ -27,20 +24,20 @@ class FileController extends Controller
public function fromDisk(Request $request, string $disk)
{
$imagePath = $request->route("any");
$disk = $this->fileService->loadDisk($disk);
$disk = $this->fileService->loadPublicDisk($disk);
return response()->file($disk->path($imagePath));
}
public function thumb(Request $request, string $disk)
{
$imagePath = "thumbs/" . $request->route("any");
$disk = $this->fileService->loadDisk($disk);
$disk = $this->fileService->loadPublicDisk($disk);
return response()->file($disk->path($imagePath));
}
public function download(Request $request)
{
$disk = $this->fileService->loadDisk();
$disk = $this->fileService->loadPublicDisk();
return $disk->download($request->input("path"));
}