mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-05 20:49:40 +00:00
Add a grammar and spell checker assistant (#72)
This commit is contained in:
1 parent
77e7e044e3
commit
05a7e44de4
14 files changed
+212
-5
No files matched your search
@@ -13,6 +13,7 @@
|
||||
<link href="system/MudBlazor.Markdown/MudBlazor.Markdown.min.css" rel="stylesheet" />
|
||||
<link href="app.css" rel="stylesheet" />
|
||||
<HeadOutlet/>
|
||||
<script src="diff.js"></script>
|
||||
</head>
|
||||
|
||||
<body style="overflow: hidden;">
|
||||
|
||||
@@ -16,10 +16,44 @@
|
||||
}
|
||||
</MudForm>
|
||||
<Issues IssuesData="@this.inputIssues"/>
|
||||
|
||||
@if (this.resultingContentBlock is not null)
|
||||
|
||||
@if (this.isProcessing)
|
||||
{
|
||||
<ContentBlockComponent Role="@this.resultingContentBlock.Role" Type="@this.resultingContentBlock.ContentType" Time="@this.resultingContentBlock.Time" Content="@this.resultingContentBlock.Content" Class="mr-2"/>
|
||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Class="mb-6" />
|
||||
}
|
||||
<div id="@ASSISTANT_RESULT_DIV_ID" class="mr-2 mt-3">
|
||||
@if (this.ShowResult && this.resultingContentBlock is not null)
|
||||
{
|
||||
<ContentBlockComponent Role="@this.resultingContentBlock.Role" Type="@this.resultingContentBlock.ContentType" Time="@this.resultingContentBlock.Time" Content="@this.resultingContentBlock.Content"/>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div id="@AFTER_RESULT_DIV_ID" class="mt-3">
|
||||
</div>
|
||||
|
||||
@if (this.FooterButtons.Count > 0)
|
||||
{
|
||||
<MudStack Row="@true" Wrap="Wrap.Wrap" Class="mt-3 mr-2">
|
||||
@foreach (var buttonData in this.FooterButtons)
|
||||
{
|
||||
switch (buttonData)
|
||||
{
|
||||
case var _ when !string.IsNullOrWhiteSpace(buttonData.Tooltip):
|
||||
<MudTooltip Text="@buttonData.Tooltip">
|
||||
<MudButton Variant="Variant.Filled" Color="@buttonData.Color" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()">
|
||||
@buttonData.Text
|
||||
</MudButton>
|
||||
</MudTooltip>
|
||||
break;
|
||||
|
||||
default:
|
||||
<MudButton Variant="Variant.Filled" Color="@buttonData.Color" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()">
|
||||
@buttonData.Text
|
||||
</MudButton>
|
||||
break;
|
||||
}
|
||||
}
|
||||
</MudStack>
|
||||
}
|
||||
</ChildContent>
|
||||
</InnerScrolling>
|
||||
@@ -18,6 +18,15 @@ public abstract partial class AssistantBase : ComponentBase
|
||||
[Inject]
|
||||
protected ThreadSafeRandom RNG { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected ISnackbar Snackbar { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected Rust Rust { get; init; } = null!;
|
||||
|
||||
internal const string AFTER_RESULT_DIV_ID = "afterAssistantResult";
|
||||
internal const string ASSISTANT_RESULT_DIV_ID = "assistantResult";
|
||||
|
||||
protected abstract string Title { get; }
|
||||
|
||||
protected abstract string Description { get; }
|
||||
@@ -26,6 +35,10 @@ public abstract partial class AssistantBase : ComponentBase
|
||||
|
||||
private protected virtual RenderFragment? Body => null;
|
||||
|
||||
protected virtual bool ShowResult => true;
|
||||
|
||||
protected virtual IReadOnlyList<ButtonData> FooterButtons => [];
|
||||
|
||||
protected static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
|
||||
|
||||
protected AIStudio.Settings.Provider providerSettings;
|
||||
@@ -35,6 +48,7 @@ public abstract partial class AssistantBase : ComponentBase
|
||||
private ChatThread? chatThread;
|
||||
private ContentBlock? resultingContentBlock;
|
||||
private string[] inputIssues = [];
|
||||
private bool isProcessing;
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
@@ -96,7 +110,7 @@ public abstract partial class AssistantBase : ComponentBase
|
||||
return time;
|
||||
}
|
||||
|
||||
protected async Task AddAIResponseAsync(DateTimeOffset time)
|
||||
protected async Task<string> AddAIResponseAsync(DateTimeOffset time)
|
||||
{
|
||||
var aiText = new ContentText
|
||||
{
|
||||
@@ -114,10 +128,26 @@ public abstract partial class AssistantBase : ComponentBase
|
||||
};
|
||||
|
||||
this.chatThread?.Blocks.Add(this.resultingContentBlock);
|
||||
this.isProcessing = true;
|
||||
this.StateHasChanged();
|
||||
|
||||
// Use the selected provider to get the AI response.
|
||||
// By awaiting this line, we wait for the entire
|
||||
// content to be streamed.
|
||||
await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(), this.JsRuntime, this.SettingsManager, this.providerSettings.Model, this.chatThread);
|
||||
|
||||
this.isProcessing = false;
|
||||
this.StateHasChanged();
|
||||
|
||||
// Return the AI response:
|
||||
return aiText.Text;
|
||||
}
|
||||
|
||||
private static string? GetButtonIcon(string icon)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(icon))
|
||||
return null;
|
||||
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Components.Rendering;
|
||||
namespace AIStudio.Components;
|
||||
|
||||
//
|
||||
// See https://stackoverflow.com/a/77300384/2258393 for why this class is needed
|
||||
// See https://stackoverflow.com/a/77300384/2258393 for why this class is necessary
|
||||
//
|
||||
|
||||
public abstract class AssistantBaseCore : AssistantBase
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
<ThirdPartyComponent Name="flexi_logger" Developer="emabee & Open Source Community" LicenseName="MIT & Apache-2.0" LicenseUrl="https://github.com/emabee/flexi_logger" RepositoryUrl="https://github.com/emabee/flexi_logger" UseCase="This Rust library is used to output the app's messages to the terminal. This is helpful during development and troubleshooting. This feature is initially invisible; when the app is started via the terminal, the messages become visible."/>
|
||||
<ThirdPartyComponent Name="HtmlAgilityPack" Developer="ZZZ Projects & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/zzzprojects/html-agility-pack/blob/master/LICENSE" RepositoryUrl="https://github.com/zzzprojects/html-agility-pack" UseCase="We use the HtmlAgilityPack to extract content from the web. This is necessary, e.g., when you provide a URL as input for an assistant."/>
|
||||
<ThirdPartyComponent Name="ReverseMarkdown" Developer="Babu Annamalai & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/mysticmind/reversemarkdown-net/blob/master/LICENSE" RepositoryUrl="https://github.com/mysticmind/reversemarkdown-net" UseCase="This library is used to convert HTML to Markdown. This is necessary, e.g., when you provide a URL as input for an assistant."/>
|
||||
<ThirdPartyComponent Name="wikEd diff" Developer="Cacycle & Open Source Community" LicenseName="None (public domain)" LicenseUrl="https://en.wikipedia.org/wiki/User:Cacycle/diff#License" RepositoryUrl="https://en.wikipedia.org/wiki/User:Cacycle/diff" UseCase="This library is used to display the differences between two texts. This is necessary, e.g., for the grammar and spelling assistant."/>
|
||||
</MudGrid>
|
||||
</ExpansionPanel>
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Verified" HeaderText="License: FSL-1.1-MIT">
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
|
||||
<AssistantBlock Name="Text Summarizer" Description="Using a LLM to summarize a given text." Icon="@Icons.Material.Filled.TextSnippet" Link="/assistant/summarizer"/>
|
||||
<AssistantBlock Name="Translation" Description="Translate text into another language." Icon="@Icons.Material.Filled.Translate" Link="/assistant/translation"/>
|
||||
<AssistantBlock Name="Grammar & Spelling" Description="Check grammar and spelling of a given text." Icon="@Icons.Material.Filled.Edit" Link="/assistant/grammar-spelling"/>
|
||||
</MudStack>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
@using AIStudio.Tools
|
||||
@page "/assistant/grammar-spelling"
|
||||
@inherits AssistantBaseCore
|
||||
|
||||
<MudTextField T="string" @bind-Text="@this.inputText" Validation="@this.ValidateText" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="Your input to check" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.NameSelectingOptional())" @bind-Value="@this.selectedTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="Language" AllowOther="@true" OtherValue="CommonLanguages.OTHER" @bind-OtherInput="@this.customTargetLanguage" ValidateOther="@this.ValidateCustomLanguage" LabelOther="Custom language" />
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
|
||||
<MudButton Variant="Variant.Filled" Class="mb-3" OnClick="() => this.ProofreadText()">
|
||||
Proofread
|
||||
</MudButton>
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Components.Pages.GrammarSpelling;
|
||||
|
||||
public partial class AssistantGrammarSpelling : AssistantBaseCore
|
||||
{
|
||||
protected override string Title => "Grammar and Spelling Checker";
|
||||
|
||||
protected override string Description =>
|
||||
"""
|
||||
Check the grammar and spelling of a text.
|
||||
""";
|
||||
|
||||
protected override string SystemPrompt =>
|
||||
$"""
|
||||
You are an expert in languages and their rules. For example, you know how US and UK English or German in
|
||||
Germany and German in Austria differ. You receive text as input. You check the spelling and grammar of
|
||||
this text according to the rules of {this.SystemPromptLanguage()}. You never add information. You
|
||||
never ask the user for additional information. You do not attempt to improve the wording of the text.
|
||||
Your response includes only the corrected text. Do not explain your changes. If no changes are needed,
|
||||
you return the text unchanged.
|
||||
""";
|
||||
|
||||
protected override bool ShowResult => false;
|
||||
|
||||
protected override IReadOnlyList<ButtonData> FooterButtons => new[]
|
||||
{
|
||||
new ButtonData("Copy corrected text", Icons.Material.Filled.ContentCopy, Color.Default, string.Empty, this.CopyToClipboard),
|
||||
};
|
||||
|
||||
private string inputText = string.Empty;
|
||||
private CommonLanguages selectedTargetLanguage;
|
||||
private string customTargetLanguage = string.Empty;
|
||||
private string correctedText = string.Empty;
|
||||
|
||||
private string? ValidateText(string text)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(text))
|
||||
return "Please provide a text as input. You might copy the desired text from a document or a website.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidateCustomLanguage(string language)
|
||||
{
|
||||
if(this.selectedTargetLanguage == CommonLanguages.OTHER && string.IsNullOrWhiteSpace(language))
|
||||
return "Please provide a custom language.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string SystemPromptLanguage()
|
||||
{
|
||||
var lang = this.selectedTargetLanguage switch
|
||||
{
|
||||
CommonLanguages.AS_IS => "the source language",
|
||||
CommonLanguages.OTHER => this.customTargetLanguage,
|
||||
|
||||
_ => $"{this.selectedTargetLanguage.Name()}",
|
||||
};
|
||||
|
||||
if (string.IsNullOrWhiteSpace(lang))
|
||||
return "the source language";
|
||||
|
||||
return lang;
|
||||
}
|
||||
|
||||
private async Task ProofreadText()
|
||||
{
|
||||
if (!this.inputIsValid)
|
||||
return;
|
||||
|
||||
this.CreateChatThread();
|
||||
var time = this.AddUserRequest(this.inputText);
|
||||
|
||||
this.correctedText = await this.AddAIResponseAsync(time);
|
||||
await this.JsRuntime.GenerateAndShowDiff(this.inputText, this.correctedText);
|
||||
}
|
||||
|
||||
private async Task CopyToClipboard()
|
||||
{
|
||||
await this.Rust.CopyText2Clipboard(this.JsRuntime, this.Snackbar, this.correctedText);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace AIStudio.Tools;
|
||||
|
||||
public readonly record struct ButtonData(string Text, string Icon, Color Color, string Tooltip, Func<Task> AsyncAction);
|
||||
@@ -42,4 +42,12 @@ public static class CommonLanguageExtensions
|
||||
|
||||
return language.Name();
|
||||
}
|
||||
|
||||
public static string NameSelectingOptional(this CommonLanguages language)
|
||||
{
|
||||
if(language is CommonLanguages.AS_IS)
|
||||
return "Do not specify the language";
|
||||
|
||||
return language.Name();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using AIStudio.Components;
|
||||
|
||||
namespace AIStudio.Tools;
|
||||
|
||||
public static class JsRuntimeExtensions
|
||||
{
|
||||
public static async Task GenerateAndShowDiff(this IJSRuntime jsRuntime, string text1, string text2)
|
||||
{
|
||||
await jsRuntime.InvokeVoidAsync("generateDiff", text1, text2, AssistantBase.ASSISTANT_RESULT_DIV_ID, AssistantBase.AFTER_RESULT_DIV_ID);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
window.generateDiff = function (text1, text2, divDiff, divLegend) {
|
||||
let wikEdDiff = new WikEdDiff();
|
||||
let targetDiv = document.getElementById(divDiff)
|
||||
targetDiv.innerHTML = wikEdDiff.diff(text1, text2);
|
||||
targetDiv.classList.add('mud-typography-body1');
|
||||
|
||||
let legend = document.getElementById(divLegend);
|
||||
legend.innerHTML = `
|
||||
<div class="legend mt-2">
|
||||
<h3>Legend</h3>
|
||||
<ul class="mt-2">
|
||||
<li><span class="wikEdDiffMarkRight" title="Moved block" id="wikEdDiffMark999" onmouseover="wikEdDiffBlockHandler(undefined, this, 'mouseover');"></span> Original block position</li>
|
||||
<li><span title="+" class="wikEdDiffInsert">Inserted<span class="wikEdDiffSpace"><span class="wikEdDiffSpaceSymbol"></span> </span>text<span class="wikEdDiffNewline"> </span></span></li>
|
||||
<li><span title="−" class="wikEdDiffDelete">Deleted<span class="wikEdDiffSpace"><span class="wikEdDiffSpaceSymbol"></span> </span>text<span class="wikEdDiffNewline"> </span></span></li>
|
||||
<li><span class="wikEdDiffBlockLeft" title="◀" id="wikEdDiffBlock999" onmouseover="wikEdDiffBlockHandler(undefined, this, 'mouseover');">Moved<span class="wikEdDiffSpace"><span class="wikEdDiffSpaceSymbol"></span> </span>block<span class="wikEdDiffNewline"> </span></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
# v0.8.8, build 170
|
||||
- Added a grammar and spell checker assistant
|
||||
- Improved all assistants by showing a progress bar while processing
|
||||
- Upgraded MudBlazor to v7.6.0
|
||||
@@ -0,0 +1,2 @@
|
||||
// @license released into the public domain
|
||||
"use strict";var wikEdDiffConfig,WED,WikEdDiff=function(){this.config={fullDiff:!1,showBlockMoves:!0,charDiff:!0,repeatedDiff:!0,recursiveDiff:!0,recursionMax:10,unlinkBlocks:!0,unlinkMax:5,blockMinLength:3,coloredBlocks:!1,noUnicodeSymbols:!1,stripTrailingNewline:!0,debug:!1,timer:!1,unitTesting:!1,regExpLetters:"a-zA-Z0-9"+"00AA00B500BA00C0-00D600D8-00F600F8-02C102C6-02D102E0-02E402EC02EE0370-037403760377037A-037D03860388-038A038C038E-03A103A3-03F503F7-0481048A-05270531-055605590561-058705D0-05EA05F0-05F20620-064A066E066F0671-06D306D506E506E606EE06EF06FA-06FC06FF07100712-072F074D-07A507B107CA-07EA07F407F507FA0800-0815081A082408280840-085808A008A2-08AC0904-0939093D09500958-09610971-09770979-097F0985-098C098F09900993-09A809AA-09B009B209B6-09B909BD09CE09DC09DD09DF-09E109F009F10A05-0A0A0A0F0A100A13-0A280A2A-0A300A320A330A350A360A380A390A59-0A5C0A5E0A72-0A740A85-0A8D0A8F-0A910A93-0AA80AAA-0AB00AB20AB30AB5-0AB90ABD0AD00AE00AE10B05-0B0C0B0F0B100B13-0B280B2A-0B300B320B330B35-0B390B3D0B5C0B5D0B5F-0B610B710B830B85-0B8A0B8E-0B900B92-0B950B990B9A0B9C0B9E0B9F0BA30BA40BA8-0BAA0BAE-0BB90BD00C05-0C0C0C0E-0C100C12-0C280C2A-0C330C35-0C390C3D0C580C590C600C610C85-0C8C0C8E-0C900C92-0CA80CAA-0CB30CB5-0CB90CBD0CDE0CE00CE10CF10CF20D05-0D0C0D0E-0D100D12-0D3A0D3D0D4E0D600D610D7A-0D7F0D85-0D960D9A-0DB10DB3-0DBB0DBD0DC0-0DC60E01-0E300E320E330E40-0E460E810E820E840E870E880E8A0E8D0E94-0E970E99-0E9F0EA1-0EA30EA50EA70EAA0EAB0EAD-0EB00EB20EB30EBD0EC0-0EC40EC60EDC-0EDF0F000F40-0F470F49-0F6C0F88-0F8C1000-102A103F1050-1055105A-105D106110651066106E-10701075-1081108E10A0-10C510C710CD10D0-10FA10FC-1248124A-124D1250-12561258125A-125D1260-1288128A-128D1290-12B012B2-12B512B8-12BE12C012C2-12C512C8-12D612D8-13101312-13151318-135A1380-138F13A0-13F41401-166C166F-167F1681-169A16A0-16EA1700-170C170E-17111720-17311740-17511760-176C176E-17701780-17B317D717DC1820-18771880-18A818AA18B0-18F51900-191C1950-196D1970-19741980-19AB19C1-19C71A00-1A161A20-1A541AA71B05-1B331B45-1B4B1B83-1BA01BAE1BAF1BBA-1BE51C00-1C231C4D-1C4F1C5A-1C7D1CE9-1CEC1CEE-1CF11CF51CF61D00-1DBF1E00-1F151F18-1F1D1F20-1F451F48-1F4D1F50-1F571F591F5B1F5D1F5F-1F7D1F80-1FB41FB6-1FBC1FBE1FC2-1FC41FC6-1FCC1FD0-1FD31FD6-1FDB1FE0-1FEC1FF2-1FF41FF6-1FFC2071207F2090-209C21022107210A-211321152119-211D212421262128212A-212D212F-2139213C-213F2145-2149214E218321842C00-2C2E2C30-2C5E2C60-2CE42CEB-2CEE2CF22CF32D00-2D252D272D2D2D30-2D672D6F2D80-2D962DA0-2DA62DA8-2DAE2DB0-2DB62DB8-2DBE2DC0-2DC62DC8-2DCE2DD0-2DD62DD8-2DDE2E2F300530063031-3035303B303C3041-3096309D-309F30A1-30FA30FC-30FF3105-312D3131-318E31A0-31BA31F0-31FF3400-4DB54E00-9FCCA000-A48CA4D0-A4FDA500-A60CA610-A61FA62AA62BA640-A66EA67F-A697A6A0-A6E5A717-A71FA722-A788A78B-A78EA790-A793A7A0-A7AAA7F8-A801A803-A805A807-A80AA80C-A822A840-A873A882-A8B3A8F2-A8F7A8FBA90A-A925A930-A946A960-A97CA984-A9B2A9CFAA00-AA28AA40-AA42AA44-AA4BAA60-AA76AA7AAA80-AAAFAAB1AAB5AAB6AAB9-AABDAAC0AAC2AADB-AADDAAE0-AAEAAAF2-AAF4AB01-AB06AB09-AB0EAB11-AB16AB20-AB26AB28-AB2EABC0-ABE2AC00-D7A3D7B0-D7C6D7CB-D7FBF900-FA6DFA70-FAD9FB00-FB06FB13-FB17FB1DFB1F-FB28FB2A-FB36FB38-FB3CFB3EFB40FB41FB43FB44FB46-FBB1FBD3-FD3DFD50-FD8FFD92-FDC7FDF0-FDFBFE70-FE74FE76-FEFCFF21-FF3AFF41-FF5AFF66-FFBEFFC2-FFC7FFCA-FFCFFFD2-FFD7FFDA-FFDC".replace(/(\w{4})/g,"\\u$1"),regExpNewLines:"\\u0085\\u2028",regExpNewLinesAll:"\\n\\r\\u0085\\u2028",regExpBlanks:" \\t\\x0b\\u2000-\\u200b\\u202f\\u205f\\u3000",regExpFullStops:"\\u0589\\u06D4\\u0701\\u0702\\u0964\\u0DF4\\u1362\\u166E\\u1803\\u1809\\u2CF9\\u2CFE\\u2E3C\\u3002\\uA4FF\\uA60E\\uA6F3\\uFE52\\uFF0E\\uFF61",regExpNewParagraph:"\\f\\u2029",regExpExclamationMarks:"\\u01C3\\u01C3\\u01C3\\u055C\\u055C\\u07F9\\u1944\\u1944\\u203C\\u203C\\u2048\\u2048\\uFE15\\uFE57\\uFF01",regExpQuestionMarks:"\\u037E\\u055E\\u061F\\u1367\\u1945\\u2047\\u2049\\u2CFA\\u2CFB\\u2E2E\\uA60F\\uA6F7\\uFE56\\uFF1F",clipHeadingLeft:1500,clipParagraphLeftMax:1500,clipParagraphLeftMin:500,clipLineLeftMax:1e3,clipLineLeftMin:500,clipBlankLeftMax:1e3,clipBlankLeftMin:500,clipCharsLeft:500,clipHeadingRight:1500,clipParagraphRightMax:1500,clipParagraphRightMin:500,clipLineRightMax:1e3,clipLineRightMin:500,clipBlankRightMax:1e3,clipBlankRightMin:500,clipCharsRight:500,clipLinesRightMax:10,clipLinesLeftMax:10,clipSkipLines:5,clipSkipChars:1e3,cssMarkLeft:"◀",cssMarkRight:"▶",stylesheet:'.wikEdDiffInsert {font-weight: bold; background-color: #bbddff; color: #222; border-radius: 0.25em; padding: 0.2em 1px; } .wikEdDiffInsertBlank { background-color: #66bbff; } .wikEdDiffFragment:hover .wikEdDiffInsertBlank { background-color: #bbddff; } .wikEdDiffDelete {font-weight: bold; background-color: #ffe49c; color: #222; border-radius: 0.25em; padding: 0.2em 1px; } .wikEdDiffDeleteBlank { background-color: #ffd064; } .wikEdDiffFragment:hover .wikEdDiffDeleteBlank { background-color: #ffe49c; } .wikEdDiffBlock {font-weight: bold; background-color: #e8e8e8; border-radius: 0.25em; padding: 0.2em 1px; margin: 0 1px; } .wikEdDiffBlock { color: #000; } .wikEdDiffBlock0 { background-color: #ffff80; } .wikEdLine truncated
|
||||
Reference in new issue
Block a user