Bug 1159744 - Use the panel implementations from the Pocket add-on for the Pocket feature. r=dolske

This commit is contained in:
Jared Wein 2015-05-01 17:16:42 -04:00
parent 067587ac3e
commit 06a33771c5
47 changed files with 4514 additions and 293 deletions

View File

@ -1920,6 +1920,6 @@ pref("dom.serviceWorkers.enabled", true);
pref("browser.pocket.enabled", false);
pref("browser.pocket.hostname", "localhost");
pref("browser.pocket.removedByUser", false);
pref("browser.pocket.oAuthConsumerKey", "40249-e88c401e1b1f2242d9e441c4");
pref("browser.pocket.useLocaleList", true);
pref("browser.pocket.enabledLocales", "en-US");

View File

@ -1300,4 +1300,7 @@
# starting with an empty iframe here in browser.xul from a Ts standpoint.
</deck>
<script type="application/javascript" src="chrome://browser/content/pocket/pktApi.js"/>
<script type="application/javascript" src="chrome://browser/content/pocket/main.js"/>
</window>

View File

@ -23,7 +23,9 @@ const kWhitelist = [
{sourceName: /loop\/.*sdk-content\/.*\.css$/i},
// Highlighter CSS uses chrome-only pseudo-class, see bug 985597.
{sourceName: /highlighter\.css/i,
errorMessage: /Unknown pseudo-class.*moz-native-anonymous/i}
errorMessage: /Unknown pseudo-class.*moz-native-anonymous/i},
// Tracked in bug 1160629.
{sourceName: /pocket\/panels\/css\/.*\.css/i},
];
let moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm");

View File

@ -26,8 +26,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
"resource://gre/modules/CharsetMenu.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
"resource://gre/modules/ReaderMode.jsm");
XPCOMUtils.defineLazyGetter(this, "CharsetBundle", function() {
const kCharsetBundle = "chrome://global/locale/charsetMenu.properties";
@ -1087,128 +1085,8 @@ if (Services.prefs.getBoolPref("browser.pocket.enabled")) {
viewId: "PanelUI-pocketView",
label: PocketBundle.GetStringFromName("pocket-button.label"),
tooltiptext: PocketBundle.GetStringFromName("pocket-button.tooltiptext"),
onCreated(node) {
let doc = node.ownerDocument;
let elementsNeedingStrings = [
"pocket-header",
"pocket-login-required-tagline",
"pocket-signup-with-fxa",
"pocket-signup-with-email",
"pocket-account-question",
"pocket-login-now",
"pocket-page-saved-header",
"pocket-open-pocket",
"pocket-remove-page",
"pocket-page-tags-field",
"pocket-page-tags-add",
"pocket-page-suggested-tags-header",
"pocket-signup-or",
];
for (let elementId of elementsNeedingStrings) {
let el = doc.getElementById(elementId);
let string = PocketBundle.GetStringFromName(elementId);
switch (el.localName) {
case "button":
el.label = string;
break;
case "textbox":
el.setAttribute("placeholder", string);
break;
default:
el.textContent = string;
break;
}
}
let addTagsField = doc.getElementById("pocket-page-tags-field");
let addTagsButton = doc.getElementById("pocket-page-tags-add");
addTagsField.addEventListener("input", this);
addTagsButton.addEventListener("command", this);
},
onViewShowing(event) {
let doc = event.target.ownerDocument;
let loginView = doc.getElementById("pocket-login-required");
let pageSavedView = doc.getElementById("pocket-page-saved");
let showPageSaved = Pocket.isLoggedIn;
loginView.hidden = showPageSaved;
pageSavedView.hidden = !showPageSaved;
if (!showPageSaved)
return;
let gBrowser = doc.defaultView.gBrowser;
let uri = gBrowser.currentURI;
if (uri.schemeIs("about"))
uri = ReaderMode.getOriginalUrl(uri.spec);
else
uri = uri.spec;
if (!uri)
return; //TODO should prevent the panel from showing
Pocket.save(uri, gBrowser.contentTitle).then(
item => {
doc.getElementById("pocket-remove-page").itemId = item.item_id;
},
error => {dump(error + "\n");}
);
},
onViewHiding(event) {
let doc = event.target.ownerDocument;
doc.getElementById("pocket-remove-page").itemId = null;
},
handleEvent: function(event) {
let doc = event.target.ownerDocument;
let field = doc.getElementById("pocket-page-tags-field");
let button = doc.getElementById("pocket-page-tags-add");
switch (event.type) {
case "input":
button.disabled = !field.value.trim();
break;
case "command":
Pocket.tag(doc.getElementById("pocket-remove-page").itemId,
field.value);
field.value = "";
break;
}
},
USER_REMOVED_PREF: "browser.pocket.removedByUser",
onWidgetAdded(aWidgetId, aArea, aPosition) {
if (aWidgetId != this.id) {
return;
}
let placement = CustomizableUI.getPlacementOfWidget(this.id);
let widgetInUI = placement && placement.area;
Services.prefs.setBoolPref(this.USER_REMOVED_PREF, !widgetInUI);
},
onWidgetRemoved(aWidgetId, aArea) {
if (aWidgetId != this.id) {
return;
}
let placement = CustomizableUI.getPlacementOfWidget(this.id);
let widgetInUI = placement && placement.area;
Services.prefs.setBoolPref(this.USER_REMOVED_PREF, !widgetInUI);
},
onWidgetReset(aNode, aContainer) {
if (aNode.id != this.id) {
return;
}
Services.prefs.setBoolPref(this.USER_REMOVED_PREF, !aContainer);
},
onWidgetUndoMove(aNode, aContainer) {
if (aNode.id != this.id) {
return;
}
Services.prefs.setBoolPref(this.USER_REMOVED_PREF, !aContainer);
}
onViewShowing: Pocket.onPanelViewShowing,
onViewHiding: Pocket.onPanelViewHiding,
};
CustomizableWidgets.push(pocketButton);

View File

@ -231,40 +231,9 @@
<panelview id="PanelUI-pocketView" flex="1">
<vbox class="panel-subview-body">
<label id="pocket-header"/>
<vbox id="pocket-login-required" hidden="true">
<label id="pocket-login-required-tagline" class="pocket-subheader"/>
<description id="pocket-fxa-options">
<button id="pocket-signup-with-fxa" class="pocket-button"/>
<label id="pocket-signup-or"/>
<button id="pocket-signup-with-email" class="pocket-button"/>
</description>
<label id="pocket-account-question"/>
<label id="pocket-login-now" class="text-link"/>
</vbox>
<vbox id="pocket-page-saved" hidden="true">
<label id="pocket-page-saved-header" class="pocket-header"/>
<hbox id="pocket-page-saved-next-steps" pack="center">
<label id="pocket-open-pocket" class="text-link"/>
<label id="pocket-remove-page" class="text-link"
onclick="Pocket.remove(this.itemId);"/>
</hbox>
<hbox id="pocket-separator">
<box class="pocket-separator-colorstop"/>
<box class="pocket-separator-colorstop"/>
<box class="pocket-separator-colorstop"/>
<box class="pocket-separator-colorstop"/>
</hbox>
<vbox id="pocket-page-tags">
<hbox id="pocket-page-tags-form">
<textbox id="pocket-page-tags-field"
flex="1"/>
<button id="pocket-page-tags-add" disabled="true"/>
</hbox>
<label id="pocket-page-suggested-tags-header"/>
<description id="pocket-page-suggested-tags"/>
</vbox>
</vbox>
<hbox id="pocket-panel-container" align="top" flex="1">
<iframe id="pocket-panel-iframe" type="content"/>
</hbox>
</vbox>
</panelview>

View File

@ -9,6 +9,16 @@ this.EXPORTED_SYMBOLS = ["Pocket"];
Cu.import("resource://gre/modules/Http.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "PocketBundle", function() {
const kPocketBundle = "chrome://browser/content/browser-pocket.properties";
return Services.strings.createBundle(kPocketBundle);
});
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
"resource://gre/modules/ReaderMode.jsm");
let Pocket = {
get isLoggedIn() {
@ -105,5 +115,19 @@ let Pocket = {
onError(new Error(error + " - " + errorMessage));
}
});
}
},
/**
* Functions related to the Pocket panel UI.
*/
onPanelViewShowing(event) {
let window = event.target.ownerDocument.defaultView;
window.pktUI.pocketButtonOnCommand(event);
window.pktUI.pocketPanelDidShow(event)
},
onPanelViewHiding(event) {
let window = event.target.ownerDocument.defaultView;
window.pktUI.pocketPanelDidHide(event);
},
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -3,7 +3,35 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
browser.jar:
content/browser/pocket/img/signup_logo.png (img/signup_logo.png)
content/browser/pocket/img/signup_logo@2x.png (img/signup_logo@2x.png)
content/browser/pocket/img/signup_or.png (img/signup_or.png)
content/browser/pocket/img/signup_or@2x.png (img/signup_or@2x.png)
content/browser/pocket/main.js (main.js)
content/browser/pocket/pktApi.js (pktApi.js)
content/browser/pocket/panels/saved.html (panels/saved.html)
content/browser/pocket/panels/signup.html (panels/signup.html)
content/browser/pocket/panels/css/normalize.css (panels/css/normalize.css)
content/browser/pocket/panels/css/firasans.css (panels/css/firasans.css)
content/browser/pocket/panels/css/saved.css (panels/css/saved.css)
content/browser/pocket/panels/css/signup.css (panels/css/signup.css)
content/browser/pocket/panels/fonts/FiraSans-Regular.woff (panels/fonts/FiraSans-Regular.woff)
content/browser/pocket/panels/img/pocketlogo@1x.png (panels/img/pocketlogo@1x.png)
content/browser/pocket/panels/img/pocketlogo@2x.png (panels/img/pocketlogo@2x.png)
content/browser/pocket/panels/img/pocketlogosolo@1x.png (panels/img/pocketlogosolo@1x.png)
content/browser/pocket/panels/img/pocketlogosolo@2x.png (panels/img/pocketlogosolo@2x.png)
content/browser/pocket/panels/img/pocketmultidevices@1x.png (panels/img/pocketmultidevices@1x.png)
content/browser/pocket/panels/img/pocketmultidevices@2x.png (panels/img/pocketmultidevices@2x.png)
content/browser/pocket/panels/img/signup_firefoxlogo@1x.png (panels/img/signup_firefoxlogo@1x.png)
content/browser/pocket/panels/img/signup_firefoxlogo@2x.png (panels/img/signup_firefoxlogo@2x.png)
content/browser/pocket/panels/img/signup_help@1x.png (panels/img/signup_help@1x.png)
content/browser/pocket/panels/img/signup_help@2x.png (panels/img/signup_help@2x.png)
content/browser/pocket/panels/img/tag_close@1x.png (panels/img/tag_close@1x.png)
content/browser/pocket/panels/img/tag_close@2x.png (panels/img/tag_close@2x.png)
content/browser/pocket/panels/js/dictionary.js (panels/js/dictionary.js)
content/browser/pocket/panels/js/messages.js (panels/js/messages.js)
content/browser/pocket/panels/js/saved.js (panels/js/saved.js)
content/browser/pocket/panels/js/signup.js (panels/js/signup.js)
content/browser/pocket/panels/js/tmpl.js (panels/js/tmpl.js)
content/browser/pocket/panels/js/vendor/jquery-2.1.1.min.js (panels/js/vendor/jquery-2.1.1.min.js)
content/browser/pocket/panels/js/vendor/handlebars.runtime.js (panels/js/vendor/handlebars.runtime.js)
content/browser/pocket/panels/js/vendor/jquery.tokeninput.min.js (panels/js/vendor/jquery.tokeninput.min.js)
content/browser/pocket/panels/tmpl/saved_premiumshell.handlebars (panels/tmpl/saved_premiumshell.handlebars)
content/browser/pocket/panels/tmpl/saved_shell.handlebars (panels/tmpl/saved_shell.handlebars)
content/browser/pocket/panels/tmpl/signup_shell.handlebars (panels/tmpl/signup_shell.handlebars)

View File

@ -0,0 +1,723 @@
/*
* LICENSE
*
* POCKET MARKS
*
* Notwithstanding the permitted uses of the Software (as defined below) pursuant to the license set forth below, "Pocket," "Read It Later" and the Pocket icon and logos (collectively, the Pocket Marks) are registered and common law trademarks of Read It Later, Inc. This means that, while you have considerable freedom to redistribute and modify the Software, there are tight restrictions on your ability to use the Pocket Marks. This license does not grant you any rights to use the Pocket Marks except as they are embodied in the Software.
*
* ---
*
* SOFTWARE
*
* 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.
*/
/*
* Pocket UI module
*
* Handles interactions with Pocket buttons, panels and menus.
*
*/
// TODO : Get the toolbar icons from Firefox's build (Nikki needs to give us a red saved icon)
// TODO : [needs clarificaiton from Fx] Firefox's plan was to hide Pocket from context menus until the user logs in. Now that it's an extension I'm wondering if we still need to do this.
// TODO : [needs clarificaiton from Fx] Reader mode (might be a something they need to do since it's in html, need to investigate their code)
// TODO : [needs clarificaiton from Fx] Move prefs within pktApi.s to sqlite or a local file so it's not editable (and is safer)
// TODO : [nice to have] - Immediately save, buffer the actions in a local queue and send (so it works offline, works like our native extensions)
// TODO : Remove console.log entries
var pktUI = (function() {
// -- Initialization (on startup and new windows) -- //
var inited = false;
var currentPanelDidShow, currentPanelDidHide;
var _isHidden = false;
var _notificationTimeout;
var prefBranch = Services.prefs.getBranch("browser.pocket.settings.");
/**
* Initalizes Pocket UI and panels
*/
function onLoad() {
if (inited)
return;
// Install the button (Only on first run, if a user removes the button, you do not want to restore it)
// TODO, only do this if in a certain language
// TODO - Ask Mozilla what the best way is to have this update when a user restores browser defaults
// TODO - this needs to run only on a main window - if the first run happens on a window without the toolbar, then it will never try to run it again
if (!prefBranch.prefHasUserValue('installed')) {
// If user has social add-on installed, uninstall it
if (Social.getManifestByOrigin("https://getpocket.com")) {
Social.uninstallProvider("https://getpocket.com", function(){ /* callback */ });
}
// if user has legacy pkt add-on installed, flag it so we can handle it correctly
prefBranch.setBoolPref('hasLegacyExtension', hasLegacyExtension());
var id = "pocket-menu-button";
var toolbar = document.getElementById("nav-bar");
var before = null;
// Is the bookmarks button in the toolbar?
if (toolbar.currentSet.match("bookmarks-menu-button")) {
var elem = document.getElementById("bookmarks-menu-button");
if (elem)
before = elem.nextElementSibling;
}
// Otherwise, just add it to the end of the toolbar (because before is null)
toolbar.insertItem(id, before);
toolbar.setAttribute("currentset", toolbar.currentSet);
document.persist(toolbar.id, "currentset");
prefBranch.setBoolPref('installed', true);
}
// Context Menu Event
document.getElementById('contentAreaContextMenu').addEventListener("popupshowing", contextOnPopupShowing, false);
// Hide the extension based on certain criteria
hideIntegrationIfNeeded();
inited = true;
}
/**
* Called when window/chrome is unloaded
*/
function onUnload() {
}
/**
* Mark all Pocket integration chrome elements as hidden if certain criteria apply (ex: legacy Pocket extension users or unsupported languages)
*/
function hideIntegrationIfNeeded() {
var hideIntegration = false;
// Check if the user had the legacy extension the last time we looked
if (prefBranch.getBoolPref('hasLegacyExtension')) {
if (hasLegacyExtension()) {
hideIntegration = true; // they still have it, hide new native integration
}
else {
// if they originally had it, but no longer do, then we should remove the pref so we no longer have to check
prefBranch.setBoolPref('hasLegacyExtension', false);
}
}
// TODO
// If language other than launch languages (en-US currently) {
// hideIntegration = true;
//}
// Hide the integration if needed
if (hideIntegration) {
// Note, we don't hide the context menus here, that's handled in contextOnPopupShowing
var elements = ['pocket-menu-button', 'BMB_openPocketWebapp'];
for(var i=0; i<elements.length; i++) {
document.getElementById(elements[i]).setAttribute('hidden', true);
}
_isHidden = true;
}
else
_isHidden = false
}
// -- Event Handling -- //
/**
* Event handler when Pocket toolbar button is pressed
*/
function pocketButtonOnCommand(event) {
tryToSaveCurrentPage();
}
function pocketPanelDidShow(event) {
if (currentPanelDidShow) {
currentPanelDidShow(event);
}
}
function pocketPanelDidHide(event) {
if (currentPanelDidHide) {
currentPanelDidHide(event);
}
// clear the panel
getPanelFrame().setAttribute('src', 'about:blank');
}
/**
* Event handler when Pocket bookmark bar entry is pressed
*/
function pocketBookmarkBarOpenPocketCommand(event) {
openTabWithUrl('https://getpocket.com/a/', true);
}
/**
* Event handler when Pocket context menu button is presed
*/
// Determine which context menus to show before it's shown
function contextOnPopupShowing() {
var saveLinkId = "PKT_context_saveLink";
var savePageId = "PKT_context_savePage";
if (isHidden()) {
gContextMenu.showItem(saveLinkId, false);
gContextMenu.showItem(savePageId, false);
} else if ( (gContextMenu.onSaveableLink || ( gContextMenu.inDirList && gContextMenu.onLink )) ) {
gContextMenu.showItem(saveLinkId, true);
gContextMenu.showItem(savePageId, false);
} else if (gContextMenu.isTextSelected) {
gContextMenu.showItem(saveLinkId, false);
gContextMenu.showItem(savePageId, false);
} else if (!gContextMenu.onTextInput) {
gContextMenu.showItem(saveLinkId, false);
gContextMenu.showItem(savePageId, true);
} else {
gContextMenu.showItem(saveLinkId, false);
gContextMenu.showItem(savePageId, false);
}
}
function pocketContextSaveLinkOnCommand(event) {
// TODO : Unsafe CPOW Usage when saving with Page context menu (Ask Mozilla for help with this one)
var linkNode = gContextMenu.target || document.popupNode;
// Get parent node in case of text nodes (old safari versions)
if (linkNode.nodeType == Node.TEXT_NODE) {
linkNode = linkNode.parentNode;
}
// If for some reason, it's not an element node, abort
if (linkNode.nodeType != Node.ELEMENT_NODE) {
return;
}
// Try to get a link element in the parent chain as we can be in the
// last child element
var currentElement = linkNode;
while (currentElement !== null) {
if (currentElement.nodeType == Node.ELEMENT_NODE &&
currentElement.nodeName.toLowerCase() == 'a')
{
// We have a link element try to save it
linkNode = currentElement;
break;
}
currentElement = currentElement.parentNode;
}
var link = linkNode.href;
tryToSaveUrl(link);
event.stopPropagation();
}
function pocketContextSavePageOnCommand(event) {
tryToSaveCurrentPage();
}
// -- Communication to API -- //
/**
* Either save or attempt to log the user in
*/
function tryToSaveCurrentPage() {
tryToSaveUrl(getCurrentUrl(), getCurrentTitle());
}
function tryToSaveUrl(url, title) {
// If the user is logged in, go ahead and save the current page
if (pktApi.isUserLoggedIn()) {
saveAndShowConfirmation(url, title);
return;
}
// If the user is not logged in, show the logged-out state to prompt them to authenticate
showSignUp();
}
// -- Panel UI -- //
/**
* Show the sign-up panel
*/
function showSignUp() {
showPanel("chrome://browser/content/pocket/panels/signup.html", {
onShow: function() {
// Open and resize the panel
resizePanel({
width: 300,
height: 550
});
},
onHide: panelDidHide,
});
}
/**
* Show the logged-out state / sign-up panel
*/
function saveAndShowConfirmation(url, title) {
// Validate parameter
// TODO: Show some kind of error
if (typeof url === 'undefined') { return; }
if (!url.startsWith("http") && !url.startsWith('https')) { return; };
showPanel("chrome://browser/content/pocket/panels/saved.html?premiumStatus=" + (pktApi.isPremiumUser() ? '1' : '0'), {
onShow: function() {
// Open and resize the panel
resizePanel({
width: 350,
height: 266
});
var options = {
success: function(data, response) {
var item = data.item;
var successResponse = {
status: "success",
item: item
};
sendMessage('saveLink', successResponse);
},
error: function(error, response) {
sendErrorMessage('saveLink', error);
}
}
// Add title if given
if (typeof title !== "undefined") {
options.title = title;
}
// Send the link
pktApi.addLink(url, options);
},
onHide: panelDidHide,
});
}
/**
* Open a generic panel
*/
function showPanel(url, options) {
// We don't have to hide and show the panel again if it's already shown
// as if the user tries to click again on the toolbar button the overlay
// will close instead of the button will be clicked
var iframe = getPanelFrame();
// Register event handlers
registerEventMessages();
// Load the iframe
iframe.setAttribute('src', url);
// Uncomment to leave panel open -- for debugging
// panel.setAttribute('noautohide', true);
// panel.setAttribute('consumeoutsideclicks', false);
//
// For some reason setting onpopupshown and onpopuphidden on the panel directly didn't work, so
// do it this hacky way for now
currentPanelDidShow = options.onShow;
currentPanelDidHide = options.onHide;
}
/**
* Resize the panel
* options = {
* width: ,
* height: ,
* animate [default false]
* }
*/
function resizePanel(options) {
var iframe = getPanelFrame();
iframe.width = options.width;
iframe.height = options.height;
return;
// TODO : Animate the change if given options.animate = true
getPanel().sizeTo(options.width, options.height);
setTimeout(function(){
// we set the iframe size directly because it does not automatically stretch vertically
var height = document.getElementById('pocket-panel-container').clientHeight + 'px';
getPanelFrame().style.height = height;
},1);
}
/**
* Called when the signup and saved panel was hidden
*/
function panelDidHide() {
console.log("Panel did hide");
}
// -- Communication to Panels -- //
// https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Interaction_between_privileged_and_non-privileged_pages
/**
* Register a listener and callback for a specific messageId
*/
function addMessageListener(messageId, callback) {
document.addEventListener('PKT_'+messageId, function(e) {
// ignore to ensure we do not pick up other events in the browser
if (e.target.tagName !== 'PKTMESSAGEFROMPANELELEMENT') {
return;
}
// Send payload to callback
callback(JSON.parse(e.target.getAttribute("payload"))[0]);
// Cleanup the element
e.target.parentNode.removeChild(e.target);
}, false, true);
}
/**
* Remove a message listener
*/
function removeMessageListener(messageId, callback) {
document.removeMessageListener('PKT_'+messageId, callback);
}
/**
* Send a message to the panel's iframe
*/
function sendMessage(messageId, payload) {
var doc = getPanelFrame().contentWindow.document;
var AnswerEvt = doc.createElement("PKTMessage");
AnswerEvt.setAttribute("payload", JSON.stringify([payload]));
doc.documentElement.appendChild(AnswerEvt);
var event = doc.createEvent("HTMLEvents");
event.initEvent('PKT_'+messageId, true, false);
AnswerEvt.dispatchEvent(event);
}
/**
* Helper function to package an error object and send it to the panel iframe as a message response
*/
function sendErrorMessage(messageId, error) {
var errorResponse = {status: "error", error: error.message};
sendMessage(messageId, errorResponse);
}
/**
* Register all of the messages needed for the panels
*/
function registerEventMessages() {
// TODO : There are likely some possible race conditions possible here, for example if the user clicks the button quickly multiple times, due to the async property of the messages, a message may be picked up for an older panel. We should consider updating this to include some sort of panelId that changes per open.
var iframe = getPanelFrame();
// Only register the messages once
if (iframe.getAttribute('did_init') == 1) {
return;
}
iframe.setAttribute('did_init', 1);
// When the panel is displayed it generated an event called
// "show": we will listen for that event and when it happens,
// send our own "show" event to the panel's script, so the
// script can prepare the panel for display.
addMessageListener("show", function(payload) {
// Let panel know that it is ready
sendMessage('show');
});
// Open a new tab with a given url and activate if
addMessageListener("openTabWithUrl", function(payload) {
var activate = true;
if (typeof payload.activate !== "undefined") {
activate = payload.activate;
}
openTabWithUrl(payload.url, activate);
sendMessage("openTabWithUrlResponse", url);
});
// Close the panel
addMessageListener("close", function(payload) {
getPanel().hidePopup();
});
// Send the current url to the panel
addMessageListener("getCurrentURL", function(payload) {
sendMessage('getCurrentURLResponse', getCurrentUrl());
});
// Callback post initialization to tell background script that panel is "ready" for communication.
addMessageListener("listenerReady", function(payload) {
console.log('got a listener init');
});
addMessageListener("resizePanel", function(payload) {
resizePanel(payload);
});
// Ask for recently accessed/used tags for auto complete
addMessageListener("getTags", function(payload) {
pktApi.getTags(function(tags, usedTags) {
sendMessage('getTagsResponse', {tags, usedTags});
});
});
// Ask for suggested tags based on passed url
addMessageListener("getSuggestedTags", function(payload) {
var responseMessageId = "getSuggestedTagsResponse";
pktApi.getSuggestedTagsForURL(payload.url, {
success: function(data, response) {
var suggestedTags = data.suggested_tags;
var successResponse = {
status: "success",
value: {
"suggestedTags" : suggestedTags
}
}
sendMessage(responseMessageId, successResponse);
},
error: function(error, response) {
sendErrorMessage(responseMessageId, error);
}
})
});
// Pass url and array list of tags, add to existing save item accordingly
addMessageListener("addTags", function(payload) {
var responseMessageId = "addTagsResponse";
pktApi.addTagsToURL(payload.url, payload.tags, {
success: function(data, response) {
var successResponse = {status: "success"};
sendMessage(responseMessageId, successResponse);
},
error: function(error, response) {
sendErrorMessage(responseMessageId, error);
}
});
});
// Based on clicking "remove page" CTA, and passed unique item id, remove the item
addMessageListener("deleteItem", function(payload) {
var responseMessageId = "deleteItemResponse";
pktApi.deleteItem(payload.itemId, {
success: function(data, response) {
var successResponse = {status: "success"};
sendMessage(responseMessageId, successResponse);
},
error: function(error, response) {
sendErrorMessage(responseMessageId, error);
}
})
});
}
// -- Browser Navigation -- //
/**
* Open a new tab with a given url and notify the iframe panel that it was opened
*/
function openTabWithUrl(url, activate) {
var tab = gBrowser.addTab(url);
if (activate) {
gBrowser.selectedTab = tab;
}
}
// -- Helper Functions -- //
function getCurrentUrl() {
return getBrowser().currentURI.spec;
}
function getCurrentTitle() {
return getBrowser().contentTitle;
}
function getPanel() {
var frame = getPanelFrame();
var panel = frame;
while (panel && panel.localName != "panel") {
panel = panel.parentNode;
}
return panel;
}
function getPanelFrame() {
return document.getElementById('pocket-panel-iframe');
}
function hasLegacyExtension() {
return !!document.getElementById('RIL_urlbar_add');
}
function isHidden() {
return _isHidden;
}
function isUserLoggedIntoFxA() {
// TODO : verify with Firefox this is the right way to do this
var user = fxAccounts.getSignedInUser();
if (user && user.email)
return true;
return false;
}
/**
* Toolbar animations
*/
function showPocketAnimation() {
// Borrowed from bookmark star animation:
// https://dxr.mozilla.org/mozilla-central/source/browser/base/content/browser-places.js#1568
// TODO : Clean-up : Probably don't need all of this since the css animation does most of the heavy lifting
// TODO : Do not show when saving from context menu -- or get really fancy and launch the icon from the link that was saved into the bookmark menu
function getCenteringTransformForRects(rectToPosition, referenceRect) {
let topDiff = referenceRect.top - rectToPosition.top;
let leftDiff = referenceRect.left - rectToPosition.left;
let heightDiff = referenceRect.height - rectToPosition.height;
let widthDiff = referenceRect.width - rectToPosition.width;
return [(leftDiff + .5 * widthDiff) + "px", (topDiff + .5 * heightDiff) + "px"];
}
if (_notificationTimeout) {
clearTimeout(this._notificationTimeout);
}
var button = document.getElementById('pocket-menu-button');
var bookmarksButton = document.getElementById('bookmarks-menu-button');
var notifier = document.getElementById("pocketed-notification-anchor");
var dropmarkerNotifier = document.getElementById("bookmarked-notification-dropmarker-anchor");
// If the Pocket button is not immediately after the bookmark button, then do not do the animation
// (because it's hard-coded for the positions right now)
// TODO - double check this in small and large toolbar button sizes
if (bookmarksButton.nextElementSibling != button)
return;
if (notifier.style.transform == '') {
// Get all the relevant nodes and computed style objects
let dropmarker = document.getAnonymousElementByAttribute(bookmarksButton, "anonid", "dropmarker");
let dropmarkerIcon = document.getAnonymousElementByAttribute(dropmarker, "class", "dropmarker-icon");
let dropmarkerStyle = getComputedStyle(dropmarkerIcon);
// Check for RTL and get bounds
let isRTL = getComputedStyle(button).direction == "rtl"; // need this?
let buttonRect = button.getBoundingClientRect();
let notifierRect = notifier.getBoundingClientRect();
let dropmarkerRect = dropmarkerIcon.getBoundingClientRect();
let dropmarkerNotifierRect = dropmarkerNotifier.getBoundingClientRect();
// Compute, but do not set, transform for pocket icon
let [translateX, translateY] = getCenteringTransformForRects(notifierRect, buttonRect);
let starIconTransform = "translate(" + (translateX) + ", " + translateY + ")";
if (isRTL) {
starIconTransform += " scaleX(-1)";
}
// Compute, but do not set, transform for dropmarker
[translateX, translateY] = getCenteringTransformForRects(dropmarkerNotifierRect, dropmarkerRect);
let dropmarkerTransform = "translate(" + translateX + ", " + translateY + ")";
// Do all layout invalidation in one go:
notifier.style.transform = starIconTransform;
dropmarkerNotifier.style.transform = dropmarkerTransform;
let dropmarkerAnimationNode = dropmarkerNotifier.firstChild;
dropmarkerAnimationNode.style.MozImageRegion = dropmarkerStyle.MozImageRegion;
dropmarkerAnimationNode.style.listStyleImage = dropmarkerStyle.listStyleImage;
}
let isInOverflowPanel = button.getAttribute("overflowedItem") == "true";
if (!isInOverflowPanel) {
notifier.setAttribute("notification", "finish");
button.setAttribute("notification", "finish");
dropmarkerNotifier.setAttribute("notification", "finish");
}
_notificationTimeout = setTimeout( () => {
notifier.removeAttribute("notification");
dropmarkerNotifier.removeAttribute("notification");
button.removeAttribute("notification");
dropmarkerNotifier.style.transform = '';
notifier.style.transform = '';
}, 1000);
}
/**
* Public functions
*/
return {
onLoad: onLoad,
onUnload: onUnload,
pocketButtonOnCommand: pocketButtonOnCommand,
pocketPanelDidShow: pocketPanelDidShow,
pocketPanelDidHide: pocketPanelDidHide,
pocketContextSaveLinkOnCommand,
pocketContextSavePageOnCommand,
pocketBookmarkBarOpenPocketCommand,
tryToSaveUrl: tryToSaveUrl,
isHidden
};
}());

View File

@ -0,0 +1,35 @@
Unless where otherwise noted, the following license applies to the files
within this directory and descendents of this directory.
POCKET MARKS
Notwithstanding the permitted uses of the Software (as defined below) pursuant
to the license set forth below, "Pocket," "Read It Later" and the Pocket icon
and logos (collectively, the “Pocket Marks”) are registered and common law
trademarks of Read It Later, Inc. This means that, while you have considerable
freedom to redistribute and modify the Software, there are tight restrictions
on your ability to use the Pocket Marks. This license does not grant you any
rights to use the Pocket Marks except as they are embodied in the Software.
---
SOFTWARE
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.

View File

