目次
-
0. ブックマークレットとは
1. ブックマークレットの追加方法
2. 実際のブックマークレット
2-1. 一つのタスクに対する操作
2-1-1. ストーリーのスッキリ表示
2-1-2. コメントの展開
2-1-3. 作業間リンクの非表示
2-1-4. 完了したサブタスクの非表示
2-1-5. タスクの全幅表示
2-2. プロジェクトなどタスクリストに対する操作
2-2-1. ︎ セクションの表示切り替え
2-2-2. サブタスクの表示切り替え
2-2-3. すべてのタスクの完了
2-2-4. すべてのタスクの未完了
2-2-6. カレンダータスクの一括展開
2-3. その他の操作
3. 工夫が必要だったところ
3-1. プレースホルダータスク
3-2. 負荷の軽減
4. 他の方法との比較
4-1. JavaScriptを使用する方法との比較
4-2. JavaScriptとCSSの比較
5. 規約等の考慮
5-1. リバースエンジニアリング
5-2. デザインの変更可能性
5-3. 免責
6. 謝辞
プロジェクトのすべてのタスクを完了・未完了にする、プロジェクトのセクションをすべて展開する・折りたたむ、タスクのコメントをすべて展開する、といった一括操作をワンクリックで行う方法をご紹介します。
0. ブックマークレットとは
ブックマークレットとは、JavaScriptのコードをブラウザーのブックマークに登録して、クリックにより実行できるようにしたものです。JavaScriptコードを「javascript:(function() {
」と「})();
」で挟むことでブックマークが可能になります。
Asanaを含むWebサイトのフロントエンドは、HTML(部品を表すマークアップ言語)、CSS(レイアウトやデザインを指定する言語)、JavaScript(プログラミング言語)という3つの言語で主に構成されています。
HTMLはタグで囲まれた部品(ノード)が階層構造になっており、JavaScriptから操作することができます。これはDOM(ドキュメントオブジェクトモデル)と呼ばれる仕組みです。
1. ブックマークレットの追加方法
Asana bookmarklets にアクセスし、青字のリンクをブックマークバーにドラッグ&ドロップして追加してください。
横幅を取らないように、スクリーンショットのようにブックマークレット用のフォルダーを作成してそこに追加していくのがおすすめです。ブックマークバーに直接追加する場合は、名前を編集して絵文字だけにするなど、短くするのがおすすめです。
手作業で登録する場合は、以下のことを行います。
- 任意のページをブックマークに登録します。
- 登録時に「その他…」をクリックし、保存場所を選択し、名前とURLを編集します。
- この投稿の下にあるブックマークのタイトルを「名前」に、
javascript:
で始まるコードを「URL」に、それぞれ入力します。
ブックマークレットのリストをセクション分けするには、ブックマークのセパレーターツールがおすすめです。Drag meの部分をブックマークにドラッグするだけでセパレーター(横線)を挿入できます。
Asanaのタブを開いた状態で、登録したブックマークレットをクリックすると、自動操作を行えます。
2. 実際のブックマークレット
Asana bookmarklets に記載されているブックマークレットのソースコードと説明です。
2-1. 一つのタスクに対する操作
2-1-1.
ストーリーのスッキリ表示
以下の「コメントの展開」と「作業間リンクの非表示」を両方行います。
javascript:(function() {
const expandLink = document.querySelector('.TaskStoryFeed-expandLink');
if (expandLink && expandLink.textContent.match(/\d/)) expandLink.click();
document.querySelectorAll('.TruncatedRichText-expand').forEach(link => link.click());
document.querySelectorAll('.TaskStoryFeed-expandMiniStoriesLink').forEach(link => link.click());
document.querySelectorAll('.BacklinkMiniStory').forEach(line => {line.parentNode.style.display = 'none';});
})();
2-1-2.
コメントの展開
タスクストーリーの「その他 7 件のコメント」や「もっと見る」をすべてクリックします。
javascript:(function() {
const expandLink = document.querySelector('.TaskStoryFeed-expandLink');
if (expandLink && expandLink.textContent.match(/\d/)) expandLink.click();
document.querySelectorAll('.TruncatedRichText-expand').forEach(link => link.click());
document.querySelectorAll('.TaskStoryFeed-expandMiniStoriesLink').forEach(link => link.click());
})();
2-1-3. ↔ 作業間リンクの非表示
タスクストーリーの「 <ユーザー名> が次のタスク内でこのタスクをメンションしました: <タスクのリンク>」を非表示にします。参照タスクなど、多数のタスクからリンクされているためコメントや添付ファイルが見づらい場合に便利です。
javascript:(function() {
document.querySelectorAll('.BacklinkMiniStory').forEach(line => {line.parentNode.style.display = 'none';});
})();
2-1-4. ⫚ 完了したサブタスクの非表示
右側のタスク詳細ウィンドウで、完了したサブタスクをすべて非表示にします。ブックマークレットをもう一度クリックすると、サブタスクを再表示できます。
サブタスク数が多い場合は、Asana Load MoreというChrome拡張機能をインストールするのもおすすめです。
javascript:(function() {
const completedSubtaskRows = document.querySelectorAll('.SubtaskTaskRow--completed');
completedSubtaskRows.forEach(row => {
row.parentNode.parentNode.style.display = row.parentNode.parentNode.style.display? '': 'none';
});
})();
2-1-5.
タスクの全幅表示
タスクを全画面 (Tab+X) 表示にしたとき、横幅は最大 764px に制限されています。このブックマークレットを使用すると、画面幅を最大に使って本当にフルスクリーン表示することができます。
また、タスク一覧の右側にタスクの詳細が表示される場合、画面幅の75%まで拡張します。
javascript:(function() {
const focusModePane = document.querySelector('.Pane.FocusModePage-taskPane');
if (focusModePane) {
if (focusModePane.style.flexBasis == 'auto') {
focusModePane.style.flexBasis = '';
focusModePane.style.width = '';
} else {
focusModePane.style.flexBasis = 'auto';
focusModePane.style.width = '100%';
}
}
const detailsOverlay = document.querySelector('.FullWidthPageStructureWithDetailsOverlay-detailsOverlay');
if (detailsOverlay) {
if (!detailsOverlay.style.maxWidth) {
detailsOverlay.style.maxWidth = '75%';
detailsOverlay.style.width = '75%';
} else {
detailsOverlay.style.maxWidth = '700px';
detailsOverlay.style.width = '55%';
}
}
})();
2-2. プロジェクトなどタスクリストに対する操作
注意: タスクが多数あり一画面に表示しきれていない場合は、以下のブックマークレットを実行する前に、Asana画面を一番下までスクロールし、すべてのタスクを読み込んでください。
2-2-1.
︎ セクションの表示切り替え
リストビューでセクションの展開・折りたたみを切り替えます。
javascript:(function() {
const firstButtonIcon = document.querySelector('.TaskGroupHeader-toggleButton .Icon');
if (!firstButtonIcon) return;
const firstTriangleClassName = firstButtonIcon.classList.contains('DownTriangleIcon')? 'DownTriangleIcon': 'RightTriangleIcon';
document.querySelectorAll(`.TaskGroupHeader-toggleButton .${firstTriangleClassName}`).forEach(buttonIcon => buttonIcon.parentNode.click());
})();
2-2-2. ▷ サブタスクの表示切り替え
サブタスクの小さい▶︎をクリックし、サブタスクの展開・折りたたみを切り替えます。
- リストビュー: まず「ソート: なし」に設定します。サブタスクのあるタスクが多数ある場合にはタスクをリロードし、「他のサブタスクを読み込む」を順にクリックするため時間がかかります。
- ボードビュー: ボードビューに表示されているサブタスクのみ表示を切り替えられます。画面外のタスクには影響しません。
javascript:(function() {
const boardSubtaskToggleButtons = document.querySelectorAll('.SubtaskCountToggleButton');
if (boardSubtaskToggleButtons.length) {
boardSubtaskToggleButtons.forEach(button => button.click());
return;
}
const firstListSubtaskButton = document.querySelector('.ProjectSpreadsheetGridRow-subtaskToggleButton');
if (!firstListSubtaskButton) return;
const firstTriangleClassName = firstListSubtaskButton.firstElementChild.classList.contains('DownTriangleIcon')? 'DownTriangleIcon': 'RightTriangleIcon';
const taskPlaceholderHTMLCollection = document.getElementsByClassName('SpreadsheetTaskRowScrollPlaceholder');
const taskGroup = document.querySelector('.TaskGroup');
const buttonAtTheBottom = document.querySelector('.SpreadsheetPotGridContents-addSectionButton');
setTimeout(function () {if (buttonAtTheBottom) buttonAtTheBottom.scrollIntoView();}, 30);
setTimeout(function () {taskGroup.style.display = 'none';}, 60);
let monitorTaskStructure = setInterval(() => {
if (taskPlaceholderHTMLCollection.length == 0) {
document.querySelectorAll('.ProjectSpreadsheetGridRow-subtaskToggleButton').forEach(function (buttonIcon) {
if (buttonIcon.firstElementChild.classList.contains(firstTriangleClassName)) buttonIcon.click();
});
taskGroup.style.display = '';
clearInterval(monitorTaskStructure);
const loadMoreLinkHTMLCollection = document.getElementsByClassName('SpreadsheetTaskList-showMoreLink');
setTimeout(() => {
let clickingLoadMoreLinks = setInterval(function() {
if (!loadMoreLinkHTMLCollection.length) {
clearInterval(clickingLoadMoreLinks);
} else {
loadMoreLinkHTMLCollection[0].scrollIntoView();
loadMoreLinkHTMLCollection[0].click();
}
}, 100);
}, 200);
}
}, 100);
})();
2-2-3.
すべてのタスクの完了
リストビューに表示されているすべての未完了タスクを完了にします。多数ある場合は一度タスクの表示を消したあと、50個ずつクリックしていきます。
javascript:(function() {
const taskPlaceholderHTMLCollection = document.getElementsByClassName('SpreadsheetTaskRowScrollPlaceholder');
if (!taskPlaceholderHTMLCollection.length) {
document.querySelectorAll('.TaskRowCompletionStatus-taskCompletionIcon--incomplete').forEach(incompleteIcon => incompleteIcon.parentNode.click());
} else {
const taskGroup = document.querySelector('.TaskGroup');
const buttonAtTheBottom = document.querySelector('.SpreadsheetPotGridContents-addSectionButton');
setTimeout(function () {if (buttonAtTheBottom) buttonAtTheBottom.scrollIntoView();}, 30);
setTimeout(function () {
taskGroup.style.display = 'none';
const progressIndicator = document.createElement('span');
progressIndicator.setAttribute('id', 'progressIndicator');
progressIndicator.textContent = '処理しています';
taskGroup.parentNode.appendChild(progressIndicator);
}, 60);
let monitorTaskStructure = setInterval(() => {
if (taskPlaceholderHTMLCollection.length == 0) {
clearInterval(monitorTaskStructure);
const progressIndicator = document.querySelector('#progressIndicator');
const allTasks = Array.from(document.querySelectorAll('.TaskRowCompletionStatus-taskCompletionIcon--incomplete'));
const numProcesses = Math.floor(allTasks.length / 50) + 1;
let counter = 0;
let loopTasks = setInterval(() => {
progressIndicator.textContent = `処理しています (${counter}/${numProcesses})`;
for (let i = 50 * counter; i < Math.min(allTasks.length, 50 * (counter + 1)); i++) {
allTasks[i].parentNode.click();
if (i == allTasks.length - 1) {
clearInterval(loopTasks);
progressIndicator.remove();
taskGroup.style.display = '';
}
}
counter += 1;
}, 500);
}
}, 100);
}
})();
2-2-4.
すべてのタスクの未完了
リストビューに表示されているすべての完了タスクを未完了に戻します。多数ある場合は一度タスクの表示を消したあと、50個ずつクリックしていきます。
javascript:(function() {
const taskPlaceholderHTMLCollection = document.getElementsByClassName('SpreadsheetTaskRowScrollPlaceholder');
if (!taskPlaceholderHTMLCollection.length) {
document.querySelectorAll('.TaskRowCompletionStatus-taskCompletionIcon--complete').forEach(incompleteIcon => incompleteIcon.parentNode.click());
} else {
const taskGroup = document.querySelector('.TaskGroup');
const buttonAtTheBottom = document.querySelector('.SpreadsheetPotGridContents-addSectionButton');
setTimeout(function () {if (buttonAtTheBottom) buttonAtTheBottom.scrollIntoView();}, 30);
setTimeout(function () {
taskGroup.style.display = 'none';
const progressIndicator = document.createElement('span');
progressIndicator.setAttribute('id', 'progressIndicator');
progressIndicator.textContent = '処理しています';
taskGroup.parentNode.appendChild(progressIndicator);
}, 60);
let monitorTaskStructure = setInterval(() => {
if (taskPlaceholderHTMLCollection.length == 0) {
clearInterval(monitorTaskStructure);
const progressIndicator = document.querySelector('#progressIndicator');
const allTasks = Array.from(document.querySelectorAll('.TaskRowCompletionStatus-taskCompletionIcon--complete'));
const numProcesses = Math.floor(allTasks.length / 50) + 1;
let counter = 0;
let loopTasks = setInterval(() => {
progressIndicator.textContent = `処理しています (${counter}/${numProcesses})`;
for (let i = 50 * counter; i < Math.min(allTasks.length, 50 * (counter + 1)); i++) {
allTasks[i].parentNode.click();
if (i == allTasks.length - 1) {
clearInterval(loopTasks);
progressIndicator.remove();
taskGroup.style.display = '';
}
}
counter += 1;
}, 500);
}
}, 100);
}
})();
2-2-5.
マイタスクのみ

