添加项目文件。
This commit is contained in:
9
TightWiki.Engine.Implementation/AggregatedSearchToken.cs
Normal file
9
TightWiki.Engine.Implementation/AggregatedSearchToken.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace TightWiki.Engine.Implementation
|
||||
{
|
||||
public class AggregatedSearchToken
|
||||
{
|
||||
public string Token { get; set; } = string.Empty;
|
||||
public double Weight { get; set; }
|
||||
public string DoubleMetaphone { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
22
TightWiki.Engine.Implementation/CommentHandler.cs
Normal file
22
TightWiki.Engine.Implementation/CommentHandler.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using TightWiki.Engine.Library;
|
||||
using TightWiki.Engine.Library.Interfaces;
|
||||
using static TightWiki.Engine.Library.Constants;
|
||||
|
||||
namespace TightWiki.Engine.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles wiki comments. These are generally removed from the result.
|
||||
/// </summary>
|
||||
public class CommentHandler : ICommentHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles a wiki comment.
|
||||
/// </summary>
|
||||
/// <param name="state">Reference to the wiki state object</param>
|
||||
/// <param name="text">The comment text</param>
|
||||
public HandlerResult Handle(ITightEngineState state, string text)
|
||||
{
|
||||
return new HandlerResult() { Instructions = [HandlerResultInstruction.TruncateTrailingLine] };
|
||||
}
|
||||
}
|
||||
}
|
||||
31
TightWiki.Engine.Implementation/CompletionHandler.cs
Normal file
31
TightWiki.Engine.Implementation/CompletionHandler.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using TightWiki.Engine.Library.Interfaces;
|
||||
using TightWiki.Models;
|
||||
using TightWiki.Repository;
|
||||
|
||||
namespace TightWiki.Engine.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles wiki completion events.
|
||||
/// </summary>
|
||||
public class CompletionHandler : ICompletionHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles wiki completion events. Is called when the wiki processing competes for a given page.
|
||||
/// </summary>
|
||||
/// <param name="state">Reference to the wiki state object</param>
|
||||
public void Complete(ITightEngineState state)
|
||||
{
|
||||
if (GlobalConfiguration.RecordCompilationMetrics)
|
||||
{
|
||||
StatisticsRepository.InsertCompilationStatistics(state.Page.Id,
|
||||
state.ProcessingTime.TotalMilliseconds,
|
||||
state.MatchCount,
|
||||
state.ErrorCount,
|
||||
state.OutgoingLinks.Count,
|
||||
state.Tags.Count,
|
||||
state.HtmlResult.Length,
|
||||
state.Page.Body.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
44
TightWiki.Engine.Implementation/EmojiHandler.cs
Normal file
44
TightWiki.Engine.Implementation/EmojiHandler.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using TightWiki.Engine.Library;
|
||||
using TightWiki.Engine.Library.Interfaces;
|
||||
using TightWiki.Models;
|
||||
using static TightWiki.Engine.Library.Constants;
|
||||
|
||||
namespace TightWiki.Engine.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles wiki emojis.
|
||||
/// </summary>
|
||||
public class EmojiHandler : IEmojiHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles an emoji instruction.
|
||||
/// </summary>
|
||||
/// <param name="state">Reference to the wiki state object</param>
|
||||
/// <param name="key">The lookup key for the given emoji.</param>
|
||||
/// <param name="scale">The desired 1-100 scale factor for the emoji.</param>
|
||||
public HandlerResult Handle(ITightEngineState state, string key, int scale)
|
||||
{
|
||||
var emoji = GlobalConfiguration.Emojis.FirstOrDefault(o => o.Shortcut == key);
|
||||
|
||||
if (GlobalConfiguration.Emojis.Exists(o => o.Shortcut == key))
|
||||
{
|
||||
if (scale != 100 && scale > 0 && scale <= 500)
|
||||
{
|
||||
var emojiImage = $"<img src=\"{GlobalConfiguration.BasePath}/file/Emoji/{key.Trim('%')}?Scale={scale}\" alt=\"{emoji?.Name}\" />";
|
||||
|
||||
return new HandlerResult(emojiImage);
|
||||
}
|
||||
else
|
||||
{
|
||||
var emojiImage = $"<img src=\"{GlobalConfiguration.BasePath}/file/Emoji/{key.Trim('%')}\" alt=\"{emoji?.Name}\" />";
|
||||
|
||||
return new HandlerResult(emojiImage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new HandlerResult(key) { Instructions = [HandlerResultInstruction.DisallowNestedProcessing] };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
TightWiki.Engine.Implementation/ExceptionHandler.cs
Normal file
27
TightWiki.Engine.Implementation/ExceptionHandler.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using TightWiki.Engine.Library.Interfaces;
|
||||
using TightWiki.Repository;
|
||||
|
||||
namespace TightWiki.Engine.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles exceptions thrown by the wiki engine.
|
||||
/// </summary>
|
||||
public class ExceptionHandler : IExceptionHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when an exception is thrown by the wiki engine.
|
||||
/// </summary>
|
||||
/// <param name="state">Reference to the wiki state object</param>
|
||||
/// <param name="ex">Optional exception, in the case that this was an actual exception.</param>
|
||||
/// <param name="customText">Text that accompanies the exception.</param>
|
||||
public void Log(ITightEngineState state, Exception? ex, string customText)
|
||||
{
|
||||
if (ex != null)
|
||||
{
|
||||
ExceptionRepository.InsertException(ex, customText);
|
||||
}
|
||||
|
||||
ExceptionRepository.InsertException(customText);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
TightWiki.Engine.Implementation/ExternalLinkHandler.cs
Normal file
38
TightWiki.Engine.Implementation/ExternalLinkHandler.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using TightWiki.Engine.Library;
|
||||
using TightWiki.Engine.Library.Interfaces;
|
||||
using static TightWiki.Engine.Library.Constants;
|
||||
|
||||
namespace TightWiki.Engine.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles links the wiki to another site.
|
||||
/// </summary>
|
||||
public class ExternalLinkHandler : IExternalLinkHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles an internal wiki link.
|
||||
/// </summary>
|
||||
/// <param name="state">Reference to the wiki state object</param>
|
||||
/// <param name="link">The address of the external site being linked to.</param>
|
||||
/// <param name="text">The text which should be show in the absence of an image.</param>
|
||||
/// <param name="image">The image that should be shown.</param>
|
||||
/// <param name="imageScale">The 0-100 image scale factor for the given image.</param>
|
||||
public HandlerResult Handle(ITightEngineState state, string link, string? text, string? image)
|
||||
{
|
||||
if (string.IsNullOrEmpty(image))
|
||||
{
|
||||
return new HandlerResult($"<a href=\"{link}\">{text}</a>")
|
||||
{
|
||||
Instructions = [HandlerResultInstruction.DisallowNestedProcessing]
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return new HandlerResult($"<a href=\"{link}\"><img src=\"{image}\" border =\"0\"></a>")
|
||||
{
|
||||
Instructions = [HandlerResultInstruction.DisallowNestedProcessing]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
TightWiki.Engine.Implementation/HeadingHandler.cs
Normal file
33
TightWiki.Engine.Implementation/HeadingHandler.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using TightWiki.Engine.Library;
|
||||
using TightWiki.Engine.Library.Interfaces;
|
||||
using static TightWiki.Engine.Library.Constants;
|
||||
|
||||
namespace TightWiki.Engine.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles wiki headings. These are automatically added to the table of contents.
|
||||
/// </summary>
|
||||
public class HeadingHandler : IHeadingHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles wiki headings. These are automatically added to the table of contents.
|
||||
/// </summary>
|
||||
/// <param name="state">Reference to the wiki state object</param>
|
||||
/// <param name="depth">The size of the header, also used for table of table of contents indentation.</param>
|
||||
/// <param name="link">The self link reference.</param>
|
||||
/// <param name="text">The text for the self link.</param>
|
||||
public HandlerResult Handle(ITightEngineState state, int depth, string link, string text)
|
||||
{
|
||||
if (depth >= 2 && depth <= 6)
|
||||
{
|
||||
int fontSize = 8 - depth;
|
||||
if (fontSize < 5) fontSize = 5;
|
||||
|
||||
string html = "<font size=\"" + fontSize + "\"><a name=\"" + link + "\"><span class=\"WikiH" + (depth - 1).ToString() + "\">" + text + "</span></a></font>\r\n";
|
||||
return new HandlerResult(html);
|
||||
}
|
||||
|
||||
return new HandlerResult() { Instructions = [HandlerResultInstruction.Skip] };
|
||||
}
|
||||
}
|
||||
}
|
||||
130
TightWiki.Engine.Implementation/Helpers.cs
Normal file
130
TightWiki.Engine.Implementation/Helpers.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using DuoVia.FuzzyStrings;
|
||||
using NTDLS.Helpers;
|
||||
using TightWiki.Caching;
|
||||
using TightWiki.Engine.Library.Interfaces;
|
||||
using TightWiki.Library.Interfaces;
|
||||
using TightWiki.Models.DataModels;
|
||||
using TightWiki.Repository;
|
||||
using static TightWiki.Engine.Library.Constants;
|
||||
|
||||
namespace TightWiki.Engine.Implementation
|
||||
{
|
||||
public class Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Inserts a new page if Page.Id == 0, other wise updates the page. All metadata is written to the database.
|
||||
/// </summary>
|
||||
/// <param name="sessionState"></param>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="page"></param>
|
||||
/// <returns></returns>
|
||||
public static int UpsertPage(ITightEngine wikifier, Page page, ISessionState? sessionState = null)
|
||||
{
|
||||
bool isNewlyCreated = page.Id == 0;
|
||||
|
||||
page.Id = PageRepository.SavePage(page);
|
||||
|
||||
RefreshPageMetadata(wikifier, page, sessionState);
|
||||
|
||||
if (isNewlyCreated)
|
||||
{
|
||||
//This will update the PageId of references that have been saved to the navigation link.
|
||||
PageRepository.UpdateSinglePageReference(page.Navigation, page.Id);
|
||||
}
|
||||
|
||||
return page.Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds the page and writes all aspects to the database.
|
||||
/// </summary>
|
||||
/// <param name="sessionState"></param>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="page"></param>
|
||||
public static void RefreshPageMetadata(ITightEngine wikifier, Page page, ISessionState? sessionState = null)
|
||||
{
|
||||
//We omit function calls from the tokenization process because they are too dynamic for static searching.
|
||||
var state = wikifier.Transform(sessionState, page, null,
|
||||
[WikiMatchType.StandardFunction]);
|
||||
|
||||
PageRepository.UpdatePageTags(page.Id, state.Tags);
|
||||
PageRepository.UpdatePageProcessingInstructions(page.Id, state.ProcessingInstructions);
|
||||
|
||||
var pageTokens = ParsePageTokens(state).Select(o =>
|
||||
new PageToken
|
||||
{
|
||||
PageId = page.Id,
|
||||
Token = o.Token,
|
||||
DoubleMetaphone = o.DoubleMetaphone,
|
||||
Weight = o.Weight
|
||||
}).ToList();
|
||||
|
||||
PageRepository.SavePageSearchTokens(pageTokens);
|
||||
|
||||
PageRepository.UpdatePageReferences(page.Id, state.OutgoingLinks);
|
||||
|
||||
WikiCache.ClearCategory(WikiCacheKey.Build(WikiCache.Category.Page, [page.Id]));
|
||||
WikiCache.ClearCategory(WikiCacheKey.Build(WikiCache.Category.Page, [page.Navigation]));
|
||||
}
|
||||
|
||||
public static List<AggregatedSearchToken> ParsePageTokens(ITightEngineState state)
|
||||
{
|
||||
var parsedTokens = new List<WeightedSearchToken>();
|
||||
|
||||
parsedTokens.AddRange(ComputeParsedPageTokens(state.HtmlResult, 1));
|
||||
parsedTokens.AddRange(ComputeParsedPageTokens(state.Page.Description, 1.2));
|
||||
parsedTokens.AddRange(ComputeParsedPageTokens(string.Join(" ", state.Tags), 1.4));
|
||||
parsedTokens.AddRange(ComputeParsedPageTokens(state.Page.Name, 1.6));
|
||||
|
||||
var aggregatedTokens = parsedTokens.GroupBy(o => o.Token).Select(o => new AggregatedSearchToken
|
||||
{
|
||||
Token = o.Key,
|
||||
DoubleMetaphone = o.Key.ToDoubleMetaphone(),
|
||||
Weight = o.Sum(g => g.Weight)
|
||||
}).ToList();
|
||||
|
||||
return aggregatedTokens;
|
||||
}
|
||||
|
||||
internal static List<WeightedSearchToken> ComputeParsedPageTokens(string content, double weightMultiplier)
|
||||
{
|
||||
var searchConfig = ConfigurationRepository.GetConfigurationEntryValuesByGroupName("Search");
|
||||
|
||||
var exclusionWords = searchConfig?.Value<string>("Word Exclusions")?
|
||||
.Split([',', ';'], StringSplitOptions.RemoveEmptyEntries).Distinct() ?? new List<string>();
|
||||
var strippedContent = Html.StripHtml(content);
|
||||
|
||||
var tokens = strippedContent.Split([' ', '\n', '\t', '-', '_']).ToList();
|
||||
|
||||
if (searchConfig?.Value<bool>("Split Camel Case") == true)
|
||||
{
|
||||
var allSplitTokens = new List<string>();
|
||||
|
||||
foreach (var token in tokens)
|
||||
{
|
||||
var splitTokens = Text.SplitCamelCase(token);
|
||||
if (splitTokens.Count > 1)
|
||||
{
|
||||
splitTokens.ForEach(t => allSplitTokens.Add(t));
|
||||
}
|
||||
}
|
||||
|
||||
tokens.AddRange(allSplitTokens);
|
||||
}
|
||||
|
||||
tokens = tokens.ConvertAll(d => d.ToLowerInvariant());
|
||||
|
||||
tokens.RemoveAll(o => exclusionWords.Contains(o));
|
||||
|
||||
var searchTokens = (from w in tokens
|
||||
group w by w into g
|
||||
select new WeightedSearchToken
|
||||
{
|
||||
Token = g.Key,
|
||||
Weight = g.Count() * weightMultiplier
|
||||
}).ToList();
|
||||
|
||||
return searchTokens.Where(o => string.IsNullOrWhiteSpace(o.Token) == false).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
153
TightWiki.Engine.Implementation/InternalLinkHandler.cs
Normal file
153
TightWiki.Engine.Implementation/InternalLinkHandler.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using TightWiki.Engine.Library;
|
||||
using TightWiki.Engine.Library.Interfaces;
|
||||
using TightWiki.Library;
|
||||
using TightWiki.Models;
|
||||
using TightWiki.Repository;
|
||||
using static TightWiki.Engine.Library.Constants;
|
||||
|
||||
namespace TightWiki.Engine.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles links from one wiki page to another.
|
||||
/// </summary>
|
||||
public class InternalLinkHandler : IInternalLinkHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles an internal wiki link.
|
||||
/// </summary>
|
||||
/// <param name="state">Reference to the wiki state object</param>
|
||||
/// <param name="pageNavigation">The navigation for the linked page.</param>
|
||||
/// <param name="pageName">The name of the page being linked to.</param>
|
||||
/// <param name="linkText">The text which should be show in the absence of an image.</param>
|
||||
/// <param name="image">The image that should be shown.</param>
|
||||
/// <param name="imageScale">The 0-100 image scale factor for the given image.</param>
|
||||
public HandlerResult Handle(ITightEngineState state, NamespaceNavigation pageNavigation,
|
||||
string pageName, string linkText, string? image, int imageScale)
|
||||
{
|
||||
var page = PageRepository.GetPageRevisionByNavigation(pageNavigation);
|
||||
|
||||
if (page == null)
|
||||
{
|
||||
if (state.Session?.CanCreate == true)
|
||||
{
|
||||
if (image != null)
|
||||
{
|
||||
string href;
|
||||
|
||||
if (image.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase)
|
||||
|| image.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
//The image is external.
|
||||
href = $"<a href=\"{GlobalConfiguration.BasePath}/Page/Create?Name={pageName}\"><img src=\"{GlobalConfiguration.BasePath}{image}?Scale={imageScale}\" /></a>";
|
||||
}
|
||||
else if (image.Contains('/'))
|
||||
{
|
||||
//The image is located on another page.
|
||||
href = $"<a href=\"{GlobalConfiguration.BasePath}/Page/Create?Name={pageName}\"><img src=\"{GlobalConfiguration.BasePath}/Page/Image/{image}?Scale={imageScale}\" /></a>";
|
||||
}
|
||||
else
|
||||
{
|
||||
//The image is located on this page, but this page does not exist.
|
||||
href = $"<a href=\"{GlobalConfiguration.BasePath}/Page/Create?Name={pageName}\">{linkText}</a>";
|
||||
}
|
||||
|
||||
return new HandlerResult(href)
|
||||
{
|
||||
Instructions = [HandlerResultInstruction.DisallowNestedProcessing]
|
||||
};
|
||||
}
|
||||
else if (linkText != null)
|
||||
{
|
||||
var href = $"<a href=\"{GlobalConfiguration.BasePath}/Page/Create?Name={pageName}\">{linkText}</a>"
|
||||
+ "<font color=\"#cc0000\" size=\"2\">?</font>";
|
||||
|
||||
return new HandlerResult(href)
|
||||
{
|
||||
Instructions = [HandlerResultInstruction.DisallowNestedProcessing]
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("No link or image was specified.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//The page does not exist and the user does not have permission to create it.
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
string mockHref;
|
||||
|
||||
if (image.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase)
|
||||
|| image.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
//The image is external.
|
||||
mockHref = $"<img src=\"{GlobalConfiguration.BasePath}{image}?Scale={imageScale}\" />";
|
||||
}
|
||||
else if (image.Contains('/'))
|
||||
{
|
||||
//The image is located on another page.
|
||||
mockHref = $"<img src=\"{GlobalConfiguration.BasePath}/Page/Image/{image}?Scale={imageScale}\" />";
|
||||
}
|
||||
else
|
||||
{
|
||||
//The image is located on this page, but this page does not exist.
|
||||
mockHref = $"linkText";
|
||||
}
|
||||
|
||||
return new HandlerResult(mockHref)
|
||||
{
|
||||
Instructions = [HandlerResultInstruction.DisallowNestedProcessing]
|
||||
};
|
||||
}
|
||||
else if (linkText != null)
|
||||
{
|
||||
return new HandlerResult(linkText)
|
||||
{
|
||||
Instructions = [HandlerResultInstruction.DisallowNestedProcessing]
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("No link or image was specified.");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string href;
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
if (image.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase)
|
||||
|| image.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
//The image is external.
|
||||
href = $"<a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\"><img src=\"{GlobalConfiguration.BasePath}{image}\" /></a>";
|
||||
}
|
||||
else if (image.Contains('/'))
|
||||
{
|
||||
//The image is located on another page.
|
||||
href = $"<a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\"><img src=\"{GlobalConfiguration.BasePath}/Page/Image/{image}?Scale={imageScale}\" /></a>";
|
||||
}
|
||||
else
|
||||
{
|
||||
//The image is located on this page.
|
||||
href = $"<a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\"><img src=\"{GlobalConfiguration.BasePath}/Page/Image/{state.Page.Navigation}/{image}?Scale={imageScale}\" /></a>";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Just a plain ol' internal page link.
|
||||
href = $"<a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{linkText}</a>";
|
||||
}
|
||||
|
||||
return new HandlerResult(href)
|
||||
{
|
||||
Instructions = [HandlerResultInstruction.DisallowNestedProcessing]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
34
TightWiki.Engine.Implementation/MarkupHandler.cs
Normal file
34
TightWiki.Engine.Implementation/MarkupHandler.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using TightWiki.Engine.Library;
|
||||
using TightWiki.Engine.Library.Interfaces;
|
||||
using static TightWiki.Engine.Library.Constants;
|
||||
|
||||
namespace TightWiki.Engine.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles basic markup/style instructions like bole, italic, underline, etc.
|
||||
/// </summary>
|
||||
public class MarkupHandler : IMarkupHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles basic markup instructions like bole, italic, underline, etc.
|
||||
/// </summary>
|
||||
/// <param name="state">Reference to the wiki state object</param>
|
||||
/// <param name="sequence">The sequence of symbols that were found to denotate this markup instruction,</param>
|
||||
/// <param name="scopeBody">The body of text to apply the style to.</param>
|
||||
public HandlerResult Handle(ITightEngineState state, char sequence, string scopeBody)
|
||||
{
|
||||
switch (sequence)
|
||||
{
|
||||
case '~': return new HandlerResult($"<strike>{scopeBody}</strike>");
|
||||
case '*': return new HandlerResult($"<strong>{scopeBody}</strong>");
|
||||
case '_': return new HandlerResult($"<u>{scopeBody}</u>");
|
||||
case '/': return new HandlerResult($"<i>{scopeBody}</i>");
|
||||
case '!': return new HandlerResult($"<mark>{scopeBody}</mark>");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return new HandlerResult() { Instructions = [HandlerResultInstruction.Skip] };
|
||||
}
|
||||
}
|
||||
}
|
||||
174
TightWiki.Engine.Implementation/PostProcessingFunctionHandler.cs
Normal file
174
TightWiki.Engine.Implementation/PostProcessingFunctionHandler.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
using System.Text;
|
||||
using TightWiki.Engine.Function;
|
||||
using TightWiki.Engine.Implementation.Utility;
|
||||
using TightWiki.Engine.Library;
|
||||
using TightWiki.Engine.Library.Interfaces;
|
||||
using TightWiki.Models;
|
||||
using static TightWiki.Engine.Function.FunctionPrototypeCollection;
|
||||
using static TightWiki.Engine.Library.Constants;
|
||||
|
||||
namespace TightWiki.Engine.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles post-processing function calls.
|
||||
/// </summary>
|
||||
public class PostProcessingFunctionHandler : IPostProcessingFunctionHandler
|
||||
{
|
||||
private static FunctionPrototypeCollection? _collection;
|
||||
|
||||
public FunctionPrototypeCollection Prototypes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_collection == null)
|
||||
{
|
||||
_collection = new FunctionPrototypeCollection(WikiFunctionType.Standard);
|
||||
|
||||
#region Prototypes.
|
||||
|
||||
_collection.Add("##Tags: <string>{styleName(Flat,List)}='List'");
|
||||
_collection.Add("##TagCloud: <string>[pageTag] | <integer>{Top}='1000'");
|
||||
_collection.Add("##SearchCloud: <string>[searchPhrase] | <integer>{Top}='1000'");
|
||||
_collection.Add("##TOC:<bool>{alphabetized}='false'");
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
return _collection;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called to handle function calls when proper prototypes are matched.
|
||||
/// </summary>
|
||||
/// <param name="state">Reference to the wiki state object</param>
|
||||
/// <param name="function">The parsed function call and all its parameters and their values.</param>
|
||||
/// <param name="scopeBody">This is not a scope function, this should always be null</param>
|
||||
public HandlerResult Handle(ITightEngineState state, FunctionCall function, string? scopeBody = null)
|
||||
{
|
||||
switch (function.Name.ToLower())
|
||||
{
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
//Displays a tag link list.
|
||||
case "tags": //##tags
|
||||
{
|
||||
string styleName = function.Parameters.Get<string>("styleName").ToLower();
|
||||
var html = new StringBuilder();
|
||||
|
||||
if (styleName == "list")
|
||||
{
|
||||
html.Append("<ul>");
|
||||
foreach (var tag in state.Tags)
|
||||
{
|
||||
html.Append($"<li><a href=\"{GlobalConfiguration.BasePath}/Tag/Browse/{tag}\">{tag}</a>");
|
||||
}
|
||||
html.Append("</ul>");
|
||||
}
|
||||
else if (styleName == "flat")
|
||||
{
|
||||
foreach (var tag in state.Tags)
|
||||
{
|
||||
if (html.Length > 0) html.Append(" | ");
|
||||
html.Append($"<a href=\"{GlobalConfiguration.BasePath}/Tag/Browse/{tag}\">{tag}</a>");
|
||||
}
|
||||
}
|
||||
|
||||
return new HandlerResult(html.ToString());
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "tagcloud":
|
||||
{
|
||||
var top = function.Parameters.Get<int>("Top");
|
||||
string seedTag = function.Parameters.Get<string>("pageTag");
|
||||
|
||||
string html = TagCloud.Build(seedTag, top);
|
||||
return new HandlerResult(html);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "searchcloud":
|
||||
{
|
||||
var top = function.Parameters.Get<int>("Top");
|
||||
var tokens = function.Parameters.Get<string>("searchPhrase").Split(" ", StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
|
||||
string html = SearchCloud.Build(tokens, top);
|
||||
return new HandlerResult(html);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
//Displays a table of contents for the page based on the header tags.
|
||||
case "toc":
|
||||
{
|
||||
bool alphabetized = function.Parameters.Get<bool>("alphabetized");
|
||||
|
||||
var html = new StringBuilder();
|
||||
|
||||
var tags = (from t in state.TableOfContents
|
||||
orderby t.StartingPosition
|
||||
select t).ToList();
|
||||
|
||||
var unordered = new List<TableOfContentsTag>();
|
||||
var ordered = new List<TableOfContentsTag>();
|
||||
|
||||
if (alphabetized)
|
||||
{
|
||||
int level = tags.FirstOrDefault()?.Level ?? 0;
|
||||
|
||||
foreach (var tag in tags)
|
||||
{
|
||||
if (level != tag.Level)
|
||||
{
|
||||
ordered.AddRange(unordered.OrderBy(o => o.Text));
|
||||
unordered.Clear();
|
||||
level = tag.Level;
|
||||
}
|
||||
|
||||
unordered.Add(tag);
|
||||
}
|
||||
|
||||
ordered.AddRange(unordered.OrderBy(o => o.Text));
|
||||
unordered.Clear();
|
||||
|
||||
tags = ordered.ToList();
|
||||
}
|
||||
|
||||
int currentLevel = 0;
|
||||
|
||||
foreach (var tag in tags)
|
||||
{
|
||||
if (tag.Level > currentLevel)
|
||||
{
|
||||
while (currentLevel < tag.Level)
|
||||
{
|
||||
html.Append("<ul>");
|
||||
currentLevel++;
|
||||
}
|
||||
}
|
||||
else if (tag.Level < currentLevel)
|
||||
{
|
||||
while (currentLevel > tag.Level)
|
||||
{
|
||||
|
||||
html.Append("</ul>");
|
||||
currentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
html.Append("<li><a href=\"#" + tag.HrefTag + "\">" + tag.Text + "</a></li>");
|
||||
}
|
||||
|
||||
while (currentLevel > 0)
|
||||
{
|
||||
html.Append("</ul>");
|
||||
currentLevel--;
|
||||
}
|
||||
|
||||
return new HandlerResult(html.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return new HandlerResult() { Instructions = [HandlerResultInstruction.Skip] };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
using TightWiki.Engine.Function;
|
||||
using TightWiki.Engine.Library;
|
||||
using TightWiki.Engine.Library.Interfaces;
|
||||
using static TightWiki.Engine.Function.FunctionPrototypeCollection;
|
||||
using static TightWiki.Engine.Library.Constants;
|
||||
using static TightWiki.Library.Constants;
|
||||
|
||||
namespace TightWiki.Engine.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles processing-instruction function calls, these functions affect the way the page is processed, but are not directly replaced with text.
|
||||
/// </summary>
|
||||
public class ProcessingInstructionFunctionHandler : IProcessingInstructionFunctionHandler
|
||||
{
|
||||
private static FunctionPrototypeCollection? _collection;
|
||||
|
||||
public FunctionPrototypeCollection Prototypes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_collection == null)
|
||||
{
|
||||
_collection = new FunctionPrototypeCollection(WikiFunctionType.Instruction);
|
||||
|
||||
#region Prototypes.
|
||||
|
||||
//Processing instructions:
|
||||
_collection.Add("@@Deprecate:");
|
||||
_collection.Add("@@Protect:<bool>{isSilent}='false'");
|
||||
_collection.Add("@@Tags: <string:infinite>[pageTags]");
|
||||
_collection.Add("@@Template:");
|
||||
_collection.Add("@@Review:");
|
||||
_collection.Add("@@NoCache:");
|
||||
_collection.Add("@@Include:");
|
||||
_collection.Add("@@Draft:");
|
||||
_collection.Add("@@HideFooterComments:");
|
||||
_collection.Add("@@Title:<string>[pageTitle]");
|
||||
_collection.Add("@@HideFooterLastModified:");
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
return _collection;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called to handle function calls when proper prototypes are matched.
|
||||
/// </summary>
|
||||
/// <param name="state">Reference to the wiki state object</param>
|
||||
/// <param name="function">The parsed function call and all its parameters and their values.</param>
|
||||
/// <param name="scopeBody">This is not a scope function, this should always be null</param>
|
||||
public HandlerResult Handle(ITightEngineState state, FunctionCall function, string? scopeBody = null)
|
||||
{
|
||||
switch (function.Name.ToLower())
|
||||
{
|
||||
//We check wikifierSession.Factory.CurrentNestLevel here because we don't want to include the processing instructions on any parent pages that are injecting this one.
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
//Associates tags with a page. These are saved with the page and can also be displayed.
|
||||
case "tags": //##tag(pipe|separated|list|of|tags)
|
||||
{
|
||||
var tags = function.Parameters.GetList<string>("pageTags");
|
||||
state.Tags.AddRange(tags);
|
||||
state.Tags = state.Tags.Distinct().ToList();
|
||||
|
||||
return new HandlerResult(string.Empty)
|
||||
{
|
||||
Instructions = [HandlerResultInstruction.TruncateTrailingLine]
|
||||
};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "title":
|
||||
{
|
||||
state.PageTitle = function.Parameters.Get<string>("pageTitle");
|
||||
|
||||
return new HandlerResult(string.Empty)
|
||||
{
|
||||
Instructions = [HandlerResultInstruction.TruncateTrailingLine]
|
||||
};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "hidefooterlastmodified":
|
||||
{
|
||||
state.ProcessingInstructions.Add(WikiInstruction.HideFooterLastModified);
|
||||
|
||||
return new HandlerResult(string.Empty)
|
||||
{
|
||||
Instructions = [HandlerResultInstruction.TruncateTrailingLine]
|
||||
};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "hidefootercomments":
|
||||
{
|
||||
state.ProcessingInstructions.Add(WikiInstruction.HideFooterComments);
|
||||
return new HandlerResult(string.Empty)
|
||||
{
|
||||
Instructions = [HandlerResultInstruction.TruncateTrailingLine]
|
||||
};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "nocache":
|
||||
{
|
||||
state.ProcessingInstructions.Add(WikiInstruction.NoCache);
|
||||
return new HandlerResult(string.Empty)
|
||||
{
|
||||
Instructions = [HandlerResultInstruction.TruncateTrailingLine]
|
||||
};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "deprecate":
|
||||
{
|
||||
if (state.NestDepth == 0)
|
||||
{
|
||||
state.ProcessingInstructions.Add(WikiInstruction.Deprecate);
|
||||
state.Headers.Add("<div class=\"alert alert-danger\">This page has been deprecated and will eventually be deleted.</div>");
|
||||
}
|
||||
return new HandlerResult(string.Empty)
|
||||
{
|
||||
Instructions = [HandlerResultInstruction.TruncateTrailingLine]
|
||||
};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "protect":
|
||||
{
|
||||
if (state.NestDepth == 0)
|
||||
{
|
||||
bool isSilent = function.Parameters.Get<bool>("isSilent");
|
||||
state.ProcessingInstructions.Add(WikiInstruction.Protect);
|
||||
if (isSilent == false)
|
||||
{
|
||||
state.Headers.Add("<div class=\"alert alert-info\">This page has been protected and can not be changed by non-moderators.</div>");
|
||||
}
|
||||
}
|
||||
return new HandlerResult(string.Empty)
|
||||
{
|
||||
Instructions = [HandlerResultInstruction.TruncateTrailingLine]
|
||||
};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "template":
|
||||
{
|
||||
if (state.NestDepth == 0)
|
||||
{
|
||||
state.ProcessingInstructions.Add(WikiInstruction.Template);
|
||||
state.Headers.Add("<div class=\"alert alert-secondary\">This page is a template and will not appear in indexes or glossaries.</div>");
|
||||
}
|
||||
return new HandlerResult(string.Empty)
|
||||
{
|
||||
Instructions = [HandlerResultInstruction.TruncateTrailingLine]
|
||||
};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "review":
|
||||
{
|
||||
if (state.NestDepth == 0)
|
||||
{
|
||||
state.ProcessingInstructions.Add(WikiInstruction.Review);
|
||||
state.Headers.Add("<div class=\"alert alert-warning\">This page has been flagged for review, its content may be inaccurate.</div>");
|
||||
}
|
||||
return new HandlerResult(string.Empty)
|
||||
{
|
||||
Instructions = [HandlerResultInstruction.TruncateTrailingLine]
|
||||
};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "include":
|
||||
{
|
||||
if (state.NestDepth == 0)
|
||||
{
|
||||
state.ProcessingInstructions.Add(WikiInstruction.Include);
|
||||
state.Headers.Add("<div class=\"alert alert-secondary\">This page is an include and will not appear in indexes or glossaries.</div>");
|
||||
}
|
||||
return new HandlerResult(string.Empty)
|
||||
{
|
||||
Instructions = [HandlerResultInstruction.TruncateTrailingLine]
|
||||
};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "draft":
|
||||
{
|
||||
if (state.NestDepth == 0)
|
||||
{
|
||||
state.ProcessingInstructions.Add(WikiInstruction.Draft);
|
||||
state.Headers.Add("<div class=\"alert alert-warning\">This page is a draft and may contain incorrect information and/or experimental styling.</div>");
|
||||
}
|
||||
return new HandlerResult(string.Empty)
|
||||
{
|
||||
Instructions = [HandlerResultInstruction.TruncateTrailingLine]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return new HandlerResult() { Instructions = [HandlerResultInstruction.Skip] };
|
||||
}
|
||||
}
|
||||
}
|
||||
373
TightWiki.Engine.Implementation/ScopeFunctionHandler.cs
Normal file
373
TightWiki.Engine.Implementation/ScopeFunctionHandler.cs
Normal file
@@ -0,0 +1,373 @@
|
||||
using NTDLS.Helpers;
|
||||
using System.Text;
|
||||
using TightWiki.Engine.Function;
|
||||
using TightWiki.Engine.Implementation.Utility;
|
||||
using TightWiki.Engine.Library;
|
||||
using TightWiki.Engine.Library.Interfaces;
|
||||
using static TightWiki.Engine.Function.FunctionPrototypeCollection;
|
||||
using static TightWiki.Engine.Library.Constants;
|
||||
|
||||
namespace TightWiki.Engine.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Handled scope function calls.
|
||||
/// </summary>
|
||||
public class ScopeFunctionHandler : IScopeFunctionHandler
|
||||
{
|
||||
private static FunctionPrototypeCollection? _collection;
|
||||
|
||||
public FunctionPrototypeCollection Prototypes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_collection == null)
|
||||
{
|
||||
_collection = new FunctionPrototypeCollection(WikiFunctionType.Scoped);
|
||||
|
||||
#region Prototypes.
|
||||
|
||||
_collection.Add("$$Code: <string>{language(auto,wiki,cpp,lua,graphql,swift,r,yaml,kotlin,scss,shell,vbnet,json,objectivec,perl,diff,wasm,php,xml,bash,csharp,css,go,ini,javascript,less,makefile,markdown,plaintext,python,python-repl,ruby,rust,sql,typescript)}='auto'");
|
||||
_collection.Add("$$Bullets: <string>{type(unordered,ordered)}='unordered'");
|
||||
_collection.Add("$$Order: <string>{direction(ascending,descending)}='ascending'");
|
||||
_collection.Add("$$Jumbotron:");
|
||||
_collection.Add("$$Callout: <string>{styleName(default,primary,secondary,success,info,warning,danger)}='default' | <string>{titleText}=''");
|
||||
_collection.Add("$$Background: <string>{styleName(default,primary,secondary,light,dark,success,info,warning,danger,muted)}='default'");
|
||||
_collection.Add("$$Foreground: <string>{styleName(default,primary,secondary,light,dark,success,info,warning,danger,muted)}='default'");
|
||||
_collection.Add("$$Alert: <string>{styleName(default,primary,secondary,light,dark,success,info,warning,danger)}='default' | <string>{titleText}=''");
|
||||
_collection.Add("$$Card: <string>{styleName(default,primary,secondary,light,dark,success,info,warning,danger)}='default' | <string>{titleText}=''");
|
||||
_collection.Add("$$Collapse: <string>{linkText}='Show'");
|
||||
_collection.Add("$$Table: <boolean>{hasBorder}='true' | <boolean>{isFirstRowHeader}='true'");
|
||||
_collection.Add("$$StripedTable: <boolean>{hasBorder}='true' | <boolean>{isFirstRowHeader}='true'");
|
||||
_collection.Add("$$DefineSnippet: <string>[name]");
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
return _collection;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called to handle function calls when proper prototypes are matched.
|
||||
/// </summary>
|
||||
/// <param name="state">Reference to the wiki state object</param>
|
||||
/// <param name="function">The parsed function call and all its parameters and their values.</param>
|
||||
/// <param name="scopeBody">The the text that the function is designed to affect.</param>
|
||||
public HandlerResult Handle(ITightEngineState state, FunctionCall function, string? scopeBody = null)
|
||||
{
|
||||
scopeBody.EnsureNotNull($"The function '{function.Name}' scope body can not be null");
|
||||
|
||||
switch (function.Name.ToLower())
|
||||
{
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "code":
|
||||
{
|
||||
var html = new StringBuilder();
|
||||
|
||||
string language = function.Parameters.Get<string>("language");
|
||||
if (string.IsNullOrEmpty(language) || language?.ToLower() == "auto")
|
||||
{
|
||||
html.Append($"<pre>");
|
||||
html.Append($"<code>{scopeBody.Replace("\r\n", "\n").Replace("\n", SoftBreak)}</code></pre>");
|
||||
}
|
||||
else
|
||||
{
|
||||
html.Append($"<pre class=\"language-{language}\">");
|
||||
html.Append($"<code>{scopeBody.Replace("\r\n", "\n").Replace("\n", SoftBreak)}</code></pre>");
|
||||
}
|
||||
|
||||
return new HandlerResult(html.ToString())
|
||||
{
|
||||
Instructions = [HandlerResultInstruction.DisallowNestedProcessing]
|
||||
};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "stripedtable":
|
||||
case "table":
|
||||
{
|
||||
var html = new StringBuilder();
|
||||
|
||||
var hasBorder = function.Parameters.Get<bool>("hasBorder");
|
||||
var isFirstRowHeader = function.Parameters.Get<bool>("isFirstRowHeader");
|
||||
|
||||
html.Append($"<table class=\"table");
|
||||
|
||||
if (function.Name.Equals("stripedtable", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
html.Append(" table-striped");
|
||||
}
|
||||
if (hasBorder)
|
||||
{
|
||||
html.Append(" table-bordered");
|
||||
}
|
||||
|
||||
html.Append($"\">");
|
||||
|
||||
var lines = scopeBody.Split(['\n'], StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim()).Where(o => o.Length > 0);
|
||||
|
||||
int rowNumber = 0;
|
||||
|
||||
foreach (var lineText in lines)
|
||||
{
|
||||
var columns = lineText.Split("||");
|
||||
|
||||
if (rowNumber == 0 && isFirstRowHeader)
|
||||
{
|
||||
html.Append($"<thead>");
|
||||
}
|
||||
else if (rowNumber == 1 && isFirstRowHeader || rowNumber == 0 && isFirstRowHeader == false)
|
||||
{
|
||||
html.Append($"<tbody>");
|
||||
}
|
||||
|
||||
html.Append($"<tr>");
|
||||
foreach (var columnText in columns)
|
||||
{
|
||||
if (rowNumber == 0 && isFirstRowHeader)
|
||||
{
|
||||
html.Append($"<td><strong>{columnText}</strong></td>");
|
||||
}
|
||||
else
|
||||
{
|
||||
html.Append($"<td>{columnText}</td>");
|
||||
}
|
||||
}
|
||||
|
||||
if (rowNumber == 0 && isFirstRowHeader)
|
||||
{
|
||||
html.Append($"</thead>");
|
||||
}
|
||||
html.Append($"</tr>");
|
||||
|
||||
rowNumber++;
|
||||
}
|
||||
|
||||
html.Append($"</tbody>");
|
||||
html.Append($"</table>");
|
||||
|
||||
return new HandlerResult(html.ToString());
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "bullets":
|
||||
{
|
||||
var html = new StringBuilder();
|
||||
|
||||
string type = function.Parameters.Get<string>("type");
|
||||
|
||||
if (type == "unordered")
|
||||
{
|
||||
var lines = scopeBody.Split(['\n'], StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim()).Where(o => o.Length > 0);
|
||||
|
||||
int currentLevel = 0;
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
int newIndent = 0;
|
||||
for (; newIndent < line.Length && line[newIndent] == '>'; newIndent++)
|
||||
{
|
||||
//Count how many '>' are at the start of the line.
|
||||
}
|
||||
newIndent++;
|
||||
|
||||
if (newIndent < currentLevel)
|
||||
{
|
||||
for (; currentLevel != newIndent; currentLevel--)
|
||||
{
|
||||
html.Append($"</ul>");
|
||||
}
|
||||
}
|
||||
else if (newIndent > currentLevel)
|
||||
{
|
||||
for (; currentLevel != newIndent; currentLevel++)
|
||||
{
|
||||
html.Append($"<ul>");
|
||||
}
|
||||
}
|
||||
|
||||
html.Append($"<li>{line.Trim(['>'])}</li>");
|
||||
}
|
||||
|
||||
for (; currentLevel > 0; currentLevel--)
|
||||
{
|
||||
html.Append($"</ul>");
|
||||
}
|
||||
}
|
||||
else if (type == "ordered")
|
||||
{
|
||||
var lines = scopeBody.Split(['\n'], StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim()).Where(o => o.Length > 0);
|
||||
|
||||
int currentLevel = 0;
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
int newIndent = 0;
|
||||
for (; newIndent < line.Length && line[newIndent] == '>'; newIndent++)
|
||||
{
|
||||
//Count how many '>' are at the start of the line.
|
||||
}
|
||||
newIndent++;
|
||||
|
||||
if (newIndent < currentLevel)
|
||||
{
|
||||
for (; currentLevel != newIndent; currentLevel--)
|
||||
{
|
||||
html.Append($"</ol>");
|
||||
}
|
||||
}
|
||||
else if (newIndent > currentLevel)
|
||||
{
|
||||
for (; currentLevel != newIndent; currentLevel++)
|
||||
{
|
||||
html.Append($"<ol>");
|
||||
}
|
||||
}
|
||||
|
||||
html.Append($"<li>{line.Trim(['>'])}</li>");
|
||||
}
|
||||
|
||||
for (; currentLevel > 0; currentLevel--)
|
||||
{
|
||||
html.Append($"</ol>");
|
||||
}
|
||||
}
|
||||
return new HandlerResult(html.ToString());
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "definesnippet":
|
||||
{
|
||||
var html = new StringBuilder();
|
||||
|
||||
string name = function.Parameters.Get<string>("name");
|
||||
|
||||
if (!state.Snippets.TryAdd(name, scopeBody))
|
||||
{
|
||||
state.Snippets[name] = scopeBody;
|
||||
}
|
||||
|
||||
return new HandlerResult(html.ToString());
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "alert":
|
||||
{
|
||||
var html = new StringBuilder();
|
||||
|
||||
string titleText = function.Parameters.Get<string>("titleText");
|
||||
string style = function.Parameters.Get<string>("styleName").ToLower();
|
||||
style = style == "default" ? "" : $"alert-{style}";
|
||||
|
||||
if (!string.IsNullOrEmpty(titleText)) scopeBody = $"<h1>{titleText}</h1>{scopeBody}";
|
||||
html.Append($"<div class=\"alert {style}\">{scopeBody}</div>");
|
||||
return new HandlerResult(html.ToString());
|
||||
}
|
||||
|
||||
case "order":
|
||||
{
|
||||
var html = new StringBuilder();
|
||||
|
||||
string direction = function.Parameters.Get<string>("direction");
|
||||
var lines = scopeBody.Split("\n").Select(o => o.Trim()).ToList();
|
||||
|
||||
if (direction == "ascending")
|
||||
{
|
||||
html.Append(string.Join("\r\n", lines.OrderBy(o => o)));
|
||||
}
|
||||
else
|
||||
{
|
||||
html.Append(string.Join("\r\n", lines.OrderByDescending(o => o)));
|
||||
}
|
||||
return new HandlerResult(html.ToString());
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "jumbotron":
|
||||
{
|
||||
var html = new StringBuilder();
|
||||
|
||||
string titleText = function.Parameters.Get("titleText", "");
|
||||
html.Append($"<div class=\"mt-4 p-5 bg-secondary text-white rounded\">");
|
||||
if (!string.IsNullOrEmpty(titleText)) html.Append($"<h1>{titleText}</h1>");
|
||||
html.Append($"<p>{scopeBody}</p>");
|
||||
html.Append($"</div>");
|
||||
return new HandlerResult(html.ToString());
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "foreground":
|
||||
{
|
||||
var html = new StringBuilder();
|
||||
|
||||
var style = BGFGStyle.GetForegroundStyle(function.Parameters.Get("styleName", "default")).Swap();
|
||||
html.Append($"<p class=\"{style.ForegroundStyle} {style.BackgroundStyle}\">{scopeBody}</p>");
|
||||
return new HandlerResult(html.ToString());
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "background":
|
||||
{
|
||||
var html = new StringBuilder();
|
||||
|
||||
var style = BGFGStyle.GetBackgroundStyle(function.Parameters.Get("styleName", "default"));
|
||||
html.Append($"<div class=\"p-3 mb-2 {style.ForegroundStyle} {style.BackgroundStyle}\">{scopeBody}</div>");
|
||||
return new HandlerResult(html.ToString());
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "collapse":
|
||||
{
|
||||
var html = new StringBuilder();
|
||||
|
||||
string linkText = function.Parameters.Get<string>("linktext");
|
||||
string uid = "A" + Guid.NewGuid().ToString().Replace("-", "");
|
||||
html.Append($"<a data-bs-toggle=\"collapse\" href=\"#{uid}\" role=\"button\" aria-expanded=\"false\" aria-controls=\"{uid}\">{linkText}</a>");
|
||||
html.Append($"<div class=\"collapse\" id=\"{uid}\">");
|
||||
html.Append($"<div class=\"card card-body\"><p class=\"card-text\">{scopeBody}</p></div></div>");
|
||||
return new HandlerResult(html.ToString());
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "callout":
|
||||
{
|
||||
var html = new StringBuilder();
|
||||
|
||||
string titleText = function.Parameters.Get<string>("titleText");
|
||||
string style = function.Parameters.Get<string>("styleName").ToLower();
|
||||
style = style == "default" ? "" : style;
|
||||
|
||||
html.Append($"<div class=\"bd-callout bd-callout-{style}\">");
|
||||
if (string.IsNullOrWhiteSpace(titleText) == false) html.Append($"<h4>{titleText}</h4>");
|
||||
html.Append($"{scopeBody}");
|
||||
html.Append($"</div>");
|
||||
return new HandlerResult(html.ToString());
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
case "card":
|
||||
{
|
||||
var html = new StringBuilder();
|
||||
|
||||
string titleText = function.Parameters.Get<string>("titleText");
|
||||
var style = BGFGStyle.GetBackgroundStyle(function.Parameters.Get("styleName", "default"));
|
||||
|
||||
html.Append($"<div class=\"card {style.ForegroundStyle} {style.BackgroundStyle} mb-3\">");
|
||||
if (string.IsNullOrEmpty(titleText) == false)
|
||||
{
|
||||
html.Append($"<div class=\"card-header\">{titleText}</div>");
|
||||
}
|
||||
html.Append("<div class=\"card-body\">");
|
||||
html.Append($"<p class=\"card-text\">{scopeBody}</p>");
|
||||
html.Append("</div>");
|
||||
html.Append("</div>");
|
||||
return new HandlerResult(html.ToString());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new HandlerResult() { Instructions = [HandlerResultInstruction.Skip] };
|
||||
}
|
||||
}
|
||||
}
|
||||
1187
TightWiki.Engine.Implementation/StandardFunctionHandler.cs
Normal file
1187
TightWiki.Engine.Implementation/StandardFunctionHandler.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Version>2.20.1</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<DebugSymbols>False</DebugSymbols>
|
||||
<DebugType>None</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\TightWiki.Engine.Library\TightWiki.Engine.Library.csproj" />
|
||||
<ProjectReference Include="..\TightWiki.Library\TightWiki.Library.csproj" />
|
||||
<ProjectReference Include="..\TightWiki.Models\TightWiki.Models.csproj" />
|
||||
<ProjectReference Include="..\TightWiki.Repository\TightWiki.Repository.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
70
TightWiki.Engine.Implementation/Utility/BGFGStyle.cs
Normal file
70
TightWiki.Engine.Implementation/Utility/BGFGStyle.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
namespace TightWiki.Engine.Implementation.Utility
|
||||
{
|
||||
public class BGFGStyle
|
||||
{
|
||||
public string ForegroundStyle { get; set; } = String.Empty;
|
||||
public string BackgroundStyle { get; set; } = String.Empty;
|
||||
|
||||
public BGFGStyle(string foregroundStyle, string backgroundStyle)
|
||||
{
|
||||
ForegroundStyle = foregroundStyle;
|
||||
BackgroundStyle = backgroundStyle;
|
||||
}
|
||||
|
||||
public BGFGStyle Swap()
|
||||
{
|
||||
return new BGFGStyle(BackgroundStyle, ForegroundStyle);
|
||||
}
|
||||
|
||||
public BGFGStyle()
|
||||
{
|
||||
}
|
||||
|
||||
public static readonly Dictionary<string, BGFGStyle> ForegroundStyles = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "primary", new BGFGStyle("text-primary", "") },
|
||||
{ "secondary", new BGFGStyle("text-secondary", "") },
|
||||
{ "success", new BGFGStyle("text-success", "") },
|
||||
{ "danger", new BGFGStyle("text-danger", "") },
|
||||
{ "warning", new BGFGStyle("text-warning", "") },
|
||||
{ "info", new BGFGStyle("text-info", "") },
|
||||
{ "light", new BGFGStyle("text-light", "") },
|
||||
{ "dark", new BGFGStyle("text-dark", "") },
|
||||
{ "muted", new BGFGStyle("text-muted", "") },
|
||||
{ "white", new BGFGStyle("text-white", "bg-dark") }
|
||||
};
|
||||
|
||||
public static readonly Dictionary<string, BGFGStyle> BackgroundStyles = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "muted", new BGFGStyle("text-muted", "") },
|
||||
{ "primary", new BGFGStyle("text-white", "bg-primary") },
|
||||
{ "secondary", new BGFGStyle("text-white", "bg-secondary") },
|
||||
{ "info", new BGFGStyle("text-white", "bg-info") },
|
||||
{ "success", new BGFGStyle("text-white", "bg-success") },
|
||||
{ "warning", new BGFGStyle("bg-warning", "") },
|
||||
{ "danger", new BGFGStyle("text-white", "bg-danger") },
|
||||
{ "light", new BGFGStyle("text-black", "bg-light") },
|
||||
{ "dark", new BGFGStyle("text-white", "bg-dark") }
|
||||
};
|
||||
|
||||
public static BGFGStyle GetBackgroundStyle(string style)
|
||||
{
|
||||
if (BackgroundStyles.TryGetValue(style, out var html))
|
||||
{
|
||||
return html;
|
||||
}
|
||||
|
||||
return new BGFGStyle();
|
||||
}
|
||||
|
||||
public static BGFGStyle GetForegroundStyle(string style)
|
||||
{
|
||||
if (ForegroundStyles.TryGetValue(style, out var html))
|
||||
{
|
||||
return html;
|
||||
}
|
||||
|
||||
return new BGFGStyle();
|
||||
}
|
||||
}
|
||||
}
|
||||
50
TightWiki.Engine.Implementation/Utility/Differentiator.cs
Normal file
50
TightWiki.Engine.Implementation/Utility/Differentiator.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System.Text;
|
||||
|
||||
namespace TightWiki.Engine.Implementation.Utility
|
||||
{
|
||||
public static class Differentiator
|
||||
{
|
||||
/// <summary>
|
||||
/// This leaves a lot to be desired.
|
||||
/// </summary>
|
||||
/// <param name="thisRev"></param>
|
||||
/// <param name="prevRev"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetComparisonSummary(string thisRev, string prevRev)
|
||||
{
|
||||
var summary = new StringBuilder();
|
||||
|
||||
var thisRevLines = thisRev.Split('\n');
|
||||
var prevRevLines = prevRev.Split('\n');
|
||||
int thisRevLineCount = thisRevLines.Length;
|
||||
int prevRevLinesCount = prevRevLines.Length;
|
||||
|
||||
int linesAdded = prevRevLines.Except(thisRevLines).Count();
|
||||
int linesDeleted = thisRevLines.Except(prevRevLines).Count();
|
||||
|
||||
if (thisRevLineCount != prevRevLinesCount)
|
||||
{
|
||||
summary.Append($"{Math.Abs(thisRevLineCount - prevRevLinesCount):N0} lines changed.");
|
||||
}
|
||||
|
||||
if (linesAdded > 0)
|
||||
{
|
||||
if (summary.Length > 0) summary.Append(' ');
|
||||
summary.Append($"{linesAdded:N0} lines added.");
|
||||
}
|
||||
|
||||
if (linesDeleted > 0)
|
||||
{
|
||||
if (summary.Length > 0) summary.Append(' ');
|
||||
summary.Append($"{linesDeleted:N0} lines deleted.");
|
||||
}
|
||||
|
||||
if (summary.Length == 0)
|
||||
{
|
||||
summary.Append($"No changes detected.");
|
||||
}
|
||||
|
||||
return summary.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
54
TightWiki.Engine.Implementation/Utility/SearchCloud.cs
Normal file
54
TightWiki.Engine.Implementation/Utility/SearchCloud.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System.Text;
|
||||
using TightWiki.Models;
|
||||
using TightWiki.Models.DataModels;
|
||||
using TightWiki.Repository;
|
||||
|
||||
namespace TightWiki.Engine.Implementation.Utility
|
||||
{
|
||||
public class SearchCloud
|
||||
{
|
||||
public static string Build(List<string> searchTokens, int? maxCount = null)
|
||||
{
|
||||
var pages = PageRepository.PageSearch(searchTokens).OrderByDescending(o => o.Score).ToList();
|
||||
|
||||
if (maxCount > 0)
|
||||
{
|
||||
pages = pages.Take((int)maxCount).ToList();
|
||||
}
|
||||
|
||||
int pageCount = pages.Count;
|
||||
int fontSize = 7;
|
||||
int sizeStep = (pageCount > fontSize ? pageCount : (fontSize * 2)) / fontSize;
|
||||
int pageIndex = 0;
|
||||
|
||||
var pageList = new List<TagCloudItem>();
|
||||
|
||||
foreach (var page in pages)
|
||||
{
|
||||
pageList.Add(new TagCloudItem(page.Name, pageIndex, "<font size=\"" + fontSize + $"\"><a href=\"{GlobalConfiguration.BasePath}/" + page.Navigation + "\">" + page.Name + "</a></font>"));
|
||||
|
||||
if ((pageIndex % sizeStep) == 0)
|
||||
{
|
||||
fontSize--;
|
||||
}
|
||||
|
||||
pageIndex++;
|
||||
}
|
||||
|
||||
var cloudHtml = new StringBuilder();
|
||||
|
||||
pageList.Sort(TagCloudItem.CompareItem);
|
||||
|
||||
cloudHtml.Append("<table align=\"center\" border=\"0\" width=\"100%\"><tr><td><p align=\"justify\">");
|
||||
|
||||
foreach (TagCloudItem tag in pageList)
|
||||
{
|
||||
cloudHtml.Append(tag.HTML + " ");
|
||||
}
|
||||
|
||||
cloudHtml.Append("</p></td></tr></table>");
|
||||
|
||||
return cloudHtml.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
55
TightWiki.Engine.Implementation/Utility/TagCloud.cs
Normal file
55
TightWiki.Engine.Implementation/Utility/TagCloud.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System.Text;
|
||||
using TightWiki.Library;
|
||||
using TightWiki.Models;
|
||||
using TightWiki.Models.DataModels;
|
||||
using TightWiki.Repository;
|
||||
|
||||
namespace TightWiki.Engine.Implementation.Utility
|
||||
{
|
||||
public static class TagCloud
|
||||
{
|
||||
public static string Build(string seedTag, int? maxCount)
|
||||
{
|
||||
var tags = PageRepository.GetAssociatedTags(seedTag).OrderByDescending(o => o.PageCount).ToList();
|
||||
|
||||
if (maxCount > 0)
|
||||
{
|
||||
tags = tags.Take((int)maxCount).ToList();
|
||||
}
|
||||
|
||||
int tagCount = tags.Count;
|
||||
int fontSize = 7;
|
||||
int sizeStep = (tagCount > fontSize ? tagCount : (fontSize * 2)) / fontSize;
|
||||
int tagIndex = 0;
|
||||
|
||||
var tagList = new List<TagCloudItem>();
|
||||
|
||||
foreach (var tag in tags)
|
||||
{
|
||||
tagList.Add(new TagCloudItem(tag.Tag, tagIndex, "<font size=\"" + fontSize + $"\"><a href=\"{GlobalConfiguration.BasePath}/Tag/Browse/" + NamespaceNavigation.CleanAndValidate(tag.Tag) + "\">" + tag.Tag + "</a></font>"));
|
||||
|
||||
if ((tagIndex % sizeStep) == 0)
|
||||
{
|
||||
fontSize--;
|
||||
}
|
||||
|
||||
tagIndex++;
|
||||
}
|
||||
|
||||
var cloudHtml = new StringBuilder();
|
||||
|
||||
tagList.Sort(TagCloudItem.CompareItem);
|
||||
|
||||
cloudHtml.Append("<table align=\"center\" border=\"0\" width=\"100%\"><tr><td><p align=\"justify\">");
|
||||
|
||||
foreach (TagCloudItem tag in tagList)
|
||||
{
|
||||
cloudHtml.Append(tag.HTML + " ");
|
||||
}
|
||||
|
||||
cloudHtml.Append("</p></td></tr></table>");
|
||||
|
||||
return cloudHtml.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
8
TightWiki.Engine.Implementation/WeightedSearchToken.cs
Normal file
8
TightWiki.Engine.Implementation/WeightedSearchToken.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace TightWiki.Engine.Implementation
|
||||
{
|
||||
public class WeightedSearchToken
|
||||
{
|
||||
public string Token { get; set; } = string.Empty;
|
||||
public double Weight { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user