Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
60c10781f0 |
|||
|
625e062b4b |
|||
|
1455ba5187 |
|||
|
605d885917 |
5 changed files with 384 additions and 167 deletions
|
|
@ -7,8 +7,14 @@ APP_INSTALL_PREFIX=preprod-
|
|||
|
||||
include .$(APP_INSTALL_ENV).env
|
||||
|
||||
generated/:
|
||||
@mkdir -p $@
|
||||
reinstall: copy-binaries
|
||||
@for project in $(APP_PROJECT_NAMES); do \
|
||||
echo "🚀 Starting $(APP_INSTALL_PREFIX)yavsc$${project}" \
|
||||
sudo systemctl start $(APP_INSTALL_PREFIX)yavsc$${project} ; done
|
||||
@echo "💯 backend started"
|
||||
|
||||
reinstall-prod:
|
||||
make APP_INSTALL_ENV= APP_INSTALL_PREFIX= APP_INSTALL_ENV=production
|
||||
|
||||
generated/$(APP_INSTALL_PREFIX)yavscOrg.service:
|
||||
generated/$(APP_INSTALL_PREFIX)yavscBlogs.service:
|
||||
|
|
@ -62,7 +68,6 @@ copy-binaries: build_publish_Org build_publish_Blogs build_publish_Api stop-serv
|
|||
@sudo chown -R $(USER_AND_GROUP) $(DESTDIR)$(APPDIR)
|
||||
@sync
|
||||
|
||||
|
||||
$(DESTDIR)/etc/systemd/system/$(APP_INSTALL_PREFIX)yavsc%.service: generated/$(APP_INSTALL_PREFIX)yavsc%.service
|
||||
sudo cp $^ $@
|
||||
sudo chown root:root $@
|
||||
|
|
@ -81,21 +86,11 @@ install: copy-binaries copy-services
|
|||
sudo systemctl start yavsc$${project} ; \
|
||||
done
|
||||
|
||||
reinstall: copy-binaries
|
||||
@for project in $(APP_PROJECT_NAMES); do \
|
||||
sudo systemctl start yavsc$${project} ; done
|
||||
|
||||
reinstall-prod:
|
||||
make APP_INSTALL_ENV= APP_INSTALL_PREFIX=
|
||||
|
||||
reinstall-pre: copy-binaries
|
||||
@for project in $(APP_PROJECT_NAMES); do \
|
||||
sudo systemctl start yavscPre$${project} ; done
|
||||
|
||||
stop-services:
|
||||
@for project in $(APP_PROJECT_NAMES); \
|
||||
do \
|
||||
sudo systemctl stop yavsc$${project} ; \
|
||||
sudo systemctl stop $(APP_INSTALL_PREFIX)yavsc$${project} ; \
|
||||
done
|
||||
|
||||
$(SLNDIR)/src/Yavsc.Org/bin/$(CONFIGURATION)/$(DOTNET_FRAMEWORK)/publish: build_publish
|
||||
|
|
@ -118,3 +113,5 @@ showBlogsLogs:
|
|||
clean:
|
||||
@rm -rf generated
|
||||
|
||||
generated/:
|
||||
@mkdir -p $@
|
||||
|
|
|
|||
|
|
@ -18,7 +18,10 @@ namespace Yavsc.ViewComponents
|
|||
}
|
||||
public async Task<IViewComponentResult> InvokeAsync(long id)
|
||||
{
|
||||
var comment = await context.Comment.Include(c=>c.Children).FirstOrDefaultAsync(c => c.Id==id);
|
||||
var comment = await context.Comment
|
||||
.Include(c => c.Children)
|
||||
.Include(c => c.Author)
|
||||
.FirstOrDefaultAsync(c => c.Id==id);
|
||||
if (comment == null)
|
||||
throw new InvalidOperationException();
|
||||
ViewBag.apictlr = "/api/v1/blogcomments";
|
||||
|
|
|
|||
|
|
@ -2,101 +2,234 @@
|
|||
@{
|
||||
ViewBag.Title = Model.Title;
|
||||
}
|
||||
@section scripts {
|
||||
|
||||
<script src="~/js/comment.js" asp-append-version="true"></script>
|
||||
<script>
|
||||
$.psc.blogcomment.prototype.options.lang = '@System.Globalization.CultureInfo.CurrentUICulture.Name';
|
||||
$.psc.blogcomment.prototype.options.apictrlr = '/api/v1/blogcomments';
|
||||
$.psc.blogcomment.prototype.options.authorId = '@User.GetUserId()';
|
||||
$.psc.blogcomment.prototype.options.authorName = '@User.GetUserName()';
|
||||
$(document).ready(function() {
|
||||
$('#cmtBtn').click(function() {
|
||||
$.ajax({
|
||||
async: true,
|
||||
cache: false,
|
||||
type: 'POST',
|
||||
method: 'POST',
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify({
|
||||
Article: $('#Comment').val(),
|
||||
ReceiverId: @Model.Id
|
||||
}),
|
||||
error: function(xhr,data) {
|
||||
if (xhr.status === 400)
|
||||
{
|
||||
if (xhr.responseJSON)
|
||||
{
|
||||
$('#commentValidation').html(
|
||||
xhr.responseJSON.Article);
|
||||
} else {
|
||||
$('#commentValidation').html(
|
||||
"Une erreur est survenue : "+xhr.status+"<br/>"+
|
||||
"<code><pre>"+xhr.responseText+"</pre></code>"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
success: function (data) {
|
||||
var comment = $('#Comment').val();
|
||||
$('#Comment').val('');
|
||||
$('#commentValidation').empty();
|
||||
var nnode = '<div data-type="blogcomment" data-id="'+data.id+'" data-allow-edit="True" data-allow-moderate="@ViewBag.moderatoFlag" data-date="'+data.dateCreated+'" data-username="@User.GetUserName()">'+comment+'</div>';
|
||||
$('#comments').append($(nnode).blogcomment())
|
||||
},
|
||||
url:'/api/v1/blogcomments'
|
||||
});
|
||||
});
|
||||
})
|
||||
</script>
|
||||
@section header {
|
||||
<style>
|
||||
.avatar, .commentmeta, .blogcomment p , .subcomments {
|
||||
display: inline-block;
|
||||
.blog-details .post {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 1rem;
|
||||
align-items: start;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.blog-details .post-photo {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
object-fit: cover;
|
||||
border-radius: .375rem;
|
||||
}
|
||||
.blog-details .post-title {
|
||||
margin: 0 0 .25rem;
|
||||
}
|
||||
.blog-details .post-meta {
|
||||
font-size: .85rem;
|
||||
color: var(--bs-secondary-color, #6c757d);
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
.blog-details .post-article {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
#comments {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.blogcomment {
|
||||
display: inline-block;
|
||||
margin:.3em;
|
||||
margin: .3em 0;
|
||||
padding: .5rem .75rem;
|
||||
border-left: 3px solid var(--bs-border-color, #dee2e6);
|
||||
/* Quartz ships white body text on a coloured page bg, so
|
||||
use a semi-transparent black overlay rather than
|
||||
--bs-tertiary-bg (which is near-white in the quartz
|
||||
light variant and would render white-on-white). */
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
border-radius: .25rem;
|
||||
}
|
||||
.blogcomment .comment-body > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.blogcomment .commentmeta {
|
||||
display: block;
|
||||
font-size: .75rem;
|
||||
color: var(--bs-secondary-color, #6c757d);
|
||||
margin-top: .25rem;
|
||||
}
|
||||
.blogcomment .reply-btn {
|
||||
padding: 0;
|
||||
font-size: .75rem;
|
||||
}
|
||||
.subcomments {
|
||||
margin-left: 1.25rem;
|
||||
margin-top: .5rem;
|
||||
}
|
||||
.reply-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
margin: .5rem 0;
|
||||
max-width: 40rem;
|
||||
}
|
||||
.reply-form .form-control {
|
||||
flex: 1;
|
||||
}
|
||||
.comment-form .input-group {
|
||||
max-width: 40rem;
|
||||
}
|
||||
#commentValidation,
|
||||
.reply-validation {
|
||||
color: var(--bs-danger, #dc3545);
|
||||
font-size: .8rem;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
@section scripts {
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
var api = '/api/v1/blogcomments';
|
||||
var receiverId = @Model.Id;
|
||||
var userName = @Json.Serialize(User.GetUserName());
|
||||
// Reply buttons are rendered server-side only when the
|
||||
// user is authenticated, so a non-empty userName here
|
||||
// means freshly appended nodes may carry one too.
|
||||
var canReply = !!userName;
|
||||
|
||||
<div class="post">
|
||||
<div class="float-left">
|
||||
<img class="photo" src="@Model.Photo" >
|
||||
</div>
|
||||
// Build a plain, escaped comment node from a just-posted
|
||||
// comment. The server-side Comment view component is what
|
||||
// markdown-renders and decorates persisted comments; a
|
||||
// newly created one is plain text until the page reloads.
|
||||
function buildCommentNode(text, data, parentId) {
|
||||
var $node = $('<div class="blogcomment"></div>')
|
||||
.attr('data-type', 'blogcomment')
|
||||
.attr('data-id', data.id)
|
||||
.attr('data-receiver-id', receiverId);
|
||||
if (parentId != null) $node.attr('data-parent-id', parentId);
|
||||
$node.append($('<div class="comment-body"></div>').append($('<p></p>').text(text)));
|
||||
$node.append($('<span class="commentmeta"></span>')
|
||||
.text('— ' + (userName || 'Vous') + ', posté à ' + new Date(data.dateCreated).toLocaleTimeString()));
|
||||
if (canReply) {
|
||||
$node.append($('<button type="button" class="btn btn-link btn-sm reply-btn">Répondre</button>'));
|
||||
}
|
||||
$node.append($('<div class="subcomments"></div>'));
|
||||
return $node;
|
||||
}
|
||||
|
||||
<h1 ismarkdown>@Model.Title</h1>
|
||||
function postComment(text, parentId, $validation) {
|
||||
return $.ajax({
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
url: api,
|
||||
data: JSON.stringify({
|
||||
Article: text,
|
||||
ReceiverId: receiverId,
|
||||
ParentId: parentId
|
||||
})
|
||||
}).fail(function (xhr) {
|
||||
var msg;
|
||||
if (xhr.status === 400 && xhr.responseJSON) {
|
||||
msg = xhr.responseJSON.Article || xhr.responseJSON.title || 'Validation error';
|
||||
} else {
|
||||
msg = 'Une erreur est survenue : ' + xhr.status;
|
||||
}
|
||||
($validation || $('#commentValidation')).text(msg);
|
||||
});
|
||||
}
|
||||
|
||||
// Top-level comment form.
|
||||
$('#cmtBtn').click(function () {
|
||||
var text = $('#Comment').val();
|
||||
if (!text) return;
|
||||
$('#commentValidation').empty();
|
||||
postComment(text, null, $('#commentValidation')).done(function (data) {
|
||||
$('#comments').append(buildCommentNode(text, data, null));
|
||||
$('#Comment').val('');
|
||||
});
|
||||
});
|
||||
|
||||
// Tree replies — delegated so it works for every nested
|
||||
// comment, including ones appended at runtime.
|
||||
$('#comments').on('click', '.reply-btn', function () {
|
||||
var $comment = $(this).closest('[data-type="blogcomment"]');
|
||||
// Toggle: if a reply form is already open for this
|
||||
// comment, close it instead of opening a second one.
|
||||
var $existing = $comment.children('.reply-form');
|
||||
if ($existing.length) { $existing.remove(); return; }
|
||||
|
||||
var $form = $('<div class="reply-form"></div>');
|
||||
$form.append('<input class="form-control reply-input" placeholder="..." />');
|
||||
$form.append('<button type="button" class="btn btn-secondary reply-submit">Envoyer</button>');
|
||||
$form.append('<span class="reply-validation"></span>');
|
||||
// Insert before this comment's .subcomments so nested
|
||||
// replies land in the right place.
|
||||
$comment.children('.subcomments').before($form);
|
||||
$form.find('.reply-input').focus();
|
||||
});
|
||||
|
||||
$('#comments').on('click', '.reply-submit', function () {
|
||||
var $form = $(this).closest('.reply-form');
|
||||
var $comment = $form.closest('[data-type="blogcomment"]');
|
||||
var parentId = parseInt($comment.attr('data-id'), 10);
|
||||
var text = $form.find('.reply-input').val();
|
||||
if (!text) return;
|
||||
var $val = $form.find('.reply-validation');
|
||||
$val.empty();
|
||||
postComment(text, parentId, $val).done(function (data) {
|
||||
var $sub = $comment.children('.subcomments');
|
||||
$sub.append(buildCommentNode(text, data, parentId));
|
||||
$form.remove();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
}
|
||||
|
||||
<div class="blog-details">
|
||||
<div class="post card">
|
||||
@if (!string.IsNullOrEmpty(Model.Photo))
|
||||
{
|
||||
<img class="post-photo" src="@Model.Photo" alt="@Model.Title" />
|
||||
}
|
||||
<div>
|
||||
<h1 class="post-title">@Model.Title</h1>
|
||||
<div class="post-meta">
|
||||
@Html.DisplayFor(m => m.Author)
|
||||
<markdown>@Html.DisplayFor(m=>m.Article)</markdown>
|
||||
|
||||
@if (Model.DateModified > Model.DateCreated)
|
||||
{
|
||||
<span> · modifié le @Model.DateModified.ToString("dddd d MMM yyyy à H:mm")</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span> · posté le @Model.DateCreated.ToString("dddd d MMM yyyy à H:mm")</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="post-body post-article">
|
||||
<markdown>@Model.Article</markdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="comments">
|
||||
@if (Model.Comments!=null) {
|
||||
foreach (var comment in Model.Comments.Where(c=>c.ParentId==null)) {
|
||||
@if (Model.Comments != null)
|
||||
{
|
||||
foreach (var comment in Model.Comments.Where(c => c.ParentId == null))
|
||||
{
|
||||
@await Component.InvokeAsync("Comment", new { id = comment.Id })
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@if (User.GetUserId()!=null) {
|
||||
<div class="form-horizontal">
|
||||
|
||||
@if (User.GetUserId() != null)
|
||||
{
|
||||
<div class="comment-form form-horizontal">
|
||||
<div class="input-group">
|
||||
<input name="Comment" id="Comment" class="form-control" placeholder="..." />
|
||||
<span class="input-group-btn">
|
||||
<span id="commentValidation"></span>
|
||||
<input type="button" value="Comment" class="btn btn-secondary"
|
||||
data-receiverId="@Model.Id" id="cmtBtn"
|
||||
/>
|
||||
<input type="button" value="Comment" class="btn btn-secondary" id="cmtBtn" />
|
||||
</span>
|
||||
</div>
|
||||
<span asp-validation-for="Article" class="text-danger" ></span>
|
||||
</div>
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
<p><i>Vous devez être identifié pour commenter.</i></p>
|
||||
}
|
||||
|
||||
<a asp-action="Index" class="btn btn-link">Back to List</a>
|
||||
|
||||
</div>
|
||||
|
|
@ -1,85 +1,155 @@
|
|||
|
||||
@model IEnumerable<IBlogPost>
|
||||
@{
|
||||
ViewBag.Title = "Blogs, l'index";
|
||||
|
||||
// Pagination reflects the Index(string id, int skip, int take)
|
||||
// call profile. The service applies Skip/Take server-side, so
|
||||
// the view only needs the current page params to build Prev/Next
|
||||
// links — it does not receive a total count.
|
||||
int currentSkip = Context.Request.Query.TryGetValue("skip", out var skipVal) && int.TryParse(skipVal, out var s) ? s : 0;
|
||||
int currentTake = Context.Request.Query.TryGetValue("take", out var takeVal) && int.TryParse(takeVal, out var t) ? t : 25;
|
||||
|
||||
var posts = Model.ToList();
|
||||
bool hasPrev = currentSkip > 0;
|
||||
bool hasNext = posts.Count >= currentTake;
|
||||
|
||||
int prevSkip = Math.Max(0, currentSkip - currentTake);
|
||||
int nextSkip = currentSkip + currentTake;
|
||||
|
||||
int maxSummaryLen = 256;
|
||||
}
|
||||
@section header {
|
||||
<style>
|
||||
.collapsed {
|
||||
height: 1em;
|
||||
.blog-index {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
.sametitlegrip {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
.blog-index .post {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 1rem;
|
||||
align-items: start;
|
||||
}
|
||||
.sametitle {
|
||||
overflow: hidden;
|
||||
transition: height 1s;
|
||||
.blog-index .post-photo {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
object-fit: cover;
|
||||
border-radius: .375rem;
|
||||
}
|
||||
td {
|
||||
transition: height 1s;
|
||||
.blog-index .post-body {
|
||||
min-width: 0;
|
||||
}
|
||||
div.row {
|
||||
border-bottom: dashed black 1px;
|
||||
.blog-index .index-post-title {
|
||||
margin: 0 0 .25rem;
|
||||
font-size: 1.15rem;
|
||||
}
|
||||
.blog-index .post-meta {
|
||||
font-size: .8rem;
|
||||
color: var(--bs-secondary-color, #6c757d);
|
||||
}
|
||||
.blog-index .post-author img {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
border-radius: 50%;
|
||||
vertical-align: middle;
|
||||
margin-right: .25rem;
|
||||
}
|
||||
.blog-index .actiongroup {
|
||||
margin-top: .5rem;
|
||||
display: flex;
|
||||
gap: .5rem;
|
||||
}
|
||||
.blog-index .post-summary {
|
||||
margin: .25rem 0;
|
||||
}
|
||||
.blog-index .post-summary > p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.blog-pager {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
@section scripts {
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$(".sametitle").addClass("collapsed")
|
||||
.on("mouseover",function(){
|
||||
$(this).removeClass("collapsed")
|
||||
}).on("mouseout",function(){
|
||||
$(this).addClass("collapsed")
|
||||
});
|
||||
}
|
||||
)
|
||||
</script>
|
||||
}
|
||||
<h2>@ViewBag.Title</h2>
|
||||
<p class="text-success">@ViewBag.StatusMessage</p>
|
||||
|
||||
<div class="blog-index">
|
||||
@{
|
||||
int maxTextLen = 75;
|
||||
foreach (var post in Model) {
|
||||
<div class="post card">
|
||||
|
||||
|
||||
<a asp-action="Details" asp-route-id="@post.Id" class="bloglink" >
|
||||
<div class="float-left"><img class="photo card-photo" src="@post.Photo" ></div>
|
||||
<h3 class="index-post-title">@post.Title</h3>
|
||||
</a>
|
||||
<div>
|
||||
<a asp-action="Details" asp-route-id="@post.Id">
|
||||
<markdown summary="@maxTextLen">@post.Article</markdown></a>
|
||||
<span style="font-size:x-small;">@Html.DisplayFor(m => post.Author)</span>
|
||||
<span style="font-size:xx-small;">
|
||||
posté le @post.DateCreated.ToString("dddd d MMM yyyy à H:mm")
|
||||
@if ((post.DateModified - post.DateCreated).Minutes > 0){
|
||||
@:- Modifié le @post.DateModified.ToString("dddd d MMM yyyy à H:mm")
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<div class="actiongroup">
|
||||
@if ((await AuthorizationService.AuthorizeAsync(User, post, new ReadPermission())).Succeeded)
|
||||
@if (!posts.Any())
|
||||
{
|
||||
<a asp-action="Details" asp-route-id="@post.Id" class="btn btn-light">Details</a>
|
||||
<p>Néant</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a asp-action="Details" asp-route-id="@post.Id" class="btn btn-light">Details</a>
|
||||
<div class="blog-index">
|
||||
@foreach (var post in posts)
|
||||
{
|
||||
var canEdit = (await AuthorizationService.AuthorizeAsync(User, post, new EditPermission())).Succeeded;
|
||||
<article class="post card">
|
||||
@if (!string.IsNullOrEmpty(post.Photo))
|
||||
{
|
||||
<a asp-action="Details" asp-route-id="@post.Id" class="bloglink">
|
||||
<img class="post-photo" src="@post.Photo" alt="@post.Title" />
|
||||
</a>
|
||||
}
|
||||
@if ((await AuthorizationService.AuthorizeAsync(User, post, new EditPermission())).Succeeded)
|
||||
<div class="post-body">
|
||||
<h3 class="index-post-title">
|
||||
<a asp-action="Details" asp-route-id="@post.Id" class="bloglink">@post.Title</a>
|
||||
</h3>
|
||||
<div class="post-summary">
|
||||
<a asp-action="Details" asp-route-id="@post.Id" class="bloglink text-decoration-none">
|
||||
<markdown summary="@maxSummaryLen">@post.Article</markdown>
|
||||
</a>
|
||||
</div>
|
||||
<div class="post-meta">
|
||||
@if (post.Author != null)
|
||||
{
|
||||
<span class="post-author">
|
||||
@if (!string.IsNullOrEmpty(post.Author.Avatar))
|
||||
{
|
||||
<img src="@post.Author.Avatar" alt="" />
|
||||
}
|
||||
@@@post.Author.UserName
|
||||
</span>
|
||||
<span> · </span>
|
||||
}
|
||||
<span>posté le @post.DateCreated.ToString("dddd d MMM yyyy à H:mm")</span>
|
||||
@if (post.DateModified > post.DateCreated)
|
||||
{
|
||||
<span> · modifié le @post.DateModified.ToString("dddd d MMM yyyy à H:mm")</span>
|
||||
}
|
||||
</div>
|
||||
<div class="actiongroup">
|
||||
<a asp-action="Details" asp-route-id="@post.Id" class="btn btn-light">Details</a>
|
||||
@if (canEdit)
|
||||
{
|
||||
<a asp-action="Delete" asp-route-id="@post.Id" class="btn btn-danger">Delete</a>
|
||||
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</article>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if(Model.Count()==0){<p>Néant</p>}
|
||||
<nav class="blog-pager" aria-label="Pagination">
|
||||
@if (hasPrev)
|
||||
{
|
||||
<a asp-action="Index"
|
||||
asp-route-skip="@prevSkip"
|
||||
asp-route-take="@currentTake"
|
||||
class="btn btn-outline-secondary">← Précédent</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span></span>
|
||||
}
|
||||
@if (hasNext)
|
||||
{
|
||||
<a asp-action="Index"
|
||||
asp-route-skip="@nextSkip"
|
||||
asp-route-take="@currentTake"
|
||||
class="btn btn-outline-secondary">Suivant →</a>
|
||||
}
|
||||
</nav>
|
||||
}
|
||||
|
|
@ -1,16 +1,30 @@
|
|||
@model Comment
|
||||
|
||||
<div data-type="blogcomment" data-id="@Model.Id" data-receiver-id="@Model.ReceiverId" data-allow-edit="@(User.GetUserId()==Model.AuthorId?"true":"false")"
|
||||
data-allow-moderate="@ViewBag.moderatoFlag" data-date="@Model.DateCreated.ToString("yyyy-MM-dd hh:mm:ss")" data-username="@Model.Author.UserName" >
|
||||
<div class="blogcomment" data-type="blogcomment"
|
||||
data-id="@Model.Id"
|
||||
data-receiver-id="@Model.ReceiverId"
|
||||
data-allow-edit="@(User.GetUserId()==Model.AuthorId?"true":"false")"
|
||||
data-allow-moderate="@ViewBag.moderatoFlag"
|
||||
data-date="@Model.DateCreated.ToString("yyyy-MM-dd hh:mm:ss")"
|
||||
data-username="@(Model.Author?.UserName)">
|
||||
<div class="comment-body">
|
||||
<markdown>@Model.Article</markdown>
|
||||
|
||||
@if (Model.Children!=null) {
|
||||
<div class="subcomments">
|
||||
@foreach (var comment in Model.Children)
|
||||
</div>
|
||||
<span class="commentmeta">
|
||||
— @(Model.Author?.UserName ?? "Anonyme"),
|
||||
posté le @Model.DateCreated.ToString("dddd d MMM yyyy à H:mm")
|
||||
</span>
|
||||
@if (User.GetUserId() != null)
|
||||
{
|
||||
@await this.Component.InvokeAsync("Comment", new { id = comment.Id});
|
||||
<button type="button" class="btn btn-link btn-sm reply-btn">Répondre</button>
|
||||
}
|
||||
<div class="subcomments">
|
||||
@if (Model.Children != null)
|
||||
{
|
||||
foreach (var comment in Model.Children)
|
||||
{
|
||||
@await this.Component.InvokeAsync("Comment", new { id = comment.Id })
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue