Compare commits
77 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dfe4576725 | |||
| fea7610665 | |||
| 828fca7e8f | |||
| 065b121e10 | |||
| f949852c1a | |||
| 986d3420cb | |||
| e9537862ca | |||
| fda4bced92 | |||
| c3e6f9ff64 | |||
| b208e79d15 | |||
| 19e8d648fc | |||
| 800e2746e0 | |||
| bb27811ddf | |||
| 52a1ec5c5a | |||
| 07b72b0a2c | |||
| 4ef16f3630 | |||
| e06e1db671 | |||
| 174a119c2e | |||
| 55db377abf | |||
| cebe24ea67 | |||
| b729df6923 | |||
| 843f560710 | |||
| 7574d67d80 | |||
| 19931cb4d1 | |||
| 6458c1e71d | |||
| 63232585ab | |||
| 6d15591601 | |||
| 32c8378020 | |||
| d0cd8228cc | |||
| c45a3847f8 | |||
| c0b3878674 | |||
| f868219981 | |||
| 8ac0567e66 | |||
| 02f8f5970a | |||
| 0cd4e08716 | |||
| cf3d621587 | |||
| 6fc0a65b6f | |||
| a73ee21568 | |||
| ff54bcc2ef | |||
| ab1517cc8f | |||
| 9f724a3243 | |||
| ae65ca47f6 | |||
| 74d2fcc4fa | |||
| 82174afdea | |||
| ffc39f078d | |||
| 7c4e19afbc | |||
| 7b10bfca1d | |||
| 0e5ac08641 | |||
| 1505aaa909 | |||
| d9e2c4954a | |||
| 97ad9de3d2 | |||
| 9e140be0ec | |||
| a737c2d571 | |||
| c43c29eb14 | |||
| 0c00f76657 | |||
| 4165bfb95d | |||
| 570dbf747e | |||
| 14cbd0a845 | |||
| c99634bb46 | |||
| 246696f331 | |||
| 0643578d15 | |||
| 3aa9191cba | |||
| c97be8666e | |||
| 509d7c13f2 | |||
| 50c8af7bda | |||
| 5d6869c118 | |||
| ec15f21e67 | |||
| 36165444cf | |||
| 322962403d | |||
| db37653748 | |||
| 5a13ddb2ec | |||
| 9bbd53b586 | |||
| a04e338ce2 | |||
| 2429d4acb5 | |||
| 113533408d | |||
| f9806f60c9 | |||
| 1f3ebafe69 |
@@ -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
|
||||||
|
|||||||
+8
-8
@@ -8,16 +8,17 @@
|
|||||||
"ext-zip": "*",
|
"ext-zip": "*",
|
||||||
"ext-sqlite3": "*",
|
"ext-sqlite3": "*",
|
||||||
"ext-imagick": "*",
|
"ext-imagick": "*",
|
||||||
"php": "^8.2",
|
"ext-pdo": "*",
|
||||||
"guzzlehttp/guzzle": "^7.2",
|
"php": "^8.3",
|
||||||
"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",
|
||||||
|
"guzzlehttp/guzzle": "^7.9"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpstan/phpstan": "^1.8"
|
"laravel/framework": "^10.48",
|
||||||
|
"phpstan/phpstan": "^1.12"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
@@ -25,8 +26,7 @@
|
|||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"src/Response.php",
|
"src/Response.php",
|
||||||
"src/macros.php",
|
"src/macros.php"
|
||||||
"src/File/Uploader.php"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"extra": {
|
"extra": {
|
||||||
|
|||||||
Generated
+4001
-597
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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",
|
||||||
|
|||||||
Vendored
+225
File diff suppressed because one or more lines are too long
Vendored
-5
File diff suppressed because one or more lines are too long
Vendored
-188
File diff suppressed because one or more lines are too long
Vendored
+3
-6
@@ -1,14 +1,11 @@
|
|||||||
{
|
{
|
||||||
"main.js": {
|
"main.js": {
|
||||||
"file": "assets/main.88a796e4.js",
|
"file": "assets/main-C4XTQmaY.js",
|
||||||
|
"name": "main",
|
||||||
"src": "main.js",
|
"src": "main.js",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"css": [
|
"css": [
|
||||||
"assets/main.587d6006.css"
|
"assets/main-BJijircB.css"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"main.css": {
|
|
||||||
"file": "assets/main.587d6006.css",
|
|
||||||
"src": "main.css"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Vendored
+3
@@ -5,6 +5,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import {loadHtmxFormsBehaviour} from "./htmx-form.js";
|
||||||
|
|
||||||
|
loadHtmxFormsBehaviour();
|
||||||
window.axios = axios;
|
window.axios = axios;
|
||||||
export const axiosInstance = axios;
|
export const axiosInstance = axios;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
};
|
||||||
|
}
|
||||||
+39
-3
@@ -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 "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,3 +30,39 @@ export function stripHtml(html = "") {
|
|||||||
export function randomId(length = 10) {
|
export function randomId(length = 10) {
|
||||||
return Math.random().toString(36).substring(2, length + 2);
|
return Math.random().toString(36).substring(2, length + 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function clickOutside(node) {
|
||||||
|
|
||||||
|
const handleClick = event => {
|
||||||
|
if (node && !node.contains(event.target) && !event.defaultPrevented) {
|
||||||
|
node.dispatchEvent(
|
||||||
|
new CustomEvent('click_outside', node)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('click', handleClick, true);
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
document.removeEventListener('click', handleClick, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
export function loadHtmxFormsBehaviour(){
|
||||||
|
document.querySelectorAll(".form").forEach(el => {
|
||||||
|
initHtmxForm(el);
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function initHtmxForm(el){
|
||||||
|
el.addEventListener("htmx:responseError", (e) => {
|
||||||
|
el.querySelector(".form-errors").innerHTML = e.detail.xhr.response;
|
||||||
|
});
|
||||||
|
|
||||||
|
const formEl = el.querySelector("form");
|
||||||
|
|
||||||
|
if(!formEl.getAttribute("hx-redirect")){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
el.addEventListener("htmx:afterOnLoad", (e) => {
|
||||||
|
if(e.detail.successful){
|
||||||
|
return window.location.href = formEl.getAttribute("hx-redirect");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
+1
-12
@@ -1,23 +1,13 @@
|
|||||||
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 * as bootstrap from "bootstrap";
|
|
||||||
import Mustache from "mustache";
|
import Mustache from "mustache";
|
||||||
|
import 'htmx.org';
|
||||||
|
|
||||||
Mustache.escape = function (value) {
|
Mustache.escape = function (value) {
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
function enableTooltipsAnywhere() {
|
|
||||||
// Enable tooltips everywhere
|
|
||||||
let tooltipTriggerList = [].slice.call(
|
|
||||||
document.querySelectorAll('[data-bs-toggle="tooltip"]')
|
|
||||||
);
|
|
||||||
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
|
||||||
return new bootstrap.Tooltip(tooltipTriggerEl);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define all components
|
// Define all components
|
||||||
const entryComponents = {
|
const entryComponents = {
|
||||||
@@ -61,4 +51,3 @@ let loadSvelte = function () {
|
|||||||
|
|
||||||
// document.addEventListener("turbo:load", loadSvelte);
|
// document.addEventListener("turbo:load", loadSvelte);
|
||||||
document.addEventListener("DOMContentLoaded", loadSvelte);
|
document.addEventListener("DOMContentLoaded", loadSvelte);
|
||||||
document.addEventListener("DOMContentLoaded", enableTooltipsAnywhere);
|
|
||||||
|
|||||||
@@ -21,8 +21,8 @@
|
|||||||
setContext("channel", channel);
|
setContext("channel", channel);
|
||||||
setContext("user", user);
|
setContext("user", user);
|
||||||
</script>
|
</script>
|
||||||
<div class="text-center">
|
<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}/>
|
||||||
|
|||||||
@@ -4,9 +4,10 @@
|
|||||||
import RecordEdit from "./records/Edit.svelte";
|
import RecordEdit from "./records/Edit.svelte";
|
||||||
import ContentIndex from "./content/Index.svelte";
|
import ContentIndex from "./content/Index.svelte";
|
||||||
import {setContext} from "svelte";
|
import {setContext} from "svelte";
|
||||||
import Navbar from "./Navbar.svelte";
|
import Navbar from "./layout/Navbar.svelte";
|
||||||
import HomeIndex from "./home/Index.svelte";
|
import HomeIndex from "./home/Index.svelte";
|
||||||
import BuildReport from "./build/Report.svelte";
|
import BuildReport from "./build/Report.svelte";
|
||||||
|
import Header from "./layout/Header.svelte";
|
||||||
|
|
||||||
const components = {
|
const components = {
|
||||||
members: Members,
|
members: Members,
|
||||||
@@ -35,7 +36,16 @@
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Navbar schema={data.schema}/>
|
|
||||||
|
|
||||||
<svelte:component this={components[view]} {title} {...data}/>
|
<div class="main-wrapper">
|
||||||
|
<div class="sidebar-content">
|
||||||
|
<Navbar schema={data.schema}/>
|
||||||
|
</div>
|
||||||
|
<div class="main-content">
|
||||||
|
<Header />
|
||||||
|
<svelte:component this={components[view]} {title} {...data}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,126 +0,0 @@
|
|||||||
<script>
|
|
||||||
import Avatar from "./account/Avatar.svelte";
|
|
||||||
import NavbarMenu from "./NavbarMenu.svelte";
|
|
||||||
import {getContext} from "svelte";
|
|
||||||
|
|
||||||
export let schema;
|
|
||||||
const channel = getContext("channel");
|
|
||||||
const readableSchemas = getContext("readableSchemas");
|
|
||||||
const user = getContext("user");
|
|
||||||
|
|
||||||
let contentIsOpen = false;
|
|
||||||
const fileSchemas = readableSchemas.filter((sc) => sc.type === "files");
|
|
||||||
const otherSchemas = readableSchemas.filter((sc) => !sc.isEntry && sc.type === "collection");
|
|
||||||
|
|
||||||
let filesIsActive = false;
|
|
||||||
let otherIsActive = false;
|
|
||||||
if(schema){
|
|
||||||
filesIsActive = fileSchemas.filter(s => s.name === schema.name).length > 0;
|
|
||||||
otherIsActive = otherSchemas.filter(s => s.name === schema.name).length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<nav class="lx-nav">
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<button on:click={(e) => contentIsOpen = true} class="btn btn-primary btn-sm d-xxl-none">« Content</button>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex align-items-center ">
|
|
||||||
<a class="nav-item" href="{channel.lucentUrl}">{channel.name}</a>
|
|
||||||
<a class="nav-item" href="{channel.lucentUrl}/members">Members</a>
|
|
||||||
|
|
||||||
{#if channel.generateCommand}
|
|
||||||
<a href="{channel.lucentUrl}/build-report" class="btn btn-outline-primary btn-sm d-">Build website</a>
|
|
||||||
{/if}
|
|
||||||
<!-- <div>-->
|
|
||||||
<!-- <form method="GET">-->
|
|
||||||
<!-- <input type="search" name="filter[search_regex]" placeholder="Search"-->
|
|
||||||
<!-- class="form-control" required/>-->
|
|
||||||
<!-- </form>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a class="nav-item" href="{channel.lucentUrl}/profile">
|
|
||||||
<Avatar side="28" name={user.name}/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="offcanvas offcanvas-start d-xxl-block show border-0 bg-light-subtle" class:d-none={!contentIsOpen}
|
|
||||||
style="padding-top:36px " data-bs-scroll="true"
|
|
||||||
data-bs-backdrop="false"
|
|
||||||
tabindex="-1" aria-labelledby="offcanvasScrollingLabel">
|
|
||||||
<!-- <div class="offcanvas-header">-->
|
|
||||||
<!-- <h5 class="offcanvas-title" id="offcanvasScrollingLabel">Content</h5>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<div class="offcanvas-body">
|
|
||||||
<button on:click={(e) => contentIsOpen = false} class="btn btn-primary btn-sm d-xxl-none mb-4">« close</button>
|
|
||||||
<div class="accordion">
|
|
||||||
<div class="accordion-item">
|
|
||||||
<h2 class="accordion-header" id="panelsStayOpen-headingMain">
|
|
||||||
<button class="accordion-button" type="button" data-bs-toggle="collapse"
|
|
||||||
data-bs-target="#panelsStayOpen-collapseMain" aria-expanded="true"
|
|
||||||
aria-controls="panelsStayOpen-collapseMain">
|
|
||||||
Main
|
|
||||||
</button>
|
|
||||||
</h2>
|
|
||||||
<div id="panelsStayOpen-collapseMain" class="accordion-collapse collapse show"
|
|
||||||
aria-labelledby="panelsStayOpen-headingMain">
|
|
||||||
<div class="accordion-body">
|
|
||||||
<NavbarMenu
|
|
||||||
schemas={ readableSchemas.filter((sc) => sc.isEntry)}
|
|
||||||
schema={schema}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#if otherSchemas.length > 0}
|
|
||||||
<div class="accordion-item">
|
|
||||||
<h2 class="accordion-header" id="panelsStayOpen-headingOther">
|
|
||||||
<button class="accordion-button" class:collapsed={!otherIsActive} type="button" data-bs-toggle="collapse"
|
|
||||||
data-bs-target="#panelsStayOpen-collapseOther" aria-expanded={otherIsActive}
|
|
||||||
aria-controls="panelsStayOpen-collapseOther">
|
|
||||||
Other
|
|
||||||
</button>
|
|
||||||
</h2>
|
|
||||||
<div id="panelsStayOpen-collapseOther" class="accordion-collapse collapse"
|
|
||||||
class:show={otherIsActive}
|
|
||||||
aria-labelledby="panelsStayOpen-headingOther">
|
|
||||||
<div class="accordion-body">
|
|
||||||
<NavbarMenu
|
|
||||||
schemas={ otherSchemas}
|
|
||||||
schema={schema}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if fileSchemas.length > 0}
|
|
||||||
<div class="accordion-item">
|
|
||||||
<h2 class="accordion-header" id="panelsStayOpen-headingFS">
|
|
||||||
<button class="accordion-button " class:collapsed={!filesIsActive} type="button" data-bs-toggle="collapse"
|
|
||||||
data-bs-target="#panelsStayOpen-collapseFS" aria-expanded={filesIsActive}
|
|
||||||
aria-controls="panelsStayOpen-collapseFS">
|
|
||||||
Filesystem
|
|
||||||
</button>
|
|
||||||
</h2>
|
|
||||||
<div id="panelsStayOpen-collapseFS" class="accordion-collapse collapse" class:show={filesIsActive}
|
|
||||||
aria-labelledby="panelsStayOpen-headingFS">
|
|
||||||
<div class="accordion-body">
|
|
||||||
<NavbarMenu
|
|
||||||
schemas={ fileSchemas}
|
|
||||||
schema={schema}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<script>
|
|
||||||
import {getContext} from "svelte";
|
|
||||||
|
|
||||||
const channel = getContext("channel");
|
|
||||||
export let schemas;
|
|
||||||
export let schema;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="list-group list-group-flush">
|
|
||||||
|
|
||||||
{#each schemas as aschema}
|
|
||||||
<a class="list-group-item list-group-item-action" class:active={aschema.name === schema?.name}
|
|
||||||
aria-current="page"
|
|
||||||
href="{channel.lucentUrl}/content/{aschema.name}">{aschema.label}</a>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -55,28 +54,28 @@
|
|||||||
<Avatar name={user.name}/>
|
<Avatar name={user.name}/>
|
||||||
</h3>
|
</h3>
|
||||||
<form on:submit={saveName}>
|
<form on:submit={saveName}>
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-5">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={name}
|
bind:value={name}
|
||||||
class="form-control"
|
class="form-control mb-3"
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<SpinnerButton label="Update"/>
|
<SpinnerButton label="Update Name"/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<form on:submit={saveEmail}>
|
<form on:submit={saveEmail}>
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-5">
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
bind:value={email}
|
bind:value={email}
|
||||||
class="form-control"
|
class="form-control mb-3"
|
||||||
placeholder="Email"
|
placeholder="Email"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<SpinnerButton label="Update"/>
|
<SpinnerButton label="Update Email"/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<script>
|
||||||
|
|
||||||
|
import Selectlist from "./Selectlist.svelte";
|
||||||
|
import Icon from "../common/Icon.svelte";
|
||||||
|
|
||||||
|
let searchEl;
|
||||||
|
let search;
|
||||||
|
export let value;
|
||||||
|
export let field;
|
||||||
|
|
||||||
|
function handleSelect(){
|
||||||
|
searchEl.focus();
|
||||||
|
searchEl.blur()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="autocomplete">
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
bind:value={search}
|
||||||
|
bind:this={searchEl}
|
||||||
|
placeholder="Search for options"
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
|
<div class="autocomplete-results">
|
||||||
|
<Selectlist
|
||||||
|
{field}
|
||||||
|
bind:value
|
||||||
|
bind:search
|
||||||
|
on:selected={handleSelect}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if value}
|
||||||
|
<div class="autocomplete-selected-value">
|
||||||
|
{#if Array.isArray(field.selectOptions)}
|
||||||
|
{value}
|
||||||
|
{:else}
|
||||||
|
{field.selectOptions[value]}
|
||||||
|
{/if}
|
||||||
|
<button
|
||||||
|
on:click|preventDefault={(e) => (value = "")}
|
||||||
|
type="button"
|
||||||
|
class="button-text"
|
||||||
|
aria-label="Close"
|
||||||
|
>
|
||||||
|
<Icon width={12} height={12} icon="close"></Icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<script>
|
||||||
|
import Fuse from "fuse.js";
|
||||||
|
import {createEventDispatcher} from "svelte";
|
||||||
|
|
||||||
|
export let field;
|
||||||
|
export let value;
|
||||||
|
export let search = "";
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
|
||||||
|
function select(e, option) {
|
||||||
|
e.preventDefault();
|
||||||
|
value = option.value;
|
||||||
|
search = "";
|
||||||
|
dispatch("selected", {option: option})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function formatOptionsForSearch(listOptions) {
|
||||||
|
if (Array.isArray(listOptions)) {
|
||||||
|
return listOptions.map(value => {
|
||||||
|
return {
|
||||||
|
value: value,
|
||||||
|
label: value,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.entries(listOptions).map(([k, v]) => {
|
||||||
|
return {
|
||||||
|
value: k,
|
||||||
|
label: v,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let formattedOptions = formatOptionsForSearch(field.selectOptions);
|
||||||
|
const fuse = new Fuse(formattedOptions, {
|
||||||
|
includeScore: false,
|
||||||
|
keys: ['value', 'label']
|
||||||
|
})
|
||||||
|
$: result = search === "" ? formattedOptions : fuse.search(search).map(resItem => resItem.item)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if result}
|
||||||
|
{#each result as suggestion (suggestion.value)}
|
||||||
|
<div
|
||||||
|
class="autocomplete-option"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
on:click={(e) => select(e, suggestion)}
|
||||||
|
on:keypress={(e) => select(e, suggestion)}
|
||||||
|
>
|
||||||
|
{suggestion.label}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
@@ -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()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -41,31 +42,41 @@
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper-tiny transparent mb-5">
|
<div class="common-wrapper">
|
||||||
<div class="lx-card mt-5">
|
<div class="lx-card mt-5">
|
||||||
|
|
||||||
<h3 class="header-small mb-5">{title}</h3>
|
<h3 class="header-small mb-5">{title}</h3>
|
||||||
|
|
||||||
<button on:click={buildWebsite} class="btn btn-outline-primary btn-sm 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}> </div>
|
||||||
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<style>
|
||||||
|
.logs{
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow: scroll;
|
||||||
|
background: var(--p90);
|
||||||
|
color: var(--p10);
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<script>
|
||||||
|
|
||||||
|
let checkboxEl = null;
|
||||||
|
|
||||||
|
export let indeterminate = false;
|
||||||
|
|
||||||
|
export let value;
|
||||||
|
export let checked = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="checkbox-wrapper">
|
||||||
|
<input bind:this={checkboxEl} on:change id="c1-13" type="checkbox" {value} {indeterminate} checked={checked}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
@@ -1,23 +1,33 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import {clickOutside} from "../../helpers.js";
|
||||||
|
|
||||||
|
let dropdownMenu;
|
||||||
|
export let orientation = "left";
|
||||||
|
|
||||||
|
export function open() {
|
||||||
|
dropdownMenu.classList.remove("hide")
|
||||||
|
}
|
||||||
|
|
||||||
|
export function close() {
|
||||||
|
dropdownMenu.classList.add("hide")
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClickOutside() {
|
||||||
|
dropdownMenu.classList.add("hide")
|
||||||
|
}
|
||||||
|
|
||||||
export let width = "300";
|
|
||||||
let dropdownMenu;
|
|
||||||
export function hide(){
|
|
||||||
dropdownMenu.classList.remove("show")
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
<div class="dropdown">
|
||||||
|
<button
|
||||||
|
class="button dropdown-button"
|
||||||
|
type="button"
|
||||||
|
on:click={open}
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
<slot name="button">Dropdown</slot>
|
||||||
|
</button>
|
||||||
|
<div bind:this={dropdownMenu} class="dropdown-menu hide orientation-{orientation}" use:clickOutside on:click_outside={handleClickOutside}>
|
||||||
|
<slot/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
</div>
|
||||||
|
|
||||||
class="btn btn-sm btn-outline-primary dropdown-toggle d-flex align-items-center"
|
|
||||||
type="button"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
data-bs-auto-close="outside"
|
|
||||||
aria-expanded="false"
|
|
||||||
>
|
|
||||||
<slot name="button">Dropdown</slot>
|
|
||||||
</button>
|
|
||||||
<div bind:this={dropdownMenu} class="dropdown-menu" style="width:{width}px;">
|
|
||||||
<slot/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@@ -3,7 +3,8 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if message}
|
{#if message}
|
||||||
<div class="alert alert-danger" role="alert">
|
<div class="notice notice-error" role="alert">
|
||||||
{message}
|
<div class="title">Submission Errors</div>
|
||||||
|
<div class="content"> {message}</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
path: '<path d="M447.1 224c0-12.56-4.781-25.13-14.35-34.76l-174.9-174.9C249.1 4.786 236.5 0 223.1 0C211.4 0 198.9 4.786 189.2 14.35L14.35 189.2C4.783 198.9-.0011 211.4-.0011 223.1c0 12.56 4.785 25.17 14.35 34.8l174.9 174.9c9.625 9.562 22.19 14.35 34.75 14.35s25.13-4.783 34.75-14.35l174.9-174.9C443.2 249.1 447.1 236.6 447.1 224zM96 248c-13.25 0-23.1-10.75-23.1-23.1s10.75-23.1 23.1-23.1S120 210.8 120 224S109.3 248 96 248zM224 376c-13.25 0-23.1-10.75-23.1-23.1s10.75-23.1 23.1-23.1s23.1 10.75 23.1 23.1S237.3 376 224 376zM224 248c-13.25 0-23.1-10.75-23.1-23.1s10.75-23.1 23.1-23.1S248 210.8 248 224S237.3 248 224 248zM224 120c-13.25 0-23.1-10.75-23.1-23.1s10.75-23.1 23.1-23.1s23.1 10.75 23.1 23.1S237.3 120 224 120zM352 248c-13.25 0-23.1-10.75-23.1-23.1s10.75-23.1 23.1-23.1s23.1 10.75 23.1 23.1S365.3 248 352 248zM591.1 192l-118.7 0c4.418 10.27 6.604 21.25 6.604 32.23c0 20.7-7.865 41.38-23.63 57.14l-136.2 136.2v46.37C320 490.5 341.5 512 368 512h223.1c26.5 0 47.1-21.5 47.1-47.1V240C639.1 213.5 618.5 192 591.1 192zM479.1 376c-13.25 0-23.1-10.75-23.1-23.1s10.75-23.1 23.1-23.1s23.1 10.75 23.1 23.1S493.2 376 479.1 376z"/>',
|
path: '<path d="M447.1 224c0-12.56-4.781-25.13-14.35-34.76l-174.9-174.9C249.1 4.786 236.5 0 223.1 0C211.4 0 198.9 4.786 189.2 14.35L14.35 189.2C4.783 198.9-.0011 211.4-.0011 223.1c0 12.56 4.785 25.17 14.35 34.8l174.9 174.9c9.625 9.562 22.19 14.35 34.75 14.35s25.13-4.783 34.75-14.35l174.9-174.9C443.2 249.1 447.1 236.6 447.1 224zM96 248c-13.25 0-23.1-10.75-23.1-23.1s10.75-23.1 23.1-23.1S120 210.8 120 224S109.3 248 96 248zM224 376c-13.25 0-23.1-10.75-23.1-23.1s10.75-23.1 23.1-23.1s23.1 10.75 23.1 23.1S237.3 376 224 376zM224 248c-13.25 0-23.1-10.75-23.1-23.1s10.75-23.1 23.1-23.1S248 210.8 248 224S237.3 248 224 248zM224 120c-13.25 0-23.1-10.75-23.1-23.1s10.75-23.1 23.1-23.1s23.1 10.75 23.1 23.1S237.3 120 224 120zM352 248c-13.25 0-23.1-10.75-23.1-23.1s10.75-23.1 23.1-23.1s23.1 10.75 23.1 23.1S365.3 248 352 248zM591.1 192l-118.7 0c4.418 10.27 6.604 21.25 6.604 32.23c0 20.7-7.865 41.38-23.63 57.14l-136.2 136.2v46.37C320 490.5 341.5 512 368 512h223.1c26.5 0 47.1-21.5 47.1-47.1V240C639.1 213.5 618.5 192 591.1 192zM479.1 376c-13.25 0-23.1-10.75-23.1-23.1s10.75-23.1 23.1-23.1s23.1 10.75 23.1 23.1S493.2 376 479.1 376z"/>',
|
||||||
viewBox: "0 0 640 512",
|
viewBox: "0 0 640 512",
|
||||||
},
|
},
|
||||||
|
|
||||||
"triangle-exclamation": {
|
"triangle-exclamation": {
|
||||||
path: '<path d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/>',
|
path: '<path d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/>',
|
||||||
viewBox: "0 0 512 512",
|
viewBox: "0 0 512 512",
|
||||||
@@ -105,7 +105,36 @@
|
|||||||
path: '<path d="M438.6 105.4C451.1 117.9 451.1 138.1 438.6 150.6L182.6 406.6C170.1 419.1 149.9 419.1 137.4 406.6L9.372 278.6C-3.124 266.1-3.124 245.9 9.372 233.4C21.87 220.9 42.13 220.9 54.63 233.4L159.1 338.7L393.4 105.4C405.9 92.88 426.1 92.88 438.6 105.4H438.6z"/>',
|
path: '<path d="M438.6 105.4C451.1 117.9 451.1 138.1 438.6 150.6L182.6 406.6C170.1 419.1 149.9 419.1 137.4 406.6L9.372 278.6C-3.124 266.1-3.124 245.9 9.372 233.4C21.87 220.9 42.13 220.9 54.63 233.4L159.1 338.7L393.4 105.4C405.9 92.88 426.1 92.88 438.6 105.4H438.6z"/>',
|
||||||
viewBox: "0 0 448 512",
|
viewBox: "0 0 448 512",
|
||||||
},
|
},
|
||||||
|
"close": {
|
||||||
|
path: '<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 17.94 6M18 18 6.06 6"/>',
|
||||||
|
viewBox: "0 0 24 24",
|
||||||
|
},
|
||||||
|
"arrow-left": {
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
"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;
|
||||||
export let height = 16;
|
export let height = 16;
|
||||||
export let icon = "";
|
export let icon = "";
|
||||||
@@ -116,15 +145,16 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
class="bi"
|
class="bi"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
{width}
|
{width}
|
||||||
{height}
|
{height}
|
||||||
viewBox={selectedIcon.viewBox}
|
viewBox={selectedIcon.viewBox}
|
||||||
aria-labelledby={icon}
|
aria-labelledby={icon}
|
||||||
role="presentation"
|
role="presentation"
|
||||||
{stroke}
|
{stroke}
|
||||||
{fill}
|
{fill}
|
||||||
|
|
||||||
>
|
>
|
||||||
{@html selectedIcon.path}
|
{@html selectedIcon.path}
|
||||||
</svg>
|
</svg>
|
||||||
@@ -132,5 +162,6 @@
|
|||||||
<style>
|
<style>
|
||||||
svg {
|
svg {
|
||||||
vertical-align: text-top;
|
vertical-align: text-top;
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary btn-spinner" {disabled}>
|
<button type="submit" class="button secondary btn-spinner" {disabled}>
|
||||||
<span
|
<span
|
||||||
class="spinner-border spinner-border-sm"
|
class="spinner-border spinner-border-sm"
|
||||||
role="status"
|
role="status"
|
||||||
|
|||||||
@@ -12,21 +12,10 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if isVisible}
|
{#if isVisible}
|
||||||
<div
|
<div class="notice notice-success" transition:fly={{ duration: 500 }} role="alert">
|
||||||
transition:fly={{ duration: 500 }}
|
<div class="title">Success</div>
|
||||||
class="lx-alert text-white bg-success border-1 border rounded px-3 py-0 text-center"
|
<div class="content"> {message}</div>
|
||||||
role="alert"
|
</div>
|
||||||
>
|
|
||||||
{message}
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
|
||||||
.lx-alert {
|
|
||||||
position: fixed;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
top: 45px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<script>
|
||||||
|
|
||||||
|
|
||||||
|
export let value;
|
||||||
|
export let checked = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input type="checkbox" {value} on:change
|
||||||
|
class="switch" {checked}/>
|
||||||
@@ -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,60 +25,44 @@
|
|||||||
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>
|
||||||
|
|
||||||
<div class="d-flex align-items-center mb-3">
|
<div style="display: flex;align-items: center; gap: 8px">
|
||||||
<span class="me-2">{selected.length} records selected</span>
|
<span class="me-2">{selected.length} records selected</span>
|
||||||
<div class="btn-group " role="group" aria-label="Basic example">
|
<button
|
||||||
<button
|
|
||||||
on:click|preventDefault={(e) => changeStatus(e, "published")}
|
on:click|preventDefault={(e) => changeStatus(e, "published")}
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-sm btn-outline-primary">Publish
|
class="button">Publish
|
||||||
</button
|
</button
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
on:click|preventDefault={(e) => changeStatus(e, "draft")}
|
on:click|preventDefault={(e) => changeStatus(e, "draft")}
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-sm btn-outline-primary">Make Draft
|
class="button">Make Draft
|
||||||
</button
|
</button
|
||||||
>
|
>
|
||||||
{#if filter["status_in"] === "trashed"}
|
{#if filter["status_in"] === "trashed"}
|
||||||
<button
|
<button
|
||||||
on:click|preventDefault={(e) => changeStatus(e, "published")}
|
|
||||||
type="button"
|
|
||||||
class="btn btn-sm btn-outline-primary">Publish
|
|
||||||
</button
|
|
||||||
>
|
|
||||||
{#if schema.hasDrafts}
|
|
||||||
<button
|
|
||||||
on:click|preventDefault={(e) => changeStatus(e, "draft")}
|
|
||||||
type="button"
|
|
||||||
class="btn btn-sm btn-outline-primary">Make Draft
|
|
||||||
</button
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
<button
|
|
||||||
on:click|preventDefault={deleteRecords}
|
on:click|preventDefault={deleteRecords}
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-sm btn-outline-primary">Delete forever
|
class="button">Delete forever
|
||||||
</button
|
</button
|
||||||
>
|
>
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click|preventDefault={(e) => changeStatus(e, "trashed")}
|
on:click|preventDefault={(e) => changeStatus(e, "trashed")}
|
||||||
class="btn btn-sm btn-outline-primary">Move to trash
|
class="button">Move to trash
|
||||||
</button
|
</button
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
import ActionsOnSelected from "./ActionsOnSelected.svelte";
|
import ActionsOnSelected from "./ActionsOnSelected.svelte";
|
||||||
import Table from "./Table.svelte";
|
import Table from "./Table.svelte";
|
||||||
import {getContext} from "svelte";
|
import {getContext} from "svelte";
|
||||||
import Grid from "./Grid.svelte";
|
|
||||||
|
|
||||||
const axios = getContext("axios");
|
const axios = getContext("axios");
|
||||||
export let schema;
|
export let schema;
|
||||||
@@ -39,6 +38,7 @@
|
|||||||
limit = response.data.limit;
|
limit = response.data.limit;
|
||||||
total = response.data.total;
|
total = response.data.total;
|
||||||
modalUrl = response.data.modalUrl;
|
modalUrl = response.data.modalUrl;
|
||||||
|
document.querySelector("dialog h3").scrollIntoView();
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@@ -46,8 +46,8 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper-large transparent ">
|
<div class="">
|
||||||
<div class="lx-card mb-4 {inModal ? 'mt-0' : 'mt-5'}">
|
<div class="{inModal ? 'mt-0' : 'mt-5'}">
|
||||||
<h3 class="header-normal mb-5 ">
|
<h3 class="header-normal mb-5 ">
|
||||||
{schema.label}
|
{schema.label}
|
||||||
</h3>
|
</h3>
|
||||||
@@ -70,28 +70,19 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if schema.type === "collection"}
|
<Table
|
||||||
<Table
|
{records}
|
||||||
{records}
|
{graph}
|
||||||
{graph}
|
{schema}
|
||||||
{schema}
|
{sortParam}
|
||||||
{sortParam}
|
{sortField}
|
||||||
{sortField}
|
{systemFields}
|
||||||
{systemFields}
|
{inModal}
|
||||||
{inModal}
|
{users}
|
||||||
{users}
|
{isWritable}
|
||||||
{isWritable}
|
bind:selected
|
||||||
bind:selected
|
/>
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<Grid
|
|
||||||
{records}
|
|
||||||
{schema}
|
|
||||||
{isWritable}
|
|
||||||
bind:selected
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Pagination
|
<Pagination
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<RenderField {record} {schema} {graph} {field}/>
|
<RenderField {record} {schema} {graph} {field}/>
|
||||||
</td>
|
</td>
|
||||||
{/each}
|
{/each}
|
||||||
{#if schema.visible.includes("status")}
|
{#if schema.visible?.includes("status")}
|
||||||
<td
|
<td
|
||||||
class="text-center"
|
class="text-center"
|
||||||
class:is-sort={"-status" == sortParam || "status" == sortParam}
|
class:is-sort={"-status" == sortParam || "status" == sortParam}
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
<Status status={record.status}/>
|
<Status status={record.status}/>
|
||||||
</td>
|
</td>
|
||||||
{/if}
|
{/if}
|
||||||
{#if schema.visible.includes("_sys.createdBy")}
|
{#if schema.visible?.includes("_sys.createdBy")}
|
||||||
<td
|
<td
|
||||||
class="text-center"
|
class="text-center"
|
||||||
class:is-sort={"-_sys.createdBy" == sortParam || "_sys.createdBy" == sortParam}
|
class:is-sort={"-_sys.createdBy" == sortParam || "_sys.createdBy" == sortParam}
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
<Avatar name={usernameById(users, record._sys.createdBy)} side={24}/>
|
<Avatar name={usernameById(users, record._sys.createdBy)} side={24}/>
|
||||||
</td>
|
</td>
|
||||||
{/if}
|
{/if}
|
||||||
{#if schema.visible.includes("_sys.updatedBy")}
|
{#if schema.visible?.includes("_sys.updatedBy")}
|
||||||
<td
|
<td
|
||||||
class="text-center"
|
class="text-center"
|
||||||
class:is-sort={"-_sys.updatedBy" == sortParam || "_sys.updatedBy" == sortParam}
|
class:is-sort={"-_sys.updatedBy" == sortParam || "_sys.updatedBy" == sortParam}
|
||||||
@@ -47,12 +47,12 @@
|
|||||||
<Avatar name={usernameById(users, record._sys.updatedBy)} side={24}/>
|
<Avatar name={usernameById(users, record._sys.updatedBy)} side={24}/>
|
||||||
</td>
|
</td>
|
||||||
{/if}
|
{/if}
|
||||||
{#if schema.visible.includes("_sys.createdAt")}
|
{#if schema.visible?.includes("_sys.createdAt")}
|
||||||
<td class:is-sort={"-_sys.createdAt" == sortParam || "_sys.createdAt" == sortParam}>
|
<td class:is-sort={"-_sys.createdAt" == sortParam || "_sys.createdAt" == sortParam}>
|
||||||
{friendlyDate(record._sys.createdAt)}
|
{friendlyDate(record._sys.createdAt)}
|
||||||
</td>
|
</td>
|
||||||
{/if}
|
{/if}
|
||||||
{#if schema.visible.includes("_sys.updatedAt")}
|
{#if schema.visible?.includes("_sys.updatedAt")}
|
||||||
<td class:is-sort={"-_sys.updatedAt" == sortParam || "_sys.updatedAt" == sortParam}>
|
<td class:is-sort={"-_sys.updatedAt" == sortParam || "_sys.updatedAt" == sortParam}>
|
||||||
{friendlyDate(record._sys.updatedAt)}
|
{friendlyDate(record._sys.updatedAt)}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
import {getContext} from "svelte";
|
import {getContext} from "svelte";
|
||||||
import Avatar from "../account/Avatar.svelte";
|
import Avatar from "../account/Avatar.svelte";
|
||||||
import {selectRecord, toggleAll} from "./functions/recordSelect.js";
|
import {selectRecord, toggleAll} from "./functions/recordSelect.js";
|
||||||
|
import Checkbox from "../common/Checkbox.svelte";
|
||||||
|
import Preview from "../files/Preview.svelte";
|
||||||
|
import {fileurl} from "../files/imageserver.js";
|
||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
|
|
||||||
@@ -20,31 +23,29 @@
|
|||||||
export let selected = [];
|
export let selected = [];
|
||||||
|
|
||||||
function eventToggleAll(e) {
|
function eventToggleAll(e) {
|
||||||
selected = toggleAll(e,records,selected)
|
selected = toggleAll(e, records, selected)
|
||||||
}
|
}
|
||||||
|
|
||||||
function select(record) {
|
function select(record) {
|
||||||
selected = selectRecord(record, selected)
|
selected = selectRecord(record, selected)
|
||||||
}
|
}
|
||||||
|
|
||||||
$: visibleColumns = schema.fields.filter(c => schema.visible.includes(c.name))
|
$: visibleColumns = schema.fields.filter(c => schema.visible?.includes(c.name) ?? [])
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="lx-table rounded">
|
<div class="table mt-5 ">
|
||||||
<table class="">
|
<table>
|
||||||
<thead class="table-light">
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{#if isWritable}
|
{#if isWritable}
|
||||||
<th>
|
<th>
|
||||||
<input
|
<Checkbox
|
||||||
on:change|preventDefault={eventToggleAll}
|
value=""
|
||||||
indeterminate={selected.length > 0 &&
|
on:change={eventToggleAll}
|
||||||
selected.length < records.length}
|
indeterminate={selected.length > 0 && selected.length < records.length}
|
||||||
checked={selected.length == records.length}
|
checked={selected.length === records.length}
|
||||||
class="form-check-input"
|
>
|
||||||
type="checkbox"
|
</Checkbox>
|
||||||
/>
|
|
||||||
</th>
|
</th>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@@ -54,13 +55,13 @@
|
|||||||
class:is-sort={field.name === sortField.name}
|
class:is-sort={field.name === sortField.name}
|
||||||
scope="col"
|
scope="col"
|
||||||
title={field.help}
|
title={field.help}
|
||||||
data-bs-toggle="tooltip"
|
>{field.label}</th
|
||||||
data-bs-placement="top">{field.label}</th
|
|
||||||
>
|
>
|
||||||
{/each}
|
{/each}
|
||||||
{#each systemFields.filter(c => schema.visible.includes(c.name)) as sysField}
|
{#each systemFields.filter(c => schema.visible?.includes(c.name)) as sysField}
|
||||||
<th>{sysField.label}</th>
|
<th class:is-sort={sysField.name === sortField.name}>{sysField.label}</th>
|
||||||
{/each}
|
{/each}
|
||||||
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -68,44 +69,58 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="title-td">
|
<td class="title-td">
|
||||||
<div
|
<div
|
||||||
class="title-td-contents d-inline-flex justify-content-between w-100 align-items-center"
|
class="title-td-contents"
|
||||||
>
|
>
|
||||||
<div class="d-flex align-items-center ">
|
{#if isWritable}
|
||||||
{#if isWritable}
|
<Checkbox
|
||||||
<div class="form-check">
|
on:change={() => select(record)}
|
||||||
<input
|
checked={selected.find((r) => r.id === record.id)}
|
||||||
on:change={() => select(record)}
|
value={record}
|
||||||
class="form-check-input "
|
>
|
||||||
type="checkbox"
|
</Checkbox>
|
||||||
checked={selected.find(
|
|
||||||
(r) => r.id === record.id
|
{/if}
|
||||||
)}
|
{#if record._file?.path}
|
||||||
value={record}
|
<div class="file-table-row">
|
||||||
/>
|
<Preview record={record} size={record._file?.width > 0 ? "medium" : "small"}/>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{#if record.status === "draft"}
|
||||||
|
<span style="text-transform: uppercase;font-size:10px">{record.status}</span>
|
||||||
|
{/if}
|
||||||
|
<a
|
||||||
|
href="{channel.lucentUrl}/records/{record.id}"
|
||||||
|
target={inModal ? "_blank" : "_self"}
|
||||||
|
>
|
||||||
|
{previewTitle(channel.schemas, record, graph)}
|
||||||
|
</a>
|
||||||
|
<span>{(record._file.size / 1024).toFixed(1)}kB</span>
|
||||||
|
|
||||||
|
{#if record._file.width > 0}
|
||||||
|
<span>{record._file.width + "x" + record._file.height}</span>
|
||||||
|
{/if}
|
||||||
|
<a
|
||||||
|
href="{fileurl(channel,record)}"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Download
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
<a
|
<a
|
||||||
|
|
||||||
class="me-2 text-decoration-none text-dark fs-6"
|
|
||||||
href="{channel.lucentUrl}/records/{record.id}"
|
href="{channel.lucentUrl}/records/{record.id}"
|
||||||
target={inModal ? "_blank" : "_self"}
|
target={inModal ? "_blank" : "_self"}
|
||||||
title={previewTitle(channel.schemas, record, graph)}
|
|
||||||
data-bs-toggle="tooltip" data-bs-placement="left"
|
|
||||||
|
|
||||||
>
|
>
|
||||||
|
{#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>
|
||||||
</div>
|
{/if}
|
||||||
<div>
|
|
||||||
<Avatar
|
|
||||||
name={usernameById(
|
|
||||||
users,
|
|
||||||
record._sys.updatedBy
|
|
||||||
)}
|
|
||||||
side={24}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<RecordRow
|
<RecordRow
|
||||||
@@ -117,6 +132,15 @@
|
|||||||
{sortField}
|
{sortField}
|
||||||
{users}
|
{users}
|
||||||
/>
|
/>
|
||||||
|
<td>
|
||||||
|
<Avatar
|
||||||
|
name={usernameById(
|
||||||
|
users,
|
||||||
|
record._sys.updatedBy
|
||||||
|
)}
|
||||||
|
side={24}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -11,13 +11,8 @@
|
|||||||
// if (edges[0]) {
|
// if (edges[0]) {
|
||||||
// firstRecord = record._children.find((r) => r.data.id === edges[0].to);
|
// firstRecord = record._children.find((r) => r.data.id === edges[0].to);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
console.log(filePreviews)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- {#if firstRecord}
|
|
||||||
<Preview record={firstRecord} size="tiny" />
|
|
||||||
{/if} -->
|
|
||||||
<div class="d-flex me-1">
|
<div class="d-flex me-1">
|
||||||
{#each filePreviews as file}
|
{#each filePreviews as file}
|
||||||
<div class="me-1">
|
<div class="me-1">
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -28,8 +28,8 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#each pages as i}
|
{#each pages as i}
|
||||||
<li class="page-item">
|
<li class="page-item" class:active={currentPage === i}>
|
||||||
{#if currentPage == i}
|
{#if currentPage === i}
|
||||||
<span class="page-link active">{i}</span>
|
<span class="page-link active">{i}</span>
|
||||||
{:else}
|
{:else}
|
||||||
<a class="page-link" on:click={(e) => goto(e, i)} href={url(i)}
|
<a class="page-link" on:click={(e) => goto(e, i)} href={url(i)}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav>
|
<nav>
|
||||||
<ul class="pagination justify-content-center">
|
<ul class="pagination">
|
||||||
{#if totalPages > 1}
|
{#if totalPages > 1}
|
||||||
<li class="page-item disabled" class:disabled={currentPage === 1}>
|
<li class="page-item disabled" class:disabled={currentPage === 1}>
|
||||||
<a on:click={first} href="/" class="page-link"> First </a>
|
<a on:click={first} href="/" class="page-link"> First </a>
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<p class="text-muted text-center">
|
<p style="display: flex;justify-content: center; gap: 4px">
|
||||||
Showing
|
Showing
|
||||||
<span class="font-medium">{+skip + 1}</span>
|
<span class="font-medium">{+skip + 1}</span>
|
||||||
to
|
to
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import {createEventDispatcher, getContext} from "svelte";
|
import {createEventDispatcher, getContext} from "svelte";
|
||||||
import {previewTitle} from "../../records/Preview";
|
import {previewTitle} from "../../records/Preview";
|
||||||
|
import Icon from "../../common/Icon.svelte";
|
||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
@@ -11,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: "",
|
||||||
@@ -24,6 +24,9 @@
|
|||||||
extractLabel(schema, key),
|
extractLabel(schema, key),
|
||||||
].reduce((mem, fn) => fn(mem), filter);
|
].reduce((mem, fn) => fn(mem), filter);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function extractOperator(key) {
|
function extractOperator(key) {
|
||||||
return (filter) => {
|
return (filter) => {
|
||||||
if (filter.isReference) {
|
if (filter.isReference) {
|
||||||
@@ -54,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;
|
||||||
}
|
}
|
||||||
@@ -73,30 +77,20 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<span class="applied-filter d-inline-block border border-primary rounded lx-small-text me-1 px-2 py-1">
|
<span class="applied-filter">
|
||||||
<div class="d-flex align-items-center justify-content-center">
|
|
||||||
{#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
|
||||||
on:click|preventDefault={() => removeFilter(key)}
|
on:click|preventDefault={() => removeFilter(key)}
|
||||||
type="button"
|
type="button"
|
||||||
class="btn-close btn-close ms-1"
|
class="button-text"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
/>
|
><Icon width={12} height={12} icon="close"></Icon></button>
|
||||||
</div>
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<style>
|
|
||||||
.applied-filter {
|
|
||||||
background-color: #fff;
|
|
||||||
line-height: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.applied-filter:hover {
|
|
||||||
opacity: .8;
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<script>
|
||||||
|
import {createEventDispatcher, getContext} from "svelte";
|
||||||
|
import Icon from "../../common/Icon.svelte";
|
||||||
|
|
||||||
|
const channel = getContext("channel");
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
export let inModal;
|
||||||
|
export let modalUrl;
|
||||||
|
const url = new URL(modalUrl ?? window.location.href);
|
||||||
|
|
||||||
|
function removeFilter(k) {
|
||||||
|
|
||||||
|
const url = new URL(modalUrl ?? window.location.href);
|
||||||
|
url.searchParams.set("skip", "0");
|
||||||
|
url.searchParams.delete("notlinked");
|
||||||
|
if (inModal) {
|
||||||
|
dispatch("refresh", url);
|
||||||
|
} else {
|
||||||
|
window.location.replace(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{#if url.searchParams.get("notlinked")}
|
||||||
|
<span class="applied-filter">
|
||||||
|
|
||||||
|
Not linked
|
||||||
|
|
||||||
|
<button
|
||||||
|
on:click|preventDefault={() => removeFilter()}
|
||||||
|
type="button"
|
||||||
|
class="button-text"
|
||||||
|
aria-label="Close"
|
||||||
|
><Icon width={12} height={12} icon="close"></Icon></button>
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import Icon from "../../common/Icon.svelte";
|
import Icon from "../../common/Icon.svelte";
|
||||||
import {createEventDispatcher} from "svelte";
|
import {createEventDispatcher} from "svelte";
|
||||||
import FilterReferenceInput from "./FilterReferenceInput.svelte";
|
|
||||||
import Dropdown from "../../common/Dropdown.svelte";
|
import Dropdown from "../../common/Dropdown.svelte";
|
||||||
|
import FilterReferenceInput from "./FilterReferenceInput.svelte";
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
export let schema;
|
export let schema;
|
||||||
@@ -14,50 +14,10 @@
|
|||||||
let dropdown;
|
let dropdown;
|
||||||
let search = "";
|
let search = "";
|
||||||
let systemFieldsFiltered = systemFields;
|
let systemFieldsFiltered = systemFields;
|
||||||
if (schema.type == "collection") {
|
if (schema.type === "collection") {
|
||||||
systemFieldsFiltered = systemFields.filter((f) => f.files === false);
|
systemFieldsFiltered = systemFields.filter((f) => f.files === false);
|
||||||
}
|
}
|
||||||
|
|
||||||
let filterableFields = [...schema.fields, ...systemFieldsFiltered].filter(
|
|
||||||
(f) => !["file", "json"].includes(f.info?.name ?? f.ui)
|
|
||||||
);
|
|
||||||
let selectedField;
|
|
||||||
|
|
||||||
let selectedInput = "";
|
|
||||||
$: operatorsFiltered = operators.filter(
|
|
||||||
(o) => o.uis.includes(selectedField?.info?.name) || o.uis[0] == "*"
|
|
||||||
);
|
|
||||||
|
|
||||||
$: selectedOperator = operatorsFiltered[0];
|
|
||||||
|
|
||||||
function addFilter(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
let filterPrefix = "";
|
|
||||||
let filterKey;
|
|
||||||
if (schema.fields.find(f => f.name === selectedField.name)) {
|
|
||||||
|
|
||||||
if (selectedField.info.name == "reference" && selectedOperator.name == "eq") {
|
|
||||||
filterPrefix = "children." + selectedField.name + ".id";
|
|
||||||
filterKey = `filter[${filterPrefix}]`;
|
|
||||||
} else {
|
|
||||||
filterPrefix = "data.";
|
|
||||||
filterKey = `filter[${filterPrefix + selectedField.name}_${selectedOperator.name}]`;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const url = new URL(modalUrl ?? window.location.href);
|
|
||||||
url.searchParams.set("skip", "0");
|
|
||||||
url.searchParams.set(filterKey, selectedInput);
|
|
||||||
if (inModal) {
|
|
||||||
dispatch("refresh", url);
|
|
||||||
dropdown.hide()
|
|
||||||
} else {
|
|
||||||
window.location = url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function submitSearch(e) {
|
function submitSearch(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let filterKeyValue = search.split("=")[0] ?? "";
|
let filterKeyValue = search.split("=")[0] ?? "";
|
||||||
@@ -79,63 +39,195 @@
|
|||||||
} else {
|
} else {
|
||||||
window.location.replace(url);
|
window.location.replace(url);
|
||||||
}
|
}
|
||||||
|
resetFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// New Start
|
||||||
|
let selectedInput = null;
|
||||||
|
let activeField = null;
|
||||||
|
let activeReference = null;
|
||||||
|
let activeOperator = null;
|
||||||
|
let activeMenu = "main";
|
||||||
|
let activeOperators = null;
|
||||||
|
let dataFields = [...schema.fields, ...systemFieldsFiltered].filter(
|
||||||
|
(f) => !["file", "json", "reference"].includes(f.info?.name ?? f.ui)
|
||||||
|
);
|
||||||
|
let referenceFields = [...schema.fields].filter(
|
||||||
|
(f) => ["reference"].includes(f.info?.name ?? f.ui)
|
||||||
|
);
|
||||||
|
|
||||||
|
function selectField(e, field) {
|
||||||
|
activeField = field;
|
||||||
|
activeOperators = operators.filter(
|
||||||
|
(o) => o.uis.includes(activeField?.info?.name) || o.uis[0] === "*"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectReference(e, field) {
|
||||||
|
activeReference = field;
|
||||||
|
activeOperator = operators.find(o => o.name === "eq")
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectOperator(e, operator) {
|
||||||
|
activeOperator = operator;
|
||||||
|
if (!operator.hasValue) {
|
||||||
|
applyFilter(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyFilter(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
let filterPrefix = "";
|
||||||
|
let filterKey;
|
||||||
|
let selectedField = activeField ?? activeReference;
|
||||||
|
if (schema.fields.find(f => f.name === selectedField.name)) {
|
||||||
|
if (selectedField.info.name === "reference" && activeOperator.name === "eq") {
|
||||||
|
filterPrefix = "children." + selectedField.name + ".id";
|
||||||
|
filterKey = `filter[${filterPrefix}]`;
|
||||||
|
} else {
|
||||||
|
filterPrefix = "data.";
|
||||||
|
filterKey = `filter[${filterPrefix + selectedField.name}_${activeOperator.name}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(modalUrl ?? window.location.href);
|
||||||
|
url.searchParams.set("skip", "0");
|
||||||
|
url.searchParams.set(filterKey, selectedInput);
|
||||||
|
if (inModal) {
|
||||||
|
dispatch("refresh", url);
|
||||||
|
dropdown.close()
|
||||||
|
} else {
|
||||||
|
window.location.href = url.toString();
|
||||||
|
}
|
||||||
|
resetFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetFilters() {
|
||||||
|
activeField = null;
|
||||||
|
activeOperator = null;
|
||||||
|
activeMenu = "main";
|
||||||
|
activeReference = null;
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mx-2 d-flex align-items-center">
|
|
||||||
<Dropdown bind:this={dropdown} width="300">
|
|
||||||
<div slot="button">
|
|
||||||
<Icon icon="filter"/>
|
|
||||||
<span class="ms-1">Filter</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="px-3 py-1 d-flex align-items-center">
|
<div>
|
||||||
<select bind:value={selectedField} class="form-select">
|
<Dropdown bind:this={dropdown}>
|
||||||
{#each filterableFields as field}
|
<div slot="button">
|
||||||
<option value={field}>{field.label}</option>
|
<Icon icon="filter"/>
|
||||||
{/each}
|
<span class="ms-1">Filter</span>
|
||||||
</select>
|
</div>
|
||||||
</div>
|
<div class:hide={activeMenu !== "main"}>
|
||||||
<div class="px-3 py-1 d-flex align-items-center">
|
<button class="dropdown-item button" on:click={e => activeMenu = "byField" }>
|
||||||
<select class="form-select" bind:value={selectedOperator}>
|
Filter by field
|
||||||
{#each operatorsFiltered as operator}
|
</button>
|
||||||
<option value={operator}>{operator.label}</option>
|
<button class="dropdown-item button" on:click={e => activeMenu = "byReference" }>
|
||||||
{/each}
|
Filter by Reference
|
||||||
</select>
|
</button>
|
||||||
</div>
|
<button class="dropdown-item button" on:click={e => activeMenu = "advanced" }>
|
||||||
<div class="px-3 py-1 d-flex align-items-center">
|
Advanced filter
|
||||||
{#if selectedField?.info?.name === "reference" && selectedOperator.name === "eq"}
|
</button>
|
||||||
<FilterReferenceInput field={selectedField} bind:value={selectedInput} on:addFilter={addFilter}/>
|
</div>
|
||||||
{:else}
|
<div class:hide={activeMenu !== "byField"}>
|
||||||
|
{#if !activeField}
|
||||||
|
<button class="dropdown-item button" on:click={e => activeMenu = "main" }>
|
||||||
|
<Icon icon="arrow-left"></Icon>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
{#each dataFields as field}
|
||||||
|
<button class="dropdown-item button" on:click={e => selectField(e,field)}>
|
||||||
|
{field.label}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if activeField && !activeOperator}
|
||||||
|
<button class="dropdown-item button" on:click={e => activeField = null }>
|
||||||
|
<Icon icon="arrow-left"></Icon>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
<div class="selected-filter">field: {activeField.label}</div>
|
||||||
|
|
||||||
|
{#each activeOperators as operator}
|
||||||
|
<button class="dropdown-item button" on:click={e => selectOperator(e,operator)}>
|
||||||
|
{operator.label}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
{#if activeField && activeOperator}
|
||||||
|
<button class="dropdown-item button" on:click={e => activeOperator = null }>
|
||||||
|
<Icon icon="arrow-left"></Icon>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
<div class="selected-filter">field: {activeField.label} operator: {activeOperator.label}</div>
|
||||||
|
<div class="filter-input">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
bind:value={selectedInput}
|
bind:value={selectedInput}
|
||||||
/>
|
/>
|
||||||
{/if}
|
</div>
|
||||||
</div>
|
|
||||||
<div class="px-3 py-1 d-flex align-items-center">
|
|
||||||
<button
|
<button
|
||||||
on:click={addFilter}
|
on:click={applyFilter}
|
||||||
class="btn btn-outline-primary"
|
class="button applied-filter"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
Add filter
|
Add filter
|
||||||
</button>
|
</button>
|
||||||
</div>
|
{/if}
|
||||||
|
|
||||||
<hr/>
|
</div>
|
||||||
<div><h6 class="dropdown-header">Advanced filters</h6></div>
|
<div class:hide={activeMenu !== "byReference"}>
|
||||||
<form on:submit={submitSearch}>
|
{#if !activeReference}
|
||||||
<div class="px-3 py-1 d-flex align-items-center">
|
<button class="dropdown-item button" on:click={e => activeMenu = "main" }>
|
||||||
<input
|
<Icon icon="arrow-left"></Icon>
|
||||||
bind:value={search}
|
Back
|
||||||
type="search"
|
</button>
|
||||||
class="form-control"
|
{#each referenceFields as field}
|
||||||
placeholder="Advanced filters"
|
<button class="dropdown-item button" on:click={e => selectReference(e,field)}>
|
||||||
required
|
{field.label}
|
||||||
/>
|
</button>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
{#if activeReference}
|
||||||
|
<button class="dropdown-item button" on:click={e => activeReference = null }>
|
||||||
|
<Icon icon="arrow-left"></Icon>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
<div class="selected-filter">field: {activeReference.label}</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<FilterReferenceInput field={activeReference} bind:value={selectedInput}
|
||||||
|
on:addFilter={applyFilter}/>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class:hide={activeMenu !== "advanced"}>
|
||||||
|
<button class="dropdown-item button" on:click={e => activeMenu = "main" }>
|
||||||
|
<Icon icon="arrow-left"></Icon>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
<form on:submit={submitSearch}>
|
||||||
|
<input
|
||||||
|
bind:value={search}
|
||||||
|
type="search"
|
||||||
|
class="mb-2 mt-2"
|
||||||
|
placeholder="Advanced filters"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button class="button applied-filter">
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</Dropdown>
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -42,35 +42,37 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
<div class="reference-tags">
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
on:keyup={updateResults}
|
||||||
|
bind:value={search}
|
||||||
|
placeholder={"Search for "+field.label}
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
|
|
||||||
<input
|
<div class="reference-tags-results">
|
||||||
type="search"
|
|
||||||
on:keyup={updateResults}
|
|
||||||
class="form-control dropdown-toggle"
|
|
||||||
bind:value={search}
|
|
||||||
placeholder={"Search for "+field.label}
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
autocomplete="off"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="dropdown-menu w-100">
|
{#if searchOptions}
|
||||||
|
{#each searchOptions as option (option.id)}
|
||||||
|
<div
|
||||||
|
class="reference-tags-option"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
on:click={(e) => apply(e, option)}
|
||||||
|
on:keypress={(e) => apply(e, option)}
|
||||||
|
>
|
||||||
|
{previewTitle(channel.schemas, option)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if searchOptions}
|
{:else}
|
||||||
{#each searchOptions as option (option.id)}
|
<div
|
||||||
<div
|
class="start-typing">
|
||||||
on:click={(e) => apply(e, option)}
|
Start typing...
|
||||||
on:keypress={(e) => apply(e, option)}
|
</div>
|
||||||
>
|
{/each}
|
||||||
<span class="dropdown-item">
|
{/if}
|
||||||
{previewTitle(channel.schemas, option)}
|
</div>
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{:else}
|
|
||||||
|
|
||||||
Start typing...
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import Icon from "../../common/Icon.svelte";
|
import Icon from "../../common/Icon.svelte";
|
||||||
import {createEventDispatcher} from "svelte";
|
import {createEventDispatcher} from "svelte";
|
||||||
|
import Dropdown from "../../common/Dropdown.svelte";
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
export let schema;
|
export let schema;
|
||||||
@@ -31,101 +32,91 @@
|
|||||||
|
|
||||||
function sortAsc(e, field) {
|
function sortAsc(e, field) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let prefix = systemFields.map((el) => el.name).includes(field.name) ? "" : "data.";
|
let prefix = systemFields.map((el) => el.name).includes(field.name) ? "" : "data.";
|
||||||
return triggerSortField(prefix + field.name);
|
return triggerSortField(prefix + field.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortDesc(e, field) {
|
function sortDesc(e, field) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let prefix = systemFields.map((el) => el.name).includes(field.name) ? "" : "data.";
|
let prefix = systemFields.map((el) => el.name).includes(field.name) ? "" : "data.";
|
||||||
return triggerSortField("-" + prefix + field.name);
|
return triggerSortField("-" + prefix + field.name);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class=" ">
|
|
||||||
<button
|
<Dropdown>
|
||||||
class="btn btn-sm btn-outline-primary dropdown-toggle d-flex align-items-center"
|
<div slot="button">
|
||||||
type="button"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
data-bs-auto-close="outside"
|
|
||||||
aria-expanded="false"
|
|
||||||
>
|
|
||||||
{#if sortParam.startsWith("-")}
|
{#if sortParam.startsWith("-")}
|
||||||
<Icon icon="arrow-down-wide-short"/>
|
<Icon icon="arrow-down-wide-short"/>
|
||||||
{:else}
|
{:else}
|
||||||
<Icon icon="arrow-up-short-wide"/>
|
<Icon icon="arrow-up-short-wide"/>
|
||||||
{/if}
|
{/if}
|
||||||
<span class="ms-1">{sortField.label}</span>
|
<span class="ms-1">{sortField.label}</span>
|
||||||
</button>
|
|
||||||
<div class="dropdown-menu" style="width:auto;max-width:800px;">
|
|
||||||
<div class="row">
|
|
||||||
{#each sortableFields as field}
|
|
||||||
<div class="col-4 px-3 py-1 d-flex align-items-center">
|
|
||||||
<div class="btn-group w-100">
|
|
||||||
<button
|
|
||||||
on:click={(e) => sortAsc(e, field)}
|
|
||||||
title="Sort Ascending"
|
|
||||||
class="btn btn-sm {field.name == sortField.name && !sortParam.startsWith("-")
|
|
||||||
? 'btn-primary'
|
|
||||||
: 'btn-outline-primary'} "
|
|
||||||
>
|
|
||||||
<Icon icon="arrow-up-short-wide"/>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
on:click={(e) => sortDesc(e, field)}
|
|
||||||
title="Sort Descending"
|
|
||||||
class="btn btn-sm {field.name == sortField.name && sortParam.startsWith("-")
|
|
||||||
? 'btn-primary'
|
|
||||||
: 'btn-outline-primary'} "
|
|
||||||
>
|
|
||||||
<Icon icon="arrow-down-wide-short"/>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
title="Sort Ascending"
|
|
||||||
on:click={(e) => sortAsc(e, field)}
|
|
||||||
class="btn btn-sm btn-outline-primary w-100 text-nowrap"
|
|
||||||
style="overflow: hidden;"
|
|
||||||
>
|
|
||||||
{field.label}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
<h6 class="dropdown-header px-0">System</h6>
|
|
||||||
<div class="row">
|
|
||||||
{#each systemFieldsFiltered as field}
|
|
||||||
<div class="col-4 px-3 py-1 d-flex align-items-center">
|
|
||||||
<div class="btn-group w-100">
|
|
||||||
<button
|
|
||||||
on:click={(e) => sortAsc(e, field)}
|
|
||||||
title="Sort Ascending"
|
|
||||||
class="btn btn-sm {field.name == sortParam
|
|
||||||
? 'btn-primary'
|
|
||||||
: 'btn-outline-primary'} "
|
|
||||||
>
|
|
||||||
<Icon icon="arrow-up-short-wide"/>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
on:click={(e) => sortDesc(e, field)}
|
|
||||||
title="Sort Descending"
|
|
||||||
class="btn btn-sm {'-' + field.name == sortParam
|
|
||||||
? 'btn-primary'
|
|
||||||
: 'btn-outline-primary'} "
|
|
||||||
>
|
|
||||||
<Icon icon="arrow-down-wide-short"/>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
title="Sort Ascending"
|
|
||||||
on:click={(e) => sortAsc(e, field)}
|
|
||||||
class="btn btn-sm btn-outline-primary w-100 text-nowrap"
|
|
||||||
style="overflow: hidden;"
|
|
||||||
>
|
|
||||||
{field.label}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
|
{#each sortableFields as field}
|
||||||
|
<div class="dropdown-item">
|
||||||
|
<button
|
||||||
|
on:click={(e) => sortAsc(e, field)}
|
||||||
|
title="Sort Ascending"
|
||||||
|
class="button button-icon {field.name == sortField.name && !sortParam.startsWith("-")
|
||||||
|
? 'active'
|
||||||
|
: ''} "
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
<Icon icon="arrow-up-short-wide"/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={(e) => sortDesc(e, field)}
|
||||||
|
title="Sort Descending"
|
||||||
|
class="button button-icon {field.name == sortField.name && sortParam.startsWith("-")
|
||||||
|
? 'active'
|
||||||
|
: ''} "
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
<Icon icon="arrow-down-wide-short"/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
title="Sort Ascending"
|
||||||
|
on:click={(e) => sortAsc(e, field)}
|
||||||
|
class="button"
|
||||||
|
>
|
||||||
|
{field.label}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
<h6 class="dropdown-header">System</h6>
|
||||||
|
{#each systemFieldsFiltered as field}
|
||||||
|
<div class="dropdown-item">
|
||||||
|
|
||||||
|
<button
|
||||||
|
on:click={(e) => sortAsc(e, field)}
|
||||||
|
title="Sort Ascending"
|
||||||
|
class="button button-icon {field.name == sortParam
|
||||||
|
? 'active'
|
||||||
|
: ''} "
|
||||||
|
>
|
||||||
|
<Icon icon="arrow-up-short-wide"/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={(e) => sortDesc(e, field)}
|
||||||
|
title="Sort Descending"
|
||||||
|
class="button button-icon {'-' + field.name == sortParam
|
||||||
|
? 'active'
|
||||||
|
: ''} "
|
||||||
|
>
|
||||||
|
<Icon icon="arrow-down-wide-short"/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
title="Sort Ascending"
|
||||||
|
on:click={(e) => sortAsc(e, field)}
|
||||||
|
class="button"
|
||||||
|
>
|
||||||
|
{field.label}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
import Icon from "../../common/Icon.svelte";
|
import Icon from "../../common/Icon.svelte";
|
||||||
import SortFields from "./SortFields.svelte";
|
import SortFields from "./SortFields.svelte";
|
||||||
import AppliedFilter from "./AppliedFilter.svelte";
|
import AppliedFilter from "./AppliedFilter.svelte";
|
||||||
import {getContext, createEventDispatcher} from "svelte";
|
import {createEventDispatcher, getContext} from "svelte";
|
||||||
|
import Dropdown from "../../common/Dropdown.svelte";
|
||||||
|
import AppliedFilterNotLinked from "./AppliedFilterNotLinked.svelte";
|
||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
|
|
||||||
@@ -47,8 +49,8 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mb-3 d-flex align-items-center justify-content-between">
|
<div class="toolbar">
|
||||||
<div class=" d-flex align-items-center">
|
<div class="toolbar-filters">
|
||||||
|
|
||||||
<SortFields
|
<SortFields
|
||||||
{schema}
|
{schema}
|
||||||
@@ -72,78 +74,74 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<form method="GET" on:submit={search}>
|
<form method="GET" on:submit={search}>
|
||||||
<input type="search" name="filter[search_regex]" placeholder="Search"
|
<input type="search" name="filter[search_regex]" placeholder="Search"
|
||||||
class="form-control" required>
|
class="search" required>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex align-items-center ">
|
<div style="display:flex;align-items: center;gap:4px">
|
||||||
{#if schema.type === "collection"}
|
{#if schema.type === "collection"}
|
||||||
{#if !inModal && isWritable}
|
{#if !inModal && isWritable}
|
||||||
<a
|
<a
|
||||||
href="{channel.lucentUrl}/records/new?schema={schema.name}"
|
href="{channel.lucentUrl}/records/new?schema={schema.name}"
|
||||||
class="btn btn-sm btn-primary"
|
class="button"
|
||||||
>
|
>
|
||||||
New Record
|
New Record
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
{:else }
|
{:else }
|
||||||
<div class="d-inline-block ms-1">
|
<div>
|
||||||
<Uploader {schema} on:uploadComplete={uploadComplete}/>
|
<Uploader {schema} on:uploadComplete={uploadComplete}/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if !inModal}
|
{#if !inModal}
|
||||||
<div class="dropdown d-inline-block">
|
<Dropdown orientation="right">
|
||||||
<button
|
<div slot="button">
|
||||||
class="btn btn-link btn-sm"
|
|
||||||
type="button"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
aria-expanded="false"
|
|
||||||
>
|
|
||||||
<Icon icon="ellipsis-vertical"/>
|
<Icon icon="ellipsis-vertical"/>
|
||||||
</button>
|
</div>
|
||||||
|
{#if filter["status_in"] === "trashed"}
|
||||||
<ul class="dropdown-menu">
|
{#if isWritable}
|
||||||
{#if filter["status_in"] === "trashed"}
|
<a
|
||||||
{#if isWritable}
|
class="dropdown-item"
|
||||||
<li>
|
href="{channel.lucentUrl}/content/{schema.name}/emptyTrash"
|
||||||
<a
|
>
|
||||||
class="dropdown-item"
|
Empty trash
|
||||||
href="{channel.lucentUrl}/content/{schema.name}/emptyTrash"
|
</a>
|
||||||
>
|
|
||||||
Empty trash
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
{:else}
|
|
||||||
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
class="dropdown-item"
|
|
||||||
href={csvUrl}
|
|
||||||
>Export to CSV</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
class="dropdown-item"
|
|
||||||
href="{channel.lucentUrl}/content/{schema.name}?filter[status_in]=trashed"
|
|
||||||
>View trashed records</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
{:else}
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="dropdown-item"
|
||||||
|
href={csvUrl}
|
||||||
|
>Export to CSV</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="dropdown-item"
|
||||||
|
href="{channel.lucentUrl}/content/{schema.name}?filter[status_in]=trashed"
|
||||||
|
>View trashed records</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="dropdown-item"
|
||||||
|
href="{channel.lucentUrl}/content/{schema.name}?notlinked=*"
|
||||||
|
>View unlinked records</a
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
</Dropdown>
|
||||||
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{#if Object.entries(filter).length > 0}
|
<div class="applied-filters">
|
||||||
<div class=" d-flex mb-3">
|
<AppliedFilterNotLinked
|
||||||
|
{inModal}
|
||||||
|
{modalUrl}
|
||||||
|
on:refresh
|
||||||
|
></AppliedFilterNotLinked>
|
||||||
|
{#if Object.entries(filter).length > 0}
|
||||||
{#each Object.entries(filter) as [k, v]}
|
{#each Object.entries(filter) as [k, v]}
|
||||||
<AppliedFilter
|
<AppliedFilter
|
||||||
{schema}
|
{schema}
|
||||||
@@ -156,6 +154,7 @@
|
|||||||
on:refresh
|
on:refresh
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
{/if}
|
||||||
{/if}
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
<script>
|
||||||
|
|
||||||
|
import {createEventDispatcher, getContext} from "svelte";
|
||||||
|
import Icon from "../common/Icon.svelte";
|
||||||
|
import Index from "../content/Index.svelte";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
let dialogEl;
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
const channel = getContext("channel");
|
||||||
|
$: data = {};
|
||||||
|
let selectedRecords = [];
|
||||||
|
// onMount(() => {
|
||||||
|
// load();
|
||||||
|
// });
|
||||||
|
|
||||||
|
|
||||||
|
export function close(e) {
|
||||||
|
if(e){
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogEl.close()
|
||||||
|
selectedRecords = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function load(schema) {
|
||||||
|
axios
|
||||||
|
.get(channel.lucentUrl + "/content/" + schema)
|
||||||
|
.then((response) => {
|
||||||
|
data = response.data;
|
||||||
|
})
|
||||||
|
.catch((error) => console.log(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
function insert(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
dispatch("insert", {
|
||||||
|
records: selectedRecords,
|
||||||
|
action: "insert",
|
||||||
|
schema: data.schema.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function replace(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
dispatch("insert", {
|
||||||
|
records: selectedRecords,
|
||||||
|
action: "replace",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function open(schema) {
|
||||||
|
dialogEl.showModal()
|
||||||
|
load(schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<dialog bind:this={dialogEl}>
|
||||||
|
{#if data.schema}
|
||||||
|
<div class="dialog-header">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="button"
|
||||||
|
on:click={insert}
|
||||||
|
disabled={selectedRecords.length === 0}
|
||||||
|
>
|
||||||
|
Insert
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="button"
|
||||||
|
on:click={replace}
|
||||||
|
disabled={selectedRecords.length === 0}
|
||||||
|
>
|
||||||
|
Replace
|
||||||
|
</button>
|
||||||
|
{#if selectedRecords.length > 0}
|
||||||
|
<span class="">
|
||||||
|
{selectedRecords.length} records selected
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<button
|
||||||
|
on:click|preventDefault={close}
|
||||||
|
type="button"
|
||||||
|
class="button close"
|
||||||
|
aria-label="Close"
|
||||||
|
>
|
||||||
|
<Icon icon="close"></Icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dialog-body">
|
||||||
|
<Index {...data} bind:selected={selectedRecords}></Index>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/if}
|
||||||
|
</dialog>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<script>
|
||||||
|
|
||||||
|
import Icon from "../common/Icon.svelte";
|
||||||
|
|
||||||
|
let dialogEl;
|
||||||
|
|
||||||
|
$: data = {};
|
||||||
|
|
||||||
|
export function close(e) {
|
||||||
|
if (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
dialogEl.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function open() {
|
||||||
|
dialogEl.showModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<dialog bind:this={dialogEl}>
|
||||||
|
<div class="dialog-header">
|
||||||
|
<button
|
||||||
|
on:click|preventDefault={close}
|
||||||
|
type="button"
|
||||||
|
class="button close"
|
||||||
|
aria-label="Close"
|
||||||
|
>
|
||||||
|
<Icon icon="close"></Icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dialog-body" style="min-width: 900px">
|
||||||
|
<slot/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</dialog>
|
||||||
@@ -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)) ?? [];
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import Icon from "../common/Icon.svelte";
|
import Icon from "../common/Icon.svelte";
|
||||||
import {imgurl} from "../files/imageserver";
|
import {imgurl} from "./imageserver.js";
|
||||||
import {getContext} from "svelte";
|
import {getContext} from "svelte";
|
||||||
|
|
||||||
export let record;
|
export let record;
|
||||||
@@ -28,41 +28,50 @@
|
|||||||
fontSize = "13";
|
fontSize = "13";
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<div style="display: flex;align-items: center;gap: 5px;">
|
||||||
|
{#if record}
|
||||||
|
|
||||||
{#if record}
|
{#if record._file.mime.startsWith("image")}
|
||||||
{#if record._file.mime.startsWith("image")}
|
<!-- href={imgurl(record)} -->
|
||||||
<!-- href={imgurl(record)} -->
|
<a
|
||||||
<a
|
href="{channel.lucentUrl}/records/{record.id}"
|
||||||
href="{channel.lucentUrl}/records/{record.id}"
|
title={record._file.originalName}
|
||||||
title={record._file.path}
|
style="width:{imageSide}px;height:{imageSide}px"
|
||||||
class="d-flex align-items-center justify-content-center "
|
|
||||||
style="width:{imageSide}px;height:{imageSide}px"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
class="rounded w-100"
|
|
||||||
src={imgurl(record)}
|
|
||||||
alt={record._file.path}
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
{:else}
|
|
||||||
<a
|
|
||||||
href="{channel.lucentUrl}/records/{record.id}"
|
|
||||||
title={record._file.path}
|
|
||||||
class="btn btn-outline-primary btn-sm d-flex align-items-center justify-content-center"
|
|
||||||
style="width:{imageSide}px;height:{imageSide}px"
|
|
||||||
>
|
|
||||||
<Icon icon="file" width={fileSide} height={fileSide}/>
|
|
||||||
<span class="ms-2" style="font-size:{fontSize}px"
|
|
||||||
>.{record._file.path.split(".").pop()}</span
|
|
||||||
>
|
>
|
||||||
</a>
|
<img
|
||||||
|
class="rounded w-100"
|
||||||
|
src={imgurl(channel,record)}
|
||||||
|
alt={record._file.path}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
{:else}
|
||||||
|
<a
|
||||||
|
href="{channel.lucentUrl}/records/{record.id}"
|
||||||
|
title={record._file.path}
|
||||||
|
class="file-preview-small"
|
||||||
|
style="width:{imageSide}px;height:{imageSide}px"
|
||||||
|
>
|
||||||
|
<Icon icon="file" width={fileSide} height={fileSide}/>
|
||||||
|
<span class="ms-2"
|
||||||
|
>.{record._file.path.split(".").pop().toLowerCase()}</span
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{#if showFilename}
|
||||||
{#if showFilename}
|
<a
|
||||||
<a
|
href="{channel.lucentUrl}/records/{record.id}"
|
||||||
href="{channel.lucentUrl}/records/{record.id}"
|
title={record._file.path}
|
||||||
title={record._file.path}
|
class="preview-file-filename lx-small-text text-decoration-none"
|
||||||
class="preview-file-filename lx-small-text text-decoration-none"
|
>{record._file.path} </a
|
||||||
>{record._file.path}</a
|
>
|
||||||
>
|
{/if}
|
||||||
{/if}
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
img{
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -43,16 +43,15 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<fieldset disabled={isLoading}>
|
<fieldset class="upload-button" disabled={isLoading}>
|
||||||
<label class="btn btn-primary btn-sm btn-spinner ">
|
<label class="button primary btn-spinner ">
|
||||||
|
<span
|
||||||
|
class="spinner-border spinner-border-sm"
|
||||||
|
role="status"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
Upload file
|
Upload file
|
||||||
<span
|
|
||||||
class="spinner-border spinner-border-sm"
|
|
||||||
role="status"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<span class="visually-hidden">Loading...</span>
|
|
||||||
</span>
|
|
||||||
<input
|
<input
|
||||||
on:input={upload}
|
on:input={upload}
|
||||||
class="form-control"
|
class="form-control"
|
||||||
|
|||||||
@@ -1,15 +1,32 @@
|
|||||||
import {getContext} from "svelte";
|
export function imgurl(channel, record) {
|
||||||
|
if (record._file.mime === "image/svg+xml") {
|
||||||
export function imgurl(record) {
|
return fileurl(channel, record);
|
||||||
|
|
||||||
if(record._file.mime === "image/svg+xml"){
|
|
||||||
return fileurl(record);
|
|
||||||
}
|
}
|
||||||
const channel = getContext("channel")
|
const pathAr = record._file.path.split("/");
|
||||||
return channel.filesUrl + `/thumbs/${record._file.path}`;
|
return channel.disks[record._file.disk] + `/${pathAr[0]}/thumbs/${pathAr[1]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fileurl(record) {
|
export function fileurl(channel, record) {
|
||||||
const channel = getContext("channel")
|
return channel.disks[record._file.disk] + `/${record._file.path}`;
|
||||||
return channel.filesUrl + `/${record._file.path}`;
|
}
|
||||||
|
|
||||||
|
export function htmlurl(channel, record, preset) {
|
||||||
|
|
||||||
|
let html = "";
|
||||||
|
let url = fileurl(channel, record)
|
||||||
|
|
||||||
|
if (record._file.width > 0) {
|
||||||
|
let presetUrl = url;
|
||||||
|
if (preset) {
|
||||||
|
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}" />`
|
||||||
|
} else if (record._file.mime === "image/svg+xml") {
|
||||||
|
html = `<img src="${url}" alt="${record._file.path}"/>`
|
||||||
|
} else {
|
||||||
|
html = `<a href="${url}">${record._file.originalName}</a>`
|
||||||
|
}
|
||||||
|
|
||||||
|
return html;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { uniqueId } from "lodash";
|
|
||||||
export let label;
|
|
||||||
export let name;
|
|
||||||
export let group;
|
|
||||||
export let value;
|
|
||||||
export let help;
|
|
||||||
let id = uniqueId();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="form-check">
|
|
||||||
<input
|
|
||||||
class="form-check-input"
|
|
||||||
type="radio"
|
|
||||||
{value}
|
|
||||||
{name}
|
|
||||||
bind:group
|
|
||||||
{id}
|
|
||||||
/>
|
|
||||||
<label class="form-check-label" for={id}>
|
|
||||||
{label}
|
|
||||||
</label>
|
|
||||||
{#if help}
|
|
||||||
<span class="text-muted">{help}</span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
@@ -21,24 +21,21 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper-normal transparent">
|
|
||||||
|
|
||||||
<h3 class="header-small mb-4 mt-5">Latest Content changes</h3>
|
<h3 class="header-small mb-4 mt-5">Latest Content changes</h3>
|
||||||
{#if records.length > 0}
|
{#if records.length > 0}
|
||||||
<div class="lx-card mb-4">
|
|
||||||
<div class="lx-table p-0">
|
|
||||||
<table class="">
|
|
||||||
<tbody>
|
|
||||||
{#each records as record (record.id)}
|
|
||||||
<tr>
|
|
||||||
<RecordRow {graph} {record} {users}/>
|
|
||||||
</tr>
|
|
||||||
{/each}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/if}
|
<div class="table">
|
||||||
|
<table class="">
|
||||||
|
<tbody>
|
||||||
|
{#each records as record (record.id)}
|
||||||
|
<tr>
|
||||||
|
<RecordRow {graph} {record} {users}/>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/if}
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import {formatDistanceToNow, parseJSON} from "date-fns";
|
import {formatDistanceToNow, parseJSON} from "date-fns";
|
||||||
import Avatar from "../account/Avatar.svelte";
|
import Avatar from "../account/Avatar.svelte";
|
||||||
import Status from "../records/Status.svelte";
|
|
||||||
import {previewTitle} from "../records/Preview";
|
import {previewTitle} from "../records/Preview";
|
||||||
import Preview from "../files/Preview.svelte";
|
import Preview from "../files/Preview.svelte";
|
||||||
import {usernameById} from "../account/users";
|
import {usernameById} from "../account/users";
|
||||||
@@ -19,29 +18,30 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
|
<div class="row-name">
|
||||||
|
{#if record.status === "draft"}
|
||||||
|
<span class="status">DRAFT</span>
|
||||||
|
{/if}
|
||||||
{#if schema.type === "files"}
|
{#if schema.type === "files"}
|
||||||
<Preview {record} size="tiny"/>
|
<Preview {record} size="tiny" showFilename={true}/>
|
||||||
{:else}
|
{:else}
|
||||||
<a
|
<a
|
||||||
href="{channel.lucentUrl}/records/{record.id}"
|
href="{channel.lucentUrl}/records/{record.id}"
|
||||||
class="text-decoration-none text-dark d-block"
|
|
||||||
>
|
>
|
||||||
{previewTitle(channel.schemas, record, graph)}
|
{previewTitle(channel.schemas, record, graph)}
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td><a
|
<td><a
|
||||||
class="text-decoration-none lx-small-text"
|
href="{channel.lucentUrl}/content/{schema.name}">{schema.label}</a
|
||||||
href="{channel.lucentUrl}/content/{schema.name}">{schema.label}</a
|
|
||||||
>
|
>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="text-center">
|
|
||||||
<Status status={record.status}/>
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<div class="d-flex">
|
<div style="display: flex;gap: 14px">
|
||||||
<Avatar name={usernameById(users, record._sys.updatedBy)} side={24}/>
|
<Avatar name={usernameById(users, record._sys.updatedBy)} side={24}/>
|
||||||
<div class="ms-2">
|
<div class="ms-2">
|
||||||
{frieldlyUpdatedAt}
|
{frieldlyUpdatedAt}
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<script>
|
||||||
|
import Avatar from "../account/Avatar.svelte";
|
||||||
|
import {getContext} from "svelte";
|
||||||
|
import Dropdown from "../common/Dropdown.svelte";
|
||||||
|
|
||||||
|
const channel = getContext("channel");
|
||||||
|
const user = getContext("user");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="top-nav ">
|
||||||
|
<a class="top-nav-item" href="{channel.lucentUrl}/members">Members</a>
|
||||||
|
|
||||||
|
{#if channel.commands.length > 0}
|
||||||
|
<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}
|
||||||
|
|
||||||
|
<a href="{channel.lucentUrl}/profile">
|
||||||
|
<Avatar side="28" name={user.name}/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<script>
|
||||||
|
import {getContext} from "svelte";
|
||||||
|
import Folder from "./Folder.svelte";
|
||||||
|
|
||||||
|
export let schema;
|
||||||
|
const channel = getContext("channel");
|
||||||
|
const readableSchemas = getContext("readableSchemas");
|
||||||
|
|
||||||
|
function addToFolder(tree, folderPath, aSchema) {
|
||||||
|
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>
|
||||||
|
<div class="sidebar-top">
|
||||||
|
<a class="logo" href="{channel.lucentUrl}">{channel.name}</a>
|
||||||
|
<a class="nav-item" href="{channel.lucentUrl}/profile">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar">
|
||||||
|
<Folder folder={schemaTree} {schema} ></Folder>
|
||||||
|
</div>
|
||||||
@@ -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 = ``;
|
||||||
|
} 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(() => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import Sortable from "sortablejs";
|
import Sortable from "sortablejs";
|
||||||
import { onMount, createEventDispatcher } from "svelte";
|
import {createEventDispatcher, onMount} from "svelte";
|
||||||
|
|
||||||
export let sortableClass = "";
|
export let sortableClass = "";
|
||||||
// export let handle;
|
// export let handle;
|
||||||
export let isTable = false;
|
export let isTable = false;
|
||||||
@@ -10,50 +11,29 @@
|
|||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
let options = {
|
let options = {
|
||||||
// handle: ".sortable-handle",
|
|
||||||
// draggable: ".quote-line-wrapper",
|
|
||||||
// filter: ".not-draggable", // Selectors that do not lead to dragging (String or Function)
|
|
||||||
// preventOnFilter: true,
|
|
||||||
animation: 150, // ms, animation speed moving items when sorting, `0` — without animation
|
animation: 150, // ms, animation speed moving items when sorting, `0` — without animation
|
||||||
easing: "cubic-bezier(1, 0, 0, 1)",
|
easing: "cubic-bezier(1, 0, 0, 1)",
|
||||||
|
direction: 'vertical',
|
||||||
onUpdate: function (/**Event*/ evt) {
|
onUpdate: function (/**Event*/ evt) {
|
||||||
// reorder(evt.oldIndex,evt.newIndex);
|
|
||||||
// console.log(evt)
|
|
||||||
dispatch("update", {
|
dispatch("update", {
|
||||||
source: evt.oldIndex,
|
source: evt.oldIndex,
|
||||||
target: evt.newIndex,
|
target: evt.newIndex,
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
onMove(event) {
|
|
||||||
// if (event.related.className.indexOf("not-draggable") > -1) {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// if (handle) {
|
|
||||||
// options.handle = handle;
|
|
||||||
// }
|
|
||||||
sortableInstance = Sortable.create(sortableContainer, options);
|
sortableInstance = Sortable.create(sortableContainer, options);
|
||||||
});
|
});
|
||||||
|
|
||||||
// function reorder(from, to) {
|
|
||||||
// let newList = JSON.parse(JSON.stringify(value));
|
|
||||||
// let fromElem = newList[from];
|
|
||||||
// newList.splice(from, 1);
|
|
||||||
// newList.splice(to, 0, fromElem);
|
|
||||||
// value = newList;
|
|
||||||
// dispatch("reordered", value);
|
|
||||||
// }
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if isTable}
|
{#if isTable}
|
||||||
<tbody class="sortable-container {sortableClass}" bind:this={sortableContainer}>
|
<tbody class="sortable-container {sortableClass}" bind:this={sortableContainer}>
|
||||||
<slot />
|
<slot/>
|
||||||
</tbody>
|
</tbody>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="sortable-container {sortableClass}" bind:this={sortableContainer}>
|
<div class="sortable-container {sortableClass}" bind:this={sortableContainer}>
|
||||||
<slot />
|
<slot/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
import "tinymce/icons/default";
|
import "tinymce/icons/default";
|
||||||
import "tinymce/themes/silver";
|
import "tinymce/themes/silver";
|
||||||
import "tinymce/skins/ui/oxide/skin.css";
|
import "tinymce/skins/ui/oxide/skin.css";
|
||||||
import contentUiSkinCss from "tinymce/skins/ui/oxide/content.css";
|
import contentUiSkinCss from "tinymce/skins/ui/oxide/content.css?inline";
|
||||||
|
import customcss from "./tinymce.css?inline";
|
||||||
|
|
||||||
import "tinymce/plugins/link";
|
import "tinymce/plugins/link";
|
||||||
import "tinymce/plugins/code";
|
import "tinymce/plugins/code";
|
||||||
@@ -52,7 +53,7 @@
|
|||||||
toolbar_sticky: true,
|
toolbar_sticky: true,
|
||||||
skin: false,
|
skin: false,
|
||||||
content_css: false,
|
content_css: false,
|
||||||
content_style: contentUiSkinCss.toString(),
|
content_style: contentUiSkinCss.toString() + customcss.toString(),
|
||||||
branding: false,
|
branding: false,
|
||||||
inline: false,
|
inline: false,
|
||||||
plugins: plugins,
|
plugins: plugins,
|
||||||
@@ -67,8 +68,7 @@
|
|||||||
browser_spellcheck: true,
|
browser_spellcheck: true,
|
||||||
max_height: 600,
|
max_height: 600,
|
||||||
// media_poster: false,
|
// media_poster: false,
|
||||||
content_style:
|
|
||||||
"img {max-width: 100%;height: auto;",
|
|
||||||
setup: function (editor) {
|
setup: function (editor) {
|
||||||
activeEditor = editor;
|
activeEditor = editor;
|
||||||
|
|
||||||
@@ -100,6 +100,9 @@
|
|||||||
tinymce.init({...config, ...additionalConfig});
|
tinymce.init({...config, ...additionalConfig});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export function insertMedia(info){
|
||||||
|
activeEditor.execCommand('InsertHTML', false, info.html);
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<script>
|
||||||
|
import {onDestroy, onMount} from "svelte";
|
||||||
|
import Trix from "trix"
|
||||||
|
|
||||||
|
export let value = "";
|
||||||
|
export let field;
|
||||||
|
let editor;
|
||||||
|
|
||||||
|
|
||||||
|
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.heading3 = {
|
||||||
|
tagName: 'h3',
|
||||||
|
terminal: true,
|
||||||
|
breakOnReturn: true,
|
||||||
|
group: false
|
||||||
|
}
|
||||||
|
// console.log(Trix.config)
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="tox-wrapper">
|
||||||
|
<input id="x-{field.name}" {value} type="hidden">
|
||||||
|
<trix-editor
|
||||||
|
bind:this={editor}
|
||||||
|
class=" content"
|
||||||
|
input="x-{field.name}"
|
||||||
|
role="textbox"
|
||||||
|
tabindex="0"
|
||||||
|
on:trix-change={updateValue}
|
||||||
|
|
||||||
|
></trix-editor>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
.mce-content-body .img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mce-content-body{
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
.mce-content-body p{
|
||||||
|
|
||||||
|
margin-bottom: 14px;
|
||||||
|
&:last-child{
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mce-content-body ul {
|
||||||
|
padding: 0 0 0 16px;
|
||||||
|
list-style: none outside none;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.mce-content-body li::before {
|
||||||
|
content: "—";
|
||||||
|
opacity: .5;
|
||||||
|
font-size: 12px;
|
||||||
|
padding-right: 6px;
|
||||||
|
vertical-align: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mce-content-body li {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
import Avatar from "../account/Avatar.svelte";
|
import Avatar from "../account/Avatar.svelte";
|
||||||
import {fly} from "svelte/transition";
|
import {fly} from "svelte/transition";
|
||||||
import {createEventDispatcher} from "svelte";
|
import {createEventDispatcher} from "svelte";
|
||||||
|
import Dropdown from "../common/Dropdown.svelte";
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
export let member;
|
export let member;
|
||||||
@@ -21,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,
|
||||||
@@ -35,61 +33,50 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
transition:fly={{ duration: 200 }}
|
transition:fly={{ duration: 200 }}
|
||||||
class="d-flex justify-content-between align-items-center mb-3 "
|
class="member-item"
|
||||||
>
|
>
|
||||||
<div class="d-flex align-items-center status-{member.roles.includes('removed') ? 'removed' : 'active'}">
|
<div class="member-name status-{member.roles.includes('removed') ? 'removed' : 'active'}">
|
||||||
<Avatar name={member.name ?? "" } side={32}/>
|
<Avatar name={member.name ?? "" } side={32}/>
|
||||||
<div class="ms-3 ">
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<span class="fs-5">
|
{member.name}
|
||||||
{member.name}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
{member.email}
|
<div>
|
||||||
</div>
|
{member.email}
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div class="dropdown dropdown-center">
|
|
||||||
<button
|
|
||||||
class=" dropdown-toggle btn btn-light"
|
|
||||||
type="button"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
aria-expanded="false"
|
|
||||||
>
|
|
||||||
Roles
|
|
||||||
</button>
|
|
||||||
<div class="dropdown-menu">
|
|
||||||
<h6 class="dropdown-header">Remove role</h6>
|
|
||||||
{#each roles as role}
|
|
||||||
{#if member.roles.includes(role)}
|
|
||||||
<button
|
|
||||||
class="dropdown-item text-capitalize"
|
|
||||||
on:click={(e) => removeFrom(e,role)}
|
|
||||||
>
|
|
||||||
{role}
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
<div>
|
|
||||||
<hr class="dropdown-divider">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h6 class="dropdown-header">Add role</h6>
|
|
||||||
{#each roles as role}
|
|
||||||
{#if !member.roles.includes(role)}
|
|
||||||
<button
|
|
||||||
class="dropdown-item text-capitalize"
|
|
||||||
on:click={(e) => addTo(e,role)}
|
|
||||||
>
|
|
||||||
{role}
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<Dropdown orientation="right">
|
||||||
|
<div slot="button">
|
||||||
|
Roles
|
||||||
|
</div>
|
||||||
|
<h6 class="dropdown-header">Remove role</h6>
|
||||||
|
{#each roles as role}
|
||||||
|
{#if member.roles.includes(role)}
|
||||||
|
<button
|
||||||
|
class="dropdown-item button"
|
||||||
|
on:click={(e) => removeFrom(e,role)}
|
||||||
|
>
|
||||||
|
{role}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
|
||||||
|
<h6 class="dropdown-header">Add role</h6>
|
||||||
|
{#each roles as role}
|
||||||
|
{#if !member.roles.includes(role)}
|
||||||
|
<button
|
||||||
|
class="dropdown-item button"
|
||||||
|
on:click={(e) => addTo(e,role)}
|
||||||
|
>
|
||||||
|
{role}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
</Dropdown>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<style>
|
<style>
|
||||||
.status-removed {
|
.status-removed {
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
import ErrorAlert from "../common/ErrorAlert.svelte";
|
import ErrorAlert from "../common/ErrorAlert.svelte";
|
||||||
import SuccessAlert from "../common/SuccessAlert.svelte";
|
import SuccessAlert from "../common/SuccessAlert.svelte";
|
||||||
import SpinnerButton from "../common/SpinnerButton.svelte";
|
import SpinnerButton from "../common/SpinnerButton.svelte";
|
||||||
import Radio from "../forms/Radio.svelte";
|
|
||||||
import MemberSettingsCard from "./MemberSettingsCard.svelte";
|
import MemberSettingsCard from "./MemberSettingsCard.svelte";
|
||||||
import {getContext} from "svelte";
|
import {getContext} from "svelte";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper-tiny transparent mb-5">
|
<div class="common-wrapper">
|
||||||
<div class="lx-card mt-5">
|
<div class="lx-card mt-5">
|
||||||
<h3 class="header-small mb-5">Invite people</h3>
|
<h3 class="header-small mb-5">Invite people</h3>
|
||||||
<ErrorAlert message={errorMessage}/>
|
<ErrorAlert message={errorMessage}/>
|
||||||
@@ -72,12 +72,12 @@
|
|||||||
>Invitee Name</label
|
>Invitee Name</label
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={name}
|
bind:value={name}
|
||||||
class="form-control"
|
class="form-control"
|
||||||
id="inviteeName"
|
id="inviteeName"
|
||||||
placeholder="Member name"
|
placeholder="Member name"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -85,24 +85,24 @@
|
|||||||
>Invitee Email Address</label
|
>Invitee Email Address</label
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
bind:value={email}
|
bind:value={email}
|
||||||
class="form-control"
|
class="form-control"
|
||||||
id="inviteeEmail"
|
id="inviteeEmail"
|
||||||
placeholder="Member email"
|
placeholder="Member email"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="me-3">
|
<div class="me-3">
|
||||||
{#each channel.roles.filter((r) => r !== "removed") as arole}
|
<select bind:value={role}>
|
||||||
<Radio
|
{#each channel.roles.filter((r) => r !== "removed") as arole}
|
||||||
bind:group={role}
|
<option
|
||||||
value={arole}
|
value={arole}
|
||||||
name="role"
|
|
||||||
label={arole}
|
>{arole}</option>
|
||||||
/>
|
{/each}
|
||||||
{/each}
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-5 d-block text-center">
|
<div class="mt-5 d-block text-center">
|
||||||
@@ -111,17 +111,15 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="lx-card mt-3">
|
<div class="member-list">
|
||||||
<h3 class="header-small mb-5">Members</h3>
|
<h3 class="header-small mb-5 mt-5">Members</h3>
|
||||||
{#each users as user}
|
{#each users as user}
|
||||||
<MemberSettingsCard
|
<MemberSettingsCard
|
||||||
member={user}
|
member={user}
|
||||||
roles={channel.roles}
|
roles={channel.roles}
|
||||||
on:update={update}
|
on:update={update}
|
||||||
on:reinvite={(e) => invite(e.detail.email, e.detail.role)}
|
on:reinvite={(e) => invite(e.detail.email, e.detail.role)}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<script>
|
<script>
|
||||||
import {afterUpdate, getContext, onMount} from "svelte";
|
import {afterUpdate, getContext, onMount} from "svelte";
|
||||||
import {isEqual} from "lodash";
|
import axios from "axios";
|
||||||
import Manager from "./Manager.svelte";
|
import EditHeader from "./header/EditHeader.svelte"
|
||||||
import EditHeader from "./EditHeader.svelte"
|
|
||||||
import StatusSelect from "./StatusSelect.svelte"
|
|
||||||
import FilePreview from "./FilePreview.svelte"
|
import FilePreview from "./FilePreview.svelte"
|
||||||
import ContentTabs from "./ContentTabs.svelte"
|
import ContentTabs from "./header/ContentTabs.svelte"
|
||||||
import FormField from "./FormField.svelte"
|
import FormField from "./FormField.svelte"
|
||||||
import Graph from "./Graph.svelte"
|
import Graph from "./Graph.svelte"
|
||||||
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 {hasDataChanged} from "./editor.js";
|
||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
|
|
||||||
@@ -19,9 +19,9 @@
|
|||||||
records: [],
|
records: [],
|
||||||
edges: []
|
edges: []
|
||||||
};
|
};
|
||||||
export let recordHistory;
|
// export let recordHistory;
|
||||||
export let isCreateMode;
|
export let isCreateMode;
|
||||||
export let isWritable = false;
|
// export let isWritable = false;
|
||||||
export let users;
|
export let users;
|
||||||
let originalContent;
|
let originalContent;
|
||||||
let activeContentTab = "";
|
let activeContentTab = "";
|
||||||
@@ -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,
|
||||||
@@ -151,51 +148,44 @@
|
|||||||
|
|
||||||
<svelte:window on:beforeunload={beforeUnload}/>
|
<svelte:window on:beforeunload={beforeUnload}/>
|
||||||
|
|
||||||
<div class="wrapper-normal transparent">
|
<div class="record-edit">
|
||||||
<Manager managerRecords={recordHistory} {graph}/>
|
<div class="tools-header">
|
||||||
<EditHeader {schema} {record} {isCreateMode} {graph} bind:activeContentTab/>
|
<!-- <Manager managerRecords={recordHistory} {graph}/>-->
|
||||||
|
<EditHeader {schema} bind:record {isCreateMode} bind:activeContentTab/>
|
||||||
{#if !["_graph", "_info"].includes(activeContentTab) && isWritable}
|
{#if isCreateMode}
|
||||||
<div class="shadow-lg "
|
<button
|
||||||
style="position:fixed;bottom:0;left:0px;width:100%;background: rgb(206, 223, 210);z-index:1050"
|
class="button primary btn-spinner"
|
||||||
>
|
on:click={save}
|
||||||
<div
|
|
||||||
class="d-flex mt-3 mb-3 align-items-center justify-content-center"
|
|
||||||
>
|
>
|
||||||
<StatusSelect bind:status={record.status} {record} {schema}/>
|
|
||||||
{#if isCreateMode}
|
|
||||||
<button
|
|
||||||
class="ms-2 btn btn-primary btn-spinner"
|
|
||||||
on:click={save}
|
|
||||||
>
|
|
||||||
<span
|
<span
|
||||||
class="spinner-border spinner-border-sm"
|
class="spinner-border spinner-border-sm"
|
||||||
role="status"
|
role="status"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
Create
|
Create
|
||||||
</button>
|
</button>
|
||||||
{:else if hasUnsavedData}
|
{:else if hasUnsavedData}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="ms-2 btn btn-primary btn-spinner"
|
class="button primary ms-2 btn btn-primary btn-spinner"
|
||||||
on:click={save}
|
on:click={save}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="spinner-border spinner-border-sm"
|
class="spinner-border spinner-border-sm"
|
||||||
role="status"
|
role="status"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
<Title {schema} {record} {isCreateMode}/>
|
||||||
|
|
||||||
|
|
||||||
<ErrorAlert message={errorMessage}/>
|
<ErrorAlert message={errorMessage}/>
|
||||||
|
|
||||||
<div class=" mt-4" style="margin-bottom:150px">
|
<div class=" mt-4" style="margin-bottom:150px;position:relative;">
|
||||||
<ContentTabs
|
<ContentTabs
|
||||||
{schema}
|
{schema}
|
||||||
{isCreateMode}
|
{isCreateMode}
|
||||||
@@ -203,7 +193,6 @@
|
|||||||
/>
|
/>
|
||||||
{#if !["_graph", "_info"].includes(activeContentTab)}
|
{#if !["_graph", "_info"].includes(activeContentTab)}
|
||||||
<FilePreview {record} {schema}/>
|
<FilePreview {record} {schema}/>
|
||||||
<!-- <fieldset disabled="disabled"> -->
|
|
||||||
{#each activeFields as field (field.name)}
|
{#each activeFields as field (field.name)}
|
||||||
{#if activeContentTab === field.group}
|
{#if activeContentTab === field.group}
|
||||||
<FormField
|
<FormField
|
||||||
@@ -217,7 +206,6 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
<!-- </fieldset> -->
|
|
||||||
{:else if activeContentTab === "_graph"}
|
{:else if activeContentTab === "_graph"}
|
||||||
<Graph {graph} {record}/>
|
<Graph {graph} {record}/>
|
||||||
{:else if activeContentTab === "_info"}
|
{:else if activeContentTab === "_info"}
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
<script>
|
|
||||||
import {getContext} from "svelte";
|
|
||||||
import Icon from "../common/Icon.svelte";
|
|
||||||
import {previewTitle} from "./Preview";
|
|
||||||
|
|
||||||
const channel = getContext("channel");
|
|
||||||
export let schema;
|
|
||||||
export let graph;
|
|
||||||
export let record;
|
|
||||||
export let isCreateMode;
|
|
||||||
export let activeContentTab;
|
|
||||||
|
|
||||||
function clone(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
axios.post(channel.lucentUrl + "/records/clone/" + record.id).then(response => {
|
|
||||||
window.location = channel.lucentUrl + "/records/" + response.data.id;
|
|
||||||
}).catch(error => {
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h3 class="header-normal mt-5 mb-0">
|
|
||||||
<a
|
|
||||||
class="text-muted d-block text-decoration-none fs-6 mb-1"
|
|
||||||
href="{channel.lucentUrl}/content/{schema.name}"
|
|
||||||
>{schema.label.toUpperCase()}</a
|
|
||||||
>
|
|
||||||
|
|
||||||
<span class="text-dark d-block">
|
|
||||||
{#if !isCreateMode}
|
|
||||||
{previewTitle(channel.schemas, record, graph)}
|
|
||||||
{:else}
|
|
||||||
New Record
|
|
||||||
{/if}
|
|
||||||
</span>
|
|
||||||
{#if !isCreateMode}
|
|
||||||
<div class="dropdown d-inline-block">
|
|
||||||
<button
|
|
||||||
class="btn btn-link btn-sm"
|
|
||||||
type="button"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
aria-expanded="false"
|
|
||||||
>
|
|
||||||
<Icon icon="ellipsis"/>
|
|
||||||
</button>
|
|
||||||
<div class="dropdown-menu">
|
|
||||||
|
|
||||||
<h6 class="dropdown-header">Record Actions</h6>
|
|
||||||
<a
|
|
||||||
class="dropdown-item"
|
|
||||||
href="{channel.lucentUrl}/records/new?schema={schema.name}"
|
|
||||||
>Create new</a
|
|
||||||
>
|
|
||||||
{#if !isCreateMode}
|
|
||||||
<a
|
|
||||||
class="dropdown-item"
|
|
||||||
on:click={clone}
|
|
||||||
href={channel.lucentUrl}
|
|
||||||
>
|
|
||||||
Clone
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
<a
|
|
||||||
on:click|preventDefault={(e) =>
|
|
||||||
(activeContentTab = "_info")}
|
|
||||||
class="dropdown-item"
|
|
||||||
href="{channel.lucentUrl}">Revisions</a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</h3>
|
|
||||||
@@ -1,55 +1,50 @@
|
|||||||
<script>
|
<script>
|
||||||
import Preview from "../files/Preview.svelte";
|
import Preview from "../files/Preview.svelte";
|
||||||
import {fileurl} from "../files/imageserver"
|
import {fileurl} from "../files/imageserver"
|
||||||
|
import {getContext} from "svelte"
|
||||||
|
|
||||||
|
const channel = getContext("channel");
|
||||||
export let record;
|
export let record;
|
||||||
export let schema;
|
export let schema;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if schema.type === "files"}
|
{#if schema.type === "files"}
|
||||||
<div class="row mb-4">
|
<div class="record-edit-file-preview">
|
||||||
<div class="col" style="max-width:276px">
|
<div>
|
||||||
<Preview {record} size="large"/>
|
<Preview {record} size="large"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="file-details">
|
||||||
<ul class="list-group ">
|
|
||||||
<li class="list-group-item border-primary">
|
<div class="file-details-item">
|
||||||
<span class="text-muted">Filename</span>
|
<span class="text-muted">Filename</span>
|
||||||
<span>{record._file.path}</span>
|
<span>{record._file.path}</span>
|
||||||
</li>
|
</div>
|
||||||
<li class="list-group-item border-primary">
|
<div class="file-details-item">
|
||||||
<span class="text-muted">Original name</span>
|
<span class="text-muted">Original name</span>
|
||||||
<span>{record._file.originalName}</span>
|
<span>{record._file.originalName}</span>
|
||||||
</li>
|
</div>
|
||||||
<li class="list-group-item border-primary">
|
<div class="file-details-item">
|
||||||
<span class="text-muted">Mime type</span>
|
<span class="text-muted">Mime type</span>
|
||||||
<span>{record._file.mime}</span>
|
<span>{record._file.mime}</span>
|
||||||
</li>
|
</div>
|
||||||
{#if record._file.width}
|
{#if record._file.width}
|
||||||
<li class="list-group-item border-primary">
|
<div class="file-details-item">
|
||||||
<span class="text-muted">Dimensions</span>
|
<span class="text-muted">Dimensions</span>
|
||||||
<span>{record._file.width}x{record._file.height}</span>
|
<span>{record._file.width}x{record._file.height}</span>
|
||||||
</li>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<li class="list-group-item border-primary">
|
<div class="file-details-item">
|
||||||
<span class="text-muted">File size</span>
|
<span class="text-muted">File size</span>
|
||||||
<span>{(record._file.size / 1024).toFixed(1)}kB</span>
|
<span>{(record._file.size / 1024).toFixed(1)}kB</span>
|
||||||
</li>
|
</div>
|
||||||
<li class="list-group-item border-primary">
|
<div class="file-details-item">
|
||||||
<span class="text-muted">Checksum</span>
|
<span class="text-muted">Checksum</span>
|
||||||
<span>{record._file.checksum}</span>
|
<span>{record._file.checksum}</span>
|
||||||
</li>
|
</div>
|
||||||
<li class="list-group-item border-primary">
|
<div class="file-details-item">
|
||||||
<span class="text-muted">Download</span>
|
<a class="button primary" target="_blank" style="display: inline-flex" href="{fileurl(channel,record)}">Download</a>
|
||||||
<a href="{fileurl(record)}">{record._file.path}</a>
|
</div>
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
|
||||||
.list-group {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -2,14 +2,10 @@
|
|||||||
import Text from "./elements/Text.svelte";
|
import Text from "./elements/Text.svelte";
|
||||||
import Slug from "./elements/Slug.svelte";
|
import Slug from "./elements/Slug.svelte";
|
||||||
import Reference from "./elements/Reference.svelte";
|
import Reference from "./elements/Reference.svelte";
|
||||||
import ReferenceInline from "./elements/ReferenceInline.svelte";
|
|
||||||
import Block from "./block/Block.svelte";
|
|
||||||
import Color from "./elements/Color.svelte";
|
import 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";
|
||||||
@@ -17,7 +13,6 @@
|
|||||||
import Json from "./elements/JSON.svelte";
|
import Json from "./elements/JSON.svelte";
|
||||||
import Markdown from "./elements/Markdown.svelte";
|
import Markdown from "./elements/Markdown.svelte";
|
||||||
import FieldHeader from "./elements/FieldHeader.svelte";
|
import FieldHeader from "./elements/FieldHeader.svelte";
|
||||||
import ReferenceTable from "./elements/ReferenceTable.svelte";
|
|
||||||
import ReferenceTags from "./elements/ReferenceTags.svelte";
|
import ReferenceTags from "./elements/ReferenceTags.svelte";
|
||||||
|
|
||||||
const formElements = {
|
const formElements = {
|
||||||
@@ -28,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,
|
||||||
};
|
};
|
||||||
@@ -47,24 +40,9 @@
|
|||||||
const id = `field-${field.name}-${record.id}`;
|
const id = `field-${field.name}-${record.id}`;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="card editor-field">
|
<div class="editor-field">
|
||||||
<FieldHeader {schema} {field} {id}/>
|
<FieldHeader {field} {id}/>
|
||||||
{#if field.info.name === "reference" && field.layout === "inline"}
|
{#if field.info.name === "reference" && field.layout === "tags"}
|
||||||
<ReferenceInline
|
|
||||||
bind:graph
|
|
||||||
{record}
|
|
||||||
{field}
|
|
||||||
{validationErrors}
|
|
||||||
/>
|
|
||||||
{:else if field.info.name === "reference" && field.layout === "table"}
|
|
||||||
<ReferenceTable
|
|
||||||
bind:graph
|
|
||||||
{id}
|
|
||||||
{record}
|
|
||||||
{field}
|
|
||||||
{validationErrors}
|
|
||||||
/>
|
|
||||||
{:else if field.info.name === "reference" && field.layout === "tags"}
|
|
||||||
<ReferenceTags
|
<ReferenceTags
|
||||||
bind:graph
|
bind:graph
|
||||||
{id}
|
{id}
|
||||||
@@ -82,15 +60,6 @@
|
|||||||
/>
|
/>
|
||||||
{:else if field.info.name === "file"}
|
{:else if field.info.name === "file"}
|
||||||
<File bind:graph {record} {field} {validationErrors}/>
|
<File bind:graph {record} {field} {validationErrors}/>
|
||||||
{:else if field.info.name === "block"}
|
|
||||||
<Block
|
|
||||||
bind:graph
|
|
||||||
bind:value={data[field.name]}
|
|
||||||
{record}
|
|
||||||
{id}
|
|
||||||
{field}
|
|
||||||
{validationErrors}
|
|
||||||
/>
|
|
||||||
{:else if field.info.name === "text"}
|
{:else if field.info.name === "text"}
|
||||||
<Text
|
<Text
|
||||||
bind:value={data[field.name]}
|
bind:value={data[field.name]}
|
||||||
@@ -115,6 +84,26 @@
|
|||||||
{isCreateMode}
|
{isCreateMode}
|
||||||
{id}
|
{id}
|
||||||
/>
|
/>
|
||||||
|
{:else if field.info.name === "rich"}
|
||||||
|
<RichEditor
|
||||||
|
bind:value={data[field.name]}
|
||||||
|
{schema}
|
||||||
|
{field}
|
||||||
|
{validationErrors}
|
||||||
|
{isCreateMode}
|
||||||
|
bind:graph
|
||||||
|
{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}
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import PreviewCardSmall from "./PreviewCardSmall.svelte";
|
|
||||||
import PreviewCard from "./PreviewCard.svelte";
|
|
||||||
import Icon from "../common/Icon.svelte";
|
|
||||||
import Preview from "../files/Preview.svelte";
|
|
||||||
import {getContext} from "svelte";
|
import {getContext} from "svelte";
|
||||||
import {uniqBy} from "lodash";
|
import PreviewReference from "./previews/PreviewReference.svelte";
|
||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
export let graph;
|
export let graph;
|
||||||
export let record;
|
|
||||||
|
|
||||||
function findEdgeField(schema, edgeField){
|
function findEdgeField(schema, edgeField){
|
||||||
if(edgeField.includes(":")){
|
if(edgeField.includes(":")){
|
||||||
let edgeFieldAr = edgeField.split(":");
|
let edgeFieldAr = edgeField.split(":");
|
||||||
@@ -18,121 +12,37 @@
|
|||||||
return schema.fields.find((f) => f.name === edgeField);
|
return schema.fields.find((f) => f.name === edgeField);
|
||||||
}
|
}
|
||||||
|
|
||||||
let parentEdgesByField = graph.parentEdges
|
let backlinks = graph.parentEdges.map(edge => {
|
||||||
.filter((edge) => edge.source !== record.id && edge.depth === 1)
|
const parentRecord = graph.records.find( record => record.id === edge.source);
|
||||||
.reduce((carry, edge) => {
|
|
||||||
let schema = channel.schemas.find((s) => s.name === edge.sourceSchema);
|
|
||||||
let edgeField = findEdgeField(schema,edge.field);
|
|
||||||
let schemaField = edge.sourceSchema + edgeField;
|
|
||||||
|
|
||||||
let arecord = graph.records.find((n) => {
|
let schema = channel.schemas.find((s) => s.name === parentRecord.schema);
|
||||||
return n.id === edge.source;
|
let edgeField = findEdgeField(schema,edge.field);
|
||||||
});
|
if(!edgeField){
|
||||||
if (!carry[schemaField]) {
|
return null;
|
||||||
carry[schemaField] = {
|
}
|
||||||
field: edgeField,
|
return {
|
||||||
schema: schema,
|
field: edgeField.label,
|
||||||
nodes: [],
|
record: parentRecord
|
||||||
};
|
}
|
||||||
}
|
}).filter( edgeOrNull => !!edgeOrNull)
|
||||||
if (arecord) {
|
|
||||||
carry[schemaField].nodes.push(arecord);
|
|
||||||
carry[schemaField].nodes = uniqBy(carry[schemaField].nodes,"id");
|
|
||||||
}
|
|
||||||
return carry;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
|
|
||||||
let childrenEdgesByField = graph.edges
|
|
||||||
.filter((edge) => edge.source === record.id && edge.depth === 1)
|
|
||||||
.reduce((carry, edge) => {
|
|
||||||
|
|
||||||
let schema = channel.schemas.find((s) => s.name === record.schema);
|
|
||||||
let edgeField = findEdgeField(schema,edge.field);
|
|
||||||
|
|
||||||
// let schemaField = edge.targetSchema + edgeField;
|
|
||||||
let schemaField = edgeField.name + edge.targetSchema;
|
|
||||||
|
|
||||||
if (!carry[schemaField]) {
|
|
||||||
carry[schemaField] = {
|
|
||||||
field: edgeField,
|
|
||||||
nodes: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let arecord = graph.records.find((n) => {
|
|
||||||
return n.id === edge.target;
|
|
||||||
});
|
|
||||||
if (arecord) {
|
|
||||||
carry[schemaField].nodes.push(arecord);
|
|
||||||
carry[schemaField].nodes = uniqBy(carry[schemaField].nodes,"id");
|
|
||||||
}
|
|
||||||
return carry;
|
|
||||||
}, {});
|
|
||||||
</script>
|
</script>
|
||||||
|
<div class="editor-field">
|
||||||
|
{#each backlinks as backlink}
|
||||||
|
<div style="margin: 0 0 15px;position: relative;">
|
||||||
|
<span style="
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
display: block;
|
||||||
|
|
||||||
{#each Object.entries(parentEdgesByField) as [fieldName, fieldData]}
|
"
|
||||||
<div class="lx-card mt-3">
|
>In <i>{backlink.field}</i> of</span>
|
||||||
<div class="text-center mb-3 d-flex justify-content-center align-items-center text-uppercase ">
|
<PreviewReference
|
||||||
<span>{fieldData.schema.label}</span>
|
record={backlink.record}
|
||||||
<Icon icon="angle-right" width="12" height="12"/>
|
hasDelete={false}
|
||||||
<span>{fieldData.field.label}</span>
|
{graph}
|
||||||
</div>
|
/>
|
||||||
<div class="d-flex justify-content-center text-center flex-wrap">
|
|
||||||
{#each fieldData.nodes as node}
|
|
||||||
{#if node._file?.path}
|
|
||||||
<div class="ms-2 mb-2" style="max-height:64px;">
|
|
||||||
<Preview record={node} size="small"/>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<div class="ms-2 mb-2">
|
|
||||||
<PreviewCardSmall {graph} record={node}/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
<!-- <div class="text-center mt-3 d-block">{fieldData.field.label}</div>-->
|
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
|
Nothing links to this record
|
||||||
{/each}
|
{/each}
|
||||||
{#if Object.entries(parentEdgesByField).length > 0}
|
</div>
|
||||||
<div class="text-center my-4">
|
|
||||||
<Icon icon="angles-down" width="32" height="32"/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<div style="max-width:400px;margin:0 auto;">
|
|
||||||
<PreviewCard {graph} record={record}/>
|
|
||||||
</div>
|
|
||||||
{#if Object.entries(childrenEdgesByField).length > 0}
|
|
||||||
<div class="text-center my-4">
|
|
||||||
<Icon icon="angles-down" width="32" height="32"/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#each Object.entries(childrenEdgesByField) as [fieldName, fieldData]}
|
|
||||||
<div class="lx-card mt-3">
|
|
||||||
<div class="text-center mb-5 d-block">{fieldData.field.label}</div>
|
|
||||||
<div class="d-flex justify-content-center text-center flex-wrap">
|
|
||||||
{#each fieldData.nodes as node}
|
|
||||||
{#if fieldData.field.info.ui === "file"}
|
|
||||||
<div
|
|
||||||
class="ms-2 mb-2"
|
|
||||||
style="max-width:64px;overflow:hidden;white-space: nowrap;text-overflow: ellipsis;"
|
|
||||||
>
|
|
||||||
<Preview
|
|
||||||
record={node}
|
|
||||||
size="small"
|
|
||||||
showFilename={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<div class="ms-2 mb-2">
|
|
||||||
<PreviewCardSmall {graph} record={node}/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
</style>
|
|
||||||
@@ -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, sortBy} 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();
|
||||||
@@ -123,19 +124,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="lx-card mt-4">
|
<div class="revisions">
|
||||||
{#if schema.revisions > 0}
|
{#if schema.revisions > 0}
|
||||||
<div class="header-small mb-3">Revisions</div>
|
<div class="header-small mb-3">Revisions</div>
|
||||||
{#each revisions as revision}
|
{#each revisions as revision}
|
||||||
{#if revision._sys.version != record._sys.version}
|
{#if revision._sys.version !== record._sys.version}
|
||||||
<div
|
<div
|
||||||
class="row p-2 rounded"
|
class="revision"
|
||||||
class:active={revision._sys.version ===
|
class:active={revision._sys.version ===
|
||||||
selectedRevision?._sys.version}
|
selectedRevision?._sys.version}
|
||||||
>
|
>
|
||||||
|
|
||||||
<div class="col-2">version {revision._sys.version}</div>
|
<div class="version">
|
||||||
<div class="col-5">
|
<span>version {revision._sys.version}</span>
|
||||||
<Avatar
|
<Avatar
|
||||||
name={usernameById(users, revision._sys.updatedBy)}
|
name={usernameById(users, revision._sys.updatedBy)}
|
||||||
side={24}
|
side={24}
|
||||||
@@ -147,7 +148,7 @@
|
|||||||
<button
|
<button
|
||||||
disabled={revision._sys.version ===
|
disabled={revision._sys.version ===
|
||||||
selectedRevision?._sys.version}
|
selectedRevision?._sys.version}
|
||||||
class="btn btn-sm btn-outline-primary"
|
class="button"
|
||||||
on:click={(e) => compare(e, revision)}
|
on:click={(e) => compare(e, revision)}
|
||||||
>Compare
|
>Compare
|
||||||
</button
|
</button
|
||||||
@@ -164,14 +165,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div bind:this={revisionSection}>
|
<div bind:this={revisionSection}>
|
||||||
{#if selectedRevision}
|
{#if selectedRevision}
|
||||||
<div class="mt-4">
|
<div class="selected-revision">
|
||||||
{#if fieldsWithDiff.length > 0}
|
{#if fieldsWithDiff.length > 0}
|
||||||
<p class="text-center fw-bold mb-3 mt-5">
|
<p class="text-center fw-bold mb-3 mt-5">
|
||||||
If you choose to rollback to this revision
|
If you choose to rollback to this revision
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
on:click={rollback}
|
on:click={rollback}
|
||||||
class="btn btn-primary mb-5 d-block mx-auto"
|
class="button"
|
||||||
>
|
>
|
||||||
Rollback to version {selectedRevision._sys.version}
|
Rollback to version {selectedRevision._sys.version}
|
||||||
</button>
|
</button>
|
||||||
@@ -189,29 +190,25 @@
|
|||||||
{field.label}
|
{field.label}
|
||||||
</div> -->
|
</div> -->
|
||||||
<div
|
<div
|
||||||
class="lx-card row p-4 mb-4 w-100"
|
class="revision-field"
|
||||||
style="overflow:hidden"
|
style="overflow:hidden"
|
||||||
>
|
>
|
||||||
<div class="col-5">
|
<div class="compare-left">
|
||||||
<RevisionCell
|
<RevisionCell
|
||||||
{field}
|
{field}
|
||||||
side={record.data[field.name]}
|
side={record.data[field.name]}
|
||||||
colorClass="text-danger"
|
colorClass="text-danger"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-2">
|
<div class="compare-center">
|
||||||
<div
|
<span class="me-1">{field.label}</span>
|
||||||
class="h-100 d-flex align-items-center justify-content-center text-secondary"
|
<Icon
|
||||||
>
|
icon="angle-right"
|
||||||
<span class="me-1">{field.label}</span>
|
width="12"
|
||||||
<Icon
|
height="12"
|
||||||
icon="angle-right"
|
/>
|
||||||
width="12"
|
|
||||||
height="12"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5">
|
<div class="compare-right">
|
||||||
<RevisionCell
|
<RevisionCell
|
||||||
edges={selectedRevision._edges}
|
edges={selectedRevision._edges}
|
||||||
{field}
|
{field}
|
||||||
@@ -235,22 +232,25 @@
|
|||||||
</p>
|
</p>
|
||||||
{#each Object.entries(edgeFieldsDiff) as [field, edges]}
|
{#each Object.entries(edgeFieldsDiff) as [field, edges]}
|
||||||
<div
|
<div
|
||||||
class="lx-card row p-4 mb-4 w-100"
|
class="revision-references"
|
||||||
style="overflow:hidden"
|
style="overflow:hidden"
|
||||||
>
|
>
|
||||||
<div class="col-4">
|
<div class="reference-field">
|
||||||
{field}:
|
{field}:
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8">
|
<div class="reference-compare">
|
||||||
<p class="mb-2 text-danger">Record</p>
|
|
||||||
|
<p class="">Record</p>
|
||||||
{#each edges.record as edge}
|
{#each edges.record as edge}
|
||||||
<RevisionEdgeRow {edge} />
|
<RevisionEdgeRow {edge}/>
|
||||||
{:else}
|
{:else}
|
||||||
<p>No references</p>
|
<p>No references</p>
|
||||||
{/each}
|
{/each}
|
||||||
<p class="mt-4 mb-2 text-success">Revision</p>
|
</div>
|
||||||
|
<div class="reference-compare">
|
||||||
|
<p class="text-success">Revision</p>
|
||||||
{#each edges.revision as edge}
|
{#each edges.revision as edge}
|
||||||
<RevisionEdgeRow {edge} />
|
<RevisionEdgeRow {edge}/>
|
||||||
{:else}
|
{:else}
|
||||||
<p>No references</p>
|
<p>No references</p>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -263,17 +263,3 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
|
||||||
.label {
|
|
||||||
width: 180px;
|
|
||||||
margin-right: 10px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.active {
|
|
||||||
background-color: #eee;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<script>
|
<script>
|
||||||
import {afterUpdate, createEventDispatcher, onMount,getContext} 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 "./ContentTabs.svelte";
|
import ContentTabs from "./header/ContentTabs.svelte";
|
||||||
import StatusSelect from "./StatusSelect.svelte";
|
|
||||||
import ErrorAlert from "../common/ErrorAlert.svelte";
|
import ErrorAlert from "../common/ErrorAlert.svelte";
|
||||||
|
import EditHeader from "./header/EditHeader.svelte";
|
||||||
|
import axios from "axios";
|
||||||
|
import Title from "./header/Title.svelte";
|
||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
@@ -78,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,
|
||||||
@@ -135,7 +133,6 @@
|
|||||||
resolve(null);
|
resolve(null);
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
// setOriginalContent();
|
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
if (typeof error.response.data.error === "string") {
|
if (typeof error.response.data.error === "string") {
|
||||||
errorMessage = error.response.data.error;
|
errorMessage = error.response.data.error;
|
||||||
@@ -144,9 +141,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
resolve(null);
|
resolve(null);
|
||||||
// msgSuccess = null;
|
|
||||||
// msgError = error.response.data.error;
|
|
||||||
// submitted = false;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -154,15 +148,45 @@
|
|||||||
|
|
||||||
<svelte:window on:beforeunload={beforeUnload}/>
|
<svelte:window on:beforeunload={beforeUnload}/>
|
||||||
|
|
||||||
<div class="inline-edit my-4">
|
<div class="inline-edit record-edit">
|
||||||
|
<div class="tools-header">
|
||||||
|
<EditHeader {schema} bind:record {isCreateMode} bind:activeContentTab/>
|
||||||
|
{#if isCreateMode}
|
||||||
|
<button
|
||||||
|
class="button primary btn-spinner"
|
||||||
|
on:click={save}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="spinner-border spinner-border-sm"
|
||||||
|
role="status"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
Create
|
||||||
|
</button>
|
||||||
|
{:else if hasUnsavedData}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="button primary ms-2 btn btn-primary btn-spinner"
|
||||||
|
on:click={save}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="spinner-border spinner-border-sm"
|
||||||
|
role="status"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<Title {schema} {record} {isCreateMode}/>
|
||||||
<ErrorAlert message={errorMessage}/>
|
<ErrorAlert message={errorMessage}/>
|
||||||
|
|
||||||
<div class=" mt-1">
|
<div class=" mt-4" style="margin-bottom:150px;position:relative;">
|
||||||
<ContentTabs
|
<ContentTabs
|
||||||
{schema}
|
{schema}
|
||||||
{isCreateMode}
|
{isCreateMode}
|
||||||
bind:active={activeContentTab}
|
bind:active={activeContentTab}
|
||||||
{record}
|
|
||||||
/>
|
/>
|
||||||
<FilePreview {record} {schema}/>
|
<FilePreview {record} {schema}/>
|
||||||
<!-- <fieldset disabled="disabled"> -->
|
<!-- <fieldset disabled="disabled"> -->
|
||||||
@@ -181,48 +205,5 @@
|
|||||||
{/each}
|
{/each}
|
||||||
<!-- </fieldset> -->
|
<!-- </fieldset> -->
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<div class="d-flex mt-3 align-items-center justify-content-center">
|
|
||||||
{#if schema.hasDrafts}
|
|
||||||
<StatusSelect bind:status={record.status} {schema}/>
|
|
||||||
{/if}
|
|
||||||
{#if isCreateMode}
|
|
||||||
<button
|
|
||||||
class="ms-2 btn btn-primary btn-spinner"
|
|
||||||
on:click={save}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="spinner-border spinner-border-sm"
|
|
||||||
role="status"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
Add
|
|
||||||
</button>
|
|
||||||
{:else}
|
|
||||||
<button
|
|
||||||
disabled={!hasUnsavedData}
|
|
||||||
class="ms-2 btn btn-primary btn-spinner"
|
|
||||||
on:click={save}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="spinner-border spinner-border-sm"
|
|
||||||
role="status"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
Save
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
<button class="ms-2 btn btn-link" on:click={cancel}>
|
|
||||||
cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
|
||||||
.inline-edit {
|
|
||||||
padding: 44px;
|
|
||||||
background-color: #eee;
|
|
||||||
border-radius: 32px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
<script>
|
|
||||||
|
|
||||||
import Icon from "../common/Icon.svelte";
|
|
||||||
import PreviewCardSmall from "./PreviewCardSmall.svelte";
|
|
||||||
|
|
||||||
export let managerRecords;
|
|
||||||
|
|
||||||
export let graph;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if managerRecords.length > 0}
|
|
||||||
<div
|
|
||||||
class="record-history d-flex justify-content-center align-items-center w-100 mb-4 mt-4"
|
|
||||||
>
|
|
||||||
{#each managerRecords.reverse() as arecord, i}
|
|
||||||
{#if i !== 0}
|
|
||||||
<Icon icon="angle-right"/>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="mx-3 p-0 my-0">
|
|
||||||
<PreviewCardSmall record={arecord} {graph}/>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.record-history {
|
|
||||||
/* background-color: #fff; */
|
|
||||||
padding: 15px 10px;
|
|
||||||
border-radius: 32px;
|
|
||||||
line-height: 12px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -3,30 +3,12 @@ import {stripHtml} from "../../helpers";
|
|||||||
|
|
||||||
export function previewTitle(schemas, record, graph) {
|
export function previewTitle(schemas, record, graph) {
|
||||||
let schema = schemas.find((aSchema) => aSchema.name === record?.schema);
|
let schema = schemas.find((aSchema) => aSchema.name === record?.schema);
|
||||||
|
if (!schema?.cardTitle) {
|
||||||
if (!schema?.titleTemplate) {
|
|
||||||
return noTemplate(schema, record);
|
return noTemplate(schema, record);
|
||||||
}
|
}
|
||||||
|
|
||||||
let recordData = record.data;
|
let recordData = record.data;
|
||||||
let template = Mustache.parse(schema.titleTemplate);
|
let render = Mustache.render(schema.cardTitle, recordData);
|
||||||
|
|
||||||
let referencePreviews = template
|
|
||||||
.filter(segment => segment[0] === "name") // keep only template tags
|
|
||||||
.map((segment) => segment[1]) // map to fieldNames
|
|
||||||
.filter(fieldName => { // keep only references
|
|
||||||
let schemaField = schema.fields.find(f => f.name === fieldName)
|
|
||||||
return schemaField?.info.name === "reference";
|
|
||||||
}).reduce((carry, field) => { // map to records
|
|
||||||
let edge = graph.edges.find(edge => edge.source === record.id && edge.field === field)
|
|
||||||
let referenceRecord = graph.records.find(rec => rec.id === edge?.target)
|
|
||||||
carry[field] = previewTitle(schemas, referenceRecord, graph);
|
|
||||||
return carry;
|
|
||||||
}, {});
|
|
||||||
recordData = {...recordData, ...referencePreviews}
|
|
||||||
|
|
||||||
let render = Mustache.render(schema.titleTemplate, recordData);
|
|
||||||
|
|
||||||
if (!render || render === "") {
|
if (!render || render === "") {
|
||||||
return noTemplate(schema, record);
|
return noTemplate(schema, record);
|
||||||
}
|
}
|
||||||
@@ -43,7 +25,7 @@ function noTemplate(schema, record) {
|
|||||||
record?.data[schema.fields.filter((f) => f.info.name === "text")[0]?.name]
|
record?.data[schema.fields.filter((f) => f.info.name === "text")[0]?.name]
|
||||||
).slice(0, 300);
|
).slice(0, 300);
|
||||||
|
|
||||||
if(title.trim() == ""){
|
if(title.trim() === ""){
|
||||||
return "~Untitled~";
|
return "~Untitled~";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
<script>
|
|
||||||
import Icon from "../common/Icon.svelte";
|
|
||||||
|
|
||||||
import { getContext, createEventDispatcher } from "svelte";
|
|
||||||
import Preview from "../files/Preview.svelte";
|
|
||||||
import { previewTitle } from "./Preview";
|
|
||||||
import Status from "./Status.svelte";
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
const channel = getContext("channel");
|
|
||||||
export let graph;
|
|
||||||
export let record;
|
|
||||||
export let classes = "";
|
|
||||||
export let hasDelete = false;
|
|
||||||
|
|
||||||
let schema = channel.schemas.find((aschema) => aschema.name === record.schema);
|
|
||||||
let cardTitle = previewTitle(channel.schemas, record, graph);
|
|
||||||
function remove(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
dispatch("remove", record.id);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
|
|
||||||
class="card mb-2 bg-light {classes}"
|
|
||||||
style="border-color:{schema.color ?? '#ccc'}; border-width: 1px;"
|
|
||||||
>
|
|
||||||
<div class="card-body d-flex">
|
|
||||||
{#if schema.type === "files"}
|
|
||||||
<div style="max-width:94px;margin-right:15px">
|
|
||||||
<Preview {record} size="small" />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<div class="overflow-hidden">
|
|
||||||
<a
|
|
||||||
class="title-link m-0 fs-5 text-decoration-none text-dark d-block"
|
|
||||||
href="{channel.lucentUrl}/records/{record.id}"
|
|
||||||
title={cardTitle}
|
|
||||||
>
|
|
||||||
{cardTitle}
|
|
||||||
</a>
|
|
||||||
<small class="text-muted">
|
|
||||||
{schema.label}
|
|
||||||
</small>
|
|
||||||
<small class="text-muted">
|
|
||||||
{#if record.status === "draft"}
|
|
||||||
<Status status={record.status} />
|
|
||||||
{/if}
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if hasDelete}
|
|
||||||
<div class="position-absolute end-0" style="top:5px">
|
|
||||||
<button
|
|
||||||
class="trash-button text-dark btn btn-sm btn-link"
|
|
||||||
on:click={remove}
|
|
||||||
><Icon icon="trash-can" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
.card .trash-button {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.card:hover .trash-button {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.title-link {
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,230 +0,0 @@
|
|||||||
<script>
|
|
||||||
import Icon from "../common/Icon.svelte";
|
|
||||||
|
|
||||||
|
|
||||||
import {createEventDispatcher, onMount, getContext} from "svelte";
|
|
||||||
import Preview from "../files/Preview.svelte";
|
|
||||||
import InlineEdit from "./InlineEdit.svelte";
|
|
||||||
import Reference from "../content/elements/Reference.svelte";
|
|
||||||
import File from "../content/elements/File.svelte";
|
|
||||||
|
|
||||||
const channel = getContext("channel");
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
export let isFirst;
|
|
||||||
export let isLast;
|
|
||||||
export let toDelete = false;
|
|
||||||
export let record;
|
|
||||||
let editRecord;
|
|
||||||
let editGraph;
|
|
||||||
let schema = channel.schemas.find((aschema) => aschema.name === record.schema);
|
|
||||||
$: editMode = false;
|
|
||||||
$: expanded = false;
|
|
||||||
|
|
||||||
function editInline(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
axios
|
|
||||||
.get(channel.lucentUrl + "/records/editInline/" + record.id)
|
|
||||||
.then((response) => {
|
|
||||||
record = response.data;
|
|
||||||
editRecord = response.data.record;
|
|
||||||
editGraph = response.data.graph;
|
|
||||||
editMode = true;
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveup(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
dispatch("moveup");
|
|
||||||
}
|
|
||||||
|
|
||||||
function movedn(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
dispatch("movedn");
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleInlinesaved(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
dispatch("inlinesaved", e.detail);
|
|
||||||
editMode = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function remove(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
dispatch("remove", record.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function trash(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
dispatch("trash", record.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function undo(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
dispatch("undoremove", record.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function cancel(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
editMode = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
editMode = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
function deleteFromChannel(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
axios
|
|
||||||
.post(channel.lucentUrl +"/records/status/trashed", [record])
|
|
||||||
.then((response) => {
|
|
||||||
dispatch("remove", record.id);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{#if toDelete}
|
|
||||||
<div class="lx-card bg-danger bg-opacity-10 text-center">
|
|
||||||
<p>Item was removed from the current record.</p>
|
|
||||||
<p>
|
|
||||||
<button
|
|
||||||
class="btn btn-sm btn-outline border border-1 border-dark"
|
|
||||||
on:click={undo}>Undo
|
|
||||||
</button
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="btn btn-sm btn-danger "
|
|
||||||
on:click={deleteFromChannel}
|
|
||||||
>Delete completely from channel
|
|
||||||
</button
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
<button class="btn btn-sm btn-link" on:click={remove}
|
|
||||||
>Dismiss Message
|
|
||||||
</button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
{:else if editMode === true}
|
|
||||||
<InlineEdit
|
|
||||||
{schema}
|
|
||||||
record={editRecord}
|
|
||||||
graph={editGraph}
|
|
||||||
isCreateMode={false}
|
|
||||||
on:cancel={cancel}
|
|
||||||
on:inlinesaved={handleInlinesaved}
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<div class="lx-card mt-4 bg-primary bg-opacity-10">
|
|
||||||
<div class="actions">
|
|
||||||
<small class="text-muted">{schema.label}</small>
|
|
||||||
<button
|
|
||||||
class="btn btn-sm btn-link"
|
|
||||||
on:click|preventDefault={editInline}
|
|
||||||
>
|
|
||||||
<Icon icon="pencil" width={12} height={12}/>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="btn btn-sm btn-link"
|
|
||||||
on:click={(e) => (expanded = !expanded)}
|
|
||||||
>
|
|
||||||
{#if expanded}
|
|
||||||
<Icon icon="compress" width={12} height={12}/>
|
|
||||||
{:else}
|
|
||||||
<Icon icon="expand" width={12} height={12}/>
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
<div class="dropdown d-inline-block">
|
|
||||||
<button
|
|
||||||
class="btn btn-link btn-sm"
|
|
||||||
type="button"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
aria-expanded="false"
|
|
||||||
>
|
|
||||||
<Icon icon="ellipsis"/>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="dropdown-menu">
|
|
||||||
<a
|
|
||||||
class="dropdown-item"
|
|
||||||
href="/records/{record.id}"
|
|
||||||
target="_blank"
|
|
||||||
>Edit in new tab
|
|
||||||
</a>
|
|
||||||
<button class="dropdown-item" on:click={trash}>
|
|
||||||
Remove
|
|
||||||
</button>
|
|
||||||
<div class="text-center mt-3">
|
|
||||||
<!-- <a class="dropdown-item" href="#">Clone</a> -->
|
|
||||||
|
|
||||||
{#if !isFirst}
|
|
||||||
<button
|
|
||||||
class="btn btn-sm btn-outline-primary border-0"
|
|
||||||
on:click|preventDefault={moveup}
|
|
||||||
>
|
|
||||||
<Icon icon="circle-chevron-up"/>
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
{#if !isLast}
|
|
||||||
<button
|
|
||||||
class="btn btn-sm btn-outline-primary border-0"
|
|
||||||
on:click|preventDefault={movedn}
|
|
||||||
>
|
|
||||||
<Icon icon="circle-chevron-down"/>
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="inline-preview" class:expanded>
|
|
||||||
{#if schema.type === "files"}
|
|
||||||
<Preview {record} size="small"/>
|
|
||||||
{/if}
|
|
||||||
{#each schema.fields.filter((f) => !(f.trashed || ["tab"].includes(f.ui) || ["id"].includes(f.name))) as field}
|
|
||||||
<span class="text-muted d-block mt-2" style="font-size:13px"
|
|
||||||
>{field.label}</span
|
|
||||||
>
|
|
||||||
{#if field.ui === "reference"}
|
|
||||||
<Reference {record} {field}/>
|
|
||||||
{:else if field.ui === "file"}
|
|
||||||
<File {record} {field}/>
|
|
||||||
{:else}
|
|
||||||
{@html record.data[field.name]}
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.lx-card {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lx-card .inline-preview {
|
|
||||||
max-height: 120px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lx-card .inline-preview.expanded {
|
|
||||||
max-height: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lx-card .actions {
|
|
||||||
top: 10px;
|
|
||||||
right: 44px;
|
|
||||||
position: absolute;
|
|
||||||
/* visibility: hidden; */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* .lx-card:hover .actions {
|
|
||||||
visibility: visible;
|
|
||||||
} */
|
|
||||||
</style>
|
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
<script>
|
|
||||||
import {getContext} from "svelte";
|
|
||||||
import {getStatus, getStatusList} from "./StatusText";
|
|
||||||
|
|
||||||
const channel = getContext("channel");
|
|
||||||
export let status = "draft";
|
|
||||||
export let record;
|
|
||||||
export let schema;
|
|
||||||
let dropdown;
|
|
||||||
$: currentStatus = getStatus(status);
|
|
||||||
const statusList = Object.values(getStatusList());
|
|
||||||
|
|
||||||
function updateStatus(e, statusValue) {
|
|
||||||
// e.preventDefault();
|
|
||||||
status = statusValue;
|
|
||||||
dropdown.click();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- Example split danger button -->
|
|
||||||
<div class="d-flex justify-content-between">
|
|
||||||
<div class="btn-group dropup">
|
|
||||||
<button type="button" class="btn btn-{currentStatus.bg}"
|
|
||||||
>{currentStatus.text}</button
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
bind:this={dropdown}
|
|
||||||
type="button"
|
|
||||||
class="btn btn-{currentStatus.bg} dropdown-toggle dropdown-toggle-split"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
aria-expanded="false"
|
|
||||||
>
|
|
||||||
<span class="visually-hidden">Toggle Dropdown</span>
|
|
||||||
</button>
|
|
||||||
<div class="dropdown-menu">
|
|
||||||
<div class="dropdown-header">Change status to</div>
|
|
||||||
{#each statusList as astatus}
|
|
||||||
{#if astatus.value !== status}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="dropdown-item my-2 rounded w-100 bg-{astatus.bg} text-{astatus.color}"
|
|
||||||
on:click={(e) => updateStatus(e, astatus.value)}
|
|
||||||
>
|
|
||||||
{astatus.text}
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{#if channel.previewTarget}
|
|
||||||
<a href="{channel.previewTargetUrl}?schema={schema.name}&id={record.id}" target="_blank" class="btn btn-info ms-3">
|
|
||||||
Preview
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
<script>
|
|
||||||
|
|
||||||
import BlockButtons from "./BlockButtons.svelte";
|
|
||||||
import BlockElements from "./BlockElements.svelte";
|
|
||||||
import {flip} from "svelte/animate";
|
|
||||||
import {quintOut} from 'svelte/easing';
|
|
||||||
import {getContext} from "svelte";
|
|
||||||
const channel = getContext("channel");
|
|
||||||
export let record;
|
|
||||||
export let field;
|
|
||||||
export let value = [];
|
|
||||||
export let graph;
|
|
||||||
let blockSchema = channel.schemas.find((s) => s.name === field.schema);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<div class=" ">
|
|
||||||
<div class="inline-card-wrapper">
|
|
||||||
<BlockButtons
|
|
||||||
bind:blockData={value}
|
|
||||||
{blockSchema}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{#each value as blockItemData (blockItemData.id)}
|
|
||||||
<div class="block-field-wrapper" animate:flip="{{delay: 250, duration: 250, easing: quintOut}}">
|
|
||||||
<BlockElements
|
|
||||||
bind:block={blockItemData}
|
|
||||||
bind:blockData={value}
|
|
||||||
{record}
|
|
||||||
{field}
|
|
||||||
bind:graph
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
<script>
|
|
||||||
import Icon from "../../common/Icon.svelte";
|
|
||||||
import {insertBlock} from "./block";
|
|
||||||
|
|
||||||
export let blockId = "";
|
|
||||||
export let blockData;
|
|
||||||
export let blockSchema;
|
|
||||||
$: showOptions = false;
|
|
||||||
|
|
||||||
function createBlock(e, ui) {
|
|
||||||
e.preventDefault();
|
|
||||||
blockData = insertBlock(blockData,ui);
|
|
||||||
showOptions = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
<div class="d-flex justify-content-left mb-2 ">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class:is-first={!blockId}
|
|
||||||
class=" btn btn-lg btn-link text-decoration-none block-buttons"
|
|
||||||
on:click|preventDefault={(e) => (showOptions = !showOptions)}
|
|
||||||
>
|
|
||||||
<Icon width={24} height={24} icon="circle-plus"/>
|
|
||||||
</button>
|
|
||||||
{#if showOptions}
|
|
||||||
<div class="d-flex ">
|
|
||||||
{#each blockSchema.fields as validUi}
|
|
||||||
<div class="ms-2">
|
|
||||||
<button
|
|
||||||
class="btn btn-sm btn-primary"
|
|
||||||
on:click={(e) => createBlock(e, validUi)}
|
|
||||||
>{validUi.label}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<style>
|
|
||||||
:global(.block-field-wrapper) {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.block-field-wrapper .block-buttons) {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
:global(.block-field-wrapper:hover .block-buttons) {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block-buttons {
|
|
||||||
padding: 0px;
|
|
||||||
z-index: 1;
|
|
||||||
margin: 0px ;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
<script>
|
|
||||||
import Heading from "./elements/Heading.svelte";
|
|
||||||
import Textarea from "./elements/Textarea.svelte";
|
|
||||||
import Rich from "./elements/Rich.svelte";
|
|
||||||
import Markdown from "./elements/Markdown.svelte";
|
|
||||||
import Reference from "./elements/Reference.svelte";
|
|
||||||
import Icon from "../../common/Icon.svelte";
|
|
||||||
import {insertBlock} from "./block";
|
|
||||||
import {getContext} from "svelte";
|
|
||||||
import {findIndex} from "lodash";
|
|
||||||
import File from "./elements/File.svelte";
|
|
||||||
|
|
||||||
const channel = getContext("channel");
|
|
||||||
export let record;
|
|
||||||
export let blockData;
|
|
||||||
export let field;
|
|
||||||
export let graph;
|
|
||||||
|
|
||||||
|
|
||||||
export let block;
|
|
||||||
let blockSchema = channel.schemas.find((s) => s.name === field.schema);
|
|
||||||
|
|
||||||
function createBlock(e, ui, blockId) {
|
|
||||||
e.preventDefault();
|
|
||||||
blockData = insertBlock(blockData, ui, blockId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteBlock(e, blockId) {
|
|
||||||
e.preventDefault();
|
|
||||||
blockData = blockData.filter(b => b.id !== blockId)
|
|
||||||
}
|
|
||||||
|
|
||||||
function upBlock(e, blockId) {
|
|
||||||
e.preventDefault();
|
|
||||||
let blockIndex = findIndex(blockData, (b) => b.id === blockId);
|
|
||||||
let tempBlock = blockData[blockIndex];
|
|
||||||
blockData[blockIndex] = blockData[blockIndex - 1];
|
|
||||||
blockData[blockIndex - 1] = tempBlock;
|
|
||||||
}
|
|
||||||
|
|
||||||
function downBlock(e, blockId) {
|
|
||||||
e.preventDefault();
|
|
||||||
let blockIndex = findIndex(blockData, (b) => b.id === blockId);
|
|
||||||
let tempBlock = blockData[blockIndex];
|
|
||||||
blockData[blockIndex] = blockData[blockIndex + 1];
|
|
||||||
blockData[blockIndex + 1] = tempBlock;
|
|
||||||
}
|
|
||||||
|
|
||||||
function blockIsFirst(blockId) {
|
|
||||||
return findIndex(blockData, (b) => b.id === blockId) === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function blockIsLast(blockId) {
|
|
||||||
return findIndex(blockData, (b) => b.id === blockId) === blockData.length - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="card block-editor-field d-flex">
|
|
||||||
<div class="d-flex justify-content-between">
|
|
||||||
<span class="text-muted d-block fs-6 mb-1">{block.meta.label}</span>
|
|
||||||
<div class="dropdown d-inline-block">
|
|
||||||
<button
|
|
||||||
class="btn btn-link btn-sm"
|
|
||||||
type="button"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
aria-expanded="false"
|
|
||||||
>
|
|
||||||
<Icon icon="ellipsis"/>
|
|
||||||
</button>
|
|
||||||
<div class="dropdown-menu">
|
|
||||||
|
|
||||||
<h6 class="dropdown-header">
|
|
||||||
Block id: <input class="form-control-plaintext" readonly value={block.id}/>
|
|
||||||
Block name: <input class="form-control-plaintext" readonly value={block.meta.name}/>
|
|
||||||
</h6>
|
|
||||||
<div>
|
|
||||||
<hr class="dropdown-divider">
|
|
||||||
</div>
|
|
||||||
<h6 class="dropdown-header">Actions</h6>
|
|
||||||
<button
|
|
||||||
|
|
||||||
class="dropdown-item"
|
|
||||||
class:d-none={blockIsFirst(block.id)}
|
|
||||||
on:click={(e) => upBlock(e, block.id)}
|
|
||||||
>Move up
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="dropdown-item"
|
|
||||||
class:d-none={blockIsLast(block.id)}
|
|
||||||
on:click={(e) => downBlock(e, block.id)}
|
|
||||||
>Move down
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="dropdown-item text-danger"
|
|
||||||
on:click={(e) => deleteBlock(e, block.id)}
|
|
||||||
>Delete
|
|
||||||
</button
|
|
||||||
>
|
|
||||||
<h6 class="dropdown-header">Insert after</h6>
|
|
||||||
|
|
||||||
{#each blockSchema.fields as blockField}
|
|
||||||
<button
|
|
||||||
class="dropdown-item"
|
|
||||||
on:click={(e) => createBlock(e, blockField, block.id)}
|
|
||||||
>{blockField.label}
|
|
||||||
</button
|
|
||||||
>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#if block.meta.info.name === "heading"}
|
|
||||||
|
|
||||||
<Heading
|
|
||||||
bind:block={block}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{:else if block.meta.info.name === "textarea"}
|
|
||||||
|
|
||||||
<Textarea
|
|
||||||
bind:block={block}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{:else if block.meta.info.name === "rich"}
|
|
||||||
<Rich
|
|
||||||
bind:block={block}
|
|
||||||
/>
|
|
||||||
{:else if block.meta.info.name === "markdown"}
|
|
||||||
<Markdown
|
|
||||||
bind:block={block}
|
|
||||||
/>
|
|
||||||
{:else if block.meta.info.name === "file"}
|
|
||||||
<File
|
|
||||||
{record}
|
|
||||||
{field}
|
|
||||||
bind:graph
|
|
||||||
bind:block={block}
|
|
||||||
/>
|
|
||||||
{:else if block.meta.info.name === "reference"}
|
|
||||||
<Reference
|
|
||||||
{record}
|
|
||||||
{field}
|
|
||||||
bind:graph
|
|
||||||
bind:block={block}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.block-editor-field{
|
|
||||||
|
|
||||||
margin: 10px 0;
|
|
||||||
border-color: transparent;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import {randomId} from "../../../helpers.js";
|
|
||||||
|
|
||||||
export function insertBlock(blockData, blockField, afterBlockId = null) {
|
|
||||||
|
|
||||||
if (!afterBlockId) {
|
|
||||||
return [{
|
|
||||||
meta: blockField,
|
|
||||||
id: randomId(),
|
|
||||||
value: null
|
|
||||||
}, ...blockData];
|
|
||||||
}
|
|
||||||
|
|
||||||
return blockData.reduce((carry, block) => {
|
|
||||||
carry.push(block)
|
|
||||||
if (block.id === afterBlockId) {
|
|
||||||
carry.push({
|
|
||||||
meta: blockField,
|
|
||||||
id: randomId(),
|
|
||||||
value: null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return carry;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
<script>
|
|
||||||
import {getContext} from "svelte";
|
|
||||||
import PreviewCard from "../../PreviewCard.svelte";
|
|
||||||
import {sortByField} from "../../../edges/sortEdges";
|
|
||||||
import ReferenceInlineButtons from "../../elements/ReferenceInlineButtons.svelte"
|
|
||||||
import Sortable from "../../../libs/Sortable.svelte";
|
|
||||||
import {insertEdges} from "../../elements/reference";
|
|
||||||
import BrowseModal from "../../elements/BrowseModal.svelte";
|
|
||||||
|
|
||||||
|
|
||||||
const channel = getContext("channel");
|
|
||||||
export let block;
|
|
||||||
export let record;
|
|
||||||
export let field;
|
|
||||||
export let graph;
|
|
||||||
let browseModal;
|
|
||||||
let blockFieldName = field.name + ":" + block.id;
|
|
||||||
|
|
||||||
$: references = graph.edges
|
|
||||||
.filter((edge) => edge.field === blockFieldName)
|
|
||||||
.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) =>
|
|
||||||
block.meta.collections.includes(aschema.name)
|
|
||||||
);
|
|
||||||
|
|
||||||
function removeReference(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
graph.edges = graph.edges.filter(
|
|
||||||
(edge) => !(edge.target === e.detail && edge.field === blockFieldName)
|
|
||||||
);
|
|
||||||
block.value = graph.edges.filter(edge => edge.field === blockFieldName) ?? [];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function openBrowseModal(e, schema) {
|
|
||||||
e.preventDefault();
|
|
||||||
browseModal.open(schema);
|
|
||||||
}
|
|
||||||
|
|
||||||
function reorder(e) {
|
|
||||||
graph.edges = sortByField(e.detail.source, e.detail.target, graph.edges, blockFieldName, references);
|
|
||||||
}
|
|
||||||
|
|
||||||
function insert(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
browseModal.close();
|
|
||||||
graph = insertEdges(graph,record,e.detail.records,blockFieldName,e.detail.action);
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="mb-0">
|
|
||||||
{#if block.meta.collections.length === 1}
|
|
||||||
<button
|
|
||||||
class="btn btn-outline-primary"
|
|
||||||
on:click={(e) => openBrowseModal(e, collections[0].name)}
|
|
||||||
>
|
|
||||||
Browse
|
|
||||||
</button>
|
|
||||||
{:else}
|
|
||||||
<div class="dropdown d-inline-block">
|
|
||||||
<button
|
|
||||||
class="btn btn-outline-primary btn-sm"
|
|
||||||
type="button"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
aria-expanded="false"
|
|
||||||
>
|
|
||||||
Browse
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
{#each collections as collection}
|
|
||||||
<li>
|
|
||||||
<!-- {`${channelurl}/content/${collection.name}?parent=${record.id}&parentfield=${field.name}`} -->
|
|
||||||
<a
|
|
||||||
class="dropdown-item"
|
|
||||||
on:click={(e) =>
|
|
||||||
openBrowseModal(e, collection.name)}
|
|
||||||
href="/">{collection.label}</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{#if references.length > 0}
|
|
||||||
<Sortable sortableClass="row row-cols-3 mt-3" on:update={reorder}>
|
|
||||||
{#each references as reference (reference.id)}
|
|
||||||
<div class="col mb-3">
|
|
||||||
<PreviewCard
|
|
||||||
classes="h-100"
|
|
||||||
record={reference}
|
|
||||||
hasDelete={true}
|
|
||||||
on:remove={removeReference}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</Sortable>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<BrowseModal bind:this={browseModal} on:insert={insert}/>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<script>
|
|
||||||
export let block;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="mb-0">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id={block.id}
|
|
||||||
class="form-control"
|
|
||||||
bind:value={block.value}
|
|
||||||
autocomplete="off"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<script>
|
|
||||||
import Codemirror from "../../../libs/CodemirrorMarkdown.svelte";
|
|
||||||
|
|
||||||
|
|
||||||
export let block;
|
|
||||||
// export let id;
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
|
|
||||||
<Codemirror bind:value={block.value} />
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
<script>
|
|
||||||
import {getContext} from "svelte";
|
|
||||||
import PreviewCard from "../../PreviewCard.svelte";
|
|
||||||
import {sortByField} from "../../../edges/sortEdges";
|
|
||||||
import ReferenceInlineButtons from "../../elements/ReferenceInlineButtons.svelte"
|
|
||||||
import Sortable from "../../../libs/Sortable.svelte";
|
|
||||||
import {insertEdges} from "../../elements/reference";
|
|
||||||
|
|
||||||
|
|
||||||
const channel = getContext("channel");
|
|
||||||
export let block;
|
|
||||||
export let record;
|
|
||||||
export let field;
|
|
||||||
export let graph;
|
|
||||||
|
|
||||||
let blockFieldName = field.name + ":" + block.id;
|
|
||||||
|
|
||||||
$: references = graph.edges
|
|
||||||
.filter((edge) => edge.field === blockFieldName)
|
|
||||||
.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) =>
|
|
||||||
block.meta.collections.includes(aschema.name)
|
|
||||||
);
|
|
||||||
|
|
||||||
function removeReference(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
graph.edges = graph.edges.filter(
|
|
||||||
(edge) => !(edge.target === e.detail && edge.field === blockFieldName)
|
|
||||||
);
|
|
||||||
block.value = graph.edges.filter(edge => edge.field === blockFieldName) ?? [];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function reorder(e) {
|
|
||||||
graph.edges = sortByField(e.detail.source, e.detail.target, graph.edges, blockFieldName, references);
|
|
||||||
}
|
|
||||||
|
|
||||||
function insert(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
graph = insertEdges(graph,record,e.detail.records,blockFieldName,e.detail.action);
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="inline-card-wrapper">
|
|
||||||
<ReferenceInlineButtons
|
|
||||||
buttonClass="mt-2"
|
|
||||||
recordId={null}
|
|
||||||
schemas={collections}
|
|
||||||
on:insert={insert}
|
|
||||||
on:save={insert}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{#if references.length > 0}
|
|
||||||
<Sortable sortableClass="row row-cols-3 mt-3" on:update={reorder}>
|
|
||||||
{#each references as reference (reference.id)}
|
|
||||||
<div class="col mb-3">
|
|
||||||
<PreviewCard
|
|
||||||
classes="h-100"
|
|
||||||
record={reference}
|
|
||||||
hasDelete={true}
|
|
||||||
on:remove={removeReference}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</Sortable>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<script>
|
|
||||||
import Tinymce from "../../../libs/Tinymce.svelte";
|
|
||||||
|
|
||||||
export let block;
|
|
||||||
let additionalConfig = {};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="mb-0">
|
|
||||||
<Tinymce bind:value={block.value} {additionalConfig}/>
|
|
||||||
</div>
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
<script>
|
|
||||||
import {onMount} from "svelte";
|
|
||||||
|
|
||||||
export let block;
|
|
||||||
let thisEl;
|
|
||||||
|
|
||||||
function resize(e) {
|
|
||||||
let el;
|
|
||||||
if (e.target) {
|
|
||||||
el = e.target;
|
|
||||||
} else {
|
|
||||||
el = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
el.style.overflow = "hidden";
|
|
||||||
el.style.height = "1px";
|
|
||||||
el.style.height = +el.scrollHeight + "px";
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
resize(thisEl);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="mb-0">
|
|
||||||
|
|
||||||
<textarea
|
|
||||||
bind:value={block.value}
|
|
||||||
bind:this={thisEl}
|
|
||||||
on:input={resize}
|
|
||||||
id={block.id}
|
|
||||||
class="form-control"
|
|
||||||
autocomplete="off"></textarea>
|
|
||||||
</div>
|
|
||||||
<style>
|
|
||||||
textarea {
|
|
||||||
resize: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export function hasDataChanged(isCreateMode, originalContent, newContent){
|
||||||
|
if (isCreateMode) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return JSON.stringify(originalContent) !== JSON.stringify(newContent);
|
||||||
|
}
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
<script>
|
|
||||||
import {createEventDispatcher, getContext} from "svelte";
|
|
||||||
import Index from "../../content/Index.svelte";
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
const channel = getContext("channel");
|
|
||||||
$: data = {};
|
|
||||||
let isOpen = false;
|
|
||||||
let selectedRecords = [];
|
|
||||||
// onMount(() => {
|
|
||||||
// load();
|
|
||||||
// });
|
|
||||||
|
|
||||||
export function open(schema) {
|
|
||||||
isOpen = true;
|
|
||||||
load(schema);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function close() {
|
|
||||||
isOpen = false;
|
|
||||||
selectedRecords = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function load(schema) {
|
|
||||||
axios
|
|
||||||
.get(channel.lucentUrl + "/content/" + schema)
|
|
||||||
.then((response) => {
|
|
||||||
data = response.data;
|
|
||||||
})
|
|
||||||
.catch((error) => console.log(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
function insert(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
dispatch("insert", {
|
|
||||||
records: selectedRecords,
|
|
||||||
action: "insert",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function replace(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
dispatch("insert", {
|
|
||||||
records: selectedRecords,
|
|
||||||
action: "replace",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if data.schema}
|
|
||||||
<div
|
|
||||||
class="modal fade show"
|
|
||||||
tabindex="-1"
|
|
||||||
class:d-block={isOpen}
|
|
||||||
aria-modal="true"
|
|
||||||
role="dialog"
|
|
||||||
style="background: rgba(100,100,100,.6);"
|
|
||||||
>
|
|
||||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-primary me-1"
|
|
||||||
on:click={insert}
|
|
||||||
disabled={selectedRecords.length === 0}
|
|
||||||
>
|
|
||||||
Insert
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-outline-primary me-3"
|
|
||||||
on:click={replace}
|
|
||||||
disabled={selectedRecords.length === 0}
|
|
||||||
>
|
|
||||||
Replace
|
|
||||||
</button>
|
|
||||||
{#if selectedRecords.length > 0}
|
|
||||||
<span class="">
|
|
||||||
{selectedRecords.length} records selected
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
on:click|preventDefault={(e) => (isOpen = false)}
|
|
||||||
type="button"
|
|
||||||
class="btn-close"
|
|
||||||
data-bs-dismiss="modal"
|
|
||||||
aria-label="Close"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<Index {...data} bind:selected={selectedRecords}/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.modal-dialog {
|
|
||||||
width: auto;
|
|
||||||
max-width: 100%;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-content {
|
|
||||||
margin: 40px auto;
|
|
||||||
width: auto;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div class="field-checkbox">
|
||||||
<div class="form-check form-check-inline">
|
<div class="form-check form-check-inline">
|
||||||
<input
|
<input
|
||||||
class="form-check-input"
|
class="form-check-input"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
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;
|
||||||
@@ -9,23 +10,21 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mb-0">
|
<div class="mb-0">
|
||||||
<div class="input-group ">
|
<div style="display: flex; align-items: center;gap: 10px">
|
||||||
<div style="width:64px;">
|
<input
|
||||||
<input
|
|
||||||
type="color"
|
type="color"
|
||||||
{id}
|
{id}
|
||||||
class="form-control form-control-color"
|
style="border: none;background: transparent;padding: 0;width:64px;"
|
||||||
disabled={field.readonly && !isCreateMode}
|
disabled={field.readonly && !isCreateMode}
|
||||||
bind:value
|
bind:value
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class:is-invalid={errorMessage}
|
class:is-invalid={errorMessage}
|
||||||
{id}
|
{id}
|
||||||
class="form-control"
|
class="form-control"
|
||||||
bind:value
|
bind:value
|
||||||
readonly={field.readonly && !isCreateMode}
|
readonly={field.readonly && !isCreateMode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
<script>
|
|
||||||
import {getContext} from "svelte";
|
|
||||||
import {debounce} from "lodash";
|
|
||||||
import {previewTitle} from "../Preview";
|
|
||||||
|
|
||||||
const channel = getContext("channel");
|
|
||||||
export let field;
|
|
||||||
|
|
||||||
export let value;
|
|
||||||
export let search;
|
|
||||||
$: options = [];
|
|
||||||
export const update = debounce((e) => {
|
|
||||||
axios
|
|
||||||
.get("/records/suggestions", {
|
|
||||||
params: {
|
|
||||||
schema: field.optionsFrom,
|
|
||||||
field: field.optionsField,
|
|
||||||
value: search,
|
|
||||||
ui: field.ui,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
options = response.data;
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log(error);
|
|
||||||
});
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
function select(e, option) {
|
|
||||||
e.preventDefault();
|
|
||||||
value = option.data[field.optionsField];
|
|
||||||
search = "";
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if field.optionsFrom}
|
|
||||||
{#each options as option (option.id)}
|
|
||||||
<div
|
|
||||||
on:click={(e) => select(e, option)}
|
|
||||||
on:keypress={(e) => select(e, option)}
|
|
||||||
>
|
|
||||||
<span class="dropdown-item">
|
|
||||||
{previewTitle(channel.schemas, option)}
|
|
||||||
<small class="text-muted "
|
|
||||||
>{option.data[field.optionsField]}</small
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
{#if search && field.optionsSuggest}
|
|
||||||
<div
|
|
||||||
on:click={(e) => {
|
|
||||||
value = search;
|
|
||||||
search = "";
|
|
||||||
}}
|
|
||||||
on:keypress={(e) => {
|
|
||||||
value = search;
|
|
||||||
search = "";
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span class="dropdown-item">
|
|
||||||
Add "{search}"
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
No results
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
@@ -1,21 +1,14 @@
|
|||||||
<script>
|
<script>
|
||||||
import Datalist from "./Datalist.svelte";
|
|
||||||
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";
|
||||||
import Icon from "../../common/Icon.svelte";
|
|
||||||
|
|
||||||
export let field;
|
export let field;
|
||||||
export let value;
|
export let value;
|
||||||
export let id;
|
export let id;
|
||||||
export let isCreateMode;
|
export let isCreateMode;
|
||||||
export let validationErrors;
|
export let validationErrors;
|
||||||
$: search = "";
|
|
||||||
$: listMode = field.optionsFrom && !(field.readonly && !isCreateMode);
|
|
||||||
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
||||||
let list;
|
|
||||||
let pickerInput;
|
let pickerInput;
|
||||||
let pickerInstance;
|
let pickerInstance;
|
||||||
let flatpickrOptions = {
|
let flatpickrOptions = {
|
||||||
@@ -34,9 +27,7 @@
|
|||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (!field.readonly || isCreateMode) {
|
if (!field.readonly || isCreateMode) {
|
||||||
if (listMode) {
|
|
||||||
flatpickrOptions.clickOpens = false;
|
|
||||||
}
|
|
||||||
pickerInstance = flatpickr(pickerInput, flatpickrOptions);
|
pickerInstance = flatpickr(pickerInput, flatpickrOptions);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -44,55 +35,8 @@
|
|||||||
|
|
||||||
<div class="mb-0">
|
<div class="mb-0">
|
||||||
|
|
||||||
{#if listMode}
|
|
||||||
<div class="dropdown d-flex">
|
<input
|
||||||
<input
|
|
||||||
type="search"
|
|
||||||
{id}
|
|
||||||
on:keyup={list.update}
|
|
||||||
on:focus={list.update}
|
|
||||||
class="form-control dropdown-toggle"
|
|
||||||
class:is-invalid={errorMessage}
|
|
||||||
bind:value={search}
|
|
||||||
bind:this={pickerInput}
|
|
||||||
placeholder="Search for options"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
autocomplete="off"
|
|
||||||
readonly={field.readonly && !isCreateMode}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
class="btn btn-light ms-1"
|
|
||||||
on:click|preventDefault={(e) => pickerInstance.open()}
|
|
||||||
>
|
|
||||||
<Icon icon="calendar"/>
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu w-100">
|
|
||||||
{#if field.optionsFrom}
|
|
||||||
<Datalist
|
|
||||||
{field}
|
|
||||||
bind:this={list}
|
|
||||||
bind:value
|
|
||||||
bind:search
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{#if value}
|
|
||||||
<span class="badge rounded-pill bg-light text-dark fs-6 mt-3">
|
|
||||||
<div class="d-flex align-items-center ">
|
|
||||||
{value}
|
|
||||||
<button
|
|
||||||
on:click|preventDefault={(e) => (value = "")}
|
|
||||||
type="button"
|
|
||||||
class="btn-close btn-sm ms-1"
|
|
||||||
style="font-size:10px"
|
|
||||||
aria-label="Close"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
{:else}
|
|
||||||
<input
|
|
||||||
type="text"
|
type="text"
|
||||||
{id}
|
{id}
|
||||||
class="form-control"
|
class="form-control"
|
||||||
@@ -101,8 +45,7 @@
|
|||||||
bind:this={pickerInput}
|
bind:this={pickerInput}
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
readonly={field.readonly && !isCreateMode}
|
readonly={field.readonly && !isCreateMode}
|
||||||
/>
|
/>
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if errorMessage}
|
{#if errorMessage}
|
||||||
<div class="invalid-feedback d-block">
|
<div class="invalid-feedback d-block">
|
||||||
|
|||||||
@@ -1,27 +1,23 @@
|
|||||||
<script>
|
<script>
|
||||||
import Datalist from "./Datalist.svelte";
|
|
||||||
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";
|
||||||
import Icon from "../../common/Icon.svelte";
|
|
||||||
|
|
||||||
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;
|
||||||
$: search = "";
|
|
||||||
$: listMode = field.optionsFrom && !(field.readonly && !isCreateMode);
|
|
||||||
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
$: errorMessage = getErrorMessage(validationErrors, field.name);
|
||||||
|
|
||||||
export let id;
|
export let id;
|
||||||
let list;
|
let wrapperDiv;
|
||||||
let pickerInput;
|
let pickerInput;
|
||||||
let pickerInstance;
|
let pickerInstance;
|
||||||
let flatpickrOptions = {
|
let flatpickrOptions = {
|
||||||
enableTime: false,
|
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",
|
||||||
@@ -41,64 +37,14 @@
|
|||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (!field.readonly || isCreateMode) {
|
if (!field.readonly || isCreateMode) {
|
||||||
if (listMode) {
|
|
||||||
flatpickrOptions.clickOpens = false;
|
|
||||||
}
|
|
||||||
pickerInstance = flatpickr(pickerInput, flatpickrOptions);
|
pickerInstance = flatpickr(pickerInput, flatpickrOptions);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mb-0">
|
<div class="mb-0" bind:this={wrapperDiv}>
|
||||||
{#if listMode}
|
|
||||||
<div class="dropdown d-flex">
|
<input
|
||||||
<input
|
|
||||||
type="search"
|
|
||||||
{id}
|
|
||||||
on:keyup={list.update}
|
|
||||||
on:focus={list.update}
|
|
||||||
class="form-control dropdown-toggle"
|
|
||||||
class:is-invalid={errorMessage}
|
|
||||||
bind:value={search}
|
|
||||||
bind:this={pickerInput}
|
|
||||||
placeholder="Search for options"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
autocomplete="off"
|
|
||||||
readonly={field.readonly && !isCreateMode}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
class="btn btn-light ms-1"
|
|
||||||
on:click|preventDefault={(e) => pickerInstance.open()}
|
|
||||||
>
|
|
||||||
<Icon icon="calendar"/>
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu w-100">
|
|
||||||
{#if field.optionsFrom}
|
|
||||||
<Datalist
|
|
||||||
{field}
|
|
||||||
bind:this={list}
|
|
||||||
bind:value
|
|
||||||
bind:search
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{#if value}
|
|
||||||
<span class="badge rounded-pill bg-light text-dark fs-6 mt-3">
|
|
||||||
<div class="d-flex align-items-center ">
|
|
||||||
{value}
|
|
||||||
<button
|
|
||||||
on:click|preventDefault={(e) => (value = "")}
|
|
||||||
type="button"
|
|
||||||
class="btn-close btn-sm ms-1"
|
|
||||||
style="font-size:10px"
|
|
||||||
aria-label="Close"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
{:else}
|
|
||||||
<input
|
|
||||||
type="text"
|
type="text"
|
||||||
{id}
|
{id}
|
||||||
class="form-control"
|
class="form-control"
|
||||||
@@ -107,10 +53,9 @@
|
|||||||
bind:this={pickerInput}
|
bind:this={pickerInput}
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
readonly={field.readonly && !isCreateMode}
|
readonly={field.readonly && !isCreateMode}
|
||||||
/>
|
/>
|
||||||
{/if}
|
<span class="system-help-text"
|
||||||
<small class=" text-primary opacity-50"
|
>Dates are displayed according to your timezone: {timezone}</span
|
||||||
>Dates are displayed according to your timezone: {timezone}</small
|
|
||||||
>
|
>
|
||||||
|
|
||||||
{#if errorMessage}
|
{#if errorMessage}
|
||||||
|
|||||||
@@ -3,20 +3,20 @@
|
|||||||
export let id;
|
export let id;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mb-1">
|
<div class="field-header">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="labels">
|
||||||
<div>
|
<div class="label-and-help">
|
||||||
<label for={id} class="form-label"
|
<label for={id}
|
||||||
>{field.label}</label
|
>{field.label}</label
|
||||||
>
|
>
|
||||||
{#if field.help}
|
{#if field.help}
|
||||||
<small class=" text-primary opacity-50">{field.help}</small>
|
<small class="help-text light-text">{field.help}</small>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
class="text-decoration-none"
|
class="text-decoration-none"
|
||||||
><code class="text-primary opacity-50">{field.name}</code>
|
><code class="field-id">{field.name}</code>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
<script>
|
<script>
|
||||||
import {getContext} from "svelte";
|
|
||||||
import {uniqBy} from "lodash";
|
|
||||||
import {sortByField} from "../../edges/sortEdges";
|
import {sortByField} from "../../edges/sortEdges";
|
||||||
import PreviewCard from "../PreviewCard.svelte";
|
|
||||||
import Sortable from "../../libs/Sortable.svelte";
|
import Sortable from "../../libs/Sortable.svelte";
|
||||||
import BrowseModal from "./BrowseModal.svelte";
|
import PreviewFile from "../previews/PreviewFile.svelte";
|
||||||
|
import Dropdown from "../../common/Dropdown.svelte";
|
||||||
|
import Dialog from "../../dialog/Dialog.svelte";
|
||||||
|
import {
|
||||||
|
fullDeleteRecord,
|
||||||
|
graphToReferences,
|
||||||
|
insertEdges,
|
||||||
|
removeReferenceFromGraph,
|
||||||
|
restoreReferenceToGraph
|
||||||
|
} from "./reference.js";
|
||||||
|
import {getContext} from "svelte";
|
||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
export let field;
|
export let field;
|
||||||
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) {
|
||||||
@@ -43,75 +53,49 @@
|
|||||||
function insert(e) {
|
function insert(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
browseModal.close();
|
browseModal.close();
|
||||||
const recordsToInsert = e.detail.records;
|
graph = insertEdges(graph, record, e.detail.records, field.name, e.detail.action);
|
||||||
const action = e.detail.action;
|
|
||||||
let newEdges = recordsToInsert.map((r) => {
|
|
||||||
return {
|
|
||||||
target: r.id,
|
|
||||||
source: record.id,
|
|
||||||
sourceSchema: record.schema,
|
|
||||||
targetSchema: r.schema,
|
|
||||||
field: field.name,
|
|
||||||
rank: ""
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
let replacedEdges = graph.edges ?? [];
|
|
||||||
if (action === "replace") {
|
|
||||||
replacedEdges = replacedEdges.filter((e) => e.field !== field.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
graph.records = uniqBy([...graph.records, ...recordsToInsert], (r) => r.id);
|
|
||||||
graph.edges = uniqBy([...replacedEdges, ...newEdges], (e) => e.target + e.field);
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mb-0">
|
<div class="mb-0">
|
||||||
{#if field.collections.length === 1}
|
{#if field.collections.length === 1}
|
||||||
<button
|
<button
|
||||||
class="btn btn-outline-primary"
|
class="button"
|
||||||
on:click={(e) => openBrowseModal(e, collections[0].name)}
|
on:click={(e) => openBrowseModal(e, collections[0].name)}
|
||||||
>
|
>
|
||||||
Browse
|
Browse
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="dropdown d-inline-block">
|
<Dropdown>
|
||||||
<button
|
<div slot="button">
|
||||||
class="btn btn-outline-primary btn-sm"
|
|
||||||
type="button"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
aria-expanded="false"
|
|
||||||
>
|
|
||||||
Browse
|
Browse
|
||||||
</button>
|
</div>
|
||||||
<ul class="dropdown-menu">
|
{#each collections as collection}
|
||||||
{#each collections as collection}
|
<!-- {`${channelurl}/content/${collection.name}?parent=${record.id}&parentfield=${field.name}`} -->
|
||||||
<li>
|
<a
|
||||||
<!-- {`${channelurl}/content/${collection.name}?parent=${record.id}&parentfield=${field.name}`} -->
|
class="dropdown-item"
|
||||||
<a
|
on:click={(e) => openBrowseModal(e, collection.name)}
|
||||||
class="dropdown-item"
|
href="/">{collection.label}</a
|
||||||
on:click={(e) =>
|
>
|
||||||
openBrowseModal(e, collection.name)}
|
{/each}
|
||||||
href="/">{collection.label}</a
|
</Dropdown>
|
||||||
>
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if references.length > 0}
|
{#if references.length > 0}
|
||||||
<Sortable sortableClass="row row-cols-3 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)}
|
||||||
<div class="col mb-3">
|
<!--This div helps the sorting thing-->
|
||||||
<PreviewCard
|
<div>
|
||||||
classes="h-100"
|
<PreviewFile
|
||||||
record={reference}
|
record={reference.record}
|
||||||
|
edge={reference.edge}
|
||||||
hasDelete={true}
|
hasDelete={true}
|
||||||
on:remove={removeReference}
|
on:remove={removeReference}
|
||||||
/>
|
on:restore={restoreReference}
|
||||||
|
on:fulldelete={fullDelete}
|
||||||
|
></PreviewFile>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</Sortable>
|
</Sortable>
|
||||||
{/if}
|
{/if}
|
||||||
<BrowseModal bind:this={browseModal} on:insert={insert}/>
|
<Dialog bind:this={browseModal} on:insert={insert}></Dialog>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user