Files
note/Note/FormatNoteViewController.swift
T

325 lines
13 KiB
Swift
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// FormatNoteViewController.swift
// Note
//
// Created by fish on 2026/7/12.
//
import UIKit
class FormatNoteViewController: UIViewController {
private var textView: UITextView!
private var iCloudButton: UIButton!
private var noteButton: UIButton!
private var doneButton: UIBarButtonItem!
private var pasteButton: UIBarButtonItem!
private var resetButton: UIBarButtonItem!
private var rightBarButtonMode: RightBarButtonMode = .paste
private var isCloudFormatted = false
private var isNoteFormatted = false
private enum RightBarButtonMode {
case paste
case reset
}
override func viewDidLoad() {
super.viewDidLoad()
title = "格式化笔记"
navigationItem.largeTitleDisplayMode = .never
view.backgroundColor = .systemGroupedBackground
setupUI()
}
private func setupUI() {
textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.backgroundColor = UIColor { traitCollection in
traitCollection.userInterfaceStyle == .dark ? .secondarySystemBackground : .white
}
textView.layer.cornerRadius = 12
textView.layer.borderWidth = 0.5
textView.layer.borderColor = UIColor.separator.cgColor
textView.layer.masksToBounds = true
textView.font = UIFont.preferredFont(forTextStyle: .body)
textView.textContainerInset = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 12)
textView.isEditable = true
textView.delegate = self
view.addSubview(textView)
doneButton = UIBarButtonItem(title: "完成", style: .plain, target: self, action: #selector(didTapDoneButton))
pasteButton = UIBarButtonItem(title: "粘贴文案", style: .plain, target: self, action: #selector(didTapPasteButton))
resetButton = UIBarButtonItem(title: "重置", style: .plain, target: self, action: #selector(didTapResetButton))
updateRightBarButtonItem()
iCloudButton = createBottomButton(title: "iCloud 格式化", color: .systemBlue)
iCloudButton.addTarget(self, action: #selector(didTapiCloudButton), for: .touchUpInside)
view.addSubview(iCloudButton)
noteButton = createBottomButton(title: "Note 格式化", color: .systemGreen)
noteButton.addTarget(self, action: #selector(didTapNoteButton), for: .touchUpInside)
view.addSubview(noteButton)
let safeArea = view.safeAreaLayoutGuide
let buttonHeight: CGFloat = 52
let spacing: CGFloat = 16
NSLayoutConstraint.activate([
textView.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: spacing),
textView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: spacing),
textView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -spacing),
iCloudButton.topAnchor.constraint(equalTo: textView.bottomAnchor, constant: spacing),
iCloudButton.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: spacing),
iCloudButton.trailingAnchor.constraint(equalTo: safeArea.centerXAnchor, constant: -spacing / 2),
iCloudButton.heightAnchor.constraint(equalToConstant: buttonHeight),
iCloudButton.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor, constant: -spacing),
noteButton.topAnchor.constraint(equalTo: textView.bottomAnchor, constant: spacing),
noteButton.leadingAnchor.constraint(equalTo: safeArea.centerXAnchor, constant: spacing / 2),
noteButton.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -spacing),
noteButton.heightAnchor.constraint(equalToConstant: buttonHeight),
noteButton.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor, constant: -spacing)
])
}
private func createBottomButton(title: String, color: UIColor) -> UIButton {
var config = UIButton.Configuration.filled()
config.title = title
config.baseBackgroundColor = color
config.baseForegroundColor = .white
config.cornerStyle = .medium
config.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)
let button = UIButton(configuration: config)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}
@objc private func didTapDoneButton() {
textView.resignFirstResponder()
}
@objc private func didTapPasteButton() {
if let string = UIPasteboard.general.string {
textView.text = string
}
}
private func trimmedText() -> String {
return textView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
}
private func showEmptyAlert() {
let alert = UIAlertController(title: "提示", message: "请输入内容后再进行格式化", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "确定", style: .default))
present(alert, animated: true)
}
@objc private func didTapResetButton() {
textView.text = ""
rightBarButtonMode = .paste
isCloudFormatted = false
isNoteFormatted = false
iCloudButton.setTitle("iCloud 格式化", for: .normal)
noteButton.setTitle("Note 格式化", for: .normal)
updateRightBarButtonItem()
}
private func updateRightBarButtonItem() {
if textView.isFirstResponder {
navigationItem.rightBarButtonItem = doneButton
} else {
switch rightBarButtonMode {
case .paste:
navigationItem.rightBarButtonItem = pasteButton
case .reset:
navigationItem.rightBarButtonItem = resetButton
}
}
}
@objc private func didTapiCloudButton() {
guard !trimmedText().isEmpty else {
showEmptyAlert()
return
}
if isCloudFormatted {
saveToFiles()
} else {
textView.text = formatForCloud(textView.text)
isCloudFormatted = true
iCloudButton.setTitle("保存到 iCloud", for: .normal)
rightBarButtonMode = .reset
updateRightBarButtonItem()
}
}
@objc private func didTapNoteButton() {
guard !trimmedText().isEmpty else {
showEmptyAlert()
return
}
if isNoteFormatted {
copyAndOpenNotes()
} else {
textView.text = formatForNote(textView.text)
isNoteFormatted = true
noteButton.setTitle("复制并打开备忘录", for: .normal)
rightBarButtonMode = .reset
updateRightBarButtonItem()
}
}
private func copyAndOpenNotes() {
UIPasteboard.general.string = textView.text
guard let url = URL(string: "mobilenotes://") else { return }
guard UIApplication.shared.canOpenURL(url) else { return }
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
private func formatForCloud(_ text: String) -> String {
var content = text
content = content.replacingOccurrences(of: " ", with: "")
content = content.replacingOccurrences(of: " ", with: "")
content = content.replacingOccurrences(of: "\u{00A0}", with: "")
content = content.replacingOccurrences(of: "# ", with: "")
content = content.replacingOccurrences(of: "#", with: "")
content = content.replacingOccurrences(of: "", with: "。")
content = content.replacingOccurrences(of: "!", with: "。")
content = content.replacingOccurrences(of: ";", with: "。")
content = content.replacingOccurrences(of: "", with: "。")
content = content.replacingOccurrences(of: ".", with: "。")
content = content.replacingOccurrences(of: "", with: "(")
content = content.replacingOccurrences(of: "", with: ")")
content = content.replacingOccurrences(of: "?", with: "")
content = content.replacingOccurrences(of: ",", with: "")
content = content.replacingOccurrences(of: ":", with: "")
let numberReplacements: [(String, String)] = [
("20、", "二十、"), ("19、", "十九、"), ("18、", "十八、"), ("17、", "十七、"),
("16、", "十六、"), ("15、", "十五、"), ("14、", "十四、"), ("13、", "十三、"),
("12、", "十二、"), ("11、", "十一、"), ("10、", "十、"),
("9、", "九、"), ("8、", "八、"), ("7、", "七、"), ("6、", "六、"),
("5、", "五、"), ("4、", "四、"), ("3、", "三、"), ("2、", "二、"), ("1、", "一、")
]
for (old, new) in numberReplacements {
content = content.replacingOccurrences(of: old, with: new)
}
content = content.replacingOccurrences(of: "。", with: "。\n")
content = content.replacingOccurrences(of: "", with: "\n")
content = removeEmptyLines(content)
content = content.replacingOccurrences(of: "\\s*", with: "\n\n", options: .regularExpression)
content = content.replacingOccurrences(of: "。", with: "。\n")
content = content.replacingOccurrences(of: "", with: "\n")
content = content.replacingOccurrences(of: "\n\n", with: "")
content = content.replacingOccurrences(of: ":一、", with: "\n\n一、")
content = content.replacingOccurrences(of: " ", with: "")
content = content.replacingOccurrences(of: "---", with: "---\n")
content = content.replacingOccurrences(of: "日星期", with: "日 星期")
content = "# " + content
content = insertEmptyLineAfterFirstLine(content)
return content
}
private func formatForNote(_ text: String) -> String {
var content = text
content = content.replacingOccurrences(of: "# ", with: "")
content = content.replacingOccurrences(of: "#", with: "")
content = content.replacingOccurrences(of: "日星期", with: "日 星期")
// 第一行固定为标题
let allLines = removeEmptyLines(content).components(separatedBy: .newlines)
guard let title = allLines.first else { return content }
// 剩余内容按 --- 分段
let bodyLines = Array(allLines.dropFirst())
var groups: [[String]] = []
var currentGroup: [String] = []
for line in bodyLines {
if line == "---" {
if !currentGroup.isEmpty {
groups.append(currentGroup)
currentGroup = []
}
} else {
currentGroup.append(line)
}
}
if !currentGroup.isEmpty {
groups.append(currentGroup)
}
var result = title
for group in groups {
result += "\n\n\n" + group.joined(separator: "\n")
}
return result.trimmingCharacters(in: .whitespacesAndNewlines)
}
private func saveToFiles() {
let fileName = generateFileName()
let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent(fileName)
do {
try textView.text.write(to: fileURL, atomically: true, encoding: .utf8)
let documentPicker = UIDocumentPickerViewController(forExporting: [fileURL], asCopy: true)
documentPicker.delegate = self
present(documentPicker, animated: true)
} catch {
print("Failed to write file: \(error)")
}
}
private func generateFileName() -> String {
let text = textView.text ?? ""
let firstLine = text.components(separatedBy: .newlines).first ?? ""
let trimmed = firstLine.trimmingCharacters(in: .whitespaces)
let withoutHash = trimmed.replacingOccurrences(of: "^#\\s*", with: "", options: .regularExpression)
let cleanName = withoutHash.trimmingCharacters(in: .whitespaces)
if cleanName.isEmpty {
return "formatted_note_\(Int(Date().timeIntervalSince1970)).md"
}
return cleanName + ".md"
}
private func removeEmptyLines(_ text: String) -> String {
let lines = text.components(separatedBy: .newlines)
let nonEmptyLines = lines.filter { !$0.trimmingCharacters(in: .whitespaces).isEmpty }
return nonEmptyLines.joined(separator: "\n")
}
private func insertEmptyLineAfterFirstLine(_ text: String) -> String {
var lines = text.components(separatedBy: .newlines)
if !lines.isEmpty {
lines.insert("", at: 1)
}
return lines.joined(separator: "\n")
}
}
extension FormatNoteViewController: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
// File saved to selected location
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
// User cancelled
}
}
extension FormatNoteViewController: UITextViewDelegate {
func textViewDidBeginEditing(_ textView: UITextView) {
updateRightBarButtonItem()
}
func textViewDidEndEditing(_ textView: UITextView) {
updateRightBarButtonItem()
}
}