提交 c2be8bbe 编写于 作者: 璃白.'s avatar 璃白. 🌻

feat:编辑器主体开发

上级 8b0cf758
node_modules
node_modules/*
dist
.vscode
*.css
{
"scripts": {
"dev": "webpack serve --mode=development",
"build": "webpack"
},
"devDependencies": {
"css-loader": "^5.2.6",
"less": "^4.1.1",
"less-loader": "^9.0.0",
"style-loader": "^2.0.0",
"url-loader": "^4.1.1",
"vue-loader": "^15.9.7",
"vue-template-compiler": "^2.6.12",
"webpack": "^5.38.1",
"webpack-cli": "^4.7.0",
"webpack-dev-server": "^3.11.2"
},
"dependencies": {
"marked": "^2.0.6",
"vue": "^2.6.12"
}
}
<template>
<div class="md_container">
<markdown-header :showPreview.sync="showPreview" />
<markdownPreview v-if="showPreview" :content="html" />
<textarea
v-else
@change="changeText"
@focus="isFocus = true"
@blur="isFocus = false"
v-model="text"
rows="10"
></textarea>
<markdown-footer v-if="!showPreview" />
</div>
</template>
<script>
import markdownHeader from "./components/md-header";
import markdownFooter from "./components/md-footer";
import markdownPreview from "./components/md-preview";
import marked from "marked";
export default {
components: { markdownHeader, markdownFooter, markdownPreview },
data() {
return {
text: "",
html: "",
showPreview: false,
isFocus: false
};
},
methods: {
changeText() {
const text = this.text;
this.html = marked(text);
this.$emit("change", { text, html: this.html });
}
}
};
</script>
<style lang="less" scoped>
.md_container {
width: 1000px;
margin: 200px auto;
border: 1px solid var(--md-editor-theme-color);
border-radius: 4px;
padding: 10px 16px;
box-sizing: border-box;
textarea {
display: block;
width: 100%;
padding: 10px 0;
box-sizing: border-box;
color: #303030;
resize: none;
font-family: "Menlo", "DejaVu Sans Mono", "Liberation Mono", "Consolas",
"Ubuntu Mono", "Courier New", "andale mono", "lucida console", monospace;
}
}
</style>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" id="bold"><path d="M4 2h3.963c.63 0 1.216.05 1.76.148.555.086 1.036.24 1.444.461.407.222.728.53.963.923.234.382.351.868.351 1.459 0 .258-.043.523-.13.794-.073.258-.197.51-.37.757-.16.233-.37.443-.63.627-.258.185-.56.32-.907.406v.074c.852.173 1.488.486 1.908.942.432.443.648 1.064.648 1.864 0 .616-.123 1.151-.37 1.607a3.068 3.068 0 0 1-1 1.107c-.42.283-.914.492-1.482.628A7.806 7.806 0 0 1 8.333 14H4V2zm3.889 4.726c.518 0 .895-.117 1.13-.35.234-.234.351-.536.351-.905 0-.37-.117-.634-.351-.794-.235-.16-.612-.24-1.13-.24H7v2.29h.889zm.222 4.837c.63 0 1.08-.117 1.352-.35.284-.234.426-.573.426-1.016 0-.443-.142-.763-.426-.96-.272-.197-.722-.295-1.352-.295H7v2.621h1.111z"></path></svg>
\ No newline at end of file
@import "./variable.less";
@import "./iconfont.less";
* {
padding: 0;
margin: 0;
}
html,
body,
#app {
padding: 0;
margin: 0;
min-height: 100vh;
}
ul,
ol,
li,
input,
textarea {
list-style: none;
outline: none;
border: none;
padding: 0;
}
img {
display: block;
margin: 0;
}
@font-face {
font-family: "iconfont"; /* Project id */
src: url('iconfont.ttf?t=1622541171105') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-baojiaquotation:before {
content: "\e63a";
}
.icon-renwu:before {
content: "\e63f";
}
.icon-youxuliebiao:before {
content: "\e6f0";
}
.icon-bold:before {
content: "\e678";
}
.icon-biaoge:before {
content: "\e615";
}
.icon-lianjie:before {
content: "\e645";
}
.icon-unorderedList:before {
content: "\e620";
}
.icon-code:before {
content: "\e625";
}
.icon-fullScreen:before {
content: "\e627";
}
.icon-italic:before {
content: "\eaef";
}
.icon-tupian:before {
content: "\e607";
}
:root {
--md-editor-theme-color: #dbdbdb;
--md-editor-theme-color-active: #409eff;
}
<template>
<div class="md_footer">
<div class="doc"></div>
<upload-files />
</div>
</template>
<script>
import uploadFiles from "./upload-files";
export default {
components: { uploadFiles },
data() {
return {};
}
};
</script>
<style lang="less" scoped>
.md_footer {
display: flex;
justify-content: space-between;
align-items: center;
border-top: 1px solid var(--md-editor-theme-color);
padding-top: 10px;
box-sizing: border-box;
font-size: 14px;
color: #666;
}
</style>
<template>
<div class="md_header">
<div class="header_tabs">
<div
:class="['tab_item', { active: !showPreview }]"
@click="toggleTab('edit')"
>
编辑
</div>
<div
:class="['tab_item', { active: showPreview }]"
@click="toggleTab('preview')"
>
预览
</div>
</div>
<div class="header_tools" v-if="!showPreview">
<tool-button
:info="item"
v-for="(item, index) in toolButtonList"
:key="index"
/>
</div>
</div>
</template>
<script>
import toolButton from "./tool-button";
export default {
components: { toolButton },
props: {
showPreview: {
type: Boolean,
default: false
}
},
data() {
return {
toolButtonList: [
{
name: "bold",
format: "**"
},
{
name: "italic"
},
{
name: "baojiaquotation"
},
{
name: "code"
},
{
name: "lianjie"
},
{
name: "unorderedList"
},
{
name: "youxuliebiao"
},
{
name: "renwu"
},
{
name: "biaoge"
},
{
name: "fullScreen"
}
]
};
},
methods: {
toggleTab(type) {
this.$emit("update:showPreview", type === "preview");
}
}
};
</script>
<style lang="less" scoped>
.md_header {
display: flex;
justify-content: space-between;
align-items: center;
height: 32px;
border-bottom: 1px solid var(--md-editor-theme-color);
.header_tabs {
display: flex;
justify-content: space-between;
font-size: 14px;
padding-bottom: 10px;
box-sizing: border-box;
.tab_item {
color: #666;
cursor: pointer;
position: relative;
padding: 0 6px;
box-sizing: border-box;
&::after {
display: block;
content: "";
position: absolute;
bottom: -12px;
width: 0;
height: 3px;
left: 50%;
transform: translateX(-50%);
background: transparent;
transition: all 0.3s;
}
&:hover {
color: #000;
&::after {
width: 100%;
background: #666;
}
}
&.active {
color: #000;
font-weight: 700;
&::after {
width: 100%;
background: var(--md-editor-theme-color-active);
}
}
& + .tab_item {
margin-left: 10px;
}
}
}
.header_tools {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 10px;
box-sizing: border-box;
/deep/.tool_button {
margin: 0 8px;
}
}
}
</style>
<template>
<div class="md_preview">
<div v-html="content"></div>
</div>
</template>
<script>
export default {
props: {
content: {
type: String,
default: ""
}
},
data() {
return {};
}
};
</script>
<style lang="less" scoped>
.md_preview {
min-height: 170px;
padding: 10px 0;
box-sizing: border-box;
color: #303030;
}
</style>
<template>
<div class="tool_button">
<span :class="['icon iconfont', `icon-${info.name}`]"></span>
</div>
</template>
<script>
export default {
props: {
info: {
type: Object,
default: () => {}
}
}
};
</script>
<style lang="less" scoped>
.tool_button {
.icon {
font-size: 18px;
color: #666;
cursor: pointer;
&:hover {
color: #000;
}
}
}
</style>
<template>
<div class="upload_files" @click="upload">
<span :class="['icon iconfont', 'icon-tupian']"></span>
<span>添加附件</span>
</div>
</template>
<script>
export default {
data() {
return {};
},
methods: {
upload() {}
}
};
</script>
<style lang="less" scoped>
.upload_files {
cursor: pointer;
.icon {
font-size: 20px;
color: #666;
cursor: pointer;
display: inline-block;
vertical-align: bottom;
&:hover {
color: #000;
}
}
}
</style>
import Vue from "vue";
import App from "./App";
import "@/assets/style/global.less";
function MdEditor(obj) {
const { el, onChange } = obj;
if (!el || !document.querySelector(el)) throw new Error("请指定容器");
new Vue({
render: h =>
h(App, {
on: {
change(val) {
onChange(val);
}
}
})
}).$mount(el);
}
window.MdEditor = MdEditor;
const path = require("path");
const VueLoaderPlugin = require("vue-loader/lib/plugin");
module.exports = {
entry: path.resolve(__dirname, "./src/main.js"),
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist")
},
devServer: {
contentBase: path.resolve(__dirname, "dist"),
open: true
},
resolve: {
extensions: [".js", ".vue", ".json"],
alias: {
"@": path.resolve(__dirname, "./src")
}
},
module: {
rules: [
{
test: /\.vue$/,
loader: "vue-loader"
},
{
test: /\.less$/i,
use: ["style-loader", "css-loader", "less-loader"]
},
{
test: /\.ttf$/,
loader: "url-loader"
}
]
},
plugins: [new VueLoaderPlugin()]
};
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册