@talex-touch/utils 1.0.29 → 1.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,202 @@
1
+ import { TalexTouch } from '../types'
2
+
3
+ /**
4
+ * Window animation controller return type
5
+ */
6
+ export interface WindowAnimationController {
7
+ /**
8
+ * Update window height with animation
9
+ * @param newHeight - The new height to animate to
10
+ * @param duration - Animation duration in seconds (default: 0.5)
11
+ * @returns Promise that resolves to true if animation completed successfully, false if interrupted
12
+ */
13
+ updateHeight: (newHeight: number, duration?: number) => Promise<boolean>
14
+
15
+ /**
16
+ * Cancel current animation
17
+ * @returns Promise that resolves to true if there was an animation to cancel, false otherwise
18
+ */
19
+ cancel: () => Promise<boolean>
20
+
21
+ /**
22
+ * Toggle window visibility
23
+ * @param visible - Optional parameter to explicitly set visibility state
24
+ * @returns Promise that resolves to true if operation completed successfully, false otherwise
25
+ */
26
+ toggleWindow: (visible?: boolean) => Promise<boolean>
27
+
28
+ /**
29
+ * Change current window instance
30
+ * @param newWindow - New TouchWindow instance
31
+ */
32
+ changeWindow: (newWindow: TalexTouch.ITouchWindow) => void
33
+ }
34
+
35
+ /**
36
+ * Tracks the state of an animation
37
+ */
38
+ interface AnimationState {
39
+ intervalId: NodeJS.Timeout | null
40
+ completed: boolean
41
+ }
42
+
43
+ /**
44
+ * Simple easing function for smooth animation
45
+ */
46
+ function easeInOutCubic(t: number): number {
47
+ return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2
48
+ }
49
+
50
+ /**
51
+ * Use native Node.js animation for window (main process only)
52
+ * @param window - TouchWindow instance (optional, can be set later with changeWindow)
53
+ * @returns WindowAnimationController with updateHeight, cancel, toggleWindow, and changeWindow methods
54
+ */
55
+ export function useWindowAnimation(window?: TalexTouch.ITouchWindow): WindowAnimationController {
56
+ // Store current window reference inside the function scope
57
+ let currentWindow: TalexTouch.ITouchWindow | null = window || null
58
+
59
+ const animationState: AnimationState = {
60
+ intervalId: null,
61
+ completed: false
62
+ }
63
+
64
+ /**
65
+ * Check if current window is valid
66
+ * @returns true if window is valid, false otherwise
67
+ */
68
+ const isWindowValid = (): boolean => {
69
+ return (
70
+ currentWindow !== null &&
71
+ currentWindow.window !== null &&
72
+ !currentWindow.window.isDestroyed()
73
+ )
74
+ }
75
+
76
+ /**
77
+ * Get current window with validation
78
+ * @returns current window or throws error if invalid
79
+ */
80
+ const getCurrentWindow = (): TalexTouch.ITouchWindow => {
81
+ if (!isWindowValid()) {
82
+ throw new Error('Window is not valid or has been destroyed')
83
+ }
84
+ return currentWindow!
85
+ }
86
+
87
+ const updateHeight = async (newHeight: number, duration: number = 0.5): Promise<boolean> => {
88
+ try {
89
+ const window = getCurrentWindow()
90
+
91
+ // Cancel any existing animation
92
+ if (animationState.intervalId) {
93
+ clearInterval(animationState.intervalId)
94
+ animationState.intervalId = null
95
+ }
96
+
97
+ // Reset state for new animation
98
+ animationState.completed = false
99
+
100
+ const browserWindow = window.window
101
+ const [currentWidth, currentHeight] = browserWindow.getSize()
102
+ const [x, y] = browserWindow.getPosition()
103
+
104
+ const startHeight = currentHeight
105
+ const heightDelta = newHeight - startHeight
106
+ const startTime = Date.now()
107
+ const durationMs = duration * 1000
108
+
109
+ return new Promise<boolean>((resolve) => {
110
+ animationState.intervalId = setInterval(() => {
111
+ // Check if window is still valid
112
+ if (!isWindowValid()) {
113
+ if (animationState.intervalId) {
114
+ clearInterval(animationState.intervalId)
115
+ animationState.intervalId = null
116
+ }
117
+ resolve(false)
118
+ return
119
+ }
120
+
121
+ const elapsed = Date.now() - startTime
122
+ const progress = Math.min(elapsed / durationMs, 1)
123
+ const easedProgress = easeInOutCubic(progress)
124
+ const animatedHeight = Math.round(startHeight + heightDelta * easedProgress)
125
+
126
+ browserWindow.setSize(currentWidth, animatedHeight)
127
+ browserWindow.setPosition(x, y)
128
+
129
+ if (progress >= 1) {
130
+ if (animationState.intervalId) {
131
+ clearInterval(animationState.intervalId)
132
+ animationState.intervalId = null
133
+ }
134
+ animationState.completed = true
135
+ resolve(true)
136
+ }
137
+ }, 16) // ~60fps
138
+ })
139
+ } catch (error) {
140
+ console.error('Error in updateHeight:', error)
141
+ return Promise.resolve(false)
142
+ }
143
+ }
144
+
145
+ const cancel = async (): Promise<boolean> => {
146
+ if (animationState.intervalId) {
147
+ clearInterval(animationState.intervalId)
148
+ animationState.intervalId = null
149
+ return Promise.resolve(true)
150
+ }
151
+ return Promise.resolve(false)
152
+ }
153
+
154
+ const toggleWindow = async (visible?: boolean): Promise<boolean> => {
155
+ try {
156
+ const window = getCurrentWindow()
157
+ const browserWindow = window.window
158
+
159
+ // Determine target visibility state
160
+ const targetVisible = visible !== undefined ? visible : !browserWindow.isVisible()
161
+
162
+ if (targetVisible) {
163
+ // Show window
164
+ browserWindow.show()
165
+ } else {
166
+ // Hide window
167
+ if (process.platform === 'darwin') {
168
+ // On macOS, we can simply hide the window
169
+ browserWindow.hide()
170
+ } else {
171
+ // On other platforms, move window far off-screen before hiding
172
+ browserWindow.setPosition(-100000, -100000)
173
+ browserWindow.hide()
174
+ }
175
+ }
176
+
177
+ return Promise.resolve(true)
178
+ } catch (error) {
179
+ console.error('Error in toggleWindow:', error)
180
+ return Promise.resolve(false)
181
+ }
182
+ }
183
+
184
+ const changeWindow = (newWindow: TalexTouch.ITouchWindow): void => {
185
+ // Cancel any ongoing animation
186
+ if (animationState.intervalId) {
187
+ clearInterval(animationState.intervalId)
188
+ animationState.intervalId = null
189
+ }
190
+
191
+ // Set new window
192
+ currentWindow = newWindow
193
+ }
194
+
195
+ return {
196
+ updateHeight,
197
+ cancel,
198
+ toggleWindow,
199
+ changeWindow
200
+ }
201
+ }
202
+
@@ -0,0 +1,56 @@
1
+ export interface ClerkUser {
2
+ id: string
3
+ emailAddresses: Array<{
4
+ emailAddress: string
5
+ id: string
6
+ }>
7
+ firstName?: string
8
+ lastName?: string
9
+ username?: string
10
+ imageUrl?: string
11
+ createdAt: string
12
+ updatedAt: string
13
+ }
14
+
15
+ export interface ClerkAuthState {
16
+ isLoaded: boolean
17
+ isSignedIn: boolean
18
+ user: ClerkUser | null
19
+ sessionId: string | null
20
+ }
21
+
22
+ export interface LoginOptions {
23
+ onSuccess?: (user: any) => void
24
+ onError?: (error: any) => void
25
+ }
26
+
27
+ export interface LoginResult {
28
+ success: boolean
29
+ user?: any
30
+ error?: any
31
+ }
32
+
33
+ export interface CurrentUser {
34
+ id: string
35
+ name: string
36
+ email: string
37
+ avatar?: string
38
+ provider: string
39
+ }
40
+
41
+ /**
42
+ * Clerk Auth Config
43
+ */
44
+ export interface ClerkConfig {
45
+ publishableKey: string
46
+ domain?: string
47
+ signInUrl?: string
48
+ signUpUrl?: string
49
+ afterSignInUrl?: string
50
+ afterSignUpUrl?: string
51
+ }
52
+
53
+ export type ClerkResourceSnapshot = {
54
+ user?: any | null
55
+ session?: { id?: string | null } | null
56
+ }
package/auth/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './clerk-types'
2
+ export * from './useClerkConfig'
3
+ export * from './useClerkProvider'
4
+ export * from './useAuthState'
File without changes
@@ -0,0 +1,44 @@
1
+ import { createGlobalState } from '@vueuse/core'
2
+ import { shallowReactive, computed } from 'vue'
3
+ import { ClerkAuthState, CurrentUser } from './clerk-types'
4
+
5
+ export const useAuthState = createGlobalState(() => {
6
+ const authState = shallowReactive<ClerkAuthState>({
7
+ isLoaded: false,
8
+ isSignedIn: false,
9
+ user: null,
10
+ sessionId: null
11
+ })
12
+
13
+ return { authState }
14
+ })
15
+
16
+ export function useCurrentUser() {
17
+ const { authState } = useAuthState()
18
+
19
+ const currentUser = computed((): CurrentUser | null => {
20
+ if (!authState.isSignedIn || !authState.user) {
21
+ return null
22
+ }
23
+
24
+ const { firstName, lastName, username, imageUrl } = authState.user
25
+ let name = ''
26
+ if (firstName || lastName) {
27
+ name = [firstName, lastName].filter(Boolean).join(' ')
28
+ } else {
29
+ name = username || ''
30
+ }
31
+
32
+ const email = authState.user.emailAddresses?.[0]?.emailAddress || ''
33
+
34
+ return {
35
+ id: authState.user.id,
36
+ name,
37
+ email,
38
+ avatar: imageUrl,
39
+ provider: 'clerk'
40
+ }
41
+ })
42
+
43
+ return { currentUser, authState }
44
+ }
@@ -0,0 +1,40 @@
1
+ import { ClerkConfig } from "./clerk-types"
2
+
3
+ const clerkPublishableKey = (import.meta.env as any).VITE_CLERK_PUBLISHABLE_KEY
4
+ const clerkDomain = (import.meta.env as any).VITE_CLERK_DOMAIN
5
+
6
+ if (!clerkPublishableKey?.length) {
7
+ throw new Error('VITE_CLERK_PUBLISHABLE_KEY is not set')
8
+ }
9
+
10
+ export const CLERK_PUBLISHABLE_KEY_KEY = 'clerk-publishable-key'
11
+
12
+ export const defaultClerkConfig: ClerkConfig = {
13
+ publishableKey: clerkPublishableKey,
14
+ domain: clerkDomain,
15
+ signInUrl: '/sign-in',
16
+ signUpUrl: '/sign-up',
17
+ afterSignInUrl: '/home',
18
+ afterSignUpUrl: '/home'
19
+ }
20
+
21
+ export function useClerkConfig() {
22
+ const getClerkConfig = (): ClerkConfig => {
23
+ return {
24
+ ...defaultClerkConfig,
25
+ publishableKey:
26
+ localStorage.getItem(CLERK_PUBLISHABLE_KEY_KEY) || defaultClerkConfig.publishableKey
27
+ }
28
+ }
29
+
30
+ const setClerkConfig = (config: Partial<ClerkConfig>): void => {
31
+ if (config.publishableKey) {
32
+ localStorage.setItem(CLERK_PUBLISHABLE_KEY_KEY, config.publishableKey)
33
+ }
34
+ }
35
+
36
+ return {
37
+ getClerkConfig,
38
+ setClerkConfig
39
+ }
40
+ }
@@ -0,0 +1,51 @@
1
+ import { Clerk } from '@clerk/clerk-js'
2
+ import { useClerkConfig } from './useClerkConfig'
3
+
4
+ let clerkInstance: Clerk | null = null
5
+
6
+ export function useClerkProvider() {
7
+ const initializeClerk = async (): Promise<Clerk> => {
8
+ if (clerkInstance) {
9
+ return clerkInstance
10
+ }
11
+
12
+ const { getClerkConfig } = useClerkConfig()
13
+ const config = getClerkConfig()
14
+
15
+ if (!config.publishableKey) {
16
+ throw new Error('Clerk publishable key is required')
17
+ }
18
+
19
+ try {
20
+ clerkInstance = new Clerk(config.publishableKey)
21
+ await clerkInstance.load()
22
+
23
+ console.log('Clerk initialized successfully')
24
+ return clerkInstance
25
+ } catch (error) {
26
+ console.error('Failed to initialize Clerk:', error)
27
+ throw error
28
+ }
29
+ }
30
+
31
+ const getClerk = (): Clerk | null => {
32
+ return clerkInstance
33
+ }
34
+
35
+ const isClerkInitialized = (): boolean => {
36
+ return clerkInstance !== null
37
+ }
38
+
39
+ const cleanupClerk = (): void => {
40
+ if (clerkInstance) {
41
+ clerkInstance = null
42
+ }
43
+ }
44
+
45
+ return {
46
+ initializeClerk,
47
+ getClerk,
48
+ isClerkInitialized,
49
+ cleanupClerk
50
+ }
51
+ }
package/channel/index.ts CHANGED
@@ -175,3 +175,9 @@ export interface StandardChannelData extends RawStandardChannelData {
175
175
  export type IChannelData = any //boolean | number | string | null | undefined | {
176
176
  // [prop: string]: any
177
177
  // }
178
+
179
+ // Default export for Vite compatibility (only values, not types)
180
+ export default {
181
+ ChannelType,
182
+ DataCode
183
+ }
@@ -7,7 +7,10 @@
7
7
  * @version 1.0.0
8
8
  */
9
9
 
10
- import path from 'path'
10
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
11
+ const path = typeof window === 'undefined'
12
+ ? require('path')
13
+ : require('path-browserify')
11
14
  import {
12
15
  type FileScanOptions,
13
16
  DEFAULT_SCAN_OPTIONS,
@@ -14,7 +14,7 @@ const _appSettingOriginData = {
14
14
  },
15
15
  lang: {
16
16
  followSystem: true,
17
- locale: 0,
17
+ locale: 'zh-CN',
18
18
  },
19
19
  keyBind: {
20
20
  summon: 'CTRL + E',
@@ -39,6 +39,20 @@ const _appSettingOriginData = {
39
39
  searchEngine: {
40
40
  logsEnabled: false,
41
41
  },
42
+ window: {
43
+ closeToTray: true,
44
+ startMinimized: false,
45
+ startSilent: false,
46
+ },
47
+ setup: {
48
+ accessibility: false,
49
+ notifications: false,
50
+ autoStart: false,
51
+ showTray: true,
52
+ adminPrivileges: false,
53
+ hideDock: false,
54
+ },
55
+ layout: 'simple',
42
56
  };
43
57
 
44
58
  export const appSettingOriginData = Object.freeze(_appSettingOriginData)
@@ -1,4 +1,7 @@
1
- import path from 'path-browserify'
1
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
2
+ const path = typeof window === 'undefined'
3
+ ? require('path')
4
+ : require('path-browserify')
2
5
 
3
6
  /**
4
7
  * Enum for various file types.
@@ -19,6 +19,7 @@ export class PollingService {
19
19
  private tasks = new Map<string, PollingTask>();
20
20
  private timerId: NodeJS.Timeout | null = null;
21
21
  private isRunning = false;
22
+ private quitListenerCleanup?: () => void;
22
23
 
23
24
  private constructor() {
24
25
  // Private constructor to enforce singleton pattern
@@ -111,17 +112,53 @@ export class PollingService {
111
112
  */
112
113
  public start(): void {
113
114
  if (this.isRunning) {
115
+ console.warn('[PollingService] Already running, skipping start.');
114
116
  return;
115
117
  }
116
118
  this.isRunning = true;
117
- console.debug('[PollingService] Service started.');
119
+ console.log('[PollingService] Polling service started');
120
+ this._setupQuitListener();
118
121
  this._reschedule();
119
122
  }
120
123
 
124
+ /**
125
+ * Sets up Electron app quit listener if running in Electron environment
126
+ * Uses lazy resolution to avoid hard dependency on Electron
127
+ */
128
+ private _setupQuitListener(): void {
129
+ // Check if we're in Electron environment
130
+ try {
131
+ // Use dynamic require to avoid hard dependency on Electron
132
+ // Similar to the approach used in packages/utils/plugin/channel.ts
133
+ const electron = (globalThis as any)?.electron ??
134
+ (typeof require !== 'undefined' ? require('electron') : null);
135
+
136
+ if (electron?.app) {
137
+ const app = electron.app;
138
+
139
+ // Listen to before-quit event
140
+ const quitHandler = () => {
141
+ this.stop('app quit');
142
+ };
143
+
144
+ app.on('before-quit', quitHandler);
145
+
146
+ // Store cleanup function
147
+ this.quitListenerCleanup = () => {
148
+ app.removeListener('before-quit', quitHandler);
149
+ };
150
+ }
151
+ } catch (error) {
152
+ // Not in Electron environment or Electron not available
153
+ // This is fine, just skip the quit listener setup
154
+ }
155
+ }
156
+
121
157
  /**
122
158
  * Stops the polling service and clears all scheduled tasks.
159
+ * @param reason - Optional reason for stopping the service (for logging purposes)
123
160
  */
124
- public stop(): void {
161
+ public stop(reason?: string): void {
125
162
  if (!this.isRunning) {
126
163
  return;
127
164
  }
@@ -130,7 +167,18 @@ export class PollingService {
130
167
  clearTimeout(this.timerId);
131
168
  this.timerId = null;
132
169
  }
133
- console.log('[PollingService] Service stopped.');
170
+
171
+ // Clean up quit listener
172
+ if (this.quitListenerCleanup) {
173
+ this.quitListenerCleanup();
174
+ this.quitListenerCleanup = undefined;
175
+ }
176
+
177
+ if (reason) {
178
+ console.log(`[PollingService] Stopping polling service: ${reason}`);
179
+ } else {
180
+ console.log('[PollingService] Polling service stopped');
181
+ }
134
182
  }
135
183
 
136
184
  private _reschedule(): void {
@@ -140,7 +140,7 @@ The core interface for all search results:
140
140
  interface ISearchItem {
141
141
  name: string; // Display name
142
142
  desc: string; // Description
143
- icon: IPluginIcon; // Icon configuration
143
+ icon: ITuffIcon; // Icon configuration
144
144
  push: boolean; // Push mode support
145
145
  names: string[]; // Searchable names
146
146
  keyWords: string[]; // Search keywords
@@ -972,7 +972,7 @@ class TuffUtils {
972
972
  * @param type - 图标类型
973
973
  * @returns {TuffIcon} 创建的图标对象
974
974
  */
975
- static createIcon(value: string, type: 'emoji' | 'url' | 'base64' | 'component' = 'emoji'): TuffIcon {
975
+ static createIcon(value: string, type: 'emoji' | 'url' | 'file' = 'emoji'): TuffIcon {
976
976
  return {
977
977
  type,
978
978
  value