This commit is contained in:
2023-07-11 16:37:04 +10:00
parent 2dbe9955d9
commit d345be889e
8 changed files with 645 additions and 451 deletions

View File

@@ -767,7 +767,7 @@ const setLink = () => {
}; };
const setImage = async () => { const setImage = async () => {
let result = await openDialog(SMDialogMedia); let result = await openDialog(SMDialogMedia, { allowUpload: true });
if (result) { if (result) {
const mediaResult = result as Media; const mediaResult = result as Media;
editor.value editor.value
@@ -775,6 +775,8 @@ const setImage = async () => {
.focus() .focus()
.setImage({ .setImage({
src: mediaResult.url, src: mediaResult.url,
title: mediaResult.title,
alt: mediaResult.description,
}) })
.run(); .run();
} }

View File

@@ -1,5 +1,12 @@
<template> <template>
<div class="sm-input flex flex-col flex-1"> <div
:class="[
'sm-input',
'flex',
'flex-col',
'flex-1',
{ 'sm-input-small': small },
]">
<div <div
:class="[ :class="[
'relative', 'relative',
@@ -16,42 +23,78 @@
'transform-origin-top-left', 'transform-origin-top-left',
'text-gray', 'text-gray',
'block', 'block',
'translate-x-5',
'scale-100', 'scale-100',
'transition', 'transition',
small ? ['text-sm', '-top-0.5'] : 'top-0.5', small
? ['translate-x-4', 'text-sm', '-top-1.5']
: ['translate-x-5', 'top-0.5'],
]" ]"
>{{ label }}</label >{{ label }}</label
> >
<input <template v-if="!props.textarea">
:type="props.type" <input
:class="[ :type="props.type"
'w-full', :class="[
'text-gray-6', 'w-full',
'flex-1', 'text-gray-6',
'px-4', 'flex-1',
small ? ['text-sm', 'pt-3'] : ['text-lg', 'pt-5'], small
feedbackInvalid ? 'border-red-6' : 'border-gray', ? ['text-sm', 'pt-3', 'px-3']
feedbackInvalid ? 'border-2' : 'border-1', : ['text-lg', 'pt-5', 'px-4'],
{ 'bg-gray-1': disabled }, feedbackInvalid ? 'border-red-6' : 'border-gray',
{ 'rounded-l-2': !slots.prepend }, feedbackInvalid ? 'border-2' : 'border-1',
{ 'rounded-r-2': !slots.append }, { 'bg-gray-1': disabled },
]" { 'rounded-l-2': !slots.prepend },
v-bind="{ { 'rounded-r-2': !slots.append },
id: id, ]"
autofocus: props.autofocus, v-bind="{
autocomplete: props.type === 'email' ? 'email' : null, id: id,
spellcheck: props.type === 'email' ? false : null, autofocus: props.autofocus,
autocorrect: props.type === 'email' ? 'on' : null, autocomplete: props.type === 'email' ? 'email' : null,
autocapitalize: props.type === 'email' ? 'off' : null, spellcheck: props.type === 'email' ? false : null,
}" autocorrect: props.type === 'email' ? 'on' : null,
@focus="handleFocus" autocapitalize: props.type === 'email' ? 'off' : null,
@blur="handleBlur" }"
@input="handleInput" @focus="handleFocus"
@keyup="handleKeyup" @blur="handleBlur"
:value="value" @input="handleInput"
:disabled="disabled" /> @keyup="handleKeyup"
<template v-if="slots.append"><slot name="append"></slot></template> :value="value"
:disabled="disabled" />
<template v-if="slots.append"
><slot name="append"></slot
></template>
</template>
<template v-else>
<textarea
:class="[
'w-full',
'text-gray-6',
'flex-1',
small
? ['text-sm', 'pt-3', 'px-3']
: ['text-lg', 'pt-5', 'px-4'],
feedbackInvalid ? 'border-red-6' : 'border-gray',
feedbackInvalid ? 'border-2' : 'border-1',
{ 'bg-gray-1': disabled },
{ 'rounded-l-2': !slots.prepend },
{ 'rounded-r-2': !slots.append },
]"
v-bind="{
id: id,
autofocus: props.autofocus,
autocomplete: props.type === 'email' ? 'email' : null,
spellcheck: props.type === 'email' ? false : null,
autocorrect: props.type === 'email' ? 'on' : null,
autocapitalize: props.type === 'email' ? 'off' : null,
}"
@focus="handleFocus"
@blur="handleBlur"
@input="handleInput"
@keyup="handleKeyup"
:value="value"
:disabled="disabled"></textarea>
</template>
</div> </div>
<p v-if="feedbackInvalid" class="px-2 pt-2 text-xs text-red-6"> <p v-if="feedbackInvalid" class="px-2 pt-2 text-xs text-red-6">
{{ feedbackInvalid }} {{ feedbackInvalid }}
@@ -128,6 +171,11 @@ const props = defineProps({
default: false, default: false,
required: false, required: false,
}, },
textarea: {
type: Boolean,
default: false,
required: false,
},
}); });
const slots = useSlots(); const slots = useSlots();
@@ -149,21 +197,21 @@ const label = ref(
? props.label ? props.label
: typeof props.control == "string" : typeof props.control == "string"
? toTitleCase(props.control) ? toTitleCase(props.control)
: "" : "",
); );
const value = ref( const value = ref(
props.modelValue != undefined props.modelValue != undefined
? props.modelValue ? props.modelValue
: control != null : control != null
? control.value ? control.value
: "" : "",
); );
const id = ref( const id = ref(
props.id != undefined props.id != undefined
? props.id ? props.id
: typeof props.control == "string" && props.control.length > 0 : typeof props.control == "string" && props.control.length > 0
? props.control ? props.control
: generateRandomElementId() : generateRandomElementId(),
); );
const feedbackInvalid = ref(props.feedbackInvalid); const feedbackInvalid = ref(props.feedbackInvalid);
const active = ref(value.value?.toString().length ?? 0 > 0); const active = ref(value.value?.toString().length ?? 0 > 0);
@@ -174,7 +222,7 @@ watch(
() => value.value, () => value.value,
(newValue) => { (newValue) => {
active.value = newValue.toString().length > 0 || focused.value == true; active.value = newValue.toString().length > 0 || focused.value == true;
} },
); );
if (props.modelValue != undefined) { if (props.modelValue != undefined) {
@@ -182,7 +230,7 @@ if (props.modelValue != undefined) {
() => props.modelValue, () => props.modelValue,
(newValue) => { (newValue) => {
value.value = newValue; value.value = newValue;
} },
); );
} }
@@ -190,14 +238,14 @@ watch(
() => props.feedbackInvalid, () => props.feedbackInvalid,
(newValue) => { (newValue) => {
feedbackInvalid.value = newValue; feedbackInvalid.value = newValue;
} },
); );
watch( watch(
() => props.disabled, () => props.disabled,
(newValue) => { (newValue) => {
disabled.value = newValue; disabled.value = newValue;
} },
); );
if (typeof control === "object" && control !== null) { if (typeof control === "object" && control !== null) {
@@ -208,7 +256,7 @@ if (typeof control === "object" && control !== null) {
? "" ? ""
: control.validation.result.invalidMessages[0]; : control.validation.result.invalidMessages[0];
}, },
{ deep: true } { deep: true },
); );
watch( watch(
@@ -216,7 +264,7 @@ if (typeof control === "object" && control !== null) {
(newValue) => { (newValue) => {
value.value = newValue; value.value = newValue;
}, },
{ deep: true } { deep: true },
); );
} }
@@ -260,5 +308,8 @@ const handleKeyup = (event: Event) => {
.input-active label { .input-active label {
transform: translate(16px, 6px) scale(0.7); transform: translate(16px, 6px) scale(0.7);
} }
&.sm-input-small .input-active label {
transform: translate(12px, 7px) scale(0.7);
}
} }
</style> </style>

