Merge pull request 'v1.5.1 Releases' (PR#53) from dev into main
Build Plugin JAR File / build (push) Successful in 1m44s Details
Build Plugin JAR File / github-release (push) Has been skipped Details

Reviewed-on: #53
This commit is contained in:
zhengyi 2024-01-21 18:38:29 +08:00
commit 23cdb944a6
18 changed files with 390 additions and 91 deletions

97
CHANGELOG.md Normal file
View File

@ -0,0 +1,97 @@
# 更新记录
### TODO v1.5.2
- [ ] 更换vditor版本为官方版本 (pending vditor 3.9.9)
- [ ] 修复所见即所得模式下使用工具栏修改属性不生效的问题 (pending vditor 3.9.9)
- [ ] 为用户/开发者提供接入自定义渲染块的能力
- [ ] 设置中添加调试快捷开关
### v1.5.1
- ✨ 添加图集的快捷插入按钮
- 🐛 修复脑图、图标、echart暗色模式渲染错误的问题
- 🐛 解决更新插件前台资源缓存的问题
- 🐛 解决暗色模式渲染异常的问题
- 🐛 解决主题代码块等样式覆盖的问题
### v1.5.0
- ✨ 使用新的快速插入表单,界面更美观
- ✨ 支持渲染其他插件/主题的自定义组件(需要开发者支持)
- ✨ 支持加入快速插入链接(需要开发者支持)
- ✨ 允许用户禁用HTML代码块隐藏的特性
- ✨ 支持用户自定义上传图片的格式
- ✨ vditor-halo-render添加组图组件(gallery)
- 🐛 修复图片拖拽插入错位的问题
- 🐛 修复echartsmindmap无法渲染的问题
- 🐛 修复所见即所得模式下超链接颜色丢失的问题
- 🚀 优化前端渲染逻辑去除无用的dom查询
### v1.4.0
- ✨ 支持编辑器加载状态展示
- ✨ 支持拖拽上传图片
- ✨ 添加快速插入自定义块的按钮
- ✨ 添加仅在Markdown文章注入解析器的选项
- ⬆️ 更新halo-vditor至3.9.9版本对应vditor 3.9.8
- ⬆️ 更新vditor-halo-render到1.1.0版本
- ✨ 重新设计tip、git模块
- ✨ 添加资源下载模块
- ✨ 为git添加fork、topic信息
- ✨ 为git添加缓存
- ✨ 添加对Gitee的支持
- 🐛 修复invalid type界面边框与其他模块不对齐的问题
### v1.3.2
- 🐛 编辑器编写代码块出现闪屏问题
### v1.3.1
- 🐛 修复Vditor编辑器切换到其他编辑器导致的样式冲突问题
- 🐛 修复编辑器在Halo中出现页面滚动条的问题
- 🐛 修复前台数学公式无法渲染的问题
### v1.3.0
- ✨ 支持更多tips渲染
- ✨ 支持github仓库信息渲染
- ✨ 升级vditor为分支halo-vditor版本 https://github.com/justice2001/halo-vditor
- ⬆️ 升级halo-vditor到3.9.8版本(对应vditor3.9.7)
- 🐛 修复页面偶尔出现加载shi问题使用本地资源
### v1.2.0
- 🚸 将设置拆分为编辑器和渲染两个部分
- ✨ 为编辑器添加多国语言支持(简中、繁中、英语)
- ✨ 添加启用编辑器代码块的选项
- ✨ 添加跟随Joe 3.0主题切换暗色模式的选项
- 🐛 修复单页未对内容进行渲染的问题
- 🐛 修复所见即所得模式下部分内容被小工具栏遮挡的问题
### v1.1.0
- ⬆️ 更新 Halo 最低兼容版本至 2.8.0
- ✨ 添加默认编辑模式设置
- ✨ 添加打字机模式设置
- ✨ 添加插入图片功能(快捷键:⇧⌘P)
- ✨ 添加自动暗色模式的支持
- 🐛 修复了编辑器中有序列表左侧序号消失的问题
- 🐛 修复全屏编辑器左侧被Halo导航栏遮挡的问题
- 🧪 实验性特性: 支持多媒体渲染
### v1.0.1
- ⚡ 修改为仅在文章中加入vditor render优化部分页面加载速度
- ⚡ 修改vditor库从本地加载优化页面加载速度
- 🐛 修复由于id重复可能存在的冲突问题
- ⚰️ 删除了部分无用代码
### v1.0.0
- ✨ 集成强大的Markdown编辑器Vditor进Halo中
- ✨ 提供所见即所得(wysiwyg)、即时渲染(ir)、分屏渲染(sv)三种渲染模式
- ✨ 支持大纲、数学公式、脑图、图表、流程图、甘特图、时序图、五线谱、多媒体、语音阅读、标题锚点、代码高亮及复制、graphviz 渲染、plantumlUML图
- ✨ 支持字符计数
- ✨ 支持在前台注入渲染脚本(需在设置中开启)

View File

@ -24,6 +24,13 @@ const lang: I18nLang = {
link: "Link",
password: "Password",
quick_insert: "Quick Insert",
insert_gallery: "Insert Gallery",
grid: "Grid",
linear: "Linear",
image_group: "Image Group",
image: "Image",
image_alt: "Alt",
image_alt_ph: "Use File Name If You Dont Place This",
};
export default lang;

View File

@ -24,6 +24,13 @@ const lang: I18nLang = {
link: "链接",
password: "密码",
quick_insert: "快速插入",
insert_gallery: "插入图集",
grid: "九宫格",
linear: "线性",
image_group: "图片组",
image: "图片",
image_alt: "图片介绍",
image_alt_ph: "不填写此项则默认使用图像文件名",
};
export default lang;

View File

@ -24,6 +24,13 @@ const lang: I18nLang = {
link: "鏈接",
password: "密碼",
quick_insert: "快速插入",
insert_gallery: "插入圖集",
grid: "九宮格",
linear: "線性",
image_group: "圖片組",
image: "圖片",
image_alt: "圖片介紹",
image_alt_ph: "不填寫此項則默認使用影像檔名",
};
export default lang;

View File

@ -31,8 +31,11 @@ const generateCode = () => {
emit("done", htmlEncode(code));
return;
}
props.schema.handler && props.schema.handler(data.value);
emit("done", null);
emit(
"done",
(props.schema.handler && props.schema.handler(data.value)) || null
);
};
// FormKit IDSchema

