fontastic 1.1.0 → 1.2.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.
@@ -12,12 +12,6 @@ on:
12
12
 
13
13
  jobs:
14
14
  claude-review:
15
- # Optional: Filter by PR author
16
- # if: |
17
- # github.event.pull_request.user.login == 'external-contributor' ||
18
- # github.event.pull_request.user.login == 'new-developer' ||
19
- # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
20
-
21
15
  runs-on: ubuntu-latest
22
16
  permissions:
23
17
  contents: read
@@ -0,0 +1,25 @@
1
+ on:
2
+ push:
3
+ branches:
4
+ - master
5
+ workflow_dispatch:
6
+
7
+ concurrency:
8
+ group: ${{ github.workflow }}-${{ github.ref }}
9
+ cancel-in-progress: true
10
+
11
+ permissions:
12
+ contents: write
13
+ pull-requests: write
14
+
15
+ name: release-please
16
+
17
+ jobs:
18
+ release-please:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: googleapis/release-please-action@v4.4.0
22
+ with:
23
+ token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
24
+ config-file: release-please-config.json
25
+ manifest-file: .release-please-manifest.json
@@ -1,117 +1,14 @@
1
- name: Release
1
+ name: Release Build
2
2
 
3
3
  on:
4
- workflow_dispatch:
5
- inputs:
6
- bump:
7
- description: 'Version bump type'
8
- required: true
9
- type: choice
10
- options: [patch, minor, major]
4
+ release:
5
+ types: [published]
11
6
 
12
7
  permissions:
13
8
  contents: write
14
9
 
15
10
  jobs:
16
- version:
17
- runs-on: ubuntu-22.04
18
- outputs:
19
- version: ${{ steps.version.outputs.version }}
20
- steps:
21
- - name: Guard — owner only
22
- if: github.actor != github.repository_owner
23
- run: |
24
- echo "Only the repository owner can trigger releases."
25
- exit 1
26
-
27
- - name: Checkout
28
- uses: actions/checkout@v6
29
- with:
30
- fetch-depth: 0
31
-
32
- - name: Setup Node 24
33
- uses: actions/setup-node@v6
34
- with:
35
- node-version: '24'
36
-
37
- - name: Configure git
38
- run: |
39
- git config user.name "github-actions[bot]"
40
- git config user.email "github-actions[bot]@users.noreply.github.com"
41
-
42
- - name: Bump version
43
- env:
44
- BUMP: ${{ github.event.inputs.bump }}
45
- run: |
46
- npm version "$BUMP" --no-git-tag-version
47
- node -e "const fs=require('fs');const app=JSON.parse(fs.readFileSync('./app/package.json'));app.version=require('./package.json').version;fs.writeFileSync('./app/package.json',JSON.stringify(app,null,2)+'\n');"
48
-
49
- - name: Capture new version
50
- id: version
51
- run: echo "version=$(jq -r .version package.json)" >> "$GITHUB_OUTPUT"
52
-
53
- - name: Generate changelog
54
- run: |
55
- node -e "
56
- const { execSync } = require('child_process');
57
- const fs = require('fs');
58
-
59
- const lastTag = execSync('git describe --tags --abbrev=0').toString().trim();
60
- const log = execSync('git log ' + lastTag + '..HEAD --format=%s').toString().trim();
61
-
62
- const types = [
63
- ['feat', '### Features'],
64
- ['fix', '### Bug Fixes'],
65
- ['refactor', '### Refactoring'],
66
- ['revert', '### Reverts'],
67
- ['perf', '### Performance'],
68
- ];
69
- const typeMap = Object.fromEntries(types.map(([k, v]) => [k, v]));
70
-
71
- const pattern = /^(\w+)(\(.+?\))?(!)?: (.+)$/;
72
- const groups = {};
73
-
74
- for (const line of log.split('\n')) {
75
- const m = line.match(pattern);
76
- if (!m) continue;
77
- const type = m[1];
78
- const scope = m[2] ? m[2].slice(1, -1) : null;
79
- const msg = m[4];
80
- const label = typeMap[type];
81
- if (!label) continue;
82
- if (!groups[type]) groups[type] = [];
83
- groups[type].push(scope ? '- **' + scope + '**: ' + msg : '- ' + msg);
84
- }
85
-
86
- const sections = types
87
- .filter(([k]) => groups[k])
88
- .map(([k, header]) => header + '\n' + groups[k].join('\n'))
89
- .join('\n\n');
90
-
91
- const body = sections || '_No conventional commits found since last release._';
92
- fs.writeFileSync('CHANGELOG_BODY.md', body);
93
- console.log('Changelog written.');
94
- "
95
-
96
- - name: Commit, tag, and push
97
- env:
98
- VERSION: ${{ steps.version.outputs.version }}
99
- run: |
100
- git add package.json app/package.json
101
- git commit -m "[Bumped Version] ${VERSION}"
102
- git push origin master
103
- git tag "${VERSION}"
104
- git push origin "${VERSION}"
105
-
106
- - name: Create GitHub Release
107
- env:
108
- GH_TOKEN: ${{ github.token }}
109
- VERSION: ${{ steps.version.outputs.version }}
110
- run: gh release create "${VERSION}" --notes-file CHANGELOG_BODY.md --title "${VERSION}"
111
-
112
-
113
11
  build:
