Browse Source

feat: 在编辑器中应用样式,退格支持,bug: 目前编辑项的类型变了会失去焦点

ChenYangfan 6 years ago
parent
commit
289a52abdb

+ 33 - 3
src/components/editor/EditableItem.vue

@@ -9,13 +9,19 @@
     @keydown.stop="handleKeyDown"
     contenteditable
     v-html="item.content"
+    :class="item.content ? '' : 'placeholder'"
+    :placeholder="item.placeholder"
   >
   </component>
 </template>
 
 <script>
 import blockTypeRegs from '../../utils/block-type-regs'
-import { MODIFY_ITEM, ADD_ITEM_AFTER } from '../../store/types/editor-types'
+import {
+  MODIFY_ITEM,
+  ADD_ITEM_AFTER,
+  DELETE_ITEM
+} from '../../store/types/editor-types'
 import { mapState } from 'vuex'
 export default {
   name: 'editable-item',
@@ -51,7 +57,23 @@ export default {
             const focusNode = document.getElementById(this.item.id).nextSibling
             focusNode.focus()
           })
+          break
 
+        case 'Backspace':
+          if (!this.isCompositionStarted && !this.item.content) {
+            e.preventDefault()
+            this.$store.commit(DELETE_ITEM, {
+              pageId: this.pageId,
+              itemId: this.item.id
+            })
+            const focusNode = document.getElementById(this.item.id)
+              .previousElementSibling
+            this.$nextTick(() => {
+              focusNode.focus()
+              const focusRange = window.getSelection().getRangeAt(0)
+              focusRange.setStart(focusRange.startContainer.parentNode, 1)
+            })
+          }
           break
 
         default:
