merge v0.23.0-rc changes
This commit is contained in:
@@ -107,7 +107,7 @@
|
||||
</button>
|
||||
|
||||
{#if active}
|
||||
<div class="accordion-content" transition:slide={{ duration: 150 }}>
|
||||
<div class="accordion-content" transition:slide={{ delay: 10, duration: 150 }}>
|
||||
<slot />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<script>
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
|
||||
export let value = "";
|
||||
export let options = []; // [{label: "Option 1", value: "opt1"}, {label: "Option 2", value: "opt2"}, ...]
|
||||
|
||||
const uniqueId = "list_" + CommonHelper.randomString(5);
|
||||
</script>
|
||||
|
||||
<input
|
||||
type={$$restProps.type || "text"}
|
||||
list={uniqueId}
|
||||
{value}
|
||||
on:input={(e) => {
|
||||
value = e.target.value;
|
||||
}}
|
||||
{...$$restProps}
|
||||
/>
|
||||
|
||||
<datalist id={uniqueId}>
|
||||
{#each options as opt}
|
||||
<option value={opt.value}>{opt.label || ""}</option>
|
||||
{/each}
|
||||
</datalist>
|
||||
@@ -34,7 +34,7 @@
|
||||
JSON.stringify({
|
||||
index: i,
|
||||
group: group,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
dispatch("drag", e);
|
||||
@@ -79,6 +79,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
draggable={!disabled}
|
||||
class="draggable"
|
||||
@@ -102,7 +103,7 @@
|
||||
|
||||
<style>
|
||||
.draggable {
|
||||
user-select: none;
|
||||
user-select: text;
|
||||
outline: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
addLabelListeners();
|
||||
}
|
||||
|
||||
$: if (editor && baseCollection?.schema) {
|
||||
$: if (editor && baseCollection?.fields) {
|
||||
editor.dispatch({
|
||||
effects: [langCompartment.reconfigure(ruleLang())],
|
||||
});
|
||||
@@ -172,7 +172,7 @@
|
||||
|
||||
// Return a collection keys hash string that can be used to compare with previous states.
|
||||
function getCollectionKeysChangeHash(collection) {
|
||||
return JSON.stringify([collection?.name, collection?.type, collection?.schema]);
|
||||
return JSON.stringify([collection?.name, collection?.type, collection?.fields]);
|
||||
}
|
||||
|
||||
// Merge the base collection in a new list with the provided collections.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import ApiClient from "@/utils/ApiClient";
|
||||
import { addInfoToast } from "@/stores/toasts";
|
||||
import { confirm } from "@/stores/confirmation";
|
||||
import Field from "@/components/base/Field.svelte";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
@@ -9,22 +11,27 @@
|
||||
let password = "";
|
||||
let passwordConfirm = "";
|
||||
let isLoading = false;
|
||||
let isUploading = false;
|
||||
|
||||
let backupFileInput;
|
||||
|
||||
$: isBusy = isLoading || isUploading;
|
||||
|
||||
async function submit() {
|
||||
if (isLoading) {
|
||||
if (isBusy) {
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading = true;
|
||||
|
||||
try {
|
||||
await ApiClient.admins.create({
|
||||
await ApiClient.collection("_superusers").create({
|
||||
email,
|
||||
password,
|
||||
passwordConfirm,
|
||||
});
|
||||
|
||||
await ApiClient.admins.authWithPassword(email, password);
|
||||
await ApiClient.collection("_superusers").authWithPassword(email, password);
|
||||
|
||||
dispatch("submit");
|
||||
} catch (err) {
|
||||
@@ -33,11 +40,61 @@
|
||||
|
||||
isLoading = false;
|
||||
}
|
||||
|
||||
function resetSelectedBackupFile() {
|
||||
if (backupFileInput) {
|
||||
backupFileInput.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
function uploadBackupConfirm(file) {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
confirm(
|
||||
`Note that we don't perform validations for the uploaded backup files. Proceed with caution and only if you trust the file source.\n\n` +
|
||||
`Do you really want to upload and initialize "${file.name}"?`,
|
||||
() => {
|
||||
uploadBackup(file);
|
||||
},
|
||||
() => {
|
||||
resetSelectedBackupFile();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async function uploadBackup(file) {
|
||||
if (!file || isBusy) {
|
||||
return;
|
||||
}
|
||||
|
||||
isUploading = true;
|
||||
|
||||
try {
|
||||
await ApiClient.backups.upload({ file: file });
|
||||
|
||||
await ApiClient.backups.restore(file.name);
|
||||
|
||||
addInfoToast("Please wait while extracting the uploaded archive!");
|
||||
|
||||
// optimistic restore completion
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
|
||||
dispatch("submit");
|
||||
} catch (err) {
|
||||
ApiClient.error(err);
|
||||
}
|
||||
|
||||
resetSelectedBackupFile();
|
||||
|
||||
isUploading = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<form class="block" autocomplete="off" on:submit|preventDefault={submit}>
|
||||
<div class="content txt-center m-b-base">
|
||||
<h4>Create your first admin account in order to continue</h4>
|
||||
<h4>Create your first superuser account in order to continue</h4>
|
||||
</div>
|
||||
|
||||
<Field class="form-field required" name="email" let:uniqueId>
|
||||
@@ -56,7 +113,7 @@
|
||||
bind:value={password}
|
||||
required
|
||||
/>
|
||||
<div class="help-block">Minimum 10 characters.</div>
|
||||
<div class="help-block">Recommended at least 10 characters.</div>
|
||||
</Field>
|
||||
|
||||
<Field class="form-field required" name="passwordConfirm" let:uniqueId>
|
||||
@@ -67,10 +124,34 @@
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-lg btn-block btn-next"
|
||||
class:btn-disabled={isLoading}
|
||||
class:btn-disabled={isBusy}
|
||||
class:btn-loading={isLoading}
|
||||
>
|
||||
<span class="txt">Create and login</span>
|
||||
<span class="txt">Create superuser and login</span>
|
||||
<i class="ri-arrow-right-line" />
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<hr />
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<label
|
||||
for="backupFileInput"
|
||||
class="btn btn-lg btn-hint btn-transparent btn-block"
|
||||
class:btn-disabled={isBusy}
|
||||
class:btn-loading={isUploading}
|
||||
>
|
||||
<i class="ri-upload-cloud-line" />
|
||||
<span class="txt">Or initialize from backup</span>
|
||||
</label>
|
||||
<input
|
||||
bind:this={backupFileInput}
|
||||
id="backupFileInput"
|
||||
type="file"
|
||||
class="hidden"
|
||||
accept=".zip"
|
||||
on:change={(e) => {
|
||||
uploadBackupConfirm(e.target?.files?.[0]);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
<script>
|
||||
import tooltip from "@/actions/tooltip";
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
|
||||
const detailedDateFormat = "yyyy-MM-dd HH:mm:ss.SSS";
|
||||
|
||||
export let model;
|
||||
|
||||
let tooltipDates = [];
|
||||
|
||||
$: if (model) {
|
||||
refreshTooltipDates();
|
||||
}
|
||||
|
||||
function refreshTooltipDates() {
|
||||
tooltipDates = [];
|
||||
|
||||
if (model.created) {
|
||||
tooltipDates.push(
|
||||
"Created: " + CommonHelper.formatToLocalDate(model.created, detailedDateFormat) + " Local"
|
||||
);
|
||||
}
|
||||
|
||||
if (model.updated) {
|
||||
tooltipDates.push(
|
||||
"Updated: " + CommonHelper.formatToLocalDate(model.updated, detailedDateFormat) + " Local"
|
||||
);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<i
|
||||
class="ri-calendar-event-line txt-disabled"
|
||||
use:tooltip={{
|
||||
text: tooltipDates.join("\n"),
|
||||
position: "left",
|
||||
}}
|
||||
/>
|
||||
@@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import Select from "@/components/base/Select.svelte";
|
||||
import BaseSelectOption from "@/components/base/BaseSelectOption.svelte";
|
||||
import Select from "@/components/base/Select.svelte";
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
|
||||
// original select props
|
||||
export let items = [];
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import Dragline from "@/components/base/Dragline.svelte";
|
||||
|
||||
const widthStorageKey = "@adminSidebarWidth";
|
||||
const widthStorageKey = "@superuserSidebarWidth";
|
||||
|
||||
let classes = "";
|
||||
export { classes as class }; // export reserved keyword
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script>
|
||||
import { superuser } from "@/stores/superuser";
|
||||
|
||||
export let center = false;
|
||||
|
||||
let classes = "";
|
||||
@@ -13,13 +15,15 @@
|
||||
<footer class="page-footer">
|
||||
<slot name="footer" />
|
||||
|
||||
<a href={import.meta.env.PB_DOCS_URL} target="_blank" rel="noopener noreferrer">
|
||||
<i class="ri-book-open-line txt-sm" />
|
||||
<span class="txt">Docs</span>
|
||||
</a>
|
||||
<span class="delimiter">|</span>
|
||||
<a href={import.meta.env.PB_RELEASES} target="_blank" rel="noopener noreferrer" title="Releases">
|
||||
<span class="txt">PocketBase {import.meta.env.PB_VERSION}</span>
|
||||
</a>
|
||||
{#if $superuser?.id}
|
||||
<a href={import.meta.env.PB_DOCS_URL} target="_blank" rel="noopener noreferrer">
|
||||
<i class="ri-book-open-line txt-sm" />
|
||||
<span class="txt">Docs</span>
|
||||
</a>
|
||||
<span class="delimiter">|</span>
|
||||
<a href={import.meta.env.PB_RELEASES} target="_blank" rel="noopener noreferrer" title="Releases">
|
||||
<span class="txt">PocketBase {import.meta.env.PB_VERSION}</span>
|
||||
</a>
|
||||
{/if}
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
@@ -2,35 +2,32 @@
|
||||
import { tick } from "svelte";
|
||||
import tooltip from "@/actions/tooltip";
|
||||
|
||||
export let mask = false;
|
||||
export let value = "";
|
||||
export let mask = "******";
|
||||
|
||||
let inputElem;
|
||||
let locked = false;
|
||||
|
||||
$: locked = value === mask;
|
||||
|
||||
async function unlock() {
|
||||
value = "";
|
||||
locked = false;
|
||||
mask = false;
|
||||
await tick();
|
||||
inputElem?.focus();
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if locked}
|
||||
{#if mask}
|
||||
<div class="form-field-addon">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-transparent btn-circle"
|
||||
use:tooltip={{ position: "left", text: "Set new value" }}
|
||||
on:click={() => unlock()}
|
||||
on:click|preventDefault={unlock}
|
||||
>
|
||||
<i class="ri-key-line" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<input readonly type="text" placeholder={mask} {...$$restProps} />
|
||||
<input readonly type="text" placeholder="******" {...$$restProps} />
|
||||
{:else}
|
||||
<input bind:this={inputElem} bind:value type="password" autocomplete="new-password" {...$$restProps} />
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<script>
|
||||
import CodeBlock from "@/components/base/CodeBlock.svelte";
|
||||
|
||||
const SDK_PREFERENCE_KEY = "pb_sdk_preference";
|
||||
|
||||
let classes = "m-b-sm";
|
||||
export { classes as class }; // export reserved keyword
|
||||
|
||||
export let js = "";
|
||||
export let dart = "";
|
||||
|
||||
let activeTab = localStorage.getItem(SDK_PREFERENCE_KEY) || "javascript";
|
||||
|
||||
$: if (activeTab) {
|
||||
// store user preference
|
||||
localStorage.setItem(SDK_PREFERENCE_KEY, activeTab);
|
||||
}
|
||||
|
||||
$: sdkExamples = [
|
||||
{
|
||||
title: "JavaScript",
|
||||
language: "javascript",
|
||||
content: js,
|
||||
url: import.meta.env.PB_JS_SDK_URL,
|
||||
},
|
||||
{
|
||||
title: "Dart",
|
||||
language: "dart",
|
||||
content: dart,
|
||||
url: import.meta.env.PB_DART_SDK_URL,
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<div class="tabs sdk-tabs {classes}">
|
||||
<div class="tabs-header compact combined left">
|
||||
{#each sdkExamples as example (example.language)}
|
||||
<button
|
||||
class="tab-item"
|
||||
class:active={activeTab === example.language}
|
||||
on:click={() => (activeTab = example.language)}
|
||||
>
|
||||
<div class="txt">{example.title}</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="tabs-content">
|
||||
{#each sdkExamples as example (example.language)}
|
||||
<div class="tab-item" class:active={activeTab === example.language}>
|
||||
<CodeBlock language={example.language} content={example.content} />
|
||||
<div class="txt-right">
|
||||
<em class="txt-sm txt-hint">
|
||||
<a href={example.url} target="_blank" rel="noopener noreferrer">
|
||||
{example.title} SDK
|
||||
</a>
|
||||
</em>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.sdk-tabs .tabs-header .tab-item {
|
||||
min-width: 100px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import { createEventDispatcher, onMount } from "svelte";
|
||||
import { fly } from "svelte/transition";
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const uniqueId = "search_" + CommonHelper.randomString(7);
|
||||
@@ -10,7 +10,7 @@
|
||||
export let placeholder = 'Search term or filter like created > "2022-01-01"...';
|
||||
|
||||
// autocomplete filter component fields
|
||||
export let autocompleteCollection = CommonHelper.initCollection();
|
||||
export let autocompleteCollection = null;
|
||||
export let extraAutocompleteKeys = [];
|
||||
|
||||
let filterComponent;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script>
|
||||
import { onMount, createEventDispatcher } from "svelte";
|
||||
import tooltip from "@/actions/tooltip";
|
||||
import Toggler from "@/components/base/Toggler.svelte";
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
export let id = "";
|
||||
export let noOptionsText = "No options found";
|
||||
@@ -13,7 +13,8 @@
|
||||
export let disabled = false;
|
||||
export let readonly = false;
|
||||
export let upside = false;
|
||||
export let selected = multiple ? [] : undefined;
|
||||
export let zeroFunc = () => (multiple ? [] : undefined);
|
||||
export let selected = zeroFunc();
|
||||
export let toggle = multiple; // toggle option on click
|
||||
export let closable = true; // close the dropdown on option select/deselect
|
||||
export let labelComponent = undefined; // custom component to use for each selected option label
|
||||
@@ -23,6 +24,8 @@
|
||||
export let searchable = false; // whether to show the dropdown options search input
|
||||
export let searchFunc = undefined; // custom search option filter: `function(item, searchTerm):boolean`
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let classes = "";
|
||||
export { classes as class }; // export reserved keyword
|
||||
|
||||
@@ -54,9 +57,11 @@
|
||||
let normalized = CommonHelper.toArray(selected);
|
||||
if (CommonHelper.inArray(normalized, item)) {
|
||||
CommonHelper.removeByValue(normalized, item);
|
||||
selected = normalized;
|
||||
selected = multiple ? normalized : normalized?.[0] || zeroFunc();
|
||||
}
|
||||
|
||||
dispatch("change", { selected });
|
||||
|
||||
// emulate native change event
|
||||
container?.dispatchEvent(new CustomEvent("change", { detail: selected, bubbles: true }));
|
||||
}
|
||||
@@ -71,6 +76,8 @@
|
||||
selected = item;
|
||||
}
|
||||
|
||||
dispatch("change", { selected });
|
||||
|
||||
// emulate native change event
|
||||
container?.dispatchEvent(new CustomEvent("change", { detail: selected, bubbles: true }));
|
||||
}
|
||||
@@ -80,7 +87,12 @@
|
||||
}
|
||||
|
||||
export function reset() {
|
||||
selected = multiple ? [] : undefined;
|
||||
selected = zeroFunc();
|
||||
|
||||
dispatch("change", { selected });
|
||||
|
||||
// emulate native change event
|
||||
container?.dispatchEvent(new CustomEvent("change", { detail: selected, bubbles: true }));
|
||||
}
|
||||
|
||||
export function showDropdown() {
|
||||
|
||||
Reference in New Issue
Block a user