work
This commit is contained in:
@@ -767,7 +767,7 @@ const setLink = () => {
|
||||
};
|
||||
|
||||
const setImage = async () => {
|
||||
let result = await openDialog(SMDialogMedia);
|
||||
let result = await openDialog(SMDialogMedia, { allowUpload: true });
|
||||
if (result) {
|
||||
const mediaResult = result as Media;
|
||||
editor.value
|
||||
@@ -775,6 +775,8 @@ const setImage = async () => {
|
||||
.focus()
|
||||
.setImage({
|
||||
src: mediaResult.url,
|
||||
title: mediaResult.title,
|
||||
alt: mediaResult.description,
|
||||
})
|
||||
.run();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<template>
|
||||
<div class="sm-input flex flex-col flex-1">
|
||||
<div
|
||||
:class="[
|
||||
'sm-input',
|
||||
'flex',
|
||||
'flex-col',
|
||||
'flex-1',
|
||||
{ 'sm-input-small': small },
|
||||
]">
|
||||
<div
|
||||
:class="[
|
||||
'relative',
|
||||
@@ -16,42 +23,78 @@
|
||||
'transform-origin-top-left',
|
||||
'text-gray',
|
||||
'block',
|
||||
'translate-x-5',
|
||||
'scale-100',
|
||||
'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
|
||||
>
|
||||
<input
|
||||
:type="props.type"
|
||||
:class="[
|
||||
'w-full',
|
||||
'text-gray-6',
|
||||
'flex-1',
|
||||
'px-4',
|
||||
small ? ['text-sm', 'pt-3'] : ['text-lg', 'pt-5'],
|
||||
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" />
|
||||
<template v-if="slots.append"><slot name="append"></slot></template>
|
||||
<template v-if="!props.textarea">
|
||||
<input
|
||||
:type="props.type"
|
||||
: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" />
|
||||
<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>
|
||||
<p v-if="feedbackInvalid" class="px-2 pt-2 text-xs text-red-6">
|
||||
{{ feedbackInvalid }}
|
||||
@@ -128,6 +171,11 @@ const props = defineProps({
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
textarea: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
|
||||
const slots = useSlots();
|
||||
@@ -149,21 +197,21 @@ const label = ref(
|
||||
? props.label
|
||||
: typeof props.control == "string"
|
||||
? toTitleCase(props.control)
|
||||
: ""
|
||||
: "",
|
||||
);
|
||||
const value = ref(
|
||||
props.modelValue != undefined
|
||||
? props.modelValue
|
||||
: control != null
|
||||
? control.value
|
||||
: ""
|
||||
: "",
|
||||
);
|
||||
const id = ref(
|
||||
props.id != undefined
|
||||
? props.id
|
||||
: typeof props.control == "string" && props.control.length > 0
|
||||
? props.control
|
||||
: generateRandomElementId()
|
||||
: generateRandomElementId(),
|
||||
);
|
||||
const feedbackInvalid = ref(props.feedbackInvalid);
|
||||
const active = ref(value.value?.toString().length ?? 0 > 0);
|
||||
@@ -174,7 +222,7 @@ watch(
|
||||
() => value.value,
|
||||
(newValue) => {
|
||||
active.value = newValue.toString().length > 0 || focused.value == true;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (props.modelValue != undefined) {
|
||||
@@ -182,7 +230,7 @@ if (props.modelValue != undefined) {
|
||||
() => props.modelValue,
|
||||
(newValue) => {
|
||||
value.value = newValue;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -190,14 +238,14 @@ watch(
|
||||
() => props.feedbackInvalid,
|
||||
(newValue) => {
|
||||
feedbackInvalid.value = newValue;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.disabled,
|
||||
(newValue) => {
|
||||
disabled.value = newValue;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (typeof control === "object" && control !== null) {
|
||||
@@ -208,7 +256,7 @@ if (typeof control === "object" && control !== null) {
|
||||
? ""
|
||||
: control.validation.result.invalidMessages[0];
|
||||
},
|
||||
{ deep: true }
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
@@ -216,7 +264,7 @@ if (typeof control === "object" && control !== null) {
|
||||
(newValue) => {
|
||||
value.value = newValue;
|
||||
},
|
||||
{ deep: true }
|
||||
{ deep: true },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -260,5 +308,8 @@ const handleKeyup = (event: Event) => {
|
||||
.input-active label {
|
||||
transform: translate(16px, 6px) scale(0.7);
|
||||
}
|
||||
&.sm-input-small .input-active label {
|
||||
transform: translate(12px, 7px) scale(0.7);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<div class="tab-group">
|
||||
<ul class="flex">
|
||||
<div class="mb-4">
|
||||
<ul class="flex relative">
|
||||
<li
|
||||
v-for="tab in tabs"
|
||||
:key="tab.id"
|
||||
:class="[
|
||||
'p-4',
|
||||
'-mb-0.2',
|
||||
'px-4',
|
||||
'py-2',
|
||||
'-mb-1px',
|
||||
'border-1',
|
||||
'rounded-t-2',
|
||||
'border-gray',
|
||||
@@ -81,54 +82,3 @@ watch(
|
||||
|
||||
provide("selectedTab", selectedTab);
|
||||
</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
@@ -167,3 +167,8 @@ export interface ShortlinkCollection {
|
||||
export interface ShortlinkResponse {
|
||||
shortlink: Shortlink;
|
||||
}
|
||||
|
||||
export interface ApiInfo {
|
||||
version: string;
|
||||
max_upload_size: number;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/**
|
||||
* Test if target is a boolean
|
||||
*
|
||||
* @param {unknown} target The varible to test
|
||||
* @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
|
||||
*
|
||||
* @param {unknown} target The varible to test
|
||||
* @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
|
||||
*
|
||||
* @param {unknown} target The varible to test
|
||||
* @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
|
||||
*
|
||||
* @param {unknown} target The varible to test
|
||||
* @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.
|
||||
*
|
||||
* @param {number} bytes The bytes to convert.
|
||||
* @param {number} decimalPlaces The number of places to force.
|
||||
* @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)) {
|
||||
return "0 Bytes";
|
||||
}
|
||||
@@ -59,12 +58,16 @@ export const bytesReadable = (bytes: number): string => {
|
||||
let tempBytes = bytes;
|
||||
|
||||
while (
|
||||
Math.round(Math.abs(tempBytes) * r) / r >= 1000 &&
|
||||
Math.round(Math.abs(tempBytes) * r) / r >= 1024 &&
|
||||
u < units.length - 1
|
||||
) {
|
||||
tempBytes /= 1000;
|
||||
tempBytes /= 1024;
|
||||
++u;
|
||||
}
|
||||
|
||||
return tempBytes.toFixed(1) + " " + units[u];
|
||||
if (decimalPlaces === undefined) {
|
||||
return tempBytes.toFixed(2).replace(/\.?0+$/, "") + " " + units[u];
|
||||
}
|
||||
|
||||
return tempBytes.toFixed(decimalPlaces) + " " + units[u];
|
||||
};
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
type ApplicationStoreEventKeyUpCallback = (event: KeyboardEvent) => boolean;
|
||||
type ApplicationStoreEventKeyPressCallback = (event: KeyboardEvent) => boolean;
|
||||
|
||||
export interface ApplicationStore {
|
||||
hydrated: boolean;
|
||||
unavailable: boolean;
|
||||
dynamicTitle: string;
|
||||
eventKeyUpStack: ApplicationStoreEventKeyUpCallback[];
|
||||
eventKeyPressStack: ApplicationStoreEventKeyPressCallback[];
|
||||
pageLoaderTimeout: number;
|
||||
_addedListener: boolean;
|
||||
}
|
||||
@@ -18,6 +20,7 @@ export const useApplicationStore = defineStore({
|
||||
unavailable: false,
|
||||
dynamicTitle: "",
|
||||
eventKeyUpStack: [],
|
||||
eventKeyPressStack: [],
|
||||
pageLoaderTimeout: 0,
|
||||
_addedListener: false,
|
||||
}),
|
||||
@@ -45,7 +48,7 @@ export const useApplicationStore = defineStore({
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -53,7 +56,38 @@ export const useApplicationStore = defineStore({
|
||||
|
||||
removeKeyUpListener(callback: ApplicationStoreEventKeyUpCallback) {
|
||||
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,
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
.bg-center { background-position: center; }
|
||||
.whitespace-nowrap {white-space: nowrap; }
|
||||
.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 hr { border-top: 1px solid #aaa; margin: 1.5rem 0; }
|
||||
.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.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 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-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)}}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
Reference in New Issue
Block a user