ProseMirror - 模塊化的富文本編輯框架

關(guān)于富文本編輯器,很多同學(xué)沒(méi)用過(guò)也聽(tīng)過(guò)了智嚷。是大家都不想去踩的坑卖丸。到底由多坑呢?
我這里摘了一部分一位大哥在知乎上的回答盏道,如果有興趣稍浆,可以去看看。
要讓一款編輯器達(dá)到商業(yè)級(jí)質(zhì)量摇天,從目前接觸到主要的例子來(lái)看粹湃,獨(dú)立開(kāi)發(fā)時(shí)間太長(zhǎng):

  • Quill編輯器Quill 從 2012 年收到第一個(gè) Issue 到 2016 年發(fā)布 1.0 版本泉坐,已經(jīng)過(guò)去了四年。
  • Prosemirror編輯器裳仆,Prosemirror 作者在 2015 年正式開(kāi)源前籌款維護(hù)時(shí)已經(jīng)開(kāi)發(fā)了半年腕让,而到發(fā)布 1.0 版本時(shí),已經(jīng)過(guò)去了接近三年歧斟。
  • Slate 從開(kāi)源到接近兩年時(shí)纯丸,仍然有一堆邊邊角角用起來(lái)莫名其妙的 bug 。

上面這幾個(gè)單人主導(dǎo)的編輯器項(xiàng)目要達(dá)到穩(wěn)定質(zhì)量静袖,時(shí)間是以年為單位來(lái)計(jì)算的觉鼻。考慮到目前互聯(lián)網(wǎng)“下周上線”的節(jié)奏队橙,動(dòng)輒幾年的時(shí)間是不劃算的坠陈。所以在人力,時(shí)間合理性各方面的約束下捐康,使用開(kāi)源框架是最好的選擇仇矾。
想要一款配置性強(qiáng),模塊化的編輯器解总,這就決定了這不是一個(gè)開(kāi)箱即用的應(yīng)用贮匕,而Quill集成了許多樣式和交互邏輯,已經(jīng)算是一個(gè)應(yīng)用了花枫,有時(shí)一些制定需求不能完全滿足刻盐。Slate是基于的React視圖層的掏膏,我們的技術(shù)棧是Vue,就不做考慮了敦锌,以后有機(jī)會(huì)可以研究一下壤追,所以最后選擇了prosemirror,但另外兩款依然是很強(qiáng)大值得去學(xué)習(xí)的編輯器框架供屉。
由于prosemirror目前使用搜索引擎能搜出來(lái)的中文資料幾乎沒(méi)有行冰,遇到問(wèn)題也只能去論壇或者issue里面搜,或者向作者提問(wèn)伶丐。以下的內(nèi)容是從官網(wǎng)悼做,加上自己在使用過(guò)程中對(duì)它的理解簡(jiǎn)化出來(lái)的。希望看完后哗魂,能讓你對(duì)prosemirror產(chǎn)生興趣肛走,并從作者的設(shè)計(jì)思路中,學(xué)到東西录别,一起分享朽色。

ProseMirror簡(jiǎn)介


A toolkit for building rich-text editors on the web

prosemirror 的作者 Marijncodemirror 編輯器和 acorn 解釋器的作者,前者已經(jīng)在 ChromeFirefox 自帶的調(diào)試工具里使用了组题,后者則是 babel 的依賴葫男。
prosemirror不是一個(gè)大而全的框架, 它是由無(wú)數(shù)個(gè)小的模塊組成,它就像樂(lè)高一樣是一個(gè)堆疊出來(lái)的編輯器崔列。
它的核心庫(kù)有:

  • prosemirror-model: 定義編輯器的文檔模型梢褐,用來(lái)描述編輯器內(nèi)容的數(shù)據(jù)結(jié)構(gòu)
  • prosemirror-state: 提供描述編輯器整個(gè)狀態(tài)的數(shù)據(jù)結(jié)構(gòu),包括selection(選擇)赵讯,以及從一個(gè)狀態(tài)到下一個(gè)狀態(tài)的transaction(事務(wù))
  • prosemirror-view: 實(shí)現(xiàn)一個(gè)在瀏覽器中將給定編輯器狀態(tài)顯示為可編輯元素盈咳,并且處理用戶交互的用戶界面組件
  • prosemirror-transform: 包括以記錄和重放的方式修改文檔的功能,這是state模塊中transaction(事務(wù))的基礎(chǔ)边翼,并且它使得撤銷和協(xié)作編輯成為可能鱼响。

