using NTDLS.Helpers;
using System.Reflection;
using System.Text;
using TightWiki.Engine.Function;
using TightWiki.Engine.Implementation.Utility;
using TightWiki.Engine.Library;
using TightWiki.Engine.Library.Interfaces;
using TightWiki.Library;
using TightWiki.Models;
using TightWiki.Models.DataModels;
using TightWiki.Repository;
using static TightWiki.Engine.Function.FunctionPrototypeCollection;
using static TightWiki.Engine.Library.Constants;
namespace TightWiki.Engine.Implementation
{
///
/// Handled standard function calls.
///
public class StandardFunctionHandler : IStandardFunctionHandler
{
private static FunctionPrototypeCollection? _collection;
public FunctionPrototypeCollection Prototypes
{
get
{
if (_collection == null)
{
_collection = new FunctionPrototypeCollection(WikiFunctionType.Standard);
#region Prototypes.
_collection.Add("##Snippet: [name]");
_collection.Add("##Seq: {key}='Default'");
_collection.Add("##Set: [key] | [value]");
_collection.Add("##Get: [key]");
_collection.Add("##Color: [color] | [text]");
_collection.Add("##Tag: [pageTags]"); //This is left here for backwards compatibility, Tag does not change the output so it should be a processing instruction.
_collection.Add("##SearchList: [searchPhrase] | {styleName(List,Full)}='Full' | {pageSize}='5' | {pageSelector}='true' | {allowFuzzyMatching}='false' | {showNamespace}='false'");
_collection.Add("##TagList: [pageTags] | {Top}='1000' | {styleName(List,Full)}='Full' | {showNamespace}='false'");
_collection.Add("##NamespaceGlossary: [namespaces] | {Top}='1000' | {styleName(List,Full)}='Full' | {showNamespace}='false'");
_collection.Add("##NamespaceList: [namespaces] | {Top}='1000' | {styleName(List,Full)}='Full' | {showNamespace}='false'");
_collection.Add("##TagGlossary: [pageTags] | {Top}='1000' | {styleName(List,Full)}='Full' | {showNamespace}='false'");
_collection.Add("##RecentlyModified: {Top}='1000' | {styleName(List,Full)}='Full' | {showNamespace}='false'");
_collection.Add("##TextGlossary: [searchPhrase] | {Top}='1000' | {styleName(List,Full)}='Full' | {showNamespace}='false'");
_collection.Add("##Image: [name] | {scale}='100' | {altText}=''");
_collection.Add("##File: [name] | {linkText} | {showSize}='false'");
_collection.Add("##Related: {styleName(List,Flat,Full)}='Full' | {pageSize}='10' | {pageSelector}='true'");
_collection.Add("##Similar: {similarity}='80' | {styleName(List,Flat,Full)}='Full' | {pageSize}='10' | {pageSelector}='true'");
_collection.Add("##EditLink: {linkText}='edit'");
_collection.Add("##Inject: [pageName]");
_collection.Add("##Include: [pageName]");
_collection.Add("##BR: {Count}='1'");
_collection.Add("##HR: {Height}='1'");
_collection.Add("##Revisions:{styleName(Full,List)}='Full' | {pageSize}='5' | {pageSelector}='true' | {pageName}=''");
_collection.Add("##Attachments:{styleName(Full,List)}='Full' | {pageSize}='5' | {pageSelector}='true' | {pageName}=''");
_collection.Add("##Title:");
_collection.Add("##Navigation:");
_collection.Add("##Name:");
_collection.Add("##SiteName:");
_collection.Add("##Namespace:");
_collection.Add("##Created:");
_collection.Add("##LastModified:");
_collection.Add("##AppVersion:");
_collection.Add("##ProfileGlossary: {Top}='1000' | {pageSize}='100' | {searchToken}=''");
_collection.Add("##ProfileList: {Top}='1000' | {pageSize}='100' | {searchToken}=''");
//System functions (we don't advertize these, but they aren't unsafe):
_collection.Add("##SystemEmojiCategoryList:");
_collection.Add("##SystemEmojiList:");
#endregion
}
return _collection;
}
}
private static Page? GetPageFromPathInfo(string routeData)
{
routeData = NamespaceNavigation.CleanAndValidate(routeData);
var page = PageRepository.GetPageRevisionByNavigation(routeData);
return page;
}
private static void MergeUserVariables(ref ITightEngineState state, Dictionary items)
{
foreach (var item in items)
{
state.Variables[item.Key] = item.Value;
}
}
private static void MergeSnippets(ref ITightEngineState state, Dictionary items)
{
foreach (var item in items)
{
state.Snippets[item.Key] = item.Value;
}
}
///
/// Called to handle function calls when proper prototypes are matched.
///
/// Reference to the wiki state object
/// The parsed function call and all its parameters and their values.
/// This is not a scope function, this should always be null
public HandlerResult Handle(ITightEngineState state, FunctionCall function, string? scopeBody = null)
{
switch (function.Name.ToLower())
{
//------------------------------------------------------------------------------------------------------------------------------
//Creates a glossary all user profiles.
case "profileglossary":
{
if (!Models.GlobalConfiguration.EnablePublicProfiles)
{
return new HandlerResult("Public profiles are disabled.");
}
var html = new StringBuilder();
string refTag = state.GetNextQueryToken();
int pageNumber = int.Parse(state.QueryString[refTag].ToString().DefaultWhenNullOrEmpty("1"));
var pageSize = function.Parameters.Get("pageSize");
var searchToken = function.Parameters.Get("searchToken");
var topCount = function.Parameters.Get("top");
var profiles = UsersRepository.GetAllPublicProfilesPaged(pageNumber, pageSize, searchToken);
string glossaryName = "glossary_" + new Random().Next(0, 1000000).ToString();
var alphabet = profiles.Select(p => p.AccountName.Substring(0, 1).ToUpper()).Distinct();
if (profiles.Count() > 0)
{
html.Append("");
foreach (var alpha in alphabet)
{
html.Append("" + alpha + " ");
}
html.Append("");
html.Append("");
foreach (var alpha in alphabet)
{
html.Append("- " + alpha + "
");
html.Append("");
foreach (var profile in profiles.Where(p => p.AccountName.ToLower().StartsWith(alpha.ToLower())))
{
html.Append($"- {profile.AccountName}");
html.Append("
");
}
html.Append("
");
}
html.Append("
");
}
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
//Creates a list of all user profiles.
case "profilelist":
{
if (!Models.GlobalConfiguration.EnablePublicProfiles)
{
return new HandlerResult("Public profiles are disabled.");
}
var html = new StringBuilder();
string refTag = state.GetNextQueryToken();
int pageNumber = int.Parse(state.QueryString[refTag].ToString().DefaultWhenNullOrEmpty("1"));
var pageSize = function.Parameters.Get("pageSize");
var searchToken = function.Parameters.Get("searchToken");
var profiles = UsersRepository.GetAllPublicProfilesPaged(pageNumber, pageSize, searchToken);
if (profiles.Count() > 0)
{
html.Append("");
foreach (var profile in profiles)
{
html.Append($"- {profile.AccountName}");
html.Append("
");
}
html.Append("
");
}
if (profiles.Count > 0 && profiles.First().PaginationPageCount > 1)
{
html.Append(PageSelectorGenerator.Generate(refTag, state.QueryString, profiles.First().PaginationPageCount));
}
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
case "attachments":
{
string refTag = state.GetNextQueryToken();
int pageNumber = int.Parse(state.QueryString[refTag].ToString().DefaultWhenNullOrEmpty("1"));
var navigation = NamespaceNavigation.CleanAndValidate(function.Parameters.Get("pageName", state.Page.Navigation));
string styleName = function.Parameters.Get("styleName").ToLower();
var pageSize = function.Parameters.Get("pageSize");
var pageSelector = function.Parameters.Get("pageSelector");
var attachments = PageFileRepository.GetPageFilesInfoByPageNavigationAndPageRevisionPaged(navigation, pageNumber, pageSize, state.Revision);
var html = new StringBuilder();
if (attachments.Count() > 0)
{
html.Append("");
foreach (var file in attachments)
{
if (state.Revision != null)
{
html.Append($"- {file.Name}");
}
else
{
html.Append($"
- {file.Name} ");
}
if (styleName == "full")
{
html.Append($" - ({file.FriendlySize})");
}
html.Append("
");
}
html.Append("
");
if (pageSelector && attachments.Count > 0 && attachments.First().PaginationPageCount > 1)
{
html.Append(PageSelectorGenerator.Generate(refTag, state.QueryString, attachments.First().PaginationPageCount));
}
}
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
case "revisions":
{
if (state.Session == null)
{
throw new Exception($"Localization is not supported without SessionState.");
}
string refTag = state.GetNextQueryToken();
int pageNumber = int.Parse(state.QueryString[refTag].ToString().DefaultWhenNullOrEmpty("1"));
var navigation = NamespaceNavigation.CleanAndValidate(function.Parameters.Get("pageName", state.Page.Navigation));
string styleName = function.Parameters.Get("styleName").ToLower();
var pageSize = function.Parameters.Get("pageSize");
var pageSelector = function.Parameters.Get("pageSelector");
var revisions = PageRepository.GetPageRevisionsInfoByNavigationPaged(navigation, pageNumber, null, null, pageSize);
var html = new StringBuilder();
if (revisions.Count() > 0)
{
html.Append("");
foreach (var item in revisions)
{
html.Append($"- {item.Revision} by {item.ModifiedByUserName} on {state.Session.LocalizeDateTime(item.ModifiedDate)}");
if (styleName == "full")
{
var thisRev = PageRepository.GetPageRevisionByNavigation(state.Page.Navigation, item.Revision);
var prevRev = PageRepository.GetPageRevisionByNavigation(state.Page.Navigation, item.Revision - 1);
var summaryText = Differentiator.GetComparisonSummary(thisRev?.Body ?? string.Empty, prevRev?.Body ?? string.Empty);
if (summaryText.Length > 0)
{
html.Append(" - " + summaryText);
}
}
html.Append("
");
}
html.Append("
");
if (pageSelector && revisions.Count > 0 && revisions.First().PaginationPageCount > 1)
{
html.Append(PageSelectorGenerator.Generate(refTag, state.QueryString, revisions.First().PaginationPageCount));
}
}
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
case "seq": //##Seq({Key}) Inserts a sequence into the document.
{
var key = function.Parameters.Get("Key");
var sequences = state.GetStateValue("_sequences", new Dictionary());
if (sequences.ContainsKey(key) == false)
{
sequences.Add(key, 0);
}
sequences[key]++;
return new HandlerResult(sequences[key].ToString())
{
Instructions = [HandlerResultInstruction.OnlyReplaceFirstMatch]
};
}
//------------------------------------------------------------------------------------------------------------------------------
case "editlink": //(##EditLink(link text))
{
var linkText = function.Parameters.Get("linkText");
return new HandlerResult($"{linkText}");
}
//------------------------------------------------------------------------------------------------------------------------------
//injects an un-processed wiki body into the calling page.
case "inject": //(PageName)
{
var navigation = function.Parameters.Get("pageName");
var page = GetPageFromPathInfo(navigation);
if (page != null)
{
return new HandlerResult(page.Body)
{
Instructions = [HandlerResultInstruction.TruncateTrailingLine]
};
}
throw new Exception($"The include page was not found: [{navigation}]");
}
//------------------------------------------------------------------------------------------------------------------------------
//Includes a processed wiki body into the calling page.
case "include": //(PageName)
{
var navigation = function.Parameters.Get("pageName");
var page = GetPageFromPathInfo(navigation);
if (page != null)
{
var childState = state.TransformChild(page);
MergeUserVariables(ref state, childState.Variables);
MergeSnippets(ref state, childState.Snippets);
return new HandlerResult(childState.HtmlResult)
{
Instructions = [HandlerResultInstruction.TruncateTrailingLine]
};
}
throw new Exception($"The include page was not found: [{navigation}]");
}
//------------------------------------------------------------------------------------------------------------------------------
case "set":
{
var key = function.Parameters.Get("key");
var value = function.Parameters.Get("value");
if (state.Variables.ContainsKey(key))
{
state.Variables[key] = value;
}
else
{
state.Variables.Add(key, value);
}
return new HandlerResult(string.Empty)
{
Instructions = [HandlerResultInstruction.TruncateTrailingLine]
};
}
//------------------------------------------------------------------------------------------------------------------------------
case "get":
{
var key = function.Parameters.Get("key");
if (state.Variables.TryGetValue(key, out var variable))
{
return new HandlerResult(variable);
}
throw new Exception($"The wiki variable {key} is not defined. It should be set with ##Set() before calling Get().");
}
//------------------------------------------------------------------------------------------------------------------------------
case "color":
{
var color = function.Parameters.Get("color");
var text = function.Parameters.Get("text");
return new HandlerResult($"{text}");
}
//------------------------------------------------------------------------------------------------------------------------------
//Associates tags with a page. These are saved with the page and can also be displayed.
case "tag": //##tag(pipe|separated|list|of|tags)
{
var tags = function.Parameters.GetList("pageTags");
state.Tags.AddRange(tags);
state.Tags = state.Tags.Distinct().ToList();
return new HandlerResult(string.Empty)
{
Instructions = [HandlerResultInstruction.TruncateTrailingLine]
};
}
//------------------------------------------------------------------------------------------------------------------------------
//Displays an image that is attached to the page.
case "image": //##Image(Name, [optional:default=100]Scale, [optional:default=""]Alt-Text)
{
string imageName = function.Parameters.Get("name");
string alt = function.Parameters.Get("alttext", imageName);
int scale = function.Parameters.Get("scale");
bool explicitNamespace = imageName.Contains("::");
bool isPageForeignImage = false;
string navigation = state.Page.Navigation;
if (imageName.StartsWith("http", StringComparison.InvariantCultureIgnoreCase))
{
string image = $"
";
return new HandlerResult(image);
}
else if (imageName.Contains('/'))
{
//Allow loading attached images from other pages.
int slashIndex = imageName.IndexOf("/");
navigation = NamespaceNavigation.CleanAndValidate(imageName.Substring(0, slashIndex));
imageName = imageName.Substring(slashIndex + 1);
isPageForeignImage = true;
}
if (explicitNamespace == false && state.Page.Namespace != null)
{
if (PageFileRepository.GetPageFileAttachmentInfoByPageNavigationPageRevisionAndFileNavigation(navigation, NamespaceNavigation.CleanAndValidate(imageName), state.Revision) == null)
{
//If the image does not exist, and no namespace was specified, but the page has a namespace - then default to the pages namespace.
navigation = NamespaceNavigation.CleanAndValidate($"{state.Page.Namespace}::{imageName}");
}
}
if (state.Revision != null && isPageForeignImage == false)
{
//Check for isPageForeignImage because we don't version foreign page files.
string link = $"/Page/Image/{navigation}/{NamespaceNavigation.CleanAndValidate(imageName)}/{state.Revision}";
string image = $"
";
return new HandlerResult(image);
}
else
{
string link = $"/Page/Image/{navigation}/{NamespaceNavigation.CleanAndValidate(imageName)}";
string image = $"
";
return new HandlerResult(image);
}
}
//------------------------------------------------------------------------------------------------------------------------------
//Displays an file download link
case "file": //##file(Name | Alt-Text | [optional display file size] true/false)
{
string fileName = function.Parameters.Get("name");
bool explicitNamespace = fileName.Contains("::");
bool isPageForeignFile = false;
string navigation = state.Page.Navigation;
if (fileName.Contains('/'))
{
//Allow loading attached images from other pages.
int slashIndex = fileName.IndexOf("/");
navigation = NamespaceNavigation.CleanAndValidate(fileName.Substring(0, slashIndex));
fileName = fileName.Substring(slashIndex + 1);
isPageForeignFile = true;
}
if (explicitNamespace == false && state.Page.Namespace != null)
{
if (PageFileRepository.GetPageFileAttachmentInfoByPageNavigationPageRevisionAndFileNavigation(navigation, NamespaceNavigation.CleanAndValidate(fileName), state.Revision) == null)
{
//If the image does not exist, and no namespace was specified, but the page has a namespace - then default to the pages namespace.
navigation = NamespaceNavigation.CleanAndValidate($"{state.Page.Namespace}::{fileName}");
}
}
var attachment = PageFileRepository.GetPageFileAttachmentInfoByPageNavigationPageRevisionAndFileNavigation(navigation, NamespaceNavigation.CleanAndValidate(fileName), state.Revision);
if (attachment != null)
{
string alt = function.Parameters.Get("linkText", fileName);
if (function.Parameters.Get("showSize"))
{
alt += $" ({attachment.FriendlySize})";
}
if (state.Revision != null && isPageForeignFile == false)
{
//Check for isPageForeignImage because we don't version foreign page files.
string link = $"/Page/Binary/{navigation}/{NamespaceNavigation.CleanAndValidate(fileName)}/{state.Revision}";
string image = $"{alt}";
return new HandlerResult(image);
}
else
{
string link = $"/Page/Binary/{navigation}/{NamespaceNavigation.CleanAndValidate(fileName)}";
string image = $"{alt}";
return new HandlerResult(image);
}
}
throw new Exception($"File not found [{fileName}]");
}
//------------------------------------------------------------------------------------------------------------------------------
//Creates a list of pages that have been recently modified.
case "recentlymodified": //##RecentlyModified(TopCount)
{
string styleName = function.Parameters.Get("styleName").ToLower();
var takeCount = function.Parameters.Get("top");
var showNamespace = function.Parameters.Get("showNamespace");
var pages = PageRepository.GetTopRecentlyModifiedPagesInfo(takeCount)
.OrderByDescending(o => o.ModifiedDate).ThenBy(o => o.Title).ToList();
var html = new StringBuilder();
if (pages.Count() > 0)
{
html.Append("");
foreach (var page in pages)
{
if (showNamespace)
{
html.Append($"- {page.Name}");
}
else
{
html.Append($"
- {page.Title}");
}
if (styleName == "full")
{
if (page?.Description?.Length > 0)
{
html.Append(" - " + page.Description);
}
}
html.Append("
");
}
html.Append("
");
}
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
//Creates a glossary of pages in the specified namespace.
case "namespaceglossary":
{
string glossaryName = "glossary_" + new Random().Next(0, 1000000).ToString();
var namespaces = function.Parameters.GetList("namespaces");
string styleName = function.Parameters.Get("styleName").ToLower();
var topCount = function.Parameters.Get("top");
var pages = PageRepository.GetPageInfoByNamespaces(namespaces).Take(topCount).OrderBy(o => o.Name).ToList();
var html = new StringBuilder();
var alphabet = pages.Select(p => p.Title.Substring(0, 1).ToUpper()).Distinct();
var showNamespace = function.Parameters.Get("showNamespace");
if (pages.Count() > 0)
{
html.Append("");
foreach (var alpha in alphabet)
{
html.Append("" + alpha + " ");
}
html.Append("");
html.Append("");
foreach (var alpha in alphabet)
{
html.Append("- " + alpha + "
");
html.Append("");
foreach (var page in pages.Where(p => p.Title.ToLower().StartsWith(alpha.ToLower())))
{
if (showNamespace)
{
html.Append($"- {page.Name}");
}
else
{
html.Append($"
- {page.Title}");
}
if (styleName == "full")
{
if (page?.Description?.Length > 0)
{
html.Append(" - " + page.Description);
}
}
html.Append("
");
}
html.Append("
");
}
html.Append("
");
}
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
//Creates a list of pages by searching the page tags.
case "namespacelist":
{
string styleName = function.Parameters.Get("styleName").ToLower();
var topCount = function.Parameters.Get("top");
var namespaces = function.Parameters.GetList("namespaces");
var showNamespace = function.Parameters.Get("showNamespace");
var pages = PageRepository.GetPageInfoByNamespaces(namespaces).Take(topCount).OrderBy(o => o.Name).ToList();
var html = new StringBuilder();
if (pages.Count() > 0)
{
html.Append("");
foreach (var page in pages)
{
if (showNamespace)
{
html.Append($"- {page.Name}");
}
else
{
html.Append($"
- {page.Title}");
}
if (styleName == "full")
{
if (page?.Description?.Length > 0)
{
html.Append(" - " + page.Description);
}
}
html.Append("
");
}
html.Append("
");
}
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
//Creates a glossary of pages with the specified comma separated tags.
case "tagglossary":
{
string glossaryName = "glossary_" + new Random().Next(0, 1000000).ToString();
var tags = function.Parameters.GetList("pageTags");
string styleName = function.Parameters.Get("styleName").ToLower();
var topCount = function.Parameters.Get("top");
var pages = PageRepository.GetPageInfoByTags(tags).Take(topCount).OrderBy(o => o.Name).ToList();
var html = new StringBuilder();
var alphabet = pages.Select(p => p.Title.Substring(0, 1).ToUpper()).Distinct();
var showNamespace = function.Parameters.Get("showNamespace");
if (pages.Count() > 0)
{
html.Append("");
foreach (var alpha in alphabet)
{
html.Append("" + alpha + " ");
}
html.Append("");
html.Append("");
foreach (var alpha in alphabet)
{
html.Append("- " + alpha + "
");
html.Append("");
foreach (var page in pages.Where(p => p.Title.ToLower().StartsWith(alpha.ToLower())))
{
if (showNamespace)
{
html.Append($"- {page.Name}");
}
else
{
html.Append($"
- {page.Title}");
}
if (styleName == "full")
{
if (page?.Description?.Length > 0)
{
html.Append(" - " + page.Description);
}
}
html.Append("
");
}
html.Append("
");
}
html.Append("
");
}
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
//Creates a glossary by searching page's body text for the specified comma separated list of words.
case "textglossary":
{
string glossaryName = "glossary_" + new Random().Next(0, 1000000).ToString();
var searchTokens = function.Parameters.Get("searchPhrase").Split(" ", StringSplitOptions.RemoveEmptyEntries).ToList();
var topCount = function.Parameters.Get("top");
var showNamespace = function.Parameters.Get("showNamespace");
var pages = PageRepository.PageSearch(searchTokens).Take(topCount).OrderBy(o => o.Name).ToList();
var html = new StringBuilder();
var alphabet = pages.Select(p => p.Title.Substring(0, 1).ToUpper()).Distinct();
string styleName = function.Parameters.Get("styleName").ToLower();
if (pages.Count() > 0)
{
html.Append("");
foreach (var alpha in alphabet)
{
html.Append("" + alpha + " ");
}
html.Append("");
html.Append("");
foreach (var alpha in alphabet)
{
html.Append("- " + alpha + "
");
html.Append("");
foreach (var page in pages.Where(p => p.Title.ToLower().StartsWith(alpha.ToLower())))
{
if (showNamespace)
{
html.Append($"- {page.Name}");
}
else
{
html.Append($"
- {page.Title}");
}
if (styleName == "full")
{
if (page?.Description?.Length > 0)
{
html.Append(" - " + page.Description);
}
}
html.Append("
");
}
html.Append("
");
}
html.Append("
");
}
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
//Creates a list of pages by searching the page body for the specified text.
case "searchlist":
{
string styleName = function.Parameters.Get("styleName").ToLower();
string refTag = state.GetNextQueryToken();
int pageNumber = int.Parse(state.QueryString[refTag].ToString().DefaultWhenNullOrEmpty("1"));
var pageSize = function.Parameters.Get("pageSize");
var pageSelector = function.Parameters.Get("pageSelector");
var allowFuzzyMatching = function.Parameters.Get("allowFuzzyMatching");
var searchTokens = function.Parameters.Get("searchPhrase").Split(" ", StringSplitOptions.RemoveEmptyEntries).ToList();
var showNamespace = function.Parameters.Get("showNamespace");
var pages = PageRepository.PageSearchPaged(searchTokens, pageNumber, pageSize, allowFuzzyMatching);
var html = new StringBuilder();
if (pages.Count() > 0)
{
html.Append("");
foreach (var page in pages)
{
if (showNamespace)
{
html.Append($"- {page.Name}");
}
else
{
html.Append($"
- {page.Title}");
}
if (styleName == "full")
{
if (page?.Description?.Length > 0)
{
html.Append(" - " + page.Description);
}
}
html.Append("
");
}
html.Append("
");
}
if (pageSelector && (pageNumber > 1 || (pages.Count > 0 && pages.First().PaginationPageCount > 1)))
{
html.Append(PageSelectorGenerator.Generate(refTag, state.QueryString, pages.FirstOrDefault()?.PaginationPageCount ?? 1));
}
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
//Creates a list of pages by searching the page tags.
case "taglist":
{
string styleName = function.Parameters.Get("styleName").ToLower();
var topCount = function.Parameters.Get("top");
var tags = function.Parameters.GetList("pageTags");
var showNamespace = function.Parameters.Get("showNamespace");
var pages = PageRepository.GetPageInfoByTags(tags).Take(topCount).OrderBy(o => o.Name).ToList();
var html = new StringBuilder();
if (pages.Count() > 0)
{
html.Append("");
foreach (var page in pages)
{
if (showNamespace)
{
html.Append($"- {page.Name}");
}
else
{
html.Append($"
- {page.Title}");
}
if (styleName == "full")
{
if (page?.Description?.Length > 0)
{
html.Append(" - " + page.Description);
}
}
html.Append("
");
}
html.Append("
");
}
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
//Displays a list of other related pages based on tags.
case "similar": //##Similar()
{
string refTag = state.GetNextQueryToken();
var similarity = function.Parameters.Get("similarity");
int pageNumber = int.Parse(state.QueryString[refTag].ToString().DefaultWhenNullOrEmpty("1"));
var pageSize = function.Parameters.Get("pageSize");
var pageSelector = function.Parameters.Get("pageSelector");
string styleName = function.Parameters.Get("styleName").ToLower();
var html = new StringBuilder();
var pages = PageRepository.GetSimilarPagesPaged(state.Page.Id, similarity, pageNumber, pageSize);
if (styleName == "list")
{
html.Append("");
foreach (var page in pages)
{
html.Append($"- {page.Title}");
}
html.Append("
");
}
else if (styleName == "flat")
{
foreach (var page in pages)
{
if (html.Length > 0) html.Append(" | ");
html.Append($"{page.Title}");
}
}
else if (styleName == "full")
{
html.Append("");
foreach (var page in pages)
{
html.Append($"- {page.Title} - {page.Description}");
}
html.Append("
");
}
if (pageSelector && pages.Count > 0 && pages.First().PaginationPageCount > 1)
{
html.Append(PageSelectorGenerator.Generate(refTag, state.QueryString, pages.First().PaginationPageCount));
}
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
//Displays a list of other related pages based incoming links.
case "related": //##related
{
string refTag = state.GetNextQueryToken();
int pageNumber = int.Parse(state.QueryString[refTag].ToString().DefaultWhenNullOrEmpty("1"));
var pageSize = function.Parameters.Get("pageSize");
var pageSelector = function.Parameters.Get("pageSelector");
string styleName = function.Parameters.Get("styleName").ToLower();
var html = new StringBuilder();
var pages = PageRepository.GetRelatedPagesPaged(state.Page.Id, pageNumber, pageSize);
if (styleName == "list")
{
html.Append("");
foreach (var page in pages)
{
html.Append($"- {page.Title}");
}
html.Append("
");
}
else if (styleName == "flat")
{
foreach (var page in pages)
{
if (html.Length > 0) html.Append(" | ");
html.Append($"{page.Title}");
}
}
else if (styleName == "full")
{
html.Append("");
foreach (var page in pages)
{
html.Append($"- {page.Title} - {page.Description}");
}
html.Append("
");
}
if (pageSelector && pages.Count > 0 && pages.First().PaginationPageCount > 1)
{
html.Append(PageSelectorGenerator.Generate(refTag, state.QueryString, pages.First().PaginationPageCount));
}
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
//Displays the date and time that the current page was last modified.
case "lastmodified":
{
if (state.Session == null)
{
throw new Exception($"Localization is not supported without SessionState.");
}
if (state.Page.ModifiedDate != DateTime.MinValue)
{
var localized = state.Session.LocalizeDateTime(state.Page.ModifiedDate);
return new HandlerResult($"{localized.ToShortDateString()} {localized.ToShortTimeString()}");
}
return new HandlerResult(string.Empty);
}
//------------------------------------------------------------------------------------------------------------------------------
//Displays the date and time that the current page was created.
case "created":
{
if (state.Session == null)
{
throw new Exception($"Localization is not supported without SessionState.");
}
if (state.Page.CreatedDate != DateTime.MinValue)
{
var localized = state.Session.LocalizeDateTime(state.Page.CreatedDate);
return new HandlerResult($"{localized.ToShortDateString()} {localized.ToShortTimeString()}");
}
return new HandlerResult(string.Empty);
}
//------------------------------------------------------------------------------------------------------------------------------
//Displays the version of the wiki.
case "appversion":
{
var version = string.Join('.', (Assembly.GetExecutingAssembly()
.GetName().Version?.ToString() ?? "0.0.0.0").Split('.').Take(3)); //Major.Minor.Patch
return new HandlerResult(version);
}
//------------------------------------------------------------------------------------------------------------------------------
//Displays the title of the current page.
case "name":
{
return new HandlerResult(state.Page.Title);
}
//------------------------------------------------------------------------------------------------------------------------------
//Displays the title of the site.
case "sitename":
{
return new HandlerResult(TightWiki.Models.GlobalConfiguration.Name);
}
//------------------------------------------------------------------------------------------------------------------------------
//Displays the title of the current page in title form.
case "title":
{
return new HandlerResult($"{state.Page.Title}
");
}
//------------------------------------------------------------------------------------------------------------------------------
//Displays the namespace of the current page.
case "namespace":
{
return new HandlerResult(state.Page.Namespace ?? string.Empty);
}
//------------------------------------------------------------------------------------------------------------------------------
//Displays the namespace of the current page.
case "snippet":
{
string name = function.Parameters.Get("name");
if (state.Snippets.ContainsKey(name))
{
return new HandlerResult(state.Snippets[name]);
}
else
{
return new HandlerResult(string.Empty);
}
}
//------------------------------------------------------------------------------------------------------------------------------
//Inserts empty lines into the page.
case "br":
case "nl":
case "newline": //##NewLine([optional:default=1]count)
{
var lineBreaks = new StringBuilder();
int count = function.Parameters.Get("Count");
for (int i = 0; i < count; i++)
{
lineBreaks.Append("
");
}
return new HandlerResult(lineBreaks.ToString());
}
//Inserts a horizontal rule
case "hr":
{
int size = function.Parameters.Get("height");
return new HandlerResult($"
");
}
//------------------------------------------------------------------------------------------------------------------------------
//Displays the navigation text for the current page.
case "navigation":
{
return new HandlerResult(state.Page.Navigation);
}
//------------------------------------------------------------------------------------------------------------------------------
case "systememojilist":
{
StringBuilder html = new();
html.Append($"");
html.Append($"");
html.Append($"");
html.Append($"| Name | ");
html.Append($"Image | ");
html.Append($"Shortcut | ");
html.Append($"
");
html.Append($"");
string category = state.QueryString["Category"].ToString();
html.Append($"");
if (string.IsNullOrWhiteSpace(category) == false)
{
var emojis = EmojiRepository.GetEmojisByCategory(category);
foreach (var emoji in emojis)
{
html.Append($"");
html.Append($"| {emoji.Name} | ");
//html.Append($" | ");
html.Append($"}\") | ");
html.Append($"{emoji.Shortcut} | ");
html.Append($"
");
}
}
html.Append($"");
html.Append($"
");
return new HandlerResult(html.ToString())
{
Instructions = [HandlerResultInstruction.DisallowNestedProcessing]
};
}
//------------------------------------------------------------------------------------------------------------------------------
case "systememojicategorylist":
{
var categories = EmojiRepository.GetEmojiCategoriesGrouped();
StringBuilder html = new();
html.Append($"");
int rowNumber = 0;
html.Append($"");
html.Append($"");
html.Append($"| Name | ");
html.Append($"Count of Emojis | ");
html.Append($"
");
html.Append($"");
foreach (var category in categories)
{
if (rowNumber == 1)
{
html.Append($"");
}
html.Append($"");
html.Append($"| {category.Category} | ");
html.Append($"{category.EmojiCount:N0} | ");
html.Append($"
");
rowNumber++;
}
html.Append($"");
html.Append($"
");
return new HandlerResult(html.ToString())
{
Instructions = [HandlerResultInstruction.DisallowNestedProcessing]
};
}
}
return new HandlerResult() { Instructions = [HandlerResultInstruction.Skip] };
}
}
}