Skip to content

Commit 16c0b6c

Browse files
authored
Merge pull request #167 from Gnathonic/develop
Release v1.4.0
2 parents 64f4a81 + f247fcc commit 16c0b6c

146 files changed

Lines changed: 10536 additions & 2190 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/pr-target-guard.yml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,23 @@ name: PR Target Guard
22

33
on:
44
pull_request_target:
5-
types: [opened]
5+
types: [opened, reopened, synchronize]
66

77
jobs:
8-
check-target:
8+
# Block any PR to main that doesn't come from develop
9+
require-develop-to-main:
10+
runs-on: ubuntu-latest
11+
if: github.event.pull_request.base.ref == 'main' && github.event.pull_request.head.ref != 'develop'
12+
steps:
13+
- name: Block non-develop PRs to main
14+
run: |
15+
gh pr comment ${{ github.event.number }} --repo ${{ github.repository }} --body "PRs to \`main\` must come from \`develop\`. Please target \`develop\` instead, or merge your changes into \`develop\` first."
16+
exit 1
17+
env:
18+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
19+
20+
# Redirect external contributors to develop
21+
redirect-forks:
922
runs-on: ubuntu-latest
1023
if: github.event.pull_request.base.ref == 'main' && github.event.pull_request.head.repo.fork
1124
steps:

CHANGELOG.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,47 @@
11
# Changelog
22

3+
## [1.4.0] - 2026-02-20
4+
5+
### Added
6+
7+
- **Bulk cover cropping** - Crop a cover and apply the same crop region to the next volume in the series, making it easy to set consistent covers across an entire series
8+
- **Cloud thumbnail previews** - Cloud-only volumes now show cover thumbnails in the catalog before downloading
9+
- **Faster catalog scrolling** - Thumbnail rendering rewritten for smoother performance, especially on mobile
10+
- **AnkiConnect field action badges** - Visual indicators showing Append/Unchanged/Replace behavior for each field
11+
12+
### Changed
13+
14+
- **AnkiConnect field modal layout** - Tiered accordion design for clearer field organization
15+
- **Cloud sync now preserves cover images** - Uploads and downloads keep your custom covers and metadata intact
16+
17+
### Fixed
18+
19+
- **Duplicate cloud folders** - Automatically detects and merges duplicate series folders
20+
- **Google Drive series deletion** - No longer fails intermittently
21+
- **Mobile import page reloads** - Fixed unexpected full-page reloads during file imports on mobile
22+
- **Image-only archive imports** - Now prompts for confirmation instead of silently failing
23+
- **Routing race conditions** - Fixed premature redirect to catalog when the library loads slowly
24+
- **Loading vs not-found states** - Proper distinction between "loading" and "does not exist" for series/volumes
25+
- **Zoom notification removed** - No longer interferes with Yomitan dictionary popups
26+
- **Image dragging in cropper** - Disabled unwanted drag behavior during crop selection
27+
- **AnkiConnect field modal fixes** - Truncated long previews, proper crop bounds, scroll wheel support, improved styling
28+
- **Reader spread navigation** - Fixed forward/backward paging with two-page spreads
29+
30+
## [1.3.0] - 2026-01-06
31+
32+
### Added
33+
34+
- **Dynamic AnkiConnect field mapping** - Configure which template populates each Anki field with a visual UI
35+
- **Template variables for fields** - Use `{selection}`, `{sentence}`, `{image}`, `{series}`, `{volume}` in any field
36+
- **Mixed field templates** - Combine text and images in the same field (e.g., `{sentence} {image}`)
37+
- **Android mode detection** - Auto-detects AnkiConnect Android limitations with manual override option
38+
- **Connection-gated AnkiConnect settings** - Settings only appear after connecting, with live model/deck/field data from Anki
39+
40+
### Changed
41+
42+
- **AnkiConnect settings redesign** - Complete overhaul with connection status, enable toggle, and organized sections
43+
- **Card mode selection** - Now uses radio buttons instead of dropdown for clearer selection
44+
345
## [1.2.3] - 2026-01-15
446

547
### Added

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mokuro-reader",
3-
"version": "1.2.3",
3+
"version": "1.4.0",
44
"private": true,
55
"scripts": {
66
"dev": "vite dev",

src/lib/anki-connect/cropper.ts

Lines changed: 101 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,40 @@ export function expandTextBoxBounds(
2626
];
2727
}
2828

