├── .gitignore ├── chrome ├── locale │ ├── ja │ │ ├── browser.properties │ │ ├── errors.properties │ │ ├── star.dtd │ │ ├── urlEditor.properties │ │ ├── toolbar.properties │ │ ├── commentViewer.properties │ │ ├── star.properties │ │ ├── config.properties │ │ ├── selectTagDialog.dtd │ │ ├── urlEditor.dtd │ │ ├── embed.properties │ │ ├── toolbar.dtd │ │ ├── addPanel.properties │ │ ├── popups.properties │ │ ├── popups.dtd │ │ ├── sidebar.dtd │ │ ├── browser.dtd │ │ └── addPanel.dtd │ └── en-US │ │ ├── browser.properties │ │ ├── star.dtd │ │ ├── errors.properties │ │ ├── urlEditor.properties │ │ ├── toolbar.properties │ │ ├── commentViewer.properties │ │ ├── star.properties │ │ ├── config.properties │ │ ├── selectTagDialog.dtd │ │ ├── urlEditor.dtd │ │ ├── embed.properties │ │ ├── toolbar.dtd │ │ ├── addPanel.properties │ │ ├── popups.dtd │ │ ├── popups.properties │ │ ├── sidebar.dtd │ │ └── browser.dtd ├── content │ ├── browser │ │ ├── 07-TabObserver.js │ │ ├── 00_core.js │ │ ├── 12-Star-Creator.js │ │ ├── 10-SyncProgressBar.js │ │ ├── 21-Recent-Button.js │ │ ├── 06-Uninstall.js │ │ ├── 40-UserGuide.js │ │ ├── 13-Star-Palette.js │ │ ├── 05-Migration.js │ │ ├── 14-Star-AddButton.js │ │ ├── 13-Star-Operator.js │ │ ├── 20-AddPanelManager.js │ │ ├── 25-ContextMenu.js │ │ ├── 13-Star-Loader.js │ │ ├── 12-Star-Command.js │ │ ├── 25-LinkClickOverlay.js │ │ └── 40-ShortCut.js │ ├── popups.css │ ├── widgets.css │ ├── addPanel.linux.css │ ├── sidebar │ │ ├── sidebarBindings.css │ │ ├── searchbar.css │ │ ├── bookmarkTreeContext.xul │ │ ├── 10-BookmarkTreeContext.js │ │ └── 20-SidebarManager.js │ ├── tests │ │ ├── test-database.html │ │ ├── test-remote-command.html │ │ ├── test.html │ │ └── test-database.js │ ├── common │ │ ├── 10-Strings.js │ │ ├── 08-hOpenUILink.js │ │ ├── 10-thread.js │ │ ├── 50-WebProgressListenerPrototype.js │ │ ├── 05-delegator.js │ │ ├── 02-utils.js │ │ ├── 50-tree-view.js │ │ ├── 50-BookmarkContext.js │ │ └── 05-HTMLDocumentCreator.js │ ├── toolbar.css │ ├── addPanel │ │ ├── 20-shortcutkey.js │ │ ├── 10-FavoriteBookmark.js │ │ ├── 20-TitleGuesser.js │ │ └── 20-URLSuggestion.js │ ├── index.html │ ├── selectTagDialog │ │ ├── 20-SelectTagDialogManager.js │ │ └── 10-TagList.js │ ├── addPanel.css │ ├── widgets.xml │ ├── selectTagDialog.xul │ ├── addPanel.xul │ ├── popups.xml │ ├── urlEditor │ │ └── 05-urlEditor.js │ ├── urlEditor.xul │ ├── autoloader.js │ └── popupsOverlay.xul ├── skin │ └── classic │ │ ├── images │ │ ├── b.gif │ │ ├── edit.gif │ │ ├── page.gif │ │ ├── star.gif │ │ ├── b_add.gif │ │ ├── b_link.gif │ │ ├── buttons.png │ │ ├── caution.png │ │ ├── close.gif │ │ ├── favicon.ico │ │ ├── hints.gif │ │ ├── search.png │ │ ├── 10-users.gif │ │ ├── b_comment.gif │ │ ├── counter_0.png │ │ ├── counter_1.png │ │ ├── counter_2.png │ │ ├── counter_3.png │ │ ├── counter_4.png │ │ ├── counter_5.png │ │ ├── counter_6.png │ │ ├── counter_7.png │ │ ├── counter_8.png │ │ ├── counter_9.png │ │ ├── favicon32.png │ │ ├── icon-mail.png │ │ ├── icon-mixi.png │ │ ├── logo-text.png │ │ ├── noimages.png │ │ ├── search_on.png │ │ ├── star-blue.gif │ │ ├── star-red.gif │ │ ├── star-temp.gif │ │ ├── add-private.png │ │ ├── add-twitter.png │ │ ├── b_add_16x16.gif │ │ ├── icon-twitter.png │ │ ├── nbookmarks.png │ │ ├── star-green.gif │ │ ├── star-loading.gif │ │ ├── star-palette.png │ │ ├── star-purple.gif │ │ ├── transparent.png │ │ ├── view-comment.gif │ │ ├── add-read-later.png │ │ ├── close-comment.png │ │ ├── icon-evernote.png │ │ ├── icon-facebook.png │ │ ├── b-comment-balloon.png │ │ ├── b_comment_loading.gif │ │ ├── comment_loading.gif │ │ ├── comment_loading.png │ │ ├── config_search_top.png │ │ ├── counter_disable.png │ │ ├── counter_ignored.png │ │ ├── searchbar-icons.png │ │ ├── star-add-button.gif │ │ ├── option-help-marker.png │ │ ├── config_search_bottom.png │ │ ├── comment-viewer-toggle-on.png │ │ └── comment-viewer-toggle-off.png │ │ ├── browser.osx.css │ │ ├── selectTagDialog.css │ │ ├── vimperator.css │ │ ├── config.css │ │ ├── sidebar.css │ │ ├── searchbar.css │ │ ├── commentViewer.css │ │ ├── toolbar.osx.css │ │ └── star.css └── icons │ └── default │ ├── hBookmarkAddPanelWindow.ico │ └── hBookmarkAddPanelWindow.xpm ├── tests └── javascripts │ ├── data │ ├── test.null.data │ ├── naoya.search.data │ └── secondlife.search.data │ ├── Strings.test.properties │ ├── btil.js │ ├── autoloader-test │ └── 01_foo.js │ ├── TagSearch-component.test.js │ ├── Strings.test.js │ ├── UserUtils.test.js │ ├── event-service-test.xul │ ├── FavoriteBookmark.test.js │ ├── sidebar-xul.test.js │ ├── delegator.test.js │ ├── selector2xpath.js │ ├── HTMLDocumentCreator.js │ ├── bookmarktreeview.test.js │ ├── TagContext.test.js │ ├── autoloader.test.js │ ├── SiteInfo.js │ ├── utils.test.js │ ├── prefs.test.js │ ├── tagtreeview.test.js │ ├── URLSuggestion.js │ ├── ExpireCache.test.js │ ├── suffixarray.test.js │ ├── URLNormalizer.js │ └── utils-module.test.js ├── .gitmodules ├── resources ├── css │ ├── widget-embedder.css │ └── search-embedder.css └── modules │ ├── 55-UserUtils.js │ ├── 20-URLNormalizer.jsm │ ├── 30-SiteInfoSet-Search.jsm │ ├── 71-FullTextSearch.js │ ├── 53-Prefs.js │ ├── 65-Model.js │ └── 21-SiteInfoSet-HatenaStar-Converter.jsm ├── README.markdown ├── license-ja.txt ├── chrome.manifest ├── install.rdf └── searchplugins └── hatenabookmark.opensearch.xml /.gitignore: -------------------------------------------------------------------------------- 1 | xpi/ 2 | 3 | -------------------------------------------------------------------------------- /chrome/locale/ja/browser.properties: -------------------------------------------------------------------------------- 1 | counter.ignoredLabel = \u0020\u0020\u0020- 2 | -------------------------------------------------------------------------------- /chrome/locale/ja/errors.properties: -------------------------------------------------------------------------------- 1 | failToDeleteBookmarks = 以下のブックマークの削除に失敗しました。 2 | -------------------------------------------------------------------------------- /chrome/locale/en-US/browser.properties: -------------------------------------------------------------------------------- 1 | counter.ignoredLabel = \u0020\u0020\u0020- 2 | -------------------------------------------------------------------------------- /chrome/content/browser/07-TabObserver.js: -------------------------------------------------------------------------------- 1 | 2 | var EXPORT = ['TabObserver']; 3 | 4 | 5 | -------------------------------------------------------------------------------- /chrome/locale/ja/star.dtd: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/javascripts/data/test.null.data: -------------------------------------------------------------------------------- 1 | hoge 123 foo bar tarao 2 | hage 234 goo car tarao 3 | -------------------------------------------------------------------------------- /chrome/locale/en-US/star.dtd: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/javascripts/Strings.test.properties: -------------------------------------------------------------------------------- 1 | hello=Hello, world! 2 | helloSomething=Hello, %S world! 3 | -------------------------------------------------------------------------------- /chrome/locale/en-US/errors.properties: -------------------------------------------------------------------------------- 1 | failToDeleteBookmarks = Unable to delete the following bookmarks. 2 | -------------------------------------------------------------------------------- /tests/javascripts/btil.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/tests/javascripts/btil.js -------------------------------------------------------------------------------- /chrome/locale/en-US/urlEditor.properties: -------------------------------------------------------------------------------- 1 | regexError = url regex invalid. 2 | defaultConfirm = Are you sure? 3 | 4 | -------------------------------------------------------------------------------- /chrome/locale/ja/urlEditor.properties: -------------------------------------------------------------------------------- 1 | regexError = 入力された正規表現にエラーがあります。 2 | defaultConfirm = 設定を初期値に戻します。よろしいですか? 3 | -------------------------------------------------------------------------------- /chrome/content/popups.css: -------------------------------------------------------------------------------- 1 | #hBookmark-bookmark-tooltip { 2 | -moz-binding: url("popups.xml#bookmark-tooltip"); 3 | } 4 | -------------------------------------------------------------------------------- /chrome/skin/classic/images/b.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/b.gif -------------------------------------------------------------------------------- /tests/javascripts/autoloader-test/01_foo.js: -------------------------------------------------------------------------------- 1 | var EXPORT = ["Foo"]; 2 | 3 | var Foo = { baz: 42 }; 4 | var Bar = { baz: 31 }; 5 | -------------------------------------------------------------------------------- /chrome/skin/classic/images/edit.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/edit.gif -------------------------------------------------------------------------------- /chrome/skin/classic/images/page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/page.gif -------------------------------------------------------------------------------- /chrome/skin/classic/images/star.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/star.gif -------------------------------------------------------------------------------- /chrome/skin/classic/images/b_add.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/b_add.gif -------------------------------------------------------------------------------- /chrome/skin/classic/images/b_link.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/b_link.gif -------------------------------------------------------------------------------- /chrome/skin/classic/images/buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/buttons.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/caution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/caution.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/close.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/close.gif -------------------------------------------------------------------------------- /chrome/skin/classic/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/favicon.ico -------------------------------------------------------------------------------- /chrome/skin/classic/images/hints.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/hints.gif -------------------------------------------------------------------------------- /chrome/skin/classic/images/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/search.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/10-users.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/10-users.gif -------------------------------------------------------------------------------- /chrome/skin/classic/images/b_comment.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/b_comment.gif -------------------------------------------------------------------------------- /chrome/skin/classic/images/counter_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/counter_0.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/counter_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/counter_1.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/counter_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/counter_2.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/counter_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/counter_3.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/counter_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/counter_4.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/counter_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/counter_5.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/counter_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/counter_6.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/counter_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/counter_7.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/counter_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/counter_8.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/counter_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/counter_9.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/favicon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/favicon32.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/icon-mail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/icon-mail.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/icon-mixi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/icon-mixi.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/logo-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/logo-text.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/noimages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/noimages.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/search_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/search_on.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/star-blue.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/star-blue.gif -------------------------------------------------------------------------------- /chrome/skin/classic/images/star-red.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/star-red.gif -------------------------------------------------------------------------------- /chrome/skin/classic/images/star-temp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/star-temp.gif -------------------------------------------------------------------------------- /tests/javascripts/data/naoya.search.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/tests/javascripts/data/naoya.search.data -------------------------------------------------------------------------------- /chrome/content/widgets.css: -------------------------------------------------------------------------------- 1 | .hBookmark-link, .hBookmarkLink { 2 | -moz-binding: url("chrome://hatenabookmark/content/widgets.xml#link"); 3 | } 4 | -------------------------------------------------------------------------------- /chrome/locale/ja/toolbar.properties: -------------------------------------------------------------------------------- 1 | tagFilter.noTagTitle = はてなブックマーク 2 | tagFilter.noTagDescription = タグの付いたブックマークがありません。ブックマークにタグをつけてからお使いください。 3 | -------------------------------------------------------------------------------- /chrome/skin/classic/images/add-private.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/add-private.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/add-twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/add-twitter.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/b_add_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/b_add_16x16.gif -------------------------------------------------------------------------------- /chrome/skin/classic/images/icon-twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/icon-twitter.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/nbookmarks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/nbookmarks.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/star-green.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/star-green.gif -------------------------------------------------------------------------------- /chrome/skin/classic/images/star-loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/star-loading.gif -------------------------------------------------------------------------------- /chrome/skin/classic/images/star-palette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/star-palette.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/star-purple.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/star-purple.gif -------------------------------------------------------------------------------- /chrome/skin/classic/images/transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/transparent.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/view-comment.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/view-comment.gif -------------------------------------------------------------------------------- /chrome/content/browser/00_core.js: -------------------------------------------------------------------------------- 1 | 2 | //var EXPORT = ["init"]; 3 | // 4 | //var init = function() { 5 | //// hBookmark.Sync.all(); 6 | //}; 7 | 8 | -------------------------------------------------------------------------------- /chrome/skin/classic/images/add-read-later.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/add-read-later.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/close-comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/close-comment.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/icon-evernote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/icon-evernote.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/icon-facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/icon-facebook.png -------------------------------------------------------------------------------- /tests/javascripts/data/secondlife.search.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/tests/javascripts/data/secondlife.search.data -------------------------------------------------------------------------------- /chrome/icons/default/hBookmarkAddPanelWindow.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/icons/default/hBookmarkAddPanelWindow.ico -------------------------------------------------------------------------------- /chrome/skin/classic/browser.osx.css: -------------------------------------------------------------------------------- 1 | 2 | #urlbar[searchenabled="true"] { 3 | /* osx ... */ 4 | background-color: transparent !important; 5 | } 6 | -------------------------------------------------------------------------------- /chrome/skin/classic/images/b-comment-balloon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/b-comment-balloon.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/b_comment_loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/b_comment_loading.gif -------------------------------------------------------------------------------- /chrome/skin/classic/images/comment_loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/comment_loading.gif -------------------------------------------------------------------------------- /chrome/skin/classic/images/comment_loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/comment_loading.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/config_search_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/config_search_top.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/counter_disable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/counter_disable.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/counter_ignored.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/counter_ignored.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/searchbar-icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/searchbar-icons.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/star-add-button.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/star-add-button.gif -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "chrome/content/tests/submodule-qunit"] 2 | path = chrome/content/tests/submodule-qunit 3 | url = git://github.com/jquery/qunit.git 4 | -------------------------------------------------------------------------------- /chrome/skin/classic/images/option-help-marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/option-help-marker.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/config_search_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/config_search_bottom.png -------------------------------------------------------------------------------- /chrome/content/addPanel.linux.css: -------------------------------------------------------------------------------- 1 | checkbox[preference] { 2 | -moz-binding: url("chrome://hatenabookmark/content/addPanel.xml#preference-checkbox-with-spacing"); 3 | } 4 | -------------------------------------------------------------------------------- /chrome/locale/en-US/toolbar.properties: -------------------------------------------------------------------------------- 1 | tagFilter.noTagTitle = Hatena Bookmark 2 | tagFilter.noTagDescription = No bookmarks have been tagged. Please tag your bookmarks. 3 | -------------------------------------------------------------------------------- /chrome/skin/classic/images/comment-viewer-toggle-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/comment-viewer-toggle-on.png -------------------------------------------------------------------------------- /chrome/skin/classic/images/comment-viewer-toggle-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatena/hatena-bookmark-xul/HEAD/chrome/skin/classic/images/comment-viewer-toggle-off.png -------------------------------------------------------------------------------- /chrome/locale/ja/commentViewer.properties: -------------------------------------------------------------------------------- 1 | showAllBookmarksTooltip = すべてのブックマークを表示 2 | showCommentedBookmarksTooltip = コメント付きのブックマークのみを表示 3 | noBookmarkLabel = 表示できるブックマークコメントはありません。 4 | -------------------------------------------------------------------------------- /chrome/locale/en-US/commentViewer.properties: -------------------------------------------------------------------------------- 1 | showAllBookmarksTooltip = Show All Bookmarks 2 | showCommentedBookmarksTooltip = Show Bookmarks with a Comment 3 | noBookmarkLabel = There is no bookmark with a comment. 4 | -------------------------------------------------------------------------------- /chrome/content/sidebar/sidebarBindings.css: -------------------------------------------------------------------------------- 1 | @namespace "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; 2 | 3 | bookmarksearchbar { 4 | -moz-binding: url("chrome://hatenabookmark/content/sidebar/searchbar.xml#searchbar"); 5 | } 6 | -------------------------------------------------------------------------------- /chrome/locale/ja/star.properties: -------------------------------------------------------------------------------- 1 | starChar = \u2606 2 | coloredStarChar = \u2605 3 | addButtonLabel = [スターを追加] 4 | openQuote = \u201c 5 | closeQuote = \u201d 6 | starGuideURL = http://s.hatena.ne.jp/guide 7 | starShopURL = https://www.hatena.ne.jp/shop/star 8 | -------------------------------------------------------------------------------- /chrome/locale/en-US/star.properties: -------------------------------------------------------------------------------- 1 | starChar = \u2606 2 | coloredStarChar = \u2605 3 | addButtonLabel = [Add Star] 4 | openQuote = \u201c 5 | closeQuote = \u201d 6 | starGuideURL = http://s.hatena.ne.jp/guide 7 | starShopURL = https://www.hatena.ne.jp/shop/star 8 | -------------------------------------------------------------------------------- /chrome/skin/classic/selectTagDialog.css: -------------------------------------------------------------------------------- 1 | #tag-list-col-count { 2 | text-align: right; 3 | } 4 | 5 | #tag-list-col-count > .treecol-text { 6 | text-align: left; 7 | } 8 | 9 | #tag-list > treechildren::-moz-tree-cell(count) { 10 | padding-right: 5px; 11 | } 12 | -------------------------------------------------------------------------------- /chrome/content/sidebar/searchbar.css: -------------------------------------------------------------------------------- 1 | @namespace "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; 2 | 3 | .searchbar-textbox { 4 | -moz-binding: url("searchbar.xml#searchbar-textbox"); 5 | } 6 | 7 | .searchbar-engine-button { 8 | -moz-user-focus: none; 9 | } 10 | -------------------------------------------------------------------------------- /chrome/locale/ja/config.properties: -------------------------------------------------------------------------------- 1 | 2 | nowLogin = %S で現在ログイン中です 3 | reSyncNotice = すべて同期し直すにはしばらく時間がかかります。よろしいですか? 4 | deleteAllNotice = 削除してよろしいですか? 5 | loginToHatena = この操作には、はてなへのログインが必要です 6 | nowSyncing = 現在同期中です 7 | setShortcut = キーボードを押して、ショートカットを設定します 8 | cancel = キャンセル 9 | 10 | -------------------------------------------------------------------------------- /tests/javascripts/TagSearch-component.test.js: -------------------------------------------------------------------------------- 1 | function testGetTagSearchService() { 2 | let TagSearch = Cc["@mozilla.org/autocomplete/search;1?name=hbookmark-tag"]. 3 | getService(Ci.nsIAutoCompleteSearch); 4 | assert.isTrue(TagSearch instanceof Ci.nsIAutoCompleteSearch); 5 | } 6 | -------------------------------------------------------------------------------- /chrome/skin/classic/vimperator.css: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * XXX 4 | */ 5 | #hBookmark-status-count-container label { 6 | display: inline !important; 7 | opacity: 1 !important; 8 | cursor: pointer !important; 9 | } 10 | 11 | #hBookmark-status-count-container image { 12 | display: none !important; 13 | } 14 | -------------------------------------------------------------------------------- /chrome/locale/ja/selectTagDialog.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /chrome/locale/en-US/config.properties: -------------------------------------------------------------------------------- 1 | 2 | nowLogin = Logged in as %S 3 | reSyncNotice = Are you sure you want to synchronize your bookmarks? 4 | deleteAllNotice = Are you sure? 5 | loginToHatena = You need to login to Hatena first 6 | nowSyncing = Syncing Bookmarks… 7 | setShortcut = Please type a key from the keyboard to assign as a shortcut 8 | cancel = Cancel 9 | 10 | -------------------------------------------------------------------------------- /chrome/locale/ja/urlEditor.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /chrome/locale/en-US/selectTagDialog.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /chrome/locale/en-US/urlEditor.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /chrome/content/tests/test-database.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | はてなブックマーク — Tests — Database 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/javascripts/Strings.test.js: -------------------------------------------------------------------------------- 1 | var hBookmark; 2 | 3 | function warmUp() { 4 | utils.include("btil.js"); 5 | let g = loadAutoloader("chrome://hatenabookmark/content/unknown.js"); 6 | hBookmark = g.hBookmark; 7 | } 8 | 9 | function testGet() { 10 | let uriSpec = baseURL + "Strings.test.properties"; 11 | let strings = new hBookmark.Strings(uriSpec); 12 | assert.equals("Hello, world!", strings.get("hello")); 13 | assert.equals("Hello, Strings world!", 14 | strings.get("helloSomething", ["Strings"])); 15 | } 16 | -------------------------------------------------------------------------------- /chrome/content/common/10-Strings.js: -------------------------------------------------------------------------------- 1 | var EXPORT = ["Strings", "stringsGetter"]; 2 | 3 | function Strings(propertiesFile) { 4 | this._bundle = Cc["@mozilla.org/intl/stringbundle;1"] 5 | .getService(Ci.nsIStringBundleService) 6 | .createBundle(propertiesFile); 7 | } 8 | 9 | extend(Strings.prototype, { 10 | get: function Strings_get(name, args) { 11 | return args 12 | ? this._bundle.formatStringFromName(name, args, args.length) 13 | : this._bundle.GetStringFromName(name); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /chrome/content/common/08-hOpenUILink.js: -------------------------------------------------------------------------------- 1 | 2 | var EXPORT = ['hOpenUILink']; 3 | 4 | var hOpenUILink = function(link, ev) { 5 | let where = whereToOpenLink(ev); 6 | if (where === 'current' && Prefs.link.get('openInNewTab')) 7 | where = 'tab'; 8 | if ((where === "tab" || where === "tabshifted") && 9 | Prefs.bookmark.get("link.supportTreeStyleTab") && 10 | typeof TreeStyleTabService !== "undefined") 11 | TreeStyleTabService.readyToOpenChildTab(getTopWin().gBrowser.currentTab, false); 12 | return openUILinkIn(link, where); 13 | } 14 | -------------------------------------------------------------------------------- /chrome/skin/classic/config.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | #BrowserPreferences radio[pane] { 4 | list-style-image: none; 5 | -moz-image-region: rect(0px 0px 0px 0px); 6 | } 7 | 8 | .paneButtonIcon { 9 | display: none; 10 | } 11 | 12 | .paneButtonLabel { 13 | padding-top: 5px; 14 | padding-bottom: 5px; 15 | } 16 | 17 | checkbox[src] > .checkbox-label-box { 18 | -moz-box-align: center; 19 | } 20 | 21 | checkbox[src] > .checkbox-label-box > .checkbox-icon { 22 | -moz-margin-end: 5px; 23 | } 24 | 25 | /* 26 | .windowDialog, 27 | .windowDialog prefpane { 28 | padding: 1000px; 29 | } 30 | */ 31 | 32 | -------------------------------------------------------------------------------- /chrome/content/browser/12-Star-Creator.js: -------------------------------------------------------------------------------- 1 | 2 | function Creator(title, location) { 3 | this.title = title || ''; 4 | this.location = location || ''; 5 | } 6 | 7 | extend(Creator.prototype, { 8 | createForEntry: function SC_createForEntry(entry, highlight) { 9 | return Star.createStarsForEntry(entry, highlight, true, 10 | this.title, this.location); 11 | }, 12 | 13 | createPlaceholder: function SC_createPlaceholder(uri) { 14 | return Star.createPlaceholder(uri, this.title, this.location); 15 | }, 16 | }); 17 | 18 | Star.Creator = Creator; 19 | -------------------------------------------------------------------------------- /chrome/content/tests/test-remote-command.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | はてなブックマーク — Tests — RemoteCommand 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /chrome/content/toolbar.css: -------------------------------------------------------------------------------- 1 | #hBookmark-toolbar-recent-line { 2 | -moz-binding: url("chrome://hatenabookmark/content/toolbar.xml#recent-line"); 3 | } 4 | .hBookmark-toolbar-recent-line-container { 5 | overflow-x: hidden; 6 | } 7 | .hBookmark-toolbar-recent-popup { 8 | -moz-binding: url("chrome://hatenabookmark/content/toolbar.xml#recent-popup"); 9 | } 10 | .hBookmark-toolbar-recent-popup .menuitem-iconic .menu-iconic-left { 11 | display: -moz-box; 12 | visibility: visible; 13 | } 14 | .hBookmark-tag-filter-popup { 15 | -moz-binding: url("chrome://hatenabookmark/content/toolbar.xml#tag-filter-popup"); 16 | } 17 | -------------------------------------------------------------------------------- /resources/css/widget-embedder.css: -------------------------------------------------------------------------------- 1 | .hBookmark-widget { 2 | background: none !important; 3 | text-decoration: none !important; 4 | margin: 0 0 0 2px; 5 | border: none !important; 6 | display: inline !important; 7 | } 8 | .hBookmark-widget > img { 9 | border: none; 10 | vertical-align: middle; 11 | -moz-force-broken-image-icon: 1; 12 | } 13 | .hBookmark-widget.hBookmark-widget-add-button > img { 14 | width: 16px !important; 15 | height: 12px !important; 16 | } 17 | .hBookmark-widget.hBookmark-widget-comments > img { 18 | width: 14px !important; 19 | height: 13px !important; 20 | } 21 | -------------------------------------------------------------------------------- /chrome/locale/ja/embed.properties: -------------------------------------------------------------------------------- 1 | showEntryText = [はてなブックマークで表示] 2 | showEntryTitle = このエントリーをはてなブックマークで表示 3 | showCommentText = [コメントビューワーで表示] 4 | showCommentTitle = このエントリーをコメントビューワーで表示 5 | addBookmarkText = [はてなブックマークに追加] 6 | addBookmarkTitle = このエントリーをはてなブックマークに追加 7 | 8 | search.title = はてなブックマークからの検索 9 | # You can use these patterns: query, count, total, and elapsed. 10 | search.statusPattern = {total} 件中 {count} 件 ({elapsed} 秒) 11 | search.showAllLabel = 検索結果をすべて表示 \u00bb 12 | search.standbyLabel = はてなブックマークから検索しています... 13 | search.noMatchError = はてなブックマークには該当する結果が存在しませんでした 14 | search.unexpectedError = はてなブックマークから検索できませんでした 15 | -------------------------------------------------------------------------------- /resources/modules/55-UserUtils.js: -------------------------------------------------------------------------------- 1 | Components.utils.import("resource://hatenabookmark/modules/00-utils.jsm"); 2 | loadPrecedingModules.call(this); 3 | 4 | var EXPORTED_SYMBOLS = ["UserUtils"]; 5 | 6 | var UserUtils = { 7 | getProfileIcon: function UU_getProfileIcon(name, isLarge) { 8 | name = String(name); 9 | return sprintf('http://cdn1.www.st-hatena.com/users/%s/%s/profile%s.gif', 10 | name.substring(0, 2), name, isLarge ? '' : '_s'); 11 | }, 12 | getHomepage: function UU_getHomepage(name, service) { 13 | return sprintf('http://%s.hatena.ne.jp/%s/', service || 'www', name); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /chrome/content/addPanel/20-shortcutkey.js: -------------------------------------------------------------------------------- 1 | 2 | function setShortcutKey() { 3 | let win = getTopWin(); 4 | let key = win.document.getElementById("hBookmark-key-comment"); 5 | if (!key) return; 6 | key = document.importNode(key, false); 7 | let command = key.getAttribute("oncommand"); 8 | command = command.replace(/\bhBookmark\b/g, "getTopWin().hBookmark"); 9 | key.setAttribute("oncommand", command); 10 | let keyset = document.createElementNS(hBookmark.XUL_NS, "keyset"); 11 | keyset.appendChild(key); 12 | document.documentElement.appendChild(keyset); 13 | } 14 | 15 | window.addEventListener("load", setShortcutKey, false); 16 | -------------------------------------------------------------------------------- /chrome/skin/classic/sidebar.css: -------------------------------------------------------------------------------- 1 | /* 2 | #tag-tree > treechildren::-moz-tree-image(tag) { 3 | list-style-image: url("chrome://hatenabookmark/skin/images/buttons.png") !important; 4 | -moz-image-region: rect(46px 196px 62px 180px); 5 | -moz-margin-end: 3px; 6 | } 7 | */ 8 | 9 | #hBookmarkTagTree_count { 10 | text-align: right; 11 | } 12 | 13 | #hBookmarkTagTree_count > .treecol-text { 14 | text-align: left; 15 | } 16 | 17 | #tag-tree > treechildren::-moz-tree-cell(count) { 18 | padding-right: 5px; 19 | } 20 | 21 | #bookmark-tree > treechildren::-moz-tree-image { 22 | width: 16px; 23 | height: 16px; 24 | -moz-margin-end: 5px; 25 | } 26 | -------------------------------------------------------------------------------- /chrome/content/common/10-thread.js: -------------------------------------------------------------------------------- 1 | 2 | // (function() { 3 | // p('loaded'); 4 | // // var Thread = function { 5 | // // return ThreadManager.newThread(0); 6 | // // }; 7 | // let fib = (function() { 8 | // let i = 0, j = 0; 9 | // return function() { 10 | // p(i); 11 | // let t = i; 12 | // i = j; 13 | // j += t; 14 | // } 15 | // })(); 16 | // 17 | // 18 | // let t = ThreadManager.newThread(0); 19 | // let m = ThreadManager.mainThread; 20 | // do 21 | // { 22 | // m.processNextEvent(true); 23 | // fib(); 24 | // } 25 | // while (m.hasPendingEvents()); 26 | // 27 | // })(); 28 | 29 | 30 | -------------------------------------------------------------------------------- /tests/javascripts/UserUtils.test.js: -------------------------------------------------------------------------------- 1 | var hBookmark; 2 | 3 | function warmUp() { 4 | utils.include('btil.js'); 5 | let global = loadAutoloader('chrome://hatenabookmark/content/unknown.xul'); 6 | hBookmark = global.hBookmark; 7 | } 8 | 9 | function testUserUtils() { 10 | let UserUtils = hBookmark.UserUtils; 11 | assert.isDefined(UserUtils); 12 | assert.matches(/\/users\/fo\/foo\/profile_s\.gif$/, 13 | UserUtils.getProfileIcon('foo', false)); 14 | assert.equals('http://www.hatena.ne.jp/bar/', 15 | UserUtils.getHomepage('bar')); 16 | assert.equals('http://b.hatena.ne.jp/baz/', 17 | UserUtils.getHomepage('baz', 'b')); 18 | } 19 | -------------------------------------------------------------------------------- /chrome/locale/en-US/embed.properties: -------------------------------------------------------------------------------- 1 | showEntryText = [Show on Hatena Bookmark] 2 | showEntryTitle = Show This Entry on Hatena Bookmark 3 | showCommentText = [Show on CommentViewer] 4 | showCommentTitle = Show This Entry on CommentViewer 5 | addBookmarkText = [Add to Hatena Bookmark] 6 | addBookmarkTitle = Add This Entry to Hatena Bookmark 7 | 8 | search.title = Search in Hatena Bookmark 9 | # You can use these patterns: query, count, total, and elapsed. 10 | search.statusPattern = {count} of {total} ({elapsed} sec) 11 | search.showAllLabel = Show all search results \u00bb 12 | search.standbyLabel = Searching in Hatena Bookmark… 13 | search.noMatchError = No result in your Hatena Bookmark 14 | search.unexpectedError = Can't search in your Hatena Bookmark 15 | -------------------------------------------------------------------------------- /chrome/locale/ja/toolbar.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/javascripts/event-service-test.xul: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/javascripts/FavoriteBookmark.test.js: -------------------------------------------------------------------------------- 1 | var hBookmark; 2 | 3 | function warmUp() { 4 | utils.include("btil.js"); 5 | let global = loadAutoloader("chrome://hatenabookmark/content/addPanel.xml"); 6 | hBookmark = global.hBookmark; 7 | } 8 | 9 | function testFavoriteBookmark() { 10 | let f = { 11 | name: "anonymous", 12 | body: "my comment", 13 | tags: ["foo", "bar"] 14 | }; 15 | let fav = new hBookmark.FavoriteBookmark(f); 16 | 17 | assert.equals(f.name, fav.name); 18 | assert.matches(/\/users\/an\/anonymous\/profile_s\.gif$/, 19 | fav.getProfileIcon(false)); 20 | assert.equals("[foo][bar] my comment", fav.comment); 21 | 22 | let image = fav.createImage(); 23 | assert.isTrue(image.favorite === fav); 24 | } 25 | -------------------------------------------------------------------------------- /chrome/content/tests/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hatena Bookmark - Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /chrome/locale/en-US/toolbar.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/javascripts/sidebar-xul.test.js: -------------------------------------------------------------------------------- 1 | 2 | var _global = this; 3 | 4 | var doc; 5 | var win; 6 | 7 | function warmUp() { 8 | var helper = { utils: utils }; 9 | utils.include('btil.js', helper); 10 | helper.load(_global); 11 | 12 | var browserWin = utils.openTestWindow(); 13 | yield 700; 14 | // アラートをはさまないとオーバーレイが 15 | // 適用される前に次の処理に移ってしまう。 16 | // アラートをはさんでもタイミングによってはうまくいかない。なぜ? 17 | browserWin.alert('Are you ready?'); 18 | browserWin.toggleSidebar("viewHBookmarkSidebar", true); 19 | yield 300; 20 | doc = browserWin.document.getElementById("sidebar").contentDocument; 21 | win = doc.defaultView; 22 | } 23 | 24 | function coolDown() { 25 | utils.closeTestWindow(); 26 | } 27 | 28 | function testId() { 29 | assert.equals(doc.documentElement.id, "hBookmarkSidebar"); 30 | } 31 | -------------------------------------------------------------------------------- /chrome/content/sidebar/bookmarkTreeContext.xul: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /chrome/locale/ja/addPanel.properties: -------------------------------------------------------------------------------- 1 | title.add = ブックマークを追加 2 | title.edit = ブックマークを編集 3 | saveLabel.add = 追加 4 | saveLabel.edit = 保存 5 | 6 | # strftime format 7 | dateFormat = %Y/%m/%d %H:%M 8 | alreadyBookmarked = このエントリーは %S にブックマークしました 9 | 10 | # https://developer.mozilla.org/ja/Localization_and_Plurals を参照 11 | usersPluralRuleNum = 1 12 | usersLabel = #1 user;#1 users 13 | 14 | # first number is current comment length, second number is max comment length 15 | commentCounterLabel = %S / %S 16 | 17 | urlSuggestor.description.meta = ブックマークページをさらにブックマークしようとしています。 18 | urlSuggestor.acceptLabel.meta = 以下の URL に変更 19 | urlSuggestor.description.canonical = このページには別の URL が提示されています。 20 | urlSuggestor.acceptLabel.canonical = 以下の URL に変更 21 | urlSuggestor.description.default = このページには別の URL が提示されています。 22 | urlSuggestor.acceptLabel.default = 以下の URL に変更 23 | -------------------------------------------------------------------------------- /chrome/locale/ja/popups.properties: -------------------------------------------------------------------------------- 1 | tagContext.openBookmarksLabel = %S のブックマークを開く 2 | tagContext.openBookmarksKey = B 3 | tagContext.filterToolbarLabel = ツールバーを [%S] で絞り込む 4 | tagContext.filterToolbarKey = F 5 | tagContext.editTagLabel = タグ [%S] を編集... 6 | tagContext.editTagKey = E 7 | tagContext.renameLabel = [%S] を置換... 8 | tagContext.renameKey = R 9 | tagContext.deleteLabel = [%S] を削除 10 | tagContext.deleteKey = L 11 | tagContext.deleteBookmarksLabel = %S のブックマークを削除 12 | tagContext.deleteBookmarksKey = D 13 | 14 | prompt.title = はてなブックマーク 15 | prompt.confirmDeleteBookmarks = %S 件のブックマークを削除してもよろしいですか? 16 | prompt.encourageLogin = この操作を続けるためにはログインする必要があります。ログインしますか? 17 | prompt.editTagTitle = タグを編集 18 | prompt.renameDescription = %S の新しい名前を入力してください 19 | prompt.deleteDescription = %S を削除してもよろしいですか? 20 | 21 | tagError.invalidCharacter = タグに次の文字を使うことはできません: ? % / : [ ] 22 | tagError.tooLong = タグは %S バイト以下にしてください。(%S バイトが入力されました) 23 | -------------------------------------------------------------------------------- /tests/javascripts/delegator.test.js: -------------------------------------------------------------------------------- 1 | 2 | var _global = this; 3 | var hBookmark; 4 | 5 | function warmUp() { 6 | utils.include("btil.js"); 7 | var global = loadAutoloader("chrome://hatenabookmark/content/unknown.xul"); 8 | hBookmark = global.hBookmark; 9 | hBookmark.extend(_global, hBookmark); 10 | } 11 | 12 | 13 | function createObj() 14 | { 15 | let o = { 16 | foo: 'foo', 17 | get getterFoo() this.foo, 18 | set setterFoo(val) this.foo = val, 19 | getFoo: function() this.foo, 20 | }; 21 | return o; 22 | } 23 | 24 | function testDelegator() 25 | { 26 | let obj = createObj(); 27 | let d = new Delegator(obj, false); 28 | assert.equals(obj.foo, d.getFoo()); 29 | assert.notEquals(obj.foo, d.foo); 30 | 31 | d = new Delegator(obj); 32 | assert.equals(obj.foo, d.getFoo()); 33 | assert.equals(obj.foo, d.foo); 34 | assert.equals(obj.foo, d.getterFoo); 35 | } 36 | 37 | -------------------------------------------------------------------------------- /chrome/locale/ja/popups.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /chrome/content/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | HB — XUL 9 | 10 | 11 | 12 | 13 | 14 | 15 |

HB — XUL

16 |
検索
17 | 19 |
20 |

最近ブックマークしたエントリー

21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /chrome/content/common/50-WebProgressListenerPrototype.js: -------------------------------------------------------------------------------- 1 | var EXPORT = ["WebProgressListenerPrototype"]; 2 | 3 | var WebProgressListenerPrototype = { 4 | onLocationChange: function (progress, request, location) {}, 5 | onStateChange: function (progress, request, flags, status) {}, 6 | onProgressChange: function (progress, request, curSelf, maxSelf, 7 | curTotal, maxTotal) {}, 8 | onProgressChange64: function (progress, request, curSelf, maxSelf, 9 | curTotal, maxTotal) {}, 10 | onStatusChange: function (progress, request, status, message) {}, 11 | onSecurityChange: function (progress, request, state) {}, 12 | onRefreshAttempted: function (progress, refreshURI, millis, sameURI) true, 13 | QueryInterface: XPCOMUtils.generateQI([ 14 | Ci.nsIWebProgressListener, 15 | Ci.nsIWebProgressListener2, 16 | Ci.nsISupportsWeakReference, 17 | ]), 18 | }; 19 | -------------------------------------------------------------------------------- /chrome/locale/en-US/addPanel.properties: -------------------------------------------------------------------------------- 1 | title.add = Add Bookmark 2 | title.edit = Edit Bookmark 3 | saveLabel.add = Add 4 | saveLabel.edit = Save 5 | 6 | # strftime format 7 | dateFormat = %Y-%m-%d %H:%M 8 | alreadyBookmarked = You have bookmarked this entry at %S 9 | 10 | # See https://developer.mozilla.org/en/Localization_and_Plurals 11 | usersPluralRuleNum = 1 12 | usersLabel = #1 user;#1 users 13 | 14 | # First number is the current comment length, the second number is the max comment length 15 | commentCounterLabel = %S / %S 16 | 17 | urlSuggestor.description.meta = You are bookmarking a bookmark page. 18 | urlSuggestor.acceptLabel.meta = Change to the Following URL 19 | urlSuggestor.description.canonical = You can choose another URL to bookmark this page. 20 | urlSuggestor.acceptLabel.canonical = Change to the Following URL 21 | urlSuggestor.description.default = You can choose another URL to bookmark this page. 22 | urlSuggestor.acceptLabel.default = Change to the Following URL 23 | -------------------------------------------------------------------------------- /chrome/locale/en-US/popups.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /chrome/content/selectTagDialog/20-SelectTagDialogManager.js: -------------------------------------------------------------------------------- 1 | var EXPORT = ["SelectTagDialogManager"]; 2 | 3 | function SelectTagDialogManager(dialog) { 4 | this.dialog = dialog; 5 | this.dialog.manager = this; 6 | this.tagList = new TagList(document.getElementById("tag-list")); 7 | this.tagList.treeBody.addEventListener( 8 | "click", method(this, "onTagListClick"), false); 9 | this.argument = window.arguments[0] || {}; 10 | } 11 | 12 | extend(SelectTagDialogManager.prototype, { 13 | destroy: function STDM_destroy() { 14 | this.dialog.manager = null; 15 | this.dialog = null; 16 | this.tagList = null; 17 | }, 18 | 19 | onAccept: function STDM_onAccept(event) { 20 | this.argument.tag = this.tagList.selectedTag; 21 | return true; 22 | }, 23 | 24 | onTagListClick: function STDM_onTagListClick(event) { 25 | if (event.button === 0 && event.detail === 2) 26 | this.dialog.acceptDialog(); 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /chrome/skin/classic/searchbar.css: -------------------------------------------------------------------------------- 1 | @namespace "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; 2 | 3 | /* XXX Should be at a separated file? */ 4 | .search-go-container.mac { 5 | /* In XUL box model, min-width includes padding-end length. */ 6 | min-width: 14px; 7 | } 8 | 9 | .hBookmark-searchbar-mode-menuitem[selected="true"] { 10 | font-weight: bold; 11 | } 12 | 13 | .hBookmark-searchbar-mode-menuitem { 14 | list-style-image: url("chrome://hatenabookmark/skin/images/searchbar-icons.png"); 15 | } 16 | 17 | .hBookmark-searchbar-mode-menuitem[value="all"] { 18 | -moz-image-region: rect(0px, 16px, 16px, 0px); 19 | } 20 | .hBookmark-searchbar-mode-menuitem[value="comment"] { 21 | -moz-image-region: rect(0px, 32px, 16px, 16px); 22 | } 23 | .hBookmark-searchbar-mode-menuitem[value="title"] { 24 | -moz-image-region: rect(0px, 48px, 16px, 32px); 25 | } 26 | .hBookmark-searchbar-mode-menuitem[value="url"] { 27 | -moz-image-region: rect(0px, 64px, 16px, 48px); 28 | } 29 | -------------------------------------------------------------------------------- /tests/javascripts/selector2xpath.js: -------------------------------------------------------------------------------- 1 | 2 | Components.utils.import('resource://hatenabookmark/modules/21-SiteInfoSet-HatenaStar-Converter.jsm'); 3 | 4 | function testSelector2xpath() { 5 | assert.is('self::*', selector2xpath('parent')); 6 | assert.is('descendant::div[contains(concat(" ", normalize-space(@class), " "), " article ")]', 7 | selector2xpath('div.article')); 8 | assert.is('descendant::*[@id = "content"]', 9 | selector2xpath('#content')); 10 | assert.is('descendant::div/descendant::p/child::span', 11 | selector2xpath('div p > span')); 12 | assert.is('descendant::a[not(preceding-sibling::*)]', 13 | selector2xpath('a:first-child')); 14 | assert.is('descendant::p[contains(concat(" ", normalize-space(@class), " "), " foo ") and count(preceding-sibling::*) = 2]', 15 | selector2xpath('p.foo:nth-child(3)')); 16 | assert.is('descendant::div | descendant::p', 17 | selector2xpath('div, p')); 18 | } 19 | -------------------------------------------------------------------------------- /tests/javascripts/HTMLDocumentCreator.js: -------------------------------------------------------------------------------- 1 | var hBookmark; 2 | 3 | function warmUp() { 4 | utils.include("btil.js"); 5 | let g = loadAutoloader("chrome://hatenabookmark/content/unknown.xul"); 6 | hBookmark = g.hBookmark; 7 | } 8 | 9 | function testFromString() { 10 | let source = ' \ 11 | \n\ 12 | hello\ 13 | \ 14 | world \ 15 |

 

'; 16 | let doc = hBookmark.HTMLDocumentCreator.fromString(source); 17 | assert.isTrue(doc instanceof Ci.nsIDOMHTMLDocument); 18 | assert.is('hello', doc.title); 19 | assert.isTrue(doc.body instanceof Ci.nsIDOMHTMLBodyElement); 20 | assert.is(Ci.nsIDOMNode.TEXT_NODE, doc.body.firstChild.nodeType); 21 | assert.is('world', doc.body.firstChild.nodeValue.replace(/\s+/g, '')); 22 | let paras = doc.getElementsByTagName('p'); 23 | assert.is(1, paras.length); 24 | assert.is('\u00a0', paras[0].textContent); 25 | } 26 | -------------------------------------------------------------------------------- /tests/javascripts/bookmarktreeview.test.js: -------------------------------------------------------------------------------- 1 | var hBookmark; 2 | var view; 3 | var treeBox = { 4 | treeBody: {}, 5 | columns: { 6 | getFirstColumn: function () ({ 7 | element: { getAttribute: function () null } 8 | }) 9 | }, 10 | rowCountChanged: function () {}, 11 | invalidateRow: function () {}, 12 | invalidate: function () {}, 13 | getPageLength: function () 10 14 | }; 15 | 16 | function warmUp() { 17 | utils.include("btil.js"); 18 | var global = loadAutoloader("chrome://hatenabookmark/content/sidebar.xul"); 19 | hBookmark = global.hBookmark; 20 | } 21 | 22 | function setUp() { 23 | prepareDatabase(hBookmark); 24 | 25 | view = new hBookmark.BookmarkTreeView(); 26 | view.setTree(treeBox); 27 | } 28 | 29 | function testBookmarkTreeView() { 30 | assert.equals(view.rowCount, 3); 31 | 32 | view.showByTags(["Perl"]); 33 | assert.equals(view.rowCount, 2); 34 | 35 | view.showBySearchString("js"); 36 | assert.equals(view.rowCount, 1); 37 | } 38 | -------------------------------------------------------------------------------- /chrome/content/addPanel.css: -------------------------------------------------------------------------------- 1 | .hBookmarkAddPanel { 2 | -moz-binding: url("chrome://hatenabookmark/content/addPanel.xml#add-panel"); 3 | } 4 | 5 | .hBookmarkTagComplete { 6 | -moz-binding: url("chrome://hatenabookmark/content/addPanel.xml#tag-complete"); 7 | } 8 | .hBookmarkTagSelector { 9 | display: block; 10 | -moz-binding: url("chrome://hatenabookmark/content/addPanel.xml#tag-selector"); 11 | } 12 | .hBookmarkImageSelector { 13 | -moz-binding: url("chrome://hatenabookmark/content/addPanel.xml#image-selector"); 14 | } 15 | .hBookmarkURLSuggestor { 16 | -moz-binding: url("chrome://hatenabookmark/content/addPanel.xml#url-suggestor"); 17 | } 18 | #hBookmarkFavoriteBookmarkTooltip { 19 | -moz-binding: url("chrome://hatenabookmark/content/addPanel.xml#favorite-bookmark-tooltip"); 20 | } 21 | label[emptytext] { 22 | -moz-binding: url("chrome://hatenabookmark/content/addPanel.xml#placeholder-label"); 23 | } 24 | checkbox[preference] { 25 | -moz-binding: url("chrome://hatenabookmark/content/addPanel.xml#preference-checkbox"); 26 | } 27 | 28 | 29 | -------------------------------------------------------------------------------- /chrome/content/common/05-delegator.js: -------------------------------------------------------------------------------- 1 | 2 | var EXPORT = ['Delegator']; 3 | 4 | /* 5 | * メソッドの委譲を行う 6 | */ 7 | 8 | var Delegator = function(obj, propertyDelegate) { 9 | this.__target__obj = obj; 10 | if (typeof propertyDelegate == 'undefined') 11 | propertyDelegate = true; 12 | 13 | if (propertyDelegate) { 14 | this.__propertyDelegate__(); 15 | } 16 | }; 17 | 18 | Delegator.prototype = { 19 | get __getTarget__() this.__target__obj, 20 | __noSuchMethod__: function (method, args) { 21 | let target = this.__getTarget__; 22 | return target[method].apply(target, args); 23 | }, 24 | __propertyDelegate__: function() { 25 | // XXX: prototype チェインのプロパティはどうする? 26 | let target = this.__getTarget__; 27 | let addedKeys = []; 28 | for (let key in target) { 29 | if (typeof this[key] == 'undefined') { 30 | this[key] = target[key]; 31 | addedKeys.push(key); 32 | } 33 | } 34 | return addedKeys; 35 | } 36 | }; 37 | 38 | 39 | -------------------------------------------------------------------------------- /chrome/locale/en-US/popups.properties: -------------------------------------------------------------------------------- 1 | tagContext.openBookmarksLabel = Open %S Bookmarks 2 | tagContext.openBookmarksKey = B 3 | tagContext.filterToolbarLabel = Filter Toolbar with [%S] 4 | tagContext.filterToolbarKey = F 5 | tagContext.editTagLabel = Edit Tag [%S]… 6 | tagContext.editTagKey = E 7 | tagContext.renameLabel = Rename [%S]… 8 | tagContext.renameKey = R 9 | tagContext.deleteLabel = Delete [%S] 10 | tagContext.deleteKey = L 11 | tagContext.deleteBookmarksLabel = Delete %S Bookmarks 12 | tagContext.deleteBookmarksKey = D 13 | 14 | prompt.title = Hatena Bookmark 15 | prompt.confirmDeleteBookmarks = Are you sure you want to remove %S bookmarks? 16 | prompt.encourageLogin = You have to login to continue this operation. Would you like to login? 17 | prompt.editTagTitle = Edit Tag 18 | prompt.renameDescription = Please enter new name for %S. 19 | prompt.deleteDescription = Are you sure you want to delete %S? 20 | 21 | tagError.invalidCharacter = You can't use the following characters in your tag: ? % / : [ ] 22 | tagError.tooLong = Tag must be up to %S bytes. (You entered %S bytes.) 23 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # はてなブックマーク Firefox 拡張 2 | 3 | * 4 | * 5 | 6 | ## 開発者向け情報 7 | 8 | ### アドオンのインストール方法 9 | 10 | `Rakefile` に `install` および `uninstall` コマンドが書かれているのでこれを利用するのが良い。 11 | なお Firefox Developer Edition にインストールしたい場合や普段利用しているプロファイルとは別のものにインストールしたい場合は `NAME` を与えると良い。 12 | 13 | ```sh 14 | $ rake install 15 | $ NAME=dev-edition-default rake install # Firefox Developer Edition 16 | $ NAME=develop rake install # develop プロファイルにインストール 17 | ``` 18 | 19 | ### ブランチの使い方 20 | 21 | 永続的なブランチとして次の 2 つがあります。 22 | 23 | * master ブランチ 24 | * dev ブランチ 25 | 26 | 基本的な開発の流れは、dev ブランチからトピックブランチを切り、開発を進めてトピックブランチを 27 | dev ブランチにマージする、というものです。 28 | リリース時に dev ブランチを master ブランチにマージします。 29 | GitHub で pull request を送る際も、dev ブランチから新たにブランチを切り、dev 30 | ブランチ向けに pull request してください。 31 | 32 | ### テストについて 33 | 34 | 古いテストが tests 以下にあるが, うまく動かせないものが多いようである. 35 | 最近は QUnit を使って chrome/content/tests 以下にテストを書いているので, 36 | 今後テストを追加する場合はそちらに追加すること. 37 | 38 | テストを実行させるには, 開発用にソースコードから拡張をインストールした 39 | 状態で, 下記 URL にアクセスする. 40 | 41 | * chrome://hatenabookmark/content/tests/test.html 42 | -------------------------------------------------------------------------------- /chrome/content/widgets.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | %browser; 5 | ]> 6 | 9 | 10 | 12 | 13 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/javascripts/TagContext.test.js: -------------------------------------------------------------------------------- 1 | var hBookmark = null; 2 | 3 | function warmUp() { 4 | utils.include("btil.js"); 5 | let global = loadAutoloader("chrome://hatenabookmark/content/sidebar.xul"); 6 | hBookmark = global.hBookmark; 7 | } 8 | 9 | var lastOpenedURI = ""; 10 | function openUILinkIn(uri, where) { 11 | lastOpenedURI = uri; 12 | } 13 | 14 | function buildTagContext(tagContext, tags) { 15 | let originalPopupNode = document.popupNode; 16 | let popupNode = document.createElement('element'); 17 | popupNode.tags = tags; 18 | document.popupNode = popupNode; 19 | let result = tagContext.build(null); 20 | document.popupNode = originalPopupNode; 21 | return result; 22 | } 23 | 24 | // TagContext側でdocument.popupNodeを参照すると 25 | // セキュリティエラーになってしまう……。 26 | testOpenIn.priority = 'never'; 27 | function testOpenIn() { 28 | let ctx = new hBookmark.TagContext(); 29 | let result = buildTagContext(ctx, ["日本語", "tag"]); 30 | assert.isTrue(result); 31 | ctx.openIn('current'); 32 | assert.equals('http://b.hatena.ne.jp/' + hBookmark.User.user.name + 33 | '/%E6%97%A5%E6%9C%AC%E8%AA%9E/tag', 34 | lastOpenedURI); 35 | } 36 | -------------------------------------------------------------------------------- /chrome/content/addPanel/10-FavoriteBookmark.js: -------------------------------------------------------------------------------- 1 | var EXPORT = ["FavoriteBookmark"]; 2 | 3 | function FavoriteBookmark(favorite) { 4 | this._favorite = favorite; 5 | } 6 | 7 | extend(FavoriteBookmark.prototype, { 8 | get name() this._favorite.name, 9 | 10 | get comment() { 11 | let comment = ""; 12 | if (this._favorite.tags.length) 13 | comment = "[" + this._favorite.tags.join("][") + "]"; 14 | comment += (comment && " ") + this._favorite.body; 15 | return comment; 16 | }, 17 | 18 | getProfileIcon: function FB_getProfileIcon(isLarge) { 19 | return UserUtils.getProfileIcon(this._favorite.name, isLarge); 20 | }, 21 | 22 | getHomepage: function FB_getHomepage(service) { 23 | return UserUtils.getHomepage(this._favorite.name, service); 24 | }, 25 | 26 | createImage: function FB_createImage() { 27 | let image = document.createElementNS(XUL_NS, "image"); 28 | image.setAttribute("src", this.getProfileIcon(false)); 29 | image.setAttribute("onclick", "if (event.button < 2) hBookmark.hOpenUILink(this.favorite.getHomepage('b'), event);"); 30 | image.favorite = this; 31 | return image; 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /license-ja.txt: -------------------------------------------------------------------------------- 1 | 2 | 本ソフトウェアの著作権は、 3 | コードに特に明記がない物は株式会社はてなに帰属します。 4 | また、本ソフトウェアのライセンスは MIT License とします。 5 | 6 | ==== 7 | 8 | The MIT License 9 | 10 | Copyright (c) 2009–2013 Hatena Co., Ltd 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining a copy 13 | of this software and associated documentation files (the "Software"), to deal 14 | in the Software without restriction, including without limitation the rights 15 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | copies of the Software, and to permit persons to whom the Software is 17 | furnished to do so, subject to the following conditions: 18 | 19 | The above copyright notice and this permission notice shall be included in 20 | all copies or substantial portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | THE SOFTWARE. 29 | 30 | -------------------------------------------------------------------------------- /chrome/content/browser/10-SyncProgressBar.js: -------------------------------------------------------------------------------- 1 | 2 | var SyncProggressBar = { 3 | init: function SyncProggressBar_init () { 4 | if (this._inited) return; 5 | 6 | this._inited = true; 7 | 8 | let events = ['start', 'complete', 'fail', 'progress']; 9 | for (var i = 0; i < events.length; i++) { 10 | let eName = events[i]; 11 | Sync.createListener(eName, method(this, eName + 'Handler')); 12 | } 13 | //box.removeAttribute('hidden'); 14 | //meter.value = 0; 15 | //meter.value = i/len*100|0; 16 | //box.setAttribute('hidden', true); 17 | }, 18 | startHandler: function(e) { 19 | this.box.removeAttribute('hidden'); 20 | }, 21 | completeHandler: function(e) { 22 | this.box.setAttribute('hidden', true); 23 | }, 24 | failHandler: function(e) { 25 | this.box.setAttribute('hidden', true); 26 | }, 27 | progressHandler: function(e) { 28 | this.meter.setAttribute('value', e.data.value); 29 | }, 30 | }; 31 | elementGetter(SyncProggressBar, 'box', 'hBookmarkBroadcaster-syncbar', document); 32 | elementGetter(SyncProggressBar, 'meter', 'hBookmarkBroadcaster-sync', document); 33 | 34 | EventService.createListener('load', function() { 35 | SyncProggressBar.init(); 36 | }); 37 | 38 | -------------------------------------------------------------------------------- /chrome/content/browser/21-Recent-Button.js: -------------------------------------------------------------------------------- 1 | 2 | var EXPORT = ["RecentButton"]; 3 | 4 | elementGetter(this, 'subviewBody', 'PanelUI-hBookmark-recent-items', document); 5 | 6 | var RecentButton = { 7 | tag: "", 8 | get count() hBookmark.Prefs.bookmark.get('recentItemCount'), 9 | getBookmarks: function() { 10 | try { 11 | return hBookmark.Model.Bookmark.findRecent(this.count); 12 | } catch (ex) { 13 | return []; 14 | } 15 | }, 16 | createItem: function(bookmark) { 17 | let item = document.createElementNS(XUL_NS, "toolbarbutton"); 18 | item.setAttribute("label", bookmark.title); 19 | item.setAttribute("class", "subviewbutton"); 20 | hBookmark.getFaviconImageUriAsync(bookmark.url, faviconUriStr => { 21 | if (faviconUriStr !== "") item.setAttribute("image", faviconUriStr); 22 | }); 23 | item.bookmark = bookmark 24 | return item; 25 | }, 26 | }; 27 | 28 | CustomizableUI.createWidget({ 29 | id: "hBookmark-toolbar-recent-button", 30 | type: "view", 31 | label: "Haten Recent Bookmarks", 32 | viewId: "PanelUI-hBookmark-recent-view", 33 | onViewShowing: function (aEvent) { 34 | hBookmark.UIUtils.deleteContents(subviewBody); 35 | RecentButton.getBookmarks().forEach(bookmark => { 36 | subviewBody.appendChild(RecentButton.createItem(bookmark)); 37 | }); 38 | }, 39 | }); 40 | 41 | 42 | -------------------------------------------------------------------------------- /tests/javascripts/autoloader.test.js: -------------------------------------------------------------------------------- 1 | var hBookmark = null; 2 | var scriptDir = null; 3 | 4 | function warmUp() { 5 | utils.include("btil.js"); 6 | 7 | const EXTENSION_ID = "bookmark@hatena.ne.jp"; 8 | var em = Cc["@mozilla.org/extensions/manager;1"].getService(Ci.nsIExtensionManager); 9 | var location = em.getInstallLocation(EXTENSION_ID); 10 | var srcFile = location.getItemFile(EXTENSION_ID, "tests/javascripts/autoloader-test"); 11 | var destDir = location.getItemFile(EXTENSION_ID, "chrome/content"); 12 | scriptDir = utils.cosmeticClone(srcFile, destDir, "autoloader-test"); 13 | utils.dump(String(scriptDir)); 14 | } 15 | 16 | function coolDown() { 17 | utils.dump(String(scriptDir)); 18 | if (scriptDir) 19 | utils.scheduleToRemove(scriptDir); 20 | } 21 | 22 | function setUp() { 23 | var global = loadAutoloader("chrome://hatenabookmark/content/autoloader-test.xul"); 24 | hBookmark = global.hBookmark; 25 | } 26 | 27 | function testModuleLoaded() { 28 | assert.isDefined(hBookmark.Foo); 29 | assert.equals(hBookmark.Foo.baz, 42); 30 | assert.isUndefined(hBookmark.Bar); 31 | } 32 | 33 | function testGetScriptPaths() { 34 | var paths = hBookmark.load.getScriptURIs("chrome://hatenabookmark/content/autoloader-test/"); 35 | assert.equals(paths, ["chrome://hatenabookmark/content/autoloader-test/01_foo.js"]); 36 | } 37 | -------------------------------------------------------------------------------- /chrome.manifest: -------------------------------------------------------------------------------- 1 | content hatenabookmark chrome/content/ 2 | overlay chrome://browser/content/browser.xul chrome://hatenabookmark/content/overlay.xul 3 | resource hatenabookmark resources/ 4 | skin hatenabookmark classic/1.0 chrome/skin/classic/ 5 | style chrome://browser/content/browser.xul chrome://hatenabookmark/skin/browser.osx.css os=Darwin 6 | style chrome://browser/content/browser.xul chrome://hatenabookmark/skin/toolbar.osx.css os=Darwin 7 | style chrome://global/content/customizeToolbar.xul chrome://hatenabookmark/skin/toolbar.css 8 | style chrome://global/content/customizeToolbar.xul chrome://hatenabookmark/skin/toolbar.osx.css os=Darwin 9 | style chrome://hatenabookmark/content/addPanel.xul chrome://hatenabookmark/content/addPanel.linux.css os=Linux 10 | style chrome://hatenabookmark/content/addPanel.xul chrome://hatenabookmark/content/addPanel.linux.css os=FreeBSD 11 | style chrome://hatenabookmark/content/addPanel.xul chrome://hatenabookmark/content/addPanel.linux.css os=NetBSD 12 | style chrome://hatenabookmark/content/addPanel.xul chrome://hatenabookmark/content/addPanel.linux.css os=OpenBSD 13 | style chrome://hatenabookmark/content/addPanel.xul chrome://hatenabookmark/content/addPanel.linux.css os=DragonFly 14 | style chrome://hatenabookmark/content/addPanel.xul chrome://hatenabookmark/content/addPanel.linux.css os=SunOS 15 | locale hatenabookmark en-US chrome/locale/en-US/ 16 | locale hatenabookmark ja chrome/locale/ja/ 17 | -------------------------------------------------------------------------------- /chrome/locale/ja/sidebar.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /chrome/icons/default/hBookmarkAddPanelWindow.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static char *icon[] = { 3 | /* columns rows colors chars-per-pixel */ 4 | "32 32 4 1", 5 | " c #2C6EBD", 6 | ". c #417CC4", 7 | "X c #5F91CD", 8 | "o c gray100", 9 | /* pixels */ 10 | " ", 11 | " ", 12 | " XXXXXXXXXXXXXXXXXXXXXXXXXXXX ", 13 | " XXXXXXXXXXXXXXXXXXXXXXXXXXXX ", 14 | " XXXXXXXXXXXXXXXXXXXXXXXXXXXX ", 15 | " XXXXXXXXXXXXXXXXXXXXXXXXXXXX ", 16 | " XXXXXX.. ..XXXXXXXX ", 17 | " XXXXXX.. ..XXXXXXXX ", 18 | " XXXXXX oooooooooo ..XXXXXX ", 19 | " XXXXXX oooooooooo ..XXXXXX ", 20 | " XXXXXX oooo oooo XXXXXX ", 21 | " XXXXXX oooo oooo XXXXXX ", 22 | " XXXX oooo oooo XXXX ", 23 | " XXXX oooo oooo XXXX ", 24 | " oooooooooooo ", 25 | " oooooooooooo ", 26 | " oooo oooo ", 27 | " oooo oooo ", 28 | " oooo oooo ", 29 | " oooo oooo ", 30 | " oooo oooo ", 31 | " oooo oooo ", 32 | " oooooooooooo ", 33 | " oooooooooooo ", 34 | " ", 35 | " ", 36 | " ", 37 | " ", 38 | " ", 39 | " ", 40 | " ", 41 | " " 42 | }; 43 | -------------------------------------------------------------------------------- /chrome/locale/en-US/sidebar.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/javascripts/SiteInfo.js: -------------------------------------------------------------------------------- 1 | function setUp() { 2 | Components.utils.import("resource://hatenabookmark/modules/20-SiteInfo.jsm"); 3 | } 4 | 5 | function testSiteInfo() { 6 | let set = new SiteInfoSet({ 7 | matcher: SiteInfoSet.createURLMatcher('url'), 8 | sources: [ 9 | { items: [ 10 | { 11 | url: "^http://www\\.google(?:\\.\\w+){1,2}/", 12 | title: "Google", 13 | }, 14 | { 15 | url: /^http:\/\/www\.yahoo\.co\.jp\//, 16 | title: "Yahoo", 17 | dynamic: function (context, defaultValue) "dynamic result", 18 | }, 19 | ] }, 20 | ], 21 | }); 22 | 23 | let info; 24 | 25 | info = set.get(createDocumentForURL("http://www.google.co.jp/")); 26 | assert.isTrue(info instanceof SiteInfo); 27 | assert.equals("Google", info.data.title); 28 | 29 | info = set.get(createDocumentForURL("http://www.yahoo.co.jp/path/to/something")); 30 | assert.isTrue(info instanceof SiteInfo); 31 | assert.equals("Yahoo", info.data.title); 32 | assert.equals("dynamic result", info.query("dynamic")); 33 | } 34 | 35 | function createDocumentForURL(url) { 36 | let doc = { 37 | defaultView: { 38 | location: { 39 | href: url, 40 | }, 41 | GetWeakReference: function () ({ 42 | QueryReferent: function () doc.defaultView, 43 | }), 44 | QueryInterface: function () this, 45 | } 46 | }; 47 | return doc; 48 | } 49 | -------------------------------------------------------------------------------- /tests/javascripts/utils.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jsm じゃないほうの util 3 | */ 4 | 5 | var _global = this; 6 | var hBookmark; 7 | 8 | function warmUp() { 9 | utils.include('btil.js'); 10 | var tempGlobal = loadAutoloader("chrome://hatenabookmark/content/unknown.xul"); 11 | hBookmark = tempGlobal.hBookmark; 12 | hBookmark.extend(_global, hBookmark); 13 | } 14 | 15 | function setUp() 16 | { 17 | } 18 | 19 | function testSprintf() 20 | { 21 | assert.equals('abcdef', sprintf('a%sc%se%s', 'b','d', 'f')); 22 | assert.equals('10 20 b 30.55', sprintf('%s %d %s %f', 10, 20.33, 'b', 30.55)); 23 | } 24 | 25 | function testKeysValues() 26 | { 27 | let o = { 28 | k1: 'a', 29 | k2: 'b', 30 | k3: 'c', 31 | }; 32 | assert.equals(['k1', 'k2', 'k3'], keys(o)); 33 | assert.equals(['a', 'b', 'c'], values(o)); 34 | } 35 | 36 | function testNewURI() { 37 | let spec = "http://example.org/"; 38 | let uri = newURI(spec); 39 | assert.equals(uri.spec, spec); 40 | 41 | uri = newURI("bar", null, "http://example.org/foo"); 42 | assert.equals(uri.spec, "http://example.org/bar"); 43 | } 44 | 45 | function testNetsyncget() 46 | { 47 | let res = net.sync_get('http://b.hatena.ne.jp'); 48 | 49 | assert.isTrue(res.responseText.length > 0); 50 | } 51 | 52 | function testMakeQuery() { 53 | var data = { 54 | foo: '日本語 & English', 55 | bar: undefined, 56 | baz: ['Hello', 42, '世界'], 57 | }; 58 | assert.equals('foo=%E6%97%A5%E6%9C%AC%E8%AA%9E+%26+English&' + 59 | 'baz=Hello&baz=42&baz=%E4%B8%96%E7%95%8C', 60 | net.makeQuery(data)) 61 | } 62 | -------------------------------------------------------------------------------- /chrome/content/browser/06-Uninstall.js: -------------------------------------------------------------------------------- 1 | 2 | var Uninstaller; 3 | if (shared.has('Uninstaller')) { 4 | 5 | Uninstaller = shared.get('Uninstaller'); 6 | 7 | } else { 8 | 9 | Uninstaller = { 10 | flag: false, 11 | observe: function(aSubject, aTopic, aData) { 12 | if (aTopic == 'em-action-requested') { 13 | if (aSubject instanceof Ci.nsIUpdateItem && aSubject.id == EXT_ID) { 14 | switch (aData) 15 | { 16 | case 'item-cancel-action': 17 | Uninstaller.flag = false; 18 | break; 19 | // case 'item-disabled': 20 | case 'item-uninstalled': 21 | Uninstaller.flag = true; 22 | break; 23 | } 24 | } 25 | } else if (aTopic == 'quit-application') { 26 | if (Uninstaller.flag) { 27 | let pd = DirectoryService.get("ProfD", Ci.nsIFile); 28 | pd.append('hatenabookmark'); 29 | if (pd.exists() && pd.isDirectory()) { 30 | pd.remove(true); 31 | } 32 | } 33 | ObserverService.removeObserver(Uninstaller, 'em-action-requested'); 34 | ObserverService.removeObserver(Uninstaller, 'quit-application'); 35 | } 36 | }, 37 | QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]), 38 | } 39 | 40 | ObserverService.addObserver(Uninstaller, 'quit-application', false); 41 | ObserverService.addObserver(Uninstaller, 'em-action-requested', false); 42 | 43 | shared.set('Uninstaller', Uninstaller); 44 | }; 45 | -------------------------------------------------------------------------------- /chrome/skin/classic/commentViewer.css: -------------------------------------------------------------------------------- 1 | 2 | @namespace url(http://www.w3.org/1999/xhtml); 3 | /* XHTML_NS CSS */ 4 | 5 | #hBookmark-comment-div { 6 | -moz-user-focus: normal !important; 7 | -moz-user-select: text !important; 8 | } 9 | 10 | #hBookmark-comment-list { 11 | margin: 0px; 12 | padding: 0px; 13 | } 14 | 15 | #hBookmark-comment-close { 16 | cursor: pointer; 17 | padding:0 6px; 18 | float: right; 19 | } 20 | 21 | #hBookmark-comment-list li img { 22 | vertical-align: middle; 23 | } 24 | 25 | #hBookmark-comment-list { 26 | margin: 10px; 27 | } 28 | 29 | li.notice { 30 | color: #333 !important; 31 | border-bottom: 0px dotted #ddd !important; 32 | } 33 | 34 | #hBookmark-comment-list li { 35 | text-indent: -20px; 36 | padding: 0px 20px 8px 20px; 37 | margin: 0 0 8px 0; 38 | word-wrap: break-word; 39 | border-bottom: 1px solid rgba(0, 0, 0, 0.1); 40 | display: block; 41 | } 42 | 43 | #hBookmark-comment-list li a.username { 44 | font-weight: normal; 45 | padding: 0 3px; 46 | color: #00d !important; 47 | font-size: 90%; 48 | } 49 | 50 | #hBookmark-comment-list li.mine a.username { 51 | font-weight: bold; 52 | } 53 | 54 | span.comment { 55 | padding: 0 3px 0 5px; 56 | } 57 | 58 | span.timestamp { 59 | color: #999; 60 | font-size: 80%; 61 | } 62 | 63 | a.tag { 64 | text-decoration: none; 65 | color: #66c; 66 | font-size: 90%; 67 | } 68 | 69 | a.users { 70 | text-decoration: underline; 71 | color: #FF6e6e; 72 | font-weight: bold; 73 | padding-left: 14px; 74 | } 75 | 76 | a.commentlink { 77 | text-decoration: underline; 78 | color: #66c; 79 | padding-left: 3px; 80 | padding-right: 3px; 81 | } 82 | -------------------------------------------------------------------------------- /chrome/content/sidebar/10-BookmarkTreeContext.js: -------------------------------------------------------------------------------- 1 | var EXPORT = ['BookmarkTreeContext']; 2 | 3 | function BookmarkTreeContext(popup) { 4 | popup.addEventListener('popupshowing', this, false); 5 | popup.addEventListener('command', this, false); 6 | } 7 | 8 | var ID_PREFIX = 'hBookmark-bookmark-context-'; 9 | 10 | BookmarkTreeContext.items = { 11 | showRecent: { 12 | onCommand: function (treeView) treeView.showBySearchString(''), 13 | shouldDisplay: function (treeView) !treeView.isAscending, 14 | }, 15 | showOld: { 16 | onCommand: function (treeView) treeView.showBySearchString(''), 17 | shouldDisplay: function (treeView) treeView.isAscending, 18 | }, 19 | }; 20 | 21 | extend(BookmarkTreeContext.prototype, { 22 | forEachItem: function BTC_forEachItem(callback, thisObject) { 23 | for (let [key, item] in new Iterator(BookmarkTreeContext.items)) { 24 | let menuitem = document.getElementById(ID_PREFIX + key); 25 | if (!menuitem) continue; 26 | callback.call(thisObject, item, menuitem); 27 | } 28 | }, 29 | 30 | handleEvent: function BTC_handleEvent(event) { 31 | treeView = document.popupNode.parentNode.view.wrappedJSObject; 32 | switch (event.type) { 33 | case 'popupshowing': 34 | this.forEachItem(function (item, menuitem) { 35 | menuitem.hidden = !item.shouldDisplay(treeView); 36 | }, this); 37 | break; 38 | 39 | case 'command': 40 | this.forEachItem(function (item, menuitem) { 41 | if (menuitem === event.target) item.onCommand(treeView); 42 | }, this); 43 | break; 44 | } 45 | }, 46 | }); 47 | -------------------------------------------------------------------------------- /chrome/content/addPanel/20-TitleGuesser.js: -------------------------------------------------------------------------------- 1 | var EXPORT = ["TitleGuesser"]; 2 | 3 | function TitleGuesser(url, callback) { 4 | this.callback = callback; 5 | this.xhr = new XMLHttpRequest(); 6 | this.xhr.open("GET", url); 7 | this.xhr.overrideMimeType("text/plain; charset=x-user-defined"); 8 | this.xhr.addEventListener("load", this, false); 9 | this.xhr.addEventListener("error", this, false); 10 | this.xhr.send(null); 11 | } 12 | 13 | extend(TitleGuesser.prototype, { 14 | handleEvent: function TG_handleEvent(event) { 15 | this.callback((event.type === "load") ? this.getTitle() : null); 16 | }, 17 | getTitle: function TG_getTitle() { 18 | let contentType = this.xhr.getResponseHeader("Content-Type"); 19 | // タイトルは先頭付近にあると推測されるので先頭 2KB だけ調べる 20 | let html = this.xhr.responseText.substring(0, 2048); 21 | html = html.replace(/[\u0100-\uffff]/g, function (c) { 22 | return String.fromCharCode(c.charCodeAt(0) & 0xff); 23 | }); 24 | let encoding = this.getEncoding(contentType) || this.getEncoding(html); 25 | if (encoding) { 26 | try { 27 | let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. 28 | getService(Ci.nsIScriptableUnicodeConverter); 29 | converter.charset = encoding; 30 | html = converter.ConvertToUnicode(html); 31 | } catch (ex) {} 32 | } 33 | let match = html.match(/(.*?)<\/title>/i); 34 | return match && decodeReferences(match[1]); 35 | }, 36 | getEncoding: function TG_getEncoding(source) { 37 | let match = /\bcharset\s*=\s*([\w.-]+)/i.exec(source || ""); 38 | return match && match[1]; 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /install.rdf: -------------------------------------------------------------------------------- 1 | <?xml version="1.0"?> 2 | <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 3 | xmlns:em="http://www.mozilla.org/2004/em-rdf#"> 4 | <Description about="urn:mozilla:install-manifest"> 5 | <em:id>bookmark@hatena.ne.jp</em:id> 6 | <em:name>Hatena Bookmark</em:name> 7 | <em:version>2.3.12</em:version> 8 | <em:type>2</em:type> 9 | <em:optionsURL>chrome://hatenabookmark/content/config.xul</em:optionsURL> 10 | <em:iconURL>chrome://hatenabookmark/skin/images/favicon32.png</em:iconURL> 11 | <em:unpack>true</em:unpack> 12 | <em:localized> 13 | <Description> 14 | <em:name>Hatena Bookmark</em:name> 15 | <em:locale>en-US</em:locale> 16 | <em:description>Hatena Bookmark Firefox Add-on</em:description> 17 | <em:creator>Hatena</em:creator> 18 | <em:homepageURL>http://b.hatena.ne.jp/help/firefox_addon</em:homepageURL> 19 | </Description> 20 | </em:localized> 21 | <em:localized> 22 | <Description> 23 | <em:name>Hatena Bookmark</em:name> 24 | <em:locale>ja</em:locale> 25 | <em:description>はてなブックマークへのブックマーク追加や、高速な検索、ツールバー、サイドバー機能などを提供します</em:description> 26 | <em:creator>Hatena</em:creator> 27 | <em:homepageURL>http://b.hatena.ne.jp/help/firefox_addon</em:homepageURL> 28 | </Description> 29 | </em:localized> 30 | 31 | <em:targetApplication> 32 | <Description> 33 | <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> 34 | <em:minVersion>6.0</em:minVersion> 35 | <em:maxVersion>47.*</em:maxVersion> 36 | </Description> 37 | </em:targetApplication> 38 | </Description> 39 | </RDF> 40 | -------------------------------------------------------------------------------- /chrome/content/common/02-utils.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * utils 内部では、 4 | * 頭に _ のついてないローカル変数はすべて EXPORT の対象となる 5 | */ 6 | 7 | // 生成される関数の属するグローバルオブジェクトが 8 | // このウィンドウであるように、ここで宣言しなおす 9 | function bind(func, self) function () func.apply(self, arguments); 10 | function method(self, methodName) function () self[methodName].apply(self, arguments); 11 | 12 | 13 | // Timer 14 | 15 | var Timer = function(interval, repeatCount) { 16 | EventService.implement(this); 17 | this.currentCount = 0; 18 | this.interval = interval || 60; // ms 19 | this.repeatCount = repeatCount || 0; 20 | } 21 | 22 | Timer.prototype = { 23 | start: function() { 24 | this._running = true; 25 | this.loopTimer = setTimeout(function(self) { 26 | self.loop(); 27 | }, this.interval, this); 28 | }, 29 | reset: function() { 30 | this.stop(); 31 | this.currentCount = 0; 32 | }, 33 | stop: function() { 34 | this.clearTimer(); 35 | this._running = false; 36 | }, 37 | clearTimer: function() { 38 | if (this.loopTimer) { 39 | clearTimeout(this.loopTimer); 40 | delete this.loopTimer; 41 | } 42 | }, 43 | get running() this._running, 44 | loop: function() { 45 | if (!this.running) return; 46 | this.currentCount++; 47 | if (this.repeatCount && this.currentCount >= this.repeatCount) { 48 | this.stop(); 49 | this.dispatch('timer'); 50 | this.dispatch('timerComplete'); 51 | return; 52 | } 53 | this.dispatch('timer'); 54 | this.loopTimer = setTimeout(function(self) { 55 | self.loop(); 56 | }, this.interval, this); 57 | }, 58 | } 59 | 60 | 61 | var EXPORT = Object.keys(this).filter(name => name[0] !== "_" && name !== "EXPORT"); 62 | 63 | 64 | -------------------------------------------------------------------------------- /chrome/skin/classic/toolbar.osx.css: -------------------------------------------------------------------------------- 1 | #hBookmarkToolbar { 2 | min-height: 23px !important; 3 | } 4 | 5 | #nav-bar #hBookmark-toolbar-sidebar-button { 6 | -moz-image-region: rect(0px 244px 23px 208px); 7 | } 8 | 9 | #nav-bar #hBookmark-toolbar-sidebar-button[checked="true"] { 10 | -moz-image-region: rect(23px 244px 46px 208px); 11 | } 12 | 13 | #nav-bar #hBookmark-toolbar-add-button { 14 | -moz-image-region: rect(0px 111px 23px 75px); 15 | } 16 | 17 | #nav-bar #hBookmark-toolbar-add-button:active { 18 | -moz-image-region: rect(23px 111px 46px 75px); 19 | } 20 | 21 | #nav-bar toolbar[iconsize="small"] #hBookmark-toolbar-add-button { 22 | -moz-image-region: rect(0px 111px 23px 75px); 23 | } 24 | 25 | #nav-bar toolbar[iconsize="small"] #hBookmark-toolbar-add-button:active { 26 | -moz-image-region: rect(23px 111px 46px 75px); 27 | } 28 | 29 | #nav-bar #hBookmark-toolbar-home-button { 30 | -moz-image-region: rect(0px 147px 23px 111px); 31 | } 32 | 33 | #nav-bar #hBookmark-toolbar-home-button:active { 34 | -moz-image-region: rect(23px 147px 46px 111px); 35 | } 36 | 37 | #nav-bar toolbar[iconsize="small"] #hBookmark-toolbar-home-button { 38 | -moz-image-region: rect(0px 147px 23px 111px); 39 | } 40 | 41 | #nav-bar toolbar[iconsize="small"] #hBookmark-toolbar-home-button:active { 42 | -moz-image-region: rect(23px 147px 46px 111px); 43 | } 44 | 45 | #nav-bar #hBookmark-toolbar-recent-button { 46 | -moz-image-region: rect(0px 183px 23px 147px); 47 | } 48 | 49 | #nav-bar #hBookmark-toolbar-recent-button:active { 50 | -moz-image-region: rect(23px 183px 46px 147px); 51 | } 52 | 53 | toolbar[iconsize="small"] #hBookmark-toolbar-recent-button { 54 | -moz-image-region: rect(0px 183px 23px 147px); 55 | } 56 | 57 | toolbar[iconsize="small"] #hBookmark-toolbar-recent-button:active { 58 | -moz-image-region: rect(23px 183px 46px 147px); 59 | } 60 | -------------------------------------------------------------------------------- /chrome/content/browser/40-UserGuide.js: -------------------------------------------------------------------------------- 1 | var EXPORT = ['UserGuide']; 2 | 3 | var UserGuide = { 4 | start: function UG_start(allowRestart) { 5 | let firstRun = shared.has('firstRun'); 6 | let loggedIn = !!User.user; 7 | let everLoggedIn = Prefs.bookmark.get('everLoggedIn'); 8 | let everBookmarked = Prefs.bookmark.get('everBookmarked'); 9 | 10 | if (allowRestart && everLoggedIn && !loggedIn) { 11 | UserGuide.tryRestart(); 12 | return; 13 | } 14 | p('start user guide'); 15 | 16 | if (!everLoggedIn) { 17 | UIUtils.openHatenaGuide(); 18 | UserGuide.watchFirstBookmark(); 19 | } else if (!everBookmarked) { 20 | UIUtils.openBookmarkGuide(); 21 | UserGuide.watchFirstBookmark(); 22 | } else if (firstRun) { 23 | UIUtils.openStartupGuide(); 24 | } 25 | }, 26 | 27 | tryRestart: function UG_tryRestart() { 28 | p('try to restart user guide'); 29 | function restart() { 30 | if (!restart) return; 31 | listener.unlisten(); 32 | restart = listener = null; 33 | UserGuide.start(false); 34 | } 35 | let listener = EventService.createListener('UserChange', restart); 36 | setTimeout(restart, 3000); 37 | }, 38 | 39 | watchFirstBookmark: function UG_watchFirstBookmark() { 40 | Prefs.bookmark.createListener('everBookmarked', UserGuide.onFirstBookmark); 41 | }, 42 | 43 | onFirstBookmark: function UG_onFirstBookmark() { 44 | if (!Prefs.bookmark.get('everBookmarked')) return; 45 | if (shared.has('alreadyBookmarked')) 46 | UIUtils.openStartupGuide(); 47 | else 48 | UIUtils.openDoneBookmarkGuide(); 49 | }, 50 | }; 51 | 52 | EventService.createListener('firstPreload', function () { 53 | setTimeout(function () UserGuide.start(true), 200); 54 | }); 55 | -------------------------------------------------------------------------------- /tests/javascripts/prefs.test.js: -------------------------------------------------------------------------------- 1 | 2 | var _global = this; 3 | var hBookmark; 4 | 5 | function warmUp() { 6 | utils.include("btil.js"); 7 | var global = loadAutoloader("chrome://hatenabookmark/content/unknown.xul"); 8 | hBookmark = global.hBookmark; 9 | hBookmark.extend(_global, hBookmark); 10 | } 11 | 12 | function setUp() { 13 | utils.setPref('extensions.hatenabookmark.strfoo', 'foo'); 14 | utils.setPref('extensions.hatenabookmark.strbar', 'bar'); 15 | utils.setPref('extensions.hatenabookmark.intbar', 3); 16 | utils.setPref('extensions.hatenabookmark.bar', 'bar'); 17 | utils.setPref('extensions.hatenabookmark.hogehoge.hugahuga', 100); 18 | utils.setPref('toplevel.bar', 'foo'); 19 | Prefs.global.register(); 20 | Prefs.bookmark.register(); 21 | } 22 | 23 | function tearDown() { 24 | Prefs.global.unregister(); 25 | Prefs.bookmark.unregister(); 26 | } 27 | 28 | 29 | function testPref() 30 | { 31 | assert.equals('foo', Prefs.bookmark.get('strfoo')); 32 | assert.equals(3, Prefs.bookmark.get('intbar')); 33 | assert.equals('bar', Prefs.bookmark.get('bar')); 34 | Prefs.bookmark.set('setstrbar', 'string'); 35 | assert.equals(utils.getPref('extensions.hatenabookmark.setstrbar'), 'string'); 36 | Prefs.bookmark.set('setbarint', 4); 37 | assert.equals(utils.getPref('extensions.hatenabookmark.setbarint'), 4); 38 | Prefs.global.clear('toplevel.bar'); 39 | assert.isFalse(Prefs.global.get('toplevel.bar')); 40 | } 41 | 42 | function testObserve() { 43 | var loaded = { value: false }; 44 | var loaded2 = { value: false }; 45 | Prefs.bookmark.createListener('strfoo', function(e) { 46 | loaded.value = true; 47 | }); 48 | Prefs.global.createListener('extensions.hatenabookmark.strfoo', function(e) { 49 | loaded2.value = true; 50 | }); 51 | utils.setPref('extensions.hatenabookmark.strfoo', 'change'); 52 | yield loaded; 53 | yield loaded2; 54 | } 55 | 56 | -------------------------------------------------------------------------------- /resources/modules/20-URLNormalizer.jsm: -------------------------------------------------------------------------------- 1 | Components.utils.import("resource://hatenabookmark/modules/00-utils.jsm"); 2 | 3 | var EXPORTED_SYMBOLS = ['URLNormalizer']; 4 | 5 | 6 | var URLNormalizer = { 7 | normalize: function normalize(url) { 8 | if (!(url instanceof Ci.nsIURL)) return null; 9 | url = url.clone().QueryInterface(Ci.nsIURL); 10 | 11 | url.filePath = url.filePath.replace(/%7E/g, '~'); 12 | url.query = url.query.replace(/(?:^|&)ref=rss(?=&|$)/, ''); 13 | url.ref = url.ref.replace(/^(?:see)?more$/, ''); 14 | 15 | if (url.asciiHost in URLNormalizer.rules) 16 | url = URLNormalizer.rules[url.asciiHost](url) || url; 17 | return url; 18 | }, 19 | }; 20 | 21 | URLNormalizer.rules = { 22 | 'www.amazon.co.jp': function normalize_Amazon(url) { 23 | let match = url.path.match(/(?:\/(?:ASIN|dp|product)\/|[?&;]asins=)(\w{10})\b/); 24 | if (!match) return url; 25 | url.host = 'www.amazon.co.jp'; 26 | url.filePath = '/gp/product/' + match[1]; 27 | url.query = ''; 28 | return url; 29 | }, 30 | 31 | 'd.hatena.ne.jp': function normalize_HatenaDiary(url) { 32 | let match = url.path.match(/^\/asin\/(\w{10})(?:[\/?#]|$)/); 33 | if (!match) return url; 34 | url.filePath = '/asin/' + match[1]; 35 | url.query = ''; 36 | return url; 37 | }, 38 | 39 | 'www.youtube.com': function normalize_YouTube_www(url) { 40 | let match = url.query.match(/(?:^|&)(v=[^&]+)/); 41 | if (!match) return url; 42 | url.query = match[1]; 43 | return url; 44 | }, 45 | 46 | 'youtube.com': function normalize_YouTube_noprefix(url) { 47 | url = this['www.youtube.com'](url); 48 | url.host = 'www.youtube.com'; 49 | return url; 50 | }, 51 | }; 52 | 53 | URLNormalizer.rules['amazon.co.jp'] = URLNormalizer.rules['www.amazon.co.jp']; 54 | URLNormalizer.rules['jp.youtube.com'] = URLNormalizer.rules['www.youtube.com']; 55 | -------------------------------------------------------------------------------- /chrome/content/selectTagDialog.xul: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <?xml-stylesheet type="text/css" href="chrome://global/skin/"?> 3 | <?xml-stylesheet type="text/css" href="chrome://hatenabookmark/skin/selectTagDialog.css"?> 4 | <!DOCTYPE dialog SYSTEM "chrome://hatenabookmark/locale/selectTagDialog.dtd"> 5 | <dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" 6 | id="hBookmarkSelectTagDialog" 7 | title="&hBookmark.selectTag.title;" 8 | buttons="accept,cancel" 9 | width="&hBookmark.selectTag.initialWidth;" 10 | height="&hBookmark.selectTag.initialHeight;" 11 | persist="width height screenX screenY" 12 | onload="new hBookmark.SelectTagDialogManager(document.documentElement);" 13 | onunload="document.documentElement.manager.destroy();" 14 | ondialogaccept="return document.documentElement.manager.onAccept(event);"> 15 | <script type="application/javascript" 16 | src="chrome://hatenabookmark/content/autoloader.js"/> 17 | 18 | <vbox flex="1"> 19 | <description>&hBookmark.selectTag.description;</description> 20 | 21 | <tree id="tag-list" 22 | seltype="single" 23 | hidecolumnpicker="true" 24 | flex="1"> 25 | <treecols> 26 | <treecol id="tag-list-col-tag" 27 | label="&hBookmark.selectTag.tagLabel;" 28 | sortActive="true" 29 | sortDirection="natural" 30 | persist="width sortActive sortDirection" 31 | flex="5"/> 32 | <splitter class="tree-splitter"/> 33 | <treecol id="tag-list-col-count" 34 | label="&hBookmark.selectTag.countLabel;" 35 | persist="width sortActive sortDirection" 36 | flex="1"/> 37 | </treecols> 38 | <treechildren/> 39 | </tree> 40 | </vbox> 41 | </dialog> 42 | -------------------------------------------------------------------------------- /chrome/content/addPanel.xul: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <?xml-stylesheet type="text/css" href="chrome://global/skin/"?> 3 | <?xml-stylesheet type="text/css" href="addPanel.css"?> 4 | <?xml-stylesheet type="text/css" href="chrome://hatenabookmark/content/widgets.css"?> 5 | <?xml-stylesheet type="text/css" href="chrome://hatenabookmark/skin/addPanel.css"?> 6 | <?xml-stylesheet type="text/css" href="chrome://hatenabookmark/skin/browser.css"?> 7 | <!DOCTYPE dialog SYSTEM "chrome://hatenabookmark/locale/addPanel.dtd"> 8 | <dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" 9 | id="hBookmarkAddPanelWindow" 10 | buttonlabelextra2="&hBookmark.addPanel.deleteLabel;" 11 | buttons="accept,cancel" 12 | defaultButton="accept" 13 | width="&hBookmark.addPanel.initialWidth;" 14 | height="&hBookmark.addPanel.initialHeight;" 15 | persist="screenX screenY width height" 16 | onload="var bookmark = window.arguments[0] && window.arguments[0].bookmark; 17 | if (bookmark) 18 | document.getElementById('hBookmarkAddPanelContent').show(bookmark);" 19 | ondialogaccept="document.getElementById('hBookmarkAddPanelContent').saveBookmark();" 20 | ondialogextra2="document.getElementById('hBookmarkAddPanelContent').deleteBookmark();"> 21 | <script type="application/javascript" 22 | src="chrome://hatenabookmark/content/autoloader.js"/> 23 | <script type="application/javascript" 24 | src="chrome://browser/content/utilityOverlay.js"/> 25 | <popupset> 26 | <panel 27 | id="hBookmark-panel-tagcomplete" 28 | noautofocus="true" 29 | noautohide="false" 30 | > 31 | <richlistbox 32 | disableKeyNavigation="true" 33 | rows="10" 34 | id="hBookmark-tagcomplete-listbox" 35 | /> 36 | </panel> 37 | </popupset> 38 | <vbox id="hBookmarkAddPanelContent" class="hBookmarkAddPanel"/> 39 | </dialog> 40 | -------------------------------------------------------------------------------- /chrome/content/common/50-tree-view.js: -------------------------------------------------------------------------------- 1 | var EXPORT = ["TreeView"]; 2 | 3 | // nsITreeView インターフェイスを実装 4 | // https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsITreeView 5 | function TreeView() {} 6 | 7 | extend(TreeView, { 8 | SORT_NATURAL: 0, 9 | SORT_ASCENDING: 1, 10 | SORT_DESCENDING: -1, 11 | }); 12 | 13 | extend(TreeView.prototype, { 14 | get wrappedJSObject () this, 15 | rowCount: 0, 16 | selection: null, 17 | canDrop: function (index, orientation) false, 18 | cycleCell: function (row, col) {}, 19 | cycleHeader: function (col) {}, 20 | drop: function (row, orientation) {}, 21 | getCellProperties: function (row, col, properties) {}, 22 | getCellText: function (row, col) "", 23 | getCellValue: function (row, col) "", 24 | getColumnProperties: function (col, properties) {}, 25 | getImageSrc: function (row, col) "", 26 | getLevel: function (index) 0, 27 | getParentIndex: function (rowIndex) -1, 28 | getProgressMode: function (row, col) 0, 29 | getRowProperties: function (index, properties) {}, 30 | hasNextSibling: function (rowIndex, afterIndex) false, 31 | isContainer: function (index) false, 32 | isContainerEmpty: function (index) false, 33 | isContainerOpen: function (index) false, 34 | isEditable: function (row, col) false, 35 | isSelectable: function (row, col) false, 36 | isSeparator: function (index) false, 37 | isSorted: function () false, 38 | performAction: function (action) {}, 39 | performActionOnCell: function (action, row, col) {}, 40 | performActionOnRow: function (action, row) {}, 41 | selectionChanged: function () {}, 42 | setCellText: function (row, col, value) {}, 43 | setCellValue: function (row, col, value) {}, 44 | setTree: function (tree) {}, 45 | toggleOpenState: function (index) {} 46 | }); 47 | -------------------------------------------------------------------------------- /searchplugins/hatenabookmark.opensearch.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" 3 | xmlns:moz="http://www.mozilla.org/2006/browser/search/"> 4 | <ShortName>はてなブックマーク</ShortName> 5 | <Description>はてなブックマーク</Description> 6 | <InputEncoding>utf-8</InputEncoding> 7 | <Image width="16" height="16"><![CDATA[data:image/x-icon;base64, AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOd5Uv/JXyr/yV8q/8lfKv/JXyr/yV8q/8lfKv/JXyr/yV8q/8lfKv/JXyr/yV8q/8lfKv/JXyr/AAAAAAAAAAD6pYX/53lS/+d5Uv/neVL/53lS/+d5Uv/neVL/53lS/+d5Uv/neVL/53lS/+d5Uv/neVL/yV8q/wAAAAAAAAAA+qWF/+d5Uv/neVL/53lS/+d5Uv/neVL/53lS/+d5Uv/neVL/53lS/+d5Uv/neVL/53lS/8lfKv8AAAAAAAAAAPqlhf/neVL/53lS/+d5Uv/////////////////////////////////neVL/53lS/+d5Uv/JXyr/AAAAAAAAAAD6pYX/53lS/+d5Uv/neVL////////////neVL/53lS/+d5Uv///////////+d5Uv/neVL/yV8q/wAAAAAAAAAA+qWF/+d5Uv/neVL/53lS////////////53lS/+d5Uv/neVL////////////neVL/53lS/8lfKv8AAAAAAAAAAPqlhf/neVL/53lS/+d5Uv///////////+d5Uv/neVL/53lS////////////53lS/+d5Uv/JXyr/AAAAAAAAAAD6pYX/53lS/+d5Uv/neVL/////////////////////////////////53lS/+d5Uv/neVL/yV8q/wAAAAAAAAAA+qWF/+d5Uv/neVL/53lS////////////53lS/+d5Uv///////////+d5Uv/neVL/53lS/8lfKv8AAAAAAAAAAPqlhf/neVL/53lS/+d5Uv///////////+d5Uv/neVL////////////neVL/53lS/+d5Uv/JXyr/AAAAAAAAAAD6pYX/53lS/+d5Uv/neVL////////////////////////////neVL/53lS/+d5Uv/neVL/yV8q/wAAAAAAAAAA+qWF/+d5Uv/neVL/53lS/+d5Uv/neVL/53lS/+d5Uv/neVL/53lS/+d5Uv/neVL/53lS/8lfKv8AAAAAAAAAAPqlhf/neVL/53lS/+d5Uv/neVL/53lS/+d5Uv/neVL/53lS/+d5Uv/neVL/53lS/+d5Uv/JXyr/AAAAAAAAAAD6pYX/+qWF//qlhf/6pYX/+qWF//qlhf/6pYX/+qWF//qlhf/6pYX/+qWF//qlhf/6pYX/53lS/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAIABAACAAQAAgAEAAIABAACAAQAAgAEAAIABAACAAQAAgAEAAIABAACAAQAAgAEAAIABAACAAQAA//8AAA==]]></Image> 8 | <Url type="text/html" method="GET" template="http://b.hatena.ne.jp/search?ref=firefox&q={searchTerms}" /> 9 | </OpenSearchDescription> 10 | -------------------------------------------------------------------------------- /tests/javascripts/tagtreeview.test.js: -------------------------------------------------------------------------------- 1 | var hBookmark; 2 | var view; 3 | var treeBox = { 4 | treeBody: {}, 5 | rowCountChanged: function () {}, 6 | invalidateRow: function () {} 7 | }; 8 | 9 | function warmUp() { 10 | utils.include("btil.js"); 11 | var global = loadAutoloader("chrome://hatenabookmark/content/sidebar.xul"); 12 | hBookmark = global.hBookmark; 13 | } 14 | 15 | function setUp() { 16 | prepareDatabase(hBookmark); 17 | 18 | view = new hBookmark.TagTreeView(); 19 | view._setSortKey = function () {}; 20 | view.setTree(treeBox); 21 | } 22 | 23 | function testTagTreeView() { 24 | assert.equals(view.rowCount, 3); 25 | var col = { id: "hBookmarkTagTree_currentTag" }; 26 | var cellTexts = [0, 1, 2].map(function (i) view.getCellText(i, col)); 27 | assert.equals(cellTexts.concat().sort(), ["JavaScript", "Perl", "Ruby"]); 28 | assert.equals(cellTexts[1], "Perl"); 29 | assert.equals(view.isContainer(1), true); 30 | assert.equals(view.isContainerEmpty(1), false); 31 | } 32 | 33 | function testOpenClose() { 34 | view.toggleOpenState(1); 35 | assert.equals(view.rowCount, 5); 36 | 37 | assert.equals(view.getLevel(2), 1); 38 | assert.equals(view.getParentIndex(2), 1); 39 | assert.equals(view.hasNextSibling(2), true); 40 | 41 | assert.equals(view.getLevel(4), 0); 42 | assert.equals(view.getParentIndex(4), -1); 43 | assert.equals(view.hasNextSibling(4), false); 44 | 45 | view.toggleOpenState(1); 46 | assert.equals(view.rowCount, 3); 47 | 48 | assert.equals(view.isContainerOpen(1), false); 49 | 50 | assert.equals(view.getLevel(2), 0); 51 | assert.equals(view.getParentIndex(2), -1); 52 | assert.equals(view.hasNextSibling(2), false); 53 | } 54 | 55 | function testSelection() { 56 | view.selection = { currentIndex: 1 }; 57 | assert.equals(view.selectedTags.join(), ["Perl"].join()); 58 | 59 | view.toggleOpenState(1); 60 | view.selection.currentIndex = 2; 61 | assert.equals(view.selectedTags.join(), ["Perl", "JavaScript"].join()); 62 | } 63 | 64 | 65 | -------------------------------------------------------------------------------- /resources/modules/30-SiteInfoSet-Search.jsm: -------------------------------------------------------------------------------- 1 | Components.utils.import("resource://hatenabookmark/modules/00-utils.jsm"); 2 | loadPrecedingModules.call(this); 3 | 4 | var EXPORTED_SYMBOLS = []; 5 | 6 | var builtInSearchSiteInfo = [ 7 | /* 8 | { 9 | url: 10 | baseDomain: 11 | query: 12 | encoding: 13 | annotation: 14 | annotationPosition: 15 | style: 16 | disable: 17 | }, 18 | */ 19 | { // Google Web Search 20 | url: /^http:\/\/www\.google(?:\.\w+){1,2}\/search\?/, 21 | baseDomain: /^google\./, 22 | query: /[?&;]q=([^?&;#]+)/, 23 | encoding: /[?&;]ie=([\w-]+)/, 24 | //annotation: 'id("res")', 25 | annotation: function (doc) { 26 | let rhs = doc.getElementById("rhs"); 27 | if (rhs) { 28 | let div = doc.createElement('div'); 29 | rhs.appendChild(div); 30 | return div; 31 | } 32 | return doc.getElementById("res"); 33 | }, 34 | annotationPosition: 'first', 35 | style: 36 | '#rhs #hBookmark-search {' + 37 | 'margin: 1.2em 1.5em 0 0.7em;' + 38 | 'width: auto;' + 39 | 'float: none;' + 40 | '}' + 41 | '#res #hBookmark-search {' + 42 | 'font-size: 0.8em;' + 43 | 'margin-right: -32%;' + 44 | '}', 45 | }, 46 | { // Yahoo Web Search 47 | url: /^http:\/\/search\.yahoo(?:\.\w+){1,2}\/search\?/, 48 | baseDomain: /^yahoo\./, 49 | query: /[?&;]p=([^?&;#]+)/, 50 | encoding: /[?&;]ei=([\w-]+)/, 51 | annotation: 'id("sIn")', 52 | style: '#hBookmark-search { width: auto; }', 53 | }, 54 | ]; 55 | 56 | var Search = new SiteInfoSet({ 57 | matcher: SiteInfoSet.createURLMatcher('url'), 58 | sources: [ 59 | { file: 'Search.user.siteinfo.js' }, 60 | { items: builtInSearchSiteInfo }, 61 | ], 62 | }); 63 | 64 | SiteInfoSet.Search = Search; 65 | -------------------------------------------------------------------------------- /chrome/locale/ja/browser.dtd: -------------------------------------------------------------------------------- 1 | <!ENTITY hBookmark.toolbar.title "はてなブックマークツールバー"> 2 | <!ENTITY hBookmark.toolbar.titleKey "A"> 3 | <!ENTITY hBookmark.toolbar.recentLabel "最近のはてなブックマーク"> 4 | <!ENTITY hBookmark.toolbar.add "はてなブックマークに追加"> 5 | <!ENTITY hBookmark.toolbar.home "自分のはてなブックマークページ"> 6 | <!ENTITY hBookmark.toolbar.homeKey "H"> 7 | <!ENTITY hBookmark.toolbar.top "はてなブックマークのトップページ"> 8 | <!ENTITY hBookmark.toolbar.topKey "T"> 9 | <!ENTITY hBookmark.toolbar.favorite "自分のお気に入りページ"> 10 | <!ENTITY hBookmark.toolbar.favoriteKey "F"> 11 | 12 | <!ENTITY hBookmark.sidebar.label "はてなブックマークサイドバー"> 13 | <!ENTITY hBookmark.sidebar.key "A"> 14 | 15 | <!ENTITY hBookmark.statusbar.label "はてなパネル"> 16 | <!ENTITY hBookmark.statusbar.now_syncing "データを同期しています..."> 17 | <!ENTITY hBookmark.statusbar.tooltip.addButton "はてなブックマークに追加・編集"> 18 | <!ENTITY hBookmark.statusbar.tooltip.counter "はてなブックマークで表示"> 19 | <!ENTITY hBookmark.statusbar.tooltip.comment "ブックマークコメントを表示"> 20 | 21 | <!ENTITY hBookmark.subview.add "追加・編集"> 22 | <!ENTITY hBookmark.subview.comment "ブックマークコメントを表示"> 23 | <!ENTITY hBookmark.subview.showentry "はてなブックマークで表示"> 24 | <!ENTITY hBookmark.subview.sync "データの同期"> 25 | 26 | <!ENTITY hBookmark.menu.main "はてなブックマーク"> 27 | <!ENTITY hBookmark.menu.mainKey "B"> 28 | <!ENTITY hBookmark.menu.sync "データの同期"> 29 | <!ENTITY hBookmark.menu.syncKey "S"> 30 | <!ENTITY hBookmark.menu.config "設定"> 31 | <!ENTITY hBookmark.menu.configKey "C"> 32 | <!ENTITY hBookmark.menu.help "ヘルプ"> 33 | <!ENTITY hBookmark.menu.helpKey "H"> 34 | 35 | <!ENTITY hBookmark.menu.add "このページをはてなブックマークに追加"> 36 | <!ENTITY hBookmark.menu.addKey "B"> 37 | <!ENTITY hBookmark.menu.addlink "このリンクをはてなブックマークに追加"> 38 | <!ENTITY hBookmark.menu.addlinkKey "B"> 39 | <!ENTITY hBookmark.menu.showentry "このページをはてなブックマークで表示"> 40 | <!ENTITY hBookmark.menu.showentryKey "E"> 41 | <!ENTITY hBookmark.menu.showlink "このリンクをはてなブックマークで表示"> 42 | <!ENTITY hBookmark.menu.showlinkKey "E"> 43 | <!ENTITY hBookmark.menu.showcomment "このリンクをコメントビューワーで表示"> 44 | <!ENTITY hBookmark.menu.showcommentKey "C"> 45 | <!ENTITY hBookmark.menu.searchtext "はてなブックマークで検索: "> 46 | <!ENTITY hBookmark.menu.searchtextKey "B"> 47 | -------------------------------------------------------------------------------- /chrome/content/popups.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <bindings xmlns="http://www.mozilla.org/xbl" 3 | xmlns:xbl="http://www.mozilla.org/xbl" 4 | xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> 5 | 6 | <binding id="bookmark-tooltip" 7 | extends="chrome://global/content/bindings/popup.xml#tooltip"> 8 | <content> 9 | <xul:description anonid="titleField"/> 10 | <xul:description anonid="urlField"/> 11 | <xul:description anonid="commentField"/> 12 | </content> 13 | 14 | <implementation> 15 | <field name="titleField"> 16 | document.getAnonymousElementByAttribute(this, "anonid", "titleField") 17 | </field> 18 | <field name="urlField"> 19 | document.getAnonymousElementByAttribute(this, "anonid", "urlField") 20 | </field> 21 | <field name="commentField"> 22 | document.getAnonymousElementByAttribute(this, "anonid", "commentField") 23 | </field> 24 | </implementation> 25 | 26 | <handlers> 27 | <handler event="popupshowing"><![CDATA[ 28 | let target = hBookmark.UIUtils.getBookmarkElement( 29 | document.tooltipNode); 30 | if (!target || ("hoveredBookmark" in target && 31 | !target.hoveredBookmark)) 32 | return false; 33 | let bookmark = target.hoveredBookmark || target.bookmark; 34 | this.titleField.textContent = bookmark.title; 35 | this.urlField.textContent = bookmark.url; 36 | this.urlField.hidden = (bookmark.title === bookmark.url); 37 | let comment = bookmark.comment.replace(/^(?:\[[^\[\]]+\])+(?=[^\[])/, "$& "); 38 | this.commentField.textContent = comment; 39 | this.commentField.hidden = !/\S/.test(comment); 40 | return true; 41 | ]]></handler> 42 | </handlers> 43 | </binding> 44 | 45 | </bindings> 46 | -------------------------------------------------------------------------------- /resources/modules/71-FullTextSearch.js: -------------------------------------------------------------------------------- 1 | Components.utils.import("resource://hatenabookmark/modules/00-utils.jsm"); 2 | loadPrecedingModules.call(this); 3 | 4 | var EXPORTED_SYMBOLS = ['FullTextSearch']; 5 | 6 | var FullTextSearch = { 7 | cache: new HTTPCache('searchCache', { 8 | expire: 60 * 60, 9 | baseURL: B_HTTP, 10 | seriarizer: 'uneval', // XXX The correct spell is "serializer" 11 | json: true, 12 | }), 13 | 14 | canSearch: function FTS_canSearch() !!User.user && User.user.canUseFullTextSearch, 15 | 16 | search: function FTS_search(query, onResult, options) { 17 | if (!FullTextSearch.canSearch()) { 18 | new BuiltInTimer({ 19 | observe: function () onResult(null), 20 | }, 10, Ci.nsITimer.TYPE_ONE_SHOT); 21 | return; 22 | } 23 | let path = FullTextSearch.getJsonPath(query, options); 24 | p(path); 25 | FullTextSearch.cache.async_get(path, function FTS_search_callback(data) { 26 | if (!data) 27 | FullTextSearch.cache.clear(path); 28 | onResult(data); 29 | }); 30 | }, 31 | 32 | searchSync: function FTS_searchSync(query, options) { 33 | if (!FullTextSearch.canSearch()) return null; 34 | let path = FullTextSearch.getJsonPath(query, options); 35 | return FullTextSearch.cache.get(path); 36 | }, 37 | 38 | getJsonPath: function FTS_getJsonPath(query, options) { 39 | options = options || {}; 40 | options.json = true; 41 | return FullTextSearch.getPath(query, options); 42 | }, 43 | 44 | getPath: function FTS_getPath(query, options) { 45 | options = options || {}; 46 | let prefs = Prefs.bookmark; 47 | return User.user.name + 48 | '/search' + (options.json ? '/json' : '') + '?q=' + encodeURIComponent(query) + 49 | '&limit=' + (options.limit || prefs.get('embed.searchCount')) + 50 | '&snip=' + (options.snippetLength || prefs.get('embed.searchSnippetLength')) + 51 | '&sort=' + (options.sortBy || prefs.get('embed.searchSortBy')); 52 | }, 53 | }; 54 | -------------------------------------------------------------------------------- /chrome/content/browser/13-Star-Palette.js: -------------------------------------------------------------------------------- 1 | 2 | var Palette = { 3 | get panel() { 4 | let panel = document.getElementById('hBookmark-star-palette'); 5 | panel.addEventListener('click', this.onPanelClick, false); 6 | panel.addEventListener('popuphidden', this.onPanelHidden, false); 7 | document.addEventListener('mousedown', this.onDocumentMouseDown, true); 8 | delete this.panel; 9 | return this.panel = panel; 10 | }, 11 | 12 | button: null, 13 | 14 | show: function SP_show(colors, button, anchor) { 15 | if (this.panel.state === 'open') 16 | this.panel.hidePopup(); 17 | this.button = button; 18 | this.anchor = anchor; 19 | this.setStatus(colors); 20 | this.panel.openPopup(anchor, 'after_start', 0, 0, false, false); 21 | }, 22 | 23 | setStatus: function SP_setStatus(colors) { 24 | for (let [color, count] in new Iterator(colors)) { 25 | let image = document.getElementById('hBookmark-star-palette-' + color); 26 | if (!image) continue; 27 | if (count) 28 | image.setAttribute('canadd', 'true'); 29 | else 30 | image.removeAttribute('canadd'); 31 | } 32 | }, 33 | 34 | onPanelClick: function SP_onPanelClick(event) { 35 | if (event.button === 2) return; 36 | let target = event.target; 37 | let color = target.getAttribute('color'); 38 | if (!color) return; 39 | if (target.hasAttribute('canadd')) 40 | Palette.button.addStar(Palette.anchor, color); 41 | else 42 | hOpenUILink(Star.strings.get('starShopURL')); 43 | Palette.panel.hidePopup(); 44 | }, 45 | 46 | onPanelHidden: function SP_onPanelHidden(event) { 47 | Palette.button = Palette.anchor = null; 48 | }, 49 | 50 | onDocumentMouseDown: function SP_onDocumentMouseDown(event) { 51 | if (event.target === Palette.panel || 52 | event.target.parentNode === Palette.panel) 53 | return; 54 | Palette.panel.hidePopup(); 55 | }, 56 | }; 57 | 58 | Star.Palette = Palette; 59 | -------------------------------------------------------------------------------- /chrome/content/urlEditor/05-urlEditor.js: -------------------------------------------------------------------------------- 1 | var EXPORT = ['urlEditor']; 2 | 3 | elementGetter(this, 'addInput', 'hBookmark-urlEditor-add-input', document); 4 | elementGetter(this, 'listbox', 'hBookmark-urlEditor-listbox', document); 5 | 6 | var urlEditor = { 7 | get strings() { 8 | if (!Config._strings) { 9 | Config._strings = new Strings("chrome://hatenabookmark/locale/urlEditor.properties"); 10 | } 11 | return Config._strings; 12 | }, 13 | add: function() { 14 | let url = addInput.value; 15 | url = url.replace(/\s+/g, ''); 16 | if (!url.length) return; 17 | 18 | try { 19 | new RegExp(url); 20 | } catch(e) { 21 | return alert(urlEditor.strings.get('regexError') + "\n" + url); 22 | } 23 | addInput.value = ''; 24 | listbox.appendItem(url, url); 25 | }, 26 | remove: function() { 27 | let i = listbox.selectedIndex; 28 | if (i >= 0) 29 | listbox.removeItemAt(i); 30 | }, 31 | accept: function() { 32 | let res = []; 33 | for (var i = 0; i < listbox.getRowCount(); i++) { 34 | let item = listbox.getItemAtIndex(i); 35 | res.push(item.value); 36 | } 37 | PrefService.setCharPref('extensions.hatenabookmark.statusbar.counterIgnoreList', uneval(res)); 38 | }, 39 | reset: function() { 40 | if (window.confirm(urlEditor.strings.get('defaultConfirm'))) { 41 | PrefService.clearUserPref('extensions.hatenabookmark.statusbar.counterIgnoreList'); 42 | urlEditor.init(); 43 | } 44 | }, 45 | init: function() { 46 | let list; 47 | try { 48 | list = eval(PrefService.getCharPref('extensions.hatenabookmark.statusbar.counterIgnoreList')); 49 | } catch (e) { 50 | PrefService.clearUserPref('extensions.hatenabookmark.statusbar.counterIgnoreList'); 51 | list = eval(PrefService.getCharPref('extensions.hatenabookmark.statusbar.counterIgnoreList')); 52 | } 53 | while(listbox.getRowCount()) listbox.removeItemAt(0); 54 | list.forEach(function(v) listbox.appendItem(v, v)); 55 | }, 56 | }; 57 | -------------------------------------------------------------------------------- /chrome/content/browser/05-Migration.js: -------------------------------------------------------------------------------- 1 | 2 | var EXPORT = ['Migration']; 3 | /* 4 | * バージョンによってのコンフィグの差異を変更する 5 | */ 6 | 7 | var Migration = { 8 | // 現在の conf のバージョン 9 | CURRENT_VERSION: 3, 10 | 11 | migration: function() { 12 | let currentVer = Prefs.bookmark.get('migration.version') || 0; 13 | let migrations = Migration.Migrations; 14 | for (let i = 0,len = migrations.length; i < len; i++) { 15 | if (currentVer < i+1) { 16 | let m = migrations[i]; 17 | p('execute migration: ' + m.name); 18 | m(); 19 | Prefs.bookmark.set('migration.version', i + 1); 20 | } 21 | } 22 | }, 23 | } 24 | 25 | EventService.createListener('Migration', function() { 26 | Migration.migration(); 27 | }); 28 | 29 | Migration.Migrations = [ 30 | // デバッグログに、Migration の無名関数の名前を表示するので、つけておく 31 | function M_1_toolbarMenuInstall() { 32 | // やっぱり標準ではインストールしない 33 | return; 34 | }, 35 | function M_2_openStartPage() { 36 | // スタートページの表示は UserGuide でやるんで、 37 | // 初回インストール時は初回インストールだということを記憶するだけ。 38 | shared.set('firstRun', true); 39 | }, 40 | function M_3_renamePrefs() { 41 | const PREFIX = 'extensions.hatenabookmark.'; 42 | const OLD_IGNORE = PREFIX + 'statusbar.counterIngoreList'; 43 | const NEW_IGNORE = PREFIX + 'statusbar.counterIgnoreList'; 44 | const OLD_CAPTURE = PREFIX + 'link.linkOverlay'; 45 | const NEW_CAPTURE = PREFIX + 'link.captureAddition'; 46 | const CAPTURE_COMMENTS = PREFIX + 'link.captureComments'; 47 | 48 | if (PrefService.prefHasUserValue(OLD_IGNORE)) { 49 | let value = PrefService.getCharPref(OLD_IGNORE); 50 | PrefService.setCharPref(NEW_IGNORE, value); 51 | PrefService.clearUserPref(OLD_IGNORE); 52 | } 53 | if (PrefService.prefHasUserValue(OLD_CAPTURE)) { 54 | let value = PrefService.getBoolPref(OLD_CAPTURE); 55 | PrefService.setBoolPref(NEW_CAPTURE, value); 56 | PrefService.setBoolPref(CAPTURE_COMMENTS, value); 57 | PrefService.clearUserPref(OLD_CAPTURE); 58 | } 59 | }, 60 | ]; 61 | -------------------------------------------------------------------------------- /chrome/content/urlEditor.xul: -------------------------------------------------------------------------------- 1 | <?xml version="1.0"?> 2 | <?xml-stylesheet type="text/css" href="chrome://global/skin/"?> 3 | <?xml-stylesheet href="chrome://global/skin/" type="text/css"?> 4 | <!DOCTYPE dialog SYSTEM "chrome://hatenabookmark/locale/urlEditor.dtd"> 5 | <dialog id="hBookmark-urlEditor" 6 | width="400" 7 | height="400" 8 | xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" 9 | title="&hBookmark.urlEditor.title;" 10 | buttons="accept,cancel" 11 | ondialogaccept="return hBookmark.urlEditor.accept();" 12 | onload="hBookmark.urlEditor.init();" 13 | > 14 | <script type="application/javascript" src="chrome://hatenabookmark/content/autoloader.js"/> 15 | <vbox flex="1"> 16 | <description>&hBookmark.urlEditor.description;</description> 17 | <vbox> 18 | <textbox 19 | onkeypress="if (event.keyCode == 13) { hBookmark.urlEditor.add(); event.stopPropagation(); event.preventDefault(); } // XXX" 20 | id="hBookmark-urlEditor-add-input" 21 | multiline="false" 22 | flex="1" 23 | /> 24 | <hbox flex="1"> 25 | <button oncommand="hBookmark.urlEditor.add();" label="&hBookmark.urlEditor.add;" id="hBookmark-urlEditor-add"/> 26 | <spacer flex="1" /> 27 | </hbox> 28 | </vbox> 29 | <listbox id="hBookmark-urlEditor-listbox" 30 | seltype="single" 31 | flex="1" 32 | > 33 | <listhead> 34 | <listheader label="&hBookmark.urlEditor.urllist;"/> 35 | </listhead> 36 | <listcols> 37 | <listcol 38 | flex="1" 39 | style="min-width:120px" 40 | /> 41 | </listcols> 42 | </listbox> 43 | <hbox> 44 | <button oncommand="hBookmark.urlEditor.remove();" label="&hBookmark.urlEditor.remove;" id="hBookmark-urlEditor-remove"/> 45 | <spacer flex="1" /> 46 | <button oncommand="hBookmark.urlEditor.reset();" label="&hBookmark.urlEditor.setDefault;" id="hBookmark-urlEditor-reset"/> 47 | </hbox> 48 | </vbox> 49 | </dialog> 50 | -------------------------------------------------------------------------------- /chrome/content/autoloader.js: -------------------------------------------------------------------------------- 1 | if (!hBookmark) 2 | var hBookmark = {}; 3 | 4 | Components.utils.import("resource://hatenabookmark/modules/00-utils.jsm", 5 | hBookmark); 6 | 7 | /** 8 | * 指定されたURIのスクリプトを読み込む。 9 | * 10 | * @param {String} uri スクリプトのURI。"/"で終わっていた場合は 11 | * そのディレクトリ直下のすべてのスクリプトを読み込む。 12 | */ 13 | hBookmark.load = function (uri) { 14 | if (uri.charAt(uri.length - 1) === "/") { 15 | var load = arguments.callee; 16 | load.getScriptURIs(uri) 17 | .forEach(function (uri) load.call(this, uri), this); 18 | return; 19 | } 20 | 21 | var loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] 22 | .getService(Components.interfaces.mozIJSSubScriptLoader); 23 | var env = { __proto__: this }; 24 | loader.loadSubScript(uri, env); 25 | if (env.EXPORT) 26 | env.EXPORT.forEach(function (name) this[name] = env[name], this); 27 | }; 28 | 29 | hBookmark.load.getScriptURIs = function (dirURI) { 30 | const Cc = Components.classes; 31 | const Ci = Components.interfaces; 32 | var uris = []; 33 | var dirPath = dirURI.replace(/^[\w-]+:\/\/[\w.:-]+\//, ""); 34 | var baseURI = 'chrome://hatenabookmark/' + dirPath; 35 | // XXX jarファイルに固めるのならnsIZipReaderを使ってごにょごにょする。 36 | var ios = Cc["@mozilla.org/network/io-service;1"]. 37 | getService(Ci.nsIIOService); 38 | var baseURIObject = ios.newURI(baseURI, null, null); 39 | var registry = Cc["@mozilla.org/chrome/chrome-registry;1"]. 40 | getService(Ci.nsIChromeRegistry); 41 | var dir = registry.convertChromeURL(baseURIObject) 42 | .QueryInterface(Ci.nsIFileURL).file; 43 | if (!dir.exists() || !dir.isDirectory()) return uris; 44 | var files = dir.directoryEntries; 45 | while (files.hasMoreElements()) { 46 | var file = files.getNext().QueryInterface(Ci.nsIFile); 47 | if (/\.js$/.test(file.leafName)) 48 | uris.push(baseURI + file.leafName); 49 | } 50 | return uris.sort(); 51 | }; 52 | 53 | if (!("autoload" in hBookmark) || hBookmark.autoload) { 54 | hBookmark.loadModules(); 55 | hBookmark.load("chrome://hatenabookmark/content/common/"); 56 | hBookmark.load(location.href.replace(/\.\w+$/, "/")); 57 | } 58 | -------------------------------------------------------------------------------- /chrome/locale/en-US/browser.dtd: -------------------------------------------------------------------------------- 1 | <!ENTITY hBookmark.toolbar.title "Hatena Bookmark Toolbar"> 2 | <!ENTITY hBookmark.toolbar.titleKey "A"> 3 | <!ENTITY hBookmark.toolbar.recentLabel "Recent Hatena Bookmarks"> 4 | <!ENTITY hBookmark.toolbar.add "Add to Hatena Bookmark"> 5 | <!ENTITY hBookmark.toolbar.home "Open My Hatena Bookmark page"> 6 | <!ENTITY hBookmark.toolbar.homeKey "H"> 7 | <!ENTITY hBookmark.toolbar.top "Open Hatena Bookmark Top Page"> 8 | <!ENTITY hBookmark.toolbar.topKey "T"> 9 | <!ENTITY hBookmark.toolbar.favorite "Open My Favorites Page"> 10 | <!ENTITY hBookmark.toolbar.favoriteKey "F"> 11 | 12 | <!ENTITY hBookmark.sidebar.label "Hatena Bookmark"> 13 | <!ENTITY hBookmark.sidebar.key "A"> 14 | 15 | <!ENTITY hBookmark.statusbar.label "Hatena Panel"> 16 | <!ENTITY hBookmark.statusbar.now_syncing "Sync with Bookmarks…"> 17 | <!ENTITY hBookmark.statusbar.tooltip.addButton "Add to Hatena Bookmark"> 18 | <!ENTITY hBookmark.statusbar.tooltip.counter "Show on Hatena Bookmark"> 19 | <!ENTITY hBookmark.statusbar.tooltip.comment "Show Comments"> 20 | 21 | <!ENTITY hBookmark.subview.add "Add / Edit"> 22 | <!ENTITY hBookmark.subview.comment "Show Comments"> 23 | <!ENTITY hBookmark.subview.showentry "Show on Hatena Bookmark"> 24 | <!ENTITY hBookmark.subview.sync "Sync"> 25 | 26 | <!ENTITY hBookmark.menu.main "Hatena Bookmark"> 27 | <!ENTITY hBookmark.menu.mainKey "B"> 28 | <!ENTITY hBookmark.menu.sync "Sync"> 29 | <!ENTITY hBookmark.menu.syncKey "S"> 30 | <!ENTITY hBookmark.menu.config "Config"> 31 | <!ENTITY hBookmark.menu.configKey "C"> 32 | <!ENTITY hBookmark.menu.help "Help"> 33 | <!ENTITY hBookmark.menu.helpKey "H"> 34 | 35 | <!ENTITY hBookmark.menu.add "Add This Page to Hatena Bookmark"> 36 | <!ENTITY hBookmark.menu.addKey "B"> 37 | <!ENTITY hBookmark.menu.addlink "Add This Link to Hatena Bookmark"> 38 | <!ENTITY hBookmark.menu.addlinkKey "B"> 39 | <!ENTITY hBookmark.menu.showentry "Show This Page on Hatena Bookmark"> 40 | <!ENTITY hBookmark.menu.showentryKey "E"> 41 | <!ENTITY hBookmark.menu.showlink "Show This Link on Hatena Bookmark"> 42 | <!ENTITY hBookmark.menu.showlinkKey "E"> 43 | <!ENTITY hBookmark.menu.showcomment "Show This Link on CommentViewer"> 44 | <!ENTITY hBookmark.menu.showcommentKey "C"> 45 | <!ENTITY hBookmark.menu.searchtext "Search Hatena Bookmark for: "> 46 | <!ENTITY hBookmark.menu.searchtextKey "B"> 47 | -------------------------------------------------------------------------------- /tests/javascripts/URLSuggestion.js: -------------------------------------------------------------------------------- 1 | var hBookmark; 2 | 3 | function warmUp() { 4 | utils.include("btil.js"); 5 | let global = loadAutoloader("chrome://hatenabookmark/content/addPanel.xml"); 6 | hBookmark = global.hBookmark; 7 | // EXPORT に含まれていないメンバもテストするため直接読み込む。 8 | Components.classes["@mozilla.org/moz/jssubscript-loader;1"] 9 | .getService(Components.interfaces.mozIJSSubScriptLoader) 10 | .loadSubScript("chrome://hatenabookmark/content/addPanel/20-URLSuggestion.js", hBookmark); 11 | } 12 | 13 | function testEffectiveDomain() { 14 | assert.isTrue(hBookmark.isSameEffectiveDomain( 15 | "http://www.hatena.ne.jp/", 16 | "http://b.hatena.ne.jp/")); 17 | assert.isFalse(hBookmark.isSameEffectiveDomain( 18 | "http://www.hatena.ne.jp/", 19 | "http://www.goo.ne.jp/")); 20 | } 21 | 22 | function testMetaSuggestor() { 23 | let suggestors = hBookmark.URLSuggestion.suggestors; 24 | assert.equals( 25 | "http://b.hatena.ne.jp/guide/staff_bookmark_04", 26 | suggestors.meta("http://b.hatena.ne.jp/entry/http://b.hatena.ne.jp/guide/staff_bookmark_04", null)) 27 | assert.equals( 28 | null, 29 | suggestors.meta("http://b.hatena.ne.jp/entry/12345", null)) 30 | } 31 | 32 | function testCanonicalResponseSuggestor() { 33 | let suggestor = hBookmark.URLSuggestion.responseSuggestors.canonical; 34 | let response = { 35 | responseText: <html> 36 | <head> 37 | <link rel="canonical" href="http://example.org/canonical"/> 38 | </head> 39 | <body> 40 | <p>Hello, world</p> 41 | </body> 42 | </html>.toXMLString() 43 | } 44 | assert.equals("http://example.org/canonical", 45 | suggestor("http://example.org/", response)); 46 | 47 | response.responseText = <html> 48 | <head> 49 | <link href="canonical-url" rel="canonical"/> 50 | </head> 51 | <body> 52 | <p>Hello, world</p> 53 | </body> 54 | </html>.toXMLString(); 55 | assert.equals("http://example.org/canonical-url", 56 | suggestor("http://example.org/", response)); 57 | assert.equals("http://example.org/canonical-url#fragment", 58 | suggestor("http://example.org/#fragment", response)); 59 | } 60 | -------------------------------------------------------------------------------- /chrome/content/browser/14-Star-AddButton.js: -------------------------------------------------------------------------------- 1 | 2 | function AddButton(uri, title, location) { 3 | this.uri = uri; 4 | this.title = title || ''; 5 | this.location = location || ''; 6 | this.operator = new Star.Operator(this.uri, this.title, this.location); 7 | } 8 | 9 | extend(AddButton, { 10 | PALETTE_DELAY: 800, 11 | }); 12 | 13 | extend(AddButton.prototype, { 14 | addStar: function SAB_addStar(element, color) { 15 | if (!Star.canModify) { 16 | hOpenUILink(Star.strings.get('starGuideURL')); 17 | return; 18 | } 19 | let tempStar = this.addTemporaryStar(element); 20 | let quote = String(window.getSelection()); 21 | this.operator.add(color, quote, bind(onStarAdded, this)); 22 | function onStarAdded(star) { 23 | if (!star || star.errors) { 24 | tempStar.parentNode.removeChild(tempStar); 25 | return; 26 | } 27 | let elem = Star.createStar(star.name, star.quote, star.color); 28 | tempStar.parentNode.replaceChild(elem, tempStar); 29 | } 30 | }, 31 | 32 | addTemporaryStar: function SAB_addTemporaryStar(element) { 33 | let star = Star.createTemporaryStar(); 34 | element.parentNode.appendChild(star); 35 | return star; 36 | }, 37 | 38 | showPaletteLater: function SAB_showPaletteLater(element) { 39 | if (!Star.canModify) return; 40 | this.paletteTimer = setTimeout(method(this, 'showPalette'), 41 | AddButton.PALETTE_DELAY, element); 42 | }, 43 | 44 | showPalette: function SAB_showPalette(element) { 45 | this.paletteCommand = 46 | this.operator.getAvailableColors(bind(onGotColors, this)); 47 | function onGotColors(colors) { 48 | this.paletteCommand = null; 49 | if (!colors) return; 50 | Star.Palette.show(colors, this, element); 51 | } 52 | }, 53 | 54 | cancelPalette: function SAB_cancelPalette() { 55 | if (this.paletteTimer) { 56 | clearTimeout(this.paletteTimer); 57 | this.paletteTimer = 0; 58 | } 59 | if (this.paletteCommand) { 60 | this.paletteCommand.cancel(); 61 | this.paletteCommand = null; 62 | } 63 | }, 64 | }); 65 | 66 | Star.AddButton = AddButton; 67 | -------------------------------------------------------------------------------- /chrome/content/common/50-BookmarkContext.js: -------------------------------------------------------------------------------- 1 | var EXPORT = ["BookmarkContext"]; 2 | 3 | function BookmarkContext(popup) { 4 | this.bookmark = null; 5 | this.bookmarks = null; 6 | popup._context = this; 7 | } 8 | 9 | extend(BookmarkContext.prototype, { 10 | strings: new Strings("chrome://hatenabookmark/locale/popups.properties"), 11 | 12 | build: function EC_build(target) { 13 | target = UIUtils.getBookmarkElement(target); 14 | if (!target || !target.bookmark) return false; 15 | this.bookmark = target.bookmark; 16 | this.bookmarks = target.bookmarks || null; 17 | this._getItem("deleteAll").hidden = !this.bookmarks; 18 | if (Prefs.link.get("openInNewTab")) { 19 | this._getItem("open").removeAttribute("default"); 20 | this._getItem("openInNewTab").setAttribute("default", "true"); 21 | } else { 22 | this._getItem("open").setAttribute("default", "true"); 23 | this._getItem("openInNewTab").removeAttribute("default"); 24 | } 25 | return true; 26 | }, 27 | 28 | _getItem: function EC__getItem(key) { 29 | return document.getElementById("hBookmark-bookmark-context-" + key); 30 | }, 31 | 32 | openIn: function EC_openIn(where) { 33 | openUILinkIn(this.bookmark.url, where); 34 | }, 35 | 36 | openEntry: function EC_openEntry(event) { 37 | hOpenUILink(this.bookmark.entryURL, event); 38 | }, 39 | 40 | edit: function EC_edit() { 41 | getTopWin().hBookmark.AddPanelManager.showPanel(this.bookmark); 42 | }, 43 | 44 | delete: function EC_delete() { 45 | let bookmark = this.bookmark; 46 | let command = new RemoteCommand('delete', { 47 | bookmark: bookmark, 48 | onError: function () { 49 | UIUtils.alertBookmarkError('delete', bookmark); 50 | } 51 | }); 52 | command.execute(); 53 | }, 54 | 55 | deleteAll: function EC_deleteAll() { 56 | let bookmarks = this.bookmarks; 57 | if (!UIUtils.confirmDeleteBookmarks(bookmarks)) return; 58 | let command = new RemoteCommand('delete', { 59 | bookmarks: bookmarks, 60 | onError: function () { 61 | UIUtils.alertBookmarkError('delete', bookmarks); 62 | } 63 | }); 64 | command.execute(); 65 | } 66 | }); 67 | -------------------------------------------------------------------------------- /chrome/content/browser/13-Star-Operator.js: -------------------------------------------------------------------------------- 1 | 2 | var Command = Star.Command; 3 | 4 | function Operator(uri, title, location) { 5 | this.uri = uri; 6 | this.title = title || ''; 7 | this.location = location || ''; 8 | this.token = ''; 9 | } 10 | 11 | extend(Operator.prototype, { 12 | getAvailableColors: function SO_getAvailableColors(callback) { 13 | return new Command(Command.GET_AVAILABLE_COLORS, { uri: this.uri }, 14 | bind(onGotPalette, this)); 15 | function onGotPalette(res) { 16 | if (!res) { 17 | callback(null); 18 | return; 19 | } 20 | this.token = res.token; 21 | callback(res.color_star_counts); 22 | } 23 | }, 24 | 25 | // If you want to add a colored star, you must call |getAvailableColors| 26 | // before calling |add|. 27 | add: function SO_add(color, quote, callback) { 28 | quote = (quote === null || quote === undefined) ? '' : String(quote); 29 | if (!color || color === Star.COLOR_YELLOW) 30 | this._addNormalStar(quote, callback); 31 | else 32 | this._addColoredStar(color, quote, callback); 33 | }, 34 | 35 | _addNormalStar: function SO__addNormalStar(quote, callback) { 36 | let query = { 37 | quote: quote, 38 | uri: this.uri, 39 | title: this.title, 40 | location: this.location, 41 | rks: Star.rks, 42 | }; 43 | new Command(Command.ADD_STAR, query, callback); 44 | }, 45 | 46 | _addColoredStar: function SO__addColoredStar(color, quote, callback) { 47 | let query = { 48 | color: color, 49 | token: this.token, 50 | uri: this.uri, 51 | }; 52 | new Command(Command.SET_COLOR, query, bind(onSetColor, this)); 53 | function onSetColor(res) { 54 | if (!res) { 55 | callback(null); 56 | return; 57 | } 58 | let query = { 59 | quote: quote, 60 | uri: this.uri, 61 | title: this.title, 62 | location: this.location, 63 | rks: Star.rks, 64 | }; 65 | new Command(Command.ADD_STAR, query, callback); 66 | } 67 | }, 68 | }); 69 | 70 | Star.Operator = Operator; 71 | -------------------------------------------------------------------------------- /chrome/skin/classic/star.css: -------------------------------------------------------------------------------- 1 | 2 | .hBookmark-star { 3 | text-decoration: none; 4 | } 5 | 6 | .hBookmark-star > img { 7 | margin-bottom: 3px; 8 | border: none; 9 | vertical-align: middle; 10 | } 11 | 12 | .hBookmark-star-add-button, 13 | /* |#hBookmark-comment-list li img| での指定を上書き */ 14 | #hBookmark-comment-list .hBookmark-star-add-button { 15 | /* XXX |middle| など、フォントサイズに依存した指定のほうがよくないか? */ 16 | vertical-align: -3px; 17 | cursor: pointer; 18 | } 19 | .hBookmark-star-add-button:not(:only-child) { 20 | -moz-margin-end: 3px; 21 | } 22 | 23 | .hBookmark-star-inner-count { 24 | padding: 0 2px; 25 | cursor: pointer; 26 | } 27 | 28 | .hBookmark-star, .hBookmark-star-inner-count { 29 | font: bold 0.88em Arial, sans-serif; 30 | } 31 | 32 | .hBookmark-star.yellow, .hBookmark-star-inner-count.yellow { color: #f4b128; } 33 | .hBookmark-star.green, .hBookmark-star-inner-count.green { color: #8ed701; } 34 | .hBookmark-star.red, .hBookmark-star-inner-count.red { color: #ea475c; } 35 | .hBookmark-star.blue, .hBookmark-star-inner-count.blue { color: #57b1ff; } 36 | .hBookmark-star.purple, .hBookmark-star-inner-count.purple { color: #cd34e3; } 37 | 38 | .hBookmark-star-highlight { 39 | font-style: normal; 40 | font-weight: normal; 41 | background-color: #f4f798; 42 | } 43 | 44 | #hBookmark-star-palette { 45 | -moz-appearance: none !important; 46 | background-color: #fff; 47 | border: 1px solid #b0b0e0; 48 | padding: 1px; 49 | } 50 | 51 | .hBookmark-star-palette-color { 52 | margin: 1px 0 0 0; 53 | list-style-image: url("chrome://hatenabookmark/skin/images/star-palette.png"); 54 | cursor: pointer; 55 | } 56 | .hBookmark-star-palette-color:first-child { 57 | margin: 0; 58 | } 59 | 60 | #hBookmark-star-palette-green { 61 | -moz-image-region: rect(11px, 28px, 22px, 14px); 62 | } 63 | #hBookmark-star-palette-green[canadd] { 64 | -moz-image-region: rect(11px, 14px, 22px, 0px); 65 | } 66 | #hBookmark-star-palette-red { 67 | -moz-image-region: rect(22px, 28px, 33px, 14px); 68 | } 69 | #hBookmark-star-palette-red[canadd] { 70 | -moz-image-region: rect(22px, 14px, 33px, 0px); 71 | } 72 | #hBookmark-star-palette-blue { 73 | -moz-image-region: rect(33px, 28px, 44px, 14px); 74 | } 75 | #hBookmark-star-palette-blue[canadd] { 76 | -moz-image-region: rect(33px, 14px, 44px, 0px); 77 | } 78 | #hBookmark-star-palette-purple { 79 | -moz-image-region: rect(44px, 28px, 55px, 14px); 80 | } 81 | #hBookmark-star-palette-purple[canadd] { 82 | -moz-image-region: rect(44px, 14px, 55px, 0px); 83 | } 84 | -------------------------------------------------------------------------------- /chrome/content/popupsOverlay.xul: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <?xml-stylesheet type="text/css" href="popups.css"?> 3 | <!DOCTYPE overlay SYSTEM "chrome://hatenabookmark/locale/popups.dtd"> 4 | <overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> 5 | 6 | <popupset id="mainPopupSet"> 7 | <menupopup id="hBookmark-bookmark-context" 8 | onpopupshowing="return new hBookmark.BookmarkContext(this).build(document.popupNode);"> 9 | <menuitem id="hBookmark-bookmark-context-open" 10 | label="&hBookmark.bookmarkContext.openLabel;" 11 | accesskey="&hBookmark.bookmarkContext.openKey;" 12 | oncommand="this.parentNode._context.openIn('current');"/> 13 | <menuitem id="hBookmark-bookmark-context-openInNewTab" 14 | label="&hBookmark.bookmarkContext.openInNewTabLabel;" 15 | accesskey="&hBookmark.bookmarkContext.openInNewTabKey;" 16 | oncommand="this.parentNode._context.openIn('tab');"/> 17 | <menuseparator id="hBookmark-bookmark-context-afterOpenSeparator"/> 18 | <menuitem id="hBookmark-bookmark-context-openEntry" 19 | label="&hBookmark.bookmarkContext.openEntryLabel;" 20 | accesskey="&hBookmark.bookmarkContext.openEntryKey;" 21 | oncommand="this.parentNode._context.openEntry(event);" 22 | onclick="checkForMiddleClick(this, event);"/> 23 | <menuitem id="hBookmark-bookmark-context-edit" 24 | label="&hBookmark.bookmarkContext.editLabel;" 25 | accesskey="&hBookmark.bookmarkContext.editKey;" 26 | oncommand="this.parentNode._context.edit();"/> 27 | <menuseparator id="hBookmark-bookmark-context-beforeDeleteSeparator"/> 28 | <menuitem id="hBookmark-bookmark-context-delete" 29 | label="&hBookmark.bookmarkContext.deleteLabel;" 30 | accesskey="&hBookmark.bookmarkContext.deleteKey;" 31 | oncommand="this.parentNode._context.delete();"/> 32 | <menuitem id="hBookmark-bookmark-context-deleteAll" 33 | label="&hBookmark.bookmarkContext.deleteAllLabel;" 34 | accesskey="&hBookmark.bookmarkContext.deleteAllKey;" 35 | oncommand="this.parentNode._context.deleteAll();"/> 36 | </menupopup> 37 | 38 | <tooltip id="hBookmark-bookmark-tooltip"/> 39 | </popupset> 40 | 41 | </overlay> 42 | -------------------------------------------------------------------------------- /chrome/content/browser/20-AddPanelManager.js: -------------------------------------------------------------------------------- 1 | var EXPORT = ["AddPanelManager"]; 2 | 3 | var Bookmark = Model.Bookmark; 4 | 5 | var AddPanelManager = { 6 | currentPanel: null, 7 | 8 | // XXX AddPanelManagerではなく全体に属すべき。 9 | getBookmarkFor: function APM_getBookmarkFor(item) { 10 | let win = item instanceof Ci.nsIDOMWindow ? item : null; 11 | let uri = win ? newURI(win.location.href) : 12 | (item instanceof Ci.nsIURI) ? item : newURI(item); 13 | let url = URLNormalizer.normalize(uri).asciiSpec; 14 | let bookmark = Bookmark.searchByUrl(url)[0]; 15 | if (bookmark) return bookmark; 16 | bookmark = new Bookmark(); 17 | bookmark.title = win ? (win.document.title || url) : ""; 18 | bookmark.url = url; 19 | bookmark.comment = ""; 20 | return bookmark; 21 | }, 22 | 23 | get panelDialog() { 24 | return this.currentPanel && this.currentPanel.document.documentElement; 25 | }, 26 | 27 | get panelContent() { 28 | if (!this.currentPanel || this.currentPanel.closed) 29 | return null; 30 | let doc = this.currentPanel.document; 31 | return doc.getElementById("hBookmarkAddPanelContent"); 32 | }, 33 | 34 | toggle: function APM_toggle() { 35 | let panel = this.panelContent; 36 | if (panel && panel.bookmark.url === gBrowser.currentURI.asciiSpec) 37 | this.panelDialog.cancelDialog(); 38 | else 39 | this.showPanel(gBrowser.contentWindow); 40 | }, 41 | 42 | showPanel: function APM_showPanel(item, options) { 43 | if (!User.user) { 44 | UIUtils.encourageLogin(); 45 | return; 46 | } 47 | let bookmark = (item instanceof Bookmark) ? item : this.getBookmarkFor(item); 48 | if (!/^https?:\/\//.test(bookmark.url)) return; 49 | if (!bookmark.title && options && options.title) 50 | bookmark.title = options.title; 51 | 52 | let panel = this.panelContent; 53 | if (panel) { 54 | panel.show(bookmark); 55 | } else { 56 | this.currentPanel = window.openDialog( 57 | "chrome://hatenabookmark/content/addPanel.xul", 58 | "_blank", 59 | "chrome, dialog, resizable, alwaysRaised, centerscreen", 60 | { bookmark: bookmark }); 61 | } 62 | }, 63 | 64 | createSaveSuccessCallback: function (bookmark, confirm) { 65 | var url = bookmark.url; 66 | return function () { 67 | HTTPCache.entry.clear(url); 68 | if (confirm) gBrowser.addTab(entryURL(url)); 69 | }; 70 | }, 71 | 72 | createSaveErrorCallback: function (bookmark) { 73 | var self = this; 74 | return function () { 75 | self.showPanel(bookmark); 76 | }; 77 | }, 78 | }; 79 | -------------------------------------------------------------------------------- /chrome/content/tests/test-database.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | /** 4 | * データベース関係のテスト 5 | */ 6 | 7 | var modules = {}; 8 | Components.utils.import("resource://hatenabookmark/modules/60-Database.js", modules); 9 | 10 | QUnit.module("Entity"); 11 | 12 | QUnit.asyncTest("TIMESTAMP 型のフィールドを指定すると自動的に setter/getter が生成される", function () { 13 | var TimerEntity = modules.Entity({ 14 | name : "timertest", 15 | fields : { 16 | timeA : "TIMESTAMP", 17 | }, 18 | }); 19 | 20 | var t = new TimerEntity({ timeA: 12345678 }); 21 | // t.timeA は単なる data property ではなく accessor property 22 | 23 | ok(typeof t.timeA === "object"); 24 | strictEqual(t.timeA.getTime(), 12345678); 25 | 26 | // setter なので, 単なる数値を代入しても Date オブジェクトに変換される 27 | t.timeA = 7654321; 28 | strictEqual(t.timeA.getTime(), 7654321); 29 | 30 | // オブジェクトを代入した場合はその値がそのまま保持される 31 | var d = new Date(4545454); 32 | t.timeA = d; 33 | strictEqual(t.timeA, d); 34 | 35 | QUnit.start(); 36 | }); 37 | 38 | QUnit.asyncTest("LIST 型のフィールドを指定すると自動的に setter/getter が生成される", function () { 39 | var ListEntity = modules.Entity({ 40 | name : "listtest", 41 | fields : { 42 | listA : "LIST", 43 | }, 44 | }); 45 | 46 | var t = new ListEntity({ listA: "a,b,c" }); 47 | // t.listA は単なる data property ではなく accessor property 48 | 49 | ok(typeof t.listA === "object"); 50 | deepEqual(t.listA, ["a","b","c"]); 51 | 52 | // setter なので, 単なる文字列を代入しても配列に変換される 53 | t.listA = "d,e,f"; 54 | deepEqual(t.listA, ["d","e","f"]); 55 | 56 | // オブジェクトを代入した場合はその値がそのまま保持される 57 | var obj = ["1","3"]; 58 | t.listA = obj; 59 | strictEqual(t.listA, obj); 60 | 61 | QUnit.start(); 62 | }); 63 | 64 | QUnit.asyncTest("自動的に生成された setter/getter が複数個存在しても問題ない", function () { 65 | var TestEntity = modules.Entity({ 66 | name : "testtest", 67 | fields : { 68 | listA : "LIST", 69 | timeA : "TIMESTAMP", 70 | listB : "LIST", 71 | timeB : "TIMESTAMP", 72 | }, 73 | }); 74 | 75 | var t = new TestEntity({ listA: "a,b,c", listB: "1,2,3", timeA: 7000000, timeB: 6000000 }); 76 | 77 | deepEqual(t.listA, ["a","b","c"]); 78 | deepEqual(t.listB, ["1","2","3"]); 79 | strictEqual(t.timeA.getTime(), 7000000); 80 | strictEqual(t.timeB.getTime(), 6000000); 81 | 82 | var lA = ["la"]; 83 | var lB = ["lb"]; 84 | var tA = new Date(5000000); 85 | var tB = new Date(4000000); 86 | t.listA = lA; 87 | t.listB = lB; 88 | t.timeA = tA; 89 | t.timeB = tB; 90 | 91 | strictEqual(t.listA, lA); 92 | strictEqual(t.listB, lB); 93 | strictEqual(t.timeA, tA); 94 | strictEqual(t.timeB, tB); 95 | 96 | QUnit.start(); 97 | }); 98 | 99 | }).call(this); 100 | -------------------------------------------------------------------------------- /tests/javascripts/ExpireCache.test.js: -------------------------------------------------------------------------------- 1 | 2 | var _global = this; 3 | var hBookmark; 4 | 5 | function warmUp() { 6 | utils.include("btil.js"); 7 | var global = loadAutoloader("chrome://hatenabookmark/content/unknown.xul"); 8 | hBookmark = global.hBookmark; 9 | hBookmark.extend(_global, hBookmark); 10 | } 11 | 12 | function setUp() { 13 | let filter = "['\\^https:\\/\\/.*\\$', '\\^https?:\\/\\/192\\\\.168\\\\.\\\\d+\\\\.\\\\d+.*\\$', '\\^https?:\\/\\/172\\\\.((1[6-9])|(2[0-9])|(3[0-1]))\\\\.\\\\d+\\\\.\\\\d+.*\\$', '\\^https?:\\/\\/10\\\\.\\\\d+\\\\.\\\\d+\\\\.\\\\d+.*\\$']"; 14 | HTTPCache.counter.setFilter(eval(filter)); 15 | } 16 | 17 | function testSeriarizer() { 18 | let cache = new ExpireCache('unevaltest', null, 'uneval'); 19 | cache.set('foo', {foo: 1, bar: 2}); 20 | assert.equals(1, cache.get('foo').foo); 21 | assert.equals(2, cache.get('foo').bar); 22 | } 23 | 24 | function testCache() { 25 | let cache = new ExpireCache(); 26 | let cache2 = new ExpireCache('foo'); 27 | cache.set('foo', 1); 28 | assert.isTrue(cache.has('foo')); 29 | assert.isFalse(cache.has('oooo')); 30 | assert.equals(cache.get('foo'), 1); 31 | assert.isNull(cache2.get('foo')); 32 | assert.isFalse(cache2.get('foo')); 33 | 34 | cache.set('f', 0); 35 | assert.isTrue(cache.has('f')); 36 | 37 | cache.set('bar', 1, 1); 38 | yield 2000; 39 | assert.isNull(cache.get('bar')); 40 | assert.isFalse(cache.has('bar')); 41 | } 42 | 43 | function testHTTPCounter() { 44 | assert.isTrue(HTTPCache.counter.get('http://www.hatena.ne.jp/my') > 10); 45 | assert.isTrue(HTTPCache.counter.has('http://www.hatena.ne.jp/my')); 46 | assert.isTrue(HTTPCache.counter.get('http://www.hatena.ne.jp/my') > 10); 47 | } 48 | 49 | function testHTTPSCounter() { 50 | let c = HTTPCache.counter; 51 | 52 | assert.isFalse(c.isValid('https://example.com/')); 53 | assert.isTrue(c.isValid('http://example.com/')); 54 | } 55 | 56 | function testLocalURLS() { 57 | let c = HTTPCache.counter; 58 | assert.isFalse(c.isValid('http://10.3.2.2/hogehuga?foo=bar')); 59 | assert.isFalse(c.isValid('https://10.3.2.2/hogehuga?foo=bar')); 60 | assert.isFalse(c.isValid('http://192.168.3.22/')); 61 | assert.isFalse(c.isValid('https://192.168.3.22/')); 62 | assert.isFalse(c.isValid('http://192.168.255.200/hogehuga?foo=bar')); 63 | assert.isTrue(c.isValid('http://10.example.com/hogehuga?foo=bar')); 64 | assert.isTrue(c.isValid('http://172.168.3.22/')); 65 | assert.isFalse(c.isValid('http://172.16.3.22/')); 66 | assert.isFalse(c.isValid('http://172.16.3.22/foobar?baz')); 67 | assert.isFalse(c.isValid('http://172.22.3.22/')); 68 | assert.isFalse(c.isValid('http://172.31.3.22/')); 69 | assert.isFalse(c.isValid('https://172.31.3.22/')); 70 | assert.isTrue(c.isValid('http://172.32.3.22/')); 71 | } 72 | 73 | function testHTTPComment() { 74 | let res = HTTPCache.comment.get('http://www.hatena.ne.jp/my'); 75 | assert.isTrue(res); 76 | assert.isTrue(res.count); 77 | assert.isTrue(res.title); 78 | } 79 | 80 | 81 | -------------------------------------------------------------------------------- /tests/javascripts/suffixarray.test.js: -------------------------------------------------------------------------------- 1 | 2 | var _global = this; 3 | var hBookmark; 4 | 5 | /* 6 | * p は一時デバッグ用 7 | */ 8 | var p = function (value) { 9 | ConsoleService.logStringMessage(Array.map(arguments, String).join("\n")); 10 | return value; 11 | } 12 | 13 | /* 14 | * 簡易ベンチマーク 15 | */ 16 | p.b = function (func, name) { 17 | name = 'Benchmark ' + (name || '') + ': '; 18 | let now = new Date * 1; 19 | func(); 20 | let t = (new Date * 1) - now; 21 | p(name + t); 22 | return t; 23 | } 24 | 25 | function warmUp() { 26 | utils.include("../../chrome/content/common/05-SuffixArray.js"); 27 | // #utils.include("btil.js"); 28 | // #var global = loadAutoloader("chrome://hatenabookmark/content/unknown.xul"); 29 | // #hBookmark = global.hBookmark; 30 | // #hBookmark.extend(_global, hBookmark); 31 | } 32 | 33 | function setUp() { 34 | } 35 | 36 | function tearDown() { 37 | } 38 | 39 | function testNagayama() { 40 | // var data = utils.readFrom('data/secondlife.search.data', 'UTF-8'); 41 | var data = utils.readFrom('data/nagayama.search.data', 'UTF-8'); 42 | // var data = utils.readFrom('data/naoya.search.data', 'UTF-8'); 43 | // var data = utils.readFrom('data/otsune.search.data', 'UTF-8'); 44 | data = data.substr(0, data.length * 3/4); 45 | assert.isTrue(data.length); 46 | var sary, finder, indexes; 47 | 48 | sary = new SuffixArray(data); 49 | sary.make(); 50 | 51 | return; 52 | var word = 'ブックマーク'; 53 | //p.b(function() { 54 | // indexes = Object.keys(finder); 55 | indexes = sary.search(word); 56 | //}, 'search'); 57 | 58 | p('match:'+indexes.length + ' / ' + indexes); 59 | assert.equals('307,330,2340,4386,4779,7158,7170,8218,8251,9260,9418,11387'.split(',').map(function(i) parseInt(i)), indexes); 60 | //p(indexes.map(function(index) sary.string.substr(index-5, word.length+10)).join(" # ")); 61 | } 62 | 63 | 64 | function testSuffixArray() 65 | { 66 | return; 67 | let str = <>map foo bar map baz substr mappp ppmap maa 68 | Construction began in 1974 on the 19 story structure, with plans to name it after a construction materials company that was to call it home.[1] Construction ended in 1974 on the $16 million dollar project,[2] but the namesake suffered economic setbacks and did not move into the building, leaving it nameless.[1] The building opened in 1975, and Benjamin Franklin Federal Savings and Loan Association moved their headquarters to the building and were able to get the building named as the Benjamin Franklin Plaza.[1] 69 | </>.toString(); 70 | let sary = new SuffixArray(str); 71 | p(''+sary.search('llion')); 72 | 73 | return; 74 | 75 | let index; 76 | index = finder.next(); 77 | assert.equals(index, 3); 78 | index = finder.next(); 79 | assert.equals(index, 6); 80 | index = finder.next(); 81 | assert.equals(index, 13); 82 | 83 | assert.raises(StopIteration, function() { 84 | finder.next(); 85 | }, this); 86 | 87 | finder = sary.finder('x'); 88 | index = finder.next(); 89 | assert.equals(index, sary.length - 1); 90 | 91 | finder = sary.finder('ba'); 92 | let indexes = Object.keys(finder); 93 | assert.equals([3,6,13], indexes); 94 | } 95 | -------------------------------------------------------------------------------- /resources/css/search-embedder.css: -------------------------------------------------------------------------------- 1 | #hBookmark-search { 2 | font-size: 1em; 3 | line-height: 1.4; 4 | color: #000; 5 | margin: 0; 6 | padding: 0; 7 | width: 30%; 8 | max-width: 25em; 9 | float: right; 10 | } 11 | #hBookmark-search span, 12 | #hBookmark-search a, 13 | #hBookmark-search b, 14 | #hBookmark-search em, 15 | #hBookmark-search img, 16 | #hBookmark-search div, 17 | #hBookmark-search dl, 18 | #hBookmark-search dt, 19 | #hBookmark-search dd { 20 | font: inherit; 21 | background: none; 22 | color: inherit; 23 | margin: 0; 24 | padding: 0; 25 | border: none; 26 | } 27 | #hBookmark-search :link { 28 | text-decoration: underline; 29 | color: #2200cc; 30 | } 31 | #hBookmark-search :visited { 32 | text-decoration: underline; 33 | color: #551a8b; 34 | } 35 | #hBookmark-search .hBookmark-search-heading { 36 | margin-bottom: 0.3em; 37 | padding-bottom: 0.2em; 38 | border-bottom: 1px solid #c9d7f1; 39 | overflow: hidden; 40 | position: relative; 41 | } 42 | #hBookmark-search .hBookmark-search-title { 43 | background: url("http://cdn-ak.b.st-hatena.com/images/favicon.gif") left center no-repeat; 44 | padding-left: 18px; 45 | font-weight:bold; 46 | } 47 | #hBookmark-search .hBookmark-search-user { 48 | color: inherit; 49 | text-decoration: none; 50 | font-size: 12px; 51 | display: inline-block; 52 | text-align: right; 53 | float: right; 54 | margin-bottom: 1em; 55 | white-space: nowrap; 56 | } 57 | #hBookmark-search a > img { 58 | margin: 0 3px -5px 0; 59 | } 60 | #hBookmark-search .hBookmark-search-status { 61 | font-size: 12px; 62 | } 63 | #hBookmark-search div.hBookmark-search-container { 64 | } 65 | #hBookmark-search dl { 66 | clear: both; 67 | margin: 0; 68 | padding: 0 0 10px 20px; 69 | } 70 | #hBookmark-search dt { 71 | margin-top: 1em; 72 | } 73 | #hBookmark-search dd { 74 | font-size: 90%; 75 | margin: 0.2em 0; 76 | } 77 | #hBookmark-search .hBookmark-search-comment { 78 | color: #777777; 79 | } 80 | #hBookmark-search .hBookmark-search-tag { 81 | color: #6666cc; 82 | } 83 | #hBookmark-search dd.hBookmark-search-info { 84 | } 85 | #hBookmark-search .hBookmark-search-url { 86 | color: green; 87 | margin-right: 3px; 88 | } 89 | #hBookmark-search a.hBookmark-search-counter { 90 | display: inline-block; 91 | } 92 | 93 | #hBookmark-search dt > a > img { 94 | position: relative; 95 | margin-left: -20px; 96 | } 97 | #hBookmark-search .hBookmark-search-many { 98 | background-color: #fff0f0; 99 | color: #ff6666; 100 | font-weight: bold; 101 | } 102 | #hBookmark-search .hBookmark-search-too-many { 103 | background-color: #ffcccc; 104 | color: #ff0000; 105 | font-weight: bold; 106 | } 107 | #hBookmark-search .hBookmark-search-more { 108 | text-align: right; 109 | margin: 0.5em 0 0 0; 110 | } 111 | #hBookmark-search .hBookmark-search-more > a { 112 | background: url("http://cdn-ak.b.st-hatena.com/images/favicon.gif") left center no-repeat; 113 | padding-left: 18px; 114 | color: #7777cc; 115 | } 116 | #hBookmark-search .hBookmark-search-query, 117 | #hBookmark-search em { 118 | font-weight: bold; 119 | } 120 | -------------------------------------------------------------------------------- /chrome/content/browser/25-ContextMenu.js: -------------------------------------------------------------------------------- 1 | 2 | var EXPORT = ['ContextMenu']; 3 | 4 | elementGetter(this, 'addentry', 'hBookmark-menu-addentry', document); 5 | elementGetter(this, 'addlink', 'hBookmark-menu-addlink', document); 6 | elementGetter(this, 'showentry', 'hBookmark-menu-showentry', document); 7 | elementGetter(this, 'showlink', 'hBookmark-menu-showlink', document); 8 | elementGetter(this, 'showcomment', 'hBookmark-menu-showcomment', document); 9 | elementGetter(this, 'searchtext', 'hBookmark-menu-searchtext', document); 10 | 11 | var ContextMenu = { 12 | registerEventListeners: function() { 13 | let contextMenu = document.getElementById("contentAreaContextMenu"); 14 | contextMenu.addEventListener('popupshowing', ContextMenu.contextMenuHandler, false); 15 | }, 16 | unregisterEventListeners: function() { 17 | // contextMenu.removeEventListenr('popupshowing', ContextMenu.contextMenuHandler, false); 18 | }, 19 | contextMenuHandler: function(ev) { 20 | if (!Prefs.bookmark.get('contextmenu.enabled')) { 21 | addentry.setAttribute('hidden', true); 22 | showentry.setAttribute('hidden', true); 23 | addlink.setAttribute('hidden', true); 24 | showlink.setAttribute('hidden', true); 25 | showcomment.setAttribute('hidden', true); 26 | searchtext.setAttribute('hidden', true); 27 | return; 28 | } 29 | 30 | if (gContextMenu.onTextInput || 31 | gContextMenu.onMailtoLink || 32 | gContextMenu.onMathML || 33 | gContextMenu.isTextSelected || 34 | (gContextMenu.onImage && !gContextMenu.onLink) 35 | ) { 36 | addentry.setAttribute('hidden', true); 37 | addlink.setAttribute('hidden', true); 38 | showentry.setAttribute('hidden', true); 39 | showlink.setAttribute('hidden', true); 40 | showcomment.setAttribute('hidden', true); 41 | if (gContextMenu.isTextSelected) { 42 | let str = searchtext.getAttribute('label').split(':')[0] + ':'; 43 | let sel = document.commandDispatcher.focusedWindow.getSelection().toString(); 44 | str += ' "' + sel.substr(0, 16) + '" '; 45 | searchtext.setAttribute('label', str); 46 | searchtext.removeAttribute('hidden'); 47 | } else { 48 | searchtext.setAttribute('hidden', true); 49 | } 50 | return; 51 | } 52 | if (gContextMenu.onLink) { 53 | addentry.setAttribute('hidden', true); 54 | showentry.setAttribute('hidden', true); 55 | addlink.removeAttribute('hidden'); 56 | showlink.removeAttribute('hidden'); 57 | showcomment.removeAttribute('hidden'); 58 | searchtext.setAttribute('hidden', true); 59 | } else { 60 | addentry.removeAttribute('hidden'); 61 | addlink.setAttribute('hidden', true); 62 | showentry.removeAttribute('hidden'); 63 | showlink.setAttribute('hidden', true); 64 | showcomment.setAttribute('hidden', true); 65 | searchtext.setAttribute('hidden', true); 66 | } 67 | }, 68 | } 69 | 70 | window.addEventListener('load', ContextMenu.registerEventListeners, false) 71 | window.addEventListener('unload', ContextMenu.unregisterEventListeners, false); 72 | -------------------------------------------------------------------------------- /chrome/content/sidebar/20-SidebarManager.js: -------------------------------------------------------------------------------- 1 | var EXPORT = []; 2 | 3 | window.addEventListener("load", initializeSidebar, false); 4 | window.addEventListener("unload", disposeSidebar, false); 5 | 6 | function initializeSidebar() { 7 | User.login(); 8 | 9 | var tagTree = document.getElementById("tag-tree"); 10 | var tagTreeView = new TagTreeView(); 11 | tagTree.view = tagTreeView; 12 | 13 | let tagContext = document.getElementById("hBookmark-tag-context"); 14 | tagContext._context = new TagContext(); 15 | 16 | var bookmarkTree = document.getElementById("bookmark-tree"); 17 | var bookmarkTreeView = new BookmarkTreeView(); 18 | bookmarkTree.view = bookmarkTreeView; 19 | 20 | tagTree.addEventListener("select", tagTreeView, false); 21 | tagTree.addEventListener("click", tagTreeView, false); 22 | EventService.createListener("BookmarksUpdated", tagTreeView); 23 | EventService.createListener("UserChange", tagTreeView); 24 | 25 | let searchbar = document.getElementById("searchbar"); 26 | 27 | tagTree.addEventListener("HB_TagsSelected", bookmarkTreeView, false); 28 | searchbar.addEventListener("input", bookmarkTreeView, false); 29 | searchbar.addEventListener("HB.SearchModeChanged", bookmarkTreeView, false); 30 | bookmarkTree.addEventListener("focus", bookmarkTreeView, false); 31 | bookmarkTree.addEventListener("select", bookmarkTreeView, false); 32 | bookmarkTree.addEventListener("click", bookmarkTreeView, false); 33 | bookmarkTree.addEventListener("keypress", bookmarkTreeView, false); 34 | bookmarkTree.body.addEventListener("mouseover", bookmarkTreeView, false); 35 | bookmarkTree.body.addEventListener("mousemove", bookmarkTreeView, false); 36 | EventService.createListener("BookmarksUpdated", bookmarkTreeView); 37 | EventService.createListener("UserChange", bookmarkTreeView); 38 | 39 | let bookmarkContext = document.getElementById('hBookmark-bookmark-context'); 40 | let bookmarkTreeContext = new BookmarkTreeContext(bookmarkContext); 41 | 42 | setBoxDirection(); 43 | Prefs.bookmark.createListener("sidebar.reverseDirection", setBoxDirection); 44 | 45 | EventService.createListener("UserChange", showSidebarContent); 46 | showSidebarContent(); 47 | searchbar.focus(); 48 | 49 | // 開いた直後はデータを取得できないことがあるので遅延させる。 50 | setTimeout(function () { 51 | if (!tagTreeView.rowCount) 52 | tagTreeView.build(); 53 | }, 500); 54 | } 55 | 56 | function disposeSidebar() { 57 | document.getElementById("tag-tree").view = null; 58 | document.getElementById("bookmark-tree").view = null; 59 | } 60 | 61 | function showSidebarContent() { 62 | let isLoggedIn = !!User.user; 63 | document.getElementById("login-notification").collapsed = isLoggedIn; 64 | document.getElementById("main-content").collapsed = !isLoggedIn; 65 | if (isLoggedIn) { 66 | // ボックスの位置情報が確実に利用できるよう遅延させる。 67 | setTimeout(function () { 68 | document.getElementById("bookmark-tree").view.wrappedJSObject.update(); 69 | }, 0); 70 | } 71 | } 72 | 73 | function setBoxDirection() { 74 | let reverse = Prefs.bookmark.get("sidebar.reverseDirection"); 75 | let dir = reverse ? "reverse" : "normal"; 76 | document.getElementById("main-content").setAttribute("dir", dir); 77 | let pos = reverse ? "after_start" : "before_start"; 78 | document.getElementById("searchbar").setAttribute("popupposition", pos); 79 | } 80 | -------------------------------------------------------------------------------- /tests/javascripts/URLNormalizer.js: -------------------------------------------------------------------------------- 1 | Components.utils.import("resource://hatenabookmark/modules/00-utils.jsm"); 2 | Components.utils.import("resource://hatenabookmark/modules/20-URLNormalizer.jsm"); 3 | 4 | function testNormalize() { 5 | function doTest(expected, actual) { 6 | let uri = IOService.newURI(actual, null, null) 7 | assert.is(expected, URLNormalizer.normalize(uri).asciiSpec); 8 | } 9 | 10 | doTest('http://www.amazon.co.jp/gp/product/B0015IQ776', 11 | 'http://www.amazon.co.jp/exec/obidos/ASIN/B0015IQ776'); 12 | doTest('http://www.amazon.co.jp/gp/product/B0015IQ776', 13 | 'http://www.amazon.co.jp/exec/obidos/ASIN/B0015IQ776/'); 14 | doTest('http://www.amazon.co.jp/gp/product/B0015IQ776', 15 | 'http://www.amazon.co.jp/exec/obidos/ASIN/B0015IQ776/hatena-22/ref=nosim'); 16 | doTest('http://www.amazon.co.jp/gp/product/B0015IQ776', 17 | 'http://www.amazon.co.jp/dp/B0015IQ776/'); 18 | doTest('http://www.amazon.co.jp/gp/product/B0015IQ776', 19 | 'http://www.amazon.co.jp/dp/B0015IQ776'); 20 | doTest('http://www.amazon.co.jp/gp/product/4000077155', 21 | 'http://www.amazon.co.jp/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%B3%E3%83%9E%E3%83%B3%E7%89%A9%E7%90%86%E5%AD%A6-5-%E3%83%95%E3%82%A1%E3%82%A4%E3%83%B3%E3%83%9E%E3%83%B3/dp/4000077155/ref=pd_bxgy_b_img_b'); 22 | doTest('http://www.amazon.co.jp/gp/product/4480064141', 23 | 'http://www.amazon.co.jp/gp/product/4480064141?ie=UTF8&linkCode=as2&tag=blogsofdankog-22n'); 24 | doTest('http://www.amazon.co.jp/gp/product/4844325396', 25 | 'http://www.amazon.co.jp/gp/product/4844325396?ie=UTF8&s=books&qid=1204387191&sr=1-1'); 26 | 27 | doTest('http://www.hatena.ne.jp/', 28 | 'http://www.hatena.ne.jp'); 29 | doTest('http://www.hatena.ne.jp/', 30 | 'http://www.hatena.ne.jp/'); 31 | doTest('https://www.hatena.ne.jp/', 32 | 'https://www.hatena.ne.jp'); 33 | doTest('http://www.asahi.com/politics/update/0327/TKY200803260426.html', 34 | 'http://www.asahi.com/politics/update/0327/TKY200803260426.html?ref=rss'); 35 | doTest('http://naoya.dyndns.org/~naoya/mt/', 36 | 'http://naoya.dyndns.org/%7Enaoya/mt/'); 37 | doTest('http://naoya.dyndns.org/~naoya/mt/', 38 | 'http://naoya.dyndns.org/~naoya/mt/'); 39 | doTest('http://d.hatena.ne.jp/naoya/20080320/1206009912', 40 | 'http://d.hatena.ne.jp/naoya/20080320/1206009912#more'); 41 | doTest('http://d.hatena.ne.jp/naoya/20080320/1206009912', 42 | 'http://d.hatena.ne.jp/naoya/20080320/1206009912#seemore'); 43 | doTest('http://d.hatena.ne.jp/naoya/20080320/1206009912#komorebi', 44 | 'http://d.hatena.ne.jp/naoya/20080320/1206009912#komorebi'); 45 | 46 | doTest('http://d.hatena.ne.jp/asin/4501622601', 47 | 'http://d.hatena.ne.jp/asin/4501622601/naoyadyndnsor-22'); 48 | doTest('http://d.hatena.ne.jp/asin/4501622601', 49 | 'http://d.hatena.ne.jp/asin/4501622601/'); 50 | doTest('http://d.hatena.ne.jp/asin/4501622601', 51 | 'http://d.hatena.ne.jp/asin/4501622601'); 52 | 53 | doTest('http://jp.youtube.com/watch?v=1S3_Z3f2b20', 54 | 'http://jp.youtube.com/watch?v=1S3_Z3f2b20&feature=dir'); 55 | doTest('http://www.youtube.com/watch?v=1S3_Z3f2b20', 56 | 'http://www.youtube.com/watch?v=1S3_Z3f2b20&feature=dir'); 57 | doTest('http://www.youtube.com/watch?v=1S3_Z3f2b20', 58 | 'http://youtube.com/watch?v=1S3_Z3f2b20&feature=dir'); 59 | } 60 | -------------------------------------------------------------------------------- /resources/modules/53-Prefs.js: -------------------------------------------------------------------------------- 1 | Components.utils.import("resource://hatenabookmark/modules/00-utils.jsm"); 2 | loadPrecedingModules.call(this); 3 | 4 | var EXPORTED_SYMBOLS = ['Prefs']; 5 | 6 | var Prefs = function (branchName) { 7 | if (branchName && branchName[branchName.length-1] != '.') 8 | throw 'branchName should be "foo.branchName." -> ' + branchName; 9 | this._branch = branchName; 10 | EventService.implement(this); 11 | this.register(); 12 | } 13 | 14 | Prefs.prototype = { 15 | get prefs() { 16 | if (!this._prefs) { 17 | if (this._branch) { 18 | this._prefs = PrefService.getBranch(this._branch); 19 | // add QI 20 | this._prefs.QueryInterface(Ci.nsIPrefBranch2); 21 | } else { 22 | this._prefs = PrefService; 23 | } 24 | } 25 | return this._prefs; 26 | }, 27 | 28 | get branch() this._branch, 29 | 30 | get: function Prefs_get(name, debug) { 31 | let prefs = this.prefs; 32 | let type = prefs.getPrefType(name); 33 | 34 | try { 35 | switch (type) 36 | { 37 | case PrefService.PREF_STRING: 38 | // for multibyte values 39 | if (debug) p(['aaa', type, name]); 40 | return prefs.getComplexValue(name, 41 | Ci.nsISupportsString).data; 42 | break; 43 | case PrefService.PREF_INT: 44 | return prefs.getIntPref(name); 45 | break; 46 | default: 47 | return prefs.getBoolPref(name); 48 | break; 49 | } 50 | } catch(e) { 51 | return null; 52 | } 53 | }, 54 | 55 | set: function Prefs_set(name, value, type) { 56 | let prefs = this.prefs; 57 | if (!type) { 58 | type = prefs.getPrefType(name); 59 | } 60 | if (!type) { 61 | type = typeof value; 62 | } 63 | 64 | switch (type) 65 | { 66 | case PrefService.PREF_STRING: 67 | case 'string': 68 | // for multibyte 69 | prefs.setCharPref(name, unescape(encodeURIComponent(value))); 70 | break; 71 | case PrefService.PREF_INT: 72 | case 'number': 73 | prefs.setIntPref(name, parseInt(value)); 74 | break; 75 | default: 76 | prefs.setBoolPref(name, !!value); 77 | break; 78 | } 79 | }, 80 | 81 | getLocalized: function Prefs_getLocalized(name) { 82 | return this.prefs.getComplexValue(name, Ci.nsIPrefLocalizedString).data; 83 | }, 84 | 85 | clear: function Prefs_clear(name) { 86 | try { 87 | this.prefs.clearUserPref(name); 88 | } catch(e) {} 89 | }, 90 | 91 | register: function Prefs_register () { 92 | if (!this._observed) { 93 | this._observed = true; 94 | this.prefs.addObserver("", this, false); 95 | } 96 | }, 97 | 98 | unregister: function Prefs_unregister () { 99 | this._observed = false; 100 | this.prefs.removeObserver("", this); 101 | }, 102 | 103 | observe: function Prefs_observe (aSubject, aTopic, aData) { 104 | if (aTopic != "nsPref:changed") return; 105 | this.dispatch(aData); 106 | }, 107 | }; 108 | 109 | Prefs.global = new Prefs(''); 110 | Prefs.bookmark = new Prefs('extensions.hatenabookmark.'); 111 | Prefs.link = new Prefs('extensions.hatenabookmark.link.'); 112 | -------------------------------------------------------------------------------- /chrome/content/browser/13-Star-Loader.js: -------------------------------------------------------------------------------- 1 | 2 | var Command = Star.Command; 3 | 4 | function Loader() { 5 | this.alive = true; 6 | } 7 | 8 | extend(Loader, { 9 | ENTRIES_PER_REQUEST: 100, 10 | // 初回リクエストは応答性を高めるためにエントリ数を少なめに。 11 | FIRST_ENTRIES_PER_REQUEST: 20, 12 | }); 13 | 14 | extend(Loader.prototype, { 15 | destroy: function SL_destroy() { 16 | p('Star.Loader destroyed'); 17 | this.alive = false; 18 | }, 19 | 20 | loadBookmarkStar: function SL_loadBookmarkStar(data, 21 | onlyCommentedBookmarkStars, 22 | withPageStars, 23 | callback) { 24 | if (!this.alive) return; 25 | // 最初につけられたブックマークほどスターがついている可能性が 26 | // 高いので、まずは逆順にする。コメント付きのブックマークのほうが 27 | // スターがついている可能性が高いので、先頭に持っていく。 28 | let bookmarks = data.bookmarks.concat().reverse(); 29 | if (onlyCommentedBookmarkStars) 30 | bookmarks = bookmarks.filter(function (b) b.comment); 31 | else 32 | bookmarks.sort(function (a, b) !!b.comment - !!a.comment); 33 | this._doLoadBookmarkStars({ 34 | page: withPageStars ? data.url : null, 35 | eid: data.eid, 36 | bookmarks: bookmarks, 37 | callback: callback, 38 | }); 39 | }, 40 | 41 | _doLoadBookmarkStars: function SL__doLoadBookmarkStars(data) { 42 | if (!this.alive) return; 43 | let limit = data.page ? Loader.FIRST_ENTRIES_PER_REQUEST 44 | : Loader.ENTRIES_PER_REQUEST; 45 | let query = { 46 | t1: B_HTTP, 47 | t2: '#bookmark-' + data.eid, 48 | u: data.bookmarks.slice(0, limit).map(function (b) { 49 | return b.user + '/' + 50 | b.timestamp.substring(0, 4) + 51 | b.timestamp.substring(5, 7) + 52 | b.timestamp.substring(8, 10); 53 | }), 54 | }; 55 | if (data.page) 56 | query.uri = data.page; 57 | new Command(Command.LOAD_STARS, query, bind(onLoadStars, this)); 58 | 59 | function onLoadStars(res) { 60 | if (res) { 61 | this._invokeCallback(res.entries, data.callback, data.page); 62 | data.page = null; 63 | } 64 | data.bookmarks = data.bookmarks.slice(limit); 65 | if (data.bookmarks.length) 66 | this._doLoadBookmarkStars(data); 67 | } 68 | }, 69 | 70 | loadAllStars: function SL_loadAllStars(uri, callback) { 71 | if (!this.alive) return; 72 | let query = { uri: uri }; 73 | new Command(Command.LOAD_ALL_STARS, query, bind(onLoadStars, this)); 74 | 75 | function onLoadStars(res) { 76 | if (res) 77 | this._invokeCallback(res.entries, callback); 78 | } 79 | }, 80 | 81 | _invokeCallback: function SL__invokeCallback(entries, callback, pageURI) { 82 | if (!this.alive) return; 83 | let hasPageStars = false; 84 | entries.forEach(function (entry) { 85 | let isPageStars = entry.uri === pageURI; 86 | if (isPageStars) 87 | hasPageStars = true; 88 | try { 89 | callback(entry, isPageStars); 90 | } catch (ex) { 91 | Cu.reportError(ex); 92 | } 93 | }); 94 | // スターの読み込みが (少なくとも一度は) 完了したことを 95 | // 知らせるため、対象ページには必ずコールバックを呼び出す。 96 | if (pageURI && !hasPageStars) { 97 | callback({ uri: pageURI }, true); 98 | } 99 | }, 100 | }); 101 | 102 | Star.Loader = Loader; 103 | -------------------------------------------------------------------------------- /chrome/content/selectTagDialog/10-TagList.js: -------------------------------------------------------------------------------- 1 | var EXPORT = ["TagList"]; 2 | 3 | var COL_TAG = "tag-list-col-tag"; 4 | var COL_COUNT = "tag-list-col-count"; 5 | 6 | var TAG_ATOM = AtomService.getAtom("tag"); 7 | var COUNT_ATOM = AtomService.getAtom("count"); 8 | 9 | function TagList(treeElement) { 10 | this.tags = []; 11 | this._originalTags = null; 12 | this._tree = null; 13 | 14 | treeElement.view = this; 15 | } 16 | 17 | TagList.prototype.__proto__ = TreeView.prototype; 18 | 19 | extend(TagList.prototype, { 20 | init: function TL_init() { 21 | this._originalTags = Model.Tag.findDistinctTags(); 22 | this._sort(); 23 | }, 24 | 25 | destroy: function TL_destroy() { 26 | if (!this._tree) return; 27 | //p('destroy TagList'); 28 | }, 29 | 30 | _sort: function TL__sort() { 31 | let keyCol = null; 32 | for (let col = this._tree.columns.getFirstColumn(); 33 | col; 34 | col = col.getNext()) { 35 | if (col.element.getAttribute("sortActive") === "true") { 36 | keyCol = col.element; 37 | break; 38 | } 39 | } 40 | let tags = this._originalTags; 41 | let sortDir; 42 | if (keyCol && 43 | (sortDir = keyCol.getAttribute("sortDirection")) !== "natural") { 44 | tags = tags.concat(); 45 | switch (keyCol.id) { 46 | case COL_TAG: 47 | tags.sort(function (a, b) 48 | a.name > b.name ? 1 : a.name < b.name ? -1 : 0); 49 | if (sortDir === "descending") 50 | tags.reverse(); 51 | break; 52 | 53 | case COL_COUNT: 54 | tags.sort((sortDir === "ascending") 55 | ? function (a, b) a.count - b.count 56 | : function (a, b) b.count - a.count); 57 | break; 58 | } 59 | } 60 | this.tags = tags; 61 | }, 62 | 63 | get selectedTag() { 64 | let index = this.selection.currentIndex; 65 | return this.tags[index] || null; 66 | }, 67 | 68 | get treeBody() this._tree && this._tree.treeBody, 69 | 70 | // nsITreeView 71 | 72 | get rowCount() this.tags.length, 73 | 74 | getCellText: function TL_getCellText(row, col) { 75 | switch (col.id) { 76 | case COL_TAG: return this.tags[row].name; 77 | case COL_COUNT: return this.tags[row].count; 78 | } 79 | return ""; 80 | }, 81 | 82 | setTree: function TL_setTree(tree) { 83 | if (tree) { 84 | this._tree = tree; 85 | this.init(); 86 | } else { 87 | this.destroy(); 88 | this._tree = null; 89 | } 90 | }, 91 | 92 | getCellProperties: function TL_getCellProperties(row, col, properties) { 93 | if (!properties) { // since Gecko 22 94 | return (col.id === COL_TAG ? "tag" : "count"); 95 | } else { 96 | properties.AppendElement((col.id === COL_TAG) ? TAG_ATOM : COUNT_ATOM); 97 | } 98 | }, 99 | 100 | cycleHeader: function TL_cycleHeader(col) { 101 | let sortDir = col.element.getAttribute("sortDirection"); 102 | sortDir = (sortDir === "ascending") ? "descending" : 103 | (sortDir === "descending") ? "natural" : "ascending"; 104 | for (let c = col.columns.getFirstColumn(); c; c = c.getNext()) { 105 | let isKey = (c === col); 106 | c.element.setAttribute("sortDirection", isKey ? sortDir : "natural"); 107 | c.element.setAttribute("sortActive", isKey); 108 | } 109 | this._sort(); 110 | this._tree.invalidate(); 111 | } 112 | }); 113 | -------------------------------------------------------------------------------- /tests/javascripts/utils-module.test.js: -------------------------------------------------------------------------------- 1 | var UTILS_MODULE = "resource://hatenabookmark/modules/00-utils.jsm"; 2 | var moduleFile = null; 3 | 4 | function warmUp() { 5 | Components.utils.import(UTILS_MODULE); 6 | 7 | var resProtocol = Cc["@mozilla.org/network/protocol;1?name=resource"]. 8 | getService(Ci.nsIResProtocolHandler); 9 | var resSubst = resProtocol.getSubstitution("hatenabookmark"); 10 | moduleFile = utils.getFileFromURL(resSubst); 11 | moduleFile.append("modules"); 12 | moduleFile.append("01_foo.jsm"); 13 | utils.writeTo(<![CDATA[ 14 | var EXPORTED_SYMBOLS = ["Foo"]; 15 | var Foo = { baz: 42 }; 16 | var Bar = { baz: 12 }; 17 | ]]>.toString(), moduleFile, "utf-8"); 18 | } 19 | 20 | function coolDown() { 21 | if (moduleFile) 22 | utils.scheduleToRemove(moduleFile); 23 | } 24 | 25 | function testExtend() { 26 | var o = { bar: 0 }; 27 | extend(o, { 28 | _foo: "", 29 | get foo () this._foo.toUpperCase(), 30 | set foo (val) this._foo = val + "", 31 | bar: 42 32 | }); 33 | o.foo = "hello"; 34 | assert.equals(o.foo, "HELLO"); 35 | assert.equals(o.bar, 42); 36 | 37 | extend(o, { bar: 12 }, false); 38 | assert.equals(o.bar, 42); 39 | } 40 | 41 | testLoadModules.shouldSkip = 'モジュールファイル一覧をキャッシュしているので新規追加されたファイルを読み込めない'; 42 | function testLoadModules() { 43 | loadModules(); 44 | assert.equals(typeof Foo, "object"); 45 | assert.equals(Foo.baz, 42); 46 | assert.equals(typeof Bar, "undefined"); 47 | 48 | var root = { loadModules: loadModules }; 49 | root.loadModules(); 50 | assert.equals(root.Foo.baz, 42); 51 | } 52 | 53 | function testBindMethod() { 54 | let f = function(arg) { 55 | return this.foo + arg; 56 | }; 57 | assert.equals(bind(f, {foo: 1})(0), 1); 58 | assert.equals(bind(f, {foo: 1})(1), 2); 59 | let f1 = new function() { 60 | this.bar = 1; 61 | this.callBar = function() this.bar; 62 | }; 63 | assert.equals(f1.callBar(), 1); 64 | assert.equals(method(f1, 'callBar')(), 1); 65 | assert.equals(method(f1, 'callBar').apply({bar: 2}), 1); 66 | } 67 | 68 | function testJSON() { 69 | assert.equals({ foo: 42 }, decodeJSON('{ "foo": 42 }')); 70 | assert.equals(null, decodeJSON('<error>')); 71 | 72 | assert.equals('[23,42]', encodeJSON([23, 42])); 73 | } 74 | 75 | /* 76 | function testExtendBuiltIns() { 77 | extendBuiltIns(); 78 | 79 | assert.isFunction(Array.prototype.find); 80 | assert.equals([24, 32, 17, 8, 33].find(function (n) n & 1), 17); 81 | } 82 | */ 83 | 84 | function testDecodeReferences() { 85 | assert.equals("\u00abHello &unknown; 日本語 <world>\u00bb", 86 | decodeReferences("«Hello &unknown; 日本語 <world>»")); 87 | } 88 | 89 | function testIRI() { 90 | var iri = "http://日本語.jp/天気"; 91 | assert.equals("http://xn--wgv71a119e.jp/%E5%A4%A9%E6%B0%97", 92 | iri2uri(iri)); 93 | assert.equals("http%3A%2F%2Fxn--wgv71a119e.jp%2F%25E5%25A4%25A9%25E6%25B0%2597", 94 | escapeIRI(iri)); 95 | } 96 | 97 | function testXMLExtras() { 98 | let u = Cu.import(UTILS_MODULE, {}); 99 | 100 | let xhr = new u.XMLHttpRequest(); 101 | assert.isFunction(xhr.open); 102 | assert.isTrue(xhr.onload === null); 103 | assert.isTrue(xhr instanceof Ci.nsIXMLHttpRequest); 104 | 105 | let parser = new u.DOMParser(); 106 | let doc = parser.parseFromString("<x/>", "application/xml"); 107 | assert.equals("x", doc.documentElement.nodeName); 108 | assert.isTrue(parser instanceof Ci.nsIDOMParser); 109 | 110 | let serializer = new u.XMLSerializer(); 111 | assert.equals("<x/>", serializer.serializeToString(doc)); 112 | assert.isTrue(serializer instanceof Ci.nsIDOMSerializer); 113 | } 114 | -------------------------------------------------------------------------------- /chrome/content/common/05-HTMLDocumentCreator.js: -------------------------------------------------------------------------------- 1 | var EXPORT = ['HTMLDocumentCreator']; 2 | 3 | var NS_HTMLDOCUMENT_CID = '{5d0fcdd0-4daa-11d2-b328-00805f8a3859}'; 4 | 5 | var HDC = { 6 | fromString: function HDC_fromString(source) { 7 | let doc = Components.classesByID[NS_HTMLDOCUMENT_CID].createInstance(); 8 | doc.appendChild(doc.createElement('html')); 9 | let range = doc.createRange(); 10 | range.selectNodeContents(doc.documentElement); 11 | let fragment = range.createContextualFragment(source); 12 | if (Array.some(fragment.childNodes, function (child) 13 | child instanceof Ci.nsIDOMHTMLBodyElement)) { 14 | doc.documentElement.appendChild(fragment); 15 | } else { 16 | // createContextualFragment で作った文書片には 17 | // head 要素と body 要素が欠けているので、自分で作ってやる 18 | let head = doc.createElement('head'); 19 | let headChildNames = { 20 | title: true, meta: true, link: true, script: true, style: true, 21 | object: true, base: true, isindex: true, 22 | }; 23 | let child; 24 | while ((child = fragment.firstChild)) { 25 | if ((child.nodeType === Node.ELEMENT_NODE && 26 | !(child.nodeName.toLowerCase() in headChildNames)) || 27 | (child.nodeType === Node.TEXT_NODE && 28 | /\S/.test(child.nodeValue))) 29 | break; 30 | head.appendChild(child); 31 | } 32 | let body = doc.createElement('body'); 33 | body.appendChild(fragment); 34 | doc.documentElement.appendChild(head); 35 | doc.documentElement.appendChild(body); 36 | } 37 | if (!doc.title) { 38 | // Firefox 3 では HTMLDocument.title が自動的に 39 | // 設定されないようなので、手動で設定する 40 | let title = doc.getElementsByTagName('title').item(0); 41 | if (title) 42 | doc.title = title.textContent; 43 | } 44 | return doc; 45 | }, 46 | 47 | fromURL: function HDC_fromURL(url, callback) { 48 | HDC.fromURLWithEncoding(url, null, callback); 49 | }, 50 | 51 | fromURLWithEncoding: function HDC_fromURLWithEncoding(url, encoding, callback) { 52 | let xhr = new XMLHttpRequest(); 53 | xhr.open('GET', url); 54 | if (encoding) 55 | xhr.overrideMimeType('text/html; charset=' + encoding); 56 | xhr.addEventListener('load', onEvent, false); 57 | xhr.addEventListener('error', onEvent, false); 58 | xhr.send(null); 59 | 60 | function onEvent(event) { 61 | xhr.removeEventListener('load', onEvent, false); 62 | xhr.removeEventListener('error', onEvent, false); 63 | 64 | let contentType = xhr.getResponseHeader('Content-Type') || ''; 65 | contentType = contentType.replace(/;.*/, ''); 66 | if (event.type !== 'load' || !xhr.responseText || 67 | !/\bx?html\b/i.test(contentType)) { 68 | callback(null); 69 | return; 70 | } 71 | 72 | let text = xhr.responseText; 73 | if (!encoding && text.substring(0, 10 * 1024).split('\ufffd').length > 3) { 74 | // 文字符号化方式が自動判別で、先頭 10KB 以内に Unicode 75 | // 置換文字が 3 つ以上現れるなら、文字化けしているとみなす 76 | // (少数なら偶然紛れ込んだのかもしれない) 77 | let match = text.substring(0, 2048).match(/\bcharset\s*=\s*([\w-]+)/i); 78 | if (match) { 79 | p('Mojibake ocurred, and we assume that the correct encoding is ' + match[1]); 80 | HDC.fromURLWithEncoding(url, match[1], callback); 81 | return; 82 | } 83 | } 84 | callback(HDC.fromString(text)); 85 | } 86 | }, 87 | }; 88 | 89 | var HTMLDocumentCreator = HDC; 90 | -------------------------------------------------------------------------------- /resources/modules/65-Model.js: -------------------------------------------------------------------------------- 1 | Components.utils.import("resource://hatenabookmark/modules/00-utils.jsm"); 2 | loadPrecedingModules.call(this); 3 | 4 | var EXPORTED_SYMBOLS = ["Model", "model"]; 5 | 6 | var Model = {}; 7 | 8 | extend(Model, { 9 | MODELS: [], 10 | init: function() { 11 | if (this._init) return; 12 | this._init = true; 13 | this._db = null; 14 | }, 15 | // 常にログアウト時用の Database オブジェクトを返す 16 | // ログイン時は Model.Bookmark.db や Model.Tag.db が各ユーザー用の 17 | // Database オブジェクトを返すようになっているので問題ない. 18 | // (Model.Entity メソッド参照) 19 | // 本来はログインしていない状態だと Database を触りにいかないように 20 | // するのが最もよいが, 触りにいっている箇所があるのでとりあえずの処置. 21 | // (将来的にはこのメソッドが必要ないようにしたい) 22 | get db() { 23 | if (!this._init) this.init(); 24 | if (!this._db) this._db = new Database('hatena.bookmark.sqlite'); 25 | var db = this._db; 26 | // アプリケーション終了時に DB を閉じるように 27 | var QUIT_APP_GRANTED_TOPIC = "quit-application-granted"; 28 | var observer = { 29 | observe: function (aSubject, aTopic, aData) { 30 | if (aTopic === QUIT_APP_GRANTED_TOPIC) { 31 | db.close(); 32 | ObserverService.removeObserver(observer, QUIT_APP_GRANTED_TOPIC); 33 | } 34 | }, 35 | QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]) 36 | }; 37 | ObserverService.addObserver(observer, QUIT_APP_GRANTED_TOPIC, false); 38 | return db; 39 | }, 40 | resetAll: function() { 41 | this.deleteAll(); 42 | this.createAll(); 43 | var db = this.getSafeDB(); 44 | if (db) 45 | db.version = 1; 46 | }, 47 | deleteAll: function() { 48 | this.MODELS.forEach(function(m) { 49 | try { 50 | Model[m].deinitialize(); 51 | } catch (err) { 52 | Components.utils.reportError(err); 53 | } 54 | }); 55 | }, 56 | createAll: function() { 57 | var models = this.MODELS.forEach(function(m) Model[m].initialize()); 58 | }, 59 | getSafeDB: function() { 60 | let b = model('Bookmark'); 61 | if (b) { 62 | return b.db; 63 | } else { 64 | return null; 65 | } 66 | }, 67 | migrate: function() { 68 | let db = this.getSafeDB(); 69 | if (!db) { 70 | p('migrate fail.DB not found'); 71 | return; 72 | } 73 | let version = db.version || 0; 74 | if (version == 0) { 75 | // tag の nocase のための migration 76 | p('DB migrate: ' + version); 77 | p.b(function() { 78 | try { 79 | // もし何らかの例外が起きたときには、rollback する 80 | // sqlite3 は alter table のトランザクションが可能らしい 81 | db.beginTransaction(); 82 | db.execute('alter table tags rename to temptags'); 83 | Model.Tag.initialize(); 84 | db.execute('insert into tags select id, bookmark_id, name from temptags'); 85 | db.execute('drop table temptags'); 86 | db.commitTransaction(); 87 | } catch(e) { 88 | p('migration error!'); 89 | db.rollbackTransaction(); 90 | } finally { 91 | // 92 | } 93 | }, 'tag migration'); 94 | this.db.version = 1; 95 | // this.resetAll(); 96 | } 97 | }, 98 | }); 99 | 100 | Model.Entity = function (def) { 101 | let model = extend(Entity(def), { 102 | get db() { 103 | var user = shared.get('User').user; 104 | return user ? user.database : Model.db; 105 | } 106 | }); 107 | return model; 108 | }; 109 | 110 | var model = function(name) { 111 | var m = Model[name]; 112 | if (!m) { throw 'model not found' }; 113 | return m; 114 | }; 115 | -------------------------------------------------------------------------------- /chrome/content/browser/12-Star-Command.js: -------------------------------------------------------------------------------- 1 | 2 | function Command(command, query, callback) { 3 | if (typeof query === 'object') 4 | query = net.makeQuery(query); 5 | this.method = command.method || 'GET'; 6 | this.url = Star.BASE_URI + command.name; 7 | if (this.method === 'GET') 8 | this.url += '?' + query; 9 | this.headers = {}; 10 | if (User.user) 11 | this.headers['Cookie'] = 'rk=' + encodeURIComponent(User.user.rk); 12 | this.body = null; 13 | if (this.method === 'POST') { 14 | this.body = query; 15 | this.headers['Content-Type'] = 'application/x-www-form-urlencoded'; 16 | } 17 | this.trialCount = command.trialCount || Command.DEFAULT_TRIAL_COUNT; 18 | this.timeout = command.timeout || Command.DEFAULT_TIMEOUT; 19 | this.request = null; 20 | this.timer = null; 21 | this.callback = callback; 22 | this.invoke(); 23 | } 24 | 25 | extend(Command, { 26 | LOAD_STARS: { 27 | method: 'POST', 28 | name: 'entries.simple.json', 29 | }, 30 | LOAD_ALL_STARS: { 31 | method: 'GET', 32 | name: 'entry.json', 33 | }, 34 | ADD_STAR: { 35 | method: 'GET', 36 | name: 'star.add.json', 37 | }, 38 | GET_AVAILABLE_COLORS: { 39 | method: 'GET', 40 | name: 'colorpalette.json', 41 | }, 42 | SET_COLOR: { 43 | method: 'POST', 44 | name: 'colorpalette.json', 45 | }, 46 | 47 | DEFAULT_TRIAL_COUNT: 3, 48 | DEFAULT_TIMEOUT: 15 * 1000, 49 | }); 50 | 51 | extend(Command.prototype, { 52 | invoke: function SC_invoke() { 53 | this.request = new XMLHttpRequest(); 54 | this.request.open(this.method, this.url) 55 | for (let [name, value] in new Iterator(this.headers)) 56 | this.request.setRequestHeader(name, value); 57 | this.request.addEventListener('load', this, false); 58 | this.request.addEventListener('error', this, false); 59 | this.request.addEventListener('progress', this, false); 60 | this.request.send(this.body); 61 | this.setTimer(); 62 | }, 63 | 64 | setTimer: function SC_setTimer() { 65 | if (this.timer) 66 | this.timer.cancel(); 67 | else 68 | this.timer = Cc['@mozilla.org/timer;1'].createInstance(Ci.nsITimer); 69 | this.timer.init(this, this.timeout, Ci.nsITimer.TYPE_ONE_SHOT); 70 | }, 71 | 72 | invokeCallback: function SC_invokeCallback(result) { 73 | try { 74 | this.callback(result); 75 | } finally { 76 | this.dispose(); 77 | } 78 | }, 79 | 80 | cancel: function SC_cancel() { 81 | if (!this.request) return; 82 | this.request.abort(); 83 | this.dispose(); 84 | }, 85 | 86 | dispose: function SC_dispose() { 87 | this.request.removeEventListener('load', this, false); 88 | this.request.removeEventListener('error', this, false); 89 | this.request.removeEventListener('progress', this, false); 90 | this.request = null; 91 | this.timer.cancel(); 92 | this.timer = null; 93 | }, 94 | 95 | handleEvent: function SC_handleEvent(event) { 96 | switch (event.type) { 97 | case 'load': 98 | let result = decodeJSON(this.request.responseText); 99 | if (result) { 100 | if ('rks' in result) 101 | Star.rks = result.rks; 102 | this.invokeCallback(result); 103 | break; 104 | } 105 | /* FALL THROUGH */ 106 | case 'error': 107 | case 'timeout': 108 | if (--this.trialCount > 0) 109 | this.invoke(); 110 | else 111 | this.invokeCallback(null); 112 | break; 113 | case 'progress': 114 | this.setTimer(); 115 | break; 116 | } 117 | }, 118 | 119 | observe: function SC_observe(subject, topic, data) { 120 | this.request.abort(); 121 | this.handleEvent({ type: 'timeout' }); 122 | }, 123 | }); 124 | 125 | Star.Command = Command; 126 | -------------------------------------------------------------------------------- /chrome/content/browser/25-LinkClickOverlay.js: -------------------------------------------------------------------------------- 1 | 2 | var EXPORT = ['LinkClickOverlay']; 3 | var BOOKMARK_TOP = 'http://b.hatena.ne.jp/'; 4 | var BOOKMARK_APPEND = '/append?'; 5 | var BOOKMARK_ADD = '/add?mode=confirm'; 6 | var BOOKMARK_ADD_CONFIRM = '/add.confirm?'; 7 | var BOOKMARK_ENTRY_ADD = '/entry/add/'; 8 | var ESCAPE_REGEX_CHECK = new RegExp('^https?%3A', 'i'); 9 | 10 | var LinkClickOverlay = { 11 | linkClickHandler: function(ev) { 12 | if (ev.button != 0) return; 13 | 14 | let target = ev.target; 15 | if (!(target instanceof Ci.nsIDOMHTMLElement)) return; 16 | if (target.parentNode instanceof Ci.nsIDOMHTMLAnchorElement) 17 | target = target.parentNode; 18 | 19 | if ((LinkClickOverlay.captureComments && 20 | target.className === 'hatena-bcomment-view-icon') || 21 | target.className.indexOf('hBookmark-widget-comments') !== -1) { 22 | let url = target.getAttributeNS(HB_NS, 'url'); 23 | if (!url) { 24 | let match = (target.getAttribute('onclick') || '').match(/\'https?:\/\/(?:[^\\\']|\\.)+|\"https?:\/\/(?:[^\\\"]|\\.)+/) || ['']; 25 | url = match[0].substring(1).replace(/\\((u[0-9a-zA-Z]{4}|x[0-9a-zA-Z]{2})|.)/g, function (m, seq, code) { 26 | return code ? String.fromCharCode('0x' + code.substring(1)) : seq; 27 | }); 28 | } 29 | if (url) { 30 | CommentViewer.show(url); 31 | ev.stopPropagation(); 32 | ev.preventDefault(); 33 | } 34 | } 35 | 36 | let link; 37 | if (LinkClickOverlay.captureAddition && (link = target.href)) { 38 | if (link.indexOf(BOOKMARK_TOP) == 0) { 39 | let index, url, subindex; 40 | if ((index = link.indexOf(BOOKMARK_APPEND)) > 0) { 41 | url = link.substring(index + BOOKMARK_APPEND.length); 42 | } else if ((index = link.indexOf(BOOKMARK_ENTRY_ADD)) > 0) { 43 | url = link.substring(index + BOOKMARK_ENTRY_ADD.length); 44 | url = url.replace(/%23/g, '#'); 45 | } else if ((index = link.indexOf(BOOKMARK_ADD_CONFIRM)) > 0) { 46 | subindex = index + BOOKMARK_ADD_CONFIRM.length; 47 | } else if ((index = link.indexOf(BOOKMARK_ADD)) > 0) { 48 | subindex = index + BOOKMARK_ADD.length; 49 | } 50 | if (subindex) { 51 | link.substring(subindex).split('&'). 52 | forEach(function(e) { 53 | let [key, value] = e.split('='); 54 | if (key == 'url') url = value; 55 | }); 56 | } 57 | if (url) { 58 | if (ESCAPE_REGEX_CHECK.test(url)) { 59 | url = decodeURIComponent(url); 60 | } 61 | AddPanelManager.showPanel(url); 62 | ev.stopPropagation(); 63 | ev.preventDefault(); 64 | } 65 | } 66 | } 67 | }, 68 | register: function() { 69 | gBrowser.addEventListener('click', LinkClickOverlay.linkClickHandler, true); 70 | }, 71 | unregister: function() { 72 | gBrowser.removeEventListener('click', LinkClickOverlay.linkClickHandler, true); 73 | }, 74 | get prefs() { 75 | if (!LinkClickOverlay._prefs) { 76 | LinkClickOverlay._prefs = new Prefs('extensions.hatenabookmark.link.'); 77 | } 78 | return LinkClickOverlay._prefs; 79 | }, 80 | prefsLinkOverlay: function(ev) { 81 | let LCO = LinkClickOverlay; 82 | LCO.captureAddition = LCO.prefs.get('captureAddition'); 83 | LCO.captureComments = LCO.prefs.get('captureComments'); 84 | }, 85 | onloadHandler: function(ev) { 86 | LinkClickOverlay.prefsLinkOverlay(); 87 | LinkClickOverlay.prefs.createListener('captureAddition', LinkClickOverlay.prefsLinkOverlay); 88 | LinkClickOverlay.prefs.createListener('captureComments', LinkClickOverlay.prefsLinkOverlay); 89 | LinkClickOverlay.register(); 90 | } 91 | } 92 | 93 | window.addEventListener('load', LinkClickOverlay.onloadHandler, false); 94 | 95 | 96 | -------------------------------------------------------------------------------- /chrome/content/browser/40-ShortCut.js: -------------------------------------------------------------------------------- 1 | 2 | var EXPORT = ['ShortCut']; 3 | 4 | var ShortCut = { 5 | get keyset() { 6 | // return document.getElementById('hBookmark-keyset'); 7 | // XXX: mainKeyset を override しないと、標準ショートカットを上書きできない 8 | return document.getElementById('mainKeyset'); 9 | }, 10 | get prefs() { 11 | if (!this._prefs) { 12 | let prefs = PrefService.getBranch('extensions.hatenabookmark.shortcut.'); 13 | prefs.QueryInterface(Ci.nsIPrefBranch2); 14 | this._prefs = prefs; 15 | } 16 | return this._prefs; 17 | }, 18 | register: function Prefs_register () { 19 | if (!this._observed) { 20 | this._observed = true; 21 | this.prefs.addObserver("", this, false); 22 | } 23 | }, 24 | unregister: function Prefs_unregister () { 25 | this._observed = false; 26 | this.prefs.removeObserver("", this); 27 | }, 28 | observe: function Prefs_observe (aSubject, aTopic, aData) { 29 | if (aTopic != "nsPref:changed") return; 30 | 31 | if (aData.indexOf('keys.') == 0) { 32 | let [_, key] = aData.split('.', 2); 33 | p('change shortcut key: ' + key); 34 | ShortCut.updateShortcut(key); 35 | } 36 | }, 37 | getKeyName: function(key) { 38 | return 'hBookmark-key-' + key; 39 | }, 40 | updateShortcut: function(key) { 41 | /* 42 | * XXX: keyset が、一回でも押されると変更できない 43 | * http://d.hatena.ne.jp/onozaty/20080204/p1 44 | */ 45 | let aID = this.getKeyName(key); 46 | let node = document.getElementById(aID); 47 | if (node) 48 | this.keyset.removeChild(node); 49 | 50 | let pref; 51 | try { 52 | // Prefs.bookmark.get('shortcut.keys.' + key) ではうまくいかない? 53 | pref = PrefService.getBranch('extensions.hatenabookmark.shortcut.keys.').getCharPref(key); 54 | } catch (ex) { p(ex); } 55 | let aInfo; 56 | if (pref) { 57 | aInfo = parseShortcut(pref); 58 | } 59 | if (!aInfo) { 60 | p('key pref not found. :' + key); 61 | return; 62 | } else { 63 | p('keyinfo: ' + uneval(aInfo)); 64 | } 65 | // if (!aInfo.key && !aInfo.keyCode) return; 66 | 67 | node = document.createElement('key'); 68 | node.setAttribute('id', aID); 69 | node.setAttribute('oncommand', "hBookmark.ShortCut.commands." + key + "(event);"); 70 | 71 | if (aInfo.key) 72 | node.setAttribute('key', aInfo.key); 73 | 74 | if (aInfo.keyCode) 75 | node.setAttribute('keycode', aInfo.keyCode); 76 | 77 | let modifiers = []; 78 | if (aInfo.altKey) modifiers.push('alt'); 79 | if (aInfo.ctrlKey) modifiers.push('control'); 80 | if (aInfo.shiftKey) modifiers.push('shift'); 81 | if (aInfo.metaKey) modifiers.push('meta'); 82 | modifiers = modifiers.join(','); 83 | if (modifiers) 84 | node.setAttribute('modifiers', modifiers); 85 | 86 | this.keyset.insertBefore(node, this.keyset.firstChild); 87 | p('keyset registerd:' + uneval(aInfo)); 88 | }, 89 | init: function() { 90 | this.updateShortcut('add'); 91 | this.updateShortcut('comment'); 92 | this.updateShortcut('sidebar'); 93 | } 94 | } 95 | 96 | ShortCut.commands = { 97 | add: function(ev) { 98 | hBookmark.AddPanelManager.toggle(); 99 | ev.stopPropagation(); 100 | ev.preventDefault(); 101 | }, 102 | comment: function(ev) { 103 | hBookmark.CommentViewer.toggle(); 104 | ev.stopPropagation(); 105 | ev.preventDefault(); 106 | }, 107 | sidebar: function(ev) { 108 | document.getElementById('viewHBookmarkSidebar').doCommand(); 109 | ev.stopPropagation(); 110 | ev.preventDefault(); 111 | }, 112 | } 113 | 114 | ShortCut.keys = { 115 | } 116 | 117 | window.addEventListener('load', function(e) { 118 | ShortCut.register(); 119 | ShortCut.init(); 120 | }, false); 121 | 122 | window.addEventListener('unload', function(e) { 123 | ShortCut.unregister(); 124 | }, false); 125 | 126 | 127 | -------------------------------------------------------------------------------- /chrome/content/addPanel/20-URLSuggestion.js: -------------------------------------------------------------------------------- 1 | var EXPORT = ["URLSuggestion"]; 2 | 3 | function URLSuggestion(originalURL, doc) { 4 | this.originalURL = originalURL; 5 | this.type = ""; 6 | this.url = ""; 7 | this.wantsFallback = !doc; 8 | for (let [type, suggestor] in Iterator(URLSuggestion.suggestors)) { 9 | let suggestedURL = suggestor(originalURL, doc); 10 | if (suggestedURL) { 11 | this.type = type; 12 | this.url = suggestedURL; 13 | this.wantsFallback = false; 14 | break; 15 | } 16 | } 17 | } 18 | 19 | extend(URLSuggestion.prototype, { 20 | suggest: function US_suggest(callback, thisObject) { 21 | if (this.url) { 22 | callback.call(thisObject, this); 23 | } else if (this.wantsFallback) { 24 | let originalURL = this.originalURL; 25 | let self = this; 26 | net.get(originalURL, function gotContent(res) { 27 | for (let [type, suggestor] in Iterator(URLSuggestion.responseSuggestors)) { 28 | let suggestedURL = suggestor(originalURL, res); 29 | if (suggestedURL) { 30 | self.type = type; 31 | self.url = suggestedURL; 32 | callback.call(thisObject, self); 33 | break; 34 | } 35 | } 36 | }, null, true); 37 | } 38 | } 39 | }); 40 | 41 | extend(URLSuggestion, { 42 | suggestors: { 43 | meta: function US_suggestor_meta(originalURL, doc) { 44 | const B_ENTRY = B_HTTP + "entry/"; 45 | if (!Prefs.bookmark.get("addPanel.notifyMetaBookmark") || 46 | originalURL.substring(0, B_ENTRY.length) !== B_ENTRY) 47 | return null; 48 | let url = originalURL.substring(B_ENTRY.length).replace(/%23/g, "#"); 49 | if (doc) { 50 | let link = doc.getElementById("head-entry-link"); 51 | if (link && link.href) 52 | url = link.href; 53 | } 54 | return /^https?:\/\//.test(url) ? url : null; 55 | }, 56 | 57 | canonical: function US_suggestor_canonical(originalURL, doc) { 58 | if (!Prefs.bookmark.get("addPanel.notifyCanonicalURL") || 59 | !(doc instanceof HTMLDocument)) 60 | return null; 61 | let link = doc.evaluate( 62 | '/h:html/h:head/h:link[translate(@rel, "CANONICAL", "canonical") = "canonical"]', 63 | doc, 64 | function () doc.documentElement.namespaceURI || "", 65 | XPathResult.FIRST_ORDERED_NODE_TYPE, 66 | null 67 | ).singleNodeValue; 68 | if (!link || !link.href) return null; 69 | let url = link.href; 70 | if (url.indexOf("#") === -1) 71 | url += doc.defaultView.location.hash; 72 | return (isSameEffectiveDomain(url, originalURL) && url !== originalURL) 73 | ? url : null; 74 | } 75 | }, 76 | 77 | responseSuggestors: { 78 | canonical: function US_resSugestor_canonical(originalURL, res) { 79 | if (!Prefs.bookmark.get("addPanel.notifyCanonicalURL")) return null; 80 | let match = res.responseText.match(/<link\s[^>]*\brel\s*=\s*(?:"canonical"|'canonical'|canonical\b)[^>]*>/i); 81 | if (!match) return null; 82 | match = match[0].match(/\bhref\s*=\s*(?:"(.*?)"|'(.*?)'|(\S+))/i); 83 | let href = match && decodeReferences(match[1] || match[2] || match[3] || ""); 84 | if (!href) return null; 85 | let url = newURI(href, null, originalURL).spec; 86 | let fragmentIndex = originalURL.indexOf("#"); 87 | if (fragmentIndex !== -1 && url.indexOf("#") === -1) 88 | url += originalURL.substring(fragmentIndex); 89 | return (isSameEffectiveDomain(url, originalURL) && url !== originalURL) 90 | ? url : null; 91 | } 92 | } 93 | }); 94 | 95 | function isSameEffectiveDomain(url1, url2) { 96 | const TLDService = getService("@mozilla.org/network/effective-tld-service;1", Ci.nsIEffectiveTLDService); 97 | return TLDService.getBaseDomain(newURI(url1)) === 98 | TLDService.getBaseDomain(newURI(url2)); 99 | } 100 | -------------------------------------------------------------------------------- /resources/modules/21-SiteInfoSet-HatenaStar-Converter.jsm: -------------------------------------------------------------------------------- 1 | Components.utils.import("resource://hatenabookmark/modules/00-utils.jsm"); 2 | loadPrecedingModules.call(this); 3 | var EXPORTED_SYMBOLS = []; 4 | //var EXPORTED_SYMBOLS = ['selector2xpath']; 5 | 6 | function convertHatenaStarSiteConfigToSiteInfoItem(text) { 7 | let config = decodeJSON(text); 8 | let items = []; 9 | for (let domain in config) { 10 | let entries = config[domain]; 11 | let domainPattern = '^https?://' 12 | domainPattern += (domain.charCodeAt(0) === 42 /* '*' */) 13 | ? '(?:[\w-]+\.)*' + domain.substring(2).replace(/\W/g, '\\$&') 14 | : domain.replace(/\W/g, '\\$&'); 15 | for (let i = 0; i < entries.length; i++) { 16 | let entry = entries[i]; 17 | let path = entry.path || ''; 18 | let urlPattern = domainPattern + 19 | ((path.charCodeAt(0) === 94 /* '^' */) 20 | ? path.substring(1) 21 | : '.*' + path); 22 | let entryNodes = entry.entryNodes; 23 | if (!entryNodes) continue; 24 | let paraSelector; 25 | // 同一パス下での複数エントリには非対応 26 | for (paraSelector in entryNodes) break; 27 | let paraInfos = entryNodes[paraSelector]; 28 | items.push({ 29 | domain: urlPattern, 30 | paragraph: selector2xpath(paraSelector), 31 | link: selector2xpath(paraInfos.uri || 'parent'), 32 | annotation: selector2xpath(paraInfos.container || 33 | paraInfos.uri || 'parent'), 34 | }); 35 | } 36 | } 37 | return items; 38 | } 39 | 40 | function selector2xpath(selector) { 41 | switch (selector) { 42 | case 'parent': 43 | return 'self::*'; 44 | 45 | case 'window.location': 46 | case 'document.location': 47 | return getLinkToCurrentURI; 48 | 49 | //case 'document.title': 50 | // return '""'; 51 | } 52 | 53 | selector = selector.replace(/\s+/g, ' ').replace(/ ?([,>]) ?/g, '$1'); 54 | return selector.replace(/(,)?([^,]+)/g, singleSelector2xpath); 55 | } 56 | 57 | function singleSelector2xpath(match, comma, selector) { 58 | return (comma ? ' | descendant::' : 'descendant::') + 59 | selector.replace(/([ >])?([^ >]+)/g, selectorPart2xpath); 60 | } 61 | 62 | function selectorPart2xpath(match, combinator, part) { 63 | let step = combinator ? ((combinator === ' ') ? '/descendant::' : '/child::') : '' 64 | let elementEnd = part.search(/[#.:]/); 65 | if (elementEnd === -1) return step + part; 66 | if (elementEnd) { 67 | step += part.substring(0, elementEnd); 68 | part = part.substring(elementEnd); 69 | } else { 70 | step += '*'; 71 | } 72 | return step + '[' + part.replace(/([#.:])([\w-]+(?:\((\d+)\))?)/g, classes2xpath) + ']'; 73 | } 74 | 75 | function classes2xpath(match, kind, name, index, offset) { 76 | let expr = offset ? ' and ' : ''; 77 | if (kind === '#') 78 | return expr + '@id = "' + name + '"'; 79 | if (kind === '.') 80 | return expr + 'contains(concat(" ", normalize-space(@class), " "), " ' + name + ' ")'; 81 | 82 | switch (name) { 83 | case 'first-child': 84 | case 'first': // Someone specifies incorrect pseudo classes 85 | case 'nth-child(1)': 86 | return expr + 'not(preceding-sibling::*)'; 87 | 88 | case 'last-child': 89 | return expr + 'not(following-sibling::*)'; 90 | } 91 | // Assume it is an nth-child pseudo class. 92 | return expr + 'count(preceding-sibling::*) = ' + (index - 1 || -1); 93 | } 94 | 95 | function getLinkToCurrentURI(context) { 96 | let doc = context.ownerDocument || context; 97 | // XXX doesn't work in XHTML. 98 | let apLink = doc.evaluate( 99 | '(ancestor-or-self::*/preceding-sibling::*' + 100 | '[@class = "autopagerize_page_info" or @class = "_autopagerize"]' + 101 | ')[last()]/descendant::a[@class = "autopagerize_link" and @href]', 102 | context, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null 103 | ).singleNodeValue; 104 | let a = doc.createElementNS(XHTML_NS, 'a'); 105 | a.href = apLink ? apLink.href : doc.defaultView.location.href; 106 | return a; 107 | } 108 | 109 | SiteInfoSet.converters.hatenastar = convertHatenaStarSiteConfigToSiteInfoItem; 110 | -------------------------------------------------------------------------------- /chrome/locale/ja/addPanel.dtd: -------------------------------------------------------------------------------- 1 | <!ENTITY hBookmark.addPanel.initialWidth "640"> 2 | <!ENTITY hBookmark.addPanel.initialHeight "500"> 3 | 4 | <!ENTITY hBookmark.addPanel.welcomeUserLabel.before "ようこそ"> 5 | <!ENTITY hBookmark.addPanel.welcomeUserLabel.after "さん"> 6 | 7 | <!ENTITY hBookmark.addPanel.loadingTitleLabel "現在、データを取得しています..."> 8 | <!ENTITY hBookmark.addPanel.editTitleLabel "タイトルを変更..."> 9 | <!ENTITY hBookmark.addPanel.cancelEditTitleLabel "キャンセル"> 10 | <!ENTITY hBookmark.addPanel.titleDescription "エントリーのタイトルは全ユーザ共通です"> 11 | <!ENTITY hBookmark.addPanel.titleGuidelineLabel "タイトル変更のガイドライン"> 12 | <!ENTITY hBookmark.addPanel.titleGuidelineURI "http://b.hatena.ne.jp/help/guideline_title"> 13 | <!ENTITY hBookmark.addPanel.lastTitleEditorLabel "最終変更:"> 14 | 15 | <!ENTITY hBookmark.addPanel.showCommentsLabel "ブックマークコメントを表示"> 16 | <!ENTITY hBookmark.addPanel.commentDescription "コメントを入力してください (省略可能)"> 17 | 18 | <!ENTITY hBookmark.addPanel.shareLabel "共有:"> 19 | <!ENTITY hBookmark.addPanel.optionsLabel "オプション:"> 20 | <!ENTITY hBookmark.addPanel.confirmEntryLabel "ブックマークを確認"> 21 | <!ENTITY hBookmark.addPanel.addCollectionLabel "コレクション"> 22 | <!ENTITY hBookmark.addPanel.isReadLaterLabel "あとで読む"> 23 | <!ENTITY hBookmark.addPanel.isReadLaterDescription "未読ブックマークに追加し、「あとで読む」通知を受ける場合はチェックを入れてください"> 24 | <!ENTITY hBookmark.addPanel.isReadLaterHelpTooltip "「あとで読む」機能により未読ブックマークリストやお知らせ欄・携帯端末への通知等がご利用いただけます"> 25 | <!ENTITY hBookmark.addPanel.isReadLaterHelpURI "http://b.hatena.ne.jp/help/entry/read_later"> 26 | <!ENTITY hBookmark.addPanel.isPrivateLabel "非公開"> 27 | <!ENTITY hBookmark.addPanel.isPrivateDescription "このブックマークを他のユーザーに公開しない (閲覧許可ユーザーにも公開されません)"> 28 | <!ENTITY hBookmark.addPanel.isPrivateHelpTooltip "はてなブックマークプラスにお申し込みいただくと、他のユーザーに公開しない非公開ブックマークがご利用いただけます"> 29 | <!ENTITY hBookmark.addPanel.isPrivateHelpURI "http://b.hatena.ne.jp/guide/plus?target=private&ref=firefox"> 30 | <!ENTITY hBookmark.addPanel.sendMailLabel "メール"> 31 | <!ENTITY hBookmark.addPanel.sendMailDescription "ブックマークしたページを Web 上の設定ページで指定したメールアドレスに送信する"> 32 | <!ENTITY hBookmark.addPanel.postTwitterLabel "Twitter"> 33 | <!ENTITY hBookmark.addPanel.postTwitterDescription "このブックマークを Twitter へ投稿する"> 34 | <!ENTITY hBookmark.addPanel.postTwitterHelpTooltip "Twitter 連携機能を設定すると、ブックマーク時に Twitter へも投稿することができます"> 35 | <!ENTITY hBookmark.addPanel.postTwitterHelpURI "http://b.hatena.ne.jp/guide/twitter?target=append&ref=firefox"> 36 | <!ENTITY hBookmark.addPanel.twitterDisabledNotice "(非公開ブックマークは Twitter へ投稿されません)"> 37 | <!ENTITY hBookmark.addPanel.postFacebookLabel "Facebook"> 38 | <!ENTITY hBookmark.addPanel.postFacebookDescription "このブックマークを Facebook へ投稿する"> 39 | <!ENTITY hBookmark.addPanel.postFacebookHelpTooltip "Facebook 連携機能を設定すると、ブックマーク時に Facebook へも投稿することができます"> 40 | <!ENTITY hBookmark.addPanel.postFacebookHelpURI "http://b.hatena.ne.jp/help/config_coop#facebook"> 41 | <!ENTITY hBookmark.addPanel.facebookDisabledNotice "(非公開ブックマークは Facebook へ投稿されません)"> 42 | 43 | <!ENTITY hBookmark.addPanel.postEvernoteLabel "Evernote"> 44 | <!ENTITY hBookmark.addPanel.postEvernoteDescription "このブックマークを Evernote へ投稿する"> 45 | <!ENTITY hBookmark.addPanel.postEvernoteHelpTooltip "Evernote 連携機能を設定すると、ブックマーク時に Evernote へも投稿することができます"> 46 | <!ENTITY hBookmark.addPanel.postEvernoteHelpURI "http://b.hatena.ne.jp/help/config_coop#evernote"> 47 | 48 | <!ENTITY hBookmark.addPanel.postMixiCheckLabel "mixiチェック"> 49 | <!ENTITY hBookmark.addPanel.postMixiCheckDescription "このブックマークをmixiチェックへ投稿する"> 50 | <!ENTITY hBookmark.addPanel.postMixiCheckHelpTooltip "mixiチェック連携機能を設定すると、ブックマーク時にmixiチェックへも投稿することができます"> 51 | <!ENTITY hBookmark.addPanel.postMixiCheckHelpURI "http://b.hatena.ne.jp/help/config_coop#mixi-check"> 52 | <!ENTITY hBookmark.addPanel.mixiCheckDisabledNotice "(非公開ブックマークはmixiチェックへ投稿されません)"> 53 | <!ENTITY hBookmark.addPanel.normalUserNotice "※ プラスユーザー (有料オプション) のみ利用可能です"> 54 | <!ENTITY hBookmark.addPanel.optionHelpLearnMoreText "もっと詳しく"> 55 | 56 | <!ENTITY hBookmark.addPanel.imageDescription "エントリーの画像は全ユーザ共通です"> 57 | <!ENTITY hBookmark.addPanel.imageGuidelineLabel "ガイドライン"> 58 | <!ENTITY hBookmark.addPanel.imageGuidelineURI "http://b.hatena.ne.jp/help/guideline_image"> 59 | <!ENTITY hBookmark.addPanel.lastImageEditorLabel "最終変更:"> 60 | 61 | <!ENTITY hBookmark.addPanel.recommendedTagsLabel "おすすめタグ"> 62 | <!ENTITY hBookmark.addPanel.tagsLabel "タグ"> 63 | <!ENTITY hBookmark.addPanel.showAllTagsLabel "すべてのタグを表示"> 64 | <!ENTITY hBookmark.addPanel.showFrequentTagsLabel "一部タグのみ表示"> 65 | <!ENTITY hBookmark.addPanel.tagHelpLabel "※ タグについての詳しい説明"> 66 | <!ENTITY hBookmark.addPanel.tagHelpURI "http://b.hatena.ne.jp/help/tag"> 67 | 68 | <!ENTITY hBookmark.addPanel.deleteLabel "削除"> 69 | --------------------------------------------------------------------------------