mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-18 00:46:45 +00:00
refactor(sync): consolidate LegacySyncProvider into SyncProviderId
Remove duplicate LegacySyncProvider enum and use SyncProviderId everywhere. The two enums had identical values but were separate types for historical reasons, making the "Legacy" name misleading since providers are actively used.
This commit is contained in:
parent
1e516113f6
commit
08e8329f97
18 changed files with 142 additions and 163 deletions
|
|
@ -534,7 +534,7 @@ export class DialogRecoveryPasswordComponent {
|
|||
type: 'tpl',
|
||||
className: 'tpl info-text',
|
||||
hideExpression: (m, v, field) =>
|
||||
field?.parent?.parent?.parent?.model.syncProvider !== LegacySyncProvider.SuperSync,
|
||||
field?.parent?.parent?.parent?.model.syncProvider !== SyncProviderId.SuperSync,
|
||||
templateOptions: {
|
||||
tag: 'div',
|
||||
text: '🔒 End-to-end encryption enabled automatically'
|
||||
|
|
@ -543,7 +543,7 @@ export class DialogRecoveryPasswordComponent {
|
|||
{
|
||||
type: 'btn',
|
||||
hideExpression: (m, v, field) =>
|
||||
field?.parent?.parent?.parent?.model.syncProvider !== LegacySyncProvider.SuperSync,
|
||||
field?.parent?.parent?.parent?.model.syncProvider !== SyncProviderId.SuperSync,
|
||||
templateOptions: {
|
||||
text: 'Manage Recovery Password',
|
||||
onClick: async () => {
|
||||
|
|
@ -570,7 +570,7 @@ export class DialogRecoveryPasswordComponent {
|
|||
|
||||
```typescript
|
||||
async updateSettingsFromForm(cfg: SyncConfig, isInitialSetup: boolean) {
|
||||
if (cfg.syncProvider === LegacySyncProvider.SuperSync) {
|
||||
if (cfg.syncProvider === SyncProviderId.SuperSync) {
|
||||
// Check if user has existing key
|
||||
const hasKey = await this._deviceKey.getMasterKey();
|
||||
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ ngAfterViewInit(): void {
|
|||
this._subs.add(
|
||||
syncProviderControl.valueChanges
|
||||
.pipe(skip(1))
|
||||
.subscribe(async (newProvider: LegacySyncProvider | null) => {
|
||||
.subscribe(async (newProvider: SyncProviderId | null) => {
|
||||
if (!newProvider) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -178,22 +178,22 @@ ngAfterViewInit(): void {
|
|||
// Create provider-specific config based on provider type
|
||||
let providerSpecificUpdate: Partial<SyncConfig> = {};
|
||||
|
||||
if (newProvider === LegacySyncProvider.SuperSync && privateCfg) {
|
||||
if (newProvider === SyncProviderId.SuperSync && privateCfg) {
|
||||
providerSpecificUpdate = {
|
||||
superSync: privateCfg as any,
|
||||
encryptKey: privateCfg.encryptKey || '',
|
||||
};
|
||||
} else if (newProvider === LegacySyncProvider.WebDAV && privateCfg) {
|
||||
} else if (newProvider === SyncProviderId.WebDAV && privateCfg) {
|
||||
providerSpecificUpdate = {
|
||||
webDav: privateCfg as any,
|
||||
encryptKey: privateCfg.encryptKey || '',
|
||||
};
|
||||
} else if (newProvider === LegacySyncProvider.LocalFile && privateCfg) {
|
||||
} else if (newProvider === SyncProviderId.LocalFile && privateCfg) {
|
||||
providerSpecificUpdate = {
|
||||
localFileSync: privateCfg as any,
|
||||
encryptKey: privateCfg.encryptKey || '',
|
||||
};
|
||||
} else if (newProvider === LegacySyncProvider.Dropbox && privateCfg) {
|
||||
} else if (newProvider === SyncProviderId.Dropbox && privateCfg) {
|
||||
providerSpecificUpdate = {
|
||||
encryptKey: privateCfg.encryptKey || '',
|
||||
};
|
||||
|
|
@ -214,7 +214,7 @@ ngAfterViewInit(): void {
|
|||
};
|
||||
|
||||
// For non-SuperSync providers, update encryption from global config
|
||||
if (newProvider !== LegacySyncProvider.SuperSync) {
|
||||
if (newProvider !== SyncProviderId.SuperSync) {
|
||||
this._tmpUpdatedCfg = {
|
||||
...this._tmpUpdatedCfg,
|
||||
isEncryptionEnabled: globalCfg?.isEncryptionEnabled || false,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import { combineLatest } from 'rxjs';
|
|||
import { Store } from '@ngrx/store';
|
||||
import { selectSyncConfig } from '../../features/config/store/global-config.reducer';
|
||||
import { selectEnabledIssueProviders } from '../../features/issue/store/issue-provider.selectors';
|
||||
import { LegacySyncProvider } from '../../imex/sync/legacy-sync-provider.model';
|
||||
import { SyncProviderId } from '../../op-log/sync-providers/provider.const';
|
||||
import { GlobalConfigState } from '../../features/config/global-config.model';
|
||||
import { IPC } from '../../../../electron/shared-with-frontend/ipc-events.const';
|
||||
import { IpcRendererEvent } from 'electron';
|
||||
|
|
@ -272,7 +272,7 @@ export class StartupService {
|
|||
map(([syncConfig, enabledIssueProviders]) => {
|
||||
const hasCloudSync =
|
||||
syncConfig.syncProvider !== null &&
|
||||
syncConfig.syncProvider !== LegacySyncProvider.LocalFile;
|
||||
syncConfig.syncProvider !== SyncProviderId.LocalFile;
|
||||
const hasIssueProviders = enabledIssueProviders.length > 0;
|
||||
return hasCloudSync || hasIssueProviders;
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { T } from '../../../t.const';
|
||||
import { ConfigFormSection, SyncConfig } from '../global-config.model';
|
||||
import { LegacySyncProvider } from '../../../imex/sync/legacy-sync-provider.model';
|
||||
import { SyncProviderId } from '../../../op-log/sync-providers/provider.const';
|
||||
import { IS_ANDROID_WEB_VIEW } from '../../../util/is-android-web-view';
|
||||
import { IS_ELECTRON } from '../../../app.constants';
|
||||
import { fileSyncDroid, fileSyncElectron } from '../../../op-log/model/model-config';
|
||||
|
|
@ -64,7 +64,7 @@ const createWebdavFormFields = (options: {
|
|||
},
|
||||
expressions: {
|
||||
'props.required': (field: FormlyFieldConfig) =>
|
||||
field?.parent?.parent?.model?.syncProvider === LegacySyncProvider.WebDAV,
|
||||
field?.parent?.parent?.model?.syncProvider === SyncProviderId.WebDAV,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -76,7 +76,7 @@ const createWebdavFormFields = (options: {
|
|||
},
|
||||
expressions: {
|
||||
'props.required': (field: FormlyFieldConfig) =>
|
||||
field?.parent?.parent?.model?.syncProvider === LegacySyncProvider.WebDAV,
|
||||
field?.parent?.parent?.model?.syncProvider === SyncProviderId.WebDAV,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -89,7 +89,7 @@ const createWebdavFormFields = (options: {
|
|||
},
|
||||
expressions: {
|
||||
'props.required': (field: FormlyFieldConfig) =>
|
||||
field?.parent?.parent?.model?.syncProvider === LegacySyncProvider.WebDAV,
|
||||
field?.parent?.parent?.model?.syncProvider === SyncProviderId.WebDAV,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -101,7 +101,7 @@ const createWebdavFormFields = (options: {
|
|||
},
|
||||
expressions: {
|
||||
'props.required': (field: FormlyFieldConfig) =>
|
||||
field?.parent?.parent?.model?.syncProvider === LegacySyncProvider.WebDAV,
|
||||
field?.parent?.parent?.model?.syncProvider === SyncProviderId.WebDAV,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
@ -127,14 +127,14 @@ export const SYNC_FORM: ConfigFormSection<SyncConfig> = {
|
|||
label: T.F.SYNC.FORM.L_SYNC_PROVIDER,
|
||||
required: true,
|
||||
options: [
|
||||
{ label: 'SuperSync (Beta)', value: LegacySyncProvider.SuperSync },
|
||||
{ label: LegacySyncProvider.Dropbox, value: LegacySyncProvider.Dropbox },
|
||||
{ label: 'WebDAV (experimental)', value: LegacySyncProvider.WebDAV },
|
||||
{ label: 'SuperSync (Beta)', value: SyncProviderId.SuperSync },
|
||||
{ label: SyncProviderId.Dropbox, value: SyncProviderId.Dropbox },
|
||||
{ label: 'WebDAV (experimental)', value: SyncProviderId.WebDAV },
|
||||
...(IS_ELECTRON || IS_ANDROID_WEB_VIEW
|
||||
? [
|
||||
{
|
||||
label: 'LocalFile (experimental)',
|
||||
value: LegacySyncProvider.LocalFile,
|
||||
value: SyncProviderId.LocalFile,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
|
@ -143,7 +143,7 @@ export const SYNC_FORM: ConfigFormSection<SyncConfig> = {
|
|||
},
|
||||
{
|
||||
hideExpression: (m, v, field) =>
|
||||
field?.parent?.model.syncProvider !== LegacySyncProvider.LocalFile ||
|
||||
field?.parent?.model.syncProvider !== SyncProviderId.LocalFile ||
|
||||
IS_ANDROID_WEB_VIEW,
|
||||
resetOnHide: false,
|
||||
key: 'localFileSync',
|
||||
|
|
@ -166,14 +166,14 @@ export const SYNC_FORM: ConfigFormSection<SyncConfig> = {
|
|||
},
|
||||
expressions: {
|
||||
'props.required': (field: FormlyFieldConfig) =>
|
||||
field?.parent?.parent?.model?.syncProvider === LegacySyncProvider.LocalFile,
|
||||
field?.parent?.parent?.model?.syncProvider === SyncProviderId.LocalFile,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
hideExpression: (m, v, field) =>
|
||||
field?.parent?.model.syncProvider !== LegacySyncProvider.LocalFile ||
|
||||
field?.parent?.model.syncProvider !== SyncProviderId.LocalFile ||
|
||||
!IS_ANDROID_WEB_VIEW,
|
||||
resetOnHide: false,
|
||||
key: 'localFileSync',
|
||||
|
|
@ -197,7 +197,7 @@ export const SYNC_FORM: ConfigFormSection<SyncConfig> = {
|
|||
},
|
||||
expressions: {
|
||||
'props.required': (field: FormlyFieldConfig) =>
|
||||
field?.parent?.parent?.model?.syncProvider === LegacySyncProvider.LocalFile,
|
||||
field?.parent?.parent?.model?.syncProvider === SyncProviderId.LocalFile,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
@ -206,7 +206,7 @@ export const SYNC_FORM: ConfigFormSection<SyncConfig> = {
|
|||
// WebDAV provider form fields
|
||||
{
|
||||
hideExpression: (m, v, field) =>
|
||||
field?.parent?.model.syncProvider !== LegacySyncProvider.WebDAV,
|
||||
field?.parent?.model.syncProvider !== SyncProviderId.WebDAV,
|
||||
resetOnHide: false,
|
||||
key: 'webDav',
|
||||
fieldGroup: createWebdavFormFields({
|
||||
|
|
@ -221,7 +221,7 @@ export const SYNC_FORM: ConfigFormSection<SyncConfig> = {
|
|||
// Note: No key needed - Dropbox credentials stored privately via SyncCredentialStore
|
||||
{
|
||||
hideExpression: (m, v, field) =>
|
||||
field?.parent?.model.syncProvider !== LegacySyncProvider.Dropbox,
|
||||
field?.parent?.model.syncProvider !== SyncProviderId.Dropbox,
|
||||
resetOnHide: false,
|
||||
// Custom marker for identifying this field group in config-page.component.ts
|
||||
props: { dropboxAuth: true } as any,
|
||||
|
|
@ -254,7 +254,7 @@ export const SYNC_FORM: ConfigFormSection<SyncConfig> = {
|
|||
// hideExpression: ((model: DropboxSyncConfig) => !model.accessToken),
|
||||
// Hide for SuperSync (uses fixed interval) and when manual sync only is enabled
|
||||
hideExpression: (m, v, field) =>
|
||||
field?.parent?.model.syncProvider === LegacySyncProvider.SuperSync ||
|
||||
field?.parent?.model.syncProvider === SyncProviderId.SuperSync ||
|
||||
field?.parent?.model.isManualSyncOnly === true,
|
||||
resetOnHide: true,
|
||||
templateOptions: {
|
||||
|
|
@ -269,7 +269,7 @@ export const SYNC_FORM: ConfigFormSection<SyncConfig> = {
|
|||
type: 'checkbox',
|
||||
// Only show for file-based providers (Dropbox, WebDAV, LocalFile)
|
||||
hideExpression: (m, v, field) =>
|
||||
field?.parent?.model.syncProvider === LegacySyncProvider.SuperSync ||
|
||||
field?.parent?.model.syncProvider === SyncProviderId.SuperSync ||
|
||||
field?.parent?.model.syncProvider === null,
|
||||
templateOptions: {
|
||||
label: T.F.SYNC.FORM.L_MANUAL_SYNC_ONLY,
|
||||
|
|
@ -300,8 +300,7 @@ export const SYNC_FORM: ConfigFormSection<SyncConfig> = {
|
|||
btnStyle: 'stroked',
|
||||
onClick: async (field: FormlyFieldConfig) => {
|
||||
const isSuperSync =
|
||||
field?.parent?.parent?.model?.syncProvider ===
|
||||
LegacySyncProvider.SuperSync;
|
||||
field?.parent?.parent?.model?.syncProvider === SyncProviderId.SuperSync;
|
||||
const result = isSuperSync
|
||||
? await openEncryptionPasswordChangeDialog()
|
||||
: await openEncryptionPasswordChangeDialogForFileBased();
|
||||
|
|
@ -318,8 +317,7 @@ export const SYNC_FORM: ConfigFormSection<SyncConfig> = {
|
|||
btnStyle: 'stroked',
|
||||
onClick: async (field: FormlyFieldConfig) => {
|
||||
const isSuperSync =
|
||||
field?.parent?.parent?.model?.syncProvider ===
|
||||
LegacySyncProvider.SuperSync;
|
||||
field?.parent?.parent?.model?.syncProvider === SyncProviderId.SuperSync;
|
||||
const result = isSuperSync
|
||||
? await openDisableEncryptionDialog()
|
||||
: await openDisableEncryptionDialogForFileBased();
|
||||
|
|
@ -348,7 +346,7 @@ export const SYNC_FORM: ConfigFormSection<SyncConfig> = {
|
|||
{
|
||||
type: 'collapsible',
|
||||
hideExpression: (m, v, field) =>
|
||||
field?.parent?.model.syncProvider === LegacySyncProvider.SuperSync,
|
||||
field?.parent?.model.syncProvider === SyncProviderId.SuperSync,
|
||||
props: { label: T.G.ADVANCED_CFG },
|
||||
fieldGroup: [
|
||||
{
|
||||
|
|
@ -361,7 +359,7 @@ export const SYNC_FORM: ConfigFormSection<SyncConfig> = {
|
|||
// Enable encryption button for file-based providers (shown when encryption is disabled)
|
||||
{
|
||||
hideExpression: (m: any, v: any, field?: FormlyFieldConfig) =>
|
||||
field?.parent?.parent?.model.syncProvider === LegacySyncProvider.SuperSync ||
|
||||
field?.parent?.parent?.model.syncProvider === SyncProviderId.SuperSync ||
|
||||
m.isEncryptionEnabled,
|
||||
type: 'btn',
|
||||
className: 'e2e-file-based-enable-encryption-btn',
|
||||
|
|
@ -388,7 +386,7 @@ export const SYNC_FORM: ConfigFormSection<SyncConfig> = {
|
|||
fieldGroup: [
|
||||
{
|
||||
hideExpression: (m, v, field) =>
|
||||
field?.parent?.parent?.model.syncProvider !== LegacySyncProvider.SuperSync,
|
||||
field?.parent?.parent?.model.syncProvider !== SyncProviderId.SuperSync,
|
||||
type: 'btn',
|
||||
templateOptions: {
|
||||
text: T.F.SYNC.FORM.SUPER_SYNC.BTN_GET_TOKEN,
|
||||
|
|
@ -403,7 +401,7 @@ export const SYNC_FORM: ConfigFormSection<SyncConfig> = {
|
|||
},
|
||||
{
|
||||
hideExpression: (m, v, field) =>
|
||||
field?.parent?.parent?.model.syncProvider !== LegacySyncProvider.SuperSync,
|
||||
field?.parent?.parent?.model.syncProvider !== SyncProviderId.SuperSync,
|
||||
key: 'accessToken',
|
||||
type: 'textarea',
|
||||
className: 'e2e-accessToken',
|
||||
|
|
@ -414,14 +412,14 @@ export const SYNC_FORM: ConfigFormSection<SyncConfig> = {
|
|||
},
|
||||
expressions: {
|
||||
'props.required': (field: FormlyFieldConfig) =>
|
||||
field?.parent?.parent?.model?.syncProvider === LegacySyncProvider.SuperSync,
|
||||
field?.parent?.parent?.model?.syncProvider === SyncProviderId.SuperSync,
|
||||
},
|
||||
},
|
||||
// Advanced settings for SuperSync
|
||||
{
|
||||
type: 'collapsible',
|
||||
hideExpression: (m, v, field) =>
|
||||
field?.parent?.parent?.model.syncProvider !== LegacySyncProvider.SuperSync,
|
||||
field?.parent?.parent?.model.syncProvider !== SyncProviderId.SuperSync,
|
||||
props: { label: T.G.ADVANCED_CFG },
|
||||
fieldGroup: [
|
||||
// Enable encryption button for SuperSync (shown when encryption is disabled)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { FormlyFieldConfig } from '@ngx-formly/core';
|
||||
|
||||
import { LanguageCode, DateTimeLocale } from '../../core/locale.constants';
|
||||
import { LegacySyncProvider } from '../../imex/sync/legacy-sync-provider.model';
|
||||
import { SyncProviderId } from '../../op-log/sync-providers/provider.const';
|
||||
import { ProjectCfgFormKey } from '../project/project.model';
|
||||
import { KeyboardConfig } from './keyboard-config.model';
|
||||
import { TaskReminderOptionId } from '../tasks/task.model';
|
||||
|
|
@ -162,8 +162,7 @@ export type SyncConfig = Readonly<{
|
|||
isEnabled: boolean;
|
||||
isEncryptionEnabled?: boolean;
|
||||
isCompressionEnabled?: boolean;
|
||||
// TODO migrate to SyncProviderId
|
||||
syncProvider: LegacySyncProvider | null;
|
||||
syncProvider: SyncProviderId | null;
|
||||
syncInterval: number;
|
||||
isManualSyncOnly?: boolean;
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import {
|
|||
} from './global-config.reducer';
|
||||
import { loadAllData } from '../../../root-store/meta/load-all-data.action';
|
||||
import { GlobalConfigState } from '../global-config.model';
|
||||
import { LegacySyncProvider } from '../../../imex/sync/legacy-sync-provider.model';
|
||||
import { SyncProviderId } from '../../../op-log/sync-providers/provider.const';
|
||||
import { AppDataComplete } from '../../../op-log/model/model-config';
|
||||
import { DEFAULT_GLOBAL_CONFIG } from '../default-global-config.const';
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ describe('GlobalConfigReducer', () => {
|
|||
...initialGlobalConfigState,
|
||||
sync: {
|
||||
...initialGlobalConfigState.sync,
|
||||
syncProvider: LegacySyncProvider.SuperSync, // Snapshot has user's provider
|
||||
syncProvider: SyncProviderId.SuperSync, // Snapshot has user's provider
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -110,7 +110,7 @@ describe('GlobalConfigReducer', () => {
|
|||
);
|
||||
|
||||
// Should use snapshot's syncProvider since oldState has null
|
||||
expect(result.sync.syncProvider).toBe(LegacySyncProvider.SuperSync);
|
||||
expect(result.sync.syncProvider).toBe(SyncProviderId.SuperSync);
|
||||
});
|
||||
|
||||
it('should preserve syncProvider from oldState when loading synced data', () => {
|
||||
|
|
@ -118,7 +118,7 @@ describe('GlobalConfigReducer', () => {
|
|||
...initialGlobalConfigState,
|
||||
sync: {
|
||||
...initialGlobalConfigState.sync,
|
||||
syncProvider: LegacySyncProvider.SuperSync,
|
||||
syncProvider: SyncProviderId.SuperSync,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -142,7 +142,7 @@ describe('GlobalConfigReducer', () => {
|
|||
);
|
||||
|
||||
// syncProvider should be preserved from oldState
|
||||
expect(result.sync.syncProvider).toBe(LegacySyncProvider.SuperSync);
|
||||
expect(result.sync.syncProvider).toBe(SyncProviderId.SuperSync);
|
||||
// Other config should be updated from synced data
|
||||
expect(result.misc.isDisableAnimations).toBe(true);
|
||||
});
|
||||
|
|
@ -152,7 +152,7 @@ describe('GlobalConfigReducer', () => {
|
|||
...initialGlobalConfigState,
|
||||
sync: {
|
||||
...initialGlobalConfigState.sync,
|
||||
syncProvider: LegacySyncProvider.WebDAV,
|
||||
syncProvider: SyncProviderId.WebDAV,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -160,7 +160,7 @@ describe('GlobalConfigReducer', () => {
|
|||
...initialGlobalConfigState,
|
||||
sync: {
|
||||
...initialGlobalConfigState.sync,
|
||||
syncProvider: LegacySyncProvider.LocalFile,
|
||||
syncProvider: SyncProviderId.LocalFile,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -172,7 +172,7 @@ describe('GlobalConfigReducer', () => {
|
|||
);
|
||||
|
||||
// syncProvider should be preserved from oldState, not overwritten
|
||||
expect(result.sync.syncProvider).toBe(LegacySyncProvider.WebDAV);
|
||||
expect(result.sync.syncProvider).toBe(SyncProviderId.WebDAV);
|
||||
});
|
||||
|
||||
it('should update other sync config properties while preserving syncProvider', () => {
|
||||
|
|
@ -180,7 +180,7 @@ describe('GlobalConfigReducer', () => {
|
|||
...initialGlobalConfigState,
|
||||
sync: {
|
||||
...initialGlobalConfigState.sync,
|
||||
syncProvider: LegacySyncProvider.SuperSync,
|
||||
syncProvider: SyncProviderId.SuperSync,
|
||||
syncInterval: 300000,
|
||||
},
|
||||
};
|
||||
|
|
@ -203,7 +203,7 @@ describe('GlobalConfigReducer', () => {
|
|||
);
|
||||
|
||||
// syncProvider preserved
|
||||
expect(result.sync.syncProvider).toBe(LegacySyncProvider.SuperSync);
|
||||
expect(result.sync.syncProvider).toBe(SyncProviderId.SuperSync);
|
||||
// Other sync settings updated
|
||||
expect(result.sync.syncInterval).toBe(600000);
|
||||
expect(result.sync.isCompressionEnabled).toBe(true);
|
||||
|
|
@ -218,7 +218,7 @@ describe('GlobalConfigReducer', () => {
|
|||
...initialGlobalConfigState,
|
||||
sync: {
|
||||
...initialGlobalConfigState.sync,
|
||||
syncProvider: LegacySyncProvider.SuperSync, // Not null = not sync hydration
|
||||
syncProvider: SyncProviderId.SuperSync, // Not null = not sync hydration
|
||||
isEnabled: true, // User had sync enabled
|
||||
},
|
||||
};
|
||||
|
|
@ -240,7 +240,7 @@ describe('GlobalConfigReducer', () => {
|
|||
...initialGlobalConfigState,
|
||||
sync: {
|
||||
...initialGlobalConfigState.sync,
|
||||
syncProvider: LegacySyncProvider.SuperSync,
|
||||
syncProvider: SyncProviderId.SuperSync,
|
||||
isEnabled: true, // User has sync enabled
|
||||
},
|
||||
};
|
||||
|
|
@ -264,7 +264,7 @@ describe('GlobalConfigReducer', () => {
|
|||
// isEnabled should be preserved from oldState, not overwritten by remote's false
|
||||
expect(result.sync.isEnabled).toBe(true);
|
||||
// syncProvider should also be preserved
|
||||
expect(result.sync.syncProvider).toBe(LegacySyncProvider.SuperSync);
|
||||
expect(result.sync.syncProvider).toBe(SyncProviderId.SuperSync);
|
||||
});
|
||||
|
||||
it('should preserve isEnabled=false from oldState during sync hydration', () => {
|
||||
|
|
@ -273,7 +273,7 @@ describe('GlobalConfigReducer', () => {
|
|||
...initialGlobalConfigState,
|
||||
sync: {
|
||||
...initialGlobalConfigState.sync,
|
||||
syncProvider: LegacySyncProvider.SuperSync,
|
||||
syncProvider: SyncProviderId.SuperSync,
|
||||
isEnabled: false, // User has sync disabled locally
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { FormlyConfigModule } from '../../../ui/formly-config.module';
|
|||
import { FormlyModule } from '@ngx-formly/core';
|
||||
import { FormlyFieldConfig } from '@ngx-formly/core';
|
||||
import { SyncConfig } from '../../../features/config/global-config.model';
|
||||
import { LegacySyncProvider } from '../legacy-sync-provider.model';
|
||||
import { SyncProviderId } from '../../../op-log/sync-providers/provider.const';
|
||||
import { SyncConfigService } from '../sync-config.service';
|
||||
import { SyncWrapperService } from '../sync-wrapper.service';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
|
@ -107,7 +107,7 @@ export class DialogSyncInitialCfgComponent implements AfterViewInit {
|
|||
this._subs.add(
|
||||
syncProviderControl.valueChanges
|
||||
.pipe(skip(1))
|
||||
.subscribe(async (newProvider: LegacySyncProvider | null) => {
|
||||
.subscribe(async (newProvider: SyncProviderId | null) => {
|
||||
if (!newProvider) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -133,24 +133,24 @@ export class DialogSyncInitialCfgComponent implements AfterViewInit {
|
|||
// Create provider-specific config based on provider type
|
||||
let providerSpecificUpdate: Partial<SyncConfig> = {};
|
||||
|
||||
if (newProvider === LegacySyncProvider.SuperSync && privateCfg) {
|
||||
if (newProvider === SyncProviderId.SuperSync && privateCfg) {
|
||||
providerSpecificUpdate = {
|
||||
superSync: privateCfg as any,
|
||||
encryptKey: privateCfg.encryptKey || '',
|
||||
// SuperSync stores isEncryptionEnabled in privateCfg, not globalCfg
|
||||
isEncryptionEnabled: (privateCfg as any).isEncryptionEnabled || false,
|
||||
};
|
||||
} else if (newProvider === LegacySyncProvider.WebDAV && privateCfg) {
|
||||
} else if (newProvider === SyncProviderId.WebDAV && privateCfg) {
|
||||
providerSpecificUpdate = {
|
||||
webDav: privateCfg as any,
|
||||
encryptKey: privateCfg.encryptKey || '',
|
||||
};
|
||||
} else if (newProvider === LegacySyncProvider.LocalFile && privateCfg) {
|
||||
} else if (newProvider === SyncProviderId.LocalFile && privateCfg) {
|
||||
providerSpecificUpdate = {
|
||||
localFileSync: privateCfg as any,
|
||||
encryptKey: privateCfg.encryptKey || '',
|
||||
};
|
||||
} else if (newProvider === LegacySyncProvider.Dropbox && privateCfg) {
|
||||
} else if (newProvider === SyncProviderId.Dropbox && privateCfg) {
|
||||
providerSpecificUpdate = {
|
||||
encryptKey: privateCfg.encryptKey || '',
|
||||
};
|
||||
|
|
@ -172,7 +172,7 @@ export class DialogSyncInitialCfgComponent implements AfterViewInit {
|
|||
};
|
||||
|
||||
// For non-SuperSync providers, update encryption from global config
|
||||
if (newProvider !== LegacySyncProvider.SuperSync) {
|
||||
if (newProvider !== SyncProviderId.SuperSync) {
|
||||
this._tmpUpdatedCfg = {
|
||||
...this._tmpUpdatedCfg,
|
||||
isEncryptionEnabled: globalCfg?.isEncryptionEnabled || false,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import { updateGlobalConfigSection } from '../../../../features/config/store/glo
|
|||
import { environment } from '../../../../../environments/environment';
|
||||
import { SyncProviderManager } from '../../../../op-log/sync-providers/provider-manager.service';
|
||||
import { DropboxPrivateCfg, SyncProviderId } from '../../../../op-log/sync-exports';
|
||||
import { LegacySyncProvider } from '../../legacy-sync-provider.model';
|
||||
import { confirmDialog } from '../../../../util/native-dialogs';
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -26,7 +25,7 @@ export class DropboxEffects {
|
|||
),
|
||||
tap(async ({ sectionCfg }) => {
|
||||
const syncCfg = sectionCfg as SyncConfig;
|
||||
if (syncCfg.syncProvider !== LegacySyncProvider.Dropbox) {
|
||||
if (syncCfg.syncProvider !== SyncProviderId.Dropbox) {
|
||||
return;
|
||||
}
|
||||
const provider = this._providerManager.getProviderById(SyncProviderId.Dropbox);
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
// NOTE: do not change!!
|
||||
export enum LegacySyncProvider {
|
||||
'Dropbox' = 'Dropbox',
|
||||
'WebDAV' = 'WebDAV',
|
||||
'LocalFile' = 'LocalFile',
|
||||
'SuperSync' = 'SuperSync',
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import { TaskRepeatCfgState } from '../../features/task-repeat-cfg/task-repeat-c
|
|||
import { TagState } from '../../features/tag/tag.model';
|
||||
import { SimpleCounterState } from '../../features/simple-counter/simple-counter.model';
|
||||
import { ProjectArchive } from '../../features/project/project-archive.model';
|
||||
import { LegacySyncProvider } from './legacy-sync-provider.model';
|
||||
import { SyncProviderId } from '../../op-log/sync-providers/provider.const';
|
||||
import { ProjectState } from '../../features/project/project.model';
|
||||
import { NoteState } from '../../features/note/note.model';
|
||||
import { LocalSyncMetaForProvider } from './sync.model';
|
||||
|
|
@ -30,9 +30,9 @@ export interface LegacyAppBaseData {
|
|||
}
|
||||
|
||||
export interface LocalSyncMetaModel {
|
||||
[LegacySyncProvider.WebDAV]: LocalSyncMetaForProvider;
|
||||
[LegacySyncProvider.Dropbox]: LocalSyncMetaForProvider;
|
||||
[LegacySyncProvider.LocalFile]: LocalSyncMetaForProvider;
|
||||
[SyncProviderId.WebDAV]: LocalSyncMetaForProvider;
|
||||
[SyncProviderId.Dropbox]: LocalSyncMetaForProvider;
|
||||
[SyncProviderId.LocalFile]: LocalSyncMetaForProvider;
|
||||
}
|
||||
|
||||
// NOTE: [key:string] always refers to projectId
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@ import { SyncProviderManager } from '../../op-log/sync-providers/provider-manage
|
|||
import { GlobalConfigService } from '../../features/config/global-config.service';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { SyncConfig } from '../../features/config/global-config.model';
|
||||
import { LegacySyncProvider } from './legacy-sync-provider.model';
|
||||
import { SyncProviderId } from '../../op-log/sync-exports';
|
||||
import { SyncProviderId } from '../../op-log/sync-providers/provider.const';
|
||||
import { DEFAULT_GLOBAL_CONFIG } from '../../features/config/default-global-config.const';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { WrappedProviderService } from '../../op-log/sync-providers/wrapped-provider.service';
|
||||
|
|
@ -29,7 +28,7 @@ describe('SyncConfigService', () => {
|
|||
mockSyncConfig$ = new BehaviorSubject<SyncConfig>({
|
||||
...DEFAULT_GLOBAL_CONFIG.sync,
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.LocalFile,
|
||||
syncProvider: SyncProviderId.LocalFile,
|
||||
isEncryptionEnabled: true,
|
||||
});
|
||||
|
||||
|
|
@ -78,7 +77,7 @@ describe('SyncConfigService', () => {
|
|||
|
||||
const settings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.WebDAV,
|
||||
syncProvider: SyncProviderId.WebDAV,
|
||||
syncInterval: 300000,
|
||||
isEncryptionEnabled: true,
|
||||
encryptKey: 'secret-key',
|
||||
|
|
@ -95,7 +94,7 @@ describe('SyncConfigService', () => {
|
|||
// Should only pass non-private data to global config
|
||||
expect(globalConfigService.updateSection).toHaveBeenCalledWith('sync', {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.WebDAV,
|
||||
syncProvider: SyncProviderId.WebDAV,
|
||||
syncInterval: 300000,
|
||||
isEncryptionEnabled: true,
|
||||
});
|
||||
|
|
@ -122,7 +121,7 @@ describe('SyncConfigService', () => {
|
|||
|
||||
const settings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.WebDAV,
|
||||
syncProvider: SyncProviderId.WebDAV,
|
||||
syncInterval: 300000,
|
||||
isEncryptionEnabled: true,
|
||||
encryptKey: 'test-key',
|
||||
|
|
@ -152,7 +151,7 @@ describe('SyncConfigService', () => {
|
|||
|
||||
const settings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.LocalFile,
|
||||
syncProvider: SyncProviderId.LocalFile,
|
||||
syncInterval: 300000,
|
||||
isEncryptionEnabled: true,
|
||||
encryptKey: 'test-key',
|
||||
|
|
@ -191,7 +190,7 @@ describe('SyncConfigService', () => {
|
|||
|
||||
const settings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.Dropbox,
|
||||
syncProvider: SyncProviderId.Dropbox,
|
||||
syncInterval: 300000,
|
||||
isEncryptionEnabled: true,
|
||||
encryptKey: 'dropbox-key',
|
||||
|
|
@ -232,7 +231,7 @@ describe('SyncConfigService', () => {
|
|||
// Update settings without changing the provider
|
||||
const settings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.Dropbox,
|
||||
syncProvider: SyncProviderId.Dropbox,
|
||||
syncInterval: 600000, // Changed interval
|
||||
isEncryptionEnabled: true,
|
||||
encryptKey: 'existing-key', // Same key
|
||||
|
|
@ -277,7 +276,7 @@ describe('SyncConfigService', () => {
|
|||
// Form only provides baseUrl, accessToken is empty string (reset by Formly)
|
||||
const settings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.SuperSync,
|
||||
syncProvider: SyncProviderId.SuperSync,
|
||||
syncInterval: 600000, // Changed interval (unrelated setting)
|
||||
superSync: {
|
||||
baseUrl: existingBaseUrl,
|
||||
|
|
@ -318,7 +317,7 @@ describe('SyncConfigService', () => {
|
|||
|
||||
const settings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.SuperSync,
|
||||
syncProvider: SyncProviderId.SuperSync,
|
||||
syncInterval: 300000,
|
||||
superSync: {
|
||||
baseUrl: 'https://example.com',
|
||||
|
|
@ -360,7 +359,7 @@ describe('SyncConfigService', () => {
|
|||
// Form provides empty password (e.g., from resetOnHide or form state issue)
|
||||
const settings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.WebDAV,
|
||||
syncProvider: SyncProviderId.WebDAV,
|
||||
syncInterval: 600000,
|
||||
webDav: {
|
||||
baseUrl: 'https://webdav.example.com',
|
||||
|
|
@ -400,7 +399,7 @@ describe('SyncConfigService', () => {
|
|||
// User explicitly disables encryption (false should be respected)
|
||||
const settings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.SuperSync,
|
||||
syncProvider: SyncProviderId.SuperSync,
|
||||
syncInterval: 300000,
|
||||
superSync: {
|
||||
baseUrl: 'https://example.com',
|
||||
|
|
@ -440,7 +439,7 @@ describe('SyncConfigService', () => {
|
|||
// Form provides only baseUrl, all other fields empty
|
||||
const settings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.WebDAV,
|
||||
syncProvider: SyncProviderId.WebDAV,
|
||||
syncInterval: 300000,
|
||||
webDav: {
|
||||
baseUrl: 'https://webdav.example.com',
|
||||
|
|
@ -484,7 +483,7 @@ describe('SyncConfigService', () => {
|
|||
|
||||
const settings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.WebDAV,
|
||||
syncProvider: SyncProviderId.WebDAV,
|
||||
syncInterval: 300000,
|
||||
webDav: {
|
||||
baseUrl: 'https://new.example.com', // Updated
|
||||
|
|
@ -527,7 +526,7 @@ describe('SyncConfigService', () => {
|
|||
// Form provides empty path
|
||||
const settings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.LocalFile,
|
||||
syncProvider: SyncProviderId.LocalFile,
|
||||
syncInterval: 600000,
|
||||
localFileSync: {
|
||||
syncFolderPath: '', // Empty - should not overwrite
|
||||
|
|
@ -556,7 +555,7 @@ describe('SyncConfigService', () => {
|
|||
|
||||
const settings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.WebDAV,
|
||||
syncProvider: SyncProviderId.WebDAV,
|
||||
syncInterval: 300000,
|
||||
isEncryptionEnabled: false,
|
||||
webDav: {
|
||||
|
|
@ -599,7 +598,7 @@ describe('SyncConfigService', () => {
|
|||
|
||||
const settings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.WebDAV,
|
||||
syncProvider: SyncProviderId.WebDAV,
|
||||
syncInterval: 300000,
|
||||
isEncryptionEnabled: true,
|
||||
encryptKey: 'new-key',
|
||||
|
|
@ -634,7 +633,7 @@ describe('SyncConfigService', () => {
|
|||
|
||||
const initialSettings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.LocalFile,
|
||||
syncProvider: SyncProviderId.LocalFile,
|
||||
syncInterval: 300000,
|
||||
isEncryptionEnabled: true,
|
||||
encryptKey: 'my-secret-password',
|
||||
|
|
@ -665,7 +664,7 @@ describe('SyncConfigService', () => {
|
|||
mockSyncConfig$.next({
|
||||
...DEFAULT_GLOBAL_CONFIG.sync,
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.LocalFile,
|
||||
syncProvider: SyncProviderId.LocalFile,
|
||||
isEncryptionEnabled: true,
|
||||
syncInterval: 300000,
|
||||
});
|
||||
|
|
@ -688,7 +687,7 @@ describe('SyncConfigService', () => {
|
|||
// Step 1: Simulate initial setup - user enables encryption
|
||||
const initialSettings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.LocalFile,
|
||||
syncProvider: SyncProviderId.LocalFile,
|
||||
syncInterval: 300000,
|
||||
isEncryptionEnabled: true,
|
||||
encryptKey: 'test-password-123',
|
||||
|
|
@ -708,7 +707,7 @@ describe('SyncConfigService', () => {
|
|||
mockSyncConfig$.next({
|
||||
...DEFAULT_GLOBAL_CONFIG.sync,
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.LocalFile,
|
||||
syncProvider: SyncProviderId.LocalFile,
|
||||
isEncryptionEnabled: true, // This is saved correctly
|
||||
syncInterval: 300000,
|
||||
});
|
||||
|
|
@ -733,7 +732,7 @@ describe('SyncConfigService', () => {
|
|||
mockSyncConfig$.next({
|
||||
...DEFAULT_GLOBAL_CONFIG.sync,
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.LocalFile,
|
||||
syncProvider: SyncProviderId.LocalFile,
|
||||
isEncryptionEnabled: false, // Encryption is disabled
|
||||
syncInterval: 300000,
|
||||
});
|
||||
|
|
@ -751,7 +750,7 @@ describe('SyncConfigService', () => {
|
|||
// Ensure our fix doesn't break other providers
|
||||
const webDavSettings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.WebDAV,
|
||||
syncProvider: SyncProviderId.WebDAV,
|
||||
syncInterval: 300000,
|
||||
isEncryptionEnabled: true,
|
||||
encryptKey: 'webdav-password',
|
||||
|
|
@ -793,7 +792,7 @@ describe('SyncConfigService', () => {
|
|||
// Step 1: User enables LocalFile sync with encryption
|
||||
const newSettings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.LocalFile,
|
||||
syncProvider: SyncProviderId.LocalFile,
|
||||
syncInterval: 300000,
|
||||
isEncryptionEnabled: true,
|
||||
encryptKey: 'test-password-123',
|
||||
|
|
@ -849,7 +848,7 @@ describe('SyncConfigService', () => {
|
|||
mockSyncConfig$.next({
|
||||
...DEFAULT_GLOBAL_CONFIG.sync,
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.LocalFile,
|
||||
syncProvider: SyncProviderId.LocalFile,
|
||||
isEncryptionEnabled: true,
|
||||
syncInterval: 300000,
|
||||
});
|
||||
|
|
@ -877,7 +876,7 @@ describe('SyncConfigService', () => {
|
|||
mockSyncConfig$.next({
|
||||
...DEFAULT_GLOBAL_CONFIG.sync,
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.LocalFile,
|
||||
syncProvider: SyncProviderId.LocalFile,
|
||||
isEncryptionEnabled: true,
|
||||
syncInterval: 300000,
|
||||
});
|
||||
|
|
@ -933,7 +932,7 @@ describe('SyncConfigService', () => {
|
|||
mockSyncConfig$.next({
|
||||
...DEFAULT_GLOBAL_CONFIG.sync,
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.SuperSync,
|
||||
syncProvider: SyncProviderId.SuperSync,
|
||||
superSync: {
|
||||
baseUrl: customUrl,
|
||||
},
|
||||
|
|
@ -1314,7 +1313,7 @@ describe('SyncConfigService', () => {
|
|||
// Simulate form update with NO encryptKey (stale form model)
|
||||
const formSettings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.SuperSync,
|
||||
syncProvider: SyncProviderId.SuperSync,
|
||||
syncInterval: 300000,
|
||||
isEncryptionEnabled: true,
|
||||
encryptKey: '', // Empty - simulating stale form model
|
||||
|
|
@ -1362,7 +1361,7 @@ describe('SyncConfigService', () => {
|
|||
// Simulate form update with OLD password (stale form model from before dialog)
|
||||
const formSettings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.SuperSync,
|
||||
syncProvider: SyncProviderId.SuperSync,
|
||||
syncInterval: 300000,
|
||||
isEncryptionEnabled: true,
|
||||
encryptKey: oldPassword, // Old - simulating stale form model
|
||||
|
|
@ -1409,7 +1408,7 @@ describe('SyncConfigService', () => {
|
|||
const newFormPassword = 'new-webdav-password';
|
||||
const formSettings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.WebDAV,
|
||||
syncProvider: SyncProviderId.WebDAV,
|
||||
syncInterval: 300000,
|
||||
isEncryptionEnabled: true,
|
||||
encryptKey: newFormPassword, // New password from form
|
||||
|
|
@ -1449,7 +1448,7 @@ describe('SyncConfigService', () => {
|
|||
// Simulate form update with password in settings.encryptKey (legacy path)
|
||||
const formSettings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.WebDAV,
|
||||
syncProvider: SyncProviderId.WebDAV,
|
||||
syncInterval: 300000,
|
||||
isEncryptionEnabled: true,
|
||||
encryptKey: 'settings-level-password', // Password at settings level
|
||||
|
|
@ -1496,7 +1495,7 @@ describe('SyncConfigService', () => {
|
|||
|
||||
const formSettings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.SuperSync,
|
||||
syncProvider: SyncProviderId.SuperSync,
|
||||
syncInterval: 300000,
|
||||
isEncryptionEnabled: false,
|
||||
encryptKey: '',
|
||||
|
|
@ -1550,7 +1549,7 @@ describe('SyncConfigService', () => {
|
|||
// from BEFORE the dialog updated the password
|
||||
const staleFormSettings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.SuperSync,
|
||||
syncProvider: SyncProviderId.SuperSync,
|
||||
syncInterval: 300000,
|
||||
isEncryptionEnabled: true,
|
||||
encryptKey: '', // STALE: Form model didn't have the new password
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import {
|
|||
MissingRefreshTokenAPIError,
|
||||
} from '../../op-log/core/errors/sync-errors';
|
||||
import { MAX_LWW_REUPLOAD_RETRIES } from '../../op-log/core/operation-log.const';
|
||||
import { LegacySyncProvider } from './legacy-sync-provider.model';
|
||||
|
||||
describe('SyncWrapperService', () => {
|
||||
let service: SyncWrapperService;
|
||||
|
|
@ -49,7 +48,7 @@ describe('SyncWrapperService', () => {
|
|||
let configSubject: BehaviorSubject<any>;
|
||||
let mockSyncCapableProvider: any;
|
||||
|
||||
const createMockSyncConfig = (provider: LegacySyncProvider | null): { sync: any } => ({
|
||||
const createMockSyncConfig = (provider: SyncProviderId | null): { sync: any } => ({
|
||||
sync: {
|
||||
syncProvider: provider,
|
||||
syncInterval: 60000,
|
||||
|
|
@ -57,9 +56,7 @@ describe('SyncWrapperService', () => {
|
|||
});
|
||||
|
||||
beforeEach(() => {
|
||||
configSubject = new BehaviorSubject(
|
||||
createMockSyncConfig(LegacySyncProvider.SuperSync),
|
||||
);
|
||||
configSubject = new BehaviorSubject(createMockSyncConfig(SyncProviderId.SuperSync));
|
||||
|
||||
mockSyncCapableProvider = {
|
||||
uploadOperations: jasmine.createSpy('uploadOperations'),
|
||||
|
|
@ -228,7 +225,7 @@ describe('SyncWrapperService', () => {
|
|||
|
||||
describe('_sync() - Provider handling', () => {
|
||||
it('should call _syncVectorClockToPfapi for WebDAV provider', async () => {
|
||||
configSubject.next(createMockSyncConfig(LegacySyncProvider.WebDAV));
|
||||
configSubject.next(createMockSyncConfig(SyncProviderId.WebDAV));
|
||||
mockOpLogStore.getVectorClockEntry.and.returnValue(
|
||||
Promise.resolve({
|
||||
clock: { clientA: 5 },
|
||||
|
|
@ -244,7 +241,7 @@ describe('SyncWrapperService', () => {
|
|||
});
|
||||
|
||||
it('should call _syncVectorClockToPfapi for Dropbox provider', async () => {
|
||||
configSubject.next(createMockSyncConfig(LegacySyncProvider.Dropbox));
|
||||
configSubject.next(createMockSyncConfig(SyncProviderId.Dropbox));
|
||||
mockOpLogStore.getVectorClockEntry.and.returnValue(
|
||||
Promise.resolve({
|
||||
clock: { clientA: 5 },
|
||||
|
|
@ -259,7 +256,7 @@ describe('SyncWrapperService', () => {
|
|||
});
|
||||
|
||||
it('should NOT call _syncVectorClockToPfapi for SuperSync provider', async () => {
|
||||
configSubject.next(createMockSyncConfig(LegacySyncProvider.SuperSync));
|
||||
configSubject.next(createMockSyncConfig(SyncProviderId.SuperSync));
|
||||
|
||||
await service.sync();
|
||||
|
||||
|
|
@ -1067,8 +1064,8 @@ describe('SyncWrapperService', () => {
|
|||
});
|
||||
|
||||
describe('syncProviderId$', () => {
|
||||
it('should convert LegacySyncProvider to SyncProviderId', (done) => {
|
||||
configSubject.next(createMockSyncConfig(LegacySyncProvider.SuperSync));
|
||||
it('should emit SyncProviderId from sync config', (done) => {
|
||||
configSubject.next(createMockSyncConfig(SyncProviderId.SuperSync));
|
||||
|
||||
service.syncProviderId$.subscribe((providerId) => {
|
||||
expect(providerId).toBe(SyncProviderId.SuperSync);
|
||||
|
|
@ -1091,9 +1088,7 @@ describe('SyncWrapperService', () => {
|
|||
let isConfirmedSignal: ReturnType<typeof signal<boolean>>;
|
||||
let signalConfigSubject: BehaviorSubject<any>;
|
||||
|
||||
const createSignalMockConfig = (
|
||||
provider: LegacySyncProvider | null,
|
||||
): { sync: any } => ({
|
||||
const createSignalMockConfig = (provider: SyncProviderId | null): { sync: any } => ({
|
||||
sync: {
|
||||
syncProvider: provider,
|
||||
syncInterval: 60000,
|
||||
|
|
@ -1103,7 +1098,7 @@ describe('SyncWrapperService', () => {
|
|||
const createServiceWithSignal = (initialValue: boolean): SyncWrapperService => {
|
||||
isConfirmedSignal = signal(initialValue);
|
||||
signalConfigSubject = new BehaviorSubject(
|
||||
createSignalMockConfig(LegacySyncProvider.SuperSync),
|
||||
createSignalMockConfig(SyncProviderId.SuperSync),
|
||||
);
|
||||
|
||||
const signalMockSuperSyncStatusService = {
|
||||
|
|
@ -1143,7 +1138,7 @@ describe('SyncWrapperService', () => {
|
|||
describe('with SuperSync provider', () => {
|
||||
it('should return true when SuperSyncStatusService.isConfirmedInSync is true', (done) => {
|
||||
signalService = createServiceWithSignal(true);
|
||||
signalConfigSubject.next(createSignalMockConfig(LegacySyncProvider.SuperSync));
|
||||
signalConfigSubject.next(createSignalMockConfig(SyncProviderId.SuperSync));
|
||||
|
||||
signalService.superSyncIsConfirmedInSync$.subscribe((isConfirmed) => {
|
||||
expect(isConfirmed).toBe(true);
|
||||
|
|
@ -1153,7 +1148,7 @@ describe('SyncWrapperService', () => {
|
|||
|
||||
it('should return false when SuperSyncStatusService.isConfirmedInSync is false', (done) => {
|
||||
signalService = createServiceWithSignal(false);
|
||||
signalConfigSubject.next(createSignalMockConfig(LegacySyncProvider.SuperSync));
|
||||
signalConfigSubject.next(createSignalMockConfig(SyncProviderId.SuperSync));
|
||||
|
||||
signalService.superSyncIsConfirmedInSync$.subscribe((isConfirmed) => {
|
||||
expect(isConfirmed).toBe(false);
|
||||
|
|
@ -1165,7 +1160,7 @@ describe('SyncWrapperService', () => {
|
|||
describe('with file-based providers', () => {
|
||||
it('should return false for WebDAV when status service returns false', (done) => {
|
||||
signalService = createServiceWithSignal(false);
|
||||
signalConfigSubject.next(createSignalMockConfig(LegacySyncProvider.WebDAV));
|
||||
signalConfigSubject.next(createSignalMockConfig(SyncProviderId.WebDAV));
|
||||
|
||||
signalService.superSyncIsConfirmedInSync$.subscribe((isConfirmed) => {
|
||||
expect(isConfirmed).toBe(false);
|
||||
|
|
@ -1175,7 +1170,7 @@ describe('SyncWrapperService', () => {
|
|||
|
||||
it('should return true for WebDAV when status service returns true', (done) => {
|
||||
signalService = createServiceWithSignal(true);
|
||||
signalConfigSubject.next(createSignalMockConfig(LegacySyncProvider.WebDAV));
|
||||
signalConfigSubject.next(createSignalMockConfig(SyncProviderId.WebDAV));
|
||||
|
||||
signalService.superSyncIsConfirmedInSync$.subscribe((isConfirmed) => {
|
||||
expect(isConfirmed).toBe(true);
|
||||
|
|
@ -1185,7 +1180,7 @@ describe('SyncWrapperService', () => {
|
|||
|
||||
it('should return false for Dropbox when status service returns false', (done) => {
|
||||
signalService = createServiceWithSignal(false);
|
||||
signalConfigSubject.next(createSignalMockConfig(LegacySyncProvider.Dropbox));
|
||||
signalConfigSubject.next(createSignalMockConfig(SyncProviderId.Dropbox));
|
||||
|
||||
signalService.superSyncIsConfirmedInSync$.subscribe((isConfirmed) => {
|
||||
expect(isConfirmed).toBe(false);
|
||||
|
|
@ -1195,7 +1190,7 @@ describe('SyncWrapperService', () => {
|
|||
|
||||
it('should return true for Dropbox when status service returns true', (done) => {
|
||||
signalService = createServiceWithSignal(true);
|
||||
signalConfigSubject.next(createSignalMockConfig(LegacySyncProvider.Dropbox));
|
||||
signalConfigSubject.next(createSignalMockConfig(SyncProviderId.Dropbox));
|
||||
|
||||
signalService.superSyncIsConfirmedInSync$.subscribe((isConfirmed) => {
|
||||
expect(isConfirmed).toBe(true);
|
||||
|
|
@ -1205,7 +1200,7 @@ describe('SyncWrapperService', () => {
|
|||
|
||||
it('should return false for LocalFile when status service returns false', (done) => {
|
||||
signalService = createServiceWithSignal(false);
|
||||
signalConfigSubject.next(createSignalMockConfig(LegacySyncProvider.LocalFile));
|
||||
signalConfigSubject.next(createSignalMockConfig(SyncProviderId.LocalFile));
|
||||
|
||||
signalService.superSyncIsConfirmedInSync$.subscribe((isConfirmed) => {
|
||||
expect(isConfirmed).toBe(false);
|
||||
|
|
@ -1215,7 +1210,7 @@ describe('SyncWrapperService', () => {
|
|||
|
||||
it('should return true for LocalFile when status service returns true', (done) => {
|
||||
signalService = createServiceWithSignal(true);
|
||||
signalConfigSubject.next(createSignalMockConfig(LegacySyncProvider.LocalFile));
|
||||
signalConfigSubject.next(createSignalMockConfig(SyncProviderId.LocalFile));
|
||||
|
||||
signalService.superSyncIsConfirmedInSync$.subscribe((isConfirmed) => {
|
||||
expect(isConfirmed).toBe(true);
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@ import { promiseTimeout } from '../../util/promise-timeout';
|
|||
import { devError } from '../../util/dev-error';
|
||||
import { alertDialog, confirmDialog } from '../../util/native-dialogs';
|
||||
import { UserInputWaitStateService } from './user-input-wait-state.service';
|
||||
import { LegacySyncProvider } from './legacy-sync-provider.model';
|
||||
import { SYNC_WAIT_TIMEOUT_MS, SYNC_REINIT_DELAY_MS } from './sync.const';
|
||||
import { SuperSyncStatusService } from '../../op-log/sync/super-sync-status.service';
|
||||
import { IS_ELECTRON } from '../../app.constants';
|
||||
|
|
@ -99,7 +98,7 @@ export class SyncWrapperService {
|
|||
syncInterval$: Observable<number> = this.syncCfg$.pipe(
|
||||
map((cfg) => {
|
||||
if (cfg.isManualSyncOnly) return 0;
|
||||
return cfg.syncProvider === LegacySyncProvider.SuperSync ? 60000 : cfg.syncInterval;
|
||||
return cfg.syncProvider === SyncProviderId.SuperSync ? 60000 : cfg.syncInterval;
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { TaskRepeatCfgState } from '../../features/task-repeat-cfg/task-repeat-c
|
|||
import { TagState } from '../../features/tag/tag.model';
|
||||
import { SimpleCounterState } from '../../features/simple-counter/simple-counter.model';
|
||||
import { ProjectArchive } from '../../features/project/project-archive.model';
|
||||
import { LegacySyncProvider } from './legacy-sync-provider.model';
|
||||
import { SyncProviderId } from '../../op-log/sync-providers/provider.const';
|
||||
import { ProjectState } from '../../features/project/project.model';
|
||||
import { NoteState } from '../../features/note/note.model';
|
||||
import { PlannerState } from '../../features/planner/store/planner.reducer';
|
||||
|
|
@ -60,9 +60,9 @@ export interface LocalSyncMetaForProvider {
|
|||
}
|
||||
|
||||
export interface LocalSyncMetaModel {
|
||||
[LegacySyncProvider.WebDAV]: LocalSyncMetaForProvider;
|
||||
[LegacySyncProvider.Dropbox]: LocalSyncMetaForProvider;
|
||||
[LegacySyncProvider.LocalFile]: LocalSyncMetaForProvider;
|
||||
[SyncProviderId.WebDAV]: LocalSyncMetaForProvider;
|
||||
[SyncProviderId.Dropbox]: LocalSyncMetaForProvider;
|
||||
[SyncProviderId.LocalFile]: LocalSyncMetaForProvider;
|
||||
}
|
||||
|
||||
export type AppBaseDataEntityLikeStates =
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { VectorClockService } from '../sync/vector-clock.service';
|
|||
import { ValidateStateService } from '../validation/validate-state.service';
|
||||
import { loadAllData } from '../../root-store/meta/load-all-data.action';
|
||||
import { ActionType, OpType } from '../core/operation.types';
|
||||
import { LegacySyncProvider } from '../../imex/sync/legacy-sync-provider.model';
|
||||
import { SyncProviderId } from '../sync-providers/provider.const';
|
||||
import { DEFAULT_GLOBAL_CONFIG } from '../../features/config/default-global-config.const';
|
||||
import { SnackService } from '../../core/snack/snack.service';
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ describe('SyncHydrationService', () => {
|
|||
const defaultLocalSyncConfig = {
|
||||
...DEFAULT_GLOBAL_CONFIG.sync,
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.WebDAV,
|
||||
syncProvider: SyncProviderId.WebDAV,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -537,7 +537,7 @@ describe('SyncHydrationService', () => {
|
|||
const localSyncConfig = {
|
||||
...DEFAULT_GLOBAL_CONFIG.sync,
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.WebDAV,
|
||||
syncProvider: SyncProviderId.WebDAV,
|
||||
};
|
||||
mockStore.select.and.returnValue(of(localSyncConfig));
|
||||
|
||||
|
|
@ -548,7 +548,7 @@ describe('SyncHydrationService', () => {
|
|||
sync: {
|
||||
...DEFAULT_GLOBAL_CONFIG.sync,
|
||||
isEnabled: false, // Another client had sync disabled!
|
||||
syncProvider: LegacySyncProvider.Dropbox, // Different provider too
|
||||
syncProvider: SyncProviderId.Dropbox, // Different provider too
|
||||
},
|
||||
},
|
||||
task: { ids: [], entities: {} },
|
||||
|
|
@ -568,7 +568,7 @@ describe('SyncHydrationService', () => {
|
|||
|
||||
// Step 4: Verify the snapshot has PRESERVED local settings (not remote's false)
|
||||
expect(snapshotSync['isEnabled']).toBe(true); // Local value preserved!
|
||||
expect(snapshotSync['syncProvider']).toBe(LegacySyncProvider.WebDAV); // Local provider preserved!
|
||||
expect(snapshotSync['syncProvider']).toBe(SyncProviderId.WebDAV); // Local provider preserved!
|
||||
|
||||
// Step 5: Simulate what happens on reload
|
||||
// The hydrator will call store.dispatch(loadAllData({ appDataComplete: snapshotState }))
|
||||
|
|
@ -581,7 +581,7 @@ describe('SyncHydrationService', () => {
|
|||
|
||||
// Final verification: The data dispatched to NgRx has correct local settings
|
||||
expect(dispatchedSync['isEnabled']).toBe(true);
|
||||
expect(dispatchedSync['syncProvider']).toBe(LegacySyncProvider.WebDAV);
|
||||
expect(dispatchedSync['syncProvider']).toBe(SyncProviderId.WebDAV);
|
||||
});
|
||||
|
||||
it('should preserve local isEnabled when remote has it disabled', async () => {
|
||||
|
|
@ -590,7 +590,7 @@ describe('SyncHydrationService', () => {
|
|||
of({
|
||||
...DEFAULT_GLOBAL_CONFIG.sync,
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.WebDAV,
|
||||
syncProvider: SyncProviderId.WebDAV,
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
@ -612,7 +612,7 @@ describe('SyncHydrationService', () => {
|
|||
const globalConfig = savedState['globalConfig'] as Record<string, unknown>;
|
||||
const sync = globalConfig['sync'] as Record<string, unknown>;
|
||||
expect(sync['isEnabled']).toBe(true);
|
||||
expect(sync['syncProvider']).toBe(LegacySyncProvider.WebDAV);
|
||||
expect(sync['syncProvider']).toBe(SyncProviderId.WebDAV);
|
||||
});
|
||||
|
||||
it('should preserve local isEnabled when remote has it enabled', async () => {
|
||||
|
|
@ -621,7 +621,7 @@ describe('SyncHydrationService', () => {
|
|||
of({
|
||||
...DEFAULT_GLOBAL_CONFIG.sync,
|
||||
isEnabled: false,
|
||||
syncProvider: LegacySyncProvider.WebDAV,
|
||||
syncProvider: SyncProviderId.WebDAV,
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
@ -651,7 +651,7 @@ describe('SyncHydrationService', () => {
|
|||
of({
|
||||
...DEFAULT_GLOBAL_CONFIG.sync,
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.WebDAV,
|
||||
syncProvider: SyncProviderId.WebDAV,
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
@ -660,7 +660,7 @@ describe('SyncHydrationService', () => {
|
|||
globalConfig: {
|
||||
sync: {
|
||||
isEnabled: false,
|
||||
syncProvider: LegacySyncProvider.Dropbox,
|
||||
syncProvider: SyncProviderId.Dropbox,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -677,7 +677,7 @@ describe('SyncHydrationService', () => {
|
|||
unknown
|
||||
>;
|
||||
const sync = globalConfig['sync'] as Record<string, unknown>;
|
||||
expect(sync['syncProvider']).toBe(LegacySyncProvider.WebDAV);
|
||||
expect(sync['syncProvider']).toBe(SyncProviderId.WebDAV);
|
||||
expect(sync['isEnabled']).toBe(true);
|
||||
});
|
||||
|
||||
|
|
@ -686,7 +686,7 @@ describe('SyncHydrationService', () => {
|
|||
of({
|
||||
...DEFAULT_GLOBAL_CONFIG.sync,
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.SuperSync,
|
||||
syncProvider: SyncProviderId.SuperSync,
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
@ -694,7 +694,7 @@ describe('SyncHydrationService', () => {
|
|||
globalConfig: {
|
||||
sync: {
|
||||
isEnabled: false,
|
||||
syncProvider: LegacySyncProvider.LocalFile,
|
||||
syncProvider: SyncProviderId.LocalFile,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -707,7 +707,7 @@ describe('SyncHydrationService', () => {
|
|||
const savedGlobalConfig = savedState['globalConfig'] as Record<string, unknown>;
|
||||
const savedSync = savedGlobalConfig['sync'] as Record<string, unknown>;
|
||||
expect(savedSync['isEnabled']).toBe(true);
|
||||
expect(savedSync['syncProvider']).toBe(LegacySyncProvider.SuperSync);
|
||||
expect(savedSync['syncProvider']).toBe(SyncProviderId.SuperSync);
|
||||
|
||||
// Check dispatch
|
||||
const dispatchCall = mockStore.dispatch.calls.mostRecent();
|
||||
|
|
@ -718,7 +718,7 @@ describe('SyncHydrationService', () => {
|
|||
.globalConfig as Record<string, unknown>;
|
||||
const dispatchedSync = dispatchedGlobalConfig['sync'] as Record<string, unknown>;
|
||||
expect(dispatchedSync['isEnabled']).toBe(true);
|
||||
expect(dispatchedSync['syncProvider']).toBe(LegacySyncProvider.SuperSync);
|
||||
expect(dispatchedSync['syncProvider']).toBe(SyncProviderId.SuperSync);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,9 +8,7 @@ export enum SyncProviderId {
|
|||
|
||||
/**
|
||||
* Type-safe conversion from string-based sync provider value to SyncProviderId.
|
||||
* LegacySyncProvider and SyncProviderId have identical string values but are
|
||||
* separate types for historical reasons. This provides safe conversion with
|
||||
* runtime validation.
|
||||
* Validates that a persisted string value is a valid SyncProviderId at runtime.
|
||||
*/
|
||||
export const toSyncProviderId = (
|
||||
value: string | null | undefined,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { SyncWrapperService } from '../../imex/sync/sync-wrapper.service';
|
|||
import { ShareService } from '../../core/share/share.service';
|
||||
import { UserProfileService } from '../../features/user-profile/user-profile.service';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { LegacySyncProvider } from '../../imex/sync/legacy-sync-provider.model';
|
||||
import { SyncProviderId } from '../../op-log/sync-providers/provider.const';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
describe('ConfigPageComponent', () => {
|
||||
|
|
@ -93,7 +93,7 @@ describe('ConfigPageComponent', () => {
|
|||
|
||||
const fullSyncModel = {
|
||||
isEnabled: true,
|
||||
syncProvider: LegacySyncProvider.WebDAV,
|
||||
syncProvider: SyncProviderId.WebDAV,
|
||||
syncInterval: 600000,
|
||||
webDav: webDavCfg,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -56,8 +56,7 @@ import { UserProfileService } from '../../features/user-profile/user-profile.ser
|
|||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { DialogDisableProfilesConfirmationComponent } from '../../features/user-profile/dialog-disable-profiles-confirmation/dialog-disable-profiles-confirmation.component';
|
||||
import { DialogRestorePointComponent } from '../../imex/sync/dialog-restore-point/dialog-restore-point.component';
|
||||
import { LegacySyncProvider } from '../../imex/sync/legacy-sync-provider.model';
|
||||
import { toSyncProviderId } from '../../op-log/sync-exports';
|
||||
import { SyncProviderId } from '../../op-log/sync-providers/provider.const';
|
||||
import { DialogConfirmComponent } from '../../ui/dialog-confirm/dialog-confirm.component';
|
||||
import { LS } from '../../core/persistence/storage-keys.const';
|
||||
import { MatTab, MatTabGroup, MatTabLabel } from '@angular/material/tabs';
|
||||
|
|
@ -378,7 +377,7 @@ export class ConfigPageComponent implements OnInit, OnDestroy {
|
|||
if ((item as any).props?.dropboxAuth && item.fieldGroup) {
|
||||
// Check if Dropbox is already authenticated
|
||||
const dropboxProvider = this._providerManager.getProviderById(
|
||||
toSyncProviderId(LegacySyncProvider.Dropbox)!,
|
||||
SyncProviderId.Dropbox,
|
||||
);
|
||||
|
||||
// We need to check auth status asynchronously, so we'll set initial state
|
||||
|
|
@ -419,7 +418,7 @@ export class ConfigPageComponent implements OnInit, OnDestroy {
|
|||
|
||||
const result =
|
||||
await this._syncWrapperService.configuredAuthForSyncProviderIfNecessary(
|
||||
toSyncProviderId(LegacySyncProvider.Dropbox)!,
|
||||
SyncProviderId.Dropbox,
|
||||
true, // force re-authentication even if already configured
|
||||
);
|
||||
|
||||
|
|
@ -525,7 +524,7 @@ export class ConfigPageComponent implements OnInit, OnDestroy {
|
|||
},
|
||||
{
|
||||
hideExpression: (m: Record<string, unknown>) =>
|
||||
!m.isEnabled || m.syncProvider !== LegacySyncProvider.SuperSync,
|
||||
!m.isEnabled || m.syncProvider !== SyncProviderId.SuperSync,
|
||||
type: 'btn',
|
||||
className: 'mt2 block',
|
||||
templateOptions: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue