feat: display custom emoji for timeline post

This commit is contained in:
CMK 2021-05-07 18:25:57 +08:00
parent 8409331dd8
commit faeb8d99ef
17 changed files with 166 additions and 65 deletions

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="18154" systemVersion="20D75" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="18154" systemVersion="20E232" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Application" representedClassName=".Application" syncable="YES">
<attribute name="identifier" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
<attribute name="name" attributeType="String"/>
@ -45,7 +45,6 @@
<attribute name="staticURL" attributeType="String"/>
<attribute name="url" attributeType="String"/>
<attribute name="visibleInPicker" attributeType="Boolean" usesScalarValueType="YES"/>
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="emojis" inverseEntity="Status"/>
</entity>
<entity name="History" representedClassName=".History" syncable="YES">
<attribute name="accounts" optional="YES" attributeType="String"/>
@ -102,6 +101,7 @@
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="displayName" attributeType="String"/>
<attribute name="domain" attributeType="String"/>
<attribute name="emojisData" optional="YES" attributeType="Binary"/>
<attribute name="followersCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="followingCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="header" attributeType="String"/>
@ -197,6 +197,7 @@
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="deletedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="domain" attributeType="String"/>
<attribute name="emojisData" optional="YES" attributeType="Binary"/>
<attribute name="favouritesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="id" attributeType="String"/>
<attribute name="identifier" attributeType="String"/>
@ -216,7 +217,6 @@
<relationship name="application" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Application" inverseName="status" inverseEntity="Application"/>
<relationship name="author" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="statuses" inverseEntity="MastodonUser"/>
<relationship name="bookmarkedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="bookmarked" inverseEntity="MastodonUser"/>
<relationship name="emojis" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Emoji" inverseName="status" inverseEntity="Emoji"/>
<relationship name="favouritedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="favourite" inverseEntity="MastodonUser"/>
<relationship name="homeTimelineIndexes" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="HomeTimelineIndex" inverseName="status" inverseEntity="HomeTimelineIndex"/>
<relationship name="mediaAttachments" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Attachment" inverseName="status" inverseEntity="Attachment"/>
@ -267,12 +267,12 @@
<element name="Application" positionX="0" positionY="0" width="128" height="104"/>
<element name="Attachment" positionX="0" positionY="0" width="128" height="254"/>
<element name="DomainBlock" positionX="45" positionY="162" width="128" height="89"/>
<element name="Emoji" positionX="0" positionY="0" width="128" height="149"/>
<element name="Emoji" positionX="0" positionY="0" width="128" height="134"/>
<element name="History" positionX="0" positionY="0" width="128" height="119"/>
<element name="HomeTimelineIndex" positionX="0" positionY="0" width="128" height="134"/>
<element name="MastodonAuthentication" positionX="0" positionY="0" width="128" height="209"/>
<element name="MastodonNotification" positionX="9" positionY="162" width="128" height="164"/>
<element name="MastodonUser" positionX="0" positionY="0" width="128" height="659"/>
<element name="MastodonUser" positionX="0" positionY="0" width="128" height="689"/>
<element name="Mention" positionX="0" positionY="0" width="128" height="149"/>
<element name="Poll" positionX="0" positionY="0" width="128" height="194"/>
<element name="PollOption" positionX="0" positionY="0" width="128" height="134"/>

View File

@ -25,6 +25,9 @@ final public class MastodonUser: NSManagedObject {
@NSManaged public private(set) var headerStatic: String?
@NSManaged public private(set) var note: String?
@NSManaged public private(set) var url: String?
@NSManaged public private(set) var emojisData: Data?
@NSManaged public private(set) var statusesCount: NSNumber
@NSManaged public private(set) var followingCount: NSNumber
@NSManaged public private(set) var followersCount: NSNumber
@ -88,6 +91,8 @@ extension MastodonUser {
user.headerStatic = property.headerStatic
user.note = property.note
user.url = property.url
user.emojisData = property.emojisData
user.statusesCount = NSNumber(value: property.statusesCount)
user.followingCount = NSNumber(value: property.followingCount)
user.followersCount = NSNumber(value: property.followersCount)
@ -151,6 +156,11 @@ extension MastodonUser {
self.url = url
}
}
public func update(emojisData: Data?) {
if self.emojisData != emojisData {
self.emojisData = emojisData
}
}
public func update(statusesCount: Int) {
if self.statusesCount.intValue != statusesCount {
self.statusesCount = NSNumber(value: statusesCount)
@ -270,6 +280,7 @@ extension MastodonUser {
public let headerStatic: String?
public let note: String?
public let url: String?
public let emojisData: Data?
public let statusesCount: Int
public let followingCount: Int
public let followersCount: Int
@ -292,6 +303,7 @@ extension MastodonUser {
headerStatic: String?,
note: String?,
url: String?,
emojisData: Data?,
statusesCount: Int,
followingCount: Int,
followersCount: Int,
@ -313,6 +325,7 @@ extension MastodonUser {
self.headerStatic = headerStatic
self.note = note
self.url = url
self.emojisData = emojisData
self.statusesCount = statusesCount
self.followingCount = followingCount
self.followersCount = followersCount

View File

@ -24,6 +24,8 @@ public final class Status: NSManagedObject {
@NSManaged public private(set) var spoilerText: String?
@NSManaged public private(set) var application: Application?
@NSManaged public private(set) var emojisData: Data?
// Informational
@NSManaged public private(set) var reblogsCount: NSNumber
@NSManaged public private(set) var favouritesCount: NSNumber
@ -54,7 +56,6 @@ public final class Status: NSManagedObject {
// one-to-many relationship
@NSManaged public private(set) var reblogFrom: Set<Status>?
@NSManaged public private(set) var mentions: Set<Mention>?
@NSManaged public private(set) var emojis: Set<Emoji>?
@NSManaged public private(set) var tags: Set<Tag>?
@NSManaged public private(set) var homeTimelineIndexes: Set<HomeTimelineIndex>?
@NSManaged public private(set) var mediaAttachments: Set<Attachment>?
@ -77,7 +78,6 @@ extension Status {
replyTo: Status?,
poll: Poll?,
mentions: [Mention]?,
emojis: [Emoji]?,
tags: [Tag]?,
mediaAttachments: [Attachment]?,
favouritedBy: MastodonUser?,
@ -100,6 +100,8 @@ extension Status {
status.sensitive = property.sensitive
status.spoilerText = property.spoilerText
status.application = application
status.emojisData = property.emojisData
status.reblogsCount = property.reblogsCount
status.favouritesCount = property.favouritesCount
@ -121,9 +123,6 @@ extension Status {
if let mentions = mentions {
status.mutableSetValue(forKey: #keyPath(Status.mentions)).addObjects(from: mentions)
}
if let emojis = emojis {
status.mutableSetValue(forKey: #keyPath(Status.emojis)).addObjects(from: emojis)
}
if let tags = tags {
status.mutableSetValue(forKey: #keyPath(Status.tags)).addObjects(from: tags)
}
@ -148,6 +147,12 @@ extension Status {
return status
}
public func update(emojisData: Data?) {
if self.emojisData != emojisData {
self.emojisData = emojisData
}
}
public func update(reblogsCount: NSNumber) {
if self.reblogsCount.intValue != reblogsCount.intValue {
self.reblogsCount = reblogsCount
@ -248,6 +253,8 @@ extension Status {
public let sensitive: Bool
public let spoilerText: String?
public let emojisData: Data?
public let reblogsCount: NSNumber
public let favouritesCount: NSNumber
public let repliesCount: NSNumber?
@ -269,6 +276,7 @@ extension Status {
visibility: String?,
sensitive: Bool,
spoilerText: String?,
emojisData: Data?,
reblogsCount: NSNumber,
favouritesCount: NSNumber,
repliesCount: NSNumber?,
@ -288,6 +296,7 @@ extension Status {
self.visibility = visibility
self.sensitive = sensitive
self.spoilerText = spoilerText
self.emojisData = emojisData
self.reblogsCount = reblogsCount
self.favouritesCount = favouritesCount
self.repliesCount = repliesCount

View File

@ -400,6 +400,7 @@
DBAE3F9E2616E308004B8251 /* APIService+Mute.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */; };
DBAE3FA92617106E004B8251 /* MastodonMetricFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FA82617106E004B8251 /* MastodonMetricFormatter.swift */; };
DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */; };
DBAFB7352645463500371D5F /* Emojis.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAFB7342645463500371D5F /* Emojis.swift */; };
DBB525082611EAC0002F1F29 /* Tabman in Frameworks */ = {isa = PBXBuildFile; productRef = DBB525072611EAC0002F1F29 /* Tabman */; };
DBB5250E2611EBAF002F1F29 /* ProfileSegmentedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.swift */; };
DBB525212611EBD6002F1F29 /* ProfilePagingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB525202611EBD6002F1F29 /* ProfilePagingViewController.swift */; };
@ -962,6 +963,7 @@
DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Mute.swift"; sourceTree = "<group>"; };
DBAE3FA82617106E004B8251 /* MastodonMetricFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonMetricFormatter.swift; sourceTree = "<group>"; };
DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteProfileViewModel.swift; sourceTree = "<group>"; };
DBAFB7342645463500371D5F /* Emojis.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emojis.swift; sourceTree = "<group>"; };
DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSegmentedViewController.swift; sourceTree = "<group>"; };
DBB525202611EBD6002F1F29 /* ProfilePagingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePagingViewController.swift; sourceTree = "<group>"; };
DBB5252F2611EBF3002F1F29 /* ProfilePagingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePagingViewModel.swift; sourceTree = "<group>"; };
@ -1594,6 +1596,7 @@
DB6D9F6E2635807F008423CD /* Setting.swift */,
DB6D9F4826353FD6008423CD /* Subscription.swift */,
DB6D9F4F2635761F008423CD /* SubscriptionAlerts.swift */,
DBAFB7342645463500371D5F /* Emojis.swift */,
);
path = CoreDataStack;
sourceTree = "<group>";
@ -3199,6 +3202,7 @@
DB6180ED26391C6C0018D199 /* TransitioningMath.swift in Sources */,
2D6DE40026141DF600A63F6A /* SearchViewModel.swift in Sources */,
DB51D172262832380062B7A1 /* BlurHashDecode.swift in Sources */,
DBAFB7352645463500371D5F /* Emojis.swift in Sources */,
DBCC3B89261454BA0045B23D /* CGImage.swift in Sources */,
DBCCC71E25F73297007E1AB6 /* APIService+Reblog.swift in Sources */,
DB789A2B25F9F7AB0071ACA0 /* ComposeRepliedToStatusContentCollectionViewCell.swift in Sources */,
@ -3913,8 +3917,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/TwidereProject/ActiveLabel.swift";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 4.0.0;
kind = exactVersion;
version = 5.0.1;
};
};
2D5981B825E4D7F8000FB903 /* XCRemoteSwiftPackageReference "ThirdPartyMailer" */ = {

View File

@ -6,8 +6,8 @@
"repositoryURL": "https://github.com/TwidereProject/ActiveLabel.swift",
"state": {
"branch": null,
"revision": "d6cf96e0ca4f2269021bcf8f11381ab57897f84a",
"version": "4.0.0"
"revision": "40e104063d825d1125ef4b8eeb6460eba8a57483",
"version": "5.0.1"
}
},
{

View File

@ -68,7 +68,8 @@ extension ComposeStatusSection {
}()
cell.statusView.usernameLabel.text = "@" + (status.reblog ?? status).author.acct
// set text
cell.statusView.activeTextLabel.configure(content: status.content)
//status.emoji
cell.statusView.activeTextLabel.configure(content: status.content, emojiDict: [:])
// set date
cell.statusView.dateLabel.text = status.createdAt.shortTimeAgoSinceNow

View File

@ -158,10 +158,11 @@ extension StatusSection {
.store(in: &cell.disposeBag)
// set name username
cell.statusView.nameLabel.text = {
let nameText: String = {
let author = (status.reblog ?? status).author
return author.displayName.isEmpty ? author.username : author.displayName
}()
cell.statusView.nameLabel.configure(content: nameText, emojiDict: (status.reblog ?? status).author.emojiDict)
cell.statusView.usernameLabel.text = "@" + (status.reblog ?? status).author.acct
// set avatar
if let reblog = status.reblog {
@ -176,7 +177,10 @@ extension StatusSection {
}
// set text
cell.statusView.activeTextLabel.configure(content: (status.reblog ?? status).content)
cell.statusView.activeTextLabel.configure(
content: (status.reblog ?? status).content,
emojiDict: (status.reblog ?? status).emojiDict
)
// prepare media attachments
let mediaAttachments = Array((status.reblog ?? status).mediaAttachments ?? []).sorted { $0.index.compare($1.index) == .orderedAscending }
@ -569,15 +573,16 @@ extension StatusSection {
if status.reblog != nil {
cell.statusView.headerContainerView.isHidden = false
cell.statusView.headerIconLabel.attributedText = StatusView.iconAttributedString(image: StatusView.reblogIconImage)
cell.statusView.headerInfoLabel.text = {
let headerText: String = {
let author = status.author
let name = author.displayName.isEmpty ? author.username : author.displayName
return L10n.Common.Controls.Status.userReblogged(name)
}()
cell.statusView.headerInfoLabel.configure(content: headerText, emojiDict: status.author.emojiDict)
} else if status.inReplyToID != nil {
cell.statusView.headerContainerView.isHidden = false
cell.statusView.headerIconLabel.attributedText = StatusView.iconAttributedString(image: StatusView.replyIconImage)
cell.statusView.headerInfoLabel.text = {
let headerText: String = {
guard let replyTo = status.replyTo else {
return L10n.Common.Controls.Status.userRepliedTo("-")
}
@ -585,6 +590,7 @@ extension StatusSection {
let name = author.displayName.isEmpty ? author.username : author.displayName
return L10n.Common.Controls.Status.userRepliedTo(name)
}()
cell.statusView.headerInfoLabel.configure(content: headerText, emojiDict: status.replyTo?.author.emojiDict ?? [:])
} else {
cell.statusView.headerContainerView.isHidden = true
}

View File

@ -14,6 +14,8 @@ extension ActiveLabel {
enum Style {
case `default`
case statusHeader
case statusName
case profileField
}
@ -25,6 +27,7 @@ extension ActiveLabel {
mentionColor = Asset.Colors.Label.highlight.color
hashtagColor = Asset.Colors.Label.highlight.color
URLColor = Asset.Colors.Label.highlight.color
emojiPlaceholderColor = .systemFill
#if DEBUG
text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
#endif
@ -33,6 +36,14 @@ extension ActiveLabel {
case .default:
font = .preferredFont(forTextStyle: .body)
textColor = Asset.Colors.Label.primary.color
case .statusHeader:
font = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: .systemFont(ofSize: 13, weight: .medium))
textColor = Asset.Colors.Label.secondary.color
numberOfLines = 1
case .statusName:
font = .systemFont(ofSize: 17, weight: .semibold)
textColor = Asset.Colors.Label.primary.color
numberOfLines = 1
case .profileField:
font = .preferredFont(forTextStyle: .body)
textColor = Asset.Colors.Label.primary.color
@ -44,9 +55,10 @@ extension ActiveLabel {
extension ActiveLabel {
/// status content
func configure(content: String) {
func configure(content: String, emojiDict: MastodonStatusContent.EmojiDict) {
activeEntities.removeAll()
if let parseResult = try? MastodonStatusContent.parse(status: content) {
if let parseResult = try? MastodonStatusContent.parse(content: content, emojiDict: emojiDict) {
text = parseResult.trimmed
activeEntities = parseResult.activeEntities
} else {
@ -55,8 +67,8 @@ extension ActiveLabel {
}
/// account note
func configure(note: String) {
configure(content: note)
func configure(note: String, emojiDict: MastodonStatusContent.EmojiDict) {
configure(content: note, emojiDict: emojiDict)
}
}

View File

@ -0,0 +1,36 @@
//
// Emojis.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021-5-7.
//
import Foundation
import MastodonSDK
protocol EmojiContinaer {
var emojisData: Data? { get }
}
extension EmojiContinaer {
static func encode(emojis: [Mastodon.Entity.Emoji]) -> Data? {
return try? JSONEncoder().encode(emojis)
}
var emojis: [Mastodon.Entity.Emoji]? {
let decoder = JSONDecoder()
return emojisData.flatMap { try? decoder.decode([Mastodon.Entity.Emoji].self, from: $0) }
}
var emojiDict: MastodonStatusContent.EmojiDict {
var dict = MastodonStatusContent.EmojiDict()
for emoji in emojis ?? [] {
guard let url = URL(string: emoji.url) else { continue }
dict[emoji.shortcode] = url
}
return dict
}
}

View File

@ -23,6 +23,7 @@ extension MastodonUser.Property {
headerStatic: entity.headerStatic,
note: entity.note,
url: entity.url,
emojisData: entity.emojis.flatMap { Status.encode(emojis: $0) },
statusesCount: entity.statusesCount,
followingCount: entity.followingCount,
followersCount: entity.followersCount,
@ -98,3 +99,5 @@ extension MastodonUser {
return items
}
}
extension MastodonUser: EmojiContinaer { }

View File

@ -20,6 +20,7 @@ extension Status.Property {
visibility: entity.visibility?.rawValue,
sensitive: entity.sensitive ?? false,
spoilerText: entity.spoilerText,
emojisData: entity.emojis.flatMap { Status.encode(emojis: $0) },
reblogsCount: NSNumber(value: entity.reblogsCount),
favouritesCount: NSNumber(value: entity.favouritesCount),
repliesCount: entity.repliesCount.flatMap { NSNumber(value: $0) },
@ -86,3 +87,5 @@ extension Status {
return items
}
}
extension Status: EmojiContinaer { }

View File

@ -11,9 +11,21 @@ import ActiveLabel
enum MastodonStatusContent {
static func parse(status: String) throws -> MastodonStatusContent.ParseResult {
let status = status.replacingOccurrences(of: "<br/>", with: "\n")
let rootNode = try Node.parse(document: status)
typealias EmojiShortcode = String
typealias EmojiDict = [EmojiShortcode: URL]
static func parse(content: String, emojiDict: EmojiDict) throws -> MastodonStatusContent.ParseResult {
let document: String = {
var content = content
content = content.replacingOccurrences(of: "<br/>", with: "\n")
for (shortcode, url) in emojiDict {
let emojiNode = "<span class=\"emoji\" href=\"\(url.absoluteString)\">\(shortcode)</span>"
let pattern = ":\(shortcode):"
content = content.replacingOccurrences(of: pattern, with: emojiNode)
}
return content
}()
let rootNode = try Node.parse(document: document)
let text = String(rootNode.text)
var activeEntities: [ActiveEntity] = []
@ -25,7 +37,7 @@ enum MastodonStatusContent {
case .url:
guard let href = entity.href else { continue }
let text = String(entity.text)
activeEntities.append(ActiveEntity(range: range, type: .url(text, trimmed: entity.hrefEllipsis ?? text, url: href)))
activeEntities.append(ActiveEntity(range: range, type: .url(text, trimmed: entity.hrefEllipsis ?? text, url: href, userInfo: nil)))
case .hashtag:
var userInfo: [AnyHashable: Any] = [:]
entity.href.flatMap { href in
@ -40,30 +52,47 @@ enum MastodonStatusContent {
}
let mention = String(entity.text).deletingPrefix("@")
activeEntities.append(ActiveEntity(range: range, type: .mention(mention, userInfo: userInfo)))
default:
case .emoji:
var userInfo: [AnyHashable: Any] = [:]
guard let href = entity.href else { continue }
userInfo["href"] = href
let emoji = String(entity.text)
activeEntities.append(ActiveEntity(range: range, type: .emoji(emoji, url: href, userInfo: userInfo)))
case .none:
continue
}
}
var trimmed = text
for activeEntity in activeEntities {
guard case .url = activeEntity.type else { continue }
MastodonStatusContent.trimEntity(status: &trimmed, activeEntity: activeEntity, activeEntities: activeEntities)
MastodonStatusContent.trimEntity(toot: &trimmed, activeEntity: activeEntity, activeEntities: activeEntities)
}
return ParseResult(
document: status,
document: document,
original: text,
trimmed: trimmed,
activeEntities: validate(text: trimmed, activeEntities: activeEntities) ? activeEntities : []
activeEntities: activeEntities
)
}
static func trimEntity(status: inout String, activeEntity: ActiveEntity, activeEntities: [ActiveEntity]) {
guard case let .url(text, trimmed, _, _) = activeEntity.type else { return }
static func trimEntity(toot: inout String, activeEntity: ActiveEntity, activeEntities: [ActiveEntity]) {
let text: String
let trimmed: String
switch activeEntity.type {
case .url(let _text, let _trimmed, _, _):
text = _text
trimmed = _trimmed
case .emoji(let _text, _, _):
text = _text
trimmed = " "
default:
return
}
guard let index = activeEntities.firstIndex(where: { $0.range == activeEntity.range }) else { return }
guard let range = Range(activeEntity.range, in: status) else { return }
status.replaceSubrange(range, with: trimmed)
guard let range = Range(activeEntity.range, in: toot) else { return }
toot.replaceSubrange(range, with: trimmed)
let offset = trimmed.count - text.count
activeEntity.range.length += offset
@ -73,19 +102,6 @@ enum MastodonStatusContent {
moveActiveEntity.range.location += offset
}
}
private static func validate(text: String, activeEntities: [ActiveEntity]) -> Bool {
for activeEntity in activeEntities {
let count = text.utf16.count
let endIndex = activeEntity.range.location + activeEntity.range.length
guard endIndex <= count else {
assertionFailure("Please file issue")
return false
}
}
return true
}
}
@ -106,6 +122,7 @@ extension MastodonStatusContent {
}
}
extension MastodonStatusContent {
class Node {
@ -154,6 +171,10 @@ extension MastodonStatusContent {
}
}
if _classNames.contains("emoji") {
return .emoji
}
return nil
}()
self.level = level
@ -257,6 +278,7 @@ extension MastodonStatusContent.Node {
case url
case mention
case hashtag
case emoji
}
static func entities(in node: MastodonStatusContent.Node) -> [MastodonStatusContent.Node] {

View File

@ -175,7 +175,7 @@ extension ProfileHeaderViewController {
.receive(on: DispatchQueue.main)
.sink { [weak self] isEditing, note, editingNote in
guard let self = self else { return }
self.profileHeaderView.bioActiveLabel.configure(note: note ?? "")
self.profileHeaderView.bioActiveLabel.configure(note: note ?? "", emojiDict: [:]) // FIXME: custom emoji
self.profileHeaderView.bioTextEditorView.text = editingNote ?? ""
}
.store(in: &disposeBag)

View File

@ -20,7 +20,7 @@ final class ProfileFieldView: UIView {
let valueActiveLabel: ActiveLabel = {
let label = ActiveLabel(style: .profileField)
label.configure(content: "value")
label.configure(content: "value", emojiDict: [:])
return label
}()

View File

@ -108,7 +108,7 @@ class SettingsViewController: UIViewController, NeedsDependency {
let label = ActiveLabel(style: .default)
label.textAlignment = .center
label.configure(content: "Mastodon is open source software. You can contribute or report issues on GitHub at <a href=\"https://github.com/tootsuite/mastodon\">tootsuite/mastodon</a> (v3.3.0).")
label.configure(content: "Mastodon is open source software. You can contribute or report issues on GitHub at <a href=\"https://github.com/tootsuite/mastodon\">tootsuite/mastodon</a> (v3.3.0).", emojiDict: [:])
label.delegate = self
view.addArrangedSubview(label)

View File

@ -11,7 +11,7 @@ import AVKit
import ActiveLabel
import AlamofireImage
protocol StatusViewDelegate: class {
protocol StatusViewDelegate: AnyObject {
func statusView(_ statusView: StatusView, headerInfoLabelDidPressed label: UILabel)
func statusView(_ statusView: StatusView, avatarButtonDidPressed button: UIButton)
func statusView(_ statusView: StatusView, revealContentWarningButtonDidPressed button: UIButton)
@ -69,10 +69,8 @@ final class StatusView: UIView {
return label
}()
let headerInfoLabel: UILabel = {
let label = UILabel()
label.font = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: .systemFont(ofSize: 13, weight: .medium))
label.textColor = Asset.Colors.Label.secondary.color
let headerInfoLabel: ActiveLabel = {
let label = ActiveLabel(style: .statusHeader)
label.text = "Bob reblogged"
return label
}()
@ -87,10 +85,8 @@ final class StatusView: UIView {
}()
let avatarStackedContainerButton: AvatarStackContainerButton = AvatarStackContainerButton()
let nameLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 17, weight: .semibold)
label.textColor = Asset.Colors.Label.primary.color
let nameLabel: ActiveLabel = {
let label = ActiveLabel(style: .statusName)
label.text = "Alice"
return label
}()

View File

@ -89,9 +89,6 @@ extension APIService.CoreData {
let metions = entity.mentions?.enumerated().compactMap { index, mention -> Mention in
Mention.insert(into: managedObjectContext, property: Mention.Property(id: mention.id, username: mention.username, acct: mention.acct, url: mention.url), index: index)
}
let emojis = entity.emojis?.compactMap { emoji -> Emoji in
Emoji.insert(into: managedObjectContext, property: Emoji.Property(shortcode: emoji.shortcode, url: emoji.url, staticURL: emoji.staticURL, visibleInPicker: emoji.visibleInPicker, category: emoji.category))
}
let tags = entity.tags?.compactMap { tag -> Tag in
let histories = tag.history?.compactMap { history -> History in
History.insert(into: managedObjectContext, property: History.Property(day: history.day, uses: history.uses, accounts: history.accounts))
@ -121,7 +118,6 @@ extension APIService.CoreData {
replyTo: replyTo,
poll: poll,
mentions: metions,
emojis: emojis,
tags: tags,
mediaAttachments: mediaAttachments,
favouritedBy: (entity.favourited ?? false) ? requestMastodonUser : nil,