Commits

Aaron Toth committed 6c90bbe

Major code refactor, still sifting through bugs

  • Participants

Comments (0)

Files changed (45)

File .DS_Store

Binary file added.

File appinfo.json

+{
+    "id" : "me.aaront.pinnned",
+    "version" : "0.7.0",
+    "vendor" : "Aaron Toth",
+    "type" : "web",
+    "main" : "index.html",
+    "title" : "Pinnned",
+    "icon" : "icon.png",
+    "uiRevision" : 2
+}
+enyo.depends(
+    "source/Pinnned.js",
+
+    // Views
+    "source/views/Main.js",
+    "source/views/Preferences.js",
+    "source/views/Viewer.js",
+    "source/views/Welcome.js",
+
+    // Account Services
+    "source/services/accounts/AccountUtilities.js",
+
+    // Bookmark Services
+    "source/services/bookmarks/BmarkAlchemy.js",
+    "source/services/bookmarks/PinboardApi.js",
+    "source/services/bookmarks/DeliciousApi.js",
+
+    // Panels
+    "source/panels/AccountsPanel.js",
+    "source/panels/BookmarksPanel.js",
+    "source/panels/DefaultViewerPanel.js",
+    "source/panels/WebPanel.js",
+
+    // Dialogs
+    "source/dialogs/AddAccount.js",
+    "source/dialogs/Date.js",
+    "source/dialogs/RegisterAccount.js",
+    "source/dialogs/Tag.js",
+
+    // Styles
+    "styles/Pinnned.css",
+    "styles/AccountsPanel.css",
+    "styles/BookmarksPanel.css",
+    "styles/DefaultViewerPanel.css",
+    "styles/Welcome.css"
+);

File framework_config.json

+{
+    "logLevel" : 99
+}

File icon.png

Added
New image

File images/background-account.png

Added
New image

File images/background-accountslist.png

Added
New image

File images/clock32.png

Added
New image

File images/delicious.png

Added
New image

File images/folder32.png

Added
New image

File images/home32.png

Added
New image

File images/menu-icon-back.png

Added
New image

File images/menu-icon-forward.png

Added
New image

File images/menu-icon-new.png

Added
New image

File images/menu-icon-refresh.png

Added
New image

File images/pin-alpha-big.png

Added
New image

File images/pin.png

Added
New image

File images/pinboard.png

Added
New image

File images/rss32.png

Added
New image

File images/sources/pin.temp.png

Added
New image

File images/suboption-bottomfade.png

Added
New image

File images/suboption-topfade.png

Added
New image
+<!doctype html>
+<html>
+<head>
+	<title>Pinnned</title>
+	<script src="file:///opt/PalmSDK/0.1/share/refcode/framework/enyo/1.0/framework/enyo.js" type="text/javascript"></script>
+</head>
+<body>
+<script type="text/javascript">
+	new me.aaront.Pinnned().renderInto(document.body);
+</script>
+</body>
+</html>

File source/Pinnned.js

+enyo.kind({
+    name: "me.aaront.Pinnned",
+    kind: enyo.VFlexBox,
+    components: [
+        {kind: "VFlexBox", classname: "enyo-fit", components: [
+            {kind: "VFlexBox", flex: 1, style: "background: white;", components: [
+                {flex: 1, style: "margin-left: 620px; background: black; opacity: 0.5;"}
+            ]}
+        ]},
+        {name: "mainPane", kind: "Pane", flex: 1, components: [
+            {name: "mainView", className: "dark-bg", kind: "me.aaront.Main"},
+            {name: "preferencesView", className: "enyo-bg", kind: "me.aaront.Preferences", onSave: "preferencesSaved", onCancel: "preferencesCanceled"},
+            {name: "welcomeView", className: "enyo-bg", kind: "me.aaront.Welcome", onDone: "welcomeDone"}
+        ]},
+        {kind: "AppMenu", components: [
+            {kind: "EditMenu"},
+            {caption: "Preferences & Accounts", onclick: "showPreferences"},
+            {kind: "HelpMenu", target: "http://aaront.me/pinnned"},
+            {caption: "About Pinnned...", onclick: "showAbout"}
+        ]},
+        {name: "aboutPopup", kind: "Popup", scrim: true, modal: true, className: "transitioner", width: "500px", components: [
+            {content: "Pinnned v.0.7.0-alpha", style: "font-size: 26px; padding: 6px;"},
+            {content: "Hand-crafted by Aaron Toth in Ontario, Canada", style: "font-size: 15px; padding: 6px;"},
+            {content: "Licensed under the Apache License, Version 2.0", style: "font-size: 15px; padding: 6px; color: #999;"},
+            {kind: "HFlexBox", style: "padding-top: 15px;", components: [
+                {kind: "Button", flex: 1, caption: "Licence Information", onclick: "showLicense"},
+                {kind: "Button", flex: 1, caption: "OK", onclick: "aboutOk", className: "enyo-button-affirmative", isDefault: true}
+            ]}
+        ]},
+        {name: "accountUtilities", kind: "me.aaront.AccountUtilities"},
+        {kind: "ApplicationEvents", onLoad: "showOrBypassWelcomeView"}
+    ],
+    openAppMenuHandler: function() {
+        this.$.appMenu.open();
+    },
+    closeAppMenuHandler: function() {
+        this.$.appMenu.close();
+    },
+    preferencesSaved: function(inSender, inNew) {
+        var accounts = inNew;
+        this.$.mainView.receiveAccounts(accounts);
+        this.$.mainPane.back();
+    },
+    preferencesCanceled: function(inSender, inEvent) {
+        this.$.mainPane.back(inEvent);
+    },
+    welcomeDone: function(inSender, inNew) {
+        var accounts = inNew;
+        this.$.mainView.receiveAccounts(accounts);
+    },
+    showPreferences: function() {
+        this.$.mainPane.selectViewByName("preferencesView");
+    },
+    showAbout: function() {
+        this.$.aboutPopup.openAtCenter();
+    },
+    showLicense: function() {
+        var service = new enyo.PalmService();
+        service.service = "palm://com.palm.applicationManager/";
+        service.method = "open";
+        service.call({target: "http://www.apache.org/licenses/LICENSE-2.0.txt"});
+    },
+    aboutOk: function() {
+        this.$.aboutPopup.close();
+    },
+    showOrBypassWelcomeView: function() {
+        var accounts = this.$.accountUtilities.getAccounts();
+        if (accounts.length === 0) {
+            this.$.mainPane.selectViewByName("welcome");
+        }
+    }
+});