此外,prosemirror還提供了許多的模塊组底,如prosemirror-commands基本編輯命令丈积,prosemirror-keymap鍵綁定,prosemirror-history歷史記錄斤寇,prosemirror-inputrules輸入宏桶癣,prosemirror-collab協(xié)作編輯,prosemirror-schema-basic簡(jiǎn)單文檔模式等娘锁。

現(xiàn)在你應(yīng)該大概了解了它們各自的作用牙寞,它們是整個(gè)編輯器的基礎(chǔ)。

實(shí)現(xiàn)一個(gè)編輯器demo


import {schema} from "prosemirror-schema-basic"
import {EditorState} from "prosemirror-state"
import {EditorView} from "prosemirror-view"

let state = EditorState.create({ schema })
let view = new EditorView(document.body, { state })

我們來(lái)看看上面的代碼干了什么事,從第一行開(kāi)始间雀。prosemirror要求指定一個(gè)文檔符合的模式悔详。所以從prosemirror-schema-basic引入了一個(gè)基本的schema。那么這個(gè)schema是什么呢惹挟?
因?yàn)?code>prosemirror定義了自己的數(shù)據(jù)結(jié)構(gòu)來(lái)表示文檔內(nèi)容茄螃。在prosemirror結(jié)構(gòu)HTML的Dom結(jié)構(gòu)之間,需要一次解析與轉(zhuǎn)化连锯,這兩者間相互轉(zhuǎn)化的橋梁归苍,就是我們的schema,所以要先了解一下prosemirror的文檔結(jié)構(gòu)运怖。

prosemirror文檔結(jié)構(gòu)


prosemirror的文檔是一個(gè)Node,它包含零個(gè)或多個(gè)child NodesFragment(片段)拼弃。
有點(diǎn)類似瀏覽器DOM的遞歸和樹(shù)形的結(jié)構(gòu)。但它在存儲(chǔ)內(nèi)聯(lián)內(nèi)容方式上有所不一樣摇展。

<p>This is <strong>strong text with <em>emphasis</em></strong></p>

HTML中吻氧,是這樣的樹(shù)結(jié)構(gòu):

p //"this is "
 strong //"strong text with "
     em //"emphasis"

prosemirror中,內(nèi)聯(lián)內(nèi)容被建模為平面的序列咏连,strong盯孙、em(Mark)作為paragraph(Node)的附加數(shù)據(jù):

"paragraph(Node)"
// "this is "    | "strong text with" | "emphasis"
                   "strong(Mark)"       "strong(Mark)", "em(Mark)"

prosemirror的文檔的對(duì)象結(jié)構(gòu)如下

Node:
    type: NodeType //包含了Node的名字與屬性等
    content: Fragment //包含多個(gè)Node
    attrs: Object //自定義屬性,image可以用來(lái)存儲(chǔ)src等祟滴。
    marks: [Mark, Mark...] // 包含一組Mark實(shí)例的數(shù)組振惰,例如em和strong
Mark:
    type: MarkType //包含Mark的名字與屬性等
    attrs: Object //自定義屬性

prosemirror提供了兩種類型的索引

  • 樹(shù)類型,這個(gè)和dom結(jié)構(gòu)相似踱启,你可以利用child或者childCount等方法直接訪問(wèn)到子節(jié)點(diǎn)
  • 平坦的標(biāo)記序列报账,它將標(biāo)記序列中的索引作為文檔的位置,它們是一種計(jì)數(shù)約定
    • 在整個(gè)文檔開(kāi)頭埠偿,索引位置為0
    • 進(jìn)入或離開(kāi)一個(gè)不是葉節(jié)點(diǎn)的節(jié)點(diǎn)記為一個(gè)標(biāo)記
    • 文本節(jié)點(diǎn)中的每個(gè)節(jié)點(diǎn)都算一個(gè)標(biāo)記
    • 沒(méi)有內(nèi)容的葉節(jié)點(diǎn)(例如image)也算一個(gè)標(biāo)記

例如有一個(gè)HTML片段為

<p>One</p>
<blockquote><p>Two<img src="..."></p></blockquote>

則計(jì)數(shù)標(biāo)記為

0   1 2 3 4    5
 <p> O n e </p>

5            6   7 8 9 10    11   12            13
 <blockquote> <p> T w o <img> </p> </blockquote>

