Page Menu
Home
Musing Studio
Search
Configure Global Search
Log In
Files
F13892463
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
16 KB
Subscribers
None
View Options
diff --git a/macOS/PostEditor/MacEditorTextView.swift b/macOS/PostEditor/MacEditorTextView.swift
index 6bdac42..852c38a 100644
--- a/macOS/PostEditor/MacEditorTextView.swift
+++ b/macOS/PostEditor/MacEditorTextView.swift
@@ -1,203 +1,203 @@
// Based on:
//
// MacEditorTextView
// Copyright (c) Thiago Holanda 2020
// https://twitter.com/tholanda
//
// MIT license
//
// See: https://gist.github.com/unnamedd/6e8c3fbc806b8deb60fa65d6b9affab0
import Combine
import SwiftUI
struct MacEditorTextView: NSViewRepresentable {
@Binding var text: String
var isFirstResponder: Bool = false
var isEditable: Bool = true
- var font: NSFont? = .systemFont(ofSize: 14, weight: .regular)
+ var font: NSFont? = NSFont(name: PostAppearance.serif.rawValue, size: 17)
var onEditingChanged: () -> Void = {}
var onCommit: () -> Void = {}
var onTextChange: (String) -> Void = { _ in }
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeNSView(context: Context) -> CustomTextView {
let textView = CustomTextView(
text: text,
isEditable: isEditable,
isFirstResponder: isFirstResponder,
font: font
)
textView.delegate = context.coordinator
return textView
}
func updateNSView(_ view: CustomTextView, context: Context) {
view.text = text
view.selectedRanges = context.coordinator.selectedRanges
}
}
// MARK: - Coordinator
extension MacEditorTextView {
class Coordinator: NSObject, NSTextViewDelegate {
var parent: MacEditorTextView
var selectedRanges: [NSValue] = []
var didBecomeFirstResponder: Bool = false
init(_ parent: MacEditorTextView) {
self.parent = parent
}
func textDidBeginEditing(_ notification: Notification) {
guard let textView = notification.object as? NSTextView else {
return
}
self.parent.text = textView.string
self.parent.onEditingChanged()
}
func textDidChange(_ notification: Notification) {
guard let textView = notification.object as? NSTextView else {
return
}
self.parent.text = textView.string
self.selectedRanges = textView.selectedRanges
self.parent.onTextChange(textView.string)
}
func textDidEndEditing(_ notification: Notification) {
guard let textView = notification.object as? NSTextView else {
return
}
self.parent.text = textView.string
self.parent.onCommit()
}
}
}
// MARK: - CustomTextView
final class CustomTextView: NSView {
private var isFirstResponder: Bool
private var isEditable: Bool
private var font: NSFont?
weak var delegate: NSTextViewDelegate?
var text: String {
didSet {
textView.string = text
}
}
var selectedRanges: [NSValue] = [] {
didSet {
guard selectedRanges.count > 0 else {
return
}
textView.selectedRanges = selectedRanges
}
}
private lazy var scrollView: NSScrollView = {
let scrollView = NSScrollView()
scrollView.drawsBackground = false
scrollView.borderType = .noBorder
scrollView.hasVerticalScroller = true
scrollView.hasHorizontalRuler = false
scrollView.autoresizingMask = [.width, .height]
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
private lazy var textView: NSTextView = {
let contentSize = scrollView.contentSize
let textStorage = NSTextStorage()
let layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
let textContainer = NSTextContainer(containerSize: scrollView.frame.size)
textContainer.widthTracksTextView = true
textContainer.containerSize = NSSize(
width: contentSize.width,
height: CGFloat.greatestFiniteMagnitude
)
layoutManager.addTextContainer(textContainer)
let textView = NSTextView(frame: .zero, textContainer: textContainer)
textView.autoresizingMask = .width
textView.delegate = self.delegate
textView.drawsBackground = false
textView.font = self.font
textView.isEditable = self.isEditable
textView.isHorizontallyResizable = false
textView.isVerticallyResizable = true
textView.maxSize = NSSize(
width: CGFloat.greatestFiniteMagnitude,
height: CGFloat.greatestFiniteMagnitude
)
textView.minSize = NSSize(width: 0, height: contentSize.height)
textView.textColor = NSColor.labelColor
return textView
}()
// MARK: - Init
init(text: String, isEditable: Bool, isFirstResponder: Bool, font: NSFont?) {
self.font = font
self.isFirstResponder = isFirstResponder
self.isEditable = isEditable
self.text = text
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Life cycle
override func viewWillDraw() {
super.viewWillDraw()
setupScrollViewConstraints()
setupTextView()
if isFirstResponder {
self.window?.makeFirstResponder(self.textView)
}
}
func setupScrollViewConstraints() {
scrollView.translatesAutoresizingMaskIntoConstraints = false
addSubview(scrollView)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: topAnchor),
scrollView.trailingAnchor.constraint(equalTo: trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: bottomAnchor),
scrollView.leadingAnchor.constraint(equalTo: leadingAnchor)
])
}
func setupTextView() {
scrollView.documentView = textView
}
}
diff --git a/macOS/PostEditor/PostEditorView.swift b/macOS/PostEditor/PostEditorView.swift
index 400bc3c..73a9acd 100644
--- a/macOS/PostEditor/PostEditorView.swift
+++ b/macOS/PostEditor/PostEditorView.swift
@@ -1,100 +1,98 @@
import SwiftUI
struct PostEditorView: View {
private let bodyLineSpacing: CGFloat = 17 * 0.5
@EnvironmentObject var model: WriteFreelyModel
@ObservedObject var post: WFAPost
@State private var isHovering: Bool = false
- @State private var updatingTitleFromServer: Bool = false
- @State private var updatingBodyFromServer: Bool = false
+ @State private var updatingFromServer: Bool = false
var body: some View {
PostTextEditingView(
post: post,
- updatingTitleFromServer: $updatingTitleFromServer,
- updatingBodyFromServer: $updatingBodyFromServer
+ updatingFromServer: $updatingFromServer
)
.padding()
.background(Color(NSColor.controlBackgroundColor))
.toolbar {
ToolbarItem(placement: .status) {
PostEditorStatusToolbarView(post: post)
}
ToolbarItem(placement: .primaryAction) {
Button(action: {
if model.account.isLoggedIn {
publishPost()
} else {
let mainMenu = NSApplication.shared.mainMenu
let appMenuItem = mainMenu?.item(withTitle: "WriteFreely")
let prefsItem = appMenuItem?.submenu?.item(withTitle: "Preferences…")
NSApplication.shared.sendAction(prefsItem!.action!, to: prefsItem?.target, from: nil)
}
}, label: {
Image(systemName: "paperplane")
})
.disabled(post.status == PostStatus.published.rawValue || post.body.count == 0)
}
}
.onChange(of: post.hasNewerRemoteCopy, perform: { _ in
if post.status == PostStatus.edited.rawValue && !post.hasNewerRemoteCopy {
post.status = PostStatus.published.rawValue
}
})
.onDisappear(perform: {
if post.title.count == 0
&& post.body.count == 0
&& post.status == PostStatus.local.rawValue
&& post.updatedDate == nil
&& post.postId == nil {
DispatchQueue.main.async {
model.posts.remove(post)
}
} else if post.status != PostStatus.published.rawValue {
DispatchQueue.main.async {
LocalStorageManager().saveContext()
}
}
})
}
private func publishPost() {
DispatchQueue.main.async {
LocalStorageManager().saveContext()
model.publish(post: post)
}
}
}
struct PostEditorView_EmptyPostPreviews: PreviewProvider {
static var previews: some View {
let context = LocalStorageManager.persistentContainer.viewContext
let testPost = WFAPost(context: context)
testPost.createdDate = Date()
testPost.appearance = "norm"
let model = WriteFreelyModel()
return PostEditorView(post: testPost)
.environment(\.managedObjectContext, context)
.environmentObject(model)
}
}
struct PostEditorView_ExistingPostPreviews: PreviewProvider {
static var previews: some View {
let context = LocalStorageManager.persistentContainer.viewContext
let testPost = WFAPost(context: context)
testPost.title = "Test Post Title"
testPost.body = "Here's some cool sample body text."
testPost.createdDate = Date()
testPost.appearance = "code"
let model = WriteFreelyModel()
return PostEditorView(post: testPost)
.environment(\.managedObjectContext, context)
.environmentObject(model)
}
}
diff --git a/macOS/PostEditor/PostTextEditingView.swift b/macOS/PostEditor/PostTextEditingView.swift
index c2864c0..3b7115b 100644
--- a/macOS/PostEditor/PostTextEditingView.swift
+++ b/macOS/PostEditor/PostTextEditingView.swift
@@ -1,124 +1,133 @@
import SwiftUI
struct PostTextEditingView: View {
@ObservedObject var post: WFAPost
- @Binding var updatingTitleFromServer: Bool
- @Binding var updatingBodyFromServer: Bool
- @State private var isHovering: Bool = false
+ @Binding var updatingFromServer: Bool
@State private var appearance: PostAppearance = .serif
@State private var combinedText = ""
var body: some View {
// VStack {
// TextField("Title (optional)", text: $post.title)
// .textFieldStyle(PlainTextFieldStyle())
// .padding(.horizontal, 4)
// .font(.custom(appearance.rawValue, size: 26, relativeTo: .largeTitle))
// .onChange(of: post.title) { _ in
// if post.status == PostStatus.published.rawValue && !updatingTitleFromServer {
// post.status = PostStatus.edited.rawValue
// }
// if updatingTitleFromServer {
// updatingTitleFromServer = false
// }
// }
// .padding(4)
// .background(Color(NSColor.controlBackgroundColor))
// .padding(.bottom)
// ZStack(alignment: .topLeading) {
// if post.body.count == 0 {
// Text("Write…")
// .foregroundColor(Color(NSColor.placeholderTextColor))
// .padding(.horizontal, 4)
// .padding(.vertical, 2)
// .font(.custom(appearance.rawValue, size: 17, relativeTo: .body))
// }
// TextEditor(text: $post.body)
// .font(.custom(appearance.rawValue, size: 17, relativeTo: .body))
// .opacity(post.body.count == 0 && !isHovering ? 0.0 : 1.0)
// .onChange(of: post.body) { _ in
// if post.status == PostStatus.published.rawValue && !updatingBodyFromServer {
// post.status = PostStatus.edited.rawValue
// }
// if updatingBodyFromServer {
// updatingBodyFromServer = false
// }
// }
// .onHover(perform: { hovering in
// self.isHovering = hovering
// })
// }
// .padding(4)
// .background(Color(NSColor.controlBackgroundColor))
// }
ZStack(alignment: .topLeading) {
if combinedText.count == 0 {
Text("Write…")
.foregroundColor(Color(NSColor.placeholderTextColor))
.padding(.horizontal, 5)
.font(.custom(appearance.rawValue, size: 17, relativeTo: .body))
}
- MacEditorTextView(
- text: $combinedText,
- isFirstResponder: combinedText.isEmpty,
- isEditable: true,
- font: NSFont(name: appearance.rawValue, size: 17),
- onEditingChanged: onEditingChanged,
- onCommit: onCommit,
- onTextChange: onTextChange
- )
+ if post.appearance == "sans" {
+ MacEditorTextView(
+ text: $combinedText,
+ isFirstResponder: combinedText.isEmpty,
+ isEditable: true,
+ font: NSFont(name: "OpenSans-Regular", size: 17),
+ onEditingChanged: onEditingChanged,
+ onCommit: onCommit,
+ onTextChange: onTextChange
+ )
+ } else if post.appearance == "wrap" || post.appearance == "mono" || post.appearance == "code" {
+ MacEditorTextView(
+ text: $combinedText,
+ isFirstResponder: combinedText.isEmpty,
+ isEditable: true,
+ font: NSFont(name: "Hack-Regular", size: 17),
+ onEditingChanged: onEditingChanged,
+ onCommit: onCommit,
+ onTextChange: onTextChange
+ )
+ } else {
+ MacEditorTextView(
+ text: $combinedText,
+ isFirstResponder: combinedText.isEmpty,
+ isEditable: true,
+ font: NSFont(name: "Lora-Regular", size: 17),
+ onEditingChanged: onEditingChanged,
+ onCommit: onCommit,
+ onTextChange: onTextChange
+ )
+ }
}
.background(Color(NSColor.controlBackgroundColor))
.onAppear(perform: {
- switch post.appearance {
- case "sans":
- self.appearance = .sans
- case "wrap", "mono", "code":
- self.appearance = .mono
- default:
- self.appearance = .serif
- }
- print("Font: \(appearance.rawValue)")
-
if post.title.isEmpty {
self.combinedText = post.body
} else {
self.combinedText = "# \(post.title)\n\n\(post.body)"
}
})
}
private func onEditingChanged() {
print("onEditingChanged fired")
}
private func onTextChange(_ text: String) {
- print("onTextChange fired")
extractTitle(text)
}
private func onCommit() {
print("onCommit fired")
}
private func extractTitle(_ text: String) {
var detectedTitle: String
if text.hasPrefix("# ") {
let endOfTitleIndex = text.firstIndex(of: "\n") ?? text.endIndex
detectedTitle = String(text[..<endOfTitleIndex])
self.post.title = String(detectedTitle.dropFirst("# ".count))
let remainingText = String(text.dropFirst(detectedTitle.count).dropFirst(1))
if remainingText.hasPrefix("\n") {
self.post.body = String(remainingText.dropFirst(1))
} else {
self.post.body = remainingText
}
} else {
self.post.title = ""
self.post.body = text
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Mar 12, 8:01 AM (8 h, 13 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3653350
Attached To
rWFSUI WriteFreely SwiftUI
Event Timeline
Log In to Comment