This commit is contained in:
wll8 2021-11-24 19:10:10 +08:00
commit 69175a72f2
6 changed files with 242 additions and 12 deletions

20
.github/workflows/preview.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: Surge Preview
on:
pull_request:
types: [opened, synchronize, reopened, closed]
jobs:
preview:
runs-on: ubuntu-latest
if: github.repository == 'doocs/md'
steps:
- uses: actions/checkout@v2
- uses: afc163/surge-preview@main
with:
surge_token: ${{ secrets.SURGE_TOKEN }}
github_token: ${{ secrets.GITHUB_TOKEN }}
dist: dist
build: |
npm install
npm run build:h5-netlify

View File

@ -68,6 +68,7 @@ npm run build:h5-netlify
- [x] 支持 <kbd>Ctrl</kbd> + <kbd>F</kbd> 快速格式化文档 - [x] 支持 <kbd>Ctrl</kbd> + <kbd>F</kbd> 快速格式化文档
- [x] 支持色盘取色,快速替换文章整体色调 - [x] 支持色盘取色,快速替换文章整体色调
- [x] 支持多图上传,可自定义配置图床 - [x] 支持多图上传,可自定义配置图床
- [x] 支持自定义上传逻辑
- [x] 支持在编辑框右键弹出功能选项卡 - [x] 支持在编辑框右键弹出功能选项卡
## 目前支持哪些图床 ## 目前支持哪些图床
@ -80,6 +81,7 @@ npm run build:h5-netlify
| 4 | [阿里云](https://www.aliyun.com/product/oss) | 配置 `AccessKey ID`、`AccessKey Secret`、`Bucket`、`Region` 参数 | [如何使用阿里云 OSS](https://help.aliyun.com/document_detail/31883.html) | | 4 | [阿里云](https://www.aliyun.com/product/oss) | 配置 `AccessKey ID`、`AccessKey Secret`、`Bucket`、`Region` 参数 | [如何使用阿里云 OSS](https://help.aliyun.com/document_detail/31883.html) |
| 5 | [腾讯云](https://cloud.tencent.com/act/pro/cos) | 配置 `SecretId`、`SecretKey`、`Bucket`、`Region` 参数 | [如何使用腾讯云 COS](https://cloud.tencent.com/document/product/436/38484) | | 5 | [腾讯云](https://cloud.tencent.com/act/pro/cos) | 配置 `SecretId`、`SecretKey`、`Bucket`、`Region` 参数 | [如何使用腾讯云 COS](https://cloud.tencent.com/document/product/436/38484) |
| 6 | [七牛云](https://www.qiniu.com/products/kodo) | 配置 `AccessKey`、`SecretKey`、`Bucket`、`Domain`、`Region` 参数 | [如何使用七牛云 Kodo](https://developer.qiniu.com/kodo) | | 6 | [七牛云](https://www.qiniu.com/products/kodo) | 配置 `AccessKey`、`SecretKey`、`Bucket`、`Domain`、`Region` 参数 | [如何使用七牛云 Kodo](https://developer.qiniu.com/kodo) |
| - | 自定义上传逻辑 | 是 | 参考[自定义上传逻辑参数详情](#自定义上传逻辑) |
![select-and-change-color-theme](https://doocs.oss-cn-shenzhen.aliyuncs.com/img//1606034542281-a8c99fa7-c11e-4e43-98da-e36012f54dc8.gif) ![select-and-change-color-theme](https://doocs.oss-cn-shenzhen.aliyuncs.com/img//1606034542281-a8c99fa7-c11e-4e43-98da-e36012f54dc8.gif)
@ -89,6 +91,49 @@ npm run build:h5-netlify
![doocs-md-upload-image](https://doocs.oss-cn-shenzhen.aliyuncs.com/img//1606034542512-0769a336-b9eb-4d58-83c1-29db7b54f71b.gif) ![doocs-md-upload-image](https://doocs.oss-cn-shenzhen.aliyuncs.com/img//1606034542512-0769a336-b9eb-4d58-83c1-29db7b54f71b.gif)
## 自定义上传逻辑
在工具上没有提供预定义图床的情况下,你只需要自定义上传逻辑即可,这对于例如你不方便使用公共图床,而是使用自己的上传服务时非常有用。
你只需要在给定的函数中更改上传代码即可,为了方便,这个函数提供了可能使用的一些参数:
示例代码:
```js
const {file, util, okCb, errCb} = CUSTOM_ARG
const param = new FormData()
param.append('file', file)
util.axios.post('http://127.0.0.1:9000/upload', param, {
headers: { 'Content-Type': 'multipart/form-data' }
}).then(res => {
okCb(res.url)
}).catch(err => {
errCb(err)
})
// 提供的可用参数:
// CUSTOM_ARG = {
// content, // 待上传图片的 base64
// file, // 待上传图片的 file 对象
// util: {
// axios, // axios 实例
// CryptoJS, // 加密库
// OSS, // ali-oss
// COS, // cos-js-sdk-v5
// Buffer, // buffer-from
// uuidv4, // uuid
// qiniu, // qiniu-js
// tokenTools, // 一些编码转换函数
// getDir, // 获取 年/月/日 形式的目录
// getDateFilename, // 根据文件名获取它以 时间戳+uuid 的形式
// },
// okCb: resolve, // 重要: 上传成功后给此回调传 url 即可
// errCb: reject, // 上传失败调用的函数
// }
```
如果你创建了适用于其他第三方图床的上传代码,我们非常欢迎你分享它。
## 注意事项 ## 注意事项
如果你使用了某些浏览器脚本修改了网页背景色,可能导致渲染后的文章出现背景色分块的现象,详见 [#63](https://github.com/doocs/md/issues/63)。 如果你使用了某些浏览器脚本修改了网页背景色,可能导致渲染后的文章出现背景色分块的现象,详见 [#63](https://github.com/doocs/md/issues/63)。

View File

@ -3,7 +3,16 @@
* https://hongqiye.com/doc/mockm/config/option.html * https://hongqiye.com/doc/mockm/config/option.html
* @type {import('mockm/@types/config').Config} * @type {import('mockm/@types/config').Config}
*/ */
module.exports = { module.exports = (util) => {
return {
api: {
'/upload'(req, res) {
res.json({
msg: `上传成功`,
url: util.libObj.mockjs.mock(`@image`),
})
},
},
static: [ static: [
{ // 测试 netlify 部署 { // 测试 netlify 部署
fileDir: `./dist`, fileDir: `./dist`,
@ -14,4 +23,5 @@ module.exports = {
path: `/md`, path: `/md`,
}, },
], ],
}
} }

View File

@ -7,6 +7,7 @@ import Buffer from "buffer-from";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import * as qiniu from "qiniu-js"; import * as qiniu from "qiniu-js";
import { utf16to8, base64encode, safe64 } from "../assets/scripts/tokenTools"; import { utf16to8, base64encode, safe64 } from "../assets/scripts/tokenTools";
import * as tokenTools from "../assets/scripts/tokenTools";
function getConfig(useDefault, platform) { function getConfig(useDefault, platform) {
if (useDefault) { if (useDefault) {
@ -42,6 +43,10 @@ function getConfig(useDefault, platform) {
}; };
} }
/**
* 获取 `年/月/日` 形式的目录
* @returns string
*/
function getDir() { function getDir() {
const date = new Date(); const date = new Date();
const year = date.getFullYear(); const year = date.getFullYear();
@ -50,6 +55,11 @@ function getDir() {
return `${year}/${month}/${day}`; return `${year}/${month}/${day}`;
} }
/**
* 根据文件名获取它以 `时间戳+uuid` 的形式
* @param {string} filename 文件名
* @returns
*/
function getDateFilename(filename) { function getDateFilename(filename) {
const currentTimestamp = new Date().getTime(); const currentTimestamp = new Date().getTime();
const fileSuffix = filename.split(".")[1]; const fileSuffix = filename.split(".")[1];
@ -216,6 +226,42 @@ async function txCOSFileUpload(file) {
}); });
} }
//-----------------------------------------------------------------------
// formCustom File Upload
//-----------------------------------------------------------------------
async function formCustomUpload(content, file) {
const str = `
async (CUSTOM_ARG) => {
${localStorage.getItem(`formCustomConfig`)}
}
`
return new Promise((resolve, reject) => {
const exportObj = {
content, // 待上传图片的 base64
file, // 待上传图片的 file 对象
util: {
axios: fetch, // axios 实例
CryptoJS, // 加密库
OSS, // ali-oss
COS, // cos-js-sdk-v5
Buffer, // buffer-from
uuidv4, // uuid
qiniu, // qiniu-js
tokenTools, // 一些编码转换函数
getDir, // 获取 年/月/日 形式的目录
getDateFilename, // 根据文件名获取它以 时间戳+uuid 的形式
},
okCb: resolve, // 重要: 上传成功后给此回调传 url 即可
errCb: reject, // 上传失败调用的函数
}
eval(str)(exportObj).catch(err => {
console.error(err)
reject(err)
})
});
}
function fileUpload(content, file) { function fileUpload(content, file) {
const imgHost = localStorage.getItem("imgHost"); const imgHost = localStorage.getItem("imgHost");
!imgHost && localStorage.setItem("imgHost", "default"); !imgHost && localStorage.setItem("imgHost", "default");
@ -230,6 +276,8 @@ function fileUpload(content, file) {
return giteeUpload(content, file.name); return giteeUpload(content, file.name);
case "github": case "github":
return ghFileUpload(content, file.name); return ghFileUpload(content, file.name);
case "formCustom":
return formCustomUpload(content, file);
default: default:
// return file.size / 1024 < 1024 // return file.size / 1024 < 1024
// ? giteeUpload(content, file.name) // ? giteeUpload(content, file.name)

View File

@ -374,3 +374,21 @@ function getElementStyles(element, excludes = ["width", "height"]) {
.map(([key, value]) => `${key}:${value};`) .map(([key, value]) => `${key}:${value};`)
.join(""); .join("");
} }
/**
* 移除左边多余空格
* @param {*} str
* @returns
*/
export function removeLeft(str) {
const lines = str.split('\n')
// 获取应该删除的空白符数量
const minSpaceNum = lines.filter(item => item.trim())
.map(item => item.match(/(^\s+)?/)[0].length)
.sort((a, b) => a - b)[0]
// 删除空白符
const newStr = lines
.map(item => item.slice(minSpaceNum))
.join('\n')
return newStr
}

View File

@ -5,7 +5,7 @@
:visible="value" :visible="value"
@close="$emit('close')" @close="$emit('close')"
> >
<el-tabs type="activeName" :value="'upload'"> <el-tabs type="activeName" v-model="activeName">
<el-tab-pane class="upload-panel" label="选择上传" name="upload"> <el-tab-pane class="upload-panel" label="选择上传" name="upload">
<el-select <el-select
v-model="imgHost" v-model="imgHost"
@ -288,12 +288,42 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-tab-pane> </el-tab-pane>
<el-tab-pane class="github-panel formCustom" label="自定义代码" name="formCustom">
<el-form
class="setting-form"
:model="formCustom"
label-position="right"
>
<el-form-item label="" :required="true">
<el-input
class="formCustomElInput"
ref="formCustomElInput"
type="textarea"
resize="none"
placeholder="Your custom code here."
v-model="formCustom.code">
</el-input>
<el-link
type="primary"
href="https://github.com/doocs/md#自定义上传逻辑"
target="_blank"
>参数详情</el-link
>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="formCustomSave"
>保存配置</el-button
>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs> </el-tabs>
</el-dialog> </el-dialog>
</template> </template>
<script> <script>
import { checkImage } from "../../assets/scripts/util"; import { checkImage, removeLeft } from "../../assets/scripts/util";
import CodeMirror from "codemirror/lib/codemirror";
export default { export default {
props: { props: {
@ -304,6 +334,8 @@ export default {
}, },
data() { data() {
return { return {
activeName: `upload`,
formGitHub: { formGitHub: {
repo: "", repo: "",
branch: "", branch: "",
@ -337,6 +369,21 @@ export default {
domain: "", domain: "",
region: "", region: "",
}, },
formCustom: {
code: localStorage.getItem(`formCustomConfig`) || removeLeft(`
const {file, util, okCb, errCb} = CUSTOM_ARG
const param = new FormData()
param.append('file', file)
util.axios.post('http://127.0.0.1:9000/upload', param, {
headers: { 'Content-Type': 'multipart/form-data' }
}).then(res => {
okCb(res.url)
}).catch(err => {
errCb(err)
})
`).trim(),
editor: undefined,
},
options: [ options: [
{ {
value: "default", value: "default",
@ -362,6 +409,10 @@ export default {
value: "qiniu", value: "qiniu",
label: "七牛云", label: "七牛云",
}, },
{
value: "formCustom",
label: "自定义代码",
},
], ],
imgHost: "default", imgHost: "default",
}; };
@ -454,6 +505,11 @@ export default {
localStorage.setItem("qiniuConfig", JSON.stringify(this.formQiniu)); localStorage.setItem("qiniuConfig", JSON.stringify(this.formQiniu));
this.$message.success("保存成功"); this.$message.success("保存成功");
}, },
formCustomSave() {
const str = this.formCustom.editor.getValue()
localStorage.setItem(`formCustomConfig`, str)
this.$message.success(`保存成功`)
},
beforeImageUpload(file) { beforeImageUpload(file) {
// check image // check image
@ -479,6 +535,25 @@ export default {
this.$emit("uploadImage", params.file); this.$emit("uploadImage", params.file);
}, },
}, },
watch: {
activeName: {
immediate: true,
handler(val) {
if(val === `formCustom`) {
this.$nextTick(() => {
const textarea = this.$refs.formCustomElInput.$el.querySelector(`textarea`)
this.formCustom.editor = this.formCustom.editor || CodeMirror.fromTextArea(textarea, {
mode: `javascript`,
})
this.formCustom.editor.setValue(this.formCustom.code)
})
}
},
},
},
mounted() {
},
}; };
</script> </script>
@ -518,6 +593,20 @@ export default {
.github-panel { .github-panel {
display: flex; display: flex;
justify-content: center; justify-content: center;
&.formCustom {
width: 100%;
}
.formCustomElInput {
/deep/ .CodeMirror {
border: 1px solid #eee;
height: 300px !important;
font-family: "Fira Mono", "DejaVu Sans Mono", Menlo, Consolas, "Liberation Mono", Monaco, "Lucida Console", monospace !important;
line-height: 20px;
.CodeMirror-scroll {
padding: 10px;
}
}
}
} }
.setting-form { .setting-form {