File source/dialogs/AddAccount.js

+enyo.kind({
+    name: "me.aaront.AddAccount",
+    kind: enyo.Control,
+    events: {
+        onSubmit: "",
+        onCancel: "",
+        onRegister: ""
+    },
+    components: [
+        {kind: "HFlexBox", style: "padding-top: 6px;", components: [
+            {content: "Add an Account", flex: 2, style: "font-size: 26px; padding: 6px;"},
+            {kind: "Button", flex: 1, caption: "Need an account?", onclick: "registerAccount"}
+        ]},
+        {kind: "RowGroup", caption: "Service", components: [
+            {name: "service", kind: "ListSelector", value: 1}
+        ]},
+        {kind: "RowGroup", caption: "Account Details", components: [
+            {name: "username", kind: "Input", hint: "Username", autoCapitalize: "lowercase"},
+            {name: "password", kind: "PasswordInput", hint: "Password"}
+        ]},
+        {kind: "HFlexBox", style: "padding-top: 6px;", components: [
+            {kind: "Button", flex: 1, caption: "Cancel", onclick: "doCancel"},
+            {kind: "Button", flex: 1, className: "enyo-button-affirmative", caption: "Submit", onclick: "doSubmit"}
+        ]},
+        {name: "accountUtilities", kind: "me.aaront.AccountUtilities"}
+    ],
+    create: function() {
+        this.inherited(arguments);
+        this.$.service.setItems(this.$.accountUtilities.getListOfServices());
+    },
+    service: function() {
+        return this.$.service.getValue();
+    },
+    username: function() {
+        return this.$.username.getValue();
+    },
+    password: function() {
+        return this.$.password.getValue();
+    },
+    clear: function() {
+        this.$.service.setValue("");
+        this.$.username.setValue("");
+        this.$.password.setValue("");
+    },
+    registerAccount: function(inSender) {
+        this.doRegister();
+    }
+});

File source/dialogs/Date.js

+enyo.kind({
+    name: "me.aaront.Date",
+    kind: enyo.Control,
+    events: {
+        onSubmit: "",
+        onCancel: ""
+    },
+    components: [
+        {content: "By Date", style: "font-size: 26px; padding: 6px;"},
+        {kind: "RowGroup", caption: "Date Range", components: [
+            {kind: "HFlexBox", flex: 1, components: [
+                {kind: "Spacer", flex: 1},
+                {name: "fromDate", kind: "DatePicker", label: "From"}
+            ]},
+            {kind: "HFlexBox", flex: 1, components: [
+                {kind: "Spacer", flex: 1},
+                {name: "toDate", kind: "DatePicker", label: "To"}
+            ]}
+        ]},
+        {kind: "HFlexBox", style: "padding-top: 6px;", components: [
+            {kind: "Button", flex: 1, caption: "Cancel", onclick: "doCancel"},
+            {kind: "Spacer"},
+            {kind: "Button", flex: 1, className: "enyo-button-affirmative", caption: "Submit", onclick: "doSubmit"}
+        ]}
+    ],
+    fromDate: function() {
+        return this.$.fromDate.getValue();
+    },
+    toDate: function() {
+        return this.$.toDate.getValue();
+    },
+    clear: function() {
+        this.$.fromDate.setValue(null);
+        this.$.toDate.setValue(null);
+    }
+});

File source/dialogs/RegisterAccount.js

+enyo.kind({
+    name: "me.aaront.RegisterAccount",
+    kind: enyo.Control,
+    events: {
+        onDone: "",
+        onCancel: ""
+    },
+    components: [
+        {content: "Register for an Account", flex: 2, style: "font-size: 26px; padding: 6px;"},
+        {kind: "RowGroup", caption: "Compatible Services", components: [
+            {kind: "RowItem", onclick: "pinboard", components: [
+                {kind: "Image", src: "images/pinboard.png", width: "40px", style: "float: left; padding-right: 20px;"},
+                {content: "Pinboard", flex: 1, style: "height: 30px; padding-top: 6px;"}
+            ]},
+            {kind: "RowItem", onclick: "delicious", components: [
+                {kind: "Image", src: "images/delicious.png", width: "40px", style: "float: left; padding-right: 20px;"},
+                {content: "Delicious", flex: 1, style: "height: 30px; padding-top: 6px;"}
+            ]}
+        ]},
+        {kind: "Button", flex: 1, caption: "Cancel", onclick: "doCancel"}
+    ],
+    pinboard: function(inSender) {
+        this.register("http://pinboard.in/signup/");
+    },
+    delicious: function(inSender) {
+        this.register("https://secure.delicious.com/register");
+    },
+    register: function(url) {
+        var service = new enyo.PalmService();
+        service.service = "palm://com.palm.applicationManager/";
+        service.method = "open";
+        service.call({target: url});
+        this.doDone();
+    }
+});

File source/dialogs/Tag.js

