generated at
Scrapboxの開発 - React & Websocketで作るリアルタイムWiki
今日の発表資料です shokai
発表中に同時編集可能です
まじかよymrl
sugoi
つらい
がんばれがんばれgeta6
すげぇ
楽しいkrtx
編集間違えたら ctrl-z or command-z で戻せます
わかるshokaishokaishokai
redo 機能はやくくれ

がんばれ!!!!!uzulla
え、reacttikit
すごかったminato128


!!ページ右側の「Start presentation」でスライドになります!!

Cosenseの開発 - React & Websocketで作るリアルタイムWiki
@shokai shokai
株式会社Helpfeel, Inc.



Scrapboxの開発
kininaru
2. 実装のテクニカルなこと
について話します

開発プロセス
shokai 完全リモートワーク、目覚ましをかけずに好きなだけ寝る派
横浜にいる
適当に寝て適当に起きる
京都にある
が降ったら家から出ない人もいる

自分で色々な用途に使ってみる
用途が違うと、ほしい機能も違う

techblog 兼 料理レシピ (/shokaiで公開)
大学の研究室
家族で使う
同人サークル
ゲームの編成・装備・経過のログ取り

色々な機能が欲しくなってくる
でも、なんでも入れるとボタンだらけになってよくない(難しい・・)

techblog 兼 料理レシピ
として使う場合
公開機能が欲しくなる
公開・非公開プロジェクトを設定できるようにしよう

会社や大学の研究室
スライドを作りたい
研究ノートを書いてるのに、発表のためにスライドを作り直すのは無駄
スライド作るのが面倒くさい、究極的には発表中に誰かにリアルタイム作成してもらいたい

ページ名が説明的に長くなりがち
長くなってもよい
はてブに上がってくるようなみたいなエモーショナルなタイトルをどんどんつけるべき
リンクは、入力補完でなんとかする

色々な人がいる
キーバインドを選べる
OS毎のショートカットキーの再現

Scrapboxの開発にScrapboxを使う
流れ
1. 好き放題、意見・要望・苦情を書いてもらう
2. タグで管理、shokaiのToDoやsprintに持ってくる
3. Sprint
アレが抜けてない?とか指摘される
4. 実装前に軽く設計をScrapboxに書く
5. Gyazo GIF撮ってpullreq作る

長いリストや、目次ページは作らない

個別のタスク毎に1つずつページを作る
#Gyazzへの要望 タグを付ける
自分がやるぞ!と思った物に #shokaiのToDo タグを付ける
関連ページリストで一覧できる
やりたい事は100以上ある
個別ページ内で、同時並行して議論している
「やりたい事」の状態では優先度は付けられないし、無理につけない


家の引っ越しとかもこんな感じでやれる

巨大リストを作らない
巨大なToDoリストを見ると気が滅入る
山が高すぎると登る気にならない

ページのネットワークを作る
編集していると、実はこれ同じ機能を要求しているんじゃね?というissue群に気づいたりする

Sprint
2週間分の「やるべき」「やりたい」を、 #shokaiのToDo から取り出してくる

やりたい事は全部scrapbox書いてある
ぶつかり稽古ガチ議論をする
リモートワークでも「どれからやっても良いよ」という雰囲気を作れる
たまに緊急で上から降ってくるタスクがあっても、妥当性を感じる
やらされてる感が無くなる

突然のPull Request
名案が思いついたら突然実装される


Scrapboxの実装
テクニカルな話

コア
WYSIWYGで画像やリンクも貼れるけど、編集ツールバーやボタンが無いエディタ
markdownより簡単なシンタックスで編集・文字装飾できる
Atomを参考にしてReactで実装した
contenteditableは使ってない
Socket.IOと自作の同期システムでうまいこと同時編集できる
Gitを参考にsubversionみたいなものを実装した

実装環境
EventEmitter風Store
全面的にasync-await

エディタの実装
カーソルの縦棒はdiv
緑の範囲選択もdiv3つ
隠し <textarea />
IMEウィンドウを見せるためだけに存在する
カーソルの右側に浮いていて、ついてくる
Atomが参考になった

y行目のx文字目の画面上の座標はどこか?
生テキスト こんにちは
1文字ずつ分割して
editor.html
<div class='lines'> <div class='line' id='L1'> <span class='c-1'>こ</span> <span class='c-2'>ん</span> <span class='c-3'>に</span> <span class='c-4'>ち</span> <span class='c-5'>は</span> </div> </div>
jQueryで位置を取得する
let {left, top} = $('.lines #L1 .c-4').position()

クリックした位置から何行目の何文字目なのか?を求める
逆をやればいい

あとはカーソル移動やemacsキーバインド等を自前で実装すればok

ReactjQueryは相性悪い?
と、よくインターネットに書いてある
jQueryはクロスブラウザで座標の扱いが完璧っぽい!

jQueryでDOM書き換えしなければok
render→ componentDidUpdate からjQueryで座標計算→stateに入れる→再render

複数人で同時編集

実用Gitを読む
こういう「コミット」をやりとりすればなんとかなるのでは!?
commit.json
{ "id": hash, "parent_id": parent_hash, // 1つ前への参照 "changes": [ 変更内容, 変更内容, 変更内容 ] }


編集の命令は3種類だけ
1つの commit chanegs 配列に複数の編集が格納される

insert
隣の行のidを指定して新しい行を追加
javascript
{ "_insert": positionId, // 文字を挿入する一つ下の位置にある行のId "lines": { "text": text, "id": lineId // 新しく生成された行のid } }
新規に行を挿入する
最後に挿入するときは、特殊IDの _end を指定する。
複数行を挿入するときは、insertを複数個作る


update
idを指定して更新
js
{ "_update": lineId, // 変更する行のId "lines": { "text": text } }
行を変更する

delete
idを指定して削除
js
{ "_delete": lineId, // 削除する行のId lines: -1 }
行を削除する

コンフリクトしたら?
ユーザー側でmerge画面にするわけにはいかない
コンフリクト判定はサーバー側
clientはとりあえずpushする
pushがrejectされたらpull
git rebase のような操作をする

気合マージ!!
mergeしよう → コンフリクト → pullしてみる
→ insertが参照していた行が削除されてる、どこにinsertしていいのかわからない!
→ commitをさかのぼって、「削除された行を参照したinsertをした行」を参照してinsertする