fontastic 1.3.2 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/.github/workflows/claude-code-review.yml +1 -1
  2. package/.github/workflows/claude.yml +1 -1
  3. package/.github/workflows/macos.yml +5 -5
  4. package/.github/workflows/release-please.yml +1 -1
  5. package/.github/workflows/release.yml +2 -2
  6. package/.github/workflows/ubuntu.yml +7 -7
  7. package/.github/workflows/windows.yml +5 -5
  8. package/README.md +4 -4
  9. package/angular.json +8 -3
  10. package/app/core/FontFinder.js +17 -12
  11. package/app/core/FontFinder.ts +18 -12
  12. package/app/core/FontManager.js +13 -14
  13. package/app/core/FontManager.ts +12 -13
  14. package/app/core/MessageHandler.js +0 -1
  15. package/app/core/MessageHandler.ts +0 -4
  16. package/app/database/entity/Store.schema.js +1 -0
  17. package/app/database/entity/Store.schema.ts +1 -0
  18. package/app/database/repository/Collection.repository.js +0 -11
  19. package/app/database/repository/Collection.repository.ts +0 -22
  20. package/app/database/repository/Store.repository.js +50 -69
  21. package/app/database/repository/Store.repository.ts +47 -86
  22. package/app/enums/ChannelType.js +0 -1
  23. package/app/enums/ChannelType.ts +0 -1
  24. package/app/main.js +3 -2
  25. package/app/main.ts +4 -3
  26. package/app/package-lock.json +144 -1104
  27. package/app/package.json +4 -6
  28. package/app/preload.js +51 -0
  29. package/app/preload.ts +59 -0
  30. package/app/types/Bridge.js +3 -0
  31. package/app/types/Bridge.ts +19 -0
  32. package/app/types/index.js +1 -0
  33. package/app/types/index.ts +1 -0
  34. package/knip.json +18 -0
  35. package/package.json +44 -53
  36. package/src/app/app.component.spec.ts +3 -3
  37. package/src/app/app.component.ts +2 -15
  38. package/src/app/core/services/database/database.service.ts +8 -15
  39. package/src/app/core/services/electron/electron.service.ts +5 -46
  40. package/src/app/core/services/font-loader/font-loader.service.ts +60 -0
  41. package/src/app/core/services/index.ts +1 -0
  42. package/src/app/core/services/message/message.service.ts +19 -27
  43. package/src/app/core/services/presentation/presentation.service.ts +9 -2
  44. package/src/app/home/home.component.spec.ts +3 -3
  45. package/src/app/home/home.component.ts +4 -8
  46. package/src/app/layout/footer/footer.component.ts +6 -6
  47. package/src/app/shared/components/index.ts +0 -1
  48. package/src/app/shared/components/page-not-found/page-not-found.component.ts +2 -8
  49. package/src/app/shared/components/preview/preview.component.html +1 -0
  50. package/src/app/shared/components/preview/preview.component.ts +3 -31
  51. package/src/app/shared/components/rule-builder/rule-builder.component.html +4 -4
  52. package/src/app/shared/components/rule-builder/rule-builder.component.ts +13 -13
  53. package/src/app/shared/components/waterfall/waterfall.component.ts +1 -1
  54. package/src/app/shared/directives/index.ts +1 -1
  55. package/src/app/shared/directives/lazy-font/lazy-font.directive.ts +23 -0
  56. package/src/app/shared/shared.module.ts +3 -3
  57. package/src/main.ts +2 -2
  58. package/tsconfig.serve.json +4 -16
  59. package/app/helpers/command.js +0 -28
  60. package/app/helpers/command.ts +0 -20
  61. package/app/helpers/random.js +0 -16
  62. package/app/helpers/random.ts +0 -12
  63. package/src/app/shared/components/prompt-dialog/prompt-dialog.component.html +0 -36
  64. package/src/app/shared/components/prompt-dialog/prompt-dialog.component.ts +0 -40
  65. package/src/app/shared/directives/webview/webview.directive.spec.ts +0 -8
  66. package/src/app/shared/directives/webview/webview.directive.ts +0 -9
  67. package/src/styles/themes/dashboard.scss +0 -293
  68. package/src/styles/themes/euphoria.scss +0 -284
  69. package/src/styles/themes/mellow.scss +0 -281
  70. package/src/styles/themes/midnight.scss +0 -284
  71. package/src/styles/themes/passion.scss +0 -281
  72. package/src/styles/themes/swiss.scss +0 -284
