Compare commits

..

54 Commits

Author SHA1 Message Date
lexx b19a84b6ba playing with extensible fields 2024-10-11 16:04:44 +03:00
lexx dfe4576725 removed obsolete optionsFrom 2024-10-10 20:22:44 +03:00
lexx fea7610665 removed sass 2024-10-10 19:55:21 +03:00
lexx 828fca7e8f removed some console logs 2024-10-10 17:40:29 +03:00
lexx 065b121e10 removing unused libraries 2024-10-10 17:22:24 +03:00
lexx f949852c1a removed lodash 2024-10-10 16:44:08 +03:00
lexx 986d3420cb file paths 2024-10-09 23:36:24 +03:00
lexx e9537862ca remove deleted files 2024-10-09 23:04:01 +03:00
lexx fda4bced92 WIP delete orphan files 2024-10-09 16:57:19 +03:00
lexx c3e6f9ff64 events and hooks 2024-10-09 13:47:03 +03:00
lexx b208e79d15 filters now is a folder config only 2024-10-09 02:17:44 +03:00
lexx 19e8d648fc removed source and target schema. Not useful 2024-10-08 22:52:14 +03:00
lexx 800e2746e0 cleanup 2024-10-07 16:34:53 +03:00
lexx bb27811ddf cleanup commands 2024-10-05 18:48:38 +03:00
lexx 52a1ec5c5a singleton and embed records 2024-10-05 15:19:53 +03:00
lexx 07b72b0a2c schemas as a tree replacing the isEntry behavior 2024-10-05 00:36:37 +03:00
lexx 4ef16f3630 schemas as a tree replacing the isEntry behavior 2024-10-05 00:35:38 +03:00
lexx e06e1db671 generators gix 2024-10-04 19:26:19 +03:00
lexx 174a119c2e removed setup controller 2024-10-04 19:14:26 +03:00
lexx 55db377abf removed static generator stuff 2024-09-27 23:22:22 +03:00
lexx cebe24ea67 update provider 2024-09-27 21:18:49 +03:00
lexx b729df6923 remove static generator 2024-09-27 21:12:42 +03:00
lexx 843f560710 new build 2024-09-27 17:42:49 +03:00
lexx 7574d67d80 some styling in tables 2024-09-27 16:48:05 +03:00
lexx 19931cb4d1 update files script 2024-09-27 16:27:37 +03:00
lexx 6458c1e71d rebuilding thumbnails command 2024-09-27 15:32:35 +03:00
lexx 63232585ab storage and image model 2024-09-27 14:28:20 +03:00
lexx 6d15591601 wip stograge 2024-09-20 13:39:45 +03:00
lexx 32c8378020 refactor files 2024-09-19 23:36:43 +03:00
lexx d0cd8228cc fix replacing config 2024-09-13 18:13:15 +03:00
lexx c45a3847f8 fix rich editor embed image original 2024-09-13 18:11:57 +03:00
lexx c0b3878674 file route for template generation 2024-09-13 17:16:04 +03:00
lexx f868219981 fixes and stuff 2024-09-11 16:21:51 +03:00
lexx 8ac0567e66 fix graph ignoring missing fields 2024-09-07 15:57:31 +03:00
lexx 02f8f5970a codemirror insert 2024-09-07 15:31:56 +03:00
lexx 0cd4e08716 fixing database connections 2024-09-07 13:22:58 +03:00
lexx cf3d621587 helper commands 2024-09-07 00:03:11 +03:00
lexx 6fc0a65b6f setup complete 2024-09-06 23:30:12 +03:00
lexx a73ee21568 wip setup guide 2024-09-06 21:00:15 +03:00
lexx ff54bcc2ef wip setup guide 2024-09-06 20:59:56 +03:00
lexx ab1517cc8f tabs and date in modal fix 2024-08-30 13:38:34 +03:00
lexx 9f724a3243 better report 2024-08-27 17:59:12 +03:00
lexx ae65ca47f6 commands and logs to the database 2024-08-27 17:42:06 +03:00
lexx 74d2fcc4fa build assets 2024-08-27 12:25:42 +03:00
lexx 82174afdea fixing multiple references 2024-08-27 12:24:51 +03:00
lexx ffc39f078d trix 2024-08-25 14:45:49 +03:00
lexx 7c4e19afbc tip tap and trix 2024-08-25 14:23:20 +03:00
lexx 7b10bfca1d cleanup 2024-08-24 19:57:17 +03:00
lexx 0e5ac08641 actions 2024-08-24 19:35:07 +03:00
lexx 1505aaa909 multiple commands 2024-08-24 18:51:36 +03:00
lexx d9e2c4954a refactoring of filters 2024-08-24 17:22:40 +03:00
lexx 97ad9de3d2 readme 2024-08-24 01:30:44 +03:00
lexx 9e140be0ec updated readme 2024-08-23 21:06:53 +03:00
lexx a737c2d571 configurable disks 2024-08-23 20:58:45 +03:00
271 changed files with 9777 additions and 3916 deletions
+25 -3
View File
@@ -9,8 +9,8 @@ include_toc: true
### Requirements ### Requirements
- PHP 8.2 - PHP 8.3
- Laravel 10 - Laravel 11
- Postgres or Sqlite database - Postgres or Sqlite database
- ImageMagick - ImageMagick
@@ -82,7 +82,9 @@ return [
### Database ### Database
The recommended database for small website is sqlite. But you can also use postresql The recommended database for small website is sqlite. But you can also use postresql
Make sure to delete the existing migration scripts in your database/migrations folder.
> [!CAUTION]
> Make sure to delete the existing migration scripts in your database/migrations folder.
Then run: Then run:
@@ -90,6 +92,26 @@ Then run:
php artisan migrate php artisan migrate
``` ```
### File Storage
You can use your local filesystem or s3 compatible storage. Lucent expects you to have a valid configuration inside ``config/filesystems.php``
example:
```php
return [
'disks' => [
'lucent' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
'throw' => true,
],
],
];
```
### First user ### First user
To create your first user, head to your localhost:8000/lucent To create your first user, head to your localhost:8000/lucent
+6 -7
View File
@@ -8,18 +8,17 @@
"ext-zip": "*", "ext-zip": "*",
"ext-sqlite3": "*", "ext-sqlite3": "*",
"ext-imagick": "*", "ext-imagick": "*",
"ext-pdo": "*",
"php": "^8.3", "php": "^8.3",
"guzzlehttp/guzzle": "^7.2",
"intervention/image": "^2.7",
"phpoption/phpoption": "^1.9", "phpoption/phpoption": "^1.9",
"spatie/image-optimizer": "^1.6", "spatie/image-optimizer": "^1.6",
"staudenmeir/laravel-cte": "^1.0", "staudenmeir/laravel-cte": "^1.10",
"ext-pdo": "*", "intervention/image": "^3.8",
"mustache/mustache": "^2.14" "guzzlehttp/guzzle": "^7.9"
}, },
"require-dev": { "require-dev": {
"phpstan/phpstan": "^1.8", "laravel/framework": "^10.48",
"laravel/framework": "^10.10" "phpstan/phpstan": "^1.12"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
Generated
+287 -351
View File
File diff suppressed because it is too large Load Diff
+24
View File
@@ -0,0 +1,24 @@
<?php
return [
"env" => env("LUCENT_ENV", "production"),
"schemas_path" => env("LUCENT_SCHEMAS_PATH", "resources/lucent/schemas"),
"image_filters_path" => "app/Filters",
"database" => env('LUCENT_DB_CONNECTION', env('DB_CONNECTION', "sqlite")),
"name" => env("LUCENT_NAME", "Stoic"),
"url" => env("LUCENT_URL", env('APP_URL')),
"preview_target" => env("LUCENT_PREVIEW_TARGET", "previewTarget"),
/*
* Make available laravel artisan commands for admin users
* example:
* [
* "command1:signature" => "Description 1"
* "command2:signature" => "Description 2"
* ]
*
* */
"commands" => [],
"can_invite" => ["admin"],
"can_run_commands" => ["admin"],
"system_user_id" => ""
];
+5 -11
View File
@@ -24,7 +24,8 @@ There are 3 types of schemas
- **fields**: The list of your fields. Look the field reference for more - **fields**: The list of your fields. Look the field reference for more
- **isEntry**: If this schema is important, it will show be visible on the main the sidebar. Default: false _optional_ - **isEntry**: If this schema is important, it will show be visible on the main the sidebar. Default: false _optional_
- **sortBy**: The default sorting in the content browser _optional_ - **sortBy**: The default sorting in the content browser _optional_
- **titleTemplate**: Mustache code to customize the preview field _optional_ - **cardTitle**: Mustache code to customize the preview field _optional_
- **cardImage**: Field name of image you want to use as a preview image _optional_
- **revisions**: How many revisions are going to be kept for each record _optional_ - **revisions**: How many revisions are going to be kept for each record _optional_
- **read**: Array of user groups that have read permissions _optional_ - **read**: Array of user groups that have read permissions _optional_
- **write**: Array of user groups that have write permissions _optional_ - **write**: Array of user groups that have write permissions _optional_
@@ -40,20 +41,12 @@ There are 3 types of schemas
- **fields**: The list of your fields. Look the field reference for more - **fields**: The list of your fields. Look the field reference for more
- **isEntry**: If this schema is important, it will show be visible on the main the sidebar _optional_ - **isEntry**: If this schema is important, it will show be visible on the main the sidebar _optional_
- **sortBy**: The default sorting in the content browser _optional_ - **sortBy**: The default sorting in the content browser _optional_
- **titleTemplate**: Mustache code to customize the preview field _optional_ - **cardTitle**: Mustache code to customize the preview field _optional_
- **revisions**: How many revisions are going to be kept for each record _optional_ - **revisions**: How many revisions are going to be kept for each record _optional_
- **read**: Array of user groups that have read permissions _optional_ - **read**: Array of user groups that have read permissions _optional_
- **write**: Array of user groups that have write permissions _optional_ - **write**: Array of user groups that have write permissions _optional_
## Block Reference
- **name**: The ID of the collection. Camelcase and plural is the recommended format ex. blogPosts
- **label**: The friendly name of the schema
- **type**: The type of the collection. Should be "block"
- **fields**: The list of your fields. Look the field reference for more
A full Collection example without the fields: A full Collection example without the fields:
```json ```json
@@ -74,7 +67,8 @@ A full Collection example without the fields:
"SEO" "SEO"
], ],
"sortBy": "-_sys.createdAt", "sortBy": "-_sys.createdAt",
"titleTemplate": "{{name}} {{slug}}", "schemaTitle": "{{name}} {{slug}}",
"schemaImage": "cover",
"revisions": 15, "revisions": 15,
"read": [ "read": [
"admin", "admin",
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,11 +1,11 @@
{ {
"main.js": { "main.js": {
"file": "assets/main-BtcBvcC_.js", "file": "assets/main-C4XTQmaY.js",
"name": "main", "name": "main",
"src": "main.js", "src": "main.js",
"isEntry": true, "isEntry": true,
"css": [ "css": [
"assets/main-BWRwkaBb.css" "assets/main-BJijircB.css"
] ]
} }
} }
+11
View File
@@ -0,0 +1,11 @@
export function debounce(callback, wait) {
let timeoutId = null;
return (...args) => {
window.clearTimeout(timeoutId);
timeoutId = window.setTimeout(() => {
callback.apply(null, args);
}, wait);
};
}
+19 -3
View File
@@ -1,18 +1,18 @@
import {formatDistanceToNow, parseJSON, format, parse} from "date-fns"; import {format, formatDistanceToNow, parseJSON} from "date-fns";
export function friendlyDate(date) { export function friendlyDate(date) {
return formatDistanceToNow(parseJSON(date), {addSuffix: true}); return formatDistanceToNow(parseJSON(date), {addSuffix: true});
} }
export function readableDate(date) { export function readableDate(date) {
if(!date){ if (!date) {
return ""; return "";
} }
return format(parseJSON(date), "dd MMM yyyy"); return format(parseJSON(date), "dd MMM yyyy");
} }
export function readableDatetime(date) { export function readableDatetime(date) {
if(!date){ if (!date) {
return ""; return "";
} }
@@ -50,3 +50,19 @@ export function clickOutside(node) {
} }
} }
export function uniqueBy(list, callback) {
const itemMap = list.reduce((c, item) => {
c[callback(item)] = item;
return c;
}, {});
return Object.values(itemMap);
}
export function range(start, end) {
var ans = [];
for (let i = start; i <= end; i++) {
ans.push(i);
}
return ans;
}
-1
View File
@@ -1,5 +1,4 @@
import {axiosInstance} from "./bootstrap"; import {axiosInstance} from "./bootstrap";
import "../sass/app.scss";
import Account from "./svelte/Account.svelte"; import Account from "./svelte/Account.svelte";
import Channel from "./svelte/Channel.svelte"; import Channel from "./svelte/Channel.svelte";
import Mustache from "mustache"; import Mustache from "mustache";
+1 -1
View File
@@ -22,7 +22,7 @@
setContext("user", user); setContext("user", user);
</script> </script>
<div style="text-align: center;background: var(--p20);padding: 20px;color: var(--p90)"> <div style="text-align: center;background: var(--p20);padding: 20px;color: var(--p90)">
<h1><a class="text-decoration-none" href="{channel.lucentUrl}">{channel.name}</a></h1> <h1><a class="text-decoration-none" href="{channel.lucentUrl}">{channel.name ?? "Lucent Setup"}</a></h1>
</div> </div>
<div> <div>
<svelte:component this={components[view]} {title} {...data}/> <svelte:component this={components[view]} {title} {...data}/>
+1 -1
View File
@@ -1,6 +1,7 @@
<script> <script>
import {getContext} from "svelte"; import {getContext} from "svelte";
import SpinnerButton from "../common/SpinnerButton.svelte"; import SpinnerButton from "../common/SpinnerButton.svelte";
import axios from "axios";
const channel = getContext("channel"); const channel = getContext("channel");
let email = ""; let email = "";
@@ -14,7 +15,6 @@
email: email, email: email,
}) })
.then((response) => { .then((response) => {
console.log(response)
message = "You will receive an email with a login link" message = "You will receive an email with a login link"
}) })
.catch((error) => { .catch((error) => {
+1 -2
View File
@@ -4,6 +4,7 @@
import Avatar from "./Avatar.svelte"; import Avatar from "./Avatar.svelte";
import {getContext} from "svelte"; import {getContext} from "svelte";
import SuccessAlert from "../common/SuccessAlert.svelte"; import SuccessAlert from "../common/SuccessAlert.svelte";
import axios from "axios";
const user = getContext("user"); const user = getContext("user");
const channel = getContext("channel"); const channel = getContext("channel");
@@ -25,7 +26,6 @@
}) })
.catch((error) => { .catch((error) => {
errorMessage = error.response?.data.error; errorMessage = error.response?.data.error;
console.log({errorMessage});
}); });
} }
@@ -42,7 +42,6 @@
}) })
.catch((error) => { .catch((error) => {
errorMessage = error.response?.data.error; errorMessage = error.response?.data.error;
console.log({errorMessage});
}); });
} }
</script> </script>
+22 -11
View File
@@ -1,25 +1,27 @@
<script> <script>
import {getContext, onMount} from "svelte"; import {getContext, onMount} from "svelte";
import axios from "axios";
const channel = getContext("channel"); const channel = getContext("channel");
export let title; export let title;
export let command;
$: date = ""; $: date = "";
$: logs = ""; $: logs = "";
let anchorEl;
let inProgress = false; let inProgress = false;
function connect() { function connect() {
const eventSource = new EventSource(channel.lucentUrl + "/build-report-source"); const eventSource = new EventSource(channel.lucentUrl + "/command-report-source/" + command.signature );
eventSource.onmessage = function (event) { eventSource.onmessage = function (event) {
inProgress = true; inProgress = true;
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
date = data.date; date = data.date;
logs = data.logs; logs = data.logs;
anchorEl.scrollIntoView()
} }
eventSource.onerror = (e) => { eventSource.onerror = (e) => {
console.log(e)
eventSource.close(); eventSource.close();
inProgress = false; inProgress = false;
} }
@@ -28,8 +30,7 @@
function buildWebsite(e) { function buildWebsite(e) {
e.preventDefault(); e.preventDefault();
inProgress = true; inProgress = true;
axios.post(channel.lucentUrl + "/command/" + command.signature).then(response => {
axios.post(channel.lucentUrl + "/build").then(response => {
connect() connect()
}) })
@@ -46,26 +47,36 @@
<h3 class="header-small mb-5">{title}</h3> <h3 class="header-small mb-5">{title}</h3>
<button on:click={buildWebsite} class="button primary mb-3" disabled={inProgress}>Start Build <button on:click={buildWebsite} class="button primary mb-3" disabled={inProgress}>Start
</button> </button>
<div class="mb-3"> <div class="mb-3">
{#if inProgress} {#if inProgress}
<span class="badge text-bg-warning"> <span class="badge text-bg-warning">
Build in progress Action in progress
</span> </span>
{/if} {/if}
{#if !inProgress && logs} {#if !inProgress && logs}
<span class="badge text-bg-info"> <span class="badge text-bg-info">
Build completed Action completed
</span> </span>
{/if} {/if}
</div> </div>
<pre>{logs}</pre> <pre class="logs">{logs}
<div bind:this={anchorEl}>&nbsp;</div>
</pre>
</div> </div>
</div> </div>
<style>
.logs{
max-height: 70vh;
overflow: scroll;
background: var(--p90);
color: var(--p10);
padding: 10px;
}
</style>
+20
View File
@@ -113,6 +113,26 @@
path: '<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12l4-4m-4 4 4 4"/>', path: '<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12l4-4m-4 4 4 4"/>',
viewBox: "0 0 24 24", viewBox: "0 0 24 24",
}, },
"list": {
path: '<path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="M9 8h10M9 12h10M9 16h10M4.99 8H5m-.02 4h.01m0 4H5"/>',
viewBox: "0 0 24 24",
},
"ordered-list": {
path: '<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6h8m-8 6h8m-8 6h8M4 16a2 2 0 1 1 3.321 1.5L4 20h5M4 5l2-1v6m-2 0h4"/>',
viewBox: "0 0 24 24",
},
"italic": {
path: '<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m8.874 19 6.143-14M6 19h6.33m-.66-14H18"/>',
viewBox: "0 0 24 24",
},
"undo": {
path: '<path fill-rule="evenodd" clip-rule="evenodd" d="M7.53033 3.46967C7.82322 3.76256 7.82322 4.23744 7.53033 4.53033L5.81066 6.25H15C18.1756 6.25 20.75 8.82436 20.75 12C20.75 15.1756 18.1756 17.75 15 17.75H8.00001C7.58579 17.75 7.25001 17.4142 7.25001 17C7.25001 16.5858 7.58579 16.25 8.00001 16.25H15C17.3472 16.25 19.25 14.3472 19.25 12C19.25 9.65279 17.3472 7.75 15 7.75H5.81066L7.53033 9.46967C7.82322 9.76256 7.82322 10.2374 7.53033 10.5303C7.23744 10.8232 6.76256 10.8232 6.46967 10.5303L3.46967 7.53033C3.17678 7.23744 3.17678 6.76256 3.46967 6.46967L6.46967 3.46967C6.76256 3.17678 7.23744 3.17678 7.53033 3.46967Z" fill="#1C274C"/>',
viewBox: "0 0 24 24",
},
"destroy": {
path: '<path d="M17 7L15 9" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/><path d="M19.5 7.5L20.5 8" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/><path d="M16 3.5L16.5 4.5" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/><path d="M19 5L20 4" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/><path\n'+' d="M5.75 8.00337C6.85315 7.36523 8.13392 7 9.5 7C13.6421 7 17 10.3579 17 14.5C17 18.6421 13.6421 22 9.5 22C5.35786 22 2 18.6421 2 14.5C2 13.1339 2.36523 11.8532 3.00337 10.75" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>',
viewBox: "0 0 24 24",
}
}; };
export let width = 16; export let width = 16;
@@ -1,5 +1,6 @@
<script> <script>
import {getContext} from "svelte"; import {getContext} from "svelte";
import axios from "axios";
const channel = getContext("channel"); const channel = getContext("channel");
export let selected; export let selected;
@@ -16,7 +17,7 @@
window.location.reload(); window.location.reload();
}) })
.catch((error) => { .catch((error) => {
console.log(error); console.error(error);
}); });
} }
@@ -24,13 +25,13 @@
axios axios
.post(channel.lucentUrl + "/records/status/" + status, { .post(channel.lucentUrl + "/records/status/" + status, {
schemaName: schema.name, schemaName: schema.name,
records: selected records: selected.map((s) => s.id),
}) })
.then((response) => { .then((response) => {
window.location.reload(); window.location.reload();
}) })
.catch((error) => { .catch((error) => {
console.log(error); console.error(error);
}); });
} }
</script> </script>
@@ -50,20 +51,6 @@
</button </button
> >
{#if filter["status_in"] === "trashed"} {#if filter["status_in"] === "trashed"}
<button
on:click|preventDefault={(e) => changeStatus(e, "published")}
type="button"
class="button">Publish
</button
>
{#if schema.hasDrafts}
<button
on:click|preventDefault={(e) => changeStatus(e, "draft")}
type="button"
class="button">Make Draft
</button
>
{/if}
<button <button
on:click|preventDefault={deleteRecords} on:click|preventDefault={deleteRecords}
type="button" type="button"
@@ -6,11 +6,9 @@
import Number from "./elements/Number.svelte"; import Number from "./elements/Number.svelte";
import Text from "./elements/Text.svelte"; import Text from "./elements/Text.svelte";
import Url from "./elements/Url.svelte";
import Date from "./elements/Date.svelte"; import Date from "./elements/Date.svelte";
import Datetime from "./elements/Datetime.svelte"; import Datetime from "./elements/Datetime.svelte";
import File from "./elements/File.svelte"; import File from "./elements/File.svelte";
import Uuid from "./elements/UUID.svelte";
import Rich from "./elements/Rich.svelte"; import Rich from "./elements/Rich.svelte";
const renderElements = { const renderElements = {
@@ -22,10 +20,8 @@
checkbox: Checkbox, checkbox: Checkbox,
reference: Reference, reference: Reference,
number: Number, number: Number,
url: Url,
date: Date, date: Date,
datetime: Datetime, datetime: Datetime,
uuid: Uuid,
file: File, file: File,
}; };
export let field; export let field;
+7
View File
@@ -83,7 +83,11 @@
{#if record._file?.path} {#if record._file?.path}
<div class="file-table-row"> <div class="file-table-row">
<Preview record={record} size={record._file?.width > 0 ? "medium" : "small"}/> <Preview record={record} size={record._file?.width > 0 ? "medium" : "small"}/>
<div> <div>
{#if record.status === "draft"}
<span style="text-transform: uppercase;font-size:10px">{record.status}</span>
{/if}
<a <a
href="{channel.lucentUrl}/records/{record.id}" href="{channel.lucentUrl}/records/{record.id}"
target={inModal ? "_blank" : "_self"} target={inModal ? "_blank" : "_self"}
@@ -109,6 +113,9 @@
href="{channel.lucentUrl}/records/{record.id}" href="{channel.lucentUrl}/records/{record.id}"
target={inModal ? "_blank" : "_self"} target={inModal ? "_blank" : "_self"}
> >
{#if record.status === "draft"}
<span style="text-transform: uppercase;font-size:10px">{record.status}</span>
{/if}
{previewTitle(channel.schemas, record, graph)} {previewTitle(channel.schemas, record, graph)}
</a> </a>
{/if} {/if}
@@ -18,7 +18,7 @@
<div class="references"> <div class="references">
{#each recordEdges as recordEdge} {#each recordEdges as recordEdge}
<span class="mr-3"> <span class="reference">
<PreviewCardSmall {schemas} {graph} record={recordEdge}/> <PreviewCardSmall {schemas} {graph} record={recordEdge}/>
</span> </span>
{/each} {/each}
@@ -1,10 +0,0 @@
<script>
export let value;
</script>
<span
class="badge rounded-pill bg-primary bg-opacity-75"
style="max-width:64px; overflow:hidden; white-space: nowrap; text-overflow: ellipsis;"
title={value}
data-bs-toggle="tooltip"
>{value}</span
>
@@ -1,5 +0,0 @@
<script>
export let value;
</script>
<a href={value} target="_blank">{value}</a>
@@ -1,7 +1,7 @@
<script> <script>
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
import { range } from "lodash"; import { range } from "../../../helpers.js";
import NavItem from "./NavItem.svelte"; import NavItem from "./NavItem.svelte";
export let inModal; export let inModal;
export let modalUrl; export let modalUrl;
@@ -12,7 +12,6 @@
export let inModal; export let inModal;
export let modalUrl; export let modalUrl;
export let graph; export let graph;
let filter = { let filter = {
label: "", label: "",
operator: "", operator: "",
@@ -58,6 +57,7 @@
const filterRecord = extractFilterRecord(graph, value); const filterRecord = extractFilterRecord(graph, value);
function extractFilterRecord(graph, value) { function extractFilterRecord(graph, value) {
if (!filter.isReference) { if (!filter.isReference) {
return null; return null;
} }
@@ -82,7 +82,7 @@
{#if filter.isReference && filterRecord} {#if filter.isReference && filterRecord}
{filter.label} is {previewTitle(channel.schemas, filterRecord)} {filter.label} is {previewTitle(channel.schemas, filterRecord)}
{:else} {:else}
{filter.label} {operators.find((o) => o.name === filter.operator)?.symbol ?? ""} {value} {filter.label} {operators.find((o) => o.name === filter.operator)?.symbol ?? ""} {operators.find((o) => o.name === filter.operator)?.hasValue ? value : ""}
{/if} {/if}
<button <button
@@ -11,7 +11,6 @@
export let inModal; export let inModal;
export let modalUrl; export let modalUrl;
let dropdown; let dropdown;
let search = ""; let search = "";
let systemFieldsFiltered = systemFields; let systemFieldsFiltered = systemFields;
@@ -70,6 +69,13 @@
activeOperator = operators.find(o => o.name === "eq") activeOperator = operators.find(o => o.name === "eq")
} }
function selectOperator(e, operator) {
activeOperator = operator;
if (!operator.hasValue) {
applyFilter(e)
}
}
function applyFilter(e) { function applyFilter(e) {
e.preventDefault(); e.preventDefault();
let filterPrefix = ""; let filterPrefix = "";
@@ -146,7 +152,7 @@
<div class="selected-filter">field: {activeField.label}</div> <div class="selected-filter">field: {activeField.label}</div>
{#each activeOperators as operator} {#each activeOperators as operator}
<button class="dropdown-item button" on:click={e => activeOperator = operator }> <button class="dropdown-item button" on:click={e => selectOperator(e,operator)}>
{operator.label} {operator.label}
</button> </button>
{/each} {/each}
@@ -214,8 +220,8 @@
required required
/> />
<button class="button applied-filter"> <button class="button applied-filter">
Submit Submit
</button> </button>
</form> </form>
@@ -1,8 +1,8 @@
<script> <script>
import {createEventDispatcher, getContext} from "svelte"; import {createEventDispatcher, getContext} from "svelte";
import {debounce} from "lodash"; import {debounce} from "../../../debounce.js";
import {previewTitle} from "../../records/Preview"; import {previewTitle} from "../../records/Preview";
import axios from "axios";
const channel = getContext("channel"); const channel = getContext("channel");
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
+1 -1
View File
@@ -5,7 +5,7 @@ export function sortByField(from, to, edges, fieldName, references) {
if (from === to) { if (from === to) {
return edges; return edges;
} }
let referenceIds = references.map(r => r.id); let referenceIds = references.map(r => r.record.id);
let edgesTosort = edges?.filter((ed) => ed.field === fieldName && ed.depth === 1 && referenceIds.includes(ed.target)) ?? []; let edgesTosort = edges?.filter((ed) => ed.field === fieldName && ed.depth === 1 && referenceIds.includes(ed.target)) ?? [];
let remainingEdge = edges?.filter((ed) => !(ed.field === fieldName && ed.depth === 1)) ?? []; let remainingEdge = edges?.filter((ed) => !(ed.field === fieldName && ed.depth === 1)) ?? [];
+8 -9
View File
@@ -1,27 +1,26 @@
export function imgurl(channel, record) {
export function imgurl(channel,record) {
if (record._file.mime === "image/svg+xml") { if (record._file.mime === "image/svg+xml") {
return fileurl(channel, record); return fileurl(channel, record);
} }
return channel.filesUrl + `/thumbs/${record._file.path}`; const pathAr = record._file.path.split("/");
return channel.disks[record._file.disk] + `/${pathAr[0]}/thumbs/${pathAr[1]}`;
} }
export function fileurl(channel, record) { export function fileurl(channel, record) {
return channel.filesUrl + `/${record._file.path}`; return channel.disks[record._file.disk] + `/${record._file.path}`;
} }
export function htmlurl(channel,record, preset) { export function htmlurl(channel, record, preset) {
let html = ""; let html = "";
let url = fileurl(channel,record) let url = fileurl(channel, record)
if (record._file.width > 0) { if (record._file.width > 0) {
let presetUrl = url; let presetUrl = url;
if (preset) { if (preset) {
presetUrl = channel.filesUrl + `/templates/${preset}/${record._file.path}`; const pathAr = record._file.path.split("/");
presetUrl = channel.disks[record._file.disk] + `/${pathAr[0]}/templates/${preset}/${pathAr[1]}`;
} }
html = `<img src="${presetUrl}" alt="${record._file.path}" />` html = `<img src="${presetUrl}" alt="${record._file.path}" />`
} else if (record._file.mime === "image/svg+xml") { } else if (record._file.mime === "image/svg+xml") {
html = `<img src="${url}" alt="${record._file.path}"/>` html = `<img src="${url}" alt="${record._file.path}"/>`
+39
View File
@@ -0,0 +1,39 @@
<script>
import {getContext} from "svelte";
import Icon from "../common/Icon.svelte";
import Folder from "./Folder.svelte";
const channel = getContext("channel");
export let folder;
export let schema;
export let expanded = folder.shouldExpand;
function toggleExpand() {
expanded = !expanded;
}
</script>
<div class="sidebar-folder">
{#if folder.name !== ""}
<button class="sidebar-header" tabindex="0" on:click={toggleExpand}>
{folder.name.replaceAll("_", " ") ?? "Main"}
{#if expanded}
<Icon icon="circle-chevron-up"></Icon>
{:else}
<Icon icon="circle-chevron-down"></Icon>
{/if}
</button>
{/if}
{#if expanded}
{#each folder.folders as aFolder}
<Folder folder={aFolder} schema={schema}></Folder>
{/each}
{#each folder.files as aSchema}
<a class="sidebar-item" class:active={aSchema.name === schema?.name}
aria-current="page"
href="{channel.lucentUrl}/content/{aSchema.name}">{aSchema.label}</a>
{/each}
{/if}
</div>
+13 -11
View File
@@ -1,6 +1,7 @@
<script> <script>
import Avatar from "../account/Avatar.svelte"; import Avatar from "../account/Avatar.svelte";
import {getContext} from "svelte"; import {getContext} from "svelte";
import Dropdown from "../common/Dropdown.svelte";
const channel = getContext("channel"); const channel = getContext("channel");
const user = getContext("user"); const user = getContext("user");
@@ -10,17 +11,18 @@
<div class="top-nav "> <div class="top-nav ">
<a class="top-nav-item" href="{channel.lucentUrl}/members">Members</a> <a class="top-nav-item" href="{channel.lucentUrl}/members">Members</a>
{#if channel.generateCommand} {#if channel.commands.length > 0}
<a href="{channel.lucentUrl}/build-report" class="top-nav-item">Build website</a> <Dropdown>
<div slot="button">Actions</div>
{#each channel.commands as command}
<a href="{channel.lucentUrl}/command-report/{command.signature}" class="top-nav-item">{command.name}</a>
{/each}
</Dropdown>
{/if} {/if}
<!-- <div>-->
<!-- <form method="GET">--> <a href="{channel.lucentUrl}/profile">
<!-- <input type="search" name="filter[search_regex]" placeholder="Search"--> <Avatar side="28" name={user.name}/>
<!-- class="form-control" required/>--> </a>
<!-- </form>-->
<!-- </div>-->
<a href="{channel.lucentUrl}/profile">
<Avatar side="28" name={user.name}/>
</a>
</div> </div>
+29 -23
View File
@@ -1,14 +1,39 @@
<script> <script>
import NavbarMenu from "./NavbarMenu.svelte";
import {getContext} from "svelte"; import {getContext} from "svelte";
import Folder from "./Folder.svelte";
export let schema; export let schema;
const channel = getContext("channel"); const channel = getContext("channel");
const readableSchemas = getContext("readableSchemas"); const readableSchemas = getContext("readableSchemas");
const fileSchemas = readableSchemas.filter((sc) => sc.type === "files"); function addToFolder(tree, folderPath, aSchema) {
const otherSchemas = readableSchemas.filter((sc) => !sc.isEntry && sc.type === "collection"); let shouldExpand = aSchema.name === schema?.name;
if (folderPath === "") {
tree.files.push(aSchema)
return tree
}
const folderNames = folderPath.split(".");
folderNames.forEach(folderName => {
let queriedFolder = tree.folders.find(folder => folder.name === folderName)
if (!queriedFolder) {
queriedFolder = {name: folderName, files: [], folders: [], shouldExpand: shouldExpand};
}
folderNames.shift()
let remainingFolderPath = folderNames.join(".");
queriedFolder = addToFolder(queriedFolder, remainingFolderPath, aSchema)
tree.folders = tree.folders.filter(f => f.name !== queriedFolder.name)
tree.folders.push(queriedFolder);
})
return tree;
}
const schemaTree = readableSchemas.reduce((carry, schema) => {
carry = addToFolder(carry, schema.folder,schema)
return carry;
}, {name: "", files: [], folders: [], shouldExpand:true});
</script> </script>
<div class="sidebar-top"> <div class="sidebar-top">
<a class="logo" href="{channel.lucentUrl}">{channel.name}</a> <a class="logo" href="{channel.lucentUrl}">{channel.name}</a>
@@ -16,24 +41,5 @@
</a> </a>
</div> </div>
<div class="sidebar"> <div class="sidebar">
<Folder folder={schemaTree} {schema} ></Folder>
<NavbarMenu
title="Content"
schemas={ readableSchemas.filter((sc) => sc.isEntry)}
schema={schema}
expanded={true}
/>
<NavbarMenu
title="Files"
schemas={ fileSchemas}
schema={schema}
/>
<NavbarMenu
title="Other"
schemas={ otherSchemas}
schema={schema}
/>
</div> </div>
-34
View File
@@ -1,34 +0,0 @@
<script>
import {getContext} from "svelte";
import Icon from "../common/Icon.svelte";
const channel = getContext("channel");
export let schemas;
export let title;
export let schema;
export let expanded = false;
if(schemas.find(s => s.name === schema?.name)){
expanded = true;
}
function toggleExpand(){
expanded = !expanded;
}
</script>
<button class="sidebar-header" tabindex="0" on:click={toggleExpand}>
{title}
{#if expanded}
<Icon icon="circle-chevron-up"></Icon>
{:else}
<Icon icon="circle-chevron-down"></Icon>
{/if}
</button>
{#if expanded}
{#each schemas as aschema}
<a class="sidebar-item" class:active={aschema.name === schema?.name}
aria-current="page"
href="{channel.lucentUrl}/content/{aschema.name}">{aschema.label}</a>
{/each}
{/if}
+26 -4
View File
@@ -1,10 +1,10 @@
<script> <script>
// https://codesandbox.io/s/codemirror-remark-editor-4m4z9?file=/src/CodeEditor.js:374-387 // https://codesandbox.io/s/codemirror-remark-editor-4m4z9?file=/src/CodeEditor.js:374-387
import {onMount, onDestroy} from "svelte"; import {onDestroy, onMount} from "svelte";
import {basicSetup, EditorView} from "codemirror"; import {basicSetup, EditorView} from "codemirror";
import { autocompletion, completionKeymap } from "@codemirror/autocomplete"; import {autocompletion, completionKeymap} from "@codemirror/autocomplete";
import {EditorState, Compartment} from "@codemirror/state"; import {Compartment, EditorState} from "@codemirror/state";
import {keymap} from "@codemirror/view"; import {keymap} from "@codemirror/view";
import {indentWithTab} from "@codemirror/commands"; import {indentWithTab} from "@codemirror/commands";
import {markdown} from "@codemirror/lang-markdown"; import {markdown} from "@codemirror/lang-markdown";
@@ -15,6 +15,29 @@
export let value; export let value;
export let editable = true; export let editable = true;
export function insertMedia(info) {
let insertText = "";
if (info.record._file.width > 0) {
insertText = `![${info.record._file.originalName}](${info.url})`;
} else {
insertText = `[${info.record._file.originalName}](${info.originalUrl})`;
}
const cursor = codeMirrorView.state.selection.main.head;
const transaction = codeMirrorView.state.update({
changes: {
from: cursor,
insert: insertText,
},
// the next 2 lines will set the appropriate cursor position after inserting the new text.
selection: {anchor: cursor + 1},
scrollIntoView: true,
});
if (transaction) {
codeMirrorView.dispatch(transaction);
}
}
onMount(() => { onMount(() => {
let language = new Compartment(); let language = new Compartment();
let tabSize = new Compartment(); let tabSize = new Compartment();
@@ -51,7 +74,6 @@
}); });
}); });
onDestroy(() => { onDestroy(() => {
+2 -2
View File
@@ -100,8 +100,8 @@
tinymce.init({...config, ...additionalConfig}); tinymce.init({...config, ...additionalConfig});
}); });
export function insertMedia(html){ export function insertMedia(info){
activeEditor.execCommand('InsertHTML', false, html); activeEditor.execCommand('InsertHTML', false, info.html);
} }
</script> </script>
-96
View File
@@ -1,96 +0,0 @@
<script>
import {onDestroy, onMount} from 'svelte';
import {Editor} from '@tiptap/core'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import Heading from '@tiptap/extension-heading'
import Blockquote from '@tiptap/extension-blockquote';
import Bold from '@tiptap/extension-bold';
import BulletList from '@tiptap/extension-bullet-list';
import Code from '@tiptap/extension-code';
import History from '@tiptap/extension-history';
import Italic from '@tiptap/extension-italic';
import ListItem from '@tiptap/extension-list-item';
import OrderedList from '@tiptap/extension-ordered-list';
import Strike from '@tiptap/extension-strike';
import Table from '@tiptap/extension-table';
import TableRow from '@tiptap/extension-table-row';
import TableCell from '@tiptap/extension-table-cell';
import TableHeader from '@tiptap/extension-table-header';
import Underline from '@tiptap/extension-underline';
let element;
let editor;
export let value = "";
onMount(() => {
editor = new Editor({
element: element,
extensions: [
Document,
Paragraph,
Text,
Bold,
BulletList,
Code,
History,
Italic,
ListItem,
OrderedList,
ListItem,
Strike,
Table,
TableRow,
TableCell,
TableHeader,
Underline,
Heading.configure({
levels: [1, 2, 3],
}),
Blockquote
],
content: value,
editable: true,
onTransaction: () => {
// force re-render so `editor.isActive` works as expected
editor = editor;
},
});
});
onDestroy(() => {
if (editor) {
editor.destroy();
}
});
</script>
{#if editor}
<button
on:click={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
class:active={editor.isActive('heading', { level: 1 })}
>
H1
</button>
<button
on:click={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
class:active={editor.isActive('heading', { level: 2 })}
>
H2
</button>
<button
on:click={() => editor.chain().focus().setParagraph().run()}
class:active={editor.isActive('paragraph')}
>
P
</button>
<button
on:click={() => editor.chain().focus().toggleBold().run()}
class:active={editor.isActive('bold')}
>
Bold
</button>
{/if}
<div bind:this={element} class="content"/>
+57 -11
View File
@@ -1,21 +1,67 @@
<script> <script>
import {onMount} from "svelte"; import {onDestroy, onMount} from "svelte";
import Trix from "trix" import Trix from "trix"
import customcss from "./tinymce.css?inline";
import "trix/dist/trix.css"
export let value = ""; export let value = "";
let textareaEl; export let field;
let lastVal; let editor;
let editorWrapper;
let activeEditor;
function updateValue(e) {
value = e.target.value;
}
export function insertMedia(info){
if(info.record._file.width > 0){
var attachment = new Trix.Attachment({ content: info.html })
editor.editor.insertAttachment(attachment)
}else{
editor.editor.insertHTML(`<a href="${info.originalUrl}">${info.record._file.originalName}</a>`)
}
}
onMount(() => {
editor.addEventListener("trix-file-accept", (e) => {
e.preventDefault();
})
editor.addEventListener("trix-before-initialize", (e) => {
Trix.config.blockAttributes.heading1.tagName = 'h2';
const { toolbarElement } = e.target
const h1Button = toolbarElement.querySelector("[data-trix-attribute=heading1]")
h1Button.insertAdjacentHTML("afterend", `<button style="text-indent: initial;padding: 14px 10px !important;" type="button" class="trix-button trix-button--icon" data-trix-attribute="heading3" title="Heading 3" tabindex="-1" data-trix-active="">H3</button>`)
})
})
// onDestroy(() => {
// editor.removeEventListener("trix-before-initialize")
// })
Trix.config.blockAttributes.default.breakOnReturn = false Trix.config.blockAttributes.default.breakOnReturn = false
console.log(Trix.config) Trix.config.blockAttributes.heading3 = {
tagName: 'h3',
terminal: true,
breakOnReturn: true,
group: false
}
// console.log(Trix.config)
</script> </script>
<div bind:this={editorWrapper} class="tox-wrapper"> <div class="tox-wrapper">
<input bind:this={textareaEl} id="x" bind:value type="hidden"> <input id="x-{field.name}" {value} type="hidden">
<trix-editor class="trix-content content" input="x"></trix-editor> <trix-editor
bind:this={editor}
class=" content"
input="x-{field.name}"
role="textbox"
tabindex="0"
on:trix-change={updateValue}
></trix-editor>
</div> </div>
@@ -22,9 +22,6 @@
e.preventDefault(); e.preventDefault();
let newRoles = [...member.roles, aRole]; let newRoles = [...member.roles, aRole];
console.log(member.roles)
console.log(aRole)
console.log(newRoles)
dispatch("update", { dispatch("update", {
user: member.id, user: member.id,
roles: newRoles, roles: newRoles,
+2 -5
View File
@@ -1,6 +1,5 @@
<script> <script>
import {afterUpdate, getContext, onMount} from "svelte"; import {afterUpdate, getContext, onMount} from "svelte";
import {isEqual} from "lodash";
import axios from "axios"; import axios from "axios";
import EditHeader from "./header/EditHeader.svelte" import EditHeader from "./header/EditHeader.svelte"
import FilePreview from "./FilePreview.svelte" import FilePreview from "./FilePreview.svelte"
@@ -10,6 +9,7 @@
import Info from "./Info.svelte" import Info from "./Info.svelte"
import ErrorAlert from "../common/ErrorAlert.svelte" import ErrorAlert from "../common/ErrorAlert.svelte"
import Title from "./header/Title.svelte"; import Title from "./header/Title.svelte";
import {hasDataChanged} from "./editor.js";
const channel = getContext("channel"); const channel = getContext("channel");
@@ -74,10 +74,7 @@
} }
function checkUnsavedData() { function checkUnsavedData() {
if (isCreateMode) { return hasDataChanged(isCreateMode,originalContent,{
return false;
}
return !isEqual(originalContent, {
data: record.data, data: record.data,
schema: record.schema, schema: record.schema,
status: record.status, status: record.status,
+10 -4
View File
@@ -5,9 +5,7 @@
import Color from "./elements/Color.svelte"; import Color from "./elements/Color.svelte";
import Checkbox from "./elements/Checkbox.svelte"; import Checkbox from "./elements/Checkbox.svelte";
import Number from "./elements/Number.svelte"; import Number from "./elements/Number.svelte";
import Url from "./elements/Url.svelte";
import Date from "./elements/Date.svelte"; import Date from "./elements/Date.svelte";
import UUID from "./elements/UUID.svelte";
import File from "./elements/File.svelte"; import File from "./elements/File.svelte";
import Textarea from "./elements/Textarea.svelte"; import Textarea from "./elements/Textarea.svelte";
import Datetime from "./elements/Datetime.svelte"; import Datetime from "./elements/Datetime.svelte";
@@ -25,10 +23,8 @@
color: Color, color: Color,
checkbox: Checkbox, checkbox: Checkbox,
number: Number, number: Number,
url: Url,
date: Date, date: Date,
datetime: Datetime, datetime: Datetime,
uuid: UUID,
json: Json, json: Json,
markdown: Markdown, markdown: Markdown,
}; };
@@ -98,6 +94,16 @@
bind:graph bind:graph
{record} {record}
/> />
{:else if field.info.name === "markdown"}
<Markdown
bind:value={data[field.name]}
{schema}
{field}
{validationErrors}
{isCreateMode}
bind:graph
{record}
/>
{:else} {:else}
<svelte:component <svelte:component
this={formElement} this={formElement}
+8 -3
View File
@@ -13,13 +13,18 @@
} }
let backlinks = graph.parentEdges.map(edge => { let backlinks = graph.parentEdges.map(edge => {
let schema = channel.schemas.find((s) => s.name === edge.sourceSchema); const parentRecord = graph.records.find( record => record.id === edge.source);
let schema = channel.schemas.find((s) => s.name === parentRecord.schema);
let edgeField = findEdgeField(schema,edge.field); let edgeField = findEdgeField(schema,edge.field);
if(!edgeField){
return null;
}
return { return {
field: edgeField.label, field: edgeField.label,
record: graph.records.find( record => record.id === edge.source) record: parentRecord
} }
}) }).filter( edgeOrNull => !!edgeOrNull)
</script> </script>
<div class="editor-field"> <div class="editor-field">
{#each backlinks as backlink} {#each backlinks as backlink}
+3 -2
View File
@@ -2,11 +2,12 @@
import {friendlyDate} from "../../helpers"; import {friendlyDate} from "../../helpers";
import Avatar from "../account/Avatar.svelte"; import Avatar from "../account/Avatar.svelte";
import {usernameById} from "../account/users"; import {usernameById} from "../account/users";
import {isEqual} from "lodash";
import Icon from "../common/Icon.svelte"; import Icon from "../common/Icon.svelte";
import RevisionCell from "./revisions/RevisionCell.svelte"; import RevisionCell from "./revisions/RevisionCell.svelte";
import {getContext} from "svelte"; import {getContext} from "svelte";
import RevisionEdgeRow from "./revisions/RevisionEdgeRow.svelte"; import RevisionEdgeRow from "./revisions/RevisionEdgeRow.svelte";
import axios from "axios";
import {hasDataChanged} from "./editor.js";
const channel = getContext("channel"); const channel = getContext("channel");
export let record; export let record;
@@ -60,7 +61,7 @@
selectedRevision = revision; selectedRevision = revision;
fieldsWithDiff = schema.fields.filter((f) => { fieldsWithDiff = schema.fields.filter((f) => {
return !isEqual(selectedRevision.data[f.name], record.data[f.name]); return hasDataChanged(false,selectedRevision.data[f.name], record.data[f.name]);
}); });
getEdgesByField(fieldsWithDiff, revision) getEdgesByField(fieldsWithDiff, revision)
revisionSection.scrollIntoView(); revisionSection.scrollIntoView();
+2 -6
View File
@@ -1,7 +1,6 @@
<script> <script>
import {afterUpdate, createEventDispatcher, getContext, onMount} from "svelte"; import {afterUpdate, createEventDispatcher, getContext, onMount} from "svelte";
import {hasDataChanged} from "./editor.js";
import {isEqual} from "lodash";
import FormField from "./FormField.svelte"; import FormField from "./FormField.svelte";
import FilePreview from "./FilePreview.svelte"; import FilePreview from "./FilePreview.svelte";
import ContentTabs from "./header/ContentTabs.svelte"; import ContentTabs from "./header/ContentTabs.svelte";
@@ -80,10 +79,7 @@
} }
function checkUnsavedData() { function checkUnsavedData() {
if (isCreateMode) { return hasDataChanged(isCreateMode, originalContent, {
return false;
}
return !isEqual(originalContent, {
data: record.data, data: record.data,
schema: record.schema, schema: record.schema,
status: record.status, status: record.status,
@@ -13,11 +13,8 @@
{#if record?.data} {#if record?.data}
<a <a
href="{channel.lucentUrl}/records/{record.id}" href="{channel.lucentUrl}/records/{record.id}"
class="text-decoration-none rounded py-1 px-2 d-inline-block"
{title} {title}
style="border:2px solid {!schema.color class="reference"
? '#999'
: schema.color}!important;white-space: nowrap;"
> >
{title} {title}
</a> </a>
+6
View File
@@ -0,0 +1,6 @@
export function hasDataChanged(isCreateMode, originalContent, newContent){
if (isCreateMode) {
return false;
}
return JSON.stringify(originalContent) !== JSON.stringify(newContent);
}
@@ -1,8 +1,6 @@
<script> <script>
import {onMount} from "svelte"; import {onMount} from "svelte";
import flatpickr from "flatpickr"; import flatpickr from "flatpickr";
import "flatpickr/dist/flatpickr.css";
import "flatpickr/dist/themes/light.css";
import {getErrorMessage} from "./errorMessage"; import {getErrorMessage} from "./errorMessage";
export let field; export let field;
@@ -1,21 +1,23 @@
<script> <script>
import {onMount} from "svelte"; import {onMount} from "svelte";
import flatpickr from "flatpickr"; import flatpickr from "flatpickr";
import "flatpickr/dist/flatpickr.css";
import "flatpickr/dist/themes/light.css";
import {getErrorMessage} from "./errorMessage"; import {getErrorMessage} from "./errorMessage";
export let field; export let field;
export let value; export let value;
export let isCreateMode; export let isCreateMode;
export let validationErrors; export let validationErrors;
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
$: errorMessage = getErrorMessage(validationErrors, field.name); $: errorMessage = getErrorMessage(validationErrors, field.name);
export let id; export let id;
let wrapperDiv;
let pickerInput; let pickerInput;
let pickerInstance; let pickerInstance;
let flatpickrOptions = { let flatpickrOptions = {
appendTo: wrapperDiv,
static: true,
allowInput: true, allowInput: true,
altInput: true, altInput: true,
altFormat: "Y-m-d H:i:S", altFormat: "Y-m-d H:i:S",
@@ -40,7 +42,7 @@
}); });
</script> </script>
<div class="mb-0"> <div class="mb-0" bind:this={wrapperDiv}>
<input <input
type="text" type="text"
+28 -11
View File
@@ -4,7 +4,13 @@
import PreviewFile from "../previews/PreviewFile.svelte"; import PreviewFile from "../previews/PreviewFile.svelte";
import Dropdown from "../../common/Dropdown.svelte"; import Dropdown from "../../common/Dropdown.svelte";
import Dialog from "../../dialog/Dialog.svelte"; import Dialog from "../../dialog/Dialog.svelte";
import {insertEdges} from "./reference.js"; import {
fullDeleteRecord,
graphToReferences,
insertEdges,
removeReferenceFromGraph,
restoreReferenceToGraph
} from "./reference.js";
import {getContext} from "svelte"; import {getContext} from "svelte";
const channel = getContext("channel"); const channel = getContext("channel");
@@ -12,11 +18,7 @@
export let record; export let record;
export let graph export let graph
let browseModal; let browseModal;
$: references = graph?.edges $: references = graphToReferences(graph, record, field)
.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) => let collections = channel.schemas.filter((aschema) =>
field.collections.includes(aschema.name) field.collections.includes(aschema.name)
@@ -24,9 +26,17 @@
function removeReference(e) { function removeReference(e) {
e.preventDefault(); e.preventDefault();
graph.edges = graph.edges.filter( graph.edges = removeReferenceFromGraph(graph, field, e.detail)
(edge) => !(edge.target === e.detail && edge.field === field.name) }
);
function restoreReference(e) {
e.preventDefault();
graph.edges = restoreReferenceToGraph(graph, field, e.detail)
}
function fullDelete(e) {
e.preventDefault();
graph.edges = fullDeleteRecord(channel,graph, field, e.detail)
} }
function openBrowseModal(e, schema) { function openBrowseModal(e, schema) {
@@ -73,10 +83,17 @@
</div> </div>
{#if references.length > 0} {#if references.length > 0}
<Sortable sortableClass="mt-3" on:update={reorder}> <Sortable sortableClass="mt-3" on:update={reorder}>
{#each references as reference (reference.id)} {#each references as reference (reference.record.id)}
<!--This div helps the sorting thing--> <!--This div helps the sorting thing-->
<div> <div>
<PreviewFile record={reference} hasDelete={true} on:remove={removeReference}></PreviewFile> <PreviewFile
record={reference.record}
edge={reference.edge}
hasDelete={true}
on:remove={removeReference}
on:restore={restoreReference}
on:fulldelete={fullDelete}
></PreviewFile>
</div> </div>
{/each} {/each}
</Sortable> </Sortable>
@@ -1,21 +1,37 @@
<script> <script>
import Codemirror from "../../libs/CodemirrorMarkdown.svelte"; import Codemirror from "../../libs/CodemirrorMarkdown.svelte";
import { getErrorMessage } from "./errorMessage"; import { getErrorMessage } from "./errorMessage";
import RichEditorFiles from "./RichEditorFiles.svelte";
export let value; export let value;
export let field; export let field;
export let graph;
export let record;
export let isCreateMode; export let isCreateMode;
// export let id; // export let id;
export let validationErrors; export let validationErrors;
$: errorMessage = getErrorMessage(validationErrors, field.name); $: errorMessage = getErrorMessage(validationErrors, field.name);
let editor;
function insertMedia(e){
editor.insertMedia(e.detail)
}
</script> </script>
<div class="mb-3"> <div class="mb-3">
<Codemirror bind:value editable={!field.readonly || isCreateMode} /> <Codemirror bind:this={editor} bind:value editable={!field.readonly || isCreateMode} />
{#if field.collections.length > 0}
<RichEditorFiles
bind:graph
{record}
{field}
{validationErrors}
on:editor-insert={insertMedia}
>
</RichEditorFiles>
{/if}
{#if errorMessage} {#if errorMessage}
<div class="invalid-feedback d-block"> <div class="invalid-feedback d-block">
{errorMessage} {errorMessage}
@@ -1,12 +1,17 @@
<script> <script>
import {getContext} from "svelte"; import {getContext} from "svelte";
import {insertEdges} from "./reference"; import {
fullDeleteRecord,
graphToReferences,
insertEdges,
removeReferenceFromGraph,
restoreReferenceToGraph
} from "./reference";
import {getErrorMessage} from "./errorMessage"; import {getErrorMessage} from "./errorMessage";
import {sortByField} from "../../edges/sortEdges"; import {sortByField} from "../../edges/sortEdges";
import ReferenceInlineButtons from "./ReferenceInlineButtons.svelte"; import ReferenceInlineButtons from "./ReferenceInlineButtons.svelte";
import Sortable from "../../libs/Sortable.svelte"; import Sortable from "../../libs/Sortable.svelte";
import PreviewReference from "../previews/PreviewReference.svelte"; import PreviewReference from "../previews/PreviewReference.svelte";
import axios from "axios";
const channel = getContext("channel"); const channel = getContext("channel");
export let record; export let record;
@@ -16,11 +21,7 @@
$: errorMessage = getErrorMessage(validationErrors, field.name); $: errorMessage = getErrorMessage(validationErrors, field.name);
$: references = graph.edges $: references = graphToReferences(graph,record,field)
.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) => let collections = channel.schemas.filter((aschema) =>
field.collections.includes(aschema.name) field.collections.includes(aschema.name)
@@ -28,27 +29,25 @@
function removeReference(e) { function removeReference(e) {
e.preventDefault(); e.preventDefault();
graph.edges = graph.edges.filter( graph.edges = removeReferenceFromGraph(graph,field,e.detail)
(edge) => !(edge.target === e.detail && edge.field === field.name) }
);
function restoreReference(e) {
e.preventDefault();
graph.edges = restoreReferenceToGraph(graph,field,e.detail)
}
function fullDelete(e) {
e.preventDefault();
graph.edges = fullDeleteRecord(channel,graph, field, e.detail)
} }
function reorder(e) { 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) { function insert(e) {
e.preventDefault(); e.preventDefault();
// axios.post(channel.lucentUrl + "/edges/insert-many", {
// source: record.id,
// sourceSchema: record.schema,
// targetSchema: e.detail.schema,
// field: field.name,
// targets: e.detail.records.map(r => r.id),
// }).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);
} }
@@ -69,13 +68,16 @@
</div> </div>
{#if references.length > 0} {#if references.length > 0}
<Sortable sortableClass="row row-cols-3 mt-3" on:update={reorder}> <Sortable sortableClass="row row-cols-3 mt-3" on:update={reorder}>
{#each references as reference (reference.id)} {#each references as reference (reference.record.id)}
<div> <div>
<PreviewReference <PreviewReference
{graph} {graph}
record={reference} record={reference.record}
edge={reference.edge}
hasDelete={true} hasDelete={true}
on:remove={removeReference} on:remove={removeReference}
on:restore={restoreReference}
on:fulldelete={fullDelete}
/> />
</div> </div>
{/each} {/each}
@@ -43,6 +43,7 @@
function createInlineReference(e, schemaUId) { function createInlineReference(e, schemaUId) {
e.preventDefault(); e.preventDefault();
inLineCreateRecord = null;
axios axios
.get(channel.lucentUrl + "/records/newInline?schema=" + schemaUId) .get(channel.lucentUrl + "/records/newInline?schema=" + schemaUId)
.then((response) => { .then((response) => {
@@ -59,27 +60,29 @@
<div <div
style="display: flex;align-items: center;gap:4px" style="display: flex;align-items: center;gap:4px"
> >
{#each schemas as schema} <Dropdown>
<Dropdown> <div slot="button">New</div>
<div slot="button" class:is-first={!recordId}> {#each schemas as schema}
{schema.label}
</div>
<button <button
class=" button" class=" button"
on:click={(e) => on:click={(e) =>
createInlineReference(e, schema.name)} createInlineReference(e, schema.name)}
>Create New Record >{schema.label}
</button> </button>
{/each}
</Dropdown>
<Dropdown>
<div slot="button"> <Icon icon="magnifying-glass"/></div>
{#each schemas as schema}
<button <button
class="button" class="button"
on:click={(e) => openBrowseModal(e, schema.name)} on:click={(e) => openBrowseModal(e, schema.name)}
> >{schema.label}
<Icon icon="magnifying-glass"/> </button>
Search {/each}
</button
> </Dropdown>
</Dropdown>
{/each}
</div> </div>
{:else} {:else}
<div style="display:flex;align-items: center;gap: 4px"> <div style="display:flex;align-items: center;gap: 4px">
@@ -1,10 +1,11 @@
<script> <script>
import {getContext} from "svelte"; import {getContext} from "svelte";
import {debounce} from "lodash"; import {debounce} from "../../../debounce.js";
import {previewTitle} from "../Preview"; import {previewTitle} from "../Preview";
import {getErrorMessage} from "./errorMessage"; import {getErrorMessage} from "./errorMessage";
import {insertEdges} from "./reference.js"; import {insertEdges} from "./reference.js";
import Icon from "../../common/Icon.svelte"; import Icon from "../../common/Icon.svelte";
import axios from "axios";
const channel = getContext("channel"); const channel = getContext("channel");
export let field; export let field;
@@ -18,7 +19,7 @@
$: references = graph.edges $: references = graph.edges
.filter((edge) => edge.field === field.name) .filter((edge) => edge.field === field.name)
.map((edge) => { .map((edge) => {
return graph.records.find((increc) => increc.id == edge.target && record.id == edge.source); return graph.records.find((increc) => increc.id === edge.target && record.id === edge.source);
}).filter((rec) => (rec?.id ? true : false)) ?? []; }).filter((rec) => (rec?.id ? true : false)) ?? [];
let search = "" let search = ""
@@ -48,11 +49,9 @@
.then((response) => { .then((response) => {
searchOptions = []; searchOptions = [];
insert(e, response.data.records[0]); insert(e, response.data.records[0]);
console.log(response)
}) })
.catch((error) => { .catch((error) => {
searchOptions = []; searchOptions = [];
console.log(error);
}); });
} }
@@ -79,7 +78,6 @@
}) })
.catch((error) => { .catch((error) => {
searchOptions = []; searchOptions = [];
console.log(error);
}); });
}, 500); }, 500);
@@ -1,7 +1,7 @@
<script> <script>
import Tinymce from "../../libs/Tinymce.svelte";
import RichEditorFiles from "./RichEditorFiles.svelte"; import RichEditorFiles from "./RichEditorFiles.svelte";
import {getErrorMessage} from "./errorMessage"; import {getErrorMessage} from "./errorMessage";
import Trix from "../../libs/Trix.svelte";
export let value; export let value;
export let field; export let field;
@@ -24,8 +24,7 @@
<div class="mb-0"> <div class="mb-0">
<Trix {field} bind:this={editor} bind:value></Trix>
<Tinymce bind:this={editor} bind:value {additionalConfig}/>
{#if field.collections.length > 0} {#if field.collections.length > 0}
<RichEditorFiles <RichEditorFiles
bind:graph bind:graph
@@ -38,7 +37,7 @@
</RichEditorFiles> </RichEditorFiles>
{/if} {/if}
<!-- <TipTap bind:value />-->
{#if errorMessage} {#if errorMessage}
<div class="invalid-feedback d-block"> <div class="invalid-feedback d-block">
@@ -1,6 +1,4 @@
<script> <script>
import {sortByField} from "../../edges/sortEdges";
import Sortable from "../../libs/Sortable.svelte";
import PreviewFile from "../previews/PreviewFile.svelte"; import PreviewFile from "../previews/PreviewFile.svelte";
import Dropdown from "../../common/Dropdown.svelte"; import Dropdown from "../../common/Dropdown.svelte";
import Dialog from "../../dialog/Dialog.svelte"; import Dialog from "../../dialog/Dialog.svelte";
@@ -70,7 +68,8 @@
{#each references as reference (reference.id)} {#each references as reference (reference.id)}
<!--This div helps the sorting thing--> <!--This div helps the sorting thing-->
<div> <div>
<PreviewFile record={reference} hasDelete={true} hasInsert={true} on:remove={removeReference} on:editor-insert></PreviewFile> <PreviewFile record={reference} hasDelete={true} hasInsert={true} on:remove={removeReference}
on:editor-insert></PreviewFile>
</div> </div>
{/each} {/each}
{/if} {/if}
@@ -1,49 +0,0 @@
<script>
import { v4 as uuidv4 } from "uuid";
import { getContext } from "svelte";
import Icon from "../../common/Icon.svelte";
import { getErrorMessage } from "./errorMessage";
const channelurl = getContext("channelurl");
export let validationErrors;
$: errorMessage = getErrorMessage(validationErrors, field.name);
export let field;
export let value;
export let id;
export let isCreateMode;
let readonly = field.readonly && !isCreateMode;
function generateId(e) {
e.preventDefault();
value = uuidv4();
}
</script>
<div class="mb-0">
<div class="d-flex justify-content-between">
<input
type="text"
{id}
class="form-control"
class:is-invalid={errorMessage}
bind:value
autocomplete="off"
{readonly}
/>
{#if !readonly}
<button
class="btn btn-primary ms-2"
title="Generate a new UUIDv4"
on:click={generateId}
>
<Icon icon="dice" />
</button>
{/if}
</div>
{#if errorMessage}
<div class="invalid-feedback d-block">
{errorMessage}
</div>
{/if}
</div>
@@ -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>
+50 -5
View File
@@ -1,12 +1,11 @@
import {uniqBy} from "lodash"; import {uniqueBy} from "../../../helpers.js";
import axios from "axios";
export function insertEdges(graph, sourceRecord, targetRecords, fieldName, action = "") { export function insertEdges(graph, sourceRecord, targetRecords, fieldName, action = "") {
let newEdges = targetRecords.map((r) => { let newEdges = targetRecords.map((r) => {
return { return {
target: r.id, target: r.id,
source: sourceRecord.id, source: sourceRecord.id,
sourceSchema: sourceRecord.schema,
targetSchema: r.schema,
field: fieldName, field: fieldName,
depth: 1, depth: 1,
rank: "" rank: ""
@@ -18,7 +17,53 @@ export function insertEdges(graph, sourceRecord, targetRecords, fieldName, actio
replacedEdges = replacedEdges.filter((edge) => edge.field !== field.name); replacedEdges = replacedEdges.filter((edge) => edge.field !== field.name);
} }
graph.records = uniqBy([...graph.records, ...targetRecords], (r) => r.id); graph.records = uniqueBy([...graph.records, ...targetRecords], (r) => r.id);
graph.edges = uniqBy([...replacedEdges, ...newEdges], (edge) => edge.source + edge.target + edge.field + edge.depth); graph.edges = uniqueBy([...replacedEdges, ...newEdges], (edge) => edge.source + edge.target + edge.field + edge.depth);
return graph; return graph;
} }
export function graphToReferences(graph,record,field){
return graph.edges
.filter((edge) => edge.field === field.name)
.map((edge) => {
return {
record: graph.records.find((increc) => increc.id === edge.target && record.id === edge.source),
edge: edge
};
}).filter((rec) => (rec.record?.id ? true : false)) ?? [];
}
export function removeReferenceFromGraph(graph,field,id){
return graph.edges.map(
(edge) => {
if(edge.target === id && edge.field === field.name){
edge._isTrashed = true;
}
return edge;
}
);
}
export function restoreReferenceToGraph(graph,field,id){
return graph.edges.map(
(edge) => {
if(edge.target === id && edge.field === field.name){
edge._isTrashed = false;
}
return edge;
}
);
}
export function fullDeleteRecord(channel,graph,field,id){
axios
.post(channel.lucentUrl + "/records/status/trashed" , {
records: [id]
});
return graph.edges.filter(
(edge) => !(edge.target === id && edge.field === field.name)
);
}
@@ -4,13 +4,14 @@
import {createEventDispatcher, getContext} from "svelte"; import {createEventDispatcher, getContext} from "svelte";
import Preview from "../../files/Preview.svelte"; import Preview from "../../files/Preview.svelte";
import {previewTitle} from "./../Preview"; import {previewTitle} from "./../Preview";
import {htmlurl} from "../../files/imageserver.js" import {fileurl, htmlurl} from "../../files/imageserver.js"
import Status from "./../Status.svelte"; import Status from "./../Status.svelte";
import Dropdown from "../../common/Dropdown.svelte"; import Dropdown from "../../common/Dropdown.svelte";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const channel = getContext("channel"); const channel = getContext("channel");
export let record; export let record;
export let edge;
export let hasDelete = false; export let hasDelete = false;
export let hasInsert = false; export let hasInsert = false;
@@ -23,15 +24,31 @@
dispatch("remove", record.id); dispatch("remove", record.id);
} }
function restore(e) {
e.preventDefault();
dispatch("restore", record.id);
}
function fullDelete(e) {
e.preventDefault();
dispatch("fulldelete", record.id);
}
function insert(e, preset) { function insert(e, preset) {
e.preventDefault(); e.preventDefault();
let html = htmlurl(channel,record, preset) let html = htmlurl(channel, record, preset)
dispatch("editor-insert", html); let url = !preset ? `/${record._file.path}` : `/templates/${preset}/${record._file.path}`;
dispatch("editor-insert", {
html: html,
url: channel.filesUrl + url,
originalUrl: channel.filesUrl + "/" + record._file.path,
record: record
});
} }
</script> </script>
<div class="preview-file"> <div class="preview-file" class:is-trashed={edge?._isTrashed}>
<div style="display: flex;align-items: center;gap: 10px;"> <div style="display: flex;align-items: center;gap: 10px;">
<div class="image"> <div class="image">
<Preview {record} size="small"/> <Preview {record} size="small"/>
@@ -43,6 +60,9 @@
href="{channel.lucentUrl}/records/{record.id}" href="{channel.lucentUrl}/records/{record.id}"
> >
{cardTitle} {cardTitle}
{#if edge?._isTrashed}
<span class="trashed-text">will remove on save</span>
{/if}
</a> </a>
<small class="d-block"> <small class="d-block">
from {schema.label} from {schema.label}
@@ -72,12 +92,30 @@
{/if} {/if}
{#if hasDelete} {#if hasDelete}
<div class="reference-action"> <div class="reference-action">
<button {#if edge?._isTrashed}
class="button" <button
on:click={remove} title="Restore"
> class="button"
<Icon icon="trash-can"/> on:click={restore}
</button> >
<Icon icon="undo"/>
</button>
<button
title="Delete from everywhere"
class="button"
on:click={fullDelete}
>
<Icon icon="destroy"/>
</button>
{:else}
<button
title="Remove"
class="button"
on:click={remove}
>
<Icon icon="trash-can"/>
</button>
{/if}
</div> </div>
{/if} {/if}
</div> </div>
@@ -10,21 +10,32 @@
const channel = getContext("channel"); const channel = getContext("channel");
export let graph; export let graph;
export let record; export let record;
export let edge;
export let hasDelete = false; export let hasDelete = false;
let schema = channel.schemas.find((aschema) => aschema.name === record.schema); let schema = channel.schemas.find((aschema) => aschema.name === record.schema);
let cardTitle = previewTitle(channel.schemas, record, graph); let cardTitle = previewTitle(channel.schemas, record, graph);
const cardImageEdge = graph.edges.find(e => e.source === record.id && e.field === schema.cardImage); const cardImageEdge = graph.edges.find(e => e.source === record.id && e.field === schema.cardImage);
let cardImageRecord = graph.records.find(r => r.id === cardImageEdge?.target); let cardImageRecord = graph.records.find(r => r.id === cardImageEdge?.target);
function remove(e) { function remove(e) {
e.preventDefault(); e.preventDefault();
dispatch("remove", record.id); dispatch("remove", record.id);
} }
function restore(e) {
e.preventDefault();
dispatch("restore", record.id);
}
function fullDelete(e) {
e.preventDefault();
dispatch("fulldelete", record.id);
}
</script> </script>
<div class="preview-reference"> <div class="preview-reference" class:is-trashed={edge?._isTrashed}>
<div style="display: flex;align-items: center;gap: 10px;"> <div style="display: flex;align-items: center;gap: 10px;">
{#if cardImageRecord} {#if cardImageRecord}
@@ -38,7 +49,12 @@
class="record-title" class="record-title"
href="{channel.lucentUrl}/records/{record.id}" href="{channel.lucentUrl}/records/{record.id}"
> >
{cardTitle} {cardTitle}
{#if edge?._isTrashed}
<span class="trashed-text">will remove on save</span>
{/if}
</a> </a>
<small class="d-block"> <small class="d-block">
from {schema.label} from {schema.label}
@@ -53,12 +69,30 @@
</div> </div>
{#if hasDelete} {#if hasDelete}
<div class="reference-action"> <div class="reference-action">
<button {#if edge?._isTrashed}
class="button" <button
on:click={remove} title="Restore"
> class="button"
<Icon icon="trash-can"/> on:click={restore}
</button> >
<Icon icon="undo"/>
</button>
<button
title="Delete from everywhere"
class="button"
on:click={fullDelete}
>
<Icon icon="destroy"/>
</button>
{:else}
<button
title="Remove"
class="button"
on:click={remove}
>
<Icon icon="trash-can"/>
</button>
{/if}
</div> </div>
{/if} {/if}
</div> </div>
+3414 -1044
View File
File diff suppressed because it is too large Load Diff
+4 -25
View File
@@ -11,41 +11,20 @@
"@codemirror/lang-markdown": "^6.2.5", "@codemirror/lang-markdown": "^6.2.5",
"@codemirror/state": "^6.4.1", "@codemirror/state": "^6.4.1",
"@sveltejs/vite-plugin-svelte": "^3.1.1", "@sveltejs/vite-plugin-svelte": "^3.1.1",
"@tiptap/core": "^2.6.4",
"@tiptap/extension-blockquote": "^2.6.4",
"@tiptap/extension-bold": "^2.6.4",
"@tiptap/extension-bullet-list": "^2.6.4",
"@tiptap/extension-code": "^2.6.4",
"@tiptap/extension-document": "^2.6.4",
"@tiptap/extension-heading": "^2.6.4",
"@tiptap/extension-history": "^2.6.4",
"@tiptap/extension-italic": "^2.6.4",
"@tiptap/extension-list-item": "^2.6.4",
"@tiptap/extension-ordered-list": "^2.6.4",
"@tiptap/extension-paragraph": "^2.6.4",
"@tiptap/extension-strike": "^2.6.4",
"@tiptap/extension-table": "^2.6.4",
"@tiptap/extension-table-cell": "^2.6.4",
"@tiptap/extension-table-header": "^2.6.4",
"@tiptap/extension-table-row": "^2.6.4",
"@tiptap/extension-text": "^2.6.4",
"@tiptap/extension-underline": "^2.6.4",
"@tiptap/pm": "^2.6.4",
"axios": "^1.7.4", "axios": "^1.7.4",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"flatpickr": "^4.6.13", "flatpickr": "^4.6.13",
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"htmx.org": "^2.0.1", "htmx.org": "^2.0.1",
"install": "^0.13.0",
"laravel-vite-plugin": "^1.0.5", "laravel-vite-plugin": "^1.0.5",
"lodash": "^4.17.21",
"mustache": "^4.2.0", "mustache": "^4.2.0",
"postcss": "8.4.31", "npm": "^10.8.2",
"sass": "^1.77.8",
"sortablejs": "^1.15.2", "sortablejs": "^1.15.2",
"svelte": "^4.2.18", "svelte": "^4.2.18",
"tinymce": "^6.8.4", "tinymce": "^6.8.4",
"uuid": "^10.0.0", "trix": "^2.1.5",
"vite": "5.2.6" "vite": "5.4.6"
} }
} }
+22
View File
@@ -0,0 +1,22 @@
.scope-login {
display: flex;
height: 100vh;
.bg-image {
width: 50%;
background: url("/vendor/lucent/public/art.jpg");
background-repeat: no-repeat;
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
}
.login-form{
width: 50%;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
}
+51
View File
@@ -0,0 +1,51 @@
.autocomplete {
position: relative;
z-index: 1000;
overflow: visible;
.autocomplete-option {
cursor: pointer;
font-size: 14px;
padding: 3px 10px;
&:hover {
background: var(--p40);
border-radius: 12px;
}
}
&:focus-within {
.autocomplete-results{
display: flex;
}
}
}
.autocomplete-selected-value {
font-size: 13px;
margin-top: 10px;
border-radius: 12px;
background: var(--p30);
padding: 3px 10px;
display: inline-flex;
justify-content: center;
gap: 4px;
line-height: 22px;
&:hover {
background: var(--p40);
}
}
.autocomplete-results {
display: none;
flex-direction: column;
padding: 10px;
overflow: visible;
position: absolute;
border-radius: 12px;
z-index: 20;
background: var(--p30);
//border: 1px solid var(--p40);
transition: 600ms;
flex-grow: 1;
top: 45px;
width: 100%;
}
+23
View File
@@ -0,0 +1,23 @@
.avatar {
/* Center the content */
display: inline-block;
vertical-align: middle;
/* Used to position the content */
position: relative;
/* Colors */
color: #fff;
/* Rounded border */
border-radius: 50%;
}
.avatar__letters {
/* Center the content */
left: 50%;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
}
+94
View File
@@ -0,0 +1,94 @@
.button {
border-radius: 12px;
background: var(--p20);
padding: 3px 10px;
cursor: pointer;
border: 0px solid var(--p30);
font-size: 14px;
min-height: 27px;
display: flex;
align-items: center;
gap: 4px;
color: var(--text);
&:focus {
}
&:hover {
background: var(--p30);
}
&:active {
background: var(--p50) !important;
box-shadow: none;
}
&.active {
background: var(--p30);
}
&.secondary {
background: var(--p30);
&:hover {
background: var(--p40);
}
}
&.primary {
background: var(--p70);
color: var(--p10);
&:hover {
background: var(--p90);
}
}
&[disabled] {
pointer-events: none;
opacity: .7;
color: var(--text);
}
}
.upload-button {
padding: 0;
border: none;
label {
font-size: 14px;
line-height: 14px;
font-weight: normal;
background: var(--p80) !important;
color: var(--p10);
}
}
.button-text {
border: none;
padding: 0;
background: transparent;
cursor: pointer;
}
.spinner-border {
width: 12px;
height: 12px;
border: 2px solid var(--p10);
border-bottom-color: var(--p30);
border-radius: 50%;
display: inline-block;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
+104
View File
@@ -0,0 +1,104 @@
@supports (-webkit-appearance: none) or (-moz-appearance: none) {
.checkbox-wrapper input[type=checkbox] {
--active-inner: var(--p10);
--focus: 2px var(--p30);
--border-hover: var(--p30);
--disabled: #F6F8FF;
--disabled-inner: #E1E6F9;
-webkit-appearance: none;
-moz-appearance: none;
height: 21px;
outline: none;
display: inline-block;
vertical-align: top;
position: relative;
margin: 0;
cursor: pointer;
border: 1px solid var(--bc, var(--p30));
background: var(--b, var(--p10));
transition: background 0.3s, border-color 0.3s, box-shadow 0.2s;
}
.checkbox-wrapper input[type=checkbox]:after {
content: "";
display: block;
left: 0;
top: 0;
position: absolute;
transition: transform var(--d-t, 0.3s) var(--d-t-e, ease), opacity var(--d-o, 0.2s);
}
.checkbox-wrapper input[type=checkbox]:checked {
--b: var(--p40);
--bc: var(--p40);
--d-o: .3s;
--d-t: .6s;
--d-t-e: cubic-bezier(.2, .85, .32, 1.2);
}
.checkbox-wrapper input[type=checkbox]:disabled {
--b: var(--disabled);
cursor: not-allowed;
opacity: 0.9;
}
.checkbox-wrapper input[type=checkbox]:disabled:checked {
--b: var(--disabled-inner);
--bc: var(--p40);
}
.checkbox-wrapper input[type=checkbox]:disabled + label {
cursor: not-allowed;
}
.checkbox-wrapper input[type=checkbox]:hover:not(:checked):not(:disabled) {
--bc: var(--border-hover);
}
.checkbox-wrapper input[type=checkbox]:focus {
box-shadow: 0 0 0 var(--focus);
}
.checkbox-wrapper input[type=checkbox]:not(.switch) {
width: 21px;
}
.checkbox-wrapper input[type=checkbox]:not(.switch):after {
opacity: var(--o, 0);
}
.checkbox-wrapper input[type=checkbox]:not(.switch):checked {
--o: 1;
}
.checkbox-wrapper input[type=checkbox] + label {
display: inline-block;
vertical-align: middle;
cursor: pointer;
margin-left: 4px;
}
.checkbox-wrapper input[type=checkbox]:not(.switch) {
border-radius: 7px;
}
.checkbox-wrapper input[type=checkbox]:not(.switch):after {
width: 5px;
height: 9px;
border: 2px solid var(--active-inner);
border-top: 0;
border-left: 0;
left: 7px;
top: 4px;
transform: rotate(var(--r, 20deg));
}
.checkbox-wrapper input[type=checkbox]:not(.switch):checked {
--r: 43deg;
}
}
.checkbox-wrapper * {
box-sizing: inherit;
}
.checkbox-wrapper *:before,
.checkbox-wrapper *:after {
box-sizing: inherit;
}
.checkbox-wrapper input[type=checkbox]:indeterminate {
--b: var(--p40);
--bc: var(--p40);
--d-o: .3s;
--d-t: .6s;
--d-t-e: cubic-bezier(.2, .85, .32, 1.2);
}
+24
View File
@@ -0,0 +1,24 @@
.is-editable-false{
.cm-content{
background-color: var(--p10);
}
}
.cm-focused{
.cm-content{
background-color: var(--p10);
color: var(--p100);
}
}
.cm-content{
background-color: var(--p20);
}
.ͼ4 .cm-line ::selection, .ͼ4 .cm-line::selection{
background: var(--p40) !important;
}
.cm-activeLine{
background-color: var(--p20)!important;
}
+43
View File
@@ -0,0 +1,43 @@
.flatpickr-wrapper {
display: block !important;
}
.editor-field {
.flatpickr-calendar {
border-radius: 12px !important;
}
.flatpickr-months .flatpickr-month {
background: var(--p30);
color: var(--text);
font-size: 12px;
}
.flatpickr-current-month .flatpickr-monthDropdown-months {
background: var(--p30);
}
.flatpickr-weekdays{
background: var(--p30);
color: var(--text);
}
.flatpickr-weekdaycontainer .flatpickr-weekday{
background: var(--p30);
color: var(--text);
}
.flatpickr-days{
background: var(--p10);
color: var(--text);
}
.flatpickr-time{
background: var(--p10);
color: var(--text);
}
}
+54
View File
@@ -0,0 +1,54 @@
//
//:modal {
// background-color: beige;
// border: 2px solid burlywood;
// border-radius: 5px;
//}
html {
//scrollbar-gutter: stable;
}
body:has(dialog[open]) {
overflow: hidden;
}
dialog {
margin: 2vh auto;
background-color: var(--p10);
padding: 34px;
border: none;
border-radius: 12px;
overflow: auto;
max-height: 96vh;
box-shadow: none!important;
//position: relative;
.close {
position: absolute;
top: 10px;
right: 0px;
}
.dialog-body {
width: fit-content;
}
}
dialog::backdrop {
backdrop-filter: blur(3px);
}
.dialog-header {
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 8px;
position: sticky;
top: -34px;
z-index: 999;
background-color: var(--p10);
padding: 10px 0;
}
+73
View File
@@ -0,0 +1,73 @@
.dropdown {
position: relative;
overflow: visible;
}
.dropdown-button > div {
display: flex;
align-items: center;
gap: 3px;
}
.dropdown-menu {
display: flex;
flex-direction: column;
padding: 10px;
overflow: visible;
position: absolute;
border-radius: 12px;
z-index: 22;
background: var(--p20);
transition: 600ms;
flex-grow: 1;
top: 35px;
min-width: max-content;
border: 1px solid var(--p30);
&.orientation-right {
right: 0;
}
&.orientation-left {
left: 0;
}
}
.dropdown-header, .dropdown-item {
display: flex;
align-items: center;
gap: 3px;
text-wrap: nowrap;
}
.dropdown-header {
padding: 10px;
}
.dropdown-item {
font-size: 14px;
padding: 3px 10px;
&:hover {
background: var(--p30);
border-radius: 12px;
button {
background: var(--p30);
}
}
.button-icon {
flex-shrink: 0;
}
}
.editor-field{
.dropdown-menu {
background: var(--p30);
}
}
+95
View File
@@ -0,0 +1,95 @@
label {
display: block;
font-weight: 700;
margin-bottom: 4px;
}
input[type=text],input[type=number],input[type=search],input[type=email],textarea{
width: 100%;
background: var(--p20);
border: 1px solid var(--p50);
border-radius: 5px;
padding: 5px 7px;
font-size: 16px;
&:focus{
background: var(--p10);
}
}
textarea{
resize: none;
}
select{
width: 100%;
background: var(--p20);
border: 1px solid var(--p50);
border-radius: 5px;
padding: 5px 7px;
font-size: 16px;
&:focus{
background: var(--p10);
}
}
.htmx-indicator {
display: none;
}
.htmx-request .htmx-indicator {
display: inline;
}
.htmx-request.htmx-indicator {
display: inline;
}
.bt {
appearance: none;
background-color: #000;
background-image: none;
border: 1px solid #000;
border-radius: 4px;
box-shadow: #fff 4px 4px 0 0, #000 4px 4px 0 1px;
box-sizing: border-box;
color: #fff;
cursor: pointer;
display: inline-block;
font-family: ITCAvantGardeStd-Bk, Arial, sans-serif;
font-size: 16px;
font-weight: 400;
line-height: 20px;
margin: 0 5px 10px 0;
overflow: visible;
padding: 8px 40px;
text-align: center;
text-transform: none;
touch-action: manipulation;
user-select: none;
-webkit-user-select: none;
vertical-align: middle;
white-space: nowrap;
}
.bt:focus {
text-decoration: none;
}
.bt:hover {
text-decoration: none;
}
.bt:active {
box-shadow: rgba(0, 0, 0, .125) 0 3px 5px inset;
outline: 0;
}
.bt:not([disabled]):active {
box-shadow: #fff 2px 2px 0 0, #000 2px 2px 0 1px;
transform: translate(2px, 2px);
}
+52
View File
@@ -0,0 +1,52 @@
.mt-1{margin-top: 4px}
.mt-2{margin-top: 8px}
.mt-3{margin-top: 12px}
.mt-4{margin-top: 16px}
.mt-5{margin-top: 20px}
.mb-1{margin-bottom: 4px}
.mb-2{margin-bottom: 8px}
.mb-3{margin-bottom: 12px}
.mb-4{margin-bottom: 16px}
.mb-5{margin-bottom: 20px}
.pt-1{padding-top: 4px}
.pt-2{padding-top: 8px}
.pt-3{padding-top: 12px}
.pt-4{padding-top: 16px}
.pt-5{padding-top: 20px}
.pb-1{padding-bottom: 4px}
.pb-2{padding-bottom: 8px}
.pb-3{padding-bottom: 12px}
.pb-4{padding-bottom: 16px}
.pb-5{padding-bottom: 20px}
.gap-1{gap: 4px}
.gap-2{gap: 8px}
.gap-3{gap: 12px}
.gap-4{gap: 16px}
.gap-5{gap: 20px}
.hide{
display: none!important;
}
.hidden{
visibility: hidden;
}
.d-block{
display: block;
}
.d-inline-block{
display: inline-block;
}
.is-bold{
font-weight: 700;
}
.in-place{
padding: 36px;
}
+19
View File
@@ -0,0 +1,19 @@
.sidebar-content{
min-width: 300px;
max-width: 400px;
position: relative;
}
.main-content {
position: relative;
width: fit-content;
min-width: 900px;
}
.main-wrapper {
display: flex;
justify-content: center;
gap: 40px;
padding: 20px;
position: relative;
}
+19
View File
@@ -0,0 +1,19 @@
.member-list{
display: flex;
flex-direction: column;
gap: 5px;
}
.member-item{
background: var(--p30);
border-radius: 12px;
padding: 12px;
display: flex;
justify-content: space-between;
align-items: center;
.member-name{
display: flex;
align-items: center;
gap: 10px;
}
}
+26
View File
@@ -0,0 +1,26 @@
.notice {
background-color: var(--p20);
padding: 14px;
margin: 2rem 0;
position: relative;
font-size: 16px;
line-height: 24px;
border-radius: 12px;
//border: 3px solid var(--border-base);
}
.notice .title {
content: "NOTE";
//position: absolute;
border-radius: 12px;
display: block;
font-weight: bold;
}
.notice.notice-success{
background: var(--suc20);
}
.notice.notice-error{
background: var(--err10);
}
+38
View File
@@ -0,0 +1,38 @@
.pagination {
margin: 20px auto 10px ;
display: flex;
justify-content: center;
align-items: center;
gap: 4px;
list-style: none;
padding: 0;
li {
a,span{
font-size: 14px;
border-radius: 12px;
padding: 4px 18px;
background: var(--p20);
&:hover{
background: var(--p30);
}
}
&.disabled {
pointer-events: none;
opacity: .7;
}
&.active {
span{
background: var(--p30);
}
}
}
}
+68
View File
@@ -0,0 +1,68 @@
.preview-file,.preview-reference{
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
background: var(--p10);
border-radius: 12px;
&.is-trashed{
border: 2px solid var(--err10);
background: var(--p20);
}
.trashed-text{
background: var(--err10);
font-size: 12px;
padding:2px 10px;
}
.image{
display: flex;
}
.reference-action{
display: none;
}
&:hover{
background: var(--p30);
.reference-action{
display: flex;
align-items: center;
gap: 3px;
}
}
}
.file-preview-small{
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 2px;
border-radius: 12px;
padding: 4px;
//background: var(--p10);
}
.preview-reference{
background: var(--p10);
padding: 10px 20px;
}
.sortable-container {
display: flex;
flex-direction: column;
gap: 5px;
}
.sortable-ghost{
border: 2px dashed var(--p60);
}
.sortable-drag { opacity: 0 !important; } .sortable-ghost { opacity: 1 !important; }
+147
View File
@@ -0,0 +1,147 @@
.record-edit {
position: relative;
max-width: 900px;
.invalid-feedback {
color: var(--text-error);
font-size: 15px;
line-height: 20px;
margin-top: 10px;
}
}
.record-header {
margin: 10px 0 0;
.schema-name {
font-size: 14px;
}
.record-title {
font-size: 18px;
display: block;
}
}
.tools-header {
margin: 30px 0 0;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
font-size: 14px;
position: relative;
z-index: 20;
padding: 10px;
border-radius: 12px;
background: var(--p20);
}
.editor-field {
background: var(--p20);
padding: 18px;
position: relative;
border-radius: 12px;
margin: 6px 0;
border-color: transparent;
.button:not(.primary) {
background: var(--p30);
&:hover {
background: var(--p40);
}
}
dialog {
.button:not(.primary) {
background: var(--p20);
&:hover {
background: var(--p30);
}
}
}
}
.field-header {
margin-bottom: 4px;
position: relative;
.labels {
display: flex;
justify-content: space-between;
align-items: center;
}
.label-and-help {
display: flex;
align-items: center;
gap: 6px;
}
label {
font-size: 14px;
line-height: 14px;
margin: 0;
font-weight: 700;
}
.help-text {
font-size: 14px;
line-height: 14px
}
code {
}
}
.system-help-text {
font-size: 14px;
line-height: 14px;
margin-top: 10px;
}
.field-checkbox {
display: flex;
gap: 20px;
align-items: center;
.form-check-inline {
display: flex;
align-items: center;
gap: 4px;
}
.form-check-label {
font-size: 14px;
line-height: 14px;
}
}
.record-edit-file-preview {
display: flex;
gap: 20px;
.file-details {
width: 50%;
display: flex;
flex-direction: column;
gap: 5px;
}
.file-details-item {
.text-muted {
color: var(--grey-dark);
}
}
}
+60
View File
@@ -0,0 +1,60 @@
.reference-tags {
position: relative;
z-index: 20;
.reference-tags-option {
cursor: pointer;
font-size: 14px;
padding: 3px 10px;
&:hover {
background: var(--p40);
border-radius: 12px;
}
}
&:focus-within {
.reference-tags-results {
display: flex;
}
}
}
.reference-tags-selected-value {
font-size: 13px;
margin-top: 10px;
border-radius: 12px;
background: var(--p30);
padding: 3px 10px;
display: inline-flex;
justify-content: center;
gap: 4px;
line-height: 22px;
&:hover {
background: var(--p40);
}
}
.reference-tags-results {
display: none;
flex-direction: column;
padding: 10px;
overflow: visible;
position: absolute;
border-radius: 12px;
z-index: 20;
background: var(--p30);
//border: 2px solid var(--background-2);
transition: 600ms;
flex-grow: 1;
top: 45px;
width: 100%;
.start-typing {
font-style: italic;
font-size: 13px;
}
}
+46
View File
@@ -0,0 +1,46 @@
/*
1. Use a more-intuitive box-sizing model.
*/
*, *::before, *::after {
box-sizing: border-box;
}
/*
2. Remove default margin
*/
* {
margin: 0;
}
/*
Typographic tweaks!
3. Add accessible line-height
4. Improve text rendering
*/
body {
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
/*
5. Improve media defaults
*/
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}
/*
6. Remove built-in form typography styles
*/
input, button, textarea, select {
font: inherit;
}
/*
7. Avoid text overflows
*/
p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word;
}
/*
8. Create a root stacking context
*/
#root, #__next {
isolation: isolate;
}
+87
View File
@@ -0,0 +1,87 @@
.revisions{
display: flex;
flex-direction: column;
gap: 5px;
.revision{
justify-content: space-between;
display: flex;
gap: 20px;
align-items: center;
background: var(--p20);
padding: 12px;
border-radius: 12px;
.version{
display: flex;
gap: 10px;
}
&.active{
background: var(--p30);
}
}
}
.selected-revision{
margin-top: 30px;
align-items: center;
background: var(--p20);
padding: 12px;
border-radius: 12px;
.button{
background: var(--p30);
}
.revision-field{
display: flex;
gap: 20px;
align-items: center;
padding: 20px 0;
border-bottom: 1px solid var(--p30);
flex: 1;
.compare-left{
width: 45%;
border-radius: 12px;
padding: 20px;
background: var(--p30);
}
.compare-right{
width: 45%;
border-radius: 12px;
padding: 20px;
background: var(--p30);
}
.compare-center{
width: 10%;
height: 100%;
display: flex;
gap: 20px;
align-items: center;
}
}
}
.reference-field{
width: 100px;
}
.revision-references{
display: flex;
gap: 20px;
align-items: center;
padding: 20px 0;
border-bottom: 1px solid var(--p30);
}
.reference-compare{
width: 45%;
border-radius: 12px;
padding: 20px;
background: var(--p30);
}
+150
View File
@@ -0,0 +1,150 @@
.tiptap {
width: 100%;
background: var(--p20);
border: 1px solid var(--p50);
border-radius: 0 0 5px 5px;
padding: 15px 15px;
font-size: 16px;
:first-child {
margin-top: 0;
}
&:focus {
background: var(--p10);
}
img {
&.ProseMirror-selectednode {
box-shadow: 0 0 1px 2px var(--p70);
}
}
}
.editor-field {
.editor-toolbar {
display: flex;
gap: 4px;
background: var(--p30);
border-radius: 5px 5px 0 0;
padding: 5px 7px;
.button:not(.primary) {
font-weight: 700;
&.active {
background: var(--p40);
}
}
}
}
.content {
.tiptap {
li > p {
display: inline;
}
}
}
trix-editor {
background: var(--p20)!important;
border: 1px solid var(--p50)!important;
border-radius: 0 0 5px 5px!important;
padding: 15px 15px!important;
& > div {
margin-bottom: 14px;
font-size: 16px;
line-height: 23px;
}
&:focus {
background: var(--p10)!important;
}
figure.attachment{
display: flex!important;
flex-direction: column!important;
justify-content: center;
align-items: center;
gap: 10px;
}
.attachment {
background: var(--p20);
padding: 12px 0;
text-align: center;
display: flex;
justify-content: center;
img {
margin-bottom: 0;
}
}
[data-trix-mutable].attachment img {
box-shadow: 0 0 1px 2px var(--p70) !important;
}
.trix-button--remove {
box-shadow: none !important;
border: 2px solid var(--p40) !important;
}
.trix-button--remove:hover {
border: 2px solid var(--p40);
}
a {
color: var(--p80);
}
}
trix-toolbar {
.trix-button-row {
display: flex;
}
.trix-button-group {
background: transparent !important;
border: none !important;
display: flex !important;
gap: 4px;
}
.trix-button-group--history-tools,.trix-button-group--file-tools
{
display: none !important;
}
.trix-button {
border-radius: 6px !important;
background: var(--p30) !important;
padding: 14px 22px !important;
margin: 0 !important;
cursor: pointer;
border: 0px solid var(--p30) !important;
font-size: 14px !important;
min-height: 27px !important;
display: flex !important;
align-items: center !important;
gap: 4px;
color: var(--text) !important;
&:before{
background-size: 22px!important;
}
&:hover{
background: var(--p40) !important;
}
&.trix-active{
background: var(--p50) !important;
}
}
}
+98
View File
@@ -0,0 +1,98 @@
.sidebar-top{
border: 0px solid var(--p30);
font-size: 18px;
padding: 20px 20px ;
display: flex;
align-items: center;
justify-content: space-between;
background: var(--p20);
margin-bottom: 15px;
border-radius: 12px;
}
.sidebar {
//border: 1px solid var(--border-context);
border-radius: 12px;
font-size: 15px;
line-height: 28px;
padding: 20px;
background: var(--p20);
display: flex;
flex-direction: column;
gap: 3px;
}
.sidebar-folder{
width: 100%;
margin: 3px 12px 3px;
.sidebar-folder{
margin-left: 5px;
}
}
.sidebar-header {
width: 100%;
display: flex;
cursor: pointer;
justify-content: space-between;
align-items: center;
background: var(--p30);
font-size: 16px;
padding: 3px 12px 3px;
color: var(--text);
border: none;
border-radius: 12px;
&:focus{
box-shadow: none;
}
&:hover {
background: var(--p40);
}
&:first-child {
}
&:last-child {
border-bottom: none;
}
}
.sidebar-item {
color: var(--text);
display: block;
font-size: 14px;
padding: 3px 12px;
text-decoration: none;
transition: 600ms;
border-radius: 12px;
&:last-child {
border-bottom: none;
}
&:hover {
background: var(--p30);
}
&.active {
background: var(--p40);
}
}
.top-nav{
display: flex;
justify-content: end;
align-items: center;
gap: 10px;
}
.top-nav-item{
border-radius: 12px;
font-size: 14px;
background: var(--p20);
padding: 3px 10px ;
&:hover {
background: var(--p30);
}
}
+34
View File
@@ -0,0 +1,34 @@
input.switch {
-webkit-appearance: none;
width: 34px;
height: 18px;
border: 1px solid var(--p40);
position: relative;
border-radius: 50px;
box-sizing: content-box;
cursor: pointer;
transition: background 150ms ease-in-out;
background: white;
}
input.switch::after {
top: 2px;
left: 2px;
transition: left 150ms ease-in-out;
content: " ";
width: 14px;
height: 14px;
background: var(--p40);
box-shadow: inset 0 0 0px 1px var(--p40);
position: absolute;
border-radius: 50px;
}
input.switch:checked {
background: var(--p50);
}
input.switch:checked::after {
left: calc(100% - 17px);
background: var(--p10);
}
+140
View File
@@ -0,0 +1,140 @@
.table {
min-width: 600px;
overflow: auto;
background: var(--p20);
padding: 1px;
font-size: 14px;
border-radius: 12px;
table {
background: var(--p20);
width: 100%;
border-collapse: separate;
border: none;
border-spacing: 0;
}
thead {
border-radius: 12px;
tr {
border-radius: 12px;
}
}
th {
font-size: 14px;
font-weight: normal;
white-space: nowrap;
max-width: 400px;
border: none;
background: var(--p20);
text-align: left;
padding: 8px 16px;
&.is-sort {
font-weight: 700;
}
&:first-child {
border-radius: 12px 0 0 0;
}
&:last-child {
border-radius: 0 12px 0 0;
}
}
td {
font-weight: normal;
white-space: nowrap;
max-width: 400px;
height: 48px;
padding: 4px 16px;
border: none;
overflow: hidden;
//img{
// width: 48px;
//}
.status {
color: var(--text);
font-size: 80%;
}
.row-name {
display: flex;
align-items: center;
gap: 6px;
}
.title-td-contents {
display: flex;
align-items: center;
gap: 6px;
font-size: 14px;
line-height: 14px;
}
}
tbody {
tr {
border-radius: 12px;
background: var(--p10);
border: none;
&:has(input:checked) {
background: var(--p30);
}
&:hover {
background: var(--p20);
}
}
}
tr:nth-child(odd) {
//background-color: #f9f9f9;
}
td:nth-child(odd) {
// border-left: 1px solid #e4e4e4;
// border-right: 1px solid #e4e4e4;
}
th:nth-child(odd) {
}
.field-ui-number {
text-align: right;
}
.references{
display: flex;
gap: 4px;
.reference{
font-size: 13px;
border-radius: 12px;
background: var(--p30);
padding: 1px 5px;
}
}
}
.file-table-row {
display: flex;
align-items: center;
gap: 5px;
& > div {
display: flex;
flex-flow: column;
gap: 5px;
}
}
+11
View File
@@ -0,0 +1,11 @@
.tabs{
padding: 0;
margin: 20px 0 20px;
display: flex;
gap: 4px;
flex-wrap: wrap;
.tab{
list-style: none;
}
}
+64
View File
@@ -0,0 +1,64 @@
.toolbar{
display: flex;
align-items: center;
gap: 5px;
justify-content: space-between;
input.search{
border-radius: 12px;
background: var(--p20);
padding: 4px 10px;
cursor: pointer;
border: none;
font-size: 14px;
}
.selected-filter{
font-size: 13px;
border-radius: 12px;
margin: 2px 0;
background: var(--p30);
padding: 3px 10px;
display: flex;
gap: 4px;
line-height: 22px;
}
.filter-input{
margin: 10px 0 ;
input{
font-size: 13px;
}
}
.applied-filter{
background: var(--p30);
}
}
.toolbar-filters{ display: flex;
align-items: center;
gap: 5px;}
.applied-filters{
display: flex;
gap: 4px;
margin-top: 10px;
.applied-filter {
font-size: 13px;
border-radius: 12px;
background: var(--p20);
padding: 3px 10px;
display: flex;
justify-content: center;
gap: 4px;
line-height: 22px;
}
.applied-filter:hover {
background-color: var(--p30);
}
}
+106
View File
@@ -0,0 +1,106 @@
.content {
font-size: 16px;
line-height: 20px;
font-family: var(--main-font);
color: var(--text);
p{
margin-bottom: 14px;
&:last-child{
margin-bottom: 0;
}
}
h1{
font-size: 24px;
line-height: 34px;
}
h2{
font-size: 20px;
line-height: 30px;
}
h3{
font-size: 18px;
line-height: 28px;
}
ul {
padding: 0 0 0 16px;
list-style: none outside none;
li::before {
content: "—";
opacity: .5;
font-size: 12px;
padding-right: 6px;
vertical-align: 10%;
}
li {
list-style: none;
padding: 0;
}
}
code{
background: var(--p30);
padding: 0 6px;
border-radius: 12px;
}
img{
margin-bottom: 14px;
}
blockquote{
border:1px solid var(--p30);
border-radius: 12px;
padding: 12px 40px;
position: relative;
&::before{
content: "\201C";
color: var(--p60);
font-size:4em;
position: absolute;
left: 10px;
top: 20px;
}
&::after{
content: '';
}
}
pre {
background: var(--grey-light);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
}
.lx-small-text {
font-size: 12px;
line-height: 15px;
}
.light-text{
color: var(--text-light);
}
+114
View File
@@ -0,0 +1,114 @@
.wrapper-tiny {
background-color: var(--p20);
border-radius: 12px;
margin: 44px auto;
width: 600px;
padding: 44px;
}
.common-wrapper {
background-color: var(--p20);
margin: 20px 0;
padding: 20px;
border-radius: 12px;
}
.wrapper-normal {
background-color: #fff;
border-radius: 32px;
margin: 44px auto;
width: 1000px;
padding: 44px;
&.transparent {
margin: 0px auto;
padding: 0px;
background-color: transparent;
}
}
.wrapper-large {
background-color: #fff;
border-radius: 32px;
margin: 44px auto;
max-width: 1920px;
min-width: 1000px;
padding: 44px;
width: fit-content;
&.transparent {
padding: 0px;
margin: 0px auto;
background-color: transparent;
}
}
@media only screen and (max-width: 1800px) {
.wrapper-normal {
margin: 0px 0px 0px auto;
padding: 20px;
&.transparent {
margin: 0px 0px 0px auto;
padding: 40px;
}
}
.wrapper-large {
margin: 44px 0px 0px auto;
padding: 44px;
&.transparent {
margin: 0px 0px 0px auto;
padding: 40px;
}
}
}
@media only screen and (max-width: 1390px) {
.wrapper-normal {
margin: 0px auto;
padding: 20px;
&.transparent {
margin: 0px auto;
padding: 40px;
}
}
.wrapper-large {
margin: 44px 0px 0px auto;
padding: 44px;
&.transparent {
margin: 0px 0px 0px auto;
padding: 40px;
}
}
}
.section-actions {
text-align: center;
padding: 32px 0;
}
.header-normal {
text-align: left;
font-weight: 400;
font-size: 20px;
}
.header-small {
text-align: left;
font-weight: 400;
font-size: 20px;
}
+5
View File
@@ -0,0 +1,5 @@
document.addEventListener("DOMContentLoaded", onReady);
function onReady(){
console.log("ready yo")
}
+108
View File
@@ -0,0 +1,108 @@
@import "./css/reset.css";
@import "./css/helpers.css";
@import "./css/notice.css";
@import "./css/auth.css";
@import "./css/typography.css";
@import "./css/sidebar.css";
@import "./css/form.css";
@import "./css/table.css";
@import "./css/avatar.css";
@import "./css/codemirror.css";
@import "./css/rich.css";
@import "./css/layout.css";
@import "./css/wrappers.css";
@import "./css/toolbar.css";
@import "./css/dropdown.css";
@import "./css/button.css";
@import "./css/checkbox.css";
@import "./css/pagination.css";
@import "./css/record-edit.css";
@import "./css/tabs.css";
@import "./css/switch.css";
@import "./css/preview.css";
@import "./css/dialog.css";
@import "./css/autocomplete.css";
@import "./css/reference-tags.css";
@import "./css/members.css";
@import "./css/revisions.css";
@import "./css/datepicker.css";
:root {
--p10: #f4f9ff;
--p20: #eaf1f9;
--p30: #b3ceff;
--p40: #8db5ff;
--p50: #70a2ff;
--p60: #679cff;
--p70: #4284ff;
--p80: #1c6bff;
--p90: #002b7a;
--p100: #000C23;
--suc10: #d1ffb8;
--suc20: #d1ffb8;
--suc30: #b5ff8d;
--suc40: #a2ff70;
--suc50: #82cc5a;
--suc80: #71b34e;
--suc90: #314c22;
--err10: #ffb9d0;
--err20: #ff9bb3;
--err30: #fe7e97;
--err40: #de617b;
--err50: #be4461;
--err80: #61001a;
--err90: #560012;
--grey-dark: #424656;
--grey-light: #a6abbd;
--text: var(--p100);
--text-light: var(--grey-dark);
--text-error: var(--err50);
--main-font: Open Sans, Arial, Helvetica, sans-serif;
}
body {
background-color: var(--p10);
font-family: var(--main-font), sans-serif;
color: var(--text);
:focus {
outline: none;
box-shadow: 0 0 1px 2px var(--p70);
}
}
.btn-spinner .spinner-border {
display: none;
}
.btn-spinner.spinner-on .spinner-border {
display: inline-block;
}
.cursor-pointer {
cursor: pointer;
}
a {
color: var(--text);
text-decoration: none;
}
.lucent-component {
position: relative;
}
+5
View File
@@ -7,11 +7,16 @@
.cm-content{ .cm-content{
background-color: var(--p10); background-color: var(--p10);
color: var(--p100);
} }
} }
.cm-content{ .cm-content{
background-color: var(--p20); background-color: var(--p20);
}
.ͼ4 .cm-line ::selection, .ͼ4 .cm-line::selection{
background: var(--p40) !important;
} }
.cm-activeLine{ .cm-activeLine{
+43
View File
@@ -0,0 +1,43 @@
.flatpickr-wrapper {
display: block !important;
}
.editor-field {
.flatpickr-calendar {
border-radius: 12px !important;
}
.flatpickr-months .flatpickr-month {
background: var(--p30);
color: var(--text);
font-size: 12px;
}
.flatpickr-current-month .flatpickr-monthDropdown-months {
background: var(--p30);
}
.flatpickr-weekdays{
background: var(--p30);
color: var(--text);
}
.flatpickr-weekdaycontainer .flatpickr-weekday{
background: var(--p30);
color: var(--text);
}
.flatpickr-days{
background: var(--p10);
color: var(--text);
}
.flatpickr-time{
background: var(--p10);
color: var(--text);
}
}
+2 -2
View File
@@ -15,7 +15,7 @@ body:has(dialog[open]) {
dialog { dialog {
margin: 2vh auto; margin: 2vh auto;
background-color: #fff; background-color: var(--p10);
padding: 34px; padding: 34px;
border: none; border: none;
border-radius: 12px; border-radius: 12px;
@@ -49,6 +49,6 @@ dialog::backdrop {
position: sticky; position: sticky;
top: -34px; top: -34px;
z-index: 999; z-index: 999;
background: #fff; background-color: var(--p10);
padding: 10px 0; padding: 10px 0;
} }
+1 -1
View File
@@ -16,7 +16,7 @@
overflow: visible; overflow: visible;
position: absolute; position: absolute;
border-radius: 12px; border-radius: 12px;
z-index: 20; z-index: 22;
background: var(--p20); background: var(--p20);
transition: 600ms; transition: 600ms;
flex-grow: 1; flex-grow: 1;
+14 -1
View File
@@ -5,6 +5,17 @@
gap: 10px; gap: 10px;
background: var(--p10); background: var(--p10);
border-radius: 12px; border-radius: 12px;
&.is-trashed{
border: 2px solid var(--err10);
background: var(--p20);
}
.trashed-text{
background: var(--err10);
font-size: 12px;
padding:2px 10px;
}
.image{ .image{
@@ -20,7 +31,9 @@
&:hover{ &:hover{
background: var(--p30); background: var(--p30);
.reference-action{ .reference-action{
display: block; display: flex;
align-items: center;
gap: 3px;
} }
} }
} }
+1 -1
View File
@@ -1,6 +1,6 @@
.record-edit { .record-edit {
position: relative; position: relative;
max-width: 900px;
.invalid-feedback { .invalid-feedback {
color: var(--text-error); color: var(--text-error);
font-size: 15px; font-size: 15px;
+150
View File
@@ -0,0 +1,150 @@
.tiptap {
width: 100%;
background: var(--p20);
border: 1px solid var(--p50);
border-radius: 0 0 5px 5px;
padding: 15px 15px;
font-size: 16px;
:first-child {
margin-top: 0;
}
&:focus {
background: var(--p10);
}
img {
&.ProseMirror-selectednode {
box-shadow: 0 0 1px 2px var(--p70);
}
}
}
.editor-field {
.editor-toolbar {
display: flex;
gap: 4px;
background: var(--p30);
border-radius: 5px 5px 0 0;
padding: 5px 7px;
.button:not(.primary) {
font-weight: 700;
&.active {
background: var(--p40);
}
}
}
}
.content {
.tiptap {
li > p {
display: inline;
}
}
}
trix-editor {
background: var(--p20)!important;
border: 1px solid var(--p50)!important;
border-radius: 0 0 5px 5px!important;
padding: 15px 15px!important;
& > div {
margin-bottom: 14px;
font-size: 16px;
line-height: 23px;
}
&:focus {
background: var(--p10)!important;
}
figure.attachment{
display: flex!important;
flex-direction: column!important;
justify-content: center;
align-items: center;
gap: 10px;
}
.attachment {
background: var(--p20);
padding: 12px 0;
text-align: center;
display: flex;
justify-content: center;
img {
margin-bottom: 0;
}
}
[data-trix-mutable].attachment img {
box-shadow: 0 0 1px 2px var(--p70) !important;
}
.trix-button--remove {
box-shadow: none !important;
border: 2px solid var(--p40) !important;
}
.trix-button--remove:hover {
border: 2px solid var(--p40);
}
a {
color: var(--p80);
}
}
trix-toolbar {
.trix-button-row {
display: flex;
}
.trix-button-group {
background: transparent !important;
border: none !important;
display: flex !important;
gap: 4px;
}
.trix-button-group--history-tools,.trix-button-group--file-tools
{
display: none !important;
}
.trix-button {
border-radius: 6px !important;
background: var(--p30) !important;
padding: 14px 22px !important;
margin: 0 !important;
cursor: pointer;
border: 0px solid var(--p30) !important;
font-size: 14px !important;
min-height: 27px !important;
display: flex !important;
align-items: center !important;
gap: 4px;
color: var(--text) !important;
&:before{
background-size: 22px!important;
}
&:hover{
background: var(--p40) !important;
}
&.trix-active{
background: var(--p50) !important;
}
}
}
+15 -2
View File
@@ -19,20 +19,33 @@
background: var(--p20); background: var(--p20);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 4px; gap: 3px;
} }
.sidebar-folder{
width: 100%;
margin: 3px 12px 3px;
.sidebar-folder{
margin-left: 5px;
}
}
.sidebar-header { .sidebar-header {
width: 100%;
display: flex; display: flex;
cursor: pointer; cursor: pointer;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
background: var(--p30); background: var(--p30);
font-size: 16px; font-size: 16px;
padding: 3px 12px 6px; padding: 3px 12px 3px;
color: var(--text); color: var(--text);
border: none; border: none;
border-radius: 12px; border-radius: 12px;
&:focus{
box-shadow: none;
}
&:hover { &:hover {
background: var(--p40); background: var(--p40);

Some files were not shown because too many files have changed in this diff Show More