クライアントサイドWeb付箋紙 Greasemonkey
グリモンでこんなの作ってみました。
これは何?
Greasemonkeyを使ったWeb付箋紙ツールです。
Web上の任意のページにメモを貼り付けておくことができます。
GreasemonkeyのGM_getValue/GM_setValueを使ってデータをクライアントサイドに保存します。
そのためログイン不要で使えますけど、複数端末で共有したり、他の人に見せてあげたりはできません。
作成動機
まるごとJavaScript & Ajax ! Vol.1に載っていたFirefox2.0のクライアントサイドストレージを使って何かできないかなと思って作りました。最初、普通の<script>タグでスクリプトを読み込まして作って*1ちゃんと動いたのですが、Greasemonkeyのuser.jsとして動かしたら、なぜかクライアントサイドストレージが読み込めず*2、GM_setValue/GM_getValueというのを知って、こっちを使うように切り替えました。
インストール
使い方
- 任意のページで Altキーを押しながら @キーを押すと新しい付箋紙を貼り付けることができます。
- 表示されたダイアログに内容を記入します。(図2)
- 初期値は現在の日時です。*3
- "<"からはじめるとHTMLタグが有効になります。
- HTMLタグを使わない場合は、\nで改行できます。
- 貼り付けた付箋紙をダブルクリックすると編集できます。(図3)
- "<"からはじめるとHTMLタグが有効になります。
- HTMLタグを使わない場合は、\nで改行できます。
- 空にして「OK」すると付箋紙が削除されます。
- REMOVE_ALLと記入すると、そのページの付箋紙を全て削除します。
はまったところなど
やろうかと思ったけどやらなかった機能
- prompt関数じゃない入力フォーム
- 面倒臭いので却下。
- 編集ボタン・削除ボタン・横幅変更ボタン
- 面倒臭いし、無くても問題ないので却下。
- 付箋紙の有効スコープの複数化 (同一ドメイン内で常に表示など)
- 多分、使い道が無いので却下。
- 同一ドメイン内の付箋紙を一括削除
- Firefox以外のブラウザへの対応
- IE/TrixieはYoung risk taker.: [javascript] IEとFirefox2.0以上で動作するClient Side Storageを使えばできるかも。でもやらない。
- Safari/CreammonkeyはMac持ってないし。OperaはglobalStorageないし。
- というか、やろうとは思わなかった。
ソース
// ==UserScript== // @name GmSticky // @namespace http://d.hatena.ne.jp/sawat/ // @description Make stikies and save at greasemonkey storage. // @include http://* // @include https://* // ==/UserScript== if(window.Sticky == void(0)) { (function() { var Util = { document: window.document, // DOM構築関連 createElement: function(tagName, parent, style, attributes, listeners, innerHTML) { var element = Util.document.createElement(tagName); if(attributes) {Util.setAttributes(element, attributes); } if(parent) {parent.appendChild(element);} if(style) {Util.setStyle(element, style); } if(innerHTML) {element.innerHTML = innerHTML;} if(listeners) {Util.setListeners(element, listeners); } return element; }, setAttributes : function(element, attributes) { for(var prop in attributes) { element.setAttribute(prop, attributes[prop]); } }, setListeners : function(element, listeners) { var func = element.addEventListener ? function(name, listener) { element.addEventListener(name, listener, false); } : function(name, listener) { element.attachEvent('on' + name, listener); } ; for(var prop in listeners) { func(prop, listeners[prop]); } }, setStyle : function(element, style) { Util.marge(element.style, style); }, marge : function(dest, src) { for(var prop in src) { dest[prop] = src[prop]; } }, escapeHtmlChar : function(str) { return str && str.replace(/</g, '<').replace(/>/g, '>').replace(/\\n/g, '<br />'); } }; /** Sticky コンストラクタ. */ window.Sticky = function Sticky(text, x, y) { this.text = text; this.div = Util.createElement('div', document.body, Sticky.STYLE, null, this.createListeners(), null); this.clickX = 0; this.clickY = 0; this.edit(text); this.setPosition(x || document.body.scrollLeft + Math.floor(document.body.clientWidth/2), y || document.body.scrollTop + Math.floor(document.body.clientHeight/2)); }; /** インスタンスのメソッド */ window.Sticky.prototype = { /** 編集 */ edit: function(newText) { newText = newText || prompt(Sticky.EDIT_MESSAGE, this.text); // キャンセル if(newText == void(0)) return; // 削除 if(newText == '') { this.dispose(); return; } // ページ内削除 if(newText == 'REMOVE_ALL') { Sticky.removeAll(); return; } // ドメイン内削除 if(newText == '!REMOVE_ALL') { Sticky.removeAllInDomain(); return; } var use_html = newText.match(/^</) != null; try { this.div.innerHTML = use_html ? newText : Util.escapeHtmlChar(newText); } catch(e) { this.div.innerHTML = Util.escapeHtmlChar(newText); } this.text = newText; Sticky.store(); }, /** 破棄 */ dispose : function(flag) { this.div.parentNode.removeChild(this.div); if(!flag) Sticky.remove(this); }, /** 配置 */ setPosition : function(x, y) { this.div.style.left = Math.max(10, Math.min(document.body.scrollWidth - this.div.clientWidth - 10, x - this.clickX)) + "px"; this.div.style.top = Math.max(10, Math.min(document.body.scrollHeight - this.div.clientHeight - 10, y - this.clickY)) + "px"; }, /** コピー */ copy : function () { return Sticky.create(this.text, parseInt(this.div.style.left)+10, parseInt(this.div.style.top)+10); }, /** 個別の付箋紙(div)に対するリスナー */ createListeners : function() { var sticky = this; return { /** ダブルクリック : 編集 */ 'dblclick' : function(event) { sticky.edit(); Sticky.moving = null; }, /** マウスダウン : ドラッグ開始 */ 'mousedown': function(event) { Sticky.moving = sticky ; sticky.clickX = event.pageX - parseInt(sticky.div.style.left); sticky.clickY = event.pageY - parseInt(sticky.div.style.top); }, /** Shift+クリック : 削除 Ctrl+クリック : コピー */ 'click' : function (event) { if(event.shiftKey) sticky.dispose(); if(event.ctrlKey) sticky.copy(); } }; }, /** 文字列化(JSON) */ toString: function() { return '{ x:' + parseInt(this.div.style.left) + ',y:' + parseInt(this.div.style.top) + ',text:"' + this.text.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"' + // " '}'; } } /** クラスのメソッド・定数(?) */ Util.marge(window.Sticky, { _storage: [], /** 新規作成 */ create: function(text, x, y, use_html) { text = text || prompt('New Sticky', document.getSelection() || new Date().toLocaleString()); if(!text) return; Sticky.add(new Sticky(text, x, y, use_html)); Sticky.store(); }, /** 登録 */ add: function(newSticky) { Sticky._storage.push(newSticky); }, /** 削除 */ remove: function(sticky) { for(var i=0,n=Sticky._storage.length; i<n; i++) { if(Sticky._storage[i] == sticky) { Sticky._storage.splice(i, 1); break; } } Sticky.store(); }, /** ページ内全削除 */ removeAll: function(sticky) { for(var i=0,n=Sticky._storage.length; i<n; i++) { Sticky._storage[i].dispose(); } Sticky._storage = []; Sticky.store(); }, /** ドメイン内全削除 */ removeAllInDomain: function(sticky) { Sticky.removeAll(); return; }, /** クライアントサイドストレージへの保存キー作成 */ key : function() { return '_stickies@' + document.location.host + document.location.pathname + document.location.search; }, /** クライアントサイドストレージからの読み込み */ load : function() { var data = GM_getValue(Sticky.key()); if(!data) return; try { var stickies = eval(decodeURI(data)); for(var i=0; i<stickies.length; i++) { Sticky.create(stickies[i].text, stickies[i].x, stickies[i].y); } } catch(e) { new Sticky('<span style="color:red;font-weight:bold">Error on load stickise data.</span>', 1, 1); } }, /** クライアントサイドストレージへの書き込み */ store : function() { var data = '[' + Sticky._storage.join(',') + ']'; GM_setValue(Sticky.key(), encodeURI(data)); }, /** 初期化 */ initialize : function () { Util.setListeners(document.getElementsByTagName('html')[0], Sticky.GLOBAL_LISTENER); setTimeout(Sticky.load, 500); }, /** 付箋紙のスタイル */ STYLE : { position: 'absolute', left: '0px', top: '0px', cursor: 'move', minimumWidth: '100px', borderColor: '#663333', borderWidth: '1px 2px 2px 1px', borderStyle: 'solid', backgroundColor: '#FFFFEF', fontSize: '90%' }, /** document全体に対するリスナー */ GLOBAL_LISTENER : { /** マウスムーブ : ドラッグ */ 'mousemove' : function (event) { if (!Sticky.moving) return; Sticky.moving.setPosition(event.pageX, event.pageY); }, /** マウスアップ : ドラッグ終了 */ 'mouseup' : function (event) { if (!Sticky.moving) return; Sticky.moving.clickX = 0; Sticky.moving.clickY = 0; Sticky.moving = null; Sticky.store(); }, /** 新規作成 */ 'keydown' : function (event) { if (event.altKey && event.keyCode == 192 /* @キー */) { Sticky.create(); } } }, /** 編集ダイアログメッセージ */ EDIT_MESSAGE : 'Edit\n'+ ' Start with "<" : Use HTML tags.\n' + ' Empty : Remove this sticky\n' + ' "REMOVE_ALL" : Remove all stickies in this page.\n', VERSION : '0.01' }); Sticky.initialize(); })(); }
追記(2/28)
GM_setValueで設定した値を削除するための関数がGreasemonkeyにないため、いろんなページに付箋を貼ったり消したりしていると次第にabout:configの設定が増えてしまいます。そのため、時々 about:config を開いて sticky でフィルタを掛けて出てくる項目をリセットすることをオススメします(図4)。
リセットされた項目はFirefoxを再起動すると完全に消去されます。ただし、再起動前に設定を削除したページを表示するとGM_getValueに失敗してエラーが発生します。
Firefoxが起動していないときに prefs.js *5を編集して削除してもかまいません。