Taskmatorでタスク管理をしていた時は、完了したタスクツリーをメールシェア機能を利用して記録保存していた。しかし、WorkFlowyのiOSアプリにはエクスポートする機能が無い。
HandyFlowyには「Export TEXT」機能や、「Export to Evernote」という機能拡張スクリプトが最初から入っているが、希望する書式では出力してくれない。
Safariには「デスクトップ用サイトを表示」する機能もあるが、
- WorkFlowyをiPhoneのSafariで開く
- 「デスクトップ用サイトを表示」でデスクトップ版に切り替える
- 狭い画面をパンしながらなんとかエクスポートする
- Draftsにペーストして置換などで整える
なんて面倒くさいことを毎日続けることにウンザリ。
面倒くさくいことは続かない。
続けたかったら、面倒くさくなくする仕組みを作らないといけない。
そこで、スクリプトをDIYで作ることにした。
書式は、
- 検索での最上位トピックは###ヘッダ(h3)
- インデント付きリストでトピックツリーを表現し、インデントは半角スペース4文字で字下げ
- ノートはトピックの行末に半角スペース2個と改行で始める
- ノートの改行のある行の行末も半角スペース2文字と改行
- 最終的にはEvernoteに保存するため、URLは自動的にリンクに変換されるので処理しない
- square brackets式に、未完トピックは[ ]、完了トピックは[x]を行頭に付ける
- 行末には「≫」で該当トピックへのリンク
Javascriptを書くなんて何年ぶりだろう?
埃をかぶったJavascriptの本を引っ張りだして、めくるページ毎に再発見に感動し、「値渡し」と「参照渡し」の違いなどでつまずいたりしながらスクリプトと格闘し、なんとか動くようにした。
var nest = 0; var pId = ""; var done = false; var name = ""; var note = ""; var tpcLst = []; function tpcTrSrch(e) { function h() { var l = e.childNodes; for (var j = 0; j < l.length; j++) { tpcTrSrch(l[j]) } } var g = e.nodeType; var f = e.nodeName; if (g == 1) { if (f == "DIV") { var d = e.attributes; var k = ""; for (var b = 0; b < d.length; b++) { switch (d.item(b).nodeName) { case "class": var k = d.item(b).nodeValue; break; case "projectid": pId = d.item(b).nodeValue.replace(/[0-9a-f]+-[0-9a-f]+-[0-9a-f]+-[0-9a-f]+-([0-9a-f]+)/, "$1"); break; default: break } } switch (true) { case (k == ""): h(); break; case (k.indexOf("project") >= 0): done = false; name = ""; note = ""; if (k.indexOf("done") >= 0) { done = true } h(); break; case (k.indexOf("name") >= 0): name = e.innerText.replace(/\n/, ""); break; case (k.indexOf("notes") >= 0): var c = e.innerText; note = (c.length == 1) ? "" : c; var a = tpcLst.length; tpcLst[a] = {}; tpcLst[a].nest = nest; tpcLst[a].pId = pId; tpcLst[a].done = done; tpcLst[a].name = name; tpcLst[a].note = note; break; case (k == "children"): nest++; h(); nest--; break; default: h(); break } } } } var tpcLst2md = function() { var b = ""; while (tpcLst.length > 0) { var d = tpcLst.shift(); if (d.nest > 0) { if (d.nest == 1) { b += "### " } else { for (var a = 0; a <= d.nest - 3; a++) { b += " " } b += (d.done) ? "- [x] " : "- [ ] " } b += d.name + " [≫](https://workflowy.com/#/" + d.pId + ")" } if (d.note.length != 0) { b += " \n"; var c = d.note.split("\n"); for (var a = 0; a < c.length; a++) { b += c[a]; if (a != c.length - 1) { b += " \n" } } } else { b += "\n" } } return b }; tpcTrSrch(document.getElementById("pageContainer")); var text = tpcLst2md(); webkit.messageHandlers.CopyToClipboard.postMessage(text); alert("クリップボードにコピーしました。");
スクリプトのインストールは慎重に。
スクリプトのインストール及びご使用は各自の自己責任でご利用ください。
スクリプトの使用によって、利用者および第三者に損害が発生したとしても、当方は一切責任を負わないものとします。
ScriptMaker等でインストール可能。
実際の動作
#WorkFlowy: Complete日付を指定してHandyFlowy起動するWorkflow - sorashimaのブログ でタスクの完了日付を指定してHandyFlowyを起動↓
スクリプトを実行しクリップボードの内容をDrafts4にペーストした図↓
([トピックのID]の部分は実際は16進数のID)
markdownプレビューすると↓
動作確認には aitatte氏の HandyFlowy上でJavaScriptやCSSの動作確認を手軽に行うスクリプトTester - aitatena を利用した。
ただ、Testerでは上手く動作したのに、ScriptMakerでイザ登録するとエラーを吐いて動かないことがよくあった。
動かなかったスクリプトをJavascriptの圧縮ツールに通し、それをJavascriptの整形ツールに通して体裁を戻したら動いてくれた。
HTMLにスクリプトとして埋め込むのに対して、HandyFlowyの方は変数名とか、スクリプトの長さ(変数名や関数名が長ければそれだけサイズが大きくなる)とか、何か制限でもあるのだろうか?Testerでは動いただけに不思議だ。 *1
ちなみに圧縮ツールに掛ける前のソースは↓
var nest = 0; var pId = ""; var done = false; var name = ""; var note = ""; var tpcLst = []; function tpcTrSrch(tNd) { function chldNdSrch() { // 全ての子DOMノードに対して同じ処理をする var tChld = tNd.childNodes; for (var i=0; i<tChld.length; i++) { tpcTrSrch(tChld[i]); } } var tNTyp = tNd.nodeType; var tNNm = tNd.nodeName; // var tNV = tNd.nodeValue; // エレメントノードなら if (tNTyp == 1) { // DIVエレメントなら if (tNNm == "DIV") { // class属性とprojectid属性を取得 var tNdAttrs = tNd.attributes; var dCls = ""; for (var i=0; i<tNdAttrs.length; i++) { switch (tNdAttrs.item(i).nodeName) { case "class": var dCls = tNdAttrs.item(i).nodeValue; break; case "projectid": pId = tNdAttrs.item(i).nodeValue.replace(/[0-9a-f]+-[0-9a-f]+-[0-9a-f]+-[0-9a-f]+-([0-9a-f]+)/,"$1"); break; default: break; } } switch (true) { // classがないDIV case (dCls == ""): // 全ての子ノードに対して同じ処理をする chldNdSrch(); break; // projectクラスなら case (dCls.indexOf("project") >= 0): done = false; name = ""; note = ""; // mainTreeRootクラスなら // if (dCls.indexOf("mainTreeRoot") >= 0) {} // doneクラスなら if (dCls.indexOf("done") >= 0) { done = true; } // 全ての子ノードに対して同じ処理をする chldNdSrch(); break; // nameクラスなら case (dCls.indexOf("name") >= 0): name = tNd.innerText.replace(/\n/,""); break; // noteクラスなら case (dCls.indexOf("notes") >= 0): var noteTxt = tNd.innerText; // noteクラスのDIVのinnerTextが改行一文字(ノートが無い場合)でなければ保存 note = (noteTxt.length == 1) ? "" : noteTxt; var j = tpcLst.length; tpcLst[j] = {}; tpcLst[j].nest = nest; tpcLst[j].pId = pId; tpcLst[j].done = done; tpcLst[j].name = name; tpcLst[j].note = note; break; // childrenクラスなら case (dCls == "children"): nest++; // 全ての子ノードに対して同じ処理をする chldNdSrch(); nest--; break; // 万が一に備えて default: // 全ての子ノードに対して同じ処理をする chldNdSrch(); break; } } } } var tpcLst2md = function () { var md = ""; while (tpcLst.length > 0) { var tp = tpcLst.shift(); if (tp.nest > 0) { //トップレベルのtopicはレベル3のヘッダー if (tp.nest == 1) { md+="### "; } else { //半角スペース4文字でインデント for (var i = 0; i<= tp.nest - 3; i++) { md += " "; } //完了は[x]、未完は[ ]を行頭に付けてブレットリスト md += (tp.done) ? "- [x] " : "- [ ] "; } //topicの後にWorkFlowyへのリンク md += tp.name + " [≫](https://workflowy.com/#/" + tp.pId + ")"; } //ノートがあれば if (tp.note.length != 0) { //topicの後にmarkdownの改行 md+=" \n"; var noteLines = tp.note.split("\n"); for (var i=0; i < noteLines.length; i++) { md += noteLines[i]; if (i != noteLines.length - 1) { //ノートの最終行以外はmarkdownの改行 md += " \n"; } } } else { //topicの後に普通の改行 md+="\n"; } } return md; } tpcTrSrch(document.getElementById("pageContainer")); var text = tpcLst2md(); //var url = "drafts4://x-callback-url/create?text=" + encodeURIComponent(text); //window.open(url); webkit.messageHandlers.CopyToClipboard.postMessage(text); alert("クリップボードにコピーしました。");