initial v0.8 pre-release

This commit is contained in:
Gani Georgiev
2022-10-30 10:28:14 +02:00
parent 9cbb2e750e
commit 90dba45d7c
388 changed files with 21580 additions and 13603 deletions
+30 -5
View File
@@ -10,10 +10,13 @@
let classes = "";
export { classes as class }; // export reserved keyword
export let draggable = false;
export let active = false;
export let interactive = true;
export let single = false; // ensures that only one accordion is expanded in its given parent container
let isDragOver = false;
$: if (active) {
clearTimeout(expandTimeoutId);
expandTimeoutId = setTimeout(() => {
@@ -49,10 +52,10 @@
}
export function collapseSiblings() {
if (single && accordionElem.parentElement) {
const handlers = accordionElem.parentElement.querySelectorAll(
".accordion.active .accordion-header.interactive"
);
if (single && accordionElem.closest(".accordions")) {
const handlers = accordionElem
.closest(".accordions")
.querySelectorAll(".accordion.active .accordion-header.interactive");
for (const handler of handlers) {
handler.click(); // @todo consider using store or other more reliable approach
}
@@ -78,14 +81,36 @@
<div
bind:this={accordionElem}
tabindex={interactive ? 0 : -1}
class="accordion {classes}"
class="accordion {isDragOver ? 'drag-over' : ''} {classes}"
class:active
on:keydown|self={keyToggle}
>
<header
class="accordion-header"
{draggable}
class:interactive
on:click|preventDefault={() => interactive && toggle()}
on:drop|preventDefault={(e) => {
if (draggable) {
isDragOver = false;
collapseSiblings();
dispatch("drop", e);
}
}}
on:dragstart={(e) => draggable && dispatch("dragstart", e)}
on:dragenter={(e) => {
if (draggable) {
isDragOver = true;
dispatch("dragenter", e);
}
}}
on:dragleave={(e) => {
if (draggable) {
isDragOver = false;
dispatch("dragleave", e);
}
}}
on:dragover|preventDefault
>
<slot name="header" {active} />
</header>
+1 -2
View File
@@ -1,5 +1,4 @@
<script>
// @todo consider replacing with readonly CodeEditor
import Prism from "prismjs";
import "prismjs/plugins/normalize-whitespace/prism-normalize-whitespace.js";
import "prismjs/components/prism-dart.js";
@@ -40,7 +39,7 @@
code {
display: block;
width: 100%;
padding: var(--xsSpacing);
padding: 10px 15px;
white-space: pre-wrap;
word-break: break-word;
}
+6 -4
View File
@@ -5,8 +5,10 @@
let confirmationPopup;
let isConfirmationBusy = false;
let confirmed = false;
$: if ($confirmation?.text) {
confirmed = false;
confirmationPopup?.show();
}
</script>
@@ -19,10 +21,11 @@
btnClose={false}
popup
on:hide={async () => {
if ($confirmation?.noCallback) {
if (!confirmed && $confirmation?.noCallback) {
$confirmation.noCallback();
}
await tick();
confirmed = false;
resetConfirmation();
}}
>
@@ -36,9 +39,7 @@
class="btn btn-secondary btn-expanded-sm"
disabled={isConfirmationBusy}
on:click={() => {
if ($confirmation?.noCallback) {
$confirmation.noCallback();
}
confirmed = false;
confirmationPopup?.hide();
}}
>
@@ -55,6 +56,7 @@
await Promise.resolve($confirmation.yesCallback());
isConfirmationBusy = false;
}
confirmed = true;
confirmationPopup?.hide();
}}
>
@@ -185,17 +185,23 @@
prefix + "updated",
];
if (collection.isAuth) {
result.push(prefix + "username");
result.push(prefix + "email");
result.push(prefix + "emailVisibility");
result.push(prefix + "verified");
}
for (const field of collection.schema) {
const key = prefix + field.name;
result.push(key);
if (field.type === "relation" && field.options.collectionId) {
const subKeys = getCollectionFieldKeys(field.options.collectionId, key + ".", level + 1);
if (subKeys.length) {
result = result.concat(subKeys);
} else {
result.push(key);
}
} else {
result.push(key);
}
}
@@ -217,28 +223,27 @@
result.push("@request.method");
result.push("@request.query.");
result.push("@request.data.");
result.push("@request.user.id");
result.push("@request.user.email");
result.push("@request.user.verified");
result.push("@request.user.created");
result.push("@request.user.updated");
result.push("@request.auth.");
result.push("@request.auth.id");
result.push("@request.auth.collectionId");
result.push("@request.auth.collectionName");
result.push("@request.auth.username");
result.push("@request.auth.email");
result.push("@request.auth.emailVisibility");
result.push("@request.auth.verified");
result.push("@request.auth.created");
result.push("@request.auth.updated");
}
// add @collections and @request.user.profile keys
// add @collections.* keys
if (includeRequestKeys || includeIndirectCollectionsKeys) {
for (const collection of mergedCollections) {
let prefix = "";
if (collection.name === import.meta.env.PB_PROFILE_COLLECTION) {
if (!includeRequestKeys) {
continue;
}
prefix = "@request.user.profile.";
} else {
if (!includeIndirectCollectionsKeys) {
continue;
}
prefix = "@collection." + collection.name + ".";
if (!includeIndirectCollectionsKeys) {
continue;
}
prefix = "@collection." + collection.name + ".";
const keys = getCollectionFieldKeys(collection.name, prefix);
for (const key of keys) {
@@ -258,8 +263,8 @@
// Returns object with all the completions matching the context.
function completions(context) {
let word = context.matchBefore(/[\@\w\.]*/);
if (word.from == word.to && !context.explicit) {
let word = context.matchBefore(/[\'\"\@\w\.]*/);
if (word && word.from == word.to && !context.explicit) {
return null;
}
@@ -269,18 +274,8 @@
options.push({ label: "@collection.*", apply: "@collection." });
}
const skipFields = [
"@request.user.profile.userId",
"@request.user.profile.created",
"@request.user.profile.updated",
];
const keys = getAllKeys(!disableRequestKeys, !disableRequestKeys && word.text.startsWith("@c"));
for (const key of keys) {
if (skipFields.includes(key)) {
continue;
}
options.push({
label: key.endsWith(".") ? key + "*" : key,
apply: key,
+19 -4
View File
@@ -1,13 +1,28 @@
<script>
export let date = "";
// note: manual trim the ms without converting to DateTime
// to help improving the rendering performance in large data sets
$: shortDate = date.length > 19 ? date.substring(0, 19) : date;
$: dateOnly = date ? date.substring(0, 10) : null;
$: timeOnly = date ? date.substring(10, 19) : null;
</script>
{#if date}
<span class="txt">{shortDate} UTC</span>
<div class="datetime">
<div class="date">{dateOnly}</div>
<div class="time">{timeOnly} UTC</div>
</div>
{:else}
<span class="txt txt-hint">N/A</span>
{/if}
<style>
.datetime {
width: 100%;
display: block;
line-height: var(--smLineHeight);
}
.time {
font-size: var(--smFontSize);
color: var(--txtHintColor);
}
</style>
@@ -0,0 +1,81 @@
<script>
import { onMount } from "svelte";
let classes = "";
export { classes as class }; // export reserved keyword
let wrapper = null;
let scrollClasses = "";
let scrollTimeoutId = null;
let observer;
export function refresh() {
if (!wrapper) {
return;
}
clearTimeout(scrollTimeoutId);
scrollTimeoutId = setTimeout(() => {
const offsetWidth = wrapper.offsetWidth;
const scrollWidth = wrapper.scrollWidth;
if (scrollWidth - offsetWidth) {
scrollClasses = "scrollable";
if (wrapper.scrollLeft === 0) {
scrollClasses += " scroll-start";
} else if (wrapper.scrollLeft + offsetWidth == scrollWidth) {
scrollClasses += " scroll-end";
}
} else {
scrollClasses = "";
}
}, 100);
}
onMount(() => {
refresh();
observer = new MutationObserver(() => {
refresh();
});
observer.observe(wrapper, {
attributeFilter: ["width"],
childList: true,
subtree: true,
});
return () => {
observer?.disconnect();
clearTimeout(scrollTimeoutId);
};
});
</script>
<svelte:window on:resize={refresh} />
<div class="horizontal-scroller-wrapper">
<slot name="before" />
<div bind:this={wrapper} class="horizontal-scroller {classes} {scrollClasses}" on:scroll={refresh}>
<slot />
</div>
<slot name="after" />
</div>
<style>
.horizontal-scroller {
width: auto;
overflow-x: auto;
}
.horizontal-scroller-wrapper {
position: relative;
}
:global(.horizontal-scroller-wrapper .columns-dropdown) {
top: 40px;
z-index: 100;
max-height: 340px;
}
</style>
+1 -1
View File
@@ -24,7 +24,7 @@
passwordConfirm,
});
await ApiClient.admins.authViaEmail(email, password);
await ApiClient.admins.authWithPassword(email, password);
dispatch("submit");
} catch (err) {
+7 -1
View File
@@ -86,10 +86,12 @@
oldFocusedElem = document.activeElement;
wrapper?.focus();
dispatch("show");
document.body.classList.add("overlay-active");
} else {
clearTimeout(contentScrollThrottle);
oldFocusedElem?.focus();
dispatch("hide");
document.body.classList.remove("overlay-active");
}
await tick();
@@ -179,13 +181,17 @@
// ensures that no artifacts remains
// (currently there is a bug with svelte transition)
wrapper?.classList?.add("hidden");
setTimeout(() => {
wrapper?.remove();
}, 0);
};
});
</script>
<svelte:window on:resize={handleResize} on:keydown={handleEscPress} />
<div class="overlay-panel-wrapper" bind:this={wrapper}>
<div bind:this={wrapper} class="overlay-panel-wrapper">
{#if active}
<div class="overlay-panel-container" class:padded={popup} class:active>
<div
+1 -1
View File
@@ -15,7 +15,7 @@
href={import.meta.env.PB_RELEASES}
class="inline-flex flex-gap-5"
target="_blank"
rel="noopener"
rel="noopener noreferrer"
title="Releases"
>
<span class="txt">PocketBase {import.meta.env.PB_VERSION}</span>
+9 -3
View File
@@ -21,15 +21,21 @@
<OverlayPanel bind:this={panel} class="image-preview" btnClose={false} popup on:show on:hide>
<svelte:fragment slot="header">
<div class="overlay-close" on:click|preventDefault={hide}>
<button type="button" class="overlay-close" on:click|preventDefault={hide}>
<i class="ri-close-line" />
</div>
</button>
</svelte:fragment>
<img src={url} alt="Preview {url}" />
<svelte:fragment slot="footer">
<a href={url} title="Download" class="link-hint txt-ellipsis">
<a
href={url}
title="Download"
target="_blank"
rel="noreferrer noopener"
class="link-hint txt-ellipsis"
>
{url.substring(url.lastIndexOf("/") + 1)}
</a>
<div class="flex-fill" />
+2 -2
View File
@@ -20,7 +20,7 @@
refreshTimeoutId = setTimeout(() => {
refreshTimeoutId = null;
tooltipData = oldTooltipData;
}, 200);
}, 150);
}
onMount(() => {
@@ -45,6 +45,6 @@
}
}
.btn.refreshing i {
animation: refresh 200ms ease-out;
animation: refresh 150ms ease-out;
}
</style>
+2 -2
View File
@@ -53,8 +53,8 @@
});
</script>
<div class="searchbar-wrapper" on:click|stopPropagation>
<form class="searchbar" on:submit|preventDefault={submit}>
<div class="searchbar-wrapper">
<form class="searchbar" on:click|stopPropagation on:submit|preventDefault={submit}>
<label for={uniqueId} class="m-l-10 txt-xl">
<i class="ri-search-line" />
</label>
+9 -5
View File
@@ -194,13 +194,15 @@
});
</script>
<div class="select {classes}" class:multiple class:disabled bind:this={container}>
<div tabindex={disabled ? "-1" : "0"} class="selected-container" class:disabled bind:this={labelDiv}>
<div bind:this={container} class="select {classes}" class:multiple class:disabled>
<div bind:this={labelDiv} tabindex={disabled ? "-1" : "0"} class="selected-container" class:disabled>
{#each CommonHelper.toArray(selected) as item}
<div class="option">
{#if labelComponent}
<svelte:component this={labelComponent} {item} {...labelComponentProps} />
{:else}<span class="txt">{item}</span>{/if}
{:else}
<span class="txt">{item}</span>
{/if}
{#if multiple || toggle}
<span
@@ -213,17 +215,19 @@
{/if}
</div>
{:else}
<div class="txt-placeholder">{selectPlaceholder}</div>
<div class="block txt-placeholder" class:link-hint={!disabled}>
{selectPlaceholder}
</div>
{/each}
</div>
{#if !disabled}
<Toggler
bind:this={toggler}
class="dropdown dropdown-block options-dropdown dropdown-left"
trigger={labelDiv}
on:show={onDropdownShow}
on:hide
bind:this={toggler}
>
{#if searchable}
<div class="form-field form-field-sm options-search">
+2 -2
View File
@@ -29,9 +29,9 @@
<div class="content">{toast.message}</div>
<div class="close" on:click|preventDefault={() => removeToast(toast)}>
<button type="button" class="close" on:click|preventDefault={() => removeToast(toast)}>
<i class="ri-close-line" />
</div>
</button>
</div>
{/each}
</div>
+39 -14
View File
@@ -9,15 +9,20 @@
let classes = "";
export { classes as class }; // export reserved keyword
let container;
let container = undefined;
let activeTrigger = undefined;
const dispatch = createEventDispatcher();
$: if (container) {
bindTrigger(trigger);
}
$: if (active) {
trigger?.classList?.add("active");
activeTrigger?.classList?.add("active");
dispatch("show");
} else {
trigger?.classList?.remove("active");
activeTrigger?.classList?.remove("active");
dispatch("hide");
}
@@ -42,7 +47,7 @@
!container ||
elem.classList.contains(closableClass) ||
// is the trigger itself (or a direct child)
(trigger?.contains(elem) && !container.contains(elem)) ||
(activeTrigger?.contains(elem) && !container.contains(elem)) ||
// is closable toggler child
(container.contains(elem) && elem.closest && elem.closest("." + closableClass))
);
@@ -51,6 +56,8 @@
function handleClickToggle(e) {
if (!active || isClosable(e.target)) {
e.preventDefault();
e.stopPropagation();
toggle();
}
}
@@ -67,13 +74,13 @@
}
function handleOutsideClick(e) {
if (active && !container?.contains(e.target) && !trigger?.contains(e.target)) {
if (active && !container?.contains(e.target) && !activeTrigger?.contains(e.target)) {
hide();
}
}
function handleEscPress(e) {
if (active && escClose && e.code == "Escape") {
if (active && escClose && e.code === "Escape") {
e.preventDefault();
hide();
}
@@ -83,16 +90,34 @@
return handleOutsideClick(e);
}
function bindTrigger(newTrigger) {
cleanup();
activeTrigger = newTrigger || container?.parentNode;
if (!activeTrigger) {
return;
}
container?.addEventListener("click", handleClickToggle);
activeTrigger.addEventListener("click", handleClickToggle);
activeTrigger.addEventListener("keydown", handleKeydownToggle);
}
function cleanup() {
if (!activeTrigger) {
return;
}
container?.removeEventListener("click", handleClickToggle);
activeTrigger.removeEventListener("click", handleClickToggle);
activeTrigger.removeEventListener("keydown", handleKeydownToggle);
}
onMount(() => {
trigger = trigger || container.parentNode;
bindTrigger();
trigger.addEventListener("click", handleClickToggle);
trigger.addEventListener("keydown", handleKeydownToggle);
return () => {
trigger.removeEventListener("click", handleClickToggle);
trigger.removeEventListener("keydown", handleKeydownToggle);
};
return () => cleanup();
});
</script>