2-2-6.
カレンダータスクの一括展開
カレンダーの月ビューで、「その他 x 件」をすべて開きます。
画面にロードされている日のタスクだけ展開されるので、上下にスクロールすると折り畳まれたままの日が存在します。
javascript:(function() {
document.querySelectorAll('.CalendarDay-xMoreText').forEach(buttonDiv => buttonDiv.click());
})();
2-3. その他の操作
2-3-1. ☰ サイドバーのサイズ変更
3. 工夫が必要だったところ
3-1. プレースホルダータスク
プロジェクトに多数のタスクがあるとき、Asanaは表示範囲から遠く離れたタスクを「タスク名だけ」のプレースホルダーの状態に簡略化してリソース(メモリやエネルギー使用量)を節約しています。その近くにスクロールすると、再度データが読み込まれます。
例: プロジェクトの一番下からスクロールしていくと、上の方のタスクが一瞬「タスク名のみ」の状態で表示され、コンマ数秒後にフル表示されます。
セクション2-2のように「すべてのタスク」を対象にする場合は、一度すべてのタスクを非表示にして、Asanaに「すべてのタスクを表示するだけの十分なスペースがあるんだ」と信じ込ませます。Asanaがすべてのプレースホルダーを完全なタスクの形に書き直したとき、処理を実行し、非表示を解除します。
3-2. 負荷の軽減
一度に行う処理が多すぎると、Asanaのサーバーに負荷がかかりすぎてしまいます。APIを使用するときも、タスクを複数選択して一括操作するときも、おおよそ数十タスクごとに処理を行うので、上記「すべてのタスクの完了・未完了」では、50個ずつ処理するようにしました。
4. 他の方法との比較
4-1. JavaScriptを使用する方法との比較
Asanaで自動化を行う方法を比較してみました。左にあるほど複雑・強力な方法であり、右に行くほど簡単な方法です。ブックマークレットはChrome拡張機能を作る1/100〜1/10くらいのコード行数で済むので、かなり気軽な方法です。セクション3の問題を考慮すると少々長くなります。
方法: | OAuth | 個人アクセストークン(PAT) | Chrome拡張 | ブックマークレット | アドレスバー |
---|---|---|---|---|---|
例: | Asana AcademyにAsanaアカウントでログイン | Asana公式拡張機能 | このトピックに記載 | ワークスペースの一覧を取得 | |
認証方法: | サーバーを立てる | PATをハードコードまたは環境変数に保存する | ログインしていることを示すブラウザーのCookie | Cookie(同左) | Cookie(同左) |
利用方法: | アクセスを認可 | PATを利用 | Chrome Web Storeからインストール | ブックマークに追加 | アドレスバーにURLを入力 |
できること: | APIを通じた操作 | APIを通じた操作 | APIを通じた操作+DOMの操作 | DOMの操作 | APIを通じたGET操作(情報の取得) |
得意なこと: | 大規模なツールに最適 | 自分用にスクリプトを書くときに便利 | 配布が楽 | 作成・配布が楽 | 最も気軽 |
できないこと: | DOMの操作 | DOMの操作 | (少しはあります) | 表示されていないタスクの操作 | POST/PUT/DELETE操作(情報の編集) |
データが多い場合: | ページネーション | ページネーション | ページネーション | プレースホルダータスク | ページネーション(自動化できない) |
例: プロジェクトのタスクをすべて完了にする
この場合、ブックマークレットを使った方がシンプルな方法で高速に処理が行えます。
- APIを利用する方法では、
/projects/<project_gid>/tasks
エンドポイントへのGETリクエストでタスクを取得し、forループで各タスクを完了するPUTリクエストを書きます。タスクの取得と更新で2回APIコールを行う必要があります。 - ブックマークレットでは、画面に表示されているタスクを直接ループしてクリックします。
4-2. JavaScriptとCSSの比較
CSSを利用してデザインをカスタマイズすることもできます。JavaScriptとの大きな違いは以下のとおりです。
- APIを使用しないので認証不要
- HTML要素のデザインを常時変更しておける
- 特定の変化が起きた場合、または特定の操作を行った場合に動的に対応することはできない
利用方法: StylishやUser CSSなどのChrome拡張機能をインストールします。userstyles.orgなどから別のユーザーが作成したCSSを利用することも、自分でCSSをカスタマイズすることもできます。
例: サイドバーの幅を変更する
- 上記2-3-1のブックマークレットを使用した方法では、ボタンを押したときにサイドバーの拡大・縮小が行えます。
- サイドバーの幅を常に変更しておきたい場合は、CSSをカスタマイズする方法が適しています。
5. 規約等の考慮
5-1. リバースエンジニアリング
AsanaのAPI規約でリバースエンジニアリングは禁止されています。ただし、右クリック > 「要素を検証」でDOM要素を特定する行為は一般に簡単に行えるものであり、Asanaの機密には何ら関わらない方法だと考えています。
5-2. デザインの変更可能性
AsanaツールのHTML要素を特定するためのIDやクラス名は、Asanaがいつでも自由に変更できます。その場合には上記のブックマークレットが機能しなくなります。気付き次第アップデートしますが、問題にお気づきの方がいらっしゃれば下の「返信」でお知らせください。
5-3. 免責
ブックマークレットの作成には細心の注意を払い、テストをおこなっていますが、それに基づくいかなる問題への責任も開発者は負えません。
6. 謝辞
- ブックマークレットのアイデアは元々 "COLLAPSE (or re-expand) ALL SECTIONS" button needed - #4 by Tony_Tsui を見たときに便利で簡単に応用できる方法だと思いました。"COLLAPSE (or re-expand) ALL SECTIONS" button needed - #47 by Skyler には別のユーザーがまとめたブックマークレットもあります。
- フォーラムリーダーのBastienさんとアンバサダーのItayさんに実際に試していただき、上記「工夫が必要だったところ」の課題に気づくことができました。
- お読みいただきありがとうございます。他に欲しい機能などあれば、コメントをお願いいたします。JavaScriptを書ける方は、ぜひご自分でもブックマークレットを作ってみてください。