mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-18 00:46:45 +00:00
build(lint): enforce Log helpers over console.* in src/app (#8239)
* build(lint): enforce Log helpers over console.* in src/app Adds an ESLint no-console rule scoped to src/app (exempting log.ts, *.spec.ts, and *.benchmark.ts) and migrates the remaining console.* call sites to use Log / TaskLog / SyncLog / IssueLog / PluginLog so their output is captured in the exportable log history users attach to bug reports. * test: spy on Log helpers instead of console after no-console migration * fix(trello): keep username out of exportable log history * test: derive today expectation from mocked DateService date
This commit is contained in:
parent
1999f2b413
commit
cbb4e28ec4
45 changed files with 172 additions and 119 deletions
|
|
@ -224,6 +224,21 @@ module.exports = tseslint.config(
|
|||
'local-rules/no-multi-entity-effect': 'warn',
|
||||
},
|
||||
},
|
||||
// App code must route logging through Log/SyncLog/OpLog/... helpers.
|
||||
// Direct console.* calls bypass the exportable log history users attach
|
||||
// to bug reports. The Log implementation itself, tests, and benchmarks
|
||||
// (which intentionally dump timing numbers to stdout) are exempt.
|
||||
{
|
||||
files: ['src/app/**/*.ts'],
|
||||
ignores: [
|
||||
'src/app/**/*.spec.ts',
|
||||
'src/app/**/*.benchmark.ts',
|
||||
'src/app/core/log.ts',
|
||||
],
|
||||
rules: {
|
||||
'no-console': 'error',
|
||||
},
|
||||
},
|
||||
// HTML files
|
||||
{
|
||||
files: ['**/*.html'],
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import { Project } from '../../../features/project/project.model';
|
|||
import { isSingleEmoji } from '../../../util/extract-first-emoji';
|
||||
import { expandCollapseAni } from '../../../ui/tree-dnd/tree.animations';
|
||||
import { Router } from '@angular/router';
|
||||
import { Log } from '../../../core/log';
|
||||
|
||||
const EXPAND_ANIMATION_RESET_DELAY_MS = 250;
|
||||
|
||||
|
|
@ -179,10 +180,7 @@ export class NavListTreeComponent implements OnDestroy {
|
|||
node: TreeNode<MenuTreeViewNode>,
|
||||
): void {
|
||||
// TODO: Implement folder context menu
|
||||
console.log(
|
||||
'Folder context menu for:',
|
||||
node.data?.k === MenuTreeKind.FOLDER ? node.data.name : 'unknown',
|
||||
);
|
||||
Log.log('Folder context menu for:', { id: node.id, kind: node.data?.k });
|
||||
}
|
||||
|
||||
private _toggleFolder(treeNodeId: string): void {
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import { firstValueFrom, Observable, of } from 'rxjs';
|
|||
import { AsyncPipe } from '@angular/common';
|
||||
import { DateService } from '../../core/date/date.service';
|
||||
import { openWorkContextSettingsDialog } from '../../features/work-context/dialog-work-context-settings/open-work-context-settings-dialog';
|
||||
import { Log } from '../../core/log';
|
||||
|
||||
@Component({
|
||||
selector: 'work-context-menu',
|
||||
|
|
@ -241,7 +242,7 @@ export class WorkContextMenuComponent implements OnInit {
|
|||
try {
|
||||
return await this._projectService.getCompletionInfo(this.contextId);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
Log.err(err);
|
||||
this._snackService.open({ type: 'ERROR', msg: T.F.PROJECT.COMPLETE.ERROR });
|
||||
return null;
|
||||
}
|
||||
|
|
@ -301,7 +302,7 @@ export class WorkContextMenuComponent implements OnInit {
|
|||
msg: T.GLOBAL_SNACK.DUPLICATE_PROJECT_ERROR,
|
||||
type: 'ERROR',
|
||||
});
|
||||
console.error(err);
|
||||
Log.err(err);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -447,7 +448,7 @@ export class WorkContextMenuComponent implements OnInit {
|
|||
msg: T.GLOBAL_SNACK.OPEN_SETTINGS_ERROR,
|
||||
type: 'ERROR',
|
||||
});
|
||||
console.error(err);
|
||||
Log.err(err);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { T } from '../../t.const';
|
|||
import { GlobalConfigService } from '../../features/config/global-config.service';
|
||||
import { getDefaultClipboardImagesPath } from '../../util/get-default-clipboard-images-path';
|
||||
import { MIME_TYPE_EXTENSIONS } from '../../../../electron/shared-with-frontend/mime-type-mapping.const';
|
||||
import { Log } from '../log';
|
||||
|
||||
const DB_NAME = 'sp-clipboard-images';
|
||||
const DB_VERSION = 1;
|
||||
|
|
@ -117,7 +118,7 @@ export class ClipboardImageService {
|
|||
return { success: true, imageUrl, markdownText };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[CLIPBOARD] Error getting file path:', error);
|
||||
Log.err('[CLIPBOARD] Error getting file path:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -164,8 +165,8 @@ export class ClipboardImageService {
|
|||
|
||||
return { success: true, imageUrl, markdownText };
|
||||
} catch (error) {
|
||||
console.error('[CLIPBOARD] Error saving clipboard image:', error);
|
||||
console.error(
|
||||
Log.err('[CLIPBOARD] Error saving clipboard image:', error);
|
||||
Log.err(
|
||||
'[CLIPBOARD] Error stack:',
|
||||
error instanceof Error ? error.stack : 'no stack',
|
||||
);
|
||||
|
|
@ -234,7 +235,7 @@ export class ClipboardImageService {
|
|||
|
||||
return { success: true, imageUrl, markdownText };
|
||||
} catch (error) {
|
||||
console.error('Error getting image from file paths:', error);
|
||||
Log.err('Error getting image from file paths:', error);
|
||||
return null; // Fall back to regular clipboard handling
|
||||
}
|
||||
}
|
||||
|
|
@ -316,7 +317,7 @@ export class ClipboardImageService {
|
|||
this._blobUrlCache.set(imageId, blobUrl);
|
||||
return blobUrl;
|
||||
} catch (error) {
|
||||
console.error('Error resolving indexeddb URL for clipboard image:', error);
|
||||
Log.err('Error resolving indexeddb URL for clipboard image:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
SimpleChanges,
|
||||
} from '@angular/core';
|
||||
import { ClipboardImageService } from './clipboard-image.service';
|
||||
import { Log } from '../log';
|
||||
|
||||
/**
|
||||
* Directive that resolves indexeddb:// URLs in img elements within the host element.
|
||||
|
|
@ -98,7 +99,7 @@ export class ResolveClipboardImagesDirective implements OnInit, OnDestroy, OnCha
|
|||
img.classList.add('indexeddb-image-error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error resolving clipboard image:', error);
|
||||
Log.err('Error resolving clipboard image:', error);
|
||||
img.alt = 'Error loading image';
|
||||
img.classList.add('indexeddb-image-error');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { Pipe, PipeTransform, inject } from '@angular/core';
|
|||
import { ClipboardImageService } from './clipboard-image.service';
|
||||
import { Observable, from, of, defer } from 'rxjs';
|
||||
import { map, shareReplay, catchError } from 'rxjs/operators';
|
||||
import { Log } from '../log';
|
||||
|
||||
@Pipe({
|
||||
name: 'resolveClipboardUrl',
|
||||
|
|
@ -38,7 +39,7 @@ export class ResolveClipboardUrlPipe implements PipeTransform {
|
|||
return url;
|
||||
}),
|
||||
catchError((error) => {
|
||||
console.error('Error resolving clipboard URL:', error);
|
||||
Log.err('Error resolving clipboard URL:', error);
|
||||
return of(url);
|
||||
}),
|
||||
shareReplay({ bufferSize: 1, refCount: false }),
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ export const logAdvancedStacktrace = (
|
|||
|
||||
// NOTE: there is an issue with this sometimes -> https://github.com/stacktracejs/stacktrace.js/issues/202
|
||||
})
|
||||
.catch(console.error);
|
||||
.catch((err) => Log.err(err));
|
||||
|
||||
const _cleanHtml = (str: string): string => {
|
||||
const div = document.createElement('div');
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Directory, Filesystem } from '@capacitor/filesystem';
|
||||
import { IS_NATIVE_PLATFORM } from '../../util/is-native-platform';
|
||||
import { ShareResult } from './share.model';
|
||||
import { Log } from '../log';
|
||||
|
||||
/**
|
||||
* Sanitize filename for safe file system usage.
|
||||
|
|
@ -43,7 +44,7 @@ export const downloadBlob = (blob: Blob, filename: string): boolean => {
|
|||
anchor.click();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.warn('Browser download failed:', error);
|
||||
Log.warn('Browser download failed:', error);
|
||||
return false;
|
||||
} finally {
|
||||
document.body.removeChild(anchor);
|
||||
|
|
@ -65,7 +66,7 @@ export const cleanupCacheFile = async (relativePath: string): Promise<void> => {
|
|||
directory: Directory.Cache,
|
||||
});
|
||||
} catch (cleanupError) {
|
||||
console.warn('Failed to cleanup shared file:', cleanupError);
|
||||
Log.warn('Failed to cleanup shared file:', cleanupError);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -120,7 +121,7 @@ export const openDownloadResult = async (result: ShareResult): Promise<void> =>
|
|||
(window as any).ea.openPath(path);
|
||||
return;
|
||||
} catch (error) {
|
||||
console.warn('Failed to open path via Electron bridge:', error);
|
||||
Log.warn('Failed to open path via Electron bridge:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -131,7 +132,7 @@ export const openDownloadResult = async (result: ShareResult): Promise<void> =>
|
|||
window.open(candidate, '_blank', 'noopener');
|
||||
return;
|
||||
} catch (error) {
|
||||
console.warn('Failed to open download in new window:', error);
|
||||
Log.warn('Failed to open download in new window:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import * as ShareTextUtil from './share-text.util';
|
|||
import * as ShareUrlBuilder from './share-url-builder.util';
|
||||
import * as ShareFileUtil from './share-file.util';
|
||||
import * as SharePlatformUtil from './share-platform.util';
|
||||
import { Log } from '../log';
|
||||
|
||||
export type ShareOutcome = 'shared' | 'cancelled' | 'unavailable' | 'failed';
|
||||
export type { ShareSupport } from './share-platform.util';
|
||||
|
|
@ -252,7 +253,7 @@ export class ShareService {
|
|||
error: 'Share cancelled',
|
||||
};
|
||||
}
|
||||
console.warn('Capacitor share failed:', error);
|
||||
Log.warn('Capacitor share failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -273,7 +274,7 @@ export class ShareService {
|
|||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
return { success: false, error: 'Share cancelled' };
|
||||
}
|
||||
console.warn('Web Share API failed:', error);
|
||||
Log.warn('Web Share API failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -524,7 +525,7 @@ export class ShareService {
|
|||
? `file://${resolvedUri}`
|
||||
: `file:///${resolvedUri}`;
|
||||
|
||||
console.debug('[ShareService] shareCanvasViaNative', {
|
||||
Log.debug('[ShareService] shareCanvasViaNative', {
|
||||
resolvedUri,
|
||||
fileUrl,
|
||||
relativePath,
|
||||
|
|
@ -535,9 +536,9 @@ export class ShareService {
|
|||
path: relativePath,
|
||||
directory: Directory.Cache,
|
||||
});
|
||||
console.debug('[ShareService] shareCanvasViaNative stat', stat);
|
||||
Log.debug('[ShareService] shareCanvasViaNative stat', stat);
|
||||
} catch (statError) {
|
||||
console.warn('[ShareService] stat failed for shared image', statError);
|
||||
Log.warn('[ShareService] stat failed for shared image', statError);
|
||||
}
|
||||
|
||||
const canShare = (await sharePlugin.canShare?.())?.value ?? true;
|
||||
|
|
@ -570,7 +571,7 @@ export class ShareService {
|
|||
error: 'Share cancelled',
|
||||
};
|
||||
}
|
||||
console.warn('Native image share failed:', error, {
|
||||
Log.warn('Native image share failed:', error, {
|
||||
fileUrl: fileUrl ?? 'n/a',
|
||||
resolvedUri: resolvedUri ?? 'n/a',
|
||||
relativePath,
|
||||
|
|
@ -633,7 +634,7 @@ export class ShareService {
|
|||
error: 'Share cancelled',
|
||||
};
|
||||
}
|
||||
console.warn('Web Share API failed:', error);
|
||||
Log.warn('Web Share API failed:', error);
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -683,7 +684,7 @@ export class ShareService {
|
|||
uri,
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn('Native download failed, falling back to browser download:', error);
|
||||
Log.warn('Native download failed, falling back to browser download:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -703,7 +704,7 @@ export class ShareService {
|
|||
target: 'download',
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn('Opening image in new tab failed:', error);
|
||||
Log.warn('Opening image in new tab failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { HttpClient } from '@angular/common/http';
|
|||
import { Observable, of } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { getEnvOptional } from '../../util/env';
|
||||
import { Log } from '../log';
|
||||
|
||||
export interface UnsplashPhoto {
|
||||
id: string;
|
||||
|
|
@ -56,7 +57,7 @@ export class UnsplashService {
|
|||
}
|
||||
|
||||
if (!this.ACCESS_KEY) {
|
||||
console.warn(
|
||||
Log.warn(
|
||||
'No Unsplash Access Key configured. Register at https://unsplash.com/developers?utm_source=super-productivity&utm_medium=referral&utm_campaign=api-credit',
|
||||
);
|
||||
return of({ results: [], total: 0, total_pages: 0 });
|
||||
|
|
@ -81,7 +82,7 @@ export class UnsplashService {
|
|||
})
|
||||
.pipe(
|
||||
catchError((error) => {
|
||||
console.error('Unsplash API error:', error);
|
||||
Log.err('Unsplash API error:', error);
|
||||
return of({ results: [], total: 0, total_pages: 0 });
|
||||
}),
|
||||
);
|
||||
|
|
@ -122,12 +123,12 @@ export class UnsplashService {
|
|||
*/
|
||||
trackPhotoDownload(photo: UnsplashPhoto): Observable<any> {
|
||||
if (!photo.links?.download_location) {
|
||||
console.warn('No download_location available for photo', photo.id);
|
||||
Log.warn('No download_location available for photo', photo.id);
|
||||
return of(null);
|
||||
}
|
||||
|
||||
if (!this.ACCESS_KEY) {
|
||||
console.warn('No Unsplash Access Key configured');
|
||||
Log.warn('No Unsplash Access Key configured');
|
||||
return of(null);
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +139,7 @@ export class UnsplashService {
|
|||
// Call the download endpoint to track usage
|
||||
return this._http.get(photo.links.download_location, { headers }).pipe(
|
||||
catchError((error) => {
|
||||
console.error('Failed to track photo download:', error);
|
||||
Log.err('Failed to track photo download:', error);
|
||||
// Don't fail the selection if tracking fails
|
||||
return of(null);
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { MatInput } from '@angular/material/input';
|
|||
import { FormsModule } from '@angular/forms';
|
||||
import { DialogClipboardImagesManagerComponent } from './dialog-clipboard-images-manager/dialog-clipboard-images-manager.component';
|
||||
import { getDefaultClipboardImagesPath } from '../../../util/get-default-clipboard-images-path';
|
||||
import { Log } from '../../../core/log';
|
||||
|
||||
@Component({
|
||||
selector: 'clipboard-images-cfg',
|
||||
|
|
@ -80,7 +81,7 @@ export class ClipboardImagesCfgComponent implements OnInit {
|
|||
this.defaultImagePath = await getDefaultClipboardImagesPath();
|
||||
this._cd.markForCheck();
|
||||
} catch (error) {
|
||||
console.error('Error loading default clipboard image path:', error);
|
||||
Log.err('Error loading default clipboard image path:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import { MatTooltip } from '@angular/material/tooltip';
|
|||
import { MatFormField, MatLabel } from '@angular/material/form-field';
|
||||
import { MatSelect } from '@angular/material/select';
|
||||
import { MatOption } from '@angular/material/core';
|
||||
import { Log } from '../../../../core/log';
|
||||
|
||||
type SortOrder = 'newest' | 'oldest' | 'largest' | 'smallest';
|
||||
|
||||
|
|
@ -100,7 +101,7 @@ export class DialogClipboardImagesManagerComponent implements OnInit {
|
|||
}
|
||||
this.imageUrls.set(urlMap);
|
||||
} catch (error) {
|
||||
console.error('Error loading clipboard images:', error);
|
||||
Log.err('Error loading clipboard images:', error);
|
||||
this._snackService.open({
|
||||
type: 'ERROR',
|
||||
msg: this._translateService.instant(T.GCF.CLIPBOARD_IMAGES.ERROR_LOADING),
|
||||
|
|
@ -124,7 +125,7 @@ export class DialogClipboardImagesManagerComponent implements OnInit {
|
|||
throw new Error('Delete operation returned false');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting clipboard image:', error);
|
||||
Log.err('Error deleting clipboard image:', error);
|
||||
this._snackService.open({
|
||||
type: 'ERROR',
|
||||
msg: this._translateService.instant(T.GCF.CLIPBOARD_IMAGES.ERROR_DELETING),
|
||||
|
|
@ -159,7 +160,7 @@ export class DialogClipboardImagesManagerComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting all clipboard images:', error);
|
||||
Log.err('Error deleting all clipboard images:', error);
|
||||
await this.loadImages();
|
||||
this._snackService.open({
|
||||
type: 'ERROR',
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { containsEmoji, extractFirstEmoji } from '../../../util/extract-first-em
|
|||
import { isSingleEmoji } from '../../../util/extract-first-emoji';
|
||||
import { startWith } from 'rxjs/operators';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { Log } from '../../../core/log';
|
||||
|
||||
@Component({
|
||||
selector: 'icon-input',
|
||||
|
|
@ -80,7 +81,7 @@ export class IconInputComponent extends FieldType<FormlyFieldConfig> implements
|
|||
this.filteredIcons.set(icons.slice(0, 50));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load material icons:', error);
|
||||
Log.err('Failed to load material icons:', error);
|
||||
this.filteredIcons.set([]);
|
||||
}
|
||||
}
|
||||
|
|
@ -123,7 +124,7 @@ export class IconInputComponent extends FieldType<FormlyFieldConfig> implements
|
|||
this.isEmoji.set(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to filter icons:', error);
|
||||
Log.err('Failed to filter icons:', error);
|
||||
this.filteredIcons.set([]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import { AppStateActions } from '../../../root-store/app-state/app-state.actions
|
|||
import { TaskSharedActions } from '../../../root-store/meta/task-shared.actions';
|
||||
import { selectAllTasks } from '../../tasks/store/task.selectors';
|
||||
import { normalizeStartOfNextDayConfig } from '../normalize-start-of-next-day-config';
|
||||
import { Log } from '../../../core/log';
|
||||
|
||||
@Injectable()
|
||||
export class GlobalConfigEffects {
|
||||
|
|
@ -243,7 +244,7 @@ export class GlobalConfigEffects {
|
|||
setTimeout(() => window.location.reload(), 1000);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to migrate user profiles:', err);
|
||||
Log.err('Failed to migrate user profiles:', err);
|
||||
this._snackService.open({
|
||||
type: 'ERROR',
|
||||
msg: 'Failed to enable user profiles. Please try again.',
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { CalendarContextInfoTarget } from '../../issue/providers/calendar/calend
|
|||
import { selectEnabledIssueProviders } from '../../issue/store/issue-provider.selectors';
|
||||
import { PluginIssueProviderRegistryService } from '../../../plugins/issue-provider/plugin-issue-provider-registry.service';
|
||||
import { PluginService } from '../../../plugins/plugin.service';
|
||||
import { IssueLog } from '../../../core/log';
|
||||
|
||||
@Component({
|
||||
selector: 'issue-provider-setup-overview',
|
||||
|
|
@ -69,7 +70,7 @@ export class IssueProviderSetupOverviewComponent {
|
|||
this.pluginProviders = allProviders.filter((p) => !p.useAgendaView);
|
||||
this.pluginCalendarProviders = allProviders.filter((p) => p.useAgendaView);
|
||||
if (!isValidIssueProviderKey(issueProviderKey)) {
|
||||
console.error(`Invalid issue provider key from plugin: "${issueProviderKey}"`);
|
||||
IssueLog.err(`Invalid issue provider key from plugin: "${issueProviderKey}"`);
|
||||
return;
|
||||
}
|
||||
this.openSetupDialog(issueProviderKey);
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ export class DialogEditIssueProviderComponent {
|
|||
|
||||
constructor() {
|
||||
this._initOAuthAndOptions().catch((err) => {
|
||||
console.error(
|
||||
IssueLog.err(
|
||||
'[DialogEditIssueProvider] OAuth init failed',
|
||||
getSafeErrorLogMeta(err),
|
||||
);
|
||||
|
|
@ -453,7 +453,7 @@ export class DialogEditIssueProviderComponent {
|
|||
}
|
||||
} catch (e) {
|
||||
anyFailed = true;
|
||||
console.error('[DialogEditIssueProvider] loadOptions failed', {
|
||||
IssueLog.err('[DialogEditIssueProvider] loadOptions failed', {
|
||||
issueProviderKey: this.issueProviderKey,
|
||||
hasFieldKey: !!field.key,
|
||||
...getSafeErrorLogMeta(e),
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { ISSUE_PROVIDER_HUMANIZED, TRELLO_TYPE } from '../../issue.const';
|
|||
import { T } from '../../../../t.const';
|
||||
import { HANDLED_ERROR_PROP_STR } from '../../../../app.constants';
|
||||
import { getErrorTxt } from '../../../../util/get-error-text';
|
||||
import { IssueLog } from '../../../../core/log';
|
||||
|
||||
const BASE_URL = 'https://api.trello.com/1';
|
||||
const DEFAULT_CARD_FIELDS = [
|
||||
|
|
@ -195,7 +196,8 @@ export class TrelloApiService {
|
|||
}),
|
||||
catchError((error) => {
|
||||
const errorMsg = `Trello failed to resolve username "${username}": ${getErrorTxt(error)}`;
|
||||
console.error(errorMsg);
|
||||
// errorMsg embeds the username (user content) — keep it out of the exportable log
|
||||
IssueLog.err('Trello failed to resolve username:', getErrorTxt(error));
|
||||
this._snackService.open({
|
||||
type: 'ERROR',
|
||||
msg: errorMsg,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import {
|
|||
} from '../../../ui/heatmap/heatmap.component';
|
||||
import { DateAdapter } from '@angular/material/core';
|
||||
import { Worklog } from '../../worklog/worklog.model';
|
||||
import { Log } from '../../../core/log';
|
||||
|
||||
interface YearlyActivityData {
|
||||
dayMap: Map<string, DayData>;
|
||||
|
|
@ -368,7 +369,7 @@ export class ActivityHeatmapComponent {
|
|||
});
|
||||
}
|
||||
} else if (result.error && result.error !== 'Share cancelled') {
|
||||
console.error('Share failed:', result.error);
|
||||
Log.err('Share failed:', result.error);
|
||||
this._snackService.open({
|
||||
type: 'ERROR',
|
||||
msg: 'Failed to share heatmap',
|
||||
|
|
@ -377,7 +378,7 @@ export class ActivityHeatmapComponent {
|
|||
} catch (error: any) {
|
||||
const isAbort = error?.name === 'AbortError' || error?.error === 'Share cancelled';
|
||||
if (!isAbort) {
|
||||
console.error('Share failed:', error);
|
||||
Log.err('Share failed:', error);
|
||||
this._snackService.open({
|
||||
type: 'ERROR',
|
||||
msg: 'Failed to share heatmap',
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import { ChartLazyLoaderService } from '../chart-lazy-loader.service';
|
|||
import { ShareService } from '../../../core/share/share.service';
|
||||
import { SnackService } from '../../../core/snack/snack.service';
|
||||
import { T } from '../../../t.const';
|
||||
import { Log } from '../../../core/log';
|
||||
|
||||
interface ChartClickEvent {
|
||||
active: ActiveElement[];
|
||||
|
|
@ -226,10 +227,10 @@ export class LazyChartComponent implements OnDestroy {
|
|||
}
|
||||
|
||||
if (!result.success && result.error && result.error !== 'Share cancelled') {
|
||||
console.error('Share failed:', result.error);
|
||||
Log.err('Share failed:', result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Share failed:', error);
|
||||
Log.err('Share failed:', error);
|
||||
} finally {
|
||||
this.isSharing.set(false);
|
||||
}
|
||||
|
|
@ -247,14 +248,14 @@ export class LazyChartComponent implements OnDestroy {
|
|||
await this.chartLoaderService.loadChartModule();
|
||||
this.isModuleLoaded.set(true);
|
||||
} catch (error) {
|
||||
console.error('Failed to load chart module:', error);
|
||||
Log.err('Failed to load chart module:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private initChart(canvas: ElementRef<HTMLCanvasElement>): void {
|
||||
const chartModule = this.chartLoaderService.getChartModule();
|
||||
if (!chartModule) {
|
||||
console.error('Chart module not loaded');
|
||||
Log.err('Chart module not loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -269,7 +270,7 @@ export class LazyChartComponent implements OnDestroy {
|
|||
this.chartInstance?.destroy();
|
||||
this.chartInstance = new Chart(canvas.nativeElement, chartConfig);
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize chart:', error);
|
||||
Log.err('Failed to initialize chart:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { of, Subscription, timer } from 'rxjs';
|
|||
import { SwipeDirective } from '../../ui/swipe-gesture/swipe.directive';
|
||||
import { CssString, StyleObject, StyleObjectToString } from '../../util/styles';
|
||||
import { LS } from '../../core/persistence/storage-keys.const';
|
||||
import { Log } from '../../core/log';
|
||||
import { readNumberLSBounded } from '../../util/ls-util';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatRippleModule } from '@angular/material/core';
|
||||
|
|
@ -558,7 +559,7 @@ export class RightPanelComponent implements AfterViewInit, OnDestroy {
|
|||
try {
|
||||
localStorage.setItem(LS.RIGHT_PANEL_WIDTH, this.currentWidth().toString());
|
||||
} catch (error) {
|
||||
console.warn('Failed to save right panel width to localStorage:', error);
|
||||
Log.warn('Failed to save right panel width to localStorage:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -577,7 +578,7 @@ export class RightPanelComponent implements AfterViewInit, OnDestroy {
|
|||
|
||||
// Additional validation
|
||||
if (!Number.isFinite(width) || width < RIGHT_PANEL_CONFIG.MIN_WIDTH) {
|
||||
console.warn('Invalid right panel width detected, using default');
|
||||
Log.warn('Invalid right panel width detected, using default');
|
||||
this.currentWidth.set(RIGHT_PANEL_CONFIG.DEFAULT_WIDTH);
|
||||
return;
|
||||
}
|
||||
|
|
@ -586,7 +587,7 @@ export class RightPanelComponent implements AfterViewInit, OnDestroy {
|
|||
const validatedWidth = clampWidth(width, RIGHT_PANEL_CONFIG.MAX_WIDTH);
|
||||
this.currentWidth.set(validatedWidth);
|
||||
} catch (error) {
|
||||
console.warn('Failed to initialize right panel width:', error);
|
||||
Log.warn('Failed to initialize right panel width:', error);
|
||||
this.currentWidth.set(RIGHT_PANEL_CONFIG.DEFAULT_WIDTH);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import { DateService } from 'src/app/core/date/date.service';
|
|||
import { GlobalTrackingIntervalService } from '../../core/global-tracking-interval/global-tracking-interval.service';
|
||||
import { ImexViewService } from '../../imex/imex-meta/imex-view.service';
|
||||
import { BatchedTimeSyncAccumulator } from '../../core/util/batched-time-sync-accumulator';
|
||||
import { Log } from '../../core/log';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
|
|
@ -223,7 +224,7 @@ export class SimpleCounterService implements OnDestroy {
|
|||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('[SimpleCounterService] Error syncing stopwatch value:', error);
|
||||
Log.err('[SimpleCounterService] Error syncing stopwatch value:', error);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { MatTooltip } from '@angular/material/tooltip';
|
|||
import { DateTimeFormatService } from '../../../core/date-time-format/date-time-format.service';
|
||||
import { getNextRepeatOccurrence } from '../store/get-next-repeat-occurrence.util';
|
||||
import { formatMonthDay } from '../../../util/format-month-day.util';
|
||||
import { Log } from '../../../core/log';
|
||||
|
||||
@Component({
|
||||
selector: 'repeat-cfg-preview',
|
||||
|
|
@ -50,7 +51,7 @@ export class RepeatCfgPreviewComponent {
|
|||
const nextLabel = this._translateService.instant(T.SCHEDULE.NEXT);
|
||||
return `${nextLabel} ${formatted}`;
|
||||
} catch (e) {
|
||||
console.warn('Failed to compute next repeat occurrence', e);
|
||||
Log.warn('Failed to compute next repeat occurrence', e);
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import { ipcAddTaskFromAppUri$ } from '../../../core/ipc-events';
|
|||
import { TaskService } from '../task.service';
|
||||
import { TaskSharedActions } from '../../../root-store/meta/task-shared.actions';
|
||||
import { LOCAL_ACTIONS } from '../../../util/local-actions.token';
|
||||
import { TaskLog } from '../../../core/log';
|
||||
|
||||
// TODO send message to electron when current task changes here
|
||||
|
||||
|
|
@ -205,7 +206,10 @@ export class TaskElectronEffects {
|
|||
tap((data) => {
|
||||
// Double-check data validity as defensive programming
|
||||
if (!data || !data.title || typeof data.title !== 'string') {
|
||||
console.error('handleAddTaskFromProtocol$ received invalid data:', data);
|
||||
TaskLog.err('handleAddTaskFromProtocol$ received invalid data', {
|
||||
hasData: !!data,
|
||||
titleType: typeof data?.title,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this._taskService.add(data.title);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
import { androidInterface } from '../../android/android-interface';
|
||||
import { generateNotificationId } from '../../android/android-notification-id.util';
|
||||
import { PlannerActions } from '../../planner/store/planner.actions';
|
||||
import { TaskLog } from '../../../core/log';
|
||||
|
||||
@Injectable()
|
||||
export class TaskReminderEffects {
|
||||
|
|
@ -97,7 +98,7 @@ export class TaskReminderEffects {
|
|||
const notificationId = generateNotificationId(task.id);
|
||||
androidInterface.cancelNativeReminder?.(notificationId);
|
||||
} catch (e) {
|
||||
console.error('Failed to cancel native reminder:', e);
|
||||
TaskLog.err('Failed to cancel native reminder:', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -115,7 +116,7 @@ export class TaskReminderEffects {
|
|||
const notificationId = generateNotificationId(task.id + '_deadline');
|
||||
androidInterface.cancelNativeReminder?.(notificationId);
|
||||
} catch (e) {
|
||||
console.error('Failed to cancel native deadline reminder:', e);
|
||||
TaskLog.err('Failed to cancel native deadline reminder:', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -218,7 +219,7 @@ export class TaskReminderEffects {
|
|||
androidInterface.cancelNativeReminder?.(deadlineNotificationId);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to cancel native reminder:', e);
|
||||
TaskLog.err('Failed to cancel native reminder:', e);
|
||||
}
|
||||
}),
|
||||
),
|
||||
|
|
@ -243,7 +244,7 @@ export class TaskReminderEffects {
|
|||
const notificationId = generateNotificationId(id);
|
||||
androidInterface.cancelNativeReminder?.(notificationId);
|
||||
} catch (e) {
|
||||
console.error('Failed to cancel native reminder:', e);
|
||||
TaskLog.err('Failed to cancel native reminder:', e);
|
||||
}
|
||||
});
|
||||
}),
|
||||
|
|
@ -267,7 +268,7 @@ export class TaskReminderEffects {
|
|||
generateNotificationId(id + '_deadline'),
|
||||
);
|
||||
} catch (e) {
|
||||
console.error('Failed to cancel native reminder:', e);
|
||||
TaskLog.err('Failed to cancel native reminder:', e);
|
||||
}
|
||||
});
|
||||
}),
|
||||
|
|
@ -290,7 +291,7 @@ export class TaskReminderEffects {
|
|||
generateNotificationId(id + '_deadline'),
|
||||
);
|
||||
} catch (e) {
|
||||
console.error('Failed to cancel native reminder:', e);
|
||||
TaskLog.err('Failed to cancel native reminder:', e);
|
||||
}
|
||||
});
|
||||
}),
|
||||
|
|
@ -313,7 +314,7 @@ export class TaskReminderEffects {
|
|||
generateNotificationId(task.id + '_deadline'),
|
||||
);
|
||||
} catch (e) {
|
||||
console.error('Failed to cancel native reminder:', e);
|
||||
TaskLog.err('Failed to cancel native reminder:', e);
|
||||
}
|
||||
// Also cancel for subtasks
|
||||
task.subTaskIds?.forEach((subId) => {
|
||||
|
|
@ -323,7 +324,7 @@ export class TaskReminderEffects {
|
|||
generateNotificationId(subId + '_deadline'),
|
||||
);
|
||||
} catch (e) {
|
||||
console.error('Failed to cancel native reminder:', e);
|
||||
TaskLog.err('Failed to cancel native reminder:', e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { TaskAttachment } from '../task-attachment.model';
|
|||
import { T } from '../../../../t.const';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ClipboardImageService } from '../../../../core/clipboard-image/clipboard-image.service';
|
||||
import { Log } from '../../../../core/log';
|
||||
|
||||
describe('TaskAttachmentListComponent', () => {
|
||||
let component: TaskAttachmentListComponent;
|
||||
|
|
@ -152,12 +153,12 @@ describe('TaskAttachmentListComponent', () => {
|
|||
.and.returnValue(Promise.reject(new Error('Permission denied')));
|
||||
setNavigatorClipboard({ writeText: writeTextSpy });
|
||||
document.execCommand = jasmine.createSpy('execCommand').and.returnValue(true);
|
||||
spyOn(console, 'warn');
|
||||
spyOn(Log, 'warn');
|
||||
|
||||
await component.copy(attachment);
|
||||
|
||||
expect(writeTextSpy).toHaveBeenCalledWith('https://example.com/test');
|
||||
expect(console.warn).toHaveBeenCalled();
|
||||
expect(Log.warn).toHaveBeenCalled();
|
||||
expect(document.execCommand).toHaveBeenCalledWith('copy');
|
||||
expect(snackService.open).toHaveBeenCalledWith(T.GLOBAL_SNACK.COPY_TO_CLIPPBOARD);
|
||||
});
|
||||
|
|
@ -180,12 +181,12 @@ describe('TaskAttachmentListComponent', () => {
|
|||
document.execCommand = jasmine
|
||||
.createSpy('execCommand')
|
||||
.and.throwError('Command not supported');
|
||||
spyOn(console, 'error');
|
||||
spyOn(Log, 'err');
|
||||
|
||||
await component.copy(attachment);
|
||||
|
||||
expect(document.execCommand).toHaveBeenCalledWith('copy');
|
||||
expect(console.error).toHaveBeenCalled();
|
||||
expect(Log.err).toHaveBeenCalled();
|
||||
expect(snackService.open).toHaveBeenCalledWith({
|
||||
msg: 'Failed to copy to clipboard. Please copy manually.',
|
||||
type: 'ERROR',
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { EnlargeImgDirective } from '../../../../ui/enlarge-img/enlarge-img.dire
|
|||
import { MatAnchor, MatButton } from '@angular/material/button';
|
||||
import { ClipboardImageService } from '../../../../core/clipboard-image/clipboard-image.service';
|
||||
import { isPathSafeToOpen } from '../../../../../../electron/shared-with-frontend/is-external-url-allowed';
|
||||
import { Log } from '../../../../core/log';
|
||||
|
||||
interface ResolvedAttachment extends TaskAttachment {
|
||||
resolvedPath?: string;
|
||||
|
|
@ -141,7 +142,7 @@ export class TaskAttachmentListComponent {
|
|||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error resolving clipboard image:', error);
|
||||
Log.err('Error resolving clipboard image:', error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -192,7 +193,7 @@ export class TaskAttachmentListComponent {
|
|||
this._copyWithFallback(attachment.path);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Clipboard write failed, trying fallback method:', error);
|
||||
Log.warn('Clipboard write failed, trying fallback method:', error);
|
||||
// Try fallback method if modern API fails
|
||||
this._copyWithFallback(attachment.path);
|
||||
}
|
||||
|
|
@ -220,7 +221,7 @@ export class TaskAttachmentListComponent {
|
|||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fallback copy failed:', error);
|
||||
Log.err('Fallback copy failed:', error);
|
||||
this._snackService.open({
|
||||
msg: 'Failed to copy to clipboard. Please copy manually.',
|
||||
type: 'ERROR',
|
||||
|
|
|
|||
|
|
@ -30,6 +30,6 @@ export const playDoneSound = async (
|
|||
source.detune.value = pitchFactor;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Error playing done sound:', e);
|
||||
TaskLog.err('Error playing done sound:', e);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
|
|||
import { MatDivider } from '@angular/material/divider';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { T } from '../../../t.const';
|
||||
import { Log } from '../../../core/log';
|
||||
|
||||
@Component({
|
||||
selector: 'user-profile-button',
|
||||
|
|
@ -94,7 +95,7 @@ export class UserProfileButtonComponent {
|
|||
try {
|
||||
await this.profileService.switchProfile(profileId);
|
||||
} catch (error) {
|
||||
console.error('Failed to switch profile:', error);
|
||||
Log.err('Failed to switch profile:', error);
|
||||
} finally {
|
||||
this.isLoading.set(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { Store } from '@ngrx/store';
|
|||
import { selectTasksWithSubTasksByIds } from '../tasks/store/task.selectors';
|
||||
import { Task, TaskWithSubTasks } from '../tasks/task.model';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { Log } from '../../core/log';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
|
|
@ -167,7 +168,7 @@ export class WorkContextMarkdownService {
|
|||
await navigator.clipboard.writeText(text);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.warn('Clipboard write failed, trying fallback method:', err);
|
||||
Log.warn('Clipboard write failed, trying fallback method:', err);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -189,7 +190,7 @@ export class WorkContextMarkdownService {
|
|||
try {
|
||||
isSuccess = document.execCommand('copy');
|
||||
} catch (err) {
|
||||
console.error('Fallback copy failed:', err);
|
||||
Log.err('Fallback copy failed:', err);
|
||||
isSuccess = false;
|
||||
} finally {
|
||||
document.body.removeChild(textarea);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { SnackService } from '../../core/snack/snack.service';
|
|||
import { SyncProviderId } from '../../op-log/sync-providers/provider.const';
|
||||
import { RestorePoint } from '../../op-log/sync-providers/provider.interface';
|
||||
import { T } from '../../t.const';
|
||||
import { SyncLog } from '../../core/log';
|
||||
|
||||
describe('SuperSyncRestoreService', () => {
|
||||
let service: SuperSyncRestoreService;
|
||||
|
|
@ -31,9 +32,9 @@ describe('SuperSyncRestoreService', () => {
|
|||
|
||||
mockSnackService = jasmine.createSpyObj('SnackService', ['open']);
|
||||
|
||||
// Spy on console methods for retry logging tests
|
||||
spyOn(console, 'warn');
|
||||
spyOn(console, 'error');
|
||||
// Spy on logger methods for retry logging tests
|
||||
spyOn(SyncLog, 'warn');
|
||||
spyOn(SyncLog, 'err');
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
|
|
@ -215,7 +216,7 @@ describe('SuperSyncRestoreService', () => {
|
|||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
expect(mockProvider.getStateAtSeq).toHaveBeenCalledTimes(1);
|
||||
expect(console.warn).toHaveBeenCalledWith(
|
||||
expect(SyncLog.warn).toHaveBeenCalledWith(
|
||||
jasmine.stringContaining('Restore failed due to network error, retrying (1/2)'),
|
||||
error,
|
||||
);
|
||||
|
|
@ -229,7 +230,7 @@ describe('SuperSyncRestoreService', () => {
|
|||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
expect(mockProvider.getStateAtSeq).toHaveBeenCalledTimes(2);
|
||||
expect(console.warn).toHaveBeenCalledWith(
|
||||
expect(SyncLog.warn).toHaveBeenCalledWith(
|
||||
jasmine.stringContaining('Restore failed due to network error, retrying (2/2)'),
|
||||
error,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { AppDataComplete } from '../../op-log/model/model-config';
|
|||
import { T } from '../../t.const';
|
||||
import { SyncProviderManager } from '../../op-log/sync-providers/provider-manager.service';
|
||||
import { BackupService } from '../../op-log/backup/backup.service';
|
||||
import { SyncLog } from '../../core/log';
|
||||
|
||||
/**
|
||||
* Service for restoring state from Super Sync server history.
|
||||
|
|
@ -81,7 +82,7 @@ export class SuperSyncRestoreService {
|
|||
|
||||
if (isNetworkError && retryCount < MAX_RETRIES) {
|
||||
retryCount++;
|
||||
console.warn(
|
||||
SyncLog.warn(
|
||||
`Restore failed due to network error, retrying (${retryCount}/${MAX_RETRIES}):`,
|
||||
error,
|
||||
);
|
||||
|
|
@ -90,7 +91,7 @@ export class SuperSyncRestoreService {
|
|||
continue;
|
||||
}
|
||||
|
||||
console.error('Failed to restore from point:', error);
|
||||
SyncLog.err('Failed to restore from point:', error);
|
||||
// The server can't replay end-to-end-encrypted ops to reconstruct an
|
||||
// earlier state, so it rejects the restore with a reason mentioning
|
||||
// encryption (surfaced in the thrown error message). Show an actionable
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import { TagService } from '../../features/tag/tag.service';
|
|||
import { sortTagLabels } from './plugin-tag-utils';
|
||||
import { getDbDateStr } from '../../util/get-db-date-str';
|
||||
import { T } from '../../t.const';
|
||||
import { PluginLog } from '../../core/log';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PluginIssueProviderAdapterService implements IssueServiceInterface {
|
||||
|
|
@ -55,7 +56,7 @@ export class PluginIssueProviderAdapterService implements IssueServiceInterface
|
|||
try {
|
||||
return await provider.definition.testConnection(pluginCfg.pluginConfig, http);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
PluginLog.err(
|
||||
`[PluginIssueAdapter] testConnection failed for ${pluginCfg.issueProviderKey}:`,
|
||||
e,
|
||||
);
|
||||
|
|
@ -78,7 +79,7 @@ export class PluginIssueProviderAdapterService implements IssueServiceInterface
|
|||
return link;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(
|
||||
PluginLog.err(
|
||||
`[PluginIssueAdapter] getIssueLink failed for ${cfg.issueProviderKey}:`,
|
||||
e,
|
||||
);
|
||||
|
|
@ -90,7 +91,7 @@ export class PluginIssueProviderAdapterService implements IssueServiceInterface
|
|||
const issue = (await this.getById(issueId, issueProviderId)) as PluginIssue | null;
|
||||
return issue?.url ?? '';
|
||||
} catch (e) {
|
||||
console.error(
|
||||
PluginLog.err(
|
||||
`[PluginIssueAdapter] getIssueLink url fallback failed for ${cfg.issueProviderKey}:`,
|
||||
e,
|
||||
);
|
||||
|
|
@ -114,7 +115,7 @@ export class PluginIssueProviderAdapterService implements IssueServiceInterface
|
|||
resolved.http,
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
PluginLog.err(
|
||||
`[PluginIssueAdapter] getById failed for ${cfg.issueProviderKey}:`,
|
||||
e,
|
||||
);
|
||||
|
|
@ -172,7 +173,7 @@ export class PluginIssueProviderAdapterService implements IssueServiceInterface
|
|||
issueData: r,
|
||||
})) as SearchResultItem[];
|
||||
} catch (e) {
|
||||
console.error(
|
||||
PluginLog.err(
|
||||
`[PluginIssueAdapter] searchIssues failed for ${cfg.issueProviderKey}:`,
|
||||
e,
|
||||
);
|
||||
|
|
@ -258,7 +259,7 @@ export class PluginIssueProviderAdapterService implements IssueServiceInterface
|
|||
this._handleRemoteDeletion(task);
|
||||
return null;
|
||||
}
|
||||
console.error(
|
||||
PluginLog.err(
|
||||
`[PluginIssueAdapter] getFreshDataForIssueTask failed for ${cfg.issueProviderKey}:`,
|
||||
e,
|
||||
);
|
||||
|
|
@ -299,7 +300,7 @@ export class PluginIssueProviderAdapterService implements IssueServiceInterface
|
|||
const existingIds = new Set(allExistingIssueIds.map(String));
|
||||
return results.filter((r) => !existingIds.has(r.id));
|
||||
} catch (e) {
|
||||
console.error(
|
||||
PluginLog.err(
|
||||
`[PluginIssueAdapter] getNewIssuesToAddToBacklog failed for ${cfg.issueProviderKey}:`,
|
||||
e,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
PluginCommentsConfig,
|
||||
PluginFieldMapping,
|
||||
} from './plugin-issue-provider.model';
|
||||
import { PluginLog } from '../../core/log';
|
||||
|
||||
const createMockDefinition = (
|
||||
overrides: Partial<IssueProviderPluginDefinition> = {},
|
||||
|
|
@ -73,7 +74,7 @@ describe('PluginIssueProviderRegistryService', () => {
|
|||
|
||||
it('should warn and reject duplicate registrations', () => {
|
||||
const definition = createMockDefinition();
|
||||
spyOn(console, 'warn');
|
||||
spyOn(PluginLog, 'warn');
|
||||
|
||||
registerProvider(service, 'dup', definition, 'First', 'First', 'icon1', 1000, {
|
||||
singular: 'A',
|
||||
|
|
@ -84,7 +85,7 @@ describe('PluginIssueProviderRegistryService', () => {
|
|||
plural: 'Bs',
|
||||
});
|
||||
|
||||
expect(console.warn).toHaveBeenCalledWith(
|
||||
expect(PluginLog.warn).toHaveBeenCalledWith(
|
||||
jasmine.stringContaining('Duplicate registration'),
|
||||
);
|
||||
// The first registration should be preserved
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
PluginFieldMapping,
|
||||
} from './plugin-issue-provider.model';
|
||||
import { IssueProviderKey } from '../../features/issue/issue.model';
|
||||
import { PluginLog } from '../../core/log';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PluginIssueProviderRegistryService {
|
||||
|
|
@ -33,7 +34,7 @@ export class PluginIssueProviderRegistryService {
|
|||
}): void {
|
||||
const key = opts.issueProviderKey ?? `plugin:${opts.pluginId}`;
|
||||
if (this._providers.has(key)) {
|
||||
console.warn(
|
||||
PluginLog.warn(
|
||||
`[PluginIssueProviderRegistry] Duplicate registration for '${key}', ignoring.`,
|
||||
);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -566,7 +566,7 @@ export class PluginBridgeService implements OnDestroy {
|
|||
* Internal method to show plugin index.html as view
|
||||
*/
|
||||
private _showIndexHtmlAsView(pluginId: string): void {
|
||||
console.log('PluginBridge: Navigating to plugin index view', {
|
||||
PluginLog.log('PluginBridge: Navigating to plugin index view', {
|
||||
pluginId,
|
||||
});
|
||||
// Navigate to the plugin index route
|
||||
|
|
@ -888,12 +888,12 @@ export class PluginBridgeService implements OnDestroy {
|
|||
// Use the TaskService remove method which handles deletion properly
|
||||
this._taskService.remove(taskWithSubTasks);
|
||||
|
||||
console.log('PluginBridge: Task deleted successfully', {
|
||||
PluginLog.log('PluginBridge: Task deleted successfully', {
|
||||
taskId,
|
||||
hadSubTasks: taskWithSubTasks.subTasks.length > 0,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('PluginBridge: Failed to delete task:', error);
|
||||
PluginLog.err('PluginBridge: Failed to delete task:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
@ -1169,7 +1169,7 @@ export class PluginBridgeService implements OnDestroy {
|
|||
// below as a normal Error.
|
||||
const entityId = composeId(pluginId, key);
|
||||
this._pluginUserPersistenceService.persistPluginUserData(entityId, dataStr);
|
||||
console.log('PluginBridge: Plugin data persisted successfully', {
|
||||
PluginLog.log('PluginBridge: Plugin data persisted successfully', {
|
||||
pluginId,
|
||||
keyLen: key?.length ?? 0,
|
||||
dataSize: new Blob([dataStr]).size,
|
||||
|
|
@ -1225,7 +1225,7 @@ export class PluginBridgeService implements OnDestroy {
|
|||
*/
|
||||
private async _triggerSync(pluginId: string): Promise<void> {
|
||||
try {
|
||||
console.log('PluginBridge: Triggering sync for plugin', pluginId);
|
||||
PluginLog.log('PluginBridge: Triggering sync for plugin', pluginId);
|
||||
await this._syncWrapperService.sync();
|
||||
PluginLog.log('PluginBridge: Sync completed successfully');
|
||||
} catch (error) {
|
||||
|
|
@ -1273,7 +1273,7 @@ export class PluginBridgeService implements OnDestroy {
|
|||
this._syncAdapterRegistry.unregister(registeredKey);
|
||||
}
|
||||
|
||||
console.log('PluginBridge: All hooks unregistered for plugin', { pluginId });
|
||||
PluginLog.log('PluginBridge: All hooks unregistered for plugin', { pluginId });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1293,7 +1293,7 @@ export class PluginBridgeService implements OnDestroy {
|
|||
const currentButtons = this._headerButtons();
|
||||
this._headerButtons.set([...currentButtons, newButton]);
|
||||
|
||||
console.log('PluginBridge: Header button registered', {
|
||||
PluginLog.log('PluginBridge: Header button registered', {
|
||||
pluginId,
|
||||
headerBtnCfg,
|
||||
});
|
||||
|
|
@ -1861,7 +1861,7 @@ export class PluginBridgeService implements OnDestroy {
|
|||
try {
|
||||
handler(isFocused);
|
||||
} catch (error) {
|
||||
console.error('Error in window focus handler:', error);
|
||||
PluginLog.err('Error in window focus handler:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -503,7 +503,7 @@ export class PluginService implements OnDestroy {
|
|||
}
|
||||
}, 100);
|
||||
}).catch((err) => {
|
||||
console.error('Plugin activation error:', err);
|
||||
PluginLog.err('Plugin activation error:', err);
|
||||
});
|
||||
|
||||
const updatedState = this._getPluginState(pluginId);
|
||||
|
|
@ -523,7 +523,7 @@ export class PluginService implements OnDestroy {
|
|||
currentState.manifest,
|
||||
);
|
||||
if (!hasConsent) {
|
||||
console.log(
|
||||
PluginLog.log(
|
||||
'Plugin requires Node.js execution permission but no stored consent found:',
|
||||
state.manifest.id,
|
||||
);
|
||||
|
|
@ -533,7 +533,7 @@ export class PluginService implements OnDestroy {
|
|||
}
|
||||
} else {
|
||||
// Plugin is not enabled, don't activate it
|
||||
console.log(`Plugin ${pluginId} is not enabled, skipping activation`);
|
||||
PluginLog.log(`Plugin ${pluginId} is not enabled, skipping activation`);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -1858,7 +1858,7 @@ export class PluginService implements OnDestroy {
|
|||
|
||||
// Only check platform availability in Electron environment
|
||||
if (!this._isElectronRuntime()) {
|
||||
console.warn(
|
||||
PluginLog.warn(
|
||||
`Plugin ${manifest.id} requires nodeExecution permission which is not available in web environment`,
|
||||
);
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -298,7 +298,7 @@ export class PluginIndexComponent implements OnInit, OnDestroy {
|
|||
// Cleanup blob URL if it exists
|
||||
if (this._currentIframeUrl) {
|
||||
cleanupPluginIframeUrl(this._currentIframeUrl);
|
||||
console.log(`Cleaned up blob URL for plugin: ${currentPluginId}`);
|
||||
PluginLog.log(`Cleaned up blob URL for plugin: ${currentPluginId}`);
|
||||
this._currentIframeUrl = null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { getTag, updateTags, removeTaskFromPlannerDays } from './task-shared-hel
|
|||
import { unique } from '../../../util/unique';
|
||||
import { getDeadlineAutoPlanDecision } from '../../../features/tasks/util/get-deadline-auto-plan-fields';
|
||||
import type { DeadlineAutoPlanContext } from '../../../features/tasks/util/get-deadline-auto-plan-fields';
|
||||
import { TaskLog } from '../../../core/log';
|
||||
|
||||
// =============================================================================
|
||||
// ACTION HANDLERS
|
||||
|
|
@ -107,15 +108,15 @@ const handleSetDeadline = (
|
|||
|
||||
// Input validation
|
||||
if (deadlineDay && !isDBDateStr(deadlineDay)) {
|
||||
console.error('Invalid deadlineDay format:', deadlineDay);
|
||||
TaskLog.err('Invalid deadlineDay format:', deadlineDay);
|
||||
return state;
|
||||
}
|
||||
if (deadlineWithTime !== undefined && !Number.isFinite(deadlineWithTime)) {
|
||||
console.error('Invalid deadlineWithTime:', deadlineWithTime);
|
||||
TaskLog.err('Invalid deadlineWithTime:', deadlineWithTime);
|
||||
return state;
|
||||
}
|
||||
if (deadlineRemindAt !== undefined && !Number.isFinite(deadlineRemindAt)) {
|
||||
console.error('Invalid deadlineRemindAt:', deadlineRemindAt);
|
||||
TaskLog.err('Invalid deadlineRemindAt:', deadlineRemindAt);
|
||||
return state;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { MatAutocomplete, MatAutocompleteTrigger } from '@angular/material/autoc
|
|||
import { MatOption } from '@angular/material/core';
|
||||
import { MaterialIconsLoaderService } from '../material-icons-loader.service';
|
||||
import { InputColorPickerComponent } from '../input-color-picker/input-color-picker.component';
|
||||
import { Log } from '../../core/log';
|
||||
|
||||
export interface CreateTagData {
|
||||
title?: string;
|
||||
|
|
@ -71,7 +72,7 @@ export class DialogCreateTagComponent {
|
|||
const icons = await this._iconLoader.loadIcons();
|
||||
this.filteredIcons.set(icons.slice(0, 50));
|
||||
} catch (error) {
|
||||
console.error('Failed to load material icons:', error);
|
||||
Log.err('Failed to load material icons:', error);
|
||||
this.filteredIcons.set([]);
|
||||
}
|
||||
}
|
||||
|
|
@ -86,7 +87,7 @@ export class DialogCreateTagComponent {
|
|||
filtered.length = Math.min(50, filtered.length);
|
||||
this.filteredIcons.set(filtered);
|
||||
} catch (error) {
|
||||
console.error('Failed to filter icons:', error);
|
||||
Log.err('Failed to filter icons:', error);
|
||||
this.filteredIcons.set([]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { UnsplashService } from '../../core/unsplash/unsplash.service';
|
|||
import { SnackService } from '../../core/snack/snack.service';
|
||||
import { T } from '../../t.const';
|
||||
import { IS_ELECTRON } from '../../app.constants';
|
||||
import { Log } from '../../core/log';
|
||||
|
||||
const MAX_BACKGROUND_IMAGE_FILE_SIZE_BYTES = 256 * 1024;
|
||||
|
||||
|
|
@ -106,7 +107,7 @@ export class FormlyImageInputComponent extends FieldType<FormlyFieldConfig> {
|
|||
|
||||
openUnsplashPicker(): void {
|
||||
if (!this.isUnsplashAvailable) {
|
||||
console.warn('Unsplash service is not available - no API key configured');
|
||||
Log.warn('Unsplash service is not available - no API key configured');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -368,7 +368,7 @@ export class MentionDirective implements OnChanges {
|
|||
|
||||
// Check if we have a valid active item before proceeding
|
||||
if (!this.searchList.activeItem) {
|
||||
console.warn('MentionDirective: No active item available for selection');
|
||||
Log.warn('MentionDirective: No active item available for selection');
|
||||
this.stopSearch();
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { inject, Pipe, PipeTransform } from '@angular/core';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { DateTimeFormatService } from '../../core/date-time-format/date-time-format.service';
|
||||
import { Log } from '../../core/log';
|
||||
|
||||
/**
|
||||
* Custom date pipe that respects the user's configured locale
|
||||
|
|
@ -38,7 +39,7 @@ export class LocaleDatePipe implements PipeTransform {
|
|||
try {
|
||||
return this._datePipe.transform(value, format, timezone, effectiveLocale);
|
||||
} catch (e) {
|
||||
console.warn('LocaleDatePipe: failed to format value', value, e);
|
||||
Log.warn('LocaleDatePipe: failed to format value', value, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import { TreeIndicatorService } from './tree-indicator.service';
|
|||
import { TREE_CONSTANTS } from './tree-constants';
|
||||
import { assertTreeId } from './tree-guards';
|
||||
import { expandCollapseAni } from './tree.animations';
|
||||
import { Log } from '../../core/log';
|
||||
import { dragDelayForTouch } from '../../util/input-intent';
|
||||
|
||||
@Component({
|
||||
|
|
@ -169,7 +170,7 @@ export class TreeDndComponent<TData = unknown> {
|
|||
this._rootDropEl = this._host.nativeElement.querySelector('.root-drop');
|
||||
this._indicatorService.clear();
|
||||
} catch (error) {
|
||||
console.error('Invalid drag start:', error);
|
||||
Log.err('Invalid drag start:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -411,7 +412,7 @@ export class TreeDndComponent<TData = unknown> {
|
|||
item.reset();
|
||||
} catch (error) {
|
||||
// Ignore reset errors - they can happen if the item is already destroyed
|
||||
console.debug('Failed to reset drag item:', error);
|
||||
Log.debug('Failed to reset drag item:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { playSound } from './play-sound';
|
||||
import { closeAudioContext } from './audio-context';
|
||||
import { Log } from '../core/log';
|
||||
|
||||
describe('playSound', () => {
|
||||
let mockAudioContext: any;
|
||||
|
|
@ -108,12 +109,12 @@ describe('playSound', () => {
|
|||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
const consoleErrorSpy = spyOn(console, 'error');
|
||||
const logErrSpy = spyOn(Log, 'err');
|
||||
fetchSpy.and.returnValue(Promise.reject(new Error('Test error')));
|
||||
|
||||
await playSound('nonexistent.mp3');
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalled();
|
||||
expect(logErrSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should reuse the same AudioContext for multiple sounds', async () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { getAudioBuffer, playBuffer } from './audio-context';
|
||||
import { Log } from '../core/log';
|
||||
|
||||
const BASE = './assets/snd';
|
||||
|
||||
|
|
@ -13,6 +14,6 @@ export const playSound = async (filePath: string, vol = 100): Promise<void> => {
|
|||
const buffer = await getAudioBuffer(`${BASE}/${filePath}`);
|
||||
await playBuffer(buffer, vol);
|
||||
} catch (e) {
|
||||
console.error('Error playing sound:', e);
|
||||
Log.err('Error playing sound:', e);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue