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

Reviewed-on: #35
This commit is contained in:
zhengyi 2023-12-30 15:48:04 +08:00
commit 31435e500d
21 changed files with 446 additions and 29 deletions

View File

@ -16,7 +16,7 @@ repositories {
}
dependencies {
implementation platform('run.halo.tools.platform:plugin:2.8.0-SNAPSHOT')
implementation platform('run.halo.tools.platform:plugin:2.11.0-SNAPSHOT')
compileOnly 'run.halo.app:api'
testImplementation 'run.halo.app:api'

View File

@ -11,10 +11,11 @@
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
},
"dependencies": {
"@halo-dev/components": "^1.7.0",
"@halo-dev/console-shared": "^2.8.0",
"@halo-dev/api-client": "^2.11.0",
"@halo-dev/components": "^1.10.0",
"@halo-dev/console-shared": "^2.11.0",
"canvas-confetti": "^1.6.0",
"@zhengyi/vditor": "^3.9.8",
"@zhengyi/vditor": "3.9.9",
"vue": "^3.3.4"
},
"devDependencies": {

View File

@ -5,15 +5,18 @@ settings:
excludeLinksFromLockfile: false
dependencies:
'@halo-dev/api-client':
specifier: ^2.11.0
version: 2.11.0
'@halo-dev/components':
specifier: ^1.7.0
specifier: ^1.10.0
version: 1.10.0(vue-router@4.2.5)(vue@3.3.12)
'@halo-dev/console-shared':
specifier: ^2.8.0
specifier: ^2.11.0
version: 2.11.0(vue-router@4.2.5)(vue@3.3.12)
'@zhengyi/vditor':
specifier: ^3.9.8
version: 3.9.8
specifier: 3.9.9
version: 3.9.9
canvas-confetti:
specifier: ^1.6.0
version: 1.9.2
@ -1019,9 +1022,14 @@ packages:
'@types/node': 16.18.68
dev: true
/@zhengyi/vditor@3.9.8:
resolution: {integrity: sha512-PjGtpoVOezS//hf567BEqg8p0cEmlptV9oxiCc8UsCg9sHaJ0EYvhvS0m6Wsg/L7ShRVZe6RrjKa9SDtRu0m6w==, tarball: https://git.mczhengyi.top/api/packages/zhengyi/npm/%40zhengyi%2Fvditor/-/3.9.8/vditor-3.9.8.tgz}
/@zhengyi/halo-render@1.1.0:
resolution: {integrity: sha512-r+aD7wvdXVRyFcw/yI/PbdIqSsC+Bnql2WSKZa8CK+ntpTvc2tybn6Wxsfw96C71QS+Q/MPe8ufwZBjtRd+hhg==, tarball: https://git.mczhengyi.top/api/packages/zhengyi/npm/%40zhengyi%2Fhalo-render/-/1.1.0/halo-render-1.1.0.tgz}
dev: false
/@zhengyi/vditor@3.9.9:
resolution: {integrity: sha512-YHlWEbVkDvctAYUAghIISPjTPqxwubLuFlgbm1L6Oq3sGdIt4YZKUJA7Psv/JHaasX/tIvvcgZyUygSjjRBzMw==, tarball: https://git.mczhengyi.top/api/packages/zhengyi/npm/%40zhengyi%2Fvditor/-/3.9.9/vditor-3.9.9.tgz}
dependencies:
'@zhengyi/halo-render': 1.1.0
diff-match-patch: 1.0.5
dev: false

View File

@ -2,6 +2,27 @@ import type { I18nLang } from "@/type/i18n";
const lang: I18nLang = {
insert_image: "Insert Image",
confirm: "Confirm",
close: "Close",
default: "Default",
warning: "Warning",
danger: "Danger",
success: "Success",
info: "Info",
insert_tips: "Insert Tips",
type: "Type",
content: "Content",
platform: "Platform",
owner: "Owner",
repo: "Repo",
insert_git: "Insert Git Repo",
insert_custom: "Insert Custom",
insert_drive: "Insert Resources Link",
baidu_net_disk: "Baidu Net Disk",
ali_drive: "Ali Drive",
title: "Title",
link: "Link",
password: "Password",
};
export default lang;

View File

@ -1,8 +1,28 @@
import type {I18nLang} from "@/type/i18n";
import type { I18nLang } from "@/type/i18n";
const lang: I18nLang = {
"insert_image": "插入图片"
}
insert_image: "插入图片",
confirm: "确定",
close: "关闭",
default: "默认",
warning: "警告",
danger: "危险",
success: "成功",
info: "信息",
insert_tips: "插入Tips",
type: "类型",
content: "内容",
platform: "平台",
owner: "所有者",
repo: "仓库",
insert_git: "插入Git仓库",
insert_custom: "插入自定义块",
insert_drive: "插入资源链接",
baidu_net_disk: "百度网盘",
ali_drive: "阿里云盘",
title: "标题",
link: "链接",
password: "密码",
};
export default lang
export default lang;

View File

@ -1,7 +1,28 @@
import type {I18nLang} from "@/type/i18n";
import type { I18nLang } from "@/type/i18n";
const lang: I18nLang = {
"insert_image": "插入圖片"
}
insert_image: "插入圖片",
confirm: "確定",
close: "關閉",
default: "默認",
warning: "警告",
danger: "危險",
success: "成功",
info: "訊息",
insert_tips: "插入Tips",
type: "類型",
content: "內容",
platform: "平臺",
owner: "所有者",
repo: "儲存庫",
insert_git: "插入Git存儲庫",
insert_custom: "插入自定義塊",
insert_drive: "插入資源鏈接",
baidu_net_disk: "百度網盤",
ali_drive: "阿里雲盤",
title: "標題",
link: "鏈接",
password: "密碼",
};
export default lang
export default lang;

View File

@ -0,0 +1,75 @@
<script setup lang="ts">
import { VModal, VButton, VSpace } from "@halo-dev/components";
import { t } from "@/utils/i18n-utils";
import { ref } from "vue";
const props = defineProps<{
open: boolean;
}>();
const emit = defineEmits<{
(event: "done", value: string): void;
(event: "close"): void;
}>();
const platform = ref("baidu");
const name = ref("");
const link = ref("");
const password = ref("");
const generateCode = () => {
let code = "\n\n```halo\n";
code += `drive:${platform.value}\n`;
code += `name: ${name.value}\n`;
code += `link: ${link.value}\n`;
if (password.value) {
code += `password: ${password.value}\n`;
}
emit("done", code + "```\n\n");
};
</script>
<template>
<VModal
:visible="props.open"
:layer-closable="false"
:title="t('insert_drive')"
@close="emit('close')"
>
<v-space align="start" direction="column" spacing="xs" style="width: 100%">
<label for="type" class="vditor-mde-label">
<span>{{ t("platform") }}</span>
<select id="platform" v-model="platform" class="vditor-mde-select">
<option value="baidu">{{ t("baidu_net_disk") }}</option>
<option value="ali">{{ t("ali_drive") }}</option>
</select>
</label>
<label for="name" class="vditor-mde-label">
<span>{{ t("title") }}</span>
<input id="name" v-model="name" type="text" class="vditor-mde-input" />
</label>
<label for="link" class="vditor-mde-label">
<span>{{ t("link") }}</span>
<input id="link" v-model="link" type="text" class="vditor-mde-input" />
</label>
<label for="password" class="vditor-mde-label">
<span>{{ t("password") }}</span>
<input
id="password"
v-model="password"
type="text"
class="vditor-mde-input"
/>
</label>
</v-space>
<template #footer>
<v-space align="center" direction="row" spacing="xs">
<v-button type="primary" @click="generateCode">
{{ t("confirm") }}
</v-button>
<v-button type="default" @click="emit('close')">
{{ t("close") }}
</v-button>
</v-space>
</template>
</VModal>
</template>

View File

@ -0,0 +1,72 @@
<script setup lang="ts">
import { VModal, VButton, VSpace } from "@halo-dev/components";
import { t } from "@/utils/i18n-utils";
import { ref } from "vue";
const URL_NOT_SHOW = ["github", "gitee"];
const props = defineProps<{
open: boolean;
}>();
const emit = defineEmits<{
(event: "done", value: string): void;
(event: "close"): void;
}>();
const url = ref("");
const platform = ref("github");
const owner = ref("");
const repo = ref("");
const generateCode = () => {
emit(
"done",
"\n\n```halo\n" +
`git:[${url.value}@${platform.value}/${owner.value}/${repo.value}]` +
"\n```\n\n"
);
};
</script>
<template>
<VModal
:visible="props.open"
:layer-closable="false"
:title="t('insert_git')"
@close="emit('close')"
>
<v-space align="start" direction="column" spacing="xs" style="width: 100%">
<label v-if="URL_NOT_SHOW.indexOf(platform) === -1" for="url">
<span>URL</span>
<input id="url" v-model="url" type="text" class="vditor-mde-input" />
</label>
<label for="type" class="vditor-mde-label">
<span>{{ t("platform") }}</span>
<select id="platform" v-model="platform" class="vditor-mde-select">
<option value="github">GitHub</option>
<option value="gitee">Gitee</option>
</select>
</label>
<label for="owner" class="vditor-mde-label">
<span>{{ t("owner") }}</span>
<input id="owner" v-model="owner" type="text" class="vditor-mde-input" />
</label>
<label for="repo" class="vditor-mde-label">
<span>{{ t("repo") }}</span>
<input id="repo" v-model="repo" type="text" class="vditor-mde-input" />
</label>
</v-space>
<template #footer>
<v-space align="center" direction="row" spacing="xs">
<v-button type="primary" @click="generateCode">
{{ t("confirm") }}
</v-button>
<v-button type="default" @click="emit('close')">
{{ t("close") }}
</v-button>
</v-space>
</template>
</VModal>
</template>
<style scoped></style>

View File

@ -0,0 +1,59 @@
<script setup lang="ts">
import { VModal, VButton, VSpace } from "@halo-dev/components";
import { ref } from "vue";
import { t } from "@/utils/i18n-utils";
const props = defineProps<{
open: boolean;
}>();
const emit = defineEmits<{
(event: "done", value: string): void;
(event: "close"): void;
}>();
const type = ref("default");
const content = ref("");
const generateCode = () => {
emit(
"done",
"\n\n```halo\n" + `tips:${type.value}\n${content.value}\n` + "```\n\n"
);
};
</script>
<template>
<VModal
:visible="props.open"
:layer-closable="false"
:title="t('insert_tips')"
@close="emit('close')"
>
<v-space align="start" direction="column" spacing="xs" style="width: 100%">
<label for="type" class="vditor-mde-label">
<span>{{ t("type") }}</span>
<select id="type" v-model="type" class="vditor-mde-select">
<option value="default">{{ t("default") }}</option>
<option value="danger">{{ t("danger") }}</option>
<option value="warn">{{ t("warning") }}</option>
<option value="info">{{ t("info") }}</option>
<option value="success">{{ t("success") }}</option>
</select>
</label>
<label for="content" class="vditor-mde-label">
<span>{{ t("content") }}</span>
<textarea id="content" v-model="content" class="vditor-mde-textarea" />
</label>
</v-space>
<template #footer>
<v-space align="center" direction="row" spacing="xs">
<v-button type="primary" @click="generateCode">
{{ t("confirm") }}
</v-button>
<v-button type="default" @click="emit('close')">
{{ t("close") }}
</v-button>
</v-space>
</template>
</VModal>
</template>

View File

@ -15,4 +15,6 @@ export declare type Options = {
showAttachment: () => void;
language: string;
codeBlockPreview: boolean;
uploadImage?: (files: File[]) => string | null | Promise;
openModal: (name: string) => void;
};

View File

@ -2,6 +2,7 @@ import zhCN from "@/i18n/zh-CN";
import type { I18nLang } from "@/type/i18n";
import zhTW from "@/i18n/zh-TW";
import enUS from "@/i18n/en-US";
import {getLanguage} from "@/utils/vditor-utils";
const langDict: { [key: string]: I18nLang } = {
zh_CN: zhCN,
@ -14,6 +15,10 @@ const langDict: { [key: string]: I18nLang } = {
* @param key key
* @param lang
*/
export function t(key: string, lang: keyof II18n): string {
export function t(
key: string,
lang: keyof II18n | undefined = undefined
): string {
if (!lang) lang = getLanguage(localStorage.getItem("locale") || "zh-CN");
return langDict[lang][key];
}

View File

@ -1 +1,2 @@
export const mdiImage = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="m8.5 13.5l2.5 3l3.5-4.5l4.5 6H5m16 1V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2"/></svg>`;
export const mdiGrid = `<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M120-520v-320h320v320H120Zm0 400v-320h320v320H120Zm400-400v-320h320v320H520Zm0 400v-320h320v320H520ZM200-600h160v-160H200v160Zm400 0h160v-160H600v160Zm0 400h160v-160H600v160Zm-400 0h160v-160H200v160Zm400-400Zm0 240Zm-240 0Zm0-240Z"/></svg>`;

View File

@ -1,5 +1,5 @@
import type { Options } from "@/type/editor";
import { mdiImage } from "@/utils/icon";
import {mdiGrid, mdiImage} from "@/utils/icon";
import { t } from "@/utils/i18n-utils";
export function getOptions(options: Options): IOptions {
@ -22,7 +22,11 @@ export function getOptions(options: Options): IOptions {
},
after: options.after,
input: options.input,
toolbar: getToolbar(options.showAttachment, getLanguage(options.language)),
toolbar: getToolbar(
options.showAttachment,
options.openModal,
getLanguage(options.language)
),
counter: {
enable: true,
},
@ -43,10 +47,13 @@ export function getOptions(options: Options): IOptions {
fullscreen: {
index: 1000,
},
upload: {
handler: options.uploadImage,
},
};
}
function getLanguage(lang = "zh-CN"): keyof II18n {
export function getLanguage(lang = "zh-CN"): keyof II18n {
switch (lang) {
case "zh-CN":
return "zh_CN";
@ -63,6 +70,7 @@ function getLanguage(lang = "zh-CN"): keyof II18n {
function getToolbar(
showAttachmentCb: () => void,
openModal: (name: string) => void,
lang: keyof II18n
): (string | IMenuItem)[] | undefined {
return [
@ -101,6 +109,29 @@ function getToolbar(
"|",
"fullscreen",
"edit-mode",
{
name: "insert_custom",
tip: t("insert_custom"),
icon: mdiGrid,
tipPosition: "n",
toolbar: [
{
name: "insert_tips",
icon: t("insert_tips"),
click: () => openModal("tips"),
},
{
name: "insert_git",
icon: t("insert_git"),
click: () => openModal("git"),
},
{
name: "insert_drive",
icon: t("insert_drive"),
click: () => openModal("drive"),
},
],
},
{
name: "more",
toolbar: ["both", "export", "outline", "info", "help"],

View File

@ -5,21 +5,36 @@ import "@zhengyi/vditor/dist/index.css";
import type { EditorConfig } from "@/type/editor";
import { getOptions } from "@/utils/vditor-utils";
import type { AttachmentLike } from "@halo-dev/console-shared";
import type { Attachment } from "@halo-dev/api-client";
import { VLoading } from "@halo-dev/components";
import TipsModel from "@/model/TipsModel.vue";
import GitModal from "@/model/GitModal.vue";
import DriveModal from "@/model/DriveModal.vue";
const props = withDefaults(
defineProps<{
raw?: string;
content: string;
uploadImage?: (file: File) => Promise<Attachment>;
}>(),
{
raw: "",
content: "",
uploadImage: undefined,
}
);
const vditor = ref();
const vditorRef = ref();
const vditorLoaded = ref(false);
const attachmentSelectorModalShow = ref(false);
// none/tips/git
const insertModel = ref("none");
const insertValue = (value: string) => {
insertModel.value = "none";
vditor.value.insertValue(value);
vditor.value.focus();
};
const emit = defineEmits<{
(event: "update:raw", value: string): void;
@ -54,6 +69,7 @@ onUnmounted(async () => {
.querySelectorAll("script[id^='vditor']")
.forEach((el) => el.remove());
document.querySelectorAll("link[id^='vditor']").forEach((el) => el.remove());
vditorLoaded.value = false;
});
onMounted(async () => {
@ -82,11 +98,34 @@ onMounted(async () => {
typeWriterMode: typeWriterMode,
after: () => {
vditor.value.setValue(props.raw || "# Title Here");
vditorLoaded.value = true;
},
input: debounceOnUpdate,
showAttachment: () => (attachmentSelectorModalShow.value = true),
language: lang,
codeBlockPreview: codeBlockPreview,
uploadImage: (files: File[]) => {
const acceptType = ["png", "jpg", "jpeg", "bmp", "gif", "webp", "svg"];
const extendName = files[0].name
.slice(files[0].name.lastIndexOf(".") + 1)
.toLowerCase();
if (acceptType.indexOf(extendName) === -1) {
vditor.value.tip("不允许上传该类型图片!", 2000);
return null;
}
if (props.uploadImage) {
vditor.value.tip("正在上传图片...", 2000);
props.uploadImage(files[0]).then((res: Attachment) => {
vditor.value.insertValue(
`\n\n![${res.spec.displayName}](${res.status.permalink})\n\n`
);
});
}
return null;
},
openModal: (name: string) => {
insertModel.value = name;
},
})
);
});
@ -94,8 +133,27 @@ onMounted(async () => {
<template>
<div id="plugin-vditor-mde">
<VLoading v-if="!vditorLoaded" style="height: 100%" />
<div id="vditor" ref="vditorRef"></div>
<div class="insert-modals">
<TipsModel
:open="insertModel === 'tips'"
@done="insertValue"
@close="insertModel = 'none'"
/>
<GitModal
:open="insertModel === 'git'"
@done="insertValue"
@close="insertModel = 'none'"
/>
<DriveModal
:open="insertModel === 'drive'"
@done="insertValue"
@close="insertModel = 'none'"
/>
</div>
<AttachmentSelectorModal
v-model:visible="attachmentSelectorModalShow"
:accepts="['image/*']"
@ -115,4 +173,38 @@ onMounted(async () => {
#plugin-vditor-mde input {
line-height: normal;
}
.insert-modals label {
width: 100%;
display: flex;
}
.insert-modals label span {
width: 60px;
text-align: right;
}
.insert-modals select {
border: 1px solid #cccccc;
border-radius: 3px;
padding-top: 8px;
padding-bottom: 8px;
margin-left: 10px;
flex: 1;
}
.insert-modals textarea {
border: 1px solid #cccccc;
border-radius: 3px;
margin-left: 10px;
flex: 1;
}
.insert-modals input[type="text"] {
border: 1px solid #cccccc;
border-radius: 3px;
margin-left: 10px;
flex: 1;
padding: 8px 10px;
}
</style>

View File

@ -4,7 +4,7 @@ cd src/main/resources/static
rm -rf dist
pwd
curl -o vditor.tgz \
https://git.mczhengyi.top/zhengyi/-/packages/npm/@zhengyi%2Fvditor/3.9.8/files/249
https://git.mczhengyi.top/zhengyi/-/packages/npm/@zhengyi%2Fvditor/3.9.9/files/258
tar -xzvf vditor.tgz
mv package/dist .
rm -rf package

View File

@ -1 +1 @@
version=1.3.1-SNAPSHOT
version=1.4.0-SNAPSHOT

View File

@ -7,4 +7,5 @@ public class RenderConfig {
Boolean enableRender;
String darkMode;
Boolean mediaRender;
Boolean onlyMarkdown;
}

View File

@ -21,7 +21,8 @@ public class VditorPostContentHandler implements ReactivePostContentHandler {
public Mono<PostContentContext> handle(PostContentContext contentContext) {
return reactiveSettingFetcher.fetch("render", RenderConfig.class)
.map(renderConfig -> {
if (renderConfig.getEnableRender()) {
if (renderConfig.getEnableRender()&&
(!renderConfig.getOnlyMarkdown() || contentContext.getRawType().equals("markdown"))) {
contentContext.setContent(ScriptUtils.renderScript(renderConfig) + "\n" + contentContext.getContent());
}
return contentContext;

View File

@ -19,7 +19,9 @@ public class VditorSinglePageContentHandler implements ReactiveSinglePageContent
public Mono<SinglePageContentContext> handle(SinglePageContentContext contentContext) {
return reactiveSettingFetcher.fetch("render", RenderConfig.class)
.map(renderConfig -> {
if (renderConfig.getEnableRender()) {
// 启用条件开启渲染器在启用仅Markdown渲染时当前页面为Markdown
if (renderConfig.getEnableRender() &&
(!renderConfig.getOnlyMarkdown() || contentContext.getRawType().equals("markdown"))) {
contentContext.setContent(ScriptUtils.renderScript(renderConfig) + "\n" + contentContext.getContent());
}
return contentContext;

View File

@ -53,4 +53,9 @@ spec:
name: mediaRender
label: "*实验性 渲染媒体标签"
help: 启用该功能会搜索文章内可解析的链接进行解析, 这可能会导致您的渲染出现问题!
value: false
value: false
- $formkit: checkbox
name: onlyMarkdown
label: "仅在Markdown模式下渲染"
help: "启用该功能将仅在Markdown格式的文章下注入渲染脚本"
value: true

View File

@ -5,7 +5,7 @@ metadata:
name: vditor-mde
spec:
enabled: true
requires: ">=2.8.0"
requires: ">=2.11.0"
author:
name: zhengyi59
website: https://github.com/justice2001/veditor-plugin