114
- needs: version
115
12
  strategy:
116
13
  matrix:
117
14
  include:
@@ -126,7 +23,7 @@ jobs:
126
23
  - name: Checkout tag
127
24
  uses: actions/checkout@v6
128
25
  with:
129
- ref: ${{ needs.version.outputs.version }}
26
+ ref: ${{ github.event.release.tag_name }}
130
27
 
131
28
  - name: Setup Node 24
132
29
  uses: actions/setup-node@v6
@@ -152,13 +49,12 @@ jobs:
152
49
  - name: Upload artifacts to release
153
50
  env:
154
51
  GH_TOKEN: ${{ github.token }}
155
- VERSION: ${{ needs.version.outputs.version }}
156
52
  shell: bash
157
53
  run: |
158
54
  shopt -s nullglob
159
55
  files=(release/*.dmg release/*.AppImage release/*.exe release/*.zip release/*.deb release/*.rpm)
160
56
  if [ ${#files[@]} -gt 0 ]; then
161
- gh release upload "${VERSION}" "${files[@]}" --clobber
57
+ gh release upload "${{ github.event.release.tag_name }}" "${files[@]}" --clobber
162
58
  else
163
59
  echo "No release artifacts found"
164
60
  exit 1
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.2.0"
3
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ ## [1.2.0](https://github.com/tomshaw/fontastic/compare/fontastic-v1.1.0...fontastic-v1.2.0) (2026-03-14)
4
+
5
+
6
+ ### 🎉 Features
7
+
8
+ * **app:** enhance application plist patching and update icon handling ([297c052](https://github.com/tomshaw/fontastic/commit/297c052a2561962d8d6d1dd2f28f31bf5dcb66ba))
9
+ * **app:** set application name to 'Fontastic' and add plist patching script ([af31cf6](https://github.com/tomshaw/fontastic/commit/af31cf61153b82c8a625755f5fc460b6d8205291))
10
+ * **collection:** implement root collection creation request handling ([cba01d0](https://github.com/tomshaw/fontastic/commit/cba01d098998797d914c75f6cd32f814b3703fb0))
11
+ * **navigation:** implement smart collection creation with input handling ([5cb72ae](https://github.com/tomshaw/fontastic/commit/5cb72ae9f576e0fb8dc1372ab411ea4e2d764191))
12
+ * **release:** add release-please configuration and manifest files ([5cb72ae](https://github.com/tomshaw/fontastic/commit/5cb72ae9f576e0fb8dc1372ab411ea4e2d764191))
13
+
14
+
15
+ ### 🛠️ Fixes
16
+
17
+ * **release:** update release configuration and manifest version ([759486b](https://github.com/tomshaw/fontastic/commit/759486b556982d90c5e3eaef38cfa0c214e7ac38))
package/app/main.js CHANGED
@@ -5,10 +5,19 @@ const path = require("path");
5
5
  const fs = require("fs");
6
6
  const node_machine_id_1 = require("node-machine-id");
7
7
  const Application_1 = require("./Application");
8
+ electron_1.app.name = 'Fontastic';
9
+ const iconPath = path.join(__dirname, '..', 'src', 'assets', 'icons', 'favicon.512x512.png');
10
+ electron_1.app.setAboutPanelOptions({
11
+ applicationName: 'Fontastic',
12
+ applicationVersion: electron_1.app.getVersion(),
13
+ iconPath,
14
+ });
8
15
  let win = null;
9
16
  let resolveAppReady;
10
- const appReadyPromise = new Promise(resolve => { resolveAppReady = resolve; });
11
- const args = process.argv.slice(1), serve = args.some(val => val === '--serve');
17
+ const appReadyPromise = new Promise((resolve) => {
18
+ resolveAppReady = resolve;
19
+ });
20
+ const args = process.argv.slice(1), serve = args.some((val) => val === '--serve');
12
21
  function createWindow() {
13
22
  const size = electron_1.screen.getPrimaryDisplay().workAreaSize;
14
23
  // Create the browser window.
@@ -21,15 +30,15 @@ function createWindow() {
21
30
  nodeIntegration: true,
22
31
  allowRunningInsecureContent: serve,
23
32
  contextIsolation: false,
24
- webSecurity: !serve
33
+ webSecurity: !serve,
25
34
  },
26
35
  });
27
36
  (0, node_machine_id_1.machineId)(true).then((machineId) => new Application_1.default(machineId, !serve, win).initialize().then(() => resolveAppReady()));
28
37
  if (serve) {
29
- Promise.resolve().then(() => require('electron-debug')).then(debug => {
38
+ Promise.resolve().then(() => require('electron-debug')).then((debug) => {
30
39
  debug.default({ isEnabled: true, showDevTools: true });
31
40
  });
32
- Promise.resolve().then(() => require('electron-reloader')).then(reloader => {
41
+ Promise.resolve().then(() => require('electron-reloader')).then((reloader) => {
33
42
  const reloaderFn = reloader.default || reloader;
34
43
  // watchRenderer: false — Angular dev server handles HMR for the renderer.
35
44
  // Without this, reloader triggers spurious reloads on macOS (issue #840).
@@ -58,10 +67,12 @@ function createWindow() {
58
67
  return win;
59
68
  }
60
69
  try {
61
- electron_1.protocol.registerSchemesAsPrivileged([{
70
+ electron_1.protocol.registerSchemesAsPrivileged([
71
+ {
62
72
  scheme: 'font',
63
- privileges: { bypassCSP: true, supportFetchAPI: true }
64
- }]);
73
+ privileges: { bypassCSP: true, supportFetchAPI: true },
74
+ },
75
+ ]);
65
76
  electron_1.ipcMain.handle('app:get-version', () => electron_1.app.getVersion());
66
77
  electron_1.ipcMain.handle('app:ready', () => appReadyPromise);
67
78
  // This method will be called when Electron has finished
package/app/main.ts CHANGED
@@ -1,18 +1,29 @@
1
- import {app, BrowserWindow, ipcMain, net, protocol, screen} from 'electron';
1
+ import { app, BrowserWindow, ipcMain, nativeImage, net, protocol, screen } from 'electron';
2
2
  import * as path from 'path';
3
3
  import * as fs from 'fs';
4
4
  import { machineId } from 'node-machine-id';
5
5
  import Application from './Application';
6
6
 
7
+ app.name = 'Fontastic';
8
+
9
+ const iconPath = path.join(__dirname, '..', 'src', 'assets', 'icons', 'favicon.512x512.png');
10
+
11
+ app.setAboutPanelOptions({
12
+ applicationName: 'Fontastic',
13
+ applicationVersion: app.getVersion(),
14
+ iconPath,
15
+ });
16
+
7
17
  let win: BrowserWindow | null = null;
8
18
  let resolveAppReady: () => void;
9
- const appReadyPromise = new Promise<void>(resolve => { resolveAppReady = resolve; });
19
+ const appReadyPromise = new Promise<void>((resolve) => {
20
+ resolveAppReady = resolve;
21
+ });
10
22
 
11
23
  const args = process.argv.slice(1),
12
- serve = args.some(val => val === '--serve');
24
+ serve = args.some((val) => val === '--serve');
13
25
 
14
26
  function createWindow(): BrowserWindow {
15
-
16
27
  const size = screen.getPrimaryDisplay().workAreaSize;
17
28
 
18
29
  // Create the browser window.
@@ -25,20 +36,18 @@ function createWindow(): BrowserWindow {
25
36
  nodeIntegration: true,
26
37
  allowRunningInsecureContent: serve,
27
38
  contextIsolation: false,
28
- webSecurity: !serve
39
+ webSecurity: !serve,
29
40
  },
30
41
  });
31
42
 
32
- machineId(true).then((machineId: string) =>
33
- new Application(machineId, !serve, win!).initialize().then(() => resolveAppReady())
34
- );
43
+ machineId(true).then((machineId: string) => new Application(machineId, !serve, win!).initialize().then(() => resolveAppReady()));
35
44
 
36
45
  if (serve) {
37
- import('electron-debug').then(debug => {
38
- debug.default({isEnabled: true, showDevTools: true});
46
+ import('electron-debug').then((debug) => {
47
+ debug.default({ isEnabled: true, showDevTools: true });
39
48
  });
40
49
 
41
- import('electron-reloader').then(reloader => {
50
+ import('electron-reloader').then((reloader) => {
42
51
  const reloaderFn = (reloader as any).default || reloader;
43
52
  // watchRenderer: false — Angular dev server handles HMR for the renderer.
44
53
  // Without this, reloader triggers spurious reloads on macOS (issue #840).
@@ -50,7 +59,7 @@ function createWindow(): BrowserWindow {
50
59
  let pathIndex = './browser/index.html';
51
60
 
52
61
  if (fs.existsSync(path.join(__dirname, '../dist/browser/index.html'))) {
53
- // Path when running electron in local folder
62
+ // Path when running electron in local folder
54
63
  pathIndex = '../dist/browser/index.html';
55
64
  }
56
65
 
@@ -71,10 +80,12 @@ function createWindow(): BrowserWindow {
71
80
  }
72
81
 
73
82
  try {
74
- protocol.registerSchemesAsPrivileged([{
75
- scheme: 'font',
76
- privileges: { bypassCSP: true, supportFetchAPI: true }
77
- }]);
83
+ protocol.registerSchemesAsPrivileged([
84
+ {
85
+ scheme: 'font',
86
+ privileges: { bypassCSP: true, supportFetchAPI: true },
87
+ },
88
+ ]);
78
89
 
79
90
  ipcMain.handle('app:get-version', () => app.getVersion());
80
91
  ipcMain.handle('app:ready', () => appReadyPromise);
@@ -107,7 +118,6 @@ try {
107
118
  createWindow();
108
119
  }
109
120
  });
110
-
111
121
  } catch (e) {
112
122
  // Catch Error
113
123
  // throw e;
package/app/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "Tom Shaw",
6
6
  "email": ""
7
7
  },
8
- "version": "1.1.0",
8
+ "version": "1.2.0",
9
9
  "main": "main.js",
10
10
  "private": true,
11
11
  "dependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fontastic",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Fontastic is an Electron-based font management and cataloging application built for organizing, browsing, and inspecting font libraries.",
5
5
  "homepage": "https://github.com/tomshaw/fontastic",
6
6
  "private": false,
@@ -24,7 +24,7 @@
24
24
  ],
25
25
  "main": "app/main.js",
26
26
  "scripts": {
27
- "postinstall": "electron-builder install-app-deps",
27
+ "postinstall": "electron-builder install-app-deps && node scripts/patch-electron-plist.js",
28
28
  "ng": "ng",
29
29
  "start": "npm-run-all -p electron:serve ng:serve",
30
30
  "ng:serve": "ng serve -c dev -o",
@@ -0,0 +1,20 @@
1
+ {
2
+ "last-release-sha": "c7f3f9afba466a150f90e374f8a401daf1a56d11",
3
+ "packages": {
4
+ ".": {
5
+ "release-type": "node",
6
+ "extra-files": ["app/package.json"],
7
+ "changelog-sections": [
8
+ { "type": "feat", "section": "🎉 Features", "hidden": false },
9
+ { "type": "fix", "section": "🛠️ Fixes", "hidden": false },
10
+ { "type": "docs", "section": "📄 Documentation", "hidden": false },
11
+ { "type": "perf", "section": "⚡ Performance", "hidden": false },
12
+ { "type": "refactor", "section": "🏗️ Refactor", "hidden": false },
13
+ { "type": "chore", "section": "♻️ Chores", "hidden": true },
14
+ { "type": "test", "section": "♻️ Chores", "hidden": true },
15
+ { "type": "build", "section": "⚙️ Automation", "hidden": true },
16
+ { "type": "ci", "section": "⚙️ Automation", "hidden": true }
17
+ ]
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,41 @@
1
+ const { execSync } = require('child_process');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+
5
+ if (process.platform !== 'darwin') {
6
+ process.exit(0);
7
+ }
8
+
9
+ const electronAppDir = path.join(
10
+ __dirname,
11
+ '..',
12
+ 'node_modules',
13
+ 'electron',
14
+ 'dist',
15
+ 'Electron.app'
16
+ );
17
+
18
+ const plist = path.join(electronAppDir, 'Contents', 'Info.plist');
19
+
20
+ if (!fs.existsSync(plist)) {
21
+ process.exit(0);
22
+ }
23
+
24
+ const pkg = require(path.join(__dirname, '..', 'package.json'));
25
+ const appName = 'Fontastic';
26
+ const appVersion = pkg.version;
27
+
28
+ execSync(`/usr/libexec/PlistBuddy -c "Set :CFBundleName ${appName}" "${plist}"`);
29
+ execSync(`/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName ${appName}" "${plist}"`);
30
+ execSync(`/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString ${appVersion}" "${plist}"`);
31
+
32
+ // Copy app icon into the Electron.app bundle
33
+ const srcIcon = path.join(__dirname, '..', 'src', 'assets', 'icons', 'favicon.icns');
34
+ const destIcon = path.join(electronAppDir, 'Contents', 'Resources', 'electron.icns');
35
+
36
+ if (fs.existsSync(srcIcon)) {
37
+ fs.copyFileSync(srcIcon, destIcon);
38
+ console.log('Copied app icon into Electron.app bundle');
39
+ }
40
+
41
+ console.log(`Patched Electron.app plist: name="${appName}", version="${appVersion}"`);
@@ -133,6 +133,13 @@ export class PresentationService {
133
133
 
134
134
  readonly selectedGlyph = signal<number | null>(null);
135
135
 
136
+ readonly createRootCollectionRequest = signal(0);
137
+
138
+ requestCreateRootCollection() {
139
+ this.navigationEnabled.set(true);
140
+ this.createRootCollectionRequest.update((v) => v + 1);
141
+ }
142
+
136
143
  readonly navigationExpandedIds = signal<number[]>([]);
137
144
 
138
145
  readonly gridEnabled = signal(true);
@@ -106,13 +106,3 @@
106
106
  </div>
107
107
  </div>
108
108
  </header>
109
-
110
- @if (showCollectionDialog) {
111
- <app-prompt-dialog
112
- title="New Collection"
113
- placeholder="Collection name"
114
- confirmText="Create"
115
- (confirmed)="onCollectionConfirmed($event)"
116
- (cancelled)="onCollectionCancelled()"
117
- />
118
- }
@@ -1,42 +1,23 @@
1
- import { Component, inject, effect } from '@angular/core';
1
+ import { Component, inject } from '@angular/core';
2
2
  import { Router } from '@angular/router';
3
- import { PromptDialogComponent } from '../../shared/components';
4
- import { DatabaseService, PresentationService } from '../../core/services';
3
+ import { PresentationService } from '../../core/services';
5
4
 
6
5
  @Component({
7
6
  selector: 'app-header',
8
7
  standalone: true,
9
- imports: [PromptDialogComponent],
8
+ imports: [],
10
9
  templateUrl: './header.component.html',
11
10
  })
12
11
  export class HeaderComponent {
13
- readonly db = inject(DatabaseService);
14
12
  private router = inject(Router);
15
13
  readonly presentation = inject(PresentationService);
16
14
 
17
- constructor() {
18
- effect(() => {
19
- console.log('Selected collection ID:', this.db.collectionId());
20
- });
21
- }
22
-
23
15
  currentUser: { name: string } | null = null;
24
16
  gravatarUrl = '';
25
17
 
26
- showCollectionDialog = false;
27
-
28
18
  handleCreateCollection(event: Event) {
29
19
  event.stopPropagation();
30
- this.showCollectionDialog = true;
31
- }
32
-
33
- onCollectionConfirmed(name: string) {
34
- this.db.collectionCreate({ title: name });
35
- this.showCollectionDialog = false;
36
- }
37
-
38
- onCollectionCancelled() {
39
- this.showCollectionDialog = false;
20
+ this.presentation.requestCreateRootCollection();
40
21
  }
41
22
 
42
23
  handleToggleSearch(_event: Event) {
@@ -16,7 +16,7 @@
16
16
  'wght' 300;
17
17
  "
18
18
  title="New Smart Collection"
19
- (click)="openCreateSmartCollection()"
19
+ (click)="$event.stopPropagation(); openCreateSmartCollection()"
20
20
  >add</span
21
21
  >
22
22
  <ul class="flex flex-col py-0.5">
@@ -64,10 +64,39 @@
64
64
  'wght' 300;
65
65
  "
66
66
  title="New Collection"
67
- (click)="openCreateRootCollection()"
67
+ (click)="$event.stopPropagation(); openCreateRootCollection()"
68
68
  >add</span
69
69
  >
70
70
  <ul class="flex-1 overflow-auto flex flex-col py-0.5">
71
+ @if (isCreating && pendingParentId === 0) {
72
+ <li>
73
+ <a class="flex items-center pr-3 py-1 text-xs font-normal" style="padding-left: 14px">
74
+ <span
75
+ class="material-symbols-outlined icon-sm mr-1"
76
+ [style.color]="'var(--text-muted)'"
77
+ style="
78
+ font-variation-settings:
79
+ 'opsz' 20,
80
+ 'wght' 300;
81
+ "
82
+ >chevron_right</span
83
+ >
84
+ <input
85
+ class="border rounded px-1 w-full outline-none text-xs"
86
+ [style.background-color]="'var(--input-bg)'"
87
+ [style.border-color]="'var(--input-border)'"
88
+ [style.color]="'var(--text-primary)'"
89
+ [(ngModel)]="creatingTitle"
90
+ (blur)="saveCreating()"
91
+ (keydown.enter)="saveCreating()"
92
+ (keydown.escape)="cancelCreating()"
93
+ (click)="$event.stopPropagation()"
94
+ placeholder="Collection name"
95
+ appAutofocus
96
+ />
97
+ </a>
98
+ </li>
99
+ }
71
100
  <ng-container *ngTemplateOutlet="subtree; context: { $implicit: tree(), level: 0 }" />
72
101
  <li
73
102
  class="flex-1 min-h-6"
@@ -142,8 +171,37 @@
142
171
  {{ node.collection.title }}
143
172
  }
144
173
  </a>
145
- @if (node.children.length && isExpanded(node.collection.id)) {
174
+ @if ((node.children.length && isExpanded(node.collection.id)) || (isCreating && pendingParentId === node.collection.id)) {
146
175
  <ul>
176
+ @if (isCreating && pendingParentId === node.collection.id) {
177
+ <li>
178
+ <a class="flex items-center pr-3 py-1 text-xs font-normal" [style.padding-left.px]="14 + (level + 1) * 14">
179
+ <span
180
+ class="material-symbols-outlined icon-sm mr-1"
181
+ [style.color]="'var(--text-muted)'"
182
+ style="
183
+ font-variation-settings:
184
+ 'opsz' 20,
185
+ 'wght' 300;
186
+ "
187
+ >chevron_right</span
188
+ >
189
+ <input
190
+ class="border rounded px-1 w-full outline-none text-xs"
191
+ [style.background-color]="'var(--input-bg)'"
192
+ [style.border-color]="'var(--input-border)'"
193
+ [style.color]="'var(--text-primary)'"
194
+ [(ngModel)]="creatingTitle"
195
+ (blur)="saveCreating()"
196
+ (keydown.enter)="saveCreating()"
197
+ (keydown.escape)="cancelCreating()"
198
+ (click)="$event.stopPropagation()"
199
+ placeholder="Collection name"
200
+ appAutofocus
201
+ />
202
+ </a>
203
+ </li>
204
+ }
147
205
  <ng-container *ngTemplateOutlet="subtree; context: { $implicit: node.children, level: level + 1 }" />
148
206
  </ul>
149
207
  }
@@ -161,16 +219,6 @@
161
219
  />
162
220
  }
163
221
 
164
- @if (showCollectionDialog) {
165
- <app-prompt-dialog
166
- title="New Collection"
167
- placeholder="Collection name"
168
- confirmText="Create"
169
- (confirmed)="onCollectionConfirmed($event)"
170
- (cancelled)="onCollectionCancelled()"
171
- />
172
- }
173
-
174
222
  @if (smartContextMenu) {
175
223
  <app-context-menu
176
224
  [x]="smartContextMenu.x"
@@ -1,10 +1,10 @@
1
- import { Component, inject, computed, OnInit, OnDestroy } from '@angular/core';
1
+ import { Component, inject, computed, effect, OnInit, OnDestroy } from '@angular/core';
2
2
  import { CommonModule } from '@angular/common';
3
3
  import { FormsModule } from '@angular/forms';
4
4
  import { DatabaseService, MessageService, PresentationService } from '../../core/services';
5
5
  import { CollapsiblePanelComponent } from '../../shared/components/collapsible-panel/collapsible-panel.component';
6
6
  import { ContextMenuComponent, ContextMenuItem } from '../../shared/components/context-menu/context-menu.component';
7
- import { PromptDialogComponent, RuleBuilderComponent } from '../../shared/components';
7
+ import { RuleBuilderComponent } from '../../shared/components';
8
8
  import { AutofocusDirective } from '../../shared/directives/autofocus/autofocus.directive';
9
9
  import { LibraryComponent } from './library/library.component';
10
10
  import { NewsStatsComponent } from './stats/stats.component';
@@ -26,7 +26,6 @@ export interface TreeNode {
26
26
  FormsModule,
27
27
  CollapsiblePanelComponent,
28
28
  ContextMenuComponent,
29
- PromptDialogComponent,
30
29
  RuleBuilderComponent,
31
30
  AutofocusDirective,
32
31
  LibraryComponent,
@@ -39,6 +38,15 @@ export class NavigationComponent implements OnInit, OnDestroy {
39
38
  private message = inject(MessageService);
40
39
  private presentation = inject(PresentationService);
41
40
 
41
+ constructor() {
42
+ effect(() => {
43
+ const req = this.presentation.createRootCollectionRequest();
44
+ if (req > 0) {
45
+ this.openCreateRootCollection();
46
+ }
47
+ });
48
+ }
49
+
42
50
  private menuToggleListener = (_event: any, panel: string) => {
43
51
  if (panel === 'expand-collections') {
44
52
  this.expandAll();
@@ -72,7 +80,8 @@ export class NavigationComponent implements OnInit, OnDestroy {
72
80
  editingTitle = '';
73
81
  allExpanded = false;
74
82
 
75
- showCollectionDialog = false;
83
+ isCreating = false;
84
+ creatingTitle = '';
76
85
  pendingParentId: number | null = null;
77
86
 
78
87
  // Smart Collection state
@@ -256,7 +265,9 @@ export class NavigationComponent implements OnInit, OnDestroy {
256
265
  switch (action) {
257
266
  case 'add-collection':
258
267
  this.pendingParentId = collection.id;
259
- this.showCollectionDialog = true;
268
+ this.isCreating = true;
269
+ this.creatingTitle = '';
270
+ this.presentation.expandNavigationId(collection.id);
260
271
  break;
261
272
  case 'add-fonts':
262
273
  this.handleAddFonts(collection);
@@ -273,17 +284,24 @@ export class NavigationComponent implements OnInit, OnDestroy {
273
284
 
274
285
  openCreateRootCollection() {
275
286
  this.pendingParentId = 0;
276
- this.showCollectionDialog = true;
287
+ this.isCreating = true;
288
+ this.creatingTitle = '';
277
289
  }
278
290
 
279
- onCollectionConfirmed(name: string) {
280
- this.db.collectionCreate({ title: name, parentId: this.pendingParentId });
281
- this.showCollectionDialog = false;
282
- this.pendingParentId = null;
291
+ saveCreating() {
292
+ const title = this.creatingTitle.trim();
293
+ if (title) {
294
+ this.db.collectionCreate({ title, parentId: this.pendingParentId });
295
+ if (this.pendingParentId) {
296
+ this.presentation.expandNavigationId(this.pendingParentId);
297
+ }
298
+ }
299
+ this.cancelCreating();
283
300
  }
284
301
 
285
- onCollectionCancelled() {
286
- this.showCollectionDialog = false;
302
+ cancelCreating() {
303
+ this.isCreating = false;
304
+ this.creatingTitle = '';
287
305
  this.pendingParentId = null;
288
306
  }
289
307
 
Binary file
Binary file
Binary file
package/src/favicon.ico CHANGED
Binary file