This commit is contained in:
Zel
2025-02-23 18:47:21 +08:00
parent eaaffeeccb
commit e46a7ca31c
104 changed files with 2630 additions and 2516 deletions

View File

@@ -47,7 +47,8 @@ namespace DummyPageGenerator
{ {
for (int i = 0; i < 1124 - _users.Count; i++) for (int i = 0; i < 1124 - _users.Count; i++)
{ {
string emailAddress = WordsRepository.GetRandomWords(1).First() + "@" + WordsRepository.GetRandomWords(1).First() + ".com"; string emailAddress = WordsRepository.GetRandomWords(1).First() + "@" +
WordsRepository.GetRandomWords(1).First() + ".com";
CreateUserAndProfile(emailAddress); CreateUserAndProfile(emailAddress);
} }
@@ -68,7 +69,7 @@ namespace DummyPageGenerator
} }
/// <summary> /// <summary>
/// Creates a user and the associated profile with claims and such. ///
/// </summary> /// </summary>
/// <param name="emailAddress"></param> /// <param name="emailAddress"></param>
/// <exception cref="Exception"></exception> /// <exception cref="Exception"></exception>
@@ -80,7 +81,8 @@ namespace DummyPageGenerator
Email = emailAddress Email = emailAddress
}; };
var result = _userManager.CreateAsync(user, WordsRepository.GetRandomWords(1).First() + Guid.NewGuid().ToString()).Result; var result = _userManager
.CreateAsync(user, WordsRepository.GetRandomWords(1).First() + Guid.NewGuid().ToString()).Result;
if (!result.Succeeded) if (!result.Succeeded)
{ {
throw new Exception(string.Join("\r\n", result.Errors.Select(o => o.Description))); throw new Exception(string.Join("\r\n", result.Errors.Select(o => o.Description)));
@@ -103,7 +105,7 @@ namespace DummyPageGenerator
} }
/// <summary> /// <summary>
/// Creates a paragraph/sentence structure. ///
/// </summary> /// </summary>
/// <param name="words"></param> /// <param name="words"></param>
/// <returns></returns> /// <returns></returns>
@@ -118,7 +120,7 @@ namespace DummyPageGenerator
} }
/// <summary> /// <summary>
/// Creates a paragraph/sentence structure with links and wiki markup. ///
/// </summary> /// </summary>
/// <param name="wordCount"></param> /// <param name="wordCount"></param>
/// <returns></returns> /// <returns></returns>
@@ -135,18 +137,19 @@ namespace DummyPageGenerator
switch (_random.Next(0, 7)) switch (_random.Next(0, 7))
{ {
case 2: //Dead link. case 2:
paragraph = paragraph.Replace(token, $"[[{token}]]"); paragraph = paragraph.Replace(token, $"[[{token}]]");
break; break;
case 4: //Wiki markup. case 4:
paragraph = paragraph.Replace(token, AddWikiMarkup(token)); paragraph = paragraph.Replace(token, AddWikiMarkup(token));
break; break;
case 6: //Good link. case 6:
var recentPage = GetRandomRecentPageName(); var recentPage = GetRandomRecentPageName();
if (recentPage != null) if (recentPage != null)
{ {
paragraph = paragraph.Replace(token, $"[[{recentPage}]]"); paragraph = paragraph.Replace(token, $"[[{recentPage}]]");
} }
break; break;
} }
} }
@@ -163,7 +166,7 @@ namespace DummyPageGenerator
return null; return null;
} }
if (_recentPageNames.Count > 200) //Shuffle and limit the recent page names. if (_recentPageNames.Count > 200)
{ {
_recentPageNames = ShuffleList(_recentPageNames).Take(100).ToList(); _recentPageNames = ShuffleList(_recentPageNames).Take(100).ToList();
} }
@@ -176,7 +179,7 @@ namespace DummyPageGenerator
{ {
lock (_pagePool) lock (_pagePool)
{ {
if (_recentPageNames.Count > 200) //Shuffle and limit the recent page names. if (_recentPageNames.Count > 200)
{ {
_recentPageNames = ShuffleList(_recentPageNames).Take(100).ToList(); _recentPageNames = ShuffleList(_recentPageNames).Take(100).ToList();
} }
@@ -186,6 +189,7 @@ namespace DummyPageGenerator
{ {
pageNames.Add(_recentPageNames[_random.Next(0, _recentPageNames.Count)]); pageNames.Add(_recentPageNames[_random.Next(0, _recentPageNames.Count)]);
} }
return pageNames; return pageNames;
} }
} }
@@ -199,8 +203,9 @@ namespace DummyPageGenerator
} }
/// <summary> /// <summary>
/// Creates a random page on the wiki. ///
/// </summary> /// </summary>
/// <param name="engine"></param>
/// <param name="userId"></param> /// <param name="userId"></param>
public void GeneratePage(IZelEngine engine, Guid userId) public void GeneratePage(IZelEngine engine, Guid userId)
{ {
@@ -216,7 +221,8 @@ namespace DummyPageGenerator
var body = new StringBuilder(); var body = new StringBuilder();
body.AppendLine($"##title ##Tag(" + string.Join(' ', ShuffleList(_tags).Take(_random.Next(1, 4))) + ")"); body.AppendLine($"##title ##Tag(" + string.Join(' ', ShuffleList(_tags).Take(_random.Next(1, 4))) +
")");
body.AppendLine($"##toc"); body.AppendLine($"##toc");
body.AppendLine($"==Overview"); body.AppendLine($"==Overview");
@@ -232,7 +238,6 @@ namespace DummyPageGenerator
if (_random.Next(100) >= 95) if (_random.Next(100) >= 95)
{ {
//Add dead links (missing pages).
textWithLinks.AddRange(WordsRepository.GetRandomWords(_random.Next(1, 2)).Select(o => $"[[{o}]]")); textWithLinks.AddRange(WordsRepository.GetRandomWords(_random.Next(1, 2)).Select(o => $"[[{o}]]"));
} }
@@ -258,7 +263,8 @@ namespace DummyPageGenerator
if (_random.Next(100) >= 70) if (_random.Next(100) >= 70)
{ {
var fileName = _fileNames[_random.Next(_fileNames.Count)] + ".txt"; ; var fileName = _fileNames[_random.Next(_fileNames.Count)] + ".txt";
;
var fileData = Encoding.UTF8.GetBytes(page.Body); var fileData = Encoding.UTF8.GetBytes(page.Body);
AttachFile(newPageId, userId, fileName, fileData); AttachFile(newPageId, userId, fileName, fileData);
} }
@@ -288,7 +294,7 @@ namespace DummyPageGenerator
} }
/// <summary> /// <summary>
/// Modifies a random page on the wiki. ///
/// </summary> /// </summary>
/// <param name="userId"></param> /// <param name="userId"></param>
public void ModifyRandomPages(IZelEngine engine, Guid userId) public void ModifyRandomPages(IZelEngine engine, Guid userId)
@@ -328,12 +334,13 @@ namespace DummyPageGenerator
} }
/// <summary> /// <summary>
/// Attaches a file to a wiki page. ///
/// </summary> /// </summary>
/// <param name="pageId"></param> /// <param name="pageId"></param>
/// <param name="userId"></param> /// <param name="userId"></param>
/// <param name="fileName"></param> /// <param name="fileName"></param>
/// <param name="fileData"></param> /// <param name="fileData"></param>
/// <exception cref="Exception"></exception>
private void AttachFile(int pageId, Guid userId, string fileName, byte[] fileData) private void AttachFile(int pageId, Guid userId, string fileName, byte[] fileData)
{ {
if (fileData.Length > GlobalConfiguration.MaxAttachmentFileSize) if (fileData.Length > GlobalConfiguration.MaxAttachmentFileSize)
@@ -372,6 +379,7 @@ namespace DummyPageGenerator
newList[k] = newList[n]; newList[k] = newList[n];
newList[n] = value; newList[n] = value;
} }
return newList; return newList;
} }

View File

@@ -4,6 +4,9 @@ using System.Runtime.Caching;
namespace ZelWiki.Caching namespace ZelWiki.Caching
{ {
/// <summary>
/// 缓存
/// </summary>
public class WikiCache public class WikiCache
{ {
public enum Category public enum Category
@@ -24,7 +27,7 @@ namespace ZelWiki.Caching
public static int CacheItemCount => MemCache.Count(); public static int CacheItemCount => MemCache.Count();
public static double CacheMemoryLimit => MemCache.CacheMemoryLimit; public static double CacheMemoryLimit => MemCache.CacheMemoryLimit;
public static MemoryCache MemCache => _memCache ?? throw new Exception("Cache has not been initialized."); public static MemoryCache MemCache => _memCache ?? throw new Exception("缓存尚未初始化");
public static void Initialize(int cacheMemoryLimitMB, int defaultCacheSeconds) public static void Initialize(int cacheMemoryLimitMB, int defaultCacheSeconds)
{ {
@@ -40,7 +43,7 @@ namespace ZelWiki.Caching
} }
/// <summary> /// <summary>
/// Gets an item from the cache. /// 获取缓存
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <param name="cacheKey"></param> /// <param name="cacheKey"></param>
@@ -60,9 +63,8 @@ namespace ZelWiki.Caching
} }
/// <summary> /// <summary>
/// Determines if the cache contains a given key. /// 确定缓存是否包含给定的key
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="cacheKey"></param> /// <param name="cacheKey"></param>
/// <returns></returns> /// <returns></returns>
public static bool Contains(IWikiCacheKey cacheKey) public static bool Contains(IWikiCacheKey cacheKey)
@@ -79,7 +81,7 @@ namespace ZelWiki.Caching
} }
/// <summary> /// <summary>
/// Gets an item from the cache. /// 获取缓存
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <param name="cacheKey"></param> /// <param name="cacheKey"></param>
@@ -98,7 +100,7 @@ namespace ZelWiki.Caching
} }
/// <summary> /// <summary>
/// Adds an item to the cache. If the item is already in the cache, this will reset its expiration. ///添加缓存
/// </summary> /// </summary>
/// <param name="cacheKey"></param> /// <param name="cacheKey"></param>
/// <param name="value"></param> /// <param name="value"></param>
@@ -127,7 +129,7 @@ namespace ZelWiki.Caching
} }
/// <summary> /// <summary>
/// Removes all entries from the cache. /// 清理
/// </summary> /// </summary>
public static void Clear() public static void Clear()
{ {
@@ -144,9 +146,9 @@ namespace ZelWiki.Caching
} }
/// <summary> /// <summary>
/// Removes cache entries that begin with the given cache key. /// 删除某个
/// </summary> /// </summary>
/// <param name="category"></param> /// <param name="cacheKey"></param>
public static void ClearCategory(WikiCacheKey cacheKey) public static void ClearCategory(WikiCacheKey cacheKey)
{ {
var keys = new List<string>(); var keys = new List<string>();
@@ -163,7 +165,7 @@ namespace ZelWiki.Caching
} }
/// <summary> /// <summary>
/// Removes cache entries in a given category. /// 删除给定类别中的缓存条目
/// </summary> /// </summary>
/// <param name="category"></param> /// <param name="category"></param>
public static void ClearCategory(Category category) public static void ClearCategory(Category category)

View File

@@ -3,17 +3,17 @@
namespace ZelWiki.Caching namespace ZelWiki.Caching
{ {
/// <summary> /// <summary>
/// Contains a verbatim cache key. /// 包含逐字缓存键
/// </summary> /// </summary>
/// <param name="key"></param> /// <param name="key"></param>
public class WikiCacheKey(string key) : IWikiCacheKey public class WikiCacheKey(string key) : IWikiCacheKey
{ {
public string Key { get; set; } = key; public string Key { get; set; } = key;
public static WikiCacheKey Build(WikiCache.Category category, object?[] segments) public static WikiCacheKey Build(Category category, object?[] segments)
=> new($"[{category}]:[{string.Join("]:[", segments)}]"); => new($"[{category}]:[{string.Join("]:[", segments)}]");
public static WikiCacheKey Build(WikiCache.Category category) public static WikiCacheKey Build(Category category)
=> new($"[{category}]"); => new($"[{category}]");
} }
} }

View File

