Skip to content

Commit 50950c6

Browse files
authored
Added the ability to edit order or hide annotation tools (#1217)
1 parent 92c3ac7 commit 50950c6

File tree

13 files changed

+368
-73
lines changed

13 files changed

+368
-73
lines changed

Zotero.xcodeproj/project.pbxproj

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,10 @@
939939
B3A47C3A29015FE800E7D90D /* TableOfContentsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3A47C3929015FE800E7D90D /* TableOfContentsState.swift */; };
940940
B3A53FF72B14CDB2004BB9D7 /* ReadEmojiTagsDbRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3A53FF62B14CDB2004BB9D7 /* ReadEmojiTagsDbRequest.swift */; };
941941
B3A6C59F252CA08300F24CBE /* PSPDFKit in Frameworks */ = {isa = PBXBuildFile; productRef = B3A6C59E252CA08300F24CBE /* PSPDFKit */; };
942+
B3A71F4B2EEC39A80006A737 /* AnnotationToolsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3A71F4A2EEC39A20006A737 /* AnnotationToolsSettingsView.swift */; };
943+
B3A71F4D2EEC39BA0006A737 /* AnnotationToolsSettingsActionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3A71F4C2EEC39B50006A737 /* AnnotationToolsSettingsActionHandler.swift */; };
944+
B3A71F4F2EEC39CD0006A737 /* AnnotationToolsSettingsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3A71F4E2EEC39CA0006A737 /* AnnotationToolsSettingsState.swift */; };
945+
B3A71F512EEC39D90006A737 /* AnnotationToolsSettingsAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3A71F502EEC39D60006A737 /* AnnotationToolsSettingsAction.swift */; };
942946
B3A94B4A2462F5D300BC7910 /* PSPDFKit+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3A94B492462F5D300BC7910 /* PSPDFKit+Extensions.swift */; };
943947
B3A95DAA29194BDE00BCCF11 /* DashedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3A95DA929194BDE00BCCF11 /* DashedView.swift */; };
944948
B3AAABD72502A40900031065 /* searchresponse_unknownfields.json in Resources */ = {isa = PBXBuildFile; fileRef = B3AAABD52502A40600031065 /* searchresponse_unknownfields.json */; };
@@ -1994,6 +1998,10 @@
19941998
B3A47C3729015FDD00E7D90D /* TableOfContentsAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableOfContentsAction.swift; sourceTree = "<group>"; };
19951999
B3A47C3929015FE800E7D90D /* TableOfContentsState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableOfContentsState.swift; sourceTree = "<group>"; };
19962000
B3A53FF62B14CDB2004BB9D7 /* ReadEmojiTagsDbRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadEmojiTagsDbRequest.swift; sourceTree = "<group>"; };
2001+
B3A71F4A2EEC39A20006A737 /* AnnotationToolsSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnotationToolsSettingsView.swift; sourceTree = "<group>"; };
2002+
B3A71F4C2EEC39B50006A737 /* AnnotationToolsSettingsActionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnotationToolsSettingsActionHandler.swift; sourceTree = "<group>"; };
2003+
B3A71F4E2EEC39CA0006A737 /* AnnotationToolsSettingsState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnotationToolsSettingsState.swift; sourceTree = "<group>"; };
2004+
B3A71F502EEC39D60006A737 /* AnnotationToolsSettingsAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnotationToolsSettingsAction.swift; sourceTree = "<group>"; };
19972005
B3A94B492462F5D300BC7910 /* PSPDFKit+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PSPDFKit+Extensions.swift"; sourceTree = "<group>"; };
19982006
B3A95DA929194BDE00BCCF11 /* DashedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashedView.swift; sourceTree = "<group>"; };
19992007
B3AAABD52502A40600031065 /* searchresponse_unknownfields.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = searchresponse_unknownfields.json; sourceTree = "<group>"; };
@@ -3651,6 +3659,7 @@
36513659
B374911F2488D7040042FF85 /* Settings */ = {
36523660
isa = PBXGroup;
36533661
children = (
3662+
B3A71F462EEC397C0006A737 /* Annotation Tools */,
36543663
B3E8FE092714297200F51458 /* Cite */,
36553664
B3E8FE4327142B0A00F51458 /* Debugging */,
36563665
B3E8FE1F271429C300F51458 /* Export */,
@@ -3715,6 +3724,41 @@
37153724
path = Controllers;
37163725
sourceTree = "<group>";
37173726
};
3727+
B3A71F462EEC397C0006A737 /* Annotation Tools */ = {
3728+
isa = PBXGroup;
3729+
children = (
3730+
B3A71F472EEC39950006A737 /* Models */,
3731+
B3A71F482EEC39950006A737 /* ViewModels */,
3732+
B3A71F492EEC39950006A737 /* Views */,
3733+
);
3734+
path = "Annotation Tools";
3735+
sourceTree = "<group>";
3736+
};
3737+
B3A71F472EEC39950006A737 /* Models */ = {
3738+
isa = PBXGroup;
3739+
children = (
3740+
B3A71F502EEC39D60006A737 /* AnnotationToolsSettingsAction.swift */,
3741+
B3A71F4E2EEC39CA0006A737 /* AnnotationToolsSettingsState.swift */,
3742+
);
3743+
path = Models;
3744+
sourceTree = "<group>";
3745+
};
3746+
B3A71F482EEC39950006A737 /* ViewModels */ = {
3747+
isa = PBXGroup;
3748+
children = (
3749+
B3A71F4C2EEC39B50006A737 /* AnnotationToolsSettingsActionHandler.swift */,
3750+
);
3751+
path = ViewModels;
3752+
sourceTree = "<group>";
3753+
};
3754+
B3A71F492EEC39950006A737 /* Views */ = {
3755+
isa = PBXGroup;
3756+
children = (
3757+
B3A71F4A2EEC39A20006A737 /* AnnotationToolsSettingsView.swift */,
3758+
);
3759+
path = Views;
3760+
sourceTree = "<group>";
3761+
};
37183762
B3AB389F238EC02D008A1ABB /* View Controllers */ = {
37193763
isa = PBXGroup;
37203764
children = (
@@ -5087,6 +5131,7 @@
50875131
B3C9D60C24DA9DEA003EA1EE /* CollectionsSearchActionHandler.swift in Sources */,
50885132
B324277825C82E6500567504 /* UnsubscribeWsMessage.swift in Sources */,
50895133
B3593F3D241A61C700760E20 /* ItemsState.swift in Sources */,
5134+
B3A71F4B2EEC39A80006A737 /* AnnotationToolsSettingsView.swift in Sources */,
50905135
B30566AE23FC051F003304F2 /* SettingsResponse.swift in Sources */,
50915136
B3422F46289BADD800C53DD2 /* ItemDetailFieldEditContentView.swift in Sources */,
50925137
B3FE4B99268DDE4900CE123F /* CitationBibliographyExportState.swift in Sources */,
@@ -5169,6 +5214,7 @@
51695214
B305661323FC051E003304F2 /* ArrayEncoding.swift in Sources */,
51705215
B305667723FC051F003304F2 /* UIFont+Extensions.swift in Sources */,
51715216
B3DF9AD52747AB4F007933CB /* ApiEndpoint.swift in Sources */,
5217+
B3A71F4F2EEC39CD0006A737 /* AnnotationToolsSettingsState.swift in Sources */,
51725218
B322B4772673A41200BC3D08 /* ReadStyleDbRequest.swift in Sources */,
51735219
B3E8FE8827143BDD00F51458 /* SyncSettingsAction.swift in Sources */,
51745220
B3F9A4B82B04C97A00684030 /* HtmlEpubSettings.swift in Sources */,
@@ -5515,6 +5561,7 @@
55155561
B3863FC82AD819AB005082F0 /* EndItemCreationDbRequest.swift in Sources */,
55165562
B3DDDAAC24CAD6810014DF99 /* InsetLabel.swift in Sources */,
55175563
B3329A552B738C4E00F17636 /* CitationAuthorCell.swift in Sources */,
5564+
B3A71F4D2EEC39BA0006A737 /* AnnotationToolsSettingsActionHandler.swift in Sources */,
55185565
B3E8FE8927143BDD00F51458 /* SyncSettingsView.swift in Sources */,
55195566
B3593F7A241A76E600760E20 /* CollectionEditState.swift in Sources */,
55205567
B3C6AB28248E1B720009AC96 /* SyncBatchProcessor.swift in Sources */,
@@ -5542,6 +5589,7 @@
55425589
B31FACD225DBC7E900DD5F14 /* ActiveObjectDeletedConflictReceiverHandler.swift in Sources */,
55435590
B3EA5A192B724E9400E283D7 /* CitationLocatorCell.swift in Sources */,
55445591
B36181FF24C9A7C000B30D56 /* LoginState.swift in Sources */,
5592+
B3A71F512EEC39D90006A737 /* AnnotationToolsSettingsAction.swift in Sources */,
55455593
B3E8FE3627142A0900F51458 /* RemoteStyle.swift in Sources */,
55465594
B3F55A1729EED04700A6716E /* ReadFilteredTagsDbRequest.swift in Sources */,
55475595
B34A9F5E25BF12F7007C9A4A /* ReadFilenameDbRequest.swift in Sources */,

Zotero/Assets/en.lproj/Localizable.strings

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,10 @@
365365
"settings.full_sync.start" = "Start Full Sync Debugging";
366366
"settings.full_sync.other_in_progress" = "Sync is already in progress. Wait for it to finish.";
367367
"settings.full_sync.in_progress" = "A full sync is in progress. Wait for it to finish and post the Debug ID to the Zotero Forums.";
368+
"settings.annotation_tools" = "Annotation Tools";
369+
"settings.annotation_tools.pdf" = "PDF";
370+
"settings.annotation_tools.html_epub" = "HTML / EPUB";
371+
"settings.annotation_tools.reset" = "Reset";
368372

369373
"shareext.save" = "Save to Zotero";
370374
"shareext.decoding_attachment" = "Searching for items";

Zotero/Models/AnnotationTool.swift

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,98 @@
66
// Copyright © 2023 Corporation for Digital Scholarship. All rights reserved.
77
//
88

9-
import Foundation
9+
import UIKit
1010

11-
enum AnnotationTool {
11+
enum AnnotationTool: Hashable, Codable {
1212
case ink
1313
case image
1414
case note
1515
case highlight
1616
case eraser
1717
case underline
1818
case freeText
19+
20+
var image: UIImage {
21+
switch self {
22+
case .highlight:
23+
return Asset.Images.Annotations.highlightLarge.image
24+
25+
case .note:
26+
return Asset.Images.Annotations.noteLarge.image
27+
28+
case .image:
29+
return Asset.Images.Annotations.areaLarge.image
30+
31+
case .ink:
32+
return Asset.Images.Annotations.inkLarge.image
33+
34+
case .eraser:
35+
return Asset.Images.Annotations.eraserLarge.image
36+
37+
case .underline:
38+
return Asset.Images.Annotations.underlineLarge.image
39+
40+
case .freeText:
41+
return Asset.Images.Annotations.textLarge.image
42+
}
43+
}
44+
45+
var name: String {
46+
switch self {
47+
case .eraser:
48+
return L10n.Pdf.AnnotationToolbar.eraser
49+
50+
case .freeText:
51+
return L10n.Pdf.AnnotationToolbar.text
52+
53+
case .highlight:
54+
return L10n.Pdf.AnnotationToolbar.highlight
55+
56+
case .image:
57+
return L10n.Pdf.AnnotationToolbar.image
58+
59+
case .ink:
60+
return L10n.Pdf.AnnotationToolbar.ink
61+
62+
case .note:
63+
return L10n.Pdf.AnnotationToolbar.note
64+
65+
case .underline:
66+
return L10n.Pdf.AnnotationToolbar.underline
67+
}
68+
}
69+
70+
var accessibilityLabel: String {
71+
switch self {
72+
case .eraser:
73+
return L10n.Accessibility.Pdf.eraserAnnotationTool
74+
75+
case .freeText:
76+
return L10n.Accessibility.Pdf.textAnnotationTool
77+
78+
case .highlight:
79+
return L10n.Accessibility.Pdf.highlightAnnotationTool
80+
81+
case .image:
82+
return L10n.Accessibility.Pdf.imageAnnotationTool
83+
84+
case .ink:
85+
return L10n.Accessibility.Pdf.inkAnnotationTool
86+
87+
case .note:
88+
return L10n.Accessibility.Pdf.noteAnnotationTool
89+
90+
case .underline:
91+
return L10n.Accessibility.Pdf.underlineAnnotationTool
92+
}
93+
}
94+
}
95+
96+
struct AnnotationToolButton: Codable, Identifiable {
97+
let type: AnnotationTool
98+
let isVisible: Bool
99+
100+
var id: AnnotationTool {
101+
return type
102+
}
19103
}

Zotero/Models/Defaults.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,36 @@ final class Defaults {
126126
@CodableUserDefault(key: "PDFReaderSettings", defaultValue: PDFSettings.default, encoder: Defaults.jsonEncoder, decoder: Defaults.jsonDecoder, defaults: .standard)
127127
var pdfSettings: PDFSettings
128128

129+
@CodableUserDefault(
130+
key: "PDFReaderAnnotationTools",
131+
defaultValue: [
132+
AnnotationToolButton(type: .highlight, isVisible: true),
133+
AnnotationToolButton(type: .underline, isVisible: true),
134+
AnnotationToolButton(type: .note, isVisible: true),
135+
AnnotationToolButton(type: .freeText, isVisible: true),
136+
AnnotationToolButton(type: .image, isVisible: true),
137+
AnnotationToolButton(type: .ink, isVisible: true),
138+
AnnotationToolButton(type: .eraser, isVisible: true)
139+
],
140+
encoder: Defaults.jsonEncoder,
141+
decoder: Defaults.jsonDecoder,
142+
defaults: .standard
143+
)
144+
var pdfAnnotationTools: [AnnotationToolButton]
145+
129146
// MARK: - HTML / Epub Settings
130147

131148
@CodableUserDefault(key: "HtmlEpubReaderSettings", defaultValue: HtmlEpubSettings.default, encoder: Defaults.jsonEncoder, decoder: Defaults.jsonDecoder, defaults: .standard)
132149
var htmlEpubSettings: HtmlEpubSettings
150+
151+
@CodableUserDefault(
152+
key: "HtmlEpubReaderAnnotationTools",
153+
defaultValue: [AnnotationToolButton(type: .highlight, isVisible: true), AnnotationToolButton(type: .underline, isVisible: true), AnnotationToolButton(type: .note, isVisible: true)],
154+
encoder: Defaults.jsonEncoder,
155+
decoder: Defaults.jsonDecoder,
156+
defaults: .standard
157+
)
158+
var htmlEpubAnnotationTools: [AnnotationToolButton]
133159
#endif
134160

135161
// MARK: - Citation / Bibliography Export

Zotero/Scenes/Detail/HTML:EPUB/Views/HtmlEpubReaderViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ class HtmlEpubReaderViewController: UIViewController, ReaderViewController, Pare
155155
documentController.parentDelegate = self
156156
documentController.view.translatesAutoresizingMaskIntoConstraints = false
157157

158-
let annotationToolbar = AnnotationToolbarViewController(tools: [.highlight, .underline, .note], undoRedoEnabled: false, size: navigationBarHeight)
158+
let annotationToolbar = AnnotationToolbarViewController(tools: Defaults.shared.htmlEpubAnnotationTools.map({ $0.type }), undoRedoEnabled: false, size: navigationBarHeight)
159159
annotationToolbar.delegate = self
160160

161161
let sidebarController = HtmlEpubSidebarViewController(viewModel: viewModel)

Zotero/Scenes/Detail/PDF/Views/AnnotationToolbarViewController.swift

Lines changed: 1 addition & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -91,77 +91,10 @@ class AnnotationToolbarViewController: UIViewController {
9191

9292
init(tools: [AnnotationTool], undoRedoEnabled: Bool, size: CGFloat) {
9393
self.size = size
94-
toolButtons = tools.map({ button(from: $0) })
94+
toolButtons = tools.map({ ToolButton(type: $0, title: $0.name, accessibilityLabel: $0.accessibilityLabel, image: $0.image, isHidden: false) })
9595
self.undoRedoEnabled = undoRedoEnabled
9696
disposeBag = DisposeBag()
9797
super.init(nibName: nil, bundle: nil)
98-
99-
func button(from tool: AnnotationTool) -> ToolButton {
100-
switch tool {
101-
case .highlight:
102-
ToolButton(
103-
type: .highlight,
104-
title: L10n.Pdf.AnnotationToolbar.highlight,
105-
accessibilityLabel: L10n.Accessibility.Pdf.highlightAnnotationTool,
106-
image: Asset.Images.Annotations.highlightLarge.image,
107-
isHidden: false
108-
)
109-
110-
case .note:
111-
ToolButton(
112-
type: .note,
113-
title: L10n.Pdf.AnnotationToolbar.note,
114-
accessibilityLabel: L10n.Accessibility.Pdf.noteAnnotationTool,
115-
image: Asset.Images.Annotations.noteLarge.image,
116-
isHidden: false
117-
)
118-
119-
case .image:
120-
ToolButton(
121-
type: .image,
122-
title: L10n.Pdf.AnnotationToolbar.image,
123-
accessibilityLabel: L10n.Accessibility.Pdf.imageAnnotationTool,
124-
image: Asset.Images.Annotations.areaLarge.image,
125-
isHidden: false
126-
)
127-
128-
case .ink:
129-
ToolButton(
130-
type: .ink,
131-
title: L10n.Pdf.AnnotationToolbar.ink,
132-
accessibilityLabel: L10n.Accessibility.Pdf.inkAnnotationTool,
133-
image: Asset.Images.Annotations.inkLarge.image,
134-
isHidden: false
135-
)
136-
137-
case .eraser:
138-
ToolButton(
139-
type: .eraser,
140-
title: L10n.Pdf.AnnotationToolbar.eraser,
141-
accessibilityLabel: L10n.Accessibility.Pdf.eraserAnnotationTool,
142-
image: Asset.Images.Annotations.eraserLarge.image,
143-
isHidden: false
144-
)
145-
146-
case .underline:
147-
ToolButton(
148-
type: .underline,
149-
title: L10n.Pdf.AnnotationToolbar.underline,
150-
accessibilityLabel: L10n.Accessibility.Pdf.underlineAnnotationTool,
151-
image: Asset.Images.Annotations.underlineLarge.image,
152-
isHidden: false
153-
)
154-
155-
case .freeText:
156-
ToolButton(
157-
type: .freeText,
158-
title: L10n.Pdf.AnnotationToolbar.text,
159-
accessibilityLabel: L10n.Accessibility.Pdf.textAnnotationTool,
160-
image: Asset.Images.Annotations.textLarge.image,
161-
isHidden: false
162-
)
163-
}
164-
}
16598
}
16699

167100
required init?(coder: NSCoder) {

Zotero/Scenes/Detail/PDF/Views/PDFReaderViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ class PDFReaderViewController: UIViewController, ReaderViewController {
232232
separator.translatesAutoresizingMaskIntoConstraints = false
233233
separator.backgroundColor = Asset.Colors.annotationSidebarBorderColor.color
234234

235-
let annotationToolbar = AnnotationToolbarViewController(tools: [.highlight, .underline, .note, .freeText, .image, .ink, .eraser], undoRedoEnabled: true, size: navigationBarHeight)
235+
let annotationToolbar = AnnotationToolbarViewController(tools: Defaults.shared.pdfAnnotationTools.map({ $0.type }), undoRedoEnabled: true, size: navigationBarHeight)
236236
annotationToolbar.delegate = self
237237

238238
let intraDocumentNavigationHandler = IntraDocumentNavigationButtonsHandler(
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//
2+
// AnnotationToolsSettingsAction.swift
3+
// Zotero
4+
//
5+
// Created by Michal Rentka on 12.12.2025.
6+
// Copyright © 2025 Corporation for Digital Scholarship. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
enum AnnotationToolsSettingsAction {
12+
case move(IndexSet, Int, AnnotationToolsSettingsState.Section)
13+
case reset(AnnotationToolsSettingsState.Section)
14+
case setVisible(Bool, AnnotationTool, AnnotationToolsSettingsState.Section)
15+
case save
16+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//
2+
// AnnotationToolsSettingsState.swift
3+
// Zotero
4+
//
5+
// Created by Michal Rentka on 12.12.2025.
6+
// Copyright © 2025 Corporation for Digital Scholarship. All rights reserved.
7+
//
8+
9+
import UIKit
10+
11+
struct AnnotationToolsSettingsState: ViewModelState {
12+
enum Section: Int {
13+
case pdf
14+
case htmlEpub
15+
}
16+
17+
var pdfTools: [AnnotationToolButton]
18+
var htmlEpubTools: [AnnotationToolButton]
19+
20+
init(pdfAnnotationTools: [AnnotationToolButton], htmlEpubAnnotationTools: [AnnotationToolButton]) {
21+
pdfTools = pdfAnnotationTools
22+
htmlEpubTools = htmlEpubAnnotationTools
23+
}
24+
25+
mutating func cleanup() {
26+
}
27+
}

0 commit comments

Comments
 (0)