+enyo.kind({
+    name: "me.aaront.Tag",
+    kind: enyo.Control,
+    events: {
+        onSubmit: "",
+        onCancel: "",
+        onSetup: ""
+    },
+    published: {
+        account: {}
+    },
+    components: [
+        {content: "By Tag", style: "font-size: 26px; padding: 6px;"},
+        {name: "tagGroup", kind: "RowGroup", caption: "Tag", components: [
+            {name: "tag", kind: "CustomListSelector", items: this.tags}
+        ]},
+        {kind: "HFlexBox", style: "padding-top: 6px;", components: [
+            {kind: "Button", flex: 1, caption: "Cancel", onclick: "doCancel"},
+            {kind: "Spacer"},
+            {kind: "Button", flex: 1, className: "enyo-button-affirmative", caption: "Submit", onclick: "doSubmit"}
+        ]},
+        {name: "bmarkAlchemy", kind: "me.aaront.BmarkAlchemy", onResults: "gotTags"}
+    ],
+    create: function() {
+        this.inherited(arguments);
+        this.tags = [];
+        this.doSetup();
+        this.$.bmarkAlchemy.getAllTags(this.account);
+    },
+    receiveAccountAndGetTags: function(account) {
+        this.account = account;
+    },
+    tag: function() {
+        return this.$.tag.getValue();
+    },
+    gotTags: function(inSender, tags) {
+        this.tags = tags;
+        this.$.tag.setItems(this.tags);
+    }
+});

File source/panels/AccountsPanel.js

+//receiveAccounts
+enyo.kind({
+    name: "me.aaront.AccountsPanel",
+    kind: enyo.VFlexBox,
+    className: "accounts-panel",
+    published: {
+        headerText: ""
+    },
+    events: {
+        onSelectAccount: ""
+    },
+    components: [
+        {name: "header", kind: "Header", className: "accounts-header"},
+        {kind: "Scroller", flex: 1, components: [
+            {name: "accountsList", kind: "VirtualRepeater", onSetupRow: "setupAccountRow", components: [
+                {kind: "Item", className: "account-item", layoutKind: "HFlexLayout", onclick: "selectAccount", components: [
+                    {name: "serviceIcon", kind: "Image"},
+                    {kind: "VFlexBox", className: "account-username-service-box", flex: 1, components: [
+                        {name: "username", className: "account-item-username", flex: 1},
+                        {name: "service", className: "account-item-service", flex: 1}
+                    ]}
+                ]},
+                {name: "accountDrawer", open: false, kind: "Drawer", components: [
+                    {name: "accountOptions", kind: "VirtualRepeater", className: "account-option", onSetupRow: "setupOptionRow", components: [
+                        {kind: "Item", className: "account-option-item", layoutKind: "HFlexLayout", onclick: "accountOptionClicked", components: [
+                            {name: "icon", className: "account-option-icon", kind: "Image"},
+                            {name: "description", className: "account-option-description", flex: 1}
+                        ]}
+                    ]}
+                ]}
+            ]}
+        ]},
+        {kind: "Toolbar", components: [
+            {kind: "GrabButton"},
+            {kind: "Spacer"}
+        ]},
+        {name: "accountUtilities", kind: "me.aaront.AccountUtilities"}
+    ],
+    create: function() {
+        this.inherited(arguments);
+        this.accounts = this.$.accountUtilities.getAccounts();
+        this.$.accountsList.render();
+        this.currentAccount = {};
+        this.options = [
+            {description: "All", page: "all", icon: "images/rss32.png"},
+            {description: "By Tag", page: "tag", icon: "images/folder32.png"},
+            {description: "By Date", page: "date", icon: "images/clock32.png"}
+        ];
+    },
+    setupAccountRow: function(inSender, inIndex) {
+        var row = this.accounts[inIndex];
+        if (row) {
+            this.$.username.setContent(row.username);
+            var serviceName = this.$.accountUtilities.serviceNameFromNumber(row.service);
+            this.$.service.setContent(serviceName);
+            var serviceIcon = this.$.accountUtilities.serviceIconFromNumber(row.service);
+            this.$.serviceIcon.setSrc(serviceIcon);
+            return true;
+        }
+    },
+    selectAccount: function(inSender, inEvent) {
+        this.currentAccount = this.accounts[inEvent.rowIndex];
+        this.toggleDrawer(inEvent.rowIndex);
+    },
+    toggleDrawer: function(inRowIndex) {
+        this.animationCount = 1;
+        this.$.accountDrawer.toggleOpen();
+        var o = this.$.accountDrawer.getOpen();
+        if (this.lastOpen != null && this.lastOpen != inRowIndex) {
+            if (this.$.accountsList.prepareRow(this.lastOpen)) {
+                this.animationCount++;
+            }
+            this.$.accountDrawer.setOpen(false);
+        }
+        this.lastOpen = o ? inRowIndex : null;
+    },
+    setupOptionRow: function(inSender, inIndex) {
+       var row = this.options[inIndex];
+        if (row) {
+            this.$.icon.setSrc(row.icon);
+            this.$.description.setContent(row.description);
+            return true;
+        }
+    },
+    accountOptionClicked: function(inSender, inEvent) {
+        var option = this.options[inEvent.rowIndex];
+        this.doSelectAccount({
+            username: this.currentAccount.username,
+            password: this.currentAccount.password,
+            service: this.currentAccount.service,
+            page: option.page
+        });
+    },
+    receiveAccounts: function(accounts) {
+        this.accounts = accounts;
+        this.$.accountsList.render();
+    }
+});

File source/panels/BookmarksPanel.js