@@ -3,22 +3,18 @@ using static ZelWiki.Caching.WikiCache;
namespace ZelWiki.Caching namespace ZelWiki.Caching
{ {
/// <summary>
/// Contains a verbatim cache key which also includes the calling function name.
/// </summary>
/// <param name="key"></param>
public class WikiCacheKeyFunction(string key) : IWikiCacheKey public class WikiCacheKeyFunction(string key) : IWikiCacheKey
{ {
public string Key { get; set; } = key; public string Key { get; set; } = key;
/// <summary> /// <summary>
/// Builds a cache key which includes the calling function name. /// 生成一个包含调用函数名称的缓存键。
/// </summary> /// </summary>
public static WikiCacheKeyFunction Build(WikiCache.Category category, object?[] segments, [CallerMemberName] string callingFunction = "") public static WikiCacheKeyFunction Build(WikiCache.Category category, object?[] segments, [CallerMemberName] string callingFunction = "")
=> new($"[{category}]:[{string.Join("]:[", segments)}]:[{callingFunction}]"); => new($"[{category}]:[{string.Join("]:[", segments)}]:[{callingFunction}]");
/// <summary> /// <summary>
/// Builds a cache key which includes the calling function name. /// 生成一个包含调用函数名称的缓存键。
/// </summary> /// </summary>
public static WikiCacheKeyFunction Build(WikiCache.Category category, [CallerMemberName] string callingFunction = "") public static WikiCacheKeyFunction Build(WikiCache.Category category, [CallerMemberName] string callingFunction = "")
=> new($"[{category}]:[{callingFunction}]"); => new($"[{category}]:[{callingFunction}]");

View File

@@ -11,6 +11,10 @@ namespace ZelWiki.Email
{ {
private readonly ILogger<WikiEmailSender> _logger; private readonly ILogger<WikiEmailSender> _logger;
/// <summary>
///
/// </summary>
/// <param name="logger"></param>
public WikiEmailSender(ILogger<WikiEmailSender> logger) public WikiEmailSender(ILogger<WikiEmailSender> logger)
{ {
_logger = logger; _logger = logger;

View File

@@ -1,17 +1,19 @@
namespace ZelWiki.Engine.Function namespace ZelWiki.Engine.Function
{ {
/// <summary> /// <summary>
/// Contains information about an actual function call, its supplied parameters, and is matched with a defined function. /// 包含有关实际函数调用及其提供的参数的信息,并与定义的函数相匹配。
/// </summary> /// </summary>
public class FunctionCall public class FunctionCall
{ {
/// <summary> /// <summary>
/// The name of the function being called. ///
/// </summary> /// </summary>
public string Name { get; private set; } public string Name { get; private set; }
public FunctionPrototype Prototype { get; set; } public FunctionPrototype Prototype { get; set; }
/// <summary> /// <summary>
/// The arguments supplied by the caller. /// T
/// </summary> /// </summary>
public FunctionParameters Parameters { get; private set; } public FunctionParameters Parameters { get; private set; }
@@ -25,7 +27,7 @@
{ {
if (arg.StartsWith(':') && arg.Contains('=')) if (arg.StartsWith(':') && arg.Contains('='))
{ {
var parsed = arg.Substring(1); //Skip the colon. var parsed = arg.Substring(1);
int index = parsed.IndexOf('='); int index = parsed.IndexOf('=');
var name = parsed.Substring(0, index).Trim().ToLower(); var name = parsed.Substring(0, index).Trim().ToLower();
var value = parsed.Substring(index + 1).Trim(); var value = parsed.Substring(index + 1).Trim();
@@ -42,9 +44,9 @@
} }
/// <summary> /// <summary>
/// Checks the passed value against the function prototype to ensure that the variable is the correct type, value, etc. /// 对照函数原型检查传递的值,以确保变量的类型,值等正确
/// </summary> /// </summary>
/// <param name="segment"></param> /// <param name="param"></param>
/// <param name="value"></param> /// <param name="value"></param>
/// <exception cref="Exception"></exception> /// <exception cref="Exception"></exception>
private void EnforcePrototypeParamValue(PrototypeParameter param, string value) private void EnforcePrototypeParamValue(PrototypeParameter param, string value)
@@ -53,21 +55,25 @@
{ {
if (bool.TryParse(value, out bool _) == false) if (bool.TryParse(value, out bool _) == false)
{ {
throw new Exception($"Function [{Name}], the value [{value}] passed to parameter [{param.Name}] could not be converted to boolean."); throw new Exception(
$"函数 [{Name}] 传递给 [{param.Name}] 的值 [{value}] 无法转化成布尔");
} }
} }
if (param.Type == "integer") if (param.Type == "integer")
{ {
if (int.TryParse(value, out int _) == false) if (int.TryParse(value, out int _) == false)
{ {
throw new Exception($"Function [{Name}], the value [{value}] passed to parameter [{param.Name}] could not be converted to integer."); throw new Exception(
$"函数 [{Name}] 传递给 [{param.Name}] 的值 [{value}] 无法转化成整数.");
} }
} }
else if (param.Type == "float") else if (param.Type == "float")
{ {
if (double.TryParse(value, out double _) == false) if (double.TryParse(value, out double _) == false)
{ {
throw new Exception($"Function [{Name}], the value [{value}] passed to parameter [{param.Name}] could not be converted to float."); throw new Exception(
$"函数 [{Name}] 传递给 [{param.Name}] 的值 [{value}] 无法转化成小数.");
} }
} }
@@ -75,24 +81,22 @@
{ {
if (param.AllowedValues.Contains(value.ToLower()) == false) if (param.AllowedValues.Contains(value.ToLower()) == false)
{ {
throw new Exception($"Function [{Name}], the value [{value}] passed to parameter [{param.Name}] is not allowed. Allowed values are [{string.Join(",", param.AllowedValues)}]."); throw new Exception(
$"函数 [{Name}] 传递给 [{param.Name}] 的值 [{value}] 为非法数据. 合法值为 [{string.Join(",", param.AllowedValues)}].");
} }
} }
} }
/// <summary> /// <summary>
/// Rolls through the supplied arguments and applies them to the prototype. Also identifies which supplied arguments are associated with each ///
/// prototype argument and adds the ordinal based arguments to the name based collection. Ensures that each argument conforms with the prototype.
/// </summary> /// </summary>
/// <exception cref="Exception"></exception> /// <exception cref="Exception"></exception>
private void ApplyPrototype() private void ApplyPrototype()
{ {
int index = 0; var index = 0;
//Keep a list of the arguments as they are associated with the prototype so that we can later reference them by name.
var namedToAddLater = new List<NamedParameter>(); var namedToAddLater = new List<NamedParameter>();
//Handle non-infinite ordinal based required parameters:
for (; index < Prototype.Parameters.Count; index++) for (; index < Prototype.Parameters.Count; index++)
{ {
var param = Prototype.Parameters[index]; var param = Prototype.Parameters[index];
@@ -101,15 +105,15 @@
{ {
break; break;
} }
if (param.IsInfinite == true)
if (param.IsInfinite)
{ {
break; break;
} }
if (Parameters.Ordinals.Count > index) if (Parameters.Ordinals.Count > index)
{ {
//Good, we have a value. var value = Parameters.Ordinals[index].Value;
string value = Parameters.Ordinals[index].Value;
Parameters.Ordinals[index].AssociateWithPrototypeParam(param.Name); Parameters.Ordinals[index].AssociateWithPrototypeParam(param.Name);
EnforcePrototypeParamValue(param, value.ToLower()); EnforcePrototypeParamValue(param, value.ToLower());
@@ -123,33 +127,30 @@
bool hasEncounteredOptionalParameter = false; bool hasEncounteredOptionalParameter = false;
//Handle remaining optional parameters:
for (; index < Prototype.Parameters.Count; index++) for (; index < Prototype.Parameters.Count; index++)
{ {
var param = Prototype.Parameters[index]; var param = Prototype.Parameters[index];
if (param.IsInfinite == true) if (param.IsInfinite)
{ {
if (param.IsRequired == true) if (param.IsRequired)
{ {
//Make sure we have at least one of these required infinite parameters passed.
if (Parameters.Ordinals.Count > index) if (Parameters.Ordinals.Count > index)
{ {
//Good, we have a value. var value = Parameters.Ordinals[index].Value;
string value = Parameters.Ordinals[index].Value;
Parameters.Ordinals[index].AssociateWithPrototypeParam(param.Name); Parameters.Ordinals[index].AssociateWithPrototypeParam(param.Name);
EnforcePrototypeParamValue(param, value.ToLower()); EnforcePrototypeParamValue(param, value.ToLower());
} }
else else
{ {
throw new Exception($"Function [{Name}], the required infinite parameter [{param.Name}] was not passed."); throw new Exception(
$"函数 [{Name}], 参数 [{param.Name}] 未通过.");
} }
} }
//Now that we have encountered an infinite parameter, it will swallow up all other ordinal based arguments. Might as well check the types and exit the loop.
for (; index < Parameters.Ordinals.Count; index++) for (; index < Parameters.Ordinals.Count; index++)
{ {
string value = Parameters.Ordinals[index].Value; var value = Parameters.Ordinals[index].Value;
Parameters.Ordinals[index].AssociateWithPrototypeParam(param.Name); Parameters.Ordinals[index].AssociateWithPrototypeParam(param.Name);
EnforcePrototypeParamValue(param, value.ToLower()); EnforcePrototypeParamValue(param, value.ToLower());
namedToAddLater.Add(new NamedParameter(param.Name, value)); namedToAddLater.Add(new NamedParameter(param.Name, value));
@@ -163,18 +164,20 @@
hasEncounteredOptionalParameter = true; hasEncounteredOptionalParameter = true;
} }
if (param.IsRequired == true && hasEncounteredOptionalParameter) if (param.IsRequired && hasEncounteredOptionalParameter)
{ {
throw new Exception($"Function [{Name}], the required parameter [{param.Name}] was found after other optional parameters."); throw new Exception(
$"函数 [{Name}], 所必参数 [{param.Name}] 在其他可选参数之后找到.");
} }
else if (param.IsInfinite == true) else if (param.IsInfinite)
{ {
throw new Exception($"Function [{Name}], encountered an unexpected number of infinite parameters in prototype for [{param.Name}]."); throw new Exception(
$"函数 [{Name}], 参数溢出 [{param.Name}].");
} }
if (Parameters.Ordinals.Count > index) if (Parameters.Ordinals.Count > index)
{ {
string value = Parameters.Ordinals[index].Value; var value = Parameters.Ordinals[index].Value;
Parameters.Ordinals[index].AssociateWithPrototypeParam(param.Name); Parameters.Ordinals[index].AssociateWithPrototypeParam(param.Name);
EnforcePrototypeParamValue(param, value.ToLower()); EnforcePrototypeParamValue(param, value.ToLower());
namedToAddLater.Add(new NamedParameter(param.Name, value)); namedToAddLater.Add(new NamedParameter(param.Name, value));
@@ -183,8 +186,10 @@
foreach (var named in Parameters.Named) foreach (var named in Parameters.Named)
{ {
var param = Prototype.Parameters.Where(o => o.Name.Equals(named.Name, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault() var param = Prototype.Parameters
?? throw new Exception($"Function [{Name}], the named parameter [{named.Name}] is not defined in the function prototype."); .FirstOrDefault(o => o.Name.Equals(named.Name, StringComparison.InvariantCultureIgnoreCase))
?? throw new Exception(
$"函数 [{Name}], 命名参数 [{named.Name}] 未在函数原型中定义.");
EnforcePrototypeParamValue(param, named.Value); EnforcePrototypeParamValue(param, named.Value);
} }
@@ -194,16 +199,19 @@
var unmatchedParams = Parameters.Ordinals.Where(o => o.IsMatched == false).ToList(); var unmatchedParams = Parameters.Ordinals.Where(o => o.IsMatched == false).ToList();
if (unmatchedParams.Count != 0) if (unmatchedParams.Count != 0)
{ {
throw new Exception($"Function [{Name}], unmatched parameter value [{unmatchedParams.First().Value}]."); throw new Exception($"函数 [{Name}], 不匹配的参数值 [{unmatchedParams.First().Value}].");
} }
var nonInfiniteParams = Prototype.Parameters.Where(o => o.IsInfinite == false).Select(o => o.Name.ToLower()); var nonInfiniteParams =
var groups = Parameters.Named.Where(o => nonInfiniteParams.Contains(o.Name.ToLower())).GroupBy(o => o.Name.ToLower()).Where(o => o.Count() > 1); Prototype.Parameters.Where(o => o.IsInfinite == false).Select(o => o.Name.ToLower());
var groups = Parameters.Named.Where(o => nonInfiniteParams.Contains(o.Name.ToLower()))
.GroupBy(o => o.Name.ToLower()).Where(o => o.Count() > 1);
if (groups.Any()) if (groups.Any())
{ {
var group = groups.First(); var group = groups.First();
throw new Exception($"Function [{Name}], non-infinite parameter specified more than once: [{group.Key}]."); throw new Exception(
$"函数 [{Name}], 多次指定参数: [{group.Key}].");
} }
} }
} }

View File

@@ -4,38 +4,42 @@ namespace ZelWiki.Engine.Function
{ {
public class FunctionParameters public class FunctionParameters
{ {
/// <summary>
/// Variables set by ordinal.
/// </summary>
public List<OrdinalParameter> Ordinals { get; set; } = new();
/// <summary>
/// Variables set by name.
/// </summary>
public List<NamedParameter> Named { get; private set; } = new();
private readonly FunctionCall _owner; private readonly FunctionCall _owner;
public FunctionParameters(FunctionCall owner) public FunctionParameters(FunctionCall owner)
{ {
_owner = owner; _owner = owner;
} }
/// <summary>
///
/// </summary>
public List<OrdinalParameter> Ordinals { get; set; } = new();
/// <summary>
///
/// </summary>
public List<NamedParameter> Named { get; private set; } = new();
public T Get<T>(string name) public T Get<T>(string name)
{ {
try try
{ {
var value = Named.Where(o => o.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault()?.Value; var value = Named
.FirstOrDefault(o => o.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))?.Value;
if (value == null) if (value == null)
{ {
var prototype = _owner.Prototype.Parameters.Where(o => o.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)).First(); var prototype = _owner.Prototype.Parameters.First(o => o.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
return Converters.ConvertTo<T>(prototype.DefaultValue) ?? throw new Exception("Value cannot be null"); return Converters.ConvertTo<T>(prototype.DefaultValue) ??
throw new Exception("值不能为空");
} }
return Converters.ConvertTo<T>(value) ?? throw new Exception("Value cannot be null"); return Converters.ConvertTo<T>(value) ?? throw new Exception("值不能为空");
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new Exception($"Function [{_owner.Name}], {ex.Message}"); throw new Exception($"函数 [{_owner.Name}], {ex.Message}");
} }
} }
@@ -43,17 +47,18 @@ namespace ZelWiki.Engine.Function
{ {
try try
{ {
var value = Named.Where(o => o.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault()?.Value; var value = Named
.FirstOrDefault(o => o.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))?.Value;
if (value == null) if (value == null)
{ {
return defaultValue; return defaultValue;
} }
return Converters.ConvertTo<T>(value) ?? throw new Exception("Value cannot be null"); return Converters.ConvertTo<T>(value) ?? throw new Exception("值不能为空");
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new Exception($"Function [{_owner.Name}], {ex.Message}"); throw new Exception($"函数 [{_owner.Name}], {ex.Message}");
} }
} }
@@ -62,13 +67,14 @@ namespace ZelWiki.Engine.Function
try try
{ {
var values = Named.Where(o => o.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))? var values = Named.Where(o => o.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))?
.Select(o => Converters.ConvertTo<T>(o.Value) ?? throw new Exception("Value cannot be null"))?.ToList(); .Select(o => Converters.ConvertTo<T>(o.Value) ?? throw new Exception("值不能为空"))
?.ToList();
return values ?? new List<T>(); return values ?? new List<T>();
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new Exception($"Function [{_owner.Name}], {ex.Message}"); throw new Exception($"函数 [{_owner.Name}], {ex.Message}");
} }
} }
} }

View File

@@ -10,13 +10,15 @@ namespace ZelWiki.Engine.Function
private static partial Regex FunctionCallParser(); private static partial Regex FunctionCallParser();
/// <summary> /// <summary>
/// Parsed a function call, its parameters and matches it to a defined function and its prototype. /// 解析函数调用及其参数,并将其与已定义的函数及其原型进行匹配
/// </summary> /// </summary>
/// <param name="prototypes"></param>
/// <param name="functionCall"></param> /// <param name="functionCall"></param>
/// <param name="parseEndIndex"></param> /// <param name="parseEndIndex"></param>
/// <returns></returns> /// <returns></returns>
/// <exception cref="Exception"></exception> /// <exception cref="WikiFunctionPrototypeNotDefinedException"></exception>
public static FunctionCall ParseAndGetFunctionCall(FunctionPrototypeCollection prototypes, string functionCall, out int parseEndIndex) public static FunctionCall ParseAndGetFunctionCall(FunctionPrototypeCollection prototypes, string functionCall,
out int parseEndIndex)
{ {
var rawArguments = new List<string>(); var rawArguments = new List<string>();
@@ -25,7 +27,8 @@ namespace ZelWiki.Engine.Function
var prototype = prototypes.Get(parsed.Prefix, parsed.Name); var prototype = prototypes.Get(parsed.Prefix, parsed.Name);
if (prototype == null) if (prototype == null)
{ {
throw new WikiFunctionPrototypeNotDefinedException($"Function ({parsed.Name}) does not have a defined prototype."); throw new WikiFunctionPrototypeNotDefinedException(
$"函数 ({parsed.Name}) 没有定义的原型.");
} }
parseEndIndex = parsed.EndIndex; parseEndIndex = parsed.EndIndex;
@@ -35,18 +38,18 @@ namespace ZelWiki.Engine.Function
public static ParsedFunctionCall ParseFunctionCall(FunctionPrototypeCollection prototypes, string functionCall) public static ParsedFunctionCall ParseFunctionCall(FunctionPrototypeCollection prototypes, string functionCall)
{ {
string functionName = string.Empty; var functionName = string.Empty;
int parseEndIndex = 0; var parseEndIndex = 0;
var rawArguments = new List<string>(); var rawArguments = new List<string>();
var firstLine = functionCall.Split('\n')?.FirstOrDefault(); var firstLine = functionCall.Split('\n')?.FirstOrDefault();
if (firstLine == null || firstLine.Where(x => x == '(').Count() != firstLine.Where(x => x == ')').Count()) if (firstLine == null || firstLine.Count(x => x == '(') != firstLine.Count(x => x == ')'))
{ {
throw new WikiFunctionPrototypeSyntaxError($"Function parentheses mismatch."); throw new WikiFunctionPrototypeSyntaxError($"函数括号不匹配.");
} }
string functionPrefix = functionCall.Substring(0, 2); var functionPrefix = functionCall.Substring(0, 2);
var parameterMatches = FunctionCallParser().Matches(firstLine); var parameterMatches = FunctionCallParser().Matches(firstLine);
if (parameterMatches.Count > 0) if (parameterMatches.Count > 0)
@@ -57,12 +60,15 @@ namespace ZelWiki.Engine.Function
functionName = match.Value[..paramStartIndex].ToLower().TrimStart(['{', '#', '@']).Trim(); functionName = match.Value[..paramStartIndex].ToLower().TrimStart(['{', '#', '@']).Trim();
parseEndIndex = match.Index + match.Length; parseEndIndex = match.Index + match.Length;
string rawArgTrimmed = match.ToString().Substring(paramStartIndex, (match.ToString().Length - paramStartIndex)); string rawArgTrimmed = match.ToString()
.Substring(paramStartIndex, (match.ToString().Length - paramStartIndex));
rawArguments = ParseRawArguments(rawArgTrimmed); rawArguments = ParseRawArguments(rawArgTrimmed);
} }
else //The function call has no parameters. else //函数调用没有参数.
{ {
int endOfLine = functionCall.Substring(2).TakeWhile(c => char.IsLetterOrDigit(c)).Count(); //Find the first non-alphanumeric after the function identifier (##, @@, etc). var endOfLine =
functionCall.Substring(2).TakeWhile(c => char.IsLetterOrDigit(c))
.Count();
functionName = functionCall.Substring(2, endOfLine).ToLower().TrimStart(['{', '#', '@']).Trim(); functionName = functionCall.Substring(2, endOfLine).ToLower().TrimStart(['{', '#', '@']).Trim();
parseEndIndex = endOfLine + 2; parseEndIndex = endOfLine + 2;
} }
@@ -71,12 +77,11 @@ namespace ZelWiki.Engine.Function
} }
/// <summary> /// <summary>
/// Parses function parameters into a list of arguments based on comma separation. ///
/// String do not need to be enclosed in double-quotes unless they contain commas.
/// </summary> /// </summary>
/// <param name="paramString"></param> /// <param name="paramString"></param>
/// <returns></returns> /// <returns></returns>
/// <exception cref="Exception"></exception> /// <exception cref="WikiFunctionPrototypeSyntaxError"></exception>
public static List<string> ParseRawArgumentsAddParenthesis(string paramString) public static List<string> ParseRawArgumentsAddParenthesis(string paramString)
{ {
if (paramString.StartsWith('(') || paramString.EndsWith(')')) if (paramString.StartsWith('(') || paramString.EndsWith(')'))
@@ -88,17 +93,16 @@ namespace ZelWiki.Engine.Function
} }
/// <summary> /// <summary>
/// Parses function parameters into a list of arguments based on comma separation. ///
/// String do not need to be enclosed in double-quotes unless they contain commas.
/// </summary> /// </summary>
/// <param name="paramString"></param> /// <param name="paramString"></param>
/// <returns></returns> /// <returns></returns>
/// <exception cref="Exception"></exception> /// <exception cref="WikiFunctionPrototypeSyntaxError"></exception>
public static List<string> ParseRawArguments(string paramString) public static List<string> ParseRawArguments(string paramString)
{ {
List<string> ps = new(); List<string> ps = new();
int readPos = 0; var readPos = 0;
var singleParam = new StringBuilder(); var singleParam = new StringBuilder();
@@ -109,7 +113,7 @@ namespace ZelWiki.Engine.Function
int parenNest = 1; int parenNest = 1;
readPos++; //Skip the ( readPos++;
while (readPos < paramString.Length && char.IsWhiteSpace(paramString[readPos])) readPos++; while (readPos < paramString.Length && char.IsWhiteSpace(paramString[readPos])) readPos++;
@@ -130,7 +134,7 @@ namespace ZelWiki.Engine.Function
} }
else if (paramString[readPos] == ')' && parenNest == 0) else if (paramString[readPos] == ')' && parenNest == 0)
{ {
readPos++; //Skip the ) readPos++;
if (parenNest == 0 && readPos != paramString.Length) if (parenNest == 0 && readPos != paramString.Length)
{ {
@@ -141,6 +145,7 @@ namespace ZelWiki.Engine.Function
{ {
ps.Add(singleParam.ToString()); ps.Add(singleParam.ToString());
} }
singleParam.Clear(); singleParam.Clear();
if (parenNest == 0) if (parenNest == 0)
@@ -150,9 +155,9 @@ namespace ZelWiki.Engine.Function
} }
else if (paramString[readPos] == '\"') else if (paramString[readPos] == '\"')
{ {
readPos++; //Skip the ". readPos++;
bool escapeChar = false; var escapeChar = false;
for (;; readPos++) for (;; readPos++)
{ {
if (readPos == paramString.Length) if (readPos == paramString.Length)
@@ -166,14 +171,14 @@ namespace ZelWiki.Engine.Function
} }
else if (paramString[readPos] == '\"' && escapeChar == false) else if (paramString[readPos] == '\"' && escapeChar == false)
{ {
//Found the end of the string: readPos++;
readPos++; //Skip the ".
break; break;
} }
else else
{ {
singleParam.Append(paramString[readPos]); singleParam.Append(paramString[readPos]);
} }
escapeChar = false; escapeChar = false;
} }
@@ -181,7 +186,7 @@ namespace ZelWiki.Engine.Function
} }
else if (paramString[readPos] == ',') else if (paramString[readPos] == ',')
{ {
readPos++; //Skip the , readPos++;
while (readPos < paramString.Length && char.IsWhiteSpace(paramString[readPos])) readPos++; while (readPos < paramString.Length && char.IsWhiteSpace(paramString[readPos])) readPos++;
ps.Add(singleParam.ToString()); ps.Add(singleParam.ToString());
@@ -209,7 +214,7 @@ namespace ZelWiki.Engine.Function
} }
} }
for (int i = 0; i < ps.Count; i++) for (var i = 0; i < ps.Count; i++)
{ {
ps[i] = ps[i].Trim(); ps[i] = ps[i].Trim();
} }

View File

@@ -2,14 +2,19 @@
{ {
public class FunctionPrototype public class FunctionPrototype
{ {
public string FunctionPrefix { get; set; } = string.Empty; /// <summary>
public string ProperName { get; set; } = string.Empty; ///
public string FunctionName { get; set; } = string.Empty; /// </summary>
public List<PrototypeParameter> Parameters { get; set; }
public FunctionPrototype() public FunctionPrototype()
{ {
Parameters = new List<PrototypeParameter>(); FunctionPrefix = string.Empty;
} ProperName = string.Empty;
FunctionName = string.Empty;
Parameters = new();
}
public string FunctionPrefix { get; set; }
public string ProperName { get; set; }
public string FunctionName { get; set; }
public List<PrototypeParameter> Parameters { get; set; }
} }
} }

View File

@@ -14,6 +14,10 @@ namespace ZelWiki.Engine.Function
public WikiFunctionType FunctionTypes { get; private set; } public WikiFunctionType FunctionTypes { get; private set; }
public List<PrototypeSet> Items { get; set; } = new(); public List<PrototypeSet> Items { get; set; } = new();
/// <summary>
///
/// </summary>
/// <param name="functionTypes"></param>
public FunctionPrototypeCollection(WikiFunctionType functionTypes) public FunctionPrototypeCollection(WikiFunctionType functionTypes)
{ {
FunctionTypes = functionTypes; FunctionTypes = functionTypes;
@@ -36,40 +40,40 @@ namespace ZelWiki.Engine.Function
{ {
functionName = functionName.ToLower(); functionName = functionName.ToLower();
//$$ are scope functions and are not called by prefix, we only have prefixes to make it easier to parse return Items.Any(o =>
// the functions in the wikiText and scope functions are easy enough since they start with curly braces. (o.FunctionPrefix == functionPrefix || o.FunctionPrefix == "$$") && o.FunctionName == functionName);
return Items.Any(o => (o.FunctionPrefix == functionPrefix || o.FunctionPrefix == "$$") && o.FunctionName == functionName);
} }
public FunctionPrototype Get(string functionPrefix, string functionName) public FunctionPrototype Get(string functionPrefix, string functionName)
{ {
functionName = functionName.ToLower(); functionName = functionName.ToLower();
//$$ are scope functions and are not called by prefix, we only have prefixes to make it easier to parse var functionPrototype = Items.FirstOrDefault(o =>
// the functions in the wikiText and scope functions are easy enough since they start with curly braces. (o.FunctionPrefix == functionPrefix || o.FunctionPrefix == "$$") && o.FunctionName == functionName)
var functionPrototype = Items.Where(o => (o.FunctionPrefix == functionPrefix || o.FunctionPrefix == "$$") && o.FunctionName == functionName).FirstOrDefault()?.Value; ?.Value;
return functionPrototype return functionPrototype
?? throw new WikiFunctionPrototypeNotDefinedException($"Function ({functionName}) does not have a defined prototype."); ?? throw new WikiFunctionPrototypeNotDefinedException(
$"函数 ({functionName}) 没有定义的原型.");
} }
#region Private
private FunctionPrototype ParsePrototype(string prototypeString) private FunctionPrototype ParsePrototype(string prototypeString)
{ {
int nameStartIndex = prototypeString.TakeWhile(c => char.IsLetterOrDigit(c) == false).Count(); var nameStartIndex = prototypeString.TakeWhile(c => char.IsLetterOrDigit(c) == false).Count();
int nameEndIndex = prototypeString.IndexOf(':'); var nameEndIndex = prototypeString.IndexOf(':');
string properName = prototypeString.Substring(nameStartIndex, nameEndIndex - nameStartIndex).Trim(); var properName = prototypeString.Substring(nameStartIndex, nameEndIndex - nameStartIndex).Trim();
string functionName = properName.ToLower(); var functionName = properName.ToLower();
string functionPrefix = prototypeString.Substring(0, nameStartIndex).Trim(); var functionPrefix = prototypeString.Substring(0, nameStartIndex).Trim();
prototypeString = prototypeString.Substring(nameEndIndex + 1).Trim(); prototypeString = prototypeString.Substring(nameEndIndex + 1).Trim();
var prototype = new FunctionPrototype() { FunctionPrefix = functionPrefix, ProperName = properName, FunctionName = functionName }; var prototype = new FunctionPrototype()
{ FunctionPrefix = functionPrefix, ProperName = properName, FunctionName = functionName };
if (prototypeString.Length == 0) if (prototypeString.Length == 0)
{
//No parameters.
return prototype; return prototype;
}
var segments = prototypeString.Trim().Split('|').Select(o => o.Trim()); var segments = prototypeString.Trim().Split('|').Select(o => o.Trim());
@@ -77,13 +81,13 @@ namespace ZelWiki.Engine.Function
{ {
var prototypeSegment = new PrototypeParameter(); var prototypeSegment = new PrototypeParameter();
int index = 0; var index = 0;
if (segment[index] == '<') if (segment[index] == '<')
{ {
index++; //Skip the '<' index++;
prototypeSegment.Type = Tok(segment, ref index); prototypeSegment.Type = Tok(segment, ref index);
index++; //Skip the '>' index++;
if (prototypeSegment.Type.Contains(':')) if (prototypeSegment.Type.Contains(':'))
{ {
@@ -94,12 +98,14 @@ namespace ZelWiki.Engine.Function
prototypeSegment.IsInfinite = true; prototypeSegment.IsInfinite = true;
if (prototype.Parameters.Any(o => o.IsInfinite)) if (prototype.Parameters.Any(o => o.IsInfinite))
{ {
throw new Exception($"Function [{functionName}], prototype error: cannot contain more than one [infinite] parameter."); throw new Exception(
$"函数 [{functionName}], 原型错误: cannot contain more than one [infinite] parameter.");
} }
} }
else else
{ {
throw new Exception($"Function [{functionName}], prototype error: expected [infinite] got [{splitSeg[1]}]."); throw new Exception(
$"函数 [{functionName}], 原型错误: expected [infinite] got [{splitSeg[1]}].");
} }
} }
@@ -112,41 +118,42 @@ namespace ZelWiki.Engine.Function
prototypeSegment.IsRequired = true; prototypeSegment.IsRequired = true;
} }
index++; //Skip the '[' or '{' index++;
prototypeSegment.Name = Tok(segment, ref index); prototypeSegment.Name = Tok(segment, ref index);
if (index < segment.Length && segment[index] == '(') //Parse allowed values. if (index < segment.Length && segment[index] == '(')
{ {
int allowedValueEndIndex = segment.IndexOf(')', index); int allowedValueEndIndex = segment.IndexOf(')', index);
string roteRequiredValues = segment.Substring(index + 1, allowedValueEndIndex - index - 1); string roteRequiredValues = segment.Substring(index + 1, allowedValueEndIndex - index - 1);
prototypeSegment.AllowedValues = roteRequiredValues.Trim().Split(',').Select(o => o.Trim().ToLower()).ToList(); prototypeSegment.AllowedValues = roteRequiredValues.Trim().Split(',')
.Select(o => o.Trim().ToLower()).ToList();
index = allowedValueEndIndex; index = allowedValueEndIndex;
index++; //Skip the ')' index++;
SkipWhiteSpace(segment, ref index); SkipWhiteSpace(segment, ref index);
} }
index++; //Skip the ']' or '}' index++;
} }
else else
{ {
throw new Exception($"Function [{functionName}], prototype error: expected [{{] or [[]."); throw new Exception($"函数 [{functionName}], 原型错误: expected [{{] or [[].");
} }
SkipWhiteSpace(segment, ref index); SkipWhiteSpace(segment, ref index);
if (index < segment.Length && segment[index] == '=') if (index < segment.Length && segment[index] == '=')
{ {
index++; //Skip the '=' index++;
SkipWhiteSpace(segment, ref index); SkipWhiteSpace(segment, ref index);
if (segment[index] != '\'') if (segment[index] != '\'')
{ {
throw new Exception($"Function [{functionName}], prototype error: expected [\']."); throw new Exception($"函数 [{functionName}], 原型错误: expected [\'].");
} }
index++; //Skip the ''' index++;
prototypeSegment.DefaultValue = segment.Substring(index, (segment.Length - index) - 1); prototypeSegment.DefaultValue = segment.Substring(index, (segment.Length - index) - 1);
@@ -154,13 +161,13 @@ namespace ZelWiki.Engine.Function
if (index < segment.Length && segment[index] != '\'') if (index < segment.Length && segment[index] != '\'')
{ {
throw new Exception($"Function [{functionName}], prototype error: expected [\']."); throw new Exception($"函数 [{functionName}], 原型错误: expected [\'].");
} }
} }
} }
else else
{ {
throw new Exception($"Function [{functionName}], prototype error: expected [<]."); throw new Exception($"函数 [{functionName}], 原型错误: expected [<].");
} }
prototype.Parameters.Add(prototypeSegment); prototype.Parameters.Add(prototypeSegment);
@@ -168,9 +175,8 @@ namespace ZelWiki.Engine.Function
return prototype; return prototype;
} }
/// <summary> /// <summary>
/// Gets the next token in a string. ///
/// </summary> /// </summary>
/// <param name="str"></param> /// <param name="str"></param>
/// <param name="index"></param> /// <param name="index"></param>
@@ -203,5 +209,7 @@ namespace ZelWiki.Engine.Function
index++; index++;
} }
} }
#endregion
} }
} }

View File

@@ -5,6 +5,11 @@
public string Name { get; set; } public string Name { get; set; }
public string Value { get; set; } public string Value { get; set; }
/// <summary>
///
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
public NamedParameter(string name, string value) public NamedParameter(string name, string value)
{ {
Name = name; Name = name;

View File

@@ -2,22 +2,27 @@
{ {
public class OrdinalParameter public class OrdinalParameter
{ {
public string Value { get; set; }
/// <summary> /// <summary>
/// Has been matched to a prototype parameter? ///
/// </summary> /// </summary>
public bool IsMatched { get; set; } = false; /// <param name="value"></param>
/// <summary>
/// If matched to a prototype parameter, this is the name of the parameter.
/// </summary>
public string ParameterName { get; set; } = string.Empty;
public OrdinalParameter(string value) public OrdinalParameter(string value)
{ {
Value = value; Value = value;
IsMatched = false;
ParameterName = string.Empty;
} }
public string Value { get; set; }
/// <summary>
///
/// </summary>
public bool IsMatched { get; set; }
/// <summary>
///
/// </summary>
public string ParameterName { get; set; }
public void AssociateWithPrototypeParam(string paramName) public void AssociateWithPrototypeParam(string paramName)
{ {

View File

@@ -2,11 +2,13 @@
{ {
public class ParsedFunctionCall public class ParsedFunctionCall
{ {
public string Prefix { get; set; } = string.Empty; /// <summary>
public string Name { get; set; } = string.Empty; ///
public int EndIndex { get; set; } /// </summary>
public List<string> RawArguments { get; set; } = new List<string>(); /// <param name="prefix"></param>
/// <param name="name"></param>
/// <param name="endIndex"></param>
/// <param name="rawArguments"></param>
public ParsedFunctionCall(string prefix, string name, int endIndex, List<string> rawArguments) public ParsedFunctionCall(string prefix, string name, int endIndex, List<string> rawArguments)
{ {
Prefix = prefix; Prefix = prefix;
@@ -14,5 +16,9 @@
EndIndex = endIndex; EndIndex = endIndex;
RawArguments = rawArguments; RawArguments = rawArguments;
} }
public string Prefix { get; set; }
public string Name { get; set; }
public int EndIndex { get; set; }
public List<string> RawArguments { get; set; }
} }
} }

View File

@@ -2,11 +2,24 @@
{ {
public class PrototypeParameter public class PrototypeParameter
{ {
public string Type { get; set; } = string.Empty; /// <summary>
public string Name { get; set; } = string.Empty; ///
public string DefaultValue { get; set; } = string.Empty; /// </summary>
public bool IsRequired { get; set; } = false; public PrototypeParameter()
public bool IsInfinite { get; set; } = false; {
public List<string> AllowedValues { get; set; } = new(); Type = string.Empty;
Name = string.Empty;
DefaultValue = string.Empty;
IsRequired = false;
IsInfinite = false;
AllowedValues = new();
}
public string Type { get; set; }
public string Name { get; set; }
public string DefaultValue { get; set; }
public bool IsRequired { get; set; }
public bool IsInfinite { get; set; }
public List<string> AllowedValues { get; set; }
} }
} }

View File

@@ -2,9 +2,19 @@
{ {
public class PrototypeSet public class PrototypeSet
{ {
public string FunctionPrefix { get; set; } = string.Empty; /// <summary>
public string ProperName { get; set; } = string.Empty; ///
public string FunctionName { get; set; } = string.Empty; /// </summary>
public FunctionPrototype Value { get; set; } = new(); public PrototypeSet()
{
FunctionPrefix = string.Empty;
ProperName = string.Empty;
FunctionName = string.Empty;
Value = new();
}
public string FunctionPrefix { get; set; }
public string ProperName { get; set; }
public string FunctionName { get; set; }
public FunctionPrototype Value { get; set; }
} }
} }

View File

@@ -1,10 +1,12 @@
namespace ZelWiki.Engine.Function namespace ZelWiki.Engine.Function
{ {
[Obsolete("弃用了")]
public static class SelfDocument public static class SelfDocument
{ {
/// <summary> /// <summary>
/// Don't ever look at this. :( ///
/// </summary> /// </summary>
[Obsolete("弃用了")]
public static void CreateNotExisting() public static void CreateNotExisting()
{ {
/* /*

View File

@@ -2,8 +2,13 @@
{ {
public class AggregatedSearchToken public class AggregatedSearchToken
{ {
public string Token { get; set; } = string.Empty; public AggregatedSearchToken()
{
Token = string.Empty;
DoubleMetaphone = string.Empty;
}
public string Token { get; set; }
public double Weight { get; set; } public double Weight { get; set; }
public string DoubleMetaphone { get; set; } = string.Empty; public string DoubleMetaphone { get; set; }
} }
} }

View File

@@ -4,15 +4,16 @@ using ZelWiki.Engine.Library.Interfaces;
namespace ZelWiki.Engine.Implementation namespace ZelWiki.Engine.Implementation
{ {
/// <summary> /// <summary>
/// Handles wiki comments. These are generally removed from the result. ///
/// </summary> /// </summary>
public class CommentHandler : ICommentHandler public class CommentHandler : ICommentHandler
{ {
/// <summary> /// <summary>
/// Handles a wiki comment. /// 处理评论
/// </summary> /// </summary>
/// <param name="state">Reference to the wiki state object</param> /// <param name="state"></param>
/// <param name="text">The comment text</param> /// <param name="text"></param>
/// <returns></returns>
public HandlerResult Handle(IZelEngineState state, string text) public HandlerResult Handle(IZelEngineState state, string text)
{ {
return new HandlerResult() { Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine] }; return new HandlerResult() { Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine] };

View File

@@ -5,14 +5,14 @@ using ZelWiki.Repository;
namespace ZelWiki.Engine.Implementation namespace ZelWiki.Engine.Implementation
{ {
/// <summary> /// <summary>
/// Handles wiki completion events. ///
/// </summary> /// </summary>
public class CompletionHandler : ICompletionHandler public class CompletionHandler : ICompletionHandler
{ {
/// <summary> /// <summary>
/// Handles wiki completion events. Is called when the wiki processing competes for a given page. /// 完成事件
/// </summary> /// </summary>
/// <param name="state">Reference to the wiki state object</param> /// <param name="state"></param>
public void Complete(IZelEngineState state) public void Complete(IZelEngineState state)
{ {
if (GlobalConfiguration.RecordCompilationMetrics) if (GlobalConfiguration.RecordCompilationMetrics)

View File

@@ -5,16 +5,17 @@ using ZelWiki.Models;
namespace ZelWiki.Engine.Implementation namespace ZelWiki.Engine.Implementation
{ {
/// <summary> /// <summary>
/// Handles wiki emojis. ///
/// </summary> /// </summary>
public class EmojiHandler : IEmojiHandler public class EmojiHandler : IEmojiHandler
{ {
/// <summary> /// <summary>
/// Handles an emoji instruction. ///
/// </summary> /// </summary>
/// <param name="state">Reference to the wiki state object</param> /// <param name="state"></param>
/// <param name="key">The lookup key for the given emoji.</param> /// <param name="key"></param>
/// <param name="scale">The desired 1-100 scale factor for the emoji.</param> /// <param name="scale"></param>
/// <returns></returns>
public HandlerResult Handle(IZelEngineState state, string key, int scale) public HandlerResult Handle(IZelEngineState state, string key, int scale)
{ {
var emoji = GlobalConfiguration.Emojis.FirstOrDefault(o => o.Shortcut == key); var emoji = GlobalConfiguration.Emojis.FirstOrDefault(o => o.Shortcut == key);
@@ -23,20 +24,23 @@ namespace ZelWiki.Engine.Implementation
{ {
if (scale != 100 && scale > 0 && scale <= 500) if (scale != 100 && scale > 0 && scale <= 500)
{ {
var emojiImage = $"<img src=\"{GlobalConfiguration.BasePath}/file/Emoji/{key.Trim('%')}?Scale={scale}\" alt=\"{emoji?.Name}\" />"; var emojiImage =
$"<img src=\"{GlobalConfiguration.BasePath}/file/Emoji/{key.Trim('%')}?Scale={scale}\" alt=\"{emoji?.Name}\" />";
return new HandlerResult(emojiImage); return new HandlerResult(emojiImage);
} }
else else
{ {
var emojiImage = $"<img src=\"{GlobalConfiguration.BasePath}/file/Emoji/{key.Trim('%')}\" alt=\"{emoji?.Name}\" />"; var emojiImage =
$"<img src=\"{GlobalConfiguration.BasePath}/file/Emoji/{key.Trim('%')}\" alt=\"{emoji?.Name}\" />";
return new HandlerResult(emojiImage); return new HandlerResult(emojiImage);
} }
} }
else else
{ {
return new HandlerResult(key) { Instructions = [Constants.HandlerResultInstruction.DisallowNestedProcessing] }; return new HandlerResult(key)
{ Instructions = [Constants.HandlerResultInstruction.DisallowNestedProcessing] };
} }
} }
} }

View File

@@ -4,16 +4,16 @@ using ZelWiki.Repository;
namespace ZelWiki.Engine.Implementation namespace ZelWiki.Engine.Implementation
{ {
/// <summary> /// <summary>
/// Handles exceptions thrown by the wiki engine. /// 异常处理.
/// </summary> /// </summary>
public class ExceptionHandler : IExceptionHandler public class ExceptionHandler : IExceptionHandler
{ {
/// <summary> /// <summary>
/// Called when an exception is thrown by the wiki engine. /// 日志处理
/// </summary> /// </summary>
/// <param name="state">Reference to the wiki state object</param> /// <param name="state"></param>
/// <param name="ex">Optional exception, in the case that this was an actual exception.</param> /// <param name="ex"></param>
/// <param name="customText">Text that accompanies the exception.</param> /// <param name="customText"></param>
public void Log(IZelEngineState state, Exception? ex, string customText) public void Log(IZelEngineState state, Exception? ex, string customText)
{ {
if (ex != null) if (ex != null)

View File

@@ -4,18 +4,18 @@ using ZelWiki.Engine.Library.Interfaces;
namespace ZelWiki.Engine.Implementation namespace ZelWiki.Engine.Implementation
{ {
/// <summary> /// <summary>
/// Handles links the wiki to another site. /// 处理链接
/// </summary> /// </summary>
public class ExternalLinkHandler : IExternalLinkHandler public class ExternalLinkHandler : IExternalLinkHandler
{ {
/// <summary> /// <summary>
/// Handles an internal wiki link. /// 处理内链
/// </summary> /// </summary>
/// <param name="state">Reference to the wiki state object</param> /// <param name="state"></param>
/// <param name="link">The address of the external site being linked to.</param> /// <param name="link"></param>
/// <param name="text">The text which should be show in the absence of an image.</param> /// <param name="text"></param>
/// <param name="image">The image that should be shown.</param> /// <param name="image"></param>
/// <param name="imageScale">The 0-100 image scale factor for the given image.</param> /// <returns></returns>
public HandlerResult Handle(IZelEngineState state, string link, string? text, string? image) public HandlerResult Handle(IZelEngineState state, string link, string? text, string? image)
{ {
if (string.IsNullOrEmpty(image)) if (string.IsNullOrEmpty(image))
@@ -25,8 +25,7 @@ namespace ZelWiki.Engine.Implementation
Instructions = [Constants.HandlerResultInstruction.DisallowNestedProcessing] Instructions = [Constants.HandlerResultInstruction.DisallowNestedProcessing]
}; };
} }
else
{
return new HandlerResult($"<a href=\"{link}\"><img src=\"{image}\" border =\"0\"></a>") return new HandlerResult($"<a href=\"{link}\"><img src=\"{image}\" border =\"0\"></a>")
{ {
Instructions = [Constants.HandlerResultInstruction.DisallowNestedProcessing] Instructions = [Constants.HandlerResultInstruction.DisallowNestedProcessing]
@@ -34,4 +33,3 @@ namespace ZelWiki.Engine.Implementation
} }
} }
} }
}

View File

@@ -3,18 +3,16 @@ using ZelWiki.Engine.Library.Interfaces;
namespace ZelWiki.Engine.Implementation namespace ZelWiki.Engine.Implementation
{ {
/// <summary>
/// Handles wiki headings. These are automatically added to the table of contents.
/// </summary>
public class HeadingHandler : IHeadingHandler public class HeadingHandler : IHeadingHandler
{ {
/// <summary> /// <summary>
/// Handles wiki headings. These are automatically added to the table of contents. /// 处理白哦提
/// </summary> /// </summary>
/// <param name="state">Reference to the wiki state object</param> /// <param name="state"></param>
/// <param name="depth">The size of the header, also used for table of table of contents indentation.</param> /// <param name="depth"></param>
/// <param name="link">The self link reference.</param> /// <param name="link"></param>
/// <param name="text">The text for the self link.</param> /// <param name="text"></param>
/// <returns></returns>
public HandlerResult Handle(IZelEngineState state, int depth, string link, string text) public HandlerResult Handle(IZelEngineState state, int depth, string link, string text)
{ {
if (depth >= 2 && depth <= 6) if (depth >= 2 && depth <= 6)
@@ -22,7 +20,8 @@ namespace ZelWiki.Engine.Implementation
int fontSize = 8 - depth; int fontSize = 8 - depth;
if (fontSize < 5) fontSize = 5; 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"; 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(html);
} }

View File

@@ -9,41 +9,41 @@ using ZelWiki.Repository;
namespace ZelWiki.Engine.Implementation namespace ZelWiki.Engine.Implementation
{ {
/// <summary>
///
/// </summary>
public class Helpers public class Helpers
{ {
/// <summary> /// <summary>
/// Inserts a new page if Page.Id == 0, other wise updates the page. All metadata is written to the database. /// 更新页面 如果Id为0则新增页面
/// </summary> /// </summary>
/// <param name="sessionState"></param> /// <param name="wikifier"></param>
/// <param name="query"></param>
/// <param name="page"></param> /// <param name="page"></param>
/// <param name="sessionState"></param>
/// <returns></returns> /// <returns></returns>
public static int UpsertPage(IZelEngine wikifier, Page page, ISessionState? sessionState = null) public static int UpsertPage(IZelEngine wikifier, Page page, ISessionState? sessionState = null)
{ {
bool isNewlyCreated = page.Id == 0; var isNewlyCreated = page.Id == 0;
page.Id = PageRepository.SavePage(page); page.Id = PageRepository.SavePage(page);
RefreshPageMetadata(wikifier, page, sessionState); RefreshPageMetadata(wikifier, page, sessionState);
if (isNewlyCreated) if (isNewlyCreated)
{
//This will update the PageId of references that have been saved to the navigation link.
PageRepository.UpdateSinglePageReference(page.Navigation, page.Id); PageRepository.UpdateSinglePageReference(page.Navigation, page.Id);
}
return page.Id; return page.Id;
} }
/// <summary> /// <summary>
/// Rebuilds the page and writes all aspects to the database. /// 重建页面并将所有方面写入数据库
/// </summary> /// </summary>
/// <param name="sessionState"></param> /// <param name="wikifier"></param>
/// <param name="query"></param>
/// <param name="page"></param> /// <param name="page"></param>
/// <param name="sessionState"></param>
public static void RefreshPageMetadata(IZelEngine wikifier, Page page, ISessionState? sessionState = null) public static void RefreshPageMetadata(IZelEngine 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, var state = wikifier.Transform(sessionState, page, null,
[Constants.WikiMatchType.StandardFunction]); [Constants.WikiMatchType.StandardFunction]);
@@ -67,7 +67,13 @@ namespace ZelWiki.Engine.Implementation
WikiCache.ClearCategory(WikiCacheKey.Build(WikiCache.Category.Page, [page.Navigation])); WikiCache.ClearCategory(WikiCacheKey.Build(WikiCache.Category.Page, [page.Navigation]));
} }
public static List<AggregatedSearchToken> ParsePageTokens(IZelEngineState state) #region Private
/// <summary>
///
/// </summary>
/// <param name="state"></param>
/// <returns></returns>
private static List<AggregatedSearchToken> ParsePageTokens(IZelEngineState state)
{ {
var parsedTokens = new List<WeightedSearchToken>(); var parsedTokens = new List<WeightedSearchToken>();
@@ -86,7 +92,7 @@ namespace ZelWiki.Engine.Implementation
return aggregatedTokens; return aggregatedTokens;
} }
internal static List<WeightedSearchToken> ComputeParsedPageTokens(string content, double weightMultiplier) private static List<WeightedSearchToken> ComputeParsedPageTokens(string content, double weightMultiplier)
{ {
var searchConfig = ConfigurationRepository.GetConfigurationEntryValuesByGroupName("Search"); var searchConfig = ConfigurationRepository.GetConfigurationEntryValuesByGroupName("Search");
@@ -117,7 +123,8 @@ namespace ZelWiki.Engine.Implementation
tokens.RemoveAll(o => exclusionWords.Contains(o)); tokens.RemoveAll(o => exclusionWords.Contains(o));
var searchTokens = (from w in tokens var searchTokens = (from w in tokens
group w by w into g group w by w
into g
select new WeightedSearchToken select new WeightedSearchToken
{ {
Token = g.Key, Token = g.Key,
@@ -126,5 +133,7 @@ namespace ZelWiki.Engine.Implementation
return searchTokens.Where(o => string.IsNullOrWhiteSpace(o.Token) == false).ToList(); return searchTokens.Where(o => string.IsNullOrWhiteSpace(o.Token) == false).ToList();
} }
#endregion
} }
} }

View File

@@ -8,19 +8,21 @@ using Constants = ZelWiki.Engine.Library.Constants;
namespace ZelWiki.Engine.Implementation namespace ZelWiki.Engine.Implementation
{ {
/// <summary> /// <summary>
/// Handles links from one wiki page to another. /// 内链处理.
/// </summary> /// </summary>
public class InternalLinkHandler : IInternalLinkHandler public class InternalLinkHandler : IInternalLinkHandler
{ {
/// <summary> /// <summary>
/// Handles an internal wiki link. ///
/// </summary> /// </summary>
/// <param name="state">Reference to the wiki state object</param> /// <param name="state"></param>
/// <param name="pageNavigation">The navigation for the linked page.</param> /// <param name="pageNavigation"></param>
/// <param name="pageName">The name of the page being linked to.</param> /// <param name="pageName"></param>
/// <param name="linkText">The text which should be show in the absence of an image.</param> /// <param name="linkText"></param>
/// <param name="image">The image that should be shown.</param> /// <param name="image"></param>
/// <param name="imageScale">The 0-100 image scale factor for the given image.</param> /// <param name="imageScale"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public HandlerResult Handle(IZelEngineState state, NamespaceNavigation pageNavigation, public HandlerResult Handle(IZelEngineState state, NamespaceNavigation pageNavigation,
string pageName, string linkText, string? image, int imageScale) string pageName, string linkText, string? image, int imageScale)
{ {
@@ -37,18 +39,21 @@ namespace ZelWiki.Engine.Implementation
if (image.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) if (image.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase)
|| image.StartsWith("https://", 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>"; href =
$"<a href=\"{GlobalConfiguration.BasePath}/Page/Create?Name={pageName}\"><img src=\"{GlobalConfiguration.BasePath}{image}?Scale={imageScale}\" /></a>";
} }
else if (image.Contains('/')) 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>"; href =
$"<a href=\"{GlobalConfiguration.BasePath}/Page/Create?Name={pageName}\"><img src=\"{GlobalConfiguration.BasePath}/Page/Image/{image}?Scale={imageScale}\" /></a>";
} }
else 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>"; href =
$"<a href=\"{GlobalConfiguration.BasePath}/Page/Create?Name={pageName}\">{linkText}</a>";
} }
return new HandlerResult(href) return new HandlerResult(href)
@@ -58,7 +63,8 @@ namespace ZelWiki.Engine.Implementation
} }
else if (linkText != null) else if (linkText != null)
{ {
var href = $"<a href=\"{GlobalConfiguration.BasePath}/Page/Create?Name={pageName}\">{linkText}</a>" var href =
$"<a href=\"{GlobalConfiguration.BasePath}/Page/Create?Name={pageName}\">{linkText}</a>"
+ "<font color=\"#cc0000\" size=\"2\">?</font>"; + "<font color=\"#cc0000\" size=\"2\">?</font>";
return new HandlerResult(href) return new HandlerResult(href)
@@ -73,7 +79,7 @@ namespace ZelWiki.Engine.Implementation
} }
else else
{ {
//The page does not exist and the user does not have permission to create it. //该页面不存在,用户没有创建该页面的权限.
if (image != null) if (image != null)
{ {
@@ -82,17 +88,18 @@ namespace ZelWiki.Engine.Implementation
if (image.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) if (image.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase)
|| image.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase)) || image.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase))
{ {
//The image is external. //外部图像.
mockHref = $"<img src=\"{GlobalConfiguration.BasePath}{image}?Scale={imageScale}\" />"; mockHref = $"<img src=\"{GlobalConfiguration.BasePath}{image}?Scale={imageScale}\" />";
} }
else if (image.Contains('/')) else if (image.Contains('/'))
{ {
//The image is located on another page. //图像位于另一页.
mockHref = $"<img src=\"{GlobalConfiguration.BasePath}/Page/Image/{image}?Scale={imageScale}\" />"; mockHref =
$"<img src=\"{GlobalConfiguration.BasePath}/Page/Image/{image}?Scale={imageScale}\" />";
} }
else else
{ {
//The image is located on this page, but this page does not exist. //图像位于此页面上,但此页面不存在.
mockHref = $"linkText"; mockHref = $"linkText";
} }
@@ -110,7 +117,7 @@ namespace ZelWiki.Engine.Implementation
} }
else else
{ {
throw new Exception("No link or image was specified."); throw new Exception("未指定链接或图像.");
} }
} }
} }
@@ -123,23 +130,26 @@ namespace ZelWiki.Engine.Implementation
if (image.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) if (image.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase)
|| image.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase)) || image.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase))
{ {
//The image is external. //外部图像.
href = $"<a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\"><img src=\"{GlobalConfiguration.BasePath}{image}\" /></a>"; href =
$"<a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\"><img src=\"{GlobalConfiguration.BasePath}{image}\" /></a>";
} }
else if (image.Contains('/')) 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>"; href =
$"<a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\"><img src=\"{GlobalConfiguration.BasePath}/Page/Image/{image}?Scale={imageScale}\" /></a>";
} }
else 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>"; href =
$"<a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\"><img src=\"{GlobalConfiguration.BasePath}/Page/Image/{state.Page.Navigation}/{image}?Scale={imageScale}\" /></a>";
} }
} }
else else
{ {
//Just a plain ol' internal page link. //内链
href = $"<a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{linkText}</a>"; href = $"<a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{linkText}</a>";
} }

View File

@@ -4,16 +4,17 @@ using ZelWiki.Engine.Library.Interfaces;
namespace ZelWiki.Engine.Implementation namespace ZelWiki.Engine.Implementation
{ {
/// <summary> /// <summary>
/// Handles basic markup/style instructions like bole, italic, underline, etc. /// 处理基本的标记/样式指令,如粗体、斜体、下划线等.
/// </summary> /// </summary>
public class MarkupHandler : IMarkupHandler public class MarkupHandler : IMarkupHandler
{ {
/// <summary> /// <summary>
/// Handles basic markup instructions like bole, italic, underline, etc. /// 处理基本的标记指令,如粗体、斜体、下划线等
/// </summary> /// </summary>
/// <param name="state">Reference to the wiki state object</param> /// <param name="state"></param>
/// <param name="sequence">The sequence of symbols that were found to denotate this markup instruction,</param> /// <param name="sequence"></param>
/// <param name="scopeBody">The body of text to apply the style to.</param> /// <param name="scopeBody"></param>
/// <returns></returns>
public HandlerResult Handle(IZelEngineState state, char sequence, string scopeBody) public HandlerResult Handle(IZelEngineState state, char sequence, string scopeBody)
{ {
switch (sequence) switch (sequence)
@@ -26,7 +27,6 @@ namespace ZelWiki.Engine.Implementation
default: default:
break; break;
} }
return new HandlerResult() { Instructions = [Constants.HandlerResultInstruction.Skip] }; return new HandlerResult() { Instructions = [Constants.HandlerResultInstruction.Skip] };
} }
} }

View File

@@ -8,7 +8,7 @@ using ZelWiki.Models;
namespace ZelWiki.Engine.Implementation namespace ZelWiki.Engine.Implementation
{ {
/// <summary> /// <summary>
/// Handles post-processing function calls. /// 处理后处理函数调用.
/// </summary> /// </summary>
public class PostProcessingFunctionHandler : IPostProcessingFunctionHandler public class PostProcessingFunctionHandler : IPostProcessingFunctionHandler
{ {
@@ -20,9 +20,10 @@ namespace ZelWiki.Engine.Implementation
{ {
if (_collection == null) if (_collection == null)
{ {
_collection = new FunctionPrototypeCollection(FunctionPrototypeCollection.WikiFunctionType.Standard); _collection =
new FunctionPrototypeCollection(FunctionPrototypeCollection.WikiFunctionType.Standard);
#region Prototypes. #region
_collection.Add("##Tags: <string>{styleName(Flat,List)}='List'"); _collection.Add("##Tags: <string>{styleName(Flat,List)}='List'");
_collection.Add("##TagCloud: <string>[pageTag] | <integer>{Top}='1000'"); _collection.Add("##TagCloud: <string>[pageTag] | <integer>{Top}='1000'");
@@ -37,20 +38,19 @@ namespace ZelWiki.Engine.Implementation
} }
/// <summary> /// <summary>
/// Called to handle function calls when proper prototypes are matched. /// 当匹配到合适的原型时,调用它来处理函数调用。
/// </summary> /// </summary>
/// <param name="state">Reference to the wiki state object</param> /// <param name="state"></param>
/// <param name="function">The parsed function call and all its parameters and their values.</param> /// <param name="function"></param>
/// <param name="scopeBody">This is not a scope function, this should always be null</param> /// <param name="scopeBody"></param>
/// <returns></returns>
public HandlerResult Handle(IZelEngineState state, FunctionCall function, string? scopeBody = null) public HandlerResult Handle(IZelEngineState state, FunctionCall function, string? scopeBody = null)
{ {
switch (function.Name.ToLower()) switch (function.Name.ToLower())
{ {
//------------------------------------------------------------------------------------------------------------------------------
//Displays a tag link list.
case "tags": //##tags case "tags": //##tags
{ {
string styleName = function.Parameters.Get<string>("styleName").ToLower(); var styleName = function.Parameters.Get<string>("styleName").ToLower();
var html = new StringBuilder(); var html = new StringBuilder();
if (styleName == "list") if (styleName == "list")
@@ -60,6 +60,7 @@ namespace ZelWiki.Engine.Implementation
{ {
html.Append($"<li><a href=\"{GlobalConfiguration.BasePath}/Tag/Browse/{tag}\">{tag}</a>"); html.Append($"<li><a href=\"{GlobalConfiguration.BasePath}/Tag/Browse/{tag}\">{tag}</a>");
} }
html.Append("</ul>"); html.Append("</ul>");
} }
else if (styleName == "flat") else if (styleName == "flat")
@@ -74,7 +75,6 @@ namespace ZelWiki.Engine.Implementation
return new HandlerResult(html.ToString()); return new HandlerResult(html.ToString());
} }
//------------------------------------------------------------------------------------------------------------------------------
case "tagcloud": case "tagcloud":
{ {
var top = function.Parameters.Get<int>("Top"); var top = function.Parameters.Get<int>("Top");
@@ -84,21 +84,19 @@ namespace ZelWiki.Engine.Implementation
return new HandlerResult(html); return new HandlerResult(html);
} }
//------------------------------------------------------------------------------------------------------------------------------
case "searchcloud": case "searchcloud":
{ {
var top = function.Parameters.Get<int>("Top"); var top = function.Parameters.Get<int>("Top");
var tokens = function.Parameters.Get<string>("searchPhrase").Split(" ", StringSplitOptions.RemoveEmptyEntries).ToList(); var tokens = function.Parameters.Get<string>("searchPhrase")
.Split(" ", StringSplitOptions.RemoveEmptyEntries).ToList();
string html = SearchCloud.Build(tokens, top); string html = SearchCloud.Build(tokens, top);
return new HandlerResult(html); return new HandlerResult(html);
} }
//------------------------------------------------------------------------------------------------------------------------------
//Displays a table of contents for the page based on the header tags.
case "toc": case "toc":
{ {
bool alphabetized = function.Parameters.Get<bool>("alphabetized"); var alphabetized = function.Parameters.Get<bool>("alphabetized");
var html = new StringBuilder(); var html = new StringBuilder();
@@ -111,7 +109,7 @@ namespace ZelWiki.Engine.Implementation
if (alphabetized) if (alphabetized)
{ {
int level = tags.FirstOrDefault()?.Level ?? 0; var level = tags.FirstOrDefault()?.Level ?? 0;
foreach (var tag in tags) foreach (var tag in tags)
{ {
@@ -131,7 +129,7 @@ namespace ZelWiki.Engine.Implementation
tags = ordered.ToList(); tags = ordered.ToList();
} }
int currentLevel = 0; var currentLevel = 0;
foreach (var tag in tags) foreach (var tag in tags)
{ {
@@ -147,7 +145,6 @@ namespace ZelWiki.Engine.Implementation
{ {
while (currentLevel > tag.Level) while (currentLevel > tag.Level)
{ {
html.Append("</ul>"); html.Append("</ul>");
currentLevel--; currentLevel--;
} }

View File

@@ -5,7 +5,7 @@ using ZelWiki.Engine.Library.Interfaces;
namespace ZelWiki.Engine.Implementation namespace ZelWiki.Engine.Implementation
{ {
/// <summary> /// <summary>
/// Handles processing-instruction function calls, these functions affect the way the page is processed, but are not directly replaced with text. /// 处理处理指令函数调用,这些函数会影响页面的处理方式,但不会直接替换为文本.
/// </summary> /// </summary>
public class ProcessingInstructionFunctionHandler : IProcessingInstructionFunctionHandler public class ProcessingInstructionFunctionHandler : IProcessingInstructionFunctionHandler
{ {
@@ -17,11 +17,11 @@ namespace ZelWiki.Engine.Implementation
{ {
if (_collection == null) if (_collection == null)
{ {
_collection = new FunctionPrototypeCollection(FunctionPrototypeCollection.WikiFunctionType.Instruction); _collection =
new FunctionPrototypeCollection(FunctionPrototypeCollection.WikiFunctionType.Instruction);
#region Prototypes. #region
//Processing instructions:
_collection.Add("@@Deprecate:"); _collection.Add("@@Deprecate:");
_collection.Add("@@Protect:<bool>{isSilent}='false'"); _collection.Add("@@Protect:<bool>{isSilent}='false'");
_collection.Add("@@Tags: <string:infinite>[pageTags]"); _collection.Add("@@Tags: <string:infinite>[pageTags]");
@@ -42,19 +42,16 @@ namespace ZelWiki.Engine.Implementation
} }
/// <summary> /// <summary>
/// Called to handle function calls when proper prototypes are matched. /// 处理各种页面
/// </summary> /// </summary>
/// <param name="state">Reference to the wiki state object</param> /// <param name="state"></param>
/// <param name="function">The parsed function call and all its parameters and their values.</param> /// <param name="function"></param>
/// <param name="scopeBody">This is not a scope function, this should always be null</param> /// <param name="scopeBody"></param>
/// <returns></returns>
public HandlerResult Handle(IZelEngineState state, FunctionCall function, string? scopeBody = null) public HandlerResult Handle(IZelEngineState state, FunctionCall function, string? scopeBody = null)
{ {
switch (function.Name.ToLower()) 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) case "tags": //##tag(pipe|separated|list|of|tags)
{ {
var tags = function.Parameters.GetList<string>("pageTags"); var tags = function.Parameters.GetList<string>("pageTags");
@@ -66,8 +63,6 @@ namespace ZelWiki.Engine.Implementation
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine] Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
}; };
} }
//------------------------------------------------------------------------------------------------------------------------------
case "title": case "title":
{ {
state.PageTitle = function.Parameters.Get<string>("pageTitle"); state.PageTitle = function.Parameters.Get<string>("pageTitle");
@@ -77,8 +72,6 @@ namespace ZelWiki.Engine.Implementation
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine] Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
}; };
} }
//------------------------------------------------------------------------------------------------------------------------------
case "hidefooterlastmodified": case "hidefooterlastmodified":
{ {
state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.HideFooterLastModified); state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.HideFooterLastModified);
@@ -88,8 +81,6 @@ namespace ZelWiki.Engine.Implementation
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine] Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
}; };
} }
//------------------------------------------------------------------------------------------------------------------------------
case "hidefootercomments": case "hidefootercomments":
{ {
state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.HideFooterComments); state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.HideFooterComments);
@@ -98,8 +89,6 @@ namespace ZelWiki.Engine.Implementation
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine] Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
}; };
} }
//------------------------------------------------------------------------------------------------------------------------------
case "nocache": case "nocache":
{ {
state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.NoCache); state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.NoCache);
@@ -108,22 +97,20 @@ namespace ZelWiki.Engine.Implementation
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine] Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
}; };
} }
//------------------------------------------------------------------------------------------------------------------------------
case "deprecate": case "deprecate":
{ {
if (state.NestDepth == 0) if (state.NestDepth == 0)
{ {
state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.Deprecate); state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.Deprecate);
state.Headers.Add("<div class=\"alert alert-danger\">This page has been deprecated and will eventually be deleted.</div>"); state.Headers.Add(
"<div class=\"alert alert-danger\">此页面已被弃用,最终将被删除.</div>");
} }
return new HandlerResult(string.Empty) return new HandlerResult(string.Empty)
{ {
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine] Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
}; };
} }
//------------------------------------------------------------------------------------------------------------------------------
case "protect": case "protect":
{ {
if (state.NestDepth == 0) if (state.NestDepth == 0)
@@ -132,65 +119,67 @@ namespace ZelWiki.Engine.Implementation
state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.Protect); state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.Protect);
if (isSilent == false) 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>"); state.Headers.Add(
"<div class=\"alert alert-info\">此页面已受到保护,非版主无法更改.</div>");
} }
} }
return new HandlerResult(string.Empty) return new HandlerResult(string.Empty)
{ {
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine] Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
}; };
} }
//------------------------------------------------------------------------------------------------------------------------------
case "template": case "template":
{ {
if (state.NestDepth == 0) if (state.NestDepth == 0)
{ {
state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.Template); state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.Template);
state.Headers.Add("<div class=\"alert alert-secondary\">This page is a template and will not appear in indexes or glossaries.</div>"); state.Headers.Add(
"<div class=\"alert alert-secondary\">此页面是一个模板,不会出现在索引或术语表中.</div>");
} }
return new HandlerResult(string.Empty) return new HandlerResult(string.Empty)
{ {
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine] Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
}; };
} }
//------------------------------------------------------------------------------------------------------------------------------
case "review": case "review":
{ {
if (state.NestDepth == 0) if (state.NestDepth == 0)
{ {
state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.Review); state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.Review);
state.Headers.Add("<div class=\"alert alert-warning\">This page has been flagged for review, its content may be inaccurate.</div>"); state.Headers.Add(
"<div class=\"alert alert-warning\">此页面已被标记为待审核,其内容可能不准确.</div>");
} }
return new HandlerResult(string.Empty) return new HandlerResult(string.Empty)
{ {
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine] Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
}; };
} }
//------------------------------------------------------------------------------------------------------------------------------
case "include": case "include":
{ {
if (state.NestDepth == 0) if (state.NestDepth == 0)
{ {
state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.Include); state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.Include);
state.Headers.Add("<div class=\"alert alert-secondary\">This page is an include and will not appear in indexes or glossaries.</div>"); state.Headers.Add(
"<div class=\"alert alert-secondary\">此页为包含页,不会出现在索引或术语表中.</div>");
} }
return new HandlerResult(string.Empty) return new HandlerResult(string.Empty)
{ {
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine] Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
}; };
} }
//------------------------------------------------------------------------------------------------------------------------------
case "draft": case "draft":
{ {
if (state.NestDepth == 0) if (state.NestDepth == 0)
{ {
state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.Draft); state.ProcessingInstructions.Add(ZelWiki.Library.Constants.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>"); state.Headers.Add(
"<div class=\"alert alert-warning\">本页为草稿,可能包含不正确的信息包括但不仅限于实验性样式.</div>");
} }
return new HandlerResult(string.Empty) return new HandlerResult(string.Empty)
{ {
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine] Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]

View File

@@ -9,7 +9,7 @@ using static ZelWiki.Engine.Library.Constants;
namespace ZelWiki.Engine.Implementation namespace ZelWiki.Engine.Implementation
{ {
/// <summary> /// <summary>
/// Handled scope function calls. ///处理作用域函数调用.
/// </summary> /// </summary>
public class ScopeFunctionHandler : IScopeFunctionHandler public class ScopeFunctionHandler : IScopeFunctionHandler
{ {
@@ -23,17 +23,23 @@ namespace ZelWiki.Engine.Implementation
{ {
_collection = new FunctionPrototypeCollection(FunctionPrototypeCollection.WikiFunctionType.Scoped); _collection = new FunctionPrototypeCollection(FunctionPrototypeCollection.WikiFunctionType.Scoped);
#region Prototypes. #region
_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(
"$$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("$$Bullets: <string>{type(unordered,ordered)}='unordered'");
_collection.Add("$$Order: <string>{direction(ascending,descending)}='ascending'"); _collection.Add("$$Order: <string>{direction(ascending,descending)}='ascending'");
_collection.Add("$$Jumbotron:"); _collection.Add("$$Jumbotron:");
_collection.Add("$$Callout: <string>{styleName(default,primary,secondary,success,info,warning,danger)}='default' | <string>{titleText}=''"); _collection.Add(
_collection.Add("$$Background: <string>{styleName(default,primary,secondary,light,dark,success,info,warning,danger,muted)}='default'"); "$$Callout: <string>{styleName(default,primary,secondary,success,info,warning,danger)}='default' | <string>{titleText}=''");
_collection.Add("$$Foreground: <string>{styleName(default,primary,secondary,light,dark,success,info,warning,danger,muted)}='default'"); _collection.Add(
_collection.Add("$$Alert: <string>{styleName(default,primary,secondary,light,dark,success,info,warning,danger)}='default' | <string>{titleText}=''"); "$$Background: <string>{styleName(default,primary,secondary,light,dark,success,info,warning,danger,muted)}='default'");
_collection.Add("$$Card: <string>{styleName(default,primary,secondary,light,dark,success,info,warning,danger)}='default' | <string>{titleText}=''"); _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("$$Collapse: <string>{linkText}='Show'");
_collection.Add("$$Table: <boolean>{hasBorder}='true' | <boolean>{isFirstRowHeader}='true'"); _collection.Add("$$Table: <boolean>{hasBorder}='true' | <boolean>{isFirstRowHeader}='true'");
_collection.Add("$$StripedTable: <boolean>{hasBorder}='true' | <boolean>{isFirstRowHeader}='true'"); _collection.Add("$$StripedTable: <boolean>{hasBorder}='true' | <boolean>{isFirstRowHeader}='true'");
@@ -47,23 +53,23 @@ namespace ZelWiki.Engine.Implementation
} }
/// <summary> /// <summary>
/// Called to handle function calls when proper prototypes are matched. ///
/// </summary> /// </summary>
/// <param name="state">Reference to the wiki state object</param> /// <param name="state"></param>
/// <param name="function">The parsed function call and all its parameters and their values.</param> /// <param name="function"></param>
/// <param name="scopeBody">The the text that the function is designed to affect.</param> /// <param name="scopeBody"></param>
/// <returns></returns>
public HandlerResult Handle(IZelEngineState state, FunctionCall function, string? scopeBody = null) public HandlerResult Handle(IZelEngineState state, FunctionCall function, string? scopeBody = null)
{ {
scopeBody.EnsureNotNull($"The function '{function.Name}' scope body can not be null"); scopeBody.EnsureNotNull($"函数'{function.Name}'作用域主题为空");
switch (function.Name.ToLower()) switch (function.Name.ToLower())
{ {
//------------------------------------------------------------------------------------------------------------------------------
case "code": case "code":
{ {
var html = new StringBuilder(); var html = new StringBuilder();
string language = function.Parameters.Get<string>("language"); var language = function.Parameters.Get<string>("language");
if (string.IsNullOrEmpty(language) || language?.ToLower() == "auto") if (string.IsNullOrEmpty(language) || language?.ToLower() == "auto")
{ {
html.Append($"<pre>"); html.Append($"<pre>");
@@ -77,11 +83,9 @@ namespace ZelWiki.Engine.Implementation
return new HandlerResult(html.ToString()) return new HandlerResult(html.ToString())
{ {
Instructions = [Constants.HandlerResultInstruction.DisallowNestedProcessing] Instructions = [HandlerResultInstruction.DisallowNestedProcessing]
}; };
} }
//------------------------------------------------------------------------------------------------------------------------------
case "stripedtable": case "stripedtable":
case "table": case "table":
{ {
@@ -96,6 +100,7 @@ namespace ZelWiki.Engine.Implementation
{ {
html.Append(" table-striped"); html.Append(" table-striped");
} }
if (hasBorder) if (hasBorder)
{ {
html.Append(" table-bordered"); html.Append(" table-bordered");
@@ -103,7 +108,8 @@ namespace ZelWiki.Engine.Implementation
html.Append($"\">"); html.Append($"\">");
var lines = scopeBody.Split(['\n'], StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim()).Where(o => o.Length > 0); var lines = scopeBody.Split(['\n'], StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim())
.Where(o => o.Length > 0);
int rowNumber = 0; int rowNumber = 0;
@@ -137,6 +143,7 @@ namespace ZelWiki.Engine.Implementation
{ {
html.Append($"</thead>"); html.Append($"</thead>");
} }
html.Append($"</tr>"); html.Append($"</tr>");
rowNumber++; rowNumber++;
@@ -147,27 +154,27 @@ namespace ZelWiki.Engine.Implementation
return new HandlerResult(html.ToString()); return new HandlerResult(html.ToString());
} }
//------------------------------------------------------------------------------------------------------------------------------
case "bullets": case "bullets":
{ {
var html = new StringBuilder(); var html = new StringBuilder();
string type = function.Parameters.Get<string>("type"); var type = function.Parameters.Get<string>("type");
if (type == "unordered") if (type == "unordered")
{ {
var lines = scopeBody.Split(['\n'], StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim()).Where(o => o.Length > 0); var lines = scopeBody.Split(['\n'], StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim())
.Where(o => o.Length > 0);
int currentLevel = 0; int currentLevel = 0;
foreach (var line in lines) foreach (var line in lines)
{ {
int newIndent = 0; var newIndent = 0;
for (; newIndent < line.Length && line[newIndent] == '>'; newIndent++) for (; newIndent < line.Length && line[newIndent] == '>'; newIndent++)
{ {
//Count how many '>' are at the start of the line. //计算行开头有多少个“>”.
} }
newIndent++; newIndent++;
if (newIndent < currentLevel) if (newIndent < currentLevel)
@@ -195,17 +202,19 @@ namespace ZelWiki.Engine.Implementation
} }
else if (type == "ordered") else if (type == "ordered")
{ {
var lines = scopeBody.Split(['\n'], StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim()).Where(o => o.Length > 0); var lines = scopeBody.Split(['\n'], StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim())
.Where(o => o.Length > 0);
int currentLevel = 0; var currentLevel = 0;
foreach (var line in lines) foreach (var line in lines)
{ {
int newIndent = 0; var newIndent = 0;
for (; newIndent < line.Length && line[newIndent] == '>'; newIndent++) for (; newIndent < line.Length && line[newIndent] == '>'; newIndent++)
{ {
//Count how many '>' are at the start of the line. //计算行开头有多少个“>”.
} }
newIndent++; newIndent++;
if (newIndent < currentLevel) if (newIndent < currentLevel)
@@ -231,15 +240,14 @@ namespace ZelWiki.Engine.Implementation
html.Append($"</ol>"); html.Append($"</ol>");
} }
} }
return new HandlerResult(html.ToString()); return new HandlerResult(html.ToString());
} }
//------------------------------------------------------------------------------------------------------------------------------
case "definesnippet": case "definesnippet":
{ {
var html = new StringBuilder(); var html = new StringBuilder();
string name = function.Parameters.Get<string>("name"); var name = function.Parameters.Get<string>("name");
if (!state.Snippets.TryAdd(name, scopeBody)) if (!state.Snippets.TryAdd(name, scopeBody))
{ {
@@ -248,14 +256,12 @@ namespace ZelWiki.Engine.Implementation
return new HandlerResult(html.ToString()); return new HandlerResult(html.ToString());
} }
//------------------------------------------------------------------------------------------------------------------------------
case "alert": case "alert":
{ {
var html = new StringBuilder(); var html = new StringBuilder();
string titleText = function.Parameters.Get<string>("titleText"); var titleText = function.Parameters.Get<string>("titleText");
string style = function.Parameters.Get<string>("styleName").ToLower(); var style = function.Parameters.Get<string>("styleName").ToLower();
style = style == "default" ? "" : $"alert-{style}"; style = style == "default" ? "" : $"alert-{style}";
if (!string.IsNullOrEmpty(titleText)) scopeBody = $"<h1>{titleText}</h1>{scopeBody}"; if (!string.IsNullOrEmpty(titleText)) scopeBody = $"<h1>{titleText}</h1>{scopeBody}";
@@ -267,34 +273,25 @@ namespace ZelWiki.Engine.Implementation
{ {
var html = new StringBuilder(); var html = new StringBuilder();
string direction = function.Parameters.Get<string>("direction"); var direction = function.Parameters.Get<string>("direction");
var lines = scopeBody.Split("\n").Select(o => o.Trim()).ToList(); var lines = scopeBody.Split("\n").Select(o => o.Trim()).ToList();
if (direction == "ascending") html.Append(direction == "ascending"
{ ? string.Join("\r\n", lines.OrderBy(o => o))
html.Append(string.Join("\r\n", lines.OrderBy(o => o))); : string.Join("\r\n", lines.OrderByDescending(o => o)));
}
else
{
html.Append(string.Join("\r\n", lines.OrderByDescending(o => o)));
}
return new HandlerResult(html.ToString()); return new HandlerResult(html.ToString());
} }
//------------------------------------------------------------------------------------------------------------------------------
case "jumbotron": case "jumbotron":
{ {
var html = new StringBuilder(); var html = new StringBuilder();
string titleText = function.Parameters.Get("titleText", ""); var titleText = function.Parameters.Get("titleText", "");
html.Append($"<div class=\"mt-4 p-5 bg-secondary text-white rounded\">"); html.Append($"<div class=\"mt-4 p-5 bg-secondary text-white rounded\">");
if (!string.IsNullOrEmpty(titleText)) html.Append($"<h1>{titleText}</h1>"); if (!string.IsNullOrEmpty(titleText)) html.Append($"<h1>{titleText}</h1>");
html.Append($"<p>{scopeBody}</p>"); html.Append($"<p>{scopeBody}</p>");
html.Append($"</div>"); html.Append($"</div>");
return new HandlerResult(html.ToString()); return new HandlerResult(html.ToString());
} }
//------------------------------------------------------------------------------------------------------------------------------
case "foreground": case "foreground":
{ {
var html = new StringBuilder(); var html = new StringBuilder();
@@ -303,31 +300,27 @@ namespace ZelWiki.Engine.Implementation
html.Append($"<p class=\"{style.ForegroundStyle} {style.BackgroundStyle}\">{scopeBody}</p>"); html.Append($"<p class=\"{style.ForegroundStyle} {style.BackgroundStyle}\">{scopeBody}</p>");
return new HandlerResult(html.ToString()); return new HandlerResult(html.ToString());
} }
//------------------------------------------------------------------------------------------------------------------------------
case "background": case "background":
{ {
var html = new StringBuilder(); var html = new StringBuilder();
var style = BGFGStyle.GetBackgroundStyle(function.Parameters.Get("styleName", "default")); var style = BGFGStyle.GetBackgroundStyle(function.Parameters.Get("styleName", "default"));
html.Append($"<div class=\"p-3 mb-2 {style.ForegroundStyle} {style.BackgroundStyle}\">{scopeBody}</div>"); html.Append(
$"<div class=\"p-3 mb-2 {style.ForegroundStyle} {style.BackgroundStyle}\">{scopeBody}</div>");
return new HandlerResult(html.ToString()); return new HandlerResult(html.ToString());
} }
//------------------------------------------------------------------------------------------------------------------------------
case "collapse": case "collapse":
{ {
var html = new StringBuilder(); var html = new StringBuilder();
string linkText = function.Parameters.Get<string>("linktext"); var linkText = function.Parameters.Get<string>("linktext");
string uid = "A" + Guid.NewGuid().ToString().Replace("-", ""); var 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(
$"<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=\"collapse\" id=\"{uid}\">");
html.Append($"<div class=\"card card-body\"><p class=\"card-text\">{scopeBody}</p></div></div>"); html.Append($"<div class=\"card card-body\"><p class=\"card-text\">{scopeBody}</p></div></div>");
return new HandlerResult(html.ToString()); return new HandlerResult(html.ToString());
} }
//------------------------------------------------------------------------------------------------------------------------------
case "callout": case "callout":
{ {
var html = new StringBuilder(); var html = new StringBuilder();
@@ -342,31 +335,26 @@ namespace ZelWiki.Engine.Implementation
html.Append($"</div>"); html.Append($"</div>");
return new HandlerResult(html.ToString()); return new HandlerResult(html.ToString());
} }
//------------------------------------------------------------------------------------------------------------------------------
case "card": case "card":
{ {
var html = new StringBuilder(); var html = new StringBuilder();
string titleText = function.Parameters.Get<string>("titleText"); var titleText = function.Parameters.Get<string>("titleText");
var style = BGFGStyle.GetBackgroundStyle(function.Parameters.Get("styleName", "default")); var style = BGFGStyle.GetBackgroundStyle(function.Parameters.Get("styleName", "default"));
html.Append($"<div class=\"card {style.ForegroundStyle} {style.BackgroundStyle} mb-3\">"); html.Append($"<div class=\"card {style.ForegroundStyle} {style.BackgroundStyle} mb-3\">");
if (string.IsNullOrEmpty(titleText) == false) if (string.IsNullOrEmpty(titleText) == false)
{
html.Append($"<div class=\"card-header\">{titleText}</div>"); html.Append($"<div class=\"card-header\">{titleText}</div>");
}
html.Append("<div class=\"card-body\">"); html.Append("<div class=\"card-body\">");
html.Append($"<p class=\"card-text\">{scopeBody}</p>"); html.Append($"<p class=\"card-text\">{scopeBody}</p>");
html.Append("</div>"); html.Append("</div>");
html.Append("</div>"); html.Append("</div>");
return new HandlerResult(html.ToString()); return new HandlerResult(html.ToString());
}
} }
} return new HandlerResult() { Instructions = [HandlerResultInstruction.Skip] };
return new HandlerResult() { Instructions = [Constants.HandlerResultInstruction.Skip] };
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -2,8 +2,14 @@
{ {
public class BGFGStyle public class BGFGStyle
{ {
public string ForegroundStyle { get; set; } = String.Empty; public BGFGStyle()
public string BackgroundStyle { get; set; } = String.Empty; {
ForegroundStyle = string.Empty;
BackgroundStyle = string.Empty;
}
public string ForegroundStyle { get; set; }
public string BackgroundStyle { get; set; }
public BGFGStyle(string foregroundStyle, string backgroundStyle) public BGFGStyle(string foregroundStyle, string backgroundStyle)
{ {
@@ -16,9 +22,6 @@
return new BGFGStyle(BackgroundStyle, ForegroundStyle); return new BGFGStyle(BackgroundStyle, ForegroundStyle);
} }
public BGFGStyle()
{
}
public static readonly Dictionary<string, BGFGStyle> ForegroundStyles = new(StringComparer.OrdinalIgnoreCase) public static readonly Dictionary<string, BGFGStyle> ForegroundStyles = new(StringComparer.OrdinalIgnoreCase)
{ {

View File

@@ -5,7 +5,7 @@ namespace ZelWiki.Engine.Implementation.Utility
public static class Differentiator public static class Differentiator
{ {
/// <summary> /// <summary>
/// This leaves a lot to be desired. ///
/// </summary> /// </summary>
/// <param name="thisRev"></param> /// <param name="thisRev"></param>
/// <param name="prevRev"></param> /// <param name="prevRev"></param>
@@ -16,32 +16,32 @@ namespace ZelWiki.Engine.Implementation.Utility
var thisRevLines = thisRev.Split('\n'); var thisRevLines = thisRev.Split('\n');
var prevRevLines = prevRev.Split('\n'); var prevRevLines = prevRev.Split('\n');
int thisRevLineCount = thisRevLines.Length; var thisRevLineCount = thisRevLines.Length;
int prevRevLinesCount = prevRevLines.Length; var prevRevLinesCount = prevRevLines.Length;
int linesAdded = prevRevLines.Except(thisRevLines).Count(); var linesAdded = prevRevLines.Except(thisRevLines).Count();
int linesDeleted = thisRevLines.Except(prevRevLines).Count(); var linesDeleted = thisRevLines.Except(prevRevLines).Count();
if (thisRevLineCount != prevRevLinesCount) if (thisRevLineCount != prevRevLinesCount)
{ {
summary.Append($"{Math.Abs(thisRevLineCount - prevRevLinesCount):N0} lines changed."); summary.Append($"{Math.Abs(thisRevLineCount - prevRevLinesCount):N0} 行修改.");
} }
if (linesAdded > 0) if (linesAdded > 0)
{ {
if (summary.Length > 0) summary.Append(' '); if (summary.Length > 0) summary.Append(' ');
summary.Append($"{linesAdded:N0} lines added."); summary.Append($"{linesAdded:N0} 行新增.");
} }
if (linesDeleted > 0) if (linesDeleted > 0)
{ {
if (summary.Length > 0) summary.Append(' '); if (summary.Length > 0) summary.Append(' ');
summary.Append($"{linesDeleted:N0} lines deleted."); summary.Append($"{linesDeleted:N0} 行删除.");
} }
if (summary.Length == 0) if (summary.Length == 0)
{ {
summary.Append($"No changes detected."); summary.Append($"未修改.");
} }
return summary.ToString(); return summary.ToString();

View File

@@ -16,10 +16,10 @@ namespace ZelWiki.Engine.Implementation.Utility
pages = pages.Take((int)maxCount).ToList(); pages = pages.Take((int)maxCount).ToList();
} }
int pageCount = pages.Count; var pageCount = pages.Count;
int fontSize = 7; var fontSize = 7;
int sizeStep = (pageCount > fontSize ? pageCount : (fontSize * 2)) / fontSize; var sizeStep = (pageCount > fontSize ? pageCount : (fontSize * 2)) / fontSize;
int pageIndex = 0; var pageIndex = 0;
var pageList = new List<TagCloudItem>(); var pageList = new List<TagCloudItem>();

View File

@@ -17,10 +17,10 @@ namespace ZelWiki.Engine.Implementation.Utility
tags = tags.Take((int)maxCount).ToList(); tags = tags.Take((int)maxCount).ToList();
} }
int tagCount = tags.Count; var tagCount = tags.Count;
int fontSize = 7; var fontSize = 7;
int sizeStep = (tagCount > fontSize ? tagCount : (fontSize * 2)) / fontSize; var sizeStep = (tagCount > fontSize ? tagCount : (fontSize * 2)) / fontSize;
int tagIndex = 0; var tagIndex = 0;
var tagList = new List<TagCloudItem>(); var tagList = new List<TagCloudItem>();

View File

@@ -2,7 +2,14 @@
{ {
public class WeightedSearchToken public class WeightedSearchToken
{ {
public string Token { get; set; } = string.Empty; /// <summary>
///
/// </summary>
public WeightedSearchToken()
{
Token = string.Empty;
}
public string Token { get; set; }
public double Weight { get; set; } public double Weight { get; set; }
} }
} }

View File

@@ -1,9 +1,12 @@
namespace ZelWiki.Engine.Library namespace ZelWiki.Engine.Library
{ {
/// <summary>
///
/// </summary>
public class Constants public class Constants
{ {
public const string SoftBreak = "<!--SoftBreak-->"; //These will remain as \r\n in the final HTML. public const string SoftBreak = "<!--SoftBreak-->";
public const string HardBreak = "<!--HardBreak-->"; //These will remain as <br /> in the final HTML. public const string HardBreak = "<!--HardBreak-->";
public enum WikiMatchType public enum WikiMatchType
{ {
@@ -23,20 +26,22 @@
public enum HandlerResultInstruction public enum HandlerResultInstruction
{ {
/// <summary> /// <summary>
/// Does not process the match, allowing it to be processed by another handler. /// 不处理匹配,允许它由另一个处理程序处理
/// </summary> /// </summary>
Skip, Skip,
/// <summary> /// <summary>
/// Removes any single trailing newline after match. /// 删除匹配后的任何单个尾随换行符
/// </summary> /// </summary>
TruncateTrailingLine, TruncateTrailingLine,
/// <summary> /// <summary>
/// Will not continue to process content in this block. /// 将不会继续处理此块中的内容
/// </summary> /// </summary>
DisallowNestedProcessing, DisallowNestedProcessing,
/// <summary> /// <summary>
/// As opposed to the default functionality of replacing all matches, this will cause ony the first match to be replaced. ///
/// This also means that each match will be processed individually, which can impact performance.
/// </summary> /// </summary>
OnlyReplaceFirstMatch OnlyReplaceFirstMatch
} }

View File

@@ -1,15 +1,16 @@
namespace ZelWiki.Engine.Library.Interfaces namespace ZelWiki.Engine.Library.Interfaces
{ {
/// <summary> /// <summary>
/// Handles wiki comments. These are generally removed from the result. /// 处理评论.
/// </summary> /// </summary>
public interface ICommentHandler public interface ICommentHandler
{ {
/// <summary> /// <summary>
/// Handles a wiki comment. /// 处理评论
/// </summary> /// </summary>
/// <param name="state">Reference to the wiki state object</param> /// <param name="state"></param>
/// <param name="text">The comment text</param> /// <param name="text"></param>
/// <returns></returns>
public HandlerResult Handle(IZelEngineState state, string text); public HandlerResult Handle(IZelEngineState state, string text);
} }
} }

View File

@@ -1,14 +1,14 @@
namespace ZelWiki.Engine.Library.Interfaces namespace ZelWiki.Engine.Library.Interfaces
{ {
/// <summary> /// <summary>
/// Handles wiki completion events. /// 处理完成事件
/// </summary> /// </summary>
public interface ICompletionHandler public interface ICompletionHandler
{ {
/// <summary> /// <summary>
/// Handles wiki completion events. Is called when the wiki processing competes for a given page. /// 处理完成事件
/// </summary> /// </summary>
/// <param name="state">Reference to the wiki state object</param> /// <param name="state"></param>
public void Complete(IZelEngineState state); public void Complete(IZelEngineState state);
} }
} }

View File

@@ -1,16 +1,17 @@
namespace ZelWiki.Engine.Library.Interfaces namespace ZelWiki.Engine.Library.Interfaces
{ {
/// <summary> /// <summary>
/// Handles wiki emojis. /// emoji
/// </summary> /// </summary>
public interface IEmojiHandler public interface IEmojiHandler
{ {
/// <summary> /// <summary>
/// Handles an emoji instruction. ///
/// </summary> /// </summary>
/// <param name="state">Reference to the wiki state object</param> /// <param name="state"></param>
/// <param name="key">The lookup key for the given emoji.</param> /// <param name="key"></param>
/// <param name="scale">The desired 1-100 scale factor for the emoji.</param> /// <param name="scale"></param>
/// <returns></returns>
public HandlerResult Handle(IZelEngineState state, string key, int scale); public HandlerResult Handle(IZelEngineState state, string key, int scale);
} }
} }

View File

@@ -1,16 +1,16 @@
namespace ZelWiki.Engine.Library.Interfaces namespace ZelWiki.Engine.Library.Interfaces
{ {
/// <summary> /// <summary>
/// Handles exceptions thrown by the wiki engine. /// 错误日志
/// </summary> /// </summary>
public interface IExceptionHandler public interface IExceptionHandler
{ {
/// <summary> /// <summary>
/// Called when an exception is thrown by the wiki engine. ///
/// </summary> /// </summary>
/// <param name="state">Reference to the wiki state object</param> /// <param name="state"></param>
/// <param name="ex">Optional exception, in the case that this was an actual exception.</param> /// <param name="ex"></param>
/// <param name="customText">Text that accompanies the exception.</param> /// <param name="customText"></param>
public void Log(IZelEngineState state, Exception? ex, string customText); public void Log(IZelEngineState state, Exception? ex, string customText);
} }
} }

View File

@@ -1,18 +1,18 @@
namespace ZelWiki.Engine.Library.Interfaces namespace ZelWiki.Engine.Library.Interfaces
{ {
/// <summary> /// <summary>
/// Handles links the wiki to another site. ///
/// </summary> /// </summary>
public interface IExternalLinkHandler public interface IExternalLinkHandler
{ {
/// <summary> /// <summary>
/// Handles an internal wiki link. ///
/// </summary> /// </summary>
/// <param name="state">Reference to the wiki state object</param> /// <param name="state"></param>
/// <param name="link">The address of the external site being linked to.</param> /// <param name="link"></param>
/// <param name="text">The text which should be show in the absence of an image.</param> /// <param name="text"></param>
/// <param name="image">The image that should be shown.</param> /// <param name="image"></param>
/// <param name="imageScale">The 0-100 image scale factor for the given image.</param> /// <returns></returns>
public HandlerResult Handle(IZelEngineState state, string link, string? text, string? image); public HandlerResult Handle(IZelEngineState state, string link, string? text, string? image);
} }
} }

View File

@@ -3,22 +3,23 @@
namespace ZelWiki.Engine.Library.Interfaces namespace ZelWiki.Engine.Library.Interfaces
{ {
/// <summary> /// <summary>
/// Base function handler for standard, post-processing, scoped and processing-instruction functions. /// 处理函数.
/// </summary> /// </summary>
public interface IFunctionHandler public interface IFunctionHandler
{ {
/// <summary> /// <summary>
/// Returns a collection of function prototypes. /// 回调.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public FunctionPrototypeCollection Prototypes { get; } public FunctionPrototypeCollection Prototypes { get; }
/// <summary> /// <summary>
/// Called to handle function calls when proper prototypes are matched. /// 当匹配到合适的原型时,调用以处理函数调用
/// </summary> /// </summary>
/// <param name="state">Reference to the wiki state object</param> /// <param name="state"></param>
/// <param name="function">The parsed function call and all its parameters and their values.</param> /// <param name="function"></param>
/// <param name="scopeBody">For scope functions, this is the text that the function is designed to affect.</param> /// <param name="scopeBody"></param>
/// <returns></returns>
public HandlerResult Handle(IZelEngineState state, FunctionCall function, string? scopeBody = null); public HandlerResult Handle(IZelEngineState state, FunctionCall function, string? scopeBody = null);
} }
} }

View File

@@ -1,17 +1,18 @@
namespace ZelWiki.Engine.Library.Interfaces namespace ZelWiki.Engine.Library.Interfaces
{ {
/// <summary> /// <summary>
/// Handles wiki headings. These are automatically added to the table of contents. /// 处理标题
/// </summary> /// </summary>
public interface IHeadingHandler public interface IHeadingHandler
{ {
/// <summary> /// <summary>
/// Handles wiki headings. These are automatically added to the table of contents. ///
/// </summary> /// </summary>
/// <param name="state">Reference to the wiki state object</param> /// <param name="state"></param>
/// <param name="depth">The size of the header, also used for table of table of contents indentation.</param> /// <param name="depth"></param>
/// <param name="link">The self link reference.</param> /// <param name="link"></param>
/// <param name="text">The text for the self link.</param> /// <param name="text"></param>
/// <returns></returns>
public HandlerResult Handle(IZelEngineState state, int depth, string link, string text); public HandlerResult Handle(IZelEngineState state, int depth, string link, string text);
} }
} }

View File

@@ -3,19 +3,21 @@
namespace ZelWiki.Engine.Library.Interfaces namespace ZelWiki.Engine.Library.Interfaces
{ {
/// <summary> /// <summary>
/// Handles links from one wiki page to another. /// 处理内链
/// </summary> /// </summary>
public interface IInternalLinkHandler public interface IInternalLinkHandler
{ {
/// <summary> /// <summary>
/// Handles an internal wiki link. ///
/// </summary> /// </summary>
/// <param name="state">Reference to the wiki state object</param> /// <param name="state"></param>
/// <param name="pageNavigation">The navigation for the linked page.</param> /// <param name="pageNavigation"></param>
/// <param name="pageName">The name of the page being linked to.</param> /// <param name="pageName"></param>
/// <param name="linkText">The text which should be show in the absence of an image.</param> /// <param name="linkText"></param>
/// <param name="image">The image that should be shown.</param> /// <param name="image"></param>
/// <param name="imageScale">The 0-100 image scale factor for the given image.</param> /// <param name="imageScale"></param>
public HandlerResult Handle(IZelEngineState state, NamespaceNavigation pageNavigation, string pageName, string linkText, string? image, int imageScale); /// <returns></returns>
public HandlerResult Handle(IZelEngineState state, NamespaceNavigation pageNavigation, string pageName,
string linkText, string? image, int imageScale);
} }
} }

View File

@@ -1,16 +1,17 @@
namespace ZelWiki.Engine.Library.Interfaces namespace ZelWiki.Engine.Library.Interfaces
{ {
/// <summary> /// <summary>
/// Handles basic markup/style instructions like bole, italic, underline, etc. /// 处理基本的标记/样式指令,如粗体、斜体、下划线等.
/// </summary> /// </summary>
public interface IMarkupHandler public interface IMarkupHandler
{ {
/// <summary> /// <summary>
/// Handles basic markup instructions like bole, italic, underline, etc. ///
/// </summary> /// </summary>
/// <param name="state">Reference to the wiki state object</param> /// <param name="state"></param>
/// <param name="sequence">The sequence of symbols that were found to denotate this markup instruction,</param> /// <param name="sequence"></param>
/// <param name="scopeBody">The body of text to apply the style to.</param> /// <param name="scopeBody"></param>
/// <returns></returns>
public HandlerResult Handle(IZelEngineState state, char sequence, string scopeBody); public HandlerResult Handle(IZelEngineState state, char sequence, string scopeBody);
} }
} }

View File

@@ -1,7 +1,7 @@
namespace ZelWiki.Engine.Library.Interfaces namespace ZelWiki.Engine.Library.Interfaces
{ {
/// <summary> /// <summary>
/// Handles post-processing function calls. ///
/// </summary> /// </summary>
public interface IPostProcessingFunctionHandler : IFunctionHandler public interface IPostProcessingFunctionHandler : IFunctionHandler
{ {

View File

@@ -1,7 +1,7 @@
namespace ZelWiki.Engine.Library.Interfaces namespace ZelWiki.Engine.Library.Interfaces
{ {
/// <summary> /// <summary>
/// Handles processing-instruction function calls. ///
/// </summary> /// </summary>
public interface IProcessingInstructionFunctionHandler : IFunctionHandler public interface IProcessingInstructionFunctionHandler : IFunctionHandler
{ {

View File

@@ -1,7 +1,7 @@
namespace ZelWiki.Engine.Library.Interfaces namespace ZelWiki.Engine.Library.Interfaces
{ {
/// <summary> /// <summary>
/// Handles scope function calls. ///
/// </summary> /// </summary>
public interface IScopeFunctionHandler : IFunctionHandler public interface IScopeFunctionHandler : IFunctionHandler
{ {

View File

@@ -1,7 +1,7 @@
namespace ZelWiki.Engine.Library.Interfaces namespace ZelWiki.Engine.Library.Interfaces
{ {
/// <summary> /// <summary>
/// Handles standard function calls. ///
/// </summary> /// </summary>
public interface IStandardFunctionHandler : IFunctionHandler public interface IStandardFunctionHandler : IFunctionHandler
{ {

View File

@@ -6,68 +6,63 @@ namespace ZelWiki.Engine.Library.Interfaces
{ {
public interface IZelEngineState public interface IZelEngineState
{ {
#region Parameters. #region
ISessionState? Session { get; } public ISessionState? Session { get; }
IQueryCollection QueryString { get; } public IQueryCollection QueryString { get; }
public IZelEngine Engine { get; }
IZelEngine Engine { get; } public IPage Page { get; }
IPage Page { get; } public int? Revision { get; }
int? Revision { get; }
public HashSet<Constants.WikiMatchType> OmitMatches { get; } public HashSet<Constants.WikiMatchType> OmitMatches { get; }
public int NestDepth { get; } //Used for recursion. public int NestDepth { get; }
#endregion #endregion
#region State. #region
/// <summary>
/// Custom page title set by a call to @@Title("...")
/// </summary>
public string? PageTitle { get; set; } public string? PageTitle { get; set; }
Dictionary<string, string> Variables { get; } public Dictionary<string, string> Variables { get; }
Dictionary<string, string> Snippets { get; } public Dictionary<string, string> Snippets { get; }
List<string> Tags { get; set; } public List<string> Tags { get; set; }
List<string> ProcessingInstructions { get; } public List<string> ProcessingInstructions { get; }
List<PageReference> OutgoingLinks { get; } public List<PageReference> OutgoingLinks { get; }
List<TableOfContentsTag> TableOfContents { get; } public List<TableOfContentsTag> TableOfContents { get; }
List<string> Headers { get; } public List<string> Headers { get; }
#endregion #endregion
#region Results. #region
string HtmlResult { get; } public string HtmlResult { get; }
TimeSpan ProcessingTime { get; } public TimeSpan ProcessingTime { get; }
int ErrorCount { get; } public int ErrorCount { get; }
int MatchCount { get; } public int MatchCount { get; }
#endregion #endregion
/// <summary> /// <summary>
/// Used to store values for handlers that needs to survive only a single wiki processing session. ///
/// </summary> /// </summary>
public void SetStateValue<T>(string key, T value); public void SetStateValue<T>(string key, T value);
/// <summary> /// <summary>
/// Used to get values for handlers that needs to survive only a single wiki processing session. ///
/// </summary> /// </summary>
public bool TryGetStateValue<T>(string key, [MaybeNullWhen(false)] out T? outValue); public bool TryGetStateValue<T>(string key, [MaybeNullWhen(false)] out T? outValue);
/// <summary> /// <summary>
/// Used to get values for handlers that needs to survive only a single wiki processing session. ///
/// </summary> /// </summary>
public T GetStateValue<T>(string key, T defaultValue); public T GetStateValue<T>(string key, T defaultValue);
string GetNextQueryToken(); string GetNextQueryToken();
/// <summary> /// <summary>
/// Transforms "included" wiki pages, for example if a wiki function ///
/// injected additional wiki markup, this 'could' be processed separately.
/// </summary> /// </summary>
/// <param name="page">The child page to process</param> /// <param name="page"></param>
/// <param name="revision">The optional revision of the child page to process</param> /// <param name="revision"></param>
/// <returns></returns> /// <returns></returns>
IZelEngineState TransformChild(IPage page, int? revision = null); IZelEngineState TransformChild(IPage page, int? revision = null);
} }

View File

@@ -2,23 +2,29 @@
{ {
public class PageReference public class PageReference
{ {
/// <summary>
/// The name of the page. Such as "Sand Box" or "Some Namespace : SandBox".
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// The namespace part of the Name.
/// </summary>
public string Namespace { get; set; } = string.Empty;
/// The cleaned up version of the name, safe for passing in URLs.
public string Navigation { get; set; } = string.Empty;
public PageReference() public PageReference()
{ {
Name = string.Empty;
Namespace = string.Empty;
Navigation = string.Empty;
} }
/// <summary>
///
/// </summary>
public string Name { get; set; }
/// <summary>
///
/// </summary>
public string Namespace { get; set; }
/// <summary>
///
/// </summary>
public string Navigation { get; set; }
public override bool Equals(object? obj) public override bool Equals(object? obj)
{ {
return obj is PageReference other return obj is PageReference other
@@ -30,6 +36,12 @@
return Navigation.GetHashCode(); return Navigation.GetHashCode();
} }
/// <summary>
///
/// </summary>
/// <param name="name"></param>
/// <param name="navigation"></param>
/// <exception cref="Exception"></exception>
public PageReference(string name, string navigation) public PageReference(string name, string navigation)
{ {
var parts = name.Split("::"); var parts = name.Split("::");
@@ -45,7 +57,7 @@
} }
else else
{ {
throw new Exception($"Invalid page name {name}"); throw new Exception($"页面名称无效: {name}");
} }
Navigation = navigation; Navigation = navigation;

View File

@@ -1,7 +1,7 @@
namespace ZelWiki.Engine.Library namespace ZelWiki.Engine.Library
{ {
/// <summary> /// <summary>
/// Table of contents tag. ///
/// </summary> /// </summary>
public class TableOfContentsTag public class TableOfContentsTag
{ {
@@ -10,6 +10,13 @@
public string Text; public string Text;
public int StartingPosition; public int StartingPosition;
/// <summary>
///
/// </summary>
/// <param name="level"></param>
/// <param name="startingPosition"></param>
/// <param name="hrefTag"></param>
/// <param name="text"></param>
public TableOfContentsTag(int level, int startingPosition, string hrefTag, string text) public TableOfContentsTag(int level, int startingPosition, string hrefTag, string text)
{ {
Level = level; Level = level;

View File

@@ -5,18 +5,17 @@ namespace ZelWiki.Engine
public class WikiMatchSet public class WikiMatchSet
{ {
/// <summary> /// <summary>
/// The type of match that was found. /// 找到的匹配类型.
/// </summary> /// </summary>
public Constants.WikiMatchType MatchType { get; set; } public Constants.WikiMatchType MatchType { get; set; }
/// <summary> /// <summary>
/// The resulting content of the wiki processing. /// 内容.
/// </summary> /// </summary>
public string Content { get; set; } = string.Empty; public string Content { get; set; } = string.Empty;
/// <summary> /// <summary>
/// The content in this segment will be wikified. This is useful to disable on things like error messages ///
/// and literal blocks where the content may contain valid wiki markup but we want it to display verbatim.
/// </summary> /// </summary>
public bool AllowNestedDecode { get; set; } public bool AllowNestedDecode { get; set; }
} }

View File

@@ -2,7 +2,17 @@
{ {
public class WikiOrderedMatch public class WikiOrderedMatch
{ {
public string Value { get; set; } = string.Empty; public WikiOrderedMatch()
{
Value = string.Empty;
}
/// <summary>
///
/// </summary>
public string Value { get; set; }
/// <summary>
///
/// </summary>
public int Index { get; set; } public int Index { get; set; }
} }
} }

View File

@@ -2,6 +2,9 @@
namespace ZelWiki.Engine namespace ZelWiki.Engine
{ {
/// <summary>
/// 一些正则
/// </summary>
internal static partial class PrecompiledRegex internal static partial class PrecompiledRegex
{ {
[GeneratedRegex("\\#\\{([\\S\\s]*?)\\}\\#", RegexOptions.IgnoreCase)] [GeneratedRegex("\\#\\{([\\S\\s]*?)\\}\\#", RegexOptions.IgnoreCase)]

View File

@@ -6,7 +6,7 @@ namespace ZelWiki.Engine
internal static class WikiUtility internal static class WikiUtility
{ {
/// <summary> /// <summary>
/// Skips the namespace and returns just the page name part of the navigation. /// 跳过命名空间,只返回导航的页面名称部分.
/// </summary> /// </summary>
/// <param name="navigation"></param> /// <param name="navigation"></param>
/// <returns></returns> /// <returns></returns>
@@ -50,7 +50,7 @@ namespace ZelWiki.Engine
} }
/// <summary> /// <summary>
/// Gets a list of symbols where the symbol occurs consecutively, more than once. (e.g. "##This##") /// 获取符号连续出现多次的符号列表. ("##This##")
/// </summary> /// </summary>
/// <param name="input"></param> /// <param name="input"></param>
/// <returns></returns> /// <returns></returns>

View File

@@ -5,7 +5,7 @@ using ZelWiki.Models;
namespace ZelWiki.Engine namespace ZelWiki.Engine
{ {
/// <summary> /// <summary>
/// Tiny wikifier (reduced feature-set) for things like comments and profile bios. ///
/// </summary> /// </summary>
public class WikifierLite public class WikifierLite
{ {
@@ -17,9 +17,9 @@ namespace ZelWiki.Engine
return string.Empty; return string.Empty;
} }
for (int i = 0; i < 100; i++) //We don't want to process nested wiki forever. for (var i = 0; i < 100; i++)
{ {
bool processedMatches = false; var processedMatches = false;
var matchStore = new Dictionary<string, string>(); var matchStore = new Dictionary<string, string>();
var content = new WikiString(unprocessedText); var content = new WikiString(unprocessedText);
@@ -75,7 +75,8 @@ namespace ZelWiki.Engine
{ {
if (scale != 100 && scale > 0 && scale <= 500) if (scale != 100 && scale > 0 && scale <= 500)
{ {
var emojiImage = $"<img src=\"/file/Emoji/{key.Trim('%')}?Scale={scale}\" alt=\"{emoji?.Name}\" />"; var emojiImage =
$"<img src=\"/file/Emoji/{key.Trim('%')}?Scale={scale}\" alt=\"{emoji?.Name}\" />";
pageContent.Replace(match.Value, StoreMatch(matchStore, emojiImage)); pageContent.Replace(match.Value, StoreMatch(matchStore, emojiImage));
} }
else else
@@ -92,7 +93,7 @@ namespace ZelWiki.Engine
} }
/// <summary> /// <summary>
/// Transform basic markup such as bold, italics, underline, etc. for single and multi-line. /// 转换单行和多行的基本标记,如粗体、斜体、下划线等.
/// </summary> /// </summary>
/// <param name="pageContent"></param> /// <param name="pageContent"></param>
private static void TransformMarkup(WikiString pageContent, Dictionary<string, string> matchStore) private static void TransformMarkup(WikiString pageContent, Dictionary<string, string> matchStore)
@@ -126,12 +127,12 @@ namespace ZelWiki.Engine
} }
/// <summary> /// <summary>
/// Transform links, these can be internal Wiki links or external links. /// 转换链接这些链接可以是内部Wiki链接或外部链接.
/// </summary> /// </summary>
/// <param name="pageContent"></param> /// <param name="pageContent"></param>
private static void TransformLinks(WikiString pageContent, Dictionary<string, string> matchStore) private static void TransformLinks(WikiString pageContent, Dictionary<string, string> matchStore)
{ {
//Parse external explicit links. eg. [[http://test.net]]. //解析外部链接 [[http://test.net]].
var rgx = new Regex(@"(\[\[http\:\/\/.+?\]\])", RegexOptions.IgnoreCase); var rgx = new Regex(@"(\[\[http\:\/\/.+?\]\])", RegexOptions.IgnoreCase);
var matches = WikiUtility.OrderMatchesByLengthDescending(rgx.Matches(pageContent.ToString())); var matches = WikiUtility.OrderMatchesByLengthDescending(rgx.Matches(pageContent.ToString()));
foreach (var match in matches) foreach (var match in matches)
@@ -149,7 +150,7 @@ namespace ZelWiki.Engine
} }
} }
//Parse external explicit links. eg. [[https://test.net]]. //解析外部链接. [[https://test.net]].
rgx = new Regex(@"(\[\[https\:\/\/.+?\]\])", RegexOptions.IgnoreCase); rgx = new Regex(@"(\[\[https\:\/\/.+?\]\])", RegexOptions.IgnoreCase);
matches = WikiUtility.OrderMatchesByLengthDescending(rgx.Matches(pageContent.ToString())); matches = WikiUtility.OrderMatchesByLengthDescending(rgx.Matches(pageContent.ToString()));
foreach (var match in matches) foreach (var match in matches)
@@ -167,7 +168,7 @@ namespace ZelWiki.Engine
} }
} }
//Parse internal dynamic links. eg [[AboutUs|About Us]]. //解析内部链接 [[AboutUs|About Us]].
rgx = new Regex(@"(\[\[.+?\]\])", RegexOptions.IgnoreCase); rgx = new Regex(@"(\[\[.+?\]\])", RegexOptions.IgnoreCase);
matches = WikiUtility.OrderMatchesByLengthDescending(rgx.Matches(pageContent.ToString())); matches = WikiUtility.OrderMatchesByLengthDescending(rgx.Matches(pageContent.ToString()));
foreach (var match in matches) foreach (var match in matches)
@@ -177,11 +178,13 @@ namespace ZelWiki.Engine
if (args.Count == 1) if (args.Count == 1)
{ {
pageContent.Replace(match.Value, StoreMatch(matchStore, $"<a href=\"{GlobalConfiguration.BasePath}/{args[0]}\">{args[0]}</a>")); pageContent.Replace(match.Value,
StoreMatch(matchStore, $"<a href=\"{GlobalConfiguration.BasePath}/{args[0]}\">{args[0]}</a>"));
} }
else if (args.Count > 1) else if (args.Count > 1)
{ {
pageContent.Replace(match.Value, StoreMatch(matchStore, $"<a href=\"{GlobalConfiguration.BasePath}/{args[0]}\">{args[1]}</a>")); pageContent.Replace(match.Value,
StoreMatch(matchStore, $"<a href=\"{GlobalConfiguration.BasePath}/{args[0]}\">{args[1]}</a>"));
} }
} }
} }

View File

@@ -50,7 +50,7 @@ namespace ZelWiki.Engine
} }
/// <summary> /// <summary>
/// Transforms the content for the given page. /// 转换给定页面的内容.
/// </summary> /// </summary>
/// <param name="session">The users current state, used for localization.</param> /// <param name="session">The users current state, used for localization.</param>
/// <param name="page">The page that is being processed.</param> /// <param name="page">The page that is being processed.</param>

View File

@@ -23,12 +23,13 @@ namespace ZelWiki.Engine
private readonly string _tocName = "TOC_" + new Random().Next(0, 1000000).ToString(); private readonly string _tocName = "TOC_" + new Random().Next(0, 1000000).ToString();
private readonly Dictionary<string, object> _handlerState = new(); private readonly Dictionary<string, object> _handlerState = new();
#region Public properties. #region .
/// <summary> /// <summary>
/// Custom page title set by a call to @@Title("...") /// 通过调用设置自定义页面标题 @@Title("...")
/// </summary> /// </summary>
public string? PageTitle { get; set; } public string? PageTitle { get; set; }
public int ErrorCount { get; private set; } public int ErrorCount { get; private set; }
public int MatchCount { get; private set; } public int MatchCount { get; private set; }
public int TransformIterations { get; private set; } public int TransformIterations { get; private set; }
@@ -45,19 +46,19 @@ namespace ZelWiki.Engine
#endregion #endregion
#region Input parameters. #region .
public IPage Page { get; } public IPage Page { get; }
public int? Revision { get; } public int? Revision { get; }
public IQueryCollection QueryString { get; } public IQueryCollection QueryString { get; }
public ISessionState? Session { get; } public ISessionState? Session { get; }
public HashSet<Constants.WikiMatchType> OmitMatches { get; private set; } = new(); public HashSet<Constants.WikiMatchType> OmitMatches { get; private set; } = new();
public int NestDepth { get; private set; } //Used for recursion. public int NestDepth { get; private set; } //用于递归
#endregion #endregion
/// <summary> /// <summary>
/// Used to store values for handlers that needs to survive only a single wiki processing session. /// 存储只需要在单个wiki处理会话中生存的处理程序的值.
/// </summary> /// </summary>
public void SetStateValue<T>(string key, T value) public void SetStateValue<T>(string key, T value)
{ {
@@ -65,11 +66,12 @@ namespace ZelWiki.Engine
{ {
return; return;
} }
_handlerState[key] = value; _handlerState[key] = value;
} }
/// <summary> /// <summary>
/// Used to get values for handlers that needs to survive only a single wiki processing session. /// 获取只需要在单个处理会话中生存的处理程序的值.
/// </summary> /// </summary>
public T GetStateValue<T>(string key, T defaultValue) public T GetStateValue<T>(string key, T defaultValue)
{ {
@@ -77,12 +79,13 @@ namespace ZelWiki.Engine
{ {
return (T)value; return (T)value;
} }
SetStateValue(key, defaultValue); SetStateValue(key, defaultValue);
return defaultValue; return defaultValue;
} }
/// <summary> /// <summary>
/// Used to get values for handlers that needs to survive only a single wiki processing session. /// 尝试获取只需要在单个处理会话中生存的处理程序的值..
/// </summary> /// </summary>
public bool TryGetStateValue<T>(string key, [MaybeNullWhen(false)] out T? outValue) public bool TryGetStateValue<T>(string key, [MaybeNullWhen(false)] out T? outValue)
{ {
@@ -97,13 +100,14 @@ namespace ZelWiki.Engine
} }
/// <summary> /// <summary>
/// Creates a new instance of the ZelEngineState class. Typically created by a call to ZelEngine.Transform(). /// 创建一个实例
/// </summary> /// </summary>
/// <param name="session">The users current state, used for localization.</param> /// <param name="engine"></param>
/// <param name="page">The page that is being processed.</param> /// <param name="session"></param>
/// <param name="revision">The revision of the page that is being processed.</param> /// <param name="page"></param>
/// <param name="omitMatches">The type of matches that we want to omit from processing.</param> /// <param name="revision"></param>
/// <param name="nestDepth">The current depth of recursion.</param> /// <param name="omitMatches"></param>
/// <param name="nestDepth"></param>
internal ZelEngineState(IZelEngine engine, ISessionState? session, internal ZelEngineState(IZelEngine engine, ISessionState? session,
IPage page, int? revision = null, Constants.WikiMatchType[]? omitMatches = null, int nestDepth = 0) IPage page, int? revision = null, Constants.WikiMatchType[]? omitMatches = null, int nestDepth = 0)
{ {
@@ -123,15 +127,15 @@ namespace ZelWiki.Engine
} }
/// <summary> /// <summary>
/// Transforms "included" wiki pages, for example if a wiki function ///
/// injected additional wiki markup, this 'could' be processed separately.
/// </summary> /// </summary>
/// <param name="page">The child page to process</param> /// <param name="page"></param>
/// <param name="revision">The optional revision of the child page to process</param> /// <param name="revision"></param>
/// <returns></returns> /// <returns></returns>
public IZelEngineState TransformChild(IPage page, int? revision = null) public IZelEngineState TransformChild(IPage page, int? revision = null)
{ {
return new ZelEngineState(Engine, Session, page, revision, OmitMatches.ToArray(), NestDepth + 1).Transform(); return new ZelEngineState(Engine, Session, page, revision, OmitMatches.ToArray(), NestDepth + 1)
.Transform();
} }
internal IZelEngineState Transform() internal IZelEngineState Transform()
@@ -169,21 +173,15 @@ namespace ZelWiki.Engine
foreach (var v in Matches) foreach (var v in Matches)
{ {
if (OmitMatches.Contains(v.Value.MatchType)) if (OmitMatches.Contains(v.Value.MatchType))
{
/// When matches are omitted, the entire match will be removed from the resulting wiki text.
pageContent.Replace(v.Key, string.Empty); pageContent.Replace(v.Key, string.Empty);
}
else else
{
pageContent.Replace(v.Key, v.Value.Content); pageContent.Replace(v.Key, v.Value.Content);
} }
}
} while (length != pageContent.Length); } while (length != pageContent.Length);
pageContent.Replace(SoftBreak, "\r\n"); pageContent.Replace(SoftBreak, "\r\n");
pageContent.Replace(HardBreak, "<br />"); pageContent.Replace(HardBreak, "<br />");
//Prepend any headers that were added by wiki handlers.
foreach (var header in Headers) foreach (var header in Headers)
{ {
pageContent.Insert(0, header); pageContent.Insert(0, header);
@@ -223,7 +221,6 @@ namespace ZelWiki.Engine
TransformStandardFunctions(pageContent, false); TransformStandardFunctions(pageContent, false);
TransformProcessingInstructionFunctions(pageContent); TransformProcessingInstructionFunctions(pageContent);
//We have to replace a few times because we could have replace tags (guids) nested inside others.
int length; int length;
do do
{ {
@@ -233,15 +230,10 @@ namespace ZelWiki.Engine
if (v.Value.AllowNestedDecode) if (v.Value.AllowNestedDecode)
{ {
if (OmitMatches.Contains(v.Value.MatchType)) if (OmitMatches.Contains(v.Value.MatchType))
{
/// When matches are omitted, the entire match will be removed from the resulting wiki text.
pageContent.Replace(v.Key, string.Empty); pageContent.Replace(v.Key, string.Empty);
}
else
{
pageContent.Replace(v.Key, v.Value.Content);
}
else
pageContent.Replace(v.Key, v.Value.Content);
} }
} }
} while (length != pageContent.Length); } while (length != pageContent.Length);
@@ -250,7 +242,7 @@ namespace ZelWiki.Engine
} }
/// <summary> /// <summary>
/// Transform basic markup such as bold, italics, underline, etc. for single and multi-line. /// 转换单行和多行的基本标记,如粗体、斜体、下划线等.
/// </summary> /// </summary>
/// <param name="pageContent"></param> /// <param name="pageContent"></param>
private void TransformMarkup(WikiString pageContent) private void TransformMarkup(WikiString pageContent)
@@ -270,7 +262,8 @@ namespace ZelWiki.Engine
var result = Engine.MarkupHandler.Handle(this, symbol, body); var result = Engine.MarkupHandler.Handle(this, symbol, body);
StoreHandlerResult(result, Constants.WikiMatchType.Markup, pageContent, match.Value, result.Content); StoreHandlerResult(result, WikiMatchType.Markup, pageContent, match.Value,
result.Content);
} }
} }
@@ -279,15 +272,17 @@ namespace ZelWiki.Engine
foreach (var match in sizeUpOrderedMatches) foreach (var match in sizeUpOrderedMatches)
{ {
int headingMarkers = 0; var headingMarkers = 0;
foreach (char c in match.Value) foreach (char c in match.Value)
{ {
if (c != '^') if (c != '^')
{ {
break; break;
} }
headingMarkers++; headingMarkers++;
} }
if (headingMarkers >= 2 && headingMarkers <= 6) if (headingMarkers >= 2 && headingMarkers <= 6)
{ {
string value = match.Value.Substring(headingMarkers, match.Value.Length - headingMarkers).Trim(); string value = match.Value.Substring(headingMarkers, match.Value.Length - headingMarkers).Trim();
@@ -295,44 +290,43 @@ namespace ZelWiki.Engine
int fontSize = 1 + headingMarkers; int fontSize = 1 + headingMarkers;
if (fontSize < 1) fontSize = 1; if (fontSize < 1) fontSize = 1;
string markup = "<font size=\"" + fontSize + "\">" + value + "</font>\r\n"; var markup = "<font size=\"" + fontSize + "\">" + value + "</font>\r\n";
StoreMatch(Constants.WikiMatchType.Markup, pageContent, match.Value, markup); StoreMatch(WikiMatchType.Markup, pageContent, match.Value, markup);
} }
} }
} }
/// <summary> /// <summary>
/// Transform inline and multi-line literal blocks. These are blocks where the content will not be wikified and contain code that is encoded to display verbatim on the page. /// 转换内联和多行文字块
/// </summary> /// </summary>
/// <param name="pageContent"></param> /// <param name="pageContent"></param>
private void TransformLiterals(WikiString pageContent) private void TransformLiterals(WikiString pageContent)
{ {
//TODO: May need to do the same thing we did with TransformBlocks() to match all these if they need to be nested.
//Transform literal strings, even encodes HTML so that it displays verbatim.
var orderedMatches = WikiUtility.OrderMatchesByLengthDescending( var orderedMatches = WikiUtility.OrderMatchesByLengthDescending(
PrecompiledRegex.TransformLiterals().Matches(pageContent.ToString())); PrecompiledRegex.TransformLiterals().Matches(pageContent.ToString()));
foreach (var match in orderedMatches) foreach (var match in orderedMatches)
{ {
string value = match.Value.Substring(2, match.Value.Length - 4); var value = match.Value.Substring(2, match.Value.Length - 4);
value = HttpUtility.HtmlEncode(value); value = HttpUtility.HtmlEncode(value);
StoreMatch(Constants.WikiMatchType.Literal, pageContent, match.Value, value.Replace("\r", "").Trim().Replace("\n", "<br />\r\n"), false); StoreMatch(WikiMatchType.Literal, pageContent, match.Value,
value.Replace("\r", "").Trim().Replace("\n", "<br />\r\n"), false);
} }
} }
/// <summary> /// <summary>
/// Matching nested blocks with regex was hell, I escaped with a loop. ¯\_(ツ)_/¯ ///
/// </summary> /// </summary>
/// <param name="pageContent"></param> /// <param name="pageContent"></param>
private void TransformScopeFunctions(WikiString pageContent) private void TransformScopeFunctions(WikiString pageContent)
{ {
var content = pageContent.ToString(); var content = pageContent.ToString();
string rawBlock = string.Empty; var rawBlock = string.Empty;
int startPos = content.Length - 1; var startPos = content.Length - 1;
while (true) while (true)
{ {
@@ -341,18 +335,24 @@ namespace ZelWiki.Engine
{ {
break; break;
} }
int endPos = content.IndexOf("}}", startPos); int endPos = content.IndexOf("}}", startPos);
if (endPos < 0 || endPos < startPos) if (endPos < 0 || endPos < startPos)
{ {
var exception = new StringBuilder(); var exception = new StringBuilder();
exception.AppendLine($"<strong>A parsing error occurred after position {startPos}:<br /></strong> Unable to locate closing tag.<br /><br />"); exception.AppendLine(
$"<strong>定位后发生解析错误 {startPos}:<br /></strong> 无法找到结束标记.<br /><br />");
if (rawBlock?.Length > 0) if (rawBlock?.Length > 0)
{ {
exception.AppendLine($"<strong>The last successfully parsed block was:</strong><br /> {rawBlock}"); exception.AppendLine(
$"<strong>最后一个成功解析的块是:</strong><br /> {rawBlock}");
} }
exception.AppendLine($"<strong>The problem occurred after:</strong><br /> {pageContent.ToString().Substring(startPos)}<br /><br />");
exception.AppendLine($"<strong>The content the parser was working on is:</strong><br /> {pageContent}<br /><br />"); exception.AppendLine(
$"<strong>问题发生在:</strong><br /> {pageContent.ToString().Substring(startPos)}<br /><br />");
exception.AppendLine(
$"<strong>解析器正在处理的内容是:</strong><br /> {pageContent}<br /><br />");
throw new Exception(exception.ToString()); throw new Exception(exception.ToString());
} }
@@ -369,10 +369,10 @@ namespace ZelWiki.Engine
} }
/// <summary> /// <summary>
/// Transform blocks or sections of code, these are thinks like panels and alerts. ///
/// </summary> /// </summary>
/// <param name="pageContent"></param> /// <param name="pageContent"></param>
/// <param name="isFirstChance">Only process early functions (like code blocks)</param> /// <param name="isFirstChance"></param>
private void TransformScopeFunctions(WikiString pageContent, bool isFirstChance) private void TransformScopeFunctions(WikiString pageContent, bool isFirstChance)
{ {
// {{([\\S\\s]*)}} // {{([\\S\\s]*)}}
@@ -387,14 +387,14 @@ namespace ZelWiki.Engine
FunctionCall function; FunctionCall function;
//We are going to mock up a function call:
string mockFunctionCall = "##" + match.Value.Trim([' ', '\t', '{', '}']); string mockFunctionCall = "##" + match.Value.Trim([' ', '\t', '{', '}']);
try try
{ {
function = FunctionParser.ParseAndGetFunctionCall(functionHandler.Prototypes, mockFunctionCall, out paramEndIndex); function = FunctionParser.ParseAndGetFunctionCall(functionHandler.Prototypes, mockFunctionCall,
out paramEndIndex);
var firstChanceFunctions = new string[] { "code" }; //Process these the first time through. var firstChanceFunctions = new [] { "code" };
if (isFirstChance && firstChanceFunctions.Contains(function.Name.ToLower()) == false) if (isFirstChance && firstChanceFunctions.Contains(function.Name.ToLower()) == false)
{ {
continue; continue;
@@ -411,7 +411,8 @@ namespace ZelWiki.Engine
try try
{ {
var result = functionHandler.Handle(this, function, scopeBody); var result = functionHandler.Handle(this, function, scopeBody);
StoreHandlerResult(result, Constants.WikiMatchType.ScopeFunction, pageContent, match.Value, scopeBody); StoreHandlerResult(result, WikiMatchType.ScopeFunction, pageContent, match.Value,
scopeBody);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -421,7 +422,7 @@ namespace ZelWiki.Engine
} }
/// <summary> /// <summary>
/// Transform headings. These are the basic HTML H1-H6 headings but they are saved for the building of the table of contents. ///
/// </summary> /// </summary>
/// <param name="pageContent"></param> /// <param name="pageContent"></param>
private void TransformHeadings(WikiString pageContent) private void TransformHeadings(WikiString pageContent)
@@ -431,28 +432,32 @@ namespace ZelWiki.Engine
foreach (var match in orderedMatches) foreach (var match in orderedMatches)
{ {
int headingMarkers = 0; var headingMarkers = 0;
foreach (char c in match.Value) foreach (var c in match.Value)
{ {
if (c != '=') if (c != '=')
{ {
break; break;
} }
headingMarkers++; headingMarkers++;
} }
if (headingMarkers >= 2) if (headingMarkers >= 2)
{ {
string link = _tocName + "_" + TableOfContents.Count.ToString(); var link = _tocName + "_" + TableOfContents.Count.ToString();
string text = match.Value.Substring(headingMarkers, match.Value.Length - headingMarkers).Trim().Trim(['=']).Trim(); var text = match.Value.Substring(headingMarkers, match.Value.Length - headingMarkers).Trim()
.Trim(['=']).Trim();
var result = Engine.HeadingHandler.Handle(this, headingMarkers, link, text); var result = Engine.HeadingHandler.Handle(this, headingMarkers, link, text);
if (!result.Instructions.Contains(Constants.HandlerResultInstruction.Skip)) if (!result.Instructions.Contains(HandlerResultInstruction.Skip))
{ {
TableOfContents.Add(new TableOfContentsTag(headingMarkers - 1, match.Index, link, text)); TableOfContents.Add(new TableOfContentsTag(headingMarkers - 1, match.Index, link, text));
} }
StoreHandlerResult(result, Constants.WikiMatchType.Heading, pageContent, match.Value, result.Content); StoreHandlerResult(result, WikiMatchType.Heading, pageContent, match.Value,
result.Content);
} }
} }
} }
@@ -465,7 +470,7 @@ namespace ZelWiki.Engine
foreach (var match in orderedMatches) foreach (var match in orderedMatches)
{ {
var result = Engine.CommentHandler.Handle(this, match.Value); var result = Engine.CommentHandler.Handle(this, match.Value);
StoreHandlerResult(result, Constants.WikiMatchType.Comment, pageContent, match.Value, result.Content); StoreHandlerResult(result, WikiMatchType.Comment, pageContent, match.Value, result.Content);
} }
} }
@@ -476,23 +481,23 @@ namespace ZelWiki.Engine
foreach (var match in orderedMatches) foreach (var match in orderedMatches)
{ {
string key = match.Value.Trim().ToLower().Trim('%'); var key = match.Value.Trim().ToLower().Trim('%');
int scale = 100; var scale = 100;
var parts = key.Split(','); var parts = key.Split(',');
if (parts.Length > 1) if (parts.Length > 1)
{ {
key = parts[0]; //Image key; key = parts[0];
scale = int.Parse(parts[1]); //Image scale. scale = int.Parse(parts[1]);
} }
var result = Engine.EmojiHandler.Handle(this, $"%%{key}%%", scale); var result = Engine.EmojiHandler.Handle(this, $"%%{key}%%", scale);
StoreHandlerResult(result, Constants.WikiMatchType.Emoji, pageContent, match.Value, result.Content); StoreHandlerResult(result, WikiMatchType.Emoji, pageContent, match.Value, result.Content);
} }
} }
/// <summary> /// <summary>
/// Transform variables. ///
/// </summary> /// </summary>
/// <param name="pageContent"></param> /// <param name="pageContent"></param>
private void TransformVariables(WikiString pageContent) private void TransformVariables(WikiString pageContent)
@@ -502,7 +507,7 @@ namespace ZelWiki.Engine
foreach (var match in orderedMatches) foreach (var match in orderedMatches)
{ {
string key = match.Value.Trim(['{', '}', ' ', '\t', '$']); var key = match.Value.Trim(['{', '}', ' ', '\t', '$']);
if (key.Contains("=")) if (key.Contains("="))
{ {
var sections = key.Split('='); var sections = key.Split('=');
@@ -514,27 +519,27 @@ namespace ZelWiki.Engine
Variables[key] = value; Variables[key] = value;
} }
var identifier = StoreMatch(Constants.WikiMatchType.Variable, pageContent, match.Value, ""); var identifier = StoreMatch(WikiMatchType.Variable, pageContent, match.Value, "");
pageContent.Replace($"{identifier}\n", $"{identifier}"); //Kill trailing newline. pageContent.Replace($"{identifier}\n", $"{identifier}"); //Kill trailing newline.
} }
else else
{ {
if (Variables.TryGetValue(key, out string? value)) if (Variables.TryGetValue(key, out string? value))
{ {
var identifier = StoreMatch(Constants.WikiMatchType.Variable, pageContent, match.Value, value); var identifier = StoreMatch(WikiMatchType.Variable, pageContent, match.Value, value);
pageContent.Replace($"{identifier}\n", $"{identifier}"); //Kill trailing newline. pageContent.Replace($"{identifier}\n", $"{identifier}"); //Kill trailing newline.
} }
else else
{ {
throw new Exception($"The wiki variable {key} is not defined. It should be set with ##Set() before calling Get()."); throw new Exception(
$"未定义的变量 {key} . 在再用Get()应该先使用##Set().");
} }
} }
} }
} }
/// <summary> /// <summary>
/// Transform links, these can be internal Wiki links or external links. ///
/// </summary> /// </summary>
/// <param name="pageContent"></param> /// <param name="pageContent"></param>
private void TransformLinks(WikiString pageContent) private void TransformLinks(WikiString pageContent)
@@ -572,7 +577,6 @@ namespace ZelWiki.Engine
} }
} }
//Parse external explicit links. eg. [[https://test.net]].
orderedMatches = WikiUtility.OrderMatchesByLengthDescending( orderedMatches = WikiUtility.OrderMatchesByLengthDescending(
PrecompiledRegex.TransformExplicitHTTPsLinks().Matches(pageContent.ToString())); PrecompiledRegex.TransformExplicitHTTPsLinks().Matches(pageContent.ToString()));
@@ -605,7 +609,6 @@ namespace ZelWiki.Engine
} }
} }
//Parse internal links. eg [[About Us]], [[About Us, Learn about us]], etc..
orderedMatches = WikiUtility.OrderMatchesByLengthDescending( orderedMatches = WikiUtility.OrderMatchesByLengthDescending(
PrecompiledRegex.TransformInternalDynamicLinks().Matches(pageContent.ToString())); PrecompiledRegex.TransformInternalDynamicLinks().Matches(pageContent.ToString()));
@@ -622,29 +625,26 @@ namespace ZelWiki.Engine
if (args.Count == 1) if (args.Count == 1)
{ {
//Page navigation only. text = WikiUtility.GetPageNamePart(args[0]);
text = WikiUtility.GetPageNamePart(args[0]); //Text will be page name since we have an image.
pageName = args[0]; pageName = args[0];
} }
else if (args.Count >= 2) else if (args.Count >= 2)
{ {
//Page navigation and explicit text (possibly image).
pageName = args[0]; pageName = args[0];
string imageTag = "image:"; string imageTag = "image:";
if (args[1].StartsWith(imageTag, StringComparison.CurrentCultureIgnoreCase)) if (args[1].StartsWith(imageTag, StringComparison.CurrentCultureIgnoreCase))
{ {
image = args[1].Substring(imageTag.Length).Trim(); image = args[1].Substring(imageTag.Length).Trim();
text = WikiUtility.GetPageNamePart(args[0]); //Text will be page name since we have an image. text = WikiUtility.GetPageNamePart(args[0]);
} }
else else
{ {
text = args[1]; //Explicit text. text = args[1];
} }
if (args.Count >= 3) if (args.Count >= 3)
{ {
//Get the specified image scale.
if (int.TryParse(args[2], out imageScale) == false) if (int.TryParse(args[2], out imageScale) == false)
{ {
imageScale = 100; imageScale = 100;
@@ -659,32 +659,24 @@ namespace ZelWiki.Engine
var pageNavigation = new NamespaceNavigation(pageName); var pageNavigation = new NamespaceNavigation(pageName);
if (pageName.Trim().StartsWith("::"))
{
//The user explicitly specified the root (unnamed) namespace.
}
else if (string.IsNullOrEmpty(pageNavigation.Namespace))
{
//No namespace was specified, use the current page namespace.
pageNavigation.Namespace = Page.Namespace;
}
else
{
//Use the namespace that the user explicitly specified.
}
var result = Engine.InternalLinkHandler.Handle(this, pageNavigation, pageName.Trim(':'), text, image, imageScale); if (string.IsNullOrEmpty(pageNavigation.Namespace) || pageName.Trim().StartsWith("::"))
if (!result.Instructions.Contains(Constants.HandlerResultInstruction.Skip)) pageNavigation.Namespace = Page.Namespace;
var result = Engine.InternalLinkHandler.Handle(this, pageNavigation, pageName.Trim(':'), text, image,
imageScale);
if (!result.Instructions.Contains(HandlerResultInstruction.Skip))
{ {
OutgoingLinks.Add(new PageReference(pageName, pageNavigation.Canonical)); OutgoingLinks.Add(new PageReference(pageName, pageNavigation.Canonical));
} }
StoreHandlerResult(result, Constants.WikiMatchType.Link, pageContent, match.Value, string.Empty); StoreHandlerResult(result, WikiMatchType.Link, pageContent, match.Value, string.Empty);
} }
} }
/// <summary> /// <summary>
/// Transform processing instructions are used to flag pages for specific needs such as deletion, review, draft, etc. ///
/// </summary> /// </summary>
/// <param name="pageContent"></param> /// <param name="pageContent"></param>
private void TransformProcessingInstructionFunctions(WikiString pageContent) private void TransformProcessingInstructionFunctions(WikiString pageContent)
@@ -701,7 +693,8 @@ namespace ZelWiki.Engine
try try
{ {
function = FunctionParser.ParseAndGetFunctionCall(functionHandler.Prototypes, match.Value, out int matchEndIndex); function = FunctionParser.ParseAndGetFunctionCall(functionHandler.Prototypes, match.Value,
out int matchEndIndex);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -712,7 +705,8 @@ namespace ZelWiki.Engine
try try
{ {
var result = functionHandler.Handle(this, function, string.Empty); var result = functionHandler.Handle(this, function, string.Empty);
StoreHandlerResult(result, Constants.WikiMatchType.Instruction, pageContent, match.Value, string.Empty); StoreHandlerResult(result, Constants.WikiMatchType.Instruction, pageContent, match.Value,
string.Empty);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -722,9 +716,10 @@ namespace ZelWiki.Engine
} }
/// <summary> /// <summary>
/// Transform functions is used to call wiki functions such as including template pages, setting tags and displaying images. ///
/// </summary> /// </summary>
/// <param name="pageContent"></param> /// <param name="pageContent"></param>
/// <param name="isFirstChance"></param>
private void TransformStandardFunctions(WikiString pageContent, bool isFirstChance) private void TransformStandardFunctions(WikiString pageContent, bool isFirstChance)
{ {
//Remove the last "(\#\#[\w-]+)" if you start to have matching problems: //Remove the last "(\#\#[\w-]+)" if you start to have matching problems:
@@ -739,7 +734,8 @@ namespace ZelWiki.Engine
try try
{ {
function = FunctionParser.ParseAndGetFunctionCall(functionHandler.Prototypes, match.Value, out int matchEndIndex); function = FunctionParser.ParseAndGetFunctionCall(functionHandler.Prototypes, match.Value,
out int matchEndIndex);
} }
catch (WikiFunctionPrototypeNotDefinedException ex) catch (WikiFunctionPrototypeNotDefinedException ex)
{ {
@@ -753,6 +749,7 @@ namespace ZelWiki.Engine
continue; //This IS a function, but it is meant to be parsed at the end of processing. continue; //This IS a function, but it is meant to be parsed at the end of processing.
} }
} }
StoreError(pageContent, match.Value, ex.Message); StoreError(pageContent, match.Value, ex.Message);
continue; continue;
} }
@@ -771,7 +768,8 @@ namespace ZelWiki.Engine
try try
{ {
var result = functionHandler.Handle(this, function, string.Empty); var result = functionHandler.Handle(this, function, string.Empty);
StoreHandlerResult(result, Constants.WikiMatchType.StandardFunction, pageContent, match.Value, string.Empty); StoreHandlerResult(result, Constants.WikiMatchType.StandardFunction, pageContent, match.Value,
string.Empty);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -781,8 +779,9 @@ namespace ZelWiki.Engine
} }
/// <summary> /// <summary>
/// Transform post-process are functions that must be called after all other transformations. For example, we can't build a table-of-contents until we have parsed the entire page. ///
/// </summary> /// </summary>
/// <param name="pageContent"></param>
private void TransformPostProcessingFunctions(WikiString pageContent) private void TransformPostProcessingFunctions(WikiString pageContent)
{ {
//Remove the last "(\#\#[\w-]+)" if you start to have matching problems: //Remove the last "(\#\#[\w-]+)" if you start to have matching problems:
@@ -797,7 +796,8 @@ namespace ZelWiki.Engine
try try
{ {
function = FunctionParser.ParseAndGetFunctionCall(functionHandler.Prototypes, match.Value, out int matchEndIndex); function = FunctionParser.ParseAndGetFunctionCall(functionHandler.Prototypes, match.Value,
out int matchEndIndex);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -808,7 +808,8 @@ namespace ZelWiki.Engine
try try
{ {
var result = functionHandler.Handle(this, function, string.Empty); var result = functionHandler.Handle(this, function, string.Empty);
StoreHandlerResult(result, Constants.WikiMatchType.StandardFunction, pageContent, match.Value, string.Empty); StoreHandlerResult(result, Constants.WikiMatchType.StandardFunction, pageContent, match.Value,
string.Empty);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -821,33 +822,31 @@ namespace ZelWiki.Engine
{ {
string identifier = $"<!--{Guid.NewGuid()}-->"; string identifier = $"<!--{Guid.NewGuid()}-->";
//Replace new-lines with single character new line:
pageContent.Replace("\r\n", "\n"); pageContent.Replace("\r\n", "\n");
//Replace new-lines with an identifier so we can identify the places we are going to introduce line-breaks:
pageContent.Replace("\n", identifier); pageContent.Replace("\n", identifier);
//Replace any consecutive to-be-line-breaks that we are introducing with single line-break identifiers.
pageContent.Replace($"{identifier}{identifier}", identifier); pageContent.Replace($"{identifier}{identifier}", identifier);
//Swap in the real line-breaks.
pageContent.Replace(identifier, "<br />"); pageContent.Replace(identifier, "<br />");
} }
#region Utility. #region Utility.
private void StoreHandlerResult(HandlerResult result, Constants.WikiMatchType matchType, WikiString pageContent, string matchValue, string scopeBody) private void StoreHandlerResult(HandlerResult result, WikiMatchType matchType, WikiString pageContent,
string matchValue, string scopeBody)
{ {
if (result.Instructions.Contains(Constants.HandlerResultInstruction.Skip)) if (result.Instructions.Contains(HandlerResultInstruction.Skip))
{ {
return; return;
} }
bool allowNestedDecode = !result.Instructions.Contains(Constants.HandlerResultInstruction.DisallowNestedProcessing); bool allowNestedDecode =
!result.Instructions.Contains(HandlerResultInstruction.DisallowNestedProcessing);
string identifier; string identifier;
if (result.Instructions.Contains(Constants.HandlerResultInstruction.OnlyReplaceFirstMatch)) if (result.Instructions.Contains(HandlerResultInstruction.OnlyReplaceFirstMatch))
{ {
identifier = StoreFirstMatch(matchType, pageContent, matchValue, result.Content, allowNestedDecode); identifier = StoreFirstMatch(matchType, pageContent, matchValue, result.Content, allowNestedDecode);
} }
@@ -860,7 +859,7 @@ namespace ZelWiki.Engine
{ {
switch (instruction) switch (instruction)
{ {
case Constants.HandlerResultInstruction.TruncateTrailingLine: case HandlerResultInstruction.TruncateTrailingLine:
pageContent.Replace($"{identifier}\n", $"{identifier}"); //Kill trailing newline. pageContent.Replace($"{identifier}\n", $"{identifier}"); //Kill trailing newline.
break; break;
} }
@@ -888,7 +887,7 @@ namespace ZelWiki.Engine
{ {
Content = $"<i><font size=\"3\" color=\"#BB0000\">{{{value}}}</font></a>", Content = $"<i><font size=\"3\" color=\"#BB0000\">{{{value}}}</font></a>",
AllowNestedDecode = false, AllowNestedDecode = false,
MatchType = Constants.WikiMatchType.Error MatchType = WikiMatchType.Error
}; };
Matches.Add(identifier, matchSet); Matches.Add(identifier, matchSet);
@@ -897,7 +896,8 @@ namespace ZelWiki.Engine
return identifier; return identifier;
} }
private string StoreMatch(Constants.WikiMatchType matchType, WikiString pageContent, string match, string value, bool allowNestedDecode = true) private string StoreMatch(WikiMatchType matchType, WikiString pageContent, string match, string value,
bool allowNestedDecode = true)
{ {
MatchCount++; MatchCount++;
_matchesStoredPerIteration++; _matchesStoredPerIteration++;
@@ -917,7 +917,8 @@ namespace ZelWiki.Engine
return identifier; return identifier;
} }
private string StoreFirstMatch(Constants.WikiMatchType matchType, WikiString pageContent, string match, string value, bool allowNestedDecode = true) private string StoreFirstMatch(WikiMatchType matchType, WikiString pageContent, string match,
string value, bool allowNestedDecode = true)
{ {
MatchCount++; MatchCount++;
_matchesStoredPerIteration++; _matchesStoredPerIteration++;
@@ -940,15 +941,14 @@ namespace ZelWiki.Engine
} }
/// <summary> /// <summary>
/// Used to generate unique and regenerable tokens so different wikification process can identify ///
/// their own query strings. For instance, we can have more than one pager on a wiki page, this
/// allows each pager to track its own current page in the query string.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public string GetNextQueryToken() public string GetNextQueryToken()
{ {
_queryTokenHash = ZelWiki.Security.Helpers.Sha256(ZelWiki.Security.Helpers.EncryptString(ZelWiki.Security.Helpers.MachineKey, _queryTokenHash)); _queryTokenHash = Security.Helpers.Sha256(
return $"H{ZelWiki.Security.Helpers.Crc32(_queryTokenHash)}"; Security.Helpers.EncryptString(Security.Helpers.MachineKey, _queryTokenHash));
return $"H{Security.Helpers.Crc32(_queryTokenHash)}";
} }
#endregion #endregion

View File

@@ -6,14 +6,14 @@ namespace ZelWiki.Library
public static class ConfirmActionHelper public static class ConfirmActionHelper
{ {
/// <summary> /// <summary>
/// Generates a link that navigates via GET to a "confirm action" page where the yes link is RED, but the NO button is still GREEN. /// 生成一个链接通过GET导航到“确认操作”页面其中“是”链接为红色但“否”按钮仍为绿色。
/// </summary> /// </summary>
/// <param name="message">The message to be displayed.</param> /// <param name="basePath"></param>
/// <param name="linkLabel">the label for the link that will redirect to this confirm action page.</param> /// <param name="message"></param>
/// <param name="controllerURL">The URL which will handle the click of the "yes" or "no" for the confirm action page.</param> /// <param name="linkLabel"></param>
/// <param name="parameter">An optional parameter to pass to the page and controller function.</param> /// <param name="controllerURL"></param>
/// <param name="yesOrDefaultRedirectURL">The URL to redirect to AFTER the controller has been called if the user selected YES (or NO, if the NO link is not specified.</param> /// <param name="yesOrDefaultRedirectURL"></param>
/// <param name="noRedirectURL">The URL to redirect to AFTER the controller has been called if the user selected NO, if not specified, the same link that is provided to yesOrDefaultRedirectURL is used.</param> /// <param name="noRedirectURL"></param>
/// <returns></returns> /// <returns></returns>
public static string GenerateDangerLink(string basePath, string message, string linkLabel, string controllerURL, public static string GenerateDangerLink(string basePath, string message, string linkLabel, string controllerURL,
string? yesOrDefaultRedirectURL, string? noRedirectURL = null) string? yesOrDefaultRedirectURL, string? noRedirectURL = null)
@@ -30,18 +30,19 @@ namespace ZelWiki.Library
param.Append($"&Message={Uri.EscapeDataString(message)}"); param.Append($"&Message={Uri.EscapeDataString(message)}");
param.Append($"&Style=Danger"); param.Append($"&Style=Danger");
return $"<a class=\"btn btn-danger btn-thin\" href=\"{basePath}/Utility/ConfirmAction?{param}\">{linkLabel}</a>"; return
$"<a class=\"btn btn-danger btn-thin\" href=\"{basePath}/Utility/ConfirmAction?{param}\">{linkLabel}</a>";
} }
/// <summary> /// <summary>
/// Generates a link that navigates via GET to a "confirm action" page where the yes link is GREEN. /// 生成一个链接通过GET导航到“确认操作”页面其中“是”链接为绿色。
/// </summary> /// </summary>
/// <param name="message">The message to be displayed.</param> /// <param name="basePath"></param>
/// <param name="linkLabel">the label for the link that will redirect to this confirm action page.</param> /// <param name="message"></param>
/// <param name="controllerURL">The URL which will handle the click of the "yes" or "no" for the confirm action page.</param> /// <param name="linkLabel"></param>
/// <param name="parameter">An optional parameter to pass to the page and controller function.</param> /// <param name="controllerURL"></param>
/// <param name="yesOrDefaultRedirectURL">The URL to redirect to AFTER the controller has been called if the user selected YES (or NO, if the NO link is not specified.</param> /// <param name="yesOrDefaultRedirectURL"></param>
/// <param name="noRedirectURL">The URL to redirect to AFTER the controller has been called if the user selected NO, if not specified, the same link that is provided to yesOrDefaultRedirectURL is used.</param> /// <param name="noRedirectURL"></param>
/// <returns></returns> /// <returns></returns>
public static string GenerateSafeLink(string basePath, string message, string linkLabel, string controllerURL, public static string GenerateSafeLink(string basePath, string message, string linkLabel, string controllerURL,
string? yesOrDefaultRedirectURL, string? noRedirectURL = null) string? yesOrDefaultRedirectURL, string? noRedirectURL = null)
@@ -58,18 +59,19 @@ namespace ZelWiki.Library
param.Append($"&Message={Uri.EscapeDataString(message)}"); param.Append($"&Message={Uri.EscapeDataString(message)}");
param.Append($"&Style=Safe"); param.Append($"&Style=Safe");
return $"<a class=\"btn btn-success btn-thin\" href=\"{basePath}/Utility/ConfirmAction?{param}\">{linkLabel}</a>"; return
$"<a class=\"btn btn-success btn-thin\" href=\"{basePath}/Utility/ConfirmAction?{param}\">{linkLabel}</a>";
} }
/// <summary> /// <summary>
/// Generates a link that navigates via GET to a "confirm action" page where the yes link is YELLOW, but the NO button is still GREEN. /// 生成一个链接通过GET导航到“确认操作”页面其中“是”链接为黄色但“否”按钮仍为绿色。
/// </summary> /// </summary>
/// <param name="message">The message to be displayed.</param> /// <param name="basePath"></param>
/// <param name="linkLabel">the label for the link that will redirect to this confirm action page.</param> /// <param name="message"></param>
/// <param name="controllerURL">The URL which will handle the click of the "yes" or "no" for the confirm action page.</param> /// <param name="linkLabel"></param>
/// <param name="parameter">An optional parameter to pass to the page and controller function.</param> /// <param name="controllerURL"></param>
/// <param name="yesOrDefaultRedirectURL">The URL to redirect to AFTER the controller has been called if the user selected YES (or NO, if the NO link is not specified.</param> /// <param name="yesOrDefaultRedirectURL"></param>
/// <param name="noRedirectURL">The URL to redirect to AFTER the controller has been called if the user selected NO, if not specified, the same link that is provided to yesOrDefaultRedirectURL is used.</param> /// <param name="noRedirectURL"></param>
/// <returns></returns> /// <returns></returns>
public static string GenerateWarnLink(string basePath, string message, string linkLabel, string controllerURL, public static string GenerateWarnLink(string basePath, string message, string linkLabel, string controllerURL,
string? yesOrDefaultRedirectURL, string? noRedirectURL = null) string? yesOrDefaultRedirectURL, string? noRedirectURL = null)
@@ -86,19 +88,19 @@ namespace ZelWiki.Library
param.Append($"&Message={Uri.EscapeDataString(message)}"); param.Append($"&Message={Uri.EscapeDataString(message)}");
param.Append($"&Style=Warn"); param.Append($"&Style=Warn");
return $"<a class=\"btn btn-warning btn-thin\" href=\"{basePath}/Utility/ConfirmAction?{param}\">{linkLabel}</a>"; return
$"<a class=\"btn btn-warning btn-thin\" href=\"{basePath}/Utility/ConfirmAction?{param}\">{linkLabel}</a>";
} }
/*
/// <summary> /// <summary>
/// Generates a link that navigates via POST to a "confirm action" page where the yes button is RED, but the NO button is still GREEN. ///
/// </summary> /// </summary>
/// <param name="message">The message to be displayed.</param> /// <param name="message"></param>
/// <param name="buttonLabel">the label for the button that will redirect to this confirm action page.</param> /// <param name="buttonLabel"></param>
/// <param name="controllerURL">The URL which will handle the click of the "yes" or "no" for the confirm action page.</param> /// <param name="controllerURL"></param>
/// <param name="parameter">An optional parameter to pass to the page and controller function.</param> /// <param name="yesOrDefaultRedirectURL"></param>
/// <param name="yesOrDefaultRedirectURL">The URL to redirect to AFTER the controller has been called if the user selected YES (or NO, if the NO link is not specified.</param> /// <param name="noRedirectURL"></param>
/// <param name="noRedirectURL">The URL to redirect to AFTER the controller has been called if the user selected NO, if not specified, the same link that is provided to yesOrDefaultRedirectURL is used.</param>
/// <returns></returns> /// <returns></returns>
public static string GenerateDangerButton(string message, string buttonLabel, string controllerURL, public static string GenerateDangerButton(string message, string buttonLabel, string controllerURL,
string? yesOrDefaultRedirectURL, string? noRedirectURL = null) string? yesOrDefaultRedirectURL, string? noRedirectURL = null)
@@ -115,21 +117,22 @@ namespace ZelWiki.Library
html.Append($"<input type='hidden' name='NoRedirectURL' value='{noRedirectURL}' />"); html.Append($"<input type='hidden' name='NoRedirectURL' value='{noRedirectURL}' />");
html.Append($"<input type='hidden' name='Message' value='{message}' />"); html.Append($"<input type='hidden' name='Message' value='{message}' />");
html.Append($"<input type='hidden' name='Style' value='Danger' />"); html.Append($"<input type='hidden' name='Style' value='Danger' />");
html.Append($"<button type='submit' class='btn btn-danger rounded-0' name='ActionToConfirm' value='PurgeDeletedPages'>{buttonLabel}</button>"); html.Append(
$"<button type='submit' class='btn btn-danger rounded-0' name='ActionToConfirm' value='PurgeDeletedPages'>{buttonLabel}</button>");
html.Append("</form>"); html.Append("</form>");
return html.ToString(); return html.ToString();
} }
/// <summary> /// <summary>
/// Generates a link that navigates via POST to a "confirm action" page where the yes and no buttons are GREEN. ///
/// </summary> /// </summary>
/// <param name="message">The message to be displayed.</param> /// <param name="message"></param>
/// <param name="buttonLabel">the label for the button that will redirect to this confirm action page.</param> /// <param name="buttonLabel"></param>
/// <param name="controllerURL">The URL which will handle the click of the "yes" or "no" for the confirm action page.</param> /// <param name="controllerURL"></param>
/// <param name="parameter">An optional parameter to pass to the page and controller function.</param> /// <param name="yesOrDefaultRedirectURL"></param>
/// <param name="yesOrDefaultRedirectURL">The URL to redirect to AFTER the controller has been called if the user selected YES (or NO, if the NO link is not specified.</param> /// <param name="noRedirectURL"></param>
/// <param name="noRedirectURL">The URL to redirect to AFTER the controller has been called if the user selected NO, if not specified, the same link that is provided to yesOrDefaultRedirectURL is used.</param> /// <returns></returns>
public static string GenerateSafeButton(string message, string buttonLabel, string controllerURL, public static string GenerateSafeButton(string message, string buttonLabel, string controllerURL,
string? yesOrDefaultRedirectURL, string? noRedirectURL = null) string? yesOrDefaultRedirectURL, string? noRedirectURL = null)
{ {
@@ -146,21 +149,22 @@ namespace ZelWiki.Library
html.Append($"<input type='hidden' name='NoRedirectURL' value='{noRedirectURL}' />"); html.Append($"<input type='hidden' name='NoRedirectURL' value='{noRedirectURL}' />");
html.Append($"<input type='hidden' name='Message' value='{message}' />"); html.Append($"<input type='hidden' name='Message' value='{message}' />");
html.Append($"<input type='hidden' name='Style' value='Safe' />"); html.Append($"<input type='hidden' name='Style' value='Safe' />");
html.Append($"<button type='submit' class='btn btn-success rounded-0' name='ActionToConfirm' value='PurgeDeletedPages'>{buttonLabel}</button>"); html.Append(
$"<button type='submit' class='btn btn-success rounded-0' name='ActionToConfirm' value='PurgeDeletedPages'>{buttonLabel}</button>");
html.Append("</form>"); html.Append("</form>");
return html.ToString(); return html.ToString();
} }
/// <summary> /// <summary>
/// Generates a link that navigates via POST to a "confirm action" page where the yes button is YELLOW, but the NO button is still GREEN. ///
/// </summary> /// </summary>
/// <param name="message">The message to be displayed.</param> /// <param name="message"></param>
/// <param name="buttonLabel">the label for the button that will redirect to this confirm action page.</param> /// <param name="buttonLabel"></param>
/// <param name="controllerURL">The URL which will handle the click of the "yes" or "no" for the confirm action page.</param> /// <param name="controllerURL"></param>
/// <param name="parameter">An optional parameter to pass to the page and controller function.</param> /// <param name="yesOrDefaultRedirectURL"></param>
/// <param name="yesOrDefaultRedirectURL">The URL to redirect to AFTER the controller has been called if the user selected YES (or NO, if the NO link is not specified.</param> /// <param name="noRedirectURL"></param>
/// <param name="noRedirectURL">The URL to redirect to AFTER the controller has been called if the user selected NO, if not specified, the same link that is provided to yesOrDefaultRedirectURL is used.</param> /// <returns></returns>
public static string GenerateWarnButton(string message, string buttonLabel, string controllerURL, public static string GenerateWarnButton(string message, string buttonLabel, string controllerURL,
string? yesOrDefaultRedirectURL, string? noRedirectURL = null) string? yesOrDefaultRedirectURL, string? noRedirectURL = null)
{ {
@@ -177,11 +181,11 @@ namespace ZelWiki.Library
html.Append($"<input type='hidden' name='NoRedirectURL' value='{noRedirectURL}' />"); html.Append($"<input type='hidden' name='NoRedirectURL' value='{noRedirectURL}' />");
html.Append($"<input type='hidden' name='Message' value='{message}' />"); html.Append($"<input type='hidden' name='Message' value='{message}' />");
html.Append($"<input type='hidden' name='Style' value='Warn' />"); html.Append($"<input type='hidden' name='Style' value='Warn' />");
html.Append($"<button type='submit' class='btn btn-warning rounded-0' name='ActionToConfirm' value='PurgeDeletedPages'>{buttonLabel}</button>"); html.Append(
$"<button type='submit' class='btn btn-warning rounded-0' name='ActionToConfirm' value='PurgeDeletedPages'>{buttonLabel}</button>");
html.Append("</form>"); html.Append("</form>");
return html.ToString(); return html.ToString();
} }
*/
} }
} }

View File

@@ -3,9 +3,9 @@
public static class Constants public static class Constants
{ {
public const string CRYPTOCHECK = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; public const string CRYPTOCHECK = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public const string DEFAULTUSERNAME = "admin@tightwiki.com"; public const string DEFAULTUSERNAME = "admin@ipangci.top";
public const string DEFAULTACCOUNT = "admin"; public const string DEFAULTACCOUNT = "admin";
public const string DEFAULTPASSWORD = "2Tight2Wiki@"; public const string DEFAULTPASSWORD = "Zhu0906.";
public enum WikiTheme public enum WikiTheme
{ {
@@ -16,15 +16,15 @@
public enum AdminPasswordChangeState public enum AdminPasswordChangeState
{ {
/// <summary> /// <summary>
/// The password has not been changed, display a big warning. /// 密码为默认
/// </summary> /// </summary>
IsDefault, IsDefault,
/// <summary> /// <summary>
/// All is well! /// 已修改密码
/// </summary> /// </summary>
HasBeenChanged, HasBeenChanged,
/// <summary> /// <summary>
/// The default password status does not exist and the password needs to be set to default. /// 默认密码状态不存在,需要将密码设置为默认值
/// </summary> /// </summary>
NeedsToBeSet NeedsToBeSet
} }
@@ -45,19 +45,19 @@
public static class Roles public static class Roles
{ {
/// <summary> /// <summary>
/// Administrators can do anything. Add, edit, delete, pages, users, etc. ///管理员可以做任何事情。添加、编辑、删除、页面、用户等。
/// </summary> /// </summary>
public const string Administrator = "Administrator"; public const string Administrator = "Administrator";
/// <summary> /// <summary>
/// Read-only user with a profile. /// 成员
/// </summary> /// </summary>
public const string Member = "Member"; public const string Member = "Member";
/// <summary> /// <summary>
/// Contributor can add and edit pages. /// 版主
/// </summary> /// </summary>
public const string Contributor = "Contributor"; public const string Contributor = "Contributor";
/// <summary> /// <summary>
/// Moderators can add, edit and delete pages. /// 主持
/// </summary> /// </summary>
public const string Moderator = "Moderator"; public const string Moderator = "Moderator";
} }

View File

@@ -14,7 +14,7 @@ namespace ZelWiki.Library
foreach (var ci in CultureInfo.GetCultures(CultureTypes.SpecificCultures)) foreach (var ci in CultureInfo.GetCultures(CultureTypes.SpecificCultures))
{ {
var regionInfo = new RegionInfo(ci.Name); var regionInfo = new RegionInfo(ci.Name);
if (list.Where(o => o.Value == regionInfo.Name).Any() == false) if (list.Any(o => o.Value == regionInfo.Name) == false)
{ {
list.Add(new CountryItem list.Add(new CountryItem
{ {

View File

@@ -57,7 +57,7 @@ namespace ZelWiki.Library
image.SaveAsBmp(ms); image.SaveAsBmp(ms);
return preferredContentType; return preferredContentType;
case "image/gif": case "image/gif":
throw new NotImplementedException("Use [ResizeGifImage] for saving animated images."); throw new NotImplementedException("使用 [ResizeGifImage] 保存动图");
//image.SaveAsGif(ms); //image.SaveAsGif(ms);
//return preferredContentType; //return preferredContentType;
case "image/tiff": case "image/tiff":

View File

@@ -9,32 +9,32 @@ namespace ZelWiki.Library.Interfaces
IQueryCollection? QueryString { get; set; } IQueryCollection? QueryString { get; set; }
/// <summary> /// <summary>
/// Is the current user (or anonymous) allowed to view? ///
/// </summary> /// </summary>
public bool CanView => true; public bool CanView => true;
/// <summary> /// <summary>
/// Is the current user allowed to edit? ///
/// </summary> /// </summary>
public bool CanEdit { get; } public bool CanEdit { get; }
/// <summary> /// <summary>
/// Is the current user allowed to perform administrative functions? /// 是否允许当前用户执行管理功能?
/// </summary> /// </summary>
public bool CanAdmin { get; } public bool CanAdmin { get; }
/// <summary> /// <summary>
/// Is the current user allowed to moderate content (such as delete comments, and view moderation tools)? /// 是否允许当前用户审核内容(如删除评论和查看审核工具)?
/// </summary> /// </summary>
public bool CanModerate { get; } public bool CanModerate { get; }
/// <summary> /// <summary>
/// Is the current user allowed to create pages? ///是否允许当前用户创建页面?
/// </summary> /// </summary>
public bool CanCreate { get; } public bool CanCreate { get; }
/// <summary> /// <summary>
/// Is the current user allowed to delete unprotected pages? /// 是否允许当前用户删除未受保护的页面?
/// </summary> /// </summary>
public bool CanDelete { get; } public bool CanDelete { get; }

View File

@@ -21,7 +21,7 @@ namespace ZelWiki.Library
name = name.Substring(0, name.IndexOf('(')).Trim(); name = name.Substring(0, name.IndexOf('(')).Trim();
} }
if (list.Where(o => o.Value == name).Any() == false) if (list.Any(o => o.Value == name) == false)
{ {
list.Add(new LanguageItem list.Add(new LanguageItem
{ {

View File

@@ -5,14 +5,14 @@ namespace ZelWiki.Library
internal static class MimeTypes internal static class MimeTypes
{ {
/// <summary> /// <summary>
/// Given a file path, determine the MIME type /// 给定文件路径确定MIME类型
/// </summary> /// </summary>
/// <param name="subpath">A file path</param> /// <param name="filePath">A file path</param>
/// <param name="contentType">The resulting MIME type</param> /// <param name="contentType">The resulting MIME type</param>
/// <returns>True if MIME type could be determined</returns> /// <returns>True if MIME type could be determined</returns>
public static bool TryGetContentType(string filePath, [MaybeNullWhen(false)] out string contentType) public static bool TryGetContentType(string filePath, [MaybeNullWhen(false)] out string contentType)
{ {
string extension = Path.GetExtension(filePath); var extension = Path.GetExtension(filePath);
if (extension == null) if (extension == null)
{ {
contentType = null; contentType = null;
@@ -21,7 +21,6 @@ namespace ZelWiki.Library
return Collection.TryGetValue(extension, out contentType); return Collection.TryGetValue(extension, out contentType);
} }
//Borrowed from FileExtensionContentTypeProvider().TryGetContentType
public static Dictionary<string, string> Collection = new(StringComparer.OrdinalIgnoreCase) public static Dictionary<string, string> Collection = new(StringComparer.OrdinalIgnoreCase)
{ {
{ ".323", "text/h323" }, { ".323", "text/h323" },
@@ -78,7 +77,7 @@ namespace ZelWiki.Library
{ ".crt", "application/x-x509-ca-cert" }, { ".crt", "application/x-x509-ca-cert" },
{ ".csh", "application/x-csh" }, { ".csh", "application/x-csh" },
{ ".css", "text/css" }, { ".css", "text/css" },
{ ".csv", "text/csv" }, // https://tools.ietf.org/html/rfc7111#section-5.1 { ".csv", "text/csv" },
{ ".cur", "application/octet-stream" }, { ".cur", "application/octet-stream" },
{ ".dcr", "application/x-director" }, { ".dcr", "application/x-director" },
{ ".deploy", "application/octet-stream" }, { ".deploy", "application/octet-stream" },
@@ -106,7 +105,7 @@ namespace ZelWiki.Library
{ ".eps", "application/postscript" }, { ".eps", "application/postscript" },
{ ".etx", "text/x-setext" }, { ".etx", "text/x-setext" },
{ ".evy", "application/envoy" }, { ".evy", "application/envoy" },
{ ".exe", "application/vnd.microsoft.portable-executable" }, // https://www.iana.org/assignments/media-types/application/vnd.microsoft.portable-executable { ".exe", "application/vnd.microsoft.portable-executable" },
{ ".fdf", "application/vnd.fdf" }, { ".fdf", "application/vnd.fdf" },
{ ".fif", "application/fractals" }, { ".fif", "application/fractals" },
{ ".fla", "application/octet-stream" }, { ".fla", "application/octet-stream" },
@@ -347,7 +346,7 @@ namespace ZelWiki.Library
{ ".wcm", "application/vnd.ms-works" }, { ".wcm", "application/vnd.ms-works" },
{ ".wdb", "application/vnd.ms-works" }, { ".wdb", "application/vnd.ms-works" },
{ ".webm", "video/webm" }, { ".webm", "video/webm" },
{ ".webmanifest", "application/manifest+json" }, // https://w3c.github.io/manifest/#media-type-registration { ".webmanifest", "application/manifest+json" },
{ ".webp", "image/webp" }, { ".webp", "image/webp" },
{ ".wks", "application/vnd.ms-works" }, { ".wks", "application/vnd.ms-works" },
{ ".wm", "video/x-ms-wm" }, { ".wm", "video/x-ms-wm" },
@@ -362,8 +361,8 @@ namespace ZelWiki.Library
{ ".wmv", "video/x-ms-wmv" }, { ".wmv", "video/x-ms-wmv" },
{ ".wmx", "video/x-ms-wmx" }, { ".wmx", "video/x-ms-wmx" },
{ ".wmz", "application/x-ms-wmz" }, { ".wmz", "application/x-ms-wmz" },
{ ".woff", "application/font-woff" }, // https://www.w3.org/TR/WOFF/#appendix-b { ".woff", "application/font-woff" },
{ ".woff2", "font/woff2" }, // https://www.w3.org/TR/WOFF2/#IMT { ".woff2", "font/woff2" },
{ ".wps", "application/vnd.ms-works" }, { ".wps", "application/vnd.ms-works" },
{ ".wri", "application/x-mswrite" }, { ".wri", "application/x-mswrite" },
{ ".wrl", "x-world/x-vrml" }, { ".wrl", "x-world/x-vrml" },

View File

@@ -29,6 +29,7 @@ namespace ZelWiki.Library
{ {
return Page; return Page;
} }
return $"{Namespace}::{Page}"; return $"{Namespace}::{Page}";
} }
set set
@@ -49,9 +50,9 @@ namespace ZelWiki.Library
} }
/// <summary> /// <summary>
/// Creates a new instance of NamespaceNavigation. ///
/// </summary> /// </summary>
/// <param name="givenCanonical">Page navigation with optional namespace.</param> /// <param name="givenCanonical"></param>
public NamespaceNavigation(string givenCanonical) public NamespaceNavigation(string givenCanonical)
{ {
_lowerCase = true; _lowerCase = true;
@@ -59,10 +60,10 @@ namespace ZelWiki.Library
} }
/// <summary> /// <summary>
/// Creates a new instance of NamespaceNavigation. ///
/// </summary> /// </summary>
/// <param name="givenCanonical">Page navigation with optional namespace.</param> /// <param name="givenCanonical"></param>
/// <param name="lowerCase">If false, the namespace and page name will not be lowercased.</param> /// <param name="lowerCase"></param>
public NamespaceNavigation(string givenCanonical, bool lowerCase) public NamespaceNavigation(string givenCanonical, bool lowerCase)
{ {
_lowerCase = lowerCase; _lowerCase = lowerCase;
@@ -75,10 +76,10 @@ namespace ZelWiki.Library
} }
/// <summary> /// <summary>
/// Takes a page name with optional namespace and returns the cleaned version that can be used for matching Navigations. ///
/// </summary> /// </summary>
/// <param name="givenCanonical">Page navigation with optional namespace.</param> /// <param name="str"></param>
/// <param name="lowerCase">If false, the namespace and page name will not be lowercased.</param> /// <param name="lowerCase"></param>
/// <returns></returns> /// <returns></returns>
/// <exception cref="Exception"></exception> /// <exception cref="Exception"></exception>
public static string CleanAndValidate(string? str, bool lowerCase = true) public static string CleanAndValidate(string? str, bool lowerCase = true)
@@ -88,7 +89,6 @@ namespace ZelWiki.Library
return string.Empty; return string.Empty;
} }
//Fix names like "::Page" or "Namespace::".
str = str.Trim().Trim([':']).Trim(); str = str.Trim().Trim([':']).Trim();
if (str.Contains("::")) if (str.Contains("::"))
@@ -96,23 +96,22 @@ namespace ZelWiki.Library
var parts = str.Split("::"); var parts = str.Split("::");
if (parts.Length != 2) if (parts.Length != 2)
{ {
throw new Exception("Navigation can not contain more than one namespace."); throw new Exception("导航不能包含多个命名空间");
} }
return $"{CleanAndValidate(parts[0].Trim())}::{CleanAndValidate(parts[1].Trim(), lowerCase)}"; return $"{CleanAndValidate(parts[0].Trim())}::{CleanAndValidate(parts[1].Trim(), lowerCase)}";
} }
// Decode common HTML entities
str = str.Replace("&quot;", "\"") str = str.Replace("&quot;", "\"")
.Replace("&amp;", "&") .Replace("&amp;", "&")
.Replace("&lt;", "<") .Replace("&lt;", "<")
.Replace("&gt;", ">") .Replace("&gt;", ">")
.Replace("&nbsp;", " "); .Replace("&nbsp;", " ");
// Normalize backslashes to forward slashes
str = str.Replace('\\', '/'); str = str.Replace('\\', '/');
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach (char c in str) foreach (var c in str)
{ {
if (char.IsWhiteSpace(c) || c == '.') if (char.IsWhiteSpace(c) || c == '.')
{ {
@@ -124,21 +123,13 @@ namespace ZelWiki.Library
} }
} }
string result = sb.ToString(); var result = sb.ToString();
// Remove multiple consecutive underscores or slashes
result = Regex.Replace(result, @"[_]{2,}", "_"); result = Regex.Replace(result, @"[_]{2,}", "_");
result = Regex.Replace(result, @"[/]{2,}", "/"); result = Regex.Replace(result, @"[/]{2,}", "/");
if (lowerCase) return lowerCase ? result.TrimEnd(['/', '\\']).ToLowerInvariant() : result.TrimEnd(['/', '\\']);
{
return result.TrimEnd(['/', '\\']).ToLowerInvariant();
}
else
{
return result.TrimEnd(['/', '\\']);
}
} }
} }
} }

View File

@@ -3,6 +3,9 @@ using System.Text.RegularExpressions;
namespace ZelWiki.Library namespace ZelWiki.Library
{ {
/// <summary>
/// 导航
/// </summary>
public class Navigation public class Navigation
{ {
public static string Clean(string? str) public static string Clean(string? str)
@@ -12,29 +15,25 @@ namespace ZelWiki.Library
return string.Empty; return string.Empty;
} }
//Fix names like "::Page" or "Namespace::".
str = str.Trim().Trim([':']).Trim(); str = str.Trim().Trim([':']).Trim();
if (str.Contains("::")) if (str.Contains("::"))
{ {
throw new Exception("Navigation can not contain a namespace."); throw new Exception("导航不能包含命名空间");
} }
// Decode common HTML entities
str = str.Replace("&quot;", "\"") str = str.Replace("&quot;", "\"")
.Replace("&amp;", "&") .Replace("&amp;", "&")
.Replace("&lt;", "<") .Replace("&lt;", "<")
.Replace("&gt;", ">") .Replace("&gt;", ">")
.Replace("&nbsp;", " "); .Replace("&nbsp;", " ");
// Normalize backslashes to forward slashes
str = str.Replace('\\', '/'); str = str.Replace('\\', '/');
// Replace special sequences
str = str.Replace("::", "_").Trim(); str = str.Replace("::", "_").Trim();
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach (char c in str) foreach (var c in str)
{ {
if (char.IsWhiteSpace(c) || c == '.') if (char.IsWhiteSpace(c) || c == '.')
{ {
@@ -46,9 +45,8 @@ namespace ZelWiki.Library
} }
} }
string result = sb.ToString(); var result = sb.ToString();
// Remove multiple consecutive underscores or slashes
result = Regex.Replace(result, @"[_]{2,}", "_"); result = Regex.Replace(result, @"[_]{2,}", "_");
result = Regex.Replace(result, @"[/]{2,}", "/"); result = Regex.Replace(result, @"[/]{2,}", "/");

View File

@@ -42,25 +42,25 @@ namespace ZelWiki.Library
sb.Append($"<center>"); sb.Append($"<center>");
if (currentPage > 1) if (currentPage > 1)
{ {
sb.Append($"<a href=\"{url}?{QueryStringConverter.FromCollection(firstPage)}\">&lt;&lt; First</a>"); sb.Append($"<a href=\"{url}?{QueryStringConverter.FromCollection(firstPage)}\">&lt;&lt; 首页</a>");
sb.Append("&nbsp; | &nbsp;"); sb.Append("&nbsp; | &nbsp;");
sb.Append($"<a href=\"{url}?{QueryStringConverter.FromCollection(prevPage)}\">&lt; Previous</a>"); sb.Append($"<a href=\"{url}?{QueryStringConverter.FromCollection(prevPage)}\">&lt; 上一页</a>");
} }
else else
{ {
sb.Append($"&lt;&lt; First &nbsp; | &nbsp; &lt; Previous"); sb.Append($"&lt;&lt; 首页 &nbsp; | &nbsp; &lt; 上一页");
} }
sb.Append("&nbsp; | &nbsp;"); sb.Append("&nbsp; | &nbsp;");
if (currentPage < totalPageCount) if (currentPage < totalPageCount)
{ {
sb.Append($"<a href=\"{url}?{QueryStringConverter.FromCollection(nextPage)}\">Next &gt;</a>"); sb.Append($"<a href=\"{url}?{QueryStringConverter.FromCollection(nextPage)}\">下一页 &gt;</a>");
sb.Append("&nbsp; | &nbsp;"); sb.Append("&nbsp; | &nbsp;");
sb.Append($"<a href=\"{url}?{QueryStringConverter.FromCollection(lastPage)}\">Last &gt;&gt;</a>"); sb.Append($"<a href=\"{url}?{QueryStringConverter.FromCollection(lastPage)}\">尾页 &gt;&gt;</a>");
} }
else else
{ {
sb.Append("Next &gt; &nbsp; | &nbsp; Last &gt;&gt;"); sb.Append("下一页 &gt; &nbsp; | &nbsp; 尾页 &gt;&gt;");
} }
sb.Append($"</center>"); sb.Append($"</center>");
} }

View File

@@ -8,18 +8,19 @@ namespace ZelWiki.Library
public static class QueryStringConverter public static class QueryStringConverter
{ {
/// <summary> /// <summary>
/// Takes the current page query string and upserts the given order-by field, /// 排序
/// if the string already sorts on the given field then the order is inverted (asc/desc).
/// </summary> /// </summary>
/// <param name="context"></param>
/// <param name="value"></param>
/// <returns></returns> /// <returns></returns>
public static string OrderHelper(ISessionState context, string value) public static string OrderHelper(ISessionState context, string value)
{ {
string orderByKey = "OrderBy"; var orderByKey = "OrderBy";
string orderByDirectionKey = "OrderByDirection"; var orderByDirectionKey = "OrderByDirection";
string? currentDirection = "asc"; var currentDirection = "asc";
var collection = ToDictionary(context.QueryString); var collection = ToDictionary(context.QueryString);
//Check to see if we are sorting on the value that we are already sorted on, this would mean we need to invert the sort.
if (collection.TryGetValue(orderByKey, out var currentValue)) if (collection.TryGetValue(orderByKey, out var currentValue))
{ {
bool invertDirection = string.Equals(currentValue, value, StringComparison.InvariantCultureIgnoreCase); bool invertDirection = string.Equals(currentValue, value, StringComparison.InvariantCultureIgnoreCase);
@@ -28,14 +29,7 @@ namespace ZelWiki.Library
{ {
if (collection.TryGetValue(orderByDirectionKey, out currentDirection)) if (collection.TryGetValue(orderByDirectionKey, out currentDirection))
{ {
if (currentDirection == "asc") currentDirection = currentDirection == "asc" ? "desc" : "asc";
{
currentDirection = "desc";
}
else
{
currentDirection = "asc";
}
} }
} }
else else
@@ -54,7 +48,7 @@ namespace ZelWiki.Library
} }
/// <summary> /// <summary>
/// Takes the current page query string and upserts a query key/value, replacing any conflicting query string entry. ///
/// </summary> /// </summary>
/// <param name="queryString"></param> /// <param name="queryString"></param>
/// <param name="name"></param> /// <param name="name"></param>
@@ -79,8 +73,6 @@ namespace ZelWiki.Library
foreach (var item in queryString) foreach (var item in queryString)
{ {
//Technically, keys can be duplicated in a IQueryCollection but we do not
//support this. Use .Single() to throw exception if duplicates are found.
dictionary.Add(item.Key, item.Value.Single() ?? string.Empty); dictionary.Add(item.Key, item.Value.Single() ?? string.Empty);
} }
@@ -98,13 +90,11 @@ namespace ZelWiki.Library
return dictionary; return dictionary;
} }
// If the query string starts with '?', remove it
if (queryString.StartsWith('?')) if (queryString.StartsWith('?'))
{ {
queryString = queryString.Substring(1); queryString = queryString.Substring(1);
} }
// Split the query string into key-value pairs
var keyValuePairs = queryString.Split('&'); var keyValuePairs = queryString.Split('&');
foreach (var kvp in keyValuePairs) foreach (var kvp in keyValuePairs)
@@ -158,6 +148,7 @@ namespace ZelWiki.Library
{ {
queryString.Append('&'); queryString.Append('&');
} }
queryString.Append($"{Uri.EscapeDataString(kvp.Key)}={Uri.EscapeDataString(kvp.Value)}"); queryString.Append($"{Uri.EscapeDataString(kvp.Key)}={Uri.EscapeDataString(kvp.Value)}");
} }
@@ -176,6 +167,7 @@ namespace ZelWiki.Library
{ {
clone.Add(kvp.Key, kvp.Value); clone.Add(kvp.Key, kvp.Value);
} }
return clone; return clone;
} }
} }

View File

@@ -2,13 +2,28 @@
{ {
public class Theme public class Theme
{ {
public string Name { get; set; } = string.Empty; /// <summary>
public string DelimitedFiles { get; set; } = string.Empty; ///
public string ClassNavBar { get; set; } = string.Empty; /// </summary>
public string ClassNavLink { get; set; } = string.Empty; public Theme()
public string ClassDropdown { get; set; } = string.Empty; {
public string ClassBranding { get; set; } = string.Empty; Name = string.Empty;
public string EditorTheme { get; set; } = string.Empty; DelimitedFiles = string.Empty;
public List<string> Files { get; set; } = new(); ClassNavBar = string.Empty;
ClassNavLink = string.Empty;
ClassDropdown = string.Empty;
ClassBranding = string.Empty;
EditorTheme = string.Empty;
Files = new();
}
public string Name { get; set; }
public string DelimitedFiles { get; set; }
public string ClassNavBar { get; set; }
public string ClassNavLink { get; set; }
public string ClassDropdown { get; set; }
public string ClassBranding { get; set; }
public string EditorTheme { get; set; }
public List<string> Files { get; set; }
} }
} }

View File

@@ -2,8 +2,17 @@
{ {
public class TimeZoneItem public class TimeZoneItem
{ {
public string Text { get; set; } = string.Empty; /// <summary>
public string Value { get; set; } = string.Empty; ///
/// </summary>
public TimeZoneItem()
{
Text = string.Empty;
Value = string.Empty;
}
public string Text { get; set; }
public string Value { get; set; }
public static List<TimeZoneItem> GetAll() public static List<TimeZoneItem> GetAll()
{ {

View File

@@ -10,7 +10,6 @@ namespace ZelWiki.Library
public static string SanitizeAccountName(string fileName, char[]? extraInvalidCharacters = null) public static string SanitizeAccountName(string fileName, char[]? extraInvalidCharacters = null)
{ {
// Get array of invalid characters for file names
var invalidChars = Path.GetInvalidFileNameChars().ToList(); var invalidChars = Path.GetInvalidFileNameChars().ToList();
if (extraInvalidCharacters != null) if (extraInvalidCharacters != null)
@@ -27,7 +26,7 @@ namespace ZelWiki.Library
} }
/// <summary> /// <summary>
/// Take a height and width and enforces a max on both dimensions while maintaining the ratio. ///
/// </summary> /// </summary>
/// <param name="originalWidth"></param> /// <param name="originalWidth"></param>
/// <param name="originalHeight"></param> /// <param name="originalHeight"></param>
@@ -35,20 +34,16 @@ namespace ZelWiki.Library
/// <returns></returns> /// <returns></returns>
public static (int Width, int Height) ScaleToMaxOf(int originalWidth, int originalHeight, int maxSize) public static (int Width, int Height) ScaleToMaxOf(int originalWidth, int originalHeight, int maxSize)
{ {
// Calculate aspect ratio var aspectRatio = (float)originalWidth / originalHeight;
float aspectRatio = (float)originalWidth / originalHeight;
// Determine new dimensions based on the larger dimension
int newWidth, newHeight; int newWidth, newHeight;
if (originalWidth > originalHeight) if (originalWidth > originalHeight)
{ {
// Scale down the width to the maxSize and calculate the height
newWidth = maxSize; newWidth = maxSize;
newHeight = (int)(maxSize / aspectRatio); newHeight = (int)(maxSize / aspectRatio);
} }
else else
{ {
// Scale down the height to the maxSize and calculate the width
newHeight = maxSize; newHeight = maxSize;
newWidth = (int)(maxSize * aspectRatio); newWidth = (int)(maxSize * aspectRatio);
} }
@@ -72,7 +67,7 @@ namespace ZelWiki.Library
public static byte[] ConvertHttpFileToBytes(IFormFile image) public static byte[] ConvertHttpFileToBytes(IFormFile image)
{ {
using var stream = image.OpenReadStream(); using var stream = image.OpenReadStream();
using BinaryReader reader = new BinaryReader(stream); using var reader = new BinaryReader(stream);
return reader.ReadBytes((int)image.Length); return reader.ReadBytes((int)image.Length);
} }
@@ -80,7 +75,7 @@ namespace ZelWiki.Library
{ {
if (data == null) if (data == null)
{ {
return Array.Empty<byte>(); return [];
} }
using var compressedStream = new MemoryStream(); using var compressedStream = new MemoryStream();
@@ -88,15 +83,15 @@ namespace ZelWiki.Library
{ {
compressor.Write(data, 0, data.Length); compressor.Write(data, 0, data.Length);
} }
return compressedStream.ToArray(); return compressedStream.ToArray();
} }
public static byte[] Decompress(byte[] data) public static byte[] Decompress(byte[] data)
{ {
if (data == null) if (data == null)
{ return [];
return Array.Empty<byte>();
}
using var compressedStream = new MemoryStream(data); using var compressedStream = new MemoryStream(data);
using var decompressor = new GZipStream(compressedStream, CompressionMode.Decompress); using var decompressor = new GZipStream(compressedStream, CompressionMode.Decompress);

View File

@@ -16,7 +16,7 @@
public T? Value<T>(string name) public T? Value<T>(string name)
{ {
var value = Collection.Where(o => o.Name == name).FirstOrDefault(); var value = Collection.FirstOrDefault(o => o.Name == name);
if (value == null) if (value == null)
{ {
return default; return default;
@@ -26,7 +26,7 @@
public T Value<T>(string name, T defaultValue) public T Value<T>(string name, T defaultValue)
{ {
var value = Collection.Where(o => o.Name == name).FirstOrDefault(); var value = Collection.FirstOrDefault(o => o.Name == name);
if (value == null) if (value == null)
{ {
return defaultValue; return defaultValue;

View File

@@ -19,12 +19,7 @@ namespace ZelWiki.Models.DataModels
public T? As<T>(T defaultValue) public T? As<T>(T defaultValue)
{ {
if (Value == null) return Value == null ? defaultValue : Converters.ConvertTo<T>(Value);
{
return defaultValue;
}
return Converters.ConvertTo<T>(Value);
} }
public string DataType { get; set; } = string.Empty; public string DataType { get; set; } = string.Empty;

View File

@@ -7,23 +7,23 @@ namespace ZelWiki.Models.DataModels
public int Id { get; set; } = 0; public int Id { get; set; } = 0;
/// <summary> /// <summary>
/// The revision of this page that is being viewed. May not be the latest revision. /// 正在查看的此页面的修订版。可能不是最新版本
/// </summary> /// </summary>
public int Revision { get; set; } public int Revision { get; set; }
/// <summary> /// <summary>
/// The most current revision of this page. /// 此页面的最新修订版
/// </summary> /// </summary>
public int MostCurrentRevision { get; set; } public int MostCurrentRevision { get; set; }
public bool IsHistoricalVersion => Revision != MostCurrentRevision; public bool IsHistoricalVersion => Revision != MostCurrentRevision;
/// <summary> /// <summary>
/// Lets us know whether this page exists and is loaded. /// 此页面是否存在并已加载
/// </summary> /// </summary>
public bool Exists => Id > 0; public bool Exists => Id > 0;
/// <summary> /// <summary>
/// Count of revisions higher than Revision. /// 修订次数高于修订次数
/// </summary> /// </summary>
public int HigherRevisionCount { get; set; } public int HigherRevisionCount { get; set; }
public int DeletedRevisionCount { get; set; } public int DeletedRevisionCount { get; set; }
@@ -36,8 +36,8 @@ namespace ZelWiki.Models.DataModels
{ {
get get
{ {
int idealLength = 64; var idealLength = 64;
int maxLength = 100; var maxLength = 100;
if (Description.Length > idealLength) if (Description.Length > idealLength)
{ {

View File

@@ -1,20 +1,20 @@
namespace ZelWiki.Models.DataModels namespace ZelWiki.Models.DataModels
{ {
/// <summary> /// <summary>
/// Used to cache pre-processed wiki results. /// 用于缓存预处理的结果
/// </summary> /// </summary>
public class PageCache public class PageCache
{ {
public PageCache(string body)
{
Body = body;
}
/// <summary> /// <summary>
/// Custom page title set by a call to @@Title("...") /// 通过调用@@Title("...")设置自定义页面title
/// </summary> /// </summary>
public string? PageTitle { get; set; } public string? PageTitle { get; set; }
public string Body { get; set; } public string Body { get; set; }
public PageCache(string body)
{
Body = body;
} }
} }
}

View File

@@ -4,7 +4,7 @@ namespace ZelWiki.Models.DataModels
{ {
public int PageId { get; set; } public int PageId { get; set; }
/// <summary> /// <summary>
/// TightWiki.Library.Constants.WikiInstruction ///
/// </summary> /// </summary>
public string Instruction { get; set; } = string.Empty; public string Instruction { get; set; } = string.Empty;
} }

View File

@@ -5,7 +5,7 @@
public List<ProcessingInstruction> Collection { get; set; } = new(); public List<ProcessingInstruction> Collection { get; set; } = new();
/// <summary> /// <summary>
/// Returns true if the collection contains the given processing instruction. /// 如果集合包含给定的处理指令则返回true
/// </summary> /// </summary>
/// <param name="wikiInstruction">WikiInstruction.Protect</param> /// <param name="wikiInstruction">WikiInstruction.Protect</param>
/// <returns></returns> /// <returns></returns>

View File

@@ -13,11 +13,12 @@ namespace ZelWiki.Repository
{ {
public static class ConfigurationRepository public static class ConfigurationRepository
{ {
#region Upgrade Database. #region
public static string GetVersionStateVersion() public static string GetVersionStateVersion()
{ {
var entries = ManagedDataStorage.Config.ExecuteScalar<string>(@"Scripts\Initialization\GetVersionStateVersion.sql"); var entries =
ManagedDataStorage.Config.ExecuteScalar<string>(@"Scripts\Initialization\GetVersionStateVersion.sql");
return entries ?? "0.0.0"; return entries ?? "0.0.0";
} }
@@ -25,11 +26,12 @@ namespace ZelWiki.Repository
{ {
var version = string.Join('.', var version = string.Join('.',
(Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "0.0.0.0").Split('.').Take(3)); (Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "0.0.0.0").Split('.').Take(3));
ManagedDataStorage.Config.Execute(@"Scripts\Initialization\SetVersionStateVersion.sql", new { Version = version }); ManagedDataStorage.Config.Execute(@"Scripts\Initialization\SetVersionStateVersion.sql",
new { Version = version });
} }
/// <summary> /// <summary>
/// See @Initialization.Versions.md ///
/// </summary> /// </summary>
public static void UpgradeDatabase() public static void UpgradeDatabase()
{ {
@@ -45,43 +47,44 @@ namespace ZelWiki.Repository
if (currentPaddedVersion == storedPaddedVersion) if (currentPaddedVersion == storedPaddedVersion)
{ {
return; //The database version is already at the latest version. return;
} }
var updateScriptNames = Assembly.GetExecutingAssembly().GetManifestResourceNames() var updateScriptNames = Assembly.GetExecutingAssembly().GetManifestResourceNames()
.Where(o => o.Contains("Repository.Scripts.Initialization.Versions", StringComparison.InvariantCultureIgnoreCase)).OrderBy(o => o); .Where(o => o.Contains("Repository.Scripts.Initialization.Versions",
StringComparison.InvariantCultureIgnoreCase)).OrderBy(o => o);
string startVersionTag = ".Initialization.Versions."; var startVersionTag = ".Initialization.Versions.";
string endVersionTag = ".^"; var endVersionTag = ".^";
foreach (var updateScriptName in updateScriptNames) foreach (var updateScriptName in updateScriptNames)
{ {
int startIndex = updateScriptName.IndexOf(startVersionTag, StringComparison.InvariantCultureIgnoreCase); int startIndex =
updateScriptName.IndexOf(startVersionTag, StringComparison.InvariantCultureIgnoreCase);
if (startIndex >= 0) if (startIndex >= 0)
{ {
startIndex += startVersionTag.Length; startIndex += startVersionTag.Length;
int endIndex = updateScriptName.IndexOf(endVersionTag, startIndex, StringComparison.InvariantCultureIgnoreCase); var endIndex = updateScriptName.IndexOf(endVersionTag, startIndex,
StringComparison.InvariantCultureIgnoreCase);
if (endIndex > startIndex) if (endIndex > startIndex)
{ {
//The name of the script file without the namespaces, version numbers etc. var fullScriptName = updateScriptName.Substring(endIndex + endVersionTag.Length).Trim()
var fullScriptName = updateScriptName.Substring(endIndex + endVersionTag.Length).Trim().Replace("_", ""); .Replace("_", "");
int filesFolderVersion = Utility.PadVersionString(updateScriptName.Substring(startIndex, endIndex - startIndex).Trim().Replace("_", "")); int filesFolderVersion = Utility.PadVersionString(updateScriptName
.Substring(startIndex, endIndex - startIndex).Trim().Replace("_", ""));
if (filesFolderVersion > storedPaddedVersion) if (filesFolderVersion > storedPaddedVersion)
{ {
//Get the script text.
using var stream = assembly.GetManifestResourceStream(updateScriptName); using var stream = assembly.GetManifestResourceStream(updateScriptName);
using var reader = new StreamReader(stream.EnsureNotNull()); using var reader = new StreamReader(stream.EnsureNotNull());
var scriptText = reader.ReadToEnd(); var scriptText = reader.ReadToEnd();
//Get the script "metadata" from the file name.
var scriptNameParts = fullScriptName.Split('^'); var scriptNameParts = fullScriptName.Split('^');
//string executionOrder = scriptNameParts[0]; var databaseName = scriptNameParts[1];
string databaseName = scriptNameParts[1];
//string scriptName = scriptNameParts[2];
var databaseFactory = ManagedDataStorage.Collection.Single(o => o.Name == databaseName).Factory; var databaseFactory = ManagedDataStorage.Collection.Single(o => o.Name == databaseName)
.Factory;
databaseFactory.Execute(scriptText); databaseFactory.Execute(scriptText);
} }
@@ -95,13 +98,14 @@ namespace ZelWiki.Repository
} }
catch (Exception ex) catch (Exception ex)
{ {
ExceptionRepository.InsertException(ex, "Database upgrade failed."); ExceptionRepository.InsertException(ex, "数据库升级失败");
} }
} }
#endregion #endregion
public static ConfigurationEntries GetConfigurationEntryValuesByGroupName(string groupName, bool allowCache = true) public static ConfigurationEntries GetConfigurationEntryValuesByGroupName(string groupName,
bool allowCache = true)
{ {
if (allowCache) if (allowCache)
{ {
@@ -163,25 +167,23 @@ namespace ZelWiki.Repository
} }
/// <summary> /// <summary>
/// Determines if this is the first time the wiki has run. Returns true if it is the first time. ///
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public static bool IsFirstRun() public static bool IsFirstRun()
{ {
bool isEncryptionValid = GetCryptoCheck(); var isEncryptionValid = GetCryptoCheck();
if (isEncryptionValid == false) if (isEncryptionValid == false)
{ {
SetCryptoCheck(); SetCryptoCheck();
return true; return true;
} }
return false; return false;
} }
/// <summary> /// <summary>
/// Reads an encrypted value from the database so we can determine if encryption is setup. ///
/// If the value is missing then we are NOT setup.
/// If the value is present but we cant decrypt it, then we are NOT setup.
/// /// If the value is present and we can decrypt it, then we are setup and good to go!
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public static bool GetCryptoCheck() public static bool GetCryptoCheck()
@@ -204,7 +206,7 @@ namespace ZelWiki.Repository
} }
/// <summary> /// <summary>
/// Writes an encrypted value to the database so we can test at a later time to ensure that encryption is setup. ///
/// </summary> /// </summary>
public static void SetCryptoCheck() public static void SetCryptoCheck()
{ {
@@ -275,6 +277,7 @@ namespace ZelWiki.Repository
ConfigurationGroupId = group.Key, ConfigurationGroupId = group.Key,
}); });
} }
result.Add(nest); result.Add(nest);
} }
@@ -284,14 +287,16 @@ namespace ZelWiki.Repository
public static List<ConfigurationFlat> GetFlatConfiguration() public static List<ConfigurationFlat> GetFlatConfiguration()
=> ManagedDataStorage.Config.Query<ConfigurationFlat>("GetFlatConfiguration.sql").ToList(); => ManagedDataStorage.Config.Query<ConfigurationFlat>("GetFlatConfiguration.sql").ToList();
public static string? GetConfigurationEntryValuesByGroupNameAndEntryName(string groupName, string entryName, bool allowCache = true) public static string? GetConfigurationEntryValuesByGroupNameAndEntryName(string groupName, string entryName,
bool allowCache = true)
{ {
if (allowCache) if (allowCache)
{ {
var cacheKey = WikiCacheKeyFunction.Build(WikiCache.Category.Configuration, [groupName, entryName]); var cacheKey = WikiCacheKeyFunction.Build(WikiCache.Category.Configuration, [groupName, entryName]);
if (!WikiCache.TryGet<string>(cacheKey, out var result)) if (!WikiCache.TryGet<string>(cacheKey, out var result))
{ {
if ((result = GetConfigurationEntryValuesByGroupNameAndEntryName(groupName, entryName, false)) != null) if ((result = GetConfigurationEntryValuesByGroupNameAndEntryName(groupName, entryName, false)) !=
null)
{ {
WikiCache.Put(cacheKey, result); WikiCache.Put(cacheKey, result);
} }
@@ -306,7 +311,9 @@ namespace ZelWiki.Repository
EntryName = entryName EntryName = entryName
}; };
var configEntry = ManagedDataStorage.Config.QuerySingle<ConfigurationEntry>("GetConfigurationEntryValuesByGroupNameAndEntryName.sql", param); var configEntry =
ManagedDataStorage.Config.QuerySingle<ConfigurationEntry>(
"GetConfigurationEntryValuesByGroupNameAndEntryName.sql", param);
if (configEntry?.IsEncrypted == true) if (configEntry?.IsEncrypted == true)
{ {
try try
@@ -431,33 +438,31 @@ namespace ZelWiki.Repository
if (emoji.ImageData != null) if (emoji.ImageData != null)
{ {
var scaledImageCacheKey = WikiCacheKey.Build(WikiCache.Category.Emoji, [emoji.Shortcut, "100"]); var scaledImageCacheKey =
WikiCacheKey.Build(WikiCache.Category.Emoji, [emoji.Shortcut, "100"]);
var decompressedImageBytes = Utility.Decompress(emoji.ImageData); var decompressedImageBytes = Utility.Decompress(emoji.ImageData);
var img = Image.Load(new MemoryStream(decompressedImageBytes)); var img = Image.Load(new MemoryStream(decompressedImageBytes));
int customScalePercent = 100; var customScalePercent = 100;
var (Width, Height) = Utility.ScaleToMaxOf(img.Width, img.Height, GlobalConfiguration.DefaultEmojiHeight); var (Width, Height) = Utility.ScaleToMaxOf(img.Width, img.Height,
GlobalConfiguration.DefaultEmojiHeight);
//Adjust to any specified scaling.
Height = (int)(Height * (customScalePercent / 100.0)); Height = (int)(Height * (customScalePercent / 100.0));
Width = (int)(Width * (customScalePercent / 100.0)); Width = (int)(Width * (customScalePercent / 100.0));
//Adjusting by a ratio (and especially after applying additional scaling) may have caused one
// dimension to become very small (or even negative). So here we will check the height and width
// to ensure they are both at least n pixels and adjust both dimensions.
if (Height < 16) if (Height < 16)
{ {
Height += 16 - Height; Height += 16 - Height;
Width += 16 - Height; Width += 16 - Height;
} }
if (Width < 16) if (Width < 16)
{ {
Height += 16 - Width; Height += 16 - Width;
Width += 16 - Width; Width += 16 - Width;
} }
//These are hard to generate, so just keep it forever.
var resized = Images.ResizeGifImage(decompressedImageBytes, Width, Height); var resized = Images.ResizeGifImage(decompressedImageBytes, Width, Height);
var itemCache = new ImageCacheItem(resized, "image/gif"); var itemCache = new ImageCacheItem(resized, "image/gif");
WikiCache.Put(scaledImageCacheKey, itemCache, new CacheItemPolicy()); WikiCache.Put(scaledImageCacheKey, itemCache, new CacheItemPolicy());
@@ -497,27 +502,34 @@ namespace ZelWiki.Repository
GlobalConfiguration.FixedMenuPosition = customizationConfig.Value("Fixed Header Menu Position", false); GlobalConfiguration.FixedMenuPosition = customizationConfig.Value("Fixed Header Menu Position", false);
GlobalConfiguration.AllowSignup = membershipConfig.Value("Allow Signup", false); GlobalConfiguration.AllowSignup = membershipConfig.Value("Allow Signup", false);
GlobalConfiguration.DefaultProfileRecentlyModifiedCount = performanceConfig.Value<int>("Default Profile Recently Modified Count"); GlobalConfiguration.DefaultProfileRecentlyModifiedCount =
performanceConfig.Value<int>("Default Profile Recently Modified Count");
GlobalConfiguration.PreLoadAnimatedEmojis = performanceConfig.Value<bool>("Pre-Load Animated Emojis"); GlobalConfiguration.PreLoadAnimatedEmojis = performanceConfig.Value<bool>("Pre-Load Animated Emojis");
GlobalConfiguration.SystemTheme = GetAllThemes().Single(o => o.Name == themeName); GlobalConfiguration.SystemTheme = GetAllThemes().Single(o => o.Name == themeName);
GlobalConfiguration.DefaultEmojiHeight = customizationConfig.Value<int>("Default Emoji Height"); GlobalConfiguration.DefaultEmojiHeight = customizationConfig.Value<int>("Default Emoji Height");
GlobalConfiguration.AllowGoogleAuthentication = membershipConfig.Value<bool>("Allow Google Authentication"); GlobalConfiguration.AllowGoogleAuthentication = membershipConfig.Value<bool>("Allow Google Authentication");
GlobalConfiguration.DefaultTimeZone = customizationConfig?.Value<string>("Default TimeZone") ?? string.Empty; GlobalConfiguration.DefaultTimeZone =
GlobalConfiguration.IncludeWikiDescriptionInMeta = functionalityConfig.Value<bool>("Include wiki Description in Meta"); customizationConfig?.Value<string>("Default TimeZone") ?? string.Empty;
GlobalConfiguration.IncludeWikiDescriptionInMeta =
functionalityConfig.Value<bool>("Include wiki Description in Meta");
GlobalConfiguration.IncludeWikiTagsInMeta = functionalityConfig.Value<bool>("Include wiki Tags in Meta"); GlobalConfiguration.IncludeWikiTagsInMeta = functionalityConfig.Value<bool>("Include wiki Tags in Meta");
GlobalConfiguration.EnablePageComments = functionalityConfig.Value<bool>("Enable Page Comments"); GlobalConfiguration.EnablePageComments = functionalityConfig.Value<bool>("Enable Page Comments");
GlobalConfiguration.EnablePublicProfiles = functionalityConfig.Value<bool>("Enable Public Profiles"); GlobalConfiguration.EnablePublicProfiles = functionalityConfig.Value<bool>("Enable Public Profiles");
GlobalConfiguration.ShowCommentsOnPageFooter = functionalityConfig.Value<bool>("Show Comments on Page Footer"); GlobalConfiguration.ShowCommentsOnPageFooter =
GlobalConfiguration.ShowLastModifiedOnPageFooter = functionalityConfig.Value<bool>("Show Last Modified on Page Footer"); functionalityConfig.Value<bool>("Show Comments on Page Footer");
GlobalConfiguration.ShowLastModifiedOnPageFooter =
functionalityConfig.Value<bool>("Show Last Modified on Page Footer");
GlobalConfiguration.IncludeSearchOnNavbar = searchConfig.Value<bool>("Include Search on Navbar"); GlobalConfiguration.IncludeSearchOnNavbar = searchConfig.Value<bool>("Include Search on Navbar");
GlobalConfiguration.HTMLHeader = htmlConfig?.Value<string>("Header") ?? string.Empty; GlobalConfiguration.HTMLHeader = htmlConfig?.Value<string>("Header") ?? string.Empty;
GlobalConfiguration.HTMLFooter = htmlConfig?.Value<string>("Footer") ?? string.Empty; GlobalConfiguration.HTMLFooter = htmlConfig?.Value<string>("Footer") ?? string.Empty;
GlobalConfiguration.HTMLPreBody = htmlConfig?.Value<string>("Pre-Body") ?? string.Empty; GlobalConfiguration.HTMLPreBody = htmlConfig?.Value<string>("Pre-Body") ?? string.Empty;
GlobalConfiguration.HTMLPostBody = htmlConfig?.Value<string>("Post-Body") ?? string.Empty; GlobalConfiguration.HTMLPostBody = htmlConfig?.Value<string>("Post-Body") ?? string.Empty;
GlobalConfiguration.BrandImageSmall = customizationConfig?.Value<string>("Brand Image (Small)") ?? string.Empty; GlobalConfiguration.BrandImageSmall =
customizationConfig?.Value<string>("Brand Image (Small)") ?? string.Empty;
GlobalConfiguration.FooterBlurb = customizationConfig?.Value<string>("FooterBlurb") ?? string.Empty; GlobalConfiguration.FooterBlurb = customizationConfig?.Value<string>("FooterBlurb") ?? string.Empty;
GlobalConfiguration.MaxAvatarFileSize = filesAndAttachmentsConfig.Value<int>("Max Avatar File Size"); GlobalConfiguration.MaxAvatarFileSize = filesAndAttachmentsConfig.Value<int>("Max Avatar File Size");
GlobalConfiguration.MaxAttachmentFileSize = filesAndAttachmentsConfig.Value<int>("Max Attachment File Size"); GlobalConfiguration.MaxAttachmentFileSize =
filesAndAttachmentsConfig.Value<int>("Max Attachment File Size");
GlobalConfiguration.MaxEmojiFileSize = filesAndAttachmentsConfig.Value<int>("Max Emoji File Size"); GlobalConfiguration.MaxEmojiFileSize = filesAndAttachmentsConfig.Value<int>("Max Emoji File Size");
GlobalConfiguration.MenuItems = GetAllMenuItems(); GlobalConfiguration.MenuItems = GetAllMenuItems();

View File

@@ -2,9 +2,6 @@
namespace ZelWiki.Repository namespace ZelWiki.Repository
{ {
/// <summary>
/// Stores instances of ManagedDataStorageFactories that are used to store various parts of the data for the site.
/// </summary>
public static class ManagedDataStorage public static class ManagedDataStorage
{ {
private static (string Name, ManagedDataStorageFactory Factory)[]? _collection = null; private static (string Name, ManagedDataStorageFactory Factory)[]? _collection = null;

View File

@@ -179,8 +179,6 @@ namespace ZelWiki.Repository
var pageFileInfo = GetPageFileInfoByFileNavigation(o, item.PageId, item.FileNavigation); var pageFileInfo = GetPageFileInfoByFileNavigation(o, item.PageId, item.FileNavigation);
if (pageFileInfo == null) if (pageFileInfo == null)
{ {
//If the page file does not exist, then insert it.
var InsertPageFileParam = new var InsertPageFileParam = new
{ {
PageId = item.PageId, PageId = item.PageId,
@@ -194,7 +192,6 @@ namespace ZelWiki.Repository
o.Execute("InsertPageFile.sql", InsertPageFileParam); o.Execute("InsertPageFile.sql", InsertPageFileParam);
//Get the id of the newly inserted page file.
pageFileInfo = GetPageFileInfoByFileNavigation(o, item.PageId, item.FileNavigation) pageFileInfo = GetPageFileInfoByFileNavigation(o, item.PageId, item.FileNavigation)
?? throw new Exception("Failed find newly inserted page attachment."); ?? throw new Exception("Failed find newly inserted page attachment.");
@@ -208,18 +205,12 @@ namespace ZelWiki.Repository
var currentlyAttachedFile = GetPageCurrentRevisionAttachmentByFileNavigation(o, item.PageId, item.FileNavigation); var currentlyAttachedFile = GetPageCurrentRevisionAttachmentByFileNavigation(o, item.PageId, item.FileNavigation);
if (currentlyAttachedFile != null) if (currentlyAttachedFile != null)
{ {
//The PageFile exists and a revision of it is attached to this page revision.
//Keep track of the file revision, and determine if the file has changed (via the file hash).
currentFileRevision = currentlyAttachedFile.Revision; currentFileRevision = currentlyAttachedFile.Revision;
hasFileChanged = currentlyAttachedFile.DataHash != newDataHash; hasFileChanged = currentlyAttachedFile.DataHash != newDataHash;
} }
else else
{ {
//The file either does not exist or is not attached to the current page revision.
hasFileChanged = true; hasFileChanged = true;
//We determined earlier that the PageFile does exist, so keep track of the file revision.
currentFileRevision = pageFileInfo.Revision; currentFileRevision = pageFileInfo.Revision;
} }
@@ -227,7 +218,6 @@ namespace ZelWiki.Repository
{ {
currentFileRevision++; currentFileRevision++;
//Get the current page revision so that we can associate the page file attachment with the current page revision.
int currentPageRevision = PageRepository.GetCurrentPageRevision(o, item.PageId); int currentPageRevision = PageRepository.GetCurrentPageRevision(o, item.PageId);
var updatePageFileRevisionParam = new var updatePageFileRevisionParam = new
@@ -235,7 +225,6 @@ namespace ZelWiki.Repository
PageFileId = pageFileInfo.PageFileId, PageFileId = pageFileInfo.PageFileId,
FileRevision = currentFileRevision FileRevision = currentFileRevision
}; };
//The file has changed (or is newly inserted), bump the file revision.
o.Execute("UpdatePageFileRevision.sql", updatePageFileRevisionParam); o.Execute("UpdatePageFileRevision.sql", updatePageFileRevisionParam);
var insertPageFileRevisionParam = new var insertPageFileRevisionParam = new
@@ -250,7 +239,6 @@ namespace ZelWiki.Repository
DataHash = newDataHash, DataHash = newDataHash,
}; };
//Insert the actual file data.
o.Execute("InsertPageFileRevision.sql", insertPageFileRevisionParam); o.Execute("InsertPageFileRevision.sql", insertPageFileRevisionParam);
var associatePageFileAttachmentWithPageRevisionParam = new var associatePageFileAttachmentWithPageRevisionParam = new
@@ -259,10 +247,9 @@ namespace ZelWiki.Repository
PageFileId = pageFileInfo.PageFileId, PageFileId = pageFileInfo.PageFileId,
PageRevision = currentPageRevision, PageRevision = currentPageRevision,
FileRevision = currentFileRevision, FileRevision = currentFileRevision,
PreviousFileRevision = currentlyAttachedFile?.Revision //This is so we can disassociate the previous file revision. PreviousFileRevision = currentlyAttachedFile?.Revision
}; };
//Associate the latest version of the file with the latest version of the page.
o.Execute("AssociatePageFileAttachmentWithPageRevision.sql", associatePageFileAttachmentWithPageRevisionParam); o.Execute("AssociatePageFileAttachmentWithPageRevision.sql", associatePageFileAttachmentWithPageRevisionParam);
} }

View File

@@ -20,7 +20,8 @@ namespace ZelWiki.Repository
return ManagedDataStorage.Pages.QuerySingleOrDefault<Page>("GetPageRevisionInfoById.sql", param); return ManagedDataStorage.Pages.QuerySingleOrDefault<Page>("GetPageRevisionInfoById.sql", param);
} }
public static ProcessingInstructionCollection GetPageProcessingInstructionsByPageId(int pageId, bool allowCache = true) public static ProcessingInstructionCollection GetPageProcessingInstructionsByPageId(int pageId,
bool allowCache = true)
{ {
if (allowCache) if (allowCache)
{ {
@@ -41,7 +42,8 @@ namespace ZelWiki.Repository
return new ProcessingInstructionCollection() return new ProcessingInstructionCollection()
{ {
Collection = ManagedDataStorage.Pages.Query<ProcessingInstruction>("GetPageProcessingInstructionsByPageId.sql", param).ToList() Collection = ManagedDataStorage.Pages
.Query<ProcessingInstruction>("GetPageProcessingInstructionsByPageId.sql", param).ToList()
}; };
} }
@@ -68,7 +70,8 @@ namespace ZelWiki.Repository
} }
public static List<PageRevision> GetPageRevisionsInfoByNavigationPaged( public static List<PageRevision> GetPageRevisionsInfoByNavigationPaged(
string navigation, int pageNumber, string? orderBy = null, string? orderByDirection = null, int? pageSize = null) string navigation, int pageNumber, string? orderBy = null, string? orderByDirection = null,
int? pageSize = null)
{ {
pageSize ??= ConfigurationRepository.Get<int>("Customization", "Pagination Size"); pageSize ??= ConfigurationRepository.Get<int>("Customization", "Pagination Size");
@@ -83,7 +86,8 @@ namespace ZelWiki.Repository
{ {
using var users_db = o.Attach("users.db", "users_db"); using var users_db = o.Attach("users.db", "users_db");
var query = RepositoryHelper.TransposeOrderby("GetPageRevisionsInfoByNavigationPaged.sql", orderBy, orderByDirection); var query = RepositoryHelper.TransposeOrderby("GetPageRevisionsInfoByNavigationPaged.sql", orderBy,
orderByDirection);
return o.Query<PageRevision>(query, param).ToList(); return o.Query<PageRevision>(query, param).ToList();
}); });
} }
@@ -96,7 +100,8 @@ namespace ZelWiki.Repository
TopCount = topCount TopCount = topCount
}; };
return ManagedDataStorage.Pages.Query<PageRevision>("GetTopRecentlyModifiedPagesInfoByUserId.sql", param).ToList(); return ManagedDataStorage.Pages.Query<PageRevision>("GetTopRecentlyModifiedPagesInfoByUserId.sql", param)
.ToList();
} }
public static string? GetPageNavigationByPageId(int pageId) public static string? GetPageNavigationByPageId(int pageId)
@@ -149,12 +154,13 @@ namespace ZelWiki.Repository
}); });
} }
private static List<PageSearchToken> GetMeteredPageSearchTokens(List<string> searchTerms, bool allowFuzzyMatching, bool allowCache = true) private static List<PageSearchToken> GetMeteredPageSearchTokens(List<string> searchTerms,
bool allowFuzzyMatching, bool allowCache = true)
{ {
if (allowCache) if (allowCache)
{ {
//This caching is really just used for paging - so we don't have to do a token search for every click of next/previous. var cacheKey = WikiCacheKeyFunction.Build(WikiCache.Category.Search,
var cacheKey = WikiCacheKeyFunction.Build(WikiCache.Category.Search, [string.Join(',', searchTerms), allowFuzzyMatching]); [string.Join(',', searchTerms), allowFuzzyMatching]);
if (!WikiCache.TryGet<List<PageSearchToken>>(cacheKey, out var result)) if (!WikiCache.TryGet<List<PageSearchToken>>(cacheKey, out var result))
{ {
result = GetMeteredPageSearchTokens(searchTerms, allowFuzzyMatching, false); result = GetMeteredPageSearchTokens(searchTerms, allowFuzzyMatching, false);
@@ -182,7 +188,7 @@ namespace ZelWiki.Repository
return allTokens return allTokens
.GroupBy(token => token.PageId) .GroupBy(token => token.PageId)
.Where(group => group.Sum(g => g.Score) >= minimumMatchScore) // Filtering groups .Where(group => group.Sum(g => g.Score) >= minimumMatchScore)
.Select(group => new PageSearchToken .Select(group => new PageSearchToken
{ {
PageId = group.Key, PageId = group.Key,
@@ -204,7 +210,7 @@ namespace ZelWiki.Repository
return new List<Page>(); return new List<Page>();
} }
bool allowFuzzyMatching = ConfigurationRepository.Get<bool>("Search", "Allow Fuzzy Matching"); var allowFuzzyMatching = ConfigurationRepository.Get<bool>("Search", "Allow Fuzzy Matching");
var meteredSearchTokens = GetMeteredPageSearchTokens(searchTerms, allowFuzzyMatching == true); var meteredSearchTokens = GetMeteredPageSearchTokens(searchTerms, allowFuzzyMatching == true);
if (meteredSearchTokens.Count == 0) if (meteredSearchTokens.Count == 0)
{ {
@@ -224,7 +230,8 @@ namespace ZelWiki.Repository
}); });
} }
public static List<Page> PageSearchPaged(List<string> searchTerms, int pageNumber, int? pageSize = null, bool? allowFuzzyMatching = null) public static List<Page> PageSearchPaged(List<string> searchTerms, int pageNumber, int? pageSize = null,
bool? allowFuzzyMatching = null)
{ {
if (searchTerms.Count == 0) if (searchTerms.Count == 0)
{ {
@@ -256,7 +263,8 @@ namespace ZelWiki.Repository
}); });
} }
public static List<RelatedPage> GetSimilarPagesPaged(int pageId, int similarity, int pageNumber, int? pageSize = null) public static List<RelatedPage> GetSimilarPagesPaged(int pageId, int similarity, int pageNumber,
int? pageSize = null)
{ {
pageSize ??= ConfigurationRepository.Get<int>("Customization", "Pagination Size"); pageSize ??= ConfigurationRepository.Get<int>("Customization", "Pagination Size");
@@ -364,7 +372,8 @@ namespace ZelWiki.Repository
}); });
} }
public static List<NonexistentPage> GetMissingPagesPaged(int pageNumber, string? orderBy = null, string? orderByDirection = null) public static List<NonexistentPage> GetMissingPagesPaged(int pageNumber, string? orderBy = null,
string? orderByDirection = null)
{ {
int pageSize = ConfigurationRepository.Get<int>("Customization", "Pagination Size"); int pageSize = ConfigurationRepository.Get<int>("Customization", "Pagination Size");
@@ -478,17 +487,18 @@ namespace ZelWiki.Repository
return ManagedDataStorage.Pages.Ephemeral(o => return ManagedDataStorage.Pages.Ephemeral(o =>
{ {
using var users_db = o.Attach("users.db", "users_db"); using var users_db = o.Attach("users.db", "users_db");
var query = RepositoryHelper.TransposeOrderby("GetAllNamespacePagesPaged.sql", orderBy, orderByDirection); var query = RepositoryHelper.TransposeOrderby("GetAllNamespacePagesPaged.sql", orderBy,
orderByDirection);
return o.Query<Page>(query, param).ToList(); return o.Query<Page>(query, param).ToList();
}); });
} }
/// <summary> /// <summary>
/// Unlike the search, this method returns all pages and allows them to be paired down using the search terms. ///
/// Whereas the search requires a search term to get results. The matching here is also exact, no score based matching.
/// </summary> /// </summary>
/// <param name="pageNumber"></param> /// <param name="pageNumber"></param>
/// <param name="pageSize"></param> /// <param name="orderBy"></param>
/// <param name="orderByDirection"></param>
/// <param name="searchTerms"></param> /// <param name="searchTerms"></param>
/// <returns></returns> /// <returns></returns>
public static List<Page> GetAllPagesPaged(int pageNumber, public static List<Page> GetAllPagesPaged(int pageNumber,
@@ -512,7 +522,8 @@ namespace ZelWiki.Repository
using var deletedpagerevisions_db = o.Attach("deletedpagerevisions.db", "deletedpagerevisions_db"); using var deletedpagerevisions_db = o.Attach("deletedpagerevisions.db", "deletedpagerevisions_db");
using var tempTable = o.CreateTempTableFrom("TempPageIds", pageIds); using var tempTable = o.CreateTempTableFrom("TempPageIds", pageIds);
var query = RepositoryHelper.TransposeOrderby("GetAllPagesByPageIdPaged.sql", orderBy, orderByDirection); var query = RepositoryHelper.TransposeOrderby("GetAllPagesByPageIdPaged.sql", orderBy,
orderByDirection);
return o.Query<Page>(query, param).ToList(); return o.Query<Page>(query, param).ToList();
}); });
} }
@@ -528,11 +539,11 @@ namespace ZelWiki.Repository
} }
/// <summary> /// <summary>
/// Unlike the search, this method returns all pages and allows them to be paired down using the search terms. ///
/// Whereas the search requires a search term to get results. The matching here is also exact, no score based matching.
/// </summary> /// </summary>
/// <param name="pageNumber"></param> /// <param name="pageNumber"></param>
/// <param name="pageSize"></param> /// <param name="orderBy"></param>
/// <param name="orderByDirection"></param>
/// <param name="searchTerms"></param> /// <param name="searchTerms"></param>
/// <returns></returns> /// <returns></returns>
public static List<Page> GetAllDeletedPagesPaged(int pageNumber, string? orderBy = null, public static List<Page> GetAllDeletedPagesPaged(int pageNumber, string? orderBy = null,
@@ -554,7 +565,8 @@ namespace ZelWiki.Repository
using var users_db = o.Attach("users.db", "users_db"); using var users_db = o.Attach("users.db", "users_db");
using var tempTable = o.CreateTempTableFrom("TempPageIds", pageIds); using var tempTable = o.CreateTempTableFrom("TempPageIds", pageIds);
var query = RepositoryHelper.TransposeOrderby("GetAllDeletedPagesByPageIdPaged.sql", orderBy, orderByDirection); var query = RepositoryHelper.TransposeOrderby("GetAllDeletedPagesByPageIdPaged.sql", orderBy,
orderByDirection);
return o.Query<Page>(query, param).ToList(); return o.Query<Page>(query, param).ToList();
}); });
} }
@@ -567,7 +579,8 @@ namespace ZelWiki.Repository
}); });
} }
public static List<NamespaceStat> GetAllNamespacesPaged(int pageNumber, string? orderBy = null, string? orderByDirection = null) public static List<NamespaceStat> GetAllNamespacesPaged(int pageNumber, string? orderBy = null,
string? orderByDirection = null)
{ {
int pageSize = ConfigurationRepository.Get<int>("Customization", "Pagination Size"); int pageSize = ConfigurationRepository.Get<int>("Customization", "Pagination Size");
@@ -683,6 +696,7 @@ namespace ZelWiki.Repository
WikiCache.Put(cacheKey, result); WikiCache.Put(cacheKey, result);
} }
} }
return result; return result;
} }
@@ -719,27 +733,23 @@ namespace ZelWiki.Repository
try try
{ {
int currentPageRevision = 0; var currentPageRevision = 0;
bool hasPageChanged = false; var hasPageChanged = false;
if (page.Id == 0) if (page.Id == 0)
{ {
//This is a new page, just insert it.
page.Id = o.ExecuteScalar<int>("CreatePage.sql", pageUpsertParam); page.Id = o.ExecuteScalar<int>("CreatePage.sql", pageUpsertParam);
hasPageChanged = true; hasPageChanged = true;
} }
else else
{ {
//Get current page so we can determine if anything has changed.
var currentRevisionInfo = GetLimitedPageInfoByIdAndRevision(page.Id) var currentRevisionInfo = GetLimitedPageInfoByIdAndRevision(page.Id)
?? throw new Exception("The page could not be found."); ?? throw new Exception("The page could not be found.");
currentPageRevision = currentRevisionInfo.Revision; currentPageRevision = currentRevisionInfo.Revision;
//Update the existing page.
o.Execute("UpdatePage.sql", pageUpsertParam); o.Execute("UpdatePage.sql", pageUpsertParam);
//Determine if anything has actually changed.
hasPageChanged = currentRevisionInfo.Name != page.Name hasPageChanged = currentRevisionInfo.Name != page.Name
|| currentRevisionInfo.Namespace != page.Namespace || currentRevisionInfo.Namespace != page.Namespace
|| currentRevisionInfo.Description != page.Description || currentRevisionInfo.Description != page.Description
@@ -755,7 +765,6 @@ namespace ZelWiki.Repository
PageId = page.Id, PageId = page.Id,
PageRevision = currentPageRevision PageRevision = currentPageRevision
}; };
//The page content has actually changed (according to the checksum), so we will bump the page revision.
o.Execute("UpdatePageRevisionNumber.sql", updatePageRevisionNumberParam); o.Execute("UpdatePageRevisionNumber.sql", updatePageRevisionNumberParam);
var InsertPageRevisionParam = new var InsertPageRevisionParam = new
@@ -770,7 +779,6 @@ namespace ZelWiki.Repository
ModifiedByUserId = page.ModifiedByUserId, ModifiedByUserId = page.ModifiedByUserId,
ModifiedDate = DateTime.UtcNow, ModifiedDate = DateTime.UtcNow,
}; };
//Insert the new actual page revision entry (this is the data).
o.Execute("InsertPageRevision.sql", InsertPageRevisionParam); o.Execute("InsertPageRevision.sql", InsertPageRevisionParam);
var reassociateAllPageAttachmentsParam = new var reassociateAllPageAttachmentsParam = new
@@ -778,7 +786,6 @@ namespace ZelWiki.Repository
PageId = page.Id, PageId = page.Id,
PageRevision = currentPageRevision, PageRevision = currentPageRevision,
}; };
//Associate all page attachments with the latest revision.
o.Execute("ReassociateAllPageAttachments.sql", reassociateAllPageAttachmentsParam); o.Execute("ReassociateAllPageAttachments.sql", reassociateAllPageAttachmentsParam);
} }
@@ -795,7 +802,7 @@ namespace ZelWiki.Repository
} }
/// <summary> /// <summary>
/// Gets the page info without the content. ///
/// </summary> /// </summary>
/// <param name="navigation"></param> /// <param name="navigation"></param>
/// <returns></returns> /// <returns></returns>
@@ -1001,7 +1008,8 @@ namespace ZelWiki.Repository
{ {
using var users_db = o.Attach("users.db", "users_db"); using var users_db = o.Attach("users.db", "users_db");
var query = RepositoryHelper.TransposeOrderby("GetDeletedPageRevisionsByIdPaged.sql", orderBy, orderByDirection); var query = RepositoryHelper.TransposeOrderby("GetDeletedPageRevisionsByIdPaged.sql", orderBy,
orderByDirection);
return o.Query<DeletedPageRevision>(query, param).ToList(); return o.Query<DeletedPageRevision>(query, param).ToList();
}); });
} }
@@ -1083,7 +1091,8 @@ namespace ZelWiki.Repository
}); });
} }
public static Page? GetPageRevisionByNavigation(string givenNavigation, int? revision = null, bool allowCache = true) public static Page? GetPageRevisionByNavigation(string givenNavigation, int? revision = null,
bool allowCache = true)
{ {
var navigation = new NamespaceNavigation(givenNavigation); var navigation = new NamespaceNavigation(givenNavigation);

View File

@@ -5,11 +5,13 @@ namespace ZelWiki.Repository
internal static class RepositoryHelper internal static class RepositoryHelper
{ {
/// <summary> /// <summary>
/// Fills in a custom orderby on a given sql script. ///
/// </summary> /// </summary>
/// <param name="filename"></param> /// <param name="filename"></param>
/// <param name="orderBy"></param> /// <param name="orderBy"></param>
/// <param name="orderByDirection"></param>
/// <returns></returns> /// <returns></returns>
/// <exception cref="Exception"></exception>
public static string TransposeOrderby(string filename, string? orderBy, string? orderByDirection) public static string TransposeOrderby(string filename, string? orderBy, string? orderByDirection)
{ {
var script = ManagedDataStorageInstance.TranslateSqlScript(filename); var script = ManagedDataStorageInstance.TranslateSqlScript(filename);
@@ -19,47 +21,53 @@ namespace ZelWiki.Repository
return script; return script;
} }
string beginParentTag = "--CUSTOM_ORDER_BEGIN::"; var beginParentTag = "--CUSTOM_ORDER_BEGIN::";
string endParentTag = "--::CUSTOM_ORDER_BEGIN"; var endParentTag = "--::CUSTOM_ORDER_BEGIN";
string beginConfigTag = "--CONFIG::"; var beginConfigTag = "--CONFIG::";
string endConfigTag = "--::CONFIG"; var endConfigTag = "--::CONFIG";
while (true) while (true)
{ {
int beginParentIndex = script.IndexOf(beginParentTag, StringComparison.OrdinalIgnoreCase); var beginParentIndex = script.IndexOf(beginParentTag, StringComparison.OrdinalIgnoreCase);
int endParentIndex = script.IndexOf(endParentTag, StringComparison.OrdinalIgnoreCase); var endParentIndex = script.IndexOf(endParentTag, StringComparison.OrdinalIgnoreCase);
if (beginParentIndex > 0 && endParentIndex > beginParentIndex) if (beginParentIndex > 0 && endParentIndex > beginParentIndex)
{ {
var sectionText = script.Substring(beginParentIndex + beginParentTag.Length, (endParentIndex - beginParentIndex) - endParentTag.Length).Trim(); var sectionText = script.Substring(beginParentIndex + beginParentTag.Length,
(endParentIndex - beginParentIndex) - endParentTag.Length).Trim();
int beginConfigIndex = sectionText.IndexOf(beginConfigTag, StringComparison.OrdinalIgnoreCase); var beginConfigIndex = sectionText.IndexOf(beginConfigTag, StringComparison.OrdinalIgnoreCase);
int endConfigIndex = sectionText.IndexOf(endConfigTag, StringComparison.OrdinalIgnoreCase); var endConfigIndex = sectionText.IndexOf(endConfigTag, StringComparison.OrdinalIgnoreCase);
if (beginConfigIndex >= 0 && endConfigIndex > beginConfigIndex) if (beginConfigIndex >= 0 && endConfigIndex > beginConfigIndex)
{ {
var configText = sectionText.Substring(beginConfigIndex + beginConfigTag.Length, (endConfigIndex - beginConfigIndex) - endConfigTag.Length).Trim(); var configText = sectionText.Substring(beginConfigIndex + beginConfigTag.Length,
(endConfigIndex - beginConfigIndex) - endConfigTag.Length).Trim();
var configs = configText.Split("\n").Select(o => o.Trim()) var configs = configText.Split("\n").Select(o => o.Trim())
.Where(o => o.Contains('=')) .Where(o => o.Contains('='))
.Select(o => (Name: o.Split("=")[0], Field: o.Split("=")[1])); .Select(o => (Name: o.Split("=")[0], Field: o.Split("=")[1]));
var selectedConfig = configs.SingleOrDefault(o => string.Equals(o.Name, orderBy, StringComparison.OrdinalIgnoreCase)); var selectedConfig = configs.SingleOrDefault(o =>
string.Equals(o.Name, orderBy, StringComparison.OrdinalIgnoreCase));
if (selectedConfig == default) if (selectedConfig == default)
{ {
throw new Exception($"No order by mapping was found in '{filename}' for the field '{orderBy}'."); throw new Exception(
$"在 '{filename}' 中找不到排序字段 '{orderBy}'的映射");
} }
script = script.Substring(0, beginParentIndex) script = script.Substring(0, beginParentIndex)
+ $"ORDER BY\r\n\t{selectedConfig.Field} " + $"ORDER BY\r\n\t{selectedConfig.Field} "
+ (string.Equals(orderByDirection, "asc", StringComparison.InvariantCultureIgnoreCase) ? "asc" : "desc") + (string.Equals(orderByDirection, "asc", StringComparison.InvariantCultureIgnoreCase)
? "asc"
: "desc")
+ script.Substring(endParentIndex + endParentTag.Length); + script.Substring(endParentIndex + endParentTag.Length);
} }
else else
{ {
throw new Exception($"No order configuration was found in '{filename}'."); throw new Exception($" '{filename}'中找不到配置");
} }
} }
else else

View File

@@ -1,4 +1,3 @@
--Remove the previous page file revision attachment, if any.
DELETE FROM PageRevisionAttachment DELETE FROM PageRevisionAttachment
WHERE WHERE
PageId = @PageId PageId = @PageId
@@ -6,7 +5,7 @@ WHERE
AND FileRevision = @PreviousFileRevision AND FileRevision = @PreviousFileRevision
AND PageRevision = @PageRevision; AND PageRevision = @PageRevision;
--Associate the file revision record with the page revision.
INSERT INTO PageRevisionAttachment INSERT INTO PageRevisionAttachment
( (
PageId, PageId,

View File

@@ -1,5 +1,3 @@
--This proc is exactly like GetAllUsersPaged except it has no filter on personal infomation so it can be used for public info.
SELECT SELECT
U.UserId, U.UserId,
U.AccountName, U.AccountName,

View File

@@ -9,7 +9,6 @@ FROM
T.PageId, T.PageId,
COUNT(DISTINCT T.Token) / (@TokenCount + 0.0) as [Match], COUNT(DISTINCT T.Token) / (@TokenCount + 0.0) as [Match],
SUM(T.[Weight] * 1.5) as [Weight], SUM(T.[Weight] * 1.5) as [Weight],
--Extra weight on score for exact matches:
SUM(T.[Weight] * 1.5) * (COUNT(DISTINCT T.Token) / (@TokenCount + 0.0)) as [Score] SUM(T.[Weight] * 1.5) * (COUNT(DISTINCT T.Token) / (@TokenCount + 0.0)) as [Score]
FROM FROM
PageToken as T PageToken as T

View File

@@ -9,7 +9,6 @@ FROM
T.PageId, T.PageId,
COUNT(DISTINCT T.DoubleMetaphone) / (@TokenCount + 0.0) as [Match], COUNT(DISTINCT T.DoubleMetaphone) / (@TokenCount + 0.0) as [Match],
SUM(T.[Weight] * 1.0) as [Weight], SUM(T.[Weight] * 1.0) as [Weight],
--No weight benefits on score for fuzzy matching weight for exact matches:
(COUNT(DISTINCT T.DoubleMetaphone) / (@TokenCount + 0.0)) as [Score] (COUNT(DISTINCT T.DoubleMetaphone) / (@TokenCount + 0.0)) as [Score]
FROM FROM
PageToken as T PageToken as T

View File

@@ -19,7 +19,7 @@ INNER JOIN PageRevisionAttachment as PRA
ON PRA.PageId = P.Id ON PRA.PageId = P.Id
AND PRA.PageFileId = PF.Id AND PRA.PageFileId = PF.Id
AND PRA.PageRevision = PR.Revision AND PRA.PageRevision = PR.Revision
AND PRA.FileRevision = PF.Revision --Latest file revision. AND PRA.FileRevision = PF.Revision
INNER JOIN PageFileRevision as PFR INNER JOIN PageFileRevision as PFR
ON PFR.PageFileId = PF.Id ON PFR.PageFileId = PF.Id
AND PFR.Revision = PRA.FileRevision AND PFR.Revision = PRA.FileRevision

View File

@@ -23,7 +23,7 @@ SELECT
ON PRA.PageId = P.Id ON PRA.PageId = P.Id
AND PRA.PageFileId = PF.Id AND PRA.PageFileId = PF.Id
AND PRA.PageRevision = PR.Revision AND PRA.PageRevision = PR.Revision
AND PRA.FileRevision = PF.Revision --Latest file revision. AND PRA.FileRevision = PF.Revision
INNER JOIN PageFileRevision as PFR INNER JOIN PageFileRevision as PFR
ON PFR.PageFileId = PF.Id ON PFR.PageFileId = PF.Id
AND PFR.Revision = PRA.FileRevision AND PFR.Revision = PRA.FileRevision

View File

@@ -1,14 +0,0 @@
# Database Upgrade Initialization
When TightWiki is run, any scripts in the folders contained in "TightWiki.Repository\Scripts\Initialization\Versions"
are executed. The "previous version" of TightWiki is stored in the Config database VersionState table.
The scripts are executed in the order denoted by the name of the folders in "Version\*", these folders are
expected to be named with a three-part version scheme. MM.mm.pp (major.minor.patch).
The scripts are only executed if the three-part folder version is
greater than the "previous version" from the VersionState table.
Theses scripts are executed in the order of their name as well, their name consists of three parts:
"\^EXECUTION_ORDER\^DATABASE_NAME\^SCRIPT_NAME" where the execution order should be a zero padded numeric string,
database name is the key from ManagedDataStorage.Collection, and script name is whatever you want to call it.

View File

@@ -1,4 +1,3 @@
--Insert the actual file data.
INSERT INTO PageFileRevision INSERT INTO PageFileRevision
( (
PageFileId, PageFileId,

View File

@@ -5,7 +5,6 @@ INSERT INTO deletedpages_db.[PageFileRevision] SELECT * FROM PageFileRevision WH
INSERT INTO deletedpages_db.[PageFile] SELECT * FROM [PageFile] WHERE PageId = @PageId; INSERT INTO deletedpages_db.[PageFile] SELECT * FROM [PageFile] WHERE PageId = @PageId;
INSERT INTO deletedpages_db.[Page] SELECT * FROM [Page] WHERE Id = @PageId; INSERT INTO deletedpages_db.[Page] SELECT * FROM [Page] WHERE Id = @PageId;
--We save these so we can search for deleted pages.
INSERT INTO deletedpages_db.[PageTag] SELECT * FROM [PageTag] WHERE PageId = @PageId; INSERT INTO deletedpages_db.[PageTag] SELECT * FROM [PageTag] WHERE PageId = @PageId;
INSERT INTO deletedpages_db.[PageToken] SELECT * FROM [PageToken] WHERE PageId = @PageId; INSERT INTO deletedpages_db.[PageToken] SELECT * FROM [PageToken] WHERE PageId = @PageId;
INSERT INTO deletedpages_db.[PageProcessingInstruction] SELECT * FROM [PageProcessingInstruction] WHERE PageId = @PageId; INSERT INTO deletedpages_db.[PageProcessingInstruction] SELECT * FROM [PageProcessingInstruction] WHERE PageId = @PageId;

View File

@@ -1,4 +1,4 @@
--Cleanup
DELETE FROM DeletionMeta WHERE PageId = @PageId; DELETE FROM DeletionMeta WHERE PageId = @PageId;
DELETE FROM [PageTag] WHERE PageId = @PageId; DELETE FROM [PageTag] WHERE PageId = @PageId;

View File

@@ -1,9 +1,7 @@
BEGIN TRANSACTION; BEGIN TRANSACTION;
--Delete orphaned PageFileRevision.
DELETE FROM PageFileRevision WHERE PageFileId = @PageFileId AND Revision = @Revision; DELETE FROM PageFileRevision WHERE PageFileId = @PageFileId AND Revision = @Revision;
--Delete orphaned PageFile.
DELETE FROM PageFile DELETE FROM PageFile
WHERE Id = @PageFileId WHERE Id = @PageFileId
AND Id NOT IN (SELECT PFR.PageFileId FROM PageFileRevision as PFR WHERE PFR.PageFileId = @PageFileId); AND Id NOT IN (SELECT PFR.PageFileId FROM PageFileRevision as PFR WHERE PFR.PageFileId = @PageFileId);

View File

@@ -1,6 +1,6 @@
BEGIN TRANSACTION; BEGIN TRANSACTION;
--Delete orphaned PageFileRevision.
DELETE FROM PageFileRevision DELETE FROM PageFileRevision
WHERE (PageFileId, Revision) IN ( WHERE (PageFileId, Revision) IN (
SELECT SELECT
@@ -19,7 +19,7 @@ WHERE (PageFileId, Revision) IN (
PRA.PageFileId IS NULL PRA.PageFileId IS NULL
); );
--Delete orphaned PageFile.
DELETE FROM PageFile DELETE FROM PageFile
WHERE Id NOT IN (SELECT PageFileId FROM PageFileRevision); WHERE Id NOT IN (SELECT PageFileId FROM PageFileRevision);

View File

@@ -1,4 +1,4 @@
--Restore:
INSERT INTO [Page] SELECT * FROM deletedpages_db.[Page] WHERE Id = @PageId; INSERT INTO [Page] SELECT * FROM deletedpages_db.[Page] WHERE Id = @PageId;
INSERT INTO [PageRevision] SELECT * FROM deletedpages_db.[PageRevision] WHERE PageId = @PageId; INSERT INTO [PageRevision] SELECT * FROM deletedpages_db.[PageRevision] WHERE PageId = @PageId;
INSERT INTO [PageFile] SELECT * FROM deletedpages_db.[PageFile] WHERE PageId = @PageId; INSERT INTO [PageFile] SELECT * FROM deletedpages_db.[PageFile] WHERE PageId = @PageId;
@@ -6,7 +6,7 @@ INSERT INTO [PageFileRevision] SELECT * FROM deletedpages_db.PageFileRevision WH
INSERT INTO [PageRevisionAttachment] SELECT * FROM deletedpages_db.[PageRevisionAttachment] WHERE PageId = @PageId; INSERT INTO [PageRevisionAttachment] SELECT * FROM deletedpages_db.[PageRevisionAttachment] WHERE PageId = @PageId;
INSERT INTO [PageComment] SELECT * FROM deletedpages_db.[PageComment] WHERE PageId = @PageId; INSERT INTO [PageComment] SELECT * FROM deletedpages_db.[PageComment] WHERE PageId = @PageId;
--Cleanup
DELETE FROM deletedpages_db.DeletionMeta WHERE PageId = @PageId; DELETE FROM deletedpages_db.DeletionMeta WHERE PageId = @PageId;
DELETE FROM deletedpages_db.[PageTag] WHERE PageId = @PageId; DELETE FROM deletedpages_db.[PageTag] WHERE PageId = @PageId;

View File

@@ -1,4 +1,3 @@
-- Deleting non-current page revisions
DELETE FROM PageRevision DELETE FROM PageRevision
WHERE EXISTS ( WHERE EXISTS (
SELECT 1 SELECT 1
@@ -11,7 +10,6 @@ WHERE EXISTS (
AND PageRevision.Revision < MostRecent.MaxRevision AND PageRevision.Revision < MostRecent.MaxRevision
); );
-- Deleting non-current attachments.
DELETE FROM PageRevisionAttachment DELETE FROM PageRevisionAttachment
WHERE EXISTS ( WHERE EXISTS (
SELECT 1 SELECT 1
@@ -25,7 +23,6 @@ WHERE EXISTS (
AND PageRevisionAttachment.FileRevision < MostRecent.MaxFileRevision AND PageRevisionAttachment.FileRevision < MostRecent.MaxFileRevision
); );
-- Deleting non-current page revision attachments
DELETE FROM PageRevisionAttachment DELETE FROM PageRevisionAttachment
WHERE EXISTS ( WHERE EXISTS (
SELECT 1 SELECT 1
@@ -39,7 +36,6 @@ WHERE EXISTS (
AND PageRevisionAttachment.PageRevision < MostRecent.MaxPageRevision AND PageRevisionAttachment.PageRevision < MostRecent.MaxPageRevision
); );
-- Deleting non-current page file revisions.
DELETE FROM PageFileRevision DELETE FROM PageFileRevision
WHERE EXISTS ( WHERE EXISTS (
SELECT 1 SELECT 1
@@ -52,19 +48,16 @@ WHERE EXISTS (
AND PageFileRevision.Revision < MostRecent.MaxPageRevision AND PageFileRevision.Revision < MostRecent.MaxPageRevision
); );
-- Delete orphaned PageFileRevision
DELETE FROM PageFileRevision DELETE FROM PageFileRevision
WHERE PageFileId NOT IN ( WHERE PageFileId NOT IN (
SELECT PageFileId FROM PageRevisionAttachment SELECT PageFileId FROM PageRevisionAttachment
); );
-- Delete orphaned PageFile
DELETE FROM PageFile DELETE FROM PageFile
WHERE Id NOT IN ( WHERE Id NOT IN (
SELECT PageFileId FROM PageRevisionAttachment SELECT PageFileId FROM PageRevisionAttachment
); );
-- Assuming everything else worked, lets set all of the revisions back to 1.
UPDATE [Page] SET Revision = 1; UPDATE [Page] SET Revision = 1;
UPDATE PageRevision SET Revision = 1; UPDATE PageRevision SET Revision = 1;
UPDATE PageRevisionAttachment SET PageRevision = 1, FileRevision = 1; UPDATE PageRevisionAttachment SET PageRevision = 1, FileRevision = 1;

View File

@@ -1,4 +1,3 @@
--The ReferencesPageId is NULL by default and needs to be filled in for pages that referece orphaned pages.
UPDATE UPDATE
PageReference PageReference
SET SET

View File

@@ -11,7 +11,6 @@ DELETE FROM EmojiCategory WHERE Id IN
AND TC.Value IS NULL AND TC.Value IS NULL
); );
--Insert previously non-existing categories.
INSERT INTO EmojiCategory INSERT INTO EmojiCategory
( (
EmojiId, EmojiId,

Some files were not shown because too many files have changed in this diff Show More