@ -0,0 +1,6 @@
@font-face {
font-family: 'FiraSans';
src: url('../fonts/FiraSans-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
}

View File

@ -0,0 +1,427 @@
/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
/**
* 1. Set default font family to sans-serif.
* 2. Prevent iOS text size adjust after orientation change, without disabling
* user zoom.
*/
html {
font-family: sans-serif; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/**
* Remove default margin.
*/
body {
margin: 0;
}
/* HTML5 display definitions
========================================================================== */
/**
* Correct `block` display not defined for any HTML5 element in IE 8/9.
* Correct `block` display not defined for `details` or `summary` in IE 10/11
* and Firefox.
* Correct `block` display not defined for `main` in IE 11.
*/
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
menu,
nav,
section,
summary {
display: block;
}
/**
* 1. Correct `inline-block` display not defined in IE 8/9.
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
*/
audio,
canvas,
progress,
video {
display: inline-block; /* 1 */
vertical-align: baseline; /* 2 */
}
/**
* Prevent modern browsers from displaying `audio` without controls.
* Remove excess height in iOS 5 devices.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Address `[hidden]` styling not present in IE 8/9/10.
* Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
*/
[hidden],
template {
display: none;
}
/* Links
========================================================================== */
/**
* Remove the gray background color from active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* Improve readability when focused and also mouse hovered in all browsers.
*/
a:active,
a:hover {
outline: 0;
}
/* Text-level semantics
========================================================================== */
/**
* Address styling not present in IE 8/9/10/11, Safari, and Chrome.
*/
abbr[title] {
border-bottom: 1px dotted;
}
/**
* Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
*/
b,
strong {
font-weight: bold;
}
/**
* Address styling not present in Safari and Chrome.
*/
dfn {
font-style: italic;
}
/**
* Address variable `h1` font-size and margin within `section` and `article`
* contexts in Firefox 4+, Safari, and Chrome.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/**
* Address styling not present in IE 8/9.
*/
mark {
background: #ff0;
color: #000;
}
/**
* Address inconsistent and variable font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
/* Embedded content
========================================================================== */
/**
* Remove border when inside `a` element in IE 8/9/10.
*/
img {
border: 0;
}
/**
* Correct overflow not hidden in IE 9/10/11.
*/
svg:not(:root) {
overflow: hidden;
}
/* Grouping content
========================================================================== */
/**
* Address margin not present in IE 8/9 and Safari.
*/
figure {
margin: 1em 40px;
}
/**
* Address differences between Firefox and other browsers.
*/
hr {
-moz-box-sizing: content-box;
box-sizing: content-box;
height: 0;
}
/**
* Contain overflow in all browsers.
*/
pre {
overflow: auto;
}
/**
* Address odd `em`-unit font size rendering in all browsers.
*/
code,
kbd,
pre,
samp {
font-family: monospace, monospace;
font-size: 1em;
}
/* Forms
========================================================================== */
/**
* Known limitation: by default, Chrome and Safari on OS X allow very limited
* styling of `select`, unless a `border` property is set.
*/
/**
* 1. Correct color not being inherited.
* Known issue: affects color of disabled elements.
* 2. Correct font properties not being inherited.
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
*/
button,
input,
optgroup,
select,
textarea {
color: inherit; /* 1 */
font: inherit; /* 2 */
margin: 0; /* 3 */
}
/**
* Address `overflow` set to `hidden` in IE 8/9/10/11.
*/
button {
overflow: visible;
}
/**
* Address inconsistent `text-transform` inheritance for `button` and `select`.
* All other form control elements do not inherit `text-transform` values.
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
* Correct `select` style inheritance in Firefox.
*/
button,
select {
text-transform: none;
}
/**
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
* and `video` controls.
* 2. Correct inability to style clickable `input` types in iOS.
* 3. Improve usability and consistency of cursor style between image-type
* `input` and others.
*/
button,
html input[type="button"], /* 1 */
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button; /* 2 */
cursor: pointer; /* 3 */
}
/**
* Re-set default cursor for disabled elements.
*/
button[disabled],
html input[disabled] {
cursor: default;
}
/**
* Remove inner padding and border in Firefox 4+.
*/
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
/**
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
* the UA stylesheet.
*/
input {
line-height: normal;
}
/**
* It's recommended that you don't attempt to style these elements.
* Firefox's implementation doesn't respect box-sizing, padding, or width.
*
* 1. Address box sizing set to `content-box` in IE 8/9/10.
* 2. Remove excess padding in IE 8/9/10.
*/
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Fix the cursor style for Chrome's increment/decrement buttons. For certain
* `font-size` values of the `input`, it causes the cursor style of the
* decrement button to change from `default` to `text`.
*/
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Address `appearance` set to `searchfield` in Safari and Chrome.
* 2. Address `box-sizing` set to `border-box` in Safari and Chrome
* (include `-moz` to future-proof).
*/
input[type="search"] {
-webkit-appearance: textfield; /* 1 */
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box; /* 2 */
box-sizing: content-box;
}
/**
* Remove inner padding and search cancel button in Safari and Chrome on OS X.
* Safari (but not Chrome) clips the cancel button when the search input has
* padding (and `textfield` appearance).
*/
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* Define consistent border, margin, and padding.
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/**
* 1. Correct `color` not being inherited in IE 8/9/10/11.
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
*/
legend {
border: 0; /* 1 */
padding: 0; /* 2 */
}
/**
* Remove default vertical scrollbar in IE 8/9/10/11.
*/
textarea {
overflow: auto;
}
/**
* Don't inherit the `font-weight` (applied by a rule above).
* NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
*/
optgroup {
font-weight: bold;
}
/* Tables
========================================================================== */
/**
* Remove most spacing between table cells.
*/
table {
border-collapse: collapse;
border-spacing: 0;
}
td,
th {
padding: 0;
}

View File

@ -0,0 +1,694 @@
/* saved.css
*
* Description:
* With base elements out of the way, this sets all custom styling for the page saved dialog.
*
* Contents:
* Global
* Loading spinner
* Core detail
* Tag entry
* Recent/suggested tags
* Premium upsell
* Token input/autocomplete
*/
/*=Global
--------------------------------------------------------------------------------------- */
.pkt_ext_containersaved {
background-color: #fbfbfb;
border-radius: 4px;
display: block;
font-size: 16px;
font-family: "FiraSans", "Helvetica Neue", Helvetica, Arial, sans-serif;
padding: 0;
position: relative;
text-shadow: 0;
text-align: center;
}
.pkt_ext_cf:after {
content: " ";
display:table;
clear:both;
}
.pkt_ext_containersaved .pkt_ext_tag_detail,
.pkt_ext_containersaved .pkt_ext_recenttag_detail,
.pkt_ext_containersaved .pkt_ext_suggestedtag_detail {
margin: 0 auto;
padding: 0.25em 1em;
position: relative;
width: auto;
}
/*=Loading spinner
--------------------------------------------------------------------------------------- */
@-moz-keyframes pkt_ext_spin {
to {
-moz-transform: rotate(1turn);
-webkit-transform: rotate(1turn);
transform: rotate(1turn);
}
}
@-webkit-keyframes pkt_ext_spin {
to {
-moz-transform: rotate(1turn);
-webkit-transform: rotate(1turn);
transform: rotate(1turn);
}
}
.pkt_ext_containersaved {
font-size: 16px;
}
.pkt_ext_containersaved .pkt_ext_loadingspinner {
position: relative;
display: inline-block;
width: 2.5em;
height: 2.5em;
margin: 2em 0 0;
font-size: 10px;
text-indent: 999em;
overflow: hidden;
-moz-animation: pkt_ext_spin 0.7s infinite steps(8);
-webkit-animation: pkt_ext_spin 0.7s infinite steps(8);
animation: pkt_ext_spin 0.7s infinite steps(8);
}
.pkt_ext_containersaved .pkt_ext_loadingspinner:before,
.pkt_ext_containersaved .pkt_ext_loadingspinner:after,
.pkt_ext_containersaved .pkt_ext_loadingspinner > div:before,
.pkt_ext_containersaved .pkt_ext_loadingspinner > div:after {
content: '';
position: absolute;
top: 0;
left: 1.125em;
width: 0.25em;
height: 0.75em;
border-radius: .2em;
background: #eee;
box-shadow: 0 1.75em #eee;
-webkit-transform-origin: 50% 1.25em;
-moz-transform-origin: 50% 1.25em;
transform-origin: 50% 1.25em;
}
.pkt_ext_containersaved .pkt_ext_loadingspinner:before {
background: #555;
}
.pkt_ext_containersaved .pkt_ext_loadingspinner:after {
-webkit-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
transform: rotate(-45deg);
background: #777;
}
.pkt_ext_containersaved .pkt_ext_loadingspinner > div:before {
-webkit-transform: rotate(-90deg);
-moz-transform: rotate(-90deg);
transform: rotate(-90deg);
background: #999;
}
.pkt_ext_containersaved .pkt_ext_loadingspinner > div:after {
-webkit-transform: rotate(-135deg);
-moz-transform: rotate(-135deg);
transform: rotate(-135deg);
background: #bbb;
}
/*=Core detail
--------------------------------------------------------------------------------------- */
.pkt_ext_containersaved .pkt_ext_initload {
left: 0;
position: absolute;
top: 1em;
width: 100%;
}
.pkt_ext_containersaved .pkt_ext_detail {
max-height: 0;
opacity: 0;
position: relative;
z-index: 10;
}
.pkt_ext_container_detailactive .pkt_ext_initload,
.pkt_ext_container_finalerrorstate .pkt_ext_initload {
-webkit-transition: opacity 0.2s ease-out;
-o-transition: opacity 0.2s ease-out;
transition: opacity 0.2s ease-out;
opacity: 0;
}
.pkt_ext_container_detailactive .pkt_ext_initload .pkt_ext_loadingspinner,
.pkt_ext_container_finalerrorstate .pkt_ext_initload .pkt_ext_loadingspinner {
-moz-animation: none;
-webkit-animation: none;
animation: none;
}
.pkt_ext_container_detailactive .pkt_ext_detail {
-webkit-transition: opacity 0.2s ease-out 0.4s;
-moz-transition: opacity 0.2s ease-out 0.4s;
-ms-transition: opacity 0.2s ease-out 0.4s;
-o-transition: opacity 0.2s ease-out 0.4s;
max-height: 20em;
opacity: 1;
transition: opacity 0.2s ease-out 0.4s;
}
.pkt_ext_container_finalstate .pkt_ext_edit_msg,
.pkt_ext_container_finalstate .pkt_ext_tag_detail,
.pkt_ext_container_finalstate .pkt_ext_suggestedtag_detail,
.pkt_ext_container_finalstate .pkt_ext_item_actions {
-webkit-transition: opacity 0.2s ease-out;
-moz-transition: opacity 0.2s ease-out;
-ms-transition: opacity 0.2s ease-out;
-o-transition: opacity 0.2s ease-out;
opacity: 0;
transition: opacity 0.2s ease-out;
}
.pkt_ext_container_finalerrorstate .pkt_ext_edit_msg,
.pkt_ext_container_finalerrorstate .pkt_ext_tag_detail {
display: none;
}
.pkt_ext_containersaved h2 {
background: transparent;
border: none;
color: #333;
display: block;
float: none;
font-size: 1.125em;
font-size: 22px;
font-weight: normal;
letter-spacing: normal;
line-height: 1;
margin: 18px 0 4px;
padding: 0;
position: relative;
text-align: left;
text-transform: none;
}
.pkt_ext_containersaved .pkt_ext_logo {
background: url(../img/pocketlogosolo@1x.png) center center no-repeat;
display: block;
float: left;
height: 40px;
padding: 1.25em 1em;
position: relative;
width: 44px;
}
@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 1.5dppx) {
.pkt_ext_containersaved .pkt_ext_logo {
background-image: url(../img/pocketlogosolo@2x.png);
background-size: 44px 40px;
}
}
.pkt_ext_containersaved .pkt_ext_topdetail {
float: left;
}
.pkt_ext_container_finalerrorstate h2 {
line-height: 1.2em;
margin-bottom: 1.5em;
}
.pkt_ext_containersaved .pkt_ext_edit_msg {
display: none;
font-size: 0.875em;
left: auto;
position: absolute;
text-align: center;
top: 4.3em;
width: 100%;
}
.pkt_ext_containersaved .pkt_ext_edit_msg_error {
color: #c10000;
}
.pkt_ext_containersaved .pkt_ext_edit_msg_active {
display: block;
}
.pkt_ext_containersaved .pkt_ext_item_actions {
background: transparent;
float: none;
height: auto;
margin-bottom: 1em;
margin-top: 0;
width: auto;
}
.pkt_ext_containersaved .pkt_ext_item_actions_disabled {
opacity: 0.5;
}
.pkt_ext_container_finalstate .pkt_ext_item_actions_disabled {
opacity: 0;
}
.pkt_ext_containersaved .pkt_ext_item_actions ul {
background: none;
display: block;
float: none;
font-size: 16px;
height: auto;
margin: 0;
padding: 0;
width: 100%;
}
.pkt_ext_containersaved .pkt_ext_item_actions li {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
background: none;
border: 0;
float: left;
list-style: none;
line-height: 0.8;
height: auto;
padding-right: 0.5em;
width: auto;
}
.pkt_ext_containersaved .pkt_ext_item_actions li:before {
content: none;
}
.pkt_ext_containersaved .pkt_ext_item_actions .pkt_ext_actions_separator {
border-left: 2px solid #777;
height: 0.75em;
margin-top: 0.3em;
padding: 0;
width: 10px;
}
.pkt_ext_containersaved .pkt_ext_item_actions a {
-webkit-font-feature-settings: normal;
background: transparent;
color: #0095dd;
display: block;
font-feature-settings: normal;
font-size: 12px;
font-weight: normal;
letter-spacing: normal;
line-height: inherit;
height: auto;
margin: 0;
padding: 0.5em;
float: left;
text-align: left;
text-decoration: none;
text-transform: none;
}
.pkt_ext_containersaved .pkt_ext_item_actions a:hover {
color: #008acb;
text-decoration: none;
}
.pkt_ext_containersaved .pkt_ext_item_actions a:before,
.pkt_ext_containersaved .pkt_ext_item_actions a:after {
background: transparent;
display: none;
}
.pkt_ext_containersaved .pkt_ext_item_actions_disabled a {
cursor: default;
}
.pkt_ext_containersaved .pkt_ext_item_actions .pkt_ext_openpocket {
float: right;
padding-right: 0.7em;
text-align: right;
}
.pkt_ext_containersaved .pkt_ext_item_actions .pkt_ext_removeitem {
padding-left: 0.2em;
}
.pkt_ext_containersaved .pkt_ext_close {
background: url(../img/tag_close@1x.png) center center no-repeat;
color: #333;
display: block;
font-size: 0.8em;
height: 10px;
right: 0.5em;
overflow: hidden;
position: absolute;
text-align: center;
text-indent: -9999px;
top: -1em;
width: 10px;
}
@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 1.5dppx) {
.pkt_ext_containersaved .pkt_ext_close {
background-image: url(../img/tag_close@2x.png);
background-size: 8px 8px;
}
}
.pkt_ext_containersaved .pkt_ext_close:hover {
color: #000;
text-decoration: none;
}
/*=Tag entry
--------------------------------------------------------------------------------------- */
.pkt_ext_containersaved .pkt_ext_tag_detail {
border: 1px solid #c1c1c1;
border-radius: 2px;
font-size: 16px;
clear: both;
margin: 1.25em 1em;
padding: 0;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-box;
display: -webkit-flex;
display: flex;
}
.pkt_ext_containersaved .pkt_ext_tag_input_wrapper {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-moz-box-flex: 1;
-ms-flex: 1;
-webkit-box-flex: 1;
-webkit-flex: 1;
flex: 1;
background-color: #fff;
border-right: 1px solid #c3c3c3;
color: #333;
display: block;
float: none;
font-size: 0.875em;
list-style: none;
margin: 0;
overflow: hidden;
padding: 0.25em 0.5em;
width: 14em;
padding-left: 0.5em;
padding-right: 0.5em;
}
.pkt_ext_containersaved .pkt_ext_tag_input_wrapper .token-input-list {
display: block;
left: 0;
height: 1.7em;
overflow: hidden;
position: relative;
width: 60em;
}
.pkt_ext_containersaved .pkt_ext_tag_input_wrapper .token-input-list,
.pkt_ext_containersaved .pkt_ext_tag_input_wrapper li {
font-size: 14px;
}
.pkt_ext_containersaved .pkt_ext_tag_input_wrapper li {
height: auto;
width: auto;
}
.pkt_ext_containersaved .pkt_ext_tag_input_wrapper li:before {
content: none;
}
.pkt_ext_containersaved .pkt_ext_tag_input_wrapper input {
-webkit-appearance: caret;
border: 0;
box-shadow: none;
background-color: #fff;
color: #333;
font-size: 14px;
float: left;
line-height: normal;
height: auto;
min-height: 0;
min-width: 5em;
padding: 3px 2px 1px;
text-transform: none;
}
.pkt_ext_containersaved .pkt_ext_tag_input_wrapper input::-webkit-input-placeholder {
color: #a9a9a9;
letter-spacing: normal;
text-transform: none;
}
.pkt_ext_containersaved .input_disabled {
cursor: default;
opacity: 0.5;
}
.pkt_ext_containersaved .pkt_ext_btn {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
float: none;
font-size: 0.875em;
font-size: 14px;
letter-spacing: normal;
height: 2.2em;
min-width: 4em;
padding: 0.5em 0;
text-transform: none;
width: auto;
}
.pkt_ext_containersaved .pkt_ext_btn_disabled {
background-color: #ededed;
cursor: default;
opacity: 0.5;
}
.pkt_ext_containersaved .autocomplete-suggestions {
margin-top: 2.2em;
}
/*=Recent/suggested tags
--------------------------------------------------------------------------------------- */
.pkt_ext_containersaved .pkt_ext_suggestedtag_detail {
border-top: 1px solid #c1c1c1;
background: #ebebeb;
clear: both;
opacity: 0;
visibility: hidden;
min-height: 110px;
}
.pkt_ext_container_detailactive .pkt_ext_suggestedtag_detail {
opacity: 1;
-webkit-transition: opacity 0.2s ease-out, visibility 0.2s ease-out;
-o-transition: opacity 0.2s ease-out, visibility 0.2s ease-out;
transition: opacity 0.2s ease-out, visibility 0.2s ease-out;
visibility: visible;
}
.pkt_ext_container_finalstate .pkt_ext_suggestedtag_detail {
opacity: 0;
visibility: hidden;
}
.pkt_ext_containersaved
.pkt_ext_containersaved .pkt_ext_recenttag_detail h4,
.pkt_ext_containersaved .pkt_ext_suggestedtag_detail h4 {
color: #333;
font-size: 0.8125em;
font-size: 13px;
font-weight: normal;
font-style: normal;
letter-spacing: normal;
margin: 0.5em 0;
text-align: left;
text-transform: none;
}
.pkt_ext_containersaved .pkt_ext_recenttag_detail .pkt_ext_loadingspinner,
.pkt_ext_containersaved .pkt_ext_suggestedtag_detail .pkt_ext_loadingspinner {
display: none;
position: absolute;
}
.pkt_ext_containersaved .pkt_ext_recenttag_detail_loading .pkt_ext_loadingspinner,
.pkt_ext_containersaved .pkt_ext_suggestedtag_detail_loading .pkt_ext_loadingspinner {
display: block;
font-size: 6px;
left: 48%;
}
.pkt_ext_containersaved .pkt_ext_recenttag_detail ul,
.pkt_ext_containersaved .pkt_ext_suggestedtag_detail ul {
display: block;
margin: 0;
height: 2em;
overflow: hidden;
padding: 2px 0 0 0;
}
.pkt_ext_containersaved .pkt_ext_recenttag_detail li,
.pkt_ext_containersaved .pkt_ext_suggestedtag_detail li {
background: none;
float: left;
height: inherit;
line-height: 1.5;
list-style: none;
margin-bottom: 0.5em;
width: inherit;
}
.pkt_ext_containersaved .pkt_ext_recenttag_detail li:before,
.pkt_ext_containersaved .pkt_ext_suggestedtag_detail li:before {
content: none;
}
.pkt_ext_containersaved .pkt_ext_recenttag_detail .recenttag_msg,
.pkt_ext_containersaved .pkt_ext_suggestedtag_detail .suggestedtag_msg {
color: #333;
font-size: 0.8125em;
line-height: 1.2;
left: auto;
position: absolute;
text-align: left;
top: 2em;
}
.pkt_ext_containersaved .token_tag {
border-radius: 4px;
background: #f7f7f7;
border: 1px solid #c3c3c3;
color: #333;
font-size: 0.875em;
font-size: 14px;
font-weight: normal;
letter-spacing: normal;
margin-right: 0.5em;
padding: 0.125em 0.3125em;
text-decoration: none;
text-transform: none;
}
.pkt_ext_containersaved .token_tag:hover {
background-color: #008acb;
color: #fff;
text-decoration: none;
}
.pkt_ext_containersaved .token_tag:before,
.pkt_ext_containersaved .token_tag:after {
content: none;
}
.pkt_ext_containersaved .pkt_ext_recenttag_detail_disabled .token_tag,
.pkt_ext_containersaved .pkt_ext_recenttag_detail_disabled .token_tag:hover,
.pkt_ext_containersaved .pkt_ext_suggestedtag_detail_disabled .token_tag,
.pkt_ext_containersaved .pkt_ext_suggestedtag_detail_disabled .token_tag:hover {
background-color: #f7f7f7;
cursor: default;
opacity: 0.5;
}
.pkt_ext_containersaved .token_tag_inactive {
display: none;
}
/*=Premium upsell
--------------------------------------------------------------------------------------- */
.pkt_ext_detail .pkt_ext_premupsell {
background-color: #50bbb6;
display: block;
padding: 1.5em 0;
text-align: center;
}
.pkt_ext_premupsell h4 {
color: #fff;
font-size: 1em;
margin-bottom: 1em;
}
.pkt_ext_premupsell a {
color: #28605d;
border-bottom: 1px solid #47a7a3;
font-weight: normal;
}
.pkt_ext_premupsell a:hover {
color: #14302f;
}
/*=Token input/autocomplete
--------------------------------------------------------------------------------------- */
.token-input-dropdown-tag {
-moz-border-radius: 4px;
-moz-box-sizing: border-box;
-webkit-border-radius: 4px;
-webkit-box-sizing: border-box;
border-radius: 4px;
box-sizing: border-box;
background: #fff;
border: 1px solid #cdcdcd;
margin-top: 0.5em;
left: 0 !important;
overflow-y: auto;
top: 1.9em !important;
z-index: 9000;
}
.token-input-dropdown-tag ul {
height: inherit;
max-height: 115px;
margin: 0;
overflow: auto;
padding: 0.5em 0;
}
.token-input-dropdown-tag ul li {
background: none;
color: #333;
font-weight: normal;
font-size: 1em;
float: none;
height: inherit;
letter-spacing: normal;
list-style: none;
padding: 0.75em;
text-align: left;
text-transform: none;
width: inherit;
}
.token-input-dropdown-tag ul li:before {
content: none;
}
.token-input-dropdown ul li.token-input-selected-dropdown-item {
background-color: #008acb;
color: #fff;
}
.token-input-list {
list-style: none;
margin: 0;
padding: 0;
}
.token-input-list li {
text-align: left;
list-style: none;
}
.token-input-list li input {
border: 0;
background-color: white;
-webkit-appearance: caret;
}
.pkt_ext_containersaved .token-input-token {
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
background: none;
border-radius: 4px;
border: 1px solid #c3c3c3;
overflow: hidden;
margin: 0;
padding: 0 5px;
background-color: #f7f7f7;
color: #000;
font-weight: normal;
cursor: default;
line-height: 1.5;
display: block;
width: auto;
margin: 0 0.2em;
float: left;
}
.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled {
position: relative;
}
.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled input {
opacity: 0.5;
}
.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled .token-input-list {
opacity: 0.5;
}
.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled .pkt_ext_tag_input_blocker {
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
z-index: 5;
}
.pkt_ext_containersaved .token-input-token p {
display: inline-block;
font-size: 14px;
font-weight: normal;
line-height: inherit;
letter-spacing: normal;
padding: 0;
margin: 0;
text-transform: none;
vertical-align: top;
width: auto;
}
.pkt_ext_containersaved .token-input-token p:before {
content: none;
width: 0;
}
.pkt_ext_containersaved .token-input-token span {
color: #777;
cursor: pointer;
display: inline-block;
margin: 0 0 0 10px;
vertical-align: top;
}
.pkt_ext_containersaved .token-input-selected-token {
background-color: #008acb;
color: #fff;
}
.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled .token-input-selected-token {
background-color: #f7f7f7;
}
.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled .token-input-selected-token span {
color: #bbb;
}

