Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Slur Filter Warning #867

Merged
merged 14 commits into from
Jan 28, 2024
2 changes: 1 addition & 1 deletion Mlem/API/Models/Site/APILocalSite.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ struct APILocalSite: Decodable {
// let legalInformation: String?
// let hideModlogModNames: Bool
// let applicationEmailAdmins: Bool
// let slurFilterRegex: String?
let slurFilterRegex: String?
// let actorNameMaxLength: Int
// let federationEnabled: Bool
// let federationDebug: Bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ protocol ResponseEditorModel: Identifiable {

var modalName: String { get } // what to title the modal

var showSlurWarning: Bool { get }

var prefillContents: String? { get } // optional, contents to prepopulate the editor with

func embeddedView() -> AnyView
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ struct CommentEditor: ResponseEditorModel {
@Dependency(\.commentRepository) var commentRepository

let canUpload: Bool = true
let showSlurWarning: Bool = true
let modalName: String = "Edit Comment"
var prefillContents: String? { comment.comment.content }
let comment: APICommentView
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ struct ReplyToComment: ResponseEditorModel {

var id: Int { comment.id }
let canUpload: Bool = true
let showSlurWarning: Bool = true
let modalName: String = "New Comment"
let comment: APICommentView
let prefillContents: String?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ struct ReplyToCommentReply: ResponseEditorModel {
@Dependency(\.commentRepository) var commentRepository

let canUpload: Bool = true
let showSlurWarning: Bool = true
let modalName: String = "New Comment"
let prefillContents: String? = nil
let commentReply: ReplyModel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ struct ReplyToMention: ResponseEditorModel {
@Dependency(\.commentRepository) var commentRepository

let canUpload: Bool = true
let showSlurWarning: Bool = true
let modalName: String = "New Comment"
let prefillContents: String? = nil
let mention: MentionModel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ struct ReplyToMessage: ResponseEditorModel {

var id: Int { message.id }
let canUpload: Bool = true
let showSlurWarning: Bool = true
let modalName: String = "New Message"
let prefillContents: String? = nil
let message: MessageModel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ struct ReplyToPost: ResponseEditorModel {
@Dependency(\.commentRepository) var commentRepository

let canUpload: Bool = true
let showSlurWarning: Bool = true
let modalName: String = "New Comment"
let prefillContents: String?
let commentTracker: CommentTracker?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ struct ReportComment: ResponseEditorModel {

var id: Int { comment.id }
let canUpload: Bool = false
let showSlurWarning: Bool = false
let modalName: String = "Report Comment"
let prefillContents: String? = nil
let comment: APICommentView
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ struct ReportCommentReply: ResponseEditorModel {

var id: Int { commentReply.id }
let canUpload: Bool = false
let showSlurWarning: Bool = false
let modalName: String = "Report Comment"
let prefillContents: String? = nil
let commentReply: ReplyModel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ struct ReportMention: ResponseEditorModel {

var id: Int { mention.id }
let canUpload: Bool = false
let showSlurWarning: Bool = false
let modalName: String = "Report Comment"
let prefillContents: String? = nil
let mention: MentionModel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ struct ReportMessage: ResponseEditorModel {

var id: Int { message.id }
let canUpload: Bool = false
let showSlurWarning: Bool = false
let modalName: String = "Report Message"
let prefillContents: String? = nil
let message: MessageModel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ struct ReportPost: ResponseEditorModel {

var id: Int { post.postId }
let canUpload: Bool = false
let showSlurWarning: Bool = false
let modalName: String = "Report Post"
let prefillContents: String? = nil
let post: PostModel
Expand Down
7 changes: 7 additions & 0 deletions Mlem/Models/Trackers/SiteInformationTracker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ class SiteInformationTracker: ObservableObject {
@Published var version: SiteVersion?
@Published private(set) var allLanguages: [APILanguage] = .init()
@Published var myUserInfo: APIMyUserInfo?
@Published var slurFilterRegex: Regex<AnyRegexOutput>?

func load(account: SavedAccount) {

version = account.siteVersion
Task {
do {
Expand All @@ -36,6 +38,11 @@ class SiteInformationTracker: ObservableObject {
}
myUserInfo = response.myUser
allLanguages = response.allLanguages

if let regex = response.siteView.localSite.slurFilterRegex {
slurFilterRegex = try .init(regex)
}

} catch {
errorHandler.handle(error)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,10 @@ class LinkAttachmentModel: ObservableObject {
if let task = self.uploadTask {
task.cancel()
}
self.photosPickerItem = nil
switch self.imageModel?.state {
case .uploaded(file: let file):
if let file = file {
self.photosPickerItem = nil
Task {
do {
if let compareUrl {
Expand Down
2 changes: 1 addition & 1 deletion Mlem/Views/Shared/Composer/PostComposerView+Logic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ extension PostComposerView {
var isReadyToPost: Bool {
switch attachmentModel.imageModel?.state {
case nil, .uploaded:
return postTitle.trimmed.isNotEmpty
return postTitle.trimmed.isNotEmpty && titleSlurMatch == nil && bodySlurMatch == nil
default:
return false
}
Expand Down
85 changes: 67 additions & 18 deletions Mlem/Views/Shared/Composer/PostComposerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ extension HorizontalAlignment {
static let labelStart = HorizontalAlignment(LabelStart.self)
}

// swiftlint:disable type_body_length
struct PostComposerView: View {
private enum Field: Hashable {
case title, url, body
Expand Down Expand Up @@ -54,6 +55,9 @@ struct PostComposerView: View {

@FocusState private var focusedField: Field?

@State var titleSlurMatch: String?
@State var bodySlurMatch: String?

init(editModel: PostEditorModel) {
self.editModel = editModel

Expand All @@ -73,22 +77,7 @@ struct PostComposerView: View {
.edgesIgnoringSafeArea(.bottom)
VStack(spacing: 0) {
// Community Row
HStack {
CommunityLabelView(
community: editModel.community,
serverInstanceLocation: .bottom,
overrideShowAvatar: true
)
Spacer()
if let person = siteInformation.myUserInfo?.localUserView.person {
UserLabelView(
person: person,
serverInstanceLocation: .bottom,
overrideShowAvatar: true
)
.environment(\.layoutDirection, layoutDirection == .leftToRight ? .rightToLeft : .leftToRight)
}
}
headerView
.padding(.bottom, 15)
.padding(.horizontal)
.zIndex(1)
Expand All @@ -103,17 +92,32 @@ struct PostComposerView: View {
}
.padding(.top)
.padding(.horizontal)
.onChange(of: postTitle) { newValue in
do {
if let regex = siteInformation.slurFilterRegex {
if let output = try regex.firstMatch(in: newValue.lowercased()) {
titleSlurMatch = String(newValue[output.range])
} else {
titleSlurMatch = nil
}
}
} catch {
print("REGEX FAILED")
}
Sjmarf marked this conversation as resolved.
Show resolved Hide resolved
}

if attachmentModel.imageModel != nil || attachmentModel.url.isNotEmpty {
VStack {
let url = URL(string: attachmentModel.url)
if !(url?.isImage ?? true) {
HStack(spacing: AppConstants.postAndCommentSpacing) {
Image(systemName: "link")
Image(systemName: Icons.websiteAddress)
.foregroundStyle(.blue)
.padding(.leading, 5)
Text(attachmentModel.url)
.foregroundStyle(.secondary)
.lineLimit(1)
Spacer()
Button(action: attachmentModel.removeLinkAction, label: {
Image(systemName: Icons.close)
.fontWeight(.semibold)
Expand Down Expand Up @@ -186,6 +190,19 @@ struct PostComposerView: View {
.accessibilityLabel("Post Body")
.focused($focusedField, equals: .body)
.padding(.horizontal)
.onChange(of: postBody) { newValue in
do {
if let regex = siteInformation.slurFilterRegex {
if let output = try regex.firstMatch(in: newValue.lowercased()) {
bodySlurMatch = String(newValue[output.range])
} else {
bodySlurMatch = nil
}
}
} catch {
print("REGEX FAILED")
}
Sjmarf marked this conversation as resolved.
Show resolved Hide resolved
}

Spacer()
}
Expand Down Expand Up @@ -236,7 +253,7 @@ struct PostComposerView: View {
}
ToolbarItem(placement: .navigationBarTrailing) {
LinkUploadOptionsView(model: attachmentModel) {
Label("Attach image or link", systemImage: "link")
Label("Attach Image or Link", systemImage: Icons.websiteAddress)
}
.disabled(attachmentModel.imageModel != nil || attachmentModel.url.isNotEmpty)
}
Expand All @@ -261,4 +278,36 @@ struct PostComposerView: View {
.navigationBarColor()
.navigationBarTitleDisplayMode(.inline)
}

@ViewBuilder
var headerView: some View {
HStack {
CommunityLabelView(
community: editModel.community,
serverInstanceLocation: .bottom,
overrideShowAvatar: true
)
Spacer()
if let person = siteInformation.myUserInfo?.localUserView.person {
UserLabelView(
person: person,
serverInstanceLocation: .bottom,
overrideShowAvatar: true
)
.environment(\.layoutDirection, layoutDirection == .leftToRight ? .rightToLeft : .leftToRight)
}
}
.overlay {
if let slurMatch = titleSlurMatch == nil ? bodySlurMatch : titleSlurMatch {
ZStack {
Capsule()
.fill(.red)
Text("\"\(slurMatch)\" is disallowed.")
EricBAndrews marked this conversation as resolved.
Show resolved Hide resolved
}
.padding(-2)
}
}
.animation(.default, value: titleSlurMatch == nil && bodySlurMatch == nil)
}
}
// swiftlint:enable type_body_length
46 changes: 36 additions & 10 deletions Mlem/Views/Shared/Composer/ResponseEditorView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ struct ResponseEditorView: View {
}

@Dependency(\.errorHandler) var errorHandler
@Dependency(\.siteInformation) var siteInformation

let editorModel: any ResponseEditorModel

Expand All @@ -28,20 +29,14 @@ struct ResponseEditorView: View {
@State var editorBody: String
@State var isSubmitting: Bool = false

@State var slurMatch: String?

@FocusState private var focusedField: Field?

private var isReadyToReply: Bool {
editorBody.trimmed.isNotEmpty
}

func uploadImage() {
if editorModel.canUpload {
print("Uploading")
} else {
print("Uploading disabled for this sort of response")
}
}


var body: some View {
ScrollView {
VStack(spacing: AppConstants.postAndCommentSpacing) {
Expand All @@ -58,11 +53,42 @@ struct ResponseEditorView: View {
.onAppear {
focusedField = .editorBody
}
.onChange(of: editorBody) { newValue in
if editorModel.showSlurWarning {
do {
if let regex = siteInformation.slurFilterRegex {
if let output = try regex.firstMatch(in: newValue.lowercased()) {
slurMatch = String(newValue[output.range])
} else {
slurMatch = nil
}
}
} catch {
print("REGEX FAILED")
}
Sjmarf marked this conversation as resolved.
Show resolved Hide resolved
}
}

Divider()

editorModel.embeddedView()
if let slurMatch {
VStack {
Text("\"\(slurMatch)\" is disallowed.")
.foregroundStyle(.white)
Text("You can still post this comment, but your instance will replace \"\(slurMatch)\" with \"*removed*\".")
.multilineTextAlignment(.center)
.font(.footnote)
.foregroundStyle(.white.opacity(0.8))
}
.padding()
.frame(maxWidth: .infinity)
.background(RoundedRectangle(cornerRadius: AppConstants.largeItemCornerRadius).fill(.red))
.padding(.horizontal, 10)
} else {
editorModel.embeddedView()
}
}
.animation(.default, value: slurMatch)
.padding(.bottom, AppConstants.editorOverscroll)
}
.scrollDismissesKeyboard(.automatic)
Expand Down
Loading