Files

271 lines
10 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
namespace EasyChart.Editor
{
public static class EasyChartManualWeb
{
private const string ManualRootFolder = "Assets/EasyChart/Docs/Manual";
private const string WebRootFolder = "Assets/EasyChart/Docs/ManualWeb";
private const string DataFileName = "manual-data.js";
private const string DataFileNameZh = "manual-data.zh.js";
private const string DataFileNameEn = "manual-data.en.js";
private const string IndexFileName = "manual.html";
private const string OpenFileName = "manual-open.js";
[MenuItem("EasyChart/Manual")]
public static void Open()
{
ExportManualData();
WriteOpenRequestJs(null, null);
string indexAssetPath = WebRootFolder.TrimEnd('/') + "/" + IndexFileName;
string indexFullPath = AssetPathToFullPath(indexAssetPath);
if (string.IsNullOrEmpty(indexFullPath) || !File.Exists(indexFullPath))
{
EditorUtility.DisplayDialog("EasyChart Manual", "manual.html not found: " + indexAssetPath, "OK");
return;
}
string url = new Uri(indexFullPath).AbsoluteUri;
Debug.Log("[EasyChartManualWeb] Open manual url: " + url);
Application.OpenURL(url);
}
public static void OpenChapter(string chapterId)
{
OpenChapter(chapterId, null);
}
public static void OpenChapter(string chapterId, string anchor)
{
ExportManualData();
WriteOpenRequestJs(chapterId, anchor);
string indexAssetPath = WebRootFolder.TrimEnd('/') + "/" + IndexFileName;
string indexFullPath = AssetPathToFullPath(indexAssetPath);
if (string.IsNullOrEmpty(indexFullPath) || !File.Exists(indexFullPath))
{
EditorUtility.DisplayDialog("EasyChart Manual", "manual.html not found: " + indexAssetPath, "OK");
return;
}
// NOTE:
// Some Windows/browser combinations drop hash/query when opening file:// URLs via shell.
// We therefore persist the desired chapter into manual-open.js, which manual.html loads.
string url = new Uri(indexFullPath).AbsoluteUri;
Debug.Log("[EasyChartManualWeb] Open manual url: " + url);
Application.OpenURL(url);
}
private static void WriteOpenRequestJs(string chapterId, string anchor)
{
try
{
string webOut = AssetPathToFullPath(WebRootFolder);
Directory.CreateDirectory(webOut);
var sb = new StringBuilder(256);
sb.Append("window.EASYCHART_MANUAL_OPEN = {");
sb.Append("chapterId: \"").Append(EscapeJsString(chapterId)).Append("\",");
sb.Append(" anchor: \"").Append(EscapeJsString(anchor)).Append("\",");
sb.Append(" at: \"").Append(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")).Append("\"");
sb.Append("};\n");
File.WriteAllText(Path.Combine(webOut, OpenFileName), sb.ToString(), Encoding.UTF8);
EditorApplication.delayCall += () =>
{
if (!EditorApplication.isCompiling && !EditorApplication.isUpdating) AssetDatabase.Refresh();
};
}
catch (Exception e)
{
Debug.LogError("[EasyChartManualWeb] WriteOpenRequestJs failed: " + e);
}
}
private static void ExportManualData()
{
if (!AssetDatabase.IsValidFolder(ManualRootFolder))
{
EditorUtility.DisplayDialog("EasyChart Manual", "Manual folder not found: " + ManualRootFolder, "OK");
return;
}
string webOut = AssetPathToFullPath(WebRootFolder);
Directory.CreateDirectory(webOut);
string manualRoot = AssetPathToFullPath(ManualRootFolder);
string zhRoot = Path.Combine(manualRoot, "zh");
string enRoot = Path.Combine(manualRoot, "en");
bool hasZh = Directory.Exists(zhRoot);
bool hasEn = Directory.Exists(enRoot);
if (hasZh || hasEn)
{
if (hasZh)
{
var chaptersZh = LoadChaptersFromFolder(zhRoot);
if (chaptersZh.Count > 0)
{
string jsZh = BuildManualDataJs(chaptersZh, "EASYCHART_MANUAL_ZH");
File.WriteAllText(Path.Combine(webOut, DataFileNameZh), jsZh, Encoding.UTF8);
string legacy = BuildManualDataJs(chaptersZh, "EASYCHART_MANUAL");
File.WriteAllText(Path.Combine(webOut, DataFileName), legacy, Encoding.UTF8);
}
}
if (hasEn)
{
var chaptersEn = LoadChaptersFromFolder(enRoot);
if (chaptersEn.Count > 0)
{
string jsEn = BuildManualDataJs(chaptersEn, "EASYCHART_MANUAL_EN");
File.WriteAllText(Path.Combine(webOut, DataFileNameEn), jsEn, Encoding.UTF8);
}
}
}
else
{
var chapters = LoadChaptersFromFolder(manualRoot);
string js = BuildManualDataJs(chapters, "EASYCHART_MANUAL");
File.WriteAllText(Path.Combine(webOut, DataFileName), js, Encoding.UTF8);
}
EditorApplication.delayCall += () =>
{
if (!EditorApplication.isCompiling && !EditorApplication.isUpdating) AssetDatabase.Refresh();
};
}
private static List<Chapter> LoadChaptersFromFolder(string manualRoot)
{
var mdFiles = Directory.GetFiles(manualRoot, "*.md", SearchOption.AllDirectories)
.Select(p => p.Replace('\\', '/'))
.OrderBy(p => p, StringComparer.OrdinalIgnoreCase)
.ToList();
var chapters = new List<Chapter>(mdFiles.Count);
for (int i = 0; i < mdFiles.Count; i++)
{
string full = mdFiles[i];
string rel = full.Substring(manualRoot.Length).TrimStart('/', '\\').Replace('\\', '/');
string id = Path.GetFileNameWithoutExtension(rel);
string content = File.ReadAllText(full, Encoding.UTF8);
string title = TryExtractTitle(content) ?? Path.GetFileNameWithoutExtension(rel);
chapters.Add(new Chapter
{
id = id,
relPath = rel,
title = title,
content = content
});
}
return chapters;
}
[Serializable]
private class Chapter
{
public string id;
public string relPath;
public string title;
public string content;
}
private static string BuildManualDataJs(List<Chapter> chapters, string globalName)
{
var sb = new StringBuilder(1024 + (chapters.Count * 512));
if (string.IsNullOrEmpty(globalName)) globalName = "EASYCHART_MANUAL";
sb.Append("window.").Append(globalName).Append(" = window.").Append(globalName).Append(" || {};\n");
sb.Append("window.").Append(globalName).Append(".generatedAt = \"").Append(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")).Append("\";\n");
sb.Append("window.").Append(globalName).Append(".chapters = [\n");
for (int i = 0; i < chapters.Count; i++)
{
var ch = chapters[i];
sb.Append(" { ");
sb.Append("id: \"").Append(EscapeJsString(ch.id)).Append("\",");
sb.Append(" relPath: \"").Append(EscapeJsString(ch.relPath)).Append("\",");
sb.Append(" title: \"").Append(EscapeJsString(ch.title)).Append("\",");
sb.Append(" content: \"").Append(EscapeJsString(ch.content)).Append("\" ");
sb.Append("}");
if (i != chapters.Count - 1) sb.Append(",");
sb.Append("\n");
}
sb.Append("];\n");
return sb.ToString();
}
private static string EscapeJsString(string s)
{
if (string.IsNullOrEmpty(s)) return string.Empty;
var sb = new StringBuilder(s.Length + 16);
for (int i = 0; i < s.Length; i++)
{
char c = s[i];
switch (c)
{
case '\\': sb.Append("\\\\"); break;
case '"': sb.Append("\\\""); break;
case '\n': sb.Append("\\n"); break;
case '\r': break;
case '\t': sb.Append("\\t"); break;
default:
if (c < 32) sb.Append(' ');
else sb.Append(c);
break;
}
}
return sb.ToString();
}
private static string TryExtractTitle(string markdown)
{
if (string.IsNullOrEmpty(markdown)) return null;
var lines = markdown.Replace("\r\n", "\n").Replace("\r", "\n").Split('\n');
for (int i = 0; i < lines.Length; i++)
{
var t = (lines[i] ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(t)) continue;
if (t.StartsWith("#", StringComparison.Ordinal))
{
int level = 0;
while (level < t.Length && t[level] == '#') level++;
string title = t.Substring(level).Trim();
return string.IsNullOrEmpty(title) ? null : title;
}
break;
}
return null;
}
private static string AssetPathToFullPath(string assetPath)
{
if (string.IsNullOrEmpty(assetPath)) return string.Empty;
assetPath = assetPath.Replace('\\', '/');
if (Path.IsPathRooted(assetPath)) return assetPath;
string projectRoot = Path.GetDirectoryName(Application.dataPath);
if (string.IsNullOrEmpty(projectRoot)) return Path.GetFullPath(assetPath);
return Path.GetFullPath(Path.Combine(projectRoot, assetPath));
}
}
}