View File

@ -0,0 +1,267 @@
/* signup.css
*
* Description:
* With base elements out of the way, this sets all custom styling for the extension.
*
* Contents:
* Global
* Core detail
* Buttons
* Responsive
*/
/*=Global
--------------------------------------------------------------------------------------- */
.pkt_ext_containersignup {
background-color: #ebebeb;
color: #333;
display: block;
font-size: 16px;
font-family: "FiraSans", "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: 0;
padding: 0;
position: relative;
text-align: center;
}
.pkt_ext_containersignup_inactive {
-moz-animation: pkt_ext_hide 0.3s ease-out;
-webkit-animation: pkt_ext_hide 0.3s ease-out;
animation: pkt_ext_hide 0.3s ease-out;
opacity: 0;
visibility: hidden;
}
.pkt_ext_cf:after {
content: " ";
display:table;
clear:both;
}
@-webkit-keyframes pkt_ext_hide {
0% {
opacity: 1;
visibility: visible;
}
99% {
opacity: 0;
visibility: visible;
}
100% {
opacity: 0;
visibility: hidden;
}
}
@keyframes pkt_ext_hide {
0% {
opacity: 1;
visibility: visible;
}
99% {
opacity: 0;
visibility: visible;
}
100% {
opacity: 0;
visibility: hidden;
}
}
/*=Core detail
--------------------------------------------------------------------------------------- */
.pkt_ext_containersignup p {
font-size: 14px;
color: #333;
font-family: "FiraSans", "Helvetica Neue", Helvetica, Arial, sans-serif;
line-height: 1.3;
margin: 0 auto 1.5em;
max-width: 250px;
}
.pkt_ext_containersignup a {
color: #4c8fd0;
text-decoration: none;
}
.pkt_ext_containersignup a:hover {
color: #3076b9;
}
.pkt_ext_containersignup .pkt_ext_introrecommend {
background-color: #f4f4f4;
color: #333;
font-size: 12px;
font-weight: normal;
margin: 0;
padding: 1em 0.5em;
}
.pkt_ext_containersignup .pkt_ext_introdetail {
background-color: #fbfbfb;
border: 1px solid #c1c1c1;
border-width: 1px 0;
}
.pkt_ext_containersignup .pkt_ext_logo {
background: url(../img/pocketlogo@1x.png) center bottom no-repeat;
display: block;
height: 38px;
margin: 0 auto 15px;
padding-top: 25px;
position: relative;
text-indent: -9999px;
width: 147px;
}
@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 1.5dppx) {
.pkt_ext_containersignup .pkt_ext_logo {
background-image: url(../img/pocketlogo@2x.png);
background-size: 147px 38px;
}
}
.pkt_ext_containersignup .pkt_ext_introimg {
background: url(../img/pocketmultidevices@1x.png) center center no-repeat;
display: block;
height: 122px;
margin: 10px auto 20px;
position: relative;
text-indent: -9999px;
width: 171px;
}
@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 1.5dppx) {
.pkt_ext_containersignup .pkt_ext_introimg {
background-image: url(../img/pocketmultidevices@2x.png);
background-size: 171px 122px;
}
}
.pkt_ext_containersignup .pkt_ext_tagline {
margin-bottom: 0.5em;
}
.pkt_ext_containersignup .pkt_ext_learnmore {
font-size: 12px;
}
.pkt_ext_signupdetail h4 {
font-size: 12px;
font-weight: normal;
}
.pkt_ext_signupdetail .btn-container {
position: relative;
margin-bottom: 0.8em;
}
.pkt_ext_containersignup .ff_signuphelp {
background: url(../img/signup_help@1x.png) center center no-repeat;
display: block;
height: 18px;
margin-top: -9px;
right: -15px;
position: absolute;
text-indent: -9999px;
width: 18px;
top: 50%;
}
@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 1.5dppx) {
.pkt_ext_containersignup .ff_signuphelp {
background-image: url(../img/signup_help@2x.png);
background-size: 18px 18px;
}
}
.pkt_ext_containersignup .alreadyhave {
font-size: 12px;
max-width: 320px;
margin-top: 15px;
}
/*=Buttons
--------------------------------------------------------------------------------------- */
.pkt_ext_containersignup .btn {
background-color: #0096dd;
border: 1px solid #0095dd;
border-radius: 2px;
color: #fff;
display: inline-block;
font-family: "FiraSans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 16px;
font-weight: normal;
line-height: 1;
margin: 0;
padding: 11px 45px;
text-align: center;
text-decoration: none;
text-shadow: 0 -1px 0 rgba(142,4,17,0.5);
transition: background-color 0.1s linear;
width: auto;
}
.pkt_ext_containersignup .btn-secondary {
background-color: #fbfbfb;
border-color: #c1c1c1;
color: #444;
text-shadow: 0 1px 0 rgba(255,255,255,0.5);
}
.pkt_ext_containersignup .btn-small {
padding: 6px 20px;
}
.pkt_ext_containersignup .btn-mini {
font-size: 14px;
padding: 5px 15px 4px;
}
.pkt_ext_containersignup .btn:hover {
background-color: #008acb;
color: #fff;
text-decoration: none;
}
.pkt_ext_containersignup .btn-secondary:hover,
.pkt_ext_containersignup .btn-important:hover {
background-color: #f6f6f6;
color: #222;
}
.pkt_ext_containersignup .btn-disabled {
background-image: none;
color: #ccc;
color: rgba(255,255,255,0.6);
cursor: default;
opacity: 0.9;
}
.pkt_ext_containersignup .signup-btn-firefox,
.pkt_ext_containersignup .signup-btn-email,
.pkt_ext_containersignup .signupinterim-btn-login,
.pkt_ext_containersignup .signupinterim-btn-signup,
.pkt_ext_containersignup .forgot-btn-submit,
.pkt_ext_containersignup .forgotreset-btn-change {
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box;
min-width: 12.125em;
padding: 0.8em 1.1875em;
box-sizing: content-box;
}
.pkt_ext_containersignup .signup-btn-email {
position: relative;
z-index: 10;
}
.pkt_ext_containersignup .signup-btn-firefox {
min-width: 14.5em;
position: relative;
padding: 0;
}
.pkt_ext_containersignup .signup-btn-firefox .logo {
background: url(../img/signup_firefoxlogo@1x.png) center center no-repeat;
height: 2.6em;
left: 10px;
margin: 0;
padding: 0;
width: 22px;
position: absolute;
}
@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 144dpi) {
.pkt_ext_containersignup .signup-btn-firefox .logo {
background-image: url(../img/signup_firefoxlogo@2x.png);
background-size: 22px 22px;
}
}
.pkt_ext_containersignup .forgotreset-btn-change {
margin-bottom: 2em;
}
.pkt_ext_containersignup .signup-btn-firefox .text {
display: inline-block;
padding: 0.8em 1.625em;
position: relative;
text-shadow: none;
white-space: nowrap;
}
.pkt_ext_containersignup .signup-btn-firefox .text {
color: #fff;
}
.pkt_ext_containersignup .btn-disabled .text {
color: #ccc;
color: rgba(255,255,255,0.6);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,22 @@
Translations = {};
Translations.en =
{
addtags: "Add Tags",
alreadyhaveacct: "Already have an account?",
learnmore: "Learn More",
loginnow: "Log in",
or: "or",
pageremoved: "Page Removed",
pagesaved: "Saved to Pocket",
processingremove: "Removing Page...",
processingtags: "Adding tags...",
removepage: "Remove Page",
save: "Save",
signupemail: "Sign Up with email",
signuptosave: "Sign up to start saving, its totally free.",
suggestedtags: "Suggested Tags",
tagline: "Save links from Firefox to view in Pocket on any device, anytime.",
tagssaved: "Tags Added",
signupfirefox: "Sign Up with Firefox",
viewlist: "View List"
}

View File

@ -0,0 +1,58 @@
// Documentation of methods used here are at:
// https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Interaction_between_privileged_and_non-privileged_pages
var Messaging = (function() {
function addMessageListener(messageId, callback) {
document.addEventListener('PKT_' + messageId, function(e) {
callback(JSON.parse(e.target.getAttribute("payload"))[0]);
// TODO: Figure out why e.target.parentNode is null
// e.target.parentNode.removeChild(e.target);
},false);
}
function removeMessageListener(messageId, callback) {
document.removeEventListener('PKT_' + messageId, callback);
}
function sendMessage(messageId, payload, callback) {
// Create a callback to listen for a response
if (callback) {
// Message response id is messageId + "Response"
var messageResponseId = messageId + "Response";
var responseListener = function(responsePayload) {
callback(responsePayload);
removeMessageListener(messageResponseId, responseListener);
}
addMessageListener(messageResponseId, responseListener);
}
// Send message
var element = document.createElement("PKTMessageFromPanelElement");
element.setAttribute("payload", JSON.stringify([payload]));
document.documentElement.appendChild(element);
var evt = document.createEvent("Events");
evt.initEvent('PKT_'+messageId, true, false);
element.dispatchEvent(evt);
}
/**
* Public functions
*/
return {
addMessageListener : addMessageListener,
removeMessageListener : removeMessageListener,
sendMessage: sendMessage
};
}());

View File

@ -0,0 +1,631 @@
/*
PKT_SAVED_OVERLAY is the view itself and contains all of the methods to manipute the overlay and messaging.
It does not contain any logic for saving or communication with the extension or server.
*/
var PKT_SAVED_OVERLAY = function (options)
{
var myself = this;
this.inited = false;
this.active = false;
this.wrapper = null;
this.savedItemId = 0;
this.savedUrl = '';
this.premiumStatus = false;
this.preventCloseTimerCancel = false;
this.closeValid = true;
this.mouseInside = false;
this.autocloseTimer = null;
this.dictJSON = {};
// TODO: allow the timer to be editable?
this.autocloseTiming = 3500;
this.autocloseTimingFinalState = 1500;
this.mouseInside = false;
this.userTags = [];
this.cxt_suggested_available = 0;
this.cxt_entered = 0;
this.cxt_suggested = 0;
this.cxt_removed = 0;
this.justaddedsuggested = false;
this.fillTagContainer = function(tags,container,tagclass) {
var newtagleft = 0;
container.children().remove();
for (var i = 0; i < tags.length; i++) {
var newtag = $('<li><a href="#" class="token_tag ' + tagclass + '">' + tags[i] + '</a></li>');
container.append(newtag);
var templeft = newtag.position().left;
if (templeft > newtagleft) {
this.cxt_suggested_available++;
newtagleft = templeft;
}
else {
newtag.remove();
break;
}
}
};
this.fillUserTags = function() {
console.log('start of logic for fillUserTags');
thePKT_SAVED.sendMessage("getTags",{},function(resp)
{
console.log('got a big tag response',resp);
if (typeof resp == 'object' && typeof resp.tags == 'object')
{
myself.userTags = resp.tags;
}
});
};
this.fillSuggestedTags = function()
{
if (!$('.pkt_ext_suggestedtag_detail').length)
{
myself.suggestedTagsLoaded = true;
myself.startCloseTimer();
return;
}
console.log('calling suggested tags',myself.savedUrl);
thePKT_SAVED.sendMessage("getSuggestedTags",
{
url: myself.savedUrl || window.location.toString()
}, function(resp)
{
$('.pkt_ext_suggestedtag_detail').removeClass('pkt_ext_suggestedtag_detail_loading');
console.log('got suggested tags response',resp);
if (resp.status == 'success')
{
var newtags = [];
for (var i = 0; i < resp.value.suggestedTags.length; i++)
{
newtags.push(resp.value.suggestedTags[i].tag);
}
myself.suggestedTagsLoaded = true;
if (!myself.mouseInside) {
myself.startCloseTimer();
}
myself.fillTagContainer(newtags,$('.pkt_ext_suggestedtag_detail ul'),'token_suggestedtag');
}
else if (resp.status == 'error') {
var msg = $('<p class="suggestedtag_msg">');
msg.text(resp.error);
$('.pkt_ext_suggestedtag_detail').append(msg);
this.suggestedTagsLoaded = true;
if (!myself.mouseInside) {
myself.startCloseTimer();
}
}
});
}
this.initAutoCloseEvents = function() {
this.wrapper.on('mouseenter',function() {
myself.mouseInside = true;
myself.stopCloseTimer();
});
this.wrapper.on('mouseleave',function() {
myself.mouseInside = false;
myself.startCloseTimer();
});
this.wrapper.on('click',function(e) {
myself.closeValid = false;
});
};
this.startCloseTimer = function(manualtime)
{
/*var settime = manualtime ? manualtime : myself.autocloseTiming;
if (typeof myself.autocloseTimer == 'number')
{
clearTimeout(myself.autocloseTimer);
}
myself.autocloseTimer = setTimeout(function()
{
if (myself.closeValid || myself.preventCloseTimerCancel)
{
myself.preventCloseTimerCancel = false;
myself.closePopup();
}
}, settime);*/
};
this.stopCloseTimer = function()
{
if (myself.preventCloseTimerCancel)
{
return;
}
clearTimeout(myself.autocloseTimer);
};
this.closePopup = function() {
myself.stopCloseTimer();
thePKT_SAVED.sendMessage("close");
};
this.checkValidTagSubmit = function() {
var inputlength = $.trim($('.pkt_ext_tag_input_wrapper').find('.token-input-input-token').children('input').val()).length;
if ($('.pkt_ext_containersaved').find('.token-input-token').length || (inputlength > 0 && inputlength < 26))
{
$('.pkt_ext_containersaved').find('.pkt_ext_btn').removeClass('pkt_ext_btn_disabled');
}
else
{
$('.pkt_ext_containersaved').find('.pkt_ext_btn').addClass('pkt_ext_btn_disabled');
}
myself.updateSlidingTagList();
};
this.updateSlidingTagList = function() {
var inputleft = $('.token-input-input-token input').position().left;
var listleft = $('.token-input-list').position().left;
var listleftmanual = parseInt($('.token-input-list').css('left'));
var listleftnatural = listleft - listleftmanual;
var leftwidth = $('.pkt_ext_tag_input_wrapper').outerWidth();
if ((inputleft + listleft + 20) > leftwidth)
{
$('.token-input-list').css('left',Math.min(((inputleft + listleftnatural - leftwidth + 20)*-1),0) + 'px');
}
else
{
$('.token-input-list').css('left','0');
}
};
this.checkPlaceholderStatus = function() {
if (this.wrapper.find('.pkt_ext_tag_input_wrapper').find('.token-input-token').length)
{
this.wrapper.find('.token-input-input-token input').attr('placeholder','');
}
else
{
this.wrapper.find('.token-input-input-token input').attr('placeholder',$('.pkt_ext_tag_input').attr('placeholder')).css('width','200px');
}
};
this.initTagInput = function() {
var inputwrapper = $('.pkt_ext_tag_input_wrapper');
inputwrapper.find('.pkt_ext_tag_input').tokenInput([], {
searchDelay: 200,
minChars: 1,
animateDropdown: false,
noResultsHideDropdown: true,
scrollKeyboard: true,
emptyInputLength: 200,
search_function: function(term, cb) {
var returnlist = [];
if (term.length) {
var limit = 15;
var r = new RegExp('^' + term);
for (var i = 0; i < myself.userTags.length; i++) {
if (r.test(myself.userTags[i]) && limit > 0) {
returnlist.push({name:myself.userTags[i]});
limit--;
}
}
}
else {
returnlist.push({name:'blah'});
}
if (!$('.token-input-dropdown-tag').data('init')) {
$('.token-input-dropdown-tag').css('width',inputwrapper.outerWidth()).data('init');
inputwrapper.append($('.token-input-dropdown-tag'));
}
cb(returnlist);
},
textToData: function(text) {
if($.trim(text).length > 25 || !$.trim(text).length) {
if (text.length > 25) {
$('.pkt_ext_edit_msg').addClass('pkt_ext_edit_msg_error pkt_ext_edit_msg_active').text(myself.dictJSON.invalidTags);
changestamp = Date.now();
setTimeout(function() {
$('.token-input-input-token input').val(text).focus();
},10);
}
return null;
}
else {
$('.pkt_ext_edit_msg').removeClass('pkt_ext_edit_msg_error pkt_ext_edit_msg_active').text('');
return {name:myself.sanitizeText(text.toLowerCase())};
}
},
onReady: function() {
console.log('got to autoinput ready');
$('.token-input-dropdown').addClass('token-input-dropdown-tag');
inputwrapper.find('.token-input-input-token input').attr('placeholder',$('.tag-input').attr('placeholder')).css('width','200px');
if ($('.pkt_ext_suggestedtag_detail').length) {
myself.wrapper.find('.pkt_ext_suggestedtag_detail').on('click','.token_tag',function(e) {
e.preventDefault();
var tag = $(e.target);
if ($(this).parents('.pkt_ext_suggestedtag_detail_disabled').length) {
return;
}
myself.justaddedsuggested = true;
inputwrapper.find('.pkt_ext_tag_input').tokenInput('add',{id:inputwrapper.find('.token-input-token').length,name:tag.text()});
tag.addClass('token-suggestedtag-inactive');
$('.token-input-input-token input').focus();
});
}
$('.token-input-list').on('keydown','input',function(e) {
if (e.which == 37) {
myself.updateSlidingTagList();
}
}).on('keypress','input',function(e) {
if (e.which == 13) {
if (Date.now() - changestamp > 250) {
e.preventDefault();
myself.wrapper.find('.pkt_ext_btn').trigger('click');
}
}
}).on('keyup','input',function(e) {
myself.checkValidTagSubmit();
});
myself.checkPlaceholderStatus();
},
onAdd: function() {
myself.checkValidTagSubmit();
changestamp = Date.now();
myself.hideInactiveTags();
myself.checkPlaceholderStatus();
},
onDelete: function() {
myself.checkValidTagSubmit();
changestamp = Date.now();
myself.showActiveTags();
myself.checkPlaceholderStatus();
}
});
$('body').on('keydown',function(e) {
var key = e.keyCode || e.which;
if (key == 8) {
var selected = $('.token-input-selected-token');
if (selected.length) {
e.preventDefault();
e.stopImmediatePropagation();
inputwrapper.find('.pkt_ext_tag_input').tokenInput('remove',{name:selected.find('p').text()});
}
}
else {
if ($(e.target).parent().hasClass('token-input-input-token')) {
e.stopImmediatePropagation();
}
}
});
};
this.disableInput = function() {
this.wrapper.find('.pkt_ext_item_actions').addClass('pkt_ext_item_actions_disabled');
this.wrapper.find('.pkt_ext_btn').addClass('pkt_ext_btn_disabled');
this.wrapper.find('.pkt_ext_tag_input_wrapper').addClass('pkt_ext_tag_input_wrapper_disabled');
if (this.wrapper.find('.pkt_ext_suggestedtag_detail').length) {
this.wrapper.find('.pkt_ext_suggestedtag_detail').addClass('pkt_ext_suggestedtag_detail_disabled');
}
};
this.enableInput = function() {
this.wrapper.find('.pkt_ext_item_actions').removeClass('pkt_ext_item_actions_disabled');
this.checkValidTagSubmit();
this.wrapper.find('.pkt_ext_tag_input_wrapper').removeClass('pkt_ext_tag_input_wrapper_disabled');
if (this.wrapper.find('.pkt_ext_suggestedtag_detail').length) {
this.wrapper.find('.pkt_ext_suggestedtag_detail').removeClass('pkt_ext_suggestedtag_detail_disabled');
}
};
this.initAddTagInput = function() {
$('.pkt_ext_btn').click(function(e) {
e.preventDefault();
if ($(this).hasClass('pkt_ext_btn_disabled') || $('.pkt_ext_edit_msg_active').filter('.pkt_ext_edit_msg_error').length)
{
return;
}
myself.disableInput();
$('.pkt_ext_containersaved').find('.pkt_ext_detail h2').text(myself.dictJSON.processingtags);
var originaltags = [];
$('.token-input-token').each(function()
{
var text = $.trim($(this).find('p').text());
if (text.length)
{
originaltags.push(text);
}
});
console.log('submitting addtags message');
thePKT_SAVED.sendMessage("addTags",
{
url: myself.savedUrl || window.location.toString(),
tags: originaltags
}, function(resp)
{
console.log('got a response',resp);
if (resp.status == 'success')
{
myself.showStateFinalMsg(myself.dictJSON.tagssaved);
}
else if (resp.status == 'error')
{
$('.pkt_ext_edit_msg').addClass('pkt_ext_edit_msg_error pkt_ext_edit_msg_active').text(resp.error);
}
});
});
};
this.initRemovePageInput = function() {
$('.pkt_ext_removeitem').click(function(e) {
if ($(this).parents('.pkt_ext_item_actions_disabled').length) {
e.preventDefault();
return;
}
if ($(this).hasClass('pkt_ext_removeitem')) {
e.preventDefault();
myself.disableInput();
$('.pkt_ext_containersaved').find('.pkt_ext_detail h2').text(myself.dictJSON.processingremove);
console.log('processing page removal',myself.savedItemId);
thePKT_SAVED.sendMessage("deleteItem",
{
itemId: myself.savedItemId
},function(resp) {
console.log('got a removal message',resp);
if (resp.status == 'success') {
myself.showStateFinalMsg(myself.dictJSON.pageremoved);
}
else if (resp.status == 'error') {
$('.pkt_ext_edit_msg').addClass('pkt_ext_edit_msg_error pkt_ext_edit_msg_active').text(resp.error);
}
});
}
});
};
this.initOpenListInput = function() {
$('.pkt_ext_openpocket').click(function(e)
{
e.preventDefault();
console.log('sending new tab messsage',$(this).attr('href'));
thePKT_SAVED.sendMessage("openTabWithUrl",
{
url: $(this).attr('href'),
activate: true
});
myself.closePopup();
});
};
this.showActiveTags = function() {
if (!$('.pkt_ext_suggestedtag_detail').length) {
return;
}
var activetokenstext = '|';
$('.token-input-token').each(function(index, element) {
activetokenstext += $(element).find('p').text() + '|';
});
var inactivetags = $('.pkt_ext_suggestedtag_detail').find('.token_tag_inactive');
inactivetags.each(function(index,element) {
if (activetokenstext.indexOf('|' + $(element).text() + '|') == -1) {
$(element).removeClass('token_tag_inactive');
}
});
};
this.hideInactiveTags = function() {
if (!$('.pkt_ext_suggestedtag_detail').length) {
return;
}
var activetokenstext = '|';
$('.token-input-token').each(function(index, element) {
activetokenstext += $(element).find('p').text() + '|';
});
var activesuggestedtags = $('.token_tag').not('.token_tag_inactive');
activesuggestedtags.each(function(index,element) {
if (activetokenstext.indexOf('|' + $(element).text() + '|') > -1) {
$(element).addClass('token_tag_inactive');
}
});
};
this.showStateSaved = function(initobj) {
console.log('start of saved state',initobj);
this.wrapper.find('.pkt_ext_detail h2').text(this.dictJSON.pagesaved);
this.wrapper.find('.pkt_ext_btn').addClass('pkt_ext_btn_disabled');
if (typeof initobj.item == 'object')
{
this.savedItemId = initobj.item.item_id;
this.savedUrl = initobj.item.resolved_url;
}
$('.pkt_ext_containersaved').addClass('pkt_ext_container_detailactive').removeClass('pkt_ext_container_finalstate');
myself.fillUserTags();
if (myself.suggestedTagsLoaded) {
myself.startCloseTimer();
}
else {
myself.fillSuggestedTags();
}
};
this.sanitizeText = function(s) {
var sanitizeMap = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': '&quot;',
"'": '&#39;'
};
if (typeof s !== 'string')
{
return '';
}
else
{
return String(s).replace(/[&<>"']/g, function (str) {
return sanitizeMap[str];
});
}
};
this.showStateFinalMsg = function(msg) {
this.wrapper.find('.pkt_ext_tag_detail').one('webkitTransitionEnd transitionend msTransitionEnd oTransitionEnd',function(e)
{
$(this).off('webkitTransitionEnd transitionend msTransitionEnd oTransitionEnd');
myself.preventCloseTimerCancel = true;
myself.startCloseTimer(myself.autocloseTimingFinalState);
});
this.wrapper.addClass('pkt_ext_container_finalstate');
this.wrapper.find('.pkt_ext_detail h2').text(msg);
};
this.getTranslations = function()
{
var language = window.navigator.language.toLowerCase();
this.dictJSON = {};
var dictsuffix = 'en-US';
if (language.indexOf('en') == 0)
{
dictsuffix = 'en';
}
else if (language.indexOf('it') == 0)
{
dictsuffix = 'it';
}
else if (language.indexOf('fr-ca') == 0)
{
dictsuffix = 'fr';
}
else if (language.indexOf('fr') == 0)
{
dictsuffix = 'fr';
}
else if (language.indexOf('de') == 0)
{
dictsuffix = 'de';
}
else if (language.indexOf('es-es') == 0)
{
dictsuffix = 'es';
}
else if (language.indexOf('es-419') == 0)
{
dictsuffix = 'es_419';
}
else if (language.indexOf('es') == 0)
{
dictsuffix = 'es';
}
else if (language.indexOf('ja') == 0)
{
dictsuffix = 'ja';
}
else if (language.indexOf('nl') == 0)
{
dictsuffix = 'nl';
}
else if (language.indexOf('pt-pt') == 0)
{
dictsuffix = 'pt_PT';
}
else if (language.indexOf('pt') == 0)
{
dictsuffix = 'pt_BR';
}
else if (language.indexOf('ru') == 0)
{
dictsuffix = 'ru';
}
else if (language.indexOf('zh-tw') == 0)
{
dictsuffix = 'zh_TW';
}
else if (language.indexOf('zh') == 0)
{
dictsuffix = 'zh_CN';
}
else if (language.indexOf('ko') == 0)
{
dictsuffix = 'ko';
}
else if (language.indexOf('pl') == 0)
{
dictsuffix = 'pl';
}
// TODO: when we add all dictionaries, modify this, but for now hard code to English
dictsuffix = 'en';
this.dictJSON = Translations[dictsuffix];
};
};
PKT_SAVED_OVERLAY.prototype = {
create : function()
{
console.log('creating overlay',this.active);
if (this.active)
{
return;
}
this.active = true;
// set translations
this.getTranslations();
// Create actual content
$('body').append(Handlebars.templates.saved_shell(this.dictJSON));
// Add in premium content (if applicable based on premium status)
this.createPremiumFunctionality();
// Initialize functionality for overlay
this.wrapper = $('.pkt_ext_containersaved');
this.initTagInput();
this.initAddTagInput();
this.initRemovePageInput();
this.initOpenListInput();
this.initAutoCloseEvents();
},
createPremiumFunctionality: function()
{
if (this.premiumStatus && !$('.pkt_ext_suggestedtag_detail').length)
{
console.log('make premium');
$('body').append(Handlebars.templates.saved_premiumshell(this.dictJSON));
}
}
};
// Layer between Bookmarklet and Extensions
var PKT_SAVED = function () {};
PKT_SAVED.prototype = {
init: function () {
if (this.inited) {
return;
}
this.overlay = new PKT_SAVED_OVERLAY();
this.inited = true;
},
addMessageListener: function(messageId, callback) {
Messaging.addMessageListener(messageId, callback);
},
sendMessage: function(messageId, payload, callback) {
Messaging.sendMessage(messageId, payload, callback);
},
create: function() {
var myself = this;
var url = window.location.href.split('premiumStatus=');
if (url.length > 1)
{
myself.overlay.premiumStatus = (url[1] == '1');
}
myself.overlay.create();
// tell back end we're ready
thePKT_SAVED.sendMessage("show");
// wait confirmation of save before flipping to final saved state
thePKT_SAVED.addMessageListener("saveLink",function(resp)
{
console.log('sweet, switch to full mode because of registered hit',resp);
myself.overlay.showStateSaved(resp);
});
}
}
$(function()
{
if(!window.thePKT_SAVED){
var thePKT_SAVED = new PKT_SAVED();
window.thePKT_SAVED = thePKT_SAVED;
thePKT_SAVED.init();
}
window.thePKT_SAVED.create();
});