每個(gè)節(jié)點(diǎn)都有一個(gè)nodeSize屬性表示整個(gè)節(jié)點(diǎn)的大小。手動(dòng)解析這些位置涉及到相當(dāng)多的計(jì)數(shù)榜晦,prosemirror為我們提供了Node.resolve方法來(lái)解析這些位置冠蒋,并且能夠獲取關(guān)于這個(gè)位置更多的信息,例如父節(jié)點(diǎn)是什么乾胶,與父節(jié)點(diǎn)的偏移量抖剿,父節(jié)點(diǎn)的祖先是什么等一些其它信息。

了解了prosemirror的數(shù)據(jù)結(jié)構(gòu)识窿,回到剛才的地方斩郎,我們從prosemirror-schema-basic中引入了一個(gè)基本的schema,這個(gè)schema是什么呢喻频?通過(guò)查看源碼最后一行

export const schema = new Schema({nodes, marks})

schemaSchema通過(guò)傳入的nodes, marks生成的實(shí)例缩宜。
而在實(shí)例之前的代碼,都是在定義nodesmarks,將代碼折疊一下锻煌,發(fā)現(xiàn)nodes

{
    doc: {...} // 頂級(jí)文檔
    blockquote: {...} //<blockquote>
    code_block: {...} //<pre>
    hard_break: {...} //<br>
    heading: {...} //<h1>..<h6>
    horizontal_rule: {...} //<hr>
    image: {...} //<img>
    paragraph: {...} //<p>
    text: {...} //文本
}

marks

{
    em: {...} //<em>
    link: {...} //<a>
    strong: {...} //<strong>
    code: {...} //<code>
}

它們表示編輯器中可能會(huì)出現(xiàn)的節(jié)點(diǎn)類型以及它們嵌套的方式妓布。它們每個(gè)都包含著一套規(guī)則,用來(lái)描述prosemirror文檔Dom文檔之間的關(guān)聯(lián)宋梧,如何把Dom轉(zhuǎn)化為Node或者Node轉(zhuǎn)化為Dom匣沼。文檔中的每個(gè)節(jié)點(diǎn)都有一個(gè)對(duì)應(yīng)的類型。
從最上面開(kāi)始doc開(kāi)始看:

doc: {
  content: "block+"
}

每個(gè)schema必須定義一個(gè)頂層節(jié)點(diǎn)捂龄,即doc释涛。content控制子節(jié)點(diǎn)的哪些序列對(duì)此節(jié)點(diǎn)類型有效。
例如"paragraph"表示一個(gè)段落倦沧,"paragraph+"表示一個(gè)或多個(gè)段落唇撬,"paragraph*"表示零個(gè)或多個(gè)段落,你可以在名稱后使用類似正則表達(dá)式的范圍刀脏。同時(shí)你也可以用組合表達(dá)式例如"heading paragraph+"局荚,"{paragraph | blockquote}+"。這里"block+"表示"(paragraph | blockquote)+"愈污。
接著看看em:

em: {
  parseDOM: [
    { tag: "i" },
    { tag: "em" },
    { style: "font-style=italic" }
  ],
  toDOM: function() {
    return ["em"]
  }
}

parseDOMtoDOM表示文檔間的相互轉(zhuǎn)化耀态,上面的代碼有三條解析規(guī)則:

  • <i>標(biāo)簽
  • <em>標(biāo)簽
  • font-style=italic的樣式

當(dāng)匹配到一條規(guī)則時(shí),就呈現(xiàn)為HTML<em>結(jié)構(gòu)暂雹。
同理首装,我們可以實(shí)現(xiàn)一個(gè)下劃線的mark

underline: {
  parseDOM: [
    { tag: 'u' },
    { style: 'text-decoration:underline' }
  ],
  toDOM: function() {
    return ['span', { style: 'text-decoration:underline' }]
  }
}

NodeMark都可以使用attrs來(lái)存儲(chǔ)自定義屬性,比如image杭跪,可以在attrs中存儲(chǔ)src仙逻,alttitle涧尿。

回到剛才

import {schema} from "prosemirror-schema-basic"
import {EditorState} from "prosemirror-state"
import {EditorView} from "prosemirror-view"

let state = EditorState.create({schema})
let view = new EditorView(document.body, {state})

我們使用EditorState.create通過(guò)基礎(chǔ)規(guī)則schema創(chuàng)建了編輯器的狀態(tài)state系奉。接著,為狀態(tài)state創(chuàng)建了編輯器的視圖姑廉,并附加到了document.body缺亮。這會(huì)將我們的狀態(tài)state呈現(xiàn)為可編輯的dom節(jié)點(diǎn),并在用戶鍵入時(shí)產(chǎn)生transaction桥言。