+enyo.kind({
+    name: "me.aaront.BookmarksPanel",
+    kind: enyo.VFlexBox,
+    published: {
+        headerText: "",
+        loadData: ""
+    },
+    events: {
+        onSelectBookmark: ""
+    },
+    components: [
+        {name: "header", kind: "Header"},
+        {name: "scrim", kind: "Scrim", layoutKind: "VFlexLayout", align: "center", pack: "center", components: [
+            {kind: "SpinnerLarge"}
+        ]},
+        {name: "bookmarksList", kind: "VirtualList", flex: 1, onSetupRow: "setupBookmarksRow", components: [
+            {kind: "Divider"},
+            {kind: "Item", layoutKind: "VFlexLayout", className: "bookmark-item", onclick: "bookmarkClicked", flex: 1, components: [
+                {name: "description"},
+                {name: "time", className: "bookmark-time"}
+            ]}
+        ]},
+        {kind: "Toolbar", components: [
+            {kind: "GrabButton"},
+            {kind: "Spacer", flex: 1},
+            {kind: "RadioToolButtonGroup", flex: 4, components: [
+                {caption: "By Date", onclick: "sortByDate"},
+                {caption: "By Name", onclick: "sortByName"}
+            ]},
+            {kind: "Spacer", flex: 1}
+        ]},
+        {name: "bmarkAlchemy", kind: "me.aaront.BmarkAlchemy", onResults: "gotBookmarks"}
+    ],
+    create: function() {
+        this.inherited(arguments);
+        this.headerContentChanged();
+        this.bookmarks = [];
+        this.$.bookmarksList.render();
+        this.currentSort = 0;
+    },
+    headerContentChanged: function() {
+        this.$.header.setContent(this.headerText);
+    },
+    loadDataChanged: function() {
+        this.showBookmarks(this.loadData);
+    },
+    setupBookmarksRow: function(inSender, inIndex) {
+        var row = this.bookmarks[inIndex];
+        if (row) {
+            this.setupDivider(inIndex);
+            this.$.item.applyStyle("background-color", inSender.isSelected(inIndex) ? "silver" : null);
+            this.$.description.setContent(row.description);
+            this.$.time.setContent(row.time.toLocaleDateString());
+            return true;
+        }
+    },
+    setupDivider: function(inIndex) {
+        var group = this.getGroupName(inIndex);
+        this.$.divider.setCaption(group);
+        this.$.divider.canGenerate = Boolean(group);
+        if (Boolean(group)) {
+            this.$.item.applyStyle("border-top", "none");
+        }
+    },
+    getGroupName: function(inIndex) {
+        var r0 = this.bookmarks[inIndex - 1],
+            r1 = this.bookmarks[inIndex];
+        if (this.currentSort === 0) {
+            if (r0 && !r0.dateString) {
+                r0.dateString = r0.time.toLocaleDateString();
+            }
+            var a = r0 && r0.dateString;
+            if (!r1.dateString) {
+                r1.dateString = r1.time.toLocaleDateString();
+            }
+            var b = r1.dateString;
+            return a != b ? b : null;
+        }
+        if (this.currentSort === 1) {
+            if (r0 && !r0.letter) {
+                r0.letter = r0.description.toUpperCase().split(" ").shift()[0];
+            }
+            var a = r0 && r0.letter;
+            if (!r1.letter) {
+                r1.letter = r1.description.toUpperCase().split(" ").shift()[0];
+            }
+            var b = r1.letter;
+            return a != b ? b : null;
+        }
+    },
+    bookmarkClicked: function(inSender, inEvent) {
+        this.$.bookmarksList.select(inEvent.rowIndex);
+        var bookmark = this.bookmarks[inEvent.rowIndex];
+        this.doSelectBookmark(bookmark);
+    },
+    showBookmarks: function(data) {
+        this.$.scrim.show();
+        this.$.spinnerLarge.show();
+        this.requestByPage(data);
+    },
+    gotBookmarks: function(inSender, inBookmarks) {
+        this.bookmarks = inBookmarks;
+        this.$.spinnerLarge.hide();
+        this.$.scrim.hide();
+        this.$.bookmarksList.refresh();
+    },
+    requestByPage: function(data) {
+        switch(data.page) {
+            case "all":
+                return this.$.bmarkAlchemy.getAllBookmarks(data);
+            case "date":
+                return this.$.bmarkAlchemy.getBookmarksByDate(data);
+            case "tag":
+                return this.$.bmarkAlchemy.getBookmarksByTag(data);
+        }
+    },
+    sortByName: function() {
+        this.bookmarks.sort(function(a, b) {
+            var newA = a.description.toLowerCase(),
+                newB = b.description.toLowerCase();
+            if (newA < newB) { return -1; }
+            if (newA > newB) { return 1; }
+            return 0;
+        });
+        this.$.bookmarksList.refresh();
+        this.currentSort = 1;
+    },
+    sortByDate: function() {
+        this.bookmarks.sort(function(a, b) {
+            var newA = a.time,
+                newB = b.time;
+            if (newA > newB) { return -1; }
+            if (newA < newB) { return 1; }
+            return 0;
+
+        });
+        this.$.bookmarksList.refresh();
+        this.currentSort = 0;
+    }
+});

File source/panels/DefaultViewerPanel.js

+enyo.kind({
+  name: "me.aaront.DefaultViewerPanel",
+  kind: enyo.VFlexBox,
+  className: "dark-bg",
+  components: [
+    {kind: "VFlexBox", align: "center", pack: "center", flex: 1, components: [
+        {kind: "Image", src: "images/pin-alpha-big.png"}
+    ]}
+  ],
+  create: function() {
+    this.inherited(arguments);
+  }
+});

File source/panels/WebPanel.js

+enyo.kind({
+    name: "me.aaront.WebPanel",
+    kind: enyo.VFlexBox,
+    published: {
+        url: ""
+    },
+    components: [
+        {kind: "Scroller", flex: 1, components: [
+            {name: "webView", kind: "WebView", flex: 1, className: "enyo-view"}
+        ]},
+        {kind: "Toolbar", components: [
+            {kind: "GrabButton"},
+            {kind: "ToolButtonGroup", components: [
+                {icon: "images/menu-icon-back.png", onclick: "goBack"},
+                {icon: "images/menu-icon-forward.png", onclick: "goForward"},
+                {icon: "images/menu-icon-refresh.png", onclick: "refresh"}
+            ]},
+            {caption: "Open in Browser", onclick: "openInCard"}
+        ]}
+    ],
+    create: function() {
+        this.inherited(arguments);
+        this.urlChanged();
+    },
+    urlChanged: function() {
+        this.$.webView.setUrl(this.url);
+    },
+    openInCard: function() {
+        var currentUrl = this.$.webView.getUrl();
+        var service = new enyo.PalmService();
+        service.service = "palm://com.palm.applicationManager/";
+        service.method = "open";
+        service.call({target: currentUrl});
+    },
+    goBack: function() {
+        this.$.webView.goBack();
+    },
+    goForward: function() {
+        this.$.webView.goForward();
+    },
+    refresh: function() {
+        this.$.webView.reloadPage();
+    },
+    resize: function() {
+        this.$.webView.resize();
+    },
+    resizePage: function() {
+        this.$.webView.resize();
+    }
+});

