Skip to content

Commit

Permalink
Move data format menu into format namespace
Browse files Browse the repository at this point in the history
  • Loading branch information
danielsaidi committed Jan 18, 2024
1 parent ce84a69 commit c575a95
Show file tree
Hide file tree
Showing 16 changed files with 187 additions and 171 deletions.
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ This release starts moving types and views that relate to other types into the t
* `RichTextColorPicker` has been renamed to `RichTextColor.Picker`.
* `RichTextCommand` views are now nested within the new `RichTextCommand` type.
* `RichTextComponent` deprecates the font name and size functions.
* `RichTextDataFormatMenu` has been renamed to `RichTextDataFormat.Menu`.



Expand Down
4 changes: 2 additions & 2 deletions Sources/RichTextKit/Component/RichTextViewComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public protocol RichTextViewComponent: AnyObject,

// MARK: - Setup

/// Setup the view with a text and ``RichTextDataFormat``.
/// Setup the view with a text and data format.
func setup(
with text: NSAttributedString,
format: RichTextDataFormat
Expand Down Expand Up @@ -109,7 +109,7 @@ public extension RichTextViewComponent {
setSelectedRange(safeRange)
}

/// Setup the view with data and a ``RichTextDataFormat``.
/// Setup the view with data and a data format.
func setup(
with data: Data,
format: RichTextDataFormat
Expand Down
11 changes: 7 additions & 4 deletions Sources/RichTextKit/Data/NSAttributedString+Init.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// RichTextKit
//
// Created by Daniel Saidi on 2022-06-03.
// Copyright © 2022-2023 Daniel Saidi. All rights reserved.
// Copyright © 2022-2024 Daniel Saidi. All rights reserved.
//

import Foundation
Expand Down Expand Up @@ -74,7 +74,8 @@ private extension NSAttributedString {
try self.init(
data: data,
options: [.characterEncoding: Self.utf8],
documentAttributes: &attributes)
documentAttributes: &attributes
)
}

/**
Expand All @@ -85,7 +86,8 @@ private extension NSAttributedString {
try self.init(
data: data,
options: [.characterEncoding: Self.utf8],
documentAttributes: &attributes)
documentAttributes: &attributes
)
}

#if macOS
Expand All @@ -97,7 +99,8 @@ private extension NSAttributedString {
try self.init(
data: data,
options: [.characterEncoding: Self.utf8],
documentAttributes: &attributes)
documentAttributes: &attributes
)
}
#endif
}
Expand Down
9 changes: 4 additions & 5 deletions Sources/RichTextKit/Data/RichTextDataError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,16 @@
// RichTextKit
//
// Created by Daniel Saidi on 2022-06-04.
// Copyright © 2022-2023 Daniel Saidi. All rights reserved.
// Copyright © 2022-2024 Daniel Saidi. All rights reserved.
//

import Foundation

/**
This enum represents errors that can be thrown when getting
rich text data for a certain format.
This enum represents rich text data-related errors.
*/
public enum RichTextDataError: Error {

enum RichTextDataError: Error {
case invalidArchivedData(in: Data)
case invalidPlainTextData(in: Data)
case invalidData(in: String)
Expand Down
81 changes: 81 additions & 0 deletions Sources/RichTextKit/Data/RichTextDataFormat+Menu.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//
// RichTextDataFormat+Menu.swift
// RichTextKit
//
// Created by Daniel Saidi on 2022-12-19.
// Copyright © 2022-2024 Daniel Saidi. All rights reserved.
//

#if iOS || macOS || os(visionOS)
import SwiftUI

public extension RichTextDataFormat {

/**
This menu can be used to trigger custom actions for any
list of ``RichTextDataFormat`` values.

The menu uses customizable actions, which means that it
can be used in toolbars, menu bar commands etc. It also
has an optional `pdf` action, which for instance can be
used when exporting or sharing rich text.
*/
struct Menu: View {

public init(
title: String,
icon: Image,
formats: [Format] = Format.libraryFormats,
formatAction: @escaping (Format) -> Void,
pdfAction: (() -> Void)? = nil
) {
self.title = title
self.icon = icon
self.formats = formats
self.formatAction = formatAction
self.pdfAction = pdfAction
}

public typealias Format = RichTextDataFormat

private let title: String
private let icon: Image
private let formats: [Format]
private let formatAction: (Format) -> Void
private let pdfAction: (() -> Void)?

public var body: some View {
SwiftUI.Menu {
ForEach(formats) { format in
Button {
formatAction(format)
} label: {
Label(format.fileFormatText, icon)
}
}
if let action = pdfAction {
Button(action: action) {
Label(RTKL10n.fileFormatPdf.text, icon)
}
}
} label: {
Label(title, icon)
}
}
}
}

struct RichTextData_FormatMenu_Previews: PreviewProvider {

static var previews: some View {
VStack {
RichTextDataFormat.Menu(
title: "Export...",
icon: .richTextActionExport,
formatAction: { _ in },
pdfAction: {}
)
}
}
}
#endif
107 changes: 53 additions & 54 deletions Sources/RichTextKit/Data/RichTextDataFormat.swift
Original file line number Diff line number Diff line change
@@ -1,140 +1,139 @@
//
// RichTextDataFormat.swift
// RichTextData+Format.swift
// RichTextKit
//
// Created by Daniel Saidi on 2022-06-02.
// Copyright © 2022-2023 Daniel Saidi. All rights reserved.
// Copyright © 2022-2024 Daniel Saidi. All rights reserved.
//

import Foundation
import UniformTypeIdentifiers

/**
This enum specifies rich text data formats.

Different formats handle rich text in different ways.

For instance, ``rtf`` supports rich text attributes, styles,
formatting etc. while ``plainText`` only handles plain text.
``archivedData`` lets you archive text and attachments into
a binary archive, which is convenient if you stick to Apple
platforms, but restricts how the data can be used elsewhere.

The ``archivedData`` format uses `rtk` a file extension, as
well as a `UTType.archivedData` uniform type. You can use a
``RichTextDataFormat/vendorArchivedData(id:fileExtension:fileFormatText:uniformType:)``
value to specify custom formats.


Different formats have different capabilities. For instance,
``rtf`` supports rich text, styles, etc., while ``plainText``
only handles text. ``archivedData`` can archive text, image
data and attachments in binary archives. This is convenient
when only targeting Apple platforms, but restricts how data
can be used elsewhere.

``archivedData`` uses an `rtk` file extension, as well as a
`UTType.archivedData` uniform type. You can create a custom ``vendorArchivedData(id:fileExtension:fileFormatText:uniformType:)``
value to specify a custom data format.

Remember to configure your app for handling the UTTypes you
want to support, as well as the file extensions you want to
open with the app. Have a look at the demo app for examples.
open with the app. Take a look at the demo app for examples.
*/
public enum RichTextDataFormat: Equatable, Identifiable {

/// Archived data that's persisted with a keyed archiver.
case archivedData

/// Plain data is persisted as plain text.
case plainText

/// RTF data is persisted as formatted text.
case rtf

/// A vendor-specific archived data format.
case vendorArchivedData(
id: String,
fileExtension: String,
fileFormatText: String,
uniformType: UTType)
uniformType: UTType
)
}

public extension Collection where Element == RichTextDataFormat {

/// Get all library supported data formats.
static var libraryFormats: [RichTextDataFormat] {
RichTextDataFormat.libraryFormats
static var libraryFormats: [Element] {
Element.libraryFormats
}
}

public extension RichTextDataFormat {

/// Get all library supported data formats.
static var libraryFormats: [RichTextDataFormat] {
static var libraryFormats: [Self] {
[.archivedData, .plainText, .rtf]
}

/// The format's unique identifier.
var id: String {
switch self {
case .archivedData: return "archivedData"
case .plainText: return "plainText"
case .rtf: return "rtf"
case .vendorArchivedData(let id, _, _, _): return id
case .archivedData: "archivedData"
case .plainText: "plainText"
case .rtf: "rtf"
case .vendorArchivedData(let id, _, _, _): id
}
}

/// The formats that a format can be converted to.
var convertibleFormats: [RichTextDataFormat] {
var convertibleFormats: [Self] {
switch self {
case .vendorArchivedData: return Self.libraryFormats.removing(.archivedData)
default: return Self.libraryFormats.removing(self)
case .vendorArchivedData: Self.libraryFormats.removing(.archivedData)
default: Self.libraryFormats.removing(self)
}
}

/// The format's file format display text.
var fileFormatText: String {
switch self {
case .archivedData: return RTKL10n.fileFormatRtk.text
case .plainText: return RTKL10n.fileFormatTxt.text
case .rtf: return RTKL10n.fileFormatRtf.text
case .vendorArchivedData(_, _, let text, _): return text
case .archivedData: RTKL10n.fileFormatRtk.text
case .plainText: RTKL10n.fileFormatTxt.text
case .rtf: RTKL10n.fileFormatRtf.text
case .vendorArchivedData(_, _, let text, _): text
}
}

/// Whether or not the format is an archived data type.
var isArchivedDataFormat: Bool {
switch self {
case .archivedData: return true
case .plainText: return false
case .rtf: return false
case .vendorArchivedData: return true
case .archivedData: true
case .plainText: false
case .rtf: false
case .vendorArchivedData: true
}
}

/// The format's standard file extension.
var standardFileExtension: String {
switch self {
case .archivedData: return "rtk"
case .plainText: return "txt"
case .rtf: return "rtf"
case .vendorArchivedData(_, let ext, _, _): return ext
case .archivedData: "rtk"
case .plainText: "txt"
case .rtf: "rtf"
case .vendorArchivedData(_, let ext, _, _): ext
}
}

/// Whether or not the format supports images.
var supportsImages: Bool {
switch self {
case .archivedData: return true
case .plainText: return false
case .rtf: return false
case .vendorArchivedData: return true
case .archivedData: true
case .plainText: false
case .rtf: false
case .vendorArchivedData: true
}
}

/// The format's uniform type.
var uniformType: UTType {
switch self {
case .archivedData: return .archivedData
case .plainText: return .plainText
case .rtf: return .rtf
case .vendorArchivedData(_, _, _, let type): return type
case .archivedData: .archivedData
case .plainText: .plainText
case .rtf: .rtf
case .vendorArchivedData(_, _, _, let type): type
}
}
}

private extension Collection where Element == RichTextDataFormat {

func removing(_ format: RichTextDataFormat) -> [RichTextDataFormat] {
func removing(_ format: Element) -> [Element] {
filter { $0 != format }
}
}
Loading

0 comments on commit c575a95

Please sign in to comment.