Преглед изворни кода

improvement: 添加代码高亮支持

ChenYangfan пре 6 година
родитељ
комит
333deb8ea4

+ 1 - 0
package.json

@@ -10,6 +10,7 @@
   },
   "dependencies": {
     "core-js": "^3.4.3",
+    "highlight.js": "^9.17.1",
     "register-service-worker": "^1.6.2",
     "uuid": "^3.3.3",
     "vue": "^2.6.10",

+ 187 - 0
src/common/mode-common.scss

@@ -0,0 +1,187 @@
+.page {
+  display: flex;
+  flex-wrap: wrap;
+
+  margin: 0 auto;
+  align-items: center;
+
+  padding: 0 2rem;
+
+  box-sizing: border-box;
+  font-weight: 500;
+
+  &>div {
+    width: 100%;
+    box-sizing: border-box;
+  }
+}
+
+.slide {
+  box-sizing: border-box;
+  text-align: justify;
+  margin: 0 auto;
+  outline: none;
+
+  line-height: 1.6;
+
+  font-size: 24px;
+
+  h1,
+  h2,
+  h3,
+  h4,
+  h5,
+  h6 {
+    font-weight: 600;
+    width: 100%;
+  }
+
+  h1 {
+    font-size: 2em;
+    margin: 1em 0 0.35em 0;
+  }
+
+  h2 {
+    font-size: 1.8em;
+    margin: 1em 0 0.3em 0;
+  }
+
+  h3 {
+    font-size: 1.5em;
+    margin: .5em 0 0.2em 0;
+  }
+
+  h4 {
+    font-size: 1.35em;
+    margin: .5em 0 0.2em 0;
+  }
+
+  h5 {
+    font-size: 1.2em;
+    margin: .25em 0 0.1em 0;
+  }
+
+  h6 {
+    font-size: 1.1em;
+    margin: .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: 1em 0;
+    font-family: serif;
+    font-size: 1.5em;
+    position: relative;
+    font-style: italic;
+    width: 100%;
+    font-weight: 600;
+
+
+    &::before {
+      content: '"';
+      position: absolute;
+      transform: translateX(-150%);
+    }
+
+    &::after {
+      content: '"';
+      position: absolute;
+      transform: translateX(150%);
+    }
+  }
+
+  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;
+  }
+
+  .code {
+
+    font-family: monospace;
+    font-weight: bold;
+    text-align: left;
+    word-break: break-all;
+
+    line-height: 2;
+    display: block;
+    padding: 10px 15px;
+    border: gray 1px solid;
+  }
+
+  code {
+    width: 100%;
+    overflow: auto;
+    padding: 0 0.19em;
+    font-size: 0.8em;
+    font-weight: blod;
+  }
+}
+
+@media (min-width: 900px) {
+  .slide {
+    padding: 0 10vw;
+  }
+}

+ 8 - 0
src/common/read-mode.scss

@@ -0,0 +1,8 @@
+.read-mode {
+  .page {
+    min-height: unset;
+    padding-top: 20px;
+    padding-bottom: 20px;
+    font-size: 1rem;
+  }
+}

+ 10 - 161
src/common/show-mode.scss

@@ -1,161 +1,10 @@
-.slide {
-    box-sizing: border-box;
-    text-align: justify;
-    margin: 0 auto;
-    outline: none;
-  
-    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;
-    }
-  }
+.show-mode {
+  .page {
+    min-height: 100vh;
+  }
+
+  code {
+    font-weight: 800;
+    font-size: 1em
+  }
+}

+ 15 - 14
src/components/editor/EditableItem.vue

@@ -2,7 +2,7 @@
   <component
     ref="element"
     :is="showInfo.tagName"
-    :id="item.id"
+    :id="item.frontendId"
     @compositionstart.stop="handleCompositionStart"
     @compositionend.stop="handleCompositionEnd"
     @input.stop="handleInput"
