added records file picker support for the editor field
This commit is contained in:
@@ -160,7 +160,7 @@
|
||||
{#each [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] as index}
|
||||
<button
|
||||
type="button"
|
||||
class="link-fade thumb thumb-circle {index == avatar ? 'thumb-active' : 'thumb-sm'}"
|
||||
class="link-fade thumb thumb-circle {index == avatar ? 'thumb-primary' : 'thumb-sm'}"
|
||||
on:click={() => (avatar = index)}
|
||||
>
|
||||
<img
|
||||
|
||||
@@ -219,7 +219,7 @@
|
||||
|
||||
let result = CommonHelper.getAllCollectionIdentifiers(collection, prefix);
|
||||
|
||||
for (const field of collection.schema) {
|
||||
for (const field of collection?.schema || []) {
|
||||
const key = prefix + field.name;
|
||||
|
||||
// add relation fields
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<form class="searchbar" on:click|stopPropagation on:submit|preventDefault={submit}>
|
||||
<label for={uniqueId} class="m-l-10 txt-xl">
|
||||
<i class="ri-search-line" />
|
||||
@@ -85,7 +86,7 @@
|
||||
{#if (value.length || tempValue.length) && tempValue != value}
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-expanded btn-sm btn-warning"
|
||||
class="btn btn-expanded-sm btn-sm btn-warning"
|
||||
transition:fly={{ duration: 150, x: 5 }}
|
||||
>
|
||||
<span class="txt">Search</span>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
export let multiple = false;
|
||||
export let disabled = false;
|
||||
export let readonly = false;
|
||||
export let upside = false;
|
||||
export let selected = multiple ? [] : undefined;
|
||||
export let toggle = multiple; // toggle option on click
|
||||
export let closable = true; // close the dropdown on option select/deselect
|
||||
@@ -199,7 +200,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div bind:this={container} class="select {classes}" class:multiple class:disabled class:readonly>
|
||||
<div bind:this={container} class="select {classes}" class:upside class:multiple class:disabled class:readonly>
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<div
|
||||
bind:this={labelDiv}
|
||||
@@ -218,6 +219,7 @@
|
||||
|
||||
{#if multiple || toggle}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<span
|
||||
class="clear"
|
||||
use:tooltip={"Clear"}
|
||||
@@ -237,7 +239,7 @@
|
||||
{#if !disabled && !readonly}
|
||||
<Toggler
|
||||
bind:this={toggler}
|
||||
class="dropdown dropdown-block options-dropdown dropdown-left"
|
||||
class="dropdown dropdown-block options-dropdown dropdown-left {upside ? 'dropdown-upside' : ''}"
|
||||
trigger={labelDiv}
|
||||
on:show={onDropdownShow}
|
||||
on:hide
|
||||
@@ -276,6 +278,7 @@
|
||||
<div class="options-list">
|
||||
{#each filteredItems as item}
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
tabindex="0"
|
||||
class="dropdown-item option"
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
{:else if field.type === "editor"}
|
||||
{#if short}
|
||||
<span class="txt">
|
||||
{CommonHelper.truncate(CommonHelper.plainText(rawValue), 250)}
|
||||
{CommonHelper.truncate(CommonHelper.plainText(rawValue), 195)}
|
||||
</span>
|
||||
{:else}
|
||||
<TinyMCE
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import ApiClient from "@/utils/ApiClient";
|
||||
import scrollend from "@/actions/scrollend";
|
||||
import tooltip from "@/actions/tooltip";
|
||||
import { collections } from "@/stores/collections";
|
||||
import OverlayPanel from "@/components/base/OverlayPanel.svelte";
|
||||
import Searchbar from "@/components/base/Searchbar.svelte";
|
||||
import Field from "@/components/base/Field.svelte";
|
||||
import ObjectSelect from "@/components/base/ObjectSelect.svelte";
|
||||
import RecordUpsertPanel from "@/components/records/RecordUpsertPanel.svelte";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const uniqueId = "file_picker_" + CommonHelper.randomString(5);
|
||||
const batchSize = 50;
|
||||
|
||||
export let title = "Select a file";
|
||||
export let submitText = "Insert";
|
||||
export let fileTypes = ["image", "document", "video", "audio", "file"];
|
||||
|
||||
let pickerPanel;
|
||||
let upsertPanel;
|
||||
let filter = "";
|
||||
let list = [];
|
||||
let currentPage = 1;
|
||||
let lastItemsCount = 0;
|
||||
let isLoading = false;
|
||||
let fileCollections = [];
|
||||
let fileFields = [];
|
||||
let sizeOptions = [];
|
||||
let selectedCollection = {};
|
||||
let selectedFile = {};
|
||||
let selectedSize = "";
|
||||
|
||||
// find all collections with at least one non-protected file field
|
||||
$: fileCollections = $collections.filter((c) => {
|
||||
return (
|
||||
c.type !== "view" &&
|
||||
!!CommonHelper.toArray(c.schema).find((f) => f.type === "file" && !f.options?.protected)
|
||||
);
|
||||
});
|
||||
|
||||
// auto select the first collection from the list
|
||||
$: if (!selectedCollection?.id && fileCollections.length > 0) {
|
||||
selectedCollection = fileCollections[0];
|
||||
}
|
||||
|
||||
$: fileFields = selectedCollection?.schema?.filter((f) => f.type === "file" && !f.options?.protected);
|
||||
|
||||
// reset filter on collection change
|
||||
$: if (selectedCollection?.id) {
|
||||
clearFilter();
|
||||
refreshSizeOptions();
|
||||
}
|
||||
|
||||
// reset list on filter or collection change
|
||||
$: if (typeof filter !== "undefined" && selectedCollection?.id && pickerPanel?.isActive()) {
|
||||
loadList(true);
|
||||
}
|
||||
|
||||
$: isSelected = (record, name) => {
|
||||
return selectedFile?.name == name && selectedFile?.record?.id == record.id;
|
||||
};
|
||||
|
||||
$: hasAtleastOneFile = list.find((r) => extractFiles(r).length > 0);
|
||||
|
||||
$: canLoadMore = !isLoading && lastItemsCount == batchSize;
|
||||
|
||||
$: canSubmit = !isLoading && !!selectedFile?.name;
|
||||
|
||||
export function show() {
|
||||
loadList(true);
|
||||
|
||||
return pickerPanel?.show();
|
||||
}
|
||||
|
||||
export function hide() {
|
||||
return pickerPanel?.hide();
|
||||
}
|
||||
|
||||
function clearList() {
|
||||
list = [];
|
||||
selectedFile = {};
|
||||
selectedSize = "";
|
||||
}
|
||||
|
||||
function clearFilter() {
|
||||
filter = "";
|
||||
}
|
||||
|
||||
async function loadList(reset = false) {
|
||||
if (!selectedCollection?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading = true;
|
||||
|
||||
if (reset) {
|
||||
clearList();
|
||||
}
|
||||
|
||||
try {
|
||||
const page = reset ? 1 : currentPage + 1;
|
||||
|
||||
const fallbackSearchFields = CommonHelper.getAllCollectionIdentifiers(selectedCollection);
|
||||
|
||||
const result = await ApiClient.collection(selectedCollection.id).getList(page, batchSize, {
|
||||
filter: CommonHelper.normalizeSearchFilter(filter, fallbackSearchFields),
|
||||
sort: "-created",
|
||||
fields: "*:excerpt(100)",
|
||||
skipTotal: 1,
|
||||
requestKey: uniqueId + "loadImagePicker",
|
||||
});
|
||||
|
||||
list = CommonHelper.filterDuplicatesByKey(list.concat(result.items));
|
||||
currentPage = result.page;
|
||||
lastItemsCount = result.items.length;
|
||||
|
||||
isLoading = false;
|
||||
} catch (err) {
|
||||
if (!err.isAbort) {
|
||||
ApiClient.error(err);
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function refreshSizeOptions() {
|
||||
let sizes = [];
|
||||
|
||||
for (const field of fileFields) {
|
||||
const thumbs = CommonHelper.toArray(field.options?.thumbs);
|
||||
for (const thumb of thumbs) {
|
||||
if (!sizes.includes(thumb)) {
|
||||
sizes.push(thumb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sizeOptions = [
|
||||
{ label: "Original size", value: "" },
|
||||
{ label: "100x100 thumb", value: "100x100" },
|
||||
];
|
||||
for (const size of sizes) {
|
||||
sizeOptions.push({
|
||||
label: `${size} thumb`,
|
||||
value: size,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function extractFiles(record) {
|
||||
let result = [];
|
||||
|
||||
for (const field of fileFields) {
|
||||
const names = CommonHelper.toArray(record[field.name]);
|
||||
for (const name of names) {
|
||||
if (fileTypes.includes(CommonHelper.getFileType(name))) {
|
||||
result.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function select(record, name) {
|
||||
selectedFile = { record, name };
|
||||
}
|
||||
|
||||
function submit() {
|
||||
if (!canSubmit) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(
|
||||
"submit",
|
||||
Object.assign(
|
||||
{
|
||||
size: selectedSize,
|
||||
},
|
||||
selectedFile
|
||||
)
|
||||
);
|
||||
|
||||
hide();
|
||||
}
|
||||
</script>
|
||||
|
||||
<OverlayPanel bind:this={pickerPanel} popup class="file-picker-popup" on:hide on:show {...$$restProps}>
|
||||
<svelte:fragment slot="header">
|
||||
<h4>{title}</h4>
|
||||
</svelte:fragment>
|
||||
|
||||
{#if !fileCollections.length}
|
||||
<h6 class="txt-center txt-hint">
|
||||
You currently don't have any collection with <code>file</code> field.
|
||||
</h6>
|
||||
{:else}
|
||||
<div class="file-picker">
|
||||
<aside class="file-picker-sidebar">
|
||||
{#each fileCollections as collection (collection.id)}
|
||||
<button
|
||||
type="button"
|
||||
class="sidebar-item"
|
||||
class:active={selectedCollection?.id == collection.id}
|
||||
on:click|preventDefault={() => {
|
||||
selectedCollection = collection;
|
||||
}}
|
||||
>
|
||||
{collection.name}
|
||||
</button>
|
||||
{/each}
|
||||
</aside>
|
||||
|
||||
<div class="file-picker-content">
|
||||
<div class="flex m-b-base flex-gap-10">
|
||||
<Searchbar
|
||||
value={filter}
|
||||
placeholder="Record search term or filter..."
|
||||
autocompleteCollection={selectedCollection}
|
||||
on:submit={(e) => (filter = e.detail)}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-pill btn-transparent btn-hint p-l-xs p-r-xs"
|
||||
on:click={() => upsertPanel?.show()}
|
||||
>
|
||||
<div class="txt">New record</div>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="files-list"
|
||||
use:scrollend={{
|
||||
threshold: 150,
|
||||
callback: () => {
|
||||
if (canLoadMore) {
|
||||
loadList();
|
||||
}
|
||||
},
|
||||
}}
|
||||
>
|
||||
{#if hasAtleastOneFile}
|
||||
{#each list as record (record.id)}
|
||||
{@const names = extractFiles(record)}
|
||||
{#each names as name}
|
||||
<button
|
||||
type="button"
|
||||
class="thumb thumb-xl handle"
|
||||
use:tooltip={name + "\n(record: " + record.id + ")"}
|
||||
class:thumb-warning={isSelected(record, name)}
|
||||
on:click|preventDefault={select(record, name)}
|
||||
>
|
||||
{#if CommonHelper.hasImageExtension(name)}
|
||||
<img
|
||||
loading="lazy"
|
||||
src={ApiClient.files.getUrl(record, name, { thumb: "100x100" })}
|
||||
alt={name}
|
||||
/>
|
||||
{:else}
|
||||
<i class="ri-file-3-line" />
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
{/each}
|
||||
{:else if !isLoading}
|
||||
<div class="inline-flex">
|
||||
<span class="txt txt-hint">No records found.</span>
|
||||
{#if filter?.length}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-hint btn-sm"
|
||||
on:click|preventDefault={clearFilter}
|
||||
>
|
||||
<span class="txt">Clear filter</span>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if isLoading}
|
||||
<div class="block txt-center">
|
||||
<span class="loader loader-sm active" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<button type="button" class="btn btn-transparent m-r-auto" disabled={isLoading} on:click={hide}>
|
||||
<span class="txt">Cancel</span>
|
||||
</button>
|
||||
|
||||
{#if CommonHelper.hasImageExtension(selectedFile?.name)}
|
||||
<Field class="form-field file-picker-size-select" let:uniqueId>
|
||||
<ObjectSelect
|
||||
upside
|
||||
id={uniqueId}
|
||||
items={sizeOptions}
|
||||
disabled={!canSubmit}
|
||||
bind:keyOfSelected={selectedSize}
|
||||
/>
|
||||
</Field>
|
||||
{/if}
|
||||
|
||||
<button type="button" class="btn btn-expanded" disabled={!canSubmit} on:click={submit}>
|
||||
<span class="txt">{submitText}</span>
|
||||
</button>
|
||||
</svelte:fragment>
|
||||
</OverlayPanel>
|
||||
|
||||
<RecordUpsertPanel
|
||||
bind:this={upsertPanel}
|
||||
collection={selectedCollection}
|
||||
on:save={(e) => {
|
||||
CommonHelper.removeByKey(list, "id", e.detail.record.id);
|
||||
list.unshift(e.detail.record);
|
||||
list = list;
|
||||
|
||||
const names = extractFiles(e.detail.record);
|
||||
if (names.length > 0) {
|
||||
select(e.detail.record, names[0]);
|
||||
}
|
||||
}}
|
||||
on:delete={(e) => {
|
||||
if (selectedFile?.record?.id == e.detail.id) {
|
||||
selectedFile = {};
|
||||
}
|
||||
|
||||
CommonHelper.removeByKey(list, "id", e.detail.id);
|
||||
list = list;
|
||||
}}
|
||||
/>
|
||||
@@ -11,7 +11,7 @@
|
||||
let thumbUrl = "";
|
||||
let originalUrl = "";
|
||||
let token = "";
|
||||
let isLoadingToken = false;
|
||||
let isLoadingToken = true;
|
||||
|
||||
loadFileToken();
|
||||
|
||||
@@ -76,4 +76,6 @@
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
<PreviewPopup bind:this={previewPopup} />
|
||||
{#if hasPreview}
|
||||
<PreviewPopup bind:this={previewPopup} />
|
||||
{/if}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const sortRegex = /^([\+\-])?(\w+)$/;
|
||||
const perPage = 45;
|
||||
const perPage = 40;
|
||||
|
||||
export let collection;
|
||||
export let sort = "";
|
||||
@@ -186,10 +186,10 @@
|
||||
const currentYieldId = ++yieldedRecordsId;
|
||||
while (result.items?.length) {
|
||||
if (yieldedRecordsId != currentYieldId) {
|
||||
break; // new yeild has been started
|
||||
break; // new yield has been started
|
||||
}
|
||||
|
||||
records = records.concat(result.items.splice(0, 15));
|
||||
records = records.concat(result.items.splice(0, 20));
|
||||
|
||||
await CommonHelper.yieldToMain();
|
||||
}
|
||||
@@ -550,7 +550,7 @@
|
||||
<tr>
|
||||
<td colspan="99" class="txt-center">
|
||||
<button
|
||||
class="btn btn-expanded-lg btn-secondary"
|
||||
class="btn btn-expanded-lg btn-secondary btn-horizontal-sticky"
|
||||
disabled={isLoading}
|
||||
class:btn-loading={isLoading}
|
||||
on:click|preventDefault={() => load(currentPage + 1)}
|
||||
|
||||
@@ -214,7 +214,7 @@
|
||||
{#if !isView}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-transparent btn-hint p-l-sm p-r-sm"
|
||||
class="btn btn-pill btn-transparent btn-hint p-l-xs p-r-xs"
|
||||
on:click={() => upsertPanel?.show()}
|
||||
>
|
||||
<div class="txt">New record</div>
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
<script>
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import Field from "@/components/base/Field.svelte";
|
||||
import TinyMCE from "@tinymce/tinymce-svelte";
|
||||
import { onMount } from "svelte";
|
||||
import TinyMCE from "@tinymce/tinymce-svelte";
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import ApiClient from "@/utils/ApiClient";
|
||||
import Field from "@/components/base/Field.svelte";
|
||||
import RecordFilePicker from "@/components/records/RecordFilePicker.svelte";
|
||||
|
||||
export let field;
|
||||
export let value = "";
|
||||
|
||||
let picker;
|
||||
let editor;
|
||||
let mounted = false;
|
||||
let mountedTimeoutId = null;
|
||||
|
||||
@@ -44,8 +48,29 @@
|
||||
scriptSrc="{import.meta.env.BASE_URL}libs/tinymce/tinymce.min.js"
|
||||
{conf}
|
||||
bind:value
|
||||
on:init={(initEvent) => {
|
||||
editor = initEvent.detail.editor;
|
||||
editor.on("collections_file_picker", () => {
|
||||
picker?.show();
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<div class="tinymce-wrapper" />
|
||||
{/if}
|
||||
</Field>
|
||||
|
||||
<RecordFilePicker
|
||||
bind:this={picker}
|
||||
title="Select an image"
|
||||
fileTypes={["image"]}
|
||||
on:submit={(e) => {
|
||||
editor?.execCommand(
|
||||
"InsertImage",
|
||||
false,
|
||||
ApiClient.files.getUrl(e.detail.record, e.detail.name, {
|
||||
thumb: e.detail.size,
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
+154
-5
@@ -127,7 +127,7 @@ code {
|
||||
line-height: 1.379rem;
|
||||
padding: 0px 4px;
|
||||
white-space: nowrap;
|
||||
color: var(--txtPrimaryColor);
|
||||
color: inherit;
|
||||
background: var(--baseAlt2Color);
|
||||
border-radius: var(--baseRadius);
|
||||
}
|
||||
@@ -527,6 +527,8 @@ a,
|
||||
background: var(--baseAlt2Color);
|
||||
border-radius: var(--baseRadius);
|
||||
color: var(--txtPrimaryColor);
|
||||
outline-offset: -2px;
|
||||
outline: 2px solid transparent;
|
||||
@include shadowize();
|
||||
i {
|
||||
font-size: inherit;
|
||||
@@ -556,16 +558,31 @@ a,
|
||||
&.thumb-circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
&.thumb-active {
|
||||
box-shadow: 0px 0px 0px 2px var(--primaryColor);
|
||||
|
||||
// styles
|
||||
@each $name, $color in $colorsMap {
|
||||
&.thumb-#{$name} {
|
||||
outline-color: $color;
|
||||
}
|
||||
}
|
||||
}
|
||||
.handle.thumb:not(.thumb-active),
|
||||
a.thumb:not(.thumb-active) {
|
||||
transition: box-shadow var(--baseAnimationSpeed);
|
||||
cursor: pointer;
|
||||
transition: opacity var(--baseAnimationSpeed),
|
||||
outline-color var(--baseAnimationSpeed),
|
||||
transform var(--baseAnimationSpeed),
|
||||
box-shadow var(--baseAnimationSpeed);
|
||||
&:hover,
|
||||
&:focus {
|
||||
&:focus-visible,
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
@include shadowize(0px 2px 4px 1px var(--shadowColor));
|
||||
}
|
||||
&:active {
|
||||
transition-duration: 0s;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
@@ -889,3 +906,135 @@ a.thumb:not(.thumb-active) {
|
||||
border-radius: var(--baseRadius);
|
||||
border: 1px solid var(--baseAlt1Color);
|
||||
}
|
||||
|
||||
// sidebar menu
|
||||
.sidebar-menu {
|
||||
--sidebarListItemMargin: 10px;
|
||||
|
||||
z-index: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 200px;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
background: var(--baseColor);
|
||||
padding: calc(var(--baseSpacing) - 5px) 0 var(--smSpacing);
|
||||
& > * {
|
||||
padding: 0 var(--smSpacing);
|
||||
}
|
||||
.sidebar-content {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto; /* fallback */
|
||||
overflow-y: overlay;
|
||||
& > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
& > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.sidebar-footer {
|
||||
margin-top: var(--smSpacing);
|
||||
}
|
||||
.search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: auto;
|
||||
column-gap: 5px;
|
||||
margin: 0 0 var(--xsSpacing);
|
||||
color: var(--txtHintColor);
|
||||
opacity: 0.7;
|
||||
transition: opacity var(--baseAnimationSpeed),
|
||||
color var(--baseAnimationSpeed);
|
||||
input {
|
||||
border: 0;
|
||||
background: var(--baseColor);
|
||||
transition: box-shadow var(--baseAnimationSpeed),
|
||||
background var(--baseAnimationSpeed);
|
||||
}
|
||||
.btn-clear {
|
||||
margin-right: -8px;
|
||||
}
|
||||
&:hover,
|
||||
&:focus-within,
|
||||
&.active {
|
||||
opacity: 1;
|
||||
color: var(--txtPrimaryColor);
|
||||
input {
|
||||
background: var(--baseAlt2Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
.sidebar-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
width: 100%;
|
||||
margin: var(--baseSpacing) 0 var(--xsSpacing);
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
line-height: var(--smLineHeight);
|
||||
color: var(--txtHintColor);
|
||||
.label {
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
.sidebar-list-item {
|
||||
cursor: pointer;
|
||||
outline: 0;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
column-gap: 10px;
|
||||
margin: var(--sidebarListItemMargin) 0;
|
||||
padding: 3px 10px;
|
||||
font-size: var(--xlFontSize);
|
||||
min-height: var(--btnHeight);
|
||||
min-width: 0;
|
||||
color: var(--txtHintColor);
|
||||
border-radius: var(--baseRadius);
|
||||
user-select: none;
|
||||
transition: background var(--baseAnimationSpeed),
|
||||
color var(--baseAnimationSpeed);
|
||||
i {
|
||||
font-size: 18px;
|
||||
}
|
||||
.txt {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
// states
|
||||
&:focus-visible,
|
||||
&:hover,
|
||||
&:active,
|
||||
&.active {
|
||||
color: var(--txtPrimaryColor);
|
||||
background: var(--baseAlt1Color);
|
||||
}
|
||||
&:active {
|
||||
background: var(--baseAlt2Color);
|
||||
transition-duration: var(--activeAnimationSpeed);
|
||||
}
|
||||
}
|
||||
.sidebar-content-compact .sidebar-list-item {
|
||||
--sidebarListItemMargin: 5px;
|
||||
}
|
||||
|
||||
// responsive
|
||||
@media screen and (max-height: 600px) {
|
||||
--sidebarListItemMargin: 5px;
|
||||
}
|
||||
@media screen and (max-width: 1100px) {
|
||||
min-width: 190px;
|
||||
& > * {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
.file-picker-sidebar {
|
||||
flex-shrink: 0;
|
||||
width: 180px;
|
||||
text-align: right;
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
.sidebar-item {
|
||||
outline: 0;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
gap: 10px;
|
||||
font-weight: 600;
|
||||
padding: 5px 10px;
|
||||
margin: 0 0 10px;
|
||||
color: var(--txtHintColor);
|
||||
min-height: var(--btnHeight);
|
||||
border-radius: var(--baseRadius);
|
||||
word-break: break-word;
|
||||
transition: background var(--baseAnimationSpeed),
|
||||
color var(--baseAnimationSpeed);
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&:hover,
|
||||
&:focus-visible,
|
||||
&:active,
|
||||
&.active {
|
||||
color: var(--txtPrimaryColor);
|
||||
background: var(--baseAlt1Color);
|
||||
}
|
||||
&:active {
|
||||
background: var(--baseAlt2Color);
|
||||
transition-duration: var(--activeAnimationSpeed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.files-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
gap: var(--xsSpacing);
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
.list-item {
|
||||
cursor: pointer;
|
||||
outline: 0;
|
||||
transition: box-shadow var(--baseAnimationSpeed);
|
||||
&:hover {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.file-picker-size-select {
|
||||
width: 170px;
|
||||
margin: 0;
|
||||
.selected-container {
|
||||
min-height: var(--btnHeight);
|
||||
}
|
||||
}
|
||||
|
||||
.file-picker-content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.file-picker {
|
||||
display: flex;
|
||||
height: 440px;
|
||||
max-height: 100%;
|
||||
align-items: stretch;
|
||||
gap: var(--baseSpacing);
|
||||
}
|
||||
|
||||
.overlay-panel.file-picker-popup {
|
||||
width: 930px;
|
||||
}
|
||||
@@ -190,6 +190,16 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
// alignments
|
||||
&.txt-left {
|
||||
text-align: left;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
&.txt-right {
|
||||
text-align: right;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
// sizes
|
||||
&.btn-expanded {
|
||||
min-width: 150px;
|
||||
@@ -342,6 +352,13 @@ button {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scrolling helpers
|
||||
&.btn-horizontal-sticky {
|
||||
position: sticky;
|
||||
left: var(--xsSpacing);
|
||||
right: var(--xsSpacing);
|
||||
}
|
||||
}
|
||||
|
||||
.btns-group {
|
||||
@@ -1070,6 +1087,16 @@ select {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.upside {
|
||||
.selected-container.active {
|
||||
border-radius: 0 0 var(--baseRadius) var(--baseRadius);
|
||||
}
|
||||
.options-dropdown {
|
||||
border-radius: var(--baseRadius) var(--baseRadius) 0 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.field-type-select {
|
||||
@@ -1252,12 +1279,25 @@ select {
|
||||
}
|
||||
}
|
||||
body {
|
||||
.tox .tox-dialog {
|
||||
border: 0;
|
||||
border-radius: var(--baseRadius);
|
||||
}
|
||||
.tox .tox-dialog-wrap__backdrop {
|
||||
background: var(--overlayColor);
|
||||
}
|
||||
.tox .tox-tbtn {
|
||||
height: 30px;
|
||||
svg {
|
||||
transform: scale(0.85);
|
||||
}
|
||||
}
|
||||
.tox .tox-collection__item-checkmark,
|
||||
.tox .tox-collection__item-icon {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
transform: scale(0.85);
|
||||
}
|
||||
.tox .tox-tbtn:not(.tox-tbtn--select) {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ table {
|
||||
max-width: 300px;
|
||||
}
|
||||
.col-type-editor {
|
||||
min-width: 250px;
|
||||
min-width: 300px;
|
||||
}
|
||||
.col-type-select {
|
||||
min-width: 150px;
|
||||
@@ -168,6 +168,9 @@ table {
|
||||
.col-field-updated {
|
||||
@extend %minColWidth;
|
||||
}
|
||||
.col-field-id {
|
||||
min-width: 175px;
|
||||
}
|
||||
|
||||
tr {
|
||||
outline: 0;
|
||||
|
||||
@@ -41,3 +41,5 @@
|
||||
@import 'docs_panel';
|
||||
|
||||
@import 'schema_field';
|
||||
|
||||
@import 'file_picker';
|
||||
|
||||
@@ -845,6 +845,7 @@ export default class CommonHelper {
|
||||
* @return {Boolean}
|
||||
*/
|
||||
static hasImageExtension(filename) {
|
||||
filename = filename || "";
|
||||
return !!imageExtensions.find((ext) => filename.toLowerCase().endsWith(ext));
|
||||
}
|
||||
|
||||
@@ -855,6 +856,7 @@ export default class CommonHelper {
|
||||
* @return {Boolean}
|
||||
*/
|
||||
static hasVideoExtension(filename) {
|
||||
filename = filename || "";
|
||||
return !!videoExtensions.find((ext) => filename.toLowerCase().endsWith(ext));
|
||||
}
|
||||
|
||||
@@ -865,6 +867,7 @@ export default class CommonHelper {
|
||||
* @return {Boolean}
|
||||
*/
|
||||
static hasAudioExtension(filename) {
|
||||
filename = filename || "";
|
||||
return !!audioExtensions.find((ext) => filename.toLowerCase().endsWith(ext));
|
||||
}
|
||||
|
||||
@@ -875,6 +878,7 @@ export default class CommonHelper {
|
||||
* @return {Boolean}
|
||||
*/
|
||||
static hasDocumentExtension(filename) {
|
||||
filename = filename || "";
|
||||
return !!documentExtensions.find((ext) => filename.toLowerCase().endsWith(ext));
|
||||
}
|
||||
|
||||
@@ -1396,7 +1400,7 @@ export default class CommonHelper {
|
||||
"codesample",
|
||||
"directionality",
|
||||
],
|
||||
toolbar: "styles | alignleft aligncenter alignright | bold italic forecolor backcolor | bullist numlist | link image table codesample direction | code fullscreen",
|
||||
toolbar: "styles | alignleft aligncenter alignright | bold italic forecolor backcolor | bullist numlist | link image_picker table codesample direction | code fullscreen",
|
||||
paste_postprocess: (editor, args) => {
|
||||
cleanupPastedNode(args.node);
|
||||
},
|
||||
@@ -1464,7 +1468,7 @@ export default class CommonHelper {
|
||||
icon: "ltr",
|
||||
onAction: () => {
|
||||
window?.localStorage?.setItem(lastDirectionKey, "ltr");
|
||||
tinymce.activeEditor.execCommand("mceDirectionLTR");
|
||||
editor.execCommand("mceDirectionLTR");
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1473,7 +1477,7 @@ export default class CommonHelper {
|
||||
icon: "rtl",
|
||||
onAction: () => {
|
||||
window?.localStorage?.setItem(lastDirectionKey, "rtl");
|
||||
tinymce.activeEditor.execCommand("mceDirectionRTL");
|
||||
editor.execCommand("mceDirectionRTL");
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -1481,6 +1485,32 @@ export default class CommonHelper {
|
||||
callback(items);
|
||||
}
|
||||
});
|
||||
|
||||
editor.ui.registry.addMenuButton("image_picker", {
|
||||
icon: "image",
|
||||
fetch: (callback) => {
|
||||
const items = [
|
||||
{
|
||||
type: "menuitem",
|
||||
text: "From collection",
|
||||
icon: "gallery",
|
||||
onAction: () => {
|
||||
editor.dispatch("collections_file_picker", {})
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "menuitem",
|
||||
text: "Inline",
|
||||
icon: "browse",
|
||||
onAction: () => {
|
||||
editor.execCommand("mceImage");
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
callback(items);
|
||||
}
|
||||
})
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user