View File

@ -0,0 +1,83 @@
import type { Schema, SchemaData } from "@/type/editor";
import { t } from "@/utils/i18n-utils";
const schema: Schema = {
type: "template",
id: "gallery",
icon: "",
name: t("insert_gallery"),
formKit: [
{
$formkit: "select",
name: "type",
label: t("type"),
value: "grid",
options: {
grid: t("grid"),
linear: t("linear"),
},
},
{
$formkit: "text",
name: "title",
label: t("title"),
value: "",
},
{
$formkit: "repeater",
name: "attachments",
label: t("image_group"),
min: 1,
value: [{}],
children: [
{
$formkit: "attachment",
name: "attachment",
label: t("image"),
accepts: ["image/*"],
value: "",
},
{
$formkit: "text",
name: "attach_title",
label: t("image_alt"),
value: "",
placeholder: t("image_alt_ph"),
},
],
},
],
handler: (data: SchemaData) => {
// title type list[]
const galleryData = data as GallerySchemaData;
let html = `\`\`\`halo\ngallery:${galleryData.type}`;
if (galleryData.title) {
html += `[${galleryData.title}]`;
}
html += "\n";
galleryData.attachments?.forEach((att) => {
let title = "";
if (att.attach_title) {
title = att.attach_title;
} else {
const start = att.attachment.lastIndexOf("/");
const end = att.attachment.lastIndexOf(".");
title = att.attachment.slice(start, end);
}
html += `![${title}](${att.attachment})\n`;
});
html += "```";
return html;
},
};
export interface GallerySchemaData extends SchemaData {
title: string;
type: "grid" | "linear";
attachments: Array<{
attachment: string;
attach_title?: string;
}>;
}
export default schema;

View File