View File

@ -0,0 +1,207 @@
/*
PKT_SIGNUP_OVERLAY is the view itself and contains all of the methods to manipute the overlay and messaging.
It does not contain any logic for saving or communication with the extension or server.
*/
var PKT_SIGNUP_OVERLAY = function (options)
{
var myself = this;
this.baseHost = "getpocket.com";
this.inited = false;
this.active = false;
this.delayedStateSaved = false;
this.wrapper = null;
this.variant = window.___PKT__SIGNUP_VARIANT;
this.tagline = window.___PKT__SIGNUP_TAGLINE || '';
this.preventCloseTimerCancel = false;
// TODO: populate this with actual translations
this.translations = {};
this.closeValid = true;
this.mouseInside = false;
this.autocloseTimer = null;
this.dictJSON = {};
this.initCloseTabEvents = function() {
$('.btn,.alreadyhave > a').click(function(e)
{
e.preventDefault();
console.log('sending new tab messsage',$(this).attr('href'));
thePKT_SIGNUP.sendMessage("openTabWithUrl",
{
url: $(this).attr('href'),
activate: true
});
myself.closePopup();
});
};
this.closePopup = function() {
thePKT_SIGNUP.sendMessage("close");
};
this.sanitizeText = function(s) {
var sanitizeMap = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': '&quot;',
"'": '&#39;'
};
if (typeof s !== 'string')
{
return '';
}
else
{
return String(s).replace(/[&<>"']/g, function (str) {
return sanitizeMap[str];
});
}
};
this.getTranslations = function()
{
var language = window.navigator.language.toLowerCase();
this.dictJSON = {};
var dictsuffix = 'en-US';
if (language.indexOf('en') == 0)
{
dictsuffix = 'en';
}
else if (language.indexOf('it') == 0)
{
dictsuffix = 'it';
}
else if (language.indexOf('fr-ca') == 0)
{
dictsuffix = 'fr';
}
else if (language.indexOf('fr') == 0)
{
dictsuffix = 'fr';
}
else if (language.indexOf('de') == 0)
{
dictsuffix = 'de';
}
else if (language.indexOf('es-es') == 0)
{
dictsuffix = 'es';
}
else if (language.indexOf('es-419') == 0)
{
dictsuffix = 'es_419';
}
else if (language.indexOf('es') == 0)
{
dictsuffix = 'es';
}
else if (language.indexOf('ja') == 0)
{
dictsuffix = 'ja';
}
else if (language.indexOf('nl') == 0)
{
dictsuffix = 'nl';
}
else if (language.indexOf('pt-pt') == 0)
{
dictsuffix = 'pt_PT';
}
else if (language.indexOf('pt') == 0)
{
dictsuffix = 'pt_BR';
}
else if (language.indexOf('ru') == 0)
{
dictsuffix = 'ru';
}
else if (language.indexOf('zh-tw') == 0)
{
dictsuffix = 'zh_TW';
}
else if (language.indexOf('zh') == 0)
{
dictsuffix = 'zh_CN';
}
else if (language.indexOf('ko') == 0)
{
dictsuffix = 'ko';
}
else if (language.indexOf('pl') == 0)
{
dictsuffix = 'pl';
}
// TODO: when we add all dictionaries, modify this, but for now hard code to English
dictsuffix = 'en';
this.dictJSON = Translations[dictsuffix];
};
};
PKT_SIGNUP_OVERLAY.prototype = {
create : function()
{
var myself = this;
if (this.active)
{
return;
}
this.active = true;
// set translations
myself.getTranslations();
// Create actual content
$('body').append(Handlebars.templates.signup_shell(this.dictJSON));
// tell background we're ready
thePKT_SIGNUP.sendMessage("show");
// close events
this.initCloseTabEvents();
}
};
// Layer between Bookmarklet and Extensions
var PKT_SIGNUP = function () {};
PKT_SIGNUP.prototype = {
init: function () {
if (this.inited) {
return;
}
this.overlay = new PKT_SIGNUP_OVERLAY();
this.inited = true;
},
addMessageListener: function(messageId, callback) {
Messaging.addMessageListener(messageId, callback);
},
sendMessage: function(messageId, payload, callback) {
Messaging.sendMessage(messageId, payload, callback);
},
create: function() {
this.overlay.create();
// tell back end we're ready
thePKT_SIGNUP.sendMessage("show");
}
}
$(function()
{
if(!window.thePKT_SIGNUP){
var thePKT_SIGNUP = new PKT_SIGNUP();
window.thePKT_SIGNUP = thePKT_SIGNUP;
thePKT_SIGNUP.init();
}
window.thePKT_SIGNUP.create();
});

