|
@@ -194,7 +194,7 @@ struct PostGeneratorView: View {
|
|
|
.frame(width: 1)
|
|
.frame(width: 1)
|
|
|
.padding(.vertical, 2)
|
|
.padding(.vertical, 2)
|
|
|
|
|
|
|
|
- composeColumn(title: "Post Content", icon: "doc.text") {
|
|
|
|
|
|
|
+ composeColumn(title: "Generated result", icon: "doc.badge.sparkles") {
|
|
|
editorPanel
|
|
editorPanel
|
|
|
}
|
|
}
|
|
|
.frame(minWidth: 0, maxWidth: .infinity)
|
|
.frame(minWidth: 0, maxWidth: .infinity)
|
|
@@ -310,119 +310,143 @@ struct PostGeneratorView: View {
|
|
|
|
|
|
|
|
private var editorPanel: some View {
|
|
private var editorPanel: some View {
|
|
|
VStack(spacing: Layout.sectionSpacing) {
|
|
VStack(spacing: Layout.sectionSpacing) {
|
|
|
- PostFormCard(title: "Title", subtitle: "Filled by AI after generating, or write your own") {
|
|
|
|
|
|
|
+ mediaAttachmentSection
|
|
|
|
|
+
|
|
|
|
|
+ if viewModel.hasGeneratedContent {
|
|
|
|
|
+ generatedContentSection
|
|
|
|
|
+ } else {
|
|
|
|
|
+ PostGeneratorEmptyResultPlaceholder()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @ViewBuilder
|
|
|
|
|
+ private var mediaAttachmentSection: some View {
|
|
|
|
|
+ switch viewModel.draft.postType {
|
|
|
|
|
+ case .image:
|
|
|
|
|
+ PostFormCard(title: "Media", subtitle: "Attach an image for your post — not used by AI") {
|
|
|
|
|
+ imageUploadArea
|
|
|
|
|
+ }
|
|
|
|
|
+ case .link:
|
|
|
|
|
+ PostFormCard(title: "Media", subtitle: "Attach the URL you'll share — not used by AI") {
|
|
|
|
|
+ VStack(alignment: .leading, spacing: 6) {
|
|
|
|
|
+ PostFormField(
|
|
|
|
|
+ label: "URL",
|
|
|
|
|
+ placeholder: "https://example.com/article",
|
|
|
|
|
+ text: $viewModel.draft.linkURL
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if !viewModel.draft.linkURL.isEmpty,
|
|
|
|
|
+ !PostDraftValidator.isValidHTTPURL(viewModel.draft.linkURL) {
|
|
|
|
|
+ validationHint("Enter a valid http or https URL.")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ case .video:
|
|
|
|
|
+ PostFormCard(title: "Media", subtitle: "Attach a YouTube, Vimeo, or video URL — not used by AI") {
|
|
|
|
|
+ VStack(alignment: .leading, spacing: 6) {
|
|
|
|
|
+ PostFormField(
|
|
|
|
|
+ label: "Video URL",
|
|
|
|
|
+ placeholder: "https://youtube.com/watch?v=…",
|
|
|
|
|
+ text: $viewModel.draft.videoURL
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if !viewModel.draft.videoURL.isEmpty,
|
|
|
|
|
+ !PostDraftValidator.isValidHTTPURL(viewModel.draft.videoURL) {
|
|
|
|
|
+ validationHint("Enter a valid http or https URL.")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ case .text, .poll:
|
|
|
|
|
+ EmptyView()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private var generatedContentSection: some View {
|
|
|
|
|
+ VStack(spacing: Layout.sectionSpacing) {
|
|
|
|
|
+ PostFormCard(title: "Title", subtitle: "Generated by AI — edit if you like") {
|
|
|
PostFormField(
|
|
PostFormField(
|
|
|
label: "Post Title",
|
|
label: "Post Title",
|
|
|
- placeholder: "An engaging title for your post",
|
|
|
|
|
|
|
+ placeholder: "Generated title appears here",
|
|
|
text: $viewModel.draft.title
|
|
text: $viewModel.draft.title
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- postTypeEditor
|
|
|
|
|
- .frame(minHeight: 260, alignment: .top)
|
|
|
|
|
|
|
+ generatedPostTypeEditor
|
|
|
|
|
+ .frame(minHeight: 160, alignment: .top)
|
|
|
|
|
|
|
|
characterCountFooter
|
|
characterCountFooter
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@ViewBuilder
|
|
@ViewBuilder
|
|
|
- private var postTypeEditor: some View {
|
|
|
|
|
|
|
+ private var generatedPostTypeEditor: some View {
|
|
|
switch viewModel.draft.postType {
|
|
switch viewModel.draft.postType {
|
|
|
case .text:
|
|
case .text:
|
|
|
textPostEditor
|
|
textPostEditor
|
|
|
case .image:
|
|
case .image:
|
|
|
- imagePostEditor
|
|
|
|
|
|
|
+ imageCaptionEditor
|
|
|
case .link:
|
|
case .link:
|
|
|
- linkPostEditor
|
|
|
|
|
|
|
+ linkDescriptionEditor
|
|
|
case .video:
|
|
case .video:
|
|
|
- videoPostEditor
|
|
|
|
|
|
|
+ videoDescriptionEditor
|
|
|
case .poll:
|
|
case .poll:
|
|
|
pollPostEditor
|
|
pollPostEditor
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private var textPostEditor: some View {
|
|
private var textPostEditor: some View {
|
|
|
- PostFormCard(title: "Body", subtitle: "Markdown supported — **bold**, *italic*, links") {
|
|
|
|
|
|
|
+ PostFormCard(title: "Body", subtitle: "Generated by AI — markdown supported") {
|
|
|
PostFormTextEditor(
|
|
PostFormTextEditor(
|
|
|
label: "Post Body",
|
|
label: "Post Body",
|
|
|
- placeholder: "Write your post content here…",
|
|
|
|
|
|
|
+ placeholder: "Edit the generated body…",
|
|
|
text: $viewModel.draft.body,
|
|
text: $viewModel.draft.body,
|
|
|
minHeight: 220
|
|
minHeight: 220
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private var imagePostEditor: some View {
|
|
|
|
|
- PostFormCard(title: "Image", subtitle: "Upload an image for your post") {
|
|
|
|
|
- VStack(alignment: .leading, spacing: 12) {
|
|
|
|
|
- imageUploadArea
|
|
|
|
|
-
|
|
|
|
|
- PostFormTextEditor(
|
|
|
|
|
- label: "Caption (optional)",
|
|
|
|
|
- placeholder: "Add context for your image…",
|
|
|
|
|
- text: $viewModel.draft.body,
|
|
|
|
|
- minHeight: 80
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ private var imageCaptionEditor: some View {
|
|
|
|
|
+ PostFormCard(title: "Caption", subtitle: "Generated by AI — edit if you like") {
|
|
|
|
|
+ PostFormTextEditor(
|
|
|
|
|
+ label: "Caption (optional)",
|
|
|
|
|
+ placeholder: "Edit the generated caption…",
|
|
|
|
|
+ text: $viewModel.draft.body,
|
|
|
|
|
+ minHeight: 80
|
|
|
|
|
+ )
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private var linkPostEditor: some View {
|
|
|
|
|
- PostFormCard(title: "Link", subtitle: "Share a URL with the community") {
|
|
|
|
|
- VStack(alignment: .leading, spacing: 12) {
|
|
|
|
|
- PostFormField(
|
|
|
|
|
- label: "URL",
|
|
|
|
|
- placeholder: "https://example.com/article",
|
|
|
|
|
- text: $viewModel.draft.linkURL
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- if !viewModel.draft.linkURL.isEmpty,
|
|
|
|
|
- !PostDraftValidator.isValidHTTPURL(viewModel.draft.linkURL) {
|
|
|
|
|
- validationHint("Enter a valid http or https URL.")
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- PostFormTextEditor(
|
|
|
|
|
- label: "Description (optional)",
|
|
|
|
|
- placeholder: "Why are you sharing this link?",
|
|
|
|
|
- text: $viewModel.draft.body,
|
|
|
|
|
- minHeight: 100
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ private var linkDescriptionEditor: some View {
|
|
|
|
|
+ PostFormCard(title: "Description", subtitle: "Generated by AI — edit if you like") {
|
|
|
|
|
+ PostFormTextEditor(
|
|
|
|
|
+ label: "Description (optional)",
|
|
|
|
|
+ placeholder: "Edit the generated description…",
|
|
|
|
|
+ text: $viewModel.draft.body,
|
|
|
|
|
+ minHeight: 100
|
|
|
|
|
+ )
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private var videoPostEditor: some View {
|
|
|
|
|
- PostFormCard(title: "Video", subtitle: "Link to YouTube, Vimeo, or direct video URL") {
|
|
|
|
|
- VStack(alignment: .leading, spacing: 12) {
|
|
|
|
|
- PostFormField(
|
|
|
|
|
- label: "Video URL",
|
|
|
|
|
- placeholder: "https://youtube.com/watch?v=…",
|
|
|
|
|
- text: $viewModel.draft.videoURL
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- if !viewModel.draft.videoURL.isEmpty,
|
|
|
|
|
- !PostDraftValidator.isValidHTTPURL(viewModel.draft.videoURL) {
|
|
|
|
|
- validationHint("Enter a valid http or https URL.")
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- PostFormTextEditor(
|
|
|
|
|
- label: "Description (optional)",
|
|
|
|
|
- placeholder: "Add context for your video…",
|
|
|
|
|
- text: $viewModel.draft.body,
|
|
|
|
|
- minHeight: 100
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ private var videoDescriptionEditor: some View {
|
|
|
|
|
+ PostFormCard(title: "Description", subtitle: "Generated by AI — edit if you like") {
|
|
|
|
|
+ PostFormTextEditor(
|
|
|
|
|
+ label: "Description (optional)",
|
|
|
|
|
+ placeholder: "Edit the generated description…",
|
|
|
|
|
+ text: $viewModel.draft.body,
|
|
|
|
|
+ minHeight: 100
|
|
|
|
|
+ )
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private var pollPostEditor: some View {
|
|
private var pollPostEditor: some View {
|
|
|
- PostFormCard(title: "Poll", subtitle: "2–6 options, community votes") {
|
|
|
|
|
|
|
+ PostFormCard(title: "Poll", subtitle: "Generated options — edit if you like") {
|
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
|
ForEach(Array(viewModel.draft.pollOptions.enumerated()), id: \.element.id) { index, option in
|
|
ForEach(Array(viewModel.draft.pollOptions.enumerated()), id: \.element.id) { index, option in
|
|
|
HStack(spacing: 8) {
|
|
HStack(spacing: 8) {
|
|
|
PostFormField(
|
|
PostFormField(
|
|
|
label: "Option \(index + 1)",
|
|
label: "Option \(index + 1)",
|
|
|
- placeholder: "Enter poll option",
|
|
|
|
|
|
|
+ placeholder: "Poll option",
|
|
|
text: Binding(
|
|
text: Binding(
|
|
|
get: { option.text },
|
|
get: { option.text },
|
|
|
set: { viewModel.updatePollOption(id: option.id, text: $0) }
|
|
set: { viewModel.updatePollOption(id: option.id, text: $0) }
|
|
@@ -495,7 +519,7 @@ struct PostGeneratorView: View {
|
|
|
|
|
|
|
|
PostFormTextEditor(
|
|
PostFormTextEditor(
|
|
|
label: "Context (optional)",
|
|
label: "Context (optional)",
|
|
|
- placeholder: "Explain why you're running this poll…",
|
|
|
|
|
|
|
+ placeholder: "Edit the generated poll context…",
|
|
|
text: $viewModel.draft.body,
|
|
text: $viewModel.draft.body,
|
|
|
minHeight: 80
|
|
minHeight: 80
|
|
|
)
|
|
)
|