File source/services/accounts/AccountUtilities.js

+enyo.kind({
+   name: "me.aaront.AccountUtilities",
+   kind: enyo.Component,
+   numberToServiceIcon: {
+     1: "images/pinboard.png",
+     2: "images/delicious.png"
+   },
+   numberToServiceName: {
+     1: "Pinboard",
+     2: "Delicious"
+   },
+   serviceNameToNumber: {
+     pinboard: 1,
+     delicious: 2
+   },
+   addAccount: function(account) {
+     var newAccount = {
+         username: account.username,
+         password: account.password,
+         service: account.service
+     };
+     var existingAccounts = this.getAccounts();
+     existingAccounts.push(newAccount);
+     this.setAccounts(existingAccounts);
+   },
+   getAccounts: function() {
+     var accountCookie = enyo.getCookie("pinnned-accounts"),
+         accounts = accountCookie ? enyo.json.parse(accountCookie) : [];
+     return accounts;
+   },
+   setAccounts: function(accounts) {
+     enyo.setCookie("pinnned-accounts", enyo.json.stringify(accounts));
+   },
+   numberFromServiceName: function(name) {
+       return this.serviceNameToNumber[name];
+   },
+   serviceIconFromNumber: function(number) {
+       return this.numberToServiceIcon[number];
+   },
+   serviceNameFromNumber: function(number) {
+       return this.numberToServiceName[number];
+   },
+   getServiceNameToNumber: function() {
+       return this.serviceNameToNumber;
+   },
+   getListOfServices: function() {
+     var serviceList = [];
+     for (var key in this.numberToServiceName) {
+       var service = {caption: this.numberToServiceName[key], value: parseInt(key)}
+       serviceList.push(service);
+     }
+     return serviceList;
+   }
+});

File source/services/bookmarks/BmarkAlchemy.js

+enyo.kind({
+    name: "me.aaront.BmarkAlchemy",
+    kind: enyo.Component,
+    events: {
+        onResults: ""
+    },
+    components: [
+        {name: "accountUtilities", kind: "me.aaront.AccountUtilities"},
+        {name: "pinboardApi", kind: "me.aaront.PinboardApi", onResults: "doResults"},
+        {name: "deliciousApi", kind: "me.aaront.DeliciousApi", onResults: "doResults"}
+    ],
+    create: function() {
+        this.inherited(arguments);
+        this.serviceEnum = this.$.accountUtilities.getServiceNameToNumber();
+   },
+   getAllBookmarks: function(account) {
+     switch(account.service) {
+         case this.serviceEnum.pinboard:
+            this.$.pinboardApi.getAllBookmarks(account);
+            break;
+         case this.serviceEnum.delicious:
+            this.$.deliciousApi.getAllBookmarks(account);
+     }
+   },
+   getBookmarksByDate: function(options) {
+     switch(options.service) {
+         case this.serviceEnum.pinboard:
+            this.$.pinboardApi.getBookmarksByDate(options);
+            break;
+         case this.serviceEnum.delicious:
+            this.$.deliciousApi.getBookmarksByDate(options);
+     }
+   },
+   getBookmarksByTag: function(options) {
+     switch(options.service) {
+         case this.serviceEnum.pinboard:
+            this.$.pinboardApi.getBookmarksByTag(options);
+            break;
+         case this.serviceEnum.delicious:
+            this.$.deliciousApi.getBookmarksByTag(options);
+     }
+   },
+   getAllTags: function(account) {
+     switch(account.service) {
+         case this.serviceEnum.pinboard:
+            this.$.pinboardApi.getAllTags(account);
+            break;
+         case this.serviceEnum.delicious:
+            this.$.deliciousApi.getAllTags(account);
+     }
+   }
+});

File source/services/bookmarks/DeliciousApi.js

+enyo.kind({
+    name: "me.aaront.DeliciousApi",
+    kind: enyo.Component,
+    events: {
+        onResults: ""
+    },
+    components: [
+        {name: "getBookmarks", kind: "WebService", handleAs: "xml", contentType: "application/xml", onSuccess: "gotBookmarks", onFailure: "gotBookmarksFailure"},
+        {name: "getTags", kind: "WebService", handleAs: "xml", contentType: "application/xml", onSuccess: "gotTags", onFailure: "gotBookmarksFailure"}
+    ],
+    create: function() {
+        this.inherited(arguments);
+    },
+    getAllBookmarks: function(account) {
+        var url = 'https://'+ account.username + ':'+ account.password + '@' + 'api.del.icio.us/v1/posts/all';
+        this.$.getBookmarks.setUrl(url);
+        this.$.getBookmarks.call();
+    },
+    getBookmarksByDate: function(options) {
+        var url = 'https://'+ options.username + ':'+ options.password + '@' + 'api.del.icio.us/v1/posts/all?fromdt=' + options.fromDate + '&todt=' + options.toDate;
+        this.$.getBookmarks.setUrl(url);
+        this.$.getBookmarks.call();
+    },
+    getBookmarksByTag: function(options) {
+        var url = 'https://'+ options.username + ':'+ options.password + '@' + 'api.del.icio.us/v1/posts/all?tag=' + options.tag;
+        this.$.getBookmarks.setUrl(url);
+        this.$.getBookmarks.call();
+    },
+    getAllTags: function(account) {
+        var url = 'https://'+ account.username + ':'+ account.password + '@' + 'api.del.icio.us/v1/tags/get';
+        this.$.getTags.setUrl(url);
+        this.$.getTags.call();
+    },
+    gotBookmarks: function(inSender, inResponse, inRequest) {
+        var xmldom = new DOMParser().parseFromString(inRequest.xhr.responseText, "text/xml");
+        var postsXml = xmldom.getElementsByTagName("post");
+        var bookmarks = [];
+        for (var i=0; i < postsXml.length; i++) {
+            var attributes = postsXml[i].attributes;
+            bookmarks.push({'description': attributes.getNamedItem("description").nodeValue, 'href': attributes.getNamedItem("href").nodeValue, 'time': new Date(attributes.getNamedItem("time").nodeValue)});
+        }
+        this.doResults(bookmarks);
+    },
+    gotTags: function(inSender, inResponse, inRequest) {
+        var xmldom = new DOMParser().parseFromString(inRequest.xhr.responseText, "text/xml");
+        var tagsXml = xmldom.getElementsByTagName("tag");
+        var tags = [];
+        for (var i=0; i < tagsXml.length; i++) {
+            var attributes = tagsXml[i].attributes;
+            tags.push({'caption': attributes.getNamedItem("tag").nodeValue, 'value': attributes.getNamedItem("tag").nodeValue});
+        }
+        tags.sort(function(a, b) {
+            var newA = a.caption.toLowerCase(),
+                newB = b.caption.toLowerCase();
+            if (newA < newB) { return -1; }
+            if (newA > newB) { return 1; }
+            return 0;
+        });
+        this.doResults(tags);
+    }
+});