View File

@@ -1,12 +1,13 @@
<template> <template>
<div class="tab-group"> <div class="mb-4">
<ul class="flex"> <ul class="flex relative">
<li <li
v-for="tab in tabs" v-for="tab in tabs"
:key="tab.id" :key="tab.id"
:class="[ :class="[
'p-4', 'px-4',
'-mb-0.2', 'py-2',
'-mb-1px',
'border-1', 'border-1',
'rounded-t-2', 'rounded-t-2',
'border-gray', 'border-gray',
@@ -81,54 +82,3 @@ watch(
provide("selectedTab", selectedTab); provide("selectedTab", selectedTab);
</script> </script>
<style lang="scss">
.tab-group {
margin-bottom: 32px;
.tab-header {
list-style-type: none;
margin: 0;
padding: 0;
border-bottom: 1px solid var(--tab-color-border);
}
.tab-item {
display: inline-block;
padding: 8px 16px;
border: 1px solid transparent;
margin-bottom: -1px;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
color: var(--primary-color);
position: relative;
&.selected {
color: var(--tab-color-text);
background-color: var(--tab-color);
border-top: 1px solid var(--tab-color-border);
border-left: 1px solid var(--tab-color-border);
border-bottom: 1px solid var(--tab-color);
border-right: 1px solid var(--tab-color-border);
&::after {
display: block;
content: "";
position: absolute;
bottom: -2px;
height: 4px;
left: 0px;
right: 0px;
border-bottom: 3px solid var(--tab-color);
pointer-events: none;
}
}
&:hover:not(.selected) {
color: var(--primary-color);
background-color: var(--tab-color-hover);
border-bottom: 1px solid var(--tab-color-border);
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -167,3 +167,8 @@ export interface ShortlinkCollection {
export interface ShortlinkResponse { export interface ShortlinkResponse {
shortlink: Shortlink; shortlink: Shortlink;
} }
export interface ApiInfo {
version: string;
max_upload_size: number;
}

View File

@@ -1,6 +1,5 @@
/** /**
* Test if target is a boolean * Test if target is a boolean
*
* @param {unknown} target The varible to test * @param {unknown} target The varible to test
* @returns {boolean} If the varible is a boolean type * @returns {boolean} If the varible is a boolean type
*/ */
@@ -10,7 +9,6 @@ export function isBool(target: unknown): boolean {
/** /**
* Test if target is a number * Test if target is a number
*
* @param {unknown} target The varible to test * @param {unknown} target The varible to test
* @returns {boolean} If the varible is a number type * @returns {boolean} If the varible is a number type
*/ */
@@ -20,7 +18,6 @@ export function isNumber(target: unknown): boolean {
/** /**
* Test if target is an object * Test if target is an object
*
* @param {unknown} target The varible to test * @param {unknown} target The varible to test
* @returns {boolean} If the varible is a object type * @returns {boolean} If the varible is a object type
*/ */
@@ -30,7 +27,6 @@ export function isObject(target: unknown): boolean {
/** /**
* Test if target is a string * Test if target is a string
*
* @param {unknown} target The varible to test * @param {unknown} target The varible to test
* @returns {boolean} If the varible is a string type * @returns {boolean} If the varible is a string type
*/ */
@@ -40,11 +36,14 @@ export function isString(target: unknown): boolean {
/** /**
* Convert bytes to a human readable string. * Convert bytes to a human readable string.
*
* @param {number} bytes The bytes to convert. * @param {number} bytes The bytes to convert.
* @param {number} decimalPlaces The number of places to force.
* @returns {string} The bytes in human readable string. * @returns {string} The bytes in human readable string.
*/ */
export const bytesReadable = (bytes: number): string => { export const bytesReadable = (
bytes: number,
decimalPlaces: number = undefined,
): string => {
if (Number.isNaN(bytes)) { if (Number.isNaN(bytes)) {
return "0 Bytes"; return "0 Bytes";
} }
@@ -59,12 +58,16 @@ export const bytesReadable = (bytes: number): string => {
let tempBytes = bytes; let tempBytes = bytes;
while ( while (
Math.round(Math.abs(tempBytes) * r) / r >= 1000 && Math.round(Math.abs(tempBytes) * r) / r >= 1024 &&
u < units.length - 1 u < units.length - 1
) { ) {
tempBytes /= 1000; tempBytes /= 1024;
++u; ++u;
} }
return tempBytes.toFixed(1) + " " + units[u]; if (decimalPlaces === undefined) {
return tempBytes.toFixed(2).replace(/\.?0+$/, "") + " " + units[u];
}
return tempBytes.toFixed(decimalPlaces) + " " + units[u];
}; };

View File

@@ -1,12 +1,14 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
type ApplicationStoreEventKeyUpCallback = (event: KeyboardEvent) => boolean; type ApplicationStoreEventKeyUpCallback = (event: KeyboardEvent) => boolean;
type ApplicationStoreEventKeyPressCallback = (event: KeyboardEvent) => boolean;
export interface ApplicationStore { export interface ApplicationStore {
hydrated: boolean; hydrated: boolean;
unavailable: boolean; unavailable: boolean;
dynamicTitle: string; dynamicTitle: string;
eventKeyUpStack: ApplicationStoreEventKeyUpCallback[]; eventKeyUpStack: ApplicationStoreEventKeyUpCallback[];
eventKeyPressStack: ApplicationStoreEventKeyPressCallback[];
pageLoaderTimeout: number; pageLoaderTimeout: number;
_addedListener: boolean; _addedListener: boolean;
} }
@@ -18,6 +20,7 @@ export const useApplicationStore = defineStore({
unavailable: false, unavailable: false,
dynamicTitle: "", dynamicTitle: "",
eventKeyUpStack: [], eventKeyUpStack: [],
eventKeyPressStack: [],
pageLoaderTimeout: 0, pageLoaderTimeout: 0,
_addedListener: false, _addedListener: false,
}), }),
@@ -45,7 +48,7 @@ export const useApplicationStore = defineStore({
} }
return true; return true;
} },
); );
}); });
} }
@@ -53,7 +56,38 @@ export const useApplicationStore = defineStore({
removeKeyUpListener(callback: ApplicationStoreEventKeyUpCallback) { removeKeyUpListener(callback: ApplicationStoreEventKeyUpCallback) {
this.eventKeyUpStack = this.eventKeyUpStack.filter( this.eventKeyUpStack = this.eventKeyUpStack.filter(
(item: ApplicationStoreEventKeyUpCallback) => item !== callback (item: ApplicationStoreEventKeyUpCallback) => item !== callback,
);
},
addKeyPressListener(callback: ApplicationStoreEventKeyPressCallback) {
this.eventKeyPressStack.push(callback);
if (!this._addedListener) {
document.addEventListener(
"keypress",
(event: KeyboardEvent) => {
this.eventKeyPressStack.every(
(item: ApplicationStoreEventKeyPressCallback) => {
const result = item(event);
if (result) {
return false;
}
return true;
},
);
},
);
}
},
removeKeyPressListener(
callback: ApplicationStoreEventKeyPressCallback,
) {
this.eventKeyPressStack = this.eventKeyPressStack.filter(
(item: ApplicationStoreEventKeyPressCallback) =>
item !== callback,
); );
}, },
}, },