Transaction


當(dāng)用戶鍵入或者其他方式與視圖交互時(shí)萌踱,都會(huì)產(chǎn)生transaction。描述對(duì)state所做的更改号阿,并且可以用來(lái)創(chuàng)建新的state并鸵,然后更新視圖。
下圖是prosemirror簡(jiǎn)單的循環(huán)數(shù)據(jù)流data flow:編輯器視圖顯示給定的state扔涧,當(dāng)發(fā)生某些event時(shí)园担,它會(huì)創(chuàng)建一個(gè)transactionbroadcast它。然后,此transaction通常用于創(chuàng)建新state粉铐,該state使用其updateState方法提供給視圖 疼约。

           DOM event
        ↗            ↘
EditorView           Transaction
        ↖            ↙
        new EditorState

默認(rèn)情況下,state的更新都發(fā)生在底層蝙泼,但是程剥,你可以編寫插件plugin或者配置視圖來(lái)實(shí)現(xiàn)。例如我們修改下上面創(chuàng)建視圖的代碼:

// (Imports omitted)
let state = EditorState.create({schema})
let view = new EditorView(document.body, {
  state,
  dispatchTransaction(transaction) {
    console.log("create new transaction")
    let newState = view.state.apply(transaction)
    view.updateState(newState)
  }
})

EditorView添加了一個(gè)dispatchTransactionprop汤踏,每次創(chuàng)建了一個(gè)transaction织鲸,就會(huì)調(diào)用該函數(shù)。
這樣寫的話溪胶,每個(gè)state更新都必須手動(dòng)調(diào)用updateState搂擦。

Immutable


prosemirror的數(shù)據(jù)結(jié)構(gòu)是immutable的,不可變的哗脖,你不能直接去賦值它瀑踢,你只能通過(guò)相應(yīng)的API去創(chuàng)建新的引用。但是在不同的引用之間才避,相同的部分是共享的橱夭。這就好比,有一顆基于immutable的嵌套復(fù)雜很深的文檔樹(shù)桑逝,即使你只改變了某個(gè)地方的葉子節(jié)點(diǎn)棘劣,也會(huì)生成一棵新樹(shù),但這棵新樹(shù)楞遏,除了剛才更改的葉子節(jié)點(diǎn)外茬暇,其余部分和原有樹(shù)是共享的。有了immutable寡喝,當(dāng)每次鍵入編輯器都會(huì)產(chǎn)生新的state糙俗,你在每種不同的state之間來(lái)回切換,就能實(shí)現(xiàn)撤銷重做操作预鬓。同時(shí)臼节,更新state重繪文檔也變得更高效了。

State


是什么構(gòu)成了prosemirrorstate呢珊皿?state有三個(gè)主要組成部分:你的文檔doc, 當(dāng)前選擇selection和當(dāng)前存儲(chǔ)的markstoredMarks巨税。除此之外蟋定,還有
初始化state時(shí),你可以通過(guò)doc屬性為其提供要使用的初始文檔草添。這里我們可以使用idcontent下的 dom結(jié)構(gòu)作為編輯器的初始文檔驶兜。Dom解析器Dom結(jié)構(gòu)通過(guò)我們的解析模式schema將其轉(zhuǎn)化為prosemirror結(jié)構(gòu)

import {DOMParser} from "prosemirror-model"
import {EditorState} from "prosemirror-state"
import {schema} from "prosemirror-schema-basic"

let state = EditorState.create({
  doc: DOMParser.fromSchema(schema).parse(document.querySelector("#content"))
})

prosemirror支持多種類型的selection(并允許第三方代碼定義新的選擇類型,注:任何一個(gè)新的類型都需要繼承自Selection)抄淑。selection與文檔和其他與state相關(guān)的值一樣屠凶,也是immutable的 ,更改selection肆资,就要?jiǎng)?chuàng)建新的selection和保持它的新state矗愧。selection至少具有fromto指向當(dāng)前文檔的位置來(lái)表示選擇的范圍。最常見(jiàn)的選擇類型是TextSelection郑原,用于游標(biāo)或選定文本唉韭。prosemirror還支持NodeSelection,例如犯犁,當(dāng)你按ctrl / cmd單擊某個(gè)Node時(shí)属愤。會(huì)選擇范圍從節(jié)點(diǎn)之前的位置到其后的位置。
storedMarks則表示需要應(yīng)用于下一次輸入時(shí)的一組Mark酸役。

Plugins