@ -22,7 +22,11 @@ export interface Schema {
// 解析后处理
afterHandle?: (data: { [key: string]: string }, code: string) => string;
// 覆盖解析
handler?: (data: { [key: string]: string }) => string;
handler?: (data: SchemaData) => string;
}
export interface SchemaData {
_id?: string;
}
export interface QuickInsert {

View File

@ -4,6 +4,7 @@ import { t } from "@/utils/i18n-utils";
import tips from "@/schema/tips";
import git from "@/schema/git";
import drive from "@/schema/drive";
import gallery from "@/schema/gallery";
export function getOptions(options: Options): IOptions {
const cdn =
@ -141,6 +142,11 @@ function getToolbar(
icon: t("insert_drive"),
click: () => openModal(drive),
},
{
name: "insert_gallery",
icon: t("insert_gallery"),
click: () => openModal(gallery),
},
],
},
{

View File

@ -3,7 +3,9 @@ package top.mczhengyi.vditor.extension;
import com.google.common.base.Throwables;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.PluginWrapper;
import org.springframework.stereotype.Component;
import org.springframework.util.PropertyPlaceholderHelper;
import reactor.core.publisher.Mono;
import run.halo.app.plugin.ReactiveSettingFetcher;
import run.halo.app.theme.ReactivePostContentHandler;
@ -17,13 +19,16 @@ public class VditorPostContentHandler implements ReactivePostContentHandler {
private final ReactiveSettingFetcher reactiveSettingFetcher;
private final PluginWrapper pluginWrapper;
@Override
public Mono<PostContentContext> handle(PostContentContext contentContext) {
return reactiveSettingFetcher.fetch("render", RenderConfig.class)
.map(renderConfig -> {
if (renderConfig.getEnableRender()&&
(!renderConfig.getOnlyMarkdown() || contentContext.getRawType().equals("markdown"))) {
contentContext.setContent(ScriptUtils.renderScript(renderConfig) + "\n" + contentContext.getContent());
var content = ScriptUtils.renderScript(renderConfig) + "\n" + contentContext.getContent();
contentContext.setContent(ScriptUtils.setContentProperty(content, pluginWrapper));
}
return contentContext;
})

View File

@ -0,0 +1,35 @@
package top.mczhengyi.vditor.utils;
/**
* Script Builder
* 用于构造Script内嵌Script样式表
* @author zhengyi59
*/
public class ScriptBuilder {
private final StringBuilder script;
ScriptBuilder() {
this.script = new StringBuilder();
}
public ScriptBuilder script(String path, String id) {
this.script.append("<script src=\"/plugins/vditor-mde/assets/static/%s?version=${version}\" id=\"vditor-%s\"></script>"
.formatted(path, id));
return this;
}
public ScriptBuilder innerScript(String script) {
this.script.append("<script>%s</script>".formatted(script));
return this;
}
public ScriptBuilder stylesheet(String path, String id) {
this.script.append("<link rel=\"stylesheet\" type=\"text/css\" href=\"/plugins/vditor-mde/assets/static/%s?version=${version}\" id=\"vditor-%s\" />"
.formatted(path, id));
return this;
}
public String getScript() {
return this.script.toString();
}
}

View File

@ -1,44 +1,31 @@
package top.mczhengyi.vditor.utils;
import org.pf4j.PluginWrapper;
import org.springframework.util.PropertyPlaceholderHelper;
import top.mczhengyi.vditor.bean.RenderConfig;
import java.util.Properties;
public class ScriptUtils {
static final PropertyPlaceholderHelper
PROPERTY_PLACEHOLDER_HELPER = new PropertyPlaceholderHelper("${", "}");
public static String renderScript(RenderConfig renderConfig) {
StringBuilder script = new StringBuilder();
script.append(basicScript(renderConfig));
// 如果是跟随Joe 3.0则注入脚本
if ("joe".equals(renderConfig.getDarkMode()))
script.append(joeDarkMode());
return script.toString();
ScriptBuilder script = new ScriptBuilder();
script.stylesheet("vditor-render.css", "style")
.script("dist/method.min.js", "methods")
.script("render.js", "render");
if (renderConfig.getMediaRender())
script.script("external/media-render.js", "media");
if (!renderConfig.getDarkMode().equals("disabled")) {
script.script("dark-mode/dark-%s.js".formatted(renderConfig.getDarkMode()), "dark-mode");
}
script.innerScript("initRender()");
return script.getScript();
}
public static String basicScript(RenderConfig renderConfig) {
return """
<link rel="stylesheet" type="text/css" href="/plugins/vditor-mde/assets/static/dist/index.css" id="vditor-style" />
<script src="/plugins/vditor-mde/assets/static/dist/method.min.js"></script>
<script src="/plugins/vditor-mde/assets/static/render.js" id="vditor-render"
data-dark="%s" data-mediaRender="%s"></script>
""".formatted(renderConfig.getDarkMode(), renderConfig.getMediaRender());
}
public static String joeDarkMode() {
return """
<script>
window.addEventListener("load", () => {
var html = document.getElementsByTagName("html")[0]
if (!html) return
setTheme(html.dataset.mode)
var callback = (mutation) => {
if (mutation[0].attributeName=="data-mode") {
console.log("CHANGED")
var mode = mutation[0].target.dataset.mode
setTheme(mode)
}
}
var observer = new MutationObserver(callback)
observer.observe(html, {attributes:true})
})
</script>
""";
public static String setContentProperty(String script, PluginWrapper pluginWrapper) {
final Properties properties = new Properties();
properties.setProperty("version", pluginWrapper.getDescriptor().getVersion());
return PROPERTY_PLACEHOLDER_HELPER.replacePlaceholders(script, properties);
}
}

View File

@ -0,0 +1,7 @@
window.addEventListener("load", () => {
console.log("Use Joe Dark Mode")
let html = document.getElementsByTagName("html")[0]
if (!html) return
const mode = html.dataset.mode
setDarkMode(mode === "dark")
})

View File

@ -0,0 +1,5 @@
window.addEventListener("load", () => {
console.log("Use System Dark Mode")
let media = window.matchMedia('(prefers-color-scheme: dark)');
setDarkMode(media.matches)
})

View File

@ -0,0 +1,8 @@
addExternal((conf) => {
console.log("Run External Function: Media Render!")
let mediaRenderOption = document.getElementById("vditor-render").dataset.mediarender
if (mediaRenderOption==="true") {
let article = document.getElementById("vditor-render").parentElement;
Vditor.mediaRender(article)
}
})

View File

@ -1,11 +1,32 @@
const THEME_PREFIX="/plugins/vditor-mde/assets/static/dist/css/content-theme"
const THEME_PREFIX="/plugins/vditor-mde/assets/static/themes"
const CDN = "/plugins/vditor-mde/assets/static"
window.addEventListener("load", () => {
// 暗色模式初始化
let dark = initDarkMode()
setTheme(dark?"dark":"light")
/** 拓展处理 ({dark}) => void */
let functionList = []
let darkMode = false
/**
* 处理渲染
* @param func
*/
function addExternal(func) {
functionList.push(func)
}
/**
* 设置暗色模式
* @param {Boolean} dark
*/
function setDarkMode(dark = false) {
darkMode = dark
}
/**
* 渲染
* @param dark
*/
function render(dark) {
Vditor.setContentTheme(dark?"dark":"light", THEME_PREFIX)
const root = document.getElementById("vditor-render").parentElement
root.classList.add("vditor-reset")
// Render
@ -18,56 +39,17 @@ window.addEventListener("load", () => {
Vditor.graphvizRender(root, CDN)
Vditor.flowchartRender(root, CDN)
Vditor.haloRender(root, CDN)
// Render Media
let mediaRenderOption = document.getElementById("vditor-render").dataset.mediarender
if (mediaRenderOption==="true") {
let article = document.getElementById("vditor-render").parentElement;
Vditor.mediaRender(article)
}
})
/**
* 初始化暗色模式策略
* 创建所需监听器
* @returns {boolean} 初始暗黑模式状态
*/
function initDarkMode() {
let darkModeChange = document.getElementById("vditor-render").dataset.dark
let dark = false
// 检测暗黑模式策略
switch (darkModeChange) {
// 禁用暗黑模式
case "disabled": break
// 跟随系统
case "system":
dark = initSystemDarkMode()
}
return dark
}
/**
* 系统模式暗黑模式策略
* @returns {boolean}
*/
function initSystemDarkMode() {
let media = window.matchMedia('(prefers-color-scheme: dark)');
let callback = (e) => {
let prefersDarkMode = e.matches;
setTheme(prefersDarkMode?"dark":"light")
};
if (typeof media.addEventListener === 'function') {
media.addEventListener('change', callback);
} else if (typeof media.addListener === 'function') {
media.addListener(callback);
}
return media.matches
// Run External Plugin
functionList.forEach(func => {
func({
dark
})
})
}
/**
* 配置主题
* @param theme 主题
* 页面加载完成时处理任务
*/
function setTheme(theme) {
Vditor.setContentTheme(theme, THEME_PREFIX)
function initRender() {
window.addEventListener("load", () => render(darkMode))
}

View File

@ -0,0 +1,13 @@
/**
This is a theme for halo-plugin-vditor
Modified from vditor content_theme
*/
.vditor-reset .language-abc svg,
.vditor-reset .language-abc path {
fill: currentColor;
color: #d1d5da;
}
.language-graphviz polygon {
fill: rgba(66, 133, 244, .36);
}

View File

@ -0,0 +1,4 @@
/**
This is a theme for halo-plugin-vditor
Modified from vditor content_theme
*/

View File

@ -0,0 +1,39 @@
.vditor-reset--anchor {
padding-left: 20px;
}
.vditor-reset--error {
color: #d23f31;
font-size: 12px;
display: block;
line-height: 16px;
}
.vditor-reset .language-math,
.vditor-reset .language-echarts,
.vditor-reset .language-mindmap,
.vditor-reset .language-plantuml,
.vditor-reset .language-mermaid,
.vditor-reset .language-markmap,
.vditor-reset .language-abc,
.vditor-reset .language-flowchart,
.vditor-reset .language-graphviz {
margin-bottom: 16px;
}
.vditor-reset .language-math mjx-container:focus {
outline: none;
cursor: context-menu;
}
.vditor-reset .language-echarts,
.vditor-reset .language-mindmap {
overflow: hidden;
height: 420px;
}
.vditor-reset .language-mermaid,
.vditor-reset .language-markmap,
.vditor-reset .language-flowchart,
.vditor-reset .language-graphviz {
text-align: center;
}
.vditor-reset .language-graphviz parsererror {
overflow: auto;
}