Added toolbar to ArtikleEditor
This commit is contained in:
parent
d40e74307f
commit
633e773410
|
@ -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() {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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!;
|
||||||
|
|
|
@ -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 />
|
||||||
|
|
10
Wave/Components/Toolbar.razor
Normal file
10
Wave/Components/Toolbar.razor
Normal 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; }
|
||||||
|
}
|
11
Wave/Components/ToolbarButton.razor
Normal file
11
Wave/Components/ToolbarButton.razor
Normal 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; }
|
||||||
|
}
|
10
Wave/Components/ToolbarSection.razor
Normal file
10
Wave/Components/ToolbarSection.razor
Normal 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; }
|
||||||
|
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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);
|
||||||
|
|
2
Wave/wwwroot/css/main.min.css
vendored
2
Wave/wwwroot/css/main.min.css
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue