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++)
{
string emailAddress = WordsRepository.GetRandomWords(1).First() + "@" + WordsRepository.GetRandomWords(1).First() + ".com";
string emailAddress = WordsRepository.GetRandomWords(1).First() + "@" +
WordsRepository.GetRandomWords(1).First() + ".com";
CreateUserAndProfile(emailAddress);
}
@@ -68,7 +69,7 @@ namespace DummyPageGenerator
}
/// <summary>
/// Creates a user and the associated profile with claims and such.
///
/// </summary>
/// <param name="emailAddress"></param>
/// <exception cref="Exception"></exception>
@@ -80,7 +81,8 @@ namespace DummyPageGenerator
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)
{
throw new Exception(string.Join("\r\n", result.Errors.Select(o => o.Description)));
@@ -92,18 +94,18 @@ namespace DummyPageGenerator
UsersRepository.CreateProfile(Guid.Parse(userId), GetRandomUnusedAccountName());
var claimsToAdd = new List<Claim>
{
new (ClaimTypes.Role, membershipConfig.Value<string>("Default Signup Role").EnsureNotNull()),
new ("timezone", membershipConfig.Value<string>("Default TimeZone").EnsureNotNull()),
new (ClaimTypes.Country, membershipConfig.Value<string>("Default Country").EnsureNotNull()),
new ("language", membershipConfig.Value<string>("Default Language").EnsureNotNull()),
};
{
new(ClaimTypes.Role, membershipConfig.Value<string>("Default Signup Role").EnsureNotNull()),
new("timezone", membershipConfig.Value<string>("Default TimeZone").EnsureNotNull()),
new(ClaimTypes.Country, membershipConfig.Value<string>("Default Country").EnsureNotNull()),
new("language", membershipConfig.Value<string>("Default Language").EnsureNotNull()),
};
SecurityRepository.UpsertUserClaims(_userManager, user, claimsToAdd);
}
/// <summary>
/// Creates a paragraph/sentence structure.
///
/// </summary>
/// <param name="words"></param>
/// <returns></returns>
@@ -118,7 +120,7 @@ namespace DummyPageGenerator
}
/// <summary>
/// Creates a paragraph/sentence structure with links and wiki markup.
///
/// </summary>
/// <param name="wordCount"></param>
/// <returns></returns>
@@ -135,18 +137,19 @@ namespace DummyPageGenerator
switch (_random.Next(0, 7))
{
case 2: //Dead link.
case 2:
paragraph = paragraph.Replace(token, $"[[{token}]]");
break;
case 4: //Wiki markup.
case 4:
paragraph = paragraph.Replace(token, AddWikiMarkup(token));
break;
case 6: //Good link.
case 6:
var recentPage = GetRandomRecentPageName();
if (recentPage != null)
{
paragraph = paragraph.Replace(token, $"[[{recentPage}]]");
}
break;
}
}
@@ -163,7 +166,7 @@ namespace DummyPageGenerator
return null;
}
if (_recentPageNames.Count > 200) //Shuffle and limit the recent page names.
if (_recentPageNames.Count > 200)
{
_recentPageNames = ShuffleList(_recentPageNames).Take(100).ToList();
}
@@ -176,7 +179,7 @@ namespace DummyPageGenerator
{
lock (_pagePool)
{
if (_recentPageNames.Count > 200) //Shuffle and limit the recent page names.
if (_recentPageNames.Count > 200)
{
_recentPageNames = ShuffleList(_recentPageNames).Take(100).ToList();
}
@@ -186,6 +189,7 @@ namespace DummyPageGenerator
{
pageNames.Add(_recentPageNames[_random.Next(0, _recentPageNames.Count)]);
}
return pageNames;
}
}
@@ -199,8 +203,9 @@ namespace DummyPageGenerator
}
/// <summary>
/// Creates a random page on the wiki.
///
/// </summary>
/// <param name="engine"></param>
/// <param name="userId"></param>
public void GeneratePage(IZelEngine engine, Guid userId)
{
@@ -216,7 +221,8 @@ namespace DummyPageGenerator
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($"==Overview");
@@ -232,7 +238,6 @@ namespace DummyPageGenerator
if (_random.Next(100) >= 95)
{
//Add dead links (missing pages).
textWithLinks.AddRange(WordsRepository.GetRandomWords(_random.Next(1, 2)).Select(o => $"[[{o}]]"));
}
@@ -258,7 +263,8 @@ namespace DummyPageGenerator
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);
AttachFile(newPageId, userId, fileName, fileData);
}
@@ -288,7 +294,7 @@ namespace DummyPageGenerator
}
/// <summary>
/// Modifies a random page on the wiki.
///
/// </summary>
/// <param name="userId"></param>
public void ModifyRandomPages(IZelEngine engine, Guid userId)
@@ -312,8 +318,8 @@ namespace DummyPageGenerator
string bottomText = pageToModify.Body.Substring(endIndex);
pageToModify.Body = topText.Trim()
+ "\r\n" + GenerateWikiParagraph(_random.Next(10, 20))
+ "\r\n" + bottomText.Trim();
+ "\r\n" + GenerateWikiParagraph(_random.Next(10, 20))
+ "\r\n" + bottomText.Trim();
pageToModify.ModifiedByUserId = userId;
pageToModify.ModifiedByUserId = userId;
Helpers.UpsertPage(engine, pageToModify);
@@ -328,12 +334,13 @@ namespace DummyPageGenerator
}
/// <summary>
/// Attaches a file to a wiki page.
///
/// </summary>
/// <param name="pageId"></param>
/// <param name="userId"></param>
/// <param name="fileName"></param>
/// <param name="fileData"></param>
/// <exception cref="Exception"></exception>
private void AttachFile(int pageId, Guid userId, string fileName, byte[] fileData)
{
if (fileData.Length > GlobalConfiguration.MaxAttachmentFileSize)
@@ -372,6 +379,7 @@ namespace DummyPageGenerator
newList[k] = newList[n];
newList[n] = value;
}
return newList;
}
@@ -397,4 +405,4 @@ namespace DummyPageGenerator
}
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,17 +1,19 @@
namespace ZelWiki.Engine.Function
{
/// <summary>
/// Contains information about an actual function call, its supplied parameters, and is matched with a defined function.
/// 包含有关实际函数调用及其提供的参数的信息,并与定义的函数相匹配。
/// </summary>
public class FunctionCall
{
/// <summary>
/// The name of the function being called.
///
/// </summary>
public string Name { get; private set; }
public FunctionPrototype Prototype { get; set; }
/// <summary>
/// The arguments supplied by the caller.
/// T
/// </summary>
public FunctionParameters Parameters { get; private set; }
@@ -25,7 +27,7 @@
{
if (arg.StartsWith(':') && arg.Contains('='))
{
var parsed = arg.Substring(1); //Skip the colon.
var parsed = arg.Substring(1);
int index = parsed.IndexOf('=');
var name = parsed.Substring(0, index).Trim().ToLower();
var value = parsed.Substring(index + 1).Trim();
@@ -42,9 +44,9 @@
}
/// <summary>
/// Checks the passed value against the function prototype to ensure that the variable is the correct type, value, etc.
/// 对照函数原型检查传递的值,以确保变量的类型,值等正确
/// </summary>
/// <param name="segment"></param>
/// <param name="param"></param>
/// <param name="value"></param>
/// <exception cref="Exception"></exception>
private void EnforcePrototypeParamValue(PrototypeParameter param, string value)
@@ -53,21 +55,25 @@
{
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 (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")
{
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)
{
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>
/// 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>
/// <exception cref="Exception"></exception>
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>();
//Handle non-infinite ordinal based required parameters:
for (; index < Prototype.Parameters.Count; index++)
{
var param = Prototype.Parameters[index];
@@ -101,15 +105,15 @@
{
break;
}
if (param.IsInfinite == true)
if (param.IsInfinite)
{
break;
}
if (Parameters.Ordinals.Count > index)
{
//Good, we have a value.
string value = Parameters.Ordinals[index].Value;
var value = Parameters.Ordinals[index].Value;
Parameters.Ordinals[index].AssociateWithPrototypeParam(param.Name);
EnforcePrototypeParamValue(param, value.ToLower());
@@ -123,33 +127,30 @@
bool hasEncounteredOptionalParameter = false;
//Handle remaining optional parameters:
for (; index < Prototype.Parameters.Count; 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)
{
//Good, we have a value.
string value = Parameters.Ordinals[index].Value;
var value = Parameters.Ordinals[index].Value;
Parameters.Ordinals[index].AssociateWithPrototypeParam(param.Name);
EnforcePrototypeParamValue(param, value.ToLower());
}
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++)
{
string value = Parameters.Ordinals[index].Value;
var value = Parameters.Ordinals[index].Value;
Parameters.Ordinals[index].AssociateWithPrototypeParam(param.Name);
EnforcePrototypeParamValue(param, value.ToLower());
namedToAddLater.Add(new NamedParameter(param.Name, value));
@@ -163,18 +164,20 @@
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)
{
string value = Parameters.Ordinals[index].Value;
var value = Parameters.Ordinals[index].Value;
Parameters.Ordinals[index].AssociateWithPrototypeParam(param.Name);
EnforcePrototypeParamValue(param, value.ToLower());
namedToAddLater.Add(new NamedParameter(param.Name, value));
@@ -183,8 +186,10 @@
foreach (var named in Parameters.Named)
{
var param = Prototype.Parameters.Where(o => o.Name.Equals(named.Name, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault()
?? throw new Exception($"Function [{Name}], the named parameter [{named.Name}] is not defined in the function prototype.");
var param = Prototype.Parameters
.FirstOrDefault(o => o.Name.Equals(named.Name, StringComparison.InvariantCultureIgnoreCase))
?? throw new Exception(
$"函数 [{Name}], 命名参数 [{named.Name}] 未在函数原型中定义.");
EnforcePrototypeParamValue(param, named.Value);
}
@@ -194,17 +199,20 @@
var unmatchedParams = Parameters.Ordinals.Where(o => o.IsMatched == false).ToList();
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 groups = Parameters.Named.Where(o => nonInfiniteParams.Contains(o.Name.ToLower())).GroupBy(o => o.Name.ToLower()).Where(o => o.Count() > 1);
var nonInfiniteParams =
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())
{
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
{
/// <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;
public FunctionParameters(FunctionCall 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)
{
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)
{
var prototype = _owner.Prototype.Parameters.Where(o => o.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)).First();
return Converters.ConvertTo<T>(prototype.DefaultValue) ?? throw new Exception("Value cannot be null");
var prototype = _owner.Prototype.Parameters.First(o => o.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
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)
{
throw new Exception($"Function [{_owner.Name}], {ex.Message}");
throw new Exception($"函数 [{_owner.Name}], {ex.Message}");
}
}
@@ -43,17 +47,18 @@ namespace ZelWiki.Engine.Function
{
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)
{
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)
{
throw new Exception($"Function [{_owner.Name}], {ex.Message}");
throw new Exception($"函数 [{_owner.Name}], {ex.Message}");
}
}
@@ -62,14 +67,15 @@ namespace ZelWiki.Engine.Function
try
{
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>();
}
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();
/// <summary>
/// Parsed a function call, its parameters and matches it to a defined function and its prototype.
/// 解析函数调用及其参数,并将其与已定义的函数及其原型进行匹配
/// </summary>
/// <param name="prototypes"></param>
/// <param name="functionCall"></param>
/// <param name="parseEndIndex"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static FunctionCall ParseAndGetFunctionCall(FunctionPrototypeCollection prototypes, string functionCall, out int parseEndIndex)
/// <exception cref="WikiFunctionPrototypeNotDefinedException"></exception>
public static FunctionCall ParseAndGetFunctionCall(FunctionPrototypeCollection prototypes, string functionCall,
out int parseEndIndex)
{
var rawArguments = new List<string>();
@@ -25,7 +27,8 @@ namespace ZelWiki.Engine.Function
var prototype = prototypes.Get(parsed.Prefix, parsed.Name);
if (prototype == null)
{
throw new WikiFunctionPrototypeNotDefinedException($"Function ({parsed.Name}) does not have a defined prototype.");
throw new WikiFunctionPrototypeNotDefinedException(
$"函数 ({parsed.Name}) 没有定义的原型.");
}
parseEndIndex = parsed.EndIndex;
@@ -35,18 +38,18 @@ namespace ZelWiki.Engine.Function
public static ParsedFunctionCall ParseFunctionCall(FunctionPrototypeCollection prototypes, string functionCall)
{
string functionName = string.Empty;
int parseEndIndex = 0;
var functionName = string.Empty;
var parseEndIndex = 0;
var rawArguments = new List<string>();
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);
if (parameterMatches.Count > 0)
@@ -57,12 +60,15 @@ namespace ZelWiki.Engine.Function
functionName = match.Value[..paramStartIndex].ToLower().TrimStart(['{', '#', '@']).Trim();
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);
}
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();
parseEndIndex = endOfLine + 2;
}
@@ -71,12 +77,11 @@ namespace ZelWiki.Engine.Function
}
/// <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>
/// <param name="paramString"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
/// <exception cref="WikiFunctionPrototypeSyntaxError"></exception>
public static List<string> ParseRawArgumentsAddParenthesis(string paramString)
{
if (paramString.StartsWith('(') || paramString.EndsWith(')'))
@@ -88,17 +93,16 @@ namespace ZelWiki.Engine.Function
}
/// <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>
/// <param name="paramString"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
/// <exception cref="WikiFunctionPrototypeSyntaxError"></exception>
public static List<string> ParseRawArguments(string paramString)
{
List<string> ps = new();
int readPos = 0;
var readPos = 0;
var singleParam = new StringBuilder();
@@ -109,7 +113,7 @@ namespace ZelWiki.Engine.Function
int parenNest = 1;
readPos++; //Skip the (
readPos++;
while (readPos < paramString.Length && char.IsWhiteSpace(paramString[readPos])) readPos++;
@@ -130,7 +134,7 @@ namespace ZelWiki.Engine.Function
}
else if (paramString[readPos] == ')' && parenNest == 0)
{
readPos++; //Skip the )
readPos++;
if (parenNest == 0 && readPos != paramString.Length)
{
@@ -141,6 +145,7 @@ namespace ZelWiki.Engine.Function
{
ps.Add(singleParam.ToString());
}
singleParam.Clear();
if (parenNest == 0)
@@ -150,10 +155,10 @@ namespace ZelWiki.Engine.Function
}
else if (paramString[readPos] == '\"')
{
readPos++; //Skip the ".
readPos++;
bool escapeChar = false;
for (; ; readPos++)
var escapeChar = false;
for (;; readPos++)
{
if (readPos == paramString.Length)
{
@@ -166,14 +171,14 @@ namespace ZelWiki.Engine.Function
}
else if (paramString[readPos] == '\"' && escapeChar == false)
{
//Found the end of the string:
readPos++; //Skip the ".
readPos++;
break;
}
else
{
singleParam.Append(paramString[readPos]);
}
escapeChar = false;
}
@@ -181,7 +186,7 @@ namespace ZelWiki.Engine.Function
}
else if (paramString[readPos] == ',')
{
readPos++; //Skip the ,
readPos++;
while (readPos < paramString.Length && char.IsWhiteSpace(paramString[readPos])) readPos++;
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();
}
@@ -217,4 +222,4 @@ namespace ZelWiki.Engine.Function
return ps;
}
}
}
}

View File

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

View File

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

View File

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

View File

@@ -2,11 +2,13 @@
{
public class ParsedFunctionCall
{
public string Prefix { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public int EndIndex { get; set; }
public List<string> RawArguments { get; set; } = new List<string>();
/// <summary>
///
/// </summary>
/// <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)
{
Prefix = prefix;
@@ -14,5 +16,9 @@
EndIndex = endIndex;
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 string Type { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string DefaultValue { get; set; } = string.Empty;
public bool IsRequired { get; set; } = false;
public bool IsInfinite { get; set; } = false;
public List<string> AllowedValues { get; set; } = new();
/// <summary>
///
/// </summary>
public PrototypeParameter()
{
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 string FunctionPrefix { get; set; } = string.Empty;
public string ProperName { get; set; } = string.Empty;
public string FunctionName { get; set; } = string.Empty;
public FunctionPrototype Value { get; set; } = new();
/// <summary>
///
/// </summary>
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
{
[Obsolete("弃用了")]
public static class SelfDocument
{
/// <summary>
/// Don't ever look at this. :(
///
/// </summary>
[Obsolete("弃用了")]
public static void CreateNotExisting()
{
/*

View File

@@ -2,8 +2,13 @@
{
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 string DoubleMetaphone { get; set; } = string.Empty;
public string DoubleMetaphone { get; set; }
}
}

View File

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

View File

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

View File

@@ -5,16 +5,17 @@ using ZelWiki.Models;
namespace ZelWiki.Engine.Implementation
{
/// <summary>
/// Handles wiki emojis.
///
/// </summary>
public class EmojiHandler : IEmojiHandler
{
/// <summary>
/// Handles an emoji instruction.
///
/// </summary>
/// <param name="state">Reference to the wiki state object</param>
/// <param name="key">The lookup key for the given emoji.</param>
/// <param name="scale">The desired 1-100 scale factor for the emoji.</param>
/// <param name="state"></param>
/// <param name="key"></param>
/// <param name="scale"></param>
/// <returns></returns>
public HandlerResult Handle(IZelEngineState state, string key, int scale)
{
var emoji = GlobalConfiguration.Emojis.FirstOrDefault(o => o.Shortcut == key);
@@ -23,21 +24,24 @@ namespace ZelWiki.Engine.Implementation
{
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);
}
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);
}
}
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
{
/// <summary>
/// Handles exceptions thrown by the wiki engine.
/// 异常处理.
/// </summary>
public class ExceptionHandler : IExceptionHandler
{
/// <summary>
/// Called when an exception is thrown by the wiki engine.
/// 日志处理
/// </summary>
/// <param name="state">Reference to the wiki state object</param>
/// <param name="ex">Optional exception, in the case that this was an actual exception.</param>
/// <param name="customText">Text that accompanies the exception.</param>
/// <param name="state"></param>
/// <param name="ex"></param>
/// <param name="customText"></param>
public void Log(IZelEngineState state, Exception? ex, string customText)
{
if (ex != null)
@@ -24,4 +24,4 @@ namespace ZelWiki.Engine.Implementation
ExceptionRepository.InsertException(customText);
}
}
}
}

View File

@@ -4,18 +4,18 @@ using ZelWiki.Engine.Library.Interfaces;
namespace ZelWiki.Engine.Implementation
{
/// <summary>
/// Handles links the wiki to another site.
/// 处理链接
/// </summary>
public class ExternalLinkHandler : IExternalLinkHandler
{
/// <summary>
/// Handles an internal wiki link.
/// 处理内链
/// </summary>
/// <param name="state">Reference to the wiki state object</param>
/// <param name="link">The address of the external site being linked to.</param>
/// <param name="text">The text which should be show in the absence of an image.</param>
/// <param name="image">The image that should be shown.</param>
/// <param name="imageScale">The 0-100 image scale factor for the given image.</param>
/// <param name="state"></param>
/// <param name="link"></param>
/// <param name="text"></param>
/// <param name="image"></param>
/// <returns></returns>
public HandlerResult Handle(IZelEngineState state, string link, string? text, string? image)
{
if (string.IsNullOrEmpty(image))
@@ -25,13 +25,11 @@ namespace ZelWiki.Engine.Implementation
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]
};
}
}
}
}

View File

@@ -3,18 +3,16 @@ using ZelWiki.Engine.Library.Interfaces;
namespace ZelWiki.Engine.Implementation
{
/// <summary>
/// Handles wiki headings. These are automatically added to the table of contents.
/// </summary>
public class HeadingHandler : IHeadingHandler
{
/// <summary>
/// Handles wiki headings. These are automatically added to the table of contents.
/// 处理白哦提
/// </summary>
/// <param name="state">Reference to the wiki state object</param>
/// <param name="depth">The size of the header, also used for table of table of contents indentation.</param>
/// <param name="link">The self link reference.</param>
/// <param name="text">The text for the self link.</param>
/// <param name="state"></param>
/// <param name="depth"></param>
/// <param name="link"></param>
/// <param name="text"></param>
/// <returns></returns>
public HandlerResult Handle(IZelEngineState state, int depth, string link, string text)
{
if (depth >= 2 && depth <= 6)
@@ -22,11 +20,12 @@ namespace ZelWiki.Engine.Implementation
int fontSize = 8 - depth;
if (fontSize < 5) fontSize = 5;
string html = "<font size=\"" + fontSize + "\"><a name=\"" + link + "\"><span class=\"WikiH" + (depth - 1).ToString() + "\">" + text + "</span></a></font>\r\n";
string html = "<font size=\"" + fontSize + "\"><a name=\"" + link + "\"><span class=\"WikiH" +
(depth - 1).ToString() + "\">" + text + "</span></a></font>\r\n";
return new HandlerResult(html);
}
return new HandlerResult() { Instructions = [Constants.HandlerResultInstruction.Skip] };
}
}
}
}

View File

@@ -9,41 +9,41 @@ using ZelWiki.Repository;
namespace ZelWiki.Engine.Implementation
{
/// <summary>
///
/// </summary>
public class Helpers
{
/// <summary>
/// Inserts a new page if Page.Id == 0, other wise updates the page. All metadata is written to the database.
/// 更新页面 如果Id为0则新增页面
/// </summary>
/// <param name="sessionState"></param>
/// <param name="query"></param>
/// <param name="wikifier"></param>
/// <param name="page"></param>
/// <param name="sessionState"></param>
/// <returns></returns>
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);
RefreshPageMetadata(wikifier, page, sessionState);
if (isNewlyCreated)
{
//This will update the PageId of references that have been saved to the navigation link.
PageRepository.UpdateSinglePageReference(page.Navigation, page.Id);
}
return page.Id;
}
/// <summary>
/// Rebuilds the page and writes all aspects to the database.
/// 重建页面并将所有方面写入数据库
/// </summary>
/// <param name="sessionState"></param>
/// <param name="query"></param>
/// <param name="wikifier"></param>
/// <param name="page"></param>
/// <param name="sessionState"></param>
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,
[Constants.WikiMatchType.StandardFunction]);
@@ -51,13 +51,13 @@ namespace ZelWiki.Engine.Implementation
PageRepository.UpdatePageProcessingInstructions(page.Id, state.ProcessingInstructions);
var pageTokens = ParsePageTokens(state).Select(o =>
new PageToken
{
PageId = page.Id,
Token = o.Token,
DoubleMetaphone = o.DoubleMetaphone,
Weight = o.Weight
}).ToList();
new PageToken
{
PageId = page.Id,
Token = o.Token,
DoubleMetaphone = o.DoubleMetaphone,
Weight = o.Weight
}).ToList();
PageRepository.SavePageSearchTokens(pageTokens);
@@ -67,7 +67,13 @@ namespace ZelWiki.Engine.Implementation
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>();
@@ -86,7 +92,7 @@ namespace ZelWiki.Engine.Implementation
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");
@@ -117,14 +123,17 @@ namespace ZelWiki.Engine.Implementation
tokens.RemoveAll(o => exclusionWords.Contains(o));
var searchTokens = (from w in tokens
group w by w into g
select new WeightedSearchToken
{
Token = g.Key,
Weight = g.Count() * weightMultiplier
}).ToList();
group w by w
into g
select new WeightedSearchToken
{
Token = g.Key,
Weight = g.Count() * weightMultiplier
}).ToList();
return searchTokens.Where(o => string.IsNullOrWhiteSpace(o.Token) == false).ToList();
}
#endregion
}
}
}

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ using ZelWiki.Engine.Library.Interfaces;
namespace ZelWiki.Engine.Implementation
{
/// <summary>
/// Handles processing-instruction function calls, these functions affect the way the page is processed, but are not directly replaced with text.
/// 处理处理指令函数调用,这些函数会影响页面的处理方式,但不会直接替换为文本.
/// </summary>
public class ProcessingInstructionFunctionHandler : IProcessingInstructionFunctionHandler
{
@@ -17,11 +17,11 @@ namespace ZelWiki.Engine.Implementation
{
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("@@Protect:<bool>{isSilent}='false'");
_collection.Add("@@Tags: <string:infinite>[pageTags]");
@@ -42,163 +42,152 @@ namespace ZelWiki.Engine.Implementation
}
/// <summary>
/// Called to handle function calls when proper prototypes are matched.
/// 处理各种页面
/// </summary>
/// <param name="state">Reference to the wiki state object</param>
/// <param name="function">The parsed function call and all its parameters and their values.</param>
/// <param name="scopeBody">This is not a scope function, this should always be null</param>
/// <param name="state"></param>
/// <param name="function"></param>
/// <param name="scopeBody"></param>
/// <returns></returns>
public HandlerResult Handle(IZelEngineState state, FunctionCall function, string? scopeBody = null)
{
switch (function.Name.ToLower())
{
//We check wikifierSession.Factory.CurrentNestLevel here because we don't want to include the processing instructions on any parent pages that are injecting this one.
//------------------------------------------------------------------------------------------------------------------------------
//Associates tags with a page. These are saved with the page and can also be displayed.
case "tags": //##tag(pipe|separated|list|of|tags)
{
var tags = function.Parameters.GetList<string>("pageTags");
state.Tags.AddRange(tags);
state.Tags = state.Tags.Distinct().ToList();
return new HandlerResult(string.Empty)
{
var tags = function.Parameters.GetList<string>("pageTags");
state.Tags.AddRange(tags);
state.Tags = state.Tags.Distinct().ToList();
return new HandlerResult(string.Empty)
{
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
};
}
//------------------------------------------------------------------------------------------------------------------------------
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
};
}
case "title":
{
state.PageTitle = function.Parameters.Get<string>("pageTitle");
return new HandlerResult(string.Empty)
{
state.PageTitle = function.Parameters.Get<string>("pageTitle");
return new HandlerResult(string.Empty)
{
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
};
}
//------------------------------------------------------------------------------------------------------------------------------
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
};
}
case "hidefooterlastmodified":
{
state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.HideFooterLastModified);
return new HandlerResult(string.Empty)
{
state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.HideFooterLastModified);
return new HandlerResult(string.Empty)
{
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
};
}
//------------------------------------------------------------------------------------------------------------------------------
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
};
}
case "hidefootercomments":
{
state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.HideFooterComments);
return new HandlerResult(string.Empty)
{
state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.HideFooterComments);
return new HandlerResult(string.Empty)
{
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
};
}
//------------------------------------------------------------------------------------------------------------------------------
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
};
}
case "nocache":
{
state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.NoCache);
return new HandlerResult(string.Empty)
{
state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.NoCache);
return new HandlerResult(string.Empty)
{
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
};
}
//------------------------------------------------------------------------------------------------------------------------------
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
};
}
case "deprecate":
{
if (state.NestDepth == 0)
{
if (state.NestDepth == 0)
{
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>");
}
return new HandlerResult(string.Empty)
{
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
};
state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.Deprecate);
state.Headers.Add(
"<div class=\"alert alert-danger\">此页面已被弃用,最终将被删除.</div>");
}
//------------------------------------------------------------------------------------------------------------------------------
return new HandlerResult(string.Empty)
{
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
};
}
case "protect":
{
if (state.NestDepth == 0)
{
if (state.NestDepth == 0)
bool isSilent = function.Parameters.Get<bool>("isSilent");
state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.Protect);
if (isSilent == false)
{
bool isSilent = function.Parameters.Get<bool>("isSilent");
state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.Protect);
if (isSilent == false)
{
state.Headers.Add("<div class=\"alert alert-info\">This page has been protected and can not be changed by non-moderators.</div>");
}
state.Headers.Add(
"<div class=\"alert alert-info\">此页面已受到保护,非版主无法更改.</div>");
}
return new HandlerResult(string.Empty)
{
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
};
}
//------------------------------------------------------------------------------------------------------------------------------
return new HandlerResult(string.Empty)
{
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
};
}
case "template":
{
if (state.NestDepth == 0)
{
if (state.NestDepth == 0)
{
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>");
}
return new HandlerResult(string.Empty)
{
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
};
state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.Template);
state.Headers.Add(
"<div class=\"alert alert-secondary\">此页面是一个模板,不会出现在索引或术语表中.</div>");
}
//------------------------------------------------------------------------------------------------------------------------------
return new HandlerResult(string.Empty)
{
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
};
}
case "review":
{
if (state.NestDepth == 0)
{
if (state.NestDepth == 0)
{
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>");
}
return new HandlerResult(string.Empty)
{
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
};
state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.Review);
state.Headers.Add(
"<div class=\"alert alert-warning\">此页面已被标记为待审核,其内容可能不准确.</div>");
}
//------------------------------------------------------------------------------------------------------------------------------
return new HandlerResult(string.Empty)
{
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
};
}
case "include":
{
if (state.NestDepth == 0)
{
if (state.NestDepth == 0)
{
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>");
}
return new HandlerResult(string.Empty)
{
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
};
state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.Include);
state.Headers.Add(
"<div class=\"alert alert-secondary\">此页为包含页,不会出现在索引或术语表中.</div>");
}
//------------------------------------------------------------------------------------------------------------------------------
case "draft":
return new HandlerResult(string.Empty)
{
if (state.NestDepth == 0)
{
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>");
}
return new HandlerResult(string.Empty)
{
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
};
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
};
}
case "draft":
{
if (state.NestDepth == 0)
{
state.ProcessingInstructions.Add(ZelWiki.Library.Constants.WikiInstruction.Draft);
state.Headers.Add(
"<div class=\"alert alert-warning\">本页为草稿,可能包含不正确的信息包括但不仅限于实验性样式.</div>");
}
return new HandlerResult(string.Empty)
{
Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
};
}
}
return new HandlerResult() { Instructions = [Constants.HandlerResultInstruction.Skip] };
}
}
}
}

View File

@@ -9,7 +9,7 @@ using static ZelWiki.Engine.Library.Constants;
namespace ZelWiki.Engine.Implementation
{
/// <summary>
/// Handled scope function calls.
///处理作用域函数调用.
/// </summary>
public class ScopeFunctionHandler : IScopeFunctionHandler
{
@@ -23,17 +23,23 @@ namespace ZelWiki.Engine.Implementation
{
_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("$$Order: <string>{direction(ascending,descending)}='ascending'");
_collection.Add("$$Jumbotron:");
_collection.Add("$$Callout: <string>{styleName(default,primary,secondary,success,info,warning,danger)}='default' | <string>{titleText}=''");
_collection.Add("$$Background: <string>{styleName(default,primary,secondary,light,dark,success,info,warning,danger,muted)}='default'");
_collection.Add("$$Foreground: <string>{styleName(default,primary,secondary,light,dark,success,info,warning,danger,muted)}='default'");
_collection.Add("$$Alert: <string>{styleName(default,primary,secondary,light,dark,success,info,warning,danger)}='default' | <string>{titleText}=''");
_collection.Add("$$Card: <string>{styleName(default,primary,secondary,light,dark,success,info,warning,danger)}='default' | <string>{titleText}=''");
_collection.Add(
"$$Callout: <string>{styleName(default,primary,secondary,success,info,warning,danger)}='default' | <string>{titleText}=''");
_collection.Add(
"$$Background: <string>{styleName(default,primary,secondary,light,dark,success,info,warning,danger,muted)}='default'");
_collection.Add(
"$$Foreground: <string>{styleName(default,primary,secondary,light,dark,success,info,warning,danger,muted)}='default'");
_collection.Add(
"$$Alert: <string>{styleName(default,primary,secondary,light,dark,success,info,warning,danger)}='default' | <string>{titleText}=''");
_collection.Add(
"$$Card: <string>{styleName(default,primary,secondary,light,dark,success,info,warning,danger)}='default' | <string>{titleText}=''");
_collection.Add("$$Collapse: <string>{linkText}='Show'");
_collection.Add("$$Table: <boolean>{hasBorder}='true' | <boolean>{isFirstRowHeader}='true'");
_collection.Add("$$StripedTable: <boolean>{hasBorder}='true' | <boolean>{isFirstRowHeader}='true'");
@@ -47,326 +53,308 @@ namespace ZelWiki.Engine.Implementation
}
/// <summary>
/// Called to handle function calls when proper prototypes are matched.
///
/// </summary>
/// <param name="state">Reference to the wiki state object</param>
/// <param name="function">The parsed function call and all its parameters and their values.</param>
/// <param name="scopeBody">The the text that the function is designed to affect.</param>
/// <param name="state"></param>
/// <param name="function"></param>
/// <param name="scopeBody"></param>
/// <returns></returns>
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())
{
//------------------------------------------------------------------------------------------------------------------------------
case "code":
{
var html = new StringBuilder();
var language = function.Parameters.Get<string>("language");
if (string.IsNullOrEmpty(language) || language?.ToLower() == "auto")
{
var html = new StringBuilder();
string language = function.Parameters.Get<string>("language");
if (string.IsNullOrEmpty(language) || language?.ToLower() == "auto")
{
html.Append($"<pre>");
html.Append($"<code>{scopeBody.Replace("\r\n", "\n").Replace("\n", SoftBreak)}</code></pre>");
}
else
{
html.Append($"<pre class=\"language-{language}\">");
html.Append($"<code>{scopeBody.Replace("\r\n", "\n").Replace("\n", SoftBreak)}</code></pre>");
}
return new HandlerResult(html.ToString())
{
Instructions = [Constants.HandlerResultInstruction.DisallowNestedProcessing]
};
html.Append($"<pre>");
html.Append($"<code>{scopeBody.Replace("\r\n", "\n").Replace("\n", SoftBreak)}</code></pre>");
}
else
{
html.Append($"<pre class=\"language-{language}\">");
html.Append($"<code>{scopeBody.Replace("\r\n", "\n").Replace("\n", SoftBreak)}</code></pre>");
}
//------------------------------------------------------------------------------------------------------------------------------
return new HandlerResult(html.ToString())
{
Instructions = [HandlerResultInstruction.DisallowNestedProcessing]
};
}
case "stripedtable":
case "table":
{
var html = new StringBuilder();
var hasBorder = function.Parameters.Get<bool>("hasBorder");
var isFirstRowHeader = function.Parameters.Get<bool>("isFirstRowHeader");
html.Append($"<table class=\"table");
if (function.Name.Equals("stripedtable", StringComparison.InvariantCultureIgnoreCase))
{
var html = new StringBuilder();
var hasBorder = function.Parameters.Get<bool>("hasBorder");
var isFirstRowHeader = function.Parameters.Get<bool>("isFirstRowHeader");
html.Append($"<table class=\"table");
if (function.Name.Equals("stripedtable", StringComparison.InvariantCultureIgnoreCase))
{
html.Append(" table-striped");
}
if (hasBorder)
{
html.Append(" table-bordered");
}
html.Append($"\">");
var lines = scopeBody.Split(['\n'], StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim()).Where(o => o.Length > 0);
int rowNumber = 0;
foreach (var lineText in lines)
{
var columns = lineText.Split("||");
if (rowNumber == 0 && isFirstRowHeader)
{
html.Append($"<thead>");
}
else if (rowNumber == 1 && isFirstRowHeader || rowNumber == 0 && isFirstRowHeader == false)
{
html.Append($"<tbody>");
}
html.Append($"<tr>");
foreach (var columnText in columns)
{
if (rowNumber == 0 && isFirstRowHeader)
{
html.Append($"<td><strong>{columnText}</strong></td>");
}
else
{
html.Append($"<td>{columnText}</td>");
}
}
if (rowNumber == 0 && isFirstRowHeader)
{
html.Append($"</thead>");
}
html.Append($"</tr>");
rowNumber++;
}
html.Append($"</tbody>");
html.Append($"</table>");
return new HandlerResult(html.ToString());
html.Append(" table-striped");
}
//------------------------------------------------------------------------------------------------------------------------------
if (hasBorder)
{
html.Append(" table-bordered");
}
html.Append($"\">");
var lines = scopeBody.Split(['\n'], StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim())
.Where(o => o.Length > 0);
int rowNumber = 0;
foreach (var lineText in lines)
{
var columns = lineText.Split("||");
if (rowNumber == 0 && isFirstRowHeader)
{
html.Append($"<thead>");
}
else if (rowNumber == 1 && isFirstRowHeader || rowNumber == 0 && isFirstRowHeader == false)
{
html.Append($"<tbody>");
}
html.Append($"<tr>");
foreach (var columnText in columns)
{
if (rowNumber == 0 && isFirstRowHeader)
{
html.Append($"<td><strong>{columnText}</strong></td>");
}
else
{
html.Append($"<td>{columnText}</td>");
}
}
if (rowNumber == 0 && isFirstRowHeader)
{
html.Append($"</thead>");
}
html.Append($"</tr>");
rowNumber++;
}
html.Append($"</tbody>");
html.Append($"</table>");
return new HandlerResult(html.ToString());
}
case "bullets":
{
var html = new StringBuilder();
var type = function.Parameters.Get<string>("type");
if (type == "unordered")
{
var html = new StringBuilder();
var lines = scopeBody.Split(['\n'], StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim())
.Where(o => o.Length > 0);
string type = function.Parameters.Get<string>("type");
int currentLevel = 0;
if (type == "unordered")
foreach (var line in lines)
{
var lines = scopeBody.Split(['\n'], StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim()).Where(o => o.Length > 0);
int currentLevel = 0;
foreach (var line in lines)
var newIndent = 0;
for (; newIndent < line.Length && line[newIndent] == '>'; newIndent++)
{
int newIndent = 0;
for (; newIndent < line.Length && line[newIndent] == '>'; newIndent++)
{
//Count how many '>' are at the start of the line.
}
newIndent++;
if (newIndent < currentLevel)
{
for (; currentLevel != newIndent; currentLevel--)
{
html.Append($"</ul>");
}
}
else if (newIndent > currentLevel)
{
for (; currentLevel != newIndent; currentLevel++)
{
html.Append($"<ul>");
}
}
html.Append($"<li>{line.Trim(['>'])}</li>");
//计算行开头有多少个“>”.
}
for (; currentLevel > 0; currentLevel--)
newIndent++;
if (newIndent < currentLevel)
{
html.Append($"</ul>");
for (; currentLevel != newIndent; currentLevel--)
{
html.Append($"</ul>");
}
}
else if (newIndent > currentLevel)
{
for (; currentLevel != newIndent; currentLevel++)
{
html.Append($"<ul>");
}
}
html.Append($"<li>{line.Trim(['>'])}</li>");
}
else if (type == "ordered")
for (; currentLevel > 0; currentLevel--)
{
var lines = scopeBody.Split(['\n'], StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim()).Where(o => o.Length > 0);
int currentLevel = 0;
foreach (var line in lines)
{
int newIndent = 0;
for (; newIndent < line.Length && line[newIndent] == '>'; newIndent++)
{
//Count how many '>' are at the start of the line.
}
newIndent++;
if (newIndent < currentLevel)
{
for (; currentLevel != newIndent; currentLevel--)
{
html.Append($"</ol>");
}
}
else if (newIndent > currentLevel)
{
for (; currentLevel != newIndent; currentLevel++)
{
html.Append($"<ol>");
}
}
html.Append($"<li>{line.Trim(['>'])}</li>");
}
for (; currentLevel > 0; currentLevel--)
{
html.Append($"</ol>");
}
html.Append($"</ul>");
}
}
else if (type == "ordered")
{
var lines = scopeBody.Split(['\n'], StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim())
.Where(o => o.Length > 0);
var currentLevel = 0;
foreach (var line in lines)
{
var newIndent = 0;
for (; newIndent < line.Length && line[newIndent] == '>'; newIndent++)
{
//计算行开头有多少个“>”.
}
newIndent++;
if (newIndent < currentLevel)
{
for (; currentLevel != newIndent; currentLevel--)
{
html.Append($"</ol>");
}
}
else if (newIndent > currentLevel)
{
for (; currentLevel != newIndent; currentLevel++)
{
html.Append($"<ol>");
}
}
html.Append($"<li>{line.Trim(['>'])}</li>");
}
for (; currentLevel > 0; currentLevel--)
{
html.Append($"</ol>");
}
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
return new HandlerResult(html.ToString());
}
case "definesnippet":
{
var html = new StringBuilder();
var name = function.Parameters.Get<string>("name");
if (!state.Snippets.TryAdd(name, scopeBody))
{
var html = new StringBuilder();
string name = function.Parameters.Get<string>("name");
if (!state.Snippets.TryAdd(name, scopeBody))
{
state.Snippets[name] = scopeBody;
}
return new HandlerResult(html.ToString());
state.Snippets[name] = scopeBody;
}
//------------------------------------------------------------------------------------------------------------------------------
return new HandlerResult(html.ToString());
}
case "alert":
{
var html = new StringBuilder();
{
var html = new StringBuilder();
string titleText = function.Parameters.Get<string>("titleText");
string style = function.Parameters.Get<string>("styleName").ToLower();
style = style == "default" ? "" : $"alert-{style}";
var titleText = function.Parameters.Get<string>("titleText");
var style = function.Parameters.Get<string>("styleName").ToLower();
style = style == "default" ? "" : $"alert-{style}";
if (!string.IsNullOrEmpty(titleText)) scopeBody = $"<h1>{titleText}</h1>{scopeBody}";
html.Append($"<div class=\"alert {style}\">{scopeBody}</div>");
return new HandlerResult(html.ToString());
}
if (!string.IsNullOrEmpty(titleText)) scopeBody = $"<h1>{titleText}</h1>{scopeBody}";
html.Append($"<div class=\"alert {style}\">{scopeBody}</div>");
return new HandlerResult(html.ToString());
}
case "order":
{
var html = new StringBuilder();
{
var html = new StringBuilder();
string direction = function.Parameters.Get<string>("direction");
var lines = scopeBody.Split("\n").Select(o => o.Trim()).ToList();
var direction = function.Parameters.Get<string>("direction");
var lines = scopeBody.Split("\n").Select(o => o.Trim()).ToList();
if (direction == "ascending")
{
html.Append(string.Join("\r\n", lines.OrderBy(o => o)));
}
else
{
html.Append(string.Join("\r\n", lines.OrderByDescending(o => o)));
}
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
html.Append(direction == "ascending"
? string.Join("\r\n", lines.OrderBy(o => o))
: string.Join("\r\n", lines.OrderByDescending(o => o)));
return new HandlerResult(html.ToString());
}
case "jumbotron":
{
var html = new StringBuilder();
{
var html = new StringBuilder();
string titleText = function.Parameters.Get("titleText", "");
html.Append($"<div class=\"mt-4 p-5 bg-secondary text-white rounded\">");
if (!string.IsNullOrEmpty(titleText)) html.Append($"<h1>{titleText}</h1>");
html.Append($"<p>{scopeBody}</p>");
html.Append($"</div>");
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
var titleText = function.Parameters.Get("titleText", "");
html.Append($"<div class=\"mt-4 p-5 bg-secondary text-white rounded\">");
if (!string.IsNullOrEmpty(titleText)) html.Append($"<h1>{titleText}</h1>");
html.Append($"<p>{scopeBody}</p>");
html.Append($"</div>");
return new HandlerResult(html.ToString());
}
case "foreground":
{
var html = new StringBuilder();
{
var html = new StringBuilder();
var style = BGFGStyle.GetForegroundStyle(function.Parameters.Get("styleName", "default")).Swap();
html.Append($"<p class=\"{style.ForegroundStyle} {style.BackgroundStyle}\">{scopeBody}</p>");
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
var style = BGFGStyle.GetForegroundStyle(function.Parameters.Get("styleName", "default")).Swap();
html.Append($"<p class=\"{style.ForegroundStyle} {style.BackgroundStyle}\">{scopeBody}</p>");
return new HandlerResult(html.ToString());
}
case "background":
{
var html = new StringBuilder();
{
var html = new StringBuilder();
var style = BGFGStyle.GetBackgroundStyle(function.Parameters.Get("styleName", "default"));
html.Append($"<div class=\"p-3 mb-2 {style.ForegroundStyle} {style.BackgroundStyle}\">{scopeBody}</div>");
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
var style = BGFGStyle.GetBackgroundStyle(function.Parameters.Get("styleName", "default"));
html.Append(
$"<div class=\"p-3 mb-2 {style.ForegroundStyle} {style.BackgroundStyle}\">{scopeBody}</div>");
return new HandlerResult(html.ToString());
}
case "collapse":
{
var html = new StringBuilder();
{
var html = new StringBuilder();
string linkText = function.Parameters.Get<string>("linktext");
string uid = "A" + Guid.NewGuid().ToString().Replace("-", "");
html.Append($"<a data-bs-toggle=\"collapse\" href=\"#{uid}\" role=\"button\" aria-expanded=\"false\" aria-controls=\"{uid}\">{linkText}</a>");
html.Append($"<div class=\"collapse\" id=\"{uid}\">");
html.Append($"<div class=\"card card-body\"><p class=\"card-text\">{scopeBody}</p></div></div>");
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
var linkText = function.Parameters.Get<string>("linktext");
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($"<div class=\"collapse\" id=\"{uid}\">");
html.Append($"<div class=\"card card-body\"><p class=\"card-text\">{scopeBody}</p></div></div>");
return new HandlerResult(html.ToString());
}
case "callout":
{
var html = new StringBuilder();
{
var html = new StringBuilder();
string titleText = function.Parameters.Get<string>("titleText");
string style = function.Parameters.Get<string>("styleName").ToLower();
style = style == "default" ? "" : style;
string titleText = function.Parameters.Get<string>("titleText");
string style = function.Parameters.Get<string>("styleName").ToLower();
style = style == "default" ? "" : style;
html.Append($"<div class=\"bd-callout bd-callout-{style}\">");
if (string.IsNullOrWhiteSpace(titleText) == false) html.Append($"<h4>{titleText}</h4>");
html.Append($"{scopeBody}");
html.Append($"</div>");
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
html.Append($"<div class=\"bd-callout bd-callout-{style}\">");
if (string.IsNullOrWhiteSpace(titleText) == false) html.Append($"<h4>{titleText}</h4>");
html.Append($"{scopeBody}");
html.Append($"</div>");
return new HandlerResult(html.ToString());
}
case "card":
{
var html = new StringBuilder();
{
var html = new StringBuilder();
string titleText = function.Parameters.Get<string>("titleText");
var style = BGFGStyle.GetBackgroundStyle(function.Parameters.Get("styleName", "default"));
var titleText = function.Parameters.Get<string>("titleText");
var style = BGFGStyle.GetBackgroundStyle(function.Parameters.Get("styleName", "default"));
html.Append($"<div class=\"card {style.ForegroundStyle} {style.BackgroundStyle} mb-3\">");
if (string.IsNullOrEmpty(titleText) == false)
{
html.Append($"<div class=\"card-header\">{titleText}</div>");
}
html.Append("<div class=\"card-body\">");
html.Append($"<p class=\"card-text\">{scopeBody}</p>");
html.Append("</div>");
html.Append("</div>");
return new HandlerResult(html.ToString());
}
html.Append($"<div class=\"card {style.ForegroundStyle} {style.BackgroundStyle} mb-3\">");
if (string.IsNullOrEmpty(titleText) == false)
html.Append($"<div class=\"card-header\">{titleText}</div>");
html.Append("<div class=\"card-body\">");
html.Append($"<p class=\"card-text\">{scopeBody}</p>");
html.Append("</div>");
html.Append("</div>");
return new HandlerResult(html.ToString());
}
}
return new HandlerResult() { Instructions = [Constants.HandlerResultInstruction.Skip] };
return new HandlerResult() { Instructions = [HandlerResultInstruction.Skip] };
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,14 @@
{
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; }
}
}

View File

@@ -1,9 +1,12 @@
namespace ZelWiki.Engine.Library
{
/// <summary>
///
/// </summary>
public class Constants
{
public const string SoftBreak = "<!--SoftBreak-->"; //These will remain as \r\n in the final HTML.
public const string HardBreak = "<!--HardBreak-->"; //These will remain as <br /> in the final HTML.
public const string SoftBreak = "<!--SoftBreak-->";
public const string HardBreak = "<!--HardBreak-->";
public enum WikiMatchType
{
@@ -23,22 +26,24 @@
public enum HandlerResultInstruction
{
/// <summary>
/// Does not process the match, allowing it to be processed by another handler.
/// 不处理匹配,允许它由另一个处理程序处理
/// </summary>
Skip,
/// <summary>
/// Removes any single trailing newline after match.
/// 删除匹配后的任何单个尾随换行符
/// </summary>
TruncateTrailingLine,
/// <summary>
/// Will not continue to process content in this block.
/// 将不会继续处理此块中的内容
/// </summary>
DisallowNestedProcessing,
/// <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>
OnlyReplaceFirstMatch
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,19 +3,21 @@
namespace ZelWiki.Engine.Library.Interfaces
{
/// <summary>
/// Handles links from one wiki page to another.
/// 处理内链
/// </summary>
public interface IInternalLinkHandler
{
/// <summary>
/// Handles an internal wiki link.
///
/// </summary>
/// <param name="state">Reference to the wiki state object</param>
/// <param name="pageNavigation">The navigation for the linked page.</param>
/// <param name="pageName">The name of the page being linked to.</param>
/// <param name="linkText">The text which should be show in the absence of an image.</param>
/// <param name="image">The image that should be shown.</param>
/// <param name="imageScale">The 0-100 image scale factor for the given image.</param>
public HandlerResult Handle(IZelEngineState state, NamespaceNavigation pageNavigation, string pageName, string linkText, string? image, int imageScale);
/// <param name="state"></param>
/// <param name="pageNavigation"></param>
/// <param name="pageName"></param>
/// <param name="linkText"></param>
/// <param name="image"></param>
/// <param name="imageScale"></param>
/// <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
{
/// <summary>
/// Handles basic markup/style instructions like bole, italic, underline, etc.
/// 处理基本的标记/样式指令,如粗体、斜体、下划线等.
/// </summary>
public interface IMarkupHandler
{
/// <summary>
/// Handles basic markup instructions like bole, italic, underline, etc.
///
/// </summary>
/// <param name="state">Reference to the wiki state object</param>
/// <param name="sequence">The sequence of symbols that were found to denotate this markup instruction,</param>
/// <param name="scopeBody">The body of text to apply the style to.</param>
/// <param name="state"></param>
/// <param name="sequence"></param>
/// <param name="scopeBody"></param>
/// <returns></returns>
public HandlerResult Handle(IZelEngineState state, char sequence, string scopeBody);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,27 +2,33 @@
{
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()
{
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)
{
return obj is PageReference other
&& string.Equals(Navigation, other.Navigation, StringComparison.OrdinalIgnoreCase);
&& string.Equals(Navigation, other.Navigation, StringComparison.OrdinalIgnoreCase);
}
public override int GetHashCode()
@@ -30,6 +36,12 @@
return Navigation.GetHashCode();
}
/// <summary>
///
/// </summary>
/// <param name="name"></param>
/// <param name="navigation"></param>
/// <exception cref="Exception"></exception>
public PageReference(string name, string navigation)
{
var parts = name.Split("::");
@@ -45,10 +57,10 @@
}
else
{
throw new Exception($"Invalid page name {name}");
throw new Exception($"页面名称无效: {name}");
}
Navigation = navigation;
}
}
}
}

View File

@@ -1,7 +1,7 @@
namespace ZelWiki.Engine.Library
{
/// <summary>
/// Table of contents tag.
///
/// </summary>
public class TableOfContentsTag
{
@@ -10,6 +10,13 @@
public string Text;
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)
{
Level = level;

View File

@@ -5,18 +5,17 @@ namespace ZelWiki.Engine
public class WikiMatchSet
{
/// <summary>
/// The type of match that was found.
/// 找到的匹配类型.
/// </summary>
public Constants.WikiMatchType MatchType { get; set; }
/// <summary>
/// The resulting content of the wiki processing.
/// 内容.
/// </summary>
public string Content { get; set; } = string.Empty;
/// <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>
public bool AllowNestedDecode { get; set; }
}

View File

@@ -2,7 +2,17 @@
{
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; }
}
}

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ using ZelWiki.Models;
namespace ZelWiki.Engine
{
/// <summary>
/// Tiny wikifier (reduced feature-set) for things like comments and profile bios.
///
/// </summary>
public class WikifierLite
{
@@ -17,9 +17,9 @@ namespace ZelWiki.Engine
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 content = new WikiString(unprocessedText);
@@ -75,7 +75,8 @@ namespace ZelWiki.Engine
{
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));
}
else
@@ -92,7 +93,7 @@ namespace ZelWiki.Engine
}
/// <summary>
/// Transform basic markup such as bold, italics, underline, etc. for single and multi-line.
/// 转换单行和多行的基本标记,如粗体、斜体、下划线等.
/// </summary>
/// <param name="pageContent"></param>
private static void TransformMarkup(WikiString pageContent, Dictionary<string, string> matchStore)
@@ -126,12 +127,12 @@ namespace ZelWiki.Engine
}
/// <summary>
/// Transform links, these can be internal Wiki links or external links.
/// 转换链接这些链接可以是内部Wiki链接或外部链接.
/// </summary>
/// <param name="pageContent"></param>
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 matches = WikiUtility.OrderMatchesByLengthDescending(rgx.Matches(pageContent.ToString()));
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);
matches = WikiUtility.OrderMatchesByLengthDescending(rgx.Matches(pageContent.ToString()));
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);
matches = WikiUtility.OrderMatchesByLengthDescending(rgx.Matches(pageContent.ToString()));
foreach (var match in matches)
@@ -177,13 +178,15 @@ namespace ZelWiki.Engine
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)
{
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>
/// Transforms the content for the given page.
/// 转换给定页面的内容.
/// </summary>
/// <param name="session">The users current state, used for localization.</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 Dictionary<string, object> _handlerState = new();
#region Public properties.
#region .
/// <summary>
/// Custom page title set by a call to @@Title("...")
/// 通过调用设置自定义页面标题 @@Title("...")
/// </summary>
public string? PageTitle { get; set; }
public int ErrorCount { get; private set; }
public int MatchCount { get; private set; }
public int TransformIterations { get; private set; }
@@ -45,19 +46,19 @@ namespace ZelWiki.Engine
#endregion
#region Input parameters.
#region .
public IPage Page { get; }
public int? Revision { get; }
public IQueryCollection QueryString { get; }
public ISessionState? Session { get; }
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
/// <summary>
/// Used to store values for handlers that needs to survive only a single wiki processing session.
/// 存储只需要在单个wiki处理会话中生存的处理程序的值.
/// </summary>
public void SetStateValue<T>(string key, T value)
{
@@ -65,11 +66,12 @@ namespace ZelWiki.Engine
{
return;
}
_handlerState[key] = value;
}
/// <summary>
/// Used to get values for handlers that needs to survive only a single wiki processing session.
/// 获取只需要在单个处理会话中生存的处理程序的值.
/// </summary>
public T GetStateValue<T>(string key, T defaultValue)
{
@@ -77,12 +79,13 @@ namespace ZelWiki.Engine
{
return (T)value;
}
SetStateValue(key, defaultValue);
return defaultValue;
}
/// <summary>
/// Used to get values for handlers that needs to survive only a single wiki processing session.
/// 尝试获取只需要在单个处理会话中生存的处理程序的值..
/// </summary>
public bool TryGetStateValue<T>(string key, [MaybeNullWhen(false)] out T? outValue)
{
@@ -97,13 +100,14 @@ namespace ZelWiki.Engine
}
/// <summary>
/// Creates a new instance of the ZelEngineState class. Typically created by a call to ZelEngine.Transform().
/// 创建一个实例
/// </summary>
/// <param name="session">The users current state, used for localization.</param>
/// <param name="page">The page that is being processed.</param>
/// <param name="revision">The revision of the page that is being processed.</param>
/// <param name="omitMatches">The type of matches that we want to omit from processing.</param>
/// <param name="nestDepth">The current depth of recursion.</param>
/// <param name="engine"></param>
/// <param name="session"></param>
/// <param name="page"></param>
/// <param name="revision"></param>
/// <param name="omitMatches"></param>
/// <param name="nestDepth"></param>
internal ZelEngineState(IZelEngine engine, ISessionState? session,
IPage page, int? revision = null, Constants.WikiMatchType[]? omitMatches = null, int nestDepth = 0)
{
@@ -123,15 +127,15 @@ namespace ZelWiki.Engine
}
/// <summary>
/// Transforms "included" wiki pages, for example if a wiki function
/// injected additional wiki markup, this 'could' be processed separately.
///
/// </summary>
/// <param name="page">The child page to process</param>
/// <param name="revision">The optional revision of the child page to process</param>
/// <param name="page"></param>
/// <param name="revision"></param>
/// <returns></returns>
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()
@@ -169,21 +173,15 @@ namespace ZelWiki.Engine
foreach (var v in Matches)
{
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);
}
else
{
pageContent.Replace(v.Key, v.Value.Content);
}
}
} while (length != pageContent.Length);
pageContent.Replace(SoftBreak, "\r\n");
pageContent.Replace(HardBreak, "<br />");
//Prepend any headers that were added by wiki handlers.
foreach (var header in Headers)
{
pageContent.Insert(0, header);
@@ -223,7 +221,6 @@ namespace ZelWiki.Engine
TransformStandardFunctions(pageContent, false);
TransformProcessingInstructionFunctions(pageContent);
//We have to replace a few times because we could have replace tags (guids) nested inside others.
int length;
do
{
@@ -233,15 +230,10 @@ namespace ZelWiki.Engine
if (v.Value.AllowNestedDecode)
{
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);
}
else
{
pageContent.Replace(v.Key, v.Value.Content);
}
else
pageContent.Replace(v.Key, v.Value.Content);
}
}
} while (length != pageContent.Length);
@@ -250,7 +242,7 @@ namespace ZelWiki.Engine
}
/// <summary>
/// Transform basic markup such as bold, italics, underline, etc. for single and multi-line.
/// 转换单行和多行的基本标记,如粗体、斜体、下划线等.
/// </summary>
/// <param name="pageContent"></param>
private void TransformMarkup(WikiString pageContent)
@@ -270,7 +262,8 @@ namespace ZelWiki.Engine
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)
{
int headingMarkers = 0;
var headingMarkers = 0;
foreach (char c in match.Value)
{
if (c != '^')
{
break;
}
headingMarkers++;
}
if (headingMarkers >= 2 && headingMarkers <= 6)
{
string value = match.Value.Substring(headingMarkers, match.Value.Length - headingMarkers).Trim();
@@ -295,44 +290,43 @@ namespace ZelWiki.Engine
int fontSize = 1 + headingMarkers;
if (fontSize < 1) fontSize = 1;
string markup = "<font size=\"" + fontSize + "\">" + value + "</font>\r\n";
StoreMatch(Constants.WikiMatchType.Markup, pageContent, match.Value, markup);
var markup = "<font size=\"" + fontSize + "\">" + value + "</font>\r\n";
StoreMatch(WikiMatchType.Markup, pageContent, match.Value, markup);
}
}
}
/// <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>
/// <param name="pageContent"></param>
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(
PrecompiledRegex.TransformLiterals().Matches(pageContent.ToString()));
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);
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>
/// Matching nested blocks with regex was hell, I escaped with a loop. ¯\_(ツ)_/¯
///
/// </summary>
/// <param name="pageContent"></param>
private void TransformScopeFunctions(WikiString pageContent)
{
var content = pageContent.ToString();
string rawBlock = string.Empty;
var rawBlock = string.Empty;
int startPos = content.Length - 1;
var startPos = content.Length - 1;
while (true)
{
@@ -341,18 +335,24 @@ namespace ZelWiki.Engine
{
break;
}
int endPos = content.IndexOf("}}", startPos);
if (endPos < 0 || endPos < startPos)
{
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)
{
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());
}
@@ -369,10 +369,10 @@ namespace ZelWiki.Engine
}
/// <summary>
/// Transform blocks or sections of code, these are thinks like panels and alerts.
///
/// </summary>
/// <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)
{
// {{([\\S\\s]*)}}
@@ -387,14 +387,14 @@ namespace ZelWiki.Engine
FunctionCall function;
//We are going to mock up a function call:
string mockFunctionCall = "##" + match.Value.Trim([' ', '\t', '{', '}']);
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)
{
continue;
@@ -411,7 +411,8 @@ namespace ZelWiki.Engine
try
{
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)
{
@@ -421,7 +422,7 @@ namespace ZelWiki.Engine
}
/// <summary>
/// Transform headings. These are the basic HTML H1-H6 headings but they are saved for the building of the table of contents.
///
/// </summary>
/// <param name="pageContent"></param>
private void TransformHeadings(WikiString pageContent)
@@ -431,28 +432,32 @@ namespace ZelWiki.Engine
foreach (var match in orderedMatches)
{
int headingMarkers = 0;
foreach (char c in match.Value)
var headingMarkers = 0;
foreach (var c in match.Value)
{
if (c != '=')
{
break;
}
headingMarkers++;
}
if (headingMarkers >= 2)
{
string link = _tocName + "_" + TableOfContents.Count.ToString();
string text = match.Value.Substring(headingMarkers, match.Value.Length - headingMarkers).Trim().Trim(['=']).Trim();
var link = _tocName + "_" + TableOfContents.Count.ToString();
var text = match.Value.Substring(headingMarkers, match.Value.Length - headingMarkers).Trim()
.Trim(['=']).Trim();
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));
}
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)
{
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)
{
string key = match.Value.Trim().ToLower().Trim('%');
int scale = 100;
var key = match.Value.Trim().ToLower().Trim('%');
var scale = 100;
var parts = key.Split(',');
if (parts.Length > 1)
{
key = parts[0]; //Image key;
scale = int.Parse(parts[1]); //Image scale.
key = parts[0];
scale = int.Parse(parts[1]);
}
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>
/// Transform variables.
///
/// </summary>
/// <param name="pageContent"></param>
private void TransformVariables(WikiString pageContent)
@@ -502,7 +507,7 @@ namespace ZelWiki.Engine
foreach (var match in orderedMatches)
{
string key = match.Value.Trim(['{', '}', ' ', '\t', '$']);
var key = match.Value.Trim(['{', '}', ' ', '\t', '$']);
if (key.Contains("="))
{
var sections = key.Split('=');
@@ -514,27 +519,27 @@ namespace ZelWiki.Engine
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.
}
else
{
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.
}
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>
/// Transform links, these can be internal Wiki links or external links.
///
/// </summary>
/// <param name="pageContent"></param>
private void TransformLinks(WikiString pageContent)
@@ -572,7 +577,6 @@ namespace ZelWiki.Engine
}
}
//Parse external explicit links. eg. [[https://test.net]].
orderedMatches = WikiUtility.OrderMatchesByLengthDescending(
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(
PrecompiledRegex.TransformInternalDynamicLinks().Matches(pageContent.ToString()));
@@ -622,29 +625,26 @@ namespace ZelWiki.Engine
if (args.Count == 1)
{
//Page navigation only.
text = WikiUtility.GetPageNamePart(args[0]); //Text will be page name since we have an image.
text = WikiUtility.GetPageNamePart(args[0]);
pageName = args[0];
}
else if (args.Count >= 2)
{
//Page navigation and explicit text (possibly image).
pageName = args[0];
string imageTag = "image:";
if (args[1].StartsWith(imageTag, StringComparison.CurrentCultureIgnoreCase))
{
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
{
text = args[1]; //Explicit text.
text = args[1];
}
if (args.Count >= 3)
{
//Get the specified image scale.
if (int.TryParse(args[2], out imageScale) == false)
{
imageScale = 100;
@@ -659,32 +659,24 @@ namespace ZelWiki.Engine
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 (!result.Instructions.Contains(Constants.HandlerResultInstruction.Skip))
if (string.IsNullOrEmpty(pageNavigation.Namespace) || pageName.Trim().StartsWith("::"))
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));
}
StoreHandlerResult(result, Constants.WikiMatchType.Link, pageContent, match.Value, string.Empty);
StoreHandlerResult(result, WikiMatchType.Link, pageContent, match.Value, string.Empty);
}
}
/// <summary>
/// Transform processing instructions are used to flag pages for specific needs such as deletion, review, draft, etc.
///
/// </summary>
/// <param name="pageContent"></param>
private void TransformProcessingInstructionFunctions(WikiString pageContent)
@@ -701,7 +693,8 @@ namespace ZelWiki.Engine
try
{
function = FunctionParser.ParseAndGetFunctionCall(functionHandler.Prototypes, match.Value, out int matchEndIndex);
function = FunctionParser.ParseAndGetFunctionCall(functionHandler.Prototypes, match.Value,
out int matchEndIndex);
}
catch (Exception ex)
{
@@ -712,7 +705,8 @@ namespace ZelWiki.Engine
try
{
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)
{
@@ -722,9 +716,10 @@ namespace ZelWiki.Engine
}
/// <summary>
/// Transform functions is used to call wiki functions such as including template pages, setting tags and displaying images.
///
/// </summary>
/// <param name="pageContent"></param>
/// <param name="isFirstChance"></param>
private void TransformStandardFunctions(WikiString pageContent, bool isFirstChance)
{
//Remove the last "(\#\#[\w-]+)" if you start to have matching problems:
@@ -739,7 +734,8 @@ namespace ZelWiki.Engine
try
{
function = FunctionParser.ParseAndGetFunctionCall(functionHandler.Prototypes, match.Value, out int matchEndIndex);
function = FunctionParser.ParseAndGetFunctionCall(functionHandler.Prototypes, match.Value,
out int matchEndIndex);
}
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.
}
}
StoreError(pageContent, match.Value, ex.Message);
continue;
}
@@ -771,7 +768,8 @@ namespace ZelWiki.Engine
try
{
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)
{
@@ -781,8 +779,9 @@ namespace ZelWiki.Engine
}
/// <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>
/// <param name="pageContent"></param>
private void TransformPostProcessingFunctions(WikiString pageContent)
{
//Remove the last "(\#\#[\w-]+)" if you start to have matching problems:
@@ -797,7 +796,8 @@ namespace ZelWiki.Engine
try
{
function = FunctionParser.ParseAndGetFunctionCall(functionHandler.Prototypes, match.Value, out int matchEndIndex);
function = FunctionParser.ParseAndGetFunctionCall(functionHandler.Prototypes, match.Value,
out int matchEndIndex);
}
catch (Exception ex)
{
@@ -808,7 +808,8 @@ namespace ZelWiki.Engine
try
{
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)
{
@@ -821,33 +822,31 @@ namespace ZelWiki.Engine
{
string identifier = $"<!--{Guid.NewGuid()}-->";
//Replace new-lines with single character new line:
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);
//Replace any consecutive to-be-line-breaks that we are introducing with single line-break identifiers.
pageContent.Replace($"{identifier}{identifier}", identifier);
//Swap in the real line-breaks.
pageContent.Replace(identifier, "<br />");
}
#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;
}
bool allowNestedDecode = !result.Instructions.Contains(Constants.HandlerResultInstruction.DisallowNestedProcessing);
bool allowNestedDecode =
!result.Instructions.Contains(HandlerResultInstruction.DisallowNestedProcessing);
string identifier;
if (result.Instructions.Contains(Constants.HandlerResultInstruction.OnlyReplaceFirstMatch))
if (result.Instructions.Contains(HandlerResultInstruction.OnlyReplaceFirstMatch))
{
identifier = StoreFirstMatch(matchType, pageContent, matchValue, result.Content, allowNestedDecode);
}
@@ -860,7 +859,7 @@ namespace ZelWiki.Engine
{
switch (instruction)
{
case Constants.HandlerResultInstruction.TruncateTrailingLine:
case HandlerResultInstruction.TruncateTrailingLine:
pageContent.Replace($"{identifier}\n", $"{identifier}"); //Kill trailing newline.
break;
}
@@ -888,7 +887,7 @@ namespace ZelWiki.Engine
{
Content = $"<i><font size=\"3\" color=\"#BB0000\">{{{value}}}</font></a>",
AllowNestedDecode = false,
MatchType = Constants.WikiMatchType.Error
MatchType = WikiMatchType.Error
};
Matches.Add(identifier, matchSet);
@@ -897,7 +896,8 @@ namespace ZelWiki.Engine
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++;
_matchesStoredPerIteration++;
@@ -917,7 +917,8 @@ namespace ZelWiki.Engine
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++;
_matchesStoredPerIteration++;
@@ -940,17 +941,16 @@ namespace ZelWiki.Engine
}
/// <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>
/// <returns></returns>
public string GetNextQueryToken()
{
_queryTokenHash = ZelWiki.Security.Helpers.Sha256(ZelWiki.Security.Helpers.EncryptString(ZelWiki.Security.Helpers.MachineKey, _queryTokenHash));
return $"H{ZelWiki.Security.Helpers.Crc32(_queryTokenHash)}";
_queryTokenHash = Security.Helpers.Sha256(
Security.Helpers.EncryptString(Security.Helpers.MachineKey, _queryTokenHash));
return $"H{Security.Helpers.Crc32(_queryTokenHash)}";
}
#endregion
}
}
}

View File

@@ -6,14 +6,14 @@ namespace ZelWiki.Library
public static class ConfirmActionHelper
{
/// <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>
/// <param name="message">The message to be displayed.</param>
/// <param name="linkLabel">the label for the link that will redirect to this confirm action page.</param>
/// <param name="controllerURL">The URL which will handle the click of the "yes" or "no" for the confirm action page.</param>
/// <param name="parameter">An optional parameter to pass to the page and controller function.</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">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="basePath"></param>
/// <param name="message"></param>
/// <param name="linkLabel"></param>
/// <param name="controllerURL"></param>
/// <param name="yesOrDefaultRedirectURL"></param>
/// <param name="noRedirectURL"></param>
/// <returns></returns>
public static string GenerateDangerLink(string basePath, string message, string linkLabel, string controllerURL,
string? yesOrDefaultRedirectURL, string? noRedirectURL = null)
@@ -30,18 +30,19 @@ namespace ZelWiki.Library
param.Append($"&Message={Uri.EscapeDataString(message)}");
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>
/// Generates a link that navigates via GET to a "confirm action" page where the yes link is GREEN.
/// 生成一个链接通过GET导航到“确认操作”页面其中“是”链接为绿色。
/// </summary>
/// <param name="message">The message to be displayed.</param>
/// <param name="linkLabel">the label for the link that will redirect to this confirm action page.</param>
/// <param name="controllerURL">The URL which will handle the click of the "yes" or "no" for the confirm action page.</param>
/// <param name="parameter">An optional parameter to pass to the page and controller function.</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">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="basePath"></param>
/// <param name="message"></param>
/// <param name="linkLabel"></param>
/// <param name="controllerURL"></param>
/// <param name="yesOrDefaultRedirectURL"></param>
/// <param name="noRedirectURL"></param>
/// <returns></returns>
public static string GenerateSafeLink(string basePath, string message, string linkLabel, string controllerURL,
string? yesOrDefaultRedirectURL, string? noRedirectURL = null)
@@ -58,18 +59,19 @@ namespace ZelWiki.Library
param.Append($"&Message={Uri.EscapeDataString(message)}");
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>
/// 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>
/// <param name="message">The message to be displayed.</param>
/// <param name="linkLabel">the label for the link that will redirect to this confirm action page.</param>
/// <param name="controllerURL">The URL which will handle the click of the "yes" or "no" for the confirm action page.</param>
/// <param name="parameter">An optional parameter to pass to the page and controller function.</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">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="basePath"></param>
/// <param name="message"></param>
/// <param name="linkLabel"></param>
/// <param name="controllerURL"></param>
/// <param name="yesOrDefaultRedirectURL"></param>
/// <param name="noRedirectURL"></param>
/// <returns></returns>
public static string GenerateWarnLink(string basePath, string message, string linkLabel, string controllerURL,
string? yesOrDefaultRedirectURL, string? noRedirectURL = null)
@@ -86,19 +88,19 @@ namespace ZelWiki.Library
param.Append($"&Message={Uri.EscapeDataString(message)}");
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>
/// 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>
/// <param name="message">The message to be displayed.</param>
/// <param name="buttonLabel">the label for the button that will redirect to this confirm action page.</param>
/// <param name="controllerURL">The URL which will handle the click of the "yes" or "no" for the confirm action page.</param>
/// <param name="parameter">An optional parameter to pass to the page and controller function.</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">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="message"></param>
/// <param name="buttonLabel"></param>
/// <param name="controllerURL"></param>
/// <param name="yesOrDefaultRedirectURL"></param>
/// <param name="noRedirectURL"></param>
/// <returns></returns>
public static string GenerateDangerButton(string message, string buttonLabel, string controllerURL,
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='Message' value='{message}' />");
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>");
return html.ToString();
}
/// <summary>
/// Generates a link that navigates via POST to a "confirm action" page where the yes and no buttons are GREEN.
///
/// </summary>
/// <param name="message">The message to be displayed.</param>
/// <param name="buttonLabel">the label for the button that will redirect to this confirm action page.</param>
/// <param name="controllerURL">The URL which will handle the click of the "yes" or "no" for the confirm action page.</param>
/// <param name="parameter">An optional parameter to pass to the page and controller function.</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">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="message"></param>
/// <param name="buttonLabel"></param>
/// <param name="controllerURL"></param>
/// <param name="yesOrDefaultRedirectURL"></param>
/// <param name="noRedirectURL"></param>
/// <returns></returns>
public static string GenerateSafeButton(string message, string buttonLabel, string controllerURL,
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='Message' value='{message}' />");
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>");
return html.ToString();
}
/// <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>
/// <param name="message">The message to be displayed.</param>
/// <param name="buttonLabel">the label for the button that will redirect to this confirm action page.</param>
/// <param name="controllerURL">The URL which will handle the click of the "yes" or "no" for the confirm action page.</param>
/// <param name="parameter">An optional parameter to pass to the page and controller function.</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">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="message"></param>
/// <param name="buttonLabel"></param>
/// <param name="controllerURL"></param>
/// <param name="yesOrDefaultRedirectURL"></param>
/// <param name="noRedirectURL"></param>
/// <returns></returns>
public static string GenerateWarnButton(string message, string buttonLabel, string controllerURL,
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='Message' value='{message}' />");
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>");
return html.ToString();
}
*/
}
}
}

View File

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

View File

@@ -14,7 +14,7 @@ namespace ZelWiki.Library
foreach (var ci in CultureInfo.GetCultures(CultureTypes.SpecificCultures))
{
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
{

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ namespace ZelWiki.Library
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
{

View File

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

View File

@@ -29,6 +29,7 @@ namespace ZelWiki.Library
{
return Page;
}
return $"{Namespace}::{Page}";
}
set
@@ -49,9 +50,9 @@ namespace ZelWiki.Library
}
/// <summary>
/// Creates a new instance of NamespaceNavigation.
///
/// </summary>
/// <param name="givenCanonical">Page navigation with optional namespace.</param>
/// <param name="givenCanonical"></param>
public NamespaceNavigation(string givenCanonical)
{
_lowerCase = true;
@@ -59,10 +60,10 @@ namespace ZelWiki.Library
}
/// <summary>
/// Creates a new instance of NamespaceNavigation.
///
/// </summary>
/// <param name="givenCanonical">Page navigation with optional namespace.</param>
/// <param name="lowerCase">If false, the namespace and page name will not be lowercased.</param>
/// <param name="givenCanonical"></param>
/// <param name="lowerCase"></param>
public NamespaceNavigation(string givenCanonical, bool lowerCase)
{
_lowerCase = lowerCase;
@@ -75,10 +76,10 @@ namespace ZelWiki.Library
}
/// <summary>
/// Takes a page name with optional namespace and returns the cleaned version that can be used for matching Navigations.
///
/// </summary>
/// <param name="givenCanonical">Page navigation with optional namespace.</param>
/// <param name="lowerCase">If false, the namespace and page name will not be lowercased.</param>
/// <param name="str"></param>
/// <param name="lowerCase"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static string CleanAndValidate(string? str, bool lowerCase = true)
@@ -88,7 +89,6 @@ namespace ZelWiki.Library
return string.Empty;
}
//Fix names like "::Page" or "Namespace::".
str = str.Trim().Trim([':']).Trim();
if (str.Contains("::"))
@@ -96,23 +96,22 @@ namespace ZelWiki.Library
var parts = str.Split("::");
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)}";
}
// Decode common HTML entities
str = str.Replace("&quot;", "\"")
.Replace("&amp;", "&")
.Replace("&lt;", "<")
.Replace("&gt;", ">")
.Replace("&nbsp;", " ");
.Replace("&amp;", "&")
.Replace("&lt;", "<")
.Replace("&gt;", ">")
.Replace("&nbsp;", " ");
// Normalize backslashes to forward slashes
str = str.Replace('\\', '/');
var sb = new StringBuilder();
foreach (char c in str)
foreach (var c in str)
{
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,}", "/");
if (lowerCase)
{
return result.TrimEnd(['/', '\\']).ToLowerInvariant();
}
else
{
return result.TrimEnd(['/', '\\']);
}
return lowerCase ? result.TrimEnd(['/', '\\']).ToLowerInvariant() : result.TrimEnd(['/', '\\']);
}
}
}
}

View File

@@ -3,6 +3,9 @@ using System.Text.RegularExpressions;
namespace ZelWiki.Library
{
/// <summary>
/// 导航
/// </summary>
public class Navigation
{
public static string Clean(string? str)
@@ -11,30 +14,26 @@ namespace ZelWiki.Library
{
return string.Empty;
}
//Fix names like "::Page" or "Namespace::".
str = str.Trim().Trim([':']).Trim();
if (str.Contains("::"))
{
throw new Exception("Navigation can not contain a namespace.");
throw new Exception("导航不能包含命名空间");
}
// Decode common HTML entities
str = str.Replace("&quot;", "\"")
.Replace("&amp;", "&")
.Replace("&lt;", "<")
.Replace("&gt;", ">")
.Replace("&nbsp;", " ");
// Normalize backslashes to forward slashes
str = str.Replace('\\', '/');
// Replace special sequences
str = str.Replace("::", "_").Trim();
var sb = new StringBuilder();
foreach (char c in str)
foreach (var c in str)
{
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,}", "/");

View File

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

View File

@@ -8,18 +8,19 @@ namespace ZelWiki.Library
public static class QueryStringConverter
{
/// <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>
/// <param name="context"></param>
/// <param name="value"></param>
/// <returns></returns>
public static string OrderHelper(ISessionState context, string value)
{
string orderByKey = "OrderBy";
string orderByDirectionKey = "OrderByDirection";
string? currentDirection = "asc";
var orderByKey = "OrderBy";
var orderByDirectionKey = "OrderByDirection";
var currentDirection = "asc";
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))
{
bool invertDirection = string.Equals(currentValue, value, StringComparison.InvariantCultureIgnoreCase);
@@ -28,14 +29,7 @@ namespace ZelWiki.Library
{
if (collection.TryGetValue(orderByDirectionKey, out currentDirection))
{
if (currentDirection == "asc")
{
currentDirection = "desc";
}
else
{
currentDirection = "asc";
}
currentDirection = currentDirection == "asc" ? "desc" : "asc";
}
}
else
@@ -53,13 +47,13 @@ namespace ZelWiki.Library
return FromCollection(collection);
}
/// <summary>
/// Takes the current page query string and upserts a query key/value, replacing any conflicting query string entry.
/// </summary>
/// <param name="queryString"></param>
/// <param name="name"></param>
/// <param name="value"></param>
/// <returns></returns>
/// <summary>
///
/// </summary>
/// <param name="queryString"></param>
/// <param name="name"></param>
/// <param name="value"></param>
/// <returns></returns>
public static string Upsert(IQueryCollection? queryString, string name, string value)
{
var collection = ToDictionary(queryString);
@@ -79,8 +73,6 @@ namespace ZelWiki.Library
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);
}
@@ -98,13 +90,11 @@ namespace ZelWiki.Library
return dictionary;
}
// If the query string starts with '?', remove it
if (queryString.StartsWith('?'))
{
queryString = queryString.Substring(1);
}
// Split the query string into key-value pairs
var keyValuePairs = queryString.Split('&');
foreach (var kvp in keyValuePairs)
@@ -158,6 +148,7 @@ namespace ZelWiki.Library
{
queryString.Append('&');
}
queryString.Append($"{Uri.EscapeDataString(kvp.Key)}={Uri.EscapeDataString(kvp.Value)}");
}
@@ -176,7 +167,8 @@ namespace ZelWiki.Library
{
clone.Add(kvp.Key, kvp.Value);
}
return clone;
}
}
}
}

View File

@@ -2,13 +2,28 @@
{
public class Theme
{
public string Name { get; set; } = string.Empty;
public string DelimitedFiles { get; set; } = string.Empty;
public string ClassNavBar { get; set; } = string.Empty;
public string ClassNavLink { get; set; } = string.Empty;
public string ClassDropdown { get; set; } = string.Empty;
public string ClassBranding { get; set; } = string.Empty;
public string EditorTheme { get; set; } = string.Empty;
public List<string> Files { get; set; } = new();
/// <summary>
///
/// </summary>
public Theme()
{
Name = string.Empty;
DelimitedFiles = string.Empty;
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 string Text { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
/// <summary>
///
/// </summary>
public TimeZoneItem()
{
Text = string.Empty;
Value = string.Empty;
}
public string Text { get; set; }
public string Value { get; set; }
public static List<TimeZoneItem> GetAll()
{
@@ -17,4 +26,4 @@
return list.OrderBy(o => o.Text).ToList();
}
}
}
}

View File

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

View File

@@ -16,7 +16,7 @@
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)
{
return default;
@@ -26,7 +26,7 @@
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)
{
return defaultValue;

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,11 +13,12 @@ namespace ZelWiki.Repository
{
public static class ConfigurationRepository
{
#region Upgrade Database.
#region
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";
}
@@ -25,11 +26,12 @@ namespace ZelWiki.Repository
{
var version = string.Join('.',
(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>
/// See @Initialization.Versions.md
///
/// </summary>
public static void UpgradeDatabase()
{
@@ -45,43 +47,44 @@ namespace ZelWiki.Repository
if (currentPaddedVersion == storedPaddedVersion)
{
return; //The database version is already at the latest version.
return;
}
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.";
string endVersionTag = ".^";
var startVersionTag = ".Initialization.Versions.";
var endVersionTag = ".^";
foreach (var updateScriptName in updateScriptNames)
{
int startIndex = updateScriptName.IndexOf(startVersionTag, StringComparison.InvariantCultureIgnoreCase);
int startIndex =
updateScriptName.IndexOf(startVersionTag, StringComparison.InvariantCultureIgnoreCase);
if (startIndex >= 0)
{
startIndex += startVersionTag.Length;
int endIndex = updateScriptName.IndexOf(endVersionTag, startIndex, StringComparison.InvariantCultureIgnoreCase);
var endIndex = updateScriptName.IndexOf(endVersionTag, startIndex,
StringComparison.InvariantCultureIgnoreCase);
if (endIndex > startIndex)
{
//The name of the script file without the namespaces, version numbers etc.
var fullScriptName = updateScriptName.Substring(endIndex + endVersionTag.Length).Trim().Replace("_", "");
var fullScriptName = updateScriptName.Substring(endIndex + endVersionTag.Length).Trim()
.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)
{
//Get the script text.
using var stream = assembly.GetManifestResourceStream(updateScriptName);
using var reader = new StreamReader(stream.EnsureNotNull());
var scriptText = reader.ReadToEnd();
//Get the script "metadata" from the file name.
var scriptNameParts = fullScriptName.Split('^');
//string executionOrder = scriptNameParts[0];
string databaseName = scriptNameParts[1];
//string scriptName = scriptNameParts[2];
var databaseName = scriptNameParts[1];
var databaseFactory = ManagedDataStorage.Collection.Single(o => o.Name == databaseName).Factory;
var databaseFactory = ManagedDataStorage.Collection.Single(o => o.Name == databaseName)
.Factory;
databaseFactory.Execute(scriptText);
}
@@ -95,13 +98,14 @@ namespace ZelWiki.Repository
}
catch (Exception ex)
{
ExceptionRepository.InsertException(ex, "Database upgrade failed.");
ExceptionRepository.InsertException(ex, "数据库升级失败");
}
}
#endregion
public static ConfigurationEntries GetConfigurationEntryValuesByGroupName(string groupName, bool allowCache = true)
public static ConfigurationEntries GetConfigurationEntryValuesByGroupName(string groupName,
bool allowCache = true)
{
if (allowCache)
{
@@ -163,27 +167,25 @@ namespace ZelWiki.Repository
}
/// <summary>
/// Determines if this is the first time the wiki has run. Returns true if it is the first time.
///
/// </summary>
/// <returns></returns>
public static bool IsFirstRun()
{
bool isEncryptionValid = GetCryptoCheck();
var isEncryptionValid = GetCryptoCheck();
if (isEncryptionValid == false)
{
SetCryptoCheck();
return true;
}
return false;
}
/// <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>
/// <returns></returns>
/// <summary>
///
/// </summary>
/// <returns></returns>
public static bool GetCryptoCheck()
{
var value = ManagedDataStorage.Config.QueryFirstOrDefault<string>("GetCryptoCheck.sql") ?? string.Empty;
@@ -203,9 +205,9 @@ namespace ZelWiki.Repository
return false;
}
/// <summary>
/// Writes an encrypted value to the database so we can test at a later time to ensure that encryption is setup.
/// </summary>
/// <summary>
///
/// </summary>
public static void SetCryptoCheck()
{
var param = new
@@ -275,6 +277,7 @@ namespace ZelWiki.Repository
ConfigurationGroupId = group.Key,
});
}
result.Add(nest);
}
@@ -284,14 +287,16 @@ namespace ZelWiki.Repository
public static List<ConfigurationFlat> GetFlatConfiguration()
=> 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)
{
var cacheKey = WikiCacheKeyFunction.Build(WikiCache.Category.Configuration, [groupName, entryName]);
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);
}
@@ -306,7 +311,9 @@ namespace ZelWiki.Repository
EntryName = entryName
};
var configEntry = ManagedDataStorage.Config.QuerySingle<ConfigurationEntry>("GetConfigurationEntryValuesByGroupNameAndEntryName.sql", param);
var configEntry =
ManagedDataStorage.Config.QuerySingle<ConfigurationEntry>(
"GetConfigurationEntryValuesByGroupNameAndEntryName.sql", param);
if (configEntry?.IsEncrypted == true)
{
try
@@ -431,33 +438,31 @@ namespace ZelWiki.Repository
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 img = Image.Load(new MemoryStream(decompressedImageBytes));
int customScalePercent = 100;
var customScalePercent = 100;
var (Width, Height) = Utility.ScaleToMaxOf(img.Width, img.Height, GlobalConfiguration.DefaultEmojiHeight);
//Adjust to any specified scaling.
var (Width, Height) = Utility.ScaleToMaxOf(img.Width, img.Height,
GlobalConfiguration.DefaultEmojiHeight);
Height = (int)(Height * (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)
{
Height += 16 - Height;
Width += 16 - Height;
}
if (Width < 16)
{
Height += 16 - Width;
Width += 16 - Width;
}
//These are hard to generate, so just keep it forever.
var resized = Images.ResizeGifImage(decompressedImageBytes, Width, Height);
var itemCache = new ImageCacheItem(resized, "image/gif");
WikiCache.Put(scaledImageCacheKey, itemCache, new CacheItemPolicy());
@@ -497,27 +502,34 @@ namespace ZelWiki.Repository
GlobalConfiguration.FixedMenuPosition = customizationConfig.Value("Fixed Header Menu Position", 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.SystemTheme = GetAllThemes().Single(o => o.Name == themeName);
GlobalConfiguration.DefaultEmojiHeight = customizationConfig.Value<int>("Default Emoji Height");
GlobalConfiguration.AllowGoogleAuthentication = membershipConfig.Value<bool>("Allow Google Authentication");
GlobalConfiguration.DefaultTimeZone = customizationConfig?.Value<string>("Default TimeZone") ?? string.Empty;
GlobalConfiguration.IncludeWikiDescriptionInMeta = functionalityConfig.Value<bool>("Include wiki Description in Meta");
GlobalConfiguration.DefaultTimeZone =
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.EnablePageComments = functionalityConfig.Value<bool>("Enable Page Comments");
GlobalConfiguration.EnablePublicProfiles = functionalityConfig.Value<bool>("Enable Public Profiles");
GlobalConfiguration.ShowCommentsOnPageFooter = functionalityConfig.Value<bool>("Show Comments on Page Footer");
GlobalConfiguration.ShowLastModifiedOnPageFooter = functionalityConfig.Value<bool>("Show Last Modified on Page Footer");
GlobalConfiguration.ShowCommentsOnPageFooter =
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.HTMLHeader = htmlConfig?.Value<string>("Header") ?? string.Empty;
GlobalConfiguration.HTMLFooter = htmlConfig?.Value<string>("Footer") ?? string.Empty;
GlobalConfiguration.HTMLPreBody = htmlConfig?.Value<string>("Pre-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.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.MenuItems = GetAllMenuItems();
@@ -525,4 +537,4 @@ namespace ZelWiki.Repository
ReloadEmojis();
}
}
}
}

View File

@@ -2,9 +2,6 @@
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
{
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);
if (pageFileInfo == null)
{
//If the page file does not exist, then insert it.
var InsertPageFileParam = new
{
PageId = item.PageId,
@@ -193,8 +191,7 @@ namespace ZelWiki.Repository
};
o.Execute("InsertPageFile.sql", InsertPageFileParam);
//Get the id of the newly inserted page file.
pageFileInfo = GetPageFileInfoByFileNavigation(o, item.PageId, item.FileNavigation)
?? throw new Exception("Failed find newly inserted page attachment.");
@@ -208,26 +205,19 @@ namespace ZelWiki.Repository
var currentlyAttachedFile = GetPageCurrentRevisionAttachmentByFileNavigation(o, item.PageId, item.FileNavigation);
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;
hasFileChanged = currentlyAttachedFile.DataHash != newDataHash;
}
else
{
//The file either does not exist or is not attached to the current page revision.
hasFileChanged = true;
//We determined earlier that the PageFile does exist, so keep track of the file revision.
currentFileRevision = pageFileInfo.Revision;
}
if (hasFileChanged)
{
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);
var updatePageFileRevisionParam = new
@@ -235,7 +225,6 @@ namespace ZelWiki.Repository
PageFileId = pageFileInfo.PageFileId,
FileRevision = currentFileRevision
};
//The file has changed (or is newly inserted), bump the file revision.
o.Execute("UpdatePageFileRevision.sql", updatePageFileRevisionParam);
var insertPageFileRevisionParam = new
@@ -250,7 +239,6 @@ namespace ZelWiki.Repository
DataHash = newDataHash,
};
//Insert the actual file data.
o.Execute("InsertPageFileRevision.sql", insertPageFileRevisionParam);
var associatePageFileAttachmentWithPageRevisionParam = new
@@ -259,10 +247,9 @@ namespace ZelWiki.Repository
PageFileId = pageFileInfo.PageFileId,
PageRevision = currentPageRevision,
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);
}

View File

@@ -20,7 +20,8 @@ namespace ZelWiki.Repository
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)
{
@@ -41,7 +42,8 @@ namespace ZelWiki.Repository
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(
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");
@@ -83,7 +86,8 @@ namespace ZelWiki.Repository
{
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();
});
}
@@ -96,7 +100,8 @@ namespace ZelWiki.Repository
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)
@@ -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)
{
//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, [string.Join(',', searchTerms), allowFuzzyMatching]);
var cacheKey = WikiCacheKeyFunction.Build(WikiCache.Category.Search,
[string.Join(',', searchTerms), allowFuzzyMatching]);
if (!WikiCache.TryGet<List<PageSearchToken>>(cacheKey, out var result))
{
result = GetMeteredPageSearchTokens(searchTerms, allowFuzzyMatching, false);
@@ -167,11 +173,11 @@ namespace ZelWiki.Repository
var minimumMatchScore = ConfigurationRepository.Get<float>("Search", "Minimum Match Score");
var searchTokens = (from o in searchTerms
select new PageToken
{
Token = o,
DoubleMetaphone = o.ToDoubleMetaphone()
}).ToList();
select new PageToken
{
Token = o,
DoubleMetaphone = o.ToDoubleMetaphone()
}).ToList();
if (allowFuzzyMatching == true)
{
@@ -181,15 +187,15 @@ namespace ZelWiki.Repository
allTokens.AddRange(fuzzyTokens);
return allTokens
.GroupBy(token => token.PageId)
.Where(group => group.Sum(g => g.Score) >= minimumMatchScore) // Filtering groups
.Select(group => new PageSearchToken
{
PageId = group.Key,
Match = group.Max(g => g.Match),
Weight = group.Max(g => g.Weight),
Score = group.Max(g => g.Score)
}).ToList();
.GroupBy(token => token.PageId)
.Where(group => group.Sum(g => g.Score) >= minimumMatchScore)
.Select(group => new PageSearchToken
{
PageId = group.Key,
Match = group.Max(g => g.Match),
Weight = group.Max(g => g.Weight),
Score = group.Max(g => g.Score)
}).ToList();
}
else
{
@@ -204,7 +210,7 @@ namespace ZelWiki.Repository
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);
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)
{
@@ -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");
@@ -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");
@@ -478,17 +487,18 @@ namespace ZelWiki.Repository
return ManagedDataStorage.Pages.Ephemeral(o =>
{
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();
});
}
/// <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>
/// <param name="pageNumber"></param>
/// <param name="pageSize"></param>
/// <param name="orderBy"></param>
/// <param name="orderByDirection"></param>
/// <param name="searchTerms"></param>
/// <returns></returns>
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 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();
});
}
@@ -528,11 +539,11 @@ namespace ZelWiki.Repository
}
/// <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>
/// <param name="pageNumber"></param>
/// <param name="pageSize"></param>
/// <param name="orderBy"></param>
/// <param name="orderByDirection"></param>
/// <param name="searchTerms"></param>
/// <returns></returns>
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 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();
});
}
@@ -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");
@@ -683,6 +696,7 @@ namespace ZelWiki.Repository
WikiCache.Put(cacheKey, result);
}
}
return result;
}
@@ -719,31 +733,27 @@ namespace ZelWiki.Repository
try
{
int currentPageRevision = 0;
bool hasPageChanged = false;
var currentPageRevision = 0;
var hasPageChanged = false;
if (page.Id == 0)
{
//This is a new page, just insert it.
page.Id = o.ExecuteScalar<int>("CreatePage.sql", pageUpsertParam);
hasPageChanged = true;
}
else
{
//Get current page so we can determine if anything has changed.
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;
//Update the existing page.
o.Execute("UpdatePage.sql", pageUpsertParam);
//Determine if anything has actually changed.
hasPageChanged = currentRevisionInfo.Name != page.Name
|| currentRevisionInfo.Namespace != page.Namespace
|| currentRevisionInfo.Description != page.Description
|| currentRevisionInfo.DataHash != newDataHash;
|| currentRevisionInfo.Namespace != page.Namespace
|| currentRevisionInfo.Description != page.Description
|| currentRevisionInfo.DataHash != newDataHash;
}
if (hasPageChanged)
@@ -755,7 +765,6 @@ namespace ZelWiki.Repository
PageId = page.Id,
PageRevision = currentPageRevision
};
//The page content has actually changed (according to the checksum), so we will bump the page revision.
o.Execute("UpdatePageRevisionNumber.sql", updatePageRevisionNumberParam);
var InsertPageRevisionParam = new
@@ -770,7 +779,6 @@ namespace ZelWiki.Repository
ModifiedByUserId = page.ModifiedByUserId,
ModifiedDate = DateTime.UtcNow,
};
//Insert the new actual page revision entry (this is the data).
o.Execute("InsertPageRevision.sql", InsertPageRevisionParam);
var reassociateAllPageAttachmentsParam = new
@@ -778,7 +786,6 @@ namespace ZelWiki.Repository
PageId = page.Id,
PageRevision = currentPageRevision,
};
//Associate all page attachments with the latest revision.
o.Execute("ReassociateAllPageAttachments.sql", reassociateAllPageAttachmentsParam);
}
@@ -795,7 +802,7 @@ namespace ZelWiki.Repository
}
/// <summary>
/// Gets the page info without the content.
///
/// </summary>
/// <param name="navigation"></param>
/// <returns></returns>
@@ -1001,7 +1008,8 @@ namespace ZelWiki.Repository
{
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();
});
}
@@ -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);
@@ -1172,4 +1181,4 @@ namespace ZelWiki.Repository
#endregion
}
}
}

View File

@@ -5,11 +5,13 @@ namespace ZelWiki.Repository
internal static class RepositoryHelper
{
/// <summary>
/// Fills in a custom orderby on a given sql script.
///
/// </summary>
/// <param name="filename"></param>
/// <param name="orderBy"></param>
/// <param name="orderByDirection"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static string TransposeOrderby(string filename, string? orderBy, string? orderByDirection)
{
var script = ManagedDataStorageInstance.TranslateSqlScript(filename);
@@ -19,47 +21,53 @@ namespace ZelWiki.Repository
return script;
}
string beginParentTag = "--CUSTOM_ORDER_BEGIN::";
string endParentTag = "--::CUSTOM_ORDER_BEGIN";
var beginParentTag = "--CUSTOM_ORDER_BEGIN::";
var endParentTag = "--::CUSTOM_ORDER_BEGIN";
string beginConfigTag = "--CONFIG::";
string endConfigTag = "--::CONFIG";
var beginConfigTag = "--CONFIG::";
var endConfigTag = "--::CONFIG";
while (true)
{
int beginParentIndex = script.IndexOf(beginParentTag, StringComparison.OrdinalIgnoreCase);
int endParentIndex = script.IndexOf(endParentTag, StringComparison.OrdinalIgnoreCase);
var beginParentIndex = script.IndexOf(beginParentTag, StringComparison.OrdinalIgnoreCase);
var endParentIndex = script.IndexOf(endParentTag, StringComparison.OrdinalIgnoreCase);
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);
int endConfigIndex = sectionText.IndexOf(endConfigTag, StringComparison.OrdinalIgnoreCase);
var beginConfigIndex = sectionText.IndexOf(beginConfigTag, StringComparison.OrdinalIgnoreCase);
var endConfigIndex = sectionText.IndexOf(endConfigTag, StringComparison.OrdinalIgnoreCase);
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())
.Where(o => o.Contains('='))
.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)
{
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)
+ $"ORDER BY\r\n\t{selectedConfig.Field} "
+ (string.Equals(orderByDirection, "asc", StringComparison.InvariantCultureIgnoreCase) ? "asc" : "desc")
+ script.Substring(endParentIndex + endParentTag.Length);
+ $"ORDER BY\r\n\t{selectedConfig.Field} "
+ (string.Equals(orderByDirection, "asc", StringComparison.InvariantCultureIgnoreCase)
? "asc"
: "desc")
+ script.Substring(endParentIndex + endParentTag.Length);
}
else
{
throw new Exception($"No order configuration was found in '{filename}'.");
throw new Exception($" '{filename}'中找不到配置");
}
}
else
@@ -71,4 +79,4 @@ namespace ZelWiki.Repository
return script;
}
}
}
}

View File

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

View File

@@ -9,7 +9,6 @@ FROM
T.PageId,
COUNT(DISTINCT T.Token) / (@TokenCount + 0.0) as [Match],
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]
FROM
PageToken as T

View File

@@ -9,7 +9,6 @@ FROM
T.PageId,
COUNT(DISTINCT T.DoubleMetaphone) / (@TokenCount + 0.0) as [Match],
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]
FROM
PageToken as T

View File

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

View File

@@ -23,7 +23,7 @@ SELECT
ON PRA.PageId = P.Id
AND PRA.PageFileId = PF.Id
AND PRA.PageRevision = PR.Revision
AND PRA.FileRevision = PF.Revision --Latest file revision.
AND PRA.FileRevision = PF.Revision
INNER JOIN PageFileRevision as PFR
ON PFR.PageFileId = PF.Id
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
(
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.[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.[PageToken] SELECT * FROM [PageToken] 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 [PageTag] WHERE PageId = @PageId;

View File

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

View File

@@ -1,6 +1,6 @@
BEGIN TRANSACTION;
--Delete orphaned PageFileRevision.
DELETE FROM PageFileRevision
WHERE (PageFileId, Revision) IN (
SELECT
@@ -19,7 +19,7 @@ WHERE (PageFileId, Revision) IN (
PRA.PageFileId IS NULL
);
--Delete orphaned PageFile.
DELETE FROM PageFile
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 [PageRevision] SELECT * FROM deletedpages_db.[PageRevision] 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 [PageComment] SELECT * FROM deletedpages_db.[PageComment] WHERE PageId = @PageId;
--Cleanup
DELETE FROM deletedpages_db.DeletionMeta 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
WHERE EXISTS (
SELECT 1
@@ -11,7 +10,6 @@ WHERE EXISTS (
AND PageRevision.Revision < MostRecent.MaxRevision
);
-- Deleting non-current attachments.
DELETE FROM PageRevisionAttachment
WHERE EXISTS (
SELECT 1
@@ -25,7 +23,6 @@ WHERE EXISTS (
AND PageRevisionAttachment.FileRevision < MostRecent.MaxFileRevision
);
-- Deleting non-current page revision attachments
DELETE FROM PageRevisionAttachment
WHERE EXISTS (
SELECT 1
@@ -39,7 +36,6 @@ WHERE EXISTS (
AND PageRevisionAttachment.PageRevision < MostRecent.MaxPageRevision
);
-- Deleting non-current page file revisions.
DELETE FROM PageFileRevision
WHERE EXISTS (
SELECT 1
@@ -52,19 +48,16 @@ WHERE EXISTS (
AND PageFileRevision.Revision < MostRecent.MaxPageRevision
);
-- Delete orphaned PageFileRevision
DELETE FROM PageFileRevision
WHERE PageFileId NOT IN (
SELECT PageFileId FROM PageRevisionAttachment
);
-- Delete orphaned PageFile
DELETE FROM PageFile
WHERE Id NOT IN (
SELECT PageFileId FROM PageRevisionAttachment
);
-- Assuming everything else worked, lets set all of the revisions back to 1.
UPDATE [Page] SET Revision = 1;
UPDATE PageRevision SET Revision = 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
PageReference
SET

View File

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

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