View File

@ -0,0 +1,43 @@
(function() {
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
templates['saved_premiumshell'] = template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;
return "<div class=\"pkt_ext_suggestedtag_detail pkt_ext_suggestedtag_detail_loading\">\n <h4>"
+ escapeExpression(((helper = (helper = helpers.suggestedtags || (depth0 != null ? depth0.suggestedtags : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"suggestedtags","hash":{},"data":data}) : helper)))
+ "</h4>\n <div class=\"pkt_ext_loadingspinner\"><div></div></div>\n <ul class=\"pkt_ext_cf\">\n </ul>\n</div>";
},"useData":true});
templates['saved_shell'] = template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;
return "<div class=\"pkt_ext_initload\"> \n <div class=\"pkt_ext_loadingspinner\"><div></div></div>\n</div> \n<div class=\"pkt_ext_detail\"> \n <div class=\"pkt_ext_logo\"></div>\n <div class=\"pkt_ext_topdetail\">\n <h2>"
+ escapeExpression(((helper = (helper = helpers.pagesaved || (depth0 != null ? depth0.pagesaved : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"pagesaved","hash":{},"data":data}) : helper)))
+ "</h2>\n <nav class=\"pkt_ext_item_actions pkt_ext_cf\">\n <ul>\n <li><a class=\"pkt_ext_removeitem\" href=\"#\">"
+ escapeExpression(((helper = (helper = helpers.removepage || (depth0 != null ? depth0.removepage : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"removepage","hash":{},"data":data}) : helper)))
+ "</a></li>\n <li class=\"pkt_ext_actions_separator\"></li> \n <li><a class=\"pkt_ext_openpocket\" href=\"http://firefox.dev.readitlater.com/a\" target=\"_blank\">"
+ escapeExpression(((helper = (helper = helpers.viewlist || (depth0 != null ? depth0.viewlist : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"viewlist","hash":{},"data":data}) : helper)))
+ "</a></li>\n </ul>\n </nav> \n </div>\n <p class=\"pkt_ext_edit_msg\"></p> \n <div class=\"pkt_ext_tag_detail pkt_ext_cf\">\n <div class=\"pkt_ext_tag_input_wrapper\">\n <div class=\"pkt_ext_tag_input_blocker\"></div>\n <input class=\"pkt_ext_tag_input\" type=\"text\" placeholder=\""
+ escapeExpression(((helper = (helper = helpers.addtags || (depth0 != null ? depth0.addtags : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"addtags","hash":{},"data":data}) : helper)))
+ "\">\n </div>\n <a href=\"#\" class=\"pkt_ext_btn pkt_ext_btn_disabled\">"
+ escapeExpression(((helper = (helper = helpers.save || (depth0 != null ? depth0.save : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"save","hash":{},"data":data}) : helper)))
+ "</a>\n </div>\n</div>";
},"useData":true});
templates['signup_shell'] = template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;
return "<h4 class=\"pkt_ext_introrecommend\">Recommended by Firefox</h4>\n<div class=\"pkt_ext_introdetail\">\n <h2 class=\"pkt_ext_logo\">Pocket</h2>\n <div class=\"pkt_ext_introimg\"></div>\n <p class=\"pkt_ext_tagline\">"
+ escapeExpression(((helper = (helper = helpers.tagline || (depth0 != null ? depth0.tagline : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"tagline","hash":{},"data":data}) : helper)))
+ "</p>\n <p><a class=\"pkt_ext_learnmore\" href=\"http://getpocket.com\" target=\"_blank\">"
+ escapeExpression(((helper = (helper = helpers.learnmore || (depth0 != null ? depth0.learnmore : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"learnmore","hash":{},"data":data}) : helper)))
+ "</a></p>\n</div>\n<div class=\"pkt_ext_signupdetail\">\n <h4>"
+ escapeExpression(((helper = (helper = helpers.signuptosave || (depth0 != null ? depth0.signuptosave : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"signuptosave","hash":{},"data":data}) : helper)))
+ "</h4>\n <p class=\"btn-container\"><a href=\"https://firefox.dev.readitlater.com/ff_signup\" target=_blank\" class=\"btn signup-btn-firefox\"><span class=\"logo\"></span><span class=\"text\">"
+ escapeExpression(((helper = (helper = helpers.signupfirefox || (depth0 != null ? depth0.signupfirefox : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"signupfirefox","hash":{},"data":data}) : helper)))
+ "</span></a> <a class=\"ff_signuphelp\" href=\"https://www.mozilla.org\" target=\"_blank\">"
+ escapeExpression(((helper = (helper = helpers.help || (depth0 != null ? depth0.help : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"help","hash":{},"data":data}) : helper)))
+ "</a></p>\n <p class=\"btn-container\"><a href=\"http://firefox.dev.readitlater.com/signup?force=email&src=extension\" target=\"_blank\" class=\"btn btn-secondary signup-btn-email signup-btn-initstate\">"
+ escapeExpression(((helper = (helper = helpers.signupemail || (depth0 != null ? depth0.signupemail : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"signupemail","hash":{},"data":data}) : helper)))
+ "</a></p>\n <p class=\"alreadyhave\">"
+ escapeExpression(((helper = (helper = helpers.alreadyhaveacct || (depth0 != null ? depth0.alreadyhaveacct : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"alreadyhaveacct","hash":{},"data":data}) : helper)))
+ " <a href=\"http://firefox.dev.readitlater.com/login?ep=3&src=extension\" target=\"_blank\">"
+ escapeExpression(((helper = (helper = helpers.loginnow || (depth0 != null ? depth0.loginnow : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"loginnow","hash":{},"data":data}) : helper)))
+ "</a>.</p>\n</div>";
},"useData":true});
})();

View File

