Release 5.2
diff --git a/releases/5.2/release-5.2.zip b/releases/5.2/release-5.2.zip
new file mode 100644
index 0000000..3b14bd8
--- /dev/null
+++ b/releases/5.2/release-5.2.zip
Binary files differ
diff --git a/src/_locales/de/messages.json b/src/_locales/de/messages.json
new file mode 100644
index 0000000..59fe3be
--- /dev/null
+++ b/src/_locales/de/messages.json
@@ -0,0 +1,122 @@
+{
+ "autosave": {
+ "message": "Autospeichern - nach dem Erfassen, den Bearbeiten-Bildschirm überspringen und den erfassten Bereich direkt am Standard-Speicherort mit dem Webseiten-Titel als Dateinamen speichern"
+ },
+ "blur": {
+ "message": "Verwischen"
+ },
+ "border": {
+ "message": "Nur umranden"
+ },
+ "cancel": {
+ "message": "Abbruch"
+ },
+ "capture_area": {
+ "message": "Ausgewählten Bereich"
+ },
+ "capture_webpage": {
+ "message": "Gesamte Seite"
+ },
+ "capture_window": {
+ "message": "Sichtbaren Bereich"
+ },
+ "capturing": {
+ "message": "Erfassen, bitte warten..."
+ },
+ "close": {
+ "message": "Schließen"
+ },
+ "default_title": {
+ "message": "Bildschirmerfassung"
+ },
+ "description": {
+ "message": "Erfasse den sichtbaren Bereich eines Tabs, einen Bereich einer Webseite oder die gesamte Seite als PNG Bild."
+ },
+ "edit_tip": {
+ "message": " Unten ist der erfasste Screenshot. Sie können diesen mit den Tools links bearbeiten. Wenn fertig, auf speichern drücken."
+ },
+ "highlight": {
+ "message": "Markieren"
+ },
+ "line": {
+ "message": "Linie"
+ },
+ "loading": {
+ "message": "Die Seite wird geladen, bitte warten."
+ },
+ "lossless": {
+ "message": "Verlustfreier Screenshot"
+ },
+ "lossy": {
+ "message": "Verlustbehafteter Screenshot"
+ },
+ "name": {
+ "message": "Bildschirmerfassung"
+ },
+ "ok": {
+ "message": "OK"
+ },
+ "open_save_path": {
+ "message": "Gehe zu Verzeichnis"
+ },
+ "options": {
+ "message": "Optionen Bildschirmerfassung"
+ },
+ "quality_setting": {
+ "message": "Einstellungen Screenshot Qualität: "
+ },
+ "rect": {
+ "message": "Bereich füllen"
+ },
+ "redact": {
+ "message": "Bearbeiten"
+ },
+ "save": {
+ "message": "Speichern"
+ },
+ "save_and_close": {
+ "message": "Speichern & Schließen"
+ },
+ "save_fail": {
+ "message": "Screenshot konnte nicht gespeichert werden."
+ },
+ "save_image": {
+ "message": "Bild speichern"
+ },
+ "save_setting": {
+ "message": "Einstellungen Screenshot speichern: "
+ },
+ "save_success": {
+ "message": "Screenshot gespeichert."
+ },
+ "save_tip": {
+ "message": "Standard-Speicherort: "
+ },
+ "set_save_path": {
+ "message": "Browse..."
+ },
+ "set_save_path_title": {
+ "message": "Standard-Speicherort wählen"
+ },
+ "size_huge": {
+ "message": "Riesig"
+ },
+ "size_large": {
+ "message": "Groß"
+ },
+ "size_normal": {
+ "message": "Normal"
+ },
+ "size_small": {
+ "message": "Klein"
+ },
+ "solid_black": {
+ "message": "Schwarz"
+ },
+ "special": {
+ "message": "Dies ist eine spezielle Seite. Eine Bildschirmerfassung ist auf dieser Seite nicht möglich."
+ },
+ "text": {
+ "message": "Text"
+ }
+}
diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json
new file mode 100644
index 0000000..e33b7e0
--- /dev/null
+++ b/src/_locales/en/messages.json
@@ -0,0 +1,91 @@
+{
+ "name": {"message": "Screen Capture"},
+ "description": {"message": "Capture visible content of a tab, a region of a web page, or the whole page as a PNG image."},
+ "default_title": {"message": "Screen Capture"},
+ "capture_screen": {"message": "Capture Screen Region"},
+ "capture_window": {"message": "Capture Visible Content"},
+ "capture_area": {"message": "Capture Page Region"},
+ "capture_webpage": {"message": "Capture Whole Page"},
+ "shortcut_setting": {"message": "Shortcut Settings:"},
+ "shortcutsetting_text": {"message": "Enable keyboard shortcuts"},
+ "special": {"message": "This is a special page. Unable to perform screen capture actions on this page."},
+ "loading": {"message": "The page is loading, please wait."},
+ "capturing": {"message": "Capturing, please wait..."},
+ "cancel": {"message": "Cancel"},
+ "ok": {"message": "OK"},
+ "capture_tip": {"message": "Drag and Capture (Press Esc to Exit)"},
+ "highlight": {"message": "Highlight"},
+ "border": {"message": "Draw Border Only"},
+ "rect": {"message": "Fill Entire Area"},
+ "redact": {"message": "Redact"},
+ "solid_black": {"message": "Solid Black"},
+ "blur": {"message": "Blur"},
+ "text": {"message": "Text"},
+ "size_small": {"message": "Small"},
+ "size_normal": {"message": "Normal"},
+ "size_large": {"message": "Large"},
+ "size_huge": {"message": "Huge"},
+ "line": {"message": "Line"},
+ "edit_tip": {"message": "Below is the screenshot you captured. You can edit it with the tools on the left. Press Save when finished."},
+ "copy": {"message": "Copy"},
+ "save": {"message": "Save"},
+ "share": {"message": "Share"},
+ "print": {"message": "Print"},
+ "close": {"message": "Close"},
+ "save_image": {"message": "Save Image"},
+ "options": {"message": "Screen Capture Options"},
+ "quality_setting": {"message": "Screenshot Quality Settings: "},
+ "lossy": {"message": "Lossy screenshot"},
+ "lossless": {"message": "Lossless screenshot"},
+ "save_setting": {"message": "Screenshot Save Settings: "},
+ "save_tip": {"message": "Default save location: "},
+ "autosave": {"message": "Autosave - after capture, bypass editing screen and directly save the captured image to the default location with the web page title as default file name"},
+ "set_save_path": {"message": "Browse..."},
+ "set_save_path_title": {"message": "Select Default Save Location"},
+ "open_save_path": {"message": "Go to folder"},
+ "save_and_close": {"message": "Save & Close"},
+ "save_success": {"message": "Screenshot saved."},
+ "save_fail": {"message": "Failed to save the screenshot."},
+ "tip_copy_succeed": {"message": "Screenshot copied."},
+ "tip_copy_failed": {"message": "Failed to copy the screenshot."},
+ "upload_sites_header": {"message": "Share Image To"},
+ "sina_upload_header": {"message": "Sina Microblog"},
+ "facebook_upload_header": {"message": "Facebook"},
+ "share_to": {"message": "Share to"},
+ "deletion_title": {"message": "Remove account"},
+ "photo_link_text": {"message": "Check the Image"},
+ "account_deletion_confirm": {"message": "Do you want to remove this account?"},
+ "return_to_site_selection": {"message": "Back"},
+ "close_upload_wrapper": {"message": "Close"},
+ "share_to_other_account": {"message": "Share to another account"},
+ "share_to_sina_account": {"message": "Share to your Sina Microblog account"},
+ "share_to_facebook_account": {"message": "Share to your Facebook account"},
+ "image_caption": {"message": "Image Title"},
+ "facebook_get_photo_link": {"message": "Retrieving image link..."},
+ "bad_access_token": {"message": "Invalid Access Token."},
+ "unknown_error": {"message": "Unknown error."},
+ "failed_to_connect_to_server": {"message": "Can not connect to server."},
+ "invalid_caption": {"message": "Image title is required."},
+ "photo_size_tip": {"message": "Hint: You may fail to upload an image bigger than 3M bytes."},
+ "progress_info": {"message": "Image uploading, please wait..."},
+ "sina_failed_to_get_request_token": {"message": "Get request token failed, please try again later."},
+ "sina_failed_to_get_access_token": {"message": "Get access token failed, please try again later."},
+ "failed_to_get_user_info": {"message": "Get user information failed, please try again later."},
+ "user_denied": {"message": "User denied."},
+ "user_authentication_tip": {"message": "User authenticating, please wait..."},
+ "user_logout_tip": {"message": "Signing out, please wait..."},
+ "failed_to_register_hot_key_for_screen_capture": {"message": "The shortcut for Capture Screen Region is registered by another application. Please redefine it."},
+ "hot_key_conflict": {"message": "Shortcuts conflict."},
+ "failure_to_create_album": {"message": "Failed to create album, please try again later."},
+ "failed_to_upload_image": {"message": "Failed to upload image, please try again later."},
+ "failed_to_get_photo_link": {"message": "Failed to retrieve photo link."},
+ "picasa_upload_header": {"message": "Picasa Web"},
+ "share_to_picasa_account": {"message": "Share to your Picasa Web account"},
+ "invalid_album_id": {"message": "Album not found."},
+ "saved_to_path": {"message": "The screenshot has been saved to "},
+ "option": {"message": "Options"},
+ "imgur_upload_header": {"message": "Imgur"},
+ "share_to_imgur_account": {"message": "Share to your Imgur account"},
+ "imgur_failed_to_get_request_token": {"message": "Get request token failed, please try again later."},
+ "imgur_failed_to_get_access_token": {"message": "Get access token failed, please try again later."}
+}
diff --git a/src/_locales/fr/messages.json b/src/_locales/fr/messages.json
new file mode 100644
index 0000000..270539f
--- /dev/null
+++ b/src/_locales/fr/messages.json
@@ -0,0 +1,137 @@
+{
+ "autosave": {
+ "message": "Enregistrement Auto - apr\u00e8s capture, passe l'\u00e9cran d'\u00e9dition et sauvegarde la capture dans le dossier par d\u00e9faut avec pour nom le titre de la page web"
+ },
+ "blur": {
+ "message": "Flou"
+ },
+ "border": {
+ "message": "Contour"
+ },
+ "cancel": {
+ "message": "Annuler"
+ },
+ "capture_area": {
+ "message": "Capture r\u00e9gion"
+ },
+ "capture_webpage": {
+ "message": "Capture page enti\u00e8re"
+ },
+ "capture_window": {
+ "message": "Capture contenu visible"
+ },
+ "capturing": {
+ "message": "Capture en cours, attendez..."
+ },
+ "close": {
+ "message": "Fermer"
+ },
+ "copy": {
+ "message": "Copie"
+ },
+ "default_title": {
+ "message": "Capture \u00e9cran"
+ },
+ "description": {
+ "message": "Capture le contenu visible de l'onglet, une r\u00e9gion de la page web, ou la page enti\u00e8re en image PNG."
+ },
+ "edit_tip": {
+ "message": "Ci-dessous la capture d'\u00e9cran. Vous pouvez l'\u00e9diter avec les outils \u00e0 gauche. Cliquez Enregister quand vous avez termin\u00e9."
+ },
+ "highlight": {
+ "message": "Cadre"
+ },
+ "line": {
+ "message": "Ligne"
+ },
+ "loading": {
+ "message": "Chargement de la page, patientez..."
+ },
+ "lossless": {
+ "message": "Capture sans perte"
+ },
+ "lossy": {
+ "message": "Capture compress\u00e9e"
+ },
+ "name": {
+ "message": "Capture d'\u00e9cran"
+ },
+ "ok": {
+ "message": "OK"
+ },
+ "open_save_path": {
+ "message": "Ouvrir l'emplacement"
+ },
+ "options": {
+ "message": "Options"
+ },
+ "quality_setting": {
+ "message": "Param\u00e8tres de qualit\u00e9: "
+ },
+ "rect": {
+ "message": "Surface pleine"
+ },
+ "redact": {
+ "message": "Masque"
+ },
+ "save": {
+ "message": "Enregistrer"
+ },
+ "save_and_close": {
+ "message": "Enregistrer & Fermer"
+ },
+ "save_fail": {
+ "message": "L'enregistrement de la capture a \u00e9chou\u00e9!"
+ },
+ "save_image": {
+ "message": "Enregistrement de l'image"
+ },
+ "save_setting": {
+ "message": "Param\u00e8tres d'enregistrement de la capture: "
+ },
+ "save_success": {
+ "message": "Capture enregistr\u00e9e."
+ },
+ "save_tip": {
+ "message": "Chemin d'enregistrement par d\u00e9faut: "
+ },
+ "set_save_path": {
+ "message": "Parcourir..."
+ },
+ "set_save_path_title": {
+ "message": "Selectionnez l'emplacement d'enregistrement par d\u00e9faut"
+ },
+ "shortcut_setting": {
+ "message": "Param\u00e8tre du raccourci:"
+ },
+ "shortcutsetting_text": {
+ "message": "Activer les raccourcis clavier"
+ },
+ "size_huge": {
+ "message": "\u00c9norme"
+ },
+ "size_large": {
+ "message": "Grande"
+ },
+ "size_normal": {
+ "message": "Normale"
+ },
+ "size_small": {
+ "message": "Petite"
+ },
+ "solid_black": {
+ "message": "Noir"
+ },
+ "special": {
+ "message": "C'est une page sp\u00e9ciale. Impossible d'effectuer une capture d'\u00e9cran sur cette page."
+ },
+ "text": {
+ "message": "Texte"
+ },
+ "tip_copy_failed": {
+ "message": "La copie de la capture a \u00e9chou\u00e9!"
+ },
+ "tip_copy_succeed": {
+ "message": "Capture copi\u00e9e."
+ }
+}
diff --git a/src/_locales/pl/messages.json b/src/_locales/pl/messages.json
new file mode 100644
index 0000000..da4f205
--- /dev/null
+++ b/src/_locales/pl/messages.json
@@ -0,0 +1,131 @@
+{
+ "autosave": {
+ "message": "Autozapis - po przechwyceniu, pomiń ekran edycji i zapisz bezpośrednio w domyślnym katalogu z tytułem strony jako nazwa pliku"
+ },
+ "blur": {
+ "message": "Rozmyj"
+ },
+ "border": {
+ "message": "Kontury"
+ },
+ "cancel": {
+ "message": "Anuluj"
+ },
+ "capture_area": {
+ "message": "Przechwyć Wybrany Obszar"
+ },
+ "capture_webpage": {
+ "message": "Przechwyć Całą Stronę"
+ },
+ "capture_window": {
+ "message": "Przechwyć Widoczny Obszar"
+ },
+ "capturing": {
+ "message": "Przechwytywanie, proszę czekać ..."
+ },
+ "close": {
+ "message": "Zamknij"
+ },
+ "copy": {
+ "message": "Kopiuj"
+ },
+ "default_title": {
+ "message": "Zrzut Ekranu"
+ },
+ "description": {
+ "message": "Przechwyć widoczną część karty, wybrany fragment strony, lub całą stronę jako obraz PNG."
+ },
+ "edit_tip": {
+ "message": "Poniżej jest zrzut ekranu który zrobiłeś. Możesz go edytować za pomocą narzędzi po lewej stronie. Naciśnij Zapisz kiedy skończysz."
+ },
+ "highlight": {
+ "message": "Wyróżnij"
+ },
+ "line": {
+ "message": "Linia"
+ },
+ "loading": {
+ "message": "Strona ładuje się, proszę czekać."
+ },
+ "lossless": {
+ "message": "Kompresja bezstratna (PNG)"
+ },
+ "lossy": {
+ "message": "Kompresja stratna (JPEG)"
+ },
+ "name": {
+ "message": "Screen Capture"
+ },
+ "ok": {
+ "message": "OK"
+ },
+ "open_save_path": {
+ "message": "Idź do folderu"
+ },
+ "options": {
+ "message": "Opcje Screen Capture"
+ },
+ "quality_setting": {
+ "message": "Ustawienia Jakości Zrzutu Ekranu: "
+ },
+ "rect": {
+ "message": "Wypełnij"
+ },
+ "redact": {
+ "message": "Ocenzuruj"
+ },
+ "save": {
+ "message": "Zapisz"
+ },
+ "save_and_close": {
+ "message": "Zapisz & Zamknij"
+ },
+ "save_fail": {
+ "message": "Nie udało się zapisać zrzutu ekranu."
+ },
+ "save_image": {
+ "message": "Zapisz Obraz"
+ },
+ "save_setting": {
+ "message": "Ustawienia Zapisu Zrzutu Ekranu: "
+ },
+ "save_success": {
+ "message": "Zrzut ekranu zapisany."
+ },
+ "save_tip": {
+ "message": "Domyślna lokalizacja do zapisu: "
+ },
+ "set_save_path": {
+ "message": "Przeglądaj ..."
+ },
+ "set_save_path_title": {
+ "message": "Wybierz Domyślną Lokalizację do Zapisu"
+ },
+ "size_huge": {
+ "message": "Ogromny"
+ },
+ "size_large": {
+ "message": "Duży"
+ },
+ "size_normal": {
+ "message": "Normalny"
+ },
+ "size_small": {
+ "message": "Mały"
+ },
+ "solid_black": {
+ "message": "Zamaż"
+ },
+ "special": {
+ "message": "To jest strona specjalna. Nie można zrobić zrzutu ekranu."
+ },
+ "text": {
+ "message": "Tekst"
+ },
+ "tip_copy_failed": {
+ "message": "Nie udało się skopiować zrzutu ekranu."
+ },
+ "tip_copy_succeed": {
+ "message": "Zrzut ekranu skopiowany."
+ }
+}
diff --git a/src/_locales/ru/messages.json b/src/_locales/ru/messages.json
new file mode 100644
index 0000000..f27b5b8
--- /dev/null
+++ b/src/_locales/ru/messages.json
@@ -0,0 +1,249 @@
+{
+ "account_deletion_confirm": {
+ "message": "Удалить эту учетную запись?"
+ },
+ "autosave": {
+ "message": "Автосохранение - после захвата пропустить редактирование экрана и сразу сохранить захваченное изображение в папку по умолчанию с названием веб-страницы в качестве имени файла."
+ },
+ "bad_access_token": {
+ "message": "Недопустимый маркер доступа."
+ },
+ "blur": {
+ "message": "Пятно"
+ },
+ "border": {
+ "message": "Только бордюр"
+ },
+ "cancel": {
+ "message": "Отмена"
+ },
+ "capture_area": {
+ "message": "Область захвата"
+ },
+ "capture_screen": {
+ "message": "Захват области экрана"
+ },
+ "capture_webpage": {
+ "message": "Захват всей страницы"
+ },
+ "capture_window": {
+ "message": "Захват видимой части"
+ },
+ "capturing": {
+ "message": "Захват, подождите..."
+ },
+ "close": {
+ "message": "Закрыть"
+ },
+ "close_upload_wrapper": {
+ "message": "Закрыть"
+ },
+ "copy": {
+ "message": "Копировать"
+ },
+ "default_title": {
+ "message": "Захват экрана"
+ },
+ "deletion_title": {
+ "message": "Удалить учетную запись"
+ },
+ "description": {
+ "message": "Захват видимого содержимого вкладки, области веб-страницы, или всей страницы в формате PNG."
+ },
+ "edit_tip": {
+ "message": "Ниже приведен скриншот, того что Вы захватили. Вы можете редактировать его с помощью инструментов слева. Нажмите Сохранить по завершении."
+ },
+ "facebook_get_photo_link": {
+ "message": "Получение ссылки на изображения..."
+ },
+ "facebook_upload_header": {
+ "message": "Facebook"
+ },
+ "failed_to_connect_to_server": {
+ "message": "Невозможно подключиться к серверу."
+ },
+ "failed_to_get_photo_link": {
+ "message": "Не удалось получить фото ссылку."
+ },
+ "failed_to_get_user_info": {
+ "message": "Не удалось получить информацию о пользователе, повторите попытку позже."
+ },
+ "failed_to_register_hot_key_for_screen_capture": {
+ "message": "Ярлык для Захвата области экрана зарегистрирован другим приложением. Пожалуйста, переопределите его."
+ },
+ "failed_to_upload_image": {
+ "message": "Не удалось загрузить изображение, повторите попытку позже."
+ },
+ "failure_to_create_album": {
+ "message": "Не удалось создать альбом, повторите попытку позже."
+ },
+ "highlight": {
+ "message": "Выделить"
+ },
+ "hot_key_conflict": {
+ "message": "Конфликт ярлыков."
+ },
+ "image_caption": {
+ "message": "Название изображения"
+ },
+ "invalid_album_id": {
+ "message": "Альбом не найден."
+ },
+ "invalid_caption": {
+ "message": "Требуется название изображения."
+ },
+ "line": {
+ "message": "Линия"
+ },
+ "loading": {
+ "message": "Страница загружается, подождите."
+ },
+ "lossless": {
+ "message": "Без потери качества"
+ },
+ "lossy": {
+ "message": "C потерей качества"
+ },
+ "name": {
+ "message": "Захват экрана"
+ },
+ "ok": {
+ "message": "OK"
+ },
+ "open_save_path": {
+ "message": "Перейти в папку"
+ },
+ "options": {
+ "message": "Параметры захвата экрана"
+ },
+ "photo_link_text": {
+ "message": "Проверить изображение"
+ },
+ "photo_size_tip": {
+ "message": "Подсказка: Вы не можете загрузить изображение больше 3Мб."
+ },
+ "picasa_upload_header": {
+ "message": "Picasa"
+ },
+ "print": {
+ "message": "Печать"
+
+ },
+ "progress_info": {
+ "message": "Изображение загружается, подождите..."
+ },
+ "quality_setting": {
+ "message": "Настройки качества скриншота: "
+ },
+ "rect": {
+ "message": "Заполнить всю область"
+ },
+ "redact": {
+ "message": "Скрыть"
+ },
+ "return_to_site_selection": {
+ "message": "Назад"
+ },
+ "save": {
+ "message": "Сохранить"
+ },
+ "save_and_close": {
+ "message": "Сохранить & Закрыть"
+ },
+ "save_fail": {
+ "message": "Не удалось сохранить скриншот."
+ },
+ "save_image": {
+ "message": "Сохранить изображение"
+ },
+ "save_setting": {
+ "message": "Настройки сохранения скриншота: "
+ },
+ "save_success": {
+ "message": "Скриншот сохранен."
+ },
+ "save_tip": {
+ "message": "Место сохранения по умолчанию: "
+ },
+ "set_save_path": {
+ "message": "Обзор..."
+ },
+ "set_save_path_title": {
+ "message": "Выбор места сохранения по умолчанию."
+ },
+ "share": {
+ "message": "Отправить"
+ },
+ "share_to": {
+ "message": "Отправить в"
+ },
+ "share_to_facebook_account": {
+ "message": "Отправить на Вашу учетную запись Facebook"
+ },
+ "share_to_other_account": {
+ "message": "Отправить на другую учетную запись"
+ },
+ "share_to_picasa_account": {
+ "message": "Отправить на Вашу учетную запись Picasa"
+ },
+ "share_to_sina_account": {
+ "message": "Отправить на Вашу учетную запись в Sina Microblog"
+ },
+ "shortcut_setting": {
+ "message": "Настройки ярлыков:"
+ },
+ "shortcutsetting_text": {
+ "message": "Вкл. горячие клавиши"
+ },
+ "sina_failed_to_get_access_token": {
+ "message": "Не удалось получить маркер доступа, повторите попытку позже."
+ },
+ "sina_failed_to_get_request_token": {
+ "message": "Не удалось получить запрос маркера, повторите попытку позже."
+ },
+ "sina_upload_header": {
+ "message": "Sina Microblog"
+ },
+ "size_huge": {
+ "message": "Огромный"
+ },
+ "size_large": {
+ "message": "Большой"
+ },
+ "size_normal": {
+ "message": "Обычный"
+ },
+ "size_small": {
+ "message": "Мелкий"
+ },
+ "solid_black": {
+ "message": "Сплошной"
+ },
+ "special": {
+ "message": "Это специальная страница. Невозможно осуществить захват экрана на этой странице."
+ },
+ "text": {
+ "message": "Текст"
+ },
+ "tip_copy_failed": {
+ "message": "Не удалось скопировать скриншот."
+ },
+ "tip_copy_succeed": {
+ "message": "Скриншот скопирован."
+ },
+ "unknown_error": {
+ "message": "Неизвестная ошибка."
+ },
+ "upload_sites_header": {
+ "message": "Отправить изображение в"
+ },
+ "user_authentication_tip": {
+ "message": "Авторизация пользователя, подождите..."
+ },
+ "user_denied": {
+ "message": "Пользователю отказано."
+ },
+ "user_logout_tip": {
+ "message": "Выход, подождите..."
+ }
+}
diff --git a/src/_locales/zh_CN/messages.json b/src/_locales/zh_CN/messages.json
new file mode 100644
index 0000000..fbb6619
--- /dev/null
+++ b/src/_locales/zh_CN/messages.json
@@ -0,0 +1,94 @@
+{
+ "name": {"message": "网页截图"},
+ "description": {"message": "截取网页为图片,支持窗口截图,区域截图和整个网页截图三种方式。支持水平和垂直翻页截取超大网页,新版引进自动截图保存功能。"},
+ "default_title": {"message": "网页截图"},
+ "capture_screen": {"message": "屏幕区域截图"},
+ "capture_window": {"message": "可视网页截图"},
+ "capture_area": {"message": "网页区域截图"},
+ "capture_webpage": {"message": "整张网页截图"},
+ "special": {"message": "此页面是特殊页面,无法进行截图。"},
+ "loading": {"message": "页面正在加载,请稍等。"},
+ "capturing": {"message": "截图中,请稍候..."},
+ "cancel": {"message": "取消"},
+ "ok": {"message": "截图"},
+ "capture_tip": {"message": "拖动鼠标截图(按Esc退出)"},
+ "highlight": {"message":" 高亮标记"},
+ "border": {"message": "高亮区域只画边框"},
+ "rect": {"message": "填充高亮区域"},
+ "redact": {"message": " 涂黑"},
+ "solid_black": {"message": "纯黑"},
+ "blur" : {"message": " 模糊"},
+ "text": {"message": "添加文字"},
+ "size_small": {"message": "小"},
+ "size_normal": {"message": "正常"},
+ "size_large": {"message": "大"},
+ "size_huge": {"message": "最大"},
+ "line": {"message": " 直线"},
+ "edit_tip": {"message": "以下是您的截图,您可以利用左边的工具进行编辑,然后点击“保存” 按钮。"},
+ "copy": {"message": "拷贝"},
+ "save": {"message": "保存"},
+ "share": {"message": "分享"},
+ "print": {"message": "打印"},
+ "close": {"message": "关闭"},
+ "save_image": {"message": "保存图片"},
+ "options": {"message": "网页截图选项"},
+ "quality_setting" : {"message" : "图像质量设定:"},
+ "lossy" : {"message" : "有损截图"},
+ "lossless" : {"message" : "无损截图"},
+ "save_setting": {"message": "截图保存设置:"},
+ "save_tip": {"message": "默认保存位置:"},
+ "shortcut_setting": {"message": "快捷键设置:"},
+ "shortcutsetting_text": {"message": "使用网页截图快捷键"},
+ "autosave" : {"message" : "自动保存 - 截图后,不进行编辑,直接以网页标题为文件名保存到默认位置"},
+ "set_save_path" : {"message" : "浏览..."},
+ "set_save_path_title" : {"message": "选择默认保存位置"},
+ "open_save_path" : {"message" : "在文件夹中显示"},
+ "save_and_close": {"message": "保存并关闭"},
+ "save_success": {"message": "截图保存成功。"},
+ "save_fail": {"message": "截图保存失败。"},
+ "tip_copy_succeed": {"message": "截图拷贝成功。"},
+ "tip_copy_failed": {"message": "截图拷贝失败。"},
+ "upload_confirm": {"message": "使用当前用户分享?"},
+ "change_user": {"message": "使用其他用户"},
+ "upload_caption": {"message": "标题:"},
+ "upload_sites_header": {"message": "分享图片"},
+ "sina_upload_header": {"message": "分享到新浪微博"},
+ "facebook_upload_header": {"message": "分享到Facebook"},
+ "share_to": {"message": "分享到"},
+ "deletion_title": {"message": "删除账号"},
+ "photo_link_text": {"message": "查看图片"},
+ "account_deletion_confirm": {"message": "确认删除该账号?"},
+ "return_to_site_selection": {"message": "返 回"},
+ "close_upload_wrapper": {"message": "关 闭"},
+ "share_to_other_account": {"message": "分享到其他账号"},
+ "share_to_sina_account": {"message": "分享到你的新浪微博账号"},
+ "share_to_facebook_account": {"message": "分享到你的 Facebook 账号"},
+ "image_caption": {"message": "图片描述"},
+ "facebook_get_photo_link": {"message": "正在获取图片链接..."},
+ "bad_access_token": {"message": "无效的 Access Token。"},
+ "unknown_error": {"message": "未知错误。"},
+ "failed_to_connect_to_server": {"message": "无法连接到服务器。"},
+ "invalid_caption": {"message": "图片描述不能为空。"},
+ "photo_size_tip": {"message": "注:建议图片大小不要超过3M。"},
+ "progress_info": {"message": "图片上传中,请稍候..."},
+ "sina_failed_to_get_request_token": {"message": "无法获取 request token,请稍候再试。"},
+ "sina_failed_to_get_access_token": {"message": "无法获取 access token,请稍候再试。"},
+ "failed_to_get_user_info": {"message": "无法获取用户信息,请稍候再试。"},
+ "user_denied": {"message": "用户拒绝。"},
+ "user_authentication_tip": {"message": "用户认证中,请稍候..."},
+ "user_logout_tip": {"message": "用户注销中,请稍候..."},
+ "failed_to_register_hot_key_for_screen_capture": {"message": "屏幕区域截图快捷键已被其他应用程序占用,请重新定义。"},
+ "hot_key_conflict": {"message": "快捷键冲突。"},
+ "failure_to_create_album": {"message": "相册创建失败,请稍候再试。"},
+ "failed_to_upload_image": {"message": "图片上传失败,请稍候再试。"},
+ "failed_to_get_photo_link": {"message": "获取图片链接失败。"},
+ "picasa_upload_header": {"message": "分享到Picasa Web"},
+ "share_to_picasa_account": {"message": "分享到你的 Picasa Web 账号"},
+ "invalid_album_id": {"message": "相册不存在。"},
+ "saved_to_path": {"message": "图片已保存至 "},
+ "option": {"message": "选 项"},
+ "imgur_upload_header": {"message": "分享到 Imgur"},
+ "share_to_imgur_account": {"message": "分享到你的 Imgur 账户"},
+ "imgur_failed_to_get_request_token": {"message": "无法获取 request token,请稍候再试。"},
+ "imgur_failed_to_get_access_token": {"message": "无法获取 access token,请稍候再试。"}
+}
diff --git a/src/background.html b/src/background.html
new file mode 100644
index 0000000..779a2c6
--- /dev/null
+++ b/src/background.html
@@ -0,0 +1,14 @@
+<!--
+ * Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+ * source code is governed by a BSD-style license that can be found in the
+ * LICENSE file.
+-->
+<html>
+<head>
+</head>
+<body>
+<script src="lib/dateFormat.min.js"></script>
+<script src="js/hotkey_storage.js"></script>
+<script src="js/background.js"></script>
+</body>
+</html>
diff --git a/src/i18n_styles/en_US_upload_image.css b/src/i18n_styles/en_US_upload_image.css
new file mode 100644
index 0000000..eea0994
--- /dev/null
+++ b/src/i18n_styles/en_US_upload_image.css
@@ -0,0 +1,3 @@
+#imageCaption {
+ width: 227px;
+}
\ No newline at end of file
diff --git a/src/i18n_styles/en_options.css b/src/i18n_styles/en_options.css
new file mode 100644
index 0000000..7a8ecd4
--- /dev/null
+++ b/src/i18n_styles/en_options.css
@@ -0,0 +1,7 @@
+#hot-key-setting .capture-text {
+ display: inline-block;
+ width: 60%;
+}
+.hot-key {
+ width: 290px;
+}
\ No newline at end of file
diff --git a/src/i18n_styles/zh_CN_upload_image.css b/src/i18n_styles/zh_CN_upload_image.css
new file mode 100644
index 0000000..ed34c69
--- /dev/null
+++ b/src/i18n_styles/zh_CN_upload_image.css
@@ -0,0 +1,3 @@
+#imageCaption {
+ width: 240px;
+}
\ No newline at end of file
diff --git a/src/images/arrow.png b/src/images/arrow.png
new file mode 100644
index 0000000..220f97e
--- /dev/null
+++ b/src/images/arrow.png
Binary files differ
diff --git a/src/images/copy.png b/src/images/copy.png
new file mode 100644
index 0000000..e99c987
--- /dev/null
+++ b/src/images/copy.png
Binary files differ
diff --git a/src/images/cross.png b/src/images/cross.png
new file mode 100644
index 0000000..16ce1b7
--- /dev/null
+++ b/src/images/cross.png
Binary files differ
diff --git a/src/images/custom.png b/src/images/custom.png
new file mode 100644
index 0000000..28307bb
--- /dev/null
+++ b/src/images/custom.png
Binary files differ
diff --git a/src/images/delete_account_icon.png b/src/images/delete_account_icon.png
new file mode 100644
index 0000000..59e5c38
--- /dev/null
+++ b/src/images/delete_account_icon.png
Binary files differ
diff --git a/src/images/down_arrow.png b/src/images/down_arrow.png
new file mode 100644
index 0000000..801dc50
--- /dev/null
+++ b/src/images/down_arrow.png
Binary files differ
diff --git a/src/images/facebook_icon.png b/src/images/facebook_icon.png
new file mode 100644
index 0000000..b732890
--- /dev/null
+++ b/src/images/facebook_icon.png
Binary files differ
diff --git a/src/images/icon_128.png b/src/images/icon_128.png
new file mode 100644
index 0000000..93c951d
--- /dev/null
+++ b/src/images/icon_128.png
Binary files differ
diff --git a/src/images/icon_16.png b/src/images/icon_16.png
new file mode 100644
index 0000000..c2aac9d
--- /dev/null
+++ b/src/images/icon_16.png
Binary files differ
diff --git a/src/images/icon_19.png b/src/images/icon_19.png
new file mode 100644
index 0000000..4914477
--- /dev/null
+++ b/src/images/icon_19.png
Binary files differ
diff --git a/src/images/icon_32.png b/src/images/icon_32.png
new file mode 100644
index 0000000..86b52ac
--- /dev/null
+++ b/src/images/icon_32.png
Binary files differ
diff --git a/src/images/icon_48.png b/src/images/icon_48.png
new file mode 100644
index 0000000..231728b
--- /dev/null
+++ b/src/images/icon_48.png
Binary files differ
diff --git a/src/images/icon_close.png b/src/images/icon_close.png
new file mode 100644
index 0000000..d0d6277
--- /dev/null
+++ b/src/images/icon_close.png
Binary files differ
diff --git a/src/images/icon_copy.png b/src/images/icon_copy.png
new file mode 100644
index 0000000..01275e3
--- /dev/null
+++ b/src/images/icon_copy.png
Binary files differ
diff --git a/src/images/icon_save.png b/src/images/icon_save.png
new file mode 100644
index 0000000..e180486
--- /dev/null
+++ b/src/images/icon_save.png
Binary files differ
diff --git a/src/images/imgur_icon.png b/src/images/imgur_icon.png
new file mode 100644
index 0000000..4f252d7
--- /dev/null
+++ b/src/images/imgur_icon.png
Binary files differ
diff --git a/src/images/line.png b/src/images/line.png
new file mode 100644
index 0000000..2424ba3
--- /dev/null
+++ b/src/images/line.png
Binary files differ
diff --git a/src/images/loading.gif b/src/images/loading.gif
new file mode 100644
index 0000000..7c12c21
--- /dev/null
+++ b/src/images/loading.gif
Binary files differ
diff --git a/src/images/loading_icon.gif b/src/images/loading_icon.gif
new file mode 100644
index 0000000..a201bf2
--- /dev/null
+++ b/src/images/loading_icon.gif
Binary files differ
diff --git a/src/images/mark.png b/src/images/mark.png
new file mode 100644
index 0000000..6731379
--- /dev/null
+++ b/src/images/mark.png
Binary files differ
diff --git a/src/images/picasa_icon.png b/src/images/picasa_icon.png
new file mode 100644
index 0000000..2d4a243
--- /dev/null
+++ b/src/images/picasa_icon.png
Binary files differ
diff --git a/src/images/popup_bg.jpg b/src/images/popup_bg.jpg
new file mode 100644
index 0000000..7f30e77
--- /dev/null
+++ b/src/images/popup_bg.jpg
Binary files differ
diff --git a/src/images/print.png b/src/images/print.png
new file mode 100644
index 0000000..33b61a9
--- /dev/null
+++ b/src/images/print.png
Binary files differ
diff --git a/src/images/region.png b/src/images/region.png
new file mode 100644
index 0000000..e51e0c8
--- /dev/null
+++ b/src/images/region.png
Binary files differ
diff --git a/src/images/screen.png b/src/images/screen.png
new file mode 100644
index 0000000..7ca28c2
--- /dev/null
+++ b/src/images/screen.png
Binary files differ
diff --git a/src/images/sina_icon.png b/src/images/sina_icon.png
new file mode 100644
index 0000000..7ee4170
--- /dev/null
+++ b/src/images/sina_icon.png
Binary files differ
diff --git a/src/images/toolbar_bg.png b/src/images/toolbar_bg.png
new file mode 100644
index 0000000..66820fe
--- /dev/null
+++ b/src/images/toolbar_bg.png
Binary files differ
diff --git a/src/images/upload.png b/src/images/upload.png
new file mode 100644
index 0000000..6a88be0
--- /dev/null
+++ b/src/images/upload.png
Binary files differ
diff --git a/src/images/whole.png b/src/images/whole.png
new file mode 100644
index 0000000..4374ed3
--- /dev/null
+++ b/src/images/whole.png
Binary files differ
diff --git a/src/js/account.js b/src/js/account.js
new file mode 100644
index 0000000..d8ddd0d
--- /dev/null
+++ b/src/js/account.js
@@ -0,0 +1,75 @@
+/**
+ * Create a user.
+ * @param {Object} user
+ * properties: id, name, accessToken, expires, accessTokenSecret, albumId
+ */
+var User = function(user) {
+ for (var prop in user) {
+ this[prop] = user[prop];
+ }
+};
+
+var Account = {
+
+ getUsers: function(siteId) {
+ var users = localStorage.getItem(siteId + '_userInfo');
+ if (users) {
+ users = JSON.parse(users);
+ for (var id in users) {
+ // Remove expired user.
+ if (Account.isExpires(users[id])) {
+ delete users[id];
+ }
+ }
+ localStorage.setItem(siteId + '_userInfo', JSON.stringify(users));
+ return users;
+ }
+ return {};
+ },
+
+ getUser: function(siteId, userId) {
+ var users = Account.getUsers(siteId);
+ var user = users[userId];
+ if (user && Account.isExpires(user)) {
+ Account.removeUser(siteId, userId);
+ return null;
+ } else {
+ return user;
+ }
+ },
+
+ addUser: function(siteId, user) {
+ var users = Account.getUsers(siteId);
+ var userId = user.id;
+ if (!users[userId]) {
+ users[userId] = user;
+ users = JSON.stringify(users);
+ localStorage.setItem(siteId + '_userInfo', users);
+ }
+ },
+
+ updateUser: function(siteId, user) {
+ var users = Account.getUsers(siteId);
+ var userId = user.id;
+ if (users && users[userId]) {
+ users[userId] = user;
+ users = JSON.stringify(users);
+ localStorage.setItem(siteId + '_userInfo', users);
+ }
+ },
+
+ removeUser: function(siteId, userId) {
+ var users = Account.getUsers(siteId);
+ delete users[userId];
+ users = JSON.stringify(users);
+ localStorage.setItem(siteId + '_userInfo', users);
+ },
+
+ isExpires: function(user) {
+ var expires = user.expires;
+ if (expires) {
+ return new Date().getTime() >= expires;
+ }
+ return false;
+ }
+};
\ No newline at end of file
diff --git a/src/js/ajax.js b/src/js/ajax.js
new file mode 100644
index 0000000..2f3d4a5
--- /dev/null
+++ b/src/js/ajax.js
@@ -0,0 +1,185 @@
+(function(){
+ /**
+ * ajax is a encapsulated function that used to send data to server
+ * asynchronously. It uses XMLHttpRequest object to send textual or binary
+ * data through HTTP method GET, POST etc. It can custom request method,
+ * request header. Response can be parsed automatically by MIME type of
+ * response's Content-type, and it can handle success, error or progress event
+ * in course of sending request and retrieving response.
+ * @param {Object} option
+ */
+ function ajax(option) {
+ if (arguments.length < 1 || option.constructor != Object)
+ throw new Error('Bad parameter.');
+ var url = option.url;
+ var success = option.success;
+ var complete = option.complete;
+ if (!url || !(success || complete))
+ throw new Error('Parameter url and success or complete are required.');
+
+ var parameters = option.parameters || {};
+ var method = option.method || 'GET';
+ var status = option.status;
+ var headers = option.headers || {};
+ var data = option.data || null;
+ var multipartData = option.multipartData;
+ var queryString = constructQueryString(parameters);
+
+ if (multipartData) {
+ var boundary = multipartData.boundary || 'XMLHttpRequest2';
+ method = 'POST';
+ var multipartDataString;
+ var contentType = headers['Content-Type'] || 'multipart/form-data';
+ if (contentType.indexOf('multipart/form-data') == 0) {
+ headers['Content-Type'] = 'multipart/form-data; boundary=' + boundary;
+ multipartDataString = constructMultipartFormData(multipartData, boundary,
+ parameters);
+ } else if (contentType.indexOf('multipart/related') == 0) {
+ headers['Content-Type'] = 'multipart/related; boundary=' + boundary;
+ multipartDataString = constructMultipartRelatedData(boundary,
+ multipartData.dataList);
+ }
+
+ data = constructBufferData(multipartDataString);
+ } else {
+ if (queryString)
+ url += '?' + queryString;
+ }
+
+ var xhr = new XMLHttpRequest();
+ xhr.open(method, url, true);
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ var statusCode = xhr.status;
+ var parsedResponse = parseResponse(xhr);
+ if (complete)
+ complete(statusCode, parsedResponse);
+ if (success && (statusCode == 200 || statusCode == 304)) {
+ success(parsedResponse);
+ } else if (status) {
+ if (status[statusCode]) {
+ // Call specified status code handler
+ status[statusCode](parsedResponse);
+ } else if (status['others']) {
+ // Call others status code handler
+ status['others'](parsedResponse, statusCode);
+ }
+ }
+ }
+ };
+
+ // Handle request progress
+ var progress = option.progress;
+ if (progress) {
+ xhr.upload.addEventListener('progress', function(e) {
+ // lengthComputable return true when the length of the progress is known
+ if (e.lengthComputable) {
+ progress(e.loaded, e.total);
+ }
+ }, false);
+ }
+ // Set request header
+ for (var headerKey in headers) {
+ xhr.setRequestHeader(headerKey, headers[headerKey]);
+ }
+
+ xhr.send(data);
+ }
+
+ function constructQueryString(parameters) {
+ var tmpParameter = [];
+ for(var name in parameters) {
+ var value = parameters[name];
+ if (value.constructor == Array) {
+ value.forEach(function(val) {
+ tmpParameter.push(name + '=' + val);
+ });
+ } else {
+ tmpParameter.push(name + '=' + value);
+ }
+ }
+ return tmpParameter.join('&');
+ }
+
+ // Parse response data according to content type of response
+ function parseResponse(xhr) {
+ var ct = xhr.getResponseHeader("content-type");
+ if (typeof ct == 'string') {
+ if (ct.indexOf('xml') >= 0)
+ return xhr.responseXML;
+ else if (ct.indexOf('json') >= 0)
+ return JSON.parse(xhr.responseText);
+ }
+ return xhr.responseText;
+ }
+
+ /**
+ * Construct multipart/form-data formatted data string.
+ * @param {Object} binaryData binary data
+ * @param {String} boundary boundary of parts
+ * @param {Object} otherParameters other text parameters
+ */
+ function constructMultipartFormData(binaryData, boundary, otherParameters) {
+ var commonHeader = 'Content-Disposition: form-data; ';
+ var data = [];
+ for (var key in otherParameters) {
+
+ // Add boundary of one header part
+ data.push('--' + boundary + '\r\n');
+
+ // Add same Content-Disposition information
+ data.push(commonHeader);
+ data.push('name="' + key + '"\r\n\r\n' + otherParameters[key] + '\r\n');
+ }
+
+ // Construct file data header
+ data.push('--' + boundary + '\r\n');
+ data.push(commonHeader);
+
+ data.push('name="' + (binaryData.name || 'binaryfilename') + '"; ');
+ data.push('filename=\"' + binaryData.value + '\"\r\n');
+ data.push('Content-type: ' + binaryData.type + '\r\n\r\n');
+ data.push(binaryData.data + '\r\n');
+
+ data.push('--' + boundary + '--\r\n');
+ return data.join('');
+ }
+
+ function constructBufferData(dataString, contentType) {
+ var len = dataString.length;
+
+ // Create a 8-bit unsigned integer ArrayBuffer view
+ var data = new Uint8Array(len);
+ for (var i = 0; i < len; i++) {
+ data[i] = dataString.charCodeAt(i);
+ }
+
+ return data.buffer
+ }
+
+ function constructMultipartRelatedData(boundary, dataList) {
+ var result = [];
+ dataList.forEach(function(data) {
+ result.push('--' + boundary + '\r\n');
+ result.push('Content-Type: ' + data.contentType + '\r\n\r\n');
+ result.push(data.data + '\r\n');
+ });
+ result.push('--' + boundary + '--\r\n');
+ return result.join('');
+ }
+
+ ajax.encodeForBinary = function(string) {
+ string = encodeURI(string).replace(/%([A-Z0-9]{2})/g, '%u00$1');
+ return unescape(string);
+ };
+
+ ajax.convertEntityString = function(string) {
+ var entitychars = ['<', '>', '&', '"', '\''];
+ var entities = ['<', '>', '&', '"', '''];
+ entitychars.forEach(function(character, index) {
+ string = string.replace(character, entities[index]);
+ });
+ return string;
+ };
+ window.ajax = ajax;
+})();
\ No newline at end of file
diff --git a/src/js/background.js b/src/js/background.js
new file mode 100644
index 0000000..e3c030e
--- /dev/null
+++ b/src/js/background.js
@@ -0,0 +1,323 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+var screenshot = {
+ tab: 0,
+ canvas: document.createElement("canvas"),
+ startX: 0,
+ startY: 0,
+ scrollX: 0,
+ scrollY: 0,
+ docHeight: 0,
+ docWidth: 0,
+ visibleWidth: 0,
+ visibleHeight: 0,
+ scrollXCount: 0,
+ scrollYCount: 0,
+ scrollBarX: 17,
+ scrollBarY: 17,
+ captureStatus: true,
+ screenshotName: null,
+
+ handleHotKey: function(keyCode) {
+ if (HotKey.isEnabled()) {
+ switch (keyCode) {
+ case HotKey.getCharCode('area'):
+ screenshot.showSelectionArea();
+ break;
+ case HotKey.getCharCode('viewport'):
+ screenshot.captureWindow();
+ break;
+ case HotKey.getCharCode('fullpage'):
+ screenshot.captureWebpage();
+ break;
+ case HotKey.getCharCode('screen'):
+ screenshot.captureScreen();
+ break;
+ }
+ }
+ },
+
+ /**
+ * Receive messages from content_script, and then decide what to do next
+ */
+ addMessageListener: function() {
+ chrome.extension.onMessage.addListener(function(request, sender, response) {
+ var obj = request;
+ var hotKeyEnabled = HotKey.isEnabled();
+ switch (obj.msg) {
+ case 'capture_hot_key':
+ screenshot.handleHotKey(obj.keyCode);
+ break;
+ case 'capture_selected':
+ screenshot.captureSelected();
+ break;
+ case 'capture_window':
+ if (hotKeyEnabled) {
+ screenshot.captureWindow();
+ }
+ break;
+ case 'capture_area':
+ if (hotKeyEnabled) {
+ screenshot.showSelectionArea();
+ }
+ break;
+ case 'capture_webpage':
+ if (hotKeyEnabled) {
+ screenshot.captureWebpage();
+ }
+ break;
+ }
+ });
+ },
+
+ /**
+ * Send the Message to content-script
+ */
+ sendMessage: function(message, callback) {
+ chrome.tabs.getSelected(null, function(tab) {
+ chrome.tabs.sendMessage(tab.id, message, callback);
+ });
+ },
+
+ showSelectionArea: function() {
+ screenshot.sendMessage({msg: 'show_selection_area'}, null);
+ },
+
+ captureWindow: function() {
+ screenshot.sendMessage({msg: 'capture_window'},
+ screenshot.onResponseVisibleSize);
+ },
+
+ captureSelected: function() {
+ screenshot.sendMessage({msg: 'capture_selected'},
+ screenshot.onResponseVisibleSize);
+ },
+
+ captureWebpage: function() {
+ screenshot.sendMessage({msg: 'scroll_init'},
+ screenshot.onResponseVisibleSize);
+ },
+
+ onResponseVisibleSize: function(response) {
+ switch (response.msg) {
+ case 'capture_window':
+ screenshot.captureVisible(response.docWidth, response.docHeight);
+ break;
+ case 'scroll_init_done':
+ screenshot.startX = response.startX,
+ screenshot.startY = response.startY,
+ screenshot.scrollX = response.scrollX,
+ screenshot.scrollY = response.scrollY,
+ screenshot.canvas.width = response.canvasWidth;
+ screenshot.canvas.height = response.canvasHeight;
+ screenshot.visibleHeight = response.visibleHeight,
+ screenshot.visibleWidth = response.visibleWidth,
+ screenshot.scrollXCount = response.scrollXCount;
+ screenshot.scrollYCount = response.scrollYCount;
+ screenshot.docWidth = response.docWidth;
+ screenshot.docHeight = response.docHeight;
+ screenshot.zoom = response.zoom;
+ setTimeout("screenshot.captureAndScroll()", 100);
+ break;
+ case 'scroll_next_done':
+ screenshot.scrollXCount = response.scrollXCount;
+ screenshot.scrollYCount = response.scrollYCount;
+ setTimeout("screenshot.captureAndScroll()", 100);
+ break;
+ case 'scroll_finished':
+ screenshot.captureAndScrollDone();
+ break;
+ }
+ },
+
+ captureSpecialPage: function() {
+ var formatParam = localStorage.screenshootQuality || 'png';
+ chrome.tabs.captureVisibleTab(
+ null, {format: formatParam, quality: 50}, function(data) {
+ var image = new Image();
+ image.onload = function() {
+ screenshot.canvas.width = image.width;
+ screenshot.canvas.height = image.height;
+ var context = screenshot.canvas.getContext("2d");
+ context.drawImage(image, 0, 0);
+ screenshot.postImage();
+ };
+ image.src = data;
+ });
+ },
+
+ captureScreenCallback: function(data) {
+ var image = new Image();
+ image.onload = function() {
+ screenshot.canvas.width = image.width;
+ screenshot.canvas.height = image.height;
+ var context = screenshot.canvas.getContext("2d");
+ context.drawImage(image, 0, 0);
+ screenshot.postImage();
+ };
+ image.src = "data:image/bmp;base64," + data;
+ },
+
+ /**
+ * Use drawImage method to slice parts of a source image and draw them to
+ * the canvas
+ */
+ capturePortion: function(x, y, width, height,
+ visibleWidth, visibleHeight, docWidth, docHeight) {
+ var formatParam = localStorage.screenshootQuality || 'png';
+ chrome.tabs.captureVisibleTab(
+ null, {format: formatParam, quality: 50}, function(data) {
+ var image = new Image();
+ image.onload = function() {
+ var curHeight = image.width < docWidth ?
+ image.height - screenshot.scrollBarY : image.height;
+ var curWidth = image.height < docHeight ?
+ image.width - screenshot.scrollBarX : image.width;
+ var zoomX = curWidth / visibleWidth;
+ var zoomY = curHeight / visibleHeight;
+ screenshot.canvas.width = width * zoomX;
+ screenshot.canvas.height = height * zoomY;
+ var context = screenshot.canvas.getContext("2d");
+ context.drawImage(image, x * zoomX, y * zoomY, width * zoomX,
+ height * zoomY, 0, 0, width * zoomX, height * zoomY);
+ screenshot.postImage();
+ };
+ image.src = data;
+ });
+ },
+
+ captureVisible: function(docWidth, docHeight) {
+ var formatParam = localStorage.screenshootQuality || 'png';
+ chrome.tabs.captureVisibleTab(
+ null, {format: formatParam, quality: 50}, function(data) {
+ var image = new Image();
+ image.onload = function() {
+ var width = image.height < docHeight ?
+ image.width - 17 : image.width;
+ var height = image.width < docWidth ?
+ image.height - 17 : image.height;
+ screenshot.canvas.width = width;
+ screenshot.canvas.height = height;
+ var context = screenshot.canvas.getContext("2d");
+ context.drawImage(image, 0, 0, width, height, 0, 0, width, height);
+ screenshot.postImage();
+ };
+ image.src = data;
+ });
+ },
+
+ /**
+ * Use the drawImage method to stitching images, and render to canvas
+ */
+ captureAndScroll: function() {
+ var formatParam = localStorage.screenshootQuality || 'png';
+ chrome.tabs.captureVisibleTab(
+ null, {format: formatParam, quality: 50}, function(data) {
+ var image = new Image();
+ image.onload = function() {
+ var context = screenshot.canvas.getContext('2d');
+ var width = 0;
+ var height = 0;
+
+ // Get scroll bar's width.
+ screenshot.scrollBarY =
+ screenshot.visibleHeight < screenshot.docHeight ? 17 : 0;
+ screenshot.scrollBarX =
+ screenshot.visibleWidth < screenshot.docWidth ? 17 : 0;
+
+ // Get visible width and height of capture result.
+ var visibleWidth =
+ (image.width - screenshot.scrollBarY < screenshot.canvas.width ?
+ image.width - screenshot.scrollBarY : screenshot.canvas.width);
+ var visibleHeight =
+ (image.height - screenshot.scrollBarX < screenshot.canvas.height ?
+ image.height - screenshot.scrollBarX : screenshot.canvas.height);
+
+ // Get region capture start x coordinate.
+ var zoom = screenshot.zoom;
+ var x1 = screenshot.startX - Math.round(screenshot.scrollX * zoom);
+ var x2 = 0;
+ var y1 = screenshot.startY - Math.round(screenshot.scrollY * zoom);
+ var y2 = 0;
+
+ if ((screenshot.scrollYCount + 1) * visibleWidth >
+ screenshot.canvas.width) {
+ width = screenshot.canvas.width % visibleWidth;
+ x1 = (screenshot.scrollYCount + 1) * visibleWidth -
+ screenshot.canvas.width + screenshot.startX - screenshot.scrollX;
+ } else {
+ width = visibleWidth;
+ }
+
+ if ((screenshot.scrollXCount + 1) * visibleHeight >
+ screenshot.canvas.height) {
+ height = screenshot.canvas.height % visibleHeight;
+ if ((screenshot.scrollXCount + 1) * visibleHeight +
+ screenshot.scrollY < screenshot.docHeight) {
+ y1 = 0;
+ } else {
+ y1 = (screenshot.scrollXCount + 1) * visibleHeight +
+ screenshot.scrollY - screenshot.docHeight;
+ }
+
+ } else {
+ height = visibleHeight;
+ }
+ x2 = screenshot.scrollYCount * visibleWidth;
+ y2 = screenshot.scrollXCount * visibleHeight;
+ context.drawImage(image, x1, y1, width, height, x2, y2, width, height);
+ screenshot.sendMessage({msg: 'scroll_next', visibleWidth: visibleWidth,
+ visibleHeight: visibleHeight}, screenshot.onResponseVisibleSize);
+ };
+ image.src = data;
+ });
+ },
+
+ captureAndScrollDone: function() {
+ screenshot.postImage();
+ },
+
+ /**
+ * Post the image to 'showimage.html'
+ */
+ postImage: function() {
+ chrome.tabs.getSelected(null, function(tab) {
+ screenshot.tab = tab;
+ });
+ var date = new Date();
+ screenshot.screenshotName = "Screenshot "+dateFormat(date, 'Y-m-d H.i.s');
+ chrome.tabs.create({'url': 'showimage.html'});
+ var popup = chrome.extension.getViews({type: 'popup'})[0];
+ if (popup)
+ popup.close();
+ },
+
+ isThisPlatform: function(operationSystem) {
+ return navigator.userAgent.toLowerCase().indexOf(operationSystem) > -1;
+ },
+
+ executeScriptsInExistingTabs: function() {
+ chrome.windows.getAll(null, function(wins) {
+ for (var j = 0; j < wins.length; ++j) {
+ chrome.tabs.getAllInWindow(wins[j].id, function(tabs) {
+ for (var i = 0; i < tabs.length; ++i) {
+ if (tabs[i].url.indexOf("chrome://") != 0) {
+ chrome.tabs.executeScript(tabs[i].id, { file: 'js/page.js' });
+ chrome.tabs.executeScript(tabs[i].id, { file: 'js/shortcut.js' });
+ }
+ }
+ });
+ }
+ });
+ },
+
+ init: function() {
+ localStorage.screenshootQuality = localStorage.screenshootQuality || 'png';
+ screenshot.executeScriptsInExistingTabs();
+ screenshot.addMessageListener();
+ }
+};
+
+screenshot.init();
diff --git a/src/js/editor.js b/src/js/editor.js
new file mode 100644
index 0000000..0d284ff
--- /dev/null
+++ b/src/js/editor.js
@@ -0,0 +1,240 @@
+function Canvas() {}
+
+Canvas.prototype.drawStrokeRect = function(
+ ctx, color, x, y, width, height, lineWidth) {
+ ctx.strokeStyle = color;
+ ctx.lineWidth = lineWidth;
+ ctx.strokeRect(x, y, width, height);
+}
+
+Canvas.prototype.drawFillRect = function(ctx, color, x, y, width, height) {
+ ctx.fillStyle = color;
+ ctx.fillRect(x, y, width, height);
+}
+
+Canvas.prototype.drawEllipse = function(
+ ctx, color, x, y, xAxis, yAxis, lineWidth, type) {
+ var startX = x + xAxis;
+ var startY = y;
+ ctx.beginPath();
+ ctx.lineWidth = lineWidth;
+ ctx.moveTo(startX, startY);
+ for (var i = 0; i <= 360; i++) {
+ var degree = i * Math.PI / 180;
+ startX = x + (xAxis - 2) * Math.cos(degree);
+ startY = y - (yAxis - 2) * Math.sin(degree);
+ ctx.lineTo(startX, startY);
+ }
+ if (type == 'rect') {
+ ctx.fillStyle = changeColorToRgba(color, 0.5);
+ ctx.fill();
+ } else if (type == 'border') {
+ ctx.strokeStyle = color;
+ ctx.stroke();
+ }
+ ctx.closePath();
+}
+
+// Divide an entire phrase in an array of phrases, all with the max pixel
+// length given.
+Canvas.prototype.getLines = function(ctx, text, width, font) {
+ var words = text.split(" ");
+ var lines = [];
+ var lastLine = "";
+ var measure = 0;
+ ctx.font = font;
+ for (var i = 0; i < words.length; i++) {
+ var word = words[i];
+ measure = ctx.measureText(lastLine + word).width;
+ if (measure <= width || word == "") {
+ lastLine += word + " ";
+ } else {
+ if (lastLine != "")
+ lines.push(lastLine);
+
+ // break the word if necessary
+ measure = ctx.measureText(word).width;
+ if (measure <= width) {
+ lastLine = word + " ";
+ } else {
+ lastLine = word[0];
+ for (var j = 1; j < word.length; j++) {
+ measure = ctx.measureText(lastLine + word[j]).width;
+ if (measure <= width) {
+ lastLine += word[j];
+ } else {
+ lines.push(lastLine);
+ lastLine = word[j];
+ }
+ }
+ lastLine += " ";
+ }
+ }
+ }
+ if (lastLine != "")
+ lines.push(lastLine);
+ return lines;
+}
+
+Canvas.prototype.setText = function(
+ ctx, text, color, fontSize, fontFamily, lineHeight, x, y, width) {
+ ctx.textBaseline = 'top';
+ ctx.fillStyle = color;
+ ctx.font = fontSize + ' ' + fontFamily;
+ ctx.lineHeight = lineHeight;
+ var lines = Canvas.prototype.getLines(ctx, text, width - 2, ctx.font);
+ for (var i = 0; i < lines.length; i++)
+ ctx.fillText(lines[i], x, y + lineHeight * i, width);
+}
+
+Canvas.prototype.drawLine = function(
+ ctx, color, lineCap, lineWidth, startX, startY, endX, endY) {
+ ctx.beginPath();
+ ctx.moveTo(startX, startY);
+ ctx.strokeStyle = color;
+ ctx.lineWidth = lineWidth;
+ ctx.lineCap = lineCap;
+ ctx.lineTo(endX, endY);
+ ctx.closePath();
+ ctx.stroke();
+}
+
+Canvas.prototype.drawArrow = function(
+ ctx, color, lineWidth, arrowWidth, arrowHeight, lineCap,
+ startX, startY, endX, endY) {
+ var arrowCoordinates = calculateArrowCoordinates(
+ arrowWidth, arrowHeight,startX, startY, endX, endY);
+ ctx.beginPath();
+ ctx.strokeStyle = color;
+ ctx.lineWidth = lineWidth;
+ ctx.lineCap = lineCap;
+ ctx.moveTo(startX, startY);
+ ctx.lineTo(endX, endY);
+ ctx.lineTo(arrowCoordinates.p1.x, arrowCoordinates.p1.y);
+ ctx.moveTo(endX, endY);
+ ctx.lineTo(arrowCoordinates.p2.x, arrowCoordinates.p2.y);
+ ctx.closePath();
+ ctx.stroke();
+}
+
+Canvas.prototype.drawRoundedRect = function(
+ ctx, color, x, y, width, height, radius, type) {
+ ctx.beginPath();
+ ctx.moveTo(x, y + radius);
+ ctx.lineTo(x, y + height - radius);
+ ctx.quadraticCurveTo(x, y + height, x + radius, y + height);
+ ctx.lineTo(x + width - radius, y + height);
+ ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
+ ctx.lineTo(x + width, y + radius);
+ ctx.quadraticCurveTo(x + width, y, x + width - radius, y);
+ ctx.lineTo(x + radius, y);
+ ctx.quadraticCurveTo(x, y, x, y + radius);
+ if (type == 'rect') {
+ ctx.fillStyle = changeColorToRgba(color, 0.5);
+ ctx.fill();
+ } else if (type == 'border') {
+ ctx.strokeStyle = color;
+ ctx.lineWidth = 2;
+ ctx.stroke();
+ }
+ ctx.closePath();
+}
+
+Canvas.prototype.blurImage = function(
+ realCanvas, simulateCanvas, layerId, startX, startY, endX, endY) {
+ var x = startX < endX ? startX : endX;
+ var y = startY < endY ? startY : endY;
+ var width = Math.abs(endX - startX - 1);
+ var height = Math.abs(endY - startY - 1);
+ simulateCanvas.width = $(layerId).clientWidth + 10;
+ simulateCanvas.height = $(layerId).clientHeight + 10;
+ var ctx = simulateCanvas.getContext('2d');
+ try {
+ ctx.drawImage(realCanvas, x, y, width, height, 0, 0, width, height);
+ } catch (error) {
+ console.log(error + ', width : height = ' + width + ' : ' + height);
+ }
+ var imageData = ctx.getImageData(0, 0, width, height);
+ imageData = this.boxBlur(imageData, width, height, 10);
+ ctx.putImageData(imageData, 0, 0);
+}
+
+Canvas.prototype.boxBlur = function(image, width, height, count) {
+ var j;
+ var pix = image.data;
+ var inner = 0;
+ var outer = 0;
+ var step = 0;
+ var rowOrColumn;
+ var nextPosition;
+ var nowPosition;
+ for(rowOrColumn = 0; rowOrColumn < 2; rowOrColumn++) {
+ if (rowOrColumn) {
+ // column blurring
+ outer = width;
+ inner = height;
+ step = width * 4;
+ } else {
+ // Row blurring
+ outer = height;
+ inner = width;
+ step = 4;
+ }
+ for (var i = 0; i < outer; i++) {
+ // Calculate for r g b a
+ nextPosition = (rowOrColumn == 0 ? (i * width * 4) : (4 * i));
+ for (var k = 0; k < 4; k++) {
+ nowPosition = nextPosition + k;
+ var pixSum = 0;
+ for(var m = 0; m < count; m++) {
+ pixSum += pix[nowPosition + step * m];
+ }
+ pix[nowPosition] = pix[nowPosition + step] =
+ pix[nowPosition + step * 2] = Math.floor(pixSum/count);
+ for (j = 3; j < inner-2; j++) {
+ pixSum = Math.max(0, pixSum - pix[nowPosition + (j - 2) * step]
+ + pix[nowPosition + (j + 2) * step]);
+ pix[nowPosition + j * step] = Math.floor(pixSum/count);
+ }
+ pix[nowPosition + j * step] = pix[nowPosition + (j + 1) * step] =
+ Math.floor(pixSum / count);
+ }
+ }
+ }
+ return image;
+}
+
+function changeColorToRgba(color, opacity) {
+ var sColor = color.toLowerCase();
+ var sColorChange = [];
+ for (var i = 1; i < sColor.length; i += 2) {
+ sColorChange.push(parseInt("0x" + sColor.slice(i, i + 2)));
+ }
+ return "rgba(" + sColorChange.join(",") + "," + opacity + ")";
+}
+
+// Calculate coordinates of arrow
+function calculateArrowCoordinates(
+ arrowWidth, arrowHeight, startX, startY, endX, endY) {
+ var p1 = function() {
+ var x = startX - endX;
+ var y = startY - endY;
+ var hypotenuse = Math.sqrt(x * x + y * y);
+ hypotenuse = (hypotenuse == 0 ? arrowHeight : hypotenuse);
+ var dx = Math.round(x / hypotenuse * arrowHeight);
+ var dy = Math.round(y / hypotenuse * arrowHeight);
+ return {x: endX + dx, y: endY + dy};
+ }
+
+ var p2 = function(p1, direct) {
+ var x = p1.x - startX;
+ var y = p1.y - startY;
+ var hypotenuse = Math.sqrt(x * x + y * y);
+ hypotenuse = (hypotenuse == 0 ? arrowHeight : hypotenuse);
+ var dx = Math.round((y / hypotenuse * arrowWidth) * direct);
+ var dy = Math.round((x / hypotenuse * arrowWidth) * direct);
+ return {x: p1.x + dx, y: p1.y - dy }
+ }
+
+ return {p1:p2(p1(), 1), p2: p2(p1(), -1) } ;
+}
diff --git a/src/js/facebook.js b/src/js/facebook.js
new file mode 100644
index 0000000..68cb091
--- /dev/null
+++ b/src/js/facebook.js
@@ -0,0 +1,139 @@
+const FB_APP_ID = CURRENT_LOCALE == 'zh_CN' ? 170328509685996 : 118170701590738;
+const FB_REDIRECT_URI = 'http://www.facebook.com/connect/login_success.html';
+const FB_PERMISSION = 'offline_access,user_photos,publish_stream';
+const FB_ACCESS_TOKEN_URL = 'https://www.facebook.com/dialog/oauth';
+const FB_PHOTO_UPLOAD_URL = 'https://graph.facebook.com/me/photos';
+const FB_USER_INFO_URL = 'https://graph.facebook.com/me';
+const FB_LOGOUT_URL = 'http://m.facebook.com/logout.php?confirm=1';
+
+var Facebook = {
+ siteId: 'facebook',
+ redirectUrl: FB_REDIRECT_URI,
+ currentUserId: null,
+ accessTokenCallback: null,
+
+ isRedirectUrl: function(url) {
+ return url.indexOf(FB_REDIRECT_URI) == 0;
+ },
+
+ getAccessToken: function(callback) {
+ Facebook.accessTokenCallback = callback;
+ var url = FB_ACCESS_TOKEN_URL + '?client_id=' + FB_APP_ID +
+ '&redirect_uri=' + FB_REDIRECT_URI + '&scope=' + FB_PERMISSION +
+ '&response_type=token';
+ chrome.tabs.create({url: url});
+ },
+
+ parseAccessToken: function(url) {
+ var queryString = url.split('#')[1] || url.split('?')[1];
+ var queries = queryString.split('&');
+ var queryMap = {};
+ queries.forEach(function(pair) {
+ queryMap[pair.split('=')[0]] = pair.split('=')[1];
+ });
+ var accessToken = queryMap['access_token'];
+ if (accessToken) {
+ var user = new User({
+ accessToken: accessToken
+ });
+ Facebook.accessTokenCallback('success', user);
+ } else if (queryMap['error']) {
+ Facebook.accessTokenCallback('failure', 'user_denied');
+ }
+ Facebook.accessTokenCallback = null;
+ },
+
+ getUserInfo: function(user, callback) {
+ ajax({
+ url: FB_USER_INFO_URL,
+ parameters: {
+ 'access_token': user.accessToken
+ },
+ success: function(userInfo) {
+ userInfo = JSON.parse(userInfo);
+ if (callback) {
+ user.id = userInfo.id;
+ user.name = userInfo.name;
+ callback('success', user);
+ }
+ },
+ status: {
+ others: function() {
+ if (callback)
+ callback('failure', 'failed_to_get_user_info');
+ }
+ }
+ });
+ },
+
+ upload: function(user, caption, imageData, callback) {
+ caption = ajax.encodeForBinary(caption);
+ var params = {
+ 'access_token': user.accessToken,
+ message: caption
+ };
+
+ var binaryData = {
+ boundary: MULTIPART_FORMDATA_BOUNDARY,
+ data: imageData,
+ value: 'test.png',
+ type: 'image/png'
+ };
+
+ ajax({
+ url: FB_PHOTO_UPLOAD_URL,
+ parameters: params,
+ multipartData: binaryData,
+ success: function(data) {
+ callback('success', JSON.parse(data).id);
+ },
+ status: {
+ others: function(data) {
+ var message;
+ if (data) {
+ data = JSON.parse(data);
+ if (data.error.message.indexOf('access token') >= 0) {
+ // User removed application permission
+ // {"error":{"type":"OAuthException",
+ // "message":"Error validating access token."}}
+ message = 'bad_access_token';
+ } else {
+ // {"error":{"type":"OAuthException",
+ // "message":"(#1) An unknown error occurred"}}
+ message = 'unknown_error';
+ }
+ } else {
+ message = 'failed_to_connect_to_server';
+ }
+ callback('failure', message);
+ }
+ }
+ });
+ },
+
+ getPhotoLink: function(user, photoId, callback) {
+ ajax({
+ url: 'https://graph.facebook.com/' + photoId,
+ parameters: {
+ 'access_token': user.accessToken
+ },
+ complete: function(statusCode, data) {
+ if (statusCode == 200) {
+ callback('success', JSON.parse(data).link);
+ } else {
+ callback('failure', 'failed_to_get_photo_link');
+ }
+ }
+ });
+ },
+
+ logout: function(callback) {
+ ajax({
+ url: FB_LOGOUT_URL,
+ success: function(data) {
+ if (callback)
+ callback(data);
+ }
+ });
+ }
+};
\ No newline at end of file
diff --git a/src/js/hotkey_storage.js b/src/js/hotkey_storage.js
new file mode 100644
index 0000000..9cea0aa
--- /dev/null
+++ b/src/js/hotkey_storage.js
@@ -0,0 +1,54 @@
+var HotKey = (function() {
+ return {
+ setup: function() {
+ // Default enable hot key for capture.
+ if (!localStorage.getItem('hot_key_enabled'))
+ localStorage.setItem('hot_key_enabled', true);
+
+ // Set default hot key of capture, R V H P.
+ if (!this.get('area'))
+ this.set('area', 'R');
+ if (!this.get('viewport'))
+ this.set('viewport', 'V');
+ if (!this.get('fullpage'))
+ this.set('fullpage', 'H');
+ if (!this.get('screen'))
+ this.set('screen', 'P');
+
+ var screenCaptureHotKey = this.get('screen');
+ if (this.isEnabled()) {
+ this.set('screen', '@'); // Disable hot key for screen capture.
+ }
+ },
+
+ /**
+ * Set hot key by type.
+ * @param {String} type Hot key type, must be area/viewport/fullpage/screen.
+ * @param {String} value
+ */
+ set: function(type, value) {
+ var key = type + '_capture_hot_key';
+ localStorage.setItem(key, value);
+ },
+
+ get: function(type) {
+ return localStorage.getItem(type + '_capture_hot_key');
+ },
+
+ getCharCode: function(type) {
+ return this.get(type).charCodeAt(0);
+ },
+
+ enable: function() {
+ localStorage.setItem('hot_key_enabled', true);
+ },
+
+ disable: function(bg) {
+ localStorage.setItem('hot_key_enabled', false);
+ },
+
+ isEnabled: function() {
+ return localStorage.getItem('hot_key_enabled') == 'true';
+ }
+ }
+})();
diff --git a/src/js/imgur.js b/src/js/imgur.js
new file mode 100644
index 0000000..cfc399f
--- /dev/null
+++ b/src/js/imgur.js
@@ -0,0 +1,241 @@
+(function() {
+ const IMGUR_APP_KEY = '90922ae86b91b09541d74134a5bb0f6404e0c378f';
+ const IMGUR_APP_SECRET = 'b100656ebf595b7464e428615b9bc703';
+ const IMGUR_REQUEST_TOKEN_URL = 'https://api.imgur.com/oauth/request_token';
+ const IMGUR_USER_AUTHENTICATION_URL = 'https://api.imgur.com/oauth/authorize';
+ const IMGUR_ACCESS_TOKEN_URL = 'https://api.imgur.com/oauth/access_token';
+ const IMGUR_USER_INFO_URL = 'http://api.imgur.com/2/account.json';
+ const IMGUR_PHOTO_UPLOAD_URL = 'http://api.imgur.com/2/account/images.json';
+ const IMGUR_LOGOUT_URL = 'http://imgur.com/logout';
+ const OAUTH_SIGNATURE_METHOD = 'HMAC-SHA1';
+ const OAUTH_VERSION = '1.0';
+
+ var Imgur = window.Imgur = {
+ siteId: 'imgur',
+ currentUserId: null,
+ currentUserOauthToken: '',
+ currentUserOauthTokenSecret: '',
+ accessTokenCallback: null,
+
+ isRedirectUrl: function() {},
+
+ getAuthorizationHeader: function(message, accessor) {
+ OAuth.setTimestampAndNonce(message);
+ OAuth.SignatureMethod.sign(message, accessor);
+ return OAuth.getAuthorizationHeader("", message.parameters);
+ },
+
+ getRequestToken: function(callback) {
+ Imgur.accessTokenCallback = callback;
+ var message = {
+ action: IMGUR_REQUEST_TOKEN_URL,
+ method: 'POST',
+ parameters: {
+ 'oauth_consumer_key': IMGUR_APP_KEY,
+ 'oauth_signature_method': OAUTH_SIGNATURE_METHOD,
+ 'oauth_version': OAUTH_VERSION
+ }
+ };
+ var accessor = {
+ consumerKey: IMGUR_APP_KEY,
+ consumerSecret: IMGUR_APP_SECRET
+ };
+
+ // Get oauth signature header
+ var header = Imgur.getAuthorizationHeader(message, accessor);
+
+ ajax({
+ url: IMGUR_REQUEST_TOKEN_URL,
+ method: 'POST',
+ headers: {
+ 'Authorization': header
+ },
+ success: function(response) {
+ parameters = OAuth.getParameterMap(response);
+ var oauth_token = parameters['oauth_token'];
+ var oauth_token_secret = parameters['oauth_token_secret'];
+ Imgur.currentUserOauthToken = oauth_token;
+ Imgur.currentUserOauthTokenSecret = oauth_token_secret;
+ Imgur.getUserAuthentication(oauth_token);
+ },
+ status: {
+ others: function() {
+ callback('failure', 'imgur_failed_to_get_request_token');
+ }
+ }
+ });
+ },
+
+ getUserAuthentication: function(oauth_token) {
+ var url = IMGUR_USER_AUTHENTICATION_URL + '?oauth_token=' + oauth_token +
+ '&oauth_callback=ready';
+ chrome.tabs.create({url: url}, function(tab) {
+ chrome.tabs.onUpdated.addListener(
+ function(tabId, changeInfo, _tab) {
+ if (tabId == tab.id && changeInfo.url
+ && changeInfo.url.indexOf('oauth_verifier=') > 0) {
+ chrome.tabs.remove(tabId);
+ Imgur.parseAccessToken(changeInfo.url);
+ }
+ });
+ });
+ },
+
+ parseAccessToken: function(url) {
+ var oauth_verifier = OAuth.getParameter(url, 'oauth_verifier');
+ Imgur.getAccessToken(Imgur.accessTokenCallback, oauth_verifier);
+ Imgur.accessTokenCallback = null;
+ },
+
+ getAccessToken: function(callback, oauth_verifier) {
+ if (!oauth_verifier) {
+ Imgur.getRequestToken(callback);
+ return;
+ }
+ var message = {
+ action: IMGUR_ACCESS_TOKEN_URL,
+ method: 'POST',
+ parameters: {
+ 'oauth_consumer_key': IMGUR_APP_KEY,
+ 'oauth_token': Imgur.currentUserOauthToken,
+ 'oauth_token_secret': Imgur.currentUserOauthTokenSecret,
+ 'oauth_signature_method': OAUTH_SIGNATURE_METHOD,
+ 'oauth_verifier': oauth_verifier,
+ 'oauth_version': OAUTH_VERSION
+ }
+ };
+ var accessor = {
+ consumerKey: IMGUR_APP_KEY,
+ consumerSecret: IMGUR_APP_SECRET,
+ tokenSecret: Imgur.currentUserOauthTokenSecret
+ };
+ var header = Imgur.getAuthorizationHeader(message, accessor);
+
+ ajax({
+ url: IMGUR_ACCESS_TOKEN_URL,
+ method: 'POST',
+ headers: {
+ 'Authorization': header
+ },
+ success: function(response) {
+ responseMap = OAuth.getParameterMap(response);
+ var accessToken = responseMap.oauth_token;
+ var accessTokenSecret = responseMap.oauth_token_secret;
+ var user = new User({
+ id: null,
+ accessToken: accessToken,
+ accessTokenSecret: accessTokenSecret
+ });
+
+ callback('success', user);
+ },
+ status: {
+ others: function(data) {
+ callback('failure', 'imgur_failed_to_get_access_token');
+ }
+ }
+ });
+ },
+
+ getUserInfo: function(user, callback) {
+ var url = IMGUR_USER_INFO_URL;
+ var message = {
+ action: url,
+ method: 'GET',
+ parameters: {
+ 'oauth_consumer_key': IMGUR_APP_KEY,
+ 'oauth_token': user.accessToken,
+ 'oauth_signature_method': OAUTH_SIGNATURE_METHOD,
+ 'oauth_version': OAUTH_VERSION
+ }
+ };
+
+ var accessor = {
+ consumerSecret: IMGUR_APP_SECRET,
+ tokenSecret: user.accessTokenSecret
+ };
+
+ var header = Imgur.getAuthorizationHeader(message, accessor);
+ ajax({
+ url: url,
+ method: 'GET',
+ headers: {
+ 'Authorization': header
+ },
+ success: function(data) {
+ if (callback) {
+ user.id = data.account.url;
+ user.name = data.account.url;
+ callback('success', user);
+ }
+ },
+ status: {
+ others: function(data) {
+ callback('failure', 'failed_to_get_user_info');
+ }
+ }
+ });
+ },
+
+ upload: function(user, caption, imageData, callback) {
+ caption = encodeURIComponent(caption);
+ var message = {
+ action: IMGUR_PHOTO_UPLOAD_URL,
+ method: 'POST',
+ parameters: {
+ 'oauth_consumer_key': IMGUR_APP_KEY,
+ 'oauth_token': user.accessToken,
+ 'oauth_signature_method': OAUTH_SIGNATURE_METHOD,
+ 'oauth_version': OAUTH_VERSION
+ }
+ };
+ var accessor = {
+ consumerSecret: IMGUR_APP_SECRET,
+ tokenSecret: user.accessTokenSecret
+ };
+ var header = Imgur.getAuthorizationHeader(message, accessor);
+
+ var binaryData = {
+ boundary: MULTIPART_FORMDATA_BOUNDARY,
+ name: 'image',
+ value: 'screencapture.png',
+ data: imageData,
+ type: 'image/png'
+ };
+
+ ajax({
+ url: IMGUR_PHOTO_UPLOAD_URL,
+ method: 'POST',
+ multipartData: binaryData,
+ headers: {
+ 'Authorization': header
+ },
+ success: function(response) {
+ callback('success', response.images.links.original);
+ },
+ status: {
+ others: function(err, statusCode) {
+ if (statusCode == 401) {
+ callback('failure', 'bad_access_token');
+ } else {
+ callback('failure', 'failed_to_upload_image');
+ };
+ }
+ }
+ });
+ },
+
+ getPhotoLink: function(user, photoLink, callback) {
+ callback('success', photoLink);
+ },
+
+ logout: function(callback) {
+ ajax({
+ url: IMGUR_LOGOUT_URL,
+ success: function() {
+ callback();
+ }
+ });
+ }
+ };
+})();
\ No newline at end of file
diff --git a/src/js/isLoad.js b/src/js/isLoad.js
new file mode 100644
index 0000000..77ad5c8
--- /dev/null
+++ b/src/js/isLoad.js
@@ -0,0 +1,17 @@
+function checkScriptLoad() {
+ chrome.extension.onMessage.addListener(function(request, sender, response) {
+ if (request.msg == 'is_page_capturable') {
+ try {
+ if (isPageCapturable()) {
+ response({msg: 'capturable'});
+ } else {
+ response({msg: 'uncapturable'});
+ }
+ } catch(e) {
+ response({msg: 'loading'});
+ }
+ }
+ });
+}
+
+checkScriptLoad();
diff --git a/src/js/oauth.js b/src/js/oauth.js
new file mode 100644
index 0000000..85b2785
--- /dev/null
+++ b/src/js/oauth.js
@@ -0,0 +1,551 @@
+/*
+ * Copyright 2008 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Here's some JavaScript software for implementing OAuth.
+
+ This isn't as useful as you might hope. OAuth is based around
+ allowing tools and websites to talk to each other. However,
+ JavaScript running in web browsers is hampered by security
+ restrictions that prevent code running on one website from
+ accessing data stored or served on another.
+
+ Before you start hacking, make sure you understand the limitations
+ posed by cross-domain XMLHttpRequest.
+
+ On the bright side, some platforms use JavaScript as their
+ language, but enable the programmer to access other web sites.
+ Examples include Google Gadgets, and Microsoft Vista Sidebar.
+ For those platforms, this library should come in handy.
+*/
+
+// The HMAC-SHA1 signature method calls b64_hmac_sha1, defined by
+// http://pajhome.org.uk/crypt/md5/sha1.js
+
+/* An OAuth message is represented as an object like this:
+ {method: "GET", action: "http://server.com/path", parameters: ...}
+
+ The parameters may be either a map {name: value, name2: value2}
+ or an Array of name-value pairs [[name, value], [name2, value2]].
+ The latter representation is more powerful: it supports parameters
+ in a specific sequence, or several parameters with the same name;
+ for example [["a", 1], ["b", 2], ["a", 3]].
+
+ Parameter names and values are NOT percent-encoded in an object.
+ They must be encoded before transmission and decoded after reception.
+ For example, this message object:
+ {method: "GET", action: "http://server/path", parameters: {p: "x y"}}
+ ... can be transmitted as an HTTP request that begins:
+ GET /path?p=x%20y HTTP/1.0
+ (This isn't a valid OAuth request, since it lacks a signature etc.)
+ Note that the object "x y" is transmitted as x%20y. To encode
+ parameters, you can call OAuth.addToURL, OAuth.formEncode or
+ OAuth.getAuthorization.
+
+ This message object model harmonizes with the browser object model for
+ input elements of an form, whose value property isn't percent encoded.
+ The browser encodes each value before transmitting it. For example,
+ see consumer.setInputs in example/consumer.js.
+ */
+
+/* This script needs to know what time it is. By default, it uses the local
+ clock (new Date), which is apt to be inaccurate in browsers. To do
+ better, you can load this script from a URL whose query string contains
+ an oauth_timestamp parameter, whose value is a current Unix timestamp.
+ For example, when generating the enclosing document using PHP:
+
+ <script src="oauth.js?oauth_timestamp=<?=time()?>" ...
+
+ Another option is to call OAuth.correctTimestamp with a Unix timestamp.
+ */
+
+var OAuth; if (OAuth == null) OAuth = {};
+
+OAuth.setProperties = function setProperties(into, from) {
+ if (into != null && from != null) {
+ for (var key in from) {
+ into[key] = from[key];
+ }
+ }
+ return into;
+}
+
+OAuth.setProperties(OAuth, // utility functions
+{
+ percentEncode: function percentEncode(s) {
+ if (s == null) {
+ return "";
+ }
+ if (s instanceof Array) {
+ var e = "";
+ for (var i = 0; i < s.length; ++s) {
+ if (e != "") e += '&';
+ e += OAuth.percentEncode(s[i]);
+ }
+ return e;
+ }
+ s = encodeURIComponent(s);
+ // Now replace the values which encodeURIComponent doesn't do
+ // encodeURIComponent ignores: - _ . ! ~ * ' ( )
+ // OAuth dictates the only ones you can ignore are: - _ . ~
+ // Source: http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Functions:encodeURIComponent
+ s = s.replace(/\!/g, "%21");
+ s = s.replace(/\*/g, "%2A");
+ s = s.replace(/\'/g, "%27");
+ s = s.replace(/\(/g, "%28");
+ s = s.replace(/\)/g, "%29");
+ return s;
+ }
+,
+ decodePercent: function decodePercent(s) {
+ if (s != null) {
+ // Handle application/x-www-form-urlencoded, which is defined by
+ // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
+ s = s.replace(/\+/g, " ");
+ }
+ return decodeURIComponent(s);
+ }
+,
+ /** Convert the given parameters to an Array of name-value pairs. */
+ getParameterList: function getParameterList(parameters) {
+ if (parameters == null) {
+ return [];
+ }
+ if (typeof parameters != "object") {
+ return OAuth.decodeForm(parameters + "");
+ }
+ if (parameters instanceof Array) {
+ return parameters;
+ }
+ var list = [];
+ for (var p in parameters) {
+ list.push([p, parameters[p]]);
+ }
+ return list;
+ }
+,
+ /** Convert the given parameters to a map from name to value. */
+ getParameterMap: function getParameterMap(parameters) {
+ if (parameters == null) {
+ return {};
+ }
+ if (typeof parameters != "object") {
+ return OAuth.getParameterMap(OAuth.decodeForm(parameters + ""));
+ }
+ if (parameters instanceof Array) {
+ var map = {};
+ for (var p = 0; p < parameters.length; ++p) {
+ var key = parameters[p][0];
+ if (map[key] === undefined) { // first value wins
+ map[key] = parameters[p][1];
+ }
+ }
+ return map;
+ }
+ return parameters;
+ }
+,
+ getParameter: function getParameter(parameters, name) {
+ if (parameters instanceof Array) {
+ for (var p = 0; p < parameters.length; ++p) {
+ if (parameters[p][0] == name) {
+ return parameters[p][1]; // first value wins
+ }
+ }
+ } else {
+ return OAuth.getParameterMap(parameters)[name];
+ }
+ return null;
+ }
+,
+ formEncode: function formEncode(parameters) {
+ var form = "";
+ var list = OAuth.getParameterList(parameters);
+ for (var p = 0; p < list.length; ++p) {
+ var value = list[p][1];
+ if (value == null) value = "";
+ if (form != "") form += '&';
+ form += OAuth.percentEncode(list[p][0])
+ +'='+ OAuth.percentEncode(value);
+ }
+ return form;
+ }
+,
+ decodeForm: function decodeForm(form) {
+ var list = [];
+ var nvps = form.split('&');
+ for (var n = 0; n < nvps.length; ++n) {
+ var nvp = nvps[n];
+ if (nvp == "") {
+ continue;
+ }
+ var equals = nvp.indexOf('=');
+ var name;
+ var value;
+ if (equals < 0) {
+ name = OAuth.decodePercent(nvp);
+ value = null;
+ } else {
+ name = OAuth.decodePercent(nvp.substring(0, equals));
+ value = OAuth.decodePercent(nvp.substring(equals + 1));
+ }
+ list.push([name, value]);
+ }
+ return list;
+ }
+,
+ setParameter: function setParameter(message, name, value) {
+ var parameters = message.parameters;
+ if (parameters instanceof Array) {
+ for (var p = 0; p < parameters.length; ++p) {
+ if (parameters[p][0] == name) {
+ if (value === undefined) {
+ parameters.splice(p, 1);
+ } else {
+ parameters[p][1] = value;
+ value = undefined;
+ }
+ }
+ }
+ if (value !== undefined) {
+ parameters.push([name, value]);
+ }
+ } else {
+ parameters = OAuth.getParameterMap(parameters);
+ parameters[name] = value;
+ message.parameters = parameters;
+ }
+ }
+,
+ setParameters: function setParameters(message, parameters) {
+ var list = OAuth.getParameterList(parameters);
+ for (var i = 0; i < list.length; ++i) {
+ OAuth.setParameter(message, list[i][0], list[i][1]);
+ }
+ }
+,
+ /** Fill in parameters to help construct a request message.
+ This function doesn't fill in every parameter.
+ The accessor object should be like:
+ {consumerKey:'foo', consumerSecret:'bar', accessorSecret:'nurn', token:'krelm', tokenSecret:'blah'}
+ The accessorSecret property is optional.
+ */
+ completeRequest: function completeRequest(message, accessor) {
+ if (message.method == null) {
+ message.method = "GET";
+ }
+ var map = OAuth.getParameterMap(message.parameters);
+ if (map.oauth_consumer_key == null) {
+ OAuth.setParameter(message, "oauth_consumer_key", accessor.consumerKey || "");
+ }
+ if (map.oauth_token == null && accessor.token != null) {
+ OAuth.setParameter(message, "oauth_token", accessor.token);
+ }
+ if (map.oauth_version == null) {
+ OAuth.setParameter(message, "oauth_version", "1.0");
+ }
+ if (map.oauth_timestamp == null) {
+ OAuth.setParameter(message, "oauth_timestamp", OAuth.timestamp());
+ }
+ if (map.oauth_nonce == null) {
+ OAuth.setParameter(message, "oauth_nonce", OAuth.nonce(6));
+ }
+ OAuth.SignatureMethod.sign(message, accessor);
+ }
+,
+ setTimestampAndNonce: function setTimestampAndNonce(message) {
+ OAuth.setParameter(message, "oauth_timestamp", OAuth.timestamp());
+ OAuth.setParameter(message, "oauth_nonce", OAuth.nonce(6));
+ }
+,
+ addToURL: function addToURL(url, parameters) {
+ newURL = url;
+ if (parameters != null) {
+ var toAdd = OAuth.formEncode(parameters);
+ if (toAdd.length > 0) {
+ var q = url.indexOf('?');
+ if (q < 0) newURL += '?';
+ else newURL += '&';
+ newURL += toAdd;
+ }
+ }
+ return newURL;
+ }
+,
+ /** Construct the value of the Authorization header for an HTTP request. */
+ getAuthorizationHeader: function getAuthorizationHeader(realm, parameters) {
+ var header = 'OAuth realm="' + OAuth.percentEncode(realm) + '"';
+ var list = OAuth.getParameterList(parameters);
+ for (var p = 0; p < list.length; ++p) {
+ var parameter = list[p];
+ var name = parameter[0];
+ if (name.indexOf("oauth_") == 0) {
+ header += ',' + OAuth.percentEncode(name) + '="' + OAuth.percentEncode(parameter[1]) + '"';
+ }
+ }
+ return header;
+ }
+,
+ /** Correct the time using a parameter from the URL from which the last script was loaded. */
+ correctTimestampFromSrc: function correctTimestampFromSrc(parameterName) {
+ parameterName = parameterName || "oauth_timestamp";
+ var scripts = document.getElementsByTagName('script');
+ if (scripts == null || !scripts.length) return;
+ var src = scripts[scripts.length-1].src;
+ if (!src) return;
+ var q = src.indexOf("?");
+ if (q < 0) return;
+ parameters = OAuth.getParameterMap(OAuth.decodeForm(src.substring(q+1)));
+ var t = parameters[parameterName];
+ if (t == null) return;
+ OAuth.correctTimestamp(t);
+ }
+,
+ /** Generate timestamps starting with the given value. */
+ correctTimestamp: function correctTimestamp(timestamp) {
+ OAuth.timeCorrectionMsec = (timestamp * 1000) - (new Date()).getTime();
+ }
+,
+ /** The difference between the correct time and my clock. */
+ timeCorrectionMsec: 0
+,
+ timestamp: function timestamp() {
+ var t = (new Date()).getTime() + OAuth.timeCorrectionMsec;
+ return Math.floor(t / 1000);
+ }
+,
+ nonce: function nonce(length) {
+ var chars = OAuth.nonce.CHARS;
+ var result = "";
+ for (var i = 0; i < length; ++i) {
+ var rnum = Math.floor(Math.random() * chars.length);
+ result += chars.substring(rnum, rnum+1);
+ }
+ return result;
+ }
+});
+
+OAuth.nonce.CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
+
+/** Define a constructor function,
+ without causing trouble to anyone who was using it as a namespace.
+ That is, if parent[name] already existed and had properties,
+ copy those properties into the new constructor.
+ */
+OAuth.declareClass = function declareClass(parent, name, newConstructor) {
+ var previous = parent[name];
+ parent[name] = newConstructor;
+ if (newConstructor != null && previous != null) {
+ for (var key in previous) {
+ if (key != "prototype") {
+ newConstructor[key] = previous[key];
+ }
+ }
+ }
+ return newConstructor;
+}
+
+/** An abstract algorithm for signing messages. */
+OAuth.declareClass(OAuth, "SignatureMethod", function OAuthSignatureMethod(){});
+
+OAuth.setProperties(OAuth.SignatureMethod.prototype, // instance members
+{
+ /** Add a signature to the message. */
+ sign: function sign(message) {
+ var baseString = OAuth.SignatureMethod.getBaseString(message);
+ var signature = this.getSignature(baseString);
+ OAuth.setParameter(message, "oauth_signature", signature);
+ return signature; // just in case someone's interested
+ }
+,
+ /** Set the key string for signing. */
+ initialize: function initialize(name, accessor) {
+ var consumerSecret;
+ if (accessor.accessorSecret != null
+ && name.length > 9
+ && name.substring(name.length-9) == "-Accessor")
+ {
+ consumerSecret = accessor.accessorSecret;
+ } else {
+ consumerSecret = accessor.consumerSecret;
+ }
+ this.key = OAuth.percentEncode(consumerSecret)
+ +"&"+ OAuth.percentEncode(accessor.tokenSecret);
+ }
+});
+
+/* SignatureMethod expects an accessor object to be like this:
+ {tokenSecret: "lakjsdflkj...", consumerSecret: "QOUEWRI..", accessorSecret: "xcmvzc..."}
+ The accessorSecret property is optional.
+ */
+// Class members:
+OAuth.setProperties(OAuth.SignatureMethod, // class members
+{
+ sign: function sign(message, accessor) {
+ var name = OAuth.getParameterMap(message.parameters).oauth_signature_method;
+ if (name == null || name == "") {
+ name = "HMAC-SHA1";
+ OAuth.setParameter(message, "oauth_signature_method", name);
+ }
+ OAuth.SignatureMethod.newMethod(name, accessor).sign(message);
+ }
+,
+ /** Instantiate a SignatureMethod for the given method name. */
+ newMethod: function newMethod(name, accessor) {
+ var impl = OAuth.SignatureMethod.REGISTERED[name];
+ if (impl != null) {
+ var method = new impl();
+ method.initialize(name, accessor);
+ return method;
+ }
+ var err = new Error("signature_method_rejected");
+ var acceptable = "";
+ for (var r in OAuth.SignatureMethod.REGISTERED) {
+ if (acceptable != "") acceptable += '&';
+ acceptable += OAuth.percentEncode(r);
+ }
+ err.oauth_acceptable_signature_methods = acceptable;
+ throw err;
+ }
+,
+ /** A map from signature method name to constructor. */
+ REGISTERED : {}
+,
+ /** Subsequently, the given constructor will be used for the named methods.
+ The constructor will be called with no parameters.
+ The resulting object should usually implement getSignature(baseString).
+ You can easily define such a constructor by calling makeSubclass, below.
+ */
+ registerMethodClass: function registerMethodClass(names, classConstructor) {
+ for (var n = 0; n < names.length; ++n) {
+ OAuth.SignatureMethod.REGISTERED[names[n]] = classConstructor;
+ }
+ }
+,
+ /** Create a subclass of OAuth.SignatureMethod, with the given getSignature function. */
+ makeSubclass: function makeSubclass(getSignatureFunction) {
+ var superClass = OAuth.SignatureMethod;
+ var subClass = function() {
+ superClass.call(this);
+ };
+ subClass.prototype = new superClass();
+ // Delete instance variables from prototype:
+ // delete subclass.prototype... There aren't any.
+ subClass.prototype.getSignature = getSignatureFunction;
+ subClass.prototype.constructor = subClass;
+ return subClass;
+ }
+,
+ getBaseString: function getBaseString(message) {
+ var URL = message.action;
+ var q = URL.indexOf('?');
+ var parameters;
+ if (q < 0) {
+ parameters = message.parameters;
+ } else {
+ // Combine the URL query string with the other parameters:
+ parameters = OAuth.decodeForm(URL.substring(q + 1));
+ var toAdd = OAuth.getParameterList(message.parameters);
+ for (var a = 0; a < toAdd.length; ++a) {
+ parameters.push(toAdd[a]);
+ }
+ }
+ return OAuth.percentEncode(message.method.toUpperCase())
+ +'&'+ OAuth.percentEncode(OAuth.SignatureMethod.normalizeUrl(URL))
+ +'&'+ OAuth.percentEncode(OAuth.SignatureMethod.normalizeParameters(parameters));
+ }
+,
+ normalizeUrl: function normalizeUrl(url) {
+ var uri = OAuth.SignatureMethod.parseUri(url);
+ var scheme = uri.protocol.toLowerCase();
+ var authority = uri.authority.toLowerCase();
+ var dropPort = (scheme == "http" && uri.port == 80)
+ || (scheme == "https" && uri.port == 443);
+ if (dropPort) {
+ // find the last : in the authority
+ var index = authority.lastIndexOf(":");
+ if (index >= 0) {
+ authority = authority.substring(0, index);
+ }
+ }
+ var path = uri.path;
+ if (!path) {
+ path = "/"; // conforms to RFC 2616 section 3.2.2
+ }
+ // we know that there is no query and no fragment here.
+ return scheme + "://" + authority + path;
+ }
+,
+ parseUri: function parseUri (str) {
+ /* This function was adapted from parseUri 1.2.1
+ http://stevenlevithan.com/demo/parseuri/js/assets/parseuri.js
+ */
+ var o = {key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
+ parser: {strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/ }};
+ var m = o.parser.strict.exec(str);
+ var uri = {};
+ var i = 14;
+ while (i--) uri[o.key[i]] = m[i] || "";
+ return uri;
+ }
+,
+ normalizeParameters: function normalizeParameters(parameters) {
+ if (parameters == null) {
+ return "";
+ }
+ var list = OAuth.getParameterList(parameters);
+ var sortable = [];
+ for (var p = 0; p < list.length; ++p) {
+ var nvp = list[p];
+ if (nvp[0] != "oauth_signature") {
+ sortable.push([ OAuth.percentEncode(nvp[0])
+ + " " // because it comes before any character that can appear in a percentEncoded string.
+ + OAuth.percentEncode(nvp[1])
+ , nvp]);
+ }
+ }
+ sortable.sort(function(a,b) {
+ if (a[0] < b[0]) return -1;
+ if (a[0] > b[0]) return 1;
+ return 0;
+ });
+ var sorted = [];
+ for (var s = 0; s < sortable.length; ++s) {
+ sorted.push(sortable[s][1]);
+ }
+ return OAuth.formEncode(sorted);
+ }
+});
+
+OAuth.SignatureMethod.registerMethodClass(["PLAINTEXT", "PLAINTEXT-Accessor"],
+ OAuth.SignatureMethod.makeSubclass(
+ function getSignature(baseString) {
+ return this.key;
+ }
+ ));
+
+OAuth.SignatureMethod.registerMethodClass(["HMAC-SHA1", "HMAC-SHA1-Accessor"],
+ OAuth.SignatureMethod.makeSubclass(
+ function getSignature(baseString) {
+ b64pad = '=';
+ var signature = b64_hmac_sha1(this.key, baseString);
+ return signature;
+ }
+ ));
+
+try {
+ OAuth.correctTimestampFromSrc();
+} catch(e) {
+}
diff --git a/src/js/options.js b/src/js/options.js
new file mode 100644
index 0000000..9d61498
--- /dev/null
+++ b/src/js/options.js
@@ -0,0 +1,181 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+var bg = chrome.extension.getBackgroundPage();
+
+function $(id) {
+ return document.getElementById(id);
+}
+
+function isHighVersion() {
+ var version = navigator.userAgent.match(/Chrome\/(\d+)/)[1];
+ return version > 9;
+}
+
+function init() {
+ i18nReplace('optionTitle', 'options');
+ i18nReplace('saveAndClose', 'save_and_close');
+ i18nReplace('screenshootQualitySetting', 'quality_setting');
+ i18nReplace('lossyScreenShot', 'lossy');
+ i18nReplace('losslessScreenShot', 'lossless');
+ i18nReplace('shorcutSetting', 'shortcut_setting');
+ i18nReplace('settingShortcutText', 'shortcutsetting_text');
+ if (isHighVersion()) {
+ $('lossyScreenShot').innerText += ' (JPEG)';
+ $('losslessScreenShot').innerText += ' (PNG)';
+ }
+ $('saveAndClose').addEventListener('click', saveAndClose);
+ initScreenCaptureQuality();
+ HotKeySetting.setup();
+}
+
+function save() {
+ localStorage.screenshootQuality =
+ $('lossy').checked ? 'jpeg' : '' ||
+ $('lossless').checked ? 'png' : '';
+
+ return HotKeySetting.save();
+}
+
+function saveAndClose() {
+ if (save())
+ chrome.tabs.getSelected(null, function(tab) {
+ chrome.tabs.remove(tab.id);
+ });
+}
+
+function initScreenCaptureQuality() {
+ $('lossy').checked = localStorage.screenshootQuality == 'jpeg';
+ $('lossless').checked = localStorage.screenshootQuality == 'png';
+}
+
+function i18nReplace(id, name) {
+ return $(id).innerText = chrome.i18n.getMessage(name);
+}
+
+const CURRENT_LOCALE = chrome.i18n.getMessage('@@ui_locale');
+if (CURRENT_LOCALE != 'zh_CN') {
+ UI.addStyleSheet('./i18n_styles/en_options.css');
+}
+
+var HotKeySetting = (function() {
+ const CHAR_CODE_OF_AT = 64;
+ const CHAR_CODE_OF_A = 65;
+ const CHAR_CODE_OF_Z = 90;
+ var hotKeySelection = null;
+
+ var hotkey = {
+ setup: function() {
+ hotKeySelection = document.querySelectorAll('#hot-key-setting select');
+ // i18n.
+ $('area-capture-text').innerText =
+ chrome.i18n.getMessage('capture_area');
+ $('viewport-capture-text').innerText =
+ chrome.i18n.getMessage('capture_window');
+ $('full-page-capture-text').innerText =
+ chrome.i18n.getMessage('capture_webpage');
+ //$('screen-capture-text').innerText = chrome.i18n.getMessage('capture_screen');
+
+ for (var i = 0; i < hotKeySelection.length; i++) {
+ hotKeySelection[i].add(new Option('--', '@'));
+ for (var j = CHAR_CODE_OF_A; j <= CHAR_CODE_OF_Z; j++) {
+ var value = String.fromCharCode(j);
+ var option = new Option(value, value);
+ hotKeySelection[i].add(option);
+ }
+ }
+
+ $('area-capture-hot-key').selectedIndex =
+ HotKey.getCharCode('area') - CHAR_CODE_OF_AT;
+ $('viewport-capture-hot-key').selectedIndex =
+ HotKey.getCharCode('viewport') - CHAR_CODE_OF_AT;
+ $('full-page-capture-hot-key').selectedIndex =
+ HotKey.getCharCode('fullpage') - CHAR_CODE_OF_AT;
+ $('screen-capture-hot-key').selectedIndex =
+ HotKey.getCharCode('screen') - CHAR_CODE_OF_AT;
+
+ $('settingShortcut').addEventListener('click', function() {
+ hotkey.setState(this.checked);
+ }, false);
+
+ hotkey.setState(HotKey.isEnabled());
+ },
+
+ validate: function() {
+ var hotKeyLength =
+ Array.prototype.filter.call(hotKeySelection,
+ function (element) {
+ return element.value != '@'
+ }
+ ).length;
+ if (hotKeyLength != 0) {
+ var validateMap = {};
+ validateMap[hotKeySelection[0].value] = true;
+ validateMap[hotKeySelection[1].value] = true;
+ validateMap[hotKeySelection[2].value] = true;
+ if (hotKeyLength > 3 && hotKeySelection[3].value != '@') {
+ hotKeyLength -= 1;
+ }
+
+ if (Object.keys(validateMap).length < hotKeyLength) {
+ ErrorInfo.show('hot_key_conflict');
+ return false;
+ }
+ }
+ ErrorInfo.hide();
+ return true;
+ },
+
+ save: function() {
+ var result = true;
+ if ($('settingShortcut').checked) {
+ if (this.validate()) {
+ HotKey.enable();
+ HotKey.set('area', $('area-capture-hot-key').value);
+ HotKey.set('viewport', $('viewport-capture-hot-key').value);
+ HotKey.set('fullpage', $('full-page-capture-hot-key').value);
+ } else {
+ result = false;
+ }
+ } else {
+ HotKey.disable(bg);
+ }
+ return result;
+ },
+
+ setState: function(enabled) {
+ $('settingShortcut').checked = enabled;
+ UI.setStyle($('hot-key-setting'), 'color', enabled ? '' : '#6d6d6d');
+ for (var i = 0; i < hotKeySelection.length; i++) {
+ hotKeySelection[i].disabled = !enabled;
+ }
+ ErrorInfo.hide();
+ },
+
+ focusScreenCapture: function() {
+ $('screen-capture-hot-key').focus();
+ }
+ };
+ return hotkey;
+})();
+
+var ErrorInfo = (function() {
+ return {
+ show: function(msgKey) {
+ var infoWrapper = $('error-info');
+ var msg = chrome.i18n.getMessage(msgKey);
+ infoWrapper.innerText = msg;
+ UI.show(infoWrapper);
+ },
+
+ hide: function() {
+ var infoWrapper = $('error-info');
+ if (infoWrapper) {
+ UI.hide(infoWrapper);
+ }
+ }
+ };
+})();
+
+document.addEventListener('DOMContentLoaded', init);
diff --git a/src/js/page.js b/src/js/page.js
new file mode 100644
index 0000000..2c5702d
--- /dev/null
+++ b/src/js/page.js
@@ -0,0 +1,907 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+var page = {
+ startX: 150,
+ startY: 150,
+ endX: 400,
+ endY: 300,
+ moveX: 0,
+ moveY: 0,
+ pageWidth: 0,
+ pageHeight: 0,
+ visibleWidth: 0,
+ visibleHeight: 0,
+ dragging: false,
+ moving: false,
+ resizing: false,
+ isMouseDown: false,
+ scrollXCount: 0,
+ scrollYCount: 0,
+ scrollX: 0,
+ scrollY: 0,
+ captureWidth: 0,
+ captureHeight: 0,
+ isSelectionAreaTurnOn: false,
+ fixedElements_ : [],
+ marginTop: 0,
+ marginLeft: 0,
+ modifiedBottomRightFixedElements: [],
+ originalViewPortWidth: document.documentElement.clientWidth,
+ defaultScrollBarWidth: 17, // Default scroll bar width on windows platform.
+
+ hookBodyScrollValue: function(needHook) {
+ document.documentElement.setAttribute(
+ "__screen_capture_need_hook_scroll_value__", needHook);
+ var event = document.createEvent('Event');
+ event.initEvent('__screen_capture_check_hook_status_event__', true, true);
+ document.documentElement.dispatchEvent(event);
+ },
+
+ /**
+ * Determine if the page scrolled to bottom or right.
+ */
+ isScrollToPageEnd: function(coordinate) {
+ var body = document.body;
+ var docElement = document.documentElement;
+ if (coordinate == 'x')
+ return docElement.clientWidth + body.scrollLeft == body.scrollWidth;
+ else if (coordinate == 'y')
+ return docElement.clientHeight + body.scrollTop == body.scrollHeight;
+ },
+
+ /**
+ * Detect if the view port is located to the corner of page.
+ */
+ detectPagePosition: function() {
+ var body = document.body;
+ var pageScrollTop = body.scrollTop;
+ var pageScrollLeft = body.scrollLeft;
+ if (pageScrollTop == 0 && pageScrollLeft == 0) {
+ return 'top_left';
+ } else if (pageScrollTop == 0 && this.isScrollToPageEnd('x')) {
+ return 'top_right';
+ } else if (this.isScrollToPageEnd('y') && pageScrollLeft == 0) {
+ return 'bottom_left';
+ } else if (this.isScrollToPageEnd('y') && this.isScrollToPageEnd('x')) {
+ return 'bottom_right';
+ }
+ return null;
+ },
+
+ /**
+ * Detect fixed-positioned element's position in the view port.
+ * @param {Element} elem
+ * @return {String|Object} Return position of the element in the view port:
+ * top_left, top_right, bottom_left, bottom_right, or null.
+ */
+ detectCapturePositionOfFixedElement: function(elem) {
+ var docElement = document.documentElement;
+ var viewPortWidth = docElement.clientWidth;
+ var viewPortHeight = docElement.clientHeight;
+ var offsetWidth = elem.offsetWidth;
+ var offsetHeight = elem.offsetHeight;
+ var offsetTop = elem.offsetTop;
+ var offsetLeft = elem.offsetLeft;
+ var result = [];
+
+ // Compare distance between element and the edge of view port to determine
+ // the capture position of element.
+ if (offsetTop <= viewPortHeight - offsetTop - offsetHeight) {
+ result.push('top');
+ } else if (offsetTop < viewPortHeight) {
+ result.push('bottom');
+ }
+ if (offsetLeft <= viewPortWidth - offsetLeft - offsetWidth) {
+ result.push('left');
+ } else if (offsetLeft < viewPortWidth) {
+ result.push('right');
+ }
+
+ // If the element is out of view port, then ignore.
+ if (result.length != 2)
+ return null;
+ return result.join('_');
+ },
+
+ restoreFixedElements: function() {
+ this.fixedElements_.forEach(function(element) {
+ element[1].style.visibility = 'visible';
+ });
+ this.fixedElements_ = [];
+ },
+
+ /**
+ * Iterate DOM tree and cache visible fixed-position elements.
+ */
+ cacheVisibleFixedPositionedElements: function() {
+ var nodeIterator = document.createNodeIterator(
+ document.documentElement,
+ NodeFilter.SHOW_ELEMENT,
+ null,
+ false
+ );
+ var currentNode;
+ while (currentNode = nodeIterator.nextNode()) {
+ var nodeComputedStyle =
+ document.defaultView.getComputedStyle(currentNode, "");
+ // Skip nodes which don't have computeStyle or are invisible.
+ if (!nodeComputedStyle)
+ continue;
+ if (nodeComputedStyle.position == "fixed" &&
+ nodeComputedStyle.display != 'none' &&
+ nodeComputedStyle.visibility != 'hidden') {
+ var position =
+ this.detectCapturePositionOfFixedElement(currentNode);
+ if (position)
+ this.fixedElements_.push([position, currentNode]);
+ }
+ }
+ },
+
+ // Handle fixed-position elements for capture.
+ handleFixedElements: function(capturePosition) {
+ var docElement = document.documentElement;
+ var body = document.body;
+
+ // If page has no scroll bar, then return directly.
+ if (docElement.clientHeight == body.scrollHeight &&
+ docElement.clientWidth == body.scrollWidth)
+ return;
+
+ if (!this.fixedElements_.length) {
+ this.cacheVisibleFixedPositionedElements();
+ }
+
+ this.fixedElements_.forEach(function(element) {
+ if (element[0] == capturePosition)
+ element[1].style.visibility = 'visible';
+ else
+ element[1].style.visibility = 'hidden';
+ });
+ },
+
+ handleSecondToLastCapture: function() {
+ var docElement = document.documentElement;
+ var body = document.body;
+ var bottomPositionElements = [];
+ var rightPositionElements = [];
+ var that = this;
+ this.fixedElements_.forEach(function(element) {
+ var position = element[0];
+ if (position == 'bottom_left' || position == 'bottom_right') {
+ bottomPositionElements.push(element[1]);
+ } else if (position == 'bottom_right' || position == 'top_right') {
+ rightPositionElements.push(element[1]);
+ }
+ });
+
+ // Determine if the current capture is last but one.
+ var remainingCaptureHeight = body.scrollHeight - docElement.clientHeight -
+ body.scrollTop;
+ if (remainingCaptureHeight > 0 &&
+ remainingCaptureHeight < docElement.clientHeight) {
+ bottomPositionElements.forEach(function(element) {
+ if (element.offsetHeight > remainingCaptureHeight) {
+ element.style.visibility = 'visible';
+ var originalBottom = window.getComputedStyle(element).bottom;
+ that.modifiedBottomRightFixedElements.push(
+ ['bottom', element, originalBottom]);
+ element.style.bottom = -remainingCaptureHeight + 'px';
+ }
+ });
+ }
+
+ var remainingCaptureWidth = body.scrollWidth - docElement.clientWidth -
+ body.scrollLeft;
+ if (remainingCaptureWidth > 0 &&
+ remainingCaptureWidth < docElement.clientWidth) {
+ rightPositionElements.forEach(function(element) {
+ if (element.offsetWidth > remainingCaptureWidth) {
+ element.style.visibility = 'visible';
+ var originalRight = window.getComputedStyle(element).right;
+ that.modifiedBottomRightFixedElements.push(
+ ['right', element, originalRight]);
+ element.style.right = -remainingCaptureWidth + 'px';
+ }
+ });
+ }
+ },
+
+ restoreBottomRightOfFixedPositionElements: function() {
+ this.modifiedBottomRightFixedElements.forEach(function(data) {
+ var property = data[0];
+ var element = data[1];
+ var originalValue = data[2];
+ element.style[property] = originalValue;
+ });
+ this.modifiedBottomRightFixedElements = [];
+ },
+
+ hideAllFixedPositionedElements: function() {
+ this.fixedElements_.forEach(function(element) {
+ element[1].style.visibility = 'hidden';
+ });
+ },
+
+ hasScrollBar: function(axis) {
+ var body = document.body;
+ var docElement = document.documentElement;
+ if (axis == 'x') {
+ if (window.getComputedStyle(body).overflowX == 'scroll')
+ return true;
+ return Math.abs(body.scrollWidth - docElement.clientWidth) >=
+ page.defaultScrollBarWidth;
+ } else if (axis == 'y') {
+ if (window.getComputedStyle(body).overflowY == 'scroll')
+ return true;
+ return Math.abs(body.scrollHeight - docElement.clientHeight) >=
+ page.defaultScrollBarWidth;
+ }
+ },
+
+ getOriginalViewPortWidth: function() {
+ chrome.extension.sendMessage({ msg: 'original_view_port_width'},
+ function(originalViewPortWidth) {
+ if (originalViewPortWidth) {
+ page.originalViewPortWidth = page.hasScrollBar('y') ?
+ originalViewPortWidth - page.defaultScrollBarWidth : originalViewPortWidth;
+ } else {
+ page.originalViewPortWidth = document.documentElement.clientWidth;
+ }
+ });
+ },
+
+ calculateSizeAfterZooming: function(originalSize) {
+ var originalViewPortWidth = page.originalViewPortWidth;
+ var currentViewPortWidth = document.documentElement.clientWidth;
+ if (originalViewPortWidth == currentViewPortWidth)
+ return originalSize;
+ return Math.round(
+ originalViewPortWidth * originalSize / currentViewPortWidth);
+ },
+
+ getZoomLevel: function() {
+ return page.originalViewPortWidth / document.documentElement.clientWidth;
+ },
+
+ handleRightFloatBoxInGmail: function() {
+ var mainframe = document.getElementById('canvas_frame');
+ var boxContainer = document.querySelector('body > .dw');
+ var fBody = mainframe.contentDocument.body;
+ if (fBody.clientHeight + fBody.scrollTop == fBody.scrollHeight) {
+ boxContainer.style.display = 'block';
+ } else {
+ boxContainer.style.display = 'none';
+ }
+ },
+
+ getViewPortSize: function() {
+ var result = {
+ width: document.documentElement.clientWidth,
+ height: document.documentElement.clientHeight
+ };
+
+ if (document.compatMode == 'BackCompat') {
+ result.width = document.body.clientWidth;
+ result.height = document.body.clientHeight;
+ }
+
+ return result;
+ },
+
+ /**
+ * Check if the page is only made of invisible embed elements.
+ */
+ checkPageIsOnlyEmbedElement: function() {
+ var bodyNode = document.body.children;
+ var isOnlyEmbed = false;
+ for (var i = 0; i < bodyNode.length; i++) {
+ var tagName = bodyNode[i].tagName;
+ if (tagName == 'OBJECT' || tagName == 'EMBED' || tagName == 'VIDEO' ||
+ tagName == 'SCRIPT' || tagName == 'LINK') {
+ isOnlyEmbed = true;
+ } else if (bodyNode[i].style.display != 'none'){
+ isOnlyEmbed = false;
+ break;
+ }
+ }
+ return isOnlyEmbed;
+ },
+
+ isGMailPage: function(){
+ var hostName = window.location.hostname;
+ if (hostName == 'mail.google.com' &&
+ document.getElementById('canvas_frame')) {
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Receive messages from background page, and then decide what to do next
+ */
+ addMessageListener: function() {
+ chrome.extension.onMessage.addListener(function(request, sender, response) {
+ if (page.isSelectionAreaTurnOn) {
+ page.removeSelectionArea();
+ }
+ switch (request.msg) {
+ case 'capture_window': response(page.getWindowSize()); break;
+ case 'show_selection_area': page.showSelectionArea(); break;
+ case 'scroll_init': // Capture whole page.
+ response(page.scrollInit(0, 0, document.body.scrollWidth,
+ document.body.scrollHeight, 'captureWhole'));
+ break;
+ case 'scroll_next':
+ page.visibleWidth = request.visibleWidth;
+ page.visibleHeight = request.visibleHeight;
+ response(page.scrollNext());
+ break;
+ case 'capture_selected':
+ response(page.scrollInit(
+ page.startX, page.startY,
+ page.calculateSizeAfterZooming(page.endX - page.startX),
+ page.calculateSizeAfterZooming(page.endY - page.startY),
+ 'captureSelected'));
+ break;
+ }
+ });
+ },
+
+ /**
+ * Send Message to background page
+ */
+ sendMessage: function(message) {
+ chrome.extension.sendMessage(message);
+ },
+
+ /**
+ * Initialize scrollbar position, and get the data browser
+ */
+ scrollInit: function(startX, startY, canvasWidth, canvasHeight, type) {
+ this.hookBodyScrollValue(true);
+ page.captureHeight = canvasHeight;
+ page.captureWidth = canvasWidth;
+ var docWidth = document.body.scrollWidth;
+ var docHeight = document.body.scrollHeight;
+ window.scrollTo(startX, startY);
+
+ this.handleFixedElements('top_left');
+ this.handleSecondToLastCapture();
+
+ if (page.isGMailPage() && type == 'captureWhole') {
+ var frame = document.getElementById('canvas_frame');
+ docHeight = page.captureHeight = canvasHeight =
+ frame.contentDocument.height;
+ docWidth = page.captureWidth = canvasWidth = frame.contentDocument.width;
+ frame.contentDocument.body.scrollTop = 0;
+ frame.contentDocument.body.scrollLeft = 0;
+ page.handleRightFloatBoxInGmail();
+ }
+ page.scrollXCount = 0;
+ page.scrollYCount = 1;
+ page.scrollX = window.scrollX; // document.body.scrollLeft
+ page.scrollY = window.scrollY;
+ var viewPortSize = page.getViewPortSize();
+ return {
+ 'msg': 'scroll_init_done',
+ 'startX': page.calculateSizeAfterZooming(startX),
+ 'startY': page.calculateSizeAfterZooming(startY),
+ 'scrollX': window.scrollX,
+ 'scrollY': window.scrollY,
+ 'docHeight': docHeight,
+ 'docWidth': docWidth,
+ 'visibleWidth': viewPortSize.width,
+ 'visibleHeight': viewPortSize.height,
+ 'canvasWidth': canvasWidth,
+ 'canvasHeight': canvasHeight,
+ 'scrollXCount': 0,
+ 'scrollYCount': 0,
+ 'zoom': page.getZoomLevel()
+ };
+ },
+
+ /**
+ * Calculate the next position of the scrollbar
+ */
+ scrollNext: function() {
+ if (page.scrollYCount * page.visibleWidth >= page.captureWidth) {
+ page.scrollXCount++;
+ page.scrollYCount = 0;
+ }
+ if (page.scrollXCount * page.visibleHeight < page.captureHeight) {
+ this.restoreBottomRightOfFixedPositionElements();
+ var viewPortSize = page.getViewPortSize();
+ window.scrollTo(
+ page.scrollYCount * viewPortSize.width + page.scrollX,
+ page.scrollXCount * viewPortSize.height + page.scrollY);
+
+ var pagePosition = this.detectPagePosition();
+ if (pagePosition) {
+ this.handleFixedElements(pagePosition);
+ } else {
+ this.hideAllFixedPositionedElements();
+ }
+ this.handleSecondToLastCapture();
+
+ if (page.isGMailPage()) {
+ var frame = document.getElementById('canvas_frame');
+ frame.contentDocument.body.scrollLeft =
+ page.scrollYCount * viewPortSize.width;
+ frame.contentDocument.body.scrollTop =
+ page.scrollXCount * viewPortSize.height;
+ page.handleRightFloatBoxInGmail();
+ }
+ var x = page.scrollXCount;
+ var y = page.scrollYCount;
+ page.scrollYCount++;
+ return { msg: 'scroll_next_done',scrollXCount: x, scrollYCount: y };
+ } else {
+ window.scrollTo(page.startX, page.startY);
+ this.restoreFixedElements();
+ this.hookBodyScrollValue(false);
+ return {'msg': 'scroll_finished'};
+ }
+ },
+
+ /**
+ * Show the selection Area
+ */
+ showSelectionArea: function() {
+ page.createFloatLayer();
+ setTimeout(page.createSelectionArea, 100);
+ },
+
+ getWindowSize: function() {
+ var docWidth = document.body.clientWidth;
+ var docHeight = document.body.clientHeight;
+ if (page.isGMailPage()) {
+ var frame = document.getElementById('canvas_frame');
+ docHeight = frame.contentDocument.height;
+ docWidth = frame.contentDocument.width;
+ }
+ return {'msg':'capture_window',
+ 'docWidth': docWidth,
+ 'docHeight': docHeight};
+ },
+
+ getSelectionSize: function() {
+ page.removeSelectionArea();
+ setTimeout(function() {
+ page.sendMessage({
+ 'msg': 'capture_selected',
+ 'x': page.startX,
+ 'y': page.startY,
+ 'width': page.endX - page.startX,
+ 'height': page.endY - page.startY,
+ 'visibleWidth': document.documentElement.clientWidth,
+ 'visibleHeight': document.documentElement.clientHeight,
+ 'docWidth': document.body.clientWidth,
+ 'docHeight': document.body.clientHeight
+ })}, 100);
+ },
+
+ /**
+ * Create a float layer on the webpage
+ */
+ createFloatLayer: function() {
+ page.createDiv(document.body, 'sc_drag_area_protector');
+ },
+
+ matchMarginValue: function(str) {
+ return str.match(/\d+/);
+ },
+
+ /**
+ * Load the screenshot area interface
+ */
+ createSelectionArea: function() {
+ var areaProtector = $('sc_drag_area_protector');
+ var zoom = page.getZoomLevel();
+ var bodyStyle = window.getComputedStyle(document.body, null);
+ if ('relative' == bodyStyle['position']) {
+ page.marginTop = page.matchMarginValue(bodyStyle['marginTop']);
+ page.marginLeft = page.matchMarginValue(bodyStyle['marginLeft']);
+ areaProtector.style.top = - parseInt(page.marginTop) + 'px';
+ areaProtector.style.left = - parseInt(page.marginLeft) + 'px';
+ }
+ areaProtector.style.width =
+ Math.round((document.body.clientWidth + parseInt(page.marginLeft)) / zoom) + 'px';
+ areaProtector.style.height =
+ Math.round((document.body.clientHeight + parseInt(page.marginTop)) / zoom) + 'px';
+ areaProtector.onclick = function() {
+ event.stopPropagation();
+ return false;
+ };
+
+ // Create elements for area capture.
+ page.createDiv(areaProtector, 'sc_drag_shadow_top');
+ page.createDiv(areaProtector, 'sc_drag_shadow_bottom');
+ page.createDiv(areaProtector, 'sc_drag_shadow_left');
+ page.createDiv(areaProtector, 'sc_drag_shadow_right');
+
+ var areaElement = page.createDiv(areaProtector, 'sc_drag_area');
+ page.createDiv(areaElement, 'sc_drag_container');
+ page.createDiv(areaElement, 'sc_drag_size');
+
+ // Add event listener for 'cancel' and 'capture' button.
+ var cancel = page.createDiv(areaElement, 'sc_drag_cancel');
+ cancel.addEventListener('mousedown', function () {
+ // Remove area capture containers and event listeners.
+ page.removeSelectionArea();
+ }, true);
+ cancel.innerHTML = chrome.i18n.getMessage("cancel");
+
+ var crop = page.createDiv(areaElement, 'sc_drag_crop');
+ crop.addEventListener('mousedown', function() {
+ page.removeSelectionArea();
+ page.sendMessage({msg: 'capture_selected'});
+ }, false);
+ crop.innerHTML = chrome.i18n.getMessage('ok');
+
+ page.createDiv(areaElement, 'sc_drag_north_west');
+ page.createDiv(areaElement, 'sc_drag_north_east');
+ page.createDiv(areaElement, 'sc_drag_south_east');
+ page.createDiv(areaElement, 'sc_drag_south_west');
+
+ areaProtector.addEventListener('mousedown', page.onMouseDown, false);
+ document.addEventListener('mousemove', page.onMouseMove, false);
+ document.addEventListener('mouseup', page.onMouseUp, false);
+ $('sc_drag_container').addEventListener('dblclick', function() {
+ page.removeSelectionArea();
+ page.sendMessage({msg: 'capture_selected'});
+ }, false);
+
+ page.pageHeight = $('sc_drag_area_protector').clientHeight;
+ page.pageWidth = $('sc_drag_area_protector').clientWidth;
+
+ var areaElement = $('sc_drag_area');
+ areaElement.style.left = page.getElementLeft(areaElement) + 'px';
+ areaElement.style.top = page.getElementTop(areaElement) + 'px';
+
+ page.startX = page.getElementLeft(areaElement);
+ page.startY = page.getElementTop(areaElement);
+ page.endX = page.getElementLeft(areaElement) + 250;
+ page.endY = page.getElementTop(areaElement) + 150;
+
+ areaElement.style.width = '250px';
+ areaElement.style.height = '150px';
+ page.isSelectionAreaTurnOn = true;
+ page.updateShadow(areaElement);
+ page.updateSize();
+ },
+
+ getElementLeft: function(obj) {
+ return (document.body.scrollLeft +
+ (document.documentElement.clientWidth -
+ obj.offsetWidth) / 2);
+ },
+
+ getElementTop: function(obj) {
+ return (document.body.scrollTop +
+ (document.documentElement.clientHeight - 200 -
+ obj.offsetHeight) / 2);
+ },
+
+ /**
+ * Init selection area due to the position of the mouse when mouse down
+ */
+ onMouseDown: function() {
+ if (event.button != 2) {
+ var element = event.target;
+
+ if (element) {
+ var elementName = element.tagName;
+ if (elementName && document) {
+ page.isMouseDown = true;
+
+ var areaElement = $('sc_drag_area');
+ var xPosition = event.pageX;
+ var yPosition = event.pageY;
+
+ if (areaElement) {
+ if (element == $('sc_drag_container')) {
+ page.moving = true;
+ page.moveX = xPosition - areaElement.offsetLeft;
+ page.moveY = yPosition - areaElement.offsetTop;
+ } else if (element == $('sc_drag_north_east')) {
+ page.resizing = true;
+ page.startX = areaElement.offsetLeft;
+ page.startY = areaElement.offsetTop + areaElement.clientHeight;
+ } else if (element == $('sc_drag_north_west')) {
+ page.resizing = true;
+ page.startX = areaElement.offsetLeft + areaElement.clientWidth;
+ page.startY = areaElement.offsetTop + areaElement.clientHeight;
+ } else if (element == $('sc_drag_south_east')) {
+ page.resizing = true;
+ page.startX = areaElement.offsetLeft;
+ page.startY = areaElement.offsetTop;
+ } else if (element == $('sc_drag_south_west')) {
+ page.resizing = true;
+ page.startX = areaElement.offsetLeft + areaElement.clientWidth;
+ page.startY = areaElement.offsetTop;
+ } else {
+ page.dragging = true;
+ page.endX = 0;
+ page.endY = 0;
+ page.endX = page.startX = xPosition;
+ page.endY = page.startY = yPosition;
+ }
+ }
+ event.preventDefault();
+ }
+ }
+ }
+ },
+
+ /**
+ * Change selection area position when mouse moved
+ */
+ onMouseMove: function() {
+ var element = event.target;
+ if (element && page.isMouseDown) {
+ var areaElement = $('sc_drag_area');
+ if (areaElement) {
+ var xPosition = event.pageX;
+ var yPosition = event.pageY;
+ if (page.dragging || page.resizing) {
+ var width = 0;
+ var height = 0;
+ var zoom = page.getZoomLevel();
+ var viewWidth = Math.round(document.body.clientWidth / zoom);
+ var viewHeight = Math.round(document.body.clientHeight / zoom);
+ if (xPosition > viewWidth) {
+ xPosition = viewWidth;
+ } else if (xPosition < 0) {
+ xPosition = 0;
+ }
+ if (yPosition > viewHeight) {
+ yPosition = viewHeight;
+ } else if (yPosition < 0) {
+ yPosition = 0;
+ }
+ page.endX = xPosition;
+ page.endY = yPosition;
+ if (page.startX > page.endX) {
+ width = page.startX - page.endX;
+ areaElement.style.left = xPosition + 'px';
+ } else {
+ width = page.endX - page.startX;
+ areaElement.style.left = page.startX + 'px';
+ }
+ if (page.startY > page.endY) {
+ height = page.startY - page.endY;
+ areaElement.style.top = page.endY + 'px';
+ } else {
+ height = page.endY - page.startY;
+ areaElement.style.top = page.startY + 'px';
+ }
+ areaElement.style.height = height + 'px';
+ areaElement.style.width = width + 'px';
+ if (window.innerWidth < xPosition) {
+ document.body.scrollLeft = xPosition - window.innerWidth;
+ }
+ if (document.body.scrollTop + window.innerHeight < yPosition + 25) {
+ document.body.scrollTop = yPosition - window.innerHeight + 25;
+ }
+ if (yPosition < document.body.scrollTop) {
+ document.body.scrollTop -= 25;
+ }
+ } else if (page.moving) {
+ var newXPosition = xPosition - page.moveX;
+ var newYPosition = yPosition - page.moveY;
+ if (newXPosition < 0) {
+ newXPosition = 0;
+ } else if (newXPosition + areaElement.clientWidth > page.pageWidth) {
+ newXPosition = page.pageWidth - areaElement.clientWidth;
+ }
+ if (newYPosition < 0) {
+ newYPosition = 0;
+ } else if (newYPosition + areaElement.clientHeight >
+ page.pageHeight) {
+ newYPosition = page.pageHeight - areaElement.clientHeight;
+ }
+
+ areaElement.style.left = newXPosition + 'px';
+ areaElement.style.top = newYPosition + 'px';
+ page.endX = newXPosition + areaElement.clientWidth;
+ page.startX = newXPosition;
+ page.endY = newYPosition + areaElement.clientHeight;
+ page.startY = newYPosition;
+
+ }
+ var crop = document.getElementById('sc_drag_crop');
+ var cancel = document.getElementById('sc_drag_cancel');
+ if (event.pageY + 25 > document.body.clientHeight) {
+ crop.style.bottom = 0;
+ cancel.style.bottom = 0
+ } else {
+ crop.style.bottom = '-25px';
+ cancel.style.bottom = '-25px';
+ }
+
+ var dragSizeContainer = document.getElementById('sc_drag_size');
+ if (event.pageY < 18) {
+ dragSizeContainer.style.top = 0;
+ } else {
+ dragSizeContainer.style.top = '-18px';
+ }
+ page.updateShadow(areaElement);
+ page.updateSize();
+
+ }
+ }
+ },
+
+ /**
+ * Fix the selection area position when mouse up
+ */
+ onMouseUp: function() {
+ page.isMouseDown = false;
+ if (event.button != 2) {
+ page.resizing = false;
+ page.dragging = false;
+ page.moving = false;
+ page.moveX = 0;
+ page.moveY = 0;
+ var temp;
+ if (page.endX < page.startX) {
+ temp = page.endX;
+ page.endX = page.startX;
+ page.startX = temp;
+ }
+ if (page.endY < page.startY) {
+ temp = page.endY;
+ page.endY = page.startY;
+ page.startY = temp;
+ }
+ }
+ },
+
+ /**
+ * Update the location of the shadow layer
+ */
+ updateShadow: function(areaElement) {
+ $('sc_drag_shadow_top').style.height =
+ parseInt(areaElement.style.top) + 'px';
+ $('sc_drag_shadow_top').style.width = (parseInt(areaElement.style.left) +
+ parseInt(areaElement.style.width) + 1) + 'px';
+ $('sc_drag_shadow_left').style.height =
+ (page.pageHeight - parseInt(areaElement.style.top)) + 'px';
+ $('sc_drag_shadow_left').style.width =
+ parseInt(areaElement.style.left) + 'px';
+
+ var height = (parseInt(areaElement.style.top) +
+ parseInt(areaElement.style.height) + 1);
+ height = (height < 0) ? 0 : height;
+ var width = (page.pageWidth) - 1 - (parseInt(areaElement.style.left) +
+ parseInt(areaElement.style.width));
+ width = (width < 0) ? 0 : width;
+ $('sc_drag_shadow_right').style.height = height + 'px';
+ $('sc_drag_shadow_right').style.width = width + 'px';
+
+ height = (page.pageHeight - 1 - (parseInt(areaElement.style.top) +
+ parseInt(areaElement.style.height)));
+ height = (height < 0) ? 0 : height;
+ width = (page.pageWidth) - parseInt(areaElement.style.left);
+ width = (width < 0) ? 0 : width;
+ $('sc_drag_shadow_bottom').style.height = height + 'px';
+ $('sc_drag_shadow_bottom').style.width = width + 'px';
+ },
+
+ /**
+ * Remove selection area
+ */
+ removeSelectionArea: function() {
+ document.removeEventListener('mousedown', page.onMouseDown, false);
+ document.removeEventListener('mousemove', page.onMouseMove, false);
+ document.removeEventListener('mouseup', page.onMouseUp, false);
+ $('sc_drag_container').removeEventListener('dblclick',function() {
+ page.removeSelectionArea();
+ page.sendMessage({msg: 'capture_selected'});}, false);
+ page.removeElement('sc_drag_area_protector');
+ page.removeElement('sc_drag_area');
+ page.isSelectionAreaTurnOn = false;
+ },
+
+ /**
+ * Refresh the size info
+ */
+ updateSize: function() {
+ var width = Math.abs(page.endX - page.startX);
+ var height = Math.abs(page.endY - page.startY);
+ $('sc_drag_size').innerText = page.calculateSizeAfterZooming(width) +
+ ' x ' + page.calculateSizeAfterZooming(height);
+ },
+
+ /**
+ * create div
+ */
+ createDiv: function(parent, id) {
+ var divElement = document.createElement('div');
+ divElement.id = id;
+ parent.appendChild(divElement);
+ return divElement;
+ },
+
+ /**
+ * Remove an element
+ */
+ removeElement: function(id) {
+ if($(id)) {
+ $(id).parentNode.removeChild($(id));
+ }
+ },
+
+ injectCssResource: function(cssResource) {
+ var css = document.createElement('LINK');
+ css.type = 'text/css';
+ css.rel = 'stylesheet';
+ css.href = chrome.extension.getURL(cssResource);
+ (document.head || document.body || document.documentElement).
+ appendChild(css);
+ },
+
+ injectJavaScriptResource: function(scriptResource) {
+ var script = document.createElement("script");
+ script.type = "text/javascript";
+ script.charset = "utf-8";
+ script.src = chrome.extension.getURL(scriptResource);
+ (document.head || document.body || document.documentElement).
+ appendChild(script);
+ },
+
+ /**
+ * Remove an element
+ */
+ init: function() {
+ if (document.body.hasAttribute('screen_capture_injected')) {
+ return;
+ }
+ if (isPageCapturable()) {
+ chrome.extension.sendMessage({msg: 'page_capturable'});
+ } else {
+ chrome.extension.sendMessage({msg: 'page_uncapturable'});
+ }
+ this.injectCssResource('style.css');
+ this.addMessageListener();
+ this.injectJavaScriptResource("js/page_context.js");
+
+ // Retrieve original width of view port and cache.
+ page.getOriginalViewPortWidth();
+ }
+};
+
+/**
+ * Indicate if the current page can be captured.
+ */
+var isPageCapturable = function() {
+ return !page.checkPageIsOnlyEmbedElement();
+};
+
+function $(id) {
+ return document.getElementById(id);
+}
+
+page.init();
+
+window.addEventListener('resize', function() {
+ if (page.isSelectionAreaTurnOn) {
+ page.removeSelectionArea();
+ page.showSelectionArea();
+ }
+
+ // Reget original width of view port if browser window resized or page zoomed.
+ page.getOriginalViewPortWidth();
+}, false);
+
+// Send page url for retriving and parsing access token for facebook and picasa.
+var message = {
+ msg: 'url_for_access_token',
+ url: window.location.href
+}
+if (window.location.href.indexOf('https://api.weibo.com/oauth2/default.html') == 0) {
+ message.siteId = 'sina'
+}
+page.sendMessage(message);
diff --git a/src/js/page_context.js b/src/js/page_context.js
new file mode 100644
index 0000000..ef35563
--- /dev/null
+++ b/src/js/page_context.js
@@ -0,0 +1,300 @@
+var __screenCapturePageContext__ = {
+ clone: function(object) {
+ function StubObj() { }
+ StubObj.prototype = object;
+ var newObj = new StubObj();
+ newObj.getInternalObject = function() {
+ return this.__proto__;
+ }
+ newObj.toString = function() {
+ try {
+ return this.__proto__.toString();
+ } catch (e) {
+ return 'object Object';
+ }
+ }
+ return newObj;
+ },
+
+ bind: function(newThis, func) {
+ var args = [];
+ for(var i = 2;i < arguments.length; i++) {
+ args.push(arguments[i]);
+ }
+ return function() {
+ return func.apply(newThis, args);
+ }
+ },
+
+ bodyWrapperDelegate_: null,
+ currentHookStatus_: false,
+
+ scrollValueHooker: function(oldValue, newValue, reason) {
+ // When we hook the value of scrollLeft/Top of body, it always returns 0.
+ return 0;
+ },
+
+ toggleBodyScrollValueHookStatus: function() {
+ this.currentHookStatus_ = !this.currentHookStatus_;
+ if (this.currentHookStatus_) {
+ var This = this;
+ try {
+ document.__defineGetter__('body', function() {
+ return This.bodyWrapperDelegate_.getWrapper();
+ });
+ } catch (e) {
+ window.console.log('error' + e);
+ }
+ this.bodyWrapperDelegate_.watch('scrollLeft', this.scrollValueHooker);
+ this.bodyWrapperDelegate_.watch('scrollTop', this.scrollValueHooker);
+ } else {
+ this.bodyWrapperDelegate_.unwatch('scrollLeft', this.scrollValueHooker);
+ this.bodyWrapperDelegate_.unwatch('scrollTop', this.scrollValueHooker);
+ var This = this;
+ try {
+ document.__defineGetter__('body', function() {
+ return This.bodyWrapperDelegate_.getWrapper().getInternalObject();
+ });
+ } catch (e) {
+ window.console.log('error' + e);
+ }
+ }
+ },
+
+ checkHookStatus: function() {
+ var needHookScrollValue = document.documentElement.getAttributeNode(
+ '__screen_capture_need_hook_scroll_value__');
+ needHookScrollValue =
+ !!(needHookScrollValue && needHookScrollValue.nodeValue == 'true');
+ if (this.currentHookStatus_ != needHookScrollValue)
+ this.toggleBodyScrollValueHookStatus();
+ },
+
+ init: function() {
+ if (!this.bodyWrapperDelegate_) {
+ this.bodyWrapperDelegate_ =
+ new __screenCapturePageContext__.ObjectWrapDelegate(
+ document.body, '^(DOCUMENT_[A-Z_]+|[A-Z_]+_NODE)$');
+ document.documentElement.addEventListener(
+ '__screen_capture_check_hook_status_event__',
+ __screenCapturePageContext__.bind(this, this.checkHookStatus));
+ }
+ }
+};
+
+// ObjectWrapDelegate class will create a empty object(wrapper), map its
+// prototype to the 'originalObject', then search all non-function properties
+// (except those properties which match the propertyNameFilter) of the
+// 'orginalObject' and set corresponding getter/setter to the wrapper.
+// Then you can manipulate the wrapper as 'originalObject' because the wrapper
+// use the corresponding getter/setter to access the corresponding properties in
+// 'originalObject' and the all function calls can be call through the prototype
+// inherit.
+// After createing the wrapper object, you can use watch method to monitor any
+// property which you want to know when it has been read(get) or change(set).
+// Please see the detail comment on method watch.
+// Remember the ObjectWrapDelegate returns the wrapDelegateObject instead of
+// really wrapper object. You have to use ObjectWrapDelegate.getWrapper to get
+// real wrapper object.
+// parameter @originalObject, object which you want to wrap
+// parameter @propertyNameFilter, string, regular expression pattern string for
+// those properties you don't put in the wrap object.
+__screenCapturePageContext__.ObjectWrapDelegate = function(
+ originalObject, propertyNameFilter) {
+ this.window_ = window;
+
+ // The wrapper is the object we use to wrap the 'originalObject'.
+ this.wrapper_ = __screenCapturePageContext__.clone(originalObject);
+
+ // This array saves all properties we set our getter/setter for them.
+ this.properties_ = [];
+
+ // This object to save all watch handlers. Each watch handler is bind to one
+ // certain property which is in properties_.
+ this.watcherTable_ = {};
+
+ // Check the propertyNameFilter parameter.
+ if (typeof propertyNameFilter == 'undefined') {
+ propertyNameFilter = '';
+ } else if (typeof propertyNameFilter != 'string') {
+ try {
+ propertyNameFilter = propertyNameFilter.toString();
+ } catch (e) {
+ propertyNameFilter = '';
+ }
+ }
+ if (propertyNameFilter.length) {
+ this.propertyNameFilter_ = new RegExp('');
+ this.propertyNameFilter_.compile(propertyNameFilter);
+ } else {
+ this.propertyNameFilter_ = null;
+ }
+
+ // For closure to access the private data of class.
+ var This = this;
+
+ // Set the getter object.
+ function setGetterAndSetter(wrapper, propertyName) {
+ wrapper.__defineGetter__(propertyName, function() {
+ var internalObj = this.getInternalObject();
+ var originalReturnValue = internalObj[propertyName];
+ var returnValue = originalReturnValue;
+
+ // See whether this property has been watched.
+ var watchers = This.watcherTable_[propertyName];
+ if (watchers) {
+ // copy the watcher to a cache in case someone call unwatch inside the
+ // watchHandler.
+ var watchersCache = watchers.concat();
+ for (var i = 0, l = watchersCache.length; i < l; ++i) {
+ var watcher = watchersCache[i];
+ if (!watcher) {
+ window.console.log('wrapper\'s watch for ' + propertyName +
+ ' is unavailable!');
+ continue; // should never happend
+ }
+ originalReturnValue = returnValue;
+ try {
+ returnValue = watcher(returnValue, returnValue, 'get');
+ } catch (e) {
+ returnValue = originalReturnValue;
+ }
+ }
+ }
+ return returnValue;
+ });
+
+ // Set the setter object.
+ wrapper.__defineSetter__(propertyName, function(value) {
+ var internalObj = this.getInternalObject();
+ var originalValue = value;
+ var userValue = originalValue;
+ var oldValue;
+ try {
+ oldValue = internalObj[propertyName];
+ } catch (e) {
+ oldValue = null;
+ }
+
+ // See whether this property has been watched.
+ var watchers = This.watcherTable_[propertyName];
+ if (watchers) {
+ // copy the watcher to a cache in case someone call unwatch inside the
+ // watchHandler.
+ var watchersCache = watchers.concat();
+ for (var i = 0, l = watchersCache.length; i < l; ++i) {
+ var watcher = watchersCache[i];
+ if (!watcher) {
+ window.console.log('wrapper\'s watch for ' + propertyName +
+ ' is unavailable!');
+ continue; // should never happend
+ }
+ originalValue = userValue;
+ try {
+ userValue = watcher(oldValue, userValue, 'set');
+ } catch (e) {
+ userValue = originalValue;
+ }
+ }
+ }
+ internalObj[propertyName] = userValue;
+ });
+ };
+
+ this.cleanUp_ = function() {
+ This.window_.removeEventListener('unload', This.cleanUp_, false);
+
+ // Delete all properties
+ for (var i = 0, l = This.properties_.length; i < l; ++i) {
+ delete This.wrapper_[This.properties_[i]];
+ }
+ This.window_ = null;
+ This.wrapper_ = null;
+ This.properties_ = null;
+ This.watcherTable_ = null;
+ This.propertyNameFilter_ = null;
+ This = null;
+ }
+
+ // We only bridge the non-function properties.
+ for (var prop in originalObject) {
+ if (this.propertyNameFilter_ && this.propertyNameFilter_.test(prop)) {
+ this.propertyNameFilter_.test('');
+ continue;
+ }
+ if (typeof originalObject[prop] != 'function') {
+ this.properties_.push(prop);
+ setGetterAndSetter(this.wrapper_, prop);
+ }
+ }
+
+ // Listen the unload event.
+ this.window_.addEventListener('unload', this.cleanUp_, false);
+};
+
+__screenCapturePageContext__.ObjectWrapDelegate.prototype.getWrapper =
+ function() {
+ return this.wrapper_;
+}
+
+// Check whether a property is in the wrapper or not. If yes, return true.
+// Otherwise return false.
+__screenCapturePageContext__.ObjectWrapDelegate.prototype.hasProperty =
+ function(propertyName) {
+ for (var i = 0, l = this.properties_.length; i < l; ++i) {
+ if (propertyName == this.properties_[i])
+ return true;
+ }
+ return false;
+}
+
+// Watches for a property to be accessed or be assigned a value and runs a
+// function when that occurs.
+// Watches for accessing a property or assignment to a property named prop in
+// this object, calling handler(oldval, newval, reason) whenever prop is
+// get/set and storing the return value in that property.
+// A watchpoint can filter (or nullify) the value assignment, by returning a
+// modified newval (or by returning oldval).
+// When watchpoint is trigering by get opeartor, the oldval is equal with
+// newval. The reason will be 'get'.
+// When watchpoint is trigering by set opeartor, The reason will be 'set'.
+// If you delete a property for which a watchpoint has been set,
+// that watchpoint does not disappear. If you later recreate the property,
+// the watchpoint is still in effect.
+// To remove a watchpoint, use the unwatch method.
+// If register the watchpoint successfully, return true. Otherwise return false.
+__screenCapturePageContext__.ObjectWrapDelegate.prototype.watch = function(
+ propertyName, watchHandler) {
+ if (!this.hasProperty(propertyName))
+ return false;
+ var watchers = this.watcherTable_[propertyName];
+ if (watchers) {
+ for (var i = 0, l = watchers.length; i < l; ++i) {
+ if (watchHandler == watchers[i])
+ return true;
+ }
+ } else {
+ watchers = new Array();
+ this.watcherTable_[propertyName] = watchers;
+ }
+ watchers.push(watchHandler);
+ return true;
+}
+
+// Removes a watchpoint set with the watch method.
+__screenCapturePageContext__.ObjectWrapDelegate.prototype.unwatch = function(
+ propertyName, watchHandler) {
+ if (!this.hasProperty(propertyName))
+ return false;
+ var watchers = this.watcherTable_[propertyName];
+ if (watchers) {
+ for (var i = 0, l = watchers.length; i < l; ++i) {
+ if (watchHandler == watchers[i]) {
+ watchers.splice(i, 1);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+__screenCapturePageContext__.init();
diff --git a/src/js/picasa.js b/src/js/picasa.js
new file mode 100644
index 0000000..e6343a8
--- /dev/null
+++ b/src/js/picasa.js
@@ -0,0 +1,237 @@
+(function() {
+ const ALBUM_NAME = 'Screen Capture';
+ const CLIENT_ID = '368358534491.apps.googleusercontent.com';
+ const AUTH_URL = 'https://accounts.google.com/o/oauth2/auth';
+ const REDIRECT_URI = 'https://picasaweb.google.com';
+ const SCOPE = 'https://picasaweb.google.com/data/';
+ const RESPONSE_TYPE = 'token';
+ const ALBUM_URL = 'https://picasaweb.google.com/data/feed/api/user/' +
+ 'default';
+ const CREATE_ALBUM_URL = 'https://picasaweb.google.com/data/feed/api/user/' +
+ 'default';
+ const UPLOAD_BASE_URL = 'https://picasaweb.google.com/data/feed/api/user/' +
+ 'default/albumid/';
+ const LOGOUT_URL = 'https://picasaweb.google.com/bye?continue=' +
+ 'https://www.google.com/accounts/Logout?continue=' +
+ 'https://picasaweb.google.com';
+
+ // var picasa = window.Picasa = new Site('picasa');
+ var picasa = window.Picasa = {
+ siteId: 'picasa',
+ currentUserId: null,
+ redirectUrl: REDIRECT_URI,
+ accessTokenCallback: null,
+
+ getAccessToken: function(callback) {
+ picasa.accessTokenCallback = callback;
+ var url = AUTH_URL + '?client_id=' + CLIENT_ID + '&redirect_uri=' +
+ REDIRECT_URI + '&scope=' + SCOPE + '&response_type=' + RESPONSE_TYPE;
+ chrome.tabs.create({ url: url});
+ },
+
+ parseRedirectUrl: function(url) {
+ var result = false;
+ if (url.indexOf(REDIRECT_URI) == 0) {
+ var hash = url.split('#')[1];
+ if (hash) {
+ var params = hash.split('&');
+ var paramMap = {};
+ params.forEach(function(param) {
+ paramMap[param.split('=')[0]] = param.split('=')[1];
+ });
+
+ var accessToken = paramMap['access_token'];
+ var expires = paramMap['expires_in'];
+ if (accessToken && expires) {
+ result = {
+ accessToken: accessToken,
+ expires: expires
+ };
+ } else {
+ result = 'bad_redirect_url'; // Should never happened.
+ }
+ } else {
+ var search = url.split('?')[1];
+ if (search == 'error=access_denied')
+ result = 'access_denied';
+ }
+ }
+ return result;
+ },
+
+ isRedirectUrl: function(url) {
+ return picasa.parseRedirectUrl(url) != false;
+ },
+
+ parseAccessToken: function(redirectUrl) {
+ var parsedResult = picasa.parseRedirectUrl(redirectUrl);
+ if (parsedResult && typeof parsedResult == 'object') {
+ var user = new User({
+ accessToken: parsedResult.accessToken,
+ expires: new Date().getTime() + parsedResult.expires * 1000
+ });
+ picasa.accessTokenCallback('success', user);
+ } else {
+ picasa.accessTokenCallback('failure', 'user_denied');
+ }
+ picasa.accessTokenCallback = null;
+ },
+
+ getUserInfo: function(user, callback) {
+ ajax({
+ url: ALBUM_URL,
+ parameters: {
+ fields: 'title,gphoto:nickname,entry/title,entry/gphoto:id',
+ alt: 'json',
+ access_token: user.accessToken
+ },
+ success: function(res) {
+ var userId = res.feed.title.$t;
+ var userName = res.feed.gphoto$nickname.$t;
+ user.id = userId;
+ user.name = userName;
+
+ var albums = res.feed.entry;
+ if (albums) {
+ var length = albums.length;
+
+ // Check if user has created album "Screen Capture".
+ for (var i = 0; i < length; i++) {
+ var albumName = albums[i].title.$t;
+ if (albumName == ALBUM_NAME) {
+ user.albumId = albums[i].gphoto$id.$t;
+ break;
+ }
+ }
+ }
+
+ // Create album "Screen Capture" and retrieve album id.
+ if (!user.albumId) {
+ picasa.createAlbum(user.accessToken, function(result,
+ albumIdOrMessage) {
+ if (result == 'success') {
+ user.albumId = albumIdOrMessage;
+ callback('success', user);
+ } else {
+ callback('failure', albumIdOrMessage);
+ }
+ });
+ } else {
+ callback('success', user);
+ }
+ },
+ status: {
+ 404: function() {
+ callback('failure', 'failed_to_get_user_info');
+ }
+ }
+ });
+ },
+
+ createAlbum: function(accessToken, callback) {
+ var data = '<entry xmlns="http://www.w3.org/2005/Atom" ' +
+ 'xmlns:media="http://search.yahoo.com/mrss/" ' +
+ 'xmlns:gphoto="http://schemas.google.com/photos/2007">' +
+ '<title type="text">' + ALBUM_NAME +
+ '</title><category scheme="http://schemas.google.com/g/2005#kind" ' +
+ 'term="http://schemas.google.com/photos/2007#album"></category>' +
+ '</entry>';
+
+ ajax({
+ method: 'POST',
+ url: CREATE_ALBUM_URL,
+ parameters: {
+ alt: 'json'
+ },
+ data: data,
+ headers: {
+ 'GData-Version': 2,
+ 'Content-Type': 'application/atom+xml',
+ 'Authorization': 'OAuth ' + accessToken
+ },
+ complete: function(statusCode, album) {
+ if (statusCode == 201) {
+ var albumId = album.entry.gphoto$id.$t;
+ callback('success', albumId);
+ } else {
+ callback('failure', 'failure_to_create_album')
+ }
+ }
+ });
+ },
+
+ upload: function(user, caption, imageData, callback) {
+ caption = ajax.convertEntityString(caption);
+ caption = ajax.encodeForBinary(caption);
+
+ var imageFile = new Date().getTime() + '.png';
+ var headers = {
+ 'GData-Version': 2,
+ 'Content-Type': 'multipart/related; boundary=' +
+ MULTIPART_FORMDATA_BOUNDARY,
+ 'Authorization': 'OAuth ' + user.accessToken
+ };
+
+ var captionData = '<entry xmlns="http://www.w3.org/2005/Atom">' +
+ '<title>' + imageFile + '</title>' +
+ '<summary>' + caption + '</summary>' +
+ '<category scheme="http://schemas.google.com/g/2005#kind" ' +
+ 'term="http://schemas.google.com/photos/2007#photo"/></entry>';
+
+ var dataPart1 = {
+ contentType: 'application/atom+xml',
+ data: captionData
+ };
+ var dataPart2 = {
+ contentType: 'image/png',
+ data: imageData
+ };
+ var multipartData = {
+ boundary: MULTIPART_FORMDATA_BOUNDARY,
+ dataList: [dataPart1, dataPart2]
+ };
+
+ ajax({
+ url: UPLOAD_BASE_URL + user.albumId + '?alt=json',
+ headers: headers,
+ multipartData: multipartData,
+ complete: function(statusCode, res) {
+ if (statusCode == 201) {
+ var link = res.entry.link;
+ callback('success', link);
+ } else {
+ var message = 'failed_to_upload_image';
+ if (statusCode == 403) {
+ // bad access token
+ message = 'bad_access_token';
+ } else if (statusCode == 404 && res == 'No album found.') {
+ // Invalid album id.
+ message = 'invalid_album_id'
+ }
+ callback('failure', message);
+ }
+ }
+ });
+ },
+
+ getPhotoLink: function(user, photolinks, callback) {
+ for (var i = 0; i < photolinks.length; i++) {
+ var link = photolinks[i];
+ if (link.type == 'text/html' &&
+ link.rel == 'http://schemas.google.com/photos/2007#canonical') {
+ callback('success', link.href);
+ break;
+ }
+ }
+ },
+
+ logout: function(callback) {
+ ajax({
+ url: LOGOUT_URL,
+ success: function() {
+ callback();
+ }
+ });
+ }
+ };
+})();
\ No newline at end of file
diff --git a/src/js/popup.js b/src/js/popup.js
new file mode 100644
index 0000000..3f5690f
--- /dev/null
+++ b/src/js/popup.js
@@ -0,0 +1,173 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+function $(id) {
+ return document.getElementById(id);
+}
+
+chrome.extension.onMessage.addListener(function(request, sender, response) {
+ if (request.msg == 'page_capturable') {
+ $('tip').style.display = 'none';
+ $('captureSpecialPageItem').style.display = 'none';
+ $('captureWindowItem').style.display = 'block';
+ $('captureAreaItem').style.display = 'block';
+ $('captureWebpageItem').style.display = 'block';
+ } else if (request.msg == 'page_uncapturable') {
+ i18nReplace('tip', 'special');
+ $('tip').style.display = 'block';
+ $('captureSpecialPageItem').style.display = 'none';
+ $('captureWindowItem').style.display = 'none';
+ $('captureAreaItem').style.display = 'none';
+ $('captureWebpageItem').style.display = 'none';
+ }
+});
+
+function toDo(what) {
+ var bg = chrome.extension.getBackgroundPage();
+ switch (what) {
+ case 'capture_window':
+ bg.screenshot.captureWindow();
+ window.close();
+ break;
+ case 'capture_area':
+ bg.screenshot.showSelectionArea();
+ window.close();
+ break;
+ case 'capture_webpage':
+ bg.screenshot.captureWebpage();
+ $('loadDiv').style.display = 'block';
+ $('item').style.display = 'none';
+ break;
+ case 'capture_special_page':
+ bg.screenshot.captureSpecialPage();
+ window.close();
+ break;
+ }
+}
+
+function i18nReplace(id, name) {
+ return $(id).innerHTML = chrome.i18n.getMessage(name);
+}
+
+function resizeDivWidth(id, width) {
+ $(id).style.width = width + "px";
+}
+
+function init() {
+ i18nReplace('captureSpecialPageText', 'capture_window');
+ i18nReplace('capturing', 'capturing');
+ i18nReplace('captureWindowText', 'capture_window');
+ i18nReplace('captureAreaText', 'capture_area');
+ i18nReplace('captureWebpageText', 'capture_webpage');
+ i18nReplace('optionItem', 'option');
+
+ $('option').addEventListener('click', function () {
+ chrome.tabs.create({ url: 'options.html'});
+ }, false);
+ if (HotKey.isEnabled()) {
+ $('captureWindowShortcut').style.display = 'inline';
+ $('captureAreaShortcut').style.display = 'inline';
+ $('captureWebpageShortcut').style.display = 'inline';
+ document.body.style.minWidth = "190px"
+ } else {
+ $('captureWindowShortcut').style.display = 'none';
+ $('captureAreaShortcut').style.display = 'none';
+ $('captureWebpageShortcut').style.display = 'none';
+ document.body.style.minWidth = "140px";
+ }
+ var isScriptLoad = false;
+ chrome.tabs.getSelected(null, function(tab) {
+ if (tab.url.indexOf('chrome') == 0 || tab.url.indexOf('about') == 0) {
+ i18nReplace('tip', 'special');
+ return;
+ } else {
+ $('tip').style.display = 'none';
+ $('captureSpecialPageItem').style.display = 'block';
+ showOption();
+ }
+ chrome.tabs.sendMessage(tab.id, {msg: 'is_page_capturable'},
+ function(response) {
+ isScriptLoad = true;
+ if (response.msg == 'capturable') {
+ $('tip').style.display = 'none';
+ $('captureSpecialPageItem').style.display = 'none';
+ $('captureWindowItem').style.display = 'block';
+ $('captureAreaItem').style.display = 'block';
+ $('captureWebpageItem').style.display = 'block';
+ var textWidth = $('captureWindowText')['scrollWidth'];
+ resizeDivWidth('captureWindowText', textWidth);
+ resizeDivWidth('captureAreaText', textWidth);
+ resizeDivWidth('captureWebpageText', textWidth);
+ var bg = chrome.extension.getBackgroundPage();
+ if (bg.screenshot.isThisPlatform('mac')) {
+ $('captureAreaShortcut').innerText = '\u2325\u2318R';
+ $('captureWindowShortcut').innerText = '\u2325\u2318V';
+ $('captureWebpageShortcut').innerText = '\u2325\u2318H';
+ }
+ } else if (response.msg == 'uncapturable') {
+ i18nReplace('tip', 'special');
+ $('tip').style.display = 'block';
+ } else {
+ i18nReplace('tip', 'loading');
+ }
+ });
+ });
+ chrome.tabs.executeScript(null, {file: 'js/isLoad.js'});
+ var insertScript = function() {
+ if (isScriptLoad == false) {
+ chrome.tabs.getSelected(null, function(tab) {
+ if (tab.url.indexOf('chrome') == 0 ||
+ tab.url.indexOf('about') == 0) {
+ i18nReplace('tip', 'special');
+ } else {
+ $('tip').style.display = 'none';
+ $('captureSpecialPageItem').style.display = 'block';
+ showOption();
+ }
+ });
+ }
+ var captureItems = document.querySelectorAll('li.menuI');
+ var showSeparator = false;
+ for (var i = 0; i < captureItems.length; i++) {
+ if (window.getComputedStyle(captureItems[i]).display != 'none') {
+ showSeparator = true;
+ break;
+ }
+ }
+ $('separatorItem').style.display = showSeparator ? 'block' : 'none';
+ }
+ setTimeout(insertScript, 500);
+
+ // Update hot key.
+ if (HotKey.get('area') != '@')
+ $('captureAreaShortcut').innerText = 'Ctrl+Alt+' + HotKey.get('area');
+ if (HotKey.get('viewport') != '@') {
+ $('captureWindowShortcut').innerText = 'Ctrl+Alt+' +
+ HotKey.get('viewport');
+ }
+ if (HotKey.get('fullpage') != '@') {
+ $('captureWebpageShortcut').innerText ='Ctrl+Alt+' +
+ HotKey.get('fullpage');
+ }
+
+ $('captureSpecialPageItem').addEventListener('click', function(e) {
+ toDo('capture_special_page');
+ });
+ $('captureAreaItem').addEventListener('click', function(e) {
+ toDo('capture_area');
+ });
+ $('captureWindowItem').addEventListener('click', function(e) {
+ toDo('capture_window');
+ });
+ $('captureWebpageItem').addEventListener('click', function(e) {
+ toDo('capture_webpage');
+ });
+}
+
+function showOption() {
+ $('option').style.display = 'block';
+ $('separatorItem').style.display = 'block';
+}
+
+document.addEventListener('DOMContentLoaded', init);
diff --git a/src/js/sha1.js b/src/js/sha1.js
new file mode 100644
index 0000000..1b55982
--- /dev/null
+++ b/src/js/sha1.js
@@ -0,0 +1,202 @@
+/*
+ * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
+ * in FIPS PUB 180-1
+ * Version 2.1a Copyright Paul Johnston 2000 - 2002.
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for details.
+ */
+
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
+var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
+var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));}
+function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));}
+function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));}
+function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));}
+function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));}
+function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));}
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function sha1_vm_test()
+{
+ return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
+}
+
+/*
+ * Calculate the SHA-1 of an array of big-endian words, and a bit length
+ */
+function core_sha1(x, len)
+{
+ /* append padding */
+ x[len >> 5] |= 0x80 << (24 - len % 32);
+ x[((len + 64 >> 9) << 4) + 15] = len;
+
+ var w = Array(80);
+ var a = 1732584193;
+ var b = -271733879;
+ var c = -1732584194;
+ var d = 271733878;
+ var e = -1009589776;
+
+ for(var i = 0; i < x.length; i += 16)
+ {
+ var olda = a;
+ var oldb = b;
+ var oldc = c;
+ var oldd = d;
+ var olde = e;
+
+ for(var j = 0; j < 80; j++)
+ {
+ if(j < 16) w[j] = x[i + j];
+ else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
+ var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
+ safe_add(safe_add(e, w[j]), sha1_kt(j)));
+ e = d;
+ d = c;
+ c = rol(b, 30);
+ b = a;
+ a = t;
+ }
+
+ a = safe_add(a, olda);
+ b = safe_add(b, oldb);
+ c = safe_add(c, oldc);
+ d = safe_add(d, oldd);
+ e = safe_add(e, olde);
+ }
+ return Array(a, b, c, d, e);
+
+}
+
+/*
+ * Perform the appropriate triplet combination function for the current
+ * iteration
+ */
+function sha1_ft(t, b, c, d)
+{
+ if(t < 20) return (b & c) | ((~b) & d);
+ if(t < 40) return b ^ c ^ d;
+ if(t < 60) return (b & c) | (b & d) | (c & d);
+ return b ^ c ^ d;
+}
+
+/*
+ * Determine the appropriate additive constant for the current iteration
+ */
+function sha1_kt(t)
+{
+ return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
+ (t < 60) ? -1894007588 : -899497514;
+}
+
+/*
+ * Calculate the HMAC-SHA1 of a key and some data
+ */
+function core_hmac_sha1(key, data)
+{
+ var bkey = str2binb(key);
+ if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz);
+
+ var ipad = Array(16), opad = Array(16);
+ for(var i = 0; i < 16; i++)
+ {
+ ipad[i] = bkey[i] ^ 0x36363636;
+ opad[i] = bkey[i] ^ 0x5C5C5C5C;
+ }
+
+ var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
+ return core_sha1(opad.concat(hash), 512 + 160);
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+ var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+ var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+ return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function rol(num, cnt)
+{
+ return (num << cnt) | (num >>> (32 - cnt));
+}
+
+/*
+ * Convert an 8-bit or 16-bit string to an array of big-endian words
+ * In 8-bit function, characters >255 have their hi-byte silently ignored.
+ */
+function str2binb(str)
+{
+ var bin = Array();
+ var mask = (1 << chrsz) - 1;
+ for(var i = 0; i < str.length * chrsz; i += chrsz)
+ bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);
+ return bin;
+}
+
+/*
+ * Convert an array of big-endian words to a string
+ */
+function binb2str(bin)
+{
+ var str = "";
+ var mask = (1 << chrsz) - 1;
+ for(var i = 0; i < bin.length * 32; i += chrsz)
+ str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask);
+ return str;
+}
+
+/*
+ * Convert an array of big-endian words to a hex string.
+ */
+function binb2hex(binarray)
+{
+ var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+ var str = "";
+ for(var i = 0; i < binarray.length * 4; i++)
+ {
+ str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
+ hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
+ }
+ return str;
+}
+
+/*
+ * Convert an array of big-endian words to a base-64 string
+ */
+function binb2b64(binarray)
+{
+ var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ var str = "";
+ for(var i = 0; i < binarray.length * 4; i += 3)
+ {
+ var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16)
+ | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 )
+ | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF);
+ for(var j = 0; j < 4; j++)
+ {
+ if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
+ else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
+ }
+ }
+ return str;
+}
diff --git a/src/js/shortcut.js b/src/js/shortcut.js
new file mode 100644
index 0000000..7959178
--- /dev/null
+++ b/src/js/shortcut.js
@@ -0,0 +1,35 @@
+var shortcutKey = {
+
+ init: function() {
+ if (document.body.hasAttribute('screen_capture_injected')) {
+ return;
+ }
+ document.body.setAttribute('screen_capture_injected', true);
+ document.body.addEventListener('keydown', shortcutKey.handleShortcut,
+ false);
+ },
+
+ isThisPlatform: function(operationSystem) {
+ return navigator.userAgent.toLowerCase().indexOf(operationSystem) > -1;
+ },
+
+ handleShortcut: function (event) {
+ var isMac = shortcutKey.isThisPlatform('mac');
+ var keyCode = event.keyCode;
+ // Send compose key like Ctrl + Alt + alphabetical-key to background.
+ if ((event.ctrlKey && event.altKey && !isMac ||
+ event.metaKey && event.altKey && isMac) &&
+ keyCode > 64 && keyCode < 91) {
+ shortcutKey.sendMessage({
+ msg: 'capture_hot_key',
+ keyCode: keyCode
+ });
+ }
+ },
+
+ sendMessage: function(message) {
+ chrome.extension.sendMessage(message);
+ }
+};
+
+shortcutKey.init();
diff --git a/src/js/showimage.js b/src/js/showimage.js
new file mode 100644
index 0000000..294353d
--- /dev/null
+++ b/src/js/showimage.js
@@ -0,0 +1,824 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+function isHighVersion() {
+ var version = navigator.userAgent.match(/Chrome\/(\d+)/)[1];
+ return version > 9;
+}
+
+function $(id) {
+ return document.getElementById(id);
+}
+function i18nReplace(id, messageKey) {
+ return $(id).innerHTML = chrome.i18n.getMessage(messageKey);
+}
+UploadUI.init();
+
+var bg = chrome.extension.getBackgroundPage();
+var canvas = new Canvas();
+var photoshop = {
+ canvas: document.createElement("canvas"),
+ tabTitle: '',
+ startX: 0,
+ startY: 0,
+ endX: 0,
+ endY: 0,
+ dragFlag: false,
+ flag: 'rectangle',
+ layerId: 'layer0',
+ canvasId: '',
+ color: '#ff0000',
+ highlightColor: '',
+ lastValidAction: 0,
+ markedArea: [],
+ isDraw: true,
+ offsetX: 0,
+ offsetY: 36,
+ nowHeight: 0,
+ nowWidth: 0,
+ highlightType: 'border',
+ highlightMode: 'rectangle',
+ text: '',
+
+ i18nReplace: i18nReplace,
+
+ initCanvas: function() {
+ $('canvas').width = $('mask-canvas').width = $('photo').style.width =
+ photoshop.canvas.width = bg.screenshot.canvas.width;
+ $('canvas').height = $('mask-canvas').height = $('photo').style.height =
+ photoshop.canvas.height = bg.screenshot.canvas.height;
+ var context = photoshop.canvas.getContext('2d');
+ context.drawImage(bg.screenshot.canvas, 0, 0);
+ context = $('canvas').getContext('2d');
+ context.drawImage(photoshop.canvas, 0, 0);
+ $('canvas').style.display = 'block';
+ },
+
+ init: function() {
+ photoshop.initTools();
+ photoshop.initCanvas();
+ photoshop.tabTitle = bg.screenshot.tab.title;
+ var showBoxHeight = function() {
+ $('showBox').style.height = window.innerHeight - photoshop.offsetY - 1;
+ }
+ setTimeout(showBoxHeight, 50);
+ },
+
+ markCurrentElement: function(element) {
+ if (element && element.parentNode) {
+ var children = element.parentNode.children;
+ for (var i = 0; i < children.length; i++) {
+ var node = children[i];
+ if (node == element) {
+ element.className = 'mark';
+ } else {
+ node.className = '';
+ }
+ }
+ }
+ },
+
+ setHighLightMode: function() {
+ photoshop.highlightType = localStorage.highlightType || 'border';
+ photoshop.color = localStorage.highlightColor || '#FF0000';
+ $(photoshop.layerId).style.border = '2px solid ' + photoshop.color;
+ if (photoshop.highlightType == 'rect') {
+ $(photoshop.layerId).style.backgroundColor = photoshop.color;
+ $(photoshop.layerId).style.opacity = 0.5;
+ }
+ if (photoshop.flag == 'rectangle') {
+ $(photoshop.layerId).style.borderRadius = '0 0';
+ } else if (photoshop.flag == 'radiusRectangle') {
+ $(photoshop.layerId).style.borderRadius = '6px 6px';
+ } else if (photoshop.flag == 'ellipse') {
+ $(photoshop.layerId).style.border = '0';
+ $(photoshop.layerId).style.backgroundColor = '';
+ $(photoshop.layerId).style.opacity = 1;
+ }
+
+ },
+
+ setBlackoutMode: function() {
+ photoshop.color = '#000000';
+ $(photoshop.layerId).style.opacity = 1;
+ $(photoshop.layerId).style.backgroundColor = '#000000';
+ $(photoshop.layerId).style.border = '2px solid #000000';
+ },
+
+ setTextMode: function() {
+ localStorage.fontSize = localStorage.fontSize || '16';
+ photoshop.color = localStorage.fontColor =
+ localStorage.fontColor || '#FF0000';
+ $(photoshop.layerId).setAttribute('contentEditable', true);
+ $(photoshop.layerId).style.border = '1px dotted #333333';
+ $(photoshop.layerId).style.cursor = 'text';
+ $(photoshop.layerId).style.lineHeight = localStorage.fontSize + 'px';
+ $(photoshop.layerId).style.fontSize = localStorage.fontSize + 'px';
+ $(photoshop.layerId).style.color = photoshop.color;
+ $(photoshop.layerId).innerHTML = '<br/>';
+ var layer = $(photoshop.layerId);
+ var id = photoshop.layerId;
+ layer.addEventListener('blur', function() {
+ photoshop.setTextToArray(id);
+ }, true);
+ layer.addEventListener('click', function() {
+ this.style.border = '1px dotted #333333';
+ }, true);
+ layer.addEventListener('mouseout', function() {
+ if (!photoshop.dragFlag) {
+ this.style.borderWidth = 0;
+ }
+ }, false);
+ layer.addEventListener('mousemove', function() {
+ this.style.border = '1px dotted #333333';
+ }, false);
+ },
+
+ setTextToArray: function(id) {
+ var str = $(id).innerText.split("\n");
+ if (photoshop.markedArea.length > 0) {
+ for (var i = photoshop.markedArea.length - 1; i >= 0; i--) {
+ if (photoshop.markedArea[i].id == id) {
+ photoshop.markedArea[i].context = str;
+ break;
+ }
+ }
+ $(id).style.borderWidth = 0;
+ }
+ },
+
+ openOptionPage: function() {
+ chrome.tabs.create({url: chrome.extension.getURL("options.html")});
+ },
+
+ closeCurrentTab: function() {
+ chrome.tabs.getSelected(null, function(tab) {
+ chrome.tabs.remove(tab.id);
+ });
+ },
+
+ finish: function() {
+ var context = $('canvas').getContext('2d');
+ context.drawImage(photoshop.canvas, 0, 0);
+ },
+
+ colorRgba: function(color, opacity) {
+ var sColor = color.toLowerCase();
+ var sColorChange = [];
+ for (var i = 1; i < sColor.length; i += 2) {
+ sColorChange.push(parseInt("0x" + sColor.slice(i,i + 2)));
+ }
+ return "rgba(" + sColorChange.join(",") + "," + opacity + ")";
+ },
+
+ /**
+ * Undo the current operation
+ */
+ toDo: function(element, what) {
+ photoshop.flag = what;
+ photoshop.isDraw = true;
+ photoshop.markCurrentElement(element);
+ },
+
+ setDivStyle: function(x, y) {
+ $(photoshop.layerId).setAttribute("style", "");
+ $(photoshop.layerId).setAttribute("contentEditable", false);
+ switch(photoshop.flag) {
+ case 'rectangle':
+ case 'radiusRectangle':
+ case 'ellipse':
+ photoshop.setHighLightMode();
+ break;
+ case 'redact':
+ photoshop.setBlackoutMode();
+ break;
+ case 'text':
+ photoshop.setTextMode();
+ break;
+ case 'line':
+ case 'arrow':
+ photoshop.drawLineOnMaskCanvas(x, y, x, y, 'lineStart',
+ photoshop.layerId);
+ break;
+ case 'blur':
+ photoshop.createCanvas(photoshop.layerId);
+ break;
+ }
+ },
+
+ /**
+ * Create a layer and set style
+ */
+ createDiv: function() {
+ photoshop.lastValidAction++;
+ photoshop.layerId = 'layer' + photoshop.lastValidAction;
+ if ($(photoshop.layerId)) {
+ photoshop.removeElement(photoshop.layerId);
+ }
+ var divElement = document.createElement('div');
+ divElement.id = photoshop.layerId;
+ divElement.className = 'layer';
+ $('photo').appendChild(divElement);
+ if (photoshop.flag == 'blur') {
+ photoshop.createCanvas(photoshop.layerId);
+ }
+ return divElement;
+ },
+
+ createCanvas: function(parentId) {
+ photoshop.canvasId = 'cav-' + parentId;
+ if (!$(photoshop.canvasId)) {
+ var cav = document.createElement('canvas');
+ cav.id = photoshop.canvasId;
+ cav.width = 10;
+ cav.height = 10;
+ $(photoshop.layerId).appendChild(cav);
+ return cav;
+ }
+ return $(photoshop.canvasId);
+
+ },
+
+ createCloseButton: function(parent, id, left, top, flag) {
+ var imgElement = document.createElement('img');
+ imgElement.id = id;
+ imgElement.src = 'images/cross.png';
+ imgElement.className = 'closeButton';
+ imgElement.style.left = left - 15 + 'px';
+ if (photoshop.flag == 'line' || photoshop.flag == 'arrow') {
+ imgElement.style.left = left / 2 - 5 + 'px';
+ imgElement.style.top = top / 2 - 5 + 'px';
+ }
+ imgElement.onclick = function() {
+ $(parent).style.display = 'none';
+ photoshop.removeLayer(parent);
+ };
+ $(parent).onmousemove = function() {
+ if (!photoshop.dragFlag) {
+ photoshop.showCloseButton(id);
+ $(parent).style.zIndex = 110;
+ photoshop.isDraw = (flag == 'text' ? false : photoshop.isDraw);
+ }
+ };
+ $(parent).onmouseout = function() {
+ photoshop.hideCloseButton(id);
+ $(parent).style.zIndex = 100;
+ photoshop.isDraw = true;
+ };
+ $(parent).appendChild(imgElement);
+ return imgElement;
+ },
+
+ showCloseButton: function(id) {
+ $(id).style.display = 'block';
+ },
+
+ hideCloseButton: function(id) {
+ $(id).style.display = 'none';
+ photoshop.isDraw = true;
+ },
+
+ removeLayer: function(id) {
+ for (var i = 0; i < photoshop.markedArea.length; i++) {
+ if (photoshop.markedArea[i].id == id) {
+ photoshop.markedArea.splice(i, 1);
+ break;
+ }
+ }
+ photoshop.removeElement(id);
+ },
+
+ /**
+ * Set the starting point(x,y) when mouse pressed
+ */
+ onMouseDown: function(event) {
+ if (photoshop.isDraw && event.button != 2) {
+ photoshop.startX = event.pageX + $('showBox').scrollLeft -
+ photoshop.offsetX;
+ photoshop.startY = event.pageY + $('showBox').scrollTop -
+ photoshop.offsetY;
+ photoshop.setDivStyle(photoshop.startX, photoshop.startY);
+ photoshop.dragFlag = true;
+
+ $(photoshop.layerId).style.left = photoshop.startX + 'px';
+ $(photoshop.layerId).style.top = photoshop.startY + 'px';
+ $(photoshop.layerId).style.height = 0;
+ $(photoshop.layerId).style.width = 0;
+ $(photoshop.layerId).style.display = 'block';
+ }
+ },
+
+ onMouseUp: function(event) {
+ $('mask-canvas').style.zIndex = 10;
+ photoshop.endX = event.pageX + $('showBox').scrollLeft -
+ photoshop.offsetX;
+ if (photoshop.endX > photoshop.canvas.width) {
+ photoshop.endX = photoshop.canvas.width ;
+ }
+
+ if (photoshop.endX < 0) {
+ photoshop.endX = 0;
+ }
+
+ photoshop.endY = event.pageY + $('showBox').scrollTop -
+ photoshop.offsetY;
+ if (photoshop.endY > photoshop.canvas.height) {
+ photoshop.endY = photoshop.canvas.height ;
+ }
+ if (photoshop.endY < 0) {
+ photoshop.endY = 0;
+ }
+ if (photoshop.isDraw && photoshop.dragFlag && (photoshop.endX !=
+ photoshop.startX || photoshop.endY != photoshop.startY)) {
+ if (photoshop.flag == 'line' || photoshop.flag == 'arrow') {
+ photoshop.drawLineOnMaskCanvas(photoshop.startX, photoshop.startY,
+ photoshop.endX, photoshop.endY, 'drawEnd', photoshop.layerId);
+ } else if (photoshop.flag == 'blur') {
+ canvas.blurImage(photoshop.canvas, $(photoshop.canvasId),
+ photoshop.layerId, photoshop.startX, photoshop.startY,
+ photoshop.endX, photoshop.endY);
+ } else if (photoshop.flag == 'ellipse') {
+ photoshop.drawEllipseOnMaskCanvas(photoshop.endX,
+ photoshop.endY, 'end', photoshop.layerId);
+ }
+ photoshop.markedArea.push({
+ 'id': photoshop.layerId,
+ 'startX': photoshop.startX,
+ 'startY': photoshop.startY,
+ 'endX': photoshop.endX,
+ 'endY': photoshop.endY,
+ 'width': photoshop.nowWidth,
+ 'height': photoshop.nowHeight,
+ 'flag': photoshop.flag,
+ 'highlightType': photoshop.highlightType,
+ 'fontSize': localStorage.fontSize,
+ 'color': photoshop.color,
+ 'context': ''
+ });
+ $(photoshop.layerId).focus();
+ var imageBtnId = 'close_' + photoshop.layerId;
+ photoshop.createCloseButton(photoshop.layerId, imageBtnId,
+ photoshop.nowWidth, photoshop.nowHeight, photoshop.flag);
+ photoshop.createDiv();
+ } else if (photoshop.endX ==
+ photoshop.startX && photoshop.endY == photoshop.startY) {
+ photoshop.removeElement(photoshop.layerId);
+ photoshop.createDiv();
+ }
+ photoshop.dragFlag = false;
+
+ },
+
+ /**
+ * Refresh div‘s height and width when the mouse move
+ */
+ onMouseMove: function(event) {
+ if(photoshop.dragFlag) {
+ $('mask-canvas').style.zIndex = 200;
+ photoshop.endX = event.pageX + $('showBox').scrollLeft -
+ photoshop.offsetX;
+ if (photoshop.endX > photoshop.canvas.width) {
+ photoshop.endX = photoshop.canvas.width ;
+ }
+
+ if (photoshop.endX < 0) {
+ photoshop.endX = 0;
+ }
+
+ photoshop.endY = event.pageY + $('showBox').scrollTop -
+ photoshop.offsetY;
+ if (photoshop.endY > photoshop.canvas.height) {
+ photoshop.endY = photoshop.canvas.height ;
+ }
+ if (photoshop.endY < 0) {
+ photoshop.endY = 0;
+ }
+ photoshop.nowHeight = photoshop.endY - photoshop.startY - 1 ;
+ photoshop.nowWidth = photoshop.endX - photoshop.startX - 1 ;
+
+ if(photoshop.nowHeight < 0) {
+ $(photoshop.layerId).style.top = photoshop.endY + 'px';
+ photoshop.nowHeight = -1 * photoshop.nowHeight;
+ }
+ if(photoshop.nowWidth < 0) {
+ $(photoshop.layerId).style.left = photoshop.endX + 'px';
+ photoshop.nowWidth = -1 * photoshop.nowWidth;
+ }
+
+ $(photoshop.layerId).style.height = photoshop.nowHeight - 3;
+ $(photoshop.layerId).style.width = photoshop.nowWidth - 3;
+ if (photoshop.flag == 'line' || photoshop.flag == 'arrow') {
+ photoshop.drawLineOnMaskCanvas(photoshop.startX, photoshop.startY,
+ photoshop.endX, photoshop.endY, 'lineDrawing', photoshop.layerId);
+ } else if (photoshop.flag == 'blur') {
+ $(photoshop.layerId).style.height = photoshop.nowHeight ;
+ $(photoshop.layerId).style.width = photoshop.nowWidth ;
+ canvas.blurImage(photoshop.canvas, $(photoshop.canvasId),
+ photoshop.layerId, photoshop.startX, photoshop.startY,
+ photoshop.endX, photoshop.endY);
+ } else if (photoshop.flag == 'ellipse') {
+ photoshop.drawEllipseOnMaskCanvas(photoshop.endX,
+ photoshop.endY, 'drawing', photoshop.layerId);
+ }
+ }
+
+ },
+
+ /**
+ * Remove a div
+ */
+ removeElement: function(id) {
+ if($(id)) {
+ $(id).parentNode.removeChild($(id));
+ }
+ },
+
+ /**
+ * Use fillStyle, fillText and fillRect functions to draw rectangles,
+ * and render to canvas
+ */
+ draw: function() {
+ var context = $('canvas').getContext('2d');
+ for (var j = 0; j < photoshop.markedArea.length; j++) {
+ var mark = photoshop.markedArea[j];
+ var x = (mark.startX < mark.endX) ? mark.startX : mark.endX;
+ var y = (mark.startY < mark.endY) ? mark.startY : mark.endY;
+ var width = mark.width;
+ var height = mark.height;
+ var color = mark.color;
+ switch(mark.flag) {
+ case 'rectangle':
+ if (mark.highlightType == 'border') {
+ canvas.drawStrokeRect(context, color, x, y, width, height, 2);
+ } else {
+ var color = changeColorToRgba(color, 0.5);
+ canvas.drawFillRect(context, color, x, y, width, height);
+ }
+ break;
+ case 'radiusRectangle':
+ canvas.drawRoundedRect(
+ context, color, x, y, width, height, 6, mark.highlightType);
+ break;
+ case 'ellipse':
+ x = (mark.startX + mark.endX) / 2;
+ y = (mark.startY + mark.endY) / 2;
+ var xAxis = Math.abs(mark.endX - mark.startX) / 2;
+ var yAxis = Math.abs(mark.endY - mark.startY) / 2;
+ canvas.drawEllipse(
+ context, color, x, y, xAxis, yAxis, 3, mark.highlightType);
+ break;
+ case 'redact':
+ canvas.drawFillRect(context, color, x, y, width, height);
+ break;
+ case 'text':
+ for (var i = 0; i < mark.context.length; i++) {
+ canvas.setText(
+ context, mark.context[i], color, mark.fontSize, 'arial',
+ mark.fontSize, x, y + mark.fontSize * i, width);
+ }
+ break;
+ case 'blur':
+ var imageData = context.getImageData(
+ x, y, photoshop.markedArea[j].width,
+ photoshop.markedArea[j].height);
+ imageData = canvas.boxBlur(
+ imageData, photoshop.markedArea[j].width,
+ photoshop.markedArea[j].height, 10);
+ context.putImageData(
+ imageData, x, y, 0, 0, photoshop.markedArea[j].width,
+ photoshop.markedArea[j].height);
+ break;
+ case 'line':
+ canvas.drawLine(
+ context, color, 'round', 2,
+ mark.startX, mark.startY, mark.endX, mark.endY);
+ break;
+ case 'arrow':
+ canvas.drawArrow(
+ context, color, 2, 4, 10, 'round',
+ mark.startX, mark.startY, mark.endX, mark.endY);
+ break;
+ }
+ }
+ },
+
+ drawLineOnMaskCanvas: function(startX, startY, endX, endY, type, layerId) {
+ var ctx = $('mask-canvas').getContext('2d');
+ ctx.clearRect(0, 0, $('mask-canvas').width, $('mask-canvas').height);
+ if (type == 'drawEnd') {
+ var offset = 20;
+ var width = Math.abs(endX - photoshop.startX) > 0 ?
+ Math.abs(endX - photoshop.startX): 0;
+ var height = Math.abs(endY - photoshop.startY) > 0 ?
+ Math.abs(endY - photoshop.startY): 0;
+ var offsetLeft = parseInt($(layerId).style.left);
+ var offsetTop = parseInt($(layerId).style.top);
+ startX = startX - offsetLeft + offset / 2;
+ startY = startY - offsetTop + offset / 2;
+ endX = endX - offsetLeft + offset / 2;
+ endY = endY - offsetTop + offset / 2;
+ $(layerId).style.left = offsetLeft - offset / 2;
+ $(layerId).style.top = offsetTop - offset / 2;
+ var cavCopy = photoshop.createCanvas(layerId);
+ cavCopy.width = width + offset;
+ cavCopy.height = height + offset;
+ ctx = cavCopy.getContext('2d');
+ }
+ if (localStorage.lineType == 'line') {
+ canvas.drawLine(ctx, localStorage.lineColor, 'round', 2,
+ startX, startY, endX, endY);
+ } else {
+ canvas.drawArrow(ctx, localStorage.lineColor, 2, 4, 10, 'round',
+ startX, startY, endX, endY)
+ }
+
+ },
+
+ createColorPadStr: function(element, type) {
+ var colorList = ['#000000', '#0036ff', '#008000', '#dacb23', '#d56400',
+ '#c70000', '#be00b3', '#1e2188', '#0090ff', '#22cc01', '#ffff00',
+ '#ff9600', '#ff0000', '#ff008e', '#7072c3', '#49d2ff', '#9dff3d',
+ '#ffffff', '#ffbb59', '#ff6b6b', '#ff6bbd'];
+
+ var div = document.createElement("div");
+ div.id = "colorpad";
+ element.appendChild(div);
+
+ for(var i = 0; i < colorList.length; i++) {
+ var a = document.createElement("a");
+ var color = colorList[i];
+ a.id = color;
+ a.title = color;
+ a.style.backgroundColor = color;
+ if (color == '#ffffff') {
+ a.style.border = "1px solid #444";
+ a.style.width = "12px"
+ a.style.height = "12px";
+ }
+ a.addEventListener('click', function(e) {
+ photoshop.colorPadPick(e.target.id, type);
+ return false;
+ });
+ div.appendChild(a);
+ }
+ },
+
+ colorPadPick: function(color, type) {
+ photoshop.color = color;
+ if(type == 'highlight') {
+ localStorage.highlightColor = color;
+ photoshop.setHighlightColorBoxStyle(color);
+ } else if(type == 'text') {
+ localStorage.fontColor = color;
+ $('fontColorBox').style.backgroundColor = color;
+ } else if (type == 'line') {
+ localStorage.lineColor = color;
+ photoshop.setLineColorBoxStyle();
+ } else if (type == 'ellipse') {
+ $('ellipseBox').style.borderColor = color;
+ }
+ },
+
+ setHighlightColorBoxStyle: function(color) {
+ var highlightColorBox = $('highlightColorBox');
+ highlightColorBox.style.borderColor = color;
+ localStorage.highlightType = localStorage.highlightType || 'border';
+ if (localStorage.highlightType == 'border') {
+ highlightColorBox.style.background = '#ffffff';
+ highlightColorBox.style.opacity = 1;
+ $('borderMode').className = 'mark';
+ $('rectMode').className = '';
+ } else if (localStorage.highlightType == 'rect') {
+ highlightColorBox.style.background = color;
+ highlightColorBox.style.opacity = 0.5;
+ $('borderMode').className = '';
+ $('rectMode').className = 'mark';
+ }
+ if (photoshop.flag == 'rectangle') {
+ highlightColorBox.style.borderRadius = '0 0';
+ } else if (photoshop.flag == 'radiusRectangle') {
+ highlightColorBox.style.borderRadius = '3px 3px';
+ } else if (photoshop.flag == 'ellipse') {
+ highlightColorBox.style.borderRadius = '12px 12px';
+ }
+ photoshop.markCurrentElement($(photoshop.flag));
+ },
+
+ setBlackoutColorBoxStyle: function() {
+ localStorage.blackoutType = localStorage.blackoutType || 'redact';
+ if (localStorage.blackoutType == 'redact') {
+ $('blackoutBox').className = 'rectBox';
+ $('redact').className = 'mark';
+ $('blur').className = '';
+ } else if (localStorage.blackoutType == 'blur') {
+ $('blackoutBox').className = 'blurBox';
+ $('redact').className = '';
+ $('blur').className = 'mark';
+ }
+ },
+
+ setFontSize: function(size) {
+ var id = 'size_' + size;
+ localStorage.fontSize = size;
+ $('size_10').className = '';
+ $('size_16').className = '';
+ $('size_18').className = '';
+ $('size_32').className = '';
+ $(id).className = 'mark';
+ },
+
+ setLineColorBoxStyle: function() {
+ localStorage.lineType = localStorage.lineType || 'line';
+ photoshop.color = localStorage.lineColor =
+ localStorage.lineColor || '#FF0000';
+ var ctx = $('lineIconCav').getContext('2d');
+ ctx.clearRect(0, 0, 14, 14);
+ if (localStorage.lineType == 'line') {
+ $('straightLine').className = 'mark';
+ $('arrow').className = '';
+ canvas.drawLine(ctx, photoshop.color, 'round', 2, 1, 13, 13, 1);
+ } else if (localStorage.lineType == 'arrow') {
+ $('straightLine').className = '';
+ $('arrow').className = 'mark';
+ canvas.drawArrow(ctx, photoshop.color, 2, 4, 7, 'round',1, 13, 13, 1);
+ }
+
+ },
+
+ initTools: function() {
+ photoshop.i18nReplace('tHighlight', 'highlight');
+ photoshop.i18nReplace('tRedact', 'redact');
+ photoshop.i18nReplace('redactText', 'solid_black');
+ photoshop.i18nReplace('tText', 'text');
+ photoshop.i18nReplace('tSave', 'save');
+ photoshop.i18nReplace('tClose', 'close');
+ photoshop.i18nReplace('border', 'border');
+ photoshop.i18nReplace('rect', 'rect');
+ photoshop.i18nReplace('blurText', 'blur');
+ photoshop.i18nReplace('lineText', 'line');
+ photoshop.i18nReplace('size_10', 'size_small');
+ photoshop.i18nReplace('size_16', 'size_normal');
+ photoshop.i18nReplace('size_18', 'size_large');
+ photoshop.i18nReplace('size_32', 'size_huge');
+ var fontSize = localStorage.fontSize = localStorage.fontSize || 16;
+ if (fontSize != 10 && fontSize != 16 && fontSize != 18 && fontSize != 32) {
+ localStorage.fontSize = 16;
+ }
+ localStorage.highlightMode = photoshop.flag =
+ localStorage.highlightMode || 'rectangle';
+ localStorage.highlightColor = localStorage.highlightColor || '#FF0000';
+ localStorage.fontColor = localStorage.fontColor || '#FF0000';
+ localStorage.highlightType = photoshop.highlightType =
+ localStorage.highlightType || 'border';
+ localStorage.blackoutType = localStorage.blackoutType || 'redact';
+ localStorage.lineType = localStorage.lineType || 'line';
+ localStorage.lineColor = localStorage.lineColor || '#FF0000';
+ photoshop.setHighlightColorBoxStyle(localStorage.highlightColor);
+ $('fontColorBox').style.backgroundColor =
+ localStorage.fontColor || '#FF0000';
+ $('btnHighlight').addEventListener('click', function() {
+ photoshop.toDo(this, localStorage.highlightMode);
+ photoshop.setHighlightColorBoxStyle(localStorage.highlightColor);
+ }, false);
+
+ $('btnBlackout').addEventListener('click', function() {
+ photoshop.toDo(this, localStorage.blackoutType);
+ photoshop.setBlackoutColorBoxStyle();
+ }, false);
+
+ $('btnText').addEventListener('click', function() {
+ photoshop.toDo(this, 'text');
+ }, false);
+
+ $('btnLine').addEventListener('click', function() {
+ photoshop.toDo(this, localStorage.lineType);
+ photoshop.setLineColorBoxStyle();
+ }, false);
+
+
+
+ photoshop.setHighlightColorBoxStyle(localStorage.highlightColor);
+ $('borderMode').addEventListener('click', function() {
+ localStorage.highlightType = 'border';
+ }, false);
+ $('rectMode').addEventListener('click', function() {
+ localStorage.highlightType = 'rect';
+ }, false);
+ $('rectangle').addEventListener('click', function() {
+ localStorage.highlightMode = photoshop.flag = 'rectangle';
+ photoshop.markCurrentElement(this);
+ }, false);
+ $('radiusRectangle').addEventListener('click', function() {
+ localStorage.highlightMode = photoshop.flag = 'radiusRectangle';
+ photoshop.markCurrentElement(this);
+ }, false);
+ $('ellipse').addEventListener('click', function() {
+ localStorage.highlightMode = photoshop.flag = 'ellipse';
+ photoshop.markCurrentElement(this);
+ }, false);
+ photoshop.setBlackoutColorBoxStyle();
+ $('redact').addEventListener('click', function() {
+ localStorage.blackoutType = 'redact';
+ }, false);
+ $('blur').addEventListener('click', function() {
+ localStorage.blackoutType = 'blur';
+ }, false);
+
+ photoshop.setLineColorBoxStyle();
+
+ photoshop.createColorPadStr($('highlightColorPad'), 'highlight');
+ photoshop.createColorPadStr($('fontColorPad'), 'text');
+ photoshop.createColorPadStr($('lineColorPad'), 'line');
+
+ $('straightLine').addEventListener('click', function() {
+ localStorage.lineType = 'line';
+ photoshop.setLineColorBoxStyle();
+ }, false);
+ $('arrow').addEventListener('click', function() {
+ localStorage.lineType = 'arrow';
+ photoshop.setLineColorBoxStyle();
+ }, false);
+
+ photoshop.setFontSize(localStorage.fontSize);
+ $('size_10').addEventListener('click', function() {
+ photoshop.setFontSize(10);
+ }, false);
+ $('size_16').addEventListener('click', function() {
+ photoshop.setFontSize(16);
+ }, false);
+ $('size_18').addEventListener('click', function() {
+ photoshop.setFontSize(18);
+ }, false);
+ $('size_32').addEventListener('click', function() {
+ photoshop.setFontSize(32);
+ }, false);
+ },
+
+ drawEllipseOnMaskCanvas: function(endX, endY, type, layerId) {
+ var ctx = $('mask-canvas').getContext('2d');
+ ctx.clearRect(0, 0, $('mask-canvas').width, $('mask-canvas').height);
+ var x = (photoshop.startX + endX) / 2;
+ var y = (photoshop.startY + endY) / 2;
+ var xAxis = Math.abs(endX - photoshop.startX) / 2;
+ var yAxis = Math.abs(endY - photoshop.startY) / 2;
+ canvas.drawEllipse(ctx, photoshop.color, x, y, xAxis, yAxis, 3,
+ photoshop.highlightType);
+ if (type == 'end') {
+ var offsetLeft = parseInt($(layerId).style.left);
+ var offsetTop = parseInt($(layerId).style.top);
+ var startX = photoshop.startX - offsetLeft ;
+ var startY = photoshop.startY - offsetTop ;
+ var newEndX = photoshop.endX - offsetLeft ;
+ var newEndY = photoshop.endY - offsetTop ;
+ x = (startX + newEndX) / 2;
+ y = (startY + newEndY) / 2;
+ xAxis = Math.abs(newEndX - startX) / 2;
+ yAxis = Math.abs(newEndY - startY) / 2;
+ var cavCopy = photoshop.createCanvas(layerId);
+ cavCopy.width = Math.abs(endX - photoshop.startX);
+ cavCopy.height = Math.abs(endY - photoshop.startY);
+ var ctxCopy = cavCopy.getContext('2d');
+ canvas.drawEllipse(ctxCopy, photoshop.color, x, y,
+ xAxis, yAxis, 3, photoshop.highlightType);
+ ctx.clearRect(0, 0, $('mask-canvas').width, $('mask-canvas').height);
+ }
+ },
+
+ showTip: function(className, message, delay) {
+ delay = delay || 2000;
+ var div = document.createElement('div');
+ div.className = className;
+ div.innerHTML = message;
+ document.body.appendChild(div);
+ div.style.left = (document.body.clientWidth - div.clientWidth) / 2 + 'px';
+ window.setTimeout(function() {
+ document.body.removeChild(div);
+ }, delay);
+ }
+};
+
+photoshop.init();
+$('photo').addEventListener('mousemove', photoshop.onMouseMove, true);
+$('photo').addEventListener('mousedown', photoshop.onMouseDown, true);
+$('photo').addEventListener('mouseup', photoshop.onMouseUp, true);
+document.addEventListener('mouseup', photoshop.onMouseUp, true);
+document.addEventListener('mousemove', photoshop.onMouseMove, true);
+
+$('canvas').addEventListener(
+ 'selectstart', function f(e) { return false });
+$('mask-canvas').addEventListener(
+ 'selectstart', function f(e) { return false });
+$('btnClose').addEventListener('click', photoshop.closeCurrentTab);
+$('uploadAccountList').addEventListener('click', function(e) {
+ var target = e.target;
+ var classList = Array.prototype.slice.call(target.classList)
+ if (classList.indexOf('accountName') >= 0) {
+ var site = target.dataset.site;
+ var userId = target.dataset.userId;
+ UploadUI.upload(site, userId);
+ } else if (classList.indexOf('deleteBtn') >= 0) {
+ var accountId = target.dataset.accountId;
+ UploadUI.deleteAccountItem(accountId);
+ }
+});
diff --git a/src/js/sina_microblog.js b/src/js/sina_microblog.js
new file mode 100644
index 0000000..18b153b
--- /dev/null
+++ b/src/js/sina_microblog.js
@@ -0,0 +1,122 @@
+const APPKEY = '1350884563';
+const AUTHORIZE_URL = 'https://api.weibo.com/oauth2/authorize';
+const REDIRECT_URL = 'https://api.weibo.com/oauth2/default.html'
+const SINA_USER_INFO_URL = 'https://api.weibo.com/2/users/show.json';
+const SINA_PHOTO_UPLOAD_URL = 'https://upload.api.weibo.com/2/statuses/upload.json';
+const SINA_LOGOUT_URL = 'https://api.weibo.com/2/account/end_session.json';
+
+var SinaMicroblog = {
+ siteId: 'sina',
+ currentUserId: null,
+ accessTokenCallback: null,
+
+ isRedirectUrl: function() {},
+
+ getAccessToken: function(callback) {
+ SinaMicroblog.accessTokenCallback = callback;
+ var url = AUTHORIZE_URL + '?client_id=' + APPKEY +
+ '&redirect_uri=' + REDIRECT_URL + '&response_type=token';
+ chrome.tabs.create({url: url});
+ },
+
+ parseAccessToken: function(url) {
+ var result = 'failure';
+ var msgOrUser = 'sina_failed_to_get_access_token';
+ var hash = url.split('#')[1];
+ if (hash && typeof hash == 'string') {
+ var keyValues = hash.split('&');
+ var response = {};
+
+ for (var keyValue, i = 0, l = keyValues.length; i < l; i++) {
+ keyValue = keyValues[i].split('=');
+ response[keyValue[0]] = keyValue[1];
+ }
+
+ if (!response.error && response.access_token && response.uid) {
+ result = 'success';
+ msgOrUser = new User({
+ id: response.uid,
+ accessToken: response.access_token
+ });
+ }
+ }
+
+ SinaMicroblog.accessTokenCallback(result, msgOrUser);
+ SinaMicroblog.accessTokenCallback = null;
+ },
+
+ getUserInfo: function(user, callback) {
+ ajax({
+ url: SINA_USER_INFO_URL,
+ parameters: {
+ access_token: user.accessToken,
+ uid: user.id
+ },
+ success: function(data) {
+ if (callback) {
+ user.name = data.screen_name;
+ callback('success', user);
+ }
+ },
+ status: {
+ others: function(data) {
+ callback('failure', 'failed_to_get_user_info');
+ }
+ }
+ });
+ },
+
+ upload: function(user, caption, imageData, callback) {
+ caption = encodeURIComponent(caption);
+ var params = {
+ access_token: user.accessToken,
+ status: caption
+ };
+ var binaryData = {
+ boundary: MULTIPART_FORMDATA_BOUNDARY,
+ name: 'pic',
+ value: 'test.png',
+ data: imageData,
+ type: 'image/png'
+ };
+
+ ajax({
+ url: SINA_PHOTO_UPLOAD_URL,
+ parameters: params,
+ multipartData: binaryData,
+ success: function(microblog) {
+ callback('success', microblog.id);
+ },
+ status: {
+ others: function(res) {
+ var message = 'failed_to_upload_image';
+ var errorCode = res.error_code;
+ var invalidAccessTokenCodes =
+ [21332, 21314, 21315, 21316, 21317, 21319, 21327];
+ if (invalidAccessTokenCodes.indexOf(errorCode) >= 0) {
+ message = 'bad_access_token';
+ }
+ callback('failure', message);
+ }
+ }
+ });
+ },
+
+ getPhotoLink: function(user, microblogId, callback) {
+ var photoLink = 'http://weibo.com/' + user.id + '/profile';
+ callback('success', photoLink);
+ },
+
+ logout: function(callback) {
+ var params = {source: APPKEY};
+ ajax({
+ url: SINA_LOGOUT_URL,
+ parameters: params,
+ complete: function(statusCode, data) {
+ // Response status 403 means no user signed in
+ if ((statusCode == 200 || statusCode == 403) && callback)
+ callback(data);
+ }
+ });
+ }
+};
\ No newline at end of file
diff --git a/src/js/site.js b/src/js/site.js
new file mode 100644
index 0000000..6bb43f8
--- /dev/null
+++ b/src/js/site.js
@@ -0,0 +1,63 @@
+var Site = function(id) {
+ this.siteId = id;
+};
+
+Site.prototype = {
+ /**
+ * Get access token by user's authorization.
+ * @param {Function} callback call-back function, parameters:
+ * {String} result, success or failure; {User|String} user with access
+ * token, etc. or error message.
+ */
+ getAccessToken: function(callback) {},
+
+ /**
+ * Check if the url is redirect url for retrieving access token of current
+ * site.
+ * @param {String} url
+ * @return {Boolean} result
+ */
+ isRedirectUrl: function(url) {},
+
+ /**
+ * Parse and get access token from redirect url, then call call-back function
+ * of passed by calling getAccessToken method with access token.
+ * @param {String} url
+ */
+ parseAccessToken: function(url) {},
+
+ /**
+ * Get user information.
+ * @param {User} user
+ * @param {Function} callback call-back function, parameters:
+ * {String} result, success or failure; {User|String} user with user id,
+ * user name, etc. or error message.
+ */
+ getUserInfo: function(user, callback) {},
+
+ /**
+ * Upload image.
+ * @param {User} user user data with access token, etc.
+ * @param {String} caption image description
+ * @param {String} imageData binary image data
+ * @param callback call-back function, parameters:
+ * {String} result, success or failure; {String} photo id or error message.
+ */
+ upload: function(user, caption, imageData, callback) {},
+
+ /**
+ * Get photo link.
+ * @param {User} user user data with id, access token, etc.
+ * @param {String} photoId
+ * @param {Function} callback call-back function, parameters:
+ * {String} result, success or failure; {String} photo link or error
+ * message.
+ */
+ getPhotoLink: function(user, photoId, callback) {},
+
+ /**
+ * Log out current signed in user.
+ * @param callback
+ */
+ logout: function(callback) {}
+};
\ No newline at end of file
diff --git a/src/js/ui.js b/src/js/ui.js
new file mode 100644
index 0000000..e13ea13
--- /dev/null
+++ b/src/js/ui.js
@@ -0,0 +1,70 @@
+var UI = {
+
+ show: function(element) {
+ if (UI.getStyle(element, 'display') == 'none') {
+ // Set display value to be defined by style sheet
+ var cssRules = window.getMatchedCSSRules(element, '', true);
+ var ruleLength = cssRules.length;
+ var display;
+ for (var i = ruleLength - 1; i >= 0 ; --i) {
+ display = cssRules[i].style.display;
+ if (display && display != 'none') {
+ element.style.display = display;
+ return;
+ }
+ }
+
+ // Set display value to be UA default value
+ var tmpElement = document.createElement(element.nodeName);
+ document.body.appendChild(tmpElement);
+ display = UI.getStyle(tmpElement, 'display');
+ document.body.removeChild(tmpElement);
+ element.style.display = display;
+ }
+ },
+
+ hide: function(element) {
+ element.style.display = 'none';
+ },
+
+ setStyle: function(element) {
+ var argLength = arguments.length;
+ var arg1 = arguments[1];
+ if (argLength == 2 && arg1.constructor == Object) {
+ for (var prop in arg1) {
+ var camelCasedProp = prop.replace(/-([a-z])/gi, function(n, letter) {
+ return letter.toUpperCase();
+ });
+ element.style[camelCasedProp] = arg1[prop];
+ }
+ } else if (argLength == 3)
+ element.style[arg1] = arguments[2];
+ },
+
+ getStyle: function(element, property) {
+ return window.getComputedStyle(element)[property];
+ },
+
+ addClass: function(element, className) {
+ var classes = element.className.split(' ');
+ classes.push(className);
+ element.className = classes.join(' ');
+ },
+
+ removeClass: function(element, className) {
+ var classes = element.className.split(' ');
+ var index = classes.indexOf(className);
+ if (index >= 0) {
+ classes.splice(index, 1);
+ element.className = classes.join(' ');
+ }
+ },
+
+ addStyleSheet: function(path) {
+ var link = document.createElement('link');
+ link.setAttribute('type', 'text/css');
+ link.setAttribute('rel', 'stylesheet');
+ link.setAttribute('href', path);
+ document.head.appendChild(link);
+ }
+};
\ No newline at end of file
diff --git a/src/js/upload_ui.js b/src/js/upload_ui.js
new file mode 100644
index 0000000..5b073f5
--- /dev/null
+++ b/src/js/upload_ui.js
@@ -0,0 +1,487 @@
+const CURRENT_LOCALE = chrome.i18n.getMessage('@@ui_locale');
+const MULTIPART_FORMDATA_BOUNDARY = 'Google_Chrome_Screen_Capture';
+const HIDE_ERROR_INFO_DELAY_TIME = 5000;
+
+var UploadUI = {
+ currentSite: '',
+ uploading: false,
+ sites: {},
+
+ registerSite: function(id, siteObject) {
+ this.sites[id] = siteObject;
+ },
+
+ getSiteObject: function(id) {
+ return this.sites[id];
+ },
+
+ setUploading: function(state) {
+ UploadUI.uploading = state;
+ },
+
+ init: function() {
+ // Register supported site for image sharing.
+ UploadUI.registerSite(SinaMicroblog.siteId, SinaMicroblog);
+ UploadUI.registerSite(Facebook.siteId, Facebook);
+ UploadUI.registerSite(Picasa.siteId, Picasa);
+ UploadUI.registerSite(Imgur.siteId, Imgur);
+
+ // Import style sheet for current locale
+ if (CURRENT_LOCALE == 'zh_CN')
+ UI.addStyleSheet('./i18n_styles/zh_CN_upload_image.css');
+ else
+ UI.addStyleSheet('./i18n_styles/en_US_upload_image.css');
+
+ // Get i18n message
+ i18nReplace('shareToSinaMicroblogText', SinaMicroblog.siteId +
+ '_upload_header');
+ i18nReplace('shareToFacebookText', Facebook.siteId + '_upload_header');
+ i18nReplace('shareToPicasaText', Picasa.siteId + '_upload_header');
+ i18nReplace('lastStep', 'return_to_site_selection');
+ i18nReplace('closeUploadWrapper', 'close_upload_wrapper');
+ i18nReplace('imageCaptionText', 'image_caption');
+ i18nReplace('photoSizeTip', 'photo_size_tip');
+ i18nReplace('shareToImgurText', Imgur.siteId + '_upload_header');
+ $('requiredFlag').setAttribute('title',
+ chrome.i18n.getMessage('invalid_caption'));
+
+ // Add event listeners
+ //$('btnUpload').addEventListener('click', UploadUI.showUploadWrapper, false);
+ $('btnSave').addEventListener('click', UploadUI.saveImage, false);
+ $('closeUploadWrapper').addEventListener('click',
+ UploadUI.hideUploadWrapper, false);
+
+ $('picasaBtn').addEventListener('click', function() {
+ UploadUI.showUploadContentWrapper(Picasa.siteId);
+ });
+ $('facebookBtn').addEventListener('click', function() {
+ UploadUI.showUploadContentWrapper(Facebook.siteId);
+ }, false);
+ $('sinaMicroblogBtn').addEventListener('click', function() {
+ UploadUI.showUploadContentWrapper(SinaMicroblog.siteId);
+ }, false);
+ $('imgurBtn').addEventListener('click', function() {
+ UploadUI.showUploadContentWrapper(Imgur.siteId);
+ }, false);
+ $('shareToOtherAccount').addEventListener('click', function() {
+ var currentSite = UploadUI.currentSite;
+
+ // Validate image description first
+ if (UploadUI.validatePhotoDescription(currentSite)) {
+ var callback = function() {
+ var authenticationTip =
+ chrome.i18n.getMessage('user_authentication_tip');
+ UploadUI.showAuthenticationProgress(authenticationTip);
+ UploadUI.getAccessToken(currentSite);
+ };
+ var users = Account.getUsers(currentSite);
+ var numberOfUsers = Object.keys(users).length;
+
+ // Logout when user has authenticated app
+ if (numberOfUsers) {
+ var logoutTip = chrome.i18n.getMessage('user_logout_tip');
+ UploadUI.showAuthenticationProgress(logoutTip);
+ var site = UploadUI.getSiteObject(currentSite);
+ site.logout(callback);
+ } else {
+ callback();
+ }
+ }
+ }, false);
+ $('lastStep').addEventListener('click', UploadUI.showUploadSitesWrapper,
+ false);
+ },
+
+ showUploadWrapper: function() {
+ var uploadWrapper = $('uploadWrapper');
+ UI.show(uploadWrapper);
+
+ // Reset upload wrapper position
+ var viewportWidth = window.innerWidth;
+ var viewportHeight = window.innerHeight;
+ var wrapperWidth = uploadWrapper.offsetWidth;
+ var wrapperHeight = uploadWrapper.offsetHeight;
+
+ var left = (viewportWidth - wrapperWidth) / 2;
+ var top = (viewportHeight - wrapperHeight) / 3;
+ left = left < 0 ? 0 : left;
+ top = top < 0 ? 0 : top;
+
+ var scrollTop = document.body.scrollTop;
+ var scrollLeft = document.body.scrollLeft;
+
+ UI.setStyle(uploadWrapper, {
+ top: top + scrollTop + 'px',
+ left: left + scrollLeft + 'px'
+ });
+ UploadUI.showUploadSitesWrapper();
+ UploadUI.showOverlay();
+ },
+
+ hideUploadWrapper: function() {
+ UI.hide($('uploadWrapper'));
+ UploadUI.hideOverlay();
+ },
+
+ showOverlay: function() {
+ var overlay = $('overlay');
+ UI.setStyle(overlay, {
+ width: document.body.scrollWidth + 'px',
+ height: document.body.scrollHeight + 'px'
+ });
+ UI.show($('overlay'));
+ },
+
+ hideOverlay: function() {
+ UI.hide($('overlay'));
+ },
+
+ updateUploadHeader: function(title) {
+ $('uploadHeader').firstElementChild.firstElementChild.innerText = title;
+ },
+
+ showUploadSitesWrapper: function() {
+ var uploadHeader = chrome.i18n.getMessage('upload_sites_header');
+ UploadUI.updateUploadHeader(uploadHeader);
+ UI.show($('uploadSitesWrapper'));
+ UploadUI.hideUploadContentWrapper();
+ UI.hide($('lastStep'));
+ },
+
+ hideUploadSitesWrapper: function() {
+ UI.hide($('uploadSitesWrapper'));
+ },
+
+ showUploadContentWrapper: function(site) {
+ UploadUI.currentSite = site;
+
+ // Update upload wrapper UI
+ var uploadHeader = chrome.i18n.getMessage(site + '_upload_header');
+ UploadUI.updateUploadHeader(uploadHeader);
+ UploadUI.hideUploadSitesWrapper();
+ UploadUI.hideErrorInfo();
+ UploadUI.hideAuthenticationProgress();
+ UploadUI.clearPhotoDescription();
+ UI.show($('uploadContentWrapper'));
+ UI.show($('lastStep'));
+ UploadUI.updateShareToOtherAccountText(site);
+ UploadUI.togglePhotoDescriptionRequiredFlag(site);
+
+ // Show authenticated accounts of current site
+ UploadUI.clearAccounts();
+ var users = Account.getUsers(site);
+ for (var userId in users) {
+ UploadUI.addAuthenticatedAccount(site, userId);
+ }
+ },
+
+ hideUploadContentWrapper: function() {
+ UI.hide($('uploadContentWrapper'));
+ },
+
+ clearPhotoDescription: function() {
+ $('imageCaption').value = '';
+ },
+
+ validatePhotoDescription: function(site) {
+ var caption = $('imageCaption');
+ var invalidCaptionMsg = chrome.i18n.getMessage('invalid_caption');
+
+ // Validate photo description
+ if (site == SinaMicroblog.siteId && caption.value == '') {
+ UploadUI.showErrorInfo(invalidCaptionMsg);
+ caption.focus();
+ return false;
+ }
+ return true;
+ },
+
+ togglePhotoDescriptionRequiredFlag: function(siteId) {
+ if (siteId == SinaMicroblog.siteId)
+ UI.show($('requiredFlag'));
+ else
+ UI.hide($('requiredFlag'));
+ },
+
+ updateShareToOtherAccountText: function(siteId) {
+ var users = Account.getUsers(siteId);
+ var userLength = Object.keys(users).length;
+ if (userLength)
+ i18nReplace('shareToOtherAccount', 'share_to_other_account');
+ else
+ i18nReplace('shareToOtherAccount', 'share_to_' + siteId + '_account');
+ },
+
+ showErrorInfo: function(text) {
+ UI.show($('errorWrapper'));
+ $('errorInfo').innerHTML = text;
+ setTimeout(function() {
+ UploadUI.hideErrorInfo();
+ }, HIDE_ERROR_INFO_DELAY_TIME);
+ },
+
+ hideErrorInfo: function() {
+ UI.hide($('errorWrapper'));
+ },
+
+ showProgressBar: function(accountId) {
+ var progress = document.querySelector('#' + accountId +
+ ' .progressBar');
+ UI.show(progress);
+ },
+
+ hideProgressBar: function(accountId) {
+ var progress = document.querySelector('#' + accountId +
+ ' .progressBar');
+ UI.hide(progress);
+ },
+
+ showAuthenticationProgress: function(title) {
+ var progress = $('authenticationProgress');
+ progress.setAttribute('title', title);
+ UI.show(progress);
+ },
+
+ hideAuthenticationProgress: function() {
+ UI.hide($('authenticationProgress'));
+ },
+
+ setProgress: function(accountId, loaded, total) {
+ console.log('In setProgress, loaded: ' + loaded + ', total: ' + total);
+ var progress = document.querySelector('#' + accountId + ' .progressBar');
+
+ // One progress bar has 4 parts to represent progress
+ var level = parseInt(loaded / total / 0.25);
+ UI.setStyle(progress, 'background-position-y', '-' + (12 * level) + 'px');
+ },
+
+ showPhotoLink: function(accountId, link) {
+ var photoLink = document.querySelector('#' + accountId + ' .photoLink');
+ photoLink.setAttribute('href', link);
+ UI.setStyle(photoLink, 'display', 'inline');
+ },
+
+ hidePhotoLink: function(accountId) {
+ var photoLink = document.querySelector('#' + accountId + ' .photoLink');
+ UI.hide(photoLink);
+ },
+
+ showUploadInfo: function(accountId, text) {
+ var uploadInfo = document.querySelector('#' + accountId + ' .uploadInfo');
+ uploadInfo.innerHTML = text;
+ UI.show(uploadInfo);
+ },
+
+ hideUploadInfo: function(accountId) {
+ var uploadInfo = document.querySelector('#' + accountId + ' .uploadInfo');
+ UI.hide(uploadInfo);
+ },
+
+ clearAccounts: function() {
+ $('uploadAccountList').innerHTML = '';
+ },
+
+ addAuthenticatedAccount: function(site, userId) {
+ var template = $('accountItemTemplate').innerHTML;
+
+ // Replace i18n message
+ template = template.replace(/\$\{accountId\}/gi, site + '_' + userId);
+ var shareToText = chrome.i18n.getMessage('share_to');
+ template = template.replace(/\$\{accountName\}/gi,
+ shareToText + ' ' + Account.getUser(site, userId)['name']);
+ template = template.replace('${site}', site);
+ template = template.replace('${userId}', userId);
+ template = template.replace(/\$\{deletionTitle\}/gi,
+ chrome.i18n.getMessage('deletion_title'));
+ template = template.replace(/\$\{photoLinkText\}/gi,
+ chrome.i18n.getMessage('photo_link_text'));
+ template = template.replace(/\$\{progressInfo\}/gi,
+ chrome.i18n.getMessage('progress_info'));
+
+ // At most show 3 authenticated users
+ var uploadAccountList = $('uploadAccountList');
+ var accountsNumber = uploadAccountList.childElementCount;
+ if (accountsNumber == 2) {
+ uploadAccountList.removeChild(uploadAccountList.lastElementChild);
+ }
+ uploadAccountList.innerHTML = template + uploadAccountList.innerHTML;
+
+ $('accountName').addEventListener('click', function(e) {
+ UploadUI.upload(site, userId);
+ });
+ $('deleteBtn').addEventListener('click', function(e) {
+ e.stopPropagation();
+ UploadUI.deleteAccountItem(site + '_' + userId);
+ });
+
+ UploadUI.updateShareToOtherAccountText(site);
+ },
+
+ deleteAccountItem: function(accountId, noConfirm) {
+ if (UploadUI.uploading && !noConfirm)
+ return;
+ var confirmText = chrome.i18n.getMessage('account_deletion_confirm');
+ if (noConfirm || confirm(confirmText)) {
+ $('uploadAccountList').removeChild($(accountId));
+
+ // Clear localStorage
+ var site = accountId.split('_')[0];
+ var userId = accountId.split('_')[1];
+ Account.removeUser(site, userId);
+ UploadUI.updateShareToOtherAccountText(site);
+ }
+ },
+
+ upload: function(siteId, userId) {
+ if (UploadUI.uploading)
+ return;
+
+ // Initialize UI
+ var accountId = siteId + '_' + userId;
+ UploadUI.hideErrorInfo();
+ UploadUI.hideUploadInfo(accountId);
+ UploadUI.hidePhotoLink(accountId);
+ if (!UploadUI.validatePhotoDescription(siteId))
+ return;
+ var caption = $('imageCaption').value;
+
+ // Get ready for upload image.
+ photoshop.draw();
+ UploadUI.setUploading(true);
+ UploadUI.showProgressBar(accountId);
+
+ var site = UploadUI.getSiteObject(siteId);
+ var user = Account.getUser(siteId, userId);
+ var imageData = UploadUI.getImageData();
+ var infoText;
+
+ var callback = function(result, photoIdOrMessage) {
+ if (result == 'success') {
+ infoText = chrome.i18n.getMessage('get_photo_link');
+ UploadUI.showUploadInfo(accountId, infoText);
+ site.getPhotoLink(user, photoIdOrMessage, function(photoLinkResult,
+ photoLinkOrMessage) {
+ if (photoLinkResult == 'success') {
+ UploadUI.setUploading(false);
+ UploadUI.hideUploadInfo(accountId);
+ UploadUI.showPhotoLink(accountId, photoLinkOrMessage);
+ } else {
+ UploadUI.showErrorInfo(photoLinkOrMessage);
+ }
+ });
+ } else {
+ if (photoIdOrMessage == 'bad_access_token' ||
+ photoIdOrMessage == 'invalid_album_id') {
+ Account.removeUser(site.siteId, site.currentUserId);
+ UploadUI.deleteAccountItem(accountId, true);
+ UploadUI.getAccessToken(siteId);
+ }
+ UploadUI.setUploading(false);
+ UploadUI.hideProgressBar(accountId);
+ UploadUI.showErrorInfo(chrome.i18n.getMessage(photoIdOrMessage));
+ }
+ UploadUI.hideProgressBar(accountId);
+ };
+
+ if (user) {
+ site.currentUserId = user.id;
+ site.upload(user, caption, imageData, callback);
+ } else {
+ UploadUI.getAccessToken(siteId);
+ }
+ },
+
+ getAccessToken: function(siteId) {
+ var site = UploadUI.getSiteObject(siteId);
+ var accessTokenCallback = function(result, userOrMessage) {
+ if (result == 'success') {
+ UploadUI.getUserInfo(siteId, userOrMessage);
+ } else {
+ // Show error information according to error reason
+ UploadUI.showErrorInfo(chrome.i18n.getMessage(userOrMessage));
+ UploadUI.hideAuthenticationProgress();
+ }
+ };
+
+ site.getAccessToken(accessTokenCallback);
+ },
+
+ getUserInfo: function(siteId, user) {
+ var site = UploadUI.getSiteObject(siteId);
+ site.getUserInfo(user, function(result, userOrMessage) {
+ if (result == 'success') {
+ var userId = user.id;
+ // Check if the authenticated user is added.
+ if (!Account.getUser(siteId, userId)) {
+ site.currentUserId = userId;
+ Account.addUser(siteId, user);
+ UploadUI.addAuthenticatedAccount(siteId, userId);
+ }
+ UploadUI.upload(siteId, userId);
+ } else {
+ var msg = chrome.i18n.getMessage(userOrMessage);
+ UploadUI.showErrorInfo(msg);
+ }
+ UploadUI.hideAuthenticationProgress();
+ });
+ },
+
+ getImageData: function() {
+ var dataUrl = $('canvas').toDataURL('image/png');
+ var imageDataIndex = dataUrl.indexOf('data:image/png;base64,');
+ if (imageDataIndex != 0) {
+ return;
+ }
+
+ // Decode to binary data
+ return atob(dataUrl.substr(imageDataIndex + 22));
+ },
+
+ saveImage: function() {
+ $('canvas').toBlob(function(blob) {
+ console.log(chrome.extension.getBackgroundPage());
+ saveAs(blob, chrome.extension.getBackgroundPage().screenshot.screenshotName+".png");
+ });
+ }
+};
+
+(function() {
+// Cache tab id of edit page, so that we can get tab focus after getting access
+// token.
+var tabIdOfEditPage;
+chrome.tabs.getSelected(null, function(tab) {
+ tabIdOfEditPage = tab.id;
+});
+
+function selectTab(tabId) {
+ chrome.tabs.update(tabId, {
+ selected: true
+ });
+}
+
+function closeTab(tabId) {
+ chrome.tabs.remove(tabId);
+}
+
+function parseAccessToken(senderId, url, siteId) {
+ var sites = UploadUI.sites;
+ for (var id in sites) {
+ var site = sites[id];
+ if ((siteId && id == siteId) || site.isRedirectUrl(url)) {
+ selectTab(tabIdOfEditPage);
+ closeTab(senderId);
+ site.parseAccessToken(url);
+ return true;
+ }
+ }
+ return false;
+}
+
+chrome.extension.onMessage.addListener(function(request, sender) {
+ switch (request.msg) {
+ case 'url_for_access_token':
+ parseAccessToken(sender.tab.id, request.url, request.siteId);
+ break;
+ }
+});
+})();
diff --git a/src/lib/FileSaver.min.js b/src/lib/FileSaver.min.js
new file mode 100644
index 0000000..9a1e397
--- /dev/null
+++ b/src/lib/FileSaver.min.js
@@ -0,0 +1,2 @@
+/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
+var saveAs=saveAs||function(e){"use strict";if(typeof e==="undefined"||typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=t.createElementNS("http://www.w3.org/1999/xhtml","a"),o="download"in r,a=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},i=/constructor/i.test(e.HTMLElement)||e.safari,f=/CriOS\/[\d]+/.test(navigator.userAgent),u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},s="application/octet-stream",d=1e3*40,c=function(e){var t=function(){if(typeof e==="string"){n().revokeObjectURL(e)}else{e.remove()}};setTimeout(t,d)},l=function(e,t,n){t=[].concat(t);var r=t.length;while(r--){var o=e["on"+t[r]];if(typeof o==="function"){try{o.call(e,n||e)}catch(a){u(a)}}}},p=function(e){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)){return new Blob([String.fromCharCode(65279),e],{type:e.type})}return e},v=function(t,u,d){if(!d){t=p(t)}var v=this,w=t.type,m=w===s,y,h=function(){l(v,"writestart progress write writeend".split(" "))},S=function(){if((f||m&&i)&&e.FileReader){var r=new FileReader;r.onloadend=function(){var t=f?r.result:r.result.replace(/^data:[^;]*;/,"data:attachment/file;");var n=e.open(t,"_blank");if(!n)e.location.href=t;t=undefined;v.readyState=v.DONE;h()};r.readAsDataURL(t);v.readyState=v.INIT;return}if(!y){y=n().createObjectURL(t)}if(m){e.location.href=y}else{var o=e.open(y,"_blank");if(!o){e.location.href=y}}v.readyState=v.DONE;h();c(y)};v.readyState=v.INIT;if(o){y=n().createObjectURL(t);setTimeout(function(){r.href=y;r.download=u;a(r);h();c(y);v.readyState=v.DONE});return}S()},w=v.prototype,m=function(e,t,n){return new v(e,t||e.name||"download",n)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(e,t,n){t=t||e.name||"download";if(!n){e=p(e)}return navigator.msSaveOrOpenBlob(e,t)}}w.abort=function(){};w.readyState=w.INIT=0;w.WRITING=1;w.DONE=2;w.error=w.onwritestart=w.onprogress=w.onwrite=w.onabort=w.onerror=w.onwriteend=null;return m}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!==null){define("FileSaver.js",function(){return saveAs})}
diff --git a/src/lib/dateFormat.min.js b/src/lib/dateFormat.min.js
new file mode 100644
index 0000000..28671ba
--- /dev/null
+++ b/src/lib/dateFormat.min.js
@@ -0,0 +1,9 @@
+function dateFormat(g,c,k){function l(a,b){a.setHours(a.getHours()+parseFloat(b));return a}function m(a,b){var c="Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" ");return b?c[a.getDay()].substr(0,3):c[a.getDay()]}function n(a,b){var c="January February March April May June July August September October November December".split(" ");return b?c[a.getMonth()].substr(0,3):c[a.getMonth()]}var d={d:function(){var a=this.getDate();return 9<a?a:"0"+a},D:function(){return m(this,!0)},j:function(){return this.getDate()},
+l:function(){return m(this)},N:function(){return this.getDay()+1},S:function(){var a=this.getDate();if(/1/.test(parseInt((a+"").charAt(0))))return"th";a=parseInt((a+"").charAt(1));return 1==a?"st":2==a?"nd":3==a?"rd":"th"},w:function(){return this.getDay()},z:function(){return Math.round(Math.abs((this.getTime()-(new Date("1/1/"+this.getFullYear())).getTime())/864E5))},W:function(){var a=new Date(this.getFullYear(),0,1);return Math.ceil(((this-a)/864E5+a.getDay()+1)/7)},F:function(){return n(this)},
+m:function(){var a=this.getMonth()+1;return 9<a?a:"0"+a},M:function(){return n(this,!0)},n:function(){return this.getMonth()+1},t:function(){return(new Date(this.getFullYear(),this.getMonth()+1,0)).getDate()},L:function(){var a=this.getFullYear();return 0==a%4&&0!=a%100||0==a%400},o:function(){return parseInt(this.getFullYear())},Y:function(){return parseInt(this.getFullYear())},y:function(){return parseInt((this.getFullYear()+"").substr(-2))},a:function(){return 12<=this.getHours()?"pm":"am"},A:function(){return 12<=
+this.getHours()?"PM":"AM"},B:function(){return"@"+("00"+Math.floor((60*((this.getHours()+1)%24*60+this.getMinutes())+this.getSeconds()+.001*this.getMilliseconds())/86.4)).slice(-3)},g:function(){var a=this.getHours();return 0==a?12:12>=a?a:a-12},G:function(){return this.getHours()},h:function(){var a=this.getHours(),a=12>=a?a:a-12;return 0==a?12:9<a?a:"0"+a},H:function(){var a=this.getHours();return 9<a?a:"0"+a},i:function(){var a=this.getMinutes();return 9<a?a:"0"+a},s:function(){var a=this.getSeconds();
+return 9<a?a:"0"+a},u:function(){return this.getMilliseconds()},e:function(){var a=this.toString().match(/ ([A-Z]{3,4})([-|+]?\d{4})/);return 1<a.length?a[1]:""},I:function(){var a=new Date(this.getFullYear(),0,1),b=new Date(this.getFullYear(),6,1),a=Math.max(a.getTimezoneOffset(),b.getTimezoneOffset());return this.getTimezoneOffset()<a?1:0},O:function(){var a=this.toString().match(/ ([A-Z]{3,4})([-|+]?\d{4})/);return 2<a.length?a[2]:""},P:function(){var a=this.toString().match(/ ([A-Z]{3,4})([-|+]?\d{4})/);
+return 2<a.length?a[2].substr(0,3)+":"+a[2].substr(3,2):""},T:function(){return this.toLocaleString("en",{timeZoneName:"short"}).split(" ").pop()},Z:function(){return 60*this.getTimezoneOffset()},c:function(){return l(new Date(this),-(this.getTimezoneOffset()/60)).toISOString()},r:function(){return l(new Date(this),-(this.getTimezoneOffset()/60)).toISOString()},U:function(){return this.getTime()/1E3|0}},e={commonLogFormat:"d/M/Y:G:i:s",exif:"Y:m:d G:i:s",isoYearWeek:"Y\\WW",isoYearWeek2:"Y-\\WW",
+isoYearWeekDay:"Y\\WWj",isoYearWeekDay2:"Y-\\WW-j",mySQL:"Y-m-d h:i:s",postgreSQL:"Y.z",postgreSQL2:"Yz",soap:"Y-m-d\\TH:i:s.u",soap2:"Y-m-d\\TH:i:s.uP",unixTimestamp:"@U",xmlrpc:"Ymd\\TG:i:s",xmlrpcCompact:"Ymd\\tGis",wddx:"Y-n-j\\TG:i:s"},h={AMERICAN:"F j, Y",AMERICANSHORT:"m/d/Y",AMERICANSHORTWTIME:"m/d/Y h:i:sA",ATOM:"Y-m-d\\TH:i:sP",COOKIE:"l, d-M-Y H:i:s T",EUROPEAN:"j F Y",EUROPEANSHORT:"d.m.Y",EUROPEANSHORTWTIME:"d.m.Y H:i:s",ISO8601:"Y-m-d\\TH:i:sO",LEGAL:"j F Y",RFC822:"D, d M y H:i:s O",
+RFC850:"l, d-M-y H:i:s T",RFC1036:"D, d M y H:i:s O",RFC1123:"D, d M Y H:i:s O",RFC2822:"D, d M Y H:i:s O",RFC3339:"Y-m-d\\TH:i:sP",RSS:"D, d M Y H:i:s O",W3C:"Y-m-d\\TH:i:sP"},f={"pretty-a":"g:i.sA l jS \\o\\f F, Y","pretty-b":"g:iA l jS \\o\\f F, Y","pretty-c":"n/d/Y g:iA","pretty-d":"n/d/Y","pretty-e":"F jS - g:ia","pretty-f":"g:iA","pretty-g":"F jS, Y","pretty-h":"F jS, Y g:mA"};if(c){if("compound"==c){if(!1===k)return e;var d={},b;for(b in e)d[b]=dateFormat(g,e[b]);return d}if(e[c])return dateFormat(g,
+e[c],k);if("constants"==c){if(!1===k)return h;d={};for(b in h)d[b]=dateFormat(g,h[b]);return d}if(h[c])return dateFormat(g,h[c],k);if("pretty"==c){if(!1===k)return f;d={};for(b in f)d[b]=dateFormat(g,f[b]);return d}if(f[c])return dateFormat(g,f[c],k);e=c.split("");h="";for(b in e)(f=e[b])&&/[a-z]/i.test(f)&&!/\\/.test(h+f)&&(e[b]=d[f]?d[f].apply(g):f),h=e[b];return e.join("").replace(/\\/g,"")}return g};
diff --git a/src/manifest.json b/src/manifest.json
new file mode 100644
index 0000000..c6ca1a0
--- /dev/null
+++ b/src/manifest.json
@@ -0,0 +1,42 @@
+{
+ "version": "5.2",
+ "version_name": "5.2 beta",
+ "name": "__MSG_name__",
+ "description": "__MSG_description__",
+ "background": {
+ "page": "background.html",
+ "persistent": false
+ },
+ "browser_action": {
+ "default_icon": "images/icon_19.png",
+ "default_title": "__MSG_default_title__",
+ "default_popup": "popup.html"
+ },
+ "content_scripts": [ {
+ "matches": ["http://*/*", "https://*/*", "ftp://*/*", "file://*/*"],
+ "js": ["js/page.js", "js/shortcut.js"],
+ "css": ["style.css"],
+ "run_at": "document_end"
+ }, {
+ "matches": ["http://*/*", "https://*/*", "ftp://*/*", "file://*/*"],
+ "js": ["js/isLoad.js"],
+ "run_at": "document_start"
+ }
+ ],
+ "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
+ "default_locale": "en",
+ "icons": {
+ "16": "images/icon_16.png",
+ "19": "images/icon_19.png",
+ "32": "images/icon_32.png",
+ "48": "images/icon_48.png",
+ "128": "images/icon_128.png"
+ },
+ "manifest_version": 2,
+ "options_page": "options.html",
+ "permissions": ["tabs", "<all_urls>"],
+ "web_accessible_resources": [
+ "js/page_context.js",
+ "style.css"
+ ]
+}
diff --git a/src/options.html b/src/options.html
new file mode 100644
index 0000000..b0e51ec
--- /dev/null
+++ b/src/options.html
@@ -0,0 +1,249 @@
+<!--
+ * Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+ * source code is governed by a BSD-style license that can be found in the
+ * LICENSE file.
+-->
+<html>
+ <head>
+ <title> Screen Capture Options </title>
+ <meta charset="utf-8">
+ <style>
+ body {
+ font-family: arial, sans-serif;
+ margin:10px;
+ background: #EBEFF9;
+ font-size: 13px;
+ }
+ #header {
+ padding-bottom:1em;
+ padding-top:1em;
+ }
+ #header h1 {
+ font-size: 30px;
+ display: inline;
+ color: #3d5d6a;
+ padding-bottom: 40px;
+ padding-left: 90px;
+ padding-top: 70px;
+ background: url(images/icon_48.png) no-repeat;
+ background-position: 1px 62px;
+ }
+ h4 {
+ padding-left: 0.5em;
+ border-bottom: 2px dotted #c3c3c3;
+ color: #333333;
+ line-height: 28px;
+ font-size: 15px;
+ }
+ p {
+ padding-left: 2em;
+ font-family: verdana, arial, sans-serif, simsun;
+ font-size: 14px;
+ }
+ input {
+ cursor: pointer;
+ }
+ .section-header {
+ background: #ebeff9;
+ border-top: 1px solid #b5c7de;
+ font-size: 99%;
+ padding: 3px 0 2px 5px;
+ font-weight: bold;
+ margin-bottom: 1em;
+ margin-top: 1em;
+ }
+ .contentBox {
+ background-color: #ffffff;
+ border: 3px solid #bec9ce;
+ border-bottom-left-radius: 20px 20px;
+ border-bottom-right-radius: 20px 20px;
+ border-top-left-radius: 20px 20px;
+ border-top-right-radius: 20px 20px;
+ padding: 30px;
+ text-align: left;
+ margin: 50px auto 20px auto;
+ height: auto;
+ width: 600px;
+ }
+ .section-content {
+ padding-left: 80px;
+ line-height: 25px;
+ }
+ .colorBox {
+ width: 60px;
+ height: 20px;
+ margin: 3px 0 0 2px;
+ }
+ #colorpad {
+ position: absolute;
+ word-wrap: break-word;
+ word-break: normal;
+ z-index: 10;
+ border: #000000 solid 1px;
+ background-color: #ffffff;
+ padding: 5px;
+ width: 393px;
+ }
+ #colorpad a {
+ margin: 2px;
+ padding: 1px;
+ display: block;
+ height: 20px;
+ width: 20px;
+ float: left;
+ border: 1px solid #333333;
+ }
+ .contentLeft {
+ float: left;
+ width: 100px;
+ line-height: 28px;
+ }
+ .contentRight {
+ float: left;
+ }
+ .clear {
+ clear: both;
+ }
+ .section-content {
+ padding-left: 55px;
+ }
+ .closeBtn {
+ margin: 20px 0 0 0;
+ }
+ #footer {
+ text-align: center;
+ font-size: 13px;
+ }
+ #filePath {
+ background:#ebebe4;
+ border: 1px solid #7f9db9;
+ color:#aca799;
+ line-height: 18px;
+ }
+ #hot-key-setting {
+ margin: 26px 0 0 80px;
+ }
+ .hot-key {
+ display: inline-block;
+ width: 200px;
+ margin: 0 30px 5px 0;
+ }
+ #hot-key-setting .capture-text {
+ margin-right: 10px;
+ }
+ #error-info {
+ margin: 5px 0 5px 80px;
+ color: red;
+ }
+ #screen-capture-hot-key-set-wrapper {
+ display: none;
+ }
+ </style>
+ </head>
+<body>
+ <div class="contentBox">
+ <div id="header">
+ <h1 id="optionTitle"></h1>
+ </div>
+ <h4 id="screenshootQualitySetting"></h4>
+ <div class="section-content">
+ <div>
+ <label></label><input id="lossy" type="radio" name="quality"><span id="lossyScreenShot"></span></label>
+ <label></label><input id="lossless" type="radio" name="quality"><span id="losslessScreenShot">
+ </span></label> </div>
+ <div class="clear"></div>
+ </div>
+ <div id="shortcutSettingDiv" style="margin: 0px 0px 5px 0px">
+ <h4 id="shorcutSetting"></h4>
+ <div style="margin: 2px 0px 0px 56px">
+ <input type="checkbox" id="settingShortcut">
+ <label for="settingShortcut" id="settingShortcutText"></label>
+ </div>
+ <div id="hot-key-setting">
+ <span class="hot-key">
+ <label for="area-capture-hot-key" class="capture-text" id="area-capture-text"></label>
+ <span>Ctrl+Alt+<select id="area-capture-hot-key"></select></span>
+ </span>
+ <span class="hot-key">
+ <label for="viewport-capture-hot-key" class="capture-text" id="viewport-capture-text"></label>
+ <span>Ctrl+Alt+<select id="viewport-capture-hot-key"></select></span>
+ </span>
+ <span class="hot-key">
+ <label for="full-page-capture-hot-key" class="capture-text" id="full-page-capture-text"></label>
+ <span>Ctrl+Alt+<select id="full-page-capture-hot-key"></select></span>
+ </span>
+ </div>
+ <div id="error-info"></div>
+ </div>
+ <div class="closeBtn"><button id="saveAndClose"></button>
+ </div>
+ <div style="margin-top: 20px; border-bottom: 2px dotted grey;">
+ </div>
+ <details style="margin-top: 20px;">
+ <summary style="cursor: pointer;"><span style="font-weight: bold;">License and credits</span></summary>
+ <h4>License</h4>
+ <pre style="white-space: pre-line;">
+The MIT License (MIT)
+Copyright © 2016 Adrià Vilanova Martínez
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the “Software”), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+ </pre>
+ <div style="margin-top: 20px; border-bottom: 2px dotted grey;">
+ </div>
+ <p style="font-family: arial; font-size: 13px; padding-left: 0;">This release of "Screen Capture" is made possible by the following Open Source software:</p>
+ <h4>Screen Capture (by Google) (<a href="https://code.google.com/archive/p/chrome-screen-capture/">homepage</a>)</h4>
+ <pre style="white-space: pre-line;">
+Copyright (c) 2010, Google
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ </pre>
+
+ <h4>FileSaver.js (<a href="https://github.com/eligrey/FileSaver.js">homepage</a>)</h4>
+ <pre style="white-space: pre-line;">
+The MIT License
+
+Copyright © 2016 Eli Grey.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ </pre>
+
+ <h4>JavaScriptDateFormat (<a href="https://github.com/JDMcKinstry/JavaScriptDateFormat/">homepage</a>)</h4>
+ <pre style="white-space: pre-line;">
+Unknown license. Permission granted at: http://stackoverflow.com/a/9229385/1121392.
+ </pre>
+ </details>
+ </div>
+ <script src="js/hotkey_storage.js"></script>
+ <script src="js/ui.js"></script>
+ <script src="js/options.js"></script>
+</body>
+</html>
diff --git a/src/popup.html b/src/popup.html
new file mode 100644
index 0000000..ac3de6d
--- /dev/null
+++ b/src/popup.html
@@ -0,0 +1,129 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<style type="text/css">
+body {
+ font-family: arial, Verdana;
+ font-size: 12px;
+ width: auto;
+ overflow: visible;
+ margin-bottom: 2px;
+}
+.menu {
+ color: #3d5d6a;
+ line-height: 26px;
+ text-indent: .5em;
+ float: left;
+ white-space: nowrap;
+ background-repeat: no-repeat no-repeat;
+ background-position: 6px 5px;
+}
+.text {
+ float: left;
+ margin-left: 26px;
+}
+.shortcut {
+ color: #A1A192;
+ float: left;
+ margin-left: 26px;
+ display: none;
+}
+.menuI {
+ display: none;
+}
+ul {
+ list-style: none;
+ margin: 0px;
+ padding: 0px;
+}
+li {
+ list-style: none;
+ cursor: pointer;
+ margin: 0px;
+ padding: 0px;
+ clear: both;
+ overflow: hidden;
+ border: 1px solid white;
+}
+li:hover {
+ border-radius: 3px 3px;
+ background: -webkit-gradient(linear, left top, left bottom,
+ color-stop(0, #fff), color-stop(0.4, hsl(215, 67%, 97%)),
+ color-stop(0.41, hsl(213, 48%, 95%)));
+ border: solid 1px #c5cdd3;
+}
+#loadDiv {
+ color: #3d5d6a;
+ display: none;
+ font-weight: bold;
+ font-family: Verdana, simsun;
+ font-size: 12px;
+ margin-top: 20px;
+ line-height: 40px;
+ text-align: center;
+}
+#tip {
+ color: #3d5d6a;
+ font-size: 12px;
+ font-family: Verdana, simsun;
+ text-align: left;
+}
+.separator {
+ display: none;
+ width: 100%;
+ border-top: 1px solid #ddd;
+ margin: 5px 0;
+}
+#option {
+ display: none;
+}
+#optionItem {
+ margin-left: 26px;
+}
+</style>
+</head>
+<body>
+ <div id="loadDiv" ><img src="images/loading.gif" alt="">
+ <div id="capturing"></div>
+ </div>
+ <div id="tip"></div>
+ <div id="item">
+ <ul>
+ <li id="captureSpecialPageItem" class="menuI">
+ <div class="menu" id="captureSpecialPage"
+ style="background-image: url(images/screen.png);">
+ <span class="text" id="captureSpecialPageText"></span>
+ </div>
+ </li>
+ <li id="captureAreaItem" class="menuI">
+ <div class="menu" id="captureArea"
+ style="background-image: url(images/custom.png);">
+ <span class="text" id="captureAreaText"></span>
+ <span class="shortcut" id="captureAreaShortcut"></span>
+ </div>
+ </li>
+ <li id="captureWindowItem" class="menuI">
+ <div class="menu" id="captureWindow"
+ style="background-image: url(images/screen.png);">
+ <span class="text" id="captureWindowText"></span>
+ <span class="shortcut" id="captureWindowShortcut"></span>
+ </div>
+ </li>
+ <li id="captureWebpageItem" class="menuI">
+ <div class="menu" id="captureWebpage"
+ style="background-image: url(images/whole.png);">
+ <span class="text" id="captureWebpageText"></span>
+ <span class="shortcut" id="captureWebpageShortcut"</span>
+ </div>
+ </li>
+ <div id="separatorItem" class="separator"></div>
+ <li id="option">
+ <div id="optionItem" class="menu"></div>
+ </li>
+ </ul>
+ </div>
+ <script src="js/hotkey_storage.js"></script>
+ <script src="js/popup.js"></script>
+</body>
+</html>
diff --git a/src/showimage.css b/src/showimage.css
new file mode 100644
index 0000000..2a4c085
--- /dev/null
+++ b/src/showimage.css
@@ -0,0 +1,538 @@
+body {
+ font-size: 12px;
+ font-family: arial, Verdana;
+ margin: 0;
+ padding: 0;
+ background: #959595;
+}
+#header {
+ background: url(images/toolbar_bg.png);
+ height: 36px;
+ overflow: hidden;
+}
+#showBox {
+ overflow: auto;
+}
+#photo {
+ cursor: crosshair;
+ margin: 0;
+ padding: 0;
+ position: relative;
+}
+#tip {
+ color: #333333;
+ padding: 0 5px;
+ height: 36px;
+ font-size: 13px;
+ line-height: 36px;
+ overflow: hidden;
+ text-align: left;
+ margin: 0 180px 0 300px;
+}
+.toolbar {
+ position:fixed;
+ height: 30px;
+ margin-left: 10px;
+ padding-top: 6px;
+ z-index: 100;
+}
+.toolbar ul {
+ margin: 0px;
+ padding: 0px;
+ list-style: none;
+}
+.toolBar ul li {
+ cursor: pointer;
+ margin-right: 8px;
+ float: left;
+ background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#FEFEFE), to(#CDDCF0));
+ border: 1px solid #788390;
+ border-bottom-left-radius: 3px 3px;
+ border-bottom-right-radius: 3px 3px;
+ border-top-left-radius: 3px 3px;
+ border-top-right-radius: 3px 3px;
+ color:#333333;
+ height: 18px;
+ line-height: 18px;
+ padding: 2px 8px 2px 8px;
+ position: relative;
+}
+.toolBar ul li:hover {
+ background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#CDDCF0), to(#FEFEFE));
+ border: 1px solid #333333;
+}
+.toolbar ul li.mark {
+ background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#FEFEFE), to(#FEFEFE));
+ border: 1px solid #333333;
+}
+.toolbar li .tool {
+ float: none;
+ border: 1px #333333 solid;
+ display: none;
+}
+.toolbar li:hover .tool {
+ display: block;
+}
+.toolbar li img {
+ position: relative;
+ top: 2px;
+}
+.layer {
+ font-size:12px;
+ font-family: arial;
+ height: 0;
+ position: absolute;
+ width: 0;
+ z-index: 100;
+ line-height: 22px;
+ word-wrap: break-word;
+ text-align: left;
+ outline: 0;
+}
+.closeButton {
+ position: absolute;
+ display: none;
+ cursor: pointer;
+ top: -15px;
+}
+.clear {
+ clear: both;
+}
+.tool {
+ position: absolute;
+ border-radius: 3px 3px;
+ border:1px solid #cccccc;
+ left: -1;
+ top: 23px;
+ padding:3px;
+ padding:2px 2px 2px 2px;
+ background: #ffffff;
+ z-index: 9999;
+ -webkit-box-shadow: #8B8B8B 0px 4px 10px;
+ min-width: 90px;
+}
+#colorpad {
+ word-wrap: break-word;
+ word-break: normal;
+ background-color: #ffffff;
+ padding: 4px;
+ width: 116px;
+}
+#colorpad a {
+ margin: 1px 3px 2px 0;
+ display: block;
+ height: 14px;
+ width: 14px;
+ float: left;
+}
+#colorpad a:nth-child(7n) {
+ margin-right: 0;
+}
+.colorBox, .ellipseBox {
+ float: left;
+ margin:3px 3px 0 0;
+ height:10px;
+ width:10px;
+ border:1px solid #333333;
+}
+#lineIconCav {
+ float: left;
+ margin:2px 2px 0 0;
+}
+.rectBox {
+ float: left;
+ margin:3px 3px 0 0;
+ height:12px;
+ width:12px;
+ background: #000000;
+}
+.blurBox {
+ float: left;
+ margin:3px 10px 0 5px;
+ -webkit-box-shadow:#333333 0px 6px 5px 5px;
+}
+#mask-canvas {
+ position: absolute;
+ left: 0px;
+ top: 0px;
+}
+.tool-option {
+ text-indent: 1.5em;
+ line-height: normal;
+}
+.tool-option div {
+ padding: 4px 0;
+ cursor: pointer;
+}
+.tool-option div:hover {
+ background-color: #ffffcc;
+}
+.mark {
+ background: url(images/mark.png) no-repeat 5px 50%;
+}
+.tool-option-line {
+ margin-top: 5px;
+ border-top:1px #ccc solid;
+}
+.tool-option-line div{
+ float: left;
+ padding:5px;
+ margin:2px;
+ border:1px #ffffff solid;
+}
+.tool-option-line .mark{
+ border:1px #cccccc solid;
+ background: #ffffcc;
+}
+.tool-option-line div:hover {
+ border:1px #cccccc solid;
+ background-color: #ffffcc;
+}
+.ellipse {
+ width: 9px;
+ height: 8px;
+ display: block;
+ border-radius: 8px 8px;
+ border: 1px solid #2a5db0;
+}
+.radiusRect {
+ width: 9px;
+ height: 8px;
+ display: block;
+ border-radius:3px 3px;
+ border: 1px solid #2a5db0;
+}
+.rect {
+ width: 9px;
+ height: 8px;
+ display: block;
+ border: 1px solid #2a5db0;
+}
+.tip_succeed, .tip_failed{
+ position :fixed;
+ border-radius: 4px 4px;
+ height: 30px;
+ line-height: 30px;
+ top: 5px;
+ margin: 0 auto;
+ padding: 0 10px;
+ font-size: 12px;
+}
+.tip_succeed {
+ background: #fff1a8;
+ color: #000000;
+}
+.tip_failed {
+ color: #ffffff;
+ background: #ff8080;
+}
+#selectuploadposition {
+ border: 1px solid #ccc;
+ width: 300px;
+ height: 200px;
+ padding: 5px;
+ position: absolute;
+ z-index: 1000;
+ left: 40%;
+ top: 25%;
+ display: none;
+ -webkit-box-shadow: #cccccc 0 0 7px 1px;
+ background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#d0dff2), to(#f5f9fc));
+}
+.selectcontent
+{
+ background-color: White;
+ width:300px;
+ height:168px;
+}
+button {
+ cursor: pointer;
+}
+input {
+ cursor: pointer;
+}
+#uploadWrapper {
+ width: 380px;
+ min-height: 255px;
+ border: 1px solid #8ec3eb;
+ border-radius: 5px;
+ position: absolute;
+ background: white;
+ box-shadow: 0 0 5px 1px #a0a0a0;
+ display: none;
+ z-index: 10000;
+}
+#uploadHeader {
+ border-bottom: 1px solid #aebece;
+ height: 33px;
+ font-size: 14px;
+ font-weight: bold;
+ color: #004b80;
+ background: -webkit-gradient(linear, left top, left bottom, from(#e4f0ff), to(#b5cce8));
+}
+#uploadBody {
+ background: white url("images/popup_bg.jpg") left top no-repeat;
+ min-height: 160px;
+ padding: 10px 0;
+}
+#uploadFooter {
+ background: -webkit-gradient(linear, left top, left bottom, from(#e2effe), to(#cee1f9));
+ height: 36px;
+ text-align: right;
+ padding-right: 15px;
+}
+#uploadFooter button {
+ width: 72px;
+ height: 26px;
+ vertical-align: middle;
+ border:1px solid #8ec3eb;
+ color: #1c5f8e;
+ border-radius: 3px;
+ background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#ededed));
+}
+#uploadFooter button:hover {
+ background: -webkit-gradient(linear, left top, left bottom, from(#ededed), to(#fff));
+}
+.verticalAlignMiddle {
+ display: table-cell;
+ vertical-align: middle;
+ height: inherit;
+}
+#uploadSitesWrapper {
+ text-align: center;
+ height: 185px;
+}
+.btn {
+ display: inline-block;
+ text-align: center;
+ background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0ecfb));
+ border: 1px solid #c0cddd;
+ cursor: pointer;
+ border-radius: 3px;
+}
+.btn:hover {
+ background: -webkit-gradient(linear, left top, left bottom, from(#e0ecfb), to(#fff));
+ border: 1px solid #8ec3eb;
+}
+#uploadSitesWrapper > span.btn {
+ display: inline-block;
+ font-size: 18px;
+ height: 40px;
+ width: 238px;
+ text-align: center;
+ color: #1c68a2;
+ border-radius: 4px;
+ margin-bottom: 15px;
+}
+#uploadsiteswrapper > span.btn:last-child {
+ margin-bottom: 0;
+}
+#uploadSitesWrapper > span.btn > span.uploadSite > * {
+ vertical-align: middle;
+}
+.uploadSite {
+ display: inline-block;
+ text-align: left;
+ width: 185px;
+ line-height: 38px;
+}
+#uploadContentWrapper {
+ font-weight: bold;
+ color: #1c68a2;
+}
+#uploadContentWrapper > div {
+ text-align: left;
+ padding: 25px 0 0 30px;
+}
+#imageCaption {
+ width: 240px;
+ height: 23px;
+ padding-left: 5px;
+ outline-color: #cbecff;
+ margin-left: 10px;
+ border: 1px solid #aebece;
+ background: -webkit-gradient(linear, left top, left bottom, from(#eefbfa), to(#fff));
+ cursor: auto;
+ resize: none;
+}
+ul {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+.accountItem {
+ padding-bottom: 10px;
+}
+.accountItem > *{
+ margin-right: 6px;
+}
+.account {
+ overflow: hidden;
+ display: inline-block;
+ white-space: nowrap;
+}
+.account > *, .shareToText {
+ vertical-align: middle;
+}
+.account:hover > img{
+ visibility: visible;
+}
+.account img {
+ visibility: hidden;
+ cursor: pointer;
+}
+.accountName {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ margin-right: 10px;
+ display: inline-block;
+ max-width: 145px;
+ border-bottom: 1px solid #fff;
+}
+.accountName:hover {
+ border-bottom: 1px solid #1c68a2;
+ cursor: pointer;
+}
+.progressBar {
+ display: inline-block;
+ vertical-align: middle;
+ width: 41px;
+ height: 12px;
+ background: white url("images/loading_icon.gif") 0 0 no-repeat;
+}
+.photoLink {
+ color: #0091ff;
+ margin-left: 42px;
+ display: none;
+}
+#shareToOtherAccount {
+ display: inline-block;
+ margin-top: 6px;
+ cursor: pointer;
+ border-bottom: 1px solid #fff;
+}
+#shareToOtherAccount:hover {
+ border-bottom: 1px solid #1c68a2;
+}
+.uploadInfo {
+ display: inline-block;
+ vertical-align: middle;
+ font-weight: normal;
+}
+#overlay {
+ position: absolute;
+ opacity: 0.3;
+ background: #000;
+ display: none;
+ z-index: 200;
+ top: 0;
+ left: 0;
+}
+#requiredFlag {
+ color: red;
+}
+#uploadcontentwrapper > #errorWrapper {
+ display: none;
+ position: absolute;
+ padding: 0;
+ text-align: center;
+ width: 380px;
+ height: 25px;
+ margin-bottom: -15px;
+}
+#errorInfo {
+ display: inline-block;
+ padding: 4px 15px;
+ background: #fffed5;
+ color: red;
+ border-bottom-left-radius: 5px;
+ border-bottom-right-radius: 5px;
+ font-weight: normal;
+ white-space: nowrap;
+ max-width: 350px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+#authenticationProgress {
+ margin-left: 40px;
+ display: none;
+}
+#btnMore {
+ padding: 6px 0;
+ height: 10px;
+ width: 22px;
+ text-align: center;
+ -webkit-transition: background-image .2s ease-in 0.03s;
+}
+#btnMore:hover > #more-tools {
+ visibility: visible;
+ opacity: 1;
+ top: 28px;
+}
+#btnMore > img {
+ -webkit-transition: -webkit-transform .2s ease-in 0.03s;
+}
+#btnMore:hover > img {
+ -webkit-transform: rotate(180deg);
+ top: 0;
+}
+#more-tools {
+ visibility: hidden;
+ opacity: 0;
+ position: absolute;
+ min-width: 64px;
+ border: 1px solid #788390;
+ border-radius: 5px;
+ background: #fff;
+ padding: 8px 9px;
+ right: -1px;
+ top: 20px;
+ box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.2);
+ z-index: 1000;
+ -webkit-transition: opacity .25s ease-in 0.03s, top .25s ease-in 0.03s,
+ visibility .25s ease-in;
+}
+#more-tools::before {
+ content: "◤";
+ color: #fff;
+ position: absolute;
+ -webkit-transform: rotate(45deg);
+ right: 5px;
+ top: -9px;
+}
+#more-tools > ul > li {
+ float: none;
+ background: none;
+ text-align: left;
+ margin-top: 8px;
+ margin-right: 0;
+ border: 1px solid transparent;
+ padding: 3px 6px;
+ white-space: nowrap;
+}
+#more-tools > ul > li:first-child {
+ margin-top: 0;
+}
+#more-tools > ul > li > img {
+ margin-right: 4px;
+ top: 0;
+}
+#more-tools > ul > li > * {
+ vertical-align: middle;
+}
+#more-tools > ul > li:hover {
+ border: 1px solid #788390;
+ border-radius: 3px;
+ background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fefefe),
+ to(#cddcf0));
+}
+div.tip_succeed > a {
+ text-decoration: underline;
+ display: inline-block;
+ color: blue;
+ cursor: pointer;
+ max-width: 300px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
diff --git a/src/showimage.html b/src/showimage.html
new file mode 100644
index 0000000..745bd7c
--- /dev/null
+++ b/src/showimage.html
@@ -0,0 +1,191 @@
+<!--
+ * Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+ * source code is governed by a BSD-style license that can be found in the
+ * LICENSE file.
+-->
+<html>
+<head>
+<title>Screen Capture</title>
+<meta charset="UTF-8">
+<link href="showimage.css" rel="stylesheet" type="text/css">
+</head>
+<body>
+ <div id="header">
+ <div id="toolbar" class="toolbar">
+ <ul>
+ <li id="btnHighlight" class="mark">
+ <div class="colorBox" id="highlightColorBox"></div>
+ <span id="tHighlight">Highlight</span>
+ <div class="tool" id="highlightTool">
+ <div id="highlightColorPad"></div>
+ <div class="clear"></div>
+ <div class="tool-option" style="border-top: 1px solid #ccc; padding-top:2px; margin-top:5px;">
+ <div id="borderMode"><span id="border"></span></div>
+ <div id="rectMode"><span id="rect"></span></div>
+ </div>
+ <div class="tool-option-line">
+ <div id="rectangle" class="mark"><span class="rect"></span></div>
+ <div id="radiusRectangle"><span class="radiusRect"></span></div>
+ <div id="ellipse"><span class="ellipse"></span></div>
+ </div>
+ </div>
+ </li>
+ <li id="btnBlackout">
+ <div class="blurBox" id="blackoutBox"></div>
+ <span id="tRedact">Redact</span>
+ <div class="tool tool-option">
+ <div id="redact" class="mark"><span id="redactText"></span></div>
+ <div id="blur" ><span id="blurText"></span></div>
+ </div>
+ </li>
+ <li id="btnText">
+ <div class="colorBox" id="fontColorBox"></div>
+ <span id="tText">Text</span>
+ <div class="tool" id="fontTool">
+ <div id="fontColorPad"></div>
+ <div class="clear"></div>
+ <div class="tool-option" style="border-top: 1px solid #ccc; padding:2px 0 0 0; margin-top:5px">
+ <div id="size_10" style="font-size: 10px"></div>
+ <div id="size_16" style="font-size: 16px"></div>
+ <div id="size_18" style="font-size: 18px"></div>
+ <div id="size_32" class="mark" style="font-size: 32px"></div>
+ </div>
+ </div>
+ </li>
+ <li id="btnLine">
+ <canvas id="lineIconCav" width="14" height="14" ></canvas>
+ <span id="lineText"></span>
+ <div class="tool">
+ <div id="lineColorPad"></div>
+ <div class="clear"></div>
+ <div class="tool-option-line">
+ <div id="straightLine"><img src="images/line.png" alt=""> </div>
+ <div id="arrow"><img src="images/arrow.png" alt=""> </div>
+ </div>
+ </div>
+ </li>
+ </ul>
+ </div>
+ <div id="tip"></div>
+ <div class="toolbar" id="okBtn" style="position:fixed; right:30px; top:0px;">
+ <ul>
+ <li id="btnSave">
+ <img src="images/icon_save.png" alt="">
+ <span id="tSave"></span>
+ </li>
+ <li id="btnClose">
+ <img src="images/icon_close.png" alt="">
+ <span id="tClose">Close</span>
+ </li>
+ </ul>
+ </div>
+ <div class="clear"></div>
+ </div>
+ <div id="showBox">
+ <div id="photo">
+ <canvas id="canvas"></canvas>
+ <canvas id="mask-canvas"></canvas>
+ <div class="layer" id="layer0"></div>
+ </div>
+ </div>
+ <div id='selectuploadposition'>
+ <div class="selectcontent" >
+ <div id="selectPrompt" style="padding:30px 0 0 30px; "></div>
+ <div style="padding: 5px 0 0 30px;">
+ <div style="padding: 5px 5px;"><input id="selectSina" type= "radio"><span id="sina"></span></div>
+ <div style="padding: 5px 5px;"><input id="selectFacebook" type= "radio"><span id="facebook"></span></div>
+ </div>
+ </div>
+ <div style="float: right; margin: 5px 15px 0 0;"><button id="uploadFile"></button><button id="cancelUpload"></button></div>
+ </div>
+ <img id="pic" src="#" style="display:none" />
+ <div id="uploadWrapper" style="display: none;">
+ <div id="uploadHeader">
+ <div style="height: 31px; padding-left: 12px; border: 1px solid white; border-top-left-radius: 3px; border-top-right-radius: 3px;">
+ <span class="verticalAlignMiddle"></span>
+ </div>
+ </div>
+ <div id="uploadBody">
+ <div id="uploadSitesWrapper" class="verticalAlignMiddle">
+ <span class="btn" id="picasaBtn">
+ <span class="uploadSite">
+ <img src="images/picasa_icon.png" alt="picasa">
+ <span id="shareToPicasaText"></span>
+ </span>
+ </span>
+ <span class="btn" id="facebookBtn">
+ <span class="uploadSite">
+ <img src="images/facebook_icon.png" alt="facebook">
+ <span id="shareToFacebookText"></span>
+ </span>
+ </span>
+ <span class="btn" id="sinaMicroblogBtn">
+ <span class="uploadSite">
+ <img src="images/sina_icon.png" alt="sina">
+ <span id="shareToSinaMicroblogText"></span>
+ </span>
+ </span>
+ <span class="btn" id="imgurBtn">
+ <span class="uploadSite">
+ <img src="images/imgur_icon.png" alt="imgur">
+ <span id="shareToImgurText"></span>
+ </span>
+ </span>
+ </div>
+ <div id="uploadContentWrapper" style="display:none;">
+ <div id="errorWrapper">
+ <div id="errorInfo"></div>
+ </div>
+ <div id="captionWrapper" style="font-size: 14px; padding-top: 28px;">
+ <label id="imageCaptionText" for="imageCaption"></label>
+ <input type="text" maxLength="140" id="imageCaption" autofocus="true">
+ <span id="requiredFlag">*</span>
+ </div>
+ <div id="uploadAccountWrapper" style="min-height: 60px;">
+ <ul id="uploadAccountList"></ul>
+ <div id="shareToOtherAccount"></div>
+ <span id="authenticationProgress" class="progressBar"></span>
+ <div id="accountItemTemplate" style="display: none;">
+ <li id="${accountId}" class="accountItem">
+ <span style="display: inline-block;">
+ <span class="account">
+ <span id="accountName" class="accountName" title="${accountName}" data-site="${site}" data-user-id="${userId}">${accountName}</span>
+ <img id="deleteBtn" class="deleteBtn" src="images/delete_account_icon.png" alt="Delete" title="${deletionTitle}" data-account-id="${accountId}">
+ </span>
+ </span>
+ <span class="progressBar" style="display: none;" title="${progressInfo}"></span>
+ <span class="uploadInfo" style="display: none;"></span>
+ <span style="float: right; margin-right: 35px; font-weight: normal;">
+ <a class="photoLink" target="_blank" href="#">${photoLinkText}</a>
+ </span>
+ </li>
+ </div>
+ </div>
+ <div id="photoSizeTip" style="color: #a4a4a4; font-weight: normal; margin-bottom: 15px;"></div>
+ </div>
+ </div>
+ <div id="uploadFooter">
+ <span style="display: inline-block; height: inherit;">
+ <span class="verticalAlignMiddle">
+ <button id="lastStep" style="display: none;"></button>
+ <button id="closeUploadWrapper"></button>
+ </span>
+ </span>
+ </div>
+ </div>
+ <div id="overlay"></div>
+ <script src="lib/FileSaver.min.js"></script>
+ <script src="js/editor.js"></script>
+ <script src="js/sha1.js"></script>
+ <script src="js/oauth.js"></script>
+ <script src="js/ajax.js"></script>
+ <script src="js/account.js"></script>
+ <script src="js/ui.js"></script>
+ <script src="js/upload_ui.js"></script>
+ <script src="js/facebook.js"></script>
+ <script src="js/sina_microblog.js"></script>
+ <script src="js/picasa.js"></script>
+ <script src="js/imgur.js"></script>
+ <script src="js/showimage.js"></script>
+</body>
+</html>
diff --git a/src/style.css b/src/style.css
new file mode 100644
index 0000000..ca029b2
--- /dev/null
+++ b/src/style.css
@@ -0,0 +1,154 @@
+#sc_drag_area_protector div{
+ border-radius: 0px 0px;
+ margin:0;
+ /*max-width:100%;*/
+ min-width: 1px;
+
+}
+
+#sc_drag_area {
+ height:100px;
+ left:150px;
+ position: absolute;
+ top:100px;
+ width:250px;
+ z-index: 9999;
+}
+#sc_drag_container {
+ border: 1px solid #0000FF;
+ cursor: move ;
+ height: 100% ;
+ margin: 0;
+ overflow: hidden;
+ padding: 0;
+ position: relative ;
+ width: 100%;
+ z-index:9997;
+}
+#sc_drag_area_protector {
+ border-radius: 0px 0px;
+ display: block;
+ height:100%;
+ left:0;
+ top:0;
+ position: absolute;
+ width:100%;
+ z-index:8500;
+ margin: 0;
+
+ min-width: 1px;
+ overflow: hidden;
+}
+#sc_drag_size {
+ background-color: rgba(44, 44, 44, 0.5);
+ color:#ffffff;
+ font-family: arial,san-serif;
+ font-weight:bold;
+ font-size:12px;
+ height:18px;
+ min-width:65px !important;
+ left:12px;
+ line-height:18px;
+ position:absolute;
+ padding-left:4px;
+ padding-right:4px;
+ text-align:center;
+ top: -18px;
+ z-index:9998;
+}
+#sc_drag_cancel, #sc_drag_crop {
+ background-color: rgba(0, 0, 0, 0.5);
+ cursor:pointer;
+ color:#ffffff;
+ font-size:12px;
+ font-family: arial,san-serif;
+ font-weight:bold;
+ height:22px;
+ line-height:22px;
+ position:absolute ;
+ z-index:9998
+}
+#sc_drag_crop {
+ bottom:-25px;
+ text-align: center;
+ right:10px;
+ min-width: 30px !important;
+ padding: 0 10px;
+}
+#sc_drag_cancel {
+ bottom:-25px;
+ text-align: center;
+ right:70px;
+ min-width: 30px !important;
+ padding: 0 10px;
+}
+#sc_drag_shadow_top, #sc_drag_shadow_bottom, #sc_drag_shadow_left, #sc_drag_shadow_right {
+ background-color: #000000;
+ opacity: 0.5;
+ position: absolute;
+ z-index:7000;
+ border: 0;
+}
+#sc_drag_shadow_top {
+ left: 0;
+ top: 0;
+}
+#sc_drag_shadow_bottom {
+ bottom: 0;
+ right: 0;
+}
+#sc_drag_shadow_left {
+ bottom: 0;
+ left: 0;
+}
+#sc_drag_shadow_right {
+ right: 0;
+ top: 0;
+}
+#sc_drag_north_east, #sc_drag_north_west, #sc_drag_south_east, #sc_drag_south_west {
+ border:1px solid #FFFFFF;
+ background-color: #0000FF;
+ height: 5px;
+ position: absolute;
+ width: 5px;
+ z-index:9998;
+}
+#sc_drag_north_east {
+ cursor: ne-resize ;
+ right: -4px ;
+ top: -3px;
+}
+#sc_drag_north_west {
+ cursor: nw-resize ;
+ left: -3px ;
+ top: -3px;
+}
+#sc_drag_south_east {
+ bottom: -4px;
+ cursor: se-resize ;
+ right: -4px ;
+}
+#sc_drag_south_west {
+ bottom: -4px;
+ cursor: sw-resize ;
+ left: -3px ;
+}
+
+.sc_tip_save_status {
+ position :fixed;
+ border-radius: 4px 4px;
+ height: 30px;
+ line-height: 30px;
+ text-indent: 1em;
+ width: 200px;
+ background: #fff1a8;
+ color: #000000;
+ top:5px;
+ left:45%;
+ font-size: 12px;
+}
+
+.sc_tip_save_status a{
+ text-decoration: underline;
+ color: #2A5DB0;
+}