@@ -317,7 +317,12 @@ export class PresentationService {
317
317
  // --- Scan Progress (MessageChannelMain) ---
318
318
 
319
319
  private initScanProgress() {
320
- this.electronService.ipcRenderer.on(ChannelType.IPC_SCAN_PROGRESS_PORT, (event: any) => {
320
+ // The preload script forwards the MessagePort from the main process into
321
+ // the isolated world via window.postMessage (ports cannot cross the
322
+ // context bridge directly).
323
+ window.addEventListener('message', (event: MessageEvent) => {
324
+ if (event.source !== window || event.data?.type !== ChannelType.IPC_SCAN_PROGRESS_PORT) return;
325
+
321
326
  const port = event.ports[0];
322
327
  if (!port) return;
323
328
 
@@ -325,7 +330,9 @@ export class PresentationService {
325
330
  this.scanProgress.set(msgEvent.data);
326
331
  };
327
332
 
328
- port.onclose = () => {
333
+ // `close` fires when the main process closes its end of the channel.
334
+ // (Not yet in the TS DOM lib, but supported since Chromium 126.)
335
+ (port as MessagePort & { onclose: (() => void) | null }).onclose = () => {
329
336
  this.scanProgress.set(null);
330
337
  };
331
338
 
@@ -1,7 +1,7 @@
1
1
  import { ComponentFixture, TestBed } from '@angular/core/testing';
2
2
 
3
3
  import { HomeComponent } from './home.component';
4
- import { TranslateModule } from '@ngx-translate/core';
4
+ import { provideTranslateService } from '@ngx-translate/core';
5
5
  import { provideRouter } from '@angular/router';
6
6
 
7
7
  describe('HomeComponent', () => {
@@ -11,8 +11,8 @@ describe('HomeComponent', () => {
11
11
  beforeEach(async () => {
12
12
  await TestBed.configureTestingModule({
13
13
  declarations: [],
14
- imports: [HomeComponent, TranslateModule.forRoot()],
15
- providers: [provideRouter([])],
14
+ imports: [HomeComponent],
15
+ providers: [provideRouter([]), provideTranslateService()],
16
16
  }).compileComponents();
17
17
 
18
18
  fixture = TestBed.createComponent(HomeComponent);
@@ -1,15 +1,11 @@
1
- import { Component, OnInit } from '@angular/core';
1
+ import { Component } from '@angular/core';
2
2
  import { RouterLink } from '@angular/router';
3
- import { TranslateModule } from '@ngx-translate/core';
3
+ import { TranslatePipe } from '@ngx-translate/core';
4
4
 
5
5
  @Component({
6
6
  selector: 'app-home',
7
7
  templateUrl: './home.component.html',
8
8
  standalone: true,
9
- imports: [RouterLink, TranslateModule],
9
+ imports: [RouterLink, TranslatePipe],
10
10
  })
11
- export class HomeComponent implements OnInit {
12
- ngOnInit(): void {
13
- console.log('HomeComponent INIT');
14
- }
15
- }
11
+ export class HomeComponent {}
@@ -18,12 +18,12 @@ export class FooterComponent {
18
18
  readonly nodeVersion = signal('');
19
19
 
20
20
  constructor() {
21
- if (this.electron.isElectron) {
22
- const versions = (window as any).process.versions;
23
- this.electronVersion.set(versions.electron ?? '');
24
- this.chromeVersion.set(versions.chrome ?? '');
25
- this.nodeVersion.set(versions.node ?? '');
26
- this.electron.ipcRenderer.invoke('app:get-version').then((v: string) => this.appVersion.set(v));
21
+ const bridge = this.electron.bridge;
22
+ if (bridge) {
23
+ this.electronVersion.set(bridge.versions.electron);
24
+ this.chromeVersion.set(bridge.versions.chrome);
25
+ this.nodeVersion.set(bridge.versions.node);
26
+ bridge.invoke<string>('app:get-version').then((v) => this.appVersion.set(v));
27
27
  }
28
28
  }
29
29
  }
@@ -3,7 +3,6 @@ export * from './context-menu/context-menu.component';
3
3
  export * from './page-not-found/page-not-found.component';
4
4
  export * from './panel/panel.component';
5
5
  export * from './datagrid/datagrid.component';
6
- export * from './prompt-dialog/prompt-dialog.component';
7
6
  export * from './rule-builder/rule-builder.component';
8
7
  export * from './toolbar/toolbar.component';
9
8
  export * from './glyphs/glyphs.component';
@@ -1,14 +1,8 @@
1
- import { Component, OnInit } from '@angular/core';
1
+ import { Component } from '@angular/core';
2
2
 
3
3
  @Component({
4
4
  selector: 'app-page-not-found',
5
5
  templateUrl: './page-not-found.component.html',
6
6
  standalone: true,
7
7
  })
8
- export class PageNotFoundComponent implements OnInit {
9
- constructor() {}
10
-
11
- ngOnInit(): void {
12
- console.log('PageNotFoundComponent INIT');
13
- }
14
- }
8
+ export class PageNotFoundComponent {}
@@ -6,6 +6,7 @@
6
6
  class="pb-3 cursor-pointer transition-colors rounded-sm"
7
7
  [style.border-color]="'var(--border-subtle)'"
8
8
  [id]="'preview-store-' + store.id"
9
+ [appLazyFont]="store"
9
10
  [style.background-color]="db.storeId() === store.id ? 'var(--selected-bg)' : 'transparent'"
10
11
  (click)="selectStore(store.id)"
11
12
  >
@@ -1,11 +1,12 @@
1
- import { Component, inject, effect } from '@angular/core';
1
+ import { Component, inject } from '@angular/core';
2
2
  import { DatabaseService, MessageService, PresentationService } from '../../../core/services';
3
3
  import { ScrollResetDirective } from '../../directives/scroll-reset/scroll-reset.directive';
4
+ import { LazyFontDirective } from '../../directives/lazy-font/lazy-font.directive';
4
5
 
5
6
  @Component({
6
7
  selector: 'app-preview',
7
8
  standalone: true,
8
- imports: [ScrollResetDirective],
9
+ imports: [ScrollResetDirective, LazyFontDirective],
9
10
  templateUrl: './preview.component.html',
10
11
  })
11
12
  export class PreviewComponent {
@@ -13,17 +14,6 @@ export class PreviewComponent {
13
14
  readonly presentation = inject(PresentationService);
14
15
  private messageService = inject(MessageService);
15
16
 
16
- private registeredFonts = new Set<string>();
17
-
18
- constructor() {
19
- effect(() => {
20
- const stores = this.db.stores();
21
- if (stores) {
22
- stores.forEach((store) => this.registerFont(store));
23
- }
24
- });
25
- }
26
-
27
17
  selectStore(id: number) {
28
18
  this.db.storeId.set(id);
29
19
  document.getElementById('grid-store-' + id)?.scrollIntoView({ behavior: 'smooth', block: 'center' });
@@ -42,22 +32,4 @@ export class PreviewComponent {
42
32
  this.messageService.openPath(store.file_path);
43
33
  }
44
34
  }
45
-
46
- private registerFont(store: any) {
47
- const key = `${store.id}-${store.file_path}`;
48
- if (this.registeredFonts.has(key)) return;
49
-
50
- const url = `font://${store.file_path}`;
51
- const fontFace = new FontFace(store.full_name || store.font_family, `url('${url}')`);
52
-
53
- fontFace
54
- .load()
55
- .then((loaded) => {
56
- (document as any).fonts.add(loaded);
57
- this.registeredFonts.add(key);
58
- })
59
- .catch((err) => {
60
- console.warn(`Failed to load font: ${store.file_name}`, err);
61
- });
62
- }
63
35
  }
@@ -84,12 +84,12 @@
84
84
  Add Rule
85
85
  </button>
86
86
  <div class="flex items-center gap-2">
87
- @if (previewCount !== null) {
88
- <span class="text-xs opacity-70">{{ previewCount }} {{ previewCount === 1 ? 'font' : 'fonts' }} matched</span>
87
+ @if (previewCount() !== null) {
88
+ <span class="text-xs opacity-70">{{ previewCount() }} {{ previewCount() === 1 ? 'font' : 'fonts' }} matched</span>
89
89
  }
90
- <button class="btn btn-sm btn-default" (click)="preview()" [disabled]="previewing">
90
+ <button class="btn btn-sm btn-default" (click)="preview()" [disabled]="previewing()">
91
91
  <span class="material-symbols-outlined" style="font-size: 16px; vertical-align: middle">search</span>
92
- {{ previewing ? 'Checking...' : 'Preview' }}
92
+ {{ previewing() ? 'Checking...' : 'Preview' }}
93
93
  </button>
94
94
  </div>
95
95
  </div>
@@ -1,4 +1,4 @@
1
- import { Component, Input, Output, EventEmitter, OnInit, inject } from '@angular/core';
1
+ import { Component, Input, Output, EventEmitter, OnInit, inject, signal } from '@angular/core';
2
2
  import { FormsModule } from '@angular/forms';
3
3
  import { ModalBackdropDirective } from '../../directives/modal-backdrop/modal-backdrop.directive';
4
4
  import { DatabaseService } from '../../../core/services/database/database.service';
@@ -81,8 +81,8 @@ export class RuleBuilderComponent implements OnInit {
81
81
  title = '';
82
82
  matchType: string = 'AND';
83
83
  rules: SmartCollectionRule[] = [];
84
- previewCount: number | null = null;
85
- previewing = false;
84
+ readonly previewCount = signal<number | null>(null);
85
+ readonly previewing = signal(false);
86
86
 
87
87
  readonly fieldOptions = FIELD_OPTIONS;
88
88
 
@@ -121,32 +121,32 @@ export class RuleBuilderComponent implements OnInit {
121
121
  } else {
122
122
  rule.value = '';
123
123
  }
124
- this.previewCount = null;
124
+ this.previewCount.set(null);
125
125
  }
126
126
 
127
127
  addRule(): void {
128
128
  this.rules.push({ field: 'font_family', operator: 'contains', value: '' });
129
- this.previewCount = null;
129
+ this.previewCount.set(null);
130
130
  }
131
131
 
132
132
  removeRule(index: number): void {
133
133
  this.rules.splice(index, 1);
134
- this.previewCount = null;
134
+ this.previewCount.set(null);
135
135
  }
136
136
 
137
137
  preview(): void {
138
- if (this.rules.length === 0 || this.previewing) return;
139
- this.previewing = true;
140
- this.previewCount = null;
138
+ if (this.rules.length === 0 || this.previewing()) return;
139
+ this.previewing.set(true);
140
+ this.previewCount.set(null);
141
141
  this.db
142
142
  .smartCollectionPreview(this.rules, this.matchType)
143
143
  .then((count) => {
144
- this.previewCount = count;
145
- this.previewing = false;
144
+ this.previewCount.set(count);
145
+ this.previewing.set(false);
146
146
  })
147
147
  .catch(() => {
148
- this.previewCount = null;
149
- this.previewing = false;
148
+ this.previewCount.set(null);
149
+ this.previewing.set(false);
150
150
  });
151
151
  }
152
152
 
@@ -72,7 +72,7 @@ export class WaterfallComponent {
72
72
 
73
73
  private buildLowScale(ratio: number, baseMultiplier: number, length: number): ScaleItem[] {
74
74
  let baseSize = 1;
75
- let result = 1;
75
+ let result: number;
76
76
  const items: ScaleItem[] = [];
77
77
 
78
78
  for (let i = 0; i < length; i++) {
@@ -1,7 +1,7 @@
1
1
  export * from './autofocus/autofocus.directive';
2
2
  export * from './disabled-opacity/disabled-opacity.directive';
3
3
  export * from './hover-highlight/hover-highlight.directive';
4
+ export * from './lazy-font/lazy-font.directive';
4
5
  export * from './modal-backdrop/modal-backdrop.directive';
5
6
  export * from './scroll-reset/scroll-reset.directive';
6
7
  export * from './stop-propagation/stop-propagation.directive';
7
- export * from './webview/webview.directive';
@@ -0,0 +1,23 @@
1
+ import { Directive, ElementRef, OnDestroy, OnInit, inject, input } from '@angular/core';
2
+ import { FontLoaderService } from '../../../core/services/font-loader/font-loader.service';
3
+ import type { Store } from '@main/database/entity/Store.schema';
4
+
5
+ /** Loads the given store's font file when the host element nears the viewport. */
6
+ @Directive({
7
+ selector: '[appLazyFont]',
8
+ standalone: true,
9
+ })
10
+ export class LazyFontDirective implements OnInit, OnDestroy {
11
+ private el = inject<ElementRef<HTMLElement>>(ElementRef);
12
+ private fontLoader = inject(FontLoaderService);
13
+
14
+ readonly appLazyFont = input.required<Store>();
15
+
16
+ ngOnInit() {
17
+ this.fontLoader.observe(this.el.nativeElement, this.appLazyFont());
18
+ }
19
+
20
+ ngOnDestroy() {
21
+ this.fontLoader.unobserve(this.el.nativeElement);
22
+ }
23
+ }
@@ -1,13 +1,13 @@
1
1
  import { NgModule } from '@angular/core';
2
2
  import { CommonModule } from '@angular/common';
3
3
 
4
- import { TranslateModule } from '@ngx-translate/core';
4
+ import { TranslatePipe } from '@ngx-translate/core';
5
5
 
6
6
  import { FormsModule } from '@angular/forms';
7
7
 
8
8
  @NgModule({
9
9
  declarations: [],
10
- imports: [CommonModule, TranslateModule, FormsModule],
11
- exports: [TranslateModule, FormsModule],
10
+ imports: [CommonModule, TranslatePipe, FormsModule],
11
+ exports: [TranslatePipe, FormsModule],
12
12
  })
13
13
  export class SharedModule {}
package/src/main.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { enableProdMode, provideZoneChangeDetection } from '@angular/core';
1
+ import { enableProdMode, provideZonelessChangeDetection } from '@angular/core';
2
2
  import { bootstrapApplication } from '@angular/platform-browser';
3
3
  import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
4
4
  import { importProvidersFrom } from '@angular/core';
@@ -24,7 +24,7 @@ if (APP_CONFIG.production) {
24
24
 
25
25
  bootstrapApplication(AppComponent, {
26
26
  providers: [
27
- provideZoneChangeDetection(),
27
+ provideZonelessChangeDetection(),
28
28
  provideHttpClient(withInterceptorsFromDi()),
29
29
  provideTranslateService({
30
30
  loader: provideTranslateHttpLoader({
@@ -7,21 +7,9 @@
7
7
  "experimentalDecorators": true,
8
8
  "module": "commonjs",
9
9
  "target": "es2015",
10
- "types": [
11
- "node"
12
- ],
13
- "lib": [
14
- "es2017",
15
- "es2016",
16
- "es2015",
17
- "dom"
18
- ]
10
+ "types": ["node"],
11
+ "lib": ["es2017", "es2016", "es2015", "dom"]
19
12
  },
20
- "files": [
21
- "app/main.ts"
22
- ],
23
- "exclude": [
24
- "node_modules",
25
- "**/*.spec.ts"
26
- ]
13
+ "files": ["app/main.ts", "app/preload.ts"],
14
+ "exclude": ["node_modules", "**/*.spec.ts"]
27
15
  }
@@ -1,28 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.prompt = exports.execute = void 0;
4
- const child_process_1 = require("child_process");
5
- const sudo = require('sudo-prompt');
6
- const execute = (cmd, options = {}) => {
7
- return new Promise((resolve, reject) => {
8
- (0, child_process_1.exec)(cmd, options, (err, stdout, stderr) => {
9
- if (err) {
10
- return reject(err);
11
- }
12
- return resolve({ stdout, stderr });
13
- });
14
- });
15
- };
16
- exports.execute = execute;
17
- const prompt = (cmd, options = {}) => {
18
- return new Promise((resolve, reject) => {
19
- sudo.exec(cmd, options, (err, stdout, stderr) => {
20
- if (err) {
21
- return reject(err);
22
- }
23
- return resolve({ stdout, stderr });
24
- });
25
- });
26
- };
27
- exports.prompt = prompt;
28
- //# sourceMappingURL=command.js.map
@@ -1,20 +0,0 @@
1
- import { exec } from 'child_process';
2
- const sudo = require('sudo-prompt');
3
-
4
- export const execute = (cmd: string, options = {}) => {
5
- return new Promise((resolve, reject) => {
6
- exec(cmd, options, (err: any, stdout: any, stderr: any) => {
7
- if (err) { return reject(err); }
8
- return resolve({ stdout, stderr });
9
- });
10
- });
11
- }
12
-
13
- export const prompt = (cmd: string, options = {}) => {
14
- return new Promise((resolve, reject) => {
15
- sudo.exec(cmd, options, (err: any, stdout: any, stderr: any) => {
16
- if (err) { return reject(err); }
17
- return resolve({ stdout, stderr });
18
- });
19
- });
20
- }
@@ -1,16 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.randNumber = void 0;
4
- exports.randString = randString;
5
- function randString(len = 7) {
6
- const list = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789";
7
- let res = "";
8
- for (let i = 0; i < len; i++) {
9
- let rnd = Math.floor(Math.random() * list.length);
10
- res = res + list.charAt(rnd);
11
- }
12
- return res;
13
- }
14
- const randNumber = (n) => [...Array(n)].map(_ => Math.random() * 10 | 0).join('');
15
- exports.randNumber = randNumber;
16
- //# sourceMappingURL=random.js.map
@@ -1,12 +0,0 @@
1
-
2
- export function randString(len: number = 7) {
3
- const list = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789";
4
- let res = "";
5
- for (let i = 0; i < len; i++) {
6
- let rnd = Math.floor(Math.random() * list.length);
7
- res = res + list.charAt(rnd);
8
- }
9
- return res;
10
- }
11
-
12
- export const randNumber = (n: number) => [...Array(n)].map(_ => Math.random() * 10 | 0).join('');
@@ -1,36 +0,0 @@
1
- <div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm" appModalBackdrop (backdropClick)="cancel()">
2
- <div class="card mb-0 w-[480px]" style="box-shadow: var(--context-shadow)">
3
- <div class="card-header">
4
- <div class="card-title">
5
- <h3>{{ title }}</h3>
6
- </div>
7
- <div class="card-tools"></div>
8
- </div>
9
- <div class="card-body">
10
- <div class="mb-4">
11
- <label class="form-label" for="promptInput">Name</label>
12
- <input
13
- #promptInput
14
- type="text"
15
- class="form-input w-full"
16
- id="promptInput"
17
- [placeholder]="placeholder"
18
- [(ngModel)]="value"
19
- (keyup.enter)="confirm()"
20
- (keyup.escape)="cancel()"
21
- />
22
- </div>
23
- </div>
24
- <div class="card-footer">
25
- <div></div>
26
- <div class="card-tools">
27
- <button class="btn btn-sm btn-default" (click)="cancel()">
28
- {{ cancelText }}
29
- </button>
30
- <button class="btn btn-sm btn-theme" (click)="confirm()">
31
- {{ confirmText }}
32
- </button>
33
- </div>
34
- </div>
35
- </div>
36
- </div>
@@ -1,40 +0,0 @@
1
- import { Component, Input, Output, EventEmitter, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
2
- import { FormsModule } from '@angular/forms';
3
- import { ModalBackdropDirective } from '../../directives/modal-backdrop/modal-backdrop.directive';
4
-
5
- @Component({
6
- selector: 'app-prompt-dialog',
7
- standalone: true,
8
- imports: [FormsModule, ModalBackdropDirective],
9
- templateUrl: './prompt-dialog.component.html',
10
- })
11
- export class PromptDialogComponent implements AfterViewInit {
12
- @Input() title = 'Prompt';
13
- @Input() placeholder = '';
14
- @Input() confirmText = 'Confirm';
15
- @Input() cancelText = 'Cancel';
16
-
17
- @Output() confirmed = new EventEmitter<string>();
18
- @Output() cancelled = new EventEmitter<void>();
19
-
20
- @ViewChild('promptInput') promptInput!: ElementRef<HTMLInputElement>;
21
-
22
- value = '';
23
-
24
- ngAfterViewInit(): void {
25
- this.promptInput.nativeElement.focus();
26
- }
27
-
28
- confirm(): void {
29
- const trimmed = this.value.trim();
30
- if (trimmed) {
31
- this.confirmed.emit(trimmed);
32
- this.value = '';
33
- }
34
- }
35
-
36
- cancel(): void {
37
- this.value = '';
38
- this.cancelled.emit();
39
- }
40
- }
@@ -1,8 +0,0 @@
1
- import { WebviewDirective } from './webview.directive';
2
-
3
- describe('WebviewDirective', () => {
4
- it('should create an instance', () => {
5
- const directive = new WebviewDirective();
6
- expect(directive).toBeTruthy();
7
- });
8
- });
@@ -1,9 +0,0 @@
1
- import { Directive } from '@angular/core';
2
-
3
- @Directive({
4
- selector: 'webview',
5
- standalone: true,
6
- })
7
- export class WebviewDirective {
8
- constructor() {}
9
- }