「Places/フォルダ選択ダイアログ作成」の編集履歴(バックアップ)一覧はこちら

Places/フォルダ選択ダイアログ作成」(2015/06/02 (火) 20:39:39) の最新版変更点

追加された行は緑色になります。

削除された行は赤色になります。

-ブックマーク編集パネルにあるPlaceフォルダ選択リストメニュー/ツリービューが使いたかったので。 -editBookmarkOverlay.xul と editBookmarkOverlay.js と editBookmarkOverlay.css のコピーみたいな感じに…。 --editBookmarkOverlay.dtd はコピーせずそのまま使うことにした。 -jsコードは editBookmarkOverlay.js を読み込んで gEditItemOverlay のメソッドのいくつかを改造/置き換えた方が早くて楽なんだけど、中の動きを理解するためにあえて必要な部分をまるまるコピーしながら勉強。 --2箇所ほどバグを発見した…。 #contents() -機能としては、メニューからダイアログを開いて、placeフォルダを選択して、okで終わるとprefs.js設定にフォルダidを保存するだけ。 *ファイル構成 *.xpi ├ install.rdf ├ chrome.manifest └ content ├ browser.xul ├ browser.js └ PlaceFolderPicker ├ dialog.xul ├ dialog.js ├ PlaceFolderPicker.xul ├ PlaceFolderPicker.js └ PlaceFolderPicker.css PlaceFolderPicker.css は本当は skin フォルダに入れるべきなんだけど、面倒なので xul や js と同じフォルダに入れてる。 *ブラウザにダイアログを開くメニューを追加 **chrome.manifest パッケージ名はとりあえず安直に test で。 content test content/ overlay chrome://browser/content/browser.xul chrome://test/content/browser.xul **browser.xul ツールメニュー内にメニュー追加 #highlight(xml){ <script src="browser.js"/> <menupopup id="menu_ToolsPopup"> <menuitem id="test_open_dialog" label="PlaceFolderPicker ダイアログを開く" oncommand="testBrowser.openDialog();"/> </menupopup>} **browser.js #highlight(javascript){{ var testBrowser = { openDialog : function() { let features = "centerscreen,chrome,modal,resizable=yes"; window.openDialog('chrome://test/content/PlaceFolderPicker/dialog.xul', 'testDialog', features); }, }; }} *ダイアログを作成 **dialog.xul PlaceFolderPicker.xul を overlay で読み込む。 #highlight(xml){ <?xml version="1.0"?> <?xml-stylesheet href="chrome://global/skin/"?> <?xul-overlay href="chrome://test/content/PlaceFolderPicker/PlaceFolderPicker.xul"?> <dialog id="testDialog" title="PlaceFolderPicker テスト ダイアログ" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="testDialog.init();" onunload="testDialog.uninit();" style="min-width: 30em;" buttons="accept,cancel" ondialogaccept="return testDialog.ok();" ondialogcancel="return testDialog.cancel();"> <script src="dialog.js"/> <grid id="PlaceFolderPickerContent"/> </dialog>} **dialog.js #highlight(javascript){{ var testDialog = { init : function() { var id; try { id = Services.prefs.getIntPref("extensions.test.folderid"); } catch(e) {} PlaceFolderPicker.init(id); }, uninit : function() { PlaceFolderPicker.uninit(); }, ok : function() { Services.prefs.setIntPref("extensions.test.folderid", PlaceFolderPicker.id); PlaceFolderPicker.save(); return true; }, cancel : function() { return true; }, }; }} **PlaceFolderPicker.xul dialog.xulをoverlayする。 editBookmarkOverlay.xul からかなりコピーしてる。 #highlight(xml){ <?xml version="1.0"?> <?xml-stylesheet href="chrome://browser/skin/places/places.css"?> <?xml-stylesheet href="chrome://browser/content/places/places.css"?> <?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?> <?xml-stylesheet href="PlaceFolderPicker.css"?> <!DOCTYPE overlay [ <!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd"> %editBookmarkOverlayDTD; ]> <overlay id="PlaceFolderPicker" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <script src="PlaceFolderPicker.js"/> <!-- browser/omni.ja/chrome/browser/content/browser/places/editBookmarkOverlay.xul --> <grid id="PlaceFolderPickerContent" flex="1"> <columns><column/><column flex="1"/></columns> <rows> <row id="PFP_folderRow" align="center"> <label value="&editBookmarkOverlay.folder.label;" control="PFP_folderMenuList"/> <hbox flex="1"> <menulist id="PFP_folderMenuList" flex="1" class="folder-icon" oncommand="PlaceFolderPicker.onFolderMenuListCommand(event);"> <menupopup> <menuitem id="PFP_toolbarFolderItem" class="menuitem-iconic folder-icon"/> <menuitem id="PFP_bmRootItem" class="menuitem-iconic folder-icon"/> <menuitem id="PFP_unfiledRootItem" class="menuitem-iconic folder-icon"/> <menuseparator id="PFP_chooseFolderSeparator"/> <menuitem id="PFP_chooseFolderMenuItem" label='&editBookmarkOverlay.choose.label;' class="menuitem-iconic folder-icon"/> <menuseparator id="PFP_foldersSeparator"/> </menupopup> </menulist> <button id="PFP_foldersExpander" class="expander-down" tooltiptext="&editBookmarkOverlay.foldersExpanderDown.tooltip;" tooltiptextdown="&editBookmarkOverlay.foldersExpanderDown.tooltip;" tooltiptextup="&editBookmarkOverlay.expanderUp.tooltip;" oncommand="PlaceFolderPicker.toggleFolderTreeVisibility();"/> </hbox> </row> <row id="PFP_folderTreeRow" collapsed="true" flex="1"> <spacer/> <vbox flex="1"> <tree id="PFP_folderTree" flex="1" class="placesTree" type="places" height="150" minheight="150" editable="true" hidecolumnpicker="true" onselect="PlaceFolderPicker.onFolderTreeSelect();"> <treecols> <treecol anonid="title" flex="1" primary="true" hideheader="true"/> </treecols> <treechildren flex="1"/> </tree> <hbox> <button id="PFP_newFolderButton" label="&editBookmarkOverlay.newFolderButton.label;" accesskey="&editBookmarkOverlay.newFolderButton.accesskey;" oncommand="PlaceFolderPicker.newFolder();"/> </hbox> </vbox> </row> </rows> </grid> </overlay>} **PlaceFolderPicker.js editBookmarkOverlay.js からコピーしまくり #highlight(javascript){{ // browser/omni.ja/chrome/browser/content/browser/places/editBookmarkOverlay.js // Cu.import("resource://gre/modules/PlacesUtils.jsm"); // Cu.import("resource:///modules/PlacesUIUtils.jsm"); // Cu.import("resource://gre/modules/debug.js"); // 最近使用したフォルダのアノテーションマーク。↓はブックマーク編集パネルのと同じ。 // 共有したくなければ独自のを設定すればいい。 const LAST_USED_ANNO = "bookmarkPropertiesDialog/folderLastUsed"; // 最近使用したフォルダの表示数 const MAX_FOLDER_ITEM_IN_MENU_LIST = 5; var PlaceFolderPicker = { init : function PFP_init(id) { this.id = id; try { if (id == PlacesUtils.placesRootId) throw new Error('Placesルートフォルダは指定出来ません。'); if (id == PlacesUtils.tagsFolderId) throw new Error('タグフォルダは指定出来ません。'); let bms = PlacesUtils.bookmarks; let type = bms.getItemType(this.id); if (type != bms.TYPE_FOLDER) throw new Error('指定の id はフォルダではありません。id:' + this.id + '/type:' + type); //bms.getFolderReadonlyはfirefox36で削除された模様 //if (bms.getFolderReadonly(this.id)) // throw new Error('フォルダが読み込み専用です。id:' + this.id); } catch(e) { console.error(e); this.id = PlacesUtils.bookmarksMenuFolderId; // デフォルトのフォルダ } this._folderMenuList = this._element("folderMenuList"); this._folderTree = this._element("folderTree"); this._initFolderMenuList(); // observe changes PlacesUtils.bookmarks.addObserver(this, false); }, uninit : function PFP_uninit(save) { PlacesUtils.bookmarks.removeObserver(this); }, save : function() { // 現在のフォルダを最近使用したフォルダとしてマークアップ(特殊フォルダは除く) if (this.id != PlacesUtils.unfiledBookmarksFolderId && this.id != PlacesUtils.toolbarFolderId && this.id != PlacesUtils.bookmarksMenuFolderId) this._markFolderAsRecentlyUsed(this.id); }, _element : function(id) { return window.document.getElementById("PFP_" + id); }, _initFolderMenuList: function PFP__initFolderMenuList() { const bms = PlacesUtils.bookmarks; const annos = PlacesUtils.annotations; // 初期リストの設定 { let unfiledItem = this._element("unfiledRootItem"); unfiledItem.label = bms.getItemTitle(PlacesUtils.unfiledBookmarksFolderId); unfiledItem.folderId = PlacesUtils.unfiledBookmarksFolderId; let bmMenuItem = this._element("bmRootItem"); bmMenuItem.label = bms.getItemTitle(PlacesUtils.bookmarksMenuFolderId); bmMenuItem.folderId = PlacesUtils.bookmarksMenuFolderId; let toolbarItem = this._element("toolbarFolderItem"); toolbarItem.label = bms.getItemTitle(PlacesUtils.toolbarFolderId); toolbarItem.folderId = PlacesUtils.toolbarFolderId; } // 最近使用したフォルダのリストを取得 var folderIds = annos.getItemsWithAnnotation(LAST_USED_ANNO); // リストをソート this._recentFolders = []; for (let i = 0; i < folderIds.length; i++) { let lastUsed = annos.getItemAnnotation(folderIds[i], LAST_USED_ANNO); this._recentFolders.push({ folderId: folderIds[i], lastUsed: lastUsed }); } this._recentFolders.sort(function(a, b) { if (b.lastUsed < a.lastUsed) return -1; if (b.lastUsed > a.lastUsed) return 1; return 0; }); // 既定の数だけメニューに追加 var numberOfItems = Math.min(MAX_FOLDER_ITEM_IN_MENU_LIST, this._recentFolders.length); for (let i = 0; i < numberOfItems; i++) { this._appendFolderItemToMenupopup(this._recentFolders[i].folderId); } // 現在のフォルダをデフォルト選択 var defaultItem = this._getFolderMenuItem(this.id); this._folderMenuList.selectedItem = defaultItem; // 特殊フォルダアイコン表示のため、menulist要素に独自属性を設定する this._folderMenuList.setAttribute("selectedId", defaultItem.id ); }, _appendFolderItemToMenupopup : function PFP__appendFolderItemToMenuList(aFolderId) { var folderMenuItem = window.document.createElement("menuitem"); var folderTitle = PlacesUtils.bookmarks.getItemTitle(aFolderId) folderMenuItem.folderId = aFolderId; folderMenuItem.setAttribute("label", folderTitle); folderMenuItem.className = "menuitem-iconic folder-icon append"; this._folderMenuList.menupopup.appendChild(folderMenuItem); return folderMenuItem; }, _getFolderMenuItem : function PFP__getFolderMenuItem(aFolderId) { var menupopup = this._folderMenuList.menupopup; for (let i = 0; i < menupopup.childNodes.length; i++) { if ("folderId" in menupopup.childNodes[i] && menupopup.childNodes[i].folderId == aFolderId) return menupopup.childNodes[i]; } // 最近使用したフォルダが規定の数かそれ以上の場合、1個削除 var appendMenu = menupopup.getElementsByClassName("append"); if (appendMenu.length >= MAX_FOLDER_ITEM_IN_MENU_LIST) menupopup.removeChild(menupopup.lastChild); return this._appendFolderItemToMenupopup(aFolderId); }, onFolderMenuListCommand : function PFP_onFolderMenuListCommand(aEvent) { if (aEvent.target.id == "PFP_chooseFolderMenuItem") { // リストメニューの選択状態を元に戻し、ツリーを表示する let item = this._getFolderMenuItem(this.id); this._folderMenuList.selectedItem = item; setTimeout(function(self) self.toggleFolderTreeVisibility(), 100, this); return; } // 現在のフォルダidを更新する this.id = this._getFolderIdFromMenuList(); // 特殊フォルダアイコン表示のためのmenulist要素の独自属性を更新する this._folderMenuList.setAttribute("selectedId", this._folderMenuList.selectedItem.id ); // フォルダツリーを更新する var folderTreeRow = this._element("folderTreeRow"); if (!folderTreeRow.collapsed) { var selectedNode = this._folderTree.selectedNode; if (!selectedNode || PlacesUtils.getConcreteItemId(selectedNode) != this.id) this._folderTree.selectItems([this.id]); } }, _getFolderIdFromMenuList : function PFP__getFolderIdFromMenuList() { var selectedItem = this._folderMenuList.selectedItem; NS_ASSERT("folderId" in selectedItem, "Invalid menuitem in the folders-menulist"); return selectedItem.folderId; }, toggleFolderTreeVisibility : function PFP_toggleFolderTreeVisibility() { var expander = this._element("foldersExpander"); var folderTreeRow = this._element("folderTreeRow"); if (!folderTreeRow.collapsed) { expander.className = "expander-down"; expander.setAttribute("tooltiptext", expander.getAttribute("tooltiptextdown")); folderTreeRow.collapsed = true; this._element("chooseFolderSeparator").hidden = this._element("chooseFolderMenuItem").hidden = false; } else { expander.className = "expander-up" expander.setAttribute("tooltiptext", expander.getAttribute("tooltiptextup")); folderTreeRow.collapsed = false; const FOLDER_TREE_PLACE_URI = "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&folder=" + PlacesUIUtils.allBookmarksFolderId; this._folderTree.place = FOLDER_TREE_PLACE_URI; this._element("chooseFolderSeparator").hidden = this._element("chooseFolderMenuItem").hidden = true; this._folderTree.selectItems([this.id]); this._folderTree.focus(); } window.sizeToContent(); }, onFolderTreeSelect : function PFP_onFolderTreeSelect() { var selectedNode = this._folderTree.selectedNode; // Disable the "New Folder" button if we cannot create a new folder this._element("newFolderButton") .disabled = !this._folderTree.insertionPoint || !selectedNode; if (!selectedNode) return; var folderId = PlacesUtils.getConcreteItemId(selectedNode); if (this.id == folderId) return; var folderItem = this._getFolderMenuItem(folderId); this._folderMenuList.selectedItem = folderItem; folderItem.doCommand(); }, _markFolderAsRecentlyUsed : function PFP__markFolderAsRecentlyUsed(aFolderId) { var txns = []; // Expire old unused recent folders var anno = this._getLastUsedAnnotationObject(false); while (this._recentFolders.length > MAX_FOLDER_ITEM_IN_MENU_LIST) { var folderId = this._recentFolders.pop().folderId; let annoTxn = new PlacesSetItemAnnotationTransaction(folderId, anno); txns.push(annoTxn); } // Mark folder as recently used anno = this._getLastUsedAnnotationObject(true); let annoTxn = new PlacesSetItemAnnotationTransaction(aFolderId, anno); txns.push(annoTxn); let aggregate = new PlacesAggregatedTransaction("Update last used folders", txns); PlacesUtils.transactionManager.doTransaction(aggregate); }, /** * Returns an object which could then be used to set/unset the * LAST_USED_ANNO annotation for a folder. * * @param aLastUsed * Whether to set or unset the LAST_USED_ANNO annotation. * @returns an object representing the annotation which could then be used * with the transaction manager. */ _getLastUsedAnnotationObject : function PFP__getLastUsedAnnotationObject(aLastUsed) { return { name: LAST_USED_ANNO, type: Ci.nsIAnnotationService.TYPE_INT32, flags: 0, value: aLastUsed ? new Date().getTime() : null, expires: Ci.nsIAnnotationService.EXPIRE_NEVER, }; }, newFolder: function PFP_newFolder() { var ip = this._folderTree.insertionPoint; // default to the bookmarks menu folder if (!ip || ip.itemId == PlacesUIUtils.allBookmarksFolderId) { ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId, PlacesUtils.bookmarks.DEFAULT_INDEX, Ci.nsITreeView.DROP_ON); } // XXXmano: add a separate "New Folder" string at some point... var defaultLabel = this._element("newFolderButton").label; var txn = new PlacesCreateFolderTransaction(defaultLabel, ip.itemId, ip.index); PlacesUtils.transactionManager.doTransaction(txn); this._folderTree.focus(); this._folderTree.selectItems([ip.itemId]); PlacesUtils.asContainer(this._folderTree.selectedNode).containerOpen = true; this._folderTree.selectItems([this._lastNewItem]); this._folderTree.startEditing(this._folderTree.view.selection.currentIndex, this._folderTree.columns.getFirstColumn()); }, // nsINavBookmarkObserver onItemChanged : function PFP_onItemChanged(aItemId, aProperty, aIsAnnotationProperty, aValue, aLastModified, aItemType) { if (aProperty == "title" && aItemType == PlacesUtils.bookmarks.TYPE_FOLDER) { // If the title of a folder which is listed within the folders // menulist has been changed, we need to update the label of its // representing element. var menupopup = this._folderMenuList.menupopup; for (let i = 0; i < menupopup.childNodes.length; i++) { if ("folderId" in menupopup.childNodes[i] && menupopup.childNodes[i].folderId == aItemId) { menupopup.childNodes[i].label = aValue; break; } } } return; }, onItemAdded: function PFP_onItemAdded(aItemId) { this._lastNewItem = aItemId; }, onItemMoved: function() { }, onItemRemoved: function() { }, onBeginUpdateBatch: function() { }, onEndUpdateBatch: function() { }, onItemVisited: function() { }, }; }} **PlaceFolderPicker.css #highlight(css){{ /* browser/omni.ja/chrome/browser/skin/classic/browser/places/editBookmarkOverlay.css */ /**** folder menulist ****/ .folder-icon > .menulist-label-box > .menulist-icon { width: 16px; height: 16px; } .folder-icon > .menu-iconic-left { display: -moz-box; } .folder-icon { list-style-image: url("chrome://global/skin/icons/folder-item.png") !important; -moz-image-region: rect(0px, 32px, 16px, 16px) !important; } /**** expanders ****/ .expander-up, .expander-down { min-width: 0; margin: 0; -moz-margin-end: 4px; } .expander-up > .button-box, .expander-down > .button-box { padding: 0; } .expander-up { list-style-image: url("chrome://global/skin/icons/collapse.png"); } .expander-down { list-style-image: url("chrome://global/skin/icons/expand.png"); } #PFP_folderTree { margin-top: 2px; margin-bottom: 2px; } /* editBookmarkOverlay.js で javascript で処理していた セパレーターの非表示処理はCSSで可能 */ #PFP_foldersSeparator:last-child { display: none; } /* ::::: dropdown icons ::::: */ #PFP_folderMenuList[selectedId="PFP_toolbarFolderItem"], #PFP_toolbarFolderItem { list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png") !important; -moz-image-region: auto !important; } #PFP_folderMenuList[selectedId="PFP_bmRootItem"], #PFP_bmRootItem { list-style-image: url("chrome://browser/skin/places/bookmarksMenu.png") !important; -moz-image-region: auto !important; } #PFP_folderMenuList[selectedId="PFP_unfiledRootItem"], #PFP_unfiledRootItem { list-style-image: url("chrome://browser/skin/places/unsortedBookmarks.png") !important; -moz-image-region: auto !important; } }}
-ブックマーク編集パネルにあるPlaceフォルダ選択リストメニュー/ツリービューが使いたかったので。 -editBookmarkOverlay.xul と editBookmarkOverlay.js と editBookmarkOverlay.css のコピーみたいな感じに…。 --editBookmarkOverlay.dtd はコピーせずそのまま使うことにした。 -jsコードは editBookmarkOverlay.js を読み込んで gEditItemOverlay のメソッドのいくつかを改造/置き換えた方が早くて楽なんだけど、中の動きを理解するためにあえて必要な部分をまるまるコピーしながら勉強。 --2箇所ほどバグを発見した…。 #contents() -機能としては、メニューからダイアログを開いて、placeフォルダを選択して、okで終わるとprefs.js設定にフォルダidを保存するだけ。 *ファイル構成 *.xpi ├ install.rdf ├ chrome.manifest └ content ├ browser.xul ├ browser.js └ PlaceFolderPicker ├ dialog.xul ├ dialog.js ├ PlaceFolderPicker.xul ├ PlaceFolderPicker.js └ PlaceFolderPicker.css PlaceFolderPicker.css は本当は skin フォルダに入れるべきなんだけど、面倒なので xul や js と同じフォルダに入れてる。 *ブラウザにダイアログを開くメニューを追加 **chrome.manifest パッケージ名はとりあえず安直に test で。 content test content/ overlay chrome://browser/content/browser.xul chrome://test/content/browser.xul **browser.xul ツールメニュー内にメニュー追加 #highlight(xml){ <script src="browser.js"/> <menupopup id="menu_ToolsPopup"> <menuitem id="test_open_dialog" label="PlaceFolderPicker ダイアログを開く" oncommand="testBrowser.openDialog();"/> </menupopup>} **browser.js #highlight(javascript){{ var testBrowser = { openDialog : function() { let features = "centerscreen,chrome,modal,resizable=yes"; window.openDialog('chrome://test/content/PlaceFolderPicker/dialog.xul', 'testDialog', features); }, }; }} *ダイアログを作成 **dialog.xul PlaceFolderPicker.xul を overlay で読み込む。 #highlight(xml){ <?xml version="1.0"?> <?xml-stylesheet href="chrome://global/skin/"?> <?xul-overlay href="chrome://test/content/PlaceFolderPicker/PlaceFolderPicker.xul"?> <dialog id="testDialog" title="PlaceFolderPicker テスト ダイアログ" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="testDialog.init();" onunload="testDialog.uninit();" style="min-width: 30em;" buttons="accept,cancel" ondialogaccept="return testDialog.ok();" ondialogcancel="return testDialog.cancel();"> <script src="dialog.js"/> <grid id="PlaceFolderPickerContent"/> </dialog>} **dialog.js #highlight(javascript){{ var testDialog = { init : function() { var id; try { id = Services.prefs.getIntPref("extensions.test.folderid"); } catch(e) {} PlaceFolderPicker.init(id); }, uninit : function() { PlaceFolderPicker.uninit(); }, ok : function() { Services.prefs.setIntPref("extensions.test.folderid", PlaceFolderPicker.id); PlaceFolderPicker.save(); return true; }, cancel : function() { return true; }, }; }} **PlaceFolderPicker.xul dialog.xulをoverlayする。 editBookmarkOverlay.xul からかなりコピーしてる。 #highlight(xml){ <?xml version="1.0"?> <?xml-stylesheet href="chrome://browser/skin/places/places.css"?> <?xml-stylesheet href="chrome://browser/content/places/places.css"?> <?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?> <?xml-stylesheet href="PlaceFolderPicker.css"?> <!DOCTYPE overlay [ <!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd"> %editBookmarkOverlayDTD; ]> <overlay id="PlaceFolderPicker" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <script src="PlaceFolderPicker.js"/> <!-- browser/omni.ja/chrome/browser/content/browser/places/editBookmarkOverlay.xul --> <grid id="PlaceFolderPickerContent" flex="1"> <columns><column/><column flex="1"/></columns> <rows> <row id="PFP_folderRow" align="center"> <label value="&editBookmarkOverlay.folder.label;" control="PFP_folderMenuList"/> <hbox flex="1"> <menulist id="PFP_folderMenuList" flex="1" class="folder-icon" oncommand="PlaceFolderPicker.onFolderMenuListCommand(event);"> <menupopup> <menuitem id="PFP_toolbarFolderItem" class="menuitem-iconic folder-icon"/> <menuitem id="PFP_bmRootItem" class="menuitem-iconic folder-icon"/> <menuitem id="PFP_unfiledRootItem" class="menuitem-iconic folder-icon"/> <menuseparator id="PFP_chooseFolderSeparator"/> <menuitem id="PFP_chooseFolderMenuItem" label='&editBookmarkOverlay.choose.label;' class="menuitem-iconic folder-icon"/> <menuseparator id="PFP_foldersSeparator"/> </menupopup> </menulist> <button id="PFP_foldersExpander" class="expander-down" tooltiptext="&editBookmarkOverlay.foldersExpanderDown.tooltip;" tooltiptextdown="&editBookmarkOverlay.foldersExpanderDown.tooltip;" tooltiptextup="&editBookmarkOverlay.expanderUp.tooltip;" oncommand="PlaceFolderPicker.toggleFolderTreeVisibility();"/> </hbox> </row> <row id="PFP_folderTreeRow" collapsed="true" flex="1"> <spacer/> <vbox flex="1"> <tree id="PFP_folderTree" flex="1" class="placesTree" type="places" height="150" minheight="150" editable="true" hidecolumnpicker="true" onselect="PlaceFolderPicker.onFolderTreeSelect();"> <treecols> <treecol anonid="title" flex="1" primary="true" hideheader="true"/> </treecols> <treechildren flex="1"/> </tree> <hbox> <button id="PFP_newFolderButton" label="&editBookmarkOverlay.newFolderButton.label;" accesskey="&editBookmarkOverlay.newFolderButton.accesskey;" oncommand="PlaceFolderPicker.newFolder();"/> </hbox> </vbox> </row> </rows> </grid> </overlay>} **PlaceFolderPicker.js editBookmarkOverlay.js からコピーしまくり #highlight(javascript){{ // browser/omni.ja/chrome/browser/content/browser/places/editBookmarkOverlay.js // Cu.import("resource://gre/modules/PlacesUtils.jsm"); // Cu.import("resource:///modules/PlacesUIUtils.jsm"); // Cu.import("resource://gre/modules/debug.js"); // 最近使用したフォルダのアノテーションマーク。↓はブックマーク編集パネルのと同じ。 // 共有したくなければ独自のを設定すればいい。 const LAST_USED_ANNO = "bookmarkPropertiesDialog/folderLastUsed"; // 最近使用したフォルダの表示数 const MAX_FOLDER_ITEM_IN_MENU_LIST = 5; var PlaceFolderPicker = { init : function PFP_init(id) { this.id = id; try { if (id === undefined) throw new Error('id が指定されていません。'); if (id == PlacesUtils.placesRootId) throw new Error('Placesルートフォルダは指定出来ません。'); if (id == PlacesUtils.tagsFolderId) throw new Error('タグフォルダは指定出来ません。'); let bms = PlacesUtils.bookmarks; let type = bms.getItemType(this.id); if (type != bms.TYPE_FOLDER) throw new Error('指定の id はフォルダではありません。id:' + this.id + '/type:' + type); //bms.getFolderReadonlyはfirefox36で削除された模様 //if (bms.getFolderReadonly(this.id)) // throw new Error('フォルダが読み込み専用です。id:' + this.id); } catch(e) { console.error(e); this.id = PlacesUtils.bookmarksMenuFolderId; // デフォルトのフォルダ } this._folderMenuList = this._element("folderMenuList"); this._folderTree = this._element("folderTree"); this._initFolderMenuList(); // observe changes PlacesUtils.bookmarks.addObserver(this, false); }, uninit : function PFP_uninit(save) { PlacesUtils.bookmarks.removeObserver(this); }, save : function() { // 現在のフォルダを最近使用したフォルダとしてマークアップ(特殊フォルダは除く) if (this.id != PlacesUtils.unfiledBookmarksFolderId && this.id != PlacesUtils.toolbarFolderId && this.id != PlacesUtils.bookmarksMenuFolderId) this._markFolderAsRecentlyUsed(this.id); }, _element : function(id) { return window.document.getElementById("PFP_" + id); }, _initFolderMenuList: function PFP__initFolderMenuList() { const bms = PlacesUtils.bookmarks; const annos = PlacesUtils.annotations; // 初期リストの設定 { let unfiledItem = this._element("unfiledRootItem"); unfiledItem.label = bms.getItemTitle(PlacesUtils.unfiledBookmarksFolderId); unfiledItem.folderId = PlacesUtils.unfiledBookmarksFolderId; let bmMenuItem = this._element("bmRootItem"); bmMenuItem.label = bms.getItemTitle(PlacesUtils.bookmarksMenuFolderId); bmMenuItem.folderId = PlacesUtils.bookmarksMenuFolderId; let toolbarItem = this._element("toolbarFolderItem"); toolbarItem.label = bms.getItemTitle(PlacesUtils.toolbarFolderId); toolbarItem.folderId = PlacesUtils.toolbarFolderId; } // 最近使用したフォルダのリストを取得 var folderIds = annos.getItemsWithAnnotation(LAST_USED_ANNO); // リストをソート this._recentFolders = []; for (let i = 0; i < folderIds.length; i++) { let lastUsed = annos.getItemAnnotation(folderIds[i], LAST_USED_ANNO); this._recentFolders.push({ folderId: folderIds[i], lastUsed: lastUsed }); } this._recentFolders.sort(function(a, b) { if (b.lastUsed < a.lastUsed) return -1; if (b.lastUsed > a.lastUsed) return 1; return 0; }); // 既定の数だけメニューに追加 var numberOfItems = Math.min(MAX_FOLDER_ITEM_IN_MENU_LIST, this._recentFolders.length); for (let i = 0; i < numberOfItems; i++) { this._appendFolderItemToMenupopup(this._recentFolders[i].folderId); } // 現在のフォルダをデフォルト選択 var defaultItem = this._getFolderMenuItem(this.id); this._folderMenuList.selectedItem = defaultItem; // 特殊フォルダアイコン表示のため、menulist要素に独自属性を設定する this._folderMenuList.setAttribute("selectedId", defaultItem.id ); }, _appendFolderItemToMenupopup : function PFP__appendFolderItemToMenuList(aFolderId) { var folderMenuItem = window.document.createElement("menuitem"); var folderTitle = PlacesUtils.bookmarks.getItemTitle(aFolderId) folderMenuItem.folderId = aFolderId; folderMenuItem.setAttribute("label", folderTitle); folderMenuItem.className = "menuitem-iconic folder-icon append"; this._folderMenuList.menupopup.appendChild(folderMenuItem); return folderMenuItem; }, _getFolderMenuItem : function PFP__getFolderMenuItem(aFolderId) { var menupopup = this._folderMenuList.menupopup; for (let i = 0; i < menupopup.childNodes.length; i++) { if ("folderId" in menupopup.childNodes[i] && menupopup.childNodes[i].folderId == aFolderId) return menupopup.childNodes[i]; } // 最近使用したフォルダが規定の数かそれ以上の場合、1個削除 var appendMenu = menupopup.getElementsByClassName("append"); if (appendMenu.length >= MAX_FOLDER_ITEM_IN_MENU_LIST) menupopup.removeChild(menupopup.lastChild); return this._appendFolderItemToMenupopup(aFolderId); }, onFolderMenuListCommand : function PFP_onFolderMenuListCommand(aEvent) { if (aEvent.target.id == "PFP_chooseFolderMenuItem") { // リストメニューの選択状態を元に戻し、ツリーを表示する let item = this._getFolderMenuItem(this.id); this._folderMenuList.selectedItem = item; setTimeout(function(self) self.toggleFolderTreeVisibility(), 100, this); return; } // 現在のフォルダidを更新する this.id = this._getFolderIdFromMenuList(); // 特殊フォルダアイコン表示のためのmenulist要素の独自属性を更新する this._folderMenuList.setAttribute("selectedId", this._folderMenuList.selectedItem.id ); // フォルダツリーを更新する var folderTreeRow = this._element("folderTreeRow"); if (!folderTreeRow.collapsed) { var selectedNode = this._folderTree.selectedNode; if (!selectedNode || PlacesUtils.getConcreteItemId(selectedNode) != this.id) this._folderTree.selectItems([this.id]); } }, _getFolderIdFromMenuList : function PFP__getFolderIdFromMenuList() { var selectedItem = this._folderMenuList.selectedItem; NS_ASSERT("folderId" in selectedItem, "Invalid menuitem in the folders-menulist"); return selectedItem.folderId; }, toggleFolderTreeVisibility : function PFP_toggleFolderTreeVisibility() { var expander = this._element("foldersExpander"); var folderTreeRow = this._element("folderTreeRow"); if (!folderTreeRow.collapsed) { expander.className = "expander-down"; expander.setAttribute("tooltiptext", expander.getAttribute("tooltiptextdown")); folderTreeRow.collapsed = true; this._element("chooseFolderSeparator").hidden = this._element("chooseFolderMenuItem").hidden = false; } else { expander.className = "expander-up" expander.setAttribute("tooltiptext", expander.getAttribute("tooltiptextup")); folderTreeRow.collapsed = false; const FOLDER_TREE_PLACE_URI = "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&folder=" + PlacesUIUtils.allBookmarksFolderId; this._folderTree.place = FOLDER_TREE_PLACE_URI; this._element("chooseFolderSeparator").hidden = this._element("chooseFolderMenuItem").hidden = true; this._folderTree.selectItems([this.id]); this._folderTree.focus(); } window.sizeToContent(); }, onFolderTreeSelect : function PFP_onFolderTreeSelect() { var selectedNode = this._folderTree.selectedNode; // Disable the "New Folder" button if we cannot create a new folder this._element("newFolderButton") .disabled = !this._folderTree.insertionPoint || !selectedNode; if (!selectedNode) return; var folderId = PlacesUtils.getConcreteItemId(selectedNode); if (this.id == folderId) return; var folderItem = this._getFolderMenuItem(folderId); this._folderMenuList.selectedItem = folderItem; folderItem.doCommand(); }, _markFolderAsRecentlyUsed : function PFP__markFolderAsRecentlyUsed(aFolderId) { var txns = []; // Expire old unused recent folders var anno = this._getLastUsedAnnotationObject(false); while (this._recentFolders.length > MAX_FOLDER_ITEM_IN_MENU_LIST) { var folderId = this._recentFolders.pop().folderId; let annoTxn = new PlacesSetItemAnnotationTransaction(folderId, anno); txns.push(annoTxn); } // Mark folder as recently used anno = this._getLastUsedAnnotationObject(true); let annoTxn = new PlacesSetItemAnnotationTransaction(aFolderId, anno); txns.push(annoTxn); let aggregate = new PlacesAggregatedTransaction("Update last used folders", txns); PlacesUtils.transactionManager.doTransaction(aggregate); }, /** * Returns an object which could then be used to set/unset the * LAST_USED_ANNO annotation for a folder. * * @param aLastUsed * Whether to set or unset the LAST_USED_ANNO annotation. * @returns an object representing the annotation which could then be used * with the transaction manager. */ _getLastUsedAnnotationObject : function PFP__getLastUsedAnnotationObject(aLastUsed) { return { name: LAST_USED_ANNO, type: Ci.nsIAnnotationService.TYPE_INT32, flags: 0, value: aLastUsed ? new Date().getTime() : null, expires: Ci.nsIAnnotationService.EXPIRE_NEVER, }; }, newFolder: function PFP_newFolder() { var ip = this._folderTree.insertionPoint; // default to the bookmarks menu folder if (!ip || ip.itemId == PlacesUIUtils.allBookmarksFolderId) { ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId, PlacesUtils.bookmarks.DEFAULT_INDEX, Ci.nsITreeView.DROP_ON); } // XXXmano: add a separate "New Folder" string at some point... var defaultLabel = this._element("newFolderButton").label; var txn = new PlacesCreateFolderTransaction(defaultLabel, ip.itemId, ip.index); PlacesUtils.transactionManager.doTransaction(txn); this._folderTree.focus(); this._folderTree.selectItems([ip.itemId]); PlacesUtils.asContainer(this._folderTree.selectedNode).containerOpen = true; this._folderTree.selectItems([this._lastNewItem]); this._folderTree.startEditing(this._folderTree.view.selection.currentIndex, this._folderTree.columns.getFirstColumn()); }, // nsINavBookmarkObserver onItemChanged : function PFP_onItemChanged(aItemId, aProperty, aIsAnnotationProperty, aValue, aLastModified, aItemType) { if (aProperty == "title" && aItemType == PlacesUtils.bookmarks.TYPE_FOLDER) { // If the title of a folder which is listed within the folders // menulist has been changed, we need to update the label of its // representing element. var menupopup = this._folderMenuList.menupopup; for (let i = 0; i < menupopup.childNodes.length; i++) { if ("folderId" in menupopup.childNodes[i] && menupopup.childNodes[i].folderId == aItemId) { menupopup.childNodes[i].label = aValue; break; } } } return; }, onItemAdded: function PFP_onItemAdded(aItemId) { this._lastNewItem = aItemId; }, onItemMoved: function() { }, onItemRemoved: function() { }, onBeginUpdateBatch: function() { }, onEndUpdateBatch: function() { }, onItemVisited: function() { }, }; }} **PlaceFolderPicker.css #highlight(css){{ /* browser/omni.ja/chrome/browser/skin/classic/browser/places/editBookmarkOverlay.css */ /**** folder menulist ****/ .folder-icon > .menulist-label-box > .menulist-icon { width: 16px; height: 16px; } .folder-icon > .menu-iconic-left { display: -moz-box; } .folder-icon { list-style-image: url("chrome://global/skin/icons/folder-item.png") !important; -moz-image-region: rect(0px, 32px, 16px, 16px) !important; } /**** expanders ****/ .expander-up, .expander-down { min-width: 0; margin: 0; -moz-margin-end: 4px; } .expander-up > .button-box, .expander-down > .button-box { padding: 0; } .expander-up { list-style-image: url("chrome://global/skin/icons/collapse.png"); } .expander-down { list-style-image: url("chrome://global/skin/icons/expand.png"); } #PFP_folderTree { margin-top: 2px; margin-bottom: 2px; } /* editBookmarkOverlay.js で javascript で処理していた セパレーターの非表示処理はCSSで可能 */ #PFP_foldersSeparator:last-child { display: none; } /* ::::: dropdown icons ::::: */ #PFP_folderMenuList[selectedId="PFP_toolbarFolderItem"], #PFP_toolbarFolderItem { list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png") !important; -moz-image-region: auto !important; } #PFP_folderMenuList[selectedId="PFP_bmRootItem"], #PFP_bmRootItem { list-style-image: url("chrome://browser/skin/places/bookmarksMenu.png") !important; -moz-image-region: auto !important; } #PFP_folderMenuList[selectedId="PFP_unfiledRootItem"], #PFP_unfiledRootItem { list-style-image: url("chrome://browser/skin/places/unsortedBookmarks.png") !important; -moz-image-region: auto !important; } }}

表示オプション

横に並べて表示:
変化行の前後のみ表示: