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.
- package/.github/workflows/claude-code-review.yml +0 -6
- package/.github/workflows/release-please.yml +25 -0
- package/.github/workflows/release.yml +5 -109
- package/.release-please-manifest.json +3 -0
- package/CHANGELOG.md +17 -0
- package/app/main.js +19 -8
- package/app/main.ts +27 -17
- package/app/package.json +1 -1
- package/package.json +2 -2
- package/release-please-config.json +20 -0
- package/scripts/patch-electron-plist.js +41 -0
- package/src/app/core/services/presentation/presentation.service.ts +7 -0
- package/src/app/layout/header/header.component.html +0 -10
- package/src/app/layout/header/header.component.ts +4 -23
- package/src/app/layout/navigation/navigation.component.html +61 -13
- package/src/app/layout/navigation/navigation.component.ts +30 -12
- package/src/assets/icons/favicon.256x256.png +0 -0
- package/src/assets/icons/favicon.512x512.png +0 -0
- package/src/assets/icons/favicon.icns +0 -0
- package/src/assets/icons/favicon.ico +0 -0
- package/src/assets/icons/favicon.png +0 -0
- package/src/favicon.ico +0 -0
|
@@ -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
|
-
|
|
5
|
-
|
|
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: ${{
|
|
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 "${
|
|
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
|
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 => {
|
|
11
|
-
|
|
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 => {
|
|
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
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fontastic",
|
|
3
|
-
"version": "1.
|
|
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
|
|
1
|
+
import { Component, inject } from '@angular/core';
|
|
2
2
|
import { Router } from '@angular/router';
|
|
3
|
-
import {
|
|
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: [
|
|
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.
|
|
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 {
|
|
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
|
-
|
|
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.
|
|
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.
|
|
287
|
+
this.isCreating = true;
|
|
288
|
+
this.creatingTitle = '';
|
|
277
289
|
}
|
|
278
290
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
286
|
-
this.
|
|
302
|
+
cancelCreating() {
|
|
303
|
+
this.isCreating = false;
|
|
304
|
+
this.creatingTitle = '';
|
|
287
305
|
this.pendingParentId = null;
|
|
288
306
|
}
|
|
289
307
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/favicon.ico
CHANGED
|
Binary file
|