File source/services/bookmarks/PinboardApi.js

+enyo.kind({
+    name: "me.aaront.PinboardApi",
+    kind: enyo.Component,
+    events: {
+        onResults: ""
+    },
+    components: [
+        {name: "getBookmarks", kind: "WebService", handleAs: "xml", contentType: "application/xml", onSuccess: "gotBookmarks", onFailure: "gotBookmarksFailure"},
+        {name: "getTags", kind: "WebService", handleAs: "xml", contentType: "application/xml", onSuccess: "gotTags", onFailure: "gotBookmarksFailure"}
+    ],
+    create: function() {
+        this.inherited(arguments);
+    },
+    getAllBookmarks: function(account) {
+        var url = 'https://'+ account.username + ':'+ account.password + '@' + 'api.pinboard.in/v1/posts/all';
+        this.$.getBookmarks.setUrl(url);
+        this.$.getBookmarks.call();
+    },
+    getBookmarksByDate: function(options) {
+        var url = 'https://'+ options.username + ':'+ options.password + '@' + 'api.pinboard.in/v1/posts/all&fromdt=' + options.fromDate + '&todt=' + options.toDate;
+        this.$.getBookmarks.setUrl(url);
+        this.$.getBookmarks.call();
+    },
+    getBookmarksByTag: function(options) {
+        var url = 'https://'+ options.username + ':'+ options.password + '@' + 'api.pinboard.in/v1/posts/all&tag=' + options.tag;
+        this.$.getBookmarks.setUrl(url);
+        this.$.getBookmarks.call();
+    },
+    getAllTags: function(account) {
+        var url = 'https://'+ account.username + ':'+ account.password + '@' + 'api.pinboard.in/v1/tags/get';
+        this.$.getTags.setUrl(url);
+        this.$.getTags.call();
+    },
+    gotBookmarks: function(inSender, inResponse, inRequest) {
+        var xmldom = new DOMParser().parseFromString(inRequest.xhr.responseText, "text/xml");
+        var postsXml = xmldom.getElementsByTagName("post");
+        var bookmarks = [];
+        for (var i=0; i < postsXml.length; i++) {
+            var attributes = postsXml[i].attributes;
+            bookmarks.push({'description': attributes.getNamedItem("description").nodeValue, 'href': attributes.getNamedItem("href").nodeValue, 'time': new Date(attributes.getNamedItem("time").nodeValue)});
+        }
+        this.doResults(bookmarks);
+    },
+    gotTags: function(inSender, inResponse, inRequest) {
+        var xmldom = new DOMParser().parseFromString(inRequest.xhr.responseText, "text/xml");
+        var tagsXml = xmldom.getElementsByTagName("tag");
+        var tags = [];
+        for (var i=0; i < tagsXml.length; i++) {
+            var attributes = tagsXml[i].attributes;
+            tags.push({'caption': attributes.getNamedItem("tag").nodeValue, 'value': attributes.getNamedItem("tag").nodeValue});
+        }
+        tags.sort(function(a, b) {
+            var newA = a.caption.toLowerCase(),
+                newB = b.caption.toLowerCase();
+            if (newA < newB) { return -1; }
+            if (newA > newB) { return 1; }
+            return 0;
+        });
+        this.doResults(tags);
+    }
+});

File source/views/Main.js

