Added toolbar to ArtikleEditor

This commit is contained in:
Mia Rose Winter 2024-01-24 15:20:33 +01:00
parent d40e74307f
commit 633e773410
Signed by: miawinter
GPG key ID: 4B6F6A83178F595E
11 changed files with 230 additions and 10 deletions

View file

@ -8,10 +8,10 @@
</div> </div>
<div class="bg-base-200 p-2"> <div class="bg-base-200 p-2">
@if (!string.IsNullOrWhiteSpace(Title)) { @if (!string.IsNullOrWhiteSpace(Title)) {
<h2 class="text-2xl lg:text-4xl font-bold mb-6">@Title</h2> <h2 class="text-2xl lg:text-4xl font-bold mb-6 hyphens-auto">@Title</h2>
} }
@if (!string.IsNullOrWhiteSpace(Markdown)) { @if (!string.IsNullOrWhiteSpace(Markdown)) {
<div class="prose prose-neutral max-w-none"> <div class="prose prose-neutral max-w-none hyphens-auto text-justify">
@HtmlPreview @HtmlPreview
</div> </div>
} else { } else {
@ -44,7 +44,7 @@
protected override void OnInitialized() { protected override void OnInitialized() {
Timer = new Timer(_ => { Timer = new Timer(_ => {
UpdateHtml(); UpdateHtml();
}, null, TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(3)); }, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(3));
} }
private void UpdateHtml() { private void UpdateHtml() {

View file

@ -12,7 +12,7 @@
<li><NavLink href="" Match="NavLinkMatch.All">@Localizer["Home_Label"]</NavLink></li> <li><NavLink href="" Match="NavLinkMatch.All">@Localizer["Home_Label"]</NavLink></li>
<AuthorizeView Policy="ArticleEditPermissions"> <AuthorizeView Policy="ArticleEditPermissions">
<Authorized> <Authorized>
<li><NavLink href="article/new">@Localizer["ArticleNew_Label"]</NavLink></li> <li><NavLink href="article/new" data-enhance-nav="false">@Localizer["ArticleNew_Label"]</NavLink></li>
<li><NavLink href="drafts">@Localizer["Drafts_Label"]</NavLink></li> <li><NavLink href="drafts">@Localizer["Drafts_Label"]</NavLink></li>
</Authorized> </Authorized>
</AuthorizeView> </AuthorizeView>

View file

@ -54,10 +54,63 @@
</InputLabelComponent> </InputLabelComponent>
<AdvancedMarkdownEditor Title="@Model.Title" MarkdownCallback="() => Model.Body"> <AdvancedMarkdownEditor Title="@Model.Title" MarkdownCallback="() => Model.Body">
<InputLabelComponent LabelText="@Localizer["Body_Label"]" For="() => Model.Body"> <div class="join join-vertical min-h-96 h-full w-full" aria-role="toolbar">
<textarea class="textarea textarea-bordered w-full min-h-96 h-full" required aria-required <Toolbar>
@bind="@Model.Body" @bind:event="oninput" placeholder="@Localizer["Body_Placeholder"]" autocomplete="off"></textarea> <ToolbarSection>
</InputLabelComponent> <ToolbarButton onclick="window.insertBeforeSelection('# ', true);"
title="@Localizer["Tools_H1_Tooltip"]">
<strong>@Localizer["Tools_H1_Label"]</strong>
</ToolbarButton>
<ToolbarButton onclick="window.insertBeforeSelection('## ', true);"
title="@Localizer["Tools_H2_Tooltip"]">
<strong>@Localizer["Tools_H2_Label"]</strong>
</ToolbarButton>
<ToolbarButton onclick="window.insertBeforeSelection('### ', true);"
title="@Localizer["Tools_H3_Tooltip"]">
<strong>@Localizer["Tools_H3_Label"]</strong>
</ToolbarButton>
<ToolbarButton onclick="window.insertBeforeSelection('#### ', true);"
title="@Localizer["Tools_H4_Tooltip"]">
@Localizer["Tools_H4_Label"]
</ToolbarButton>
</ToolbarSection>
<ToolbarSection>
<ToolbarButton onclick="window.insertBeforeAndAfterSelection('**');"
title="@Localizer["Tools_Bold_Tooltip"]">
<strong>B</strong>
</ToolbarButton>
<ToolbarButton onclick="window.insertBeforeAndAfterSelection('*')"
title="@Localizer["Tools_Italic_Tooltip"]">
<em>I</em>
</ToolbarButton>
<ToolbarButton onclick="window.insertBeforeAndAfterSelection('~~')"
title="@Localizer["Tools_StrikeThrough_Tooltip"]">
<del>@Localizer["Tools_StrikeThrough_Label"]</del>
</ToolbarButton>
<ToolbarButton onclick="window.insertBeforeSelection('> ', true)"
title="@Localizer["Tools_Cite_Tooltip"]">
| <em>@Localizer["Tools_Cite_Label"]</em>
</ToolbarButton>
</ToolbarSection>
<ToolbarSection>
<ToolbarButton onclick="window.insertBeforeAndAfterSelection('`')"
title="@Localizer["Tools_CodeLine_Tooltip"]">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-4 h-4">
<path fill-rule="evenodd" d="M14.447 3.026a.75.75 0 0 1 .527.921l-4.5 16.5a.75.75 0 0 1-1.448-.394l4.5-16.5a.75.75 0 0 1 .921-.527ZM16.72 6.22a.75.75 0 0 1 1.06 0l5.25 5.25a.75.75 0 0 1 0 1.06l-5.25 5.25a.75.75 0 1 1-1.06-1.06L21.44 12l-4.72-4.72a.75.75 0 0 1 0-1.06Zm-9.44 0a.75.75 0 0 1 0 1.06L2.56 12l4.72 4.72a.75.75 0 0 1-1.06 1.06L.97 12.53a.75.75 0 0 1 0-1.06l5.25-5.25a.75.75 0 0 1 1.06 0Z" clip-rule="evenodd" />
</svg>
</ToolbarButton>
<ToolbarButton onclick="window.insertBeforeAndAfterSelection('\n```\n')"
title="@Localizer["Tools_CodeBlock_Tooltip"]">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-4 h-4">
<path fill-rule="evenodd" d="M3 6a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V6Zm14.25 6a.75.75 0 0 1-.22.53l-2.25 2.25a.75.75 0 1 1-1.06-1.06L15.44 12l-1.72-1.72a.75.75 0 1 1 1.06-1.06l2.25 2.25c.141.14.22.331.22.53Zm-10.28-.53a.75.75 0 0 0 0 1.06l2.25 2.25a.75.75 0 1 0 1.06-1.06L8.56 12l1.72-1.72a.75.75 0 1 0-1.06-1.06l-2.25 2.25Z" clip-rule="evenodd" />
</svg>
</ToolbarButton>
</ToolbarSection>
</Toolbar>
<textarea id="tool-target" class="textarea textarea-bordered outline-none w-full flex-1 join-item"
required aria-required placeholder="@Localizer["Body_Placeholder"]"
@bind="@Model.Body" @bind:event="oninput" autocomplete="off"></textarea>
</div>
</AdvancedMarkdownEditor> </AdvancedMarkdownEditor>
<div class="flex gap-2 flex-wrap mt-3"> <div class="flex gap-2 flex-wrap mt-3">
@ -79,6 +132,48 @@
</ErrorBoundary> </ErrorBoundary>
} }
<SectionContent SectionName="scripts">
<script>
window.insertBeforeSelection = function(markdown, startOfLine = false) {
const target = document.getElementById("tool-target");
const start = target.selectionStart;
const end = target.selectionEnd;
const value = target.value;
let doStart = start;
if (startOfLine) {
doStart = value.lastIndexOf("\n", start) +1;
}
target.focus();
target.value = value.substring(0, doStart) + markdown + value.substring(doStart);
target.selectionStart = start + markdown.length;
target.selectionEnd = end + markdown.length;
target.focus();
target.dispatchEvent(new Event("input", { bubbles: true }));
}
window.insertBeforeAndAfterSelection = function (markdown) {
const target = document.getElementById("tool-target");
const start = target.selectionStart;
const end = target.selectionEnd;
const value = target.value;
target.focus();
target.value = value.substring(0, start) +
markdown + value.substring(start, end) + markdown +
value.substring(end);
target.selectionStart = start + markdown.length;
target.selectionEnd = end + markdown.length;
target.focus();
target.dispatchEvent(new Event("input", { bubbles: true }));
}
</script>
</SectionContent>
@code { @code {
[CascadingParameter(Name = "TitlePrefix")] [CascadingParameter(Name = "TitlePrefix")]
private string TitlePrefix { get; set; } = default!; private string TitlePrefix { get; set; } = default!;

View file

@ -39,7 +39,7 @@
<Authorized> <Authorized>
<ArticleComponent Article="@GetArticleProtected(context.User)" /> <ArticleComponent Article="@GetArticleProtected(context.User)" />
<div class="flex gap-2 mt-3 flex-wrap"> <div class="flex gap-2 mt-3 flex-wrap">
<a class="btn btn-info w-full sm:btn-wide" href="article/@Article.Id/edit">@Localizer["Edit"]</a> <a class="btn btn-info w-full sm:btn-wide" href="article/@Article.Id/edit" data-enhance-nav="false">@Localizer["Edit"]</a>
@if (Article.Status is ArticleStatus.Draft) { @if (Article.Status is ArticleStatus.Draft) {
<form @formname="submit-for-review" method="post" @onsubmit="SubmitForReview" class="max-sm:w-full"> <form @formname="submit-for-review" method="post" @onsubmit="SubmitForReview" class="max-sm:w-full">
<AntiforgeryToken /> <AntiforgeryToken />

View file

@ -0,0 +1,10 @@
<div class="flex flex-wrap gap-1 p-2 bg-base-200" @attributes="AdditionalAttributes">
@ChildContent
</div>
@code {
[Parameter]
public required RenderFragment ChildContent { get; set; }
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object>? AdditionalAttributes { get; set; }
}

View file

@ -0,0 +1,11 @@
<button type="button" class="btn btn-accent btn-sm outline-none font-normal join-item"
@attributes="AdditionalAttributes">
@ChildContent
</button>
@code {
[Parameter]
public required RenderFragment ChildContent { get; set; }
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object>? AdditionalAttributes { get; set; }
}

View file

@ -0,0 +1,10 @@
<div class="join join-horizontal" @attributes="AdditionalAttributes">
@ChildContent
</div>
@code {
[Parameter]
public required RenderFragment ChildContent { get; set; }
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object>? AdditionalAttributes { get; set; }
}

View file

@ -140,4 +140,49 @@
<data name="ViewArticle_Label" xml:space="preserve"> <data name="ViewArticle_Label" xml:space="preserve">
<value>Öffnen</value> <value>Öffnen</value>
</data> </data>
<data name="Tools_H1_Label" xml:space="preserve">
<value>Ü1</value>
</data>
<data name="Tools_H1_Tooltip" xml:space="preserve">
<value>Primärüberschrift</value>
</data>
<data name="Tools_H2_Label" xml:space="preserve">
<value>Ü2</value>
</data>
<data name="Tools_H2_Tooltip" xml:space="preserve">
<value>Sekundärüberschrift</value>
</data>
<data name="Tools_H3_Label" xml:space="preserve">
<value>Ü3</value>
</data>
<data name="Tools_H3_Tooltip" xml:space="preserve">
<value>Level 3 Überschrift</value>
</data>
<data name="Tools_H4_Label" xml:space="preserve">
<value>Ü4</value>
</data>
<data name="Tools_H4_Tooltip" xml:space="preserve">
<value>Level 4 Überschrift</value>
</data>
<data name="Tools_StrikeThrough_Tooltip" xml:space="preserve">
<value>Text durchstreichen</value>
</data>
<data name="Tools_Cite_Label" xml:space="preserve">
<value>Zitat</value>
</data>
<data name="Tools_Italic_Tooltip" xml:space="preserve">
<value>Text kursiv stellen</value>
</data>
<data name="Tools_Cite_Tooltip" xml:space="preserve">
<value>Text als Zitat formattieren</value>
</data>
<data name="Tools_Bold_Tooltip" xml:space="preserve">
<value>Text hervorheben</value>
</data>
<data name="Tools_CodeLine_Tooltip" xml:space="preserve">
<value>Selektierten text als programmcode markieren</value>
</data>
<data name="Tools_CodeBlock_Tooltip" xml:space="preserve">
<value>Programmierblock einfügen</value>
</data>
</root> </root>

View file

@ -140,4 +140,52 @@
<data name="ViewArticle_Label" xml:space="preserve"> <data name="ViewArticle_Label" xml:space="preserve">
<value>Open</value> <value>Open</value>
</data> </data>
<data name="Tools_H1_Tooltip" xml:space="preserve">
<value>First level heading</value>
</data>
<data name="Tools_H2_Tooltip" xml:space="preserve">
<value>Second level heading</value>
</data>
<data name="Tools_H3_Tooltip" xml:space="preserve">
<value>Third level heading</value>
</data>
<data name="Tools_H4_Tooltip" xml:space="preserve">
<value>Fourth level heading</value>
</data>
<data name="Tools_H1_Label" xml:space="preserve">
<value>H1</value>
</data>
<data name="Tools_H2_Label" xml:space="preserve">
<value>H2</value>
</data>
<data name="Tools_H3_Label" xml:space="preserve">
<value>H3</value>
</data>
<data name="Tools_H4_Label" xml:space="preserve">
<value>H4</value>
</data>
<data name="Tools_Bold_Tooltip" xml:space="preserve">
<value>Make text bold</value>
</data>
<data name="Tools_Italic_Tooltip" xml:space="preserve">
<value>Make text italic</value>
</data>
<data name="Tools_StrikeThrough_Tooltip" xml:space="preserve">
<value>Strike through text</value>
</data>
<data name="Tools_Cite_Tooltip" xml:space="preserve">
<value>Make text a citation</value>
</data>
<data name="Tools_StrikeThrough_Label" xml:space="preserve">
<value>del</value>
</data>
<data name="Tools_Cite_Label" xml:space="preserve">
<value>Cite</value>
</data>
<data name="Tools_CodeLine_Tooltip" xml:space="preserve">
<value>Mark selected text as programming code</value>
</data>
<data name="Tools_CodeBlock_Tooltip" xml:space="preserve">
<value>Insert program code block</value>
</data>
</root> </root>

View file

@ -9,6 +9,7 @@ public static class MarkdownUtilities {
var pipeline = new MarkdownPipelineBuilder() var pipeline = new MarkdownPipelineBuilder()
.UsePipeTables() .UsePipeTables()
.UseEmphasisExtras() .UseEmphasisExtras()
.UseListExtras()
.DisableHtml() .DisableHtml()
.Build(); .Build();
return Markdown.ToHtml(markdown, pipeline); return Markdown.ToHtml(markdown, pipeline);

File diff suppressed because one or more lines are too long