diff --git a/package.json b/package.json index 214f53a..52f8b0b 100644 --- a/package.json +++ b/package.json @@ -26,21 +26,22 @@ "daisyui": "^5.5.18", "jsencrypt": "^3.5.4", "luxon": "^3.7.2", + "sherlockjs": "^1.4.2", "uuid": "^13.0.0", "vue": "^3.5.28", "vue-router": "^4.6.4" }, "devDependencies": { + "@antfu/eslint-config": "^7.4.3", "@tailwindcss/vite": "^4.2.0", "@tauri-apps/cli": "^2", "@vitejs/plugin-vue": "^6.0.4", + "eslint": "^9.39.2", + "eslint-plugin-format": "^1.4.0", "tailwindcss": "^4.2.0", "typescript": "~5.9.3", "vite": "^7.3.1", - "vue-tsc": "^2.1.10", - "@antfu/eslint-config": "^7.4.3", - "eslint": "^9.39.2", - "eslint-plugin-format": "^1.4.0" + "vue-tsc": "^2.1.10" }, "packageManager": "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a" } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 23cc60d..6a9b092 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ importers: luxon: specifier: ^3.7.2 version: 3.7.2 + sherlockjs: + specifier: ^1.4.2 + version: 1.4.2 uuid: specifier: ^13.0.0 version: 13.0.0 @@ -1947,6 +1950,9 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + sherlockjs@1.4.2: + resolution: {integrity: sha512-Liynk2FRTyiHLzMqoe1LvRkT3MhMjUphIBKGa1pRovDKXEaygwpha76om/qV9YTG3qFr1+UIifEuH0VU+KVuRA==} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -4094,6 +4100,8 @@ snapshots: shebang-regex@3.0.0: {} + sherlockjs@1.4.2: {} + sisteransi@1.0.5: {} source-map-js@1.2.1: {} diff --git a/src/App.vue b/src/App.vue index 2198eb5..8a87a96 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,9 +1,14 @@ diff --git a/src/components/CreateInput.vue b/src/components/CreateInput.vue new file mode 100644 index 0000000..45b227b --- /dev/null +++ b/src/components/CreateInput.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/src/components/CreateModal.vue b/src/components/CreateModal.vue new file mode 100644 index 0000000..643e6ed --- /dev/null +++ b/src/components/CreateModal.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/src/components/HelpPanel.vue b/src/components/HelpPanel.vue new file mode 100644 index 0000000..67bd18a --- /dev/null +++ b/src/components/HelpPanel.vue @@ -0,0 +1,92 @@ + + + diff --git a/src/components/TodoItem.vue b/src/components/TodoItem.vue index dcc2b4f..bf89fa1 100644 --- a/src/components/TodoItem.vue +++ b/src/components/TodoItem.vue @@ -1,6 +1,6 @@ diff --git a/src/components/TodoItemTouch.vue b/src/components/TodoItemTouch.vue new file mode 100644 index 0000000..59f35f7 --- /dev/null +++ b/src/components/TodoItemTouch.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/src/composables/useActions.ts b/src/composables/useActions.ts new file mode 100644 index 0000000..abdb7c5 --- /dev/null +++ b/src/composables/useActions.ts @@ -0,0 +1,109 @@ +import type { Task } from '../types.ts' +import { + archiveCommand, + beginCommand, + checkCommand, + deleteCommand, + dueCommand, + editTaskCommand, + flagCommand, + insertTaskCommand, + moveCommand, + restoreCommand, + stopCommand, + switchCommand, + tagRenameCommand, +} from '../utils/actions.ts' +import { parseCommand } from '../utils/parser.ts' +import { useTasks } from './useTasks.ts' + +export default function useActions() { + const { tasks: tasksOriginal, createTask, updateTask } = useTasks() + const run = (value: string) => { + const cmd = parseCommand(value) + let tasksToUpdate: Task[] = [] + let taskToCreate: Pick | null = null + const tasks = JSON.parse(JSON.stringify(tasksOriginal.value)) as Task[] + if (cmd) { + const ids = cmd.id + ? (cmd.id.match(/\d+/g) || []).map(s => Number.parseInt(s)) + : [] + switch (cmd.command.toLowerCase()) { + case 'mv': + case 'move': + tasksToUpdate = moveCommand(tasks, ids, cmd) + break + case 'b': + case 'begin': + tasksToUpdate = beginCommand(tasks, ids) + break + case 'c': + case 'check': + tasksToUpdate = checkCommand(tasks, ids) + break + case 'd': + case 'delete': + tasksToUpdate = deleteCommand(ids, cmd, tasks) + break + case 'fl': + case 'flag': + tasksToUpdate = flagCommand(tasks, ids) + break + case 'st': + case 'stop': + tasksToUpdate = stopCommand(tasks, ids) + break + case 'sw': + case 'switch': + tasksToUpdate = switchCommand(tasks, ids) + break + case 'a': + case 'archive': + tasksToUpdate = archiveCommand(ids, cmd, tasks) + break + case 're': + case 'restore': + tasksToUpdate = restoreCommand(ids, cmd, tasks) + break + case 't': + case 'task': + taskToCreate = insertTaskCommand(cmd) + break + case 'e': + case 'edit': + tasksToUpdate = editTaskCommand(ids, cmd, tasks) + break + case 'due': + tasksToUpdate = dueCommand(ids, cmd, tasks) + break + case 'tr': + case 'tagre': + case 'tagrename': + tasksToUpdate = tagRenameCommand(cmd, tasks) + break + /* Visibility */ + // case 'hide': + // updateCandidate = hideCommand(updateCandidate, cmd) + // break + // case 'show': + // updateCandidate = showCommand(updateCandidate, cmd) + // break + // /* Single command */ + // case 'search': + // updateCandidate = searchCommand(updateCandidate, cmd) + // break + // default: + // updateCandidate = otherCommand(updateCandidate, cmd, state) + // break + } + } + // console.log(tasksToUpdate, taskToCreate) + if (tasksToUpdate) { + updateTask(tasksToUpdate) + } + if (taskToCreate) { + createTask(taskToCreate) + } + } + return { run } +} diff --git a/src/composables/useApi.ts b/src/composables/useApi.ts index 9c736cb..6c033ee 100644 --- a/src/composables/useApi.ts +++ b/src/composables/useApi.ts @@ -1,42 +1,43 @@ -import { useStore } from './useStore.ts'; -import { useCrypto } from './useCrypto.ts'; +import { useCrypto } from './useCrypto.ts' +import { useStore } from './useStore.ts' -const BASE_URL = 'https://automation.deep-node.de/webhook'; +const BASE_URL = 'https://automation.deep-node.de/webhook' -type Settings = { - username: string; - password: string; -}; +interface Settings { + username: string + password: string +} -const isTauri = () => - typeof window !== 'undefined' && Boolean((window as typeof window & { __TAURI__?: unknown }).__TAURI__); +function isTauri() { + return typeof window !== 'undefined' && Boolean((window as typeof window & { __TAURI__?: unknown }).__TAURI__) +} async function buildAuthHeader(): Promise { - const {decrypt} = useCrypto(); - const { getValue } = useStore(); - const settings = await getValue('settings'); - if (!settings) return undefined; - let {username, password} = settings; - password = decrypt(password) as string; + const { decrypt } = useCrypto() + const { getValue } = useStore() + const settings = await getValue('settings') + if (!settings) + return undefined + let { username, password } = settings + password = decrypt(password) as string if (username && password) { - const token = btoa(`${username}:${password}`); - return `Basic ${token}`; + const token = btoa(`${username}:${password}`) + return `Basic ${token}` } - return undefined; + return undefined } export function useApi() { const apiFetch = async (endpoint: string, options: RequestInit = {}) => { - const url = endpoint.startsWith('http') ? endpoint : `${BASE_URL}/${endpoint}`; + const url = endpoint.startsWith('http') ? endpoint : `${BASE_URL}/${endpoint}` - const authHeader = await buildAuthHeader(); - console.log('authHeader',authHeader); + const authHeader = await buildAuthHeader() const headers = { 'Content-Type': 'application/json', ...(authHeader ? { Authorization: authHeader } : {}), ...options.headers, - } as Record; + } as Record const response = isTauri() ? await (await import('@tauri-apps/plugin-http')).fetch(url, { @@ -46,28 +47,28 @@ export function useApi() { : await fetch(url, { ...options, headers, - }); + }) if (!response.ok) { - throw new Error(`API call failed: ${response.statusText}`); + throw new Error(`API call failed: ${response.statusText}`) } - return response; - }; + return response + } const get = (endpoint: string, options: RequestInit = {}) => - apiFetch(endpoint, { ...options, method: 'GET' }); + apiFetch(endpoint, { ...options, method: 'GET' }) const post = (endpoint: string, body: unknown, options: RequestInit = {}) => - apiFetch(endpoint, { ...options, method: 'POST', body: JSON.stringify(body) }); + apiFetch(endpoint, { ...options, method: 'POST', body: JSON.stringify(body) }) const put = (endpoint: string, body: unknown, options: RequestInit = {}) => - apiFetch(endpoint, { ...options, method: 'PUT', body: JSON.stringify(body) }); + apiFetch(endpoint, { ...options, method: 'PUT', body: JSON.stringify(body) }) return { get, post, put, fetch: apiFetch, - }; + } } diff --git a/src/composables/useHistory.ts b/src/composables/useHistory.ts new file mode 100644 index 0000000..4b32cc3 --- /dev/null +++ b/src/composables/useHistory.ts @@ -0,0 +1,49 @@ +import { computed, onMounted, ref } from 'vue' +import { useStore } from './useStore.ts' + +export default function useHistory() { + const { getValue, setValue } = useStore() + + const store = ref([]) + const history = computed(() => store.value) + + const historyIndex = ref(0) + + const resetHistoryIndex = () => { + historyIndex.value = store.value.length - 1 + } + onMounted(async () => { + store.value = await getValue('history') || [] + resetHistoryIndex() + console.log({ s: store.value }) + }) + + const pushHistory = (item: string) => { + if (store.value.length > 20) + store.value.shift() + store.value.push(item.trim()) + setValue('history', store.value) + } + + const matchHistory = (item: string) => { + const match = store.value.filter(i => i.startsWith(item.trim())).pop() + if (match) { + historyIndex.value = store.value.indexOf(match) + return match + } + return '' + } + + const moveHistory = (direction: 'up' | 'down') => { + if (direction === 'up') { + historyIndex.value = Math.max(0, historyIndex.value - 1) + } + else { + historyIndex.value = Math.min(store.value.length - 1, historyIndex.value + 1) + } + } + + const historyItem = computed(() => store.value[historyIndex.value]) + + return { history, pushHistory, matchHistory, moveHistory, historyItem, resetHistoryIndex } +} diff --git a/src/composables/useStore.ts b/src/composables/useStore.ts index ed0231d..df701ad 100644 --- a/src/composables/useStore.ts +++ b/src/composables/useStore.ts @@ -1,52 +1,51 @@ -import type { Store } from '@tauri-apps/plugin-store'; +import type { Store } from '@tauri-apps/plugin-store' -let storePromise: Promise | null = null; +let storePromise: Promise | null = null -const isTauri = () => - typeof window !== 'undefined' && Boolean((window as typeof window & { __TAURI__?: unknown }).__TAURI__); +function isTauri() { + return typeof window !== 'undefined' && Boolean((window as typeof window & { __TAURI__?: unknown }).__TAURI__) +} async function getStore(): Promise { if (!storePromise) { - const { load } = await import('@tauri-apps/plugin-store'); + const { load } = await import('@tauri-apps/plugin-store') storePromise = load('store.json', { autoSave: false, defaults: {}, - }); + }) } - return storePromise; + return storePromise } export function useStore() { const setValue = async (key: string, value: T) => { - console.log('setValue',key,value); if (isTauri()) { - const store = await getStore(); - await store.set(key, value); - await store.save(); - return; + const store = await getStore() + await store.set(key, value) + await store.save() + return } - localStorage.setItem(key, JSON.stringify(value)); - }; + localStorage.setItem(key, JSON.stringify(value)) + } const getValue = async (key: string) => { - console.log('getValue',key); if (isTauri()) { - const store = await getStore(); - return store.get(key); + const store = await getStore() + return store.get(key) } - const rawValue = localStorage.getItem(key); - if (rawValue === null) return null; + const rawValue = localStorage.getItem(key) + if (rawValue === null) + return null try { - return JSON.parse(rawValue) as T; - } catch { - return null; + return JSON.parse(rawValue) as T } - }; + catch { + return null + } + } return { setValue, getValue, - }; + } } - - diff --git a/src/composables/useTasks.ts b/src/composables/useTasks.ts index 51372e1..9f715f4 100644 --- a/src/composables/useTasks.ts +++ b/src/composables/useTasks.ts @@ -1,6 +1,7 @@ import type { Task } from '../types.ts' import { useArrayUnique } from '@vueuse/core' import { ref } from 'vue' +import { TaskStatus } from '../types.ts' import { useApi } from './useApi.ts' const tasks = ref([]) @@ -29,22 +30,23 @@ export function useTasks() { } } - const createTask = async (taskData: Partial) => { + const createTask = async (taskData: Pick) => { + // Get next ID as per current logic in CreateScreen.vue + const nextId = () => tasks.value.sort((a, b) => a.id_ - b.id_).reduce((acc, task) => { + if (task.id_ === acc + 1) + return acc + 1 + return acc + }, 0) + 1 isLoading.value = true error.value = null try { - // Get next ID as per current logic in CreateScreen.vue - const nextId = tasks.value.sort((a, b) => b.id_ - a.id_).reduce((acc, task) => { - if (task.id_ === acc + 1) - return acc + 1 - return task.id_ + 1 - }, 0) - - const newTask: Partial = { - ...taskData, - id_: nextId, + const newTask: Omit = { + id_: nextId(), + status: TaskStatus.WAIT, logs: [], + lastaction: Date.now(), archived: false, + ...taskData, } const data = await api.put('e5880167-9322-4d7b-8a38-e06bae8a7734/list', { tasks: [newTask] }).then(res => res.json()) @@ -62,12 +64,12 @@ export function useTasks() { } } - const updateTask = async (task: Task) => { - console.log('updateTask', task) + const updateTask = async (task: Task | Task[]) => { isLoading.value = true error.value = null + const tasksToUpdate = (Array.isArray(task) ? task : [task]).map(t => ({ ...t, lastaction: Date.now() } as Task)) try { - const data = await api.put('e5880167-9322-4d7b-8a38-e06bae8a7734/list', { tasks: [task] }).then(res => res.json()) + const data = await api.put('e5880167-9322-4d7b-8a38-e06bae8a7734/list', { tasks: tasksToUpdate }).then(res => res.json()) if (data.tasks) { tasks.value = data.tasks } diff --git a/src/router.ts b/src/router.ts index ac15542..26dcbd6 100644 --- a/src/router.ts +++ b/src/router.ts @@ -1,13 +1,10 @@ -import { createWebHistory, createRouter } from 'vue-router' -import ListScreen from './screens/ListScreen.vue'; -import SettingsScreen from './screens/SettingsScreen.vue'; -import CreateScreen from './screens/CreateScreen.vue'; - +import { createRouter, createWebHistory } from 'vue-router' +import ListScreen from './screens/ListScreen.vue' +import SettingsScreen from './screens/SettingsScreen.vue' const routes = [ { path: '/', component: ListScreen }, { path: '/settings', component: SettingsScreen }, - { path: '/create', component: CreateScreen } ] export const router = createRouter({ diff --git a/src/screens/CreateScreen.vue b/src/screens/CreateScreen.vue deleted file mode 100644 index 1955dc4..0000000 --- a/src/screens/CreateScreen.vue +++ /dev/null @@ -1,35 +0,0 @@ - - - - - diff --git a/src/screens/ListScreen.vue b/src/screens/ListScreen.vue index 721e9a0..b98abb8 100644 --- a/src/screens/ListScreen.vue +++ b/src/screens/ListScreen.vue @@ -1,6 +1,5 @@ @@ -27,22 +35,15 @@ const collapsed = ref([])