+enyo.kind({
+    name: "me.aaront.Main",
+    kind: enyo.VFlexBox,
+    components: [
+        {name: "slider", kind: "SlidingPane", flex: 1, components: [
+            {name: "left", kind: "SlidingView", width: "200px", fixedWidth: true, components: [
+                {name: "accountsPanel", kind: "me.aaront.AccountsPanel", flex: 1, onSelectAccount: "accountSelected"}
+            ]},
+            {name: "middle", kind: "SlidingView", width: "320px", peekWidth: 60, components: [
+                {name: "bookmarksPanel", kind: "me.aaront.BookmarksPanel", flex: 1, headerText: "Bookmarks", onSelectBookmark: "bookmarkSelected"}
+            ]},
+            {name: "right", kind: "SlidingView", flex: 1, dismissable: true, onResize: "viewerResize", components: [
+                {name: "viewerPanel", kind: "me.aaront.Viewer", flex: 1}
+            ]}
+        ]},
+        {name: "tagPopup", kind: "Popup", scrim: true, modal: true, className: "transitioner", width: "350px", components: [
+            {name: "byTag", kind: "me.aaront.Tag", onSetup: "tagSetup", onCancel: "closePopup", onSubmit: "tagSubmitted"}
+        ]},
+        {name: "datePopup", kind: "Popup", scrim: true, modal: true, className: "transitioner", width: "350px", components: [
+            {name: "byDate", kind: "me.aaront.Date", onCancel: "closePopup", onSubmit: "dateSubmitted"}
+        ]}
+    ],
+    create: function() {
+        this.inherited(arguments);
+        this.currentState = {};
+    },
+    accountSelected: function(inSender, inData) {
+        this.currentState = inData;
+        switch(this.currentState.page) {
+            case "tag":
+                this.$.tagPopup.openAtCenter();
+                break;
+            case "date":
+                this.$.datePopup.openAtCenter();
+                break;
+            default:
+                this.currentState.page = "all";
+                this.$.bookmarksPanel.setLoadData(this.currentState);
+        }
+    },
+    bookmarkSelected: function(inSender, inBookmark) {
+        this.$.viewerPanel.receiveBookmark(inBookmark);
+    },
+    viewerResize: function(inSender) {
+        this.$.viewerPanel.resizePage();
+    },
+    tagSetup: function(inSender) {
+        inSender.receiveAccountAndGetTags(this.currentState);
+    },
+    closePopup: function(inSender) {
+        inSender.container.close();
+    },
+    tagSubmitted: function(inSender) {
+        this.closePopup(inSender);
+        this.currentState.tag = inSender.tag();
+        this.$.bookmarksPanel.setLoadData(this.currentState);
+    },
+    dateSubmitted: function(inSender) {
+        this.closePopup(inSender);
+        this.currentState.fromDate = this.toISODateString(inSender.fromDate());
+        this.currentState.toDate = this.toISODateString(inSender.toDate());
+        this.$.bookmarksPanel.setLoadData(this.currentState);
+    },
+    toISODateString: function(date) {
+        var pad = function(n) { return n<10 ? '0'+n : n };
+        return date.getUTCFullYear()+'-'
+            + pad(date.getUTCMonth()+1)+'-'
+            + pad(date.getUTCDate())+'T'
+            + pad(date.getUTCHours())+':'
+            + pad(date.getUTCMinutes())+':'
+            + pad(date.getUTCSeconds())+'Z'
+    },
+    receiveAccounts: function(accounts) {
+        this.$.accountsPanel.receiveAccounts(accounts);
+    }
+});

File source/views/Preferences.js

+enyo.kind({
+    name: "me.aaront.Preferences",
+    kind: enyo.VFlexBox,
+    events: {
+        onSave: "",
+        onCancel: ""
+    },
+    components: [
+        {kind: "PageHeader", content: "Pinnned Preferences"},
+        {kind: "VFlexBox", style: "width: 650px; margin: 20px auto;", flex: 1, components: [
+            {kind: "RowGroup", caption: "Accounts", components: [
+                {name: "accountsList", kind: "VirtualRepeater", onSetupRow: "setupAccountRow", components: [
+                    {kind: "SwipeableItem", onConfirm: "deleteAccountConfirmed", layoutKind: "HFlexLayout", components: [
+                        {name: "username", flex: 1},
+                        {name: "service"}
+                    ]}
+                ]}
+            ]},
+            {kind: "HFlexBox", pack: "end", style: "padding: 0 10px;", components: [
+                {name: "addButton", kind: "Button", content: "Add", onclick: "addClicked"},
+                {kind: "Spacer"},
+                {name: "saveButton", kind: "Button", className: "enyo-button-affirmative", content: "Save", onclick: "saveClicked", isDefault: true},
+                {name: "cancelButton", kind: "Button", content: "Cancel", onclick: "cancelClicked"}
+            ]}
+        ]},
+        {name: "addAccountPopup", kind: "Popup", scrim: true, modal: true, className: "transitioner", width: "500px", components: [
+            {kind: "me.aaront.AddAccount", onCancel: "closePopup", onSubmit: "addAccountSubmitted", onRegister: "newAccountRegister"}
+        ]},
+        {name: "registerAccountPopup", kind: "Popup", scrim: true, modal: true, className: "transitioner", width: "500px", components: [
+            {kind: "me.aaront.RegisterAccount", onCancel: "closePopup", onDone: "registerDone"}
+        ]},
+        {name: "accountUtilities", kind: "me.aaront.AccountUtilities"}
+    ],
+    create: function() {
+        this.inherited(arguments);
+        this.accounts = this.$.accountUtilities.getAccounts();
+        this.$.accountsList.render();
+    },
+    setupAccountRow: function(inSender, inIndex) {
+        var row = this.accounts[inIndex];
+        if (row) {
+            var serviceName = this.$.accountUtilities.serviceNameFromNumber(row.service);
+            this.$.username.setContent(row.username);
+            this.$.service.setContent(serviceName);
+            return true;
+        }
+    },
+    deleteAccountConfirmed: function(inIndex) {
+        this.accounts.splice(inIndex, 1);
+        this.$.accountsList.render();
+    },
+    addClicked: function(inSender) {
+        this.$.addAccountPopup.openAtCenter();
+    },
+    saveClicked: function(inSender) {
+        this.$.accountUtilities.setAccounts(this.accounts);
+        this.doSave(this.accounts);
+    },
+    cancelClicked: function(inSender) {
+        this.doCancel();
+    },
+    addAccountSubmitted: function(inSender) {
+        this.closePopup(inSender);
+        var account = {
+            service: inSender.service(),
+            username: inSender.username(),
+            password: inSender.password()
+        };
+        this.$.accountUtilities.addAccount(account);
+        this.accounts = this.$.accountUtilities.getAccounts();
+        inSender.clear();
+        this.$.accountsList.render();
+    },
+    newAccountRegister: function(inSender) {
+        this.closePopup(inSender);
+        this.$.registerAccountPopup.openAtCenter();
+    },
+    registerDone: function(inSender) {
+        this.closePopup(inSender);
+    },
+    closePopup: function(inSender) {
+        inSender.container.close();
+    }
+});

File source/views/Viewer.js

+enyo.kind({
+    name: "me.aaront.Viewer",
+    kind: enyo.VFlexBox,
+    components: [
+        {kind: "Pane", transitionKind: "enyo.transitions.LeftRightFlyin", flex: 1, components: [
+            {name: "defaultViewerPanel", kind: "me.aaront.DefaultViewerPanel"},
+            {name: "webPanel", kind: "me.aaront.WebPanel"}
+        ]}
+    ],
+    create: function() {
+        this.inherited(arguments);
+    },
+    resizePage: function() {
+        this.$.webPanel.resize();
+    },
+    receiveBookmark: function(bookmark) {
+        this.$.webPanel.setUrl(bookmark.href);
+        this.$.pane.selectViewByName("webPanel");
+    }
+});