plugin以各種方式擴(kuò)展編輯器和編輯器狀態(tài)住诸。當(dāng)創(chuàng)建一個(gè)新的state,你可以向其提供一系列的plugin涣澡,這些將會(huì)保存在此state和由此state派生的任何state中贱呐。并且可以影響transaction的應(yīng)用方式以及基于此state的編輯器的行為方式。
創(chuàng)建plugin時(shí)暑塑,會(huì)向其傳遞一個(gè)指定其行為的對(duì)象吼句。

let myPlugin = new Plugin({
  props: {
    handleKeyDown(view, event) {
      //當(dāng)收到keydown事件時(shí)調(diào)用
      console.log("A key was pressed!")
      return false // We did not handle this
    }
  }
})

let state = EditorState.create({schema, plugins: [myPlugin]})

當(dāng)插件需要自己的plugin state時(shí),可以通過(guò)state屬性來(lái)定義事格。

let transactionCounter = new Plugin({
  state: {
    init() { return 0 },
    apply(tr, value) { return value + 1 }
  }
})

function getTransactionCount(state) {
  return transactionCounter.getState(state)
}

上面這個(gè)插件定義了一個(gè)簡(jiǎn)單的狀態(tài)惕艳,它對(duì)已經(jīng)應(yīng)用于statetransaction進(jìn)行計(jì)數(shù)。
下面有個(gè)輔助函數(shù)驹愚,它調(diào)用了plugingetState方法远搪,從完整的編輯器的state中獲取了pluginstate

因?yàn)榫庉嬈鞯?code>state是immutable的逢捺,而且plugin state是該state的一部分谁鳍,所以plugin state也是immutable的,即它們的apply方法必須返回一個(gè)新值劫瞳,而不是修改舊值倘潜。
plugin通常可以給transaction添加一些額外信息metadata志于。例如涮因,在撤銷歷史操作時(shí),會(huì)標(biāo)記生成的transaction伺绽,當(dāng)plugin看到時(shí)养泡,他不會(huì)向普通的transaction一樣處理它嗜湃,它會(huì)特殊處理它:從撤銷堆棧頂部刪除,將該transaction放入重做堆棧澜掩。

回到最初的例子购披,我們可以將command綁定到鍵盤輸入的keymap plugin,同時(shí)還有history plugin肩榕,其通過(guò)觀察transaction來(lái)實(shí)現(xiàn)撤銷和重做刚陡。

// (Omitted repeated imports)
import {undo, redo, history} from "prosemirror-history"
import {keymap} from "prosemirror-keymap"

let state = EditorState.create({
  schema,
  plugins: [
    history(),
    keymap({"Mod-z": undo, "Mod-y": redo})
  ]
})
let view = new EditorView(document.body, {state})

創(chuàng)建state時(shí)會(huì)注冊(cè)plugin,通過(guò)這個(gè)state創(chuàng)建的視圖你將能夠按Ctrl-Z(或OS X上的Cmd-Z)來(lái)撤消上次更改点把。

Commands


上面的undo, redo是一種command橘荠,大多數(shù)的編輯操作都被視為command。它可以綁定到菜單或者鍵上郎逃,或者其他方式暴露給用戶哥童。在prosemirror中,command是實(shí)現(xiàn)編輯操作的功能褒翰,它們大多是采用編輯器statedispatch函數(shù)(EditorView.dispatch或者一些其他采用了transaction的函數(shù))完成的贮懈。下面是一個(gè)簡(jiǎn)單的例子:

function deleteSelection(state, dispatch) {
  if (state.selection.empty) return false
  if (dispatch) dispatch(state.tr.deleteSelection())
  return true
}

當(dāng)command不適用時(shí),應(yīng)該返回false或者什么也不做优训。如果適用朵你,則需要dispatch一個(gè)transaction然后返回true,為了能夠查詢command是否適用于給定state而不實(shí)際執(zhí)行它揣非,dispatch參數(shù)是可選的抡医,當(dāng)沒(méi)有傳入dispatch時(shí),command應(yīng)該只返回true早敬,而不執(zhí)行任何操作忌傻,這個(gè)可以用來(lái)使你的菜單欄變灰來(lái)表示當(dāng)前command不可執(zhí)行。
一些command可能需要與dom交互搞监,你可以為他傳遞第三個(gè)參數(shù)view水孩,即整個(gè)編輯器的視圖。
prosemirror-commands提供了許多的編輯command琐驴,從簡(jiǎn)單到復(fù)雜俘种。還同時(shí)附帶一個(gè)基礎(chǔ)的keymap, 能夠給編輯器使用的鍵綁定來(lái)使編輯器能夠執(zhí)行輸入與刪除等操作绝淡,它將許多與schema無(wú)關(guān)的command綁定到通常用于它們的鍵宙刘。它還導(dǎo)出了許多command的構(gòu)造函數(shù),例如toggleMark,它傳入一個(gè)mark類型和自定義屬性attrs牢酵,返回一個(gè)command函數(shù)荐类,用于切換當(dāng)前selection上的該mark類型。
要自定義編輯器茁帽,或允許用戶與Node進(jìn)行交互玉罐,你可以編寫自己的command
例如一個(gè)簡(jiǎn)單的清除樣式的格式刷command

