├── .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 | hoge123foo bar tarao
2 | hage234goo 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 |
18 |
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 |
2 |
4 |
5 | bookmark@hatena.ne.jp
6 | Hatena Bookmark
7 | 2.3.12
8 | 2
9 | chrome://hatenabookmark/content/config.xul
10 | chrome://hatenabookmark/skin/images/favicon32.png
11 | true
12 |
13 |
14 | Hatena Bookmark
15 | en-US
16 | Hatena Bookmark Firefox Add-on
17 | Hatena
18 | http://b.hatena.ne.jp/help/firefox_addon
19 |
20 |
21 |
22 |
23 | Hatena Bookmark
24 | ja
25 | はてなブックマークへのブックマーク追加や、高速な検索、ツールバー、サイドバー機能などを提供します
26 | Hatena
27 | http://b.hatena.ne.jp/help/firefox_addon
28 |
29 |
30 |
31 |
32 |
33 | {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
34 | 6.0
35 | 47.*
36 |
37 |
38 |
39 |
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 |
2 |
3 |
4 |
5 |
42 |
--------------------------------------------------------------------------------
/chrome/content/addPanel.xul:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
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 |
2 |
4 | はてなブックマーク
5 | はてなブックマーク
6 | utf-8
7 |
8 |
9 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/chrome/content/popups.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | document.getAnonymousElementByAttribute(this, "anonid", "titleField")
17 |
18 |
19 | document.getAnonymousElementByAttribute(this, "anonid", "urlField")
20 |
21 |
22 | document.getAnonymousElementByAttribute(this, "anonid", "commentField")
23 |
24 |
25 |
26 |
27 |
42 |
43 |
44 |
45 |
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 |
2 |
3 |
4 |
5 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
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:
36 |
37 |
38 |
39 |
40 | Hello, world
41 |
42 | .toXMLString()
43 | }
44 | assert.equals("http://example.org/canonical",
45 | suggestor("http://example.org/", response));
46 |
47 | response.responseText =
48 |
49 |
50 |
51 |
52 | Hello, world
53 |
54 | .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 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
13 |
17 |
18 |
23 |
27 |
28 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
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(.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(''));
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; 日本語 \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("", "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("", 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(/]*\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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------