From a07d5c1fb74b956a01d37b3e8cd0270e23f9b29f Mon Sep 17 00:00:00 2001
From: 1024jp <1024jp@wolfrosch.com>
Date: Sun, 21 Apr 2024 02:45:12 +0900
Subject: [PATCH 001/191] Update version number to 4.9.0-alpha (650)
---
CHANGELOG.md | 5 +++++
CotEditor.xcodeproj/project.pbxproj | 16 ++++++++--------
2 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 98995e82b..0179b9a61 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
# Change Log
+4.9.0 (unreleased)
+--------------------------
+
+
+
4.8.3 (unreleased)
--------------------------
diff --git a/CotEditor.xcodeproj/project.pbxproj b/CotEditor.xcodeproj/project.pbxproj
index 081c1a507..e925a4967 100644
--- a/CotEditor.xcodeproj/project.pbxproj
+++ b/CotEditor.xcodeproj/project.pbxproj
@@ -3734,12 +3734,12 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = "Accent Color";
- CURRENT_PROJECT_VERSION = 648;
+ CURRENT_PROJECT_VERSION = 650;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
INFOPLIST_FILE = CotEditor/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
- MARKETING_VERSION = "4.8.3-alpha";
+ MARKETING_VERSION = "4.9.0-alpha";
PRODUCT_BUNDLE_IDENTIFIER = com.coteditor.CotEditor;
PRODUCT_NAME = CotEditor;
};
@@ -3751,12 +3751,12 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = "Accent Color";
- CURRENT_PROJECT_VERSION = 648;
+ CURRENT_PROJECT_VERSION = 650;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
INFOPLIST_FILE = CotEditor/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
- MARKETING_VERSION = "4.8.3-alpha";
+ MARKETING_VERSION = "4.9.0-alpha";
PRODUCT_BUNDLE_IDENTIFIER = com.coteditor.CotEditor;
PRODUCT_NAME = CotEditor;
};
@@ -3794,12 +3794,12 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = "Accent Color";
- CURRENT_PROJECT_VERSION = 648;
+ CURRENT_PROJECT_VERSION = 650;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
INFOPLIST_FILE = CotEditor/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
- MARKETING_VERSION = "4.8.3-alpha";
+ MARKETING_VERSION = "4.9.0-alpha";
PRODUCT_BUNDLE_IDENTIFIER = com.coteditor.CotEditor;
PRODUCT_NAME = CotEditor;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SPARKLE";
@@ -3812,12 +3812,12 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = "Accent Color";
- CURRENT_PROJECT_VERSION = 648;
+ CURRENT_PROJECT_VERSION = 650;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
INFOPLIST_FILE = CotEditor/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
- MARKETING_VERSION = "4.8.3-alpha";
+ MARKETING_VERSION = "4.9.0-alpha";
PRODUCT_BUNDLE_IDENTIFIER = com.coteditor.CotEditor;
PRODUCT_NAME = CotEditor;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SPARKLE";
From c3b95d943a3e8eee0486d18f4b0d86ac330abd75 Mon Sep 17 00:00:00 2001
From: 1024jp <1024jp@wolfrosch.com>
Date: Sun, 21 Apr 2024 00:15:55 +0900
Subject: [PATCH 002/191] Update target deployment to macOS 14
---
.swiftlint.yml | 2 +-
CHANGELOG.md | 4 ++++
CotEditor.xcodeproj/project.pbxproj | 8 ++------
README.md | 2 +-
SyntaxMap/Package.swift | 2 +-
5 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/.swiftlint.yml b/.swiftlint.yml
index b9f96320e..29e050973 100644
--- a/.swiftlint.yml
+++ b/.swiftlint.yml
@@ -1,5 +1,5 @@
deployment_target:
- macOS_deployment_target: 13
+ macOS_deployment_target: 14
excluded:
- "*/.build"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0179b9a61..a03c460ff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,10 @@
4.9.0 (unreleased)
--------------------------
+### Improvements
+
+- Change the system requirement to __macOS 14 Sonoma and later__.
+
4.8.3 (unreleased)
diff --git a/CotEditor.xcodeproj/project.pbxproj b/CotEditor.xcodeproj/project.pbxproj
index e925a4967..9cd4b6051 100644
--- a/CotEditor.xcodeproj/project.pbxproj
+++ b/CotEditor.xcodeproj/project.pbxproj
@@ -3709,7 +3709,6 @@
baseConfigurationReference = 5454B92B243C8257009275BC /* UI-Tests.xcconfig */;
buildSettings = {
GENERATE_INFOPLIST_FILE = YES;
- MACOSX_DEPLOYMENT_TARGET = 13.0;
PRODUCT_BUNDLE_IDENTIFIER = com.wolfrosch.CotEditorUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_TARGET_NAME = CotEditor;
@@ -3721,7 +3720,6 @@
baseConfigurationReference = 5454B92B243C8257009275BC /* UI-Tests.xcconfig */;
buildSettings = {
GENERATE_INFOPLIST_FILE = YES;
- MACOSX_DEPLOYMENT_TARGET = 13.0;
PRODUCT_BUNDLE_IDENTIFIER = com.wolfrosch.CotEditorUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_TARGET_NAME = CotEditor;
@@ -3768,7 +3766,6 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
GENERATE_INFOPLIST_FILE = YES;
- MACOSX_DEPLOYMENT_TARGET = 13.0;
PRODUCT_BUNDLE_IDENTIFIER = com.coteditor.CotEditorTests;
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CotEditor.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CotEditor";
@@ -3781,7 +3778,6 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
GENERATE_INFOPLIST_FILE = YES;
- MACOSX_DEPLOYMENT_TARGET = 13.0;
PRODUCT_BUNDLE_IDENTIFIER = com.coteditor.CotEditorTests;
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CotEditor.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CotEditor";
@@ -3875,7 +3871,7 @@
"@executable_path/../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
- MACOSX_DEPLOYMENT_TARGET = 13.0;
+ MACOSX_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@@ -3940,7 +3936,7 @@
"@executable_path/../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
- MACOSX_DEPLOYMENT_TARGET = 13.0;
+ MACOSX_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "-enable-upcoming-feature ConciseMagicFile -enable-upcoming-feature ExistentialAny -enable-upcoming-feature ForwardTrailingClosures -enable-upcoming-feature ImplicitOpenExistentials -enable-upcoming-feature DisableOutwardActorInference -enable-upcoming-feature IsolatedDefaultValues -enable-upcoming-feature GlobalConcurrency";
diff --git a/README.md b/README.md
index 248e8e937..50c2dc1b9 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
CotEditor is a lightweight plain-text editor for macOS. The project aims to provide a general plain-text editor for everyone with an intuitive macOS-native user interface.
-- __Requirement__: macOS 13 Ventura or later
+- __Requirement__: macOS 14 Sonoma or later
- __Web Site__:
- __Mac App Store__:
- __Languages__: English, Czech, Dutch, French, German, Italian, Japanese, Portuguese, Spanish, Simplified Chinese, Traditional Chinese, Turkish
diff --git a/SyntaxMap/Package.swift b/SyntaxMap/Package.swift
index 178fa16a7..136194597 100644
--- a/SyntaxMap/Package.swift
+++ b/SyntaxMap/Package.swift
@@ -5,7 +5,7 @@ import PackageDescription
let package = Package(
name: "SyntaxMap",
platforms: [
- .macOS(.v13),
+ .macOS(.v14),
],
products: [
.library(name: "SyntaxMap", targets: ["SyntaxMap"]),
From e41a31a9c9d2f5ebc37b2680dfa4a5642bdbebe4 Mon Sep 17 00:00:00 2001
From: 1024jp <1024jp@wolfrosch.com>
Date: Mon, 22 Apr 2024 00:09:25 +0900
Subject: [PATCH 003/191] Remove workarounds for macOS 13
---
CotEditor.xcodeproj/project.pbxproj | 6 -
CotEditor/Sources/AntialiasingText.swift | 1 -
CotEditor/Sources/AppDelegate.swift | 13 +-
.../Sources/CharacterInspectorView.swift | 5 -
CotEditor/Sources/Color.swift | 33 -----
CotEditor/Sources/CommandBarView.swift | 13 +-
CotEditor/Sources/DocumentInspectorView.swift | 2 -
.../Sources/DocumentViewController.swift | 2 +-
CotEditor/Sources/DocumentWindow.swift | 22 ----
.../Sources/DocumentWindowController.swift | 34 +----
CotEditor/Sources/EditorOpacityView.swift | 33 -----
CotEditor/Sources/EditorTextView.swift | 34 +----
.../Sources/EditorTextViewController.swift | 2 +-
CotEditor/Sources/EncodingListView.swift | 1 -
CotEditor/Sources/FormPopUpButton.swift | 2 -
CotEditor/Sources/HeadingMenuItem.swift | 34 ++---
.../Sources/IncompatibleCharactersView.swift | 2 -
.../Sources/InconsistentLineEndingsView.swift | 2 -
CotEditor/Sources/NSBezierPath.swift | 44 +------
CotEditor/Sources/NSColor+NamedColors.swift | 27 +---
CotEditor/Sources/NSLayoutManager.swift | 33 -----
.../Sources/NSTextView+MultiCursor.swift | 116 +-----------------
.../Sources/NSToolbarItem+Validatable.swift | 14 +--
CotEditor/Sources/OpacitySlider.swift | 1 -
CotEditor/Sources/OutlineInspectorView.swift | 9 +-
CotEditor/Sources/Theme.swift | 6 +-
CotEditor/Sources/ThemeEditorView.swift | 15 +--
CotEditor/Sources/WarningInspectorView.swift | 1 -
.../Sources/WindowContentViewController.swift | 11 --
Tests/StringCommentingTests.swift | 7 +-
30 files changed, 32 insertions(+), 493 deletions(-)
delete mode 100644 CotEditor/Sources/Color.swift
diff --git a/CotEditor.xcodeproj/project.pbxproj b/CotEditor.xcodeproj/project.pbxproj
index 9cd4b6051..fe5c0683c 100644
--- a/CotEditor.xcodeproj/project.pbxproj
+++ b/CotEditor.xcodeproj/project.pbxproj
@@ -827,8 +827,6 @@
2AFD218A27E0434100E83E88 /* UTType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AFD218927E0434100E83E88 /* UTType.swift */; };
2AFD218B27E0434100E83E88 /* UTType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AFD218927E0434100E83E88 /* UTType.swift */; };
2AFD218D27E0442B00E83E88 /* UTTypeExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AFD218C27E0442B00E83E88 /* UTTypeExtensionTests.swift */; };
- 2AFD328929482D53000ED1C5 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AFD328829482D53000ED1C5 /* Color.swift */; };
- 2AFD328A29482D53000ED1C5 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AFD328829482D53000ED1C5 /* Color.swift */; };
2AFD328F2949B34A000ED1C5 /* RegularExpressionSyntaxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AFD328E2949B34A000ED1C5 /* RegularExpressionSyntaxTests.swift */; };
2AFE848622AE71130001C4ED /* TextContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AFE848522AE71130001C4ED /* TextContainer.swift */; };
2AFE848722AE71130001C4ED /* TextContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AFE848522AE71130001C4ED /* TextContainer.swift */; };
@@ -1332,7 +1330,6 @@
2AFB5AE71D597ABB003895A7 /* DefaultSettings+Encodings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DefaultSettings+Encodings.swift"; sourceTree = ""; };
2AFD218927E0434100E83E88 /* UTType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTType.swift; sourceTree = ""; };
2AFD218C27E0442B00E83E88 /* UTTypeExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTTypeExtensionTests.swift; sourceTree = ""; };
- 2AFD328829482D53000ED1C5 /* Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = ""; };
2AFD328E2949B34A000ED1C5 /* RegularExpressionSyntaxTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegularExpressionSyntaxTests.swift; sourceTree = ""; };
2AFE848522AE71130001C4ED /* TextContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextContainer.swift; sourceTree = ""; };
2AFECF592171C0E60065A7DE /* Bundle+AppInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+AppInfo.swift"; sourceTree = ""; };
@@ -1404,7 +1401,6 @@
2A04E9C127FEF737008C82D8 /* SwiftUI */ = {
isa = PBXGroup;
children = (
- 2AFD328829482D53000ED1C5 /* Color.swift */,
2A8321732980C41600F87D35 /* Image+Status.swift */,
2AE144B82B00DCB7005E8CF1 /* View+Alert.swift */,
2A231A271E7BD82700C2A909 /* Binding.swift */,
@@ -2898,7 +2894,6 @@
2A5C00342814698000700CAE /* Collection+BinarySearch.swift in Sources */,
2ADA15EF21C5073D00C6608B /* Collection+IndexSet.swift in Sources */,
2AE12DFC1E7DB47000681F72 /* Collection+String.swift in Sources */,
- 2AFD328929482D53000ED1C5 /* Color.swift in Sources */,
2A4257B11D22FD490086DAAD /* ColorCodePanelController.swift in Sources */,
2A1A4EB024FB9D9300B50AA0 /* Combine.swift in Sources */,
2A5E41052B0AEFBB00D5EA20 /* CommandBarView.swift in Sources */,
@@ -3264,7 +3259,6 @@
2A5C00352814698000700CAE /* Collection+BinarySearch.swift in Sources */,
2ADA15EE21C5073D00C6608B /* Collection+IndexSet.swift in Sources */,
2AE12DFB1E7DB47000681F72 /* Collection+String.swift in Sources */,
- 2AFD328A29482D53000ED1C5 /* Color.swift in Sources */,
2A4257B01D22FD490086DAAD /* ColorCodePanelController.swift in Sources */,
2A1A4EB124FB9D9300B50AA0 /* Combine.swift in Sources */,
2A5E41062B0AEFBB00D5EA20 /* CommandBarView.swift in Sources */,
diff --git a/CotEditor/Sources/AntialiasingText.swift b/CotEditor/Sources/AntialiasingText.swift
index 13c134d15..cbd887086 100644
--- a/CotEditor/Sources/AntialiasingText.swift
+++ b/CotEditor/Sources/AntialiasingText.swift
@@ -143,7 +143,6 @@ private final class CenteringTextFieldCell: NSTextFieldCell {
// MARK: - Preview
-@available(macOS 14, *)
#Preview(traits: .fixedLayout(width: 200, height: 400)) {
VStack {
AntialiasingText("Antialias Text")
diff --git a/CotEditor/Sources/AppDelegate.swift b/CotEditor/Sources/AppDelegate.swift
index aceca8ddd..0cb48563e 100644
--- a/CotEditor/Sources/AppDelegate.swift
+++ b/CotEditor/Sources/AppDelegate.swift
@@ -201,13 +201,6 @@ private enum BundleIdentifier {
// MARK: Application Delegate
- @available(macOS, deprecated: 14, message: "The secure restoration became automatically enabled on macOS 14 and later.")
- func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
-
- true
- }
-
-
#if SPARKLE
func applicationWillFinishLaunching(_ notification: Notification) {
@@ -316,11 +309,7 @@ private enum BundleIdentifier {
/// Activates self and perform New menu action (from Dock menu).
@IBAction func newDocumentActivatingApplication(_ sender: Any?) {
- if #available(macOS 14, *) {
- NSApp.activate()
- } else {
- NSApp.activate(ignoringOtherApps: true)
- }
+ NSApp.activate()
NSDocumentController.shared.newDocument(sender)
}
diff --git a/CotEditor/Sources/CharacterInspectorView.swift b/CotEditor/Sources/CharacterInspectorView.swift
index 89102197f..460c99ade 100644
--- a/CotEditor/Sources/CharacterInspectorView.swift
+++ b/CotEditor/Sources/CharacterInspectorView.swift
@@ -54,7 +54,6 @@ private struct CharacterDetailView: View {
if let description = self.info.localizedDescription {
Text(description)
.fontWeight(self.info.isComplex ? .regular : .semibold)
- .foregroundColor(.label) // Workaround to keep text color when selected (2022-12, macOS 13, FB10747746, fixed on macOS 14).
.textSelection(.enabled)
} else {
Text("Unknown", tableName: "CharacterInspector")
@@ -91,7 +90,6 @@ private struct CharacterDetailView: View {
}
}
.controlSize(.small)
- .foregroundColor(.label)
.textSelection(.enabled)
}
}.fixedSize()
@@ -141,7 +139,6 @@ private struct ScalarDetailView: View {
}
}
.monospacedDigit()
- .foregroundColor(.label)
.textSelection(.enabled)
.accessibilityLabeledPair(role: .content, id: "codePoint", in: self.accessibility)
}
@@ -156,7 +153,6 @@ private struct ScalarDetailView: View {
Group {
if let blockName = self.scalar.localizedBlockName {
Text(blockName)
- .foregroundColor(.label)
.textSelection(.enabled)
} else {
Text("No Block", tableName: "CharacterInspector")
@@ -174,7 +170,6 @@ private struct ScalarDetailView: View {
let category = self.scalar.properties.generalCategory
Text(verbatim: "\(category.longName) (\(category.shortName))")
- .foregroundColor(.label)
.textSelection(.enabled)
.accessibilityLabeledPair(role: .content, id: "category", in: self.accessibility)
}
diff --git a/CotEditor/Sources/Color.swift b/CotEditor/Sources/Color.swift
deleted file mode 100644
index 94910a6a6..000000000
--- a/CotEditor/Sources/Color.swift
+++ /dev/null
@@ -1,33 +0,0 @@
-//
-// Color.swift
-//
-// CotEditor
-// https://coteditor.com
-//
-// Created by 1024jp on 2022-12-13.
-//
-// ---------------------------------------------------------------------------
-//
-// © 2022-2024 1024jp
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-import SwiftUI
-import AppKit.NSColor
-
-extension Color {
-
- @available(macOS, deprecated: 14, message: "Use .primary instead.")
- static let label = Color(nsColor: .labelColor)
-}
diff --git a/CotEditor/Sources/CommandBarView.swift b/CotEditor/Sources/CommandBarView.swift
index 4e03f6b1c..07602c9f5 100644
--- a/CotEditor/Sources/CommandBarView.swift
+++ b/CotEditor/Sources/CommandBarView.swift
@@ -100,7 +100,7 @@ struct CommandBarView: View {
proxy.scrollTo(newValue)
}
}
- .compatibleContentMargins(.vertical, 10)
+ .contentMargins(.vertical, 10, for: .scrollContent)
.frame(maxHeight: 300)
.fixedSize(horizontal: false, vertical: true)
}
@@ -304,17 +304,6 @@ private extension ActionCommand.Kind {
private extension View {
- @available(macOS, deprecated: 14)
- func compatibleContentMargins(_ edges: Edge.Set = .all, _ length: CGFloat?) -> some View {
-
- if #available(macOS 14, *) {
- return self.contentMargins(edges, length, for: .scrollContent)
- } else {
- return self.padding(edges, length)
- }
- }
-
-
/// Performs actions when clicking the mouse on the view.
///
/// - Parameters:
diff --git a/CotEditor/Sources/DocumentInspectorView.swift b/CotEditor/Sources/DocumentInspectorView.swift
index 9cce626ab..92cc697e5 100644
--- a/CotEditor/Sources/DocumentInspectorView.swift
+++ b/CotEditor/Sources/DocumentInspectorView.swift
@@ -366,7 +366,6 @@ private extension DocumentInspectorView.Model {
// MARK: - Preview
-@available(macOS 14, *)
#Preview(traits: .fixedLayout(width: 240, height: 520)) {
let model = DocumentInspectorView.Model()
model.attributes = .init(
@@ -387,7 +386,6 @@ private extension DocumentInspectorView.Model {
return DocumentInspectorView(model: model)
}
-@available(macOS 14, *)
#Preview("Empty", traits: .fixedLayout(width: 240, height: 520)) {
DocumentInspectorView(model: .init())
}
diff --git a/CotEditor/Sources/DocumentViewController.swift b/CotEditor/Sources/DocumentViewController.swift
index 6a6068479..ca616cc53 100644
--- a/CotEditor/Sources/DocumentViewController.swift
+++ b/CotEditor/Sources/DocumentViewController.swift
@@ -742,7 +742,7 @@ final class DocumentViewController: NSSplitViewController, ThemeChanging, NSTool
let opacityView = EditorOpacityView(window: self.view.window as? DocumentWindow)
let viewController = NSHostingController(rootView: opacityView)
- if #available(macOS 14, *), let toolbarItem = sender as? NSToolbarItem {
+ if let toolbarItem = sender as? NSToolbarItem {
let popover = NSPopover()
popover.behavior = .semitransient
popover.contentViewController = viewController
diff --git a/CotEditor/Sources/DocumentWindow.swift b/CotEditor/Sources/DocumentWindow.swift
index 3cd69ed55..2b0145299 100644
--- a/CotEditor/Sources/DocumentWindow.swift
+++ b/CotEditor/Sources/DocumentWindow.swift
@@ -89,28 +89,6 @@ final class DocumentWindow: NSWindow {
}
- override func miniaturize(_ sender: Any?) {
-
- super.miniaturize(sender)
-
- // workaround an issue with Stage Manager (2023-04 macOS 13, FB12129976, fixed on macOS 14)
- if self.isFloating, ProcessInfo.processInfo.operatingSystemVersion.majorVersion < 14 {
- self.level = .normal
- }
- }
-
-
- override func makeKey() {
-
- super.makeKey()
-
- // workaround an issue with Stage Manager (2023-04 macOS 13, FB12129976, fixed on macOS 14)
- if self.isFloating, ProcessInfo.processInfo.operatingSystemVersion.majorVersion < 14 {
- self.level = .floating
- }
- }
-
-
// MARK: Actions
override func validateUserInterfaceItem(_ item: any NSValidatedUserInterfaceItem) -> Bool {
diff --git a/CotEditor/Sources/DocumentWindowController.swift b/CotEditor/Sources/DocumentWindowController.swift
index f8047a384..483d775f0 100644
--- a/CotEditor/Sources/DocumentWindowController.swift
+++ b/CotEditor/Sources/DocumentWindowController.swift
@@ -336,14 +336,6 @@ private extension NSToolbarItem.Identifier {
}
-public extension NSToolbarItem.Identifier {
-
- /// The back-deployed version of the `.inspectorTrackingSeparator` to use the same identifier to the original one for the autosaving compatibility.
- @backDeployed(before: macOS 14)
- static var inspectorTrackingSeparator: Self { Self("NSToolbarInspectorTrackingSeparatorItemIdentifier") }
-}
-
-
extension DocumentWindowController: NSToolbarDelegate {
func toolbarImmovableItemIdentifiers(_ toolbar: NSToolbar) -> Set {
@@ -426,11 +418,7 @@ extension DocumentWindowController: NSToolbarDelegate {
item.toolTip = String(localized: "Toolbar.inspector.tooltip",
defaultValue: "Show document information", table: "Document")
item.image = NSImage(systemSymbolName: "info.circle", accessibilityDescription: item.label)
- item.action = if #available(macOS 14, *) {
- #selector(NSSplitViewController.toggleInspector)
- } else {
- #selector(WindowContentViewController.toggleInspector)
- }
+ item.action = #selector(NSSplitViewController.toggleInspector)
item.visibilityPriority = .high
return item
@@ -640,21 +628,6 @@ extension DocumentWindowController: NSToolbarDelegate {
return item
case .opacity:
- guard #available(macOS 14, *) else {
- let menuItem = NSMenuItem()
- menuItem.view = OpacityHostingView(window: self.window as? DocumentWindow)
- let item = MenuToolbarItem(itemIdentifier: itemIdentifier)
- item.label = String(localized: "Toolbar.opacity.label",
- defaultValue: "Opacity", table: "Document")
- item.toolTip = String(localized: "Toolbar.opacity.tooltip",
- defaultValue: "Change editor’s opacity", table: "Document")
- item.image = NSImage(resource: .uiwindowOpacity)
- item.target = self
- item.showsIndicator = false
- item.menu = NSMenu()
- item.menu.items = [menuItem]
- return item
- }
let item = NSToolbarItem(itemIdentifier: itemIdentifier)
item.isBordered = true
item.label = String(localized: "Toolbar.opacity.label",
@@ -744,11 +717,6 @@ extension DocumentWindowController: NSToolbarDelegate {
item.delegate = self
return item
- case .inspectorTrackingSeparator where ProcessInfo.processInfo.operatingSystemVersion.majorVersion < 14:
- let splitView = (self.contentViewController as! NSSplitViewController).splitView
- let item = NSTrackingSeparatorToolbarItem(identifier: itemIdentifier, splitView: splitView, dividerIndex: 0)
- return item
-
default:
return NSToolbarItem(itemIdentifier: itemIdentifier)
}
diff --git a/CotEditor/Sources/EditorOpacityView.swift b/CotEditor/Sources/EditorOpacityView.swift
index 0e88597e3..113925b64 100644
--- a/CotEditor/Sources/EditorOpacityView.swift
+++ b/CotEditor/Sources/EditorOpacityView.swift
@@ -25,39 +25,6 @@
import SwiftUI
-@available(macOS, deprecated: 14)
-@MainActor final class OpacityHostingView: NSHostingView {
-
- convenience init(window: DocumentWindow?) {
-
- assert(window != nil)
-
- self.init(rootView: EditorOpacityView(window: window))
-
- self.frame.size = self.intrinsicContentSize
- }
-
-
- required init?(coder aDecoder: NSCoder) {
-
- // Implementing `init(coder:)` is required for toolbar item menu representation.
-
- let window = NSDocumentController.shared.currentDocument?.windowControllers.first?.window as? DocumentWindow
- assert(window != nil)
-
- super.init(rootView: EditorOpacityView(window: window))
-
- self.frame.size = self.intrinsicContentSize
- }
-
-
- @MainActor required init(rootView: EditorOpacityView) {
-
- super.init(rootView: rootView)
- }
-}
-
-
struct EditorOpacityView: View {
weak var window: DocumentWindow?
diff --git a/CotEditor/Sources/EditorTextView.swift b/CotEditor/Sources/EditorTextView.swift
index 126f77fc1..a66844fe9 100644
--- a/CotEditor/Sources/EditorTextView.swift
+++ b/CotEditor/Sources/EditorTextView.swift
@@ -83,22 +83,14 @@ class EditorTextView: NSTextView, Themable, CurrentLineHighlighting, MultiCursor
didSet {
self.needsUpdateInsertionIndicators = true
- self.updateInsertionPointTimer()
}
}
var selectionOrigins: [Int] = []
var insertionPointTimer: (any DispatchSourceTimer)?
var insertionPointOn = false
+ var insertionIndicators: [NSTextInsertionIndicator] = []
private(set) var isPerformingRectangularSelection = false
- @available(macOS 14, *)
- var insertionIndicators: [NSTextInsertionIndicator] {
-
- get { self._insertionIndicators.compactMap { $0 as? NSTextInsertionIndicator } }
- set { self._insertionIndicators = newValue }
- }
- private var _insertionIndicators: [NSView] = []
-
// for Scaling extension
var initialMagnificationScale: CGFloat = 0
var deferredMagnification: CGFloat = 0
@@ -351,7 +343,7 @@ class EditorTextView: NSTextView, Themable, CurrentLineHighlighting, MultiCursor
}
// observe key window state for insertion points drawing
- if #available(macOS 14, *), let window {
+ if let window {
self.keyStateObservers = [
NotificationCenter.default.addObserver(forName: NSWindow.didBecomeKeyNotification, object: window, queue: .main) { [weak self] _ in
self?.invalidateInsertionIndicatorDisplayMode()
@@ -408,7 +400,6 @@ class EditorTextView: NSTextView, Themable, CurrentLineHighlighting, MultiCursor
self.mouseDownPoint = self.convert(event.locationInWindow, from: nil)
self.isPerformingRectangularSelection = event.modifierFlags.contains(.option)
self.needsUpdateInsertionIndicators = true // to draw dummy indicator for proper one while selecting
- self.updateInsertionPointTimer()
let selectedRange = self.selectedRange.isEmpty ? self.selectedRange : nil
@@ -440,7 +431,6 @@ class EditorTextView: NSTextView, Themable, CurrentLineHighlighting, MultiCursor
}
self.isPerformingRectangularSelection = false
- self.updateInsertionPointTimer()
}
@@ -787,8 +777,6 @@ class EditorTextView: NSTextView, Themable, CurrentLineHighlighting, MultiCursor
self.selectionOrigins = [self.selectedRange.location]
}
- self.updateInsertionPointTimer()
-
self.needsUpdateLineHighlight = true
// invalidate current instances highlight
@@ -943,7 +931,7 @@ class EditorTextView: NSTextView, Themable, CurrentLineHighlighting, MultiCursor
super.viewWillDraw()
- if #available(macOS 14, *), self.needsUpdateInsertionIndicators {
+ if self.needsUpdateInsertionIndicators {
self.updateInsertionIndicators()
self.needsUpdateInsertionIndicators = false
}
@@ -992,16 +980,6 @@ class EditorTextView: NSTextView, Themable, CurrentLineHighlighting, MultiCursor
NSGraphicsContext.restoreGraphicsState()
}
}
-
- // draw zero-width insertion points while rectangular selection
- // -> Because the insertion point blink timer stops while dragging. (macOS 10.14)
- if self.needsDrawInsertionPoints, ProcessInfo.processInfo.operatingSystemVersion.majorVersion < 14 {
- self.insertionRanges
- .filter(\.isEmpty)
- .flatMap { self.insertionPointRects(at: $0.location) }
- .filter { $0.intersects(dirtyRect) }
- .forEach { super.drawInsertionPoint(in: $0, color: self.insertionPointColor, turnedOn: self.insertionPointOn) }
- }
}
@@ -1360,10 +1338,8 @@ class EditorTextView: NSTextView, Themable, CurrentLineHighlighting, MultiCursor
self.backgroundColor = theme.background.color
self.lineHighlightColor = theme.lineHighlightColor(forOpaqueBackground: self.isOpaque)
self.insertionPointColor = theme.effectiveInsertionPointColor
- if #available(macOS 14, *) {
- for indicator in self.insertionIndicators {
- indicator.color = self.insertionPointColor
- }
+ for indicator in self.insertionIndicators {
+ indicator.color = self.insertionPointColor
}
self.selectedTextAttributes[.backgroundColor] = theme.effectiveSelectionColor
(self.layoutManager as? LayoutManager)?.invisiblesColor = theme.invisibles.color
diff --git a/CotEditor/Sources/EditorTextViewController.swift b/CotEditor/Sources/EditorTextViewController.swift
index aea80eca4..3e4938399 100644
--- a/CotEditor/Sources/EditorTextViewController.swift
+++ b/CotEditor/Sources/EditorTextViewController.swift
@@ -61,7 +61,7 @@ final class EditorTextViewController: NSViewController, NSServicesMenuRequestor,
override func loadView() {
- let textView = if #available(macOS 14, *) { EditorTextView() } else { LegacyEditorTextView() }
+ let textView = EditorTextView()
textView.delegate = self
let scrollView = BidiScrollView()
diff --git a/CotEditor/Sources/EncodingListView.swift b/CotEditor/Sources/EncodingListView.swift
index bc90e3ce9..a0fbdd5f6 100644
--- a/CotEditor/Sources/EncodingListView.swift
+++ b/CotEditor/Sources/EncodingListView.swift
@@ -286,7 +286,6 @@ private extension CFStringEncoding {
// MARK: - Preview
-@available(macOS 14, *)
#Preview(traits: .fixedLayout(width: 400, height: 400)) {
EncodingListView()
}
diff --git a/CotEditor/Sources/FormPopUpButton.swift b/CotEditor/Sources/FormPopUpButton.swift
index 6963bd148..2fc2bb280 100644
--- a/CotEditor/Sources/FormPopUpButton.swift
+++ b/CotEditor/Sources/FormPopUpButton.swift
@@ -132,7 +132,6 @@ final class FormPopUpButtonCell: NSPopUpButtonCell {
// MARK: - Preview
-@available(macOS 14, *)
#Preview("Enabled") {
let button = FormPopUpButton()
button.addItem(withTitle: "Inu dog")
@@ -140,7 +139,6 @@ final class FormPopUpButtonCell: NSPopUpButtonCell {
return button
}
-@available(macOS 14, *)
#Preview("Disabled") {
let button = FormPopUpButton()
button.addItem(withTitle: "Inu dog")
diff --git a/CotEditor/Sources/HeadingMenuItem.swift b/CotEditor/Sources/HeadingMenuItem.swift
index 90b40a9f7..c13f8ce4d 100644
--- a/CotEditor/Sources/HeadingMenuItem.swift
+++ b/CotEditor/Sources/HeadingMenuItem.swift
@@ -25,33 +25,11 @@
import AppKit
-public extension NSMenuItem {
-
- /// A back deployed version of the NSMenuItem.sectionHeader(title:) method.
- ///
- /// - Parameter title: The title to display.
- /// - Returns: A menu item.
- @backDeployed(before: macOS 14)
- static func sectionHeader(title: String) -> NSMenuItem {
-
- HeadingMenuItem(title: title)
- }
-
-
- /// A Boolean value indicating whether the menu item is a section header.
- @backDeployed(before: macOS 14)
- final var isSectionHeader: Bool {
-
- self is HeadingMenuItem
- }
-}
-
-
-public final class HeadingMenuItem: NSMenuItem {
+final class HeadingMenuItem: NSMenuItem {
// MARK: Lifecycle
- public convenience init(title: String) {
+ convenience init(title: String) {
self.init(title: title, action: nil, keyEquivalent: "")
self.isEnabled = false
@@ -60,7 +38,7 @@ public final class HeadingMenuItem: NSMenuItem {
}
- public override func awakeFromNib() {
+ override func awakeFromNib() {
super.awakeFromNib()
@@ -68,6 +46,12 @@ public final class HeadingMenuItem: NSMenuItem {
}
+ override var isSectionHeader: Bool {
+
+ true
+ }
+
+
// MARK: Private Methods
/// Makes the menu item label heading style.
diff --git a/CotEditor/Sources/IncompatibleCharactersView.swift b/CotEditor/Sources/IncompatibleCharactersView.swift
index 1a8b20cc6..d139eee0f 100644
--- a/CotEditor/Sources/IncompatibleCharactersView.swift
+++ b/CotEditor/Sources/IncompatibleCharactersView.swift
@@ -247,7 +247,6 @@ private extension NSTextStorage {
// MARK: - Preview
-@available(macOS 14, *)
#Preview(traits: .fixedLayout(width: 240, height: 300)) {
let model = IncompatibleCharactersView.Model()
model.items = [
@@ -259,7 +258,6 @@ private extension NSTextStorage {
.padding(12)
}
-@available(macOS 14, *)
#Preview("Empty", traits: .fixedLayout(width: 240, height: 300)) {
IncompatibleCharactersView(model: .init())
.padding(12)
diff --git a/CotEditor/Sources/InconsistentLineEndingsView.swift b/CotEditor/Sources/InconsistentLineEndingsView.swift
index 44fa00083..10b9bc4af 100644
--- a/CotEditor/Sources/InconsistentLineEndingsView.swift
+++ b/CotEditor/Sources/InconsistentLineEndingsView.swift
@@ -141,7 +141,6 @@ private extension InconsistentLineEndingsView.Model {
// MARK: - Preview
-@available(macOS 14, *)
#Preview(traits: .fixedLayout(width: 240, height: 300)) {
let model = InconsistentLineEndingsView.Model()
model.items = [
@@ -152,7 +151,6 @@ private extension InconsistentLineEndingsView.Model {
.padding(12)
}
-@available(macOS 14, *)
#Preview("Empty", traits: .fixedLayout(width: 240, height: 300)) {
InconsistentLineEndingsView(model: .init())
.padding(12)
diff --git a/CotEditor/Sources/NSBezierPath.swift b/CotEditor/Sources/NSBezierPath.swift
index 4672a805a..e2c3102db 100644
--- a/CotEditor/Sources/NSBezierPath.swift
+++ b/CotEditor/Sources/NSBezierPath.swift
@@ -8,7 +8,7 @@
//
// ---------------------------------------------------------------------------
//
-// © 2018-2023 1024jp
+// © 2018-2024 1024jp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -25,48 +25,6 @@
import AppKit.NSBezierPath
-public extension NSBezierPath {
-
- /// A back deployed version of the NSBezierPath creation from a CGPath.
- ///
- /// - Parameter cgPath: A CGPath to convert to NSBezierPath.
- @backDeployed(before: macOS 14)
- convenience init(cgPath: CGPath) {
-
- self.init()
-
- cgPath.applyWithBlock { pointer in
- let element = pointer.pointee
-
- switch element.type {
- case .moveToPoint:
- self.move(to: element.points[0])
-
- case .addLineToPoint:
- self.line(to: element.points[0])
-
- case .addQuadCurveToPoint:
- let controlPoint1 = NSPoint(x: self.currentPoint.x + (2 / 3 * (element.points[0].x - self.currentPoint.x)),
- y: self.currentPoint.y + (2 / 3 * (element.points[0].y - self.currentPoint.y)))
- let controlPoint2 = NSPoint(x: element.points[1].x + (2 / 3 * (element.points[0].x - element.points[1].x)),
- y: element.points[1].y + (2 / 3 * (element.points[0].y - element.points[1].y)))
- self.curve(to: element.points[1], controlPoint1: controlPoint1, controlPoint2: controlPoint2)
-
- case .addCurveToPoint:
- self.curve(to: element.points[2], controlPoint1: element.points[0], controlPoint2: element.points[1])
-
- case .closeSubpath:
- self.close()
-
- @unknown default:
- assertionFailure()
- }
- }
- }
-}
-
-
-
// MARK: Rounded Corner
struct RectCorner: OptionSet {
diff --git a/CotEditor/Sources/NSColor+NamedColors.swift b/CotEditor/Sources/NSColor+NamedColors.swift
index 16b63514b..37fb2ccdb 100644
--- a/CotEditor/Sources/NSColor+NamedColors.swift
+++ b/CotEditor/Sources/NSColor+NamedColors.swift
@@ -8,7 +8,7 @@
//
// ---------------------------------------------------------------------------
//
-// © 2016-2023 1024jp
+// © 2016-2024 1024jp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -53,28 +53,3 @@ extension NSColor {
.map { NSColor(calibratedHue: $0, saturation: self.saturationComponent, brightness: self.brightnessComponent, alpha: self.alphaComponent) }
}
}
-
-
-
-public extension NSColor {
-
- /// The back-deployed version of the `.systemFill`.
- @backDeployed(before: macOS 14)
- static var systemFill: NSColor { .labelColor.withAlphaComponent(0.50) }
-
- /// The back-deployed version of the `.secondarySystemFill`.
- @backDeployed(before: macOS 14)
- static var secondarySystemFill: NSColor { .labelColor.withAlphaComponent(0.15) }
-
- /// The back-deployed version of the `.tertiarySystemFill`.
- @backDeployed(before: macOS 14)
- static var tertiarySystemFill: NSColor { .labelColor.withAlphaComponent(0.10) }
-
- /// The back-deployed version of the `.quaternarySystemFill`.
- @backDeployed(before: macOS 14)
- static var quaternarySystemFill: NSColor { .labelColor.withAlphaComponent(0.05) }
-
- /// The back-deployed version of the `.quinarySystemFill`.
- @backDeployed(before: macOS 14)
- static var quinarySystemFill: NSColor { .labelColor.withAlphaComponent(0.03) }
-}
diff --git a/CotEditor/Sources/NSLayoutManager.swift b/CotEditor/Sources/NSLayoutManager.swift
index e1731be93..fa41c6a5d 100644
--- a/CotEditor/Sources/NSLayoutManager.swift
+++ b/CotEditor/Sources/NSLayoutManager.swift
@@ -223,31 +223,6 @@ extension NSLayoutManager {
}
- /// Returns rects to draw insertion point for the given character index.
- ///
- /// - Note: The rects can be either in one or two when the cursor split at the boundary of the writing direction.
- ///
- /// - Parameter characterIndex: The character index.
- /// - Returns: One-pixel-width rects to draw insertion point in the layout manager coordinate.
- @available(macOS, deprecated: 14)
- final func insertionPointRects(at characterIndex: Int) -> [NSRect] {
-
- guard
- let primaryRect = self.insertionPointRect(at: characterIndex, alternate: false)
- else { return [] }
-
- guard
- UserDefaults.standard.useSplitCursor,
- let alternateRect = self.insertionPointRect(at: characterIndex, alternate: true)
- else { return [primaryRect] }
-
- return [NSRect(x: primaryRect.minX, y: primaryRect.minY,
- width: primaryRect.width, height: primaryRect.height / 2),
- NSRect(x: alternateRect.minX, y: alternateRect.minY + alternateRect.height / 2,
- width: alternateRect.width, height: alternateRect.height / 2)]
- }
-
-
/// Returns a rect to draw insertion point for the given character index.
///
/// - Parameters:
@@ -298,14 +273,6 @@ extension NSLayoutManager {
}
-private extension UserDefaults {
-
- /// Whether the user enables the system-wide "Use split cursor" option in System Settings > Keyboard > Text Input > Input Source.
- @available(macOS, deprecated: 14)
- var useSplitCursor: Bool { self.bool(forKey: "NSUseSplitCursor") }
-}
-
-
// MARK: - Debug
diff --git a/CotEditor/Sources/NSTextView+MultiCursor.swift b/CotEditor/Sources/NSTextView+MultiCursor.swift
index ea3f0d6a4..3c99500fb 100644
--- a/CotEditor/Sources/NSTextView+MultiCursor.swift
+++ b/CotEditor/Sources/NSTextView+MultiCursor.swift
@@ -30,13 +30,7 @@ import AppKit
var insertionLocations: [Int] { get set }
var selectionOrigins: [Int] { get set }
var isPerformingRectangularSelection: Bool { get }
-
- @available(macOS 14, *)
var insertionIndicators: [NSTextInsertionIndicator] { get set }
-
- @available(macOS, deprecated: 14)
- var insertionPointTimer: (any DispatchSourceTimer)? { get set }
- var insertionPointOn: Bool { get set }
}
@@ -383,7 +377,6 @@ extension MultiCursorEditing {
/// Updates insertion indicators.
- @available(macOS 14, *)
func updateInsertionIndicators() {
assert(Thread.isMainThread)
@@ -425,7 +418,7 @@ extension MultiCursorEditing {
/// This method should be Invoked when changing the state whether the receiver is the key editor receiving text input in the system.
func invalidateInsertionIndicatorDisplayMode() {
- guard #available(macOS 14, *), !self.insertionIndicators.isEmpty else { return }
+ guard !self.insertionIndicators.isEmpty else { return }
let shouldDraw = self.shouldDrawInsertionPoints
for indicator in self.insertionIndicators {
@@ -547,110 +540,3 @@ private extension NSLayoutManager {
return rects.uniqued
}
}
-
-
-
-// MARK: - LegacyEditorTextView
-
-/// Workaround subclass to let NSTextView uses the new NSTextInsertionIndicator (FB12964810).
-@available(macOS, deprecated: 14, message: "Just remove this subclass and also all the codes related to insertion point drawing.")
-final class LegacyEditorTextView: EditorTextView {
-
- override func drawInsertionPoint(in rect: NSRect, color: NSColor, turnedOn flag: Bool) {
-
- super.drawInsertionPoint(in: rect, color: color, turnedOn: flag)
-
- // draw sub insertion rects
- self.insertionLocations
- .flatMap { self.insertionPointRects(at: $0) }
- .forEach { super.drawInsertionPoint(in: $0, color: color, turnedOn: flag) }
- }
-}
-
-
-@available(macOS, deprecated: 14)
-extension MultiCursorEditing {
-
- /// Whether the receiver needs to draw insertion points by itself.
- var needsDrawInsertionPoints: Bool {
-
- self.insertionPointTimer?.isCancelled == false
- }
-
-
- /// Enables or disables `insertionPointTimer` according to the selection state.
- func updateInsertionPointTimer() {
-
- if #available(macOS 14, *) { return }
-
- if self.isPerformingRectangularSelection || (!self.insertionLocations.isEmpty && self.selectedRanges.allSatisfy({ !$0.rangeValue.isEmpty })) {
- self.enableOwnInsertionPointTimer()
- } else {
- self.insertionPointTimer?.cancel()
- }
- }
-
-
- /// Calculates rect for insertion point at `index`.
- ///
- /// - Parameter index: The character index where the insertion point will locate.
- /// - Returns: Rect where insertion point filled.
- func insertionPointRects(at index: Int) -> [NSRect] {
-
- guard let layoutManager = self.layoutManager else { assertionFailure(); return [] }
-
- let scale = self.scale
- return layoutManager.insertionPointRects(at: index)
- .map { $0.offset(by: self.textContainerOrigin) }
- .map { rect in
- NSRect(x: (rect.minX * scale).rounded(.down) / scale,
- y: rect.minY,
- width: 1 / scale,
- height: rect.height)
- }
- }
-
-
- /// Enables insertion point blink timer to draw insertion points forcibly.
- private func enableOwnInsertionPointTimer() {
-
- guard self.insertionPointTimer?.isCancelled ?? true else { return }
-
- let period = UserDefaults.standard.textInsertionPointBlinkPeriod
-
- let timer = DispatchSource.makeTimerSource(queue: .main)
- timer.schedule(deadline: .now())
- timer.setEventHandler { [unowned self] in
- self.insertionPointOn.toggle()
- let interval = self.insertionPointOn ? period.on : period.off
- timer.schedule(deadline: .now() + .milliseconds(interval))
- self.setNeedsDisplay(self.visibleRect, avoidAdditionalLayout: true)
- }
- timer.resume()
-
- self.insertionPointTimer?.cancel()
- self.insertionPointTimer = timer
- }
-}
-
-
-@available(macOS, deprecated: 14)
-private struct BlinkPeriod {
-
- var on: Int
- var off: Int
-}
-
-
-@available(macOS, deprecated: 14)
-private extension UserDefaults {
-
- var textInsertionPointBlinkPeriod: BlinkPeriod {
-
- let onPeriod = self.integer(forKey: "NSTextInsertionPointBlinkPeriodOn")
- let offPeriod = self.integer(forKey: "NSTextInsertionPointBlinkPeriodOff")
-
- return BlinkPeriod(on: (onPeriod > 0) ? onPeriod : 500,
- off: (offPeriod > 0) ? offPeriod : 500)
- }
-}
diff --git a/CotEditor/Sources/NSToolbarItem+Validatable.swift b/CotEditor/Sources/NSToolbarItem+Validatable.swift
index c4235c888..3653722cf 100644
--- a/CotEditor/Sources/NSToolbarItem+Validatable.swift
+++ b/CotEditor/Sources/NSToolbarItem+Validatable.swift
@@ -8,7 +8,7 @@
//
// ---------------------------------------------------------------------------
//
-// © 2020-2023 1024jp
+// © 2020-2024 1024jp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -47,15 +47,3 @@ extension Validatable where Self: NSToolbarItem {
}
}
}
-
-
-// MARK: -
-
-@available(macOS, deprecated: 14)
-final class MenuToolbarItem: NSMenuToolbarItem, Validatable {
-
- override func validate() {
-
- self.isEnabled = self.validate()
- }
-}
diff --git a/CotEditor/Sources/OpacitySlider.swift b/CotEditor/Sources/OpacitySlider.swift
index fbaddd64c..114ba8f1d 100644
--- a/CotEditor/Sources/OpacitySlider.swift
+++ b/CotEditor/Sources/OpacitySlider.swift
@@ -122,7 +122,6 @@ private struct OpacitySample: View {
// MARK: - Preview
-@available(macOS 14, *)
#Preview(traits: .fixedLayout(width: 200, height: 50)) {
VStack {
diff --git a/CotEditor/Sources/OutlineInspectorView.swift b/CotEditor/Sources/OutlineInspectorView.swift
index 92163441a..f2e09ed7d 100644
--- a/CotEditor/Sources/OutlineInspectorView.swift
+++ b/CotEditor/Sources/OutlineInspectorView.swift
@@ -191,11 +191,9 @@ private struct OutlineRowView: View {
var body: some View {
if self.item.isSeparator {
- if #available(macOS 14, *) {
- Divider().selectionDisabled()
- } else {
- Divider()
- }
+ Divider()
+ .selectionDisabled()
+
} else {
Text(self.item.attributedTitle(.init()
.backgroundColor(.findHighlightColor)
@@ -279,7 +277,6 @@ private extension OutlineInspectorView.Model {
// MARK: - Preview
-@available(macOS 14, *)
#Preview(traits: .fixedLayout(width: 240, height: 300)) {
let model = OutlineInspectorView.Model()
model.items = [
diff --git a/CotEditor/Sources/Theme.swift b/CotEditor/Sources/Theme.swift
index 81a758df9..37d14f0cd 100644
--- a/CotEditor/Sources/Theme.swift
+++ b/CotEditor/Sources/Theme.swift
@@ -160,11 +160,7 @@ struct Theme: Equatable {
/// Insertion point color to use.
var effectiveInsertionPointColor: NSColor {
- if #available(macOS 14, *) {
- self.insertionPoint.usesSystemSetting ? .textInsertionPointColor : self.insertionPoint.color
- } else {
- self.insertionPoint.color
- }
+ self.insertionPoint.usesSystemSetting ? .textInsertionPointColor : self.insertionPoint.color
}
diff --git a/CotEditor/Sources/ThemeEditorView.swift b/CotEditor/Sources/ThemeEditorView.swift
index 079115298..9ee0134d0 100644
--- a/CotEditor/Sources/ThemeEditorView.swift
+++ b/CotEditor/Sources/ThemeEditorView.swift
@@ -47,15 +47,10 @@ struct ThemeEditorView: View {
selection: $theme.text.binding, supportsOpacity: false)
ColorPicker(String(localized: "Invisibles:", table: "ThemeEditor"),
selection: $theme.invisibles.binding)
- if #available(macOS 14, *) {
- SystemColorPicker(String(localized: "Cursor:", table: "ThemeEditor"),
- selection: $theme.insertionPoint,
- systemColor: Color(nsColor: .textInsertionPointColor))
- } else {
- ColorPicker(String(localized: "Cursor:", table: "ThemeEditor"),
- selection: $theme.insertionPoint.binding)
- }
- }.accessibilityElement(children: .contain)
+ SystemColorPicker(String(localized: "Cursor:", table: "ThemeEditor"),
+ selection: $theme.insertionPoint,
+ systemColor: Color(nsColor: .textInsertionPointColor))
+ }
VStack(alignment: .trailing, spacing: 3) {
ColorPicker(String(localized: "Background:", table: "ThemeEditor"),
@@ -230,7 +225,6 @@ private struct ThemeMetadataView: View {
.accessibilityLabeledPair(role: .content, id: title, in: self.accessibility)
} else {
Text(text.wrappedValue)
- .foregroundColor(.label)
.textSelection(.enabled)
.accessibilityLabeledPair(role: .content, id: title, in: self.accessibility)
}
@@ -261,7 +255,6 @@ private extension Theme.SystemDefaultStyle {
// MARK: - Preview
-@available(macOS 14, *)
#Preview(traits: .fixedLayout(width: 360, height: 280)) {
ThemeEditorView(theme: try! ThemeManager.shared.setting(name: "Anura"), isBundled: false) { _ in }
}
diff --git a/CotEditor/Sources/WarningInspectorView.swift b/CotEditor/Sources/WarningInspectorView.swift
index 08677ec7c..559155717 100644
--- a/CotEditor/Sources/WarningInspectorView.swift
+++ b/CotEditor/Sources/WarningInspectorView.swift
@@ -115,7 +115,6 @@ struct WarningInspectorView: View {
// MARK: - Preview
-@available(macOS 14, *)
#Preview(traits: .fixedLayout(width: 240, height: 300)) {
WarningInspectorView(model: .init())
}
diff --git a/CotEditor/Sources/WindowContentViewController.swift b/CotEditor/Sources/WindowContentViewController.swift
index 7b9196840..ee384296e 100644
--- a/CotEditor/Sources/WindowContentViewController.swift
+++ b/CotEditor/Sources/WindowContentViewController.swift
@@ -136,17 +136,6 @@ final class WindowContentViewController: NSSplitViewController {
// MARK: Action Messages
- /// Toggles visibility of the inspector.
- @IBAction override func toggleInspector(_ sender: Any?) {
-
- if #available(macOS 14, *) {
- super.toggleInspector(sender)
- } else {
- self.inspectorViewItem?.animator().isCollapsed.toggle()
- }
- }
-
-
/// Toggles visibility of the document inspector pane.
@IBAction func getInfo(_ sender: Any?) {
diff --git a/Tests/StringCommentingTests.swift b/Tests/StringCommentingTests.swift
index 594d141b5..3632e7283 100644
--- a/Tests/StringCommentingTests.swift
+++ b/Tests/StringCommentingTests.swift
@@ -171,12 +171,7 @@ private final class CommentingTextView: NSTextView, Commenting, MultiCursorEditi
var insertionPointTimer: (any DispatchSourceTimer)?
var insertionPointOn: Bool = false
var isPerformingRectangularSelection: Bool = false
-
- @available(macOS 14, *)
- var insertionIndicators: [NSTextInsertionIndicator] {
- get { [] }
- set { _ = newValue }
- }
+ var insertionIndicators: [NSTextInsertionIndicator] = []
override var rangesForUserTextChange: [NSValue]? {
From d412711c3333d1c09c8570931b2e5efd4fd6b5d5 Mon Sep 17 00:00:00 2001
From: 1024jp <1024jp@wolfrosch.com>
Date: Sun, 21 Apr 2024 02:31:06 +0900
Subject: [PATCH 004/191] Remove workaround for StepperNumberField
---
CotEditor/Sources/StepperNumberField.swift | 23 ++++------------------
1 file changed, 4 insertions(+), 19 deletions(-)
diff --git a/CotEditor/Sources/StepperNumberField.swift b/CotEditor/Sources/StepperNumberField.swift
index e3392965a..482c9500e 100644
--- a/CotEditor/Sources/StepperNumberField.swift
+++ b/CotEditor/Sources/StepperNumberField.swift
@@ -57,10 +57,10 @@ struct StepperNumberField: View {
var body: some View {
HStack(spacing: 4) {
- TextField(text: $value.string(in: self.bounds, defaultValue: self.defaultValue), prompt: self.prompt, label: EmptyView.init)
- .monospacedDigit()
- .environment(\.layoutDirection, .rightToLeft)
- .frame(width: self.fieldWidth)
+ TextField(value: $value, format: .ranged(self.bounds), prompt: self.prompt, label: EmptyView.init)
+ .monospacedDigit()
+ .environment(\.layoutDirection, .rightToLeft)
+ .frame(width: self.fieldWidth)
Stepper(value: $value, in: self.bounds, step: self.step, label: EmptyView.init)
}
@@ -94,21 +94,6 @@ struct StepperNumberField: View {
-@available(macOS, deprecated: 14, message: "Simply bind with `format: .ranged(self.bounds)`.")
-private extension Binding where Value == Int {
-
- /// Workarounds the issue on macOS 13 that Stepper cannot share its bound value with another controllers.
- func string(in bounds: ClosedRange, defaultValue: Value? = nil) -> Binding {
-
- Binding(
- get: { self.wrappedValue.formatted(.number) },
- set: { self.wrappedValue = ((try? Value($0, format: .number)) ?? defaultValue ?? 0).clamped(to: bounds) }
- )
- }
-}
-
-
-
// MARK: - Preview
#Preview {
From 3d901438b4acdd42d6012f70652d893a745be55e Mon Sep 17 00:00:00 2001
From: 1024jp <1024jp@wolfrosch.com>
Date: Sun, 21 Apr 2024 02:15:27 +0900
Subject: [PATCH 005/191] Update .onChange(of:) API
---
CotEditor/Sources/AddRemoveButton.swift | 4 ++--
CotEditor/Sources/AppearanceSettingsView.swift | 2 +-
CotEditor/Sources/ColorCodePanelController.swift | 4 +++-
CotEditor/Sources/CommandBarView.swift | 6 +++---
CotEditor/Sources/EditorOpacityView.swift | 2 +-
CotEditor/Sources/FindPanelResultView.swift | 4 ++--
CotEditor/Sources/FindProgressView.swift | 4 ++--
CotEditor/Sources/GeneralSettingsView.swift | 2 +-
CotEditor/Sources/IncompatibleCharactersView.swift | 4 ++--
CotEditor/Sources/InconsistentLineEndingsView.swift | 4 ++--
CotEditor/Sources/ModeSettingsView.swift | 9 +++------
CotEditor/Sources/OpenPanelAccessory.swift | 2 +-
CotEditor/Sources/PatternSortView.swift | 6 +++---
CotEditor/Sources/StatusBar.swift | 4 ++--
CotEditor/Sources/SyntaxEditView.swift | 8 +++++---
CotEditor/Sources/SyntaxHighlightEditView.swift | 4 ++--
CotEditor/Sources/SyntaxMappingConflictView.swift | 2 +-
CotEditor/Sources/ThemeEditorView.swift | 4 ++--
18 files changed, 38 insertions(+), 37 deletions(-)
diff --git a/CotEditor/Sources/AddRemoveButton.swift b/CotEditor/Sources/AddRemoveButton.swift
index 67ceaf392..79b577eb2 100644
--- a/CotEditor/Sources/AddRemoveButton.swift
+++ b/CotEditor/Sources/AddRemoveButton.swift
@@ -8,7 +8,7 @@
//
// ---------------------------------------------------------------------------
//
-// © 2023 1024jp
+// © 2023-2024 1024jp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -69,7 +69,7 @@ struct AddRemoveButton: View {
self.selection = [item.id]
}
}
- .onChange(of: self.added) { newValue in
+ .onChange(of: self.added) { (_, newValue) in
self.focus?.wrappedValue = newValue
}
.help(String(localized: "Add new item", table: "AddRemoveButton", comment: "tooltip"))
diff --git a/CotEditor/Sources/AppearanceSettingsView.swift b/CotEditor/Sources/AppearanceSettingsView.swift
index cd249814c..fbed987a9 100644
--- a/CotEditor/Sources/AppearanceSettingsView.swift
+++ b/CotEditor/Sources/AppearanceSettingsView.swift
@@ -64,7 +64,7 @@ struct AppearanceSettingsView: View {
.accessibilityLabeledPair(role: .label, id: "monospacedFont", in: self.accessibility)
FontSettingView(data: $monospacedFont ?? (try! FontType.monospaced.systemFont().archivedData), antialias: $monospacedShouldAntialias, ligature: $monospacedLigature)
- .onChange(of: self.monospacedFont) { [oldValue = self.monospacedFont] newValue in
+ .onChange(of: self.monospacedFont) { (oldValue, newValue) in
guard
let newValue,
let font = NSFont(archivedData: newValue),
diff --git a/CotEditor/Sources/ColorCodePanelController.swift b/CotEditor/Sources/ColorCodePanelController.swift
index 7a0567fbb..f6ee465b7 100644
--- a/CotEditor/Sources/ColorCodePanelController.swift
+++ b/CotEditor/Sources/ColorCodePanelController.swift
@@ -146,7 +146,9 @@ private struct ColorCodePanelAccessory: View {
} label: {
EmptyView()
}
- .onChange(of: self.type) { self.apply(type: $0) }
+ .onChange(of: self.type) { (_, newValue) in
+ self.apply(type: newValue)
+ }
.labelsHidden()
Button(String(localized: "Insert", table: "ColorCode", comment: "button label")) {
diff --git a/CotEditor/Sources/CommandBarView.swift b/CotEditor/Sources/CommandBarView.swift
index 07602c9f5..ba03d9301 100644
--- a/CotEditor/Sources/CommandBarView.swift
+++ b/CotEditor/Sources/CommandBarView.swift
@@ -96,7 +96,7 @@ struct CommandBarView: View {
}
.padding(.horizontal, 10)
}
- .onChange(of: self.selection) { newValue in
+ .onChange(of: self.selection) { (_, newValue) in
proxy.scrollTo(newValue)
}
}
@@ -105,7 +105,7 @@ struct CommandBarView: View {
.fixedSize(horizontal: false, vertical: true)
}
}
- .onChange(of: self.input) { newValue in
+ .onChange(of: self.input) { (_, newValue) in
self.candidates = self.model.commands
.compactMap {
guard let result = $0.match(command: newValue) else { return nil }
@@ -114,7 +114,7 @@ struct CommandBarView: View {
.sorted(\.score)
self.selection = self.candidates.first?.id
}
- .onChange(of: self.controlActiveState) { newValue in
+ .onChange(of: self.controlActiveState) { (_, newValue) in
switch newValue {
case .key, .active:
self.keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in
diff --git a/CotEditor/Sources/EditorOpacityView.swift b/CotEditor/Sources/EditorOpacityView.swift
index 113925b64..0d38db19e 100644
--- a/CotEditor/Sources/EditorOpacityView.swift
+++ b/CotEditor/Sources/EditorOpacityView.swift
@@ -40,7 +40,7 @@ struct EditorOpacityView: View {
.foregroundStyle(.secondary)
OpacitySlider(value: $opacity)
- .onChange(of: self.opacity) { newValue in
+ .onChange(of: self.opacity) { (_, newValue) in
self.window?.backgroundAlpha = newValue
}
.controlSize(.small)
diff --git a/CotEditor/Sources/FindPanelResultView.swift b/CotEditor/Sources/FindPanelResultView.swift
index f793418de..4a8081e5f 100644
--- a/CotEditor/Sources/FindPanelResultView.swift
+++ b/CotEditor/Sources/FindPanelResultView.swift
@@ -121,7 +121,7 @@ struct FindPanelResultView: View {
.copyable(self.model.matches
.filter(with: self.selection)
.map(\.attributedLineString.string))
- .onChange(of: self.selection) { newValue in
+ .onChange(of: self.selection) { (_, newValue) in
// remove selection of previous data
if newValue.count > 1 {
let ids = self.model.matches.map(\.id)
@@ -133,7 +133,7 @@ struct FindPanelResultView: View {
guard newValue.count == 1 else { return }
self.selectMatch(newValue.first)
}
- .onChange(of: self.sortOrder) { newValue in
+ .onChange(of: self.sortOrder) { (_, newValue) in
self.model.matches.sort(using: newValue)
}
.contextMenu {
diff --git a/CotEditor/Sources/FindProgressView.swift b/CotEditor/Sources/FindProgressView.swift
index 394fcff8e..0eacc2269 100644
--- a/CotEditor/Sources/FindProgressView.swift
+++ b/CotEditor/Sources/FindProgressView.swift
@@ -87,12 +87,12 @@ struct FindProgressView: View {
.onReceive(self.timer) { _ in
self.updateDescription()
}
- .onChange(of: self.progress.isCancelled) { newValue in
+ .onChange(of: self.progress.isCancelled) { (_, newValue) in
if newValue {
self.parent?.dismiss(nil)
}
}
- .onChange(of: self.progress.isFinished) { newValue in
+ .onChange(of: self.progress.isFinished) { (_, newValue) in
if newValue {
self.updateDescription()
self.parent?.dismiss(nil)
diff --git a/CotEditor/Sources/GeneralSettingsView.swift b/CotEditor/Sources/GeneralSettingsView.swift
index 39410ae7c..787a11c56 100644
--- a/CotEditor/Sources/GeneralSettingsView.swift
+++ b/CotEditor/Sources/GeneralSettingsView.swift
@@ -85,7 +85,7 @@ struct GeneralSettingsView: View {
VStack(alignment: .leading) {
Toggle(String(localized: "Enable Auto Save with Versions", table: "GeneralSettings"), isOn: $enablesAutosaveInPlace)
- .onChange(of: self.enablesAutosaveInPlace) { newValue in
+ .onChange(of: self.enablesAutosaveInPlace) { (_, newValue) in
if newValue != self.initialEnablesAutosaveInPlace {
self.isAutosaveChangeDialogPresented = true
}
diff --git a/CotEditor/Sources/IncompatibleCharactersView.swift b/CotEditor/Sources/IncompatibleCharactersView.swift
index d139eee0f..c2c99cf34 100644
--- a/CotEditor/Sources/IncompatibleCharactersView.swift
+++ b/CotEditor/Sources/IncompatibleCharactersView.swift
@@ -99,10 +99,10 @@ struct IncompatibleCharactersView: View {
}
}
}
- .onChange(of: self.selection) { newValue in
+ .onChange(of: self.selection) { (_, newValue) in
self.model.selectItem(id: newValue)
}
- .onChange(of: self.sortOrder) { newValue in
+ .onChange(of: self.sortOrder) { (_, newValue) in
withAnimation {
self.model.items.sort(using: newValue)
}
diff --git a/CotEditor/Sources/InconsistentLineEndingsView.swift b/CotEditor/Sources/InconsistentLineEndingsView.swift
index 10b9bc4af..5ccf2583e 100644
--- a/CotEditor/Sources/InconsistentLineEndingsView.swift
+++ b/CotEditor/Sources/InconsistentLineEndingsView.swift
@@ -80,10 +80,10 @@ struct InconsistentLineEndingsView: View {
Text($0.value.label)
}
}
- .onChange(of: self.selection) { newValue in
+ .onChange(of: self.selection) { (_, newValue) in
self.model.selectItem(id: newValue)
}
- .onChange(of: self.sortOrder) { newValue in
+ .onChange(of: self.sortOrder) { (_, newValue) in
withAnimation {
self.model.items.sort(using: newValue)
}
diff --git a/CotEditor/Sources/ModeSettingsView.swift b/CotEditor/Sources/ModeSettingsView.swift
index a31538a3b..d94a47d69 100644
--- a/CotEditor/Sources/ModeSettingsView.swift
+++ b/CotEditor/Sources/ModeSettingsView.swift
@@ -41,7 +41,7 @@ struct ModeSettingsView: View {
GroupBox {
ModeOptionsView(options: $options)
.disabled(!self.selection.available)
- .onChange(of: self.options) { newValue in
+ .onChange(of: self.options) { (_, newValue) in
Task {
await ModeManager.shared.save(setting: newValue, mode: self.selection)
}
@@ -55,12 +55,9 @@ struct ModeSettingsView: View {
HelpButton(anchor: "settings_mode")
}
}
- .task {
- self.options = await ModeManager.shared.setting(for: self.selection)
- }
- .onChange(of: self.selection) { mode in // migrate to .onChange(of:initial:...
+ .onChange(of: self.selection, initial: true) { (_, newValue) in
Task {
- self.options = await ModeManager.shared.setting(for: mode)
+ self.options = await ModeManager.shared.setting(for: newValue)
}
}
.scenePadding()
diff --git a/CotEditor/Sources/OpenPanelAccessory.swift b/CotEditor/Sources/OpenPanelAccessory.swift
index d283393cd..26e35bea2 100644
--- a/CotEditor/Sources/OpenPanelAccessory.swift
+++ b/CotEditor/Sources/OpenPanelAccessory.swift
@@ -64,7 +64,7 @@ struct OpenPanelAccessory: View {
}
Toggle(String(localized: "Show invisible files", table: "OpenPanelAccessory", comment: "toggle button label"), isOn: $showsHiddenFiles)
- .onChange(of: self.showsHiddenFiles) { newValue in
+ .onChange(of: self.showsHiddenFiles) { (_, newValue) in
guard let openPanel = self.openPanel else { return }
openPanel.showsHiddenFiles = newValue
diff --git a/CotEditor/Sources/PatternSortView.swift b/CotEditor/Sources/PatternSortView.swift
index 6b8fcb01c..59b986592 100644
--- a/CotEditor/Sources/PatternSortView.swift
+++ b/CotEditor/Sources/PatternSortView.swift
@@ -99,17 +99,17 @@ struct PatternSortView: View {
.horizontalRadioGroupLayout()
.labelsHidden()
.fixedSize()
- .onChange(of: self.sortKey) { _ in self.validate() }
+ .onChange(of: self.sortKey) { self.validate() }
switch self.sortKey {
case .entire:
EmptyView()
case .column:
ColumnSortPatternView(pattern: $columnSortPattern)
- .onChange(of: self.columnSortPattern) { _ in self.validate() }
+ .onChange(of: self.columnSortPattern) { self.validate() }
case .regularExpression:
RegularExpressionSortPatternView(pattern: $regularExpressionSortPattern, error: $error)
- .onChange(of: self.regularExpressionSortPattern) { _ in self.validate() }
+ .onChange(of: self.regularExpressionSortPattern) { self.validate() }
}
}
}
diff --git a/CotEditor/Sources/StatusBar.swift b/CotEditor/Sources/StatusBar.swift
index 008dc504d..11643c344 100644
--- a/CotEditor/Sources/StatusBar.swift
+++ b/CotEditor/Sources/StatusBar.swift
@@ -207,7 +207,7 @@ struct StatusBar: View {
} label: {
EmptyView()
}
- .onChange(of: self.model.fileEncoding) { newValue in
+ .onChange(of: self.model.fileEncoding) { (_, newValue) in
self.model.document?.askChangingEncoding(to: newValue)
}
.help(String(localized: "Text Encoding", table: "Document"))
@@ -218,7 +218,7 @@ struct StatusBar: View {
LineEndingPicker(String(localized: "Line Endings", table: "Document", comment: "menu item header"),
selection: $model.lineEnding)
- .onChange(of: self.model.lineEnding) { newValue in
+ .onChange(of: self.model.lineEnding) { (_, newValue) in
self.model.document?.changeLineEnding(to: newValue)
}
.help(String(localized: "Line Endings", table: "Document"))
diff --git a/CotEditor/Sources/SyntaxEditView.swift b/CotEditor/Sources/SyntaxEditView.swift
index bed80e62e..ab32c0075 100644
--- a/CotEditor/Sources/SyntaxEditView.swift
+++ b/CotEditor/Sources/SyntaxEditView.swift
@@ -131,7 +131,7 @@ struct SyntaxEditView: View {
.focused($isNameFieldFocused)
.fontWeight(.medium)
.frame(minWidth: 80, maxWidth: 160)
- .onChange(of: self.name) { newValue in
+ .onChange(of: self.name) { (_, newValue) in
self.validate(name: newValue)
}
}
@@ -175,13 +175,15 @@ struct SyntaxEditView: View {
.onAppear {
self.name = self.originalName ?? ""
}
- .onChange(of: self.pane) { _ in
+ .onChange(of: self.pane) {
self.errors = self.syntax.validate()
}
.alert(error: $error)
.background { // store last view size
GeometryReader { geometry in
- Color.clear.onChange(of: geometry.size) { Self.viewSize = $0 }
+ Color.clear.onChange(of: geometry.size) { (_, newValue) in
+ Self.viewSize = newValue
+ }
}
}
.frame(idealWidth: Self.viewSize.width, minHeight: 500, idealHeight: Self.viewSize.height)
diff --git a/CotEditor/Sources/SyntaxHighlightEditView.swift b/CotEditor/Sources/SyntaxHighlightEditView.swift
index 472293a94..37adc1f6b 100644
--- a/CotEditor/Sources/SyntaxHighlightEditView.swift
+++ b/CotEditor/Sources/SyntaxHighlightEditView.swift
@@ -49,7 +49,7 @@ struct SyntaxHighlightEditView: View {
if let item = $items.first(where: { $0.id == wrappedItem.id }) {
Toggle(isOn: item.isRegularExpression, label: EmptyView.init)
.help(String(localized: "Regular Expression", table: "SyntaxEditor", comment: "tooltip for RE checkbox"))
- .onChange(of: item.isRegularExpression.wrappedValue) { newValue in
+ .onChange(of: item.isRegularExpression.wrappedValue) { (_, newValue) in
guard self.selection.contains(item.id) else { return }
$items
.filter(with: self.selection)
@@ -64,7 +64,7 @@ struct SyntaxHighlightEditView: View {
if let item = $items.first(where: { $0.id == wrappedItem.id }) {
Toggle(isOn: item.ignoreCase, label: EmptyView.init)
.help(String(localized: "Ignore Case", table: "SyntaxEditor", comment: "tooltip for IC checkbox"))
- .onChange(of: item.ignoreCase.wrappedValue) { newValue in
+ .onChange(of: item.ignoreCase.wrappedValue) { (_, newValue) in
guard self.selection.contains(item.id) else { return }
$items
.filter(with: self.selection)
diff --git a/CotEditor/Sources/SyntaxMappingConflictView.swift b/CotEditor/Sources/SyntaxMappingConflictView.swift
index 891f51647..4154fbfeb 100644
--- a/CotEditor/Sources/SyntaxMappingConflictView.swift
+++ b/CotEditor/Sources/SyntaxMappingConflictView.swift
@@ -128,7 +128,7 @@ private struct ConflictTable: View {
Text($0.duplicatedSyntaxes, format: .list(type: .and, width: .narrow))
}
}
- .onChange(of: self.sortOrder) { newValue in
+ .onChange(of: self.sortOrder) { (_, newValue) in
self.conflicts.sort(using: newValue)
}
.tableStyle(.bordered)
diff --git a/CotEditor/Sources/ThemeEditorView.swift b/CotEditor/Sources/ThemeEditorView.swift
index 9ee0134d0..ac8b28ce0 100644
--- a/CotEditor/Sources/ThemeEditorView.swift
+++ b/CotEditor/Sources/ThemeEditorView.swift
@@ -118,7 +118,7 @@ struct ThemeEditorView: View {
}
.accessibilityElement(children: .contain)
.accessibilityLabel(String(localized: "Theme Editor", table: "ThemeEditor"))
- .onChange(of: self.theme) { newValue in
+ .onChange(of: self.theme) { (_, newValue) in
if self.isMetadataPresenting {
// postpone notification to avoid closing the popover
self.needsNotify = true
@@ -126,7 +126,7 @@ struct ThemeEditorView: View {
self.onUpdate(newValue)
}
}
- .onChange(of: self.isMetadataPresenting) { newValue in
+ .onChange(of: self.isMetadataPresenting) { (_, newValue) in
guard !newValue, self.needsNotify else { return }
self.onUpdate(self.theme)
From 22b5d873683d2908fa4d2c10dc7621e9d1019ed3 Mon Sep 17 00:00:00 2001
From: 1024jp <1024jp@wolfrosch.com>
Date: Sun, 21 Apr 2024 01:06:06 +0900
Subject: [PATCH 006/191] Use .alternatingRowBackgrounds()
---
CotEditor/Sources/SyntaxFileMappingEditView.swift | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/CotEditor/Sources/SyntaxFileMappingEditView.swift b/CotEditor/Sources/SyntaxFileMappingEditView.swift
index 5ae62dfc1..863273f41 100644
--- a/CotEditor/Sources/SyntaxFileMappingEditView.swift
+++ b/CotEditor/Sources/SyntaxFileMappingEditView.swift
@@ -108,7 +108,8 @@ struct SyntaxFileMappingEditView: View {
}
}
}
- .listStyle(.bordered(alternatesRowBackgrounds: true))
+ .listStyle(.bordered)
+ .alternatingRowBackgrounds()
.border(Color(nsColor: .gridColor))
AddRemoveButton($items, selection: $selection, focus: $focusedField)
From e4a2e4da6d7c0a6cc32b2ac8a30bae8172550232 Mon Sep 17 00:00:00 2001
From: 1024jp <1024jp@wolfrosch.com>
Date: Sun, 21 Apr 2024 01:15:04 +0900
Subject: [PATCH 007/191] Use .alignment(_:) for TableColumn
---
CotEditor/Sources/FindPanelResultView.swift | 5 +++--
CotEditor/Sources/IncompatibleCharactersView.swift | 2 +-
CotEditor/Sources/InconsistentLineEndingsView.swift | 2 +-
CotEditor/Sources/SyntaxHighlightEditView.swift | 10 ++++++----
CotEditor/Sources/SyntaxOutlineEditView.swift | 5 +++--
5 files changed, 14 insertions(+), 10 deletions(-)
diff --git a/CotEditor/Sources/FindPanelResultView.swift b/CotEditor/Sources/FindPanelResultView.swift
index 4a8081e5f..06f4a386c 100644
--- a/CotEditor/Sources/FindPanelResultView.swift
+++ b/CotEditor/Sources/FindPanelResultView.swift
@@ -103,9 +103,10 @@ struct FindPanelResultView: View {
TableColumn(String(localized: "Line", table: "TextFind", comment: "table column header"), value: \.range.location) {
Text(self.model.target?.lineNumber(at: $0.range.location) ?? 0, format: .number)
.monospacedDigit()
- .frame(maxWidth: .infinity, alignment: .trailing)
.padding(.vertical, -2)
- }.width(ideal: 30, max: 64)
+ }
+ .width(ideal: 30, max: 64)
+ .alignment(.trailing)
TableColumn(String(localized: "Found String", table: "TextFind", comment: "table column header")) {
Text(AttributedString($0.attributedLineString(offset: 16)))
diff --git a/CotEditor/Sources/IncompatibleCharactersView.swift b/CotEditor/Sources/IncompatibleCharactersView.swift
index c2c99cf34..e4b2f8d58 100644
--- a/CotEditor/Sources/IncompatibleCharactersView.swift
+++ b/CotEditor/Sources/IncompatibleCharactersView.swift
@@ -76,9 +76,9 @@ struct IncompatibleCharactersView: View {
if let line = self.model.document?.lineEndingScanner.lineNumber(at: $0.location) {
Text(line, format: .number)
.monospacedDigit()
- .frame(maxWidth: .infinity, alignment: .trailing)
}
}
+ .alignment(.trailing)
TableColumn(String(localized: "Character", table: "Document", comment: "table column header"), value: \.value.character) {
let character = $0.value.character
diff --git a/CotEditor/Sources/InconsistentLineEndingsView.swift b/CotEditor/Sources/InconsistentLineEndingsView.swift
index 5ccf2583e..1dd89277d 100644
--- a/CotEditor/Sources/InconsistentLineEndingsView.swift
+++ b/CotEditor/Sources/InconsistentLineEndingsView.swift
@@ -72,9 +72,9 @@ struct InconsistentLineEndingsView: View {
if let line = self.model.document?.lineEndingScanner.lineNumber(at: $0.location) {
Text(line, format: .number)
.monospacedDigit()
- .frame(maxWidth: .infinity, alignment: .trailing)
}
}
+ .alignment(.trailing)
TableColumn(String(localized: "Line Ending", table: "Document", comment: "table column header"), value: \.value.rawValue) {
Text($0.value.label)
diff --git a/CotEditor/Sources/SyntaxHighlightEditView.swift b/CotEditor/Sources/SyntaxHighlightEditView.swift
index 37adc1f6b..4b7e30705 100644
--- a/CotEditor/Sources/SyntaxHighlightEditView.swift
+++ b/CotEditor/Sources/SyntaxHighlightEditView.swift
@@ -56,9 +56,10 @@ struct SyntaxHighlightEditView: View {
.filter { $0.id != item.id }
.forEach { $0.isRegularExpression.wrappedValue = newValue }
}
- .frame(maxWidth: .infinity, alignment: .center)
}
- }.width(20)
+ }
+ .width(22)
+ .alignment(.center)
TableColumn(String(localized: "IC", table: "SyntaxEditor", comment: "table column header (IC for Ignore Case)")) { wrappedItem in
if let item = $items.first(where: { $0.id == wrappedItem.id }) {
@@ -71,9 +72,10 @@ struct SyntaxHighlightEditView: View {
.filter { $0.id != item.id }
.forEach { $0.ignoreCase.wrappedValue = newValue }
}
- .frame(maxWidth: .infinity, alignment: .center)
}
- }.width(20)
+ }
+ .width(22)
+ .alignment(.center)
TableColumn(String(localized: "Begin String", table: "SyntaxEditor", comment: "table column header")) { wrappedItem in
if let item = $items.first(where: { $0.id == wrappedItem.id }) {
diff --git a/CotEditor/Sources/SyntaxOutlineEditView.swift b/CotEditor/Sources/SyntaxOutlineEditView.swift
index 1bb0f8db7..5ad28c911 100644
--- a/CotEditor/Sources/SyntaxOutlineEditView.swift
+++ b/CotEditor/Sources/SyntaxOutlineEditView.swift
@@ -47,8 +47,9 @@ struct SyntaxOutlineEditView: View {
TableColumn(String(localized: "IC", table: "SyntaxEditor", comment: "table column header (IC for Ignore Case)")) { item in
Toggle(isOn: item.ignoreCase, label: EmptyView.init)
.help(String(localized: "Ignore Case", table: "SyntaxEditor", comment: "tooltip for IC checkbox"))
- .frame(maxWidth: .infinity, alignment: .center)
- }.width(20)
+ }
+ .width(22)
+ .alignment(.center)
TableColumn(String(localized: "Regular Expression Pattern", table: "SyntaxEditor", comment: "table column header")) { item in
RegexTextField(text: item.pattern, showsError: true, showsInvisible: true)
From 88b61034d1d222cb83aa640dc695529e11050899 Mon Sep 17 00:00:00 2001
From: 1024jp <1024jp@wolfrosch.com>
Date: Sun, 21 Apr 2024 01:23:33 +0900
Subject: [PATCH 008/191] Use .onKeyPress(_:)
---
CotEditor/Sources/CommandBarView.swift | 43 ++++++--------------------
1 file changed, 9 insertions(+), 34 deletions(-)
diff --git a/CotEditor/Sources/CommandBarView.swift b/CotEditor/Sources/CommandBarView.swift
index ba03d9301..5543daa00 100644
--- a/CotEditor/Sources/CommandBarView.swift
+++ b/CotEditor/Sources/CommandBarView.swift
@@ -48,8 +48,6 @@ struct CommandBarView: View {
weak var parent: NSWindow?
- @Environment(\.controlActiveState) private var controlActiveState
-
@State private var input: String = ""
@State var candidates: [Candidate] = []
@@ -57,8 +55,6 @@ struct CommandBarView: View {
@FocusState private var focus: ActionCommand.ID?
@AccessibilityFocusState private var accessibilityFocus: ActionCommand.ID?
- @State private var keyMonitor: Any?
-
var body: some View {
@@ -114,34 +110,11 @@ struct CommandBarView: View {
.sorted(\.score)
self.selection = self.candidates.first?.id
}
- .onChange(of: self.controlActiveState) { (_, newValue) in
- switch newValue {
- case .key, .active:
- self.keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in
- if let key = event.specialKey,
- event.modifierFlags.isDisjoint(with: [.shift, .control, .option, .command])
- {
- switch key {
- case .downArrow, .upArrow:
- self.move(down: (key == .downArrow))
- return nil
- default:
- break
- }
- }
- return event
- }
-
- case .inactive:
- self.input = ""
- if let keyMonitor {
- NSEvent.removeMonitor(keyMonitor)
- self.keyMonitor = nil
- }
-
- @unknown default:
- break
- }
+ .onKeyPress(.upArrow) {
+ self.move(down: false) ? .handled : .ignored
+ }
+ .onKeyPress(.downArrow) {
+ self.move(down: true) ? .handled : .ignored
}
.frame(width: 500)
.ignoresSafeArea()
@@ -151,16 +124,18 @@ struct CommandBarView: View {
/// Moves the selection to the next one, if any exists.
///
/// - Parameter down: Whether move down or up the selection.
- private func move(down: Bool) {
+ /// - Returns: Whether the move action is performed.
+ private func move(down: Bool) -> Bool {
guard
let index = self.candidates.firstIndex(where: { $0.id == self.selection }),
let candidate = self.candidates[safe: index + (down ? 1 : -1)]
- else { return }
+ else { return false }
self.selection = candidate.id
self.focus = candidate.id
self.accessibilityFocus = candidate.id
+ return true
}
From 47262da434dd5e0b0d122ef838bff868c154b3ed Mon Sep 17 00:00:00 2001
From: 1024jp <1024jp@wolfrosch.com>
Date: Sun, 21 Apr 2024 02:11:53 +0900
Subject: [PATCH 009/191] Use @ViewLoading
---
.../Sources/ConsolePanelController.swift | 13 +++---
.../Sources/DocumentViewController.swift | 6 +--
.../Sources/EditorTextViewController.swift | 40 +++++++++----------
CotEditor/Sources/EditorViewController.swift | 8 ++--
.../FindPanelContentViewController.swift | 22 +++++-----
5 files changed, 41 insertions(+), 48 deletions(-)
diff --git a/CotEditor/Sources/ConsolePanelController.swift b/CotEditor/Sources/ConsolePanelController.swift
index 2b9d692a6..5e7ad4e6b 100644
--- a/CotEditor/Sources/ConsolePanelController.swift
+++ b/CotEditor/Sources/ConsolePanelController.swift
@@ -136,7 +136,7 @@ private final class ConsoleViewController: NSViewController {
// MARK: Private Properties
- private weak var textView: NSTextView?
+ @ViewLoading private var textView: NSTextView
private var fontSize: Double = max(UserDefaults.standard[.consoleFontSize], NSFont.smallSystemFontSize) {
@@ -168,8 +168,7 @@ private final class ConsoleViewController: NSViewController {
/// - Parameter log: The log to append.
func append(log: Console.Log) {
- guard let textView = self.textView else { return assertionFailure() }
-
+ let textView = self.textView
let lastLocation = textView.string.length
let attributedString = log.attributedString(fontSize: self.fontSize)
let range = NSRange(location: lastLocation, length: attributedString.length)
@@ -187,10 +186,8 @@ private final class ConsoleViewController: NSViewController {
/// Flushes existing log.
@IBAction func clearAll(_ sender: Any?) {
- guard let textView = self.textView else { return assertionFailure() }
-
- textView.string = ""
- NSAccessibility.post(element: textView, notification: .valueChanged)
+ self.textView.string = ""
+ NSAccessibility.post(element: self.textView, notification: .valueChanged)
}
@@ -225,7 +222,7 @@ private final class ConsoleViewController: NSViewController {
/// - Parameter fontSize: The new font size.
private func changeFontSize(_ fontSize: Double) {
- guard let storage = self.textView?.textStorage else { return }
+ guard let storage = self.textView.textStorage else { return }
storage.beginEditing()
storage.enumerateAttribute(.consolePart, type: Console.Log.Part.self, in: storage.range) { (part, range, _) in
diff --git a/CotEditor/Sources/DocumentViewController.swift b/CotEditor/Sources/DocumentViewController.swift
index ca616cc53..c56bce7be 100644
--- a/CotEditor/Sources/DocumentViewController.swift
+++ b/CotEditor/Sources/DocumentViewController.swift
@@ -66,7 +66,7 @@ final class DocumentViewController: NSSplitViewController, ThemeChanging, NSTool
private lazy var splitViewController = SplitViewController()
private lazy var statusBarModel = StatusBar.Model(document: self.document)
- private weak var statusBarItem: NSSplitViewItem?
+ @ViewLoading private var statusBarItem: NSSplitViewItem
private var documentSyntaxObserver: AnyCancellable?
private var outlineObserver: AnyCancellable?
@@ -136,7 +136,7 @@ final class DocumentViewController: NSSplitViewController, ThemeChanging, NSTool
self.setTheme(name: ThemeManager.shared.userDefaultSettingName)
self.defaultsObservers = [
defaults.publisher(for: .showStatusBar, initial: false)
- .sink { [weak self] in self?.statusBarItem?.animator().isCollapsed = !$0 },
+ .sink { [weak self] in self?.statusBarItem.animator().isCollapsed = !$0 },
defaults.publisher(for: .theme, initial: false)
.sink { [weak self] in self?.setTheme(name: $0) },
defaults.publisher(for: .showInvisibles, initial: true)
@@ -258,7 +258,7 @@ final class DocumentViewController: NSSplitViewController, ThemeChanging, NSTool
: String(localized: "Show Line Numbers", table: "MainMenu")
case #selector(toggleStatusBar):
- (item as? NSMenuItem)?.title = self.statusBarItem?.isCollapsed == false
+ (item as? NSMenuItem)?.title = !self.statusBarItem.isCollapsed
? String(localized: "Hide Status Bar", table: "MainMenu")
: String(localized: "Show Status Bar", table: "MainMenu")
diff --git a/CotEditor/Sources/EditorTextViewController.swift b/CotEditor/Sources/EditorTextViewController.swift
index 3e4938399..31653610f 100644
--- a/CotEditor/Sources/EditorTextViewController.swift
+++ b/CotEditor/Sources/EditorTextViewController.swift
@@ -40,13 +40,13 @@ final class EditorTextViewController: NSViewController, NSServicesMenuRequestor,
// MARK: Public Properties
- private(set) weak var textView: EditorTextView?
+ @ViewLoading private(set) var textView: EditorTextView
// MARK: Private Properties
private var stackView: NSStackView? { self.view as? NSStackView }
- private weak var lineNumberView: LineNumberView?
+ @ViewLoading private var lineNumberView: LineNumberView
private weak var advancedCounterView: NSView?
private weak var horizontalCounterConstraint: NSLayoutConstraint?
@@ -91,29 +91,29 @@ final class EditorTextViewController: NSViewController, NSServicesMenuRequestor,
self.identifier = NSUserInterfaceItemIdentifier("EditorTextViewController")
// observe text orientation for line number view
- self.orientationObserver = self.textView!.publisher(for: \.layoutOrientation, options: .initial)
+ self.orientationObserver = self.textView.publisher(for: \.layoutOrientation, options: .initial)
.sink { [weak self] orientation in
self?.stackView?.orientation = switch orientation {
case .horizontal: .horizontal
case .vertical: .vertical
@unknown default: fatalError()
}
- self?.lineNumberView?.orientation = orientation
+ self?.lineNumberView.orientation = orientation
}
// let line number view position follow writing direction
- self.writingDirectionObserver = self.textView!.publisher(for: \.baseWritingDirection)
+ self.writingDirectionObserver = self.textView.publisher(for: \.baseWritingDirection)
.removeDuplicates()
.map { ($0 == .rightToLeft) ? NSUserInterfaceLayoutDirection.rightToLeft : .leftToRight }
.sink { [weak self] direction in
self?.stackView?.userInterfaceLayoutDirection = direction
- (self?.textView?.enclosingScrollView as? BidiScrollView)?.scrollerDirection = direction
+ (self?.textView.enclosingScrollView as? BidiScrollView)?.scrollerDirection = direction
}
// toggle visibility of the separator of the line number view
self.defaultsObservers = [
UserDefaults.standard.publisher(for: .showLineNumberSeparator, initial: true)
- .assign(to: \.drawsSeparator, on: self.lineNumberView!),
+ .assign(to: \.drawsSeparator, on: self.lineNumberView),
]
}
@@ -216,7 +216,7 @@ final class EditorTextViewController: NSViewController, NSServicesMenuRequestor,
}
// add "Inspect Character" menu item if single character is selected
- if self.textView?.selectsSingleCharacter == true {
+ if self.textView.selectsSingleCharacter == true {
menu.insertItem(withTitle: String(localized: "Inspect Character", table: "MainMenu"),
action: #selector(showSelectionInfo),
keyEquivalent: "",
@@ -233,8 +233,7 @@ final class EditorTextViewController: NSViewController, NSServicesMenuRequestor,
/// Shows the Go To sheet.
@IBAction func gotoLocation(_ sender: Any?) {
- guard let textView = self.textView else { return assertionFailure() }
-
+ let textView = self.textView
let string = textView.string
let lineNumber = string.lineNumber(at: textView.selectedRange.location)
let lineCount = (string as NSString).substring(with: textView.selectedRange).numberOfLines
@@ -257,8 +256,7 @@ final class EditorTextViewController: NSViewController, NSServicesMenuRequestor,
/// Shows the Unicode input view.
@IBAction func showUnicodeInputPanel(_ sender: Any?) {
- guard let textView = self.textView else { return assertionFailure() }
-
+ let textView = self.textView
let view = UnicodeInputView { [unowned textView] character in
// flag to skip line ending sanitization
textView.isApprovedTextChange = true
@@ -300,9 +298,8 @@ final class EditorTextViewController: NSViewController, NSServicesMenuRequestor,
@IBAction func showSelectionInfo(_ sender: Any?) {
guard
- let textView = self.textView,
- textView.selectsSingleCharacter,
- let character = textView.selectedString.first
+ self.textView.selectsSingleCharacter,
+ let character = self.textView.selectedString.first
else { return assertionFailure() }
let characterInfo = CharacterInfo(character: character)
@@ -310,6 +307,7 @@ final class EditorTextViewController: NSViewController, NSServicesMenuRequestor,
popoverController.view = NSHostingView(rootView: CharacterInspectorView(info: characterInfo))
popoverController.view.frame.size = popoverController.view.intrinsicContentSize
+ let textView = self.textView
let positioningRect = textView.boundingRect(for: textView.selectedRange)?.insetBy(dx: -4, dy: -4) ?? .zero
textView.scrollRangeToVisible(textView.selectedRange)
@@ -324,8 +322,8 @@ final class EditorTextViewController: NSViewController, NSServicesMenuRequestor,
/// The visibility of the line number view.
var showsLineNumber: Bool {
- get { self.lineNumberView?.isHidden == false }
- set { self.lineNumberView?.isHidden = !newValue }
+ get { self.lineNumberView.isHidden == false }
+ set { self.lineNumberView.isHidden = !newValue }
}
@@ -337,8 +335,7 @@ final class EditorTextViewController: NSViewController, NSServicesMenuRequestor,
/// - Parameter image: The image to scan text.
private func popoverLiveText(image: NSImage) {
- guard let textView = self.textView else { return assertionFailure() }
-
+ let textView = self.textView
let rootView = LiveTextInsertionView(image: image) { [weak textView] string in
guard let textView else { return }
textView.replace(with: string, range: textView.selectedRange, selectedRange: nil)
@@ -374,8 +371,7 @@ final class EditorTextViewController: NSViewController, NSServicesMenuRequestor,
/// Sets and shows advanced character counter.
private func showAdvancedCharacterCounter() {
- guard let textView = self.textView else { return assertionFailure() }
-
+ let textView = self.textView
let counter = AdvancedCharacterCounter()
counter.observe(textView: textView)
let rootView = AdvancedCharacterCounterView(counter: counter) { [weak self] in
@@ -409,7 +405,7 @@ extension EditorTextViewController: NSUserInterfaceValidations {
return true
case #selector(showSelectionInfo):
- return self.textView?.selectsSingleCharacter == true
+ return self.textView.selectsSingleCharacter == true
case nil:
return false
diff --git a/CotEditor/Sources/EditorViewController.swift b/CotEditor/Sources/EditorViewController.swift
index 29c4fedac..aa3eb4565 100644
--- a/CotEditor/Sources/EditorViewController.swift
+++ b/CotEditor/Sources/EditorViewController.swift
@@ -46,7 +46,7 @@ final class EditorViewController: NSSplitViewController {
private lazy var navigationBarController: NavigationBarController = NSStoryboard(name: "NavigationBar", bundle: nil).instantiateInitialController()!
private lazy var textViewController = EditorTextViewController()
- private var navigationBarItem: NSSplitViewItem?
+ @ViewLoading private var navigationBarItem: NSSplitViewItem
private var syntaxName: String?
private var defaultObservers: [AnyCancellable] = []
@@ -85,7 +85,7 @@ final class EditorViewController: NSSplitViewController {
navigationBarItem.isCollapsed = !UserDefaults.standard[.showNavigationBar]
self.defaultObservers = [
UserDefaults.standard.publisher(for: .showNavigationBar)
- .sink { [weak self] in self?.navigationBarItem?.animator().isCollapsed = !$0 },
+ .sink { [weak self] in self?.navigationBarItem.animator().isCollapsed = !$0 },
UserDefaults.standard.publisher(for: .modes)
.sink { [weak self] _ in self?.invalidateMode() },
]
@@ -111,7 +111,7 @@ final class EditorViewController: NSSplitViewController {
switch item.action {
case #selector(toggleNavigationBar):
- (item as? NSMenuItem)?.title = self.navigationBarItem?.isCollapsed == false
+ (item as? NSMenuItem)?.title = !self.navigationBarItem.isCollapsed
? String(localized: "Hide Navigation Bar", table: "MainMenu")
: String(localized: "Show Navigation Bar", table: "MainMenu")
@@ -186,7 +186,7 @@ final class EditorViewController: NSSplitViewController {
/// Shows the menu items of the outline menu in the navigation bar.
@IBAction func openOutlineMenu(_ sender: Any) {
- self.navigationBarItem?.isCollapsed = false
+ self.navigationBarItem.isCollapsed = false
self.navigationBarController.openOutlineMenu()
}
diff --git a/CotEditor/Sources/FindPanelContentViewController.swift b/CotEditor/Sources/FindPanelContentViewController.swift
index 607e3ccb5..e6c74698e 100644
--- a/CotEditor/Sources/FindPanelContentViewController.swift
+++ b/CotEditor/Sources/FindPanelContentViewController.swift
@@ -8,7 +8,7 @@
//
// ---------------------------------------------------------------------------
//
-// © 2014-2023 1024jp
+// © 2014-2024 1024jp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -33,8 +33,8 @@ final class FindPanelContentViewController: NSSplitViewController {
private static let defaultResultViewHeight: Double = 200
- private var fieldSplitViewItem: NSSplitViewItem?
- private var resultSplitViewItem: NSSplitViewItem?
+ @ViewLoading private var fieldSplitViewItem: NSSplitViewItem
+ @ViewLoading private var resultSplitViewItem: NSSplitViewItem
private var resultObserver: AnyCancellable?
@@ -87,7 +87,7 @@ final class FindPanelContentViewController: NSSplitViewController {
super.viewWillAppear()
- self.fieldSplitViewItem?.holdingPriority = .defaultLow + 1
+ self.fieldSplitViewItem.holdingPriority = .defaultLow + 1
}
@@ -96,8 +96,8 @@ final class FindPanelContentViewController: NSSplitViewController {
super.viewWillDisappear()
- self.fieldSplitViewItem?.holdingPriority = .defaultHigh
- self.resultSplitViewItem?.isCollapsed = true
+ self.fieldSplitViewItem.holdingPriority = .defaultHigh
+ self.resultSplitViewItem.isCollapsed = true
}
@@ -106,8 +106,8 @@ final class FindPanelContentViewController: NSSplitViewController {
super.splitViewDidResizeSubviews(notification)
// collapse result view if closed
- if let item = self.resultSplitViewItem,
- item.viewController.isViewShown,
+ let item = self.resultSplitViewItem
+ if item.viewController.isViewShown,
item.viewController.view.frame.height < 1
{
item.isCollapsed = true
@@ -118,7 +118,7 @@ final class FindPanelContentViewController: NSSplitViewController {
override func splitView(_ splitView: NSSplitView, effectiveRect proposedEffectiveRect: NSRect, forDrawnRect drawnRect: NSRect, ofDividerAt dividerIndex: Int) -> NSRect {
// avoid showing draggable cursor when result view collapsed
- (self.resultSplitViewItem?.isCollapsed == true) ? .zero : proposedEffectiveRect
+ self.resultSplitViewItem.isCollapsed ? .zero : proposedEffectiveRect
}
@@ -138,7 +138,7 @@ final class FindPanelContentViewController: NSSplitViewController {
/// The view controller for the result view.
private var resultViewController: FindPanelResultViewController? {
- self.resultSplitViewItem?.viewController as? FindPanelResultViewController
+ self.resultSplitViewItem.viewController as? FindPanelResultViewController
}
@@ -163,7 +163,7 @@ final class FindPanelContentViewController: NSSplitViewController {
/// - Parameter shown: `true` to open the result view; otherwise, `false`.
private func setResultShown(_ shown: Bool) {
- guard let item = self.resultSplitViewItem else { return assertionFailure() }
+ let item = self.resultSplitViewItem
if shown {
item.viewController.view.frame.size.height.clamp(to: Self.defaultResultViewHeight...(.infinity))
From 580a90013591c4d0269c2e2badd4bd204b41427b Mon Sep 17 00:00:00 2001
From: 1024jp <1024jp@wolfrosch.com>
Date: Sun, 21 Apr 2024 02:19:53 +0900
Subject: [PATCH 010/191] Use more .foregroundStyle
---
CotEditor/Sources/SyntaxOutlineEditView.swift | 2 +-
CotEditor/Sources/UnicodeInputView.swift | 2 +-
CotEditor/Sources/WindowSettingsView.swift | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/CotEditor/Sources/SyntaxOutlineEditView.swift b/CotEditor/Sources/SyntaxOutlineEditView.swift
index 5ad28c911..ae719ba83 100644
--- a/CotEditor/Sources/SyntaxOutlineEditView.swift
+++ b/CotEditor/Sources/SyntaxOutlineEditView.swift
@@ -103,7 +103,7 @@ struct SyntaxOutlineEditView: View {
.accessibilityLabeledPair(role: .label, id: "titlePattern", in: self.accessibility)
Text("(Blank matches the whole string.)", tableName: "SyntaxEditor", comment: "label")
.controlSize(.small)
- .foregroundColor(.secondary)
+ .foregroundStyle(.secondary)
}
RegexTextField(text: $outline.template, mode: .replacement(unescapes: false), prompt: self.prompt)
diff --git a/CotEditor/Sources/UnicodeInputView.swift b/CotEditor/Sources/UnicodeInputView.swift
index 69c2e141e..060dcff98 100644
--- a/CotEditor/Sources/UnicodeInputView.swift
+++ b/CotEditor/Sources/UnicodeInputView.swift
@@ -70,7 +70,7 @@ struct UnicodeInputView: View {
.monospacedDigit() +
Text(scalar.name ?? "–")
.font(.system(size: NSFont.smallSystemFontSize))
- .foregroundColor(.secondary)
+ .foregroundStyle(.secondary)
}
}
}
diff --git a/CotEditor/Sources/WindowSettingsView.swift b/CotEditor/Sources/WindowSettingsView.swift
index 358ee1e25..33f80bcbb 100644
--- a/CotEditor/Sources/WindowSettingsView.swift
+++ b/CotEditor/Sources/WindowSettingsView.swift
@@ -72,7 +72,7 @@ struct WindowSettingsView: View {
Picker(selection: $windowTabbing) {
(Text("Respect System Setting", tableName: "WindowSettings") +
- Text(" (\(NSWindow.userTabbingPreference.label))").foregroundColor(.secondary)).tag(-1)
+ Text(" (\(NSWindow.userTabbingPreference.label))").foregroundStyle(.secondary)).tag(-1)
Divider()
From f1073904b6c1df0d90c7e839c8745f8aec3a64b1 Mon Sep 17 00:00:00 2001
From: 1024jp <1024jp@wolfrosch.com>
Date: Sun, 21 Apr 2024 02:28:04 +0900
Subject: [PATCH 011/191] Use .background(.windowBackground)
---
CotEditor/Sources/StatusBar.swift | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CotEditor/Sources/StatusBar.swift b/CotEditor/Sources/StatusBar.swift
index 11643c344..bba247bbf 100644
--- a/CotEditor/Sources/StatusBar.swift
+++ b/CotEditor/Sources/StatusBar.swift
@@ -235,7 +235,7 @@ struct StatusBar: View {
.controlSize(.small)
.padding(.leading, 10)
.frame(height: 21)
- .background(.thinMaterial) // .windowBackground on macOS 14
+ .background(.windowBackground)
}
}
From c4ca7e71cf40f38c3ba43bc9ae5aa907463634e6 Mon Sep 17 00:00:00 2001
From: 1024jp <1024jp@wolfrosch.com>
Date: Sun, 21 Apr 2024 02:35:17 +0900
Subject: [PATCH 012/191] Use .viewIfLoaded
---
CotEditor/Sources/NSViewController.swift | 2 +-
CotEditor/Sources/NSWindow+Responder.swift | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/CotEditor/Sources/NSViewController.swift b/CotEditor/Sources/NSViewController.swift
index f07ec2d9e..6fa847588 100644
--- a/CotEditor/Sources/NSViewController.swift
+++ b/CotEditor/Sources/NSViewController.swift
@@ -33,7 +33,7 @@ extension NSViewController {
/// However, it does not check whether the view is actually visible. For that, see the discussion part of `isHiddenOrHasHiddenAncestor`.
final var isViewShown: Bool {
- self.isViewLoaded && !self.view.isHiddenOrHasHiddenAncestor
+ self.viewIfLoaded?.isHiddenOrHasHiddenAncestor == false
}
diff --git a/CotEditor/Sources/NSWindow+Responder.swift b/CotEditor/Sources/NSWindow+Responder.swift
index 2f9fb65be..46ce3cba3 100644
--- a/CotEditor/Sources/NSWindow+Responder.swift
+++ b/CotEditor/Sources/NSWindow+Responder.swift
@@ -8,7 +8,7 @@
//
// ---------------------------------------------------------------------------
//
-// © 2016-2023 1024jp
+// © 2016-2024 1024jp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -33,7 +33,7 @@ extension NSViewController {
@discardableResult
final func endEditing() -> Bool {
- guard self.isViewLoaded, let window = self.view.window else { return true }
+ guard let window = self.viewIfLoaded?.window else { return true }
return window.makeFirstResponder(nil)
}
From c5c2e21eddf91a46717c4b6ded93eb357cc5c90b Mon Sep 17 00:00:00 2001
From: 1024jp <1024jp@wolfrosch.com>
Date: Sun, 21 Apr 2024 01:31:32 +0900
Subject: [PATCH 013/191] Make EncodingListView.Model @Observable
---
CotEditor/Sources/EncodingListView.swift | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/CotEditor/Sources/EncodingListView.swift b/CotEditor/Sources/EncodingListView.swift
index a0fbdd5f6..0be8abe30 100644
--- a/CotEditor/Sources/EncodingListView.swift
+++ b/CotEditor/Sources/EncodingListView.swift
@@ -24,6 +24,7 @@
//
import SwiftUI
+import Observation
private struct EncodingItem: Identifiable {
@@ -39,12 +40,12 @@ private struct EncodingItem: Identifiable {
struct EncodingListView: View {
- fileprivate final class Model: ObservableObject {
+ @Observable fileprivate final class Model {
typealias Item = EncodingItem
- @Published var items: [Item]
+ var items: [Item]
private let defaults: UserDefaults
@@ -57,7 +58,7 @@ struct EncodingListView: View {
}
- @StateObject private var model = Model()
+ @State private var model = Model()
@Environment(\.undoManager) private var undoManager
@Environment(\.dismiss) private var dismiss
From 92dbd846008a641009503919252294ae330b4099 Mon Sep 17 00:00:00 2001
From: 1024jp <1024jp@wolfrosch.com>
Date: Sun, 21 Apr 2024 03:37:36 +0900
Subject: [PATCH 014/191] Migarete ObservableObjects to Observation
---
.../Sources/AdvancedCharacterCounter.swift | 8 +-
.../AdvancedCharacterCounterView.swift | 2 +-
CotEditor/Sources/CommandBarView.swift | 7 +-
CotEditor/Sources/DocumentInspectorView.swift | 17 +--
CotEditor/Sources/FindPanelResultView.swift | 9 +-
CotEditor/Sources/FindProgress.swift | 11 +-
CotEditor/Sources/FindProgressView.swift | 2 +-
.../Sources/IncompatibleCharactersView.swift | 9 +-
.../Sources/InconsistentLineEndingsView.swift | 9 +-
.../Sources/KeyBindingsSettingsView.swift | 50 ++++----
CotEditor/Sources/OpenPanelAccessory.swift | 7 +-
CotEditor/Sources/OutlineInspectorView.swift | 17 +--
CotEditor/Sources/SavePanelAccessory.swift | 9 +-
CotEditor/Sources/StatusBar.swift | 15 +--
CotEditor/Sources/SyntaxEditView.swift | 4 +-
CotEditor/Sources/SyntaxObject.swift | 118 +++++++++---------
CotEditor/Sources/WarningInspectorView.swift | 5 +-
17 files changed, 160 insertions(+), 139 deletions(-)
diff --git a/CotEditor/Sources/AdvancedCharacterCounter.swift b/CotEditor/Sources/AdvancedCharacterCounter.swift
index a74c8a605..5ac392108 100644
--- a/CotEditor/Sources/AdvancedCharacterCounter.swift
+++ b/CotEditor/Sources/AdvancedCharacterCounter.swift
@@ -24,15 +24,15 @@
//
import AppKit
+import Observation
import Combine
-import SwiftUI
-@MainActor final class AdvancedCharacterCounter: ObservableObject {
+@MainActor @Observable final class AdvancedCharacterCounter {
// MARK: Public Properties
- @Published private(set) var entireCount: Int? = 0
- @Published private(set) var selectionCount: Int? = 0
+ private(set) var entireCount: Int? = 0
+ private(set) var selectionCount: Int? = 0
// MARK: Private Properties
diff --git a/CotEditor/Sources/AdvancedCharacterCounterView.swift b/CotEditor/Sources/AdvancedCharacterCounterView.swift
index 7ba787648..e4d6c929e 100644
--- a/CotEditor/Sources/AdvancedCharacterCounterView.swift
+++ b/CotEditor/Sources/AdvancedCharacterCounterView.swift
@@ -27,7 +27,7 @@ import SwiftUI
struct AdvancedCharacterCounterView: View {
- @StateObject var counter: AdvancedCharacterCounter
+ @State var counter: AdvancedCharacterCounter
let dismissAction: () -> Void
@AppStorage(.countUnit) private var unit: CharacterCountOptions.CharacterUnit
diff --git a/CotEditor/Sources/CommandBarView.swift b/CotEditor/Sources/CommandBarView.swift
index 5543daa00..41bf79743 100644
--- a/CotEditor/Sources/CommandBarView.swift
+++ b/CotEditor/Sources/CommandBarView.swift
@@ -25,12 +25,13 @@
import SwiftUI
import AppKit
+import Observation
struct CommandBarView: View {
- final class Model: ObservableObject {
+ @Observable final class Model {
- @Published var commands: [ActionCommand] = []
+ var commands: [ActionCommand] = []
}
@@ -43,7 +44,7 @@ struct CommandBarView: View {
}
- let model: Model
+ @State var model: Model
weak var parent: NSWindow?
diff --git a/CotEditor/Sources/DocumentInspectorView.swift b/CotEditor/Sources/DocumentInspectorView.swift
index 92cc697e5..77e2c656e 100644
--- a/CotEditor/Sources/DocumentInspectorView.swift
+++ b/CotEditor/Sources/DocumentInspectorView.swift
@@ -24,6 +24,7 @@
//
import SwiftUI
+import Observation
import Combine
final class DocumentInspectorViewController: NSHostingController, DocumentOwner {
@@ -81,14 +82,14 @@ final class DocumentInspectorViewController: NSHostingController {
@@ -59,15 +60,15 @@ struct FindPanelResultView: View {
typealias Match = TextFindAllResult.Match
- @MainActor final class Model: ObservableObject {
+ @MainActor @Observable final class Model {
- @Published var matches: [Match] = []
- @Published var findString: String = ""
+ var matches: [Match] = []
+ var findString: String = ""
weak var target: NSTextView?
}
- @ObservedObject var model: Model
+ @State var model: Model
@State private var selection: Set = []
@State private var sortOrder = [KeyPathComparator(\Match.range.location)]
diff --git a/CotEditor/Sources/FindProgress.swift b/CotEditor/Sources/FindProgress.swift
index c06ccbe3d..754b0376e 100644
--- a/CotEditor/Sources/FindProgress.swift
+++ b/CotEditor/Sources/FindProgress.swift
@@ -24,14 +24,15 @@
//
import Foundation
+import Observation
-final class FindProgress: ObservableObject, @unchecked Sendable {
+@Observable final class FindProgress: @unchecked Sendable {
- private(set) var count = 0
- var completedUnit = 0
+ @ObservationIgnored private(set) var count = 0
+ @ObservationIgnored var completedUnit = 0
- @Published private(set) var isCancelled = false
- @Published private(set) var isFinished = false
+ private(set) var isCancelled = false
+ private(set) var isFinished = false
private let scope: Range
diff --git a/CotEditor/Sources/FindProgressView.swift b/CotEditor/Sources/FindProgressView.swift
index 0eacc2269..0f9a07910 100644
--- a/CotEditor/Sources/FindProgressView.swift
+++ b/CotEditor/Sources/FindProgressView.swift
@@ -36,7 +36,7 @@ struct FindProgressView: View {
weak var parent: NSHostingController?
- @ObservedObject private var progress: FindProgress
+ @State private var progress: FindProgress
private let unit: Unit
private let label: String
diff --git a/CotEditor/Sources/IncompatibleCharactersView.swift b/CotEditor/Sources/IncompatibleCharactersView.swift
index e4b2f8d58..b219325f9 100644
--- a/CotEditor/Sources/IncompatibleCharactersView.swift
+++ b/CotEditor/Sources/IncompatibleCharactersView.swift
@@ -24,18 +24,19 @@
//
import SwiftUI
+import Observation
import Combine
import AppKit.NSTextStorage
struct IncompatibleCharactersView: View {
- @MainActor final class Model: ObservableObject {
+ @MainActor @Observable final class Model {
typealias Item = ValueRange
- @Published var items: [Item] = []
- @Published private(set) var isScanning = false
+ var items: [Item] = []
+ private(set) var isScanning = false
var document: Document? { didSet { self.invalidateObservation() } }
@@ -44,7 +45,7 @@ struct IncompatibleCharactersView: View {
}
- @ObservedObject var model: Model
+ @State var model: Model
@State private var selection: Model.Item.ID?
@State private var sortOrder: [KeyPathComparator] = []
diff --git a/CotEditor/Sources/InconsistentLineEndingsView.swift b/CotEditor/Sources/InconsistentLineEndingsView.swift
index 1dd89277d..0e795cf5e 100644
--- a/CotEditor/Sources/InconsistentLineEndingsView.swift
+++ b/CotEditor/Sources/InconsistentLineEndingsView.swift
@@ -24,17 +24,18 @@
//
import SwiftUI
+import Observation
import Combine
struct InconsistentLineEndingsView: View {
- @MainActor final class Model: ObservableObject {
+ @MainActor @Observable final class Model {
typealias Item = ValueRange
- @Published var items: [Item] = []
- @Published var lineEnding: LineEnding = .lf
+ var items: [Item] = []
+ var lineEnding: LineEnding = .lf
var document: Document? { didSet { self.invalidateObservation() } }
@@ -42,7 +43,7 @@ struct InconsistentLineEndingsView: View {
}
- @ObservedObject var model: Model
+ @State var model: Model
@State private var selection: Model.Item.ID?
@State private var sortOrder: [KeyPathComparator] = []
diff --git a/CotEditor/Sources/KeyBindingsSettingsView.swift b/CotEditor/Sources/KeyBindingsSettingsView.swift
index ffeb2e6e1..a0d35e67f 100644
--- a/CotEditor/Sources/KeyBindingsSettingsView.swift
+++ b/CotEditor/Sources/KeyBindingsSettingsView.swift
@@ -25,12 +25,12 @@
import SwiftUI
import AppKit
-import Combine
+import Observation
import OSLog
struct KeyBindingsSettingsView: View {
- @StateObject private var model = KeyBindingModel()
+ @State private var model = KeyBindingModel()
var body: some View {
@@ -40,7 +40,7 @@ struct KeyBindingsSettingsView: View {
.lineLimit(10)
.fixedSize(horizontal: false, vertical: true)
- KeyBindingTreeView(model: self.model)
+ KeyBindingTreeView(model: $model)
HStack(alignment: .firstTextBaseline) {
Button(String(localized: "Restore Defaults", table: "KeyBindingsSettings", comment: "button label")) {
@@ -68,15 +68,15 @@ struct KeyBindingsSettingsView: View {
}
-private final class KeyBindingModel: ObservableObject {
+@Observable private final class KeyBindingModel {
typealias Item = Node
- @Published private(set) var tree: [Item] = []
- @Published private(set) var isRestorable: Bool = false
- @Published var error: (any Error)?
+ private(set) var tree: [Item] = []
+ private(set) var isRestorable: Bool = false
+ var error: (any Error)?
- @Published var rootIndex: Int?
+ var rootIndex: Int?
/// Loads data from the user defaults.
@@ -122,7 +122,7 @@ private struct KeyBindingTreeView: NSViewControllerRepresentable {
typealias NSViewControllerType = NSViewController
- @ObservedObject var model: KeyBindingModel
+ @Binding var model: KeyBindingModel
func makeNSViewController(context: Context) -> NSViewController {
@@ -156,8 +156,6 @@ final class KeyBindingTreeViewController: NSViewController, NSOutlineViewDataSou
private let model: KeyBindingModel
- private var observer: AnyCancellable?
-
@IBOutlet private weak var listView: NSTableView?
@IBOutlet private weak var outlineView: NSOutlineView?
@@ -186,18 +184,7 @@ final class KeyBindingTreeViewController: NSViewController, NSOutlineViewDataSou
self.listView?.reloadData()
self.outlineView?.reloadData()
- // observe restoration
- self.observer = self.model.$isRestorable
- .filter { !$0 }
- .sink { [weak self] _ in self?.outlineView?.reloadData() }
- }
-
-
- override func viewDidDisappear() {
-
- super.viewDidDisappear()
-
- self.observer?.cancel()
+ self.observe()
}
@@ -314,6 +301,23 @@ final class KeyBindingTreeViewController: NSViewController, NSOutlineViewDataSou
self.model.save()
outlineView.reloadData(forRowIndexes: [row], columnIndexes: [column])
}
+
+
+ // MARK: Private Methods
+
+ /// Recursively observes the `.isRestorable` flag.
+ private func observe() {
+
+ withObservationTracking { [weak self] in
+ if self?.model.isRestorable == false {
+ self?.outlineView?.reloadData()
+ }
+ } onChange: {
+ Task { @MainActor [weak self] in
+ self?.observe()
+ }
+ }
+ }
}
diff --git a/CotEditor/Sources/OpenPanelAccessory.swift b/CotEditor/Sources/OpenPanelAccessory.swift
index 26e35bea2..5ec9b430a 100644
--- a/CotEditor/Sources/OpenPanelAccessory.swift
+++ b/CotEditor/Sources/OpenPanelAccessory.swift
@@ -24,17 +24,18 @@
//
import SwiftUI
+import Observation
import AppKit.NSOpenPanel
-final class OpenOptions: ObservableObject {
+@Observable final class OpenOptions {
- @Published var encoding: String.Encoding?
+ var encoding: String.Encoding?
}
struct OpenPanelAccessory: View {
- @ObservedObject var options: OpenOptions
+ @State var options: OpenOptions
weak var openPanel: NSOpenPanel?
let fileEncodings: [FileEncoding?]
diff --git a/CotEditor/Sources/OutlineInspectorView.swift b/CotEditor/Sources/OutlineInspectorView.swift
index f2e09ed7d..ddb2b2c31 100644
--- a/CotEditor/Sources/OutlineInspectorView.swift
+++ b/CotEditor/Sources/OutlineInspectorView.swift
@@ -24,6 +24,7 @@
//
import SwiftUI
+import Observation
import Combine
final class OutlineInspectorViewController: NSHostingController, DocumentOwner {
@@ -81,13 +82,13 @@ final class OutlineInspectorViewController: NSHostingController {
@@ -143,15 +144,15 @@ private extension UserDefaults {
struct StatusBar: View {
- final class Model: ObservableObject {
+ @MainActor @Observable final class Model {
- @MainActor var document: Document? { didSet { Task { @MainActor in self.observeDocument() } } }
+ var document: Document? { didSet { Task { @MainActor in self.observeDocument() } } }
- @Published var fileEncoding: FileEncoding = .utf8
- @Published var lineEnding: LineEnding = .lf
+ var fileEncoding: FileEncoding = .utf8
+ var lineEnding: LineEnding = .lf
- @Published fileprivate(set) var countResult: EditorCounter.Result = .init()
- @Published fileprivate(set) var fileSize: Int64?
+ fileprivate(set) var countResult: EditorCounter.Result = .init()
+ fileprivate(set) var fileSize: Int64?
private var defaultsObserver: AnyCancellable?
private var documentObservers: Set = []
@@ -164,7 +165,7 @@ struct StatusBar: View {
}
- @ObservedObject var model: Model
+ @State var model: Model
@State private(set) var fileEncodings: [FileEncoding?] = []
diff --git a/CotEditor/Sources/SyntaxEditView.swift b/CotEditor/Sources/SyntaxEditView.swift
index ab32c0075..e916f578e 100644
--- a/CotEditor/Sources/SyntaxEditView.swift
+++ b/CotEditor/Sources/SyntaxEditView.swift
@@ -56,7 +56,7 @@ struct SyntaxEditView: View {
}
- @StateObject var syntax: SyntaxObject
+ @State var syntax: SyntaxObject
var originalName: String?
var isBundled: Bool = false
let saveAction: SaveAction
@@ -79,7 +79,7 @@ struct SyntaxEditView: View {
init(syntax: Syntax? = nil, originalName: String? = nil, isBundled: Bool = false, saveAction: @escaping SaveAction) {
- self._syntax = StateObject(wrappedValue: SyntaxObject(value: syntax))
+ self._syntax = State(wrappedValue: SyntaxObject(value: syntax))
self.originalName = originalName
self.isBundled = isBundled
self.saveAction = saveAction
diff --git a/CotEditor/Sources/SyntaxObject.swift b/CotEditor/Sources/SyntaxObject.swift
index 8dcb86eda..e91723035 100644
--- a/CotEditor/Sources/SyntaxObject.swift
+++ b/CotEditor/Sources/SyntaxObject.swift
@@ -24,69 +24,39 @@
//
import Foundation
+import Observation
-final class SyntaxObject: ObservableObject {
+@Observable final class SyntaxObject {
+ typealias Highlight = SyntaxObjectHighlight
+ typealias Outline = SyntaxObjectOutline
+ typealias KeyString = SyntaxObjectKeyString
typealias Comment = Syntax.Comment
typealias Metadata = Syntax.Metadata
- struct Highlight: Identifiable, EmptyInitializable {
-
- let id = UUID()
-
- var begin: String = ""
- var end: String?
- var isRegularExpression: Bool = false
- var ignoreCase: Bool = false
- var description: String?
- }
+ var kind: Syntax.Kind = .general
+ var keywords: [Highlight] = []
+ var commands: [Highlight] = []
+ var types: [Highlight] = []
+ var attributes: [Highlight] = []
+ var variables: [Highlight] = []
+ var values: [Highlight] = []
+ var numbers: [Highlight] = []
+ var strings: [Highlight] = []
+ var characters: [Highlight] = []
+ var comments: [Highlight] = []
- struct Outline: Identifiable, EmptyInitializable {
-
- let id = UUID()
-
- var pattern: String = ""
- var template: String = ""
- var ignoreCase: Bool = false
- var bold: Bool = false
- var italic: Bool = false
- var underline: Bool = false
- var description: String?
- }
+ var commentDelimiters: Comment = Comment()
+ var outlines: [Outline] = []
+ var completions: [KeyString] = []
+ var filenames: [KeyString] = []
+ var extensions: [KeyString] = []
+ var interpreters: [KeyString] = []
- struct KeyString: Identifiable, EmptyInitializable {
-
- let id = UUID()
-
- var string: String = ""
- }
-
-
- @Published var kind: Syntax.Kind = .general
-
- @Published var keywords: [Highlight] = []
- @Published var commands: [Highlight] = []
- @Published var types: [Highlight] = []
- @Published var attributes: [Highlight] = []
- @Published var variables: [Highlight] = []
- @Published var values: [Highlight] = []
- @Published var numbers: [Highlight] = []
- @Published var strings: [Highlight] = []
- @Published var characters: [Highlight] = []
- @Published var comments: [Highlight] = []
-
- @Published var commentDelimiters: Comment = Comment()
- @Published var outlines: [Outline] = []
- @Published var completions: [KeyString] = []
-
- @Published var filenames: [KeyString] = []
- @Published var extensions: [KeyString] = []
- @Published var interpreters: [KeyString] = []
-
- @Published var metadata: Metadata = Metadata()
+ var metadata: Metadata = Metadata()
static func highlightKeyPath(for type: SyntaxType) -> WritableKeyPath {
@@ -107,6 +77,40 @@ final class SyntaxObject: ObservableObject {
}
+struct SyntaxObjectHighlight: Identifiable, EmptyInitializable {
+
+ let id = UUID()
+
+ var begin: String = ""
+ var end: String?
+ var isRegularExpression: Bool = false
+ var ignoreCase: Bool = false
+ var description: String?
+}
+
+
+struct SyntaxObjectOutline: Identifiable, EmptyInitializable {
+
+ let id = UUID()
+
+ var pattern: String = ""
+ var template: String = ""
+ var ignoreCase: Bool = false
+ var bold: Bool = false
+ var italic: Bool = false
+ var underline: Bool = false
+ var description: String?
+}
+
+
+struct SyntaxObjectKeyString: Identifiable, EmptyInitializable {
+
+ let id = UUID()
+
+ var string: String = ""
+}
+
+
// MARK: Definition Conversion
@@ -178,7 +182,7 @@ extension SyntaxObject {
}
-extension SyntaxObject.Highlight {
+extension SyntaxObjectHighlight {
typealias Value = Syntax.Highlight
@@ -203,7 +207,7 @@ extension SyntaxObject.Highlight {
}
-extension SyntaxObject.Outline {
+extension SyntaxObjectOutline {
typealias Value = Syntax.Outline
@@ -232,7 +236,7 @@ extension SyntaxObject.Outline {
}
-extension SyntaxObject.KeyString {
+extension SyntaxObjectKeyString {
typealias Value = String
@@ -249,7 +253,7 @@ extension SyntaxObject.KeyString {
}
-extension SyntaxObject.Highlight: Equatable {
+extension SyntaxObjectHighlight: Equatable {
static func == (lhs: Self, rhs: Self) -> Bool {
diff --git a/CotEditor/Sources/WarningInspectorView.swift b/CotEditor/Sources/WarningInspectorView.swift
index 559155717..214e784f6 100644
--- a/CotEditor/Sources/WarningInspectorView.swift
+++ b/CotEditor/Sources/WarningInspectorView.swift
@@ -24,6 +24,7 @@
//
import SwiftUI
+import Observation
final class WarningInspectorViewController: NSHostingController, DocumentOwner {
@@ -80,7 +81,7 @@ final class WarningInspectorViewController: NSHostingController
Date: Tue, 30 Apr 2024 13:58:23 +0900
Subject: [PATCH 015/191] Update some custom symbols to Xcode 15 format
---
.../Symbols/emoji.symbolset/emoji.dark.svg | 46 ++++++++-------
.../Symbols/emoji.symbolset/emoji.svg | 57 ++++++++++---------
.../paragraphsign.slash.svg | 43 +++++++-------
.../text.vertical.symbolset/text.vertical.svg | 20 +++----
.../uiwindow.opacity.svg | 46 ++++++++-------
5 files changed, 116 insertions(+), 96 deletions(-)
diff --git a/CotEditor/Assets.xcassets/Symbols/emoji.symbolset/emoji.dark.svg b/CotEditor/Assets.xcassets/Symbols/emoji.symbolset/emoji.dark.svg
index 69492ecf2..63e8faa67 100644
--- a/CotEditor/Assets.xcassets/Symbols/emoji.symbolset/emoji.dark.svg
+++ b/CotEditor/Assets.xcassets/Symbols/emoji.symbolset/emoji.dark.svg
@@ -1,10 +1,18 @@
-
+
-
+
+
@@ -19,39 +27,39 @@ PUBLIC "-//W3C//DTD SVG 1.1//EN"
Heavy
Black
-
-
+
+
-
-
+
+
-
-
+
+
Design Variations
Symbols are supported in up to nine weights and three scales.
For optimal layout with text and other symbols, vertically align
symbols with the adjacent text.
-
-
+
+
-
+
Margins
Leading and trailing margins on the left and right side of each symbol
can be adjusted by modifying the x-location of the margin guidelines.
Modifications are automatically applied proportionally to all
scales and weights.
-
-
+
+
Exporting
Symbols should be outlined when exporting to ensure the
design is preserved when submitting to Xcode.
- Template v.3.0
- Requires Xcode 13 or greater
+ Template v.5.0
+ Requires Xcode 15 or greater
Generated from emoji.dark
- Typeset at 100 points
+ Typeset at 100.0 points
Small
Medium
Large
@@ -81,13 +89,13 @@ PUBLIC "-//W3C//DTD SVG 1.1//EN"
-
+
-
+
-
+
diff --git a/CotEditor/Assets.xcassets/Symbols/emoji.symbolset/emoji.svg b/CotEditor/Assets.xcassets/Symbols/emoji.symbolset/emoji.svg
index 6b81aff66..bb41e10ce 100644
--- a/CotEditor/Assets.xcassets/Symbols/emoji.symbolset/emoji.svg
+++ b/CotEditor/Assets.xcassets/Symbols/emoji.symbolset/emoji.svg
@@ -1,15 +1,20 @@
-
+
-
-
@@ -25,39 +30,39 @@ PUBLIC "-//W3C//DTD SVG 1.1//EN"
Heavy
Black
-
-
+
+
-
-
+
+
-
-
+
+
Design Variations
Symbols are supported in up to nine weights and three scales.
For optimal layout with text and other symbols, vertically align
symbols with the adjacent text.
-
-
+
+
-
+
Margins
Leading and trailing margins on the left and right side of each symbol
can be adjusted by modifying the x-location of the margin guidelines.
Modifications are automatically applied proportionally to all
scales and weights.
-
-
+
+
Exporting
Symbols should be outlined when exporting to ensure the
design is preserved when submitting to Xcode.
- Template v.3.0
- Requires Xcode 13 or greater
+ Template v.5.0
+ Requires Xcode 15 or greater
Generated from emoji
- Typeset at 100 points
+ Typeset at 100.0 points
Small
Medium
Large
@@ -87,16 +92,16 @@ PUBLIC "-//W3C//DTD SVG 1.1//EN"
-
-
+
+
-
-
+
+
-
-
+
+
diff --git a/CotEditor/Assets.xcassets/Symbols/paragraphsign.slash.symbolset/paragraphsign.slash.svg b/CotEditor/Assets.xcassets/Symbols/paragraphsign.slash.symbolset/paragraphsign.slash.svg
index fe8827985..4020c1197 100644
--- a/CotEditor/Assets.xcassets/Symbols/paragraphsign.slash.symbolset/paragraphsign.slash.svg
+++ b/CotEditor/Assets.xcassets/Symbols/paragraphsign.slash.symbolset/paragraphsign.slash.svg
@@ -5,20 +5,19 @@ PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-
@@ -63,8 +62,8 @@ PUBLIC "-//W3C//DTD SVG 1.1//EN"
Exporting
Symbols should be outlined when exporting to ensure the
design is preserved when submitting to Xcode.
- Template v.4.0
- Requires Xcode 14 or greater
+ Template v.5.0
+ Requires Xcode 15 or greater
Generated from
Typeset at 100.0 points
Small
@@ -96,19 +95,19 @@ PUBLIC "-//W3C//DTD SVG 1.1//EN"
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
diff --git a/CotEditor/Assets.xcassets/Symbols/text.vertical.symbolset/text.vertical.svg b/CotEditor/Assets.xcassets/Symbols/text.vertical.symbolset/text.vertical.svg
index 7cc491768..3ee92a340 100644
--- a/CotEditor/Assets.xcassets/Symbols/text.vertical.symbolset/text.vertical.svg
+++ b/CotEditor/Assets.xcassets/Symbols/text.vertical.symbolset/text.vertical.svg
@@ -4,14 +4,14 @@
PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-
-
@@ -56,8 +56,8 @@ PUBLIC "-//W3C//DTD SVG 1.1//EN"
Exporting
Symbols should be outlined when exporting to ensure the
design is preserved when submitting to Xcode.
- Template v.4.0
- Requires Xcode 14 or greater
+ Template v.5.0
+ Requires Xcode 15 or greater
Generated from text.verticalorientation
Typeset at 100.0 points
Small
@@ -89,13 +89,13 @@ PUBLIC "-//W3C//DTD SVG 1.1//EN"
-
+
-
+
-
+
diff --git a/CotEditor/Assets.xcassets/Symbols/uiwindow.opacity.symbolset/uiwindow.opacity.svg b/CotEditor/Assets.xcassets/Symbols/uiwindow.opacity.symbolset/uiwindow.opacity.svg
index aa381b27e..86b39d49c 100644
--- a/CotEditor/Assets.xcassets/Symbols/uiwindow.opacity.symbolset/uiwindow.opacity.svg
+++ b/CotEditor/Assets.xcassets/Symbols/uiwindow.opacity.symbolset/uiwindow.opacity.svg
@@ -1,10 +1,18 @@
-
+
-
+
+
@@ -19,39 +27,39 @@ PUBLIC "-//W3C//DTD SVG 1.1//EN"
Heavy
Black
-
-
+
+
-
-
+
+
-
-
+
+
Design Variations
Symbols are supported in up to nine weights and three scales.
For optimal layout with text and other symbols, vertically align
symbols with the adjacent text.
-
-
+
+
-
+
Margins
Leading and trailing margins on the left and right side of each symbol
can be adjusted by modifying the x-location of the margin guidelines.
Modifications are automatically applied proportionally to all
scales and weights.
-
-
+
+
Exporting
Symbols should be outlined when exporting to ensure the
design is preserved when submitting to Xcode.
- Template v.3.0
- Requires Xcode 13 or greater
+ Template v.5.0
+ Requires Xcode 15 or greater
Generated from uiwindow.opacity
- Typeset at 100 points
+ Typeset at 100.0 points
Small
Medium
Large
@@ -81,13 +89,13 @@ PUBLIC "-//W3C//DTD SVG 1.1//EN"
-
+
-
+
-
+
From a61d94ca390dd587243bc25acc9ff9e699ec86a3 Mon Sep 17 00:00:00 2001
From: 1024jp <1024jp@wolfrosch.com>
Date: Tue, 30 Apr 2024 01:25:16 +0900
Subject: [PATCH 016/191] Add StoreKit configuration file
---
CotEditor.xcodeproj/project.pbxproj | 4 +
.../xcshareddata/xcschemes/CotEditor.xcscheme | 3 +
CotEditor/CotEditor.storekit | 138 ++++++++++++++++++
.../Localizables/InAppPurchase.xcstrings | 122 ++++++++++++++++
4 files changed, 267 insertions(+)
create mode 100644 CotEditor/CotEditor.storekit
create mode 100644 CotEditor/Localizables/InAppPurchase.xcstrings
diff --git a/CotEditor.xcodeproj/project.pbxproj b/CotEditor.xcodeproj/project.pbxproj
index fe5c0683c..c6086d8dc 100644
--- a/CotEditor.xcodeproj/project.pbxproj
+++ b/CotEditor.xcodeproj/project.pbxproj
@@ -1072,6 +1072,8 @@
2A64F2441D259E49001B229F /* SnippetManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnippetManager.swift; sourceTree = ""; };
2A64F2471D26327C001B229F /* Shortcut+Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Shortcut+Error.swift"; sourceTree = ""; };
2A64F24A1D26615A001B229F /* KeyBindingItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBindingItem.swift; sourceTree = ""; };
+ 2A65520A2BDF4D880082B7D6 /* InAppPurchase.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InAppPurchase.xcstrings; sourceTree = ""; };
+ 2A65520B2BE001A10082B7D6 /* CotEditor.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = CotEditor.storekit; sourceTree = ""; };
2A657D1C2033ED6B00C2611C /* DefaultInitializable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultInitializable.swift; sourceTree = ""; };
2A65EC252B80C01B008096C5 /* FontPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontPicker.swift; sourceTree = ""; };
2A65EC292B80C168008096C5 /* AppearanceSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceSettingsView.swift; sourceTree = ""; };
@@ -1827,6 +1829,7 @@
2A715E21261AC5960060CF84 /* CotEditor-Sparkle.entitlements */,
5454B933243C8271009275BC /* CotEditor-AdHoc.entitlements */,
2A75ACCA19E86DDB00444894 /* CotEditor.sdef */,
+ 2A65520B2BE001A10082B7D6 /* CotEditor.storekit */,
2A6E3F3C19B5218300A63E97 /* CotEditor.help */,
);
name = "Supporting Files";
@@ -2237,6 +2240,7 @@
2A5E6FC32A723CE900E33EA7 /* InfoPlist.xcstrings */,
2A5E6FC62A723F3C00E33EA7 /* ServicesMenu.xcstrings */,
2A36E3702AF9ED0B00A73534 /* Sparkle.xcstrings */,
+ 2A65520A2BDF4D880082B7D6 /* InAppPurchase.xcstrings */,
);
path = Localizables;
sourceTree = "";
diff --git a/CotEditor.xcodeproj/xcshareddata/xcschemes/CotEditor.xcscheme b/CotEditor.xcodeproj/xcshareddata/xcschemes/CotEditor.xcscheme
index a58da6e9b..3d6613b6d 100644
--- a/CotEditor.xcodeproj/xcshareddata/xcschemes/CotEditor.xcscheme
+++ b/CotEditor.xcodeproj/xcshareddata/xcschemes/CotEditor.xcscheme
@@ -81,6 +81,9 @@
ReferencedContainer = "container:CotEditor.xcodeproj">
+
+
Date: Sun, 28 Apr 2024 13:18:14 +0900
Subject: [PATCH 017/191] Add DonationManager
---
CotEditor.xcodeproj/project.pbxproj | 6 ++
CotEditor/Sources/AppDelegate.swift | 4 +
CotEditor/Sources/DefaultKeys.swift | 3 +
CotEditor/Sources/DefaultSettings.swift | 2 +
CotEditor/Sources/DonationManager.swift | 116 ++++++++++++++++++++++++
5 files changed, 131 insertions(+)
create mode 100644 CotEditor/Sources/DonationManager.swift
diff --git a/CotEditor.xcodeproj/project.pbxproj b/CotEditor.xcodeproj/project.pbxproj
index c6086d8dc..585aa7f51 100644
--- a/CotEditor.xcodeproj/project.pbxproj
+++ b/CotEditor.xcodeproj/project.pbxproj
@@ -514,6 +514,8 @@
2A9AC938244849B700D05643 /* NSLayoutManager+InvisibleDrawing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A9AC936244849B700D05643 /* NSLayoutManager+InvisibleDrawing.swift */; };
2A9B134E27E2D84E009954A4 /* NSDraggingInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A9B134D27E2D84E009954A4 /* NSDraggingInfo.swift */; };
2A9B134F27E2D84E009954A4 /* NSDraggingInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A9B134D27E2D84E009954A4 /* NSDraggingInfo.swift */; };
+ 2A9BC2782BDE00B1008B58B5 /* DonationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A9BC2772BDE00B1008B58B5 /* DonationManager.swift */; };
+ 2A9BC2792BDE00B1008B58B5 /* DonationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A9BC2772BDE00B1008B58B5 /* DonationManager.swift */; };
2A9BF3C41D382BB100E3D3E2 /* EditorTextView+Transformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A9BF3C31D382BB100E3D3E2 /* EditorTextView+Transformation.swift */; };
2A9BF3C51D382BB100E3D3E2 /* EditorTextView+Transformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A9BF3C31D382BB100E3D3E2 /* EditorTextView+Transformation.swift */; };
2A9BF3C71D38325200E3D3E2 /* String+FullwidthTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A9BF3C61D38325200E3D3E2 /* String+FullwidthTransform.swift */; };
@@ -1159,6 +1161,7 @@
2A954B232AB28B010070FB74 /* TextFind.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = TextFind.xcstrings; sourceTree = ""; };
2A9AC936244849B700D05643 /* NSLayoutManager+InvisibleDrawing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSLayoutManager+InvisibleDrawing.swift"; sourceTree = ""; };
2A9B134D27E2D84E009954A4 /* NSDraggingInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSDraggingInfo.swift; sourceTree = ""; };
+ 2A9BC2772BDE00B1008B58B5 /* DonationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DonationManager.swift; sourceTree = ""; };
2A9BF3C31D382BB100E3D3E2 /* EditorTextView+Transformation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "EditorTextView+Transformation.swift"; sourceTree = ""; };
2A9BF3C61D38325200E3D3E2 /* String+FullwidthTransform.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+FullwidthTransform.swift"; sourceTree = ""; };
2A9BF3CA1D3842FA00E3D3E2 /* String+Normalization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Normalization.swift"; sourceTree = ""; };
@@ -1470,6 +1473,7 @@
2A64F2441D259E49001B229F /* SnippetManager.swift */,
2A8DA9431D286C53003D0C4B /* ScriptManager.swift */,
2AC13A0824F112D800799A93 /* CommandLineToolManager.swift */,
+ 2A9BC2772BDE00B1008B58B5 /* DonationManager.swift */,
2A91C31A1D1BFE47007CF8BE /* UTType+SettingFile.swift */,
);
name = "Setting Managers";
@@ -2929,6 +2933,7 @@
2A71BC7C1DDC50530085AE1C /* DocumentViewController+TouchBar.swift in Sources */,
2A17A3171D2D4319001DD717 /* DocumentWindow.swift in Sources */,
2AA749C41D3C263300850802 /* DocumentWindowController.swift in Sources */,
+ 2A9BC2782BDE00B1008B58B5 /* DonationManager.swift in Sources */,
2ACDC0921D1726BD009B72D6 /* DotView.swift in Sources */,
2A38FAFE1D1C67050032231A /* DraggableArrayController.swift in Sources */,
2A68722F288A5C44006D6B41 /* DraggableHostingView.swift in Sources */,
@@ -3294,6 +3299,7 @@
2A71BC7B1DDC50530085AE1C /* DocumentViewController+TouchBar.swift in Sources */,
2A17A3161D2D4319001DD717 /* DocumentWindow.swift in Sources */,
2AA749C31D3C263300850802 /* DocumentWindowController.swift in Sources */,
+ 2A9BC2792BDE00B1008B58B5 /* DonationManager.swift in Sources */,
2ACDC0911D1726BD009B72D6 /* DotView.swift in Sources */,
2A38FAFD1D1C67050032231A /* DraggableArrayController.swift in Sources */,
2A687230288A5C44006D6B41 /* DraggableHostingView.swift in Sources */,
diff --git a/CotEditor/Sources/AppDelegate.swift b/CotEditor/Sources/AppDelegate.swift
index 0cb48563e..b07ccf534 100644
--- a/CotEditor/Sources/AppDelegate.swift
+++ b/CotEditor/Sources/AppDelegate.swift
@@ -119,6 +119,10 @@ private enum BundleIdentifier {
// instantiate shared instances
_ = DocumentController.shared
+
+ Task {
+ await DonationManager.shared.updatePurchasedProducts()
+ }
}
diff --git a/CotEditor/Sources/DefaultKeys.swift b/CotEditor/Sources/DefaultKeys.swift
index 1552dae87..bf955b308 100644
--- a/CotEditor/Sources/DefaultKeys.swift
+++ b/CotEditor/Sources/DefaultKeys.swift
@@ -106,6 +106,9 @@ extension DefaultKeys {
static let snippets = DefaultKey<[[String: String]]>("snippets")
static let fileDropArray = DefaultKey<[[String: String]]>("fileDropArray")
+ // Donation
+ static let donationBadgeType = RawRepresentableDefaultKey("donationBadgeType")
+
// Print
static let printFontSize = DefaultKey("printFontSize")
static let printTheme = DefaultKey("printTheme")
diff --git a/CotEditor/Sources/DefaultSettings.swift b/CotEditor/Sources/DefaultSettings.swift
index 4f0509891..51191fa57 100644
--- a/CotEditor/Sources/DefaultSettings.swift
+++ b/CotEditor/Sources/DefaultSettings.swift
@@ -115,6 +115,8 @@ struct DefaultSettings {
scope: "CSS"),
].map(\.dictionary),
+ .donationBadgeType: BadgeType.mug.rawValue,
+
.printFontSize: NSFont.systemFontSize,
.printBackground: false,
.printHeaderAndFooter: true,
diff --git a/CotEditor/Sources/DonationManager.swift b/CotEditor/Sources/DonationManager.swift
new file mode 100644
index 000000000..c1da4f842
--- /dev/null
+++ b/CotEditor/Sources/DonationManager.swift
@@ -0,0 +1,116 @@
+//
+// DonationManager.swift
+//
+// CotEditor
+// https://coteditor.com
+//
+// Created by 1024jp on 2024-04-28.
+//
+// ---------------------------------------------------------------------------
+//
+// © 2024 1024jp
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import Foundation
+import Observation
+import StoreKit
+
+enum Donation {
+
+ static let groupID = "21481959"
+
+ enum ProductID {
+
+ static let onetime = "com.coteditor.CotEditor.donation.onetime"
+ static let continuous = "com.coteditor.CotEditor.donation.continuous.yearly"
+ }
+}
+
+
+enum BadgeType: Int, CaseIterable, Equatable {
+
+ case mug
+ case invisible
+
+
+ var symbolName: String {
+
+ switch self {
+ case .mug: "mug"
+ case .invisible: "circle.dotted"
+ }
+ }
+}
+
+
+@MainActor @Observable final class DonationManager {
+
+ // MARK: Public Properties
+
+ static let shared = DonationManager()
+
+
+ // MARK: Private Properties
+
+ private var purchasedTransactions: Set = []
+ private var transactionObservationTask: Task?
+
+
+ // MARK: Lifecycle
+
+ init() {
+
+ self.transactionObservationTask = Task(priority: .background) { [unowned self] in
+ for await result in Transaction.updates {
+ self.updatePurchase(result)
+ }
+ }
+ }
+
+
+ // MARK: Public Methods
+
+ /// Whether the user has a valid continuous donation.
+ var hasDonated: Bool {
+
+ self.purchasedTransactions.contains { $0.subscriptionGroupID == Donation.groupID }
+ }
+
+
+ /// Update purchased donations.
+ func updatePurchasedProducts() async {
+
+ for await result in Transaction.currentEntitlements {
+ self.updatePurchase(result)
+ }
+ }
+
+
+ // MARK: Private Methods
+
+ /// Update the purchased items.
+ ///
+ /// - Parameter result: The transaction verification result to update.
+ private func updatePurchase(_ result: VerificationResult) {
+
+ guard case .verified(let transaction) = result else { return }
+
+ if transaction.revocationDate == nil {
+ self.purchasedTransactions.insert(transaction)
+ } else {
+ self.purchasedTransactions.remove(transaction)
+ }
+ }
+}
From fb2fffde59115c99409298d6d5b94d54ce7e08b2 Mon Sep 17 00:00:00 2001
From: 1024jp <1024jp@wolfrosch.com>
Date: Sun, 10 Mar 2024 20:36:25 +0900
Subject: [PATCH 018/191] Add donation badge to status bar
---
CotEditor/Localizables/Document.xcstrings | 23 +++++++++++++++++++
CotEditor/Sources/StatusBar.swift | 28 +++++++++++++++++++++++
2 files changed, 51 insertions(+)
diff --git a/CotEditor/Localizables/Document.xcstrings b/CotEditor/Localizables/Document.xcstrings
index d957ec114..94840d876 100644
--- a/CotEditor/Localizables/Document.xcstrings
+++ b/CotEditor/Localizables/Document.xcstrings
@@ -4059,6 +4059,29 @@
}
}
},
+ "Thank you for your kind support!" : {
+ "comment" : "message for users who made a donation",
+ "localizations" : {
+ "de" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Danke für deine nette Hilfe!"
+ }
+ },
+ "en-GB" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Thank you for your kind support!"
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "サポートありがとう!"
+ }
+ }
+ }
+ },
"Toolbar.comment.label" : {
"extractionState" : "extracted_with_value",
"localizations" : {
diff --git a/CotEditor/Sources/StatusBar.swift b/CotEditor/Sources/StatusBar.swift
index 2e6230da1..65701133e 100644
--- a/CotEditor/Sources/StatusBar.swift
+++ b/CotEditor/Sources/StatusBar.swift
@@ -166,6 +166,7 @@ struct StatusBar: View {
@State var model: Model
+ @State var donationManager: DonationManager = .shared
@State private(set) var fileEncodings: [FileEncoding?] = []
@@ -175,6 +176,9 @@ struct StatusBar: View {
var body: some View {
HStack {
+ if self.donationManager.hasDonated {
+ CoffeeBadge()
+ }
EditorCountView(result: self.model.countResult)
Spacer()
@@ -391,6 +395,30 @@ private struct LineEndingPicker: NSViewRepresentable {
}
+private struct CoffeeBadge: View {
+
+ @AppStorage(.donationBadgeType) private var badgeType
+
+ @State private var isMessagePresented = false
+
+
+ var body: some View {
+
+ Button {
+ self.isMessagePresented.toggle()
+ } label: {
+ Image(systemName: self.badgeType.symbolName)
+ .fontWeight(.semibold)
+ }
+ .popover(isPresented: $isMessagePresented) {
+ Text("Thank you for your kind support!", tableName: "Document", comment: "message for users who made a donation")
+ .padding(.vertical, 8)
+ .padding(.horizontal)
+ }
+ .opacity(self.badgeType == .invisible ? 0 : 1)
+ .accessibilityHidden(true)
+ }
+}
// MARK: - Preview
From bc9263b806caa0584b8e9084ff8dd97e6d26cd60 Mon Sep 17 00:00:00 2001
From: 1024jp <1024jp@wolfrosch.com>
Date: Mon, 29 Apr 2024 13:18:15 +0900
Subject: [PATCH 019/191] Add Donation settings pane
---
CotEditor.xcodeproj/project.pbxproj | 12 +
.../bag.coffee.symbolset/Contents.json | 12 +
.../bag.coffee.symbolset/bag.coffee.svg | 107 ++++
.../Symbols/espresso.symbolset/Contents.json | 12 +
.../Symbols/espresso.symbolset/espresso.svg | 107 ++++
.../Localizables/DonationSettings.xcstrings | 479 ++++++++++++++++++
CotEditor/Localizables/Settings.xcstrings | 29 ++
CotEditor/Sources/DonationSettingsView.swift | 218 ++++++++
CotEditor/Sources/SettingsPane.swift | 6 +
.../Sources/SettingsWindowController.swift | 3 +-
10 files changed, 984 insertions(+), 1 deletion(-)
create mode 100644 CotEditor/Assets.xcassets/Symbols/bag.coffee.symbolset/Contents.json
create mode 100644 CotEditor/Assets.xcassets/Symbols/bag.coffee.symbolset/bag.coffee.svg
create mode 100644 CotEditor/Assets.xcassets/Symbols/espresso.symbolset/Contents.json
create mode 100644 CotEditor/Assets.xcassets/Symbols/espresso.symbolset/espresso.svg
create mode 100644 CotEditor/Localizables/DonationSettings.xcstrings
create mode 100644 CotEditor/Sources/DonationSettingsView.swift
diff --git a/CotEditor.xcodeproj/project.pbxproj b/CotEditor.xcodeproj/project.pbxproj
index 585aa7f51..001d9d1d4 100644
--- a/CotEditor.xcodeproj/project.pbxproj
+++ b/CotEditor.xcodeproj/project.pbxproj
@@ -137,6 +137,8 @@
2A1FAD5820A74D0A00566D7C /* MutableCopying.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1FAD5720A74D0A00566D7C /* MutableCopying.swift */; };
2A1FAD5920A74D0A00566D7C /* MutableCopying.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1FAD5720A74D0A00566D7C /* MutableCopying.swift */; };
2A2179F61A07093B002C4AB1 /* SyntaxMap.json in Resources */ = {isa = PBXBuildFile; fileRef = 2A2179F51A07093B002C4AB1 /* SyntaxMap.json */; };
+ 2A21E6732BB44D5E0054C8A1 /* DonationSettings.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 2A21E6722BB44D5E0054C8A1 /* DonationSettings.xcstrings */; };
+ 2A21E6742BB44D5E0054C8A1 /* DonationSettings.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 2A21E6722BB44D5E0054C8A1 /* DonationSettings.xcstrings */; };
2A222C3024FA8E0500251084 /* UserDefaults.Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A222C2F24FA8E0500251084 /* UserDefaults.Publisher.swift */; };
2A222C3124FA8E0500251084 /* UserDefaults.Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A222C2F24FA8E0500251084 /* UserDefaults.Publisher.swift */; };
2A231A251E7B4EDC00C2A909 /* MultipleReplace+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A231A241E7B4EDC00C2A909 /* MultipleReplace+Codable.swift */; };
@@ -762,6 +764,8 @@
2AE144B72B00A963005E8CF1 /* Identifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AE144B52B00A963005E8CF1 /* Identifiable.swift */; };
2AE144B92B00DCB7005E8CF1 /* View+Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AE144B82B00DCB7005E8CF1 /* View+Alert.swift */; };
2AE144BA2B00DCB7005E8CF1 /* View+Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AE144B82B00DCB7005E8CF1 /* View+Alert.swift */; };
+ 2AE144BC2B01E341005E8CF1 /* DonationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AE144BB2B01E341005E8CF1 /* DonationSettingsView.swift */; };
+ 2AE144BD2B01E341005E8CF1 /* DonationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AE144BB2B01E341005E8CF1 /* DonationSettingsView.swift */; };
2AE144C42B0222DB005E8CF1 /* LiveTextInsertionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AE144C32B0222DB005E8CF1 /* LiveTextInsertionView.swift */; };
2AE144C52B0222DB005E8CF1 /* LiveTextInsertionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AE144C32B0222DB005E8CF1 /* LiveTextInsertionView.swift */; };
2AE3F3181D3F8A1F005B8724 /* NSAttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AE3F3171D3F8A1F005B8724 /* NSAttributedString.swift */; };
@@ -948,6 +952,7 @@
2A1EB5C319AD469500C1E37E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
2A1FAD5720A74D0A00566D7C /* MutableCopying.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutableCopying.swift; sourceTree = ""; };
2A2179F51A07093B002C4AB1 /* SyntaxMap.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = SyntaxMap.json; sourceTree = ""; };
+ 2A21E6722BB44D5E0054C8A1 /* DonationSettings.xcstrings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json.xcstrings; path = DonationSettings.xcstrings; sourceTree = ""; };
2A222C2F24FA8E0500251084 /* UserDefaults.Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.Publisher.swift; sourceTree = ""; };
2A231A241E7B4EDC00C2A909 /* MultipleReplace+Codable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MultipleReplace+Codable.swift"; sourceTree = ""; };
2A231A271E7BD82700C2A909 /* Binding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Binding.swift; sourceTree = ""; };
@@ -1291,6 +1296,7 @@
2AE12E061E7DDF0700681F72 /* CustomSurroundView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomSurroundView.swift; sourceTree = ""; };
2AE144B52B00A963005E8CF1 /* Identifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Identifiable.swift; sourceTree = ""; };
2AE144B82B00DCB7005E8CF1 /* View+Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Alert.swift"; sourceTree = ""; };
+ 2AE144BB2B01E341005E8CF1 /* DonationSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DonationSettingsView.swift; sourceTree = ""; };
2AE144C32B0222DB005E8CF1 /* LiveTextInsertionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTextInsertionView.swift; sourceTree = ""; };
2AE3F3171D3F8A1F005B8724 /* NSAttributedString.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSAttributedString.swift; sourceTree = ""; };
2AE4658627A5A7CE00D2904F /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = ""; };
@@ -1445,6 +1451,7 @@
2AFFA7C22B16E93B005652CD /* FormatSettingsView.swift */,
2ACDA2522B813FA500B2EBA8 /* SnippetsSettingsView.swift */,
2AA4D3731D1AA0AC001D261D /* KeyBindingsSettingsView.swift */,
+ 2AE144BB2B01E341005E8CF1 /* DonationSettingsView.swift */,
);
name = Panes;
sourceTree = "";
@@ -2166,6 +2173,7 @@
2AB9E4C22B830902004E5BDC /* FormatSettings.xcstrings */,
2ACDA29C2B81E8BF00B2EBA8 /* SnippetsSettings.xcstrings */,
2A1E7DCB2B889A1F004F0C07 /* KeyBindingsSettings.xcstrings */,
+ 2A21E6722BB44D5E0054C8A1 /* DonationSettings.xcstrings */,
);
name = Panes;
sourceTree = "";
@@ -2636,6 +2644,7 @@
2A39ACB92B8CE6DE00E216C9 /* CustomSurround.xcstrings in Resources */,
2A39ACF92B8CED8B00E216C9 /* CustomTabWidth.xcstrings in Resources */,
2AA672A22B8F8AA300B8F7E6 /* Document.xcstrings in Resources */,
+ 2A21E6732BB44D5E0054C8A1 /* DonationSettings.xcstrings in Resources */,
2A1E7E192B8D715F004F0C07 /* EditorOpacity.xcstrings in Resources */,
2ACDA2942B81E8B700B2EBA8 /* EditSettings.xcstrings in Resources */,
2A39AC812B8CDFC800E216C9 /* EncodingList.xcstrings in Resources */,
@@ -2724,6 +2733,7 @@
2A39ACBA2B8CE6DE00E216C9 /* CustomSurround.xcstrings in Resources */,
2A39ACFA2B8CED8B00E216C9 /* CustomTabWidth.xcstrings in Resources */,
2AA672A32B8F8AA300B8F7E6 /* Document.xcstrings in Resources */,
+ 2A21E6742BB44D5E0054C8A1 /* DonationSettings.xcstrings in Resources */,
2A1E7E1A2B8D715F004F0C07 /* EditorOpacity.xcstrings in Resources */,
2ACDA2952B81E8B700B2EBA8 /* EditSettings.xcstrings in Resources */,
2A39AC822B8CDFCA00E216C9 /* EncodingList.xcstrings in Resources */,
@@ -2934,6 +2944,7 @@
2A17A3171D2D4319001DD717 /* DocumentWindow.swift in Sources */,
2AA749C41D3C263300850802 /* DocumentWindowController.swift in Sources */,
2A9BC2782BDE00B1008B58B5 /* DonationManager.swift in Sources */,
+ 2AE144BC2B01E341005E8CF1 /* DonationSettingsView.swift in Sources */,
2ACDC0921D1726BD009B72D6 /* DotView.swift in Sources */,
2A38FAFE1D1C67050032231A /* DraggableArrayController.swift in Sources */,
2A68722F288A5C44006D6B41 /* DraggableHostingView.swift in Sources */,
@@ -3300,6 +3311,7 @@
2A17A3161D2D4319001DD717 /* DocumentWindow.swift in Sources */,
2AA749C31D3C263300850802 /* DocumentWindowController.swift in Sources */,
2A9BC2792BDE00B1008B58B5 /* DonationManager.swift in Sources */,
+ 2AE144BD2B01E341005E8CF1 /* DonationSettingsView.swift in Sources */,
2ACDC0911D1726BD009B72D6 /* DotView.swift in Sources */,
2A38FAFD1D1C67050032231A /* DraggableArrayController.swift in Sources */,
2A687230288A5C44006D6B41 /* DraggableHostingView.swift in Sources */,
diff --git a/CotEditor/Assets.xcassets/Symbols/bag.coffee.symbolset/Contents.json b/CotEditor/Assets.xcassets/Symbols/bag.coffee.symbolset/Contents.json
new file mode 100644
index 000000000..d5e8a19f6
--- /dev/null
+++ b/CotEditor/Assets.xcassets/Symbols/bag.coffee.symbolset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "symbols" : [
+ {
+ "filename" : "bag.coffee.svg",
+ "idiom" : "universal"
+ }
+ ]
+}
diff --git a/CotEditor/Assets.xcassets/Symbols/bag.coffee.symbolset/bag.coffee.svg b/CotEditor/Assets.xcassets/Symbols/bag.coffee.symbolset/bag.coffee.svg
new file mode 100644
index 000000000..fa9e07767
--- /dev/null
+++ b/CotEditor/Assets.xcassets/Symbols/bag.coffee.symbolset/bag.coffee.svg
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+ Weight/Scale Variations
+ Ultralight
+ Thin
+ Light
+ Regular
+ Medium
+ Semibold
+ Bold
+ Heavy
+ Black
+
+
+
+
+
+
+
+
+
+
+ Design Variations
+ Symbols are supported in up to nine weights and three scales.
+ For optimal layout with text and other symbols, vertically align
+ symbols with the adjacent text.
+
+
+
+
+
+ Margins
+ Leading and trailing margins on the left and right side of each symbol
+ can be adjusted by modifying the x-location of the margin guidelines.
+ Modifications are automatically applied proportionally to all
+ scales and weights.
+
+
+
+ Exporting
+ Symbols should be outlined when exporting to ensure the
+ design is preserved when submitting to Xcode.
+ Template v.5.0
+ Requires Xcode 15 or greater
+ Generated from bag.coffee
+ Typeset at 100.0 points
+ Small
+ Medium
+ Large
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CotEditor/Assets.xcassets/Symbols/espresso.symbolset/Contents.json b/CotEditor/Assets.xcassets/Symbols/espresso.symbolset/Contents.json
new file mode 100644
index 000000000..057665ffc
--- /dev/null
+++ b/CotEditor/Assets.xcassets/Symbols/espresso.symbolset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "symbols" : [
+ {
+ "filename" : "espresso.svg",
+ "idiom" : "universal"
+ }
+ ]
+}
diff --git a/CotEditor/Assets.xcassets/Symbols/espresso.symbolset/espresso.svg b/CotEditor/Assets.xcassets/Symbols/espresso.symbolset/espresso.svg
new file mode 100644
index 000000000..4b9af2684
--- /dev/null
+++ b/CotEditor/Assets.xcassets/Symbols/espresso.symbolset/espresso.svg
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+ Weight/Scale Variations
+ Ultralight
+ Thin
+ Light
+ Regular
+ Medium
+ Semibold
+ Bold
+ Heavy
+ Black
+
+
+
+
+
+
+
+
+
+
+ Design Variations
+ Symbols are supported in up to nine weights and three scales.
+ For optimal layout with text and other symbols, vertically align
+ symbols with the adjacent text.
+
+
+
+
+
+ Margins
+ Leading and trailing margins on the left and right side of each symbol
+ can be adjusted by modifying the x-location of the margin guidelines.
+ Modifications are automatically applied proportionally to all
+ scales and weights.
+
+
+
+ Exporting
+ Symbols should be outlined when exporting to ensure the
+ design is preserved when submitting to Xcode.
+ Template v.5.0
+ Requires Xcode 15 or greater
+ Generated from espresso
+ Typeset at 100.0 points
+ Small
+ Medium
+ Large
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CotEditor/Localizables/DonationSettings.xcstrings b/CotEditor/Localizables/DonationSettings.xcstrings
new file mode 100644
index 000000000..f28e03ffd
--- /dev/null
+++ b/CotEditor/Localizables/DonationSettings.xcstrings
@@ -0,0 +1,479 @@
+{
+ "sourceLanguage" : "en",
+ "strings" : {
+ "× %lld" : {
+ "comment" : "multiple sign for the quantity of items to purchase",
+ "localizations" : {
+ "cs" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "× %lld"
+ }
+ },
+ "de" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "× %lld"
+ }
+ },
+ "en-GB" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "× %lld"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "× %lld"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "× %lld"
+ }
+ },
+ "it" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "× %lld"
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "× %lld"
+ }
+ },
+ "nl" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "× %lld"
+ }
+ },
+ "pt" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "× %lld"
+ }
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "× %lld"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "× %lld"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "× %lld"
+ }
+ }
+ }
+ },
+ "As a proof of your kind support, a coffee badge appears on the status bar during continuous support." : {
+ "localizations" : {
+ "de" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Als Beweis für deine freundliche Unterstützung erscheint während des laufenden Unterstützung ein Kaffee-Badge in der Statusleiste."
+ }
+ },
+ "en-GB" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "As a proof of your kind support, a coffee badge appears on the status bar during continuous support."
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "寛大なサポートの証として、継続的な寄付の期間中ステータスバーにコーヒーバッジが表示されます。"
+ }
+ }
+ }
+ },
+ "Badge type:" : {
+ "localizations" : {
+ "de" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Badgetyp:"
+ }
+ },
+ "en-GB" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Badge type:"
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "バッジタイプ:"
+ }
+ }
+ }
+ },
+ "BadgeType.invisible.label" : {
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "de" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Unsichtbarer Kaffee"
+ }
+ },
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Invisible Coffee"
+ }
+ },
+ "en-GB" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Invisible Coffee"
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "透明コーヒー"
+ }
+ }
+ }
+ },
+ "BadgeType.mug.label" : {
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "cs" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kávový hrnek"
+ }
+ },
+ "de" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kaffeebecher"
+ }
+ },
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Coffee Mug"
+ }
+ },
+ "en-GB" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Coffee Mug"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Taza de café"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tasse de café"
+ }
+ },
+ "it" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tazza di caffè"
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "コーヒーマグ"
+ }
+ },
+ "nl" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Koffiekop"
+ }
+ },
+ "pt" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Caneca de Café"
+ }
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kahve Kupası"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "咖啡杯"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "咖啡杯"
+ }
+ }
+ }
+ },
+ "Continuous support" : {
+ "localizations" : {
+ "de" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kontinuierliche Unterstützung"
+ }
+ },
+ "en-GB" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Continuous support"
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "継続的なサポート"
+ }
+ }
+ }
+ },
+ "CotEditor provides all features for free to everyone. You can support this project by offering coffee." : {
+ "localizations" : {
+ "de" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "CotEditor bietet alle Funktionen kostenlos für alle. Du kannst dieses Projekt unterstützen, indem du Kaffee spendest."
+ }
+ },
+ "en-GB" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "CotEditor provides all features for free to everyone. You can support this project by offering coffee."
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "CotEditorはすべての人に無償ですべての機能を提供しています。コーヒーを送ることでこのプロジェクトをサポートすることができます。"
+ }
+ }
+ }
+ },
+ "Manage subscriptions" : {
+ "localizations" : {
+ "cs" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Spravovat předplatná"
+ }
+ },
+ "de" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Abos verwalten"
+ }
+ },
+ "en-GB" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Manage Subscriptions"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Gestionar suscripciones"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Gérer les abonnements"
+ }
+ },
+ "it" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Gestisci le iscrizioni"
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "サブスクリプションを管理"
+ }
+ },
+ "nl" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Beheer abonnementen"
+ }
+ },
+ "pt" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Gerenciar Assinaturas"
+ }
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Abonelikleri Yönet"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "管理订阅"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "管理訂閱項目"
+ }
+ }
+ }
+ },
+ "One-time donation" : {
+ "localizations" : {
+ "de" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Einmalige Spende"
+ }
+ },
+ "en-GB" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "One-off donation"
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "1回限りの寄付"
+ }
+ }
+ }
+ },
+ "Open in the App Store" : {
+ "localizations" : {
+ "cs" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Otevřít v App Storu"
+ }
+ },
+ "de" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Im App Store öffnen"
+ }
+ },
+ "en-GB" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Open in the App Store"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Abrir en App Store"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ouvrir dans l’App Store"
+ }
+ },
+ "it" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Apri su App Store"
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "App Storeで開く"
+ }
+ },
+ "nl" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Open in App Store"
+ }
+ },
+ "pt" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Abrir na App Store"
+ }
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "App Store’da Aç"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "在App Store中打开"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "在App Store中打開"
+ }
+ }
+ }
+ },
+ "The donation feature is available only in CotEditor distributed in the App Store." : {
+ "localizations" : {
+ "de" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Die Spendenfunktion ist nur in CotEditor verfügbar, das im App Store vertrieben wird."
+ }
+ },
+ "en-GB" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "The donation feature is available only in CotEditor distributed in the App Store."
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "寄付機能はApp Storeで配布されているCotEditorでのみ有効です。"
+ }
+ }
+ }
+ }
+ },
+ "version" : "1.0"
+}
\ No newline at end of file
diff --git a/CotEditor/Localizables/Settings.xcstrings b/CotEditor/Localizables/Settings.xcstrings
index fce6194f2..5e429ccd7 100644
--- a/CotEditor/Localizables/Settings.xcstrings
+++ b/CotEditor/Localizables/Settings.xcstrings
@@ -84,6 +84,35 @@
}
}
},
+ "SettingsPane.donation.label" : {
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "de" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Spende"
+ }
+ },
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Donation"
+ }
+ },
+ "en-GB" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Donation"
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "寄付"
+ }
+ }
+ }
+ },
"SettingsPane.edit.label" : {
"extractionState" : "extracted_with_value",
"localizations" : {
diff --git a/CotEditor/Sources/DonationSettingsView.swift b/CotEditor/Sources/DonationSettingsView.swift
new file mode 100644
index 000000000..be045aee4
--- /dev/null
+++ b/CotEditor/Sources/DonationSettingsView.swift
@@ -0,0 +1,218 @@
+//
+// DonationSettingsView.swift
+//
+// CotEditor
+// https://coteditor.com
+//
+// Created by 1024jp on 2023-11-13.
+//
+// ---------------------------------------------------------------------------
+//
+// © 2023-2024 1024jp
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import SwiftUI
+import StoreKit
+
+@MainActor struct DonationSettingsView: View {
+
+#if SPARKLE
+ var isInAppPurchaseAvailable = false
+#else
+ var isInAppPurchaseAvailable = true
+#endif
+
+ @State private var manager: DonationManager = .shared
+
+ @AppStorage(.donationBadgeType) private var badgeType: BadgeType
+
+
+ // MARK: View
+
+ var body: some View {
+
+ VStack(alignment: .leading) {
+ Text("CotEditor provides all features for free to everyone. You can support this project by offering coffee.", tableName: "DonationSettings")
+ .padding(.bottom, 10)
+
+ if self.isInAppPurchaseAvailable {
+ HStack(alignment: .top, spacing: 18) {
+ VStack(alignment: .leading) {
+ Text("Continuous support", tableName: "DonationSettings")
+ .font(.system(size: 14))
+
+ ProductView(id: Donation.ProductID.continuous) {
+ Image(.bagCoffee)
+ .font(.system(size: 36))
+ .foregroundStyle(.secondary)
+ .productIconBorder()
+ }
+
+ Link(String(localized: "Manage subscriptions", table: "DonationSettings"),
+ destination: URL(string: "itms-apps://apps.apple.com/account/subscriptions")!)
+ .textScale(.secondary)
+ .foregroundStyle(.tint)
+ .frame(maxWidth: .infinity)
+ .opacity(self.manager.hasDonated ? 1 : 0)
+ .padding(.bottom, 10)
+
+ Form {
+ Picker(String(localized: "Badge type:", table: "DonationSettings"), selection: $badgeType) {
+ ForEach(BadgeType.allCases, id: \.self) { item in
+ HStack {
+ Image(systemName: item.symbolName)
+ Text(item.label)
+ }
+ }
+ }.fixedSize()
+
+ Text("As a proof of your kind support, a coffee badge appears on the status bar during continuous support.", tableName: "DonationSettings")
+ .foregroundStyle(.secondary)
+ .controlSize(.small)
+ }.disabled(!self.manager.hasDonated)
+ }
+ .frame(maxWidth: .infinity, alignment: .leading) // for the same width
+
+ Divider()
+
+ VStack(alignment: .leading) {
+ Text("One-time donation", tableName: "DonationSettings")
+ .font(.system(size: 14))
+
+ ProductView(id: Donation.ProductID.onetime) {
+ Image(.espresso)
+ }.productViewStyle(OnetimeProductViewStyle())
+ }
+ .frame(maxWidth: .infinity, alignment: .leading) // for the same width
+ }
+ } else {
+ VStack(alignment: .center) {
+ Image(.bagCoffee)
+ .font(.system(size: 64, weight: .light))
+ .foregroundStyle(.tertiary)
+ .padding(.vertical, 6)
+ Text("The donation feature is available only in CotEditor distributed in the App Store.", tableName: "DonationSettings")
+ .foregroundStyle(.secondary)
+
+ if let url = URL(string: "itms-apps://itunes.apple.com/app/id1024640650") {
+ Link(String(localized: "Open in the App Store", table: "DonationSettings"), destination: url)
+ .foregroundStyle(.tint)
+ }
+ }.frame(maxWidth: .infinity, alignment: .center)
+ }
+
+ HStack {
+ Spacer()
+ HelpButton(anchor: "settings_appearance")
+ }
+ }
+ .scenePadding()
+ .frame(minWidth: 600, idealWidth: 600)
+ }
+}
+
+
+private struct OnetimeProductViewStyle: ProductViewStyle {
+
+ @State private var quantity = 1
+ @State private var error: (any Error)?
+
+
+ func makeBody(configuration: Configuration) -> some View {
+
+ switch configuration.state {
+ case .success(let product):
+ self.productView(product, icon: configuration.icon)
+ case .loading, .failure, .unavailable:
+ ProductView(configuration)
+ @unknown default:
+ ProductView(configuration)
+ }
+ }
+
+ /// Returns the view to display when the state is success.
+ @ViewBuilder private func productView(_ product: Product, icon: ProductViewStyleConfiguration.Icon) -> some View {
+
+ HStack(spacing: 10) {
+ icon
+ .font(.system(size: 18))
+ .foregroundStyle(.secondary)
+ .productIconBorder()
+ .frame(width: 50, height: 50)
+
+ VStack(alignment: .leading, spacing: 1) {
+ HStack(alignment: .firstTextBaseline, spacing: 4) {
+ Text(product.displayName)
+ Text("× \(self.quantity)", tableName: "DonationSettings", comment: "multiple sign for the quantity of items to purchase")
+ .monospacedDigit()
+
+ Stepper(value: $quantity, in: 1...100, label: EmptyView.init)
+
+ Spacer()
+ Button {
+ Task {
+ do {
+ _ = try await product.purchase(options: [.quantity(self.quantity)])
+ } catch {
+ self.error = error
+ }
+ }
+ } label: {
+ Text(product.price * Decimal(self.quantity), format: product.priceFormatStyle)
+ .monospacedDigit()
+ .fixedSize()
+ .contentTransition(.numericText())
+ .animation(.default, value: self.quantity)
+ }
+ }
+
+ Text(product.description)
+ .controlSize(.small)
+ .foregroundStyle(.secondary)
+ .fixedSize(horizontal: false, vertical: true)
+ }.alert(error: $error)
+ }
+ }
+}
+
+
+private extension BadgeType {
+
+ var label: String {
+
+ switch self {
+ case .mug:
+ String(localized: "BadgeType.mug.label",
+ defaultValue: "Coffee Mug",
+ table: "DonationSettings")
+ case .invisible:
+ String(localized: "BadgeType.invisible.label",
+ defaultValue: "Invisible Coffee",
+ table: "DonationSettings")
+ }
+ }
+}
+
+
+
+// MARK: - Preview
+
+#Preview {
+ DonationSettingsView()
+}
+
+#Preview("Non-AppStore version") {
+ DonationSettingsView(isInAppPurchaseAvailable: false)
+}
diff --git a/CotEditor/Sources/SettingsPane.swift b/CotEditor/Sources/SettingsPane.swift
index 40d04f0e5..d34b488c6 100644
--- a/CotEditor/Sources/SettingsPane.swift
+++ b/CotEditor/Sources/SettingsPane.swift
@@ -33,6 +33,7 @@ enum SettingsPane: String, CaseIterable {
case format
case snippets
case keyBindings
+ case donation
/// Localized label.
@@ -71,6 +72,10 @@ enum SettingsPane: String, CaseIterable {
String(localized: "SettingsPane.keyBindings.label",
defaultValue: "Key Bindings",
table: "Settings")
+ case .donation:
+ String(localized: "SettingsPane.donation.label",
+ defaultValue: "Donation",
+ table: "Settings")
}
}
@@ -87,6 +92,7 @@ enum SettingsPane: String, CaseIterable {
case .format: "doc.text"
case .snippets: "note.text"
case .keyBindings: "keyboard"
+ case .donation: "mug"
}
}
}
diff --git a/CotEditor/Sources/SettingsWindowController.swift b/CotEditor/Sources/SettingsWindowController.swift
index 27fb23cd6..076c458ae 100644
--- a/CotEditor/Sources/SettingsWindowController.swift
+++ b/CotEditor/Sources/SettingsWindowController.swift
@@ -77,7 +77,7 @@ private extension SettingsPane {
}
- private var view: any View {
+ @MainActor private var view: any View {
switch self {
case .general: GeneralSettingsView()
@@ -88,6 +88,7 @@ private extension SettingsPane {
case .format: FormatSettingsView()
case .snippets: SnippetsSettingsView()
case .keyBindings: KeyBindingsSettingsView()
+ case .donation: DonationSettingsView()
}
}
}
From 1bd1a94c7a9430505e008dca1be9dd650e6cd04e Mon Sep 17 00:00:00 2001
From: 1024jp <1024jp@wolfrosch.com>
Date: Mon, 29 Apr 2024 13:18:22 +0900
Subject: [PATCH 020/191] Add help contents
---
CHANGELOG.md | 5 ++
.../Resources/en.lproj/pgs/settings.html | 3 +
.../en.lproj/pgs/settings_donation.html | 56 ++++++++++++++++++
.../Resources/ja.lproj/pgs/settings.html | 3 +
.../ja.lproj/pgs/settings_donation.html | 57 +++++++++++++++++++
5 files changed, 124 insertions(+)
create mode 100644 CotEditor/CotEditor.help/Contents/Resources/en.lproj/pgs/settings_donation.html
create mode 100644 CotEditor/CotEditor.help/Contents/Resources/ja.lproj/pgs/settings_donation.html
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a03c460ff..3444e4508 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,11 @@
4.9.0 (unreleased)
--------------------------
+### New Feature
+
+- [AppStore ver.] Now user can donate to the CotEditor project via in-app purchase in the new Donate settings pane.
+
+
### Improvements
- Change the system requirement to __macOS 14 Sonoma and later__.
diff --git a/CotEditor/CotEditor.help/Contents/Resources/en.lproj/pgs/settings.html b/CotEditor/CotEditor.help/Contents/Resources/en.lproj/pgs/settings.html
index 8fb758b51..cad902de9 100644
--- a/CotEditor/CotEditor.help/Contents/Resources/en.lproj/pgs/settings.html
+++ b/CotEditor/CotEditor.help/Contents/Resources/en.lproj/pgs/settings.html
@@ -42,6 +42,9 @@
Key Bindings :
Customize keyboard shortcuts to execute menu commands.
+
+ Donation :
+ Support the CotEditor project by offering coffee.
+
+
+Change Donation settings in CotEditor
+
+In the CotEditor app on your Mac, use Donation settings to donate to the CotEditor project or change the settings related to continuous support. To change these settings, choose CotEditor > Settings, then click Donation.
+
+
+
+
+ Option Description
+
+
+
+
+ Continuous support
+ A continuous donation to the CotEditor project. The subsription will automatically be renewed yearly. To stop the subscription, go to the App Store from the “Manage subscriptions” link and cancel the subscription there.
+
+
+ Badge type
+ Select the badge to display on the status bar during continuous support.
+
+
+ One-time donation
+ A one-time donation to the CotEditor project. You can send it multiple times but there is no particular reward like the coffee badge by the continuous support.
+
+
+
+
+
+ The donation uses the in-app purchase on the App Store provided by Apple. This feature is available only in CotEditor downloaded from the App Store.
+ The donation buttons are available only when an internet connection exists.
+ The donations made here will be made to the individual developer.
+
+
+
+
+
+
diff --git a/CotEditor/CotEditor.help/Contents/Resources/en.lproj/pgs/settings_donation.html b/CotEditor/CotEditor.help/Contents/Resources/en.lproj/pgs/settings_donation.html
new file mode 100644
index 000000000..0a797bb2c
--- /dev/null
+++ b/CotEditor/CotEditor.help/Contents/Resources/en.lproj/pgs/settings_donation.html
@@ -0,0 +1,56 @@
+
+
+