View File

@@ -40,6 +40,8 @@
.bg-center { background-position: center; } .bg-center { background-position: center; }
.whitespace-nowrap {white-space: nowrap; } .whitespace-nowrap {white-space: nowrap; }
.spin{animation:rotate 1s infinite linear} .spin{animation:rotate 1s infinite linear}
.text-xxs { font-size: 0.6rem; line-height: 0.75rem; }
.text-bold { font-weight: bold; }
.sm-html .ProseMirror { outline: none; } .sm-html .ProseMirror { outline: none; }
.sm-html hr { border-top: 1px solid #aaa; margin: 1.5rem 0; } .sm-html hr { border-top: 1px solid #aaa; margin: 1.5rem 0; }
.sm-html pre { padding: 0 1rem; line-height: 1rem; } .sm-html pre { padding: 0 1rem; line-height: 1rem; }
@@ -54,8 +56,11 @@
.sm-html p.warning::before { color: rgba(202,138,4,1); content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath d='M12,2L1,21H23M12,6L19.53,19H4.47M11,10V14H13V10M11,16V18H13V16' fill='rgba(202,138,4,1)' /%3E%3C/svg%3E"); } .sm-html p.warning::before { color: rgba(202,138,4,1); content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath d='M12,2L1,21H23M12,6L19.53,19H4.47M11,10V14H13V10M11,16V18H13V16' fill='rgba(202,138,4,1)' /%3E%3C/svg%3E"); }
.sm-html p.danger { border: 1px solid rgba(220,38,38,1); background-color: rgba(220,38,38,0.25); } .sm-html p.danger { border: 1px solid rgba(220,38,38,1); background-color: rgba(220,38,38,0.25); }
.sm-html p.danger::before { color: rgba(220,38,38,1); content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath d='M8.27,3L3,8.27V15.73L8.27,21H15.73L21,15.73V8.27L15.73,3M8.41,7L12,10.59L15.59,7L17,8.41L13.41,12L17,15.59L15.59,17L12,13.41L8.41,17L7,15.59L10.59,12L7,8.41' fill='rgba(220,38,38,1)' /%3E%3C/svg%3E"); } .sm-html p.danger::before { color: rgba(220,38,38,1); content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath d='M8.27,3L3,8.27V15.73L8.27,21H15.73L21,15.73V8.27L15.73,3M8.41,7L12,10.59L15.59,7L17,8.41L13.41,12L17,15.59L15.59,17L12,13.41L8.41,17L7,15.59L10.59,12L7,8.41' fill='rgba(220,38,38,1)' /%3E%3C/svg%3E"); }
.sm-html img { display: block; margin: 1rem auto; max-height: 100%; max-width: 100%; }
.sm-editor::-webkit-scrollbar { background-color: transparent; width: 16px; } .sm-editor::-webkit-scrollbar { background-color: transparent; width: 16px; }
.sm-editor::-webkit-scrollbar-thumb { background-color: #aaa; border: 4px solid transparent; border-radius: 8px; background-clip: padding-box; } .sm-editor::-webkit-scrollbar-thumb { background-color: #aaa; border: 4px solid transparent; border-radius: 8px; background-clip: padding-box; }
.selected-checked { border: 3px solid rgba(2,132,199,1); position: relative; }
.selected-checked::after { display: block; position: absolute; border:1px solid white; height: 1.5rem; width: 1.5rem; background-color: rgba(2,132,199,1); top: -0.4rem; right: -0.4rem; content: ""; background-position: center; background-repeat: no-repeat; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M21,7L9,19L2.712,12.712L5.556,9.892L9.029,13.358L18.186,4.189L21,7Z' fill='rgba(255,255,255,1)' /%3E%3C/svg%3E")}
@keyframes rotate{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}} @keyframes rotate{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}
</style> </style>
</head> </head>