File source/views/Welcome.js

+enyo.kind({
+    name: "me.aaront.Welcome",
+    kind: enyo.VFlexBox,
+    events: {
+        onDone: ""
+    },
+    components: [
+        {kind: "PageHeader", content: "Welcome to Pinnned"},
+        {kind: "Scroller", flex: 1, components: [
+            {kind: "VFlexBox", flex: 1, style: "width: 600px; margin: 20px auto;", components: [
+                {name: "welcomeText",kind: "Drawer", components: [
+                    {content: "Hey there!", className: "welcome-bigtext"},
+                    {content: "Thanks for downloading Pinned, the premier social bookmarking client for the HP Touchpad!", className: "welcome-normaltext"},
+                    {content: "We just need one piece of information to get started: an account.", className: "welcome-normaltext"},
+                    {content: "Please type in your details below and click next to see your bookmarks, or "+
+                        "tap the 'I Need an Account' button to to get an account.", className: "welcome-normaltext"}
+                ]},
+                {kind: "RowGroup", caption: "Service", components: [
+                    {name: "service", kind: "ListSelector", value: 1}
+                ]},
+                {kind: "RowGroup", caption: "Account Details", components: [
+                    {name: "username", kind: "Input", hint: "Username", autoCapitalize: "lowercase", onfocus: "hideWelcomeText"},
+                    {name: "password", kind: "PasswordInput", hint: "Password", onfocus: "hideWelcomeText"}
+                ]},
+                {kind: "HFlexBox", pack: "end", style: "padding: 0 10px;", components: [
+                    {name: "registerButton", kind: "Button", content: "I Need an Account", onclick: "registerAccount"},
+                    {kind: "Spacer"},
+                    {name: "saveButton", kind: "Button", className: "enyo-button-affirmative", content: "Next", onclick: "addAccountSubmitted", isDefault: true}
+                ]}
+            ]}
+        ]},
+        {name: "registerAccountPopup", kind: "Popup", scrim: true, modal: true, className: "transitioner", width: "500px", components: [
+            {kind: "finiteloop.AccountsRegister", onCancel: "closePopup", onDone: "registerDone"}
+        ]},
+        {name: "accountUtilities", kind: "me.aaront.AccountUtilities"},
+    ],
+    create: function() {
+        this.inherited(arguments);
+        this.accounts = this.$.accountUtilities.getAccounts();
+        this.$.service.setItems(this.$.accountUtilities.getListOfServices());
+    },
+    hideWelcomeText: function(inSender) {
+        this.$.welcomeText.close();
+    },
+    registerAccount: function(inSender) {
+        this.$.accountRegisterPopup.openAtCenter();
+    },
+    registerDone: function(inSender) {
+        this.closePopup(inSender);
+    },
+    closePopup: function(inSender) {
+        inSender.container.close();
+    },
+    addAccountSubmitted: function() {
+        var account = {
+            service: this.$.service.getValue(),
+            username: this.$.service.getValue(),
+            password: this.$.password.getValue()
+        };
+        this.$.accountUtilities.addAccount(account);
+        var allAccounts = this.$.accountUtilities.getAccounts();
+        this.doDone(allAccounts);
+    }
+});

File styles/AccountsPanel.css

+.accounts-header { /*nee: logo-header*/
+    background: #333 url('../images/pin.png') -27px -29px no-repeat !important;
+    -webkit-border-image: none;
+	margin-bottom: 4px;
+	padding-top: 1px;
+	border-bottom: 1px solid #555;
+	color: #bbb;
+}
+
+.accounts-panel {
+    background: #444 url('../images/background-accountslist.png') left top repeat;
+}
+
+.account-item {
+    background: transparent url('../images/background-account.png') left top repeat-x;
+    border-top: none;
+    border-bottom: 1px solid rgba(0, 0, 0, 0.15);
+    color: #ddd;
+    font-weight: bold;
+    padding-top: 8px;
+    padding-bottom: 3px;
+}
+
+.account-username-service-box { /* nee: user-service-box */
+    padding-left: 16px;
+    padding-top: 0;
+}
+
+.account-item-username { /* nee: username */
+
+}
+
+.account-item-service { /* nee: service */
+    color: #444;
+    font-size: 0.6em;
+    letter-spacing: 1px;
+    text-transform: uppercase;
+    padding-top: 3px;
+    font-weight: normal;
+}
+
+/* account-suboption ->>> account-option */
+
+.account-option {
+    background: #222 url('../images/suboption-topfade.png') left top repeat-x;
+}
+
+.account-option-item {
+    border-top: none;
+    background: transparent url('../images/suboption-bottomfade.png') left bottom repeat-x;
+    color: #ddd;
+    padding: 10px 0 6px 13px;
+}
+
+.account-option-icon {
+    padding-right: 25px;
+}
+
+.account-option-description {
+    padding-top: 5px;
+    font-size: 0.9em;
+    color: #bbb;
+}

File styles/BookmarksPanel.css

+.bookmark-item {
+
+}
+
+.bookmark-time {
+    color: #777;
+    font-size: 0.6em;
+    letter-spacing: 1px;
+    text-transform: uppercase;
+    padding-top: 8px;
+    font-weight: normal;
+}

File styles/DefaultViewerPanel.css

+.default-viewer-panel {
+    background: #333;
+}

File styles/Pinnned.css

+.enyo-toolbar {
+	height: 56px;
+}
+
+.enyo-bg {
+	color: #333;
+}
+
+.dark-bg {
+    background: #444 url('../images/background-accountslist.png') left top repeat;
+}

File styles/Welcome.css

+.welcome-bigtext {
+    font-size: 1.1em;
+    padding-bottom: 20px;
+}
+
+.welcome-normaltext {
+    font-size: 0.8em;
+    color: #666;
+    padding-bottom: 15px;
+}