-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathArticle.cs
More file actions
261 lines (247 loc) · 10.8 KB
/
Article.cs
File metadata and controls
261 lines (247 loc) · 10.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
using Abies.Conduit.Main;
using Abies.Conduit.Routing;
using Abies.DOM;
using System.Collections.Generic;
using Abies.Conduit;
using static Abies.Html.Attributes;
using static Abies.Html.Events;
namespace Abies.Conduit.Page.Article;
public interface Message : Abies.Message
{
public record ArticleLoaded(Conduit.Page.Home.Article? Article) : Message;
public record CommentsLoaded(List<Comment> Comments) : Message;
public record CommentInputChanged(string Value) : Message;
public record SubmitComment : Message;
public record CommentSubmitted(Comment Comment) : Message;
public record DeleteComment(string Id) : Message;
public record CommentDeleted(string Id) : Message;
public record ToggleFavorite : Message;
public record ToggleFollow : Message;
public record DeleteArticle : Message;
}
public record Comment(
string Id,
string CreatedAt,
string UpdatedAt,
string Body,
Conduit.Page.Home.Profile Author
);
public record Model(
Slug Slug,
bool IsLoading = true,
Conduit.Page.Home.Article? Article = null,
List<Comment>? Comments = null,
string CommentInput = "",
bool SubmittingComment = false
);
public class Page : Element<Model, Message>
{
public static Model Initialize(Message argument)
{
return new Model(new Slug(""));
} public static Subscription Subscriptions(Model model)
{
return new Subscription();
}
public static (Model model, IEnumerable<Command> commands) Init(Slug slug)
{
return (
new Model(slug, true),
new List<Command> { new LoadArticleCommand(slug.Value), new LoadCommentsCommand(slug.Value) }
);
}
public static (Model model, IEnumerable<Command> commands) Update(Abies.Message message, Model model)
=> message switch
{
Message.ArticleLoaded articleLoaded => (
model with
{
Article = articleLoaded.Article,
IsLoading = false
},
[]
),
Message.CommentsLoaded commentsLoaded => (
model with { Comments = commentsLoaded.Comments },
[]
),
Message.CommentInputChanged inputChanged => (
model with { CommentInput = inputChanged.Value },
[]
),
Message.SubmitComment => (
model with { SubmittingComment = true },
[ new SubmitCommentCommand(model.Slug.Value, model.CommentInput) ]
),
Message.CommentSubmitted commentSubmitted => (
model with
{
Comments = model.Comments != null
? new List<Comment>(model.Comments) { commentSubmitted.Comment }
: new List<Comment> { commentSubmitted.Comment },
CommentInput = "",
SubmittingComment = false
},
[]
),
Message.DeleteComment delete => (
model,
[ new DeleteCommentCommand(model.Slug.Value, delete.Id) ]
),
Message.CommentDeleted commentDeleted => (
model with
{
Comments = model.Comments?.FindAll(c => c.Id != commentDeleted.Id)
},
[]
),
Message.ToggleFavorite => (
model,
model.Article != null ? [ new ToggleFavoriteCommand(model.Article.Slug, model.Article.Favorited) ] : []
),
Message.ToggleFollow => (
model,
model.Article != null ? [ new ToggleFollowCommand(model.Article.Author.Username, model.Article.Author.Following) ] : []
),
Message.DeleteArticle => (
model,
model.Article != null ? [ new DeleteArticleCommand(model.Article.Slug) ] : []
),
_ => (model, [])
};
private static Node ArticleMeta(Conduit.Page.Home.Article article, bool showEditDelete = false) =>
div([class_("article-meta")], [ a([href($"/profile/{article.Author.Username}")], [
img([src(article.Author.Image)])
]), div([class_("info")], [
a([href($"/profile/{article.Author.Username}")], [
text(article.Author.Username)
]),
span([class_("date")], [text(article.CreatedAt)])
]),
showEditDelete
? div([], [ a([class_("btn btn-outline-secondary btn-sm"),
href($"/editor/{article.Slug}")],
[
i([class_("ion-edit")], []),
text(" Edit Article")
]), button([class_("btn btn-outline-danger btn-sm"),
onclick(new Message.DeleteArticle())],
[
i([class_("ion-trash-a")], []),
text(" Delete Article")
])
])
: div([], [ button([class_(article.Author.Following
? "btn btn-sm btn-secondary"
: "btn btn-sm btn-outline-secondary"),
onclick(new Message.ToggleFollow())],
[
i([class_("ion-plus-round")], []),
text(article.Author.Following
? $" Unfollow {article.Author.Username}"
: $" Follow {article.Author.Username}")
]),
text(" "), button([class_(article.Favorited
? "btn btn-sm btn-primary"
: "btn btn-sm btn-outline-primary"),
onclick(new Message.ToggleFavorite())],
[
i([class_("ion-heart")], []),
text(article.Favorited
? $" Unfavorite Article ({article.FavoritesCount})"
: $" Favorite Article ({article.FavoritesCount})")
])
])
]);
private static Node ArticleBanner(Conduit.Page.Home.Article article, bool isAuthor) =>
div([class_("banner")], [
div([class_("container")], [
h1([], [text(article.Title)]),
ArticleMeta(article, isAuthor)
])
]);
private static Node ArticleContent(Conduit.Page.Home.Article article) =>
div([class_("row article-content")], [
div([class_("col-md-12")], [
p([], [text(article.Body)]), ul([class_("tag-list")],
article.TagList.ConvertAll(tag =>
li([class_("tag-default tag-pill tag-outline")], [text(tag)])
).ToArray()
)
])
]);
private static Node CommentForm(Model model, User? currentUser) =>
currentUser == null
? div([class_("row")], [
div([class_("col-xs-12 col-md-8 offset-md-2")], [
p([], [ a([href("/login")], [text("Sign in")]),
text(" or "),
a([href("/register")], [text("sign up")]),
text(" to add comments on this article.")
])
])
])
: form([class_("card comment-form")], [
div([class_("card-block")], [ textarea([class_("form-control"),
placeholder("Write a comment..."),
rows("3"),
value(model.CommentInput),
oninput(new Message.CommentInputChanged(model.CommentInput))],
[]
)
]),
div([class_("card-footer")], [ img([class_("comment-author-img"), src("")]), button([class_("btn btn-sm btn-primary"),
disabled((string.IsNullOrWhiteSpace(model.CommentInput) || model.SubmittingComment).ToString()),
onclick(new Message.SubmitComment())],
[text("Post Comment")])
])
]);
private static Node CommentCard(Comment comment, User? currentUser) =>
div([class_("card")], [
div([class_("card-block")], [
p([class_("card-text")], [text(comment.Body)])
]),
div([class_("card-footer")], [ a([class_("comment-author"), href($"/profile/{comment.Author.Username}")], [
img([class_("comment-author-img"), src(comment.Author.Image)])
]),
text(" "),
a([class_("comment-author"), href($"/profile/{comment.Author.Username}")], [
text(comment.Author.Username)
]),
span([class_("date-posted")], [text(comment.CreatedAt)]),
comment.Author.Username == (currentUser?.Username.Value ?? "")
? span([class_("mod-options")], [ i([class_("ion-trash-a"),
onclick(new Message.DeleteComment(comment.Id))], [])
])
: text("")
])
]);
private static Node CommentSection(Model model, User? currentUser) =>
div([class_("row")], [
div([class_("col-xs-12 col-md-8 offset-md-2")], [
CommentForm(model, currentUser),
..(model.Comments?.ConvertAll(c => CommentCard(c, currentUser)) ??
new List<Node> { text("Loading comments...") })
])
]);
public static Node View(Model model) =>
model.IsLoading || model.Article == null
? div([class_("article-page")], [
div([class_("container")], [
div([class_("row")], [
div([class_("col-md-10 offset-md-1")], [
text("Loading article...")
])
])
])
])
: div([class_("article-page")], [
ArticleBanner(model.Article, false), div([class_("container page")], [ div([], [ArticleContent(model.Article)]),
hr([class_("hr")]),
div([class_("article-actions")], [
ArticleMeta(model.Article, false)
]),
CommentSection(model, null)
])
]);
}