@talex-touch/utils 1.0.33 → 1.0.34

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.
@@ -8,7 +8,6 @@
8
8
  */
9
9
 
10
10
  import type { FileScanOptions } from './file-scan-constants'
11
- // eslint-disable-next-line @typescript-eslint/no-var-requires
12
11
  import {
13
12
  BASE_BLACKLISTED_DIRS,
14
13
  BLACKLISTED_EXTENSIONS,
@@ -3,6 +3,7 @@ export enum StorageList {
3
3
  SHORTCUT_SETTING = 'shortcut-setting.ini',
4
4
  OPENERS = 'openers.json',
5
5
  IntelligenceConfig = 'aisdk-config',
6
+ MARKET_SOURCES = 'market-sources.json',
6
7
  }
7
8
 
8
9
  /**
package/index.ts CHANGED
@@ -11,3 +11,4 @@ export * from './types'
11
11
  export * from './types/download'
12
12
  export * from './types/icon'
13
13
  export * from './types/update'
14
+ export * from './market'
@@ -0,0 +1,95 @@
1
+ import { StorageList } from '../common/storage/constants'
2
+ import type {
3
+ MarketProviderDefinition,
4
+ MarketSourcesPayload,
5
+ MarketSourcesStorageInfo,
6
+ MarketProviderTrustLevel,
7
+ } from './types'
8
+
9
+ export const MARKET_SOURCES_STORAGE_KEY = StorageList.MARKET_SOURCES
10
+ export const MARKET_SOURCES_STORAGE_VERSION = 1
11
+
12
+ function defineProvider(
13
+ provider: Omit<MarketProviderDefinition, 'trustLevel'> & {
14
+ trustLevel?: MarketProviderTrustLevel
15
+ },
16
+ ): MarketProviderDefinition {
17
+ return {
18
+ trustLevel: provider.trustLevel ?? 'unverified',
19
+ ...provider,
20
+ }
21
+ }
22
+
23
+ export const DEFAULT_MARKET_PROVIDERS: MarketProviderDefinition[] = [
24
+ defineProvider({
25
+ id: 'talex-official',
26
+ name: 'Talex Official',
27
+ type: 'nexusStore',
28
+ url: 'https://raw.githubusercontent.com/talex-touch/tuff-official-plugins/main/plugins.json',
29
+ description: '官方插件市场,提供经过审核的核心插件。',
30
+ enabled: true,
31
+ priority: 100,
32
+ trustLevel: 'official',
33
+ readOnly: true,
34
+ config: {
35
+ manifestUrl:
36
+ 'https://raw.githubusercontent.com/talex-touch/tuff-official-plugins/main/plugins.json',
37
+ baseUrl: 'https://raw.githubusercontent.com/talex-touch/tuff-official-plugins/main/',
38
+ },
39
+ }),
40
+ defineProvider({
41
+ id: 'github-releases',
42
+ name: 'GitHub Releases',
43
+ type: 'repository',
44
+ description: '从 GitHub 仓库 releases / manifest 中读取插件。',
45
+ enabled: false,
46
+ priority: 80,
47
+ trustLevel: 'unverified',
48
+ config: {
49
+ platform: 'github',
50
+ apiBase: 'https://api.github.com',
51
+ },
52
+ }),
53
+ defineProvider({
54
+ id: 'gitee-repos',
55
+ name: 'Gitee 仓库',
56
+ type: 'repository',
57
+ description: 'Gitee 平台插件仓库,适合国内网络。',
58
+ enabled: false,
59
+ priority: 70,
60
+ trustLevel: 'unverified',
61
+ config: {
62
+ platform: 'gitee',
63
+ apiBase: 'https://gitee.com/api/v5',
64
+ },
65
+ }),
66
+ defineProvider({
67
+ id: 'npm-scope',
68
+ name: 'NPM 包',
69
+ type: 'npmPackage',
70
+ description: '基于 NPM 关键字或 scope 的插件发布渠道。',
71
+ enabled: false,
72
+ priority: 60,
73
+ trustLevel: 'unverified',
74
+ config: {
75
+ registryUrl: 'https://registry.npmjs.org',
76
+ keyword: 'talex-touch-plugin',
77
+ },
78
+ }),
79
+ ]
80
+
81
+ export const MARKET_SOURCES_STORAGE_INFO: MarketSourcesStorageInfo = {
82
+ storageKey: MARKET_SOURCES_STORAGE_KEY,
83
+ version: MARKET_SOURCES_STORAGE_VERSION,
84
+ }
85
+
86
+ export function createDefaultMarketSourcesPayload(): MarketSourcesPayload {
87
+ const clone = typeof structuredClone === 'function'
88
+ ? structuredClone(DEFAULT_MARKET_PROVIDERS)
89
+ : JSON.parse(JSON.stringify(DEFAULT_MARKET_PROVIDERS))
90
+
91
+ return {
92
+ version: MARKET_SOURCES_STORAGE_VERSION,
93
+ sources: clone,
94
+ }
95
+ }
@@ -0,0 +1,2 @@
1
+ export * from './types'
2
+ export * from './constants'
@@ -0,0 +1,118 @@
1
+ import type { StorageList } from '../common/storage/constants'
2
+
3
+ export type MarketProviderType = 'repository' | 'nexusStore' | 'npmPackage'
4
+
5
+ export type MarketProviderTrustLevel = 'official' | 'verified' | 'unverified'
6
+
7
+ export interface MarketProviderDefinition {
8
+ id: string
9
+ name: string
10
+ type: MarketProviderType
11
+ /**
12
+ * Base URL or identifier for the provider.
13
+ * Individual provider implementations can interpret this differently.
14
+ */
15
+ url?: string
16
+ /**
17
+ * Additional configuration object for provider specific options.
18
+ */
19
+ config?: Record<string, any>
20
+ description?: string
21
+ enabled: boolean
22
+ priority: number
23
+ trustLevel?: MarketProviderTrustLevel
24
+ tags?: string[]
25
+ /**
26
+ * Whether this provider should be treated as read-only (no install)
27
+ */
28
+ readOnly?: boolean
29
+ }
30
+
31
+ export interface MarketSourcesPayload {
32
+ /**
33
+ * Schema version, used for migrations.
34
+ */
35
+ version: number
36
+ sources: MarketProviderDefinition[]
37
+ }
38
+
39
+ export type MarketInstallInstruction =
40
+ | {
41
+ type: 'url'
42
+ url: string
43
+ format?: 'zip' | 'tar' | 'tgz' | 'tpex'
44
+ integrity?: string
45
+ }
46
+ | {
47
+ type: 'npm'
48
+ packageName: string
49
+ version?: string
50
+ registry?: string
51
+ }
52
+ | {
53
+ type: 'git'
54
+ repo: string
55
+ ref?: string
56
+ sparse?: boolean
57
+ }
58
+
59
+ export interface MarketPlugin {
60
+ id: string
61
+ name: string
62
+ version?: string
63
+ description?: string
64
+ category?: string
65
+ tags?: string[]
66
+ author?: string
67
+ icon?: string
68
+ metadata?: Record<string, unknown>
69
+ readmeUrl?: string
70
+ homepage?: string
71
+ downloadUrl?: string
72
+ install?: MarketInstallInstruction
73
+ providerId: string
74
+ providerName: string
75
+ providerType: MarketProviderType
76
+ providerTrustLevel: MarketProviderTrustLevel
77
+ trusted: boolean
78
+ official?: boolean
79
+ timestamp?: number | string
80
+ }
81
+
82
+ export interface MarketProviderResultMeta {
83
+ providerId: string
84
+ providerName: string
85
+ providerType: MarketProviderType
86
+ success: boolean
87
+ error?: string
88
+ fetchedAt: number
89
+ itemCount: number
90
+ }
91
+
92
+ export interface MarketProviderListOptions {
93
+ keyword?: string
94
+ force?: boolean
95
+ }
96
+
97
+ export interface MarketHttpRequestOptions {
98
+ url: string
99
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
100
+ headers?: Record<string, string>
101
+ params?: Record<string, any>
102
+ data?: any
103
+ timeout?: number
104
+ responseType?: 'json' | 'text' | 'arraybuffer'
105
+ }
106
+
107
+ export interface MarketHttpResponse<T = unknown> {
108
+ status: number
109
+ statusText: string
110
+ headers: Record<string, string>
111
+ data: T
112
+ url: string
113
+ }
114
+
115
+ export interface MarketSourcesStorageInfo {
116
+ storageKey: StorageList
117
+ version: number
118
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@talex-touch/utils",
3
- "version": "1.0.33",
3
+ "version": "1.0.34",
4
4
  "private": false,
5
5
  "description": "Tuff series utils",
6
6
  "author": "TalexDreamSoul",
package/plugin/index.ts CHANGED
@@ -498,3 +498,4 @@ export type { IPluginLogger, LogDataType, LogItem, LogLevel } from './log/types'
498
498
  export * from './providers'
499
499
  export * from './risk'
500
500
  export * from './sdk/index'
501
+ export * from './widget'
@@ -0,0 +1,25 @@
1
+ export const DEFAULT_WIDGET_RENDERERS = {
2
+ CORE_PREVIEW_CARD: 'core-preview-card',
3
+ CORE_INTELLIGENCE_ANSWER: 'core-intelligence-answer',
4
+ } as const
5
+
6
+ const values = Object.values(DEFAULT_WIDGET_RENDERERS)
7
+ export const DEFAULT_WIDGET_RENDERER_IDS = new Set<string>(values)
8
+
9
+ export function isDefaultWidgetRenderer(id: string | undefined): boolean {
10
+ return Boolean(id) && DEFAULT_WIDGET_RENDERER_IDS.has(id!)
11
+ }
12
+
13
+ export interface WidgetRegistrationPayload {
14
+ widgetId: string
15
+ pluginName: string
16
+ featureId: string
17
+ filePath: string
18
+ code: string
19
+ styles: string
20
+ hash: string
21
+ }
22
+
23
+ export function makeWidgetId(pluginName: string, featureId: string): string {
24
+ return `${pluginName}::${featureId}`
25
+ }
@@ -0,0 +1,196 @@
1
+ import type { IStorageChannel } from './base-storage'
2
+
3
+ /**
4
+ * Storage subscription callback type
5
+ */
6
+ export type StorageSubscriptionCallback = (data: any) => void
7
+
8
+ /**
9
+ * Storage subscription manager for renderer process
10
+ * Provides easy subscription to storage updates via channel events
11
+ */
12
+ class StorageSubscriptionManager {
13
+ private channel: IStorageChannel | null = null
14
+ private subscribers = new Map<string, Set<StorageSubscriptionCallback>>()
15
+ private channelListenerRegistered = false
16
+ private pendingUpdates = new Map<string, NodeJS.Timeout>()
17
+
18
+ /**
19
+ * Initialize the subscription manager with a channel
20
+ */
21
+ init(channel: IStorageChannel): void {
22
+ this.channel = channel
23
+
24
+ if (!this.channelListenerRegistered) {
25
+ // Listen to storage:update events from main process
26
+ this.channel.regChannel('storage:update', ({ data }) => {
27
+ const { name } = data as { name: string }
28
+ this.handleStorageUpdate(name)
29
+ })
30
+ this.channelListenerRegistered = true
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Subscribe to storage changes for a specific config
36
+ * @param configName - The configuration file name (e.g., 'app-setting.ini')
37
+ * @param callback - Callback function to receive updates
38
+ * @returns Unsubscribe function
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const unsubscribe = subscribeStorage('app-setting.ini', (data) => {
43
+ * console.log('Config updated:', data)
44
+ * })
45
+ *
46
+ * // Later:
47
+ * unsubscribe()
48
+ * ```
49
+ */
50
+ subscribe(configName: string, callback: StorageSubscriptionCallback): () => void {
51
+ if (!this.subscribers.has(configName)) {
52
+ this.subscribers.set(configName, new Set())
53
+ }
54
+
55
+ this.subscribers.get(configName)!.add(callback)
56
+
57
+ // Immediately load and call with current data
58
+ if (this.channel) {
59
+ const currentData = this.channel.sendSync('storage:get', configName)
60
+ if (currentData) {
61
+ try {
62
+ callback(currentData)
63
+ }
64
+ catch (error) {
65
+ console.error(`[StorageSubscription] Callback error for "${configName}":`, error)
66
+ }
67
+ }
68
+ }
69
+
70
+ // Return unsubscribe function
71
+ return () => {
72
+ this.unsubscribe(configName, callback)
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Unsubscribe from storage changes
78
+ * @param configName - The configuration file name
79
+ * @param callback - The same callback function used in subscribe
80
+ */
81
+ unsubscribe(configName: string, callback: StorageSubscriptionCallback): void {
82
+ const callbacks = this.subscribers.get(configName)
83
+ if (callbacks) {
84
+ callbacks.delete(callback)
85
+ if (callbacks.size === 0) {
86
+ this.subscribers.delete(configName)
87
+ }
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Handle storage update events from main process
93
+ * @private
94
+ */
95
+ private async handleStorageUpdate(configName: string): Promise<void> {
96
+ const callbacks = this.subscribers.get(configName)
97
+ if (!callbacks || callbacks.size === 0) {
98
+ return
99
+ }
100
+
101
+ if (!this.channel) {
102
+ return
103
+ }
104
+
105
+ // Debounce updates to avoid excessive IPC and callback invocations
106
+ const existing = this.pendingUpdates.get(configName)
107
+ if (existing) {
108
+ clearTimeout(existing)
109
+ }
110
+
111
+ const timer = setTimeout(async () => {
112
+ // Fetch latest data
113
+ const data = await this.channel!.send('storage:get', configName)
114
+ if (!data) {
115
+ this.pendingUpdates.delete(configName)
116
+ return
117
+ }
118
+
119
+ // Notify all subscribers
120
+ callbacks.forEach((callback) => {
121
+ try {
122
+ callback(data)
123
+ }
124
+ catch (error) {
125
+ console.error(`[StorageSubscription] Callback error for "${configName}":`, error)
126
+ }
127
+ })
128
+
129
+ this.pendingUpdates.delete(configName)
130
+ }, 50) // 50ms debounce window
131
+
132
+ this.pendingUpdates.set(configName, timer)
133
+ }
134
+
135
+ /**
136
+ * Get the number of active subscriptions for a config
137
+ * @param configName - The configuration file name
138
+ * @returns Number of active callbacks subscribed to this config
139
+ */
140
+ getSubscriberCount(configName: string): number {
141
+ return this.subscribers.get(configName)?.size ?? 0
142
+ }
143
+
144
+ /**
145
+ * Clear all subscriptions
146
+ */
147
+ clear(): void {
148
+ this.subscribers.clear()
149
+ }
150
+ }
151
+
152
+ // Global singleton instance
153
+ const subscriptionManager = new StorageSubscriptionManager()
154
+
155
+ /**
156
+ * Initialize storage subscription system with channel
157
+ * Must be called before using subscribeStorage
158
+ *
159
+ * @param channel - The storage channel
160
+ */
161
+ export function initStorageSubscription(channel: IStorageChannel): void {
162
+ subscriptionManager.init(channel)
163
+ }
164
+
165
+ /**
166
+ * Subscribe to storage configuration changes
167
+ *
168
+ * @param configName - Configuration file name (e.g., 'app-setting.ini')
169
+ * @param callback - Callback function that receives updated data
170
+ * @returns Unsubscribe function
171
+ *
172
+ * @example
173
+ * ```typescript
174
+ * import { subscribeStorage } from '@talex-touch/utils/renderer/storage/storage-subscription'
175
+ *
176
+ * const unsubscribe = subscribeStorage('app-setting.ini', (data) => {
177
+ * console.log('Settings updated:', data)
178
+ * })
179
+ *
180
+ * // Clean up when no longer needed
181
+ * unsubscribe()
182
+ * ```
183
+ */
184
+ export function subscribeStorage(
185
+ configName: string,
186
+ callback: StorageSubscriptionCallback,
187
+ ): () => void {
188
+ return subscriptionManager.subscribe(configName, callback)
189
+ }
190
+
191
+ /**
192
+ * Get subscription manager instance (for debugging)
193
+ */
194
+ export function getSubscriptionManager(): StorageSubscriptionManager {
195
+ return subscriptionManager
196
+ }
package/types/icon.ts CHANGED
@@ -37,6 +37,13 @@ export interface ITuffIcon {
37
37
  /** Icon value */
38
38
  value: string
39
39
 
40
+ /**
41
+ * Icon Colorful (Only for URL type)
42
+ * @desc This prop defines whether a URL icon should be rendered in colorful mode.
43
+ * It is only applicable when the icon type is 'url'.
44
+ */
45
+ colorful?: boolean
46
+
40
47
  /** Icon status (optional) */
41
48
  status?: TuffIconStatus
42
49