29-
type CropperModal = {
29+
/**
30+
* Modal modes for the unified Anki field modal:
31+
* - configure: Edit raw template strings for field mapping (no cropper)
32+
* - create: Create new card with resolved templates (shows cropper)
33+
* - update: Update existing card with {existing} support (shows cropper)
34+
*/
35+
export type AnkiModalMode = 'configure' | 'create' | 'update';
36+
37+
type AnkiFieldModal = {
3038
open: boolean;
39+
mode: AnkiModalMode;
3140
image?: string;
3241
selectedText?: string; // The selected/highlighted text (for Front field)
3342
sentence?: string; // The full sentence/all lines (for Sentence field)
3443
tags?: string;
3544
metadata?: VolumeMetadata;
45+
pageNumber?: number;
46+
pageFilename?: string; // The filename of the current page image
3647
pages?: Page[]; // Available pages with textboxes for visual selection
3748
textBox?: [number, number, number, number]; // [xmin, ymin, xmax, ymax] for initial crop
49+
previousValues?: Record<string, string>; // field name -> previous value
50+
previousTags?: string[]; // existing tags on the card being updated
51+
previousCardId?: number;
52+
modelName?: string; // For update mode: the model name from the card being updated
3853
};
3954

40-
export const cropperStore = writable<CropperModal | undefined>(undefined);
55+
// Backwards compatibility alias
56+
type CropperModal = AnkiFieldModal;
4157

58+
export const cropperStore = writable<AnkiFieldModal | undefined>(undefined);
59+
60+
/**
61+
* Legacy function for backwards compatibility - opens modal in create mode
62+
*/
4263
export function showCropper(
4364
image: string,
4465
selectedText?: string,
@@ -47,19 +68,95 @@ export function showCropper(
4768
metadata?: VolumeMetadata,
4869
pages?: Page[],
4970
textBox?: [number, number, number, number]
71+
) {
72+
openCreateModal(image, selectedText, sentence, tags, metadata, pages, textBox);
73+
}
74+
75+
/**
76+
* Opens the modal in configure mode (no cropper, raw templates)
77+
* @param modelName - Optional model name to configure. If not provided, uses the selected model from settings.
78+
*/
79+
export function openConfigureModal(modelName?: string) {
80+
cropperStore.set({
81+
open: true,
82+
mode: 'configure',
83+
modelName
84+
});
85+
}
86+
87+
/**
88+
* Opens the modal in create mode (cropper + resolved templates)
89+
*/
90+
export function openCreateModal(
91+
image: string,
92+
selectedText?: string,
93+
sentence?: string,
94+
tags?: string,
95+
metadata?: VolumeMetadata,
96+
pages?: Page[],
97+
textBox?: [number, number, number, number],
98+
pageNumber?: number,
99+
pageFilename?: string
50100
) {
51101
cropperStore.set({
52102
open: true,
103+
mode: 'create',
53104
image,
54105
selectedText,
55106
sentence,
56107
tags,
57108
metadata,
58109
pages,
59-
textBox
110+
textBox,
111+
pageNumber,
112+
pageFilename
60113
});
61114
}
62115

116+
/**
117+
* Opens the modal in update mode (cropper + {existing} support)
118+
*/
119+
export function openUpdateModal(
120+
image: string,
121+
previousValues: Record<string, string>,
122+
previousCardId: number,
123+
modelName: string,
124+
previousTags?: string[],
125+
selectedText?: string,
126+
sentence?: string,
127+
tags?: string,
128+
metadata?: VolumeMetadata,
129+
pages?: Page[],
130+
textBox?: [number, number, number, number],
131+
pageNumber?: number,
132+
pageFilename?: string
133+
) {
134+
cropperStore.set({
135+
open: true,
136+
mode: 'update',
137+
image,
138+
selectedText,
139+
sentence,
140+
tags,
141+
metadata,
142+
pages,
143+
textBox,
144+
pageNumber,
145+
pageFilename,
146+
previousValues,
147+
previousTags,
148+
previousCardId,
149+
modelName
150+
});
151+
}
152+
153+
/**
154+
* Closes the modal
155+
*/
156+
export function closeAnkiModal() {
157+
cropperStore.set({ open: false, mode: 'create' });
158+
}
159+
63160
async function createImage(url: string) {
64161
return new Promise<HTMLImageElement>((resolve, reject) => {
65162
const image = new Image();
@@ -126,7 +223,7 @@ export async function getCroppedImg(
126223
settings.ankiConnectSettings.heightField
127224
);
128225
const blob = await canvas.convertToBlob({
129-
type: 'image/webp',
226+
type: 'image/jpeg',
130227
quality: settings.ankiConnectSettings.qualityField
131228
});
132229

0 commit comments

Comments
 (0)