Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 74d2fcc4fa | |||
| 82174afdea | |||
| ffc39f078d | |||
| 7c4e19afbc | |||
| 7b10bfca1d | |||
| 0e5ac08641 | |||
| 1505aaa909 | |||
| d9e2c4954a | |||
| 97ad9de3d2 | |||
| 9e140be0ec |
@@ -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
|
||||||
|
|||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
# Upgrade from 1.1.* to 1.2.0
|
||||||
|
|
||||||
|
## lucent.php config file
|
||||||
|
|
||||||
|
There is now an array of commands, accepting more than one.
|
||||||
|
|
||||||
|
from
|
||||||
|
```php
|
||||||
|
"generateCommand" => env("LUCENT_GENERATE_COMMAND", "generate:static"),
|
||||||
|
```
|
||||||
|
|
||||||
|
to
|
||||||
|
```php
|
||||||
|
"commands" => [
|
||||||
|
"generate:static" => "Build Website",
|
||||||
|
],
|
||||||
|
```
|
||||||
|
## config/filesystems.php
|
||||||
|
|
||||||
|
Lucent has its own filesystem.
|
||||||
|
|
||||||
|
You should now add:
|
||||||
|
|
||||||
|
```
|
||||||
|
'lucent' => [
|
||||||
|
'driver' => 'local',
|
||||||
|
'root' => storage_path('app/public'),
|
||||||
|
'url' => env('APP_URL').'/storage',
|
||||||
|
'visibility' => 'public',
|
||||||
|
'throw' => false,
|
||||||
|
],
|
||||||
|
```
|
||||||
+3
-3
@@ -8,14 +8,14 @@
|
|||||||
"ext-zip": "*",
|
"ext-zip": "*",
|
||||||
"ext-sqlite3": "*",
|
"ext-sqlite3": "*",
|
||||||
"ext-imagick": "*",
|
"ext-imagick": "*",
|
||||||
|
"ext-pdo": "*",
|
||||||
"php": "^8.3",
|
"php": "^8.3",
|
||||||
"guzzlehttp/guzzle": "^7.2",
|
"guzzlehttp/guzzle": "^7.2",
|
||||||
"intervention/image": "^2.7",
|
"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.0"
|
||||||
"ext-pdo": "*",
|
|
||||||
"mustache/mustache": "^2.14"
|
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpstan/phpstan": "^1.8",
|
"phpstan/phpstan": "^1.8",
|
||||||
|
|||||||
+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
+340
File diff suppressed because one or more lines are too long
Vendored
-1
File diff suppressed because one or more lines are too long
Vendored
-198
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"main.js": {
|
"main.js": {
|
||||||
"file": "assets/main-BtcBvcC_.js",
|
"file": "assets/main-1FLcNLEV.js",
|
||||||
"name": "main",
|
"name": "main",
|
||||||
"src": "main.js",
|
"src": "main.js",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"css": [
|
"css": [
|
||||||
"assets/main-BWRwkaBb.css"
|
"assets/main-U24QISX_.css"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,23 @@
|
|||||||
<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 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;
|
||||||
|
|
||||||
}
|
}
|
||||||
eventSource.onerror = (e) => {
|
eventSource.onerror = (e) => {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
@@ -28,8 +29,7 @@
|
|||||||
function buildWebsite(e) {
|
function buildWebsite(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
inProgress = true;
|
inProgress = true;
|
||||||
|
axios.post(channel.lucentUrl + "/command/" + command.signature).then(response => {
|
||||||
axios.post(channel.lucentUrl + "/build").then(response => {
|
|
||||||
connect()
|
connect()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -46,20 +46,19 @@
|
|||||||
|
|
||||||
<h3 class="header-small mb-5">{title}</h3>
|
<h3 class="header-small mb-5">{title}</h3>
|
||||||
|
|
||||||
<button on:click={buildWebsite} class="button primary mb-3" disabled={inProgress}>Start Build
|
<button on:click={buildWebsite} class="button primary mb-3" disabled={inProgress}>Start
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
{#if inProgress}
|
{#if inProgress}
|
||||||
<span class="badge text-bg-warning">
|
<span class="badge text-bg-warning">
|
||||||
Build in progress
|
Action in progress
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if !inProgress && logs}
|
{#if !inProgress && logs}
|
||||||
<span class="badge text-bg-info">
|
<span class="badge text-bg-info">
|
||||||
Build completed
|
Action completed
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -113,8 +113,21 @@
|
|||||||
path: '<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12l4-4m-4 4 4 4"/>',
|
path: '<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12l4-4m-4 4 4 4"/>',
|
||||||
viewBox: "0 0 24 24",
|
viewBox: "0 0 24 24",
|
||||||
},
|
},
|
||||||
|
"list": {
|
||||||
|
path: '<path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="M9 8h10M9 12h10M9 16h10M4.99 8H5m-.02 4h.01m0 4H5"/>',
|
||||||
|
viewBox: "0 0 24 24",
|
||||||
|
},
|
||||||
|
"ordered-list": {
|
||||||
|
path: '<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6h8m-8 6h8m-8 6h8M4 16a2 2 0 1 1 3.321 1.5L4 20h5M4 5l2-1v6m-2 0h4"/>',
|
||||||
|
viewBox: "0 0 24 24",
|
||||||
|
},
|
||||||
|
"italic": {
|
||||||
|
path: '<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m8.874 19 6.143-14M6 19h6.33m-.66-14H18"/>',
|
||||||
|
viewBox: "0 0 24 24",
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export let width = 16;
|
export let width = 16;
|
||||||
export let height = 16;
|
export let height = 16;
|
||||||
export let icon = "";
|
export let icon = "";
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
export let inModal;
|
export let inModal;
|
||||||
export let modalUrl;
|
export let modalUrl;
|
||||||
export let graph;
|
export let graph;
|
||||||
|
|
||||||
let filter = {
|
let filter = {
|
||||||
label: "",
|
label: "",
|
||||||
operator: "",
|
operator: "",
|
||||||
@@ -58,6 +57,7 @@
|
|||||||
const filterRecord = extractFilterRecord(graph, value);
|
const filterRecord = extractFilterRecord(graph, value);
|
||||||
|
|
||||||
function extractFilterRecord(graph, value) {
|
function extractFilterRecord(graph, value) {
|
||||||
|
|
||||||
if (!filter.isReference) {
|
if (!filter.isReference) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
{#if filter.isReference && filterRecord}
|
{#if filter.isReference && filterRecord}
|
||||||
{filter.label} is {previewTitle(channel.schemas, filterRecord)}
|
{filter.label} is {previewTitle(channel.schemas, filterRecord)}
|
||||||
{:else}
|
{:else}
|
||||||
{filter.label} {operators.find((o) => o.name === filter.operator)?.symbol ?? ""} {value}
|
{filter.label} {operators.find((o) => o.name === filter.operator)?.symbol ?? ""} {operators.find((o) => o.name === filter.operator)?.hasValue ? value : ""}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
export let inModal;
|
export let inModal;
|
||||||
export let modalUrl;
|
export let modalUrl;
|
||||||
|
|
||||||
|
|
||||||
let dropdown;
|
let dropdown;
|
||||||
let search = "";
|
let search = "";
|
||||||
let systemFieldsFiltered = systemFields;
|
let systemFieldsFiltered = systemFields;
|
||||||
@@ -70,6 +69,13 @@
|
|||||||
activeOperator = operators.find(o => o.name === "eq")
|
activeOperator = operators.find(o => o.name === "eq")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectOperator(e, operator) {
|
||||||
|
activeOperator = operator;
|
||||||
|
if (!operator.hasValue) {
|
||||||
|
applyFilter(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function applyFilter(e) {
|
function applyFilter(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let filterPrefix = "";
|
let filterPrefix = "";
|
||||||
@@ -146,7 +152,7 @@
|
|||||||
<div class="selected-filter">field: {activeField.label}</div>
|
<div class="selected-filter">field: {activeField.label}</div>
|
||||||
|
|
||||||
{#each activeOperators as operator}
|
{#each activeOperators as operator}
|
||||||
<button class="dropdown-item button" on:click={e => activeOperator = operator }>
|
<button class="dropdown-item button" on:click={e => selectOperator(e,operator)}>
|
||||||
{operator.label}
|
{operator.label}
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -214,8 +220,8 @@
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button class="button applied-filter">
|
<button class="button applied-filter">
|
||||||
Submit
|
Submit
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
<script>
|
<script>
|
||||||
import Avatar from "../account/Avatar.svelte";
|
import Avatar from "../account/Avatar.svelte";
|
||||||
import {getContext} from "svelte";
|
import {getContext} from "svelte";
|
||||||
|
import Dropdown from "../common/Dropdown.svelte";
|
||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
const user = getContext("user");
|
const user = getContext("user");
|
||||||
|
console.log( channel.commands)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<div class="top-nav ">
|
<div class="top-nav ">
|
||||||
<a class="top-nav-item" href="{channel.lucentUrl}/members">Members</a>
|
<a class="top-nav-item" href="{channel.lucentUrl}/members">Members</a>
|
||||||
|
|
||||||
{#if channel.generateCommand}
|
{#if channel.commands.length > 0}
|
||||||
<a href="{channel.lucentUrl}/build-report" class="top-nav-item">Build website</a>
|
<Dropdown>
|
||||||
|
<div slot="button">Actions</div>
|
||||||
|
{#each channel.commands as command}
|
||||||
|
<a href="{channel.lucentUrl}/command-report/{command.signature}" class="top-nav-item">{command.name}</a>
|
||||||
|
{/each}
|
||||||
|
</Dropdown>
|
||||||
|
|
||||||
{/if}
|
{/if}
|
||||||
<!-- <div>-->
|
<!-- <div>-->
|
||||||
<!-- <form method="GET">-->
|
<!-- <form method="GET">-->
|
||||||
@@ -19,8 +27,8 @@
|
|||||||
<!-- class="form-control" required/>-->
|
<!-- class="form-control" required/>-->
|
||||||
<!-- </form>-->
|
<!-- </form>-->
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
<a href="{channel.lucentUrl}/profile">
|
<a href="{channel.lucentUrl}/profile">
|
||||||
<Avatar side="28" name={user.name}/>
|
<Avatar side="28" name={user.name}/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -100,8 +100,8 @@
|
|||||||
tinymce.init({...config, ...additionalConfig});
|
tinymce.init({...config, ...additionalConfig});
|
||||||
});
|
});
|
||||||
|
|
||||||
export function insertMedia(html){
|
export function insertMedia(info){
|
||||||
activeEditor.execCommand('InsertHTML', false, html);
|
activeEditor.execCommand('InsertHTML', false, info.html);
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,9 +3,12 @@
|
|||||||
import {Editor} from '@tiptap/core'
|
import {Editor} from '@tiptap/core'
|
||||||
import Document from '@tiptap/extension-document'
|
import Document from '@tiptap/extension-document'
|
||||||
import Paragraph from '@tiptap/extension-paragraph'
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
import Dropcursor from '@tiptap/extension-dropcursor'
|
||||||
import Text from '@tiptap/extension-text'
|
import Text from '@tiptap/extension-text'
|
||||||
import Heading from '@tiptap/extension-heading'
|
import Heading from '@tiptap/extension-heading'
|
||||||
|
import HardBreak from '@tiptap/extension-hard-break'
|
||||||
import Blockquote from '@tiptap/extension-blockquote';
|
import Blockquote from '@tiptap/extension-blockquote';
|
||||||
|
import CodeBlock from '@tiptap/extension-code-block';
|
||||||
import Bold from '@tiptap/extension-bold';
|
import Bold from '@tiptap/extension-bold';
|
||||||
import BulletList from '@tiptap/extension-bullet-list';
|
import BulletList from '@tiptap/extension-bullet-list';
|
||||||
import Code from '@tiptap/extension-code';
|
import Code from '@tiptap/extension-code';
|
||||||
@@ -19,6 +22,8 @@
|
|||||||
import TableCell from '@tiptap/extension-table-cell';
|
import TableCell from '@tiptap/extension-table-cell';
|
||||||
import TableHeader from '@tiptap/extension-table-header';
|
import TableHeader from '@tiptap/extension-table-header';
|
||||||
import Underline from '@tiptap/extension-underline';
|
import Underline from '@tiptap/extension-underline';
|
||||||
|
import Image from '@tiptap/extension-image';
|
||||||
|
import Icon from "../common/Icon.svelte";
|
||||||
|
|
||||||
let element;
|
let element;
|
||||||
let editor;
|
let editor;
|
||||||
@@ -32,19 +37,22 @@
|
|||||||
Paragraph,
|
Paragraph,
|
||||||
Text,
|
Text,
|
||||||
Bold,
|
Bold,
|
||||||
|
ListItem,
|
||||||
BulletList,
|
BulletList,
|
||||||
Code,
|
Code,
|
||||||
|
CodeBlock,
|
||||||
History,
|
History,
|
||||||
Italic,
|
Italic,
|
||||||
ListItem,
|
HardBreak,
|
||||||
OrderedList,
|
OrderedList,
|
||||||
ListItem,
|
|
||||||
Strike,
|
Strike,
|
||||||
Table,
|
Table,
|
||||||
TableRow,
|
TableRow,
|
||||||
TableCell,
|
TableCell,
|
||||||
TableHeader,
|
TableHeader,
|
||||||
Underline,
|
Underline,
|
||||||
|
Dropcursor,
|
||||||
|
Image,
|
||||||
Heading.configure({
|
Heading.configure({
|
||||||
levels: [1, 2, 3],
|
levels: [1, 2, 3],
|
||||||
}),
|
}),
|
||||||
@@ -56,7 +64,11 @@
|
|||||||
// force re-render so `editor.isActive` works as expected
|
// force re-render so `editor.isActive` works as expected
|
||||||
editor = editor;
|
editor = editor;
|
||||||
},
|
},
|
||||||
|
onUpdate: ({editor}) => {
|
||||||
|
value = editor.getHTML()
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
@@ -64,33 +76,99 @@
|
|||||||
editor.destroy();
|
editor.destroy();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export function insertMedia(info){
|
||||||
|
editor.chain().focus().setImage({ src: info.url }).run()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if editor}
|
{#if editor}
|
||||||
<button
|
<div class="editor-toolbar">
|
||||||
on:click={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
|
<button
|
||||||
class:active={editor.isActive('heading', { level: 1 })}
|
class="button"
|
||||||
>
|
on:click={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
|
||||||
H1
|
class:active={editor.isActive('heading', { level: 1 })}
|
||||||
</button>
|
>
|
||||||
<button
|
H1
|
||||||
on:click={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
|
</button>
|
||||||
class:active={editor.isActive('heading', { level: 2 })}
|
<button
|
||||||
>
|
class="button"
|
||||||
H2
|
on:click={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
|
||||||
</button>
|
class:active={editor.isActive('heading', { level: 2 })}
|
||||||
<button
|
>
|
||||||
on:click={() => editor.chain().focus().setParagraph().run()}
|
H2
|
||||||
class:active={editor.isActive('paragraph')}
|
</button>
|
||||||
>
|
|
||||||
P
|
<button
|
||||||
</button>
|
class="button"
|
||||||
<button
|
on:click={() => editor.chain().focus().toggleBold().run()}
|
||||||
on:click={() => editor.chain().focus().toggleBold().run()}
|
class:active={editor.isActive('bold')}
|
||||||
class:active={editor.isActive('bold')}
|
>
|
||||||
>
|
B
|
||||||
Bold
|
</button>
|
||||||
</button>
|
<button
|
||||||
|
class="button"
|
||||||
|
on:click={() => editor.chain().focus().toggleItalic().run()}
|
||||||
|
class:active={editor.isActive('italic')}
|
||||||
|
>
|
||||||
|
<em>IT</em>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="button"
|
||||||
|
on:click={() => editor.chain().focus().toggleUnderline().run()}
|
||||||
|
class:active={editor.isActive('underline')}
|
||||||
|
>
|
||||||
|
<u>U</u>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="button"
|
||||||
|
on:click={() => editor.chain().focus().toggleStrike().run()}
|
||||||
|
class:active={editor.isActive('strike')}
|
||||||
|
>
|
||||||
|
<s>S</s>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="button"
|
||||||
|
on:click={() => editor.commands.unsetAllMarks()}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="button"
|
||||||
|
on:click={() => editor.chain().focus().toggleCode().run()}
|
||||||
|
class:active={editor.isActive('code')}
|
||||||
|
>
|
||||||
|
Code
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="button"
|
||||||
|
on:click={() => editor.chain().focus().toggleBulletList().run()}
|
||||||
|
class:active={editor.isActive('bulletList')}
|
||||||
|
>
|
||||||
|
<Icon icon="list"></Icon>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="button"
|
||||||
|
on:click={() => editor.chain().focus().toggleOrderedList().run()}
|
||||||
|
class:active={editor.isActive('orderedList')}
|
||||||
|
>
|
||||||
|
<Icon icon="ordered-list"></Icon>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="button"
|
||||||
|
on:click={() => editor.chain().focus().toggleBlockquote().run()}
|
||||||
|
class:active={editor.isActive('blockquote')}
|
||||||
|
>
|
||||||
|
""
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="button"
|
||||||
|
on:click={() => editor.chain().focus().toggleCodeBlock().run()}
|
||||||
|
class:active={editor.isActive('codeBlock')}
|
||||||
|
>
|
||||||
|
cb
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div bind:this={element} class="content"/>
|
<div bind:this={element} class="content"/>
|
||||||
@@ -1,21 +1,53 @@
|
|||||||
<script>
|
<script>
|
||||||
import {onMount} from "svelte";
|
import {onDestroy, onMount} from "svelte";
|
||||||
import Trix from "trix"
|
import Trix from "trix"
|
||||||
import customcss from "./tinymce.css?inline";
|
|
||||||
import "trix/dist/trix.css"
|
import "trix/dist/trix.css"
|
||||||
|
|
||||||
export let value = "";
|
export let value = "";
|
||||||
let textareaEl;
|
export let field;
|
||||||
let lastVal;
|
let editor;
|
||||||
let editorWrapper;
|
|
||||||
let activeEditor;
|
|
||||||
|
function updateValue(e) {
|
||||||
|
value = e.target.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function insertMedia(info){
|
||||||
|
if(info.record._file.width > 0){
|
||||||
|
var attachment = new Trix.Attachment({ content: info.html })
|
||||||
|
editor.editor.insertAttachment(attachment)
|
||||||
|
}else{
|
||||||
|
editor.editor.insertHTML(`<a href="${info.originalUrl}">${info.record._file.originalName}</a>`)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
editor.addEventListener("trix-file-accept", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// onDestroy(() => {
|
||||||
|
// editor.removeEventListener("trix-before-initialize")
|
||||||
|
// })
|
||||||
|
|
||||||
|
|
||||||
Trix.config.blockAttributes.default.breakOnReturn = false
|
Trix.config.blockAttributes.default.breakOnReturn = false
|
||||||
console.log(Trix.config)
|
console.log(Trix.config)
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={editorWrapper} class="tox-wrapper">
|
<div class="tox-wrapper">
|
||||||
<input bind:this={textareaEl} id="x" bind:value type="hidden">
|
<input id="x-{field.name}" {value} type="hidden">
|
||||||
<trix-editor class="trix-content content" input="x"></trix-editor>
|
<trix-editor
|
||||||
|
bind:this={editor}
|
||||||
|
class=" content"
|
||||||
|
input="x-{field.name}"
|
||||||
|
role="textbox"
|
||||||
|
tabindex="0"
|
||||||
|
on:trix-change={updateValue}
|
||||||
|
|
||||||
|
></trix-editor>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
|
|
||||||
function createInlineReference(e, schemaUId) {
|
function createInlineReference(e, schemaUId) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
inLineCreateRecord = null;
|
||||||
axios
|
axios
|
||||||
.get(channel.lucentUrl + "/records/newInline?schema=" + schemaUId)
|
.get(channel.lucentUrl + "/records/newInline?schema=" + schemaUId)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
@@ -59,27 +60,29 @@
|
|||||||
<div
|
<div
|
||||||
style="display: flex;align-items: center;gap:4px"
|
style="display: flex;align-items: center;gap:4px"
|
||||||
>
|
>
|
||||||
{#each schemas as schema}
|
<Dropdown>
|
||||||
<Dropdown>
|
<div slot="button">New</div>
|
||||||
<div slot="button" class:is-first={!recordId}>
|
{#each schemas as schema}
|
||||||
{schema.label}
|
|
||||||
</div>
|
|
||||||
<button
|
<button
|
||||||
class=" button"
|
class=" button"
|
||||||
on:click={(e) =>
|
on:click={(e) =>
|
||||||
createInlineReference(e, schema.name)}
|
createInlineReference(e, schema.name)}
|
||||||
>Create New Record
|
>{schema.name}
|
||||||
</button>
|
</button>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
</Dropdown>
|
||||||
|
<Dropdown>
|
||||||
|
<div slot="button"> <Icon icon="magnifying-glass"/></div>
|
||||||
|
{#each schemas as schema}
|
||||||
<button
|
<button
|
||||||
class="button"
|
class="button"
|
||||||
on:click={(e) => openBrowseModal(e, schema.name)}
|
on:click={(e) => openBrowseModal(e, schema.name)}
|
||||||
>
|
>{schema.name}
|
||||||
<Icon icon="magnifying-glass"/>
|
</button>
|
||||||
Search
|
{/each}
|
||||||
</button
|
|
||||||
>
|
</Dropdown>
|
||||||
</Dropdown>
|
|
||||||
{/each}
|
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div style="display:flex;align-items: center;gap: 4px">
|
<div style="display:flex;align-items: center;gap: 4px">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import Tinymce from "../../libs/Tinymce.svelte";
|
import Tinymce from "../../libs/Tinymce.svelte";
|
||||||
import RichEditorFiles from "./RichEditorFiles.svelte";
|
import RichEditorFiles from "./RichEditorFiles.svelte";
|
||||||
import {getErrorMessage} from "./errorMessage";
|
import {getErrorMessage} from "./errorMessage";
|
||||||
|
import Trix from "../../libs/Trix.svelte";
|
||||||
|
|
||||||
export let value;
|
export let value;
|
||||||
export let field;
|
export let field;
|
||||||
@@ -24,8 +25,8 @@
|
|||||||
|
|
||||||
<div class="mb-0">
|
<div class="mb-0">
|
||||||
|
|
||||||
|
<Trix {field} bind:this={editor} bind:value></Trix>
|
||||||
<Tinymce bind:this={editor} bind:value {additionalConfig}/>
|
<!-- <Tinymce bind:this={editor} bind:value {additionalConfig}/>-->
|
||||||
{#if field.collections.length > 0}
|
{#if field.collections.length > 0}
|
||||||
<RichEditorFiles
|
<RichEditorFiles
|
||||||
bind:graph
|
bind:graph
|
||||||
@@ -38,7 +39,7 @@
|
|||||||
|
|
||||||
</RichEditorFiles>
|
</RichEditorFiles>
|
||||||
{/if}
|
{/if}
|
||||||
<!-- <TipTap bind:value />-->
|
|
||||||
|
|
||||||
{#if errorMessage}
|
{#if errorMessage}
|
||||||
<div class="invalid-feedback d-block">
|
<div class="invalid-feedback d-block">
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import {createEventDispatcher, getContext} from "svelte";
|
import {createEventDispatcher, getContext} from "svelte";
|
||||||
import Preview from "../../files/Preview.svelte";
|
import Preview from "../../files/Preview.svelte";
|
||||||
import {previewTitle} from "./../Preview";
|
import {previewTitle} from "./../Preview";
|
||||||
import {htmlurl} from "../../files/imageserver.js"
|
import {fileurl, htmlurl} from "../../files/imageserver.js"
|
||||||
import Status from "./../Status.svelte";
|
import Status from "./../Status.svelte";
|
||||||
import Dropdown from "../../common/Dropdown.svelte";
|
import Dropdown from "../../common/Dropdown.svelte";
|
||||||
|
|
||||||
@@ -25,8 +25,13 @@
|
|||||||
|
|
||||||
function insert(e, preset) {
|
function insert(e, preset) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let html = htmlurl(channel,record, preset)
|
let html = htmlurl(channel, record, preset)
|
||||||
dispatch("editor-insert", html);
|
dispatch("editor-insert", {
|
||||||
|
html: html,
|
||||||
|
url: channel.filesUrl + `/templates/${preset}/${record._file.path}`,
|
||||||
|
originalUrl: channel.filesUrl + "/" + record._file.path,
|
||||||
|
record: record
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Generated
+2562
-587
File diff suppressed because it is too large
Load Diff
+3
-20
@@ -11,40 +11,23 @@
|
|||||||
"@codemirror/lang-markdown": "^6.2.5",
|
"@codemirror/lang-markdown": "^6.2.5",
|
||||||
"@codemirror/state": "^6.4.1",
|
"@codemirror/state": "^6.4.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.1.1",
|
"@sveltejs/vite-plugin-svelte": "^3.1.1",
|
||||||
"@tiptap/core": "^2.6.4",
|
|
||||||
"@tiptap/extension-blockquote": "^2.6.4",
|
|
||||||
"@tiptap/extension-bold": "^2.6.4",
|
|
||||||
"@tiptap/extension-bullet-list": "^2.6.4",
|
|
||||||
"@tiptap/extension-code": "^2.6.4",
|
|
||||||
"@tiptap/extension-document": "^2.6.4",
|
|
||||||
"@tiptap/extension-heading": "^2.6.4",
|
|
||||||
"@tiptap/extension-history": "^2.6.4",
|
|
||||||
"@tiptap/extension-italic": "^2.6.4",
|
|
||||||
"@tiptap/extension-list-item": "^2.6.4",
|
|
||||||
"@tiptap/extension-ordered-list": "^2.6.4",
|
|
||||||
"@tiptap/extension-paragraph": "^2.6.4",
|
|
||||||
"@tiptap/extension-strike": "^2.6.4",
|
|
||||||
"@tiptap/extension-table": "^2.6.4",
|
|
||||||
"@tiptap/extension-table-cell": "^2.6.4",
|
|
||||||
"@tiptap/extension-table-header": "^2.6.4",
|
|
||||||
"@tiptap/extension-table-row": "^2.6.4",
|
|
||||||
"@tiptap/extension-text": "^2.6.4",
|
|
||||||
"@tiptap/extension-underline": "^2.6.4",
|
|
||||||
"@tiptap/pm": "^2.6.4",
|
|
||||||
"axios": "^1.7.4",
|
"axios": "^1.7.4",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
"flatpickr": "^4.6.13",
|
"flatpickr": "^4.6.13",
|
||||||
"fuse.js": "^7.0.0",
|
"fuse.js": "^7.0.0",
|
||||||
"htmx.org": "^2.0.1",
|
"htmx.org": "^2.0.1",
|
||||||
|
"install": "^0.13.0",
|
||||||
"laravel-vite-plugin": "^1.0.5",
|
"laravel-vite-plugin": "^1.0.5",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mustache": "^4.2.0",
|
"mustache": "^4.2.0",
|
||||||
|
"npm": "^10.8.2",
|
||||||
"postcss": "8.4.31",
|
"postcss": "8.4.31",
|
||||||
"sass": "^1.77.8",
|
"sass": "^1.77.8",
|
||||||
"sortablejs": "^1.15.2",
|
"sortablejs": "^1.15.2",
|
||||||
"svelte": "^4.2.18",
|
"svelte": "^4.2.18",
|
||||||
"tinymce": "^6.8.4",
|
"tinymce": "^6.8.4",
|
||||||
|
"trix": "^2.1.5",
|
||||||
"uuid": "^10.0.0",
|
"uuid": "^10.0.0",
|
||||||
"vite": "5.2.6"
|
"vite": "5.2.6"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
overflow: visible;
|
overflow: visible;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
z-index: 20;
|
z-index: 22;
|
||||||
background: var(--p20);
|
background: var(--p20);
|
||||||
transition: 600ms;
|
transition: 600ms;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.record-edit {
|
.record-edit {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
max-width: 900px;
|
||||||
.invalid-feedback {
|
.invalid-feedback {
|
||||||
color: var(--text-error);
|
color: var(--text-error);
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
.tiptap {
|
||||||
|
width: 100%;
|
||||||
|
background: var(--p20);
|
||||||
|
border: 1px solid var(--p50);
|
||||||
|
border-radius: 0 0 5px 5px;
|
||||||
|
padding: 15px 15px;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background: var(--p10);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
&.ProseMirror-selectednode {
|
||||||
|
box-shadow: 0 0 1px 2px var(--p70);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-field {
|
||||||
|
.editor-toolbar {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
background: var(--p30);
|
||||||
|
border-radius: 5px 5px 0 0;
|
||||||
|
padding: 5px 7px;
|
||||||
|
|
||||||
|
.button:not(.primary) {
|
||||||
|
font-weight: 700;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: var(--p40);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
.tiptap {
|
||||||
|
li > p {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
trix-editor {
|
||||||
|
background: var(--p20)!important;
|
||||||
|
border: 1px solid var(--p50)!important;
|
||||||
|
border-radius: 0 0 5px 5px!important;
|
||||||
|
padding: 15px 15px!important;
|
||||||
|
& > div {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 23px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background: var(--p10)!important;
|
||||||
|
|
||||||
|
}
|
||||||
|
figure.attachment{
|
||||||
|
display: flex!important;
|
||||||
|
flex-direction: column!important;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.attachment {
|
||||||
|
background: var(--p20);
|
||||||
|
padding: 12px 0;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-trix-mutable].attachment img {
|
||||||
|
box-shadow: 0 0 1px 2px var(--p70) !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.trix-button--remove {
|
||||||
|
box-shadow: none !important;
|
||||||
|
border: 2px solid var(--p40) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trix-button--remove:hover {
|
||||||
|
border: 2px solid var(--p40);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--p80);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trix-toolbar {
|
||||||
|
.trix-button-row {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trix-button-group {
|
||||||
|
background: transparent !important;
|
||||||
|
border: none !important;
|
||||||
|
display: flex !important;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.trix-button-group--history-tools,.trix-button-group--file-tools
|
||||||
|
{
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trix-button {
|
||||||
|
border-radius: 6px !important;
|
||||||
|
background: var(--p30) !important;
|
||||||
|
padding: 14px 22px !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 0px solid var(--p30) !important;
|
||||||
|
font-size: 14px !important;
|
||||||
|
min-height: 27px !important;
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
gap: 4px;
|
||||||
|
color: var(--text) !important;
|
||||||
|
|
||||||
|
&:before{
|
||||||
|
background-size: 22px!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover{
|
||||||
|
background: var(--p40) !important;
|
||||||
|
}
|
||||||
|
&.trix-active{
|
||||||
|
background: var(--p50) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
background: var(--p20);
|
background: var(--p20);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-header {
|
.sidebar-header {
|
||||||
@@ -29,10 +29,14 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
background: var(--p30);
|
background: var(--p30);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
padding: 3px 12px 6px;
|
padding: 3px 12px 3px;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
&:focus{
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--p40);
|
background: var(--p40);
|
||||||
|
|||||||
@@ -12,6 +12,15 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
h1{
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2{
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
padding: 0 0 0 16px;
|
padding: 0 0 0 16px;
|
||||||
@@ -31,6 +40,54 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
code{
|
||||||
|
background: var(--p30);
|
||||||
|
padding: 0 6px;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img{
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote{
|
||||||
|
border:1px solid var(--p30);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px 40px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before{
|
||||||
|
content: "\201C";
|
||||||
|
color: var(--p60);
|
||||||
|
font-size:4em;
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after{
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background: var(--grey-light);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
color: var(--white);
|
||||||
|
font-family: 'JetBrainsMono', monospace;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: none;
|
||||||
|
color: inherit;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.lx-small-text {
|
.lx-small-text {
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
@import "./table";
|
@import "./table";
|
||||||
@import "./avatar";
|
@import "./avatar";
|
||||||
@import "./codemirror";
|
@import "./codemirror";
|
||||||
|
@import "./rich";
|
||||||
@import "./layout";
|
@import "./layout";
|
||||||
@import "./wrappers";
|
@import "./wrappers";
|
||||||
@import "./toolbar";
|
@import "./toolbar";
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<h2 class="mb-5">Enter Lucent</h2>
|
<h2 class="mb-5">Enter Lucent</h2>
|
||||||
|
|
||||||
<form hx-post="/lucent/login" >
|
<form hx-post="/lucent/login" >
|
||||||
|
@csrf
|
||||||
<p>Submit your email address and you will receive a <b>login link</b> to your email</p>
|
<p>Submit your email address and you will receive a <b>login link</b> to your email</p>
|
||||||
<p>Don't forget to check your spam folder</p>
|
<p>Don't forget to check your spam folder</p>
|
||||||
<div class="mt-5 mb-3">
|
<div class="mt-5 mb-3">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Lucent\Channel;
|
namespace Lucent\Channel;
|
||||||
|
|
||||||
|
use Lucent\Channel\Data\UserCommand;
|
||||||
use Lucent\Primitive\Collection;
|
use Lucent\Primitive\Collection;
|
||||||
use Lucent\Schema\Schema;
|
use Lucent\Schema\Schema;
|
||||||
|
|
||||||
@@ -13,12 +14,13 @@ final class Channel
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Collection<Schema> $schemas
|
* @param Collection<Schema> $schemas
|
||||||
|
* @param Collection<UserCommand> $commands
|
||||||
*/
|
*/
|
||||||
function __construct(
|
function __construct(
|
||||||
public string $name,
|
public string $name,
|
||||||
public string $url,
|
public string $url,
|
||||||
public string $previewTarget,
|
public string $previewTarget,
|
||||||
public string $generateCommand,
|
public Collection $commands,
|
||||||
public Collection $schemas,
|
public Collection $schemas,
|
||||||
public array $imageFilters,
|
public array $imageFilters,
|
||||||
public array $roles,
|
public array $roles,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
namespace Lucent\Channel;
|
namespace Lucent\Channel;
|
||||||
|
|
||||||
|
|
||||||
use Lucent\File\FileService;
|
use Lucent\Channel\Data\UserCommand;
|
||||||
use Lucent\Primitive\Collection;
|
use Lucent\Primitive\Collection;
|
||||||
use Lucent\Schema\Schema;
|
use Lucent\Schema\Schema;
|
||||||
use Lucent\Schema\SchemaService;
|
use Lucent\Schema\SchemaService;
|
||||||
@@ -30,11 +30,16 @@ final class ChannelService
|
|||||||
$schemaService = new SchemaService();
|
$schemaService = new SchemaService();
|
||||||
$schemasCollection = (new Collection($schemasArray["schemas"] ?? []))->map([$schemaService, 'fromArray']);
|
$schemasCollection = (new Collection($schemasArray["schemas"] ?? []))->map([$schemaService, 'fromArray']);
|
||||||
|
|
||||||
|
$userCommands = [];
|
||||||
|
foreach (config("lucent.commands") as $signature => $desc) {
|
||||||
|
$userCommands[] = new UserCommand($desc, $signature);
|
||||||
|
}
|
||||||
|
|
||||||
$channel = new Channel(
|
$channel = new Channel(
|
||||||
name: config("lucent.name") ?? "",
|
name: config("lucent.name") ?? "",
|
||||||
url: rtrim(config("lucent.url") ?? "", "/"),
|
url: rtrim(config("lucent.url") ?? "", "/"),
|
||||||
previewTarget: rtrim(config("lucent.previewTarget") ?? "", "/"),
|
previewTarget: rtrim(config("lucent.previewTarget") ?? "", "/"),
|
||||||
generateCommand: config("lucent.generateCommand") ?? "",
|
commands: Collection::make($userCommands),
|
||||||
schemas: $schemasCollection,
|
schemas: $schemasCollection,
|
||||||
imageFilters: config("lucent.imageFilters") ?? [],
|
imageFilters: config("lucent.imageFilters") ?? [],
|
||||||
roles: $schemasArray["roles"] ?? []
|
roles: $schemasArray["roles"] ?? []
|
||||||
@@ -64,7 +69,7 @@ final class ChannelService
|
|||||||
*/
|
*/
|
||||||
public function schemasReadableByRoles(array $roles): array
|
public function schemasReadableByRoles(array $roles): array
|
||||||
{
|
{
|
||||||
$schemasAllRead = $this->channel->schemas->filter(fn(Schema $schema) => empty($schema->read))->values()->pluck("name");
|
$schemasAllRead = $this->channel->schemas->filter(fn(Schema $schema) => empty($schema->read))->values()->pluck("name");
|
||||||
$schemasCanRead = $this->channel->schemas->filter(fn(Schema $schema) => count(array_intersect($schema->read ?? [], $roles)) > 0)->values()->pluck("name");
|
$schemasCanRead = $this->channel->schemas->filter(fn(Schema $schema) => count(array_intersect($schema->read ?? [], $roles)) > 0)->values()->pluck("name");
|
||||||
$schemasCanWrite = $this->channel->schemas->filter(fn(Schema $schema) => count(array_intersect($schema->write ?? [], $roles)) > 0)->values()->pluck("name");
|
$schemasCanWrite = $this->channel->schemas->filter(fn(Schema $schema) => count(array_intersect($schema->write ?? [], $roles)) > 0)->values()->pluck("name");
|
||||||
return $schemasAllRead->merge($schemasCanRead)->merge($schemasCanWrite)->unique()->values()->toArray();
|
return $schemasAllRead->merge($schemasCanRead)->merge($schemasCanWrite)->unique()->values()->toArray();
|
||||||
@@ -77,8 +82,8 @@ final class ChannelService
|
|||||||
*/
|
*/
|
||||||
public function schemasWritableByRoles(array $roles): array
|
public function schemasWritableByRoles(array $roles): array
|
||||||
{
|
{
|
||||||
$schemasAllRead = $this->channel->schemas->filter(fn(Schema $schema) => empty($schema->write ?? []))->values()->pluck("name");
|
$schemasAllRead = $this->channel->schemas->filter(fn(Schema $schema) => empty($schema->write ?? []))->values()->pluck("name");
|
||||||
$schemasCanWrite = $this->channel->schemas->filter(fn(Schema $schema) => count(array_intersect($schema->write ?? [], $roles)) > 0)->values()->pluck("name");
|
$schemasCanWrite = $this->channel->schemas->filter(fn(Schema $schema) => count(array_intersect($schema->write ?? [], $roles)) > 0)->values()->pluck("name");
|
||||||
return $schemasAllRead->merge($schemasCanWrite)->unique()->values()->toArray();
|
return $schemasAllRead->merge($schemasCanWrite)->unique()->values()->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Channel\Data;
|
||||||
|
|
||||||
|
class UserCommand
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public string $name,
|
||||||
|
public string $signature,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,19 +16,13 @@ class CompileSchemas extends Command
|
|||||||
protected $description = 'Compiles schemas';
|
protected $description = 'Compiles schemas';
|
||||||
|
|
||||||
|
|
||||||
public function __construct(
|
public function handle(SchemaService $schemaService)
|
||||||
public SchemaService $schemaService
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$configDir = base_path(config('lucent.schemas_path'));
|
$configDir = base_path(config('lucent.schemas_path'));
|
||||||
$schemasDirIterator = new DirectoryIterator($configDir);
|
$schemasDirIterator = new DirectoryIterator($configDir);
|
||||||
$schemas = [];
|
$schemas = [];
|
||||||
|
|
||||||
foreach ($schemasDirIterator as $file) {
|
foreach ($schemasDirIterator as $file) {
|
||||||
if ($file->getExtension() !== "json") {
|
if ($file->getExtension() !== "json") {
|
||||||
continue;
|
continue;
|
||||||
@@ -46,7 +40,7 @@ class CompileSchemas extends Command
|
|||||||
|
|
||||||
$schemas = collect($schemas)->sortBy("label")->values();
|
$schemas = collect($schemas)->sortBy("label")->values();
|
||||||
$roles = $schemas
|
$roles = $schemas
|
||||||
->map([$this->schemaService, 'fromArray'])
|
->map([$schemaService, 'fromArray'])
|
||||||
->whereIn("type", [Type::COLLECTION, Type::FILES])
|
->whereIn("type", [Type::COLLECTION, Type::FILES])
|
||||||
->reduce(fn($carry, Schema $schema) => array_merge(
|
->reduce(fn($carry, Schema $schema) => array_merge(
|
||||||
$carry,
|
$carry,
|
||||||
|
|||||||
+40
-6
@@ -1,15 +1,49 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"env" => env("LUCENT_ENV", "production"),
|
"env" => env("LUCENT_ENV", "production"),
|
||||||
"schemas_path" => env("LUCENT_SCHEMAS_PATH", "app/Lucent"),
|
"schemas_path" => env("LUCENT_SCHEMAS_PATH", "app/Lucent"),
|
||||||
"database" => env('LUCENT_DB_CONNECTION', env('DB_CONNECTION',"sqlite")),
|
"database" => env('LUCENT_DB_CONNECTION', env('DB_CONNECTION', "sqlite")),
|
||||||
"name" => env("LUCENT_NAME", "Lucent"),
|
"name" => env("LUCENT_NAME", "Lucent"),
|
||||||
"url" => env("LUCENT_URL", env('APP_URL')),
|
"url" => env("LUCENT_URL", env('APP_URL')),
|
||||||
"previewTarget" => env("LUCENT_PREVIEW_TARGET", "previewTarget"),
|
"previewTarget" => env("LUCENT_PREVIEW_TARGET", "previewTarget"),
|
||||||
"generateCommand" => env("LUCENT_GENERATE_COMMAND", "generate:static"),
|
/*
|
||||||
|
* Make available laravel artisan commands for admin users
|
||||||
|
* example:
|
||||||
|
* [
|
||||||
|
* "command1:signature" => "Description 1"
|
||||||
|
* "command2:signature" => "Description 2"
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
* */
|
||||||
|
"commands" => [],
|
||||||
|
/*
|
||||||
|
* Image filter will be available both for rich editor fields
|
||||||
|
* and throughout your application
|
||||||
|
*
|
||||||
|
* example:
|
||||||
|
* [
|
||||||
|
* "filterName" => Filter::class
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
* */
|
||||||
"imageFilters" => [],
|
"imageFilters" => [],
|
||||||
"canInvite" => ["admin"],
|
"canInvite" => ["admin"],
|
||||||
"canBuild" => ["admin"],
|
"canBuild" => ["admin"],
|
||||||
"systemUserId" => "",
|
"systemUserId" => "",
|
||||||
|
"schemaFields" => [
|
||||||
|
\Lucent\Schema\Ui\Checkbox::class,
|
||||||
|
\Lucent\Schema\Ui\Color::class,
|
||||||
|
\Lucent\Schema\Ui\Date::class,
|
||||||
|
\Lucent\Schema\Ui\Datetime::class,
|
||||||
|
\Lucent\Schema\Ui\File::class,
|
||||||
|
\Lucent\Schema\Ui\Json::class,
|
||||||
|
\Lucent\Schema\Ui\Markdown::class,
|
||||||
|
\Lucent\Schema\Ui\Number::class,
|
||||||
|
\Lucent\Schema\Ui\Reference::class,
|
||||||
|
\Lucent\Schema\Ui\Rich::class,
|
||||||
|
\Lucent\Schema\Ui\Slug::class,
|
||||||
|
\Lucent\Schema\Ui\Text::class,
|
||||||
|
\Lucent\Schema\Ui\Textarea::class
|
||||||
|
]
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ use Illuminate\Database\Schema\Blueprint;
|
|||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
return new class extends Migration {
|
return new class extends Migration {
|
||||||
|
|
||||||
|
protected $connection = 'lucentDb';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the migrations.
|
* Run the migrations.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ use Illuminate\Support\Facades\Schema;
|
|||||||
|
|
||||||
return new class extends Migration
|
return new class extends Migration
|
||||||
{
|
{
|
||||||
|
protected $connection = 'lucentDb';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the migrations.
|
* Run the migrations.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ use Illuminate\Database\Schema\Blueprint;
|
|||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
return new class extends Migration {
|
return new class extends Migration {
|
||||||
|
|
||||||
|
protected $connection = 'lucentDb';
|
||||||
/**
|
/**
|
||||||
* Run the migrations.
|
* Run the migrations.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ use Illuminate\Database\Schema\Blueprint;
|
|||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
return new class extends Migration {
|
return new class extends Migration {
|
||||||
|
|
||||||
|
protected $connection = 'lucentDb';
|
||||||
/**
|
/**
|
||||||
* Run the migrations.
|
* Run the migrations.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ use Illuminate\Database\Schema\Blueprint;
|
|||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
return new class extends Migration {
|
return new class extends Migration {
|
||||||
|
|
||||||
|
protected $connection = 'lucentDb';
|
||||||
/**
|
/**
|
||||||
* Run the migrations.
|
* Run the migrations.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ use Illuminate\Database\Schema\Blueprint;
|
|||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
return new class extends Migration {
|
return new class extends Migration {
|
||||||
|
|
||||||
|
protected $connection = 'lucentDb';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the migrations.
|
* Run the migrations.
|
||||||
*
|
*
|
||||||
@@ -14,7 +17,7 @@ return new class extends Migration {
|
|||||||
{
|
{
|
||||||
Schema::table('records', function (Blueprint $table) {
|
Schema::table('records', function (Blueprint $table) {
|
||||||
$table->dropIndex(['schema', 'status']);
|
$table->dropIndex(['schema', 'status']);
|
||||||
$table->index(['schema', '_sys->updatedAt','status']);
|
$table->index(['schema', '_sys->updatedAt', 'status']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +30,7 @@ return new class extends Migration {
|
|||||||
{
|
{
|
||||||
Schema::table('records', function (Blueprint $table) {
|
Schema::table('records', function (Blueprint $table) {
|
||||||
|
|
||||||
$table->dropIndex(['schema', '_sys->updatedAt','status']);
|
$table->dropIndex(['schema', '_sys->updatedAt', 'status']);
|
||||||
$table->index(['schema', 'status']);
|
$table->index(['schema', 'status']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,8 @@ namespace Lucent\Http\Controller;
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use Illuminate\Contracts\View\View;
|
use Illuminate\Contracts\View\View;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Response;
|
|
||||||
use Illuminate\Support\Facades\Artisan;
|
|
||||||
use Lucent\Channel\ChannelService;
|
use Lucent\Channel\ChannelService;
|
||||||
use Lucent\Svelte\Svelte;
|
use Lucent\Svelte\Svelte;
|
||||||
use function Lucent\Response\ok;
|
|
||||||
|
|
||||||
class BuildController extends Controller
|
class BuildController extends Controller
|
||||||
{
|
{
|
||||||
@@ -20,49 +17,66 @@ class BuildController extends Controller
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function build()
|
public function build(Request $request)
|
||||||
{
|
{
|
||||||
|
$commandSignature = $request->route("signature");
|
||||||
$buildLogFile = storage_path("lucent/build.log");
|
$buildLogFile = $this->getLogFile($commandSignature);
|
||||||
if(file_exists($buildLogFile)){
|
$pidFile = $this->getPidFile($commandSignature);
|
||||||
|
if (file_exists($buildLogFile)) {
|
||||||
unlink($buildLogFile);
|
unlink($buildLogFile);
|
||||||
}
|
}
|
||||||
|
if (file_exists($pidFile)) {
|
||||||
|
unlink($pidFile);
|
||||||
|
}
|
||||||
|
|
||||||
exec("cd " . base_path() . " && php8.3 artisan {$this->channelService->channel->generateCommand} > " . $buildLogFile . " 2>&1 & echo $!", $op);
|
exec("cd " . base_path() . " && php8.3 artisan {$commandSignature} > " . $buildLogFile . " 2>&1 & echo $!", $op);
|
||||||
$pid = (int)$op[0];
|
$pid = (int)$op[0];
|
||||||
return redirect($this->channelService->channel->lucentUrl . "/build-report");
|
file_put_contents($pidFile, $pid);
|
||||||
|
return redirect($this->channelService->channel->lucentUrl . "/command-report/" . $commandSignature);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function report(): View
|
public function report(Request $request): View
|
||||||
{
|
{
|
||||||
|
$commandSignature = $request->route("signature");
|
||||||
|
$command = $this->channelService->channel->commands->firstWhere("signature", $commandSignature);
|
||||||
|
|
||||||
return $this->svelte->render(
|
return $this->svelte->render(
|
||||||
layout: "channel",
|
layout: "channel",
|
||||||
view: "buildReport",
|
view: "buildReport",
|
||||||
title: "Build Report",
|
title: $command->name,
|
||||||
|
data: [
|
||||||
|
"command" => $command,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function reportSource()
|
public function reportSource(Request $request)
|
||||||
{
|
{
|
||||||
return response()->stream(function () {
|
$commandSignature = $request->route("signature");
|
||||||
|
return response()->stream(function () use ($commandSignature) {
|
||||||
while (true) {
|
while (true) {
|
||||||
// sleep(1); // 50ms
|
|
||||||
$data["date"] = date("Y-m-d H:i:s");
|
$data["date"] = date("Y-m-d H:i:s");
|
||||||
$data["logs"] = file_get_contents(storage_path("lucent/build.log"));
|
$data["logs"] = file_get_contents($this->getLogFile($commandSignature));
|
||||||
// $lines = explode("\n",$data["logs"]);
|
// $lines = explode("\n",$data["logs"]);
|
||||||
|
|
||||||
echo 'data: ' .json_encode($data);
|
echo 'data: ' . json_encode($data);
|
||||||
echo "\n\n";
|
echo "\n\n";
|
||||||
|
|
||||||
ob_flush();
|
ob_flush();
|
||||||
flush();
|
flush();
|
||||||
|
|
||||||
if(str_contains($data["logs"],"Finito")){
|
$pidFile = $this->getPidFile($commandSignature);
|
||||||
|
if (file_exists($pidFile)) {
|
||||||
|
$pid = file_get_contents($pidFile);
|
||||||
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(str_contains($data["logs"],"Exception")){
|
if (empty($pid)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file_exists("/proc/$pid")) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,5 +93,13 @@ class BuildController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getLogFile(string $signature): string
|
||||||
|
{
|
||||||
|
return storage_path("lucent/$signature.log");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getPidFile(string $signature): string
|
||||||
|
{
|
||||||
|
return storage_path("lucent/$signature.pid.log");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use Lucent\Account\AccountService;
|
|||||||
use Lucent\Account\AuthService;
|
use Lucent\Account\AuthService;
|
||||||
use Lucent\Channel\ChannelService;
|
use Lucent\Channel\ChannelService;
|
||||||
use Lucent\LucentException;
|
use Lucent\LucentException;
|
||||||
use Lucent\Query\Operator;
|
use Lucent\Query\Operator\OperatorRegistry;
|
||||||
use Lucent\Query\Query;
|
use Lucent\Query\Query;
|
||||||
use Lucent\Record\InputData\EdgeInputData;
|
use Lucent\Record\InputData\EdgeInputData;
|
||||||
use Lucent\Record\InputData\RecordInputData;
|
use Lucent\Record\InputData\RecordInputData;
|
||||||
@@ -25,13 +25,14 @@ use function Lucent\Response\ok;
|
|||||||
class RecordController extends Controller
|
class RecordController extends Controller
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly RecordService $recordService,
|
private readonly RecordService $recordService,
|
||||||
private readonly AccountService $accountService,
|
private readonly AccountService $accountService,
|
||||||
private readonly AuthService $authService,
|
private readonly AuthService $authService,
|
||||||
private readonly ChannelService $channelService,
|
private readonly ChannelService $channelService,
|
||||||
private readonly Svelte $svelte,
|
private readonly Svelte $svelte,
|
||||||
private readonly Query $query,
|
private readonly Query $query,
|
||||||
private readonly Manager $recordManager
|
private readonly Manager $recordManager,
|
||||||
|
private readonly OperatorRegistry $operatorRegistry
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -79,7 +80,6 @@ class RecordController extends Controller
|
|||||||
|
|
||||||
$records = $graph->getRootRecords()->toArray();
|
$records = $graph->getRootRecords()->toArray();
|
||||||
|
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
"schemas" => $this->channelService->channel->schemas,
|
"schemas" => $this->channelService->channel->schemas,
|
||||||
"schema" => $schema,
|
"schema" => $schema,
|
||||||
@@ -87,7 +87,7 @@ class RecordController extends Controller
|
|||||||
"records" => $records,
|
"records" => $records,
|
||||||
"graph" => toArray($graph),
|
"graph" => toArray($graph),
|
||||||
"systemFields" => array_values(System::list()),
|
"systemFields" => array_values(System::list()),
|
||||||
"operators" => array_values(Operator::list()),
|
"operators" => $this->operatorRegistry->all(),
|
||||||
"sortParam" => $sort,
|
"sortParam" => $sort,
|
||||||
"sortField" => $schema->fields->merge(array_values(System::list()))->firstWhere(fn($field) => $field->name === $sort || "-" . $field->name === $sort || "data." . $field->name === $sort || "-data." . $field->name === $sort),
|
"sortField" => $schema->fields->merge(array_values(System::list()))->firstWhere(fn($field) => $field->name === $sort || "-" . $field->name === $sort || "data." . $field->name === $sort || "-data." . $field->name === $sort),
|
||||||
"limit" => $limit,
|
"limit" => $limit,
|
||||||
@@ -165,7 +165,7 @@ class RecordController extends Controller
|
|||||||
|
|
||||||
$schema = $this->channelService->channel->schemas->where("name", $request->input("schema"))->first();
|
$schema = $this->channelService->channel->schemas->where("name", $request->input("schema"))->first();
|
||||||
$recordHistory = $this->recordManager->fromSession($request->session())->getRecords();
|
$recordHistory = $this->recordManager->fromSession($request->session())->getRecords();
|
||||||
$record = $this->recordService->createEmpty($schema, $this->authService->currentUserId());
|
$record = $this->recordService->createEmpty($schema);
|
||||||
$queryRecord = QueryRecord::fromRecord($record);
|
$queryRecord = QueryRecord::fromRecord($record);
|
||||||
return $this->svelte->render(
|
return $this->svelte->render(
|
||||||
layout: "channel",
|
layout: "channel",
|
||||||
@@ -200,6 +200,7 @@ class RecordController extends Controller
|
|||||||
"schema" => $schema,
|
"schema" => $schema,
|
||||||
"record" => $queryRecord,
|
"record" => $queryRecord,
|
||||||
"isCreateMode" => true,
|
"isCreateMode" => true,
|
||||||
|
"isWritable" => in_array($record->schema, $this->accountService->currentWritableSchemas())
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,7 +303,7 @@ class RecordController extends Controller
|
|||||||
id: $request->input("record.id"),
|
id: $request->input("record.id"),
|
||||||
data: $request->input("record.data"),
|
data: $request->input("record.data"),
|
||||||
status: Status::from($request->input("record.status")),
|
status: Status::from($request->input("record.status")),
|
||||||
edges: array_map(EdgeInputData::fromArray(...), $request->input("edges") ?? []),
|
edges: array_map(EdgeInputData::fromArray(...), $request->input("edges") ?? []),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -33,9 +33,9 @@ Route::group([
|
|||||||
Route::get('/profile', [AccountController::class, 'profile']);
|
Route::get('/profile', [AccountController::class, 'profile']);
|
||||||
Route::post('/account/update-name', [AccountController::class, 'updateName']);
|
Route::post('/account/update-name', [AccountController::class, 'updateName']);
|
||||||
Route::post('/account/update-email', [AccountController::class, 'updateEmail']);
|
Route::post('/account/update-email', [AccountController::class, 'updateEmail']);
|
||||||
Route::get('/build-report', [BuildController::class, 'report']);
|
Route::get('/command-report/{signature}', [BuildController::class, 'report']);
|
||||||
Route::get('/build-report-source', [BuildController::class, 'reportSource']);
|
Route::get('/command-report-source/{signature}', [BuildController::class, 'reportSource']);
|
||||||
Route::post('/build', [BuildController::class, 'build']);
|
Route::post('/command/{signature}', [BuildController::class, 'build']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Lucent;
|
|
||||||
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
|
||||||
use Lucent\Edge\Event\EdgesUpdated;
|
|
||||||
|
|
||||||
class LucentEventServiceProvider extends ServiceProvider
|
|
||||||
{
|
|
||||||
protected $listen = [
|
|
||||||
EdgesUpdated::class => [
|
|
||||||
SendEmailVerificationNotification::class,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,6 @@ use Lucent\Commands\CompileSchemas;
|
|||||||
use Lucent\Commands\LiveLink;
|
use Lucent\Commands\LiveLink;
|
||||||
use Lucent\Commands\RebuildThumbnails;
|
use Lucent\Commands\RebuildThumbnails;
|
||||||
use Lucent\Commands\RemoveOrphanEdges;
|
use Lucent\Commands\RemoveOrphanEdges;
|
||||||
use Lucent\Event\Dispatcher;
|
|
||||||
use Lucent\File\FileService;
|
use Lucent\File\FileService;
|
||||||
use Lucent\File\ImageService;
|
use Lucent\File\ImageService;
|
||||||
use Lucent\Query\DatabaseGraph\DatabaseGraph;
|
use Lucent\Query\DatabaseGraph\DatabaseGraph;
|
||||||
@@ -30,10 +29,6 @@ class LucentServiceProvider extends ServiceProvider
|
|||||||
return ChannelService::fromConfig();
|
return ChannelService::fromConfig();
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->app->singleton(Dispatcher::class, function () {
|
|
||||||
return new Dispatcher();
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->app->bind(ImageManager::class, function () {
|
$this->app->bind(ImageManager::class, function () {
|
||||||
return new ImageManager(['driver' => 'imagick']);
|
return new ImageManager(['driver' => 'imagick']);
|
||||||
});
|
});
|
||||||
@@ -45,7 +40,6 @@ class LucentServiceProvider extends ServiceProvider
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->app->register(LucentEventServiceProvider::class);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
class BuilderConverter
|
||||||
|
{
|
||||||
|
public function for(Argument $argument): IBuilderConverter
|
||||||
|
{
|
||||||
|
return new ($argument->operator->converter)($argument);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
readonly class Equals implements IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(private Argument $argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->where($this->argument->field, $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->orWhere($this->argument->field, $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatValue(): string
|
||||||
|
{
|
||||||
|
return trim($this->argument->value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
readonly class EqualsFalse implements IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(private Argument $argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->where($this->argument->field, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->orWhere($this->argument->field, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
readonly class EqualsNumber implements IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(private Argument $argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
|
||||||
|
return $builder->where($this->argument->field, $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->orWhere($this->argument->field, $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatValue(): int|float
|
||||||
|
{
|
||||||
|
$value = trim($this->argument->value);
|
||||||
|
return str_contains($value, ".") ? floatval($value) : intval($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
readonly class EqualsTrue implements IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(private Argument $argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->where($this->argument->field, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->orWhere($this->argument->field, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
readonly class Exists implements IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(private Argument $argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->whereNot($this->argument->field, "")->whereNotNull($this->argument->field);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->whereNot($this->argument->field, "")->whereNotNull($this->argument->field);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
readonly class Filter implements IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(private Argument $argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->whereJsonContains($this->argument->field, [$this->argument->value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->orWhereJsonContains($this->argument->field, [$this->argument->value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
readonly class GreaterThan implements IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(private Argument $argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
|
||||||
|
return $builder->where($this->argument->field, ">", $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->orWhere($this->argument->field, ">", $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatValue(): int|float
|
||||||
|
{
|
||||||
|
$value = trim($this->argument->value);
|
||||||
|
if (is_numeric($value)) {
|
||||||
|
return str_contains($value, ".") ? floatval($value) : intval($value);
|
||||||
|
}
|
||||||
|
return $value;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
readonly class GreaterThanEquals implements IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(private Argument $argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
|
||||||
|
return $builder->where($this->argument->field, ">=", $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->orWhere($this->argument->field, ">=", $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatValue(): int|float
|
||||||
|
{
|
||||||
|
$value = trim($this->argument->value);
|
||||||
|
if (is_numeric($value)) {
|
||||||
|
return str_contains($value, ".") ? floatval($value) : intval($value);
|
||||||
|
}
|
||||||
|
return $value;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
|
||||||
|
interface IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder;
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
readonly class In implements IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(private Argument $argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->whereIn($this->argument->field, $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->orWhereIn($this->argument->field, $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatValue(): array
|
||||||
|
{
|
||||||
|
$value = $this->argument->value;
|
||||||
|
if (is_string($value)) {
|
||||||
|
$value = explode(",", $value);
|
||||||
|
}
|
||||||
|
return array_map(fn($v) => trim($v), $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
readonly class InNum implements IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(private Argument $argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->whereIn($this->argument->field, $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->orWhereIn($this->argument->field, $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatValue(): array
|
||||||
|
{
|
||||||
|
$value = $this->argument->value;
|
||||||
|
if (is_string($value)) {
|
||||||
|
$value = explode(",", $value);
|
||||||
|
}
|
||||||
|
return array_map(fn($v) => $this->formatNumber($v), $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatNumber(string $value): float|int
|
||||||
|
{
|
||||||
|
$value = trim($value);
|
||||||
|
return str_contains($value, ".") ? floatval($value) : intval($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
readonly class IsNull implements IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(private Argument $argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->whereNull($this->argument->field);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->orWhereNull($this->argument->field);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
readonly class LessThan implements IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(private Argument $argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
|
||||||
|
return $builder->where($this->argument->field, "<", $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->orWhere($this->argument->field, "<", $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatValue(): int|float
|
||||||
|
{
|
||||||
|
$value = trim($this->argument->value);
|
||||||
|
if (is_numeric($value)) {
|
||||||
|
return str_contains($value, ".") ? floatval($value) : intval($value);
|
||||||
|
}
|
||||||
|
return $value;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
readonly class LessThanEquals implements IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(private Argument $argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
|
||||||
|
return $builder->where($this->argument->field, "<=", $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->orWhere($this->argument->field, "<=", $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatValue(): int|float
|
||||||
|
{
|
||||||
|
$value = trim($this->argument->value);
|
||||||
|
if (is_numeric($value)) {
|
||||||
|
return str_contains($value, ".") ? floatval($value) : intval($value);
|
||||||
|
}
|
||||||
|
return $value;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
readonly class NotEquals implements IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(private Argument $argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->whereNot($this->argument->field, $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->orWhereNot($this->argument->field, $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatValue(): string
|
||||||
|
{
|
||||||
|
return trim($this->argument->value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
readonly class NotEqualsFalse implements IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(private Argument $argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->whereNot($this->argument->field, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->orWhereNot($this->argument->field, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
readonly class NotEqualsNumber implements IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(private Argument $argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->whereNot($this->argument->field, $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->orWhereNot($this->argument->field, $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatValue(): int|float
|
||||||
|
{
|
||||||
|
$value = trim($this->argument->value);
|
||||||
|
return str_contains($value, ".") ? floatval($value) : intval($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
readonly class NotEqualsTrue implements IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(private Argument $argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->whereNot($this->argument->field, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->orWhereNot($this->argument->field, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
readonly class NotExists implements IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(private Argument $argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->where(fn($b) => $b->where($this->argument->field, "")->orWhereNull($this->argument->field));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->orWhere(fn($b) => $b->where($this->argument->field, "")->orWhereNull($this->argument->field));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
readonly class NotIn implements IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(private Argument $argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->whereNotIn($this->argument->field, $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->orWhereNotIn($this->argument->field, $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatValue(): array
|
||||||
|
{
|
||||||
|
$value = $this->argument->value;
|
||||||
|
if (is_string($value)) {
|
||||||
|
$value = explode(",", $value);
|
||||||
|
}
|
||||||
|
return array_map(fn($v) => trim($v), $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
readonly class NotInNum implements IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(private Argument $argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->whereNotIn($this->argument->field, $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->orWhereNotIn($this->argument->field, $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatValue(): array
|
||||||
|
{
|
||||||
|
$value = $this->argument->value;
|
||||||
|
if (is_string($value)) {
|
||||||
|
$value = explode(",", $value);
|
||||||
|
}
|
||||||
|
return array_map(fn($v) => $this->formatNumber($v), $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatNumber(string $value): float|int
|
||||||
|
{
|
||||||
|
$value = trim($value);
|
||||||
|
return str_contains($value, ".") ? floatval($value) : intval($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
readonly class NotNull implements IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(private Argument $argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->whereNotNull($this->argument->field);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->orWhereNotNull($this->argument->field);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\BuilderConverter;
|
||||||
|
|
||||||
|
use Illuminate\Database\Query\Builder;
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
readonly class Regex implements IBuilderConverter
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(private Argument $argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toAndQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
|
||||||
|
return $builder->where($this->argument->field, "like", $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOrQueryBuilder(Builder $builder): Builder
|
||||||
|
{
|
||||||
|
return $builder->orWhere($this->argument->field, "like", $this->formatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatValue(): string
|
||||||
|
{
|
||||||
|
return "%" . strtolower(trim($this->argument->value)) . "%";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
namespace Lucent\Query\Filter;
|
namespace Lucent\Query\Filter;
|
||||||
|
|
||||||
|
use Lucent\Query\Operator\Operator;
|
||||||
|
|
||||||
class Argument
|
class Argument
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public string $field,
|
public string $field,
|
||||||
public string $operator,
|
public Operator $operator,
|
||||||
public mixed $value,
|
public mixed $value,
|
||||||
){}
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+16
-139
@@ -5,19 +5,26 @@ namespace Lucent\Query;
|
|||||||
use Illuminate\Contracts\Foundation\Application;
|
use Illuminate\Contracts\Foundation\Application;
|
||||||
use Illuminate\Database\Query\Builder;
|
use Illuminate\Database\Query\Builder;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Lucent\Query\BuilderConverter\BuilderConverter;
|
||||||
use Lucent\Query\Filter\AndFilter;
|
use Lucent\Query\Filter\AndFilter;
|
||||||
use Lucent\Query\Filter\Argument;
|
use Lucent\Query\Filter\Argument;
|
||||||
use Lucent\Query\Filter\Filter;
|
use Lucent\Query\Filter\Filter;
|
||||||
use Lucent\Query\Filter\OrFilter;
|
use Lucent\Query\Filter\OrFilter;
|
||||||
|
use Lucent\Query\Operator\In;
|
||||||
|
use Lucent\Query\Operator\OperatorDetector;
|
||||||
|
use function explode;
|
||||||
|
|
||||||
final class FilterParser
|
final class FilterParser
|
||||||
{
|
{
|
||||||
|
|
||||||
public function __construct(public Application $app)
|
public function __construct(
|
||||||
|
public Application $app,
|
||||||
|
public BuilderConverter $builderConverter,
|
||||||
|
public OperatorDetector $operatorDetector,
|
||||||
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array $arguments
|
* @param array $arguments
|
||||||
* @return array<Argument>
|
* @return array<Argument>
|
||||||
@@ -25,105 +32,11 @@ final class FilterParser
|
|||||||
private function formatArguments(array $arguments): array
|
private function formatArguments(array $arguments): array
|
||||||
{
|
{
|
||||||
return collect($arguments)->reduce(function ($c, $v, $k) {
|
return collect($arguments)->reduce(function ($c, $v, $k) {
|
||||||
$c[] = $this->formatArgument($v, $k);
|
$c[] = $this->operatorDetector->detect($v, $k);
|
||||||
return $c;
|
return $c;
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function formatArgument(mixed $value, string $filter): Argument
|
|
||||||
{
|
|
||||||
|
|
||||||
$operator = $this->detectOperator($filter);
|
|
||||||
|
|
||||||
$field = $this->detectField($filter, $operator);
|
|
||||||
$formattedValue = match ($operator) {
|
|
||||||
"eq" => $this->formatText($value),
|
|
||||||
"ne" => $this->formatText($value),
|
|
||||||
"eqnum" => $this->formatNumber($value),
|
|
||||||
"nenum" => $this->formatNumber($value),
|
|
||||||
"object" => $this->formatText($value),
|
|
||||||
"in" => $this->formatListString($value),
|
|
||||||
"nin" => $this->formatListString($value),
|
|
||||||
"innum" => $this->formatListNum($value),
|
|
||||||
"ninnum" => $this->formatListNum($value),
|
|
||||||
"eqtrue" => true,
|
|
||||||
"eqfalse" => false,
|
|
||||||
"netrue" => true,
|
|
||||||
"nefalse" => false,
|
|
||||||
"regex" => "%" . strtolower($value) . "%",
|
|
||||||
"gt" => is_numeric($value) ? floatval($value) : $value,
|
|
||||||
"gte" => is_numeric($value) ? floatval($value) : $value,
|
|
||||||
"lt" => is_numeric($value) ? floatval($value) : $value,
|
|
||||||
"lte" => is_numeric($value) ? floatval($value) : $value,
|
|
||||||
"null" => null,
|
|
||||||
"nnull" => null,
|
|
||||||
"exists" => true,
|
|
||||||
"nexists" => false,
|
|
||||||
default => $value,
|
|
||||||
};
|
|
||||||
|
|
||||||
$matchedOperator = Operator::list()[$operator];
|
|
||||||
return new Argument(
|
|
||||||
field: str_replace(".", "->", $field),
|
|
||||||
operator: $matchedOperator->db,
|
|
||||||
value: $formattedValue
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function formatText(string $value): string
|
|
||||||
{
|
|
||||||
return trim($value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function formatNumber(string $value): float
|
|
||||||
{
|
|
||||||
return floatval($value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private function formatListString(mixed $value): array
|
|
||||||
{
|
|
||||||
if (is_string($value)) {
|
|
||||||
$value = explode(",", $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_map(fn($v) => $this->formatText($v), $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function formatListNum(mixed $value): array
|
|
||||||
{
|
|
||||||
if (\is_string($value)) {
|
|
||||||
$value = explode(",", $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return \array_map(fn($v) => $this->formatNumber($v), $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private function detectOperator(string $filter): string
|
|
||||||
{
|
|
||||||
$exploded = \explode("_", $filter);
|
|
||||||
$candidate = end($exploded);
|
|
||||||
$operatorsListNames = collect(Operator::list())->map(fn($o) => $o->name)->toArray();
|
|
||||||
|
|
||||||
if (\in_array($candidate, $operatorsListNames)) {
|
|
||||||
return $candidate;
|
|
||||||
}
|
|
||||||
return 'eq';
|
|
||||||
}
|
|
||||||
|
|
||||||
private function detectField(string $filter, string $operator): string
|
|
||||||
{
|
|
||||||
|
|
||||||
$exploded = explode("_", $filter);
|
|
||||||
$candidate = array_pop($exploded);
|
|
||||||
if ($candidate === $operator) {
|
|
||||||
return implode("_", $exploded);
|
|
||||||
}
|
|
||||||
return $filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private function formatReferences(array $referenceArguments): Argument
|
private function formatReferences(array $referenceArguments): Argument
|
||||||
{
|
{
|
||||||
$subqueries = collect($referenceArguments)->reduce(function ($c, $v, $k) {
|
$subqueries = collect($referenceArguments)->reduce(function ($c, $v, $k) {
|
||||||
@@ -151,7 +64,7 @@ final class FilterParser
|
|||||||
|
|
||||||
return new Argument(
|
return new Argument(
|
||||||
field: "id",
|
field: "id",
|
||||||
operator: "in",
|
operator: new In(),
|
||||||
value: $sourceIds
|
value: $sourceIds
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -174,7 +87,6 @@ final class FilterParser
|
|||||||
private function parseArguments(Filter $arguments): array
|
private function parseArguments(Filter $arguments): array
|
||||||
{
|
{
|
||||||
[$normalArguments, $referenceArguments] = $this->separateMainFromReferenceArguments($arguments);
|
[$normalArguments, $referenceArguments] = $this->separateMainFromReferenceArguments($arguments);
|
||||||
|
|
||||||
$formattedArguments = $this->formatArguments($normalArguments);
|
$formattedArguments = $this->formatArguments($normalArguments);
|
||||||
if (!empty($referenceArguments)) {
|
if (!empty($referenceArguments)) {
|
||||||
$formattedArguments[] = $this->formatReferences($referenceArguments);
|
$formattedArguments[] = $this->formatReferences($referenceArguments);
|
||||||
@@ -198,26 +110,8 @@ final class FilterParser
|
|||||||
*/
|
*/
|
||||||
private function parseAnd(Builder $builder, array $arguments): Builder
|
private function parseAnd(Builder $builder, array $arguments): Builder
|
||||||
{
|
{
|
||||||
foreach ($arguments as $argument) {
|
return collect($arguments)
|
||||||
if ($argument->operator == "in") {
|
->reduce(fn($cBuilder, Argument $arg) => $this->builderConverter->for($arg)->toAndQueryBuilder($cBuilder), $builder);
|
||||||
$builder->whereIn($argument->field, $argument->value);
|
|
||||||
} else if ($argument->operator == "nin") {
|
|
||||||
$builder->whereNotIn($argument->field, $argument->value);
|
|
||||||
} else if ($argument->operator == "exists") {
|
|
||||||
$builder->where($argument->field, "!=", "");
|
|
||||||
$builder->where($argument->field, "!=", null);
|
|
||||||
} elseif ($argument->operator == "filter") {
|
|
||||||
$builder->whereJsonContains($argument->field, [$argument->value]);
|
|
||||||
// target result
|
|
||||||
// filter[data.previousNames_object]=previousNames&filter[previousNames.name_eq]=alpha&filter[previousNames.id_eqnum]=24
|
|
||||||
// $query->whereJsonContains("data->previousNames", [["name" => "alpha", "id" => 24]]);
|
|
||||||
// $query->whereJsonContains($filter["field"], [$objectFilters]);
|
|
||||||
} else {
|
|
||||||
$builder->where($argument->field, $argument->operator, $argument->value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $builder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -225,27 +119,10 @@ final class FilterParser
|
|||||||
*/
|
*/
|
||||||
private function parseOr(Builder $builder, array $arguments): Builder
|
private function parseOr(Builder $builder, array $arguments): Builder
|
||||||
{
|
{
|
||||||
$builder->where(function (Builder $orBuilder) use ($arguments) {
|
return $builder->where(function (Builder $orBuilder) use ($arguments) {
|
||||||
foreach ($arguments as $argument) {
|
return collect($arguments)
|
||||||
if ($argument->operator == "in") {
|
->reduce(fn($cBuilder, Argument $arg) => $this->builderConverter->for($arg)->toOrQueryBuilder($cBuilder), $orBuilder);
|
||||||
$orBuilder->orWhereIn($argument->field, $argument->value);
|
|
||||||
} else if ($argument->operator == "nin") {
|
|
||||||
$orBuilder->orWhereNotIn($argument->field, $argument->value);
|
|
||||||
} else if ($argument->operator == "exists") {
|
|
||||||
$orBuilder->where($argument->field, "!=", "");
|
|
||||||
$orBuilder->where($argument->field, "!=", null);
|
|
||||||
} elseif ($argument->operator == "filter") {
|
|
||||||
$orBuilder->whereJsonContains($argument->field, [$argument->value]);
|
|
||||||
// target result
|
|
||||||
// filter[data.previousNames_object]=previousNames&filter[previousNames.name_eq]=alpha&filter[previousNames.id_eqnum]=24
|
|
||||||
// $query->whereJsonContains("data->previousNames", [["name" => "alpha", "id" => 24]]);
|
|
||||||
// $query->whereJsonContains($filter["field"], [$objectFilters]);
|
|
||||||
} else {
|
|
||||||
$orBuilder->orWhere($argument->field, $argument->operator, $argument->value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return $builder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,186 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Lucent\Query;
|
|
||||||
|
|
||||||
|
|
||||||
final class Operator
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @psalm-param string[] $uis
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
public string $name,
|
|
||||||
public string $label,
|
|
||||||
public string $symbol,
|
|
||||||
public string $db,
|
|
||||||
public array $uis,
|
|
||||||
)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, Operator>
|
|
||||||
*/
|
|
||||||
public static function list(): array
|
|
||||||
{
|
|
||||||
|
|
||||||
return [
|
|
||||||
"regex" => new Operator(
|
|
||||||
name: "regex",
|
|
||||||
label: "Search",
|
|
||||||
symbol: "~",
|
|
||||||
db: 'like',
|
|
||||||
uis: ["id", "text", "textarea", "url", "color", "date", "datetime"],
|
|
||||||
),
|
|
||||||
"eq" => new Operator(
|
|
||||||
name: "eq",
|
|
||||||
label: "Equals",
|
|
||||||
symbol: "is",
|
|
||||||
db: '=',
|
|
||||||
uis: ["id", "text", "textarea", "url", "color", "date", "datetime", "reference"],
|
|
||||||
),
|
|
||||||
"ne" => new Operator(
|
|
||||||
name: "ne",
|
|
||||||
label: "Not Equals",
|
|
||||||
symbol: "is not",
|
|
||||||
db: '!=',
|
|
||||||
uis: ["id", "text", "textarea", "url", "color", "date", "datetime"],
|
|
||||||
),
|
|
||||||
"eqnum" => new Operator(
|
|
||||||
name: "eqnum",
|
|
||||||
label: "Equals number",
|
|
||||||
symbol: "is",
|
|
||||||
db: '=',
|
|
||||||
uis: ["number"],
|
|
||||||
),
|
|
||||||
"neqnum" => new Operator(
|
|
||||||
name: "nenum",
|
|
||||||
label: "Not Equals number",
|
|
||||||
symbol: "is not",
|
|
||||||
db: '$ne',
|
|
||||||
uis: ["number"],
|
|
||||||
),
|
|
||||||
"filter" => new Operator(
|
|
||||||
name: "filter",
|
|
||||||
label: "Equals Object",
|
|
||||||
symbol: "is",
|
|
||||||
db: 'filter',
|
|
||||||
uis: [],
|
|
||||||
),
|
|
||||||
"eqtrue" => new Operator(
|
|
||||||
name: "eqtrue",
|
|
||||||
label: "Equals true",
|
|
||||||
symbol: "is",
|
|
||||||
db: '=',
|
|
||||||
uis: ["checkbox"],
|
|
||||||
),
|
|
||||||
"eqfalse" => new Operator(
|
|
||||||
name: "eqfalse",
|
|
||||||
label: "Equals false",
|
|
||||||
symbol: "is not",
|
|
||||||
db: '=',
|
|
||||||
uis: ["checkbox"],
|
|
||||||
),
|
|
||||||
"netrue" => new Operator(
|
|
||||||
name: "netrue",
|
|
||||||
label: "Not equals true",
|
|
||||||
symbol: "!=",
|
|
||||||
db: '$ne',
|
|
||||||
uis: ["checkbox"],
|
|
||||||
),
|
|
||||||
"nefalse" => new Operator(
|
|
||||||
name: "nefalse",
|
|
||||||
label: "Not equals false",
|
|
||||||
symbol: "!=",
|
|
||||||
db: '$ne',
|
|
||||||
uis: ["checkbox"],
|
|
||||||
),
|
|
||||||
"in" => new Operator(
|
|
||||||
name: "in",
|
|
||||||
label: "In list",
|
|
||||||
symbol: "in",
|
|
||||||
db: 'in',
|
|
||||||
uis: ["id", "text", "textarea", "url", "color", "date", "datetime"],
|
|
||||||
),
|
|
||||||
"innum" => new Operator(
|
|
||||||
name: "innum",
|
|
||||||
label: "In list of numbers",
|
|
||||||
symbol: "in",
|
|
||||||
db: '$in',
|
|
||||||
uis: ["number"],
|
|
||||||
),
|
|
||||||
"nin" => new Operator(
|
|
||||||
name: "nin",
|
|
||||||
label: "Not in list",
|
|
||||||
symbol: "not in",
|
|
||||||
db: 'nin',
|
|
||||||
uis: ["id", "text", "textarea", "url", "color", "date", "datetime"],
|
|
||||||
),
|
|
||||||
"ninnum" => new Operator(
|
|
||||||
name: "ninnum",
|
|
||||||
label: "Not In list of numbers",
|
|
||||||
symbol: "not in",
|
|
||||||
db: '$nin',
|
|
||||||
uis: ["number"],
|
|
||||||
),
|
|
||||||
"lt" => new Operator(
|
|
||||||
name: "lt",
|
|
||||||
label: "Less than",
|
|
||||||
symbol: "<",
|
|
||||||
db: '<',
|
|
||||||
uis: ["number", "date", "datetime"],
|
|
||||||
),
|
|
||||||
"lte" => new Operator(
|
|
||||||
name: "lte",
|
|
||||||
label: "Less than equals",
|
|
||||||
symbol: "<=",
|
|
||||||
db: '<=',
|
|
||||||
uis: ["number", "date", "datetime"],
|
|
||||||
),
|
|
||||||
"gt" => new Operator(
|
|
||||||
name: "gt",
|
|
||||||
label: "Greater than",
|
|
||||||
symbol: ">",
|
|
||||||
db: '>',
|
|
||||||
uis: ["number", "date", "datetime"],
|
|
||||||
),
|
|
||||||
"gte" => new Operator(
|
|
||||||
name: "gte",
|
|
||||||
label: "Greater than equals",
|
|
||||||
symbol: ">=",
|
|
||||||
db: '>=',
|
|
||||||
uis: ["number", "date", "datetime"],
|
|
||||||
),
|
|
||||||
"null" => new Operator(
|
|
||||||
name: "null",
|
|
||||||
label: "Is null",
|
|
||||||
symbol: "=",
|
|
||||||
db: '$eq',
|
|
||||||
uis: ["*"],
|
|
||||||
),
|
|
||||||
"nnull" => new Operator(
|
|
||||||
name: "nnull",
|
|
||||||
label: "Not null",
|
|
||||||
symbol: "!=",
|
|
||||||
db: '$ne',
|
|
||||||
uis: ["*"],
|
|
||||||
),
|
|
||||||
"exists" => new Operator(
|
|
||||||
name: "exists",
|
|
||||||
label: "Exists",
|
|
||||||
symbol: "exists",
|
|
||||||
db: 'exists',
|
|
||||||
uis: ["*"],
|
|
||||||
),
|
|
||||||
"nexists" => new Operator(
|
|
||||||
name: "nexists",
|
|
||||||
label: "Not exists",
|
|
||||||
symbol: "not exists",
|
|
||||||
db: '$exists',
|
|
||||||
uis: ["*"],
|
|
||||||
),
|
|
||||||
|
|
||||||
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
class Equals extends Operator
|
||||||
|
{
|
||||||
|
|
||||||
|
public string $name = "eq";
|
||||||
|
public string $label = "Equals";
|
||||||
|
public string $symbol = "is";
|
||||||
|
public array $uis = ["id", "text", "textarea", "url", "color", "date", "datetime", "reference"];
|
||||||
|
public bool $hasValue = true;
|
||||||
|
public string $converter = \Lucent\Query\BuilderConverter\Equals::class;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
class EqualsFalse extends Operator
|
||||||
|
{
|
||||||
|
|
||||||
|
public string $name = "eqfalse";
|
||||||
|
public string $label = "Equals False";
|
||||||
|
public string $symbol = "is";
|
||||||
|
public array $uis = ["checkbox"];
|
||||||
|
public bool $hasValue = false;
|
||||||
|
public string $converter = \Lucent\Query\BuilderConverter\EqualsFalse::class;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
class EqualsNumber extends Operator
|
||||||
|
{
|
||||||
|
|
||||||
|
public string $name = "eqnum";
|
||||||
|
public string $label = "Equals Number";
|
||||||
|
public string $symbol = "is";
|
||||||
|
public array $uis = ["number"];
|
||||||
|
public bool $hasValue = true;
|
||||||
|
public string $converter = \Lucent\Query\BuilderConverter\EqualsNumber::class;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
class EqualsTrue extends Operator
|
||||||
|
{
|
||||||
|
|
||||||
|
public string $name = "eqtrue";
|
||||||
|
public string $label = "Equals True";
|
||||||
|
public string $symbol = "is";
|
||||||
|
public array $uis = ["checkbox"];
|
||||||
|
public bool $hasValue = false;
|
||||||
|
public string $converter = \Lucent\Query\BuilderConverter\EqualsTrue::class;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
class Exists extends Operator
|
||||||
|
{
|
||||||
|
|
||||||
|
public string $name = "exists";
|
||||||
|
public string $label = "Exists";
|
||||||
|
public string $symbol = "exists";
|
||||||
|
public array $uis = ["*"];
|
||||||
|
public bool $hasValue = true;
|
||||||
|
public string $converter = \Lucent\Query\BuilderConverter\Exists::class;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
class Filter extends Operator
|
||||||
|
{
|
||||||
|
public string $name = "filter";
|
||||||
|
public string $label = "Equals Object";
|
||||||
|
public string $symbol = "is";
|
||||||
|
public array $uis = [];
|
||||||
|
public bool $hasValue = true;
|
||||||
|
public string $converter = \Lucent\Query\BuilderConverter\Filter::class;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
class GreaterThan extends Operator
|
||||||
|
{
|
||||||
|
|
||||||
|
public string $name = "gt";
|
||||||
|
public string $label = "Greater than";
|
||||||
|
public string $symbol = ">";
|
||||||
|
public array $uis = ["number", "date", "datetime"];
|
||||||
|
public bool $hasValue = true;
|
||||||
|
public string $converter = \Lucent\Query\BuilderConverter\GreaterThan::class;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
class GreaterThanEquals extends Operator
|
||||||
|
{
|
||||||
|
|
||||||
|
public string $name = "gte";
|
||||||
|
public string $label = "Greater than equals";
|
||||||
|
public string $symbol = ">";
|
||||||
|
public array $uis = ["number", "date", "datetime"];
|
||||||
|
public bool $hasValue = true;
|
||||||
|
public string $converter = \Lucent\Query\BuilderConverter\GreaterThanEquals::class;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
class In extends Operator
|
||||||
|
{
|
||||||
|
public string $name = "in";
|
||||||
|
public string $label = "In list";
|
||||||
|
public string $symbol = "in";
|
||||||
|
public array $uis = ["id", "text", "textarea", "url", "color", "date", "datetime"];
|
||||||
|
public bool $hasValue = true;
|
||||||
|
public string $converter = \Lucent\Query\BuilderConverter\In::class;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
class InNum extends Operator
|
||||||
|
{
|
||||||
|
public string $name = "innum";
|
||||||
|
public string $label = "In number list";
|
||||||
|
public string $symbol = "in";
|
||||||
|
public array $uis = ["number"];
|
||||||
|
public bool $hasValue = true;
|
||||||
|
public string $converter = \Lucent\Query\BuilderConverter\NotInNum::class;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
class IsNull extends Operator
|
||||||
|
{
|
||||||
|
|
||||||
|
public string $name = "null";
|
||||||
|
public string $label = "Is Null";
|
||||||
|
public string $symbol = "is null";
|
||||||
|
public array $uis = ["*"];
|
||||||
|
public bool $hasValue = false;
|
||||||
|
public string $converter = \Lucent\Query\BuilderConverter\IsNull::class;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
class LessThan extends Operator
|
||||||
|
{
|
||||||
|
|
||||||
|
public string $name = "lt";
|
||||||
|
public string $label = "Less than";
|
||||||
|
public string $symbol = ">";
|
||||||
|
public array $uis = ["number", "date", "datetime"];
|
||||||
|
public bool $hasValue = true;
|
||||||
|
public string $converter = \Lucent\Query\BuilderConverter\LessThan::class;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
class LessThanEquals extends Operator
|
||||||
|
{
|
||||||
|
|
||||||
|
public string $name = "lte";
|
||||||
|
public string $label = "Less than equals";
|
||||||
|
public string $symbol = ">";
|
||||||
|
public array $uis = ["number", "date", "datetime"];
|
||||||
|
public bool $hasValue = true;
|
||||||
|
public string $converter = \Lucent\Query\BuilderConverter\LessThanEquals::class;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
class NotEquals extends Operator
|
||||||
|
{
|
||||||
|
|
||||||
|
public string $name = "ne";
|
||||||
|
public string $label = "Not Equals";
|
||||||
|
public string $symbol = "is not";
|
||||||
|
public array $uis = ["id", "text", "textarea", "url", "color", "date", "datetime", "reference"];
|
||||||
|
public bool $hasValue = true;
|
||||||
|
public string $converter = \Lucent\Query\BuilderConverter\NotEquals::class;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
class NotEqualsFalse extends Operator
|
||||||
|
{
|
||||||
|
|
||||||
|
public string $name = "nefalse";
|
||||||
|
public string $label = "Not Equals False";
|
||||||
|
public string $symbol = "is";
|
||||||
|
public array $uis = ["checkbox"];
|
||||||
|
public bool $hasValue = false;
|
||||||
|
public string $converter = \Lucent\Query\BuilderConverter\NotEqualsFalse::class;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
class NotEqualsNumber extends Operator
|
||||||
|
{
|
||||||
|
|
||||||
|
public string $name = "neqnum";
|
||||||
|
public string $label = "Not Equals Number";
|
||||||
|
public string $symbol = "is";
|
||||||
|
public array $uis = ["number"];
|
||||||
|
public bool $hasValue = true;
|
||||||
|
public string $converter = \Lucent\Query\BuilderConverter\NotEqualsNumber::class;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
class NotEqualsTrue extends Operator
|
||||||
|
{
|
||||||
|
|
||||||
|
public string $name = "netrue";
|
||||||
|
public string $label = "Not Equals True";
|
||||||
|
public string $symbol = "is";
|
||||||
|
public array $uis = ["checkbox"];
|
||||||
|
public bool $hasValue = false;
|
||||||
|
public string $converter = \Lucent\Query\BuilderConverter\NotEqualsTrue::class;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
class NotExists extends Operator
|
||||||
|
{
|
||||||
|
|
||||||
|
public string $name = "nexists";
|
||||||
|
public string $label = "Not Exists";
|
||||||
|
public string $symbol = "not exists";
|
||||||
|
public array $uis = ["*"];
|
||||||
|
public bool $hasValue = true;
|
||||||
|
public string $converter = \Lucent\Query\BuilderConverter\NotExists::class;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
class NotIn extends Operator
|
||||||
|
{
|
||||||
|
public string $name = "nin";
|
||||||
|
public string $label = "Not in list";
|
||||||
|
public string $symbol = "in";
|
||||||
|
public array $uis = ["id", "text", "textarea", "url", "color", "date", "datetime"];
|
||||||
|
public bool $hasValue = true;
|
||||||
|
public string $converter = \Lucent\Query\BuilderConverter\NotIn::class;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
class NotInNum extends Operator
|
||||||
|
{
|
||||||
|
public string $name = "ninnum";
|
||||||
|
public string $label = "Not in number list";
|
||||||
|
public string $symbol = "not in";
|
||||||
|
public array $uis = ["number"];
|
||||||
|
public bool $hasValue = true;
|
||||||
|
public string $converter = \Lucent\Query\BuilderConverter\InNum::class;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
class NotNull extends Operator
|
||||||
|
{
|
||||||
|
|
||||||
|
public string $name = "nnull";
|
||||||
|
public string $label = "Is not Null";
|
||||||
|
public string $symbol = "is not null";
|
||||||
|
public array $uis = ["*"];
|
||||||
|
public bool $hasValue = false;
|
||||||
|
public string $converter = \Lucent\Query\BuilderConverter\NotNull::class;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
|
||||||
|
abstract class Operator
|
||||||
|
{
|
||||||
|
public string $name;
|
||||||
|
public string $label;
|
||||||
|
public string $symbol;
|
||||||
|
public array $uis;
|
||||||
|
public bool $hasValue;
|
||||||
|
/**
|
||||||
|
* @var class-string
|
||||||
|
*/
|
||||||
|
public string $converter;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
use Lucent\Query\Filter\Argument;
|
||||||
|
|
||||||
|
class OperatorDetector
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public OperatorRegistry $operatorRegistry
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function detect(mixed $value, string $filter): Argument
|
||||||
|
{
|
||||||
|
$operator = $this->detectOperator($filter);
|
||||||
|
$field = $this->detectField($filter, $operator->name);
|
||||||
|
return new Argument(
|
||||||
|
field: str_replace(".", "->", $field),
|
||||||
|
operator: $operator,
|
||||||
|
value: $value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function detectOperator(string $filter): Operator
|
||||||
|
{
|
||||||
|
$exploded = explode("_", $filter);
|
||||||
|
$candidate = end($exploded);
|
||||||
|
return $this->operatorRegistry->matchByName($candidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function detectField(string $filter, string $operator): string
|
||||||
|
{
|
||||||
|
$exploded = explode("_", $filter);
|
||||||
|
$candidate = array_pop($exploded);
|
||||||
|
if ($candidate === $operator) {
|
||||||
|
return implode("_", $exploded);
|
||||||
|
}
|
||||||
|
return $filter;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
|
||||||
|
final class OperatorRegistry
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return list<Operator>
|
||||||
|
*/
|
||||||
|
public function all(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new Equals(),
|
||||||
|
new NotEquals(),
|
||||||
|
new EqualsNumber(),
|
||||||
|
new NotEqualsNumber(),
|
||||||
|
new EqualsTrue(),
|
||||||
|
new EqualsFalse(),
|
||||||
|
new NotEqualsFalse(),
|
||||||
|
new NotEqualsTrue(),
|
||||||
|
new Regex(),
|
||||||
|
new In(),
|
||||||
|
new NotIn(),
|
||||||
|
new InNum(),
|
||||||
|
new NotInNum(),
|
||||||
|
new GreaterThan(),
|
||||||
|
new GreaterThanEquals(),
|
||||||
|
new LessThan(),
|
||||||
|
new LessThanEquals(),
|
||||||
|
new IsNull(),
|
||||||
|
new NotNull(),
|
||||||
|
new Exists(),
|
||||||
|
new NotExists(),
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function matchByName(string $name): Operator
|
||||||
|
{
|
||||||
|
$operator = collect($this->all())->where('name', $name)->first();
|
||||||
|
if (empty($operator)) {
|
||||||
|
return new Equals();
|
||||||
|
}
|
||||||
|
return $operator;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Query\Operator;
|
||||||
|
|
||||||
|
class Regex extends Operator
|
||||||
|
{
|
||||||
|
|
||||||
|
public string $name = "regex";
|
||||||
|
public string $label = "Search";
|
||||||
|
public string $symbol = "~";
|
||||||
|
public array $uis = ["id", "text", "textarea", "url", "color", "date", "datetime"];
|
||||||
|
public bool $hasValue = true;
|
||||||
|
public string $converter = \Lucent\Query\BuilderConverter\Regex::class;
|
||||||
|
|
||||||
|
}
|
||||||
+2
-3
@@ -161,7 +161,7 @@ final class Query
|
|||||||
|
|
||||||
private function findNotLinked(Builder $query): Builder
|
private function findNotLinked(Builder $query): Builder
|
||||||
{
|
{
|
||||||
if(empty($this->options->notLinked)){
|
if (empty($this->options->notLinked)) {
|
||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,8 +169,7 @@ final class Query
|
|||||||
$query
|
$query
|
||||||
->select("records.*")
|
->select("records.*")
|
||||||
->join('edges', 'records.id', '=', 'edges.target', 'left outer')
|
->join('edges', 'records.id', '=', 'edges.target', 'left outer')
|
||||||
->whereNull("edges.target")
|
->whereNull("edges.target");
|
||||||
;
|
|
||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Lucent\Schema\BlockUi;
|
|
||||||
|
|
||||||
use Lucent\Schema\FieldInfo;
|
|
||||||
use Lucent\Schema\FieldInterface;
|
|
||||||
use Lucent\Schema\FieldType;
|
|
||||||
use Lucent\Schema\Validator\MinMaxInterface;
|
|
||||||
|
|
||||||
class File implements FieldInterface, MinMaxInterface
|
|
||||||
{
|
|
||||||
public FieldInfo $info;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string[] $collections
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
public string $name,
|
|
||||||
public string $label,
|
|
||||||
public string $mime = "",
|
|
||||||
public ?int $min = null,
|
|
||||||
public ?int $max = null,
|
|
||||||
public array $collections = [],
|
|
||||||
)
|
|
||||||
{
|
|
||||||
$this->info = new FieldInfo("file", "File", FieldType::FILE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function format(array $input, array $output): array
|
|
||||||
{
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function failMin(mixed $value): bool
|
|
||||||
{
|
|
||||||
if (is_null($value)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return count($value) < $this->min;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function failMax(mixed $value): bool
|
|
||||||
{
|
|
||||||
if (is_null($value)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return count($value) < $this->min;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Lucent\Schema\BlockUi;
|
|
||||||
|
|
||||||
use Lucent\Schema\FieldInfo;
|
|
||||||
use Lucent\Schema\FieldInterface;
|
|
||||||
use Lucent\Schema\FieldType;
|
|
||||||
use Lucent\Schema\Nullable;
|
|
||||||
use Lucent\Schema\Validator\RequiredInterface;
|
|
||||||
|
|
||||||
class Heading implements FieldInterface
|
|
||||||
{
|
|
||||||
public FieldInfo $info;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
public string $name,
|
|
||||||
public string $label,
|
|
||||||
public ?int $min = null,
|
|
||||||
public ?int $max = null,
|
|
||||||
public string $default = "",
|
|
||||||
)
|
|
||||||
{
|
|
||||||
$this->info = new FieldInfo("heading", "Heading", FieldType::STRING);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function format(array $input, array $output): array
|
|
||||||
{
|
|
||||||
$output[$this->name] = $input[$this->name] ?? "";
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user