@@ -93,10 +93,10 @@ export default {
           case 'Enter':
             e.preventDefault()
             this.$store.commit(ADD_ITEM_AFTER, {
-              id: this.item.id
+              frontendId: this.item.frontendId
             })
             this.$store.commit(SET_FOCUS_AFTER, {
-              focusId: this.item.id
+              focusId: this.item.frontendId
             })
             break
 
@@ -104,10 +104,10 @@ export default {
             if (!this.item.content) {
               e.preventDefault()
               this.$store.commit(SET_FOCUS_BEFORE, {
-                focusId: this.item.id
+                focusId: this.item.frontendId
               })
               this.$store.commit(DELETE_ITEM, {
-                id: this.item.id
+                frontendId: this.item.frontendId
               })
               break
             }
@@ -117,7 +117,7 @@ export default {
 
             if (offset === 0 && this.range.collapsed && this.type !== 'div') {
               this.$store.commit(MODIFY_ITEM, {
-                id: this.item.id,
+                frontendId: this.item.frontendId,
                 diff: {
                   placeholder: defaultPlaceholder['div'],
                   content: this.showInfo.content
@@ -125,7 +125,7 @@ export default {
               })
 
               this.$store.commit(SET_FOCUS, {
-                diff: { focusId: this.item.id, offset }
+                diff: { focusId: this.item.frontendId, offset }
               })
             }
             break
@@ -133,7 +133,7 @@ export default {
           case 'ArrowUp':
             this.handleRangeChanged(() => {
               this.$store.commit(SET_FOCUS_BEFORE, {
-                focusId: this.item.id
+                focusId: this.item.frontendId
               })
             })
 
@@ -141,7 +141,7 @@ export default {
           case 'ArrowDown':
             this.handleRangeChanged(() => {
               this.$store.commit(SET_FOCUS_AFTER, {
-                focusId: this.item.id
+                focusId: this.item.frontendId
               })
             })
             break
@@ -173,15 +173,15 @@ export default {
       }
 
       this.$store.commit(MODIFY_ITEM, {
-        id: this.item.id,
+        frontendId: this.item.frontendId,
         diff
       })
 
       if (this.matchType(diff.content) === 'slideBreak') {
-        this.$store.commit(ADD_ITEM_AFTER, { id: this.item.id })
+        this.$store.commit(ADD_ITEM_AFTER, { frontendId: this.item.frontendId })
 
         this.$store.commit(SET_FOCUS_AFTER, {
-          focusId: this.item.id,
+          focusId: this.item.frontendId,
           offset: 0
         })
       } else {
@@ -189,7 +189,7 @@ export default {
         const offset = this.range.startOffset
 
         this.$store.commit(SET_FOCUS, {
-          diff: { focusId: this.item.id, offset }
+          diff: { focusId: this.item.frontendId, offset }
         })
       }
     },
@@ -212,9 +212,10 @@ export default {
 </script>
 
 <style lang="scss" scoped>
+@import '@/common/mode-common.scss';
 * [contenteditable] {
   outline: none;
-  padding: 10px 5px;
+  padding: 8px 2px;
   width: 100%;
 
   &:hover {

+ 7 - 16
src/components/editor/EditingPage.vue

@@ -1,8 +1,10 @@
 <template>
   <section class="page">
-    <template v-for="item in page.items">
-      <editable-item-vue :key="item.id" :item="item" />
-    </template>
+    <div>
+      <template v-for="item in page.items">
+        <editable-item-vue :item="item" :key="item.frontendId" />
+      </template>
+    </div>
   </section>
 </template>
 
@@ -18,19 +20,8 @@ export default {
 </script>
 
 <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;
-
+.page > div {
+  width: 100%;
   box-sizing: border-box;
-  border-bottom: 1px solid gray; //dev
-  font-weight: 500;
 }
 </style>

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

@@ -1,7 +1,7 @@
 <template>
-  <div class="slide">
+  <div class="slide show-mode">
     <template v-for="page in pages">
-      <editing-page-vue :key="page.id" :page="page"></editing-page-vue>
+      <editing-page-vue :key="page.frontendId" :page="page"></editing-page-vue>
     </template>
     <div v-show="false">{{ focus }}</div>
   </div>
@@ -24,7 +24,10 @@ export default {
       const items = this.$store.state.editor.items
       if (items.length === 0) {
         this.$nextTick(() => {
-          this.$store.commit(INIT_BY_DEFAULT)
+          this.$store.commit(
+            INIT_BY_DEFAULT,
+            this.$store.state.editor.slideInfo
+          )
         })
       }
       const pages = []
@@ -104,5 +107,7 @@ export default {
 </script>
 <style lang="scss" scoped>
 @import '@/common/reset.scss';
+@import '@/common/mode-common.scss';
 @import '@/common/show-mode.scss';
+@import '@/common/read-mode.scss';
 </style>

BIN
src/prototype/example.png


+ 284 - 353
src/prototype/typography.vue

@@ -1,377 +1,308 @@
 <template>
-  <section class="slides" contenteditable>
+  <section :class="showMode ? 'slide show-mode' : 'slide read-mode'">
     <section class="page">
-      <h1>界面布局排版原型</h1>
-      <div>本组件用来演示幻灯片放映界面的布局排版</div>
+      <div>
+        <h1>编程基础 I</h1>
+        <div>刘钦</div>
+      </div>
     </section>
     <section class="page">
-      <h3>无序列表</h3>
-      <ul>
-        <li>豆腐花:费时间快点发哈数据库恢复的空间</li>
-        <li>{{ chineseLorem }}</li>
-        <li>{{ englishLorem }}</li>
-        <li>{{ chineseLongLorem }}</li>
-      </ul>
+      <div>
+        <h3>Outline</h3>
+        <ul>
+          <li>代码是用来读的(实践经验)</li>
+          <li>降低复杂度 — 分解与抽象(方法学)</li>
+          <li>编程=数据结构+算法(理论逻辑)</li>
+          <li>算法建模(理论逻辑)</li>
+          <li>数据建模(理论逻辑)</li>
+          <li>编程范式 — 声明式与命令式(表现形式)</li>
+          <li>编程典型场景 — 数据处理(理论逻辑)</li>
+        </ul>
+      </div>
     </section>
     <section class="page">
-      <h3>有序列表</h3>
-      <ol>
-        <li>豆腐花:费时间快点发哈数据库恢复的空间</li>
-        <li>{{ chineseLorem }}</li>
-        <li>{{ englishLorem }}</li>
-        <li>{{ chineseLongLorem }}</li>
-      </ol>
+      <div>
+        <h2>1. 代码是用来读的!</h2>
+      </div>
     </section>
     <section class="page">
-      <h3>图片</h3>
-      <img src="./example.png" alt="" />
+      <div>
+        <h3>Write once, Read many times</h3>
+      </div>
+    </section>
+    <section class="page">
+      <div>
+        <blockquote>
+          When you program, you have to think about how someone will read your
+          code, not just how a computer will interpret it.
+        </blockquote>
+        <div>—— Kent Beck</div>
+      </div>
+    </section>
+    <section class="page">
+      <div>
+        <h3>代码可读</h3>
+        <ul>
+          <li>团队的需要</li>
+          <li>维护的需要</li>
+        </ul>
+      </div>
+    </section>
+    <section class="page">
+      <div>
+        <h3>糟糕的变量名字</h3>
+        <div class="code java">
+          <code>
+            var x = x - xx <br />
+            var xxx = aretha + SalesTax( aretha )<br />
+            var x = x + LateFee( x1, x ) + xxx <br />
+            var x = x + Interest( x1, x )
+          </code>
+        </div>
+      </div>
+    </section>
+    <section class="page">
+      <div>
+        <h3>良好的变量名字</h3>
+        <div class="code java">
+          <code>
+            var balance = balance - lastPayment <br />
+            var monthlyTotal = NewPurchases + SalesTax( newPurchases ) <br />
+            var balance = balance + LateFee( customerID, balance ) +
+            monthlyTotal<br />
+            var balance = balance + Interest( customerID, balance )
+          </code>
+        </div>
+      </div>
+    </section>
+    <section class="page">
+      <div>
+        <h3>代码是用来读的!</h3>
+      </div>
+    </section>
+    <section class="page">
+      <div>
+        <h2>2. 降低复杂度 - 分解与抽象</h2>
+      </div>
+    </section>
+    <section class="page">
+      <div>
+        <h3>为什么要降低复杂度</h3>
+      </div>
+    </section>
+    <section class="page">
+      <div>
+        <h3>世界是复杂的吗</h3>
+      </div>
+    </section>
+    <section class="page">
+      <div>
+        <h3>程序员是干什么的?</h3>
+      </div>
+    </section>
+    <section class="page">
+      <div>
+        <h3>将复杂的问题转化为代码</h3>
+      </div>
+    </section>
+    <section class="page">
+      <div>
+        <h3>有什么降低复杂度的方法?</h3>
+      </div>
+    </section>
+    <section class="page">
+      <div>
+        <h3>代码是用来读的!</h3>
+      </div>
+    </section>
+    <section class="page">
+      <div>
+        <div>降低复杂度的方法 -</div>
+        <h2>分解(组合)</h2>
+      </div>
+    </section>
+    <section class="page">
+      <div>
+        <div>*</div>
+        <div>// 打印一颗 *</div>
+      </div>
+    </section>
+    <section class="page">
+      <div>
+        <div class="code">
+          <code> print("*")</code>
+        </div>
+      </div>
+    </section>
+    <section class="page">
+      <div>
+        <div>#</div>
+        <div>// 打印一颗 #</div>
+      </div>
+    </section>
+    <section class="page">
+      <div>
+        <div class="code">
+          <code>
+            public PageResourceResponse getSlides(@RequestParam(required =
+            false, defaultValue = "") String key, @ApiParam(hidden = true)
+            @PageableDefault(size = Integer.MAX_VALUE, sort = "updateTime",
+            direction = Sort.Direction.DESC) Pageable pageable) {<br />
+            List&lt;SlideVO&gt; slideVOs = slideService.getSlides(key,
+            pageable);<br />
+            return PageResourceResponse.of(slideVOs,
+            PageResourceResponse.PageInfo.of(pageable.getPageNumber(),
+            slideVOs.size())); }
+          </code>
+        </div>
+      </div>
+    </section>
+    <section class="page">
+      <div>
+        <h3>*#</h3>
+        <br />
+        <div>// 打印 *#</div>
+        <div>// 打印一颗 * + 打印一颗 #</div>
+      </div>
+    </section>
+    <section class="page">
+      <div>
+        <div class="code">
+          <code>
+            print("*")<br />
+            print("#")
+          </code>
+        </div>
+      </div>
+    </section>
+    <section class="page">
+      <div>
+        <img src="./分解.png" alt="" />
+        <div>分解(组合)</div>
+      </div>
+    </section>
+    <section class="page">
+      <div>
+        <h2>分解(组合)的关键点</h2>
+        <ul>
+          <li>分解之后,每一部分复杂度要变小</li>
+          <li>相互之间关联要小,相对独立</li>
+        </ul>
+      </div>
+    </section>
+    <section class="page">
+      <div>
+        <img src="./好的分解和坏的分解.png" alt="" />
+        <div>好的分解和坏的分解</div>
+      </div>
     </section>
   </section>
 </template>
 
 <script>
+import Hljs from '@/utils/highlight'
+import 'highlight.js/styles/idea.css'
+
 export default {
-  data: function() {
+  data() {
     return {
-      chineseLorem: '豆腐花:费时间快点发哈数据库恢复的空间',
-      chineseLongLorem:
-        '地方会计史蒂夫好困睡觉啊回复科技示范户史蒂夫史蒂夫,史蒂夫说史蒂夫史蒂夫收费的爱上,是否是否打算史蒂夫史蒂夫史蒂夫说当时的发史蒂夫撒东方是否大。是的发送发送到发史蒂夫撒的发送到发撒东方史蒂夫撒东方四大发史蒂夫,史蒂夫撒东方撒大发史蒂夫。',
-      englishLorem:
-        'Lorem ipsum dolor sit amet consectetur adipisicing elit. Natus mollitia itaque vel consectetur beatae vero porro rerum ea blanditiis deserunt nisi voluptatibus amet quasi id praesentium, dolores enim! Mollitia, alias.'
+      showMode: true,
+      milestones: [],
+      animating: false
     }
-  }
-}
-</script>
-
-<style>
-</style>
-
-<style lang="scss" scoped>
-.page {
-  display: flex;
-  flex-direction: column;
-
-  align-items: center;
-  justify-content: center;
-  min-height: 100vh;
-
-  box-sizing: border-box;
-  border-bottom: 1px solid gray; //dev
-  font-weight: 500;
-
-  & h1,
-  h2,
-  h3,
-  h4,
-  h5,
-  h6,
-  div,
-  p,
-  li {
-    width: 100%;
-  }
-}
-
-.slides {
-  box-sizing: border-box;
-  padding: 0 12vw;
-  text-align: justify;
-  margin: 0 auto;
-  outline: none;
-
-  line-height: 1.8;
-  word-break: break-word;
-  word-wrap: break-word;
-
-  font-size: 26px;
-
-  h1,
-  h2,
-  h3,
-  h4,
-  h5,
-  h6 {
-    font-weight: 600;
-    text-align: left;
-  }
-
-  h1 {
-    font-size: 1.88em;
-    margin: 1.25em 0 1.25em 0;
-  }
-
-  h2 {
-    font-size: 1.56em;
-    margin: 2.5em 0 1.56em 0;
-  }
-
-  h3 {
-    font-size: 1.38em;
-    margin: 2.19em 0 1.25em 0;
-  }
-
-  h4 {
-    font-size: 1.25em;
-    margin: 1.56em 0 0.94em 0;
-  }
-
-  h5 {
-    font-size: 1em;
-    margin: 1.25em 0 0.63em 0;
-  }
-
-  h6 {
-    font-size: 1em;
-    margin: 1.25em 0 0.63em 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;
+  },
+  mounted() {
+    this.highlightCode()
+    const handleResize = () => {
+      if (window.innerWidth < 700) {
+        this.showMode = false
+      } else {
+        this.showMode = true
+      }
+
+      const pages = document.getElementsByClassName('page')
+      pages.forEach(page => {
+        this.milestones.push(page.offsetTop)
+      })
     }
-  }
-
-  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%;
+    handleResize()
+    window.addEventListener('resize', handleResize)
+
+    const handleSpace = e => {
+      if (e.key === ' ') {
+        e.preventDefault()
+        this.nextPage()
+      } else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
+        e.preventDefault()
+        this.lastPage()
+      } else if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
+        e.preventDefault()
+        this.nextPage()
+      }
     }
-  }
-
-  a {
-    text-decoration: underline solid transparent;
-    transition: 0.2s all ease-out;
-
-    &:hover {
-      text-decoration: underline solid #ccc;
+    window.addEventListener('keydown', handleSpace)
+  },
+  updated() {
+    this.highlightCode()
+  },
+  methods: {
+    highlightCode() {
+      const preEl = document.querySelectorAll('.code')
+      preEl.forEach(el => {
+        Hljs.highlightBlock(el)
+      })
+    },
+    nextPage() {
+      const top = this.getCurrentScrollTop()
+      this.milestones.some(milestone => {
+        if (top < milestone - 10) {
+          this.scrollToY(top, milestone, 3, position =>
+            window.scrollTo(0, position)
+          )
+          return true
+        }
+      })
+    },
+    lastPage() {
+      const currentPosition = this.getCurrentScrollTop()
+      let target = currentPosition
+      this.milestones.some(milestone => {
+        if (currentPosition > milestone + 10) {
+          target = milestone
+        } else {
+          return true
+        }
+      })
+      this.scrollToY(currentPosition, target, 3, position =>
+        window.scrollTo(0, position)
+      )
+    },
+    getCurrentScrollTop() {
+      return document.documentElement.scrollTop || document.body.scrollTop
+    },
+    scrollToY(start, end, rate, callback) {
+      this.animating = true
+
+      let step = () => {
+        if (Math.abs(end - start) < 0.5) {
+          callback(end)
+          this.animating = false
+          return
+        }
+        start = start + (end - start) / rate
+        callback(start)
+        requestAnimationFrame(step)
+      }
+
+      step()
     }
   }
-
-  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>
+</script>
 
 <style lang="scss" scoped>
-// 重置所有默认样式
-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;
-}
+@import '@/common/reset.scss';
+@import '@/common/mode-common.scss';
+@import '@/common/read-mode.scss';
+@import '@/common/show-mode.scss';
 </style>

BIN
src/prototype/分解.png


BIN
src/prototype/好的分解和坏的分解.png


+ 1 - 1
src/router/index.js

@@ -15,7 +15,7 @@ const routes = [{
 }, {
   path: '/typography',
   name: 'typography',
-  component: () => import('../prototype/typography.vue')
+  component: () => import('../prototype/Typography.vue')
 }, {
   path: '/teacher',
   name: 'teacher',

+ 6 - 2
src/server/slide.js

@@ -4,7 +4,11 @@ import {
 import axios from 'axios'
 
 export function getSlide(id) {
-  return axios.get(useSlideServer(id))
+  return axios.get(useSlideServer(id)).then(res => {
+    if (res.data.code === 200) {
+      return res.data.data
+    }
+  })
 }
 
 export function getRecentSlides() {
@@ -38,7 +42,7 @@ export function createSlide(courseId, name) {
 export function updateSlide(slideInfo, items) {
   const data = {
     ...slideInfo,
-    items: items.map()
+    items: items
   }
   return axios.post(useSlideServer('update'), data).then(res => {
     if (res.data.code === 200) {

+ 17 - 36
src/store/modules/editor.js

@@ -8,38 +8,29 @@ import {
   SET_FOCUS_BEFORE,
   SET_FOCUS_AFTER,
   USE_FOCUS,
-  GET_SLIDE,
-  CREATE_SLIDE,
-  SAVE
+  DIFF
 } from '../types/editor-types'
 
 import uuid from 'uuid'
-import {
-  getSlide,
-  createSlide,
-  updateSlide
-} from '@/server/slide'
 
 export default {
   state: {
-    slideInfo: {},
+    slideInfo: null,
     items: [],
     focus: null
   },
 
   mutations: {
     [INIT_BY_STATE](state, payload) {
-      console.log(payload)
-      state.items = payload.items
+      state.items = [...payload.items]
       state.slideInfo = {
-        ...payload,
-        items: undefined
+        ...payload
       }
     },
     [INIT_BY_DEFAULT](state, payload) {
       const newItemId = uuid.v4()
       state.items = [{
-        id: newItemId,
+        frontendId: newItemId,
         placeholder: '标题',
         content: '# '
       }]
@@ -52,11 +43,11 @@ export default {
       }
     },
     [ADD_ITEM_AFTER](state, payload) {
-      const index = state.items.indexOf(state.items.filter(item => item.id === payload.id)[0])
+      const index = state.items.indexOf(state.items.filter(item => item.frontendId === payload.frontendId)[0])
 
       state.items = [
         ...state.items.slice(0, index + 1), {
-          id: uuid.v4(),
+          frontendId: uuid.v4(),
           content: '',
           placeholder: '正文'
         },
@@ -65,7 +56,7 @@ export default {
     },
     [MODIFY_ITEM](state, payload) {
       state.items = [...state.items.map(item => {
-        if (item.id === payload.id) {
+        if (item.frontendId === payload.frontendId) {
           return {
             ...item,
             ...payload.diff
@@ -76,7 +67,7 @@ export default {
       })]
     },
     [DELETE_ITEM](state, payload) {
-      state.items = [...state.items.filter((item, index) => index === 0 || item.id !== payload.id)]
+      state.items = [...state.items.filter((item, index) => index === 0 || item.frontendId !== payload.frontendId)]
     },
     [SET_FOCUS](state, payload) {
       state.focus = {
@@ -89,10 +80,10 @@ export default {
       let tagetedFlag = false
       const isHit = state.items.some(item => {
         if (tagetedFlag) {
-          focusId = item.id
+          focusId = item.frontendId
           return true
         }
-        if (item.id === payload.focusId) {
+        if (item.frontendId === payload.focusId) {
           tagetedFlag = true
         }
         return false
@@ -112,10 +103,10 @@ export default {
       let tagetedFlag = false
       const isHit = [...state.items].reverse().some(item => {
         if (tagetedFlag) {
-          focusId = item.id
+          focusId = item.frontendId
           return true
         }
-        if (item.id === payload.focusId) {
+        if (item.frontendId === payload.focusId) {
           tagetedFlag = true
         }
         return false
@@ -132,21 +123,11 @@ export default {
     },
     [USE_FOCUS](state, payload) {
       state.focus = null
-    }
-  },
-  actions: {
-    [SAVE](state, payload) {
-      updateSlide.then(state.items.map(item => {}))
-    },
-    [CREATE_SLIDE](state, payload) {
-      createSlide({
-        courseId: payload.courseId,
-        name: payload.name
-      }).then(res => {})
     },
-
-    [GET_SLIDE](state, payload) {
-      getSlide(payload.id).then(res => {})
+    [DIFF](state, payload) {
+      // console.log(
+      //   state.items.filter(item => item.id)
+      // )
     }
   }
 }

+ 1 - 5
src/store/types/editor-types.js

@@ -8,8 +8,4 @@ export const SET_FOCUS_BEFORE = 'setFocusBefore'
 export const SET_FOCUS_AFTER = 'setFocusAfter'
 export const USE_FOCUS = 'useFocus'
 
-export const GET_SLIDE = 'getSlide'
-export const CREATE_SLIDE = 'createSlide'
-export const UPDATE_SLIDE = 'updateSlide'
-
-export const SAVE = 'save'
+export const DIFF = 'diff'

+ 20 - 0
src/utils/highlight.js

@@ -0,0 +1,20 @@
+// reference: https://99mycql.github.io/application/%E9%80%9A%E8%BF%87highlight%E5%9C%A8vue%E4%B8%AD%E5%AE%9E%E7%8E%B0%E4%BB%A3%E7%A0%81%E9%AB%98%E4%BA%AE.html
+
+import hljs from 'highlight.js/lib/highlight'
+hljs.registerLanguage('bash', require('highlight.js/lib/languages/bash'))
+hljs.registerLanguage('cpp', require('highlight.js/lib/languages/cpp'))
+hljs.registerLanguage('xml', require('highlight.js/lib/languages/xml'))
+hljs.registerLanguage('css', require('highlight.js/lib/languages/css'))
+hljs.registerLanguage('markdown', require('highlight.js/lib/languages/markdown'))
+hljs.registerLanguage('dockerfile', require('highlight.js/lib/languages/dockerfile'))
+hljs.registerLanguage('java', require('highlight.js/lib/languages/java'))
+hljs.registerLanguage('javascript', require('highlight.js/lib/languages/javascript'))
+hljs.registerLanguage('go', require('highlight.js/lib/languages/go'))
+hljs.registerLanguage('python', require('highlight.js/lib/languages/python'))
+hljs.registerLanguage('typescript', require('highlight.js/lib/languages/typescript'))
+
+hljs.configure({
+  useBR: true
+})
+
+export default hljs

+ 29 - 2
src/views/editor/EditorView.vue

@@ -1,14 +1,41 @@
 <template>
-  <editor-vue></editor-vue>
+  <section>
+    <editor-vue></editor-vue>
+    <button class="save-button" @click="save">save</button>
+  </section>
 </template>
 
 <script>
 import EditorVue from '../../components/editor/Editor.vue'
+import { updateSlide } from '../../server/slide'
+import { DIFF } from '../../store/types/editor-types'
+import { mapState } from 'vuex'
 
 export default {
-  components: { EditorVue }
+  components: { EditorVue },
+  methods: {
+    save() {
+      console.log(this.$store.state.editor.slideInfo)
+      updateSlide(
+        this.$store.state.editor.slideInfo,
+        this.$store.state.editor.items
+      ).then(res => {
+        this.$store.commit(DIFF, res)
+      })
+    }
+  },
+  computed: {
+    ...mapState({
+      slideInfo: state => state.editor.slideInfo
+    })
+  }
 }
 </script>
 
 <style lang="scss" scoped>
+.save-button {
+  position: fixed;
+  top: 10px;
+  left: 10px;
+}
 </style>

+ 3 - 3
yarn.lock

@@ -5138,10 +5138,10 @@ hex-color-regex@^1.1.0:
   resolved "https://registry.npm.taobao.org/hex-color-regex/download/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
   integrity sha1-TAb8y0YC/iYCs8k9+C1+fb8aio4=
 
-highlight.js@^9.6.0:
+highlight.js@^9.17.1, highlight.js@^9.6.0:
   version "9.17.1"
-  resolved "https://registry.npm.taobao.org/highlight.js/download/highlight.js-9.17.1.tgz#14a4eded23fd314b05886758bb906e39dd627f9a"
-  integrity sha1-FKTt7SP9MUsFiGdYu5BuOd1if5o=
+  resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.17.1.tgz#14a4eded23fd314b05886758bb906e39dd627f9a"
+  integrity sha512-TA2/doAur5Ol8+iM3Ov7qy3jYcr/QiJ2eDTdRF4dfbjG7AaaB99J5G+zSl11ljbl6cIcahgPY6SKb3sC3EJ0fw==
   dependencies:
     handlebars "^4.5.3"