Merge pull request #350 from arabcoders/dev
Fixed in browser match_str to closely match yt-dlp match_str
This commit is contained in:
commit
fd75d3cb8f
74 changed files with 235 additions and 231 deletions
|
|
@ -207,8 +207,6 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { decode } from '~/utils/importer'
|
||||
import { match_str } from '~/utils/ytdlp'
|
||||
import type { ConditionItem, ImportedConditionItem } from '~/types/conditions'
|
||||
|
||||
const emitter = defineEmits<{
|
||||
|
|
@ -292,7 +290,7 @@ const run_test = async (): Promise<void> => {
|
|||
}
|
||||
|
||||
test_data.value.in_progress = true
|
||||
test_data.value.data.status = null
|
||||
test_data.value.data.status = false
|
||||
|
||||
try {
|
||||
const response = await request('/api/conditions/test', {
|
||||
|
|
@ -363,7 +361,7 @@ const show_data = (): string => {
|
|||
}
|
||||
|
||||
const logic_test = computed(() => {
|
||||
if (!test_data.value.data || !test_data.value.data.data) {
|
||||
if (Object.keys(test_data.value.data?.data ?? {}).length < 1) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
|
@ -20,8 +20,6 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
|
||||
const emitter = defineEmits(['open_state'])
|
||||
defineProps({
|
||||
label: {
|
||||
|
|
@ -20,19 +20,16 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { request } from '~/utils/index'
|
||||
|
||||
const props = defineProps < {
|
||||
const props = defineProps<{
|
||||
image?: string
|
||||
title?: string
|
||||
loader?: () => Promise < void>
|
||||
privacy ?: boolean
|
||||
} > ()
|
||||
loader?: () => Promise<void>
|
||||
privacy?: boolean
|
||||
}>()
|
||||
|
||||
const cache = useSessionCache()
|
||||
const toast = useNotification()
|
||||
const url = ref < string | null > (null)
|
||||
const url = ref<string | null>(null)
|
||||
const error = ref(false)
|
||||
const isPreloading = ref(false)
|
||||
|
||||
|
|
@ -38,9 +38,6 @@ code {
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { request } from '~/utils/index'
|
||||
|
||||
const toast = useNotification()
|
||||
const emitter = defineEmits<{ (e: 'closeModel'): void }>()
|
||||
|
||||
|
|
@ -461,12 +461,8 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { NuxtLink } from '#components'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import moment from 'moment'
|
||||
import { getEmbedable, isEmbedable } from '~/utils/embedable'
|
||||
import { formatBytes, makeDownload, uri } from '~/utils/index'
|
||||
import Dropdown from './Dropdown.vue'
|
||||
|
||||
const emitter = defineEmits(['getInfo', 'add_new', 'getItemInfo', 'clear_search'])
|
||||
|
||||
|
|
@ -19,8 +19,6 @@ img {
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { request } from '~/utils/index'
|
||||
|
||||
const toast = useNotification()
|
||||
const emitter = defineEmits(['closeModel'])
|
||||
|
||||
|
|
@ -5,7 +5,6 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, nextTick, onBeforeUnmount } from 'vue'
|
||||
import { useIntersectionObserver } from '@vueuse/core'
|
||||
|
||||
const props = defineProps<{
|
||||
|
|
@ -232,7 +232,6 @@
|
|||
import 'assets/css/bulma-switch.css'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import type { item_request } from '~/types/item'
|
||||
import { getSeparatorsName, separators } from '~/utils/index'
|
||||
|
||||
const props = defineProps<{ item?: Partial<item_request> }>()
|
||||
const emitter = defineEmits<{
|
||||
|
|
@ -269,7 +269,6 @@
|
|||
|
||||
<script setup>
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { decode } from '~/utils/importer'
|
||||
|
||||
const emitter = defineEmits(['cancel', 'submit']);
|
||||
const toast = useNotification();
|
||||
|
|
@ -232,7 +232,6 @@
|
|||
|
||||
<script setup>
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { decode } from '~/utils/importer'
|
||||
|
||||
const emitter = defineEmits(['cancel', 'submit']);
|
||||
|
||||
|
|
@ -315,8 +315,6 @@
|
|||
<script setup>
|
||||
import moment from 'moment'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { ucFirst } from '~/utils/index'
|
||||
import { isEmbedable, getEmbedable } from '~/utils/embedable'
|
||||
|
||||
const emitter = defineEmits(['getInfo', 'clear_search', 'getItemInfo'])
|
||||
const props = defineProps({
|
||||
|
|
@ -142,7 +142,6 @@
|
|||
<script setup lang="ts">
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { POSITION } from 'vue-toastification'
|
||||
import { separators } from '~/utils/index'
|
||||
|
||||
defineProps<{ isLoading: boolean }>()
|
||||
|
||||
|
|
@ -292,7 +292,6 @@
|
|||
import 'assets/css/bulma-switch.css'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { CronExpressionParser } from 'cron-parser'
|
||||
import { decode } from '~/utils/importer'
|
||||
|
||||
const props = defineProps({
|
||||
reference: {
|
||||
|
|
@ -28,7 +28,6 @@
|
|||
<script setup lang="ts">
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import Hls from 'hls.js'
|
||||
import { makeDownload } from '~/utils/index'
|
||||
|
||||
type video_track_element = {
|
||||
file: string,
|
||||
|
|
@ -161,6 +161,9 @@ const applyPreferredColorScheme = (scheme: string) => {
|
|||
|
||||
for (let s = 0; s < document.styleSheets.length; s++) {
|
||||
const styleSheet = document.styleSheets[s]
|
||||
if (!styleSheet?.cssRules) {
|
||||
continue
|
||||
}
|
||||
|
||||
let rules: CSSRuleList
|
||||
try {
|
||||
|
|
@ -193,7 +193,6 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { request } from '~/utils/index'
|
||||
import moment from 'moment'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
|
||||
|
|
@ -115,8 +115,6 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { request } from '~/utils/index'
|
||||
import { encode } from '~/utils/importer'
|
||||
import type { ConditionItem, ImportedConditionItem } from '~/types/conditions'
|
||||
|
||||
const toast = useNotification()
|
||||
|
|
@ -85,7 +85,6 @@ import type { item_request } from '~/types/item'
|
|||
const config = useConfigStore()
|
||||
const stateStore = useStateStore()
|
||||
const socket = useSocketStore()
|
||||
const box = useConfirm()
|
||||
const bg_enable = useStorage<boolean>('random_bg', true)
|
||||
const bg_opacity = useStorage<number>('random_bg_opacity', 0.95)
|
||||
const display_style = useStorage<string>('display_style', 'cards')
|
||||
|
|
@ -111,8 +111,6 @@ code {
|
|||
|
||||
<script setup lang="ts">
|
||||
import moment from 'moment'
|
||||
import { request } from '~/utils/index'
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import type { log_line } from '~/types/logs'
|
||||
|
||||
|
|
@ -140,9 +140,6 @@ div.is-centered {
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { request } from '~/utils/index'
|
||||
import { encode } from '~/utils/importer'
|
||||
|
||||
const toast = useNotification()
|
||||
const config = useConfigStore()
|
||||
const socket = useSocketStore()
|
||||
|
|
@ -212,8 +212,6 @@ div.is-centered {
|
|||
|
||||
<script setup>
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { request } from '~/utils/index'
|
||||
import { encode } from '~/utils/importer'
|
||||
|
||||
const toast = useNotification()
|
||||
const config = useConfigStore()
|
||||
|
|
@ -328,8 +328,6 @@ div.is-centered {
|
|||
import moment from 'moment'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { CronExpressionParser } from 'cron-parser'
|
||||
import { request, sleep } from '~/utils/index'
|
||||
import { encode } from '~/utils/importer'
|
||||
import type { task_item, exported_task, error_response } from '~/types/tasks'
|
||||
|
||||
const box = useConfirm()
|
||||
|
|
@ -377,14 +375,14 @@ watch(query, () => {
|
|||
selectedElms.value = []
|
||||
})
|
||||
|
||||
watch(masterSelectAll, (value) => {
|
||||
watch(masterSelectAll, value => {
|
||||
if (!value) {
|
||||
selectedElms.value = []
|
||||
return
|
||||
}
|
||||
for (const key in filteredTasks.value) {
|
||||
const element = filteredTasks.value[key]
|
||||
if (value) {
|
||||
selectedElms.value.push(element.id)
|
||||
} else {
|
||||
selectedElms.value = []
|
||||
}
|
||||
const element = filteredTasks.value[key] as task_item
|
||||
selectedElms.value.push(element.id)
|
||||
}
|
||||
})
|
||||
|
||||
38
ui/app/plugins/autoscroll.ts
Normal file
38
ui/app/plugins/autoscroll.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
let observer: MutationObserver;
|
||||
const AUTO_SCROLL_EVENTS = ['scroll', 'mousewheel', 'touchmove'] as const;
|
||||
const events_handlers: Array<(ev: Event) => void> = [];
|
||||
|
||||
const on_mounted = (el: HTMLElement) => {
|
||||
let scrolledToBottom = true;
|
||||
|
||||
AUTO_SCROLL_EVENTS.forEach((ev, index) => {
|
||||
const handler = () => {
|
||||
scrolledToBottom = (el.scrollHeight - el.scrollTop) - el.clientHeight <= 80;
|
||||
};
|
||||
events_handlers[index] = handler;
|
||||
el.addEventListener(ev as string, handler, { passive: true } as AddEventListenerOptions);
|
||||
});
|
||||
|
||||
observer = new MutationObserver(() => {
|
||||
if (false === scrolledToBottom) return;
|
||||
el.scrollTop = el.scrollHeight;
|
||||
});
|
||||
|
||||
observer.observe(el, { childList: true, subtree: true });
|
||||
}
|
||||
const on_unmounted = (el: HTMLElement) => {
|
||||
AUTO_SCROLL_EVENTS.forEach((ev, index) => {
|
||||
const handler = events_handlers[index];
|
||||
if (handler) {
|
||||
el.removeEventListener(ev as string, handler, { passive: true } as EventListenerOptions);
|
||||
}
|
||||
});
|
||||
observer.disconnect();
|
||||
}
|
||||
|
||||
export default defineNuxtPlugin(nuxtApp => {
|
||||
nuxtApp.vueApp.directive('autoscroll', {
|
||||
mounted: on_mounted,
|
||||
unmounted: on_unmounted,
|
||||
});
|
||||
});
|
||||
54
ui/app/plugins/rtime.ts
Normal file
54
ui/app/plugins/rtime.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import moment from 'moment'
|
||||
|
||||
type RTimeElement = HTMLElement & { _next_timer?: number }
|
||||
|
||||
const parseInterval = (arg: string | undefined): number => {
|
||||
if (!arg) {
|
||||
return 60 * 1000
|
||||
}
|
||||
|
||||
const match = arg.match(/^(\d+)([smhd])$/)
|
||||
|
||||
if (!match) {
|
||||
return 60 * 1000
|
||||
}
|
||||
|
||||
const [, numStr, unit] = match
|
||||
const num = parseInt(String(numStr), 10)
|
||||
|
||||
switch (unit) {
|
||||
case 'd': return num * 24 * 3600 * 1000
|
||||
case 'h': return num * 3600 * 1000
|
||||
case 'm': return num * 60 * 1000
|
||||
case 's': return num * 1000
|
||||
default: return 60 * 1000
|
||||
}
|
||||
}
|
||||
|
||||
export default defineNuxtPlugin(nuxtApp => {
|
||||
nuxtApp.vueApp.directive('rtime', {
|
||||
mounted(el: RTimeElement, binding) {
|
||||
const intervalMs = parseInterval(binding.arg)
|
||||
const update = () => el.textContent = moment(binding.value).fromNow()
|
||||
|
||||
update()
|
||||
el._next_timer = window.setInterval(update, intervalMs)
|
||||
},
|
||||
updated(el: RTimeElement, binding) {
|
||||
if (binding.oldValue !== binding.value) {
|
||||
if (null != el._next_timer) clearInterval(el._next_timer)
|
||||
|
||||
const intervalMs = parseInterval(binding.arg)
|
||||
const update = () => el.textContent = moment(binding.value).fromNow()
|
||||
|
||||
update()
|
||||
el._next_timer = window.setInterval(update, intervalMs)
|
||||
}
|
||||
},
|
||||
beforeUnmount(el: RTimeElement) {
|
||||
if (null != el._next_timer) clearInterval(el._next_timer)
|
||||
}
|
||||
})
|
||||
|
||||
return {}
|
||||
})
|
||||
12
ui/app/plugins/toast.ts
Normal file
12
ui/app/plugins/toast.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { defineNuxtPlugin } from '#app'
|
||||
import Toast, { type PluginOptions } from 'vue-toastification'
|
||||
import 'vue-toastification/dist/index.css'
|
||||
|
||||
export default defineNuxtPlugin(nuxtApp => {
|
||||
nuxtApp.vueApp.use(Toast, {
|
||||
transition: "Vue-Toastification__bounce",
|
||||
//position: "bottom-right",
|
||||
maxToasts: 5,
|
||||
newestOnTop: true,
|
||||
} as PluginOptions)
|
||||
})
|
||||
|
|
@ -26,6 +26,7 @@ export const useConfigStore = defineStore('config', () => {
|
|||
app_commit_sha: '',
|
||||
app_build_date: '',
|
||||
app_branch: '',
|
||||
started: 0,
|
||||
},
|
||||
presets: [
|
||||
{
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { io } from "socket.io-client";
|
||||
import { ag } from "~/utils/index"
|
||||
import type { Socket as IOSocket, SocketOptions } from "socket.io-client"
|
||||
import type { ManagerOptions } from "socket.io-client";
|
||||
import type { StoreItem } from "~/types/store";
|
||||
|
||||
export const useSocketStore = defineStore('socket', () => {
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
|
|
@ -65,7 +65,7 @@ export const useSocketStore = defineStore('socket', () => {
|
|||
on('item_added', stream => {
|
||||
const json = JSON.parse(stream);
|
||||
stateStore.add('queue', json.data._id, json.data);
|
||||
toast.success(`Item queued: ${ag(stateStore.get('queue', json.data._id, {}), 'title')}`);
|
||||
toast.success(`Item queued: ${ag(stateStore.get('queue', json.data._id, {} as StoreItem), 'title')}`);
|
||||
});
|
||||
|
||||
on(['log_info', 'log_success', 'log_warning', 'log_error'], (event: string, stream: string) => {
|
||||
|
|
@ -111,7 +111,7 @@ export const useSocketStore = defineStore('socket', () => {
|
|||
return
|
||||
}
|
||||
|
||||
toast.warning(`Download cancelled: ${ag(stateStore.get('queue', id, {}), 'title')}`);
|
||||
toast.warning(`Download cancelled: ${ag(stateStore.get('queue', id, {} as StoreItem), 'title')}`);
|
||||
|
||||
if (true === stateStore.has('queue', id)) {
|
||||
stateStore.remove('queue', id);
|
||||
|
|
@ -1,24 +1,22 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { reactive, toRefs } from 'vue'
|
||||
import type { StoreItem } from '~/types/store'
|
||||
|
||||
type StateType = 'queue' | 'history'
|
||||
type KeyType = string
|
||||
type ValueType = StoreItem
|
||||
|
||||
interface State {
|
||||
queue: Record<KeyType, ValueType>
|
||||
history: Record<KeyType, ValueType>
|
||||
queue: Record<KeyType, StoreItem>
|
||||
history: Record<KeyType, StoreItem>
|
||||
}
|
||||
|
||||
export const useStateStore = defineStore('state', () => {
|
||||
const state = reactive<State>({ queue: {}, history: {} })
|
||||
|
||||
const add = (type: StateType, key: KeyType, value: ValueType): void => {
|
||||
const add = (type: StateType, key: KeyType, value: StoreItem): void => {
|
||||
state[type][key] = value
|
||||
}
|
||||
|
||||
const update = (type: StateType, key: KeyType, value: ValueType): void => {
|
||||
const update = (type: StateType, key: KeyType, value: StoreItem): void => {
|
||||
state[type][key] = value
|
||||
}
|
||||
|
||||
|
|
@ -28,7 +26,7 @@ export const useStateStore = defineStore('state', () => {
|
|||
}
|
||||
}
|
||||
|
||||
const get = (type: StateType, key: KeyType, defaultValue: ValueType | null | object = null): ValueType | null => {
|
||||
const get = (type: StateType, key: KeyType, defaultValue: StoreItem | null = null): StoreItem | null => {
|
||||
return state[type][key] || defaultValue
|
||||
}
|
||||
|
||||
|
|
@ -40,7 +38,7 @@ export const useStateStore = defineStore('state', () => {
|
|||
state[type] = {}
|
||||
}
|
||||
|
||||
const addAll = (type: StateType, data: Record<KeyType, ValueType>): void => {
|
||||
const addAll = (type: StateType, data: Record<KeyType, StoreItem>): void => {
|
||||
state[type] = data
|
||||
}
|
||||
|
||||
0
ui/types/item.d.ts → ui/app/types/item.d.ts
vendored
0
ui/types/item.d.ts → ui/app/types/item.d.ts
vendored
|
|
@ -584,16 +584,72 @@ const formatTime = (seconds: number): string => {
|
|||
*/
|
||||
const sleep = (seconds: number): Promise<void> => new Promise(resolve => setTimeout(resolve, seconds * 1000))
|
||||
|
||||
/**
|
||||
* Waits for the test function to return a truthy value.
|
||||
*
|
||||
* @param test - The function to test
|
||||
* @param timeout_ms - The maximum time to wait in milliseconds.
|
||||
* @param frequency - The frequency to check the test function in milliseconds.
|
||||
*
|
||||
* @returns The result of the test function.
|
||||
*/
|
||||
const awaiter = async (test: Function, timeout_ms: number = 20 * 1000, frequency: number = 200) => {
|
||||
if (typeof (test) != "function") {
|
||||
throw new Error("test should be a function in awaiter(test, [timeout_ms], [frequency])")
|
||||
}
|
||||
|
||||
const isNotTruthy = (val: any) => val === undefined || val === false || val === null || val.length === 0;
|
||||
const endTime: number = Date.now() + timeout_ms;
|
||||
|
||||
let result = test();
|
||||
|
||||
while (isNotTruthy(result)) {
|
||||
if (Date.now() > endTime) {
|
||||
return false;
|
||||
}
|
||||
await sleep(frequency);
|
||||
result = test();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a JavaScript object into a URL-safe base64 string.
|
||||
*
|
||||
* @param obj - The object to encode.
|
||||
* @returns A URL-safe base64-encoded string.
|
||||
*/
|
||||
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(/=+$/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a URL-safe base64 string into an object.
|
||||
*
|
||||
* @param str - The URL-safe base64-encoded string.
|
||||
* @returns The decoded JavaScript object.
|
||||
*/
|
||||
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 {
|
||||
convertCliOptions, separators, getSeparatorsName, iTrim, eTrim, sTrim, ucFirst,
|
||||
separators, convertCliOptions, getSeparatorsName, iTrim, eTrim, sTrim, ucFirst,
|
||||
getValue, ag, ag_set, awaitElement, r, copyText, dEvent, makePagination, encodePath,
|
||||
request, removeANSIColors, dec2hex, makeId, basename, dirname, getQueryParams,
|
||||
makeDownload,
|
||||
formatBytes,
|
||||
has_data,
|
||||
toggleClass,
|
||||
cleanObject,
|
||||
uri,
|
||||
formatTime,
|
||||
sleep
|
||||
makeDownload, formatBytes, has_data, toggleClass, cleanObject, uri, formatTime,
|
||||
sleep, awaiter, encode, decode
|
||||
}
|
||||
|
|
@ -5,9 +5,28 @@ function match_str(filterStr: string, dct: Record<string, any>, incomplete: bool
|
|||
return true;
|
||||
}
|
||||
|
||||
return filterStr
|
||||
.split(/(?<!\\)&/)
|
||||
.every(filterPart => _match_one(filterPart.replace(/\\&/g, '&'), dct, incomplete));
|
||||
if (/\s*&\s*$/.test(filterStr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const filterParts = filterStr
|
||||
.split(/(?:(^|[^\\]))&/g)
|
||||
.reduce((parts, part, index, array) => {
|
||||
if (index % 2 === 0) {
|
||||
parts.push((part + (array[index + 1] || '')).replace(/\\&/g, '&'));
|
||||
}
|
||||
return parts;
|
||||
}, [] as string[]);
|
||||
|
||||
if (filterParts.some(fp => !fp.trim())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return filterParts.every(filterPart => _match_one(filterPart, dct, incomplete));
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function lookup_unit_table(unitTable: Record<string, number>, s: string, strict = false): number | null {
|
||||
|
|
@ -101,7 +120,14 @@ function _match_one(filterPart: string, dct: Record<string, any>, incomplete: bo
|
|||
const opFn = negation ? (a: any, b: any) => !unnegatedOp(a, b) : unnegatedOp;
|
||||
|
||||
let value = quoted ?? plain ?? '';
|
||||
if (quote) value = value.replace(new RegExp(`\\\\${quote}`, 'g'), quote);
|
||||
|
||||
if (quote) {
|
||||
value = value.replace(new RegExp(`\\\\${quote}`, 'g'), quote);
|
||||
}
|
||||
|
||||
if (!(key! in dct)) {
|
||||
return isIncomplete(key!) || Boolean(noneInclusive);
|
||||
}
|
||||
|
||||
const actual = dct[key!];
|
||||
let numeric: number | null = null;
|
||||
|
|
@ -117,10 +143,6 @@ function _match_one(filterPart: string, dct: Record<string, any>, incomplete: bo
|
|||
throw new Error(`Operator ${op} only supports string values!`);
|
||||
}
|
||||
|
||||
if (actual == null) {
|
||||
return isIncomplete(key!) || Boolean(noneInclusive);
|
||||
}
|
||||
|
||||
return opFn(actual, numeric !== null ? numeric : value);
|
||||
}
|
||||
|
||||
|
|
@ -133,12 +155,12 @@ function _match_one(filterPart: string, dct: Record<string, any>, incomplete: bo
|
|||
const mu = filterPart.trim().match(unaryRe);
|
||||
if (mu?.groups) {
|
||||
const { op, key } = mu.groups;
|
||||
const actual = dct[key!];
|
||||
|
||||
if (actual === undefined && isIncomplete(key!)) {
|
||||
return true;
|
||||
if (!(key! in dct)) {
|
||||
return isIncomplete(key!);
|
||||
}
|
||||
|
||||
const actual = dct[key!];
|
||||
const unaryOp = UNARY_OPERATORS[op!];
|
||||
|
||||
if (!unaryOp) {
|
||||
|
|
@ -70,7 +70,11 @@ export default defineNuxtConfig({
|
|||
},
|
||||
...extraNitro,
|
||||
},
|
||||
|
||||
vite: {
|
||||
server: {
|
||||
allowedHosts: true,
|
||||
}
|
||||
},
|
||||
telemetry: false,
|
||||
compatibilityDate: "2024-07-13",
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
let observer;
|
||||
const AUTO_SCROLL_EVENTS = ['scroll', 'mousewheel', 'touchmove'];
|
||||
let events_handlers = [];
|
||||
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
nuxtApp.vueApp.directive('autoscroll', {
|
||||
mounted: async el => {
|
||||
let scrolledToBottom = true;
|
||||
|
||||
AUTO_SCROLL_EVENTS.forEach((ev, index) => {
|
||||
events_handlers[index] = () => {
|
||||
scrolledToBottom = (el.scrollHeight - el.scrollTop) - el.clientHeight <= 80;
|
||||
}
|
||||
el.addEventListener(ev, events_handlers[index], { passive: true });
|
||||
});
|
||||
|
||||
observer = new MutationObserver(_ => {
|
||||
if (false === scrolledToBottom) {
|
||||
return;
|
||||
}
|
||||
el.scrollTop = el.scrollHeight;
|
||||
});
|
||||
|
||||
observer.observe(el, { childList: true, subtree: true });
|
||||
},
|
||||
unmounted: el => {
|
||||
AUTO_SCROLL_EVENTS.forEach((ev, index) => {
|
||||
el.removeEventListener(ev, events_handlers[index], { passive: true });
|
||||
});
|
||||
observer.disconnect();
|
||||
},
|
||||
})
|
||||
});
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
import { defineNuxtPlugin } from '#app'
|
||||
import moment from 'moment'
|
||||
|
||||
const parseInterval = arg => {
|
||||
const match = arg.match(/^(\d+)([smhd])$/)
|
||||
if (!match) {
|
||||
return 60 * 1000
|
||||
}
|
||||
|
||||
const [, numStr, unit] = match
|
||||
const num = parseInt(numStr, 10)
|
||||
|
||||
switch (unit) {
|
||||
case 'd': return num * 24 * 3600 * 1000
|
||||
case 'h': return num * 3600 * 1000
|
||||
case 'm': return num * 60 * 1000
|
||||
case 's': return num * 1000
|
||||
default: return 60 * 1000
|
||||
}
|
||||
}
|
||||
|
||||
export default defineNuxtPlugin(nuxtApp => nuxtApp.vueApp.directive('rtime', {
|
||||
mounted(el, binding) {
|
||||
const intervalMs = binding.arg ? parseInterval(binding.arg) : 60 * 1000
|
||||
const update = () => el.textContent = moment(binding.value).fromNow()
|
||||
|
||||
update()
|
||||
el._next_timer = window.setInterval(update, intervalMs)
|
||||
},
|
||||
updated(el, binding) {
|
||||
if (binding.oldValue !== binding.value) {
|
||||
if (null != el._next_timer) {
|
||||
clearInterval(el._next_timer)
|
||||
}
|
||||
|
||||
const intervalMs = binding.arg ? parseInterval(binding.arg) : 60 * 1000
|
||||
|
||||
const update = () => el.textContent = moment(binding.value).fromNow()
|
||||
update()
|
||||
|
||||
el._next_timer = window.setInterval(update, intervalMs)
|
||||
}
|
||||
},
|
||||
beforeUnmount(el) {
|
||||
if (null != el._next_timer) {
|
||||
clearInterval(el._next_timer)
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import Toast from 'vue-toastification'
|
||||
import 'vue-toastification/dist/index.css'
|
||||
|
||||
export default defineNuxtPlugin(nuxtApp => nuxtApp.vueApp.use(Toast, {
|
||||
transition: "Vue-Toastification__bounce",
|
||||
//position: "bottom-right",
|
||||
maxToasts: 5,
|
||||
newestOnTop: true
|
||||
}))
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
"compilerOptions": {
|
||||
"allowImportingTsExtensions": false,
|
||||
"types": [
|
||||
"./types/globals"
|
||||
"./app/types/globals"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
// for non arrays, length is undefined, so != 0
|
||||
const isNotTruthy = (val: any) => val === undefined || val === false || val === null || val.length === 0;
|
||||
|
||||
/**
|
||||
* Waits for the test function to return a truthy value.
|
||||
*
|
||||
* @param test - The function to test
|
||||
* @param timeout_ms - The maximum time to wait in milliseconds.
|
||||
* @param frequency - The frequency to check the test function in milliseconds.
|
||||
*
|
||||
* @returns The result of the test function.
|
||||
*/
|
||||
export default async function awaiter(test: Function, timeout_ms: number = 20 * 1000, frequency: number = 200) {
|
||||
if (typeof (test) != "function") {
|
||||
throw new Error("test should be a function in awaiter(test, [timeout_ms], [frequency])")
|
||||
}
|
||||
|
||||
const endTime: number = Date.now() + timeout_ms;
|
||||
|
||||
let result = test();
|
||||
|
||||
while (isNotTruthy(result)) {
|
||||
if (Date.now() > endTime) {
|
||||
return false;
|
||||
}
|
||||
await sleep(frequency);
|
||||
result = test();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
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 }
|
||||
Loading…
Reference in a new issue