function clear(state, dispatch) {
  if (state.selection.empty) return false;
  const { $from, $to } = state.selection;
  if (dispatch) dispatch(state.tr.removeMark($from.pos, $to.pos, null));
  return true
}

總結(jié)


上述介紹可以作為對(duì)prosemirror的一個(gè)簡(jiǎn)單的認(rèn)識(shí)潘拨,了解了它的運(yùn)作原理吊输,避免你第一次接觸它的時(shí)候,看到它的這么多庫(kù)铁追,不知道從哪上手季蚂。prosemirror除了上面介紹的概念以外,還有Decorations琅束,NodeViews等扭屁,它們使你可以控制視圖繪制文檔的方式。如果你還想繼續(xù)深入的了解prosemirror涩禀,可以前往它的官網(wǎng)論壇料滥,希望你能成為它的貢獻(xiàn)者。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末艾船,一起剝皮案震驚了整個(gè)濱河市葵腹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌屿岂,老刑警劉巖践宴,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異爷怀,居然都是意外死亡阻肩,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門运授,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)烤惊,“玉大人,你說(shuō)我怎么就攤上這事徒坡∷貉酰” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵喇完,是天一觀的道長(zhǎng)伦泥。 經(jīng)常有香客問(wèn)我,道長(zhǎng)锦溪,這世上最難降的妖魔是什么不脯? 我笑而不...
    開(kāi)封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮刻诊,結(jié)果婚禮上防楷,老公的妹妹穿的比我還像新娘。我一直安慰自己则涯,他們只是感情好复局,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布冲簿。 她就那樣靜靜地躺著,像睡著了一般亿昏。 火紅的嫁衣襯著肌膚如雪峦剔。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天角钩,我揣著相機(jī)與錄音吝沫,去河邊找鬼。 笑死递礼,一個(gè)胖子當(dāng)著我的面吹牛惨险,可吹牛的內(nèi)容都是我干的脊髓。 我是一名探鬼主播供炼,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼一屋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了袋哼?” 一聲冷哼從身側(cè)響起冀墨,我...
    開(kāi)封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎涛贯,沒(méi)想到半個(gè)月后诽嘉,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡弟翘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年虫腋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片稀余。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡悦冀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出睛琳,到底是詐尸還是另有隱情盒蟆,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布师骗,位于F島的核電站历等,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏辟癌。R本人自食惡果不足惜寒屯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望黍少。 院中可真熱鬧寡夹,春花似錦处面、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至患蹂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間砸紊,已是汗流浹背传于。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留醉顽,地道東北人沼溜。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像游添,于是被迫代替她去往敵國(guó)和親系草。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理唆涝,服務(wù)發(fā)現(xiàn)找都,斷路器,智...
    卡卡羅2017閱讀 134,672評(píng)論 18 139
  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 31,938評(píng)論 2 89
  • 我們這代人對(duì)父親都是敬而遠(yuǎn)之 兒子怎么能跟父親摟摟抱抱 那個(gè)年代不太可能 尤其對(duì)中國(guó)人來(lái)講 在表達(dá)上 存在著種種障...
    累趴閱讀 284評(píng)論 0 0
  • 后會(huì)有期 火車出江蘇省了廊酣。 再見(jiàn)常州能耻。再見(jiàn)河海。再見(jiàn)每一個(gè)親愛(ài)的河海人亡驰。 車窗外夜深晓猛,偶爾路過(guò)零星的燈火。 我很平...
    Alice王志榮閱讀 357評(píng)論 0 0
  • 有次我跟富貴兒說(shuō)老娘如何如何凡辱,他問(wèn)我:你說(shuō)你是老娘戒职?你是小汪汪的媽媽呀! 今天換被套透乾,富貴兒鉆里面玩兒半天洪燥,我笑得...
    富貴兒媽媽閱讀 275評(píng)論 0 0