@@ -81,7 +103,9 @@ export default {
       const offset = this.range.startOffset
 
       this.$nextTick(() => {
-        this.range.setStart(this.range.startContainer.firstChild, offset)
+        if (this.range.startContainer.firstChild) {
+          this.range.setStart(this.range.startContainer.firstChild, offset)
+        }
 
         this.range.collapse()
         window.getSelection().removeAllRanges()
@@ -95,7 +119,8 @@ export default {
 <style lang="scss" scoped>
 * [contenteditable] {
   outline: none;
-  padding: 10px;
+  padding: 10px 5px;
+  width: 100%;
 
   &:hover {
     background: #fafcfe;
@@ -104,5 +129,10 @@ export default {
   &:focus {
     background: #f9fafc;
   }
+
+  &:empty:before {
+    content: attr(placeholder);
+    color: #bbb;
+  }
 }
 </style>

+ 17 - 2
src/components/editor/EditingPage.vue

@@ -1,5 +1,5 @@
 <template>
-  <section>
+  <section class="page">
     <template v-for="item in page.items">
       <editable-item-vue :key="item.id" :item="item" :pageId="page.id" />
     </template>
@@ -21,5 +21,20 @@ export default {
 }
 </script>
 
-<style scoped>
+<style lang="scss" scoped>
+.page {
+  display: flex;
+  flex-direction: column;
+
+  max-width: 75vw;
+  margin: 0 auto;
+
+  align-items: center;
+  justify-content: center;
+  min-height: 100vh;
+
+  box-sizing: border-box;
+  border-bottom: 1px solid gray; //dev
+  font-weight: 500;
+}
 </style>

+ 160 - 8
src/components/editor/Editor.vue

@@ -1,5 +1,5 @@
 <template>
-  <div id="container">
+  <div class="slide">
     <template v-for="page in pages">
       <editing-page-vue :key="page.id" :page="page"></editing-page-vue>
     </template>
@@ -23,15 +23,167 @@ export default {
   }
 }
 </script>
-
 <style lang="scss" scoped>
-#container {
-  min-height: 100vh;
-  padding: 10px;
-
+@import './reset.scss';
+.slide {
   box-sizing: border-box;
-
+  text-align: justify;
+  margin: 0 auto;
   outline: none;
-  border: 40px solid lightblue;
+
+  line-height: 1.6;
+  word-break: break-word;
+  word-wrap: break-word;
+
+  font-size: 22px;
+
+  h1,
+  h2,
+  h3,
+  h4,
+  h5,
+  h6 {
+    font-weight: 600;
+    text-align: left;
+  }
+
+  h1 {
+    font-size: 1.88em;
+    margin: 1.25em 0 0.5em 0;
+  }
+
+  h2 {
+    font-size: 1.56em;
+    margin: 2.5em 0 0.3em 0;
+  }
+
+  h3 {
+    font-size: 1.38em;
+    margin: 2.19em 0 0.15em 0;
+  }
+
+  h4 {
+    font-size: 1.25em;
+    margin: 1.56em 0 0.1em 0;
+  }
+
+  h5 {
+    font-size: 1em;
+    margin: 1.25em 0 0.1em 0;
+  }
+
+  h6 {
+    font-size: 1em;
+    margin: 1.25em 0 0.1em 0;
+  }
+
+  p {
+    margin: 0.44em 0;
+  }
+
+  li {
+    margin: 0.19em;
+  }
+
+  ul,
+  ol {
+    list-style: none;
+    padding-left: 1em;
+    margin-top: 0.3em;
+    margin-bottom: 0.8em;
+  }
+
+  ul > li::before {
+    content: '\02022';
+    display: inline-block;
+    color: #1976d2;
+    width: 1em;
+    margin-left: -1em;
+  }
+
+  ol {
+    counter-reset: li;
+    margin: 0 0 0 0.5em;
+
+    li {
+      text-align: left;
+    }
+  }
+
+  ol > li::before {
+    font-family: 'Courier', 'Courier New', monospace !important;
+    overflow: hidden;
+    font-size: 1em;
+    line-height: 0;
+    content: counter(li) '.';
+    color: #1976d2;
+    display: inline;
+    margin-left: -1.6em;
+    margin-right: 0.4em;
+    text-align: right;
+    counter-increment: li;
+  }
+
+  blockquote {
+    margin: 0.63em 0;
+    padding: 0.31em 0.94em;
+    border-left: #fbc02d 0.25em solid;
+    background: #fffad3;
+    position: relative;
+
+    &::before {
+      content: '!';
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 0.75em;
+      font-family: monospace;
+      color: white;
+      position: absolute;
+      left: -0.69em;
+      top: 1em;
+      width: 0.88em;
+      height: 0.88em;
+      background: #fbc02d;
+      border: 0.13em #fbc02d solid;
+      border-radius: 50%;
+    }
+  }
+
+  a {
+    text-decoration: underline solid transparent;
+    transition: 0.2s all ease-out;
+
+    &:hover {
+      text-decoration: underline solid #ccc;
+    }
+  }
+
+  li > a,
+  p > a {
+    color: #1976d2;
+  }
+
+  img {
+    max-width: 100%;
+    max-height: 100vh;
+    margin: 0.94em auto 1.88em auto;
+    display: block;
+    box-shadow: 0 0.13em 0.63em 0 #e5e5e5;
+  }
+
+  pre {
+    padding: 0.63em 0.94em;
+    border: gray 0.06em solid;
+  }
+
+  code {
+    overflow: auto;
+    word-break: normal;
+    padding: 0 0.19em;
+    font-family: 'Menlo', 'Courier New', monospace;
+    color: #1899ba;
+    font-size: 0.88em;
+  }
 }
 </style>

+ 137 - 0
src/components/editor/reset.scss

@@ -0,0 +1,137 @@
+// 重置所有默认样式
+body {
+  font-family: sans-serif;
+}
+
+html,
+body,
+div,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+p,
+dl,
+dt,
+dd,
+ol,
+ul,
+li,
+fieldset,
+form,
+label,
+input,
+legend,
+table,
+caption,
+tbody,
+tfoot,
+thead,
+tr,
+th,
+td,
+textarea,
+article,
+aside,
+audio,
+canvas,
+figure,
+footer,
+header,
+mark,
+menu,
+nav,
+section,
+time,
+video {
+  margin: 0;
+  padding: 0;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+  font-size: 100%;
+  font-weight: normal;
+}
+
+article,
+aside,
+dialog,
+figure,
+footer,
+header,
+hgroup,
+nav,
+section,
+blockquote {
+  display: block;
+}
+
+ul,
+ol {
+  list-style: none;
+}
+
+img {
+  border: 0 none;
+  vertical-align: top;
+}
+
+blockquote,
+q {
+  quotes: none;
+}
+
+blockquote:before,
+blockquote:after,
+q:before,
+q:after {
+  content: none;
+}
+
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+strong,
+em,
+i {
+  font-style: normal;
+  font-weight: normal;
+}
+
+ins {
+  text-decoration: underline;
+}
+
+del {
+  text-decoration: line-through;
+}
+
+mark {
+  background: none;
+}
+
+input::-ms-clear {
+  display: none !important;
+}
+
+body {
+  background: #fff;
+}
+
+a {
+  text-decoration: none;
+  color: #333;
+}
+
+a:hover {
+  text-decoration: underline;
+}

+ 12 - 5
src/store/modules/editor.js

@@ -58,8 +58,6 @@ export default {
     [DELETE_PAGE](state, payload) {
       state.pages = [...state.pages.filter(page => page.id !== payload.id)]
     },
-    // [SPLITE_PAGE](state, payload) {},
-    // [JOIN_PAGE](state, payload) {},
     [ADD_ITEM_AFTER](state, payload) {
       state.pages = [
         ...state.pages.map(page => {
@@ -103,9 +101,18 @@ export default {
       ]
     },
     [DELETE_ITEM](state, payload) {
-      state.pages = {
-        ...state.pages.map(page => page.id === payload.id && page.items.filter(item => item.id !== payload.id))
-      }
+      state.pages = [
+        ...state.pages.map(page => {
+          if (page.id === payload.pageId) {
+            return {
+              ...page,
+              items: page.items.filter(item => item.id !== payload.itemId)
+            }
+          } else {
+            return page
+          }
+        })
+      ]
     }
   },
   actions: {},