@ -0,0 +1,660 @@
/*
handlebars v2.0.0
Copyright (C) 2011-2014 by Yehuda Katz
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.
@license
*/
/* exported Handlebars */
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([], factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root.Handlebars = root.Handlebars || factory();
}
}(this, function () {
// handlebars/safe-string.js
var __module3__ = (function() {
"use strict";
var __exports__;
// Build out our basic SafeString type
function SafeString(string) {
this.string = string;
}
SafeString.prototype.toString = function() {
return "" + this.string;
};
__exports__ = SafeString;
return __exports__;
})();
// handlebars/utils.js
var __module2__ = (function(__dependency1__) {
"use strict";
var __exports__ = {};
/*jshint -W004 */
var SafeString = __dependency1__;
var escape = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#x27;",
"`": "&#x60;"
};
var badChars = /[&<>"'`]/g;
var possible = /[&<>"'`]/;
function escapeChar(chr) {
return escape[chr];
}
function extend(obj /* , ...source */) {
for (var i = 1; i < arguments.length; i++) {
for (var key in arguments[i]) {
if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
obj[key] = arguments[i][key];
}
}
}
return obj;
}
__exports__.extend = extend;var toString = Object.prototype.toString;
__exports__.toString = toString;
// Sourced from lodash
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
var isFunction = function(value) {
return typeof value === 'function';
};
// fallback for older versions of Chrome and Safari
/* istanbul ignore next */
if (isFunction(/x/)) {
isFunction = function(value) {
return typeof value === 'function' && toString.call(value) === '[object Function]';
};
}
var isFunction;
__exports__.isFunction = isFunction;
/* istanbul ignore next */
var isArray = Array.isArray || function(value) {
return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
};
__exports__.isArray = isArray;
function escapeExpression(string) {
// don't escape SafeStrings, since they're already safe
if (string instanceof SafeString) {
return string.toString();
} else if (string == null) {
return "";
} else if (!string) {
return string + '';
}
// Force a string conversion as this will be done by the append regardless and
// the regex test will do this transparently behind the scenes, causing issues if
// an object's to string has escaped characters in it.
string = "" + string;
if(!possible.test(string)) { return string; }
return string.replace(badChars, escapeChar);
}
__exports__.escapeExpression = escapeExpression;function isEmpty(value) {
if (!value && value !== 0) {
return true;
} else if (isArray(value) && value.length === 0) {
return true;
} else {
return false;
}
}
__exports__.isEmpty = isEmpty;function appendContextPath(contextPath, id) {
return (contextPath ? contextPath + '.' : '') + id;
}
__exports__.appendContextPath = appendContextPath;
return __exports__;
})(__module3__);
// handlebars/exception.js
var __module4__ = (function() {
"use strict";
var __exports__;
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
function Exception(message, node) {
var line;
if (node && node.firstLine) {
line = node.firstLine;
message += ' - ' + line + ':' + node.firstColumn;
}
var tmp = Error.prototype.constructor.call(this, message);
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
for (var idx = 0; idx < errorProps.length; idx++) {
this[errorProps[idx]] = tmp[errorProps[idx]];
}
if (line) {
this.lineNumber = line;
this.column = node.firstColumn;
}
}
Exception.prototype = new Error();
__exports__ = Exception;
return __exports__;
})();
// handlebars/base.js
var __module1__ = (function(__dependency1__, __dependency2__) {
"use strict";
var __exports__ = {};
var Utils = __dependency1__;
var Exception = __dependency2__;
var VERSION = "2.0.0";
__exports__.VERSION = VERSION;var COMPILER_REVISION = 6;
__exports__.COMPILER_REVISION = COMPILER_REVISION;
var REVISION_CHANGES = {
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
2: '== 1.0.0-rc.3',
3: '== 1.0.0-rc.4',
4: '== 1.x.x',
5: '== 2.0.0-alpha.x',
6: '>= 2.0.0-beta.1'
};
__exports__.REVISION_CHANGES = REVISION_CHANGES;
var isArray = Utils.isArray,
isFunction = Utils.isFunction,
toString = Utils.toString,
objectType = '[object Object]';
function HandlebarsEnvironment(helpers, partials) {
this.helpers = helpers || {};
this.partials = partials || {};
registerDefaultHelpers(this);
}
__exports__.HandlebarsEnvironment = HandlebarsEnvironment;HandlebarsEnvironment.prototype = {
constructor: HandlebarsEnvironment,
logger: logger,
log: log,
registerHelper: function(name, fn) {
if (toString.call(name) === objectType) {
if (fn) { throw new Exception('Arg not supported with multiple helpers'); }
Utils.extend(this.helpers, name);
} else {
this.helpers[name] = fn;
}
},
unregisterHelper: function(name) {
delete this.helpers[name];
},
registerPartial: function(name, partial) {
if (toString.call(name) === objectType) {
Utils.extend(this.partials, name);
} else {
this.partials[name] = partial;
}
},
unregisterPartial: function(name) {
delete this.partials[name];
}
};
function registerDefaultHelpers(instance) {
instance.registerHelper('helperMissing', function(/* [args, ]options */) {
if(arguments.length === 1) {
// A missing field in a {{foo}} constuct.
return undefined;
} else {
// Someone is actually trying to call something, blow up.
throw new Exception("Missing helper: '" + arguments[arguments.length-1].name + "'");
}
});
instance.registerHelper('blockHelperMissing', function(context, options) {
var inverse = options.inverse,
fn = options.fn;
if(context === true) {
return fn(this);
} else if(context === false || context == null) {
return inverse(this);
} else if (isArray(context)) {
if(context.length > 0) {
if (options.ids) {
options.ids = [options.name];
}
return instance.helpers.each(context, options);
} else {
return inverse(this);
}
} else {
if (options.data && options.ids) {
var data = createFrame(options.data);
data.contextPath = Utils.appendContextPath(options.data.contextPath, options.name);
options = {data: data};
}
return fn(context, options);
}
});
instance.registerHelper('each', function(context, options) {
if (!options) {
throw new Exception('Must pass iterator to #each');
}
var fn = options.fn, inverse = options.inverse;
var i = 0, ret = "", data;
var contextPath;
if (options.data && options.ids) {
contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.';
}
if (isFunction(context)) { context = context.call(this); }
if (options.data) {
data = createFrame(options.data);
}
if(context && typeof context === 'object') {
if (isArray(context)) {
for(var j = context.length; i<j; i++) {
if (data) {
data.index = i;
data.first = (i === 0);
data.last = (i === (context.length-1));
if (contextPath) {
data.contextPath = contextPath + i;
}
}
ret = ret + fn(context[i], { data: data });
}
} else {
for(var key in context) {
if(context.hasOwnProperty(key)) {
if(data) {
data.key = key;
data.index = i;
data.first = (i === 0);
if (contextPath) {
data.contextPath = contextPath + key;
}
}
ret = ret + fn(context[key], {data: data});
i++;
}
}
}
}
if(i === 0){
ret = inverse(this);
}
return ret;
});
instance.registerHelper('if', function(conditional, options) {
if (isFunction(conditional)) { conditional = conditional.call(this); }
// Default behavior is to render the positive path if the value is truthy and not empty.
// The `includeZero` option may be set to treat the condtional as purely not empty based on the
// behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
if ((!options.hash.includeZero && !conditional) || Utils.isEmpty(conditional)) {
return options.inverse(this);
} else {
return options.fn(this);
}
});
instance.registerHelper('unless', function(conditional, options) {
return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
});
instance.registerHelper('with', function(context, options) {
if (isFunction(context)) { context = context.call(this); }
var fn = options.fn;
if (!Utils.isEmpty(context)) {
if (options.data && options.ids) {
var data = createFrame(options.data);
data.contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]);
options = {data:data};
}
return fn(context, options);
} else {
return options.inverse(this);
}
});
instance.registerHelper('log', function(message, options) {
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
instance.log(level, message);
});
instance.registerHelper('lookup', function(obj, field) {
return obj && obj[field];
});
}
var logger = {
methodMap: { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' },
// State enum
DEBUG: 0,
INFO: 1,
WARN: 2,
ERROR: 3,
level: 3,
// can be overridden in the host environment
log: function(level, message) {
if (logger.level <= level) {
var method = logger.methodMap[level];
if (typeof console !== 'undefined' && console[method]) {
console[method].call(console, message);
}
}
}
};
__exports__.logger = logger;
var log = logger.log;
__exports__.log = log;
var createFrame = function(object) {
var frame = Utils.extend({}, object);
frame._parent = object;
return frame;
};
__exports__.createFrame = createFrame;
return __exports__;
})(__module2__, __module4__);
// handlebars/runtime.js
var __module5__ = (function(__dependency1__, __dependency2__, __dependency3__) {
"use strict";
var __exports__ = {};
var Utils = __dependency1__;
var Exception = __dependency2__;
var COMPILER_REVISION = __dependency3__.COMPILER_REVISION;
var REVISION_CHANGES = __dependency3__.REVISION_CHANGES;
var createFrame = __dependency3__.createFrame;
function checkRevision(compilerInfo) {
var compilerRevision = compilerInfo && compilerInfo[0] || 1,
currentRevision = COMPILER_REVISION;
if (compilerRevision !== currentRevision) {
if (compilerRevision < currentRevision) {
var runtimeVersions = REVISION_CHANGES[currentRevision],
compilerVersions = REVISION_CHANGES[compilerRevision];
throw new Exception("Template was precompiled with an older version of Handlebars than the current runtime. "+
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").");
} else {
// Use the embedded version info since the runtime doesn't know about this revision yet
throw new Exception("Template was precompiled with a newer version of Handlebars than the current runtime. "+
"Please update your runtime to a newer version ("+compilerInfo[1]+").");
}
}
}
__exports__.checkRevision = checkRevision;// TODO: Remove this line and break up compilePartial
function template(templateSpec, env) {
/* istanbul ignore next */
if (!env) {
throw new Exception("No environment passed to template");
}
if (!templateSpec || !templateSpec.main) {
throw new Exception('Unknown template object: ' + typeof templateSpec);
}
// Note: Using env.VM references rather than local var references throughout this section to allow
// for external users to override these as psuedo-supported APIs.
env.VM.checkRevision(templateSpec.compiler);
var invokePartialWrapper = function(partial, indent, name, context, hash, helpers, partials, data, depths) {
if (hash) {
context = Utils.extend({}, context, hash);
}
var result = env.VM.invokePartial.call(this, partial, name, context, helpers, partials, data, depths);
if (result == null && env.compile) {
var options = { helpers: helpers, partials: partials, data: data, depths: depths };
partials[name] = env.compile(partial, { data: data !== undefined, compat: templateSpec.compat }, env);
result = partials[name](context, options);
}
if (result != null) {
if (indent) {
var lines = result.split('\n');
for (var i = 0, l = lines.length; i < l; i++) {
if (!lines[i] && i + 1 === l) {
break;
}
lines[i] = indent + lines[i];
}
result = lines.join('\n');
}
return result;
} else {
throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
}
};
// Just add water
var container = {
lookup: function(depths, name) {
var len = depths.length;
for (var i = 0; i < len; i++) {
if (depths[i] && depths[i][name] != null) {
return depths[i][name];
}
}
},
lambda: function(current, context) {
return typeof current === 'function' ? current.call(context) : current;
},
escapeExpression: Utils.escapeExpression,
invokePartial: invokePartialWrapper,
fn: function(i) {
return templateSpec[i];
},
programs: [],
program: function(i, data, depths) {
var programWrapper = this.programs[i],
fn = this.fn(i);
if (data || depths) {
programWrapper = program(this, i, fn, data, depths);
} else if (!programWrapper) {
programWrapper = this.programs[i] = program(this, i, fn);
}
return programWrapper;
},
data: function(data, depth) {
while (data && depth--) {
data = data._parent;
}
return data;
},
merge: function(param, common) {
var ret = param || common;
if (param && common && (param !== common)) {
ret = Utils.extend({}, common, param);
}
return ret;
},
noop: env.VM.noop,
compilerInfo: templateSpec.compiler
};
var ret = function(context, options) {
options = options || {};
var data = options.data;
ret._setup(options);
if (!options.partial && templateSpec.useData) {
data = initData(context, data);
}
var depths;
if (templateSpec.useDepths) {
depths = options.depths ? [context].concat(options.depths) : [context];
}
return templateSpec.main.call(container, context, container.helpers, container.partials, data, depths);
};
ret.isTop = true;
ret._setup = function(options) {
if (!options.partial) {
container.helpers = container.merge(options.helpers, env.helpers);
if (templateSpec.usePartial) {
container.partials = container.merge(options.partials, env.partials);
}
} else {
container.helpers = options.helpers;
container.partials = options.partials;
}
};
ret._child = function(i, data, depths) {
if (templateSpec.useDepths && !depths) {
throw new Exception('must pass parent depths');
}
return program(container, i, templateSpec[i], data, depths);
};
return ret;
}
__exports__.template = template;function program(container, i, fn, data, depths) {
var prog = function(context, options) {
options = options || {};
return fn.call(container, context, container.helpers, container.partials, options.data || data, depths && [context].concat(depths));
};
prog.program = i;
prog.depth = depths ? depths.length : 0;
return prog;
}
__exports__.program = program;function invokePartial(partial, name, context, helpers, partials, data, depths) {
var options = { partial: true, helpers: helpers, partials: partials, data: data, depths: depths };
if(partial === undefined) {
throw new Exception("The partial " + name + " could not be found");
} else if(partial instanceof Function) {
return partial(context, options);
}
}
__exports__.invokePartial = invokePartial;function noop() { return ""; }
__exports__.noop = noop;function initData(context, data) {
if (!data || !('root' in data)) {
data = data ? createFrame(data) : {};
data.root = context;
}
return data;
}
return __exports__;
})(__module2__, __module4__, __module1__);
// handlebars.runtime.js
var __module0__ = (function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__) {
"use strict";
var __exports__;
/*globals Handlebars: true */
var base = __dependency1__;
// Each of these augment the Handlebars object. No need to setup here.
// (This is done to easily share code between commonjs and browse envs)
var SafeString = __dependency2__;
var Exception = __dependency3__;
var Utils = __dependency4__;
var runtime = __dependency5__;
// For compatibility and usage outside of module systems, make the Handlebars object a namespace
var create = function() {
var hb = new base.HandlebarsEnvironment();
Utils.extend(hb, base);
hb.SafeString = SafeString;
hb.Exception = Exception;
hb.Utils = Utils;
hb.escapeExpression = Utils.escapeExpression;
hb.VM = runtime;
hb.template = function(spec) {
return runtime.template(spec, hb);
};
return hb;
};
var Handlebars = create();
Handlebars.create = create;
Handlebars['default'] = Handlebars;
__exports__ = Handlebars;
return __exports__;
})(__module1__, __module3__, __module4__, __module2__, __module5__);
return __module0__;
}));

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<title>Pocket: Page Saved</title>
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/firasans.css">
<link rel="stylesheet" href="css/saved.css">
</head>
<body class="pkt_ext_containersaved" aria-live="polite">
<script type="text/javascript" src="js/vendor/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="js/vendor/handlebars.runtime.js"></script>
<script type="text/javascript" src="js/vendor/jquery.tokeninput.min.js"></script>
<script type="text/javascript" src="js/dictionary.js"></script>
<script type="text/javascript" src="js/tmpl.js"></script>
<script type="text/javascript" src="js/messages.js"></script>
<script type="text/javascript" src="js/saved.js"></script>
</body>
</html>

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<title>Pocket: Sign Up</title>
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/firasans.css">
<link rel="stylesheet" href="css/signup.css">
</head>
<body class="pkt_ext_containersignup" aria-live="polite">
<script type="text/javascript" src="js/vendor/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="js/vendor/handlebars.runtime.js"></script>
<script type="text/javascript" src="js/dictionary.js"></script>
<script type="text/javascript" src="js/tmpl.js"></script>
<script type="text/javascript" src="js/messages.js"></script>
<script type="text/javascript" src="js/signup.js"></script>
</body>
</html>

View File

@ -0,0 +1,6 @@
<div class="pkt_ext_suggestedtag_detail pkt_ext_suggestedtag_detail_loading">
<h4>{{suggestedtags}}</h4>
<div class="pkt_ext_loadingspinner"><div></div></div>
<ul class="pkt_ext_cf">
</ul>
</div>

View File

@ -0,0 +1,24 @@
<div class="pkt_ext_initload">
<div class="pkt_ext_loadingspinner"><div></div></div>
</div>
<div class="pkt_ext_detail">
<div class="pkt_ext_logo"></div>
<div class="pkt_ext_topdetail">
<h2>{{pagesaved}}</h2>
<nav class="pkt_ext_item_actions pkt_ext_cf">
<ul>
<li><a class="pkt_ext_removeitem" href="#">{{removepage}}</a></li>
<li class="pkt_ext_actions_separator"></li>
<li><a class="pkt_ext_openpocket" href="http://firefox.dev.readitlater.com/a" target="_blank">{{viewlist}}</a></li>
</ul>
</nav>
</div>
<p class="pkt_ext_edit_msg"></p>
<div class="pkt_ext_tag_detail pkt_ext_cf">
<div class="pkt_ext_tag_input_wrapper">
<div class="pkt_ext_tag_input_blocker"></div>
<input class="pkt_ext_tag_input" type="text" placeholder="{{addtags}}">
</div>
<a href="#" class="pkt_ext_btn pkt_ext_btn_disabled">{{save}}</a>
</div>
</div>

View File

@ -0,0 +1,13 @@
<h4 class="pkt_ext_introrecommend">Recommended by Firefox</h4>
<div class="pkt_ext_introdetail">
<h2 class="pkt_ext_logo">Pocket</h2>
<div class="pkt_ext_introimg"></div>
<p class="pkt_ext_tagline">{{tagline}}</p>
<p><a class="pkt_ext_learnmore" href="http://getpocket.com" target="_blank">{{learnmore}}</a></p>
</div>
<div class="pkt_ext_signupdetail">
<h4>{{signuptosave}}</h4>
<p class="btn-container"><a href="https://firefox.dev.readitlater.com/ff_signup" target=_blank" class="btn signup-btn-firefox"><span class="logo"></span><span class="text">{{signupfirefox}}</span></a> <a class="ff_signuphelp" href="https://www.mozilla.org" target="_blank">{{help}}</a></p>
<p class="btn-container"><a href="http://firefox.dev.readitlater.com/signup?force=email&src=extension" target="_blank" class="btn btn-secondary signup-btn-email signup-btn-initstate">{{signupemail}}</a></p>
<p class="alreadyhave">{{alreadyhaveacct}} <a href="http://firefox.dev.readitlater.com/login?ep=3&src=extension" target="_blank">{{loginnow}}</a>.</p>
</div>

View File

@ -0,0 +1,581 @@
/*
* LICENSE
*
* POCKET MARKS
*
* Notwithstanding the permitted uses of the Software (as defined below) pursuant to the license set forth below, "Pocket," "Read It Later" and the Pocket icon and logos (collectively, the Pocket Marks) are registered and common law trademarks of Read It Later, Inc. This means that, while you have considerable freedom to redistribute and modify the Software, there are tight restrictions on your ability to use the Pocket Marks. This license does not grant you any rights to use the Pocket Marks except as they are embodied in the Software.
*
* ---
*
* SOFTWARE
*
* 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.
*/
/*
* Pocket API module
*
* Public API Documentation: http://getpocket.com/developer/
*
*
* Definition of keys stored in preferences to preserve user state:
* premium_status: Current premium status for logged in user if available
* Can be 0 for no premium and 1 for premium
* latestSince: Last timestamp a save happened
* tags: All tags for logged in user
* usedTags: All used tags from within the extension sorted by recency
*/
var pktApi = (function() {
/**
* Configuration
*/
// Base url for all api calls
// TODO: This is a dev server and will be changed before launch
var pocketAPIhost = Services.prefs.getCharPref("browser.pocket.hostname");
// Base url for all api calls
var baseAPIUrl = "https://" + pocketAPIhost + "/v3";
/**
* Auth keys for the API requests
*/
var oAuthConsumerKey = Services.prefs.getCharPref("browser.pocket.oAuthConsumerKey");
/**
*
*/
var prefBranch = Services.prefs.getBranch("browser.pocket.settings.");
/**
* Helper
*/
var extend = function(out) {
out = out || {};
for (var i = 1; i < arguments.length; i++) {
if (!arguments[i])
continue;
for (var key in arguments[i]) {
if (arguments[i].hasOwnProperty(key))
out[key] = arguments[i][key];
}
}
return out;
}
/**
* Settings
*/
/**
* Wrapper for different plattforms to get settings for a given key
* @param {string} key A string containing the name of the key you want to
* retrieve the value of
* @return {string} String containing the value of the key. If the key
* does not exist, null is returned
*/
function getSetting(key) {
// TODO : Move this to sqlite or a local file so it's not editable (and is safer)
// https://developer.mozilla.org/en-US/Add-ons/Overlay_Extensions/XUL_School/Local_Storage
if (!prefBranch.prefHasUserValue(key))
return;
return prefBranch.getComplexValue(key, Components.interfaces.nsISupportsString).data;
}
/**
* Wrapper for different plattforms to set a value for a given key in settings
* @param {string} key A string containing the name of the key you want
* to create/update.
* @param {string} value String containing the value you want to give
* the key you are creating/updating.
*/
function setSetting(key, value) {
// TODO : Move this to sqlite or a local file so it's not editable (and is safer)
// https://developer.mozilla.org/en-US/Add-ons/Overlay_Extensions/XUL_School/Local_Storage
if (!value)
prefBranch.clearUserPref(key);
else
{
// We use complexValue as tags can have utf-8 characters in them
var str = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);
str.data = value;
prefBranch.setComplexValue(key, Components.interfaces.nsISupportsString, str);
}
}
/**
* Auth
*/
/*
* All cookies from the Pocket domain
* The return format: { cookieName:cookieValue, cookieName:cookieValue, ... }
*/
function getCookiesFromPocket() {
var cookieManager = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager2);
var pocketCookies = cookieManager.getCookiesFromHost(pocketAPIhost);
var cookies = {};
while (pocketCookies.hasMoreElements()) {
var cookie = pocketCookies.getNext().QueryInterface(Ci.nsICookie2);
cookies[cookie.name] = cookie.value;
}
return cookies;
}
/**
* Returns access token or undefined if no logged in user was found
* @return {string | undefined} Access token for logged in user user
*/
function getAccessToken() {
var pocketCookies = getCookiesFromPocket();
// If no cookie was found just return undefined
if (typeof pocketCookies['ftv1'] === "undefined") {
return undefined;
}
// Check if a new user logged in in the meantime and clearUserData if so
var sessionId = pocketCookies['fsv1'];
var lastSessionId = getSetting('fsv1');
if (sessionId !== lastSessionId) {
clearUserData();
setSetting("fsv1", sessionId);
}
// Return access token
return pocketCookies['ftv1'];
}
/**
* Returns users logged in status
* @return {Boolean} Users logged in status
*/
function isUserLoggedIn() {
return (typeof getAccessToken() !== "undefined");
}
/**
* API
*/
/**
* Helper function for executing api requests. It mainly configures the
* ajax call with default values like type, headers or dataType for an api call.
* This function is for internal usage only.
* @param {Object} options
* Possible keys:
* - {string} path: This should be the Pocket API
* endpoint to call. For example providing the path
* "/get" would result in a call to getpocket.com/v3/get
* - {Object|undefined} data: Gets passed on to the jQuery ajax
* call as data parameter
* - {function(Object data, XMLHttpRequest xhr) | undefined} success:
* A function to be called if the request succeeds.
* - {function(Error errorThrown, XMLHttpRequest xhr) | undefined} error:
* A function to be called if the request fails.
* @return {Boolean} Returns Boolean whether the api call started sucessfully
*
*/
function apiRequest(options) {
if ((typeof options === "undefined") || (typeof options.path === "undefined")) {
return false;
}
var url = baseAPIUrl + options.path;
var data = options.data || {};
data.locale_lang = window.navigator.language;
data.consumer_key = oAuthConsumerKey;
var request = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Components.interfaces.nsIXMLHttpRequest);
request.open("POST", url, true);
request.onreadystatechange = function(e){
if (request.readyState == 4) {
if (request.status === 200) {
if (options.success) {
options.success(JSON.parse(request.response), request);
}
return;
}
// TODO: Better error handling
if (options.error) {
// Check to handle Pocket error
var errorMessage = request.getResponseHeader("X-Error");
if (typeof errorMessage !== "undefined") {
options.error(new Error(errorMessage), request);
return;
}
// Handle other error
options.error(new Error(request.statusText), request);
}
}
};
// TODO - do we want to pass a special user agent?
//request.setRequestHeader("User-Agent" , 'Pocket Firefox ' + this.APP.v);
// Set headers
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
request.setRequestHeader('X-Accept',' application/json');
// Serialize and Fire off the request
var str = [];
for(var p in data) {
if (data.hasOwnProperty(p)) {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(data[p]));
}
}
request.send(str.join("&"));
return true;
}
/**
* Cleans all settings for the previously logged in user
*/
function clearUserData() {
// Clear stored information
setSetting("premium_status", undefined);
setSetting("latestSince", undefined);
setSetting("tags", undefined);
setSetting("usedTags", undefined);
}
/**
* Add a new link to Pocket
* @param {string} url URL of the link
* @param {Object | undefined} options Can provide an title and have a
* success and error callbacks
* @return {Boolean} Returns Boolean whether the api call started sucessfully
*/
function addLink(url, options) {
var since = getSetting('latestSince');
var accessToken = getAccessToken();
var sendData = {
access_token: accessToken,
url: url,
since: since ? since : 0
};
var title = options.title;
if (title !== "undefined") {
sendData.title = title;
};
return apiRequest({
path: "/firefox/save",
data: sendData,
success: function(data) {
// Update premium status, tags and since
var tags = data.tags;
if ((typeof tags !== "undefined") && Array.isArray(tags)) {
// If a tagslist is in the response replace the tags
setSetting('tags', JSON.stringify(data.tags));
}
// Update premium status
var premiumStatus = data.premium_status;
if (typeof premiumStatus !== "undefined") {
// If a premium_status is in the response replace the premium_status
setSetting("premium_status", premiumStatus);
}
// Save since value for further requests
setSetting('latestSince', data.since);
if (options.success) {
options.success.apply(options, Array.apply(null, arguments));
}
},
error: options.error
});
return sendAction(action, options);
}
/**
* Delete an item identified by item id from the users list
* @param {string} itemId The id from the item we want to remove
* @param {Object | undefined} options Can provide an actionInfo object with
* further data to send to the API. Can
* have success and error callbacks
* @return {Boolean} Returns Boolean whether the api call started sucessfully
*/
function deleteItem(itemId, options) {
var action = {
action: "delete",
item_id: itemId
};
return sendAction(action, options);
}
/**
* General function to send all kinds of actions like adding of links or
* removing of items via the API
* @param {Object} action Action object
* @param {Object | undefined} options Can provide an actionInfo object
* with further data to send to the
* API. Can have success and error
* callbacks
* @return {Boolean} Returns Boolean whether the api call started sucessfully
*/
function sendAction(action, options) {
// Options can have an 'actionInfo' object. This actionInfo object gets
// passed through to the action object that will be send to the API endpoint
if (typeof options.actionInfo !== 'undefined') {
action = extend(action, options.actionInfo);
}
return sendActions([action], options);
}
/**
* General function to send all kinds of actions like adding of links or
* removing of items via the API
* @param {Array} actions Array of action objects
* @param {Object | undefined} options Can have success and error callbacks
* @return {Boolean} Returns Boolean whether the api call started sucessfully
*/
function sendActions(actions, options) {
return apiRequest({
path: "/send",
data: {
access_token: getAccessToken(),
actions: JSON.stringify(actions)
},
success: options.success,
error: options.error
});
}
/**
* Handling Tags
*/
/**
* Add tags to the item identified by the url. Also updates the used tags
* list
* @param {string} itemId The item identifier by item id
* @param {Array} tags Tags adding to the item
* @param {Object | undefined} options Can provide an actionInfo object with
* further data to send to the API. Can
* have success and error callbacks
* @return {Boolean} Returns Boolean whether the api call started sucessfully
*/
function addTagsToItem(itemId, tags, options) {
return addTags({item_id: itemId}, tags, options);
}
/**
* Add tags to the item identified by the url. Also updates the used tags
* list
* @param {string} url The item identifier by url
* @param {Array} tags Tags adding to the item
* @param {Object} options Can provide an actionInfo object with further
* data to send to the API. Can have success and error
* callbacks
* @return {Boolean} Returns Boolean whether the api call started sucessfully
*/
function addTagsToURL(url, tags, options) {
return addTags({url: url}, tags, options);
}
/**
* Helper function to execute the add tags api call. Will be used from addTagsToURL
* and addTagsToItem but not exposed outside
* @param {string} actionPart Specific action part to add to action
* @param {Array} tags Tags adding to the item
* @param {Object | undefined} options Can provide an actionInfo object with
* further data to send to the API. Can
* have success and error callbacks
* @return {Boolean} Returns Boolean whether the api call started sucessfully
*/
function addTags(actionPart, tags, options) {
// Tags add action
var action = {
action: "tags_add",
tags: tags
};
action = extend(action, actionPart);
// Backup the success callback as we need it later
var finalSuccessCallback = options.success;
// Switch the success callback
options.success = function(data) {
// Update used tags
var usedTagsJSON = getSetting("usedTags");
var usedTags = usedTagsJSON ? JSON.parse(usedTagsJSON) : {};
// Check for each tag if it's already in the used tags
for (var i = 0; i < tags.length; i++) {
var tagToSave = tags[i].trim();
var newUsedTagObject = {
"tag": tagToSave,
"timestamp": new Date()
};
usedTags[tagToSave] = newUsedTagObject;
}
setSetting("usedTags", JSON.stringify(usedTags));
// Let the callback know that we are finished
if (finalSuccessCallback) {
finalSuccessCallback(data);
}
};
// Execute the action
return sendAction(action, options);
}
/**
* Get all cached tags and used tags within the callback
* @param {function(Array, Array, Boolean)} callback
* Function with tags and used tags as parameter.
*/
function getTags(callback) {
var tagsFromSettings = function() {
var tagsJSON = getSetting("tags");
if (typeof tagsJSON !== "undefined") {
return JSON.parse(tagsJSON)
}
return [];
}
var sortedUsedTagsFromSettings = function() {
// Get and Sort used tags
var usedTags = [];
var usedTagsJSON = getSetting("usedTags");
if (typeof usedTagsJSON !== "undefined") {
var usedTagsObject = JSON.parse(usedTagsJSON);
var usedTagsObjectArray = [];
for (var tagKey in usedTagsObject) {
usedTagsObjectArray.push(usedTagsObject[tagKey]);
}
// Sort usedTagsObjectArray based on timestamp
usedTagsObjectArray.sort(function(a, b) {
a = new Date(a.timestamp);
b = new Date(b.timestamp);
return a < b ? -1 : a > b ? 1 : 0;
});
// Get all keys tags
for (var j = 0; j < usedTagsObjectArray.length; j++) {
usedTags.push(usedTagsObjectArray[j].tag);
}
// Reverse to set the last recent used tags to the front
usedTags.reverse();
}
return usedTags;
}
if (callback) {
var tags = tagsFromSettings();
var usedTags = sortedUsedTagsFromSettings();
callback(tags, usedTags);
}
}
/**
* Helper method to check if a user is premium or not
*/
function isPremiumUser() {
return getSetting('premium_status') == 1;
}
/**
* Fetch suggested tags for a given item id
* @param {string} itemId Item id of
* @param {Object | undefined} options Can provide an actionInfo object
* with further data to send to the API.
* Can have success and error callbacks
* @return {Boolean} Returns Boolean whether the api call started sucessfully
*/
function getSuggestedTagsForItem(itemId, options) {
return getSuggestedTags({item_id: itemId}, options);
}
/**
* Fetch suggested tags for a given URL
* @param {string} url (required) The item identifier by url
* @param {Object} options Can provide an actionInfo object with further
* data to send to the API. Can have success and error
* callbacks
* @return {Boolean} Returns Boolean whether the api call started sucessfully
*/
function getSuggestedTagsForURL(url, options) {
return getSuggestedTags({url: url}, options);
}
/**
* Helper function to get suggested tags
* @return {Boolean} Returns Boolean whether the api call started sucessfully
*/
function getSuggestedTags(data, options) {
data = data || {};
options = options || {};
data.access_token = getAccessToken();
return apiRequest({
path: "/getSuggestedTags",
data: data,
success: options.success,
error: options.error
});
}
/**
* Public functions
*/
return {
isUserLoggedIn : isUserLoggedIn,
clearUserData: clearUserData,
addLink: addLink,
deleteItem: deleteItem,
addTagsToItem: addTagsToItem,
addTagsToURL: addTagsToURL,
getTags: getTags,
isPremiumUser: isPremiumUser,
getSuggestedTagsForItem: getSuggestedTagsForItem,
getSuggestedTagsForURL: getSuggestedTagsForURL
};
}());

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -1179,7 +1179,9 @@ menuitem[checked="true"].subviewbutton > .menu-iconic-left {
#customizationui-widget-multiview > .panel-viewcontainer,
#customizationui-widget-multiview > .panel-viewcontainer > .panel-viewstack,
#PanelUI-panicView > .panel-subview-body,
#PanelUI-panicView {
#PanelUI-panicView,
#PanelUI-pocketView > .panel-subview-body,
#PanelUI-pocketView {
overflow: visible;
}
@ -1236,133 +1238,6 @@ menuitem[checked="true"].subviewbutton > .menu-iconic-left {
transform: scaleX(-1);
}
#pocket-login-required,
#pocket-page-saved {
text-align: center;
}
#pocket-page-saved {
/* Needed to undo the padding that .panel-subview-body
places on all subviews. */
margin: -4px;
}
#pocket-header {
background-image: url(chrome://browser/content/pocket/img/signup_logo.png);
background-repeat: no-repeat;
background-position: center;
margin: 1rem 0;
width: 116px;
height: 29px;
text-indent: -999rem;
}
.pocket-subheader {
color: #7d7e81;
margin-bottom: 3rem;
padding: 0 2rem;
}
.pocket-button {
-moz-appearance: none;
background-color: #d3505a;
background-image: linear-gradient(to bottom, #ee5f64, #d3505a);
border: 1px solid #d13644;
border-radius: 4px;
box-shadow: inset 0 1px 1px rgba(255,255,255,0.4);
color: #fff;
font-weight: bold;
min-width: 10rem;
padding: .5rem 3rem;
transition: background-position 0.1s linear;
}
#pocket-signup-or {
text-indent: -999rem;
margin: 1rem auto;
background-image: url(chrome://browser/content/pocket/img/signup_or.png);
width: 246px;
height: 27px;
display: block;
}
#pocket-signup-with-email {
background-color: #f2f2f2;
background-image: linear-gradient(to bottom, #fff, #f2f2f2);
border-color: #d8d8d8;
color: #222;
text-shadow: 0 1px 0 rgba(255,255,255,0.5);
margin-bottom: 3rem;
}
.pocket-button:hover {
background-position: 0 -15px;
color: #fff;
}
.pocket-button:hover:active {
box-shadow: inset 0 0 6px rgba(0,0,0,0.15);
}
#pocket-signup-with-email:hover,
#pocket-signup-with-email:hover:active {
color: #222;
}
#pocket-account-question {
color: #333;
font-size: 0.875rem;
margin: 1.75rem auto 0;
}
#pocket-separator {
height: 2px;
margin-top: 1rem;
}
.pocket-separator-colorstop {
-moz-box-flex: 1;
}
.pocket-separator-colorstop:nth-child(1) {
background: #7bedb7;
}
.pocket-separator-colorstop:nth-child(2) {
background: #47bcb6;
}
.pocket-separator-colorstop:nth-child(3) {
background: #f34156;
}
.pocket-separator-colorstop:nth-child(4) {
background: #feb73d;
}
#pocket-page-tags {
background-color: #e3e3e3;
padding: 1rem 0;
text-align: start;
}
#pocket-page-suggested-tags-header {
margin-top: 1rem;
color: #666;
}
@media (min-resolution: 1.25dppx) {
#pocket-header {
background-image: url(chrome://browser/content/pocket/img/signup_logo@2x.png);
background-size: 116px 29px;
}
#pocket-signup-or {
background-image: url(chrome://browser/content/pocket/img/signup_or@2x.png);
background-size: 246px 27px;
}
}
.subviewradio {
-moz-binding: url(chrome://global/content/bindings/radio.xml#radio);
-moz-appearance: none;