improve the import/export process to allow for non-ascii characters.
This commit is contained in:
parent
746b490216
commit
86da6c2b06
10 changed files with 39 additions and 78 deletions
|
|
@ -209,6 +209,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
const emitter = defineEmits(['cancel', 'submit'])
|
const emitter = defineEmits(['cancel', 'submit'])
|
||||||
|
import { decode } from '~/utils/importer'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
reference: {
|
reference: {
|
||||||
|
|
@ -321,18 +322,8 @@ const importItem = async () => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (false === val.startsWith('{')) {
|
|
||||||
try {
|
|
||||||
val = base64UrlDecode(val)
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
toast.error(`Failed to decode string. ${e.message}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const item = JSON.parse(val)
|
const item = decode(val)
|
||||||
|
|
||||||
if (!item?._type || 'condition' !== item._type) {
|
if (!item?._type || 'condition' !== item._type) {
|
||||||
toast.error(`Invalid import string. Expected type 'condition', got '${item._type ?? 'unknown'}'.`)
|
toast.error(`Invalid import string. Expected type 'condition', got '${item._type ?? 'unknown'}'.`)
|
||||||
|
|
|
||||||
|
|
@ -267,6 +267,8 @@
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
|
import { decode } from '~/utils/importer'
|
||||||
|
|
||||||
const emitter = defineEmits(['cancel', 'submit']);
|
const emitter = defineEmits(['cancel', 'submit']);
|
||||||
const toast = useNotification();
|
const toast = useNotification();
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|
@ -339,18 +341,8 @@ const importItem = async () => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (false === val.startsWith('{')) {
|
|
||||||
try {
|
|
||||||
val = base64UrlDecode(val)
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
toast.error(`Failed to decode string. ${e.message}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const item = JSON.parse(val)
|
const item = decode(val)
|
||||||
|
|
||||||
if ('notification' !== item._type) {
|
if ('notification' !== item._type) {
|
||||||
toast.error(`Invalid import string. Expected type 'notification', got '${item._type}'.`)
|
toast.error(`Invalid import string. Expected type 'notification', got '${item._type}'.`)
|
||||||
|
|
|
||||||
|
|
@ -232,6 +232,8 @@
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
|
import { decode } from '~/utils/importer'
|
||||||
|
|
||||||
const emitter = defineEmits(['cancel', 'submit']);
|
const emitter = defineEmits(['cancel', 'submit']);
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|
@ -351,18 +353,8 @@ const importItem = async () => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (false === val.startsWith('{')) {
|
|
||||||
try {
|
|
||||||
val = base64UrlDecode(val)
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
toast.error(`Failed to decode string. ${e.message}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const item = JSON.parse(val)
|
const item = decode(val)
|
||||||
|
|
||||||
if (!item?._type || 'preset' !== item._type) {
|
if (!item?._type || 'preset' !== item._type) {
|
||||||
toast.error(`Invalid import string. Expected type 'preset', got '${item._type ?? 'unknown'}'.`)
|
toast.error(`Invalid import string. Expected type 'preset', got '${item._type ?? 'unknown'}'.`)
|
||||||
|
|
|
||||||
|
|
@ -271,6 +271,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
import { CronExpressionParser } from 'cron-parser'
|
import { CronExpressionParser } from 'cron-parser'
|
||||||
|
import { decode } from '~/utils/importer'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
reference: {
|
reference: {
|
||||||
|
|
@ -352,18 +353,8 @@ const importItem = async () => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (false === val.startsWith('{')) {
|
|
||||||
try {
|
|
||||||
val = base64UrlDecode(val)
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
toast.error(`Failed to decode string. ${e.message}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const item = JSON.parse(val)
|
const item = decode(val)
|
||||||
|
|
||||||
if ('task' !== item._type) {
|
if ('task' !== item._type) {
|
||||||
toast.error(`Invalid import string. Expected type 'task', got '${item._type}'.`)
|
toast.error(`Invalid import string. Expected type 'task', got '${item._type}'.`)
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { request } from '~/utils/index'
|
import { request } from '~/utils/index'
|
||||||
|
import { encode } from '~/utils/importer'
|
||||||
|
|
||||||
const toast = useNotification()
|
const toast = useNotification()
|
||||||
const config = useConfigStore()
|
const config = useConfigStore()
|
||||||
|
|
@ -286,6 +287,6 @@ const exportItem = item => {
|
||||||
userData['_type'] = 'condition'
|
userData['_type'] = 'condition'
|
||||||
userData['_version'] = '1.0'
|
userData['_version'] = '1.0'
|
||||||
|
|
||||||
return copyText(base64UrlEncode(JSON.stringify(userData)))
|
return copyText(encode(userData))
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,7 @@ div.is-centered {
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { request } from '~/utils/index'
|
import { request } from '~/utils/index'
|
||||||
|
import { encode } from '~/utils/importer'
|
||||||
|
|
||||||
const toast = useNotification()
|
const toast = useNotification()
|
||||||
const config = useConfigStore()
|
const config = useConfigStore()
|
||||||
|
|
@ -337,6 +338,6 @@ const exportItem = async item => {
|
||||||
data['_type'] = 'notification'
|
data['_type'] = 'notification'
|
||||||
data['_version'] = '1.0'
|
data['_version'] = '1.0'
|
||||||
|
|
||||||
return copyText(base64UrlEncode(JSON.stringify(data)))
|
return copyText(encode(data))
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -213,6 +213,7 @@ div.is-centered {
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
import { request } from '~/utils/index'
|
import { request } from '~/utils/index'
|
||||||
|
import { encode } from '~/utils/importer'
|
||||||
|
|
||||||
const toast = useNotification()
|
const toast = useNotification()
|
||||||
const config = useConfigStore()
|
const config = useConfigStore()
|
||||||
|
|
@ -400,7 +401,7 @@ const exportItem = item => {
|
||||||
userData['_type'] = 'preset'
|
userData['_type'] = 'preset'
|
||||||
userData['_version'] = '2.5'
|
userData['_version'] = '2.5'
|
||||||
|
|
||||||
return copyText(base64UrlEncode(JSON.stringify(userData)))
|
return copyText(encode(userData))
|
||||||
}
|
}
|
||||||
|
|
||||||
const calcPath = (path) => {
|
const calcPath = (path) => {
|
||||||
|
|
|
||||||
|
|
@ -243,6 +243,7 @@ import moment from 'moment'
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
import { CronExpressionParser } from 'cron-parser'
|
import { CronExpressionParser } from 'cron-parser'
|
||||||
import { request } from '~/utils/index'
|
import { request } from '~/utils/index'
|
||||||
|
import { encode } from '~/utils/importer'
|
||||||
import type { task_item, exported_task, error_response } from '~/@types/tasks'
|
import type { task_item, exported_task, error_response } from '~/@types/tasks'
|
||||||
|
|
||||||
const box = useConfirm()
|
const box = useConfirm()
|
||||||
|
|
@ -496,8 +497,7 @@ const exportItem = async (item: task_item) => {
|
||||||
data._type = 'task'
|
data._type = 'task'
|
||||||
data._version = '2.0'
|
data._version = '2.0'
|
||||||
|
|
||||||
return copyText(base64UrlEncode(JSON.stringify(data)));
|
return copyText(encode(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
21
ui/utils/importer.ts
Normal file
21
ui/utils/importer.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
const encode = (obj: Record<string, any>): string => {
|
||||||
|
const jsonStr = JSON.stringify(obj);
|
||||||
|
const utf8Bytes = new TextEncoder().encode(jsonStr);
|
||||||
|
const binary = String.fromCharCode(...utf8Bytes);
|
||||||
|
const base64 = btoa(binary);
|
||||||
|
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
const decode = (str: string): object => {
|
||||||
|
const base64 = str
|
||||||
|
.replace(/-/g, '+')
|
||||||
|
.replace(/_/g, '/')
|
||||||
|
.padEnd(str.length + (4 - str.length % 4) % 4, '=');
|
||||||
|
|
||||||
|
const binary = atob(base64);
|
||||||
|
const bytes = Uint8Array.from(binary, c => c.charCodeAt(0));
|
||||||
|
const jsonStr = new TextDecoder().decode(bytes);
|
||||||
|
return JSON.parse(jsonStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { encode, decode }
|
||||||
|
|
@ -406,33 +406,6 @@ const convertCliOptions = async opts => {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* URL Safe Base64 Encode.
|
|
||||||
*
|
|
||||||
* @param {String} data The data to encode
|
|
||||||
*
|
|
||||||
* @returns {String} The encoded data
|
|
||||||
*/
|
|
||||||
const base64UrlEncode = data => btoa(data).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* URL Safe Base64 Decode.
|
|
||||||
*
|
|
||||||
* @param {String} input The input string to decode
|
|
||||||
*
|
|
||||||
* @returns {String} The decoded data
|
|
||||||
*/
|
|
||||||
const base64UrlDecode = input => {
|
|
||||||
let base64 = input.replace(/-/g, '+').replace(/_/g, '/');
|
|
||||||
const pad = base64.length % 4;
|
|
||||||
if (pad) {
|
|
||||||
base64 += '='.repeat(4 - pad);
|
|
||||||
}
|
|
||||||
|
|
||||||
return atob(base64);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if array or object has data.
|
* Check if array or object has data.
|
||||||
*
|
*
|
||||||
|
|
@ -539,8 +512,6 @@ export {
|
||||||
makeDownload,
|
makeDownload,
|
||||||
formatBytes,
|
formatBytes,
|
||||||
convertCliOptions,
|
convertCliOptions,
|
||||||
base64UrlEncode,
|
|
||||||
base64UrlDecode,
|
|
||||||
has_data,
|
has_data,
|
||||||
toggleClass,
|
toggleClass,
|
||||||
cleanObject,
|
cleanObject,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue