mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2026-04-08 06:42:10 -07:00
* Use git service for backups and dynamic AI menus
Switch backup actions to call gitService.commitAndSync(commitOnly) so local backups work without remote auth and AI commit generation is triggered by omitting commitMessage. Make AI-related menu items always registered but use dynamic visibility/enabled checks (isAIEnabled) so they appear/disappear at runtime. Update menu item types/imports accordingly. Optimize workspace persistence to write only the single updated workspace to settings.json (stripping syncable fields when tidgi.config exists) instead of saving all workspaces; remove the old saveWorkspacesToSettings method. Add warnings/logging: warn if git worker observable is undefined, log/notify when cloud sync is skipped due to missing auth/gitUrl. Misc: remove a redundant debug log in tidgiConfig, remove native process monitoring startup call, include commitMessage for CommitDetailsPanel sync, and drop entriesFingerprint/debug noise from git log data.
* fix: avoid rewriting unchanged workspace config
* fix: separate plain and ai backup flows
* feat: add searchable settings views
* fix: narrow sync adaptor revision type
* chore: release tidgi-shared 0.1.3
* Preferences: unify Scheduled/Background tasks and fix skeleton nav
- Remove legacy background task UI/dialogs (use ScheduledTask unified system)
- Attach invisible anchors to skeleton placeholders so sidebar scrollIntoView works while loading
- Add English/zh translation keys for AddAlarm/AddHeartbeat
- Add requestIdleCallback polyfill (tests) and speed up to 0ms for tests
- Search: match English translations (txEn) in SearchResultsView
* Fix view zero-size on minimize; realign on restore/show
Guard view bounds from 0x0 content size to prevent BrowserView disappearing when window is minimized; fall back to safe offscreen size. Add window 'restore' and 'show' handlers to realign views. Add getMemoryUsage() in wiki worker and expose RSS/heap via getWorkersInfo; show worker memory in Developer Tools diagnostics.
* Fix background/quit behavior: synchronous close handler; platform-specific runOnBackground default; add getWindowMetaSync; add forced-exit timeout for before-quit cleanup
* Refactor preferences: add definitions and registry
Add a new structured preferences system: introduce definition schemas, typed item/section types, explicit section files, a registry (allSections/sectionById), side effects, action handlers, and helper builders (zodPreferencesSchema). Add custom preference UI items and registration (customItems, registerCustomSections) and tests validating schemas. Replace previous zod/settings schema files with the new definitions and make IPreferences an explicit TypeScript interface. Small UI updates: use LanguageSelectorItem and WikiUserNameItem in Guide and Help pages. Also remove net.isOnline() pre-checks from Git.commitAndSync and Git.forcePull to avoid false-negative network detections.
* Fix blank wiki after hide+reopen: re-attach orphaned views on realign
Root cause: three compounding issues when the main window is hidden and then re-shown via a second-instance shortcut click.
1. getView() now auto-removes stale entries whose webContents.isDestroyed() == true.
This allows addView() / showWorkspaceView() to recreate destroyed views instead of
silently skipping them (which left the new window blank).
2. realignView() now calls browserWindow.contentView.addChildView(view) before setBounds().
If a view survived window destruction but became orphaned (detached from its parent
BrowserWindow), re-attaching it makes it visible again. addChildView is idempotent
so normal re-entrant calls are safe.
3. window.open() existing-window branch now calls addViewForAllBrowserViews(activeWorkspace)
before realignActiveWorkspace(). This ensures that any destroyed/missing view is
recreated BEFORE the realign attempts to reposition it. The call is a no-op when
views already exist and are healthy.
* Fix blank WebContentsView after restoring hidden window (Windows bug)
Electron on Windows sometimes fails to repaint a WebContentsView that remains attached to a window that is hidden and then shown again. By unconditionally calling \
emoveChildView\ followed by \ddChildView\ during \
ealignView\ and \showView\, we force the Chromium compositor to reparent and paint the view correctly, ensuring the Wiki becomes visible as soon as the user restores the app from the background.
* Fix blank WebView on window restore: show view before realign, fix same-workspace click
Two root causes identified and fixed:
1. openWorkspaceTiddler silently skipped setActiveWorkspaceView when the user
clicked the already-active workspace icon (guard: oldId !== newId). The view
was blank and clicking its icon did nothing. Guard removed setActiveWorkspaceView
is now always called; it is safe with the same ID (hibernation guard is already
correct for that case).
2. The 'show' event handler and window.open() existing-window path were calling
realignActiveWorkspace() which only calls setBounds. On Windows, when a window
transitions from hidden/background to visible the Chromium compositor may not
repaint a WebContentsView whose bounds have not changed. Both paths now call
refreshActiveWorkspaceView() a new lightweight helper that calls showView()
(removeChildView + addChildView + setBounds + webContents.focus) before realigning.
This forces a compositor repaint and makes the wiki page visible immediately.
* Refactor view restore chain: clean up redundant repaint/realign calls
Full chain analysis identified 4 structural problems:
1. refreshActiveWorkspaceView() called TWICE concurrently on window restore:
window.open() called existedWindow.show() which fired the 'show' event
refreshActiveWorkspaceView() AND THEN immediately called refreshActiveWorkspaceView()
again explicitly, creating a race condition on removeChildView+addChildView.
2. realignView() contained removeChildView+addChildView, which ran immediately after
showView() already did removeChildView+addChildView+setBounds+focus. The second
remove+add clobbered the focus state set by showView, breaking keyboard focus.
3. setActiveWorkspaceView() called showWorkspaceView + realignActiveWorkspace, meaning
the view was remove+add+setBounds+focused by showView, then immediately remove+add+
setBounds-without-focus again by realignView. Double bounds, lost focus.
4. Same pattern in refreshActiveWorkspaceView: showWorkspaceView + realignActiveWorkspace.
Clean design after refactor:
- showView() = force-repaint path: remove+add+setBounds+focus (unchanged)
- realignView() = bounds-only: setBounds ONLY, no remove+add
- showWorkspaceView = calls showView for main+mini windows
- realignActiveWorkspace = calls realignView (now just setBounds) + buildMenu;
used for fullscreen/sidebar/resize events
- setActiveWorkspaceView = showWorkspaceView + buildMenu (not +realignActiveWorkspace)
- refreshActiveWorkspaceView = showWorkspaceView + buildMenu (not +realignActiveWorkspace);
called from 'show' window event (fire-and-forget: no rethrow)
- window.open() existing-window = show() only; 'show' event handler calls
refreshActiveWorkspaceView automatically, no duplicate call
* chore: bump electron-ipc-cat to 2.4.0
Rolling Observable timeout (120s initial, 60s idle) fixes git-upload-pack
timeout for large repos (100+ MB) during mobile sync.
* style: unify layout between Preferences and EditWorkspace
Use PageRoot and PageInner from PreferenceComponents to eliminate subtle padding/background differences. Resize EditWorkspace window to match Preferences. Clean up lint errors.
* Add E2E test for window-restore blank-view bug + log markers
Two changes:
1. Log markers added to aid diagnosis and enable E2E verification:
- [test-id-VIEW_SHOWN] in ViewService.showView()
- [test-id-REFRESH_ACTIVE_VIEW_START/DONE] in WorkspaceView.refreshActiveWorkspaceView()
2. New E2E feature: features/windowRestore.feature
Scenario 1: 'Wiki WebContentsView is visible immediately after restoring hidden window'
- hides main window (same path as close+runOnBackground)
- triggers second-instance via app.emit('second-instance')
- asserts [test-id-REFRESH_ACTIVE_VIEW_DONE] and [test-id-VIEW_SHOWN] log markers
- asserts browser view is within visible window bounds
- asserts wiki content is readable
Scenario 2: 'Clicking already-active workspace icon re-shows the WebContentsView'
- verifies the removed oldId !== newId guard: clicking current workspace must
now call setActiveWorkspaceView which fires showView
Two step definitions added to features/stepDefinitions/application.ts:
- 'I hide the main window as if closing with runOnBackground'
calls BrowserWindow.hide() directly in main process
- 'I reopen the main window as second instance would'
emits app 'second-instance' event in main process
* Fix E2E test: correct second-instance emit args, add wiki-ready wait in Background
Three issues found and fixed by running the tests:
1. app.emit('second-instance') argument order wrong
DeepLinkService listener: (_event, commandLine) => commandLine.pop()
Our emit: app.emit('second-instance', [], process.cwd(), {})
This made 'process.cwd()' land in commandLine, .pop() failed on a string.
Fix: app.emit('second-instance', {}, [], '', {}) fake Event first,
then empty argv array, then workingDirectory.
2. In test mode, window.open() skips existedWindow.show() to avoid UI popups.
The 'show' event never fired so refreshActiveWorkspaceView was never called
and the window stayed hidden from Playwright's perspective.
Fix: explicitly call mainWindow.show() via app.evaluate() after emitting
second-instance, replicating what production window.open() does.
3. Background used 'the browser view should be loaded and visible' which has
a 21-second timeout and fails before TiddlyWiki finishes initializing in
the test environment (pre-existing issue in defaultWiki.feature too).
Fix: replaced with deterministic log marker waits:
[test-id-WIKI_WORKER_STARTED] + [test-id-VIEW_LOADED]
plus 'I confirm the main window browser view is positioned within visible
window bounds' for a structural check without content dependency.
Result: both @window-restore scenarios pass (31/31 steps green, ~48s).
* Fix reopened main window restore after recreation and rebind view resize
Root cause on Windows was not the hide/show path, but the close+recreate path when tidgi mini window keeps the app alive while runOnBackground is false.
What was actually happening:
1. The user closed the main window.
2. The app stayed alive because tidgi mini window still existed.
3. A second-instance launch recreated a new main BrowserWindow.
4. The old workspace WebContentsView still existed in ViewService.
5. But the new main window missed the automatic restore because the BrowserWindow 'show' event fired inside handleCreateBasicWindow() before registerBrowserViewWindowListeners() attached the 'show' listener.
6. If the user then clicked the workspace icon, showView() reattached the old view manually, but its resize listener was still bound to the old destroyed BrowserWindow, so resizing the new window no longer resized the view.
Fix:
- ViewService now rebinds the debounced resize handler every time showView() attaches an existing view to a BrowserWindow.
- Window.open() now detects the recreate-main-window case for BrowserView windows and immediately calls refreshActiveWorkspaceView() if the active workspace already has an existing view instance.
This restores the view without waiting for a workspace icon click.
Why old E2E missed it:
- It simulated hide/show (runOnBackground=true) instead of the real user path (main window close + app kept alive by tidgi mini window).
- It only checked that the view was within visible bounds; it did not resize the window and assert the view filled the content area after the reopen.
New E2E coverage:
- Configures tidgiMiniWindow=true and runOnBackground=false before launch.
- Closes the main window, reopens it via second-instance, verifies refresh/view-shown markers, verifies bounds, resizes the recreated main window, and asserts the BrowserView fills the content area after the debounced resize handler runs.
- Scenario passes locally: 1 scenario, 20 steps, all green.
* Update pnpm-lock.yaml
* fix: address Copilot PR review issues
- Restore workspaceID from window.meta() in EditWorkspace (was hard-coded debug value)
- Add missing React/type imports to customComponentRegistry.ts, workspaceCustomComponentRegistry.ts, registerCustomSections.tsx, registerWorkspaceCustomSections.tsx, useSections.ts
- Fix HighlightText regex: use index parity (odd index = match) instead of stateful regex.test() with global flag
- Fix actionHandlers native.pickDirectory to read current preference value instead of passing the key string as a path
- Move PreferenceComponents import before registerCustomSections() call to fix import ordering
* fix: fix import ordering to satisfy dprint/eslint format rules
* fix: stabilize e2e selectors and EditWorkspace loading fallback
- align workspace section testids in e2e features
- migrate background-task e2e to scheduled-task selectors
- add edit workspace fallback loading when metadata/observable is late
- add deterministic switch testid for schema boolean items
- make sync snackbar assertion resilient to progress text changes
- clear draft-check timeout handle in sync service
* fix: add apiKey to test provider config so isAIAvailable() returns true
The AI commit message e2e test expects both commit-now-button and
commit-now-ai-button to appear. The AI button only renders when
isAIGenerateBackupTitleEnabled() returns true, which internally calls
externalAPIService.isAIAvailable(). That method requires a non-empty
apiKey for openAICompatible providers, but the test's
createProviderConfig() never set one, causing isAIAvailable() to
return false and the AI button to never render.
* feat(gitServer): add generateFullArchive for fast mobile clone
- Add generateFullArchive() to IGitServerService interface
- Implement tar archive generation: git archive + system tar append
- Archives working tree + minimal .git metadata (HEAD, refs, objects)
- Cache by HEAD commit hash, auto-cleanup old archives
- Bump tidgi-shared to 0.1.5
* fix(e2e): resolve workspace by runtime name/folder in step defs
* fix(ci): satisfy lint rules in gitServer archive generation
462 lines
17 KiB
TypeScript
462 lines
17 KiB
TypeScript
import { WorkspaceChannel } from '@/constants/channels';
|
|
import { PageType } from '@/constants/pageTypes';
|
|
import { SupportedStorageServices } from '@services/types';
|
|
import { ProxyPropertyType } from 'electron-ipc-cat/common';
|
|
import { BehaviorSubject, Observable } from 'rxjs';
|
|
import { SetOptional } from 'type-fest';
|
|
|
|
/**
|
|
* Fields that not part of config that user can edit. Change of these field won't show "save" button on edit page.
|
|
* These fields are updated during runtime and should not trigger the save button.
|
|
*/
|
|
export const nonConfigFields = ['metadata', 'lastNodeJSArgv', 'lastUrl', 'homeUrl', 'hibernated', 'active'];
|
|
|
|
// These are the canonical definitions used by both main-process and worker code.
|
|
// interface.ts re-exports them so consumers have a single import path.
|
|
export { syncableConfigFields } from './syncableConfig';
|
|
export type { ISyncableWikiConfig, SyncableConfigField } from './syncableConfig';
|
|
|
|
/**
|
|
* Fields that are device-specific and should only be stored locally.
|
|
*/
|
|
export const localOnlyFields = [
|
|
'id',
|
|
'order',
|
|
'active',
|
|
'hibernated',
|
|
'lastUrl',
|
|
'lastNodeJSArgv',
|
|
'homeUrl',
|
|
'authToken',
|
|
'picturePath',
|
|
'wikiFolderLocation',
|
|
'pageType',
|
|
'port',
|
|
] as const;
|
|
|
|
/**
|
|
* Default values for syncable config fields (stored in tidgi.config.json).
|
|
* See syncableConfig.ts for the canonical definition used by worker code.
|
|
*/
|
|
export const syncableConfigDefaultValues = {
|
|
name: '',
|
|
gitUrl: null,
|
|
storageService: SupportedStorageServices.local,
|
|
userName: '',
|
|
readOnlyMode: false,
|
|
tokenAuth: false,
|
|
enableHTTPAPI: false,
|
|
enableFileSystemWatch: false,
|
|
ignoreSymlinks: true,
|
|
backupOnInterval: true,
|
|
syncOnInterval: false,
|
|
syncOnStartup: true,
|
|
disableAudio: false,
|
|
disableNotifications: false,
|
|
hibernateWhenUnused: false,
|
|
transparentBackground: false,
|
|
excludedPlugins: [] as string[],
|
|
tagNames: [] as string[],
|
|
includeTagTree: false,
|
|
fileSystemPathFilterEnable: false,
|
|
fileSystemPathFilter: null as string | null,
|
|
rootTiddler: undefined as string | undefined,
|
|
https: undefined as { enabled: boolean; tlsCert?: string; tlsKey?: string } | undefined,
|
|
isSubWiki: false,
|
|
mainWikiID: null as string | null,
|
|
mainWikiToLink: null as string | null,
|
|
} as const;
|
|
|
|
export const localConfigDefaultValues = {
|
|
id: '',
|
|
order: 0,
|
|
active: false,
|
|
hibernated: false,
|
|
lastUrl: null as string | null,
|
|
lastNodeJSArgv: [] as string[],
|
|
homeUrl: '',
|
|
authToken: undefined as string | undefined,
|
|
picturePath: null as string | null,
|
|
pageType: null as PageType.wiki | null,
|
|
port: 5212,
|
|
} as const;
|
|
|
|
/**
|
|
* Default values for IWikiWorkspace fields. These are used for:
|
|
* 1. Initializing new workspaces
|
|
* 2. Providing default values when fields are missing from persisted config
|
|
* 3. Determining which fields need to be saved (only non-default values are persisted)
|
|
*/
|
|
export const wikiWorkspaceDefaultValues = {
|
|
...localConfigDefaultValues,
|
|
...syncableConfigDefaultValues,
|
|
} satisfies Omit<IWikiWorkspace, 'wikiFolderLocation'>;
|
|
|
|
export interface IDedicatedWorkspace {
|
|
/**
|
|
* Is this workspace selected by user, and showing corresponding webview?
|
|
*/
|
|
active: boolean;
|
|
id: string;
|
|
/**
|
|
* Display name for this wiki workspace
|
|
*/
|
|
name: string;
|
|
/**
|
|
* You can drag workspaces to reorder them
|
|
*/
|
|
order: number;
|
|
/**
|
|
* If this workspace represents a page (like help, guide, agent), this field indicates the page type.
|
|
* If null or undefined, this is a regular wiki workspace.
|
|
*/
|
|
pageType?: PageType | null;
|
|
/**
|
|
* workspace icon's path in file system
|
|
*/
|
|
picturePath: string | null;
|
|
}
|
|
|
|
/**
|
|
* A workspace is basically a TiddlyWiki instance, it can be a local/online wiki (depends on git related config). Can be a mainWiki that starts a a TiddlyWiki instance or subwiki that link to a main wiki.
|
|
*
|
|
* New value added here can be init in `sanitizeWorkspace`
|
|
*/
|
|
export interface IWikiWorkspace extends IDedicatedWorkspace {
|
|
authToken?: string;
|
|
/**
|
|
* When this workspace is a local workspace, we can still use local git to backup
|
|
*/
|
|
backupOnInterval: boolean;
|
|
disableAudio: boolean;
|
|
disableNotifications: boolean;
|
|
enableHTTPAPI: boolean;
|
|
/**
|
|
* List of plugins excluded on startup, for example `['$:/plugins/bimlas/kin-filter', '$:/plugins/dullroar/sitemap']`
|
|
*/
|
|
excludedPlugins: string[];
|
|
/**
|
|
* The online repo to back data up to
|
|
*/
|
|
gitUrl: string | null;
|
|
/**
|
|
* Hibernate workspace on startup and when switch to another workspace.
|
|
*/
|
|
hibernateWhenUnused: boolean;
|
|
/**
|
|
* Is this workspace hibernated. You can hibernate workspace manually, without setting its hibernateWhenUnused. So we record this field in workspace.
|
|
*/
|
|
hibernated: boolean;
|
|
/**
|
|
* Localhost server url to load in the electron webview
|
|
*/
|
|
homeUrl: string;
|
|
/**
|
|
* Mostly used for deploying blog. Need tls-key and tls-cert.
|
|
*/
|
|
https?: {
|
|
enabled: boolean;
|
|
tlsCert?: string;
|
|
tlsKey?: string;
|
|
};
|
|
/**
|
|
* Is this workspace a subwiki that link to a main wiki, and doesn't have its own webview?
|
|
*/
|
|
isSubWiki: boolean;
|
|
/**
|
|
* Nodejs start argument cli, used to start tiddlywiki server in terminal
|
|
*/
|
|
lastNodeJSArgv?: string[];
|
|
/**
|
|
* Last visited url, used for rememberLastPageVisited in preferences
|
|
*/
|
|
lastUrl: string | null;
|
|
/**
|
|
* ID of main wiki of the sub-wiki. Only useful when isSubWiki === true
|
|
*/
|
|
mainWikiID: string | null;
|
|
/**
|
|
* Absolute path of main wiki of the sub-wiki. Only useful when isSubWiki === true , this is the wiki repo that this subwiki's folder soft links to
|
|
*/
|
|
mainWikiToLink: string | null;
|
|
/**
|
|
* For wiki workspaces, pageType is restricted to wiki type or null for regular wiki workspaces
|
|
*/
|
|
pageType?: PageType.wiki | null;
|
|
/**
|
|
* Localhost tiddlywiki server port
|
|
*/
|
|
port: number;
|
|
/**
|
|
* Make wiki readonly if readonly is true. This is normally used for server mode, so also enable gzip.
|
|
*
|
|
* The principle is to configure anonymous reads, but writes require a login, and then give an unguessable username and password to.
|
|
*
|
|
* @url https://wiki.zhiheng.io/static/TiddlyWiki%253A%2520Readonly%2520for%2520Node.js%2520Server.html
|
|
*/
|
|
readOnlyMode: boolean;
|
|
/**
|
|
* The root tiddler for wiki. When missing, may use `$:/core/save/lazy-images`
|
|
* @url https://tiddlywiki.com/#LazyLoading
|
|
*/
|
|
rootTiddler?: string;
|
|
/**
|
|
* Storage service this workspace sync to
|
|
*/
|
|
storageService: SupportedStorageServices;
|
|
/**
|
|
* Sync wiki every interval.
|
|
* If this is false (false by default to save the CPU usage from chokidar watch), then sync will only happen if user manually trigger by click sync button in the wiki, or sync at the app open.
|
|
*/
|
|
syncOnInterval: boolean;
|
|
/**
|
|
* Commit and Sync when App starts.
|
|
*/
|
|
syncOnStartup: boolean;
|
|
/**
|
|
* When enabled (default true), syncing this main workspace also syncs/backs-up all linked sub-workspaces.
|
|
* Set to false to sync only this workspace and leave sub-workspaces untouched.
|
|
*/
|
|
syncSubWikis?: boolean;
|
|
/**
|
|
* Tag names in tiddlywiki's filesystemPath, tiddlers with any of these tags will be saved into this subwiki
|
|
*/
|
|
tagNames: string[];
|
|
/**
|
|
* When enabled, tiddlers that are indirectly tagged (tag of tag of tag...) with any of this sub-wiki's tagNames
|
|
* will also be saved to this sub-wiki. Uses the in-tagtree-of filter operator.
|
|
* Applies when creating new tiddlers and when modifying existing ones (e.g., when tags change).
|
|
*/
|
|
includeTagTree: boolean;
|
|
/**
|
|
* When enabled, also use fileSystemPathFilter expressions to match tiddlers, in addition to tagName/includeTagTree matching.
|
|
* This allows more complex matching logic using TiddlyWiki filter expressions.
|
|
*/
|
|
fileSystemPathFilterEnable: boolean;
|
|
/**
|
|
* TiddlyWiki filter expressions to match tiddlers for this workspace (one per line).
|
|
* Example: `[in-tagtree-of[Calendar]!tag[Public]!tag[Draft]]`
|
|
* Any matching filter will route the tiddler to this workspace.
|
|
* Only used when fileSystemPathFilterEnable is true.
|
|
*/
|
|
fileSystemPathFilter: string | null;
|
|
/**
|
|
* Use authenticated-user-header to provide `TIDGI_AUTH_TOKEN_HEADER` as header key to receive a value as username (we use it as token)
|
|
*/
|
|
tokenAuth: boolean;
|
|
transparentBackground: boolean;
|
|
userName: string;
|
|
/**
|
|
* folder path for this wiki workspace
|
|
*/
|
|
wikiFolderLocation: string;
|
|
/**
|
|
* Enable file system watching (experimental feature using chokidar)
|
|
* When enabled, external file changes will be synced to the wiki automatically
|
|
* This is an experimental feature and may have bugs
|
|
*/
|
|
enableFileSystemWatch: boolean;
|
|
/**
|
|
* Symlinks are similar to shortcuts. The old version used them to implement sub-wiki functionality,
|
|
* but the new version no longer needs them.You can manually delete legacy symlinks.
|
|
* When enabled, the file system watcher will skip symlinks to avoid redundant file sync operations.
|
|
*/
|
|
ignoreSymlinks: boolean;
|
|
}
|
|
export type IWorkspace = IWikiWorkspace | IDedicatedWorkspace;
|
|
|
|
/**
|
|
* Type guard to check if a workspace is a wiki workspace
|
|
*/
|
|
export function isWikiWorkspace(workspace: IWorkspace): workspace is IWikiWorkspace {
|
|
return 'wikiFolderLocation' in workspace;
|
|
}
|
|
|
|
/**
|
|
* Type guard to check if a workspace is a dedicated workspace (like help, guide, agent pages)
|
|
*/
|
|
export function isDedicatedWorkspace(workspace: IWorkspace): workspace is IDedicatedWorkspace {
|
|
return !isWikiWorkspace(workspace);
|
|
}
|
|
|
|
export interface IWorkspaceMetaData {
|
|
badgeCount?: number;
|
|
/**
|
|
* Error message if this workspace fails loading
|
|
*/
|
|
didFailLoadErrorMessage?: string | null | undefined;
|
|
/**
|
|
* indicating server or webpage is still loading
|
|
*/
|
|
isLoading?: boolean;
|
|
/**
|
|
* Is restarting service for this workspace.
|
|
*/
|
|
isRestarting?: boolean;
|
|
}
|
|
|
|
export type IWorkspaceWithMetadata = IWorkspace & {
|
|
metadata: IWorkspaceMetaData;
|
|
};
|
|
export type IWorkspacesWithMetadata = Record<string, IWorkspaceWithMetadata>;
|
|
|
|
/**
|
|
* Ignore some field that will assign default value in workspaceService.create, these field don't require to be filled in AddWorkspace form
|
|
*/
|
|
export type INewWikiWorkspaceConfig =
|
|
& SetOptional<
|
|
Omit<IWikiWorkspace, 'active' | 'hibernated' | 'id' | 'lastUrl' | 'syncOnInterval' | 'syncOnStartup'>,
|
|
| 'homeUrl'
|
|
| 'transparentBackground'
|
|
| 'picturePath'
|
|
| 'disableNotifications'
|
|
| 'disableAudio'
|
|
| 'hibernateWhenUnused'
|
|
| 'userName'
|
|
| 'order'
|
|
| 'ignoreSymlinks'
|
|
| 'backupOnInterval'
|
|
| 'enableHTTPAPI'
|
|
| 'excludedPlugins'
|
|
| 'includeTagTree'
|
|
| 'fileSystemPathFilterEnable'
|
|
| 'fileSystemPathFilter'
|
|
>
|
|
& {
|
|
useTidgiConfig?: boolean;
|
|
};
|
|
|
|
/**
|
|
* Manage workspace level preferences and workspace metadata.
|
|
*/
|
|
export interface IWorkspaceService {
|
|
/** Enter a state that no workspace is active (show welcome page) */
|
|
clearActiveWorkspace(oldActiveWorkspaceID: string | undefined): Promise<void>;
|
|
/**
|
|
* Check if a workspace exists by id
|
|
* @param id workspace id to check
|
|
* @returns true if workspace exists, false otherwise
|
|
*/
|
|
exists(id: string): Promise<boolean>;
|
|
countWorkspaces(): Promise<number>;
|
|
create(newWorkspaceConfig: INewWikiWorkspaceConfig): Promise<IWorkspace>;
|
|
get(id: string): Promise<IWorkspace | undefined>;
|
|
get$(id: string): Observable<IWorkspace | undefined>;
|
|
/**
|
|
* Get active workspace, if no active workspace, return the first workspace. Only when workspace list is empty, return undefined.
|
|
*/
|
|
getActiveWorkspace: () => Promise<IWorkspace | undefined>;
|
|
/**
|
|
* Only meant to be used in TidGi's services internally.
|
|
*/
|
|
getActiveWorkspaceSync: () => IWorkspace | undefined;
|
|
getAllMetaData: () => Promise<Record<string, Partial<IWorkspaceMetaData>>>;
|
|
getByWikiFolderLocation(wikiFolderLocation: string): Promise<IWorkspace | undefined>;
|
|
/**
|
|
* Get workspace by human readable wiki name, if no workspace found, return undefined. If multiple workspace with same name, return the first one order by sidebar.
|
|
*/
|
|
getByWikiName(wikiName: string): Promise<IWorkspace | undefined>;
|
|
getFirstWorkspace: () => Promise<IWorkspace | undefined>;
|
|
/**
|
|
* Get parent workspace of a subWorkspace, if the workspace you provided is a main workspace, return undefined.
|
|
* @param subWorkspace your workspace object
|
|
*/
|
|
getMainWorkspace(subWorkspace: IWorkspace): IWorkspace | undefined;
|
|
getMetaData: (id: string) => Promise<Partial<IWorkspaceMetaData>>;
|
|
getNextWorkspace: (id: string) => Promise<IWorkspace | undefined>;
|
|
getPreviousWorkspace: (id: string) => Promise<IWorkspace | undefined>;
|
|
getSubWorkspacesAsList(workspaceID: string): Promise<IWikiWorkspace[]>;
|
|
/**
|
|
* Only meant to be used in TidGi's services internally.
|
|
*/
|
|
getSubWorkspacesAsListSync(workspaceID: string): IWikiWorkspace[];
|
|
getWorkspaces(): Promise<Record<string, IWorkspace>>;
|
|
getWorkspacesAsList(): Promise<IWorkspace[]>;
|
|
getWorkspacesWithMetadata(): IWorkspacesWithMetadata;
|
|
/**
|
|
* Get workspace token for Git Smart HTTP authentication
|
|
* @param workspaceId workspace ID
|
|
* @returns token string or undefined if workspace not found or has no token
|
|
*/
|
|
getWorkspaceToken(workspaceId: string): Promise<string | undefined>;
|
|
/**
|
|
* Validate workspace token for Git Smart HTTP authentication
|
|
* @param workspaceId workspace ID
|
|
* @param token token to validate
|
|
* @returns true if token matches, false otherwise
|
|
*/
|
|
validateWorkspaceToken(workspaceId: string, token: string): Promise<boolean>;
|
|
/**
|
|
* Initialize default page workspaces on first startup
|
|
*/
|
|
initializeDefaultPageWorkspaces(): Promise<void>;
|
|
/**
|
|
* Open a tiddler in the workspace, open workspace's tag by default.
|
|
*/
|
|
openWorkspaceTiddler(workspace: IWorkspace, title?: string): Promise<void>;
|
|
remove(id: string): Promise<void>;
|
|
removeWorkspacePicture(id: string): Promise<void>;
|
|
set(id: string, workspace: IWorkspace, immediate?: boolean): Promise<void>;
|
|
/**
|
|
* Set new workspace to active, and make the old active workspace inactive
|
|
* @param id id to active
|
|
*/
|
|
setActiveWorkspace(id: string, oldActiveWorkspaceID: string | undefined): Promise<void>;
|
|
setWorkspacePicture(id: string, sourcePicturePath: string): Promise<void>;
|
|
setWorkspaces(newWorkspaces: Record<string, IWorkspace>): Promise<void>;
|
|
update(id: string, workspaceSetting: Partial<IWorkspace>, immediate?: boolean): Promise<void>;
|
|
updateMetaData: (id: string, options: Partial<IWorkspaceMetaData>) => Promise<void>;
|
|
/**
|
|
* Manually refresh the observable's content, that will be received by react component.
|
|
*/
|
|
updateWorkspaceSubject(): void;
|
|
workspaceDidFailLoad(id: string): Promise<boolean>;
|
|
workspaces$: BehaviorSubject<IWorkspacesWithMetadata | undefined>;
|
|
}
|
|
export const WorkspaceServiceIPCDescriptor = {
|
|
channel: WorkspaceChannel.name,
|
|
properties: {
|
|
clearActiveWorkspace: ProxyPropertyType.Function,
|
|
countWorkspaces: ProxyPropertyType.Function,
|
|
create: ProxyPropertyType.Function,
|
|
get: ProxyPropertyType.Function,
|
|
get$: ProxyPropertyType.Function$,
|
|
getActiveWorkspace: ProxyPropertyType.Function,
|
|
getAllMetaData: ProxyPropertyType.Function,
|
|
getByWikiName: ProxyPropertyType.Function,
|
|
getFirstWorkspace: ProxyPropertyType.Function,
|
|
getMainWorkspace: ProxyPropertyType.Function,
|
|
getMetaData: ProxyPropertyType.Function,
|
|
getNextWorkspace: ProxyPropertyType.Function,
|
|
getPreviousWorkspace: ProxyPropertyType.Function,
|
|
getSubWorkspacesAsList: ProxyPropertyType.Function,
|
|
getWorkspaces: ProxyPropertyType.Function,
|
|
getWorkspacesAsList: ProxyPropertyType.Function,
|
|
getWorkspacesWithMetadata: ProxyPropertyType.Function,
|
|
getWorkspaceToken: ProxyPropertyType.Function,
|
|
validateWorkspaceToken: ProxyPropertyType.Function,
|
|
initializeDefaultPageWorkspaces: ProxyPropertyType.Function,
|
|
openWorkspaceTiddler: ProxyPropertyType.Function,
|
|
remove: ProxyPropertyType.Function,
|
|
removeWorkspacePicture: ProxyPropertyType.Function,
|
|
set: ProxyPropertyType.Function,
|
|
setActiveWorkspace: ProxyPropertyType.Function,
|
|
setWorkspacePicture: ProxyPropertyType.Function,
|
|
setWorkspaces: ProxyPropertyType.Function,
|
|
update: ProxyPropertyType.Function,
|
|
updateMetaData: ProxyPropertyType.Function,
|
|
updateWorkspaceSubject: ProxyPropertyType.Value$,
|
|
workspaceDidFailLoad: ProxyPropertyType.Function,
|
|
workspaces$: ProxyPropertyType.Value$,
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Apply default values to a wiki workspace, using the centralized defaults from wikiWorkspaceDefaultValues.
|
|
* This ensures that missing fields get their default values when loading from persisted config.
|
|
* @param workspace The workspace object that may have missing fields
|
|
* @returns A new workspace object with defaults applied to missing fields
|
|
*/
|
|
export function applyWorkspaceDefaults(workspace: IWikiWorkspace): IWikiWorkspace {
|
|
return { ...wikiWorkspaceDefaultValues, ...workspace };
|
|
}
|