]> git.saurik.com Git - cydia.git/commitdiff
The Package Details screen is now pure HTML/JS.
authorJay Freeman (saurik) <saurik@saurik.com>
Sun, 10 Aug 2008 09:26:55 +0000 (09:26 +0000)
committerJay Freeman (saurik) <saurik@saurik.com>
Thu, 30 Sep 2010 07:08:16 +0000 (07:08 +0000)
Cydia.app/menes/listArrow.png [new file with mode: 0644]
Cydia.app/menes/menes.js [new file with mode: 0644]
Cydia.app/menes/pinstripes.png [new file with mode: 0644]
Cydia.app/menes/style.css [new file with mode: 0644]
Cydia.app/package.html [new file with mode: 0644]
Cydia.app/package.js [new file with mode: 0644]
Cydia.mm

diff --git a/Cydia.app/menes/listArrow.png b/Cydia.app/menes/listArrow.png
new file mode 100644 (file)
index 0000000..6421a16
Binary files /dev/null and b/Cydia.app/menes/listArrow.png differ
diff --git a/Cydia.app/menes/menes.js b/Cydia.app/menes/menes.js
new file mode 100644 (file)
index 0000000..1d153bb
--- /dev/null
@@ -0,0 +1,371 @@
+var _assert = function (expr) {
+    if (!expr) {
+        var message = "_assert(" + expr + ")";
+        alert(message);
+        throw message;
+    }
+}
+
+// Compatibility {{{
+if (typeof Array.prototype.push != "function")
+    Array.prototype.push = function (value) {
+        this[this.length] = value;
+    };
+// }}}
+
+var $ = function (arg, doc) {
+    if (this.magic_ != $.prototype.magic_)
+        return new $(arg);
+
+    var type = $.type(arg);
+
+    if (type == "function")
+        $.ready(arg);
+    else if (type == "string") {
+        if (doc == undefined)
+            doc = document;
+        if (arg.charAt(0) == '#')
+            return new $([doc.getElementById(arg.substring(1))]);
+        else if (arg.charAt(0) == '.')
+            return new $(doc.getElementsByClassName(arg.substring(1)));
+        else
+            return $([doc]).descendants(arg);
+    } else {
+        _assert(doc == undefined);
+        this.set($.array(arg));
+        return this;
+    }
+};
+
+$.type = function (value) {
+    var type = typeof value;
+
+    if (
+        type == "function" &&
+        value.toString != null &&
+        value.toString().substring(0, 8) == "[object "
+    )
+        return "object";
+    else
+        return type;
+};
+
+(function () {
+    var ready_ = null;
+
+    $.ready = function (_function) {
+        if (ready_ == null) {
+            ready_ = [];
+
+            document.addEventListener("DOMContentLoaded", function () {
+                for (var i = 0; i != ready_.length; ++i)
+                    ready_[i]();
+            }, false);
+        }
+
+        ready_.push(_function);
+    };
+})();
+
+/* XXX: verify arg3 overflow */
+$.each = function (values, _function, arg0, arg1, arg2) {
+    for (var i = 0, e = values.length; i != e; ++i)
+        _function(values[i], arg0, arg1, arg2);
+};
+
+/* XXX: verify arg3 overflow */
+$.map = function (values, _function, arg0, arg1, arg2) {
+    var mapped = [];
+    for (var i = 0, e = values.length; i != e; ++i)
+        mapped.push(_function(values[i], arg0, arg1, arg2));
+    return mapped;
+};
+
+$.array = function (values) {
+    if (values.constructor == Array)
+        return values;
+    var array = [];
+    for (var i = 0; i != values.length; ++i)
+        array.push(values[i]);
+    return array;
+};
+
+$.document = function (node) {
+    for (;;) {
+        var parent = node.parentNode;
+        if (parent == null)
+            return node;
+        node = parent;
+    }
+};
+
+$.prototype = {
+    magic_: 2041085062,
+
+    add: function (nodes) {
+        Array.prototype.push.apply(this, nodes);
+    },
+
+    set: function (nodes) {
+        this.length = 0;
+        this.add(nodes);
+    },
+
+    css: function (name, value) {
+        $.each(this, function (node) {
+            node.style[name] = value;
+        });
+    },
+
+    append: function (html) {
+        $.each(this, function (node) {
+            var doc = $.document(node);
+
+            // XXX: implement wrapper system
+            var div = doc.createElement("div");
+            div.innerHTML = html;
+
+            while (div.childNodes.length != 0) {
+                var child = div.childNodes[0];
+                node.appendChild(child);
+            }
+        });
+    },
+
+    descendants: function (expression) {
+        var descendants = $([]);
+
+        $.each(this, function (node) {
+            descendants.add(node.getElementsByTagName(expression));
+        });
+
+        return descendants;
+    },
+
+    remove: function () {
+        $.each(this, function (node) {
+            node.parentNode.removeChild(node);
+        });
+    },
+
+    parent: function () {
+        return $($.map(this, function (node) {
+            return node.parentNode;
+        }));
+    }
+};
+
+$.scroll = function (x, y) {
+    window.scrollTo(x, y);
+};
+
+// XXX: document.all?
+$.all = function (doc) {
+    if (doc == undefined)
+        doc = document;
+    return $(doc.getElementsByTagName("*"));
+};
+
+$.inject = function (a, b) {
+    if ($.type(a) == "string") {
+        $.prototype[a] = function (value) {
+            if (value == undefined)
+                return $.map(this, function (node) {
+                    return b.get(node);
+                });
+            else
+                $.each(this, function (node, value) {
+                    b.set(node, value);
+                }, value);
+        };
+    } else for (var name in a)
+        $.inject(name, a[name]);
+};
+
+$.inject({
+    html: {
+        get: function (node) {
+            return node.innerHTML;
+        },
+        set: function (node, value) {
+            node.innerHTML = value;
+        }
+    },
+
+    href: {
+        get: function (node) {
+            return node.href;
+        },
+        set: function (node, value) {
+            node.href = value;
+        }
+    },
+
+    value: {
+        get: function (node) {
+            return node.value;
+        },
+        set: function (node, value) {
+            node.value = value;
+        }
+    }
+});
+
+// Event Registration {{{
+// XXX: unable to remove registration
+$.prototype.event = function (event, _function) {
+    $.each(this, function (node) {
+        // XXX: smooth over this pointer ugliness
+        if (node.addEventListener)
+            node.addEventListener(event, _function, false);
+        else if (node.attachEvent)
+            node.attachEvent("on" + event, _function);
+        else
+            // XXX: multiple registration SNAFU
+            node["on" + event] = _function;
+    });
+};
+
+$.each([
+    "click", "load", "submit"
+], function (event) {
+    $.prototype[event] = function (_function) {
+        if (_function == undefined)
+            _assert(false);
+        else
+            this.event(event, _function);
+    };
+});
+// }}}
+// Timed Animation {{{
+$.interpolate = function (duration, event) {
+    var start = new Date();
+
+    var next = function () {
+        setTimeout(update, 0);
+    };
+
+    var update = function () {
+        var time = new Date() - start;
+
+        if (time >= duration)
+            event(1);
+        else {
+            event(time / duration);
+            next();
+        }
+    };
+
+    next();
+};
+// }}}
+// AJAX Requests {{{
+// XXX: abstract and implement other cases
+$.xhr = function (url, method, headers, data, events) {
+    var xhr = new XMLHttpRequest();
+    xhr.open(method, url, true);
+
+    for (var name in headers)
+        xhr.setRequestHeader(name.replace(/_/, "-"), headers[name]);
+
+    if (events == null)
+        events = {};
+
+    xhr.onreadystatechange = function () {
+        if (xhr.readyState == 4)
+            if (events.complete != null)
+                events.complete(xhr.responseText);
+    };
+
+    xhr.send(data);
+};
+
+$.call = function (url, post, onsuccess) {
+    var events = {};
+
+    if (onsuccess != null)
+        events.complete = function (text) {
+            onsuccess(eval(text));
+        };
+
+    if (post == null)
+        $.xhr(url, "POST", null, null, events);
+    else
+        $.xhr(url, "POST", {
+            Content_Type: "application/json"
+        }, $.json(post), events);
+};
+// }}}
+// WWW Form URL Encoder {{{
+$.form = function (parameters) {
+    var data = "";
+
+    var ampersand = false;
+    for (var name in parameters) {
+        if (!ampersand)
+            ampersand = true;
+        else
+            data += "&";
+
+        var value = parameters[name];
+
+        data += escape(name);
+        data += "=";
+        data += escape(value);
+    }
+
+    return data;
+};
+// }}}
+// JSON Serializer {{{
+$.json = function (value) {
+    if (value == null)
+        return "null";
+
+    var type = $.type(value);
+
+    if (type == "number")
+        return value;
+    else if (type == "string")
+        return "\"" + value
+            .replace(/\\/, "\\\\")
+            .replace(/\t/, "\\t")
+            .replace(/\r/, "\\r")
+            .replace(/\n/, "\\n")
+            .replace(/"/, "\\\"")
+        + "\"";
+    else if (value.constructor == Array) {
+        var json = "[";
+        var comma = false;
+
+        for (var i = 0; i != value.length; ++i) {
+            if (!comma)
+                comma = true;
+            else
+                json += ",";
+
+            json += $.json(value[i]);
+        }
+
+        return json + "]";
+    } else if (
+        value.constructor == Object &&
+        value.toString() == "[object Object]"
+    ) {
+        var json = "{";
+        var comma = false;
+
+        for (var name in value) {
+            if (!comma)
+                comma = true;
+            else
+                json += ",";
+
+            json += name + ":" + $.json(value[name]);
+        }
+        return json + "}";
+    } else {
+        return value;
+    }
+};
+// }}}
diff --git a/Cydia.app/menes/pinstripes.png b/Cydia.app/menes/pinstripes.png
new file mode 100644 (file)
index 0000000..c997775
Binary files /dev/null and b/Cydia.app/menes/pinstripes.png differ
diff --git a/Cydia.app/menes/style.css b/Cydia.app/menes/style.css
new file mode 100644 (file)
index 0000000..ffae721
--- /dev/null
@@ -0,0 +1,215 @@
+/* .clearfix {{{ */
+.clearfix:after {
+    content: ".";
+    display: block;
+    clear: both;
+    visibility: hidden;
+    line-height: 0;
+    height: 0;
+}
+
+.clearfix {
+    display: inline-block;
+}
+
+html[xmlns] .clearfix {
+    display: block;
+}
+
+* html .clearfix {
+    height: 1%;
+}
+/* }}} */
+
+* {
+    box-sizing: border-box;
+    -moz-box-sizing: border-box;
+}
+
+body {
+    font-family: Helvetica;
+    margin: 0;
+    padding: 0;
+    -webkit-text-size-adjust: none;
+    -webkit-user-select: none;
+}
+
+#page {
+    position: relative;
+}
+
+.dialog {
+    position: absolute;
+    width: 100%;
+}
+
+hr {
+    margin-top: 10px;
+}
+
+.dialog > .panel {
+    background: #c8c8c8 url(pinstripes.png);
+    padding: 1px 0 1px 0;
+}
+
+p {
+    margin: 0px;
+    padding: 0px;
+}
+
+a {
+    text-decoration: none;
+    text-underline-style: dotted;
+}
+
+strong {
+    font-weight: bold
+}
+
+/* #toolbar {{{ */
+.dialog > .toolbar {
+    background: url(toolbar.png) #6d84a2 repeat-x;
+    border-bottom: 1px solid #2d3642;
+    height: 45px;
+    padding: 10px;
+}
+
+.dialog > .toolbar > h1 {
+    color: #ffffff;
+    font-size: 20px;
+    font-weight: bold;
+    height: 100%;
+    margin: 1px auto 0 auto;
+    text-shadow: rgba(0, 0, 0, 0.4) 0px -1px 0;
+    text-align: center;
+    white-space: nowrap;
+}
+/* }}} */
+/* (back|forward)-button {{{ */
+.dialog > .toolbar > a.back-button,
+.dialog > .toolbar > a.forward-button {
+    color: #ffffff;
+    font-size: 12px;
+    font-weight: bold;
+    height: 30px;
+    line-height: 30px;
+    margin-top: -28px;
+    padding: 0 3px;
+    text-decoration: none;
+    text-shadow: rgba(0, 0, 0, 0.6) 0px -1px 0;
+    white-space: nowrap;
+}
+
+.dialog > .toolbar > a.back-button {
+    -webkit-border-image: url(backButton.png) 0 8 0 14;
+    border-width: 0 8px 0 14px;
+    float: left;
+}
+
+.dialog > .toolbar > a.forward-button {
+    -webkit-border-image: url(toolButton.png) 0 5 0 5;
+    border-width: 0 5px;
+    float: right;
+}
+/* }}} */
+/* fieldset {{{ */
+.dialog > .panel > fieldset {
+    background: #ffffff;
+    border: 1px solid #999999;
+    -webkit-border-radius: 10px;
+    font-size: 16px;
+    margin: 9px;
+    padding: 0;
+}
+
+.dialog > .panel > label {
+    display: block;
+    margin: 13px 0 -4px 27px;
+    line-height: 24px;
+    font-size: inherit;
+    font-weight: bold;
+    color: #4d4d70;
+    text-shadow: rgba(255, 255, 255, 0.75) 1px 1px 0;
+}
+
+.dialog > .panel > fieldset > a,
+.dialog > .panel > fieldset > div {
+    border-top: 1px solid #999999;
+    min-height: 19px;
+    padding: 11px 17px;
+}
+
+.dialog > .panel > fieldset > a:first-child,
+.dialog > .panel > fieldset > div:first-child {
+    border-top: none;
+}
+
+.dialog > .panel > fieldset > a img.icon,
+.dialog > .panel > fieldset > div img.icon {
+    height: auto;
+    margin: -13px 5px -10px -10px;
+    max-height: 30px;
+    min-width: 30px;
+    vertical-align: middle;
+    width: 30px;
+}
+
+.dialog > .panel > fieldset > div > p {
+    margin-top: .5em;
+    text-align: center;
+}
+
+.dialog > .panel > fieldset > div > p:first-child {
+    margin-top: 0;
+}
+
+.dialog > .panel > fieldset > a {
+    background: 275px 11px no-repeat url(listArrow.png);
+    color: inherit;
+    display: block;
+}
+
+.dialog > .panel > fieldset > div > select {
+    font-size: 16px;
+    margin: -4px -10px -5px 86px;
+    width: 190px;
+}
+
+.dialog > .panel > fieldset > div > input {
+    background: none;
+    border: none;
+    color: #193250;
+    font-size: 16px;
+    height: 45px;
+    margin: -12px -18px;
+    padding: 12px 10px 0 111px;
+    width: 302px;
+}
+
+.dialog > .panel > fieldset > div > input[type="submit"] {
+    border-width: 0 12px;
+    color: #000000;
+    display: block;
+    font-size: 20px;
+    font-weight: bold;
+    padding: 10px;
+    text-align: center;
+    -webkit-border-image: url(whiteButton.png) 0 12 0 12;
+}
+
+.dialog > .panel > fieldset > a > label,
+.dialog > .panel > fieldset > div > label {
+    font-weight: bold;
+    position: absolute;
+}
+
+.dialog > .panel > fieldset > a > label + div {
+    margin-right: 16px;
+}
+
+.dialog > .panel > fieldset > a > label + div,
+.dialog > .panel > fieldset > div > label + div {
+    color: #335588;
+    text-align: right;
+}
+/* }}} */
diff --git a/Cydia.app/package.html b/Cydia.app/package.html
new file mode 100644 (file)
index 0000000..a80177d
--- /dev/null
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-16"?>
+<html><head>
+    <title>Details</title>
+    <meta name="viewport" content="width=320, minimum-scale=1.0, maximum-scale=1.0"/>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <link rel="stylesheet" type="text/css" href="menes/style.css"/>
+    <script type="text/javascript" src="menes/menes.js"></script>
+    <script type="text/javascript" src="package.js"></script>
+    <base target="_blank"/>
+</head><body><div class="page">
+<div class="dialog">
+    <div class="panel">
+
+<fieldset>
+    <div>
+        <label id="name"></label>
+        <div id="latest"></div>
+    </div>
+
+    <a id="author-link" class="author">
+        <label>Author</label>
+        <div id="author"></div>
+    </a>
+
+    <div id="description"></div>
+
+    <a id="homepage-link" class="homepage">
+        <label>More Information</label>
+    </a>
+</fieldset>
+
+<label class="installed">Installed Package</label>
+<fieldset class="installed">
+    <div>
+        <label>Version</label>
+        <div id="installed"></div>
+    </div>
+
+    <a id="files-link"><label>Filesystem Content</label></a>
+</fieldset>
+
+<label>Package Details</label>
+<fieldset>
+    <div>
+        <label>ID</label>
+        <div id="id"></div>
+    </div>
+
+    <div class="section">
+        <label>Section</label>
+        <div id="section"></div>
+    </div>
+
+    <div class="size">
+        <label>Expanded Size</label>
+        <div id="size"></div>
+    </div>
+
+    <a id="maintainer-link" class="maintainer">
+        <label>Maintainer</label>
+        <div id="maintainer"></div>
+    </a>
+
+    <div class="trusted">
+        <img src="trusted.png" style="margin-top: 2px; position: absolute">
+        <label></label>
+        <div>This package has been signed.</div>
+    </div>
+</fieldset>
+
+<label class="source">Source Infomation</label>
+<fieldset class="source">
+    <a id="origin-link"><label id="origin"></label></a>
+</fieldset>
+
+    </div>
+</div>
+</div></body></html>
diff --git a/Cydia.app/package.js b/Cydia.app/package.js
new file mode 100644 (file)
index 0000000..63ba4d3
--- /dev/null
@@ -0,0 +1,87 @@
+/*var package = {
+    "name": "MobileTerminal",
+    "latest": "286u-5",
+    "author": {
+        "name": "Allen Porter",
+        "address": "allen.porter@gmail.com"
+    },
+    "description": "this is a sample description",
+    "homepage": "http://cydia.saurik.com/terminal.html",
+    "installed": "286u-4",
+    "id": "mobileterminal",
+    "section": "Terminal Support",
+    "size": 552*1024,
+    "maintainer": {
+        "name": "Jay Freeman",
+        "address": "saurik@saurik.com"
+    },
+    "source": {
+        "name": "Telesphoreo Tangelo"
+    }
+};*/
+
+$(function () {
+    var id = package.id;
+    var name = package.name;
+    var regarding = encodeURIComponent("Cydia/APT: " + name);
+
+    $("#name").html(name);
+    $("#latest").html(package.latest);
+
+    var author = package.author;
+    if (author == null)
+        $(".author").remove();
+    else {
+        $("#author").html(author.name);
+        $("#author-link").href("mailto:" + author.address + "?subject=" + regarding);
+    }
+
+    var description = package.description;
+    if (description == null)
+        description = package.tagline;
+    else
+        description = description.replace(/\n/g, "<br/>");
+    $("#description").html(description);
+
+    var homepage = package.homepage;
+    if (homepage == null)
+        $(".homepage").remove();
+    else
+        $("#homepage-link").href(homepage);
+
+    var installed = package.installed;
+    if (installed == null)
+        $(".installed").remove();
+    else {
+        $("#installed").html(installed);
+        $("#files-link").href("cydia://files/" + id);
+    }
+
+    $("#id").html(id);
+
+    var section = package.section;
+    if (section == null)
+        $(".section").remove();
+    else
+        $("#section").html(package.section);
+
+    var size = package.size;
+    if (size == 0)
+        $(".size").remove();
+    else
+        $("#size").html(size / 1024 + " kB");
+
+    var maintainer = package.maintainer;
+    if (maintainer == null)
+        $(".maintainer").remove();
+    else {
+        $("#maintainer").html(maintainer.name);
+        $("#maintainer-link").href("mailto:" + maintainer.address + "?subject=" + regarding);
+    }
+
+    var source = package.source;
+    if (source == null)
+        $(".source").remove();
+    else
+        $("#origin").html(source.name);
+});
index 523e6c5fffd6b15d6e224e6edf68a61b8aa4d72f..d6485b1c58aa29a0f3a5be9207ae62529425c4d8 100644 (file)
--- a/Cydia.mm
+++ b/Cydia.mm
@@ -272,11 +272,11 @@ class Pcre {
 /* Mime Addresses {{{ */
 @interface Address : NSObject {
     NSString *name_;
-    NSString *email_;
+    NSString *address_;
 }
 
 - (NSString *) name;
-- (NSString *) email;
+- (NSString *) address;
 
 + (Address *) addressWithString:(NSString *)string;
 - (Address *) initWithString:(NSString *)string;
@@ -286,8 +286,8 @@ class Pcre {
 
 - (void) dealloc {
     [name_ release];
-    if (email_ != nil)
-        [email_ release];
+    if (address_ != nil)
+        [address_ release];
     [super dealloc];
 }
 
@@ -295,24 +295,36 @@ class Pcre {
     return name_;
 }
 
-- (NSString *) email {
-    return email_;
+- (NSString *) address {
+    return address_;
 }
 
 + (Address *) addressWithString:(NSString *)string {
     return [[[Address alloc] initWithString:string] autorelease];
 }
 
++ (NSArray *) _attributeKeys {
+    return [NSArray arrayWithObjects:@"address", @"name", nil];
+}
+
+- (NSArray *) attributeKeys {
+    return [[self class] _attributeKeys];
+}
+
++ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
+    return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
+}
+
 - (Address *) initWithString:(NSString *)string {
     if ((self = [super init]) != nil) {
         const char *data = [string UTF8String];
         size_t size = [string length];
 
-        static Pcre email_r("^\"?(.*)\"? <([^>]*)>$");
+        static Pcre address_r("^\"?(.*)\"? <([^>]*)>$");
 
-        if (email_r(data, size)) {
-            name_ = [email_r[1] retain];
-            email_ = [email_r[2] retain];
+        if (address_r(data, size)) {
+            name_ = [address_r[1] retain];
+            address_ = [address_r[2] retain];
         } else {
             name_ = [[NSString alloc]
                 initWithBytes:data
@@ -320,7 +332,7 @@ class Pcre {
                 encoding:kCFStringEncodingUTF8
             ];
 
-            email_ = nil;
+            address_ = nil;
         }
     } return self;
 }
@@ -441,7 +453,7 @@ static NSString *Home_;
 static BOOL Sounds_Keyboard_;
 
 static BOOL Advanced_;
-//static BOOL Loaded_;
+static BOOL Loaded_;
 static BOOL Ignored_;
 
 static UIFont *Font12_;
@@ -817,6 +829,18 @@ class Progress :
     [super dealloc];
 }
 
++ (NSArray *) _attributeKeys {
+    return [NSArray arrayWithObjects:@"description", @"distribution", @"host", @"key", @"label", @"name", @"origin", @"trusted", @"type", @"uri", @"version", nil];
+}
+
+- (NSArray *) attributeKeys {
+    return [[self class] _attributeKeys];
+}
+
++ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
+    return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
+}
+
 - (Source *) initWithMetaIndex:(metaIndex *)index {
     if ((self = [super init]) != nil) {
         trusted_ = index->IsTrusted();
@@ -1022,7 +1046,7 @@ NSString *Scour(const char *field, const char *begin, const char *end) {
     NSString *name_;
     NSString *tagline_;
     NSString *icon_;
-    NSString *website_;
+    NSString *homepage_;
     Address *sponsor_;
     Address *author_;
     NSArray *tags_;
@@ -1063,7 +1087,7 @@ NSString *Scour(const char *field, const char *begin, const char *end) {
 - (NSString *) name;
 - (NSString *) tagline;
 - (NSString *) icon;
-- (NSString *) website;
+- (NSString *) homepage;
 - (Address *) author;
 
 - (NSArray *) relationships;
@@ -1107,8 +1131,8 @@ NSString *Scour(const char *field, const char *begin, const char *end) {
     [tagline_ release];
     if (icon_ != nil)
         [icon_ release];
-    if (website_ != nil)
-        [website_ release];
+    if (homepage_ != nil)
+        [homepage_ release];
     if (sponsor_ != nil)
         [sponsor_ release];
     if (author_ != nil)
@@ -1124,6 +1148,18 @@ NSString *Scour(const char *field, const char *begin, const char *end) {
     [super dealloc];
 }
 
++ (NSArray *) _attributeKeys {
+    return [NSArray arrayWithObjects:@"author", @"description", @"essential", @"homepage", @"icon", @"id", @"installed", @"latest", @"maintainer", @"name", @"section", @"size", @"source", @"sponsor", @"tagline", nil];
+}
+
+- (NSArray *) attributeKeys {
+    return [[self class] _attributeKeys];
+}
+
++ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
+    return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
+}
+
 - (Package *) initWithIterator:(pkgCache::PkgIterator)iterator database:(Database *)database {
     if ((self = [super init]) != nil) {
         iterator_ = iterator;
@@ -1157,11 +1193,11 @@ NSString *Scour(const char *field, const char *begin, const char *end) {
             icon_ = Scour("Icon", begin, end);
             if (icon_ != nil)
                 icon_ = [icon_ retain];
-            website_ = Scour("Homepage", begin, end);
-            if (website_ == nil)
-                website_ = Scour("Website", begin, end);
-            if (website_ != nil)
-                website_ = [website_ retain];
+            homepage_ = Scour("Homepage", begin, end);
+            if (homepage_ == nil)
+                homepage_ = Scour("Website", begin, end);
+            if (homepage_ != nil)
+                homepage_ = [homepage_ retain];
             NSString *sponsor = Scour("Sponsor", begin, end);
             if (sponsor != nil)
                 sponsor_ = [[Address addressWithString:sponsor] retain];
@@ -1368,8 +1404,8 @@ NSString *Scour(const char *field, const char *begin, const char *end) {
     return icon_;
 }
 
-- (NSString *) website {
-    return website_;
+- (NSString *) homepage {
+    return homepage_;
 }
 
 - (Address *) sponsor {
@@ -3148,12 +3184,9 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 @end
 /* }}} */
 /* Package View {{{ */
-@interface PackageView : RVPage {
-    _transient Database *database_;
-    UIPreferencesTable *table_;
+@interface PackageView : BrowserView {
     Package *package_;
     NSString *name_;
-    UITextView *description_;
     NSMutableArray *buttons_;
 }
 
@@ -3165,238 +3198,14 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 @implementation PackageView
 
 - (void) dealloc {
-    [table_ setDataSource:nil];
-    [table_ setDelegate:nil];
-
     if (package_ != nil)
         [package_ release];
     if (name_ != nil)
         [name_ release];
-    if (description_ != nil)
-        [description_ release];
-    [table_ release];
     [buttons_ release];
     [super dealloc];
 }
 
-- (int) numberOfGroupsInPreferencesTable:(UIPreferencesTable *)table {
-    int number = 2;
-    if ([package_ installed] != nil)
-        ++number;
-    if ([package_ source] != nil)
-        ++number;
-    return number;
-}
-
-- (NSString *) preferencesTable:(UIPreferencesTable *)table titleForGroup:(int)group {
-    if (group-- == 0)
-        return nil;
-    else if ([package_ installed] != nil && group-- == 0)
-        return @"Installed Package";
-    else if (group-- == 0)
-        return @"Package Details";
-    else if ([package_ source] != nil && group-- == 0)
-        return @"Source Information";
-    else _assert(false);
-}
-
-- (float) preferencesTable:(UIPreferencesTable *)table heightForRow:(int)row inGroup:(int)group withProposedHeight:(float)proposed {
-    if (description_ == nil || group != 0 || row != ([package_ author] == nil ? 1 : 2))
-        return proposed;
-    else
-        return [description_ visibleTextRect].size.height + TextViewOffset_;
-}
-
-- (int) preferencesTable:(UIPreferencesTable *)table numberOfRowsInGroup:(int)group {
-    if (group-- == 0) {
-        int number = 1;
-        if ([package_ author] != nil)
-            ++number;
-        if (description_ != nil)
-            ++number;
-        if ([package_ website] != nil)
-            ++number;
-        return number;
-    } else if ([package_ installed] != nil && group-- == 0)
-        return 2;
-    else if (group-- == 0) {
-        int number = 2;
-        if ([package_ size] != 0)
-            ++number;
-        if ([package_ maintainer] != nil)
-            ++number;
-        if ([package_ sponsor] != nil)
-            ++number;
-        if ([package_ relationships] != nil)
-            ++number;
-        if ([[package_ source] trusted])
-            ++number;
-        return number;
-    } else if ([package_ source] != nil && group-- == 0) {
-        Source *source = [package_ source];
-        NSString *description = [source description];
-        int number = 1;
-        if (description != nil && ![description isEqualToString:[source label]])
-            ++number;
-        if ([source origin] != nil)
-            ++number;
-        return number;
-    } else _assert(false);
-}
-
-- (UIPreferencesTableCell *) preferencesTable:(UIPreferencesTable *)table cellForRow:(int)row inGroup:(int)group {
-    UIPreferencesTableCell *cell = [[[UIPreferencesTableCell alloc] init] autorelease];
-    [cell setShowSelection:NO];
-
-    if (group-- == 0) {
-        if (false) {
-        } else if (row-- == 0) {
-            [cell setTitle:[package_ name]];
-            [cell setValue:[package_ latest]];
-        } else if ([package_ author] != nil && row-- == 0) {
-            [cell setTitle:@"Author"];
-            [cell setValue:[[package_ author] name]];
-            [cell setShowDisclosure:YES];
-            [cell setShowSelection:YES];
-        } else if (description_ != nil && row-- == 0) {
-            [cell addSubview:description_];
-        } else if ([package_ website] != nil && row-- == 0) {
-            [cell setTitle:@"More Information"];
-            [cell setShowDisclosure:YES];
-            [cell setShowSelection:YES];
-        } else _assert(false);
-    } else if ([package_ installed] != nil && group-- == 0) {
-        if (false) {
-        } else if (row-- == 0) {
-            [cell setTitle:@"Version"];
-            NSString *installed([package_ installed]);
-            [cell setValue:(installed == nil ? @"n/a" : installed)];
-        } else if (row-- == 0) {
-            [cell setTitle:@"Filesystem Content"];
-            [cell setShowDisclosure:YES];
-            [cell setShowSelection:YES];
-        } else _assert(false);
-    } else if (group-- == 0) {
-        if (false) {
-        } else if (row-- == 0) {
-            [cell setTitle:@"Identifier"];
-            [cell setValue:[package_ id]];
-        } else if (row-- == 0) {
-            [cell setTitle:@"Section"];
-            NSString *section([package_ section]);
-            [cell setValue:(section == nil ? @"n/a" : section)];
-        } else if ([package_ size] != 0 && row-- == 0) {
-            [cell setTitle:@"Expanded Size"];
-            [cell setValue:SizeString([package_ size])];
-        } else if ([package_ maintainer] != nil && row-- == 0) {
-            [cell setTitle:@"Maintainer"];
-            [cell setValue:[[package_ maintainer] name]];
-            [cell setShowDisclosure:YES];
-            [cell setShowSelection:YES];
-        } else if ([package_ sponsor] != nil && row-- == 0) {
-            [cell setTitle:@"Sponsor"];
-            [cell setValue:[[package_ sponsor] name]];
-            [cell setShowDisclosure:YES];
-            [cell setShowSelection:YES];
-        } else if ([package_ relationships] != nil && row-- == 0) {
-            [cell setTitle:@"Package Relationships"];
-            [cell setShowDisclosure:YES];
-            [cell setShowSelection:YES];
-        } else if ([[package_ source] trusted] && row-- == 0) {
-            [cell setIcon:[UIImage applicationImageNamed:@"trusted.png"]];
-            [cell setValue:@"This package has been signed."];
-        } else _assert(false);
-    } else if ([package_ source] != nil && group-- == 0) {
-        Source *source = [package_ source];
-        NSString *description = [source description];
-
-        if (false) {
-        } else if (row-- == 0) {
-            NSString *label = [source label];
-            if (label == nil)
-                label = [source uri];
-            [cell setTitle:label];
-            [cell setValue:[source version]];
-        } else if (description != nil && ![description isEqualToString:[source label]] && row-- == 0) {
-            [cell setValue:description];
-        } else if ([source origin] != nil && row-- == 0) {
-            [cell setTitle:@"Origin"];
-            [cell setValue:[source origin]];
-        } else _assert(false);
-    } else _assert(false);
-
-    return cell;
-}
-
-- (BOOL) canSelectRow:(int)row {
-    return YES;
-}
-
-- (void) tableRowSelected:(NSNotification *)notification {
-    int row = [table_ selectedRow];
-    if (row == INT_MAX)
-        return;
-
-    #define _else else goto _label; return; } _label:
-
-    if (true) {
-        if (row-- == 0) {
-        } else if (row-- == 0) {
-        } else if ([package_ author] != nil && row-- == 0) {
-            [delegate_ openURL:[NSURL URLWithString:[NSString stringWithFormat:@"mailto:%@?subject=%@",
-                [[package_ author] email],
-                [[NSString stringWithFormat:@"regarding apt package \"%@\"",
-                    [package_ name]
-                ] stringByAddingPercentEscapes]
-            ]]];
-        } else if (description_ != nil && row-- == 0) {
-        } else if ([package_ website] != nil && row-- == 0) {
-            NSURL *url = [NSURL URLWithString:[package_ website]];
-            BrowserView *browser = [[[BrowserView alloc] initWithBook:book_ database:database_] autorelease];
-            [browser setDelegate:delegate_];
-            [book_ pushPage:browser];
-            [browser loadURL:url];
-    } _else if ([package_ installed] != nil) {
-        if (row-- == 0) {
-        } else if (row-- == 0) {
-        } else if (row-- == 0) {
-            FileTable *files = [[[FileTable alloc] initWithBook:book_ database:database_] autorelease];
-            [files setDelegate:delegate_];
-            [files setPackage:package_];
-            [book_ pushPage:files];
-    } _else if (true) {
-        if (row-- == 0) {
-        } else if (row-- == 0) {
-        } else if (row-- == 0) {
-        } else if ([package_ size] != 0 && row-- == 0) {
-        } else if ([package_ maintainer] != nil && row-- == 0) {
-            [delegate_ openURL:[NSURL URLWithString:[NSString stringWithFormat:@"mailto:%@?subject=%@",
-                [[package_ maintainer] email],
-                [[NSString stringWithFormat:@"regarding apt package \"%@\"",
-                    [package_ name]
-                ] stringByAddingPercentEscapes]
-            ]]];
-        } else if ([package_ sponsor] != nil && row-- == 0) {
-            NSURL *url = [NSURL URLWithString:[[package_ sponsor] email]];
-            BrowserView *browser = [[[BrowserView alloc] initWithBook:book_ database:database_] autorelease];
-            [browser setDelegate:delegate_];
-            [book_ pushPage:browser];
-            [browser loadURL:url];
-        } else if ([package_ relationships] != nil && row-- == 0) {
-        } else if ([[package_ source] trusted] && row-- == 0) {
-    } _else if ([package_ source] != nil) {
-        Source *source = [package_ source];
-        NSString *description = [source description];
-
-        if (row-- == 0) {
-        } else if (row-- == 0) {
-        } else if (description != nil && ![description isEqualToString:[source label]] && row-- == 0) {
-        } else if ([source origin] != nil && row-- == 0) {
-    } _else _assert(false);
-
-    #undef _else
-}
-
 - (void) _clickButtonWithName:(NSString *)name {
     if ([name isEqualToString:@"Install"])
         [delegate_ installPackage:package_];
@@ -3420,7 +3229,16 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
     [sheet dismiss];
 }
 
+- (void) webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
+    _trace();
+    NSLog(@"%@", package_);
+    [window setValue:package_ forKey:@"package"];
+}
+
 - (void) _rightButtonClicked {
+    /*[super _rightButtonClicked];
+    return;*/
+
     int count = [buttons_ count];
     _assert(count != 0);
 
@@ -3441,7 +3259,7 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
     }
 }
 
-- (NSString *) rightButtonTitle {
+- (NSString *) _rightButtonTitle {
     int count = [buttons_ count];
     return count == 0 ? nil : count != 1 ? @"Modify" : [buttons_ objectAtIndex:0];
 }
@@ -3451,15 +3269,8 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 }
 
 - (id) initWithBook:(RVBook *)book database:(Database *)database {
-    if ((self = [super initWithBook:book]) != nil) {
+    if ((self = [super initWithBook:book database:database]) != nil) {
         database_ = database;
-
-        table_ = [[UIPreferencesTable alloc] initWithFrame:[self bounds]];
-        [self addSubview:table_];
-
-        [table_ setDataSource:self];
-        [table_ setDelegate:self];
-
         buttons_ = [[NSMutableArray alloc] initWithCapacity:4];
     } return self;
 }
@@ -3475,26 +3286,13 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
         name_ = nil;
     }
 
-    if (description_ != nil) {
-        [description_ release];
-        description_ = nil;
-    }
-
     [buttons_ removeAllObjects];
 
     if (package != nil) {
         package_ = [package retain];
         name_ = [[package id] retain];
 
-        NSString *description([package description]);
-        if (description == nil)
-            description = [package tagline];
-        if (description != nil) {
-            description_ = [GetTextView(description, 12, true) retain];
-            [description_ setTextColor:Black_];
-        }
-
-        [table_ reloadData];
+        [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"package" ofType:@"html"]]];
 
         if ([package_ source] == nil);
         else if ([package_ upgradableAndEssential:NO])
@@ -3508,10 +3306,6 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
     }
 }
 
-- (void) resetViewAnimated:(BOOL)animated {
-    [table_ resetViewAnimated:animated];
-}
-
 - (void) reloadData {
     [self setPackage:[database_ packageWithName:name_]];
     [self reloadButtons];
@@ -4286,7 +4080,7 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
     return @"Settings";
 }
 
-- (NSString *) rightButtonTitle {
+- (NSString *) _rightButtonTitle {
     return nil;
 }
 
@@ -4379,7 +4173,15 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
         page = [[[SourceTable alloc] initWithBook:book_ database:database_] autorelease];
     else if ([href isEqualToString:@"cydia://packages"])
         page = [[[InstalledView alloc] initWithBook:book_ database:database_] autorelease];
-    else if ([href hasPrefix:@"apptapp://package/"]) {
+    else if ([href hasPrefix:@"cydia://files/"]) {
+        NSString *name = [href substringFromIndex:14];
+
+        if (Package *package = [database_ packageWithName:name]) {
+            FileTable *files = [[[FileTable alloc] initWithBook:book_ database:database_] autorelease];
+            [files setPackage:package];
+            page = files;
+        }
+    } else if ([href hasPrefix:@"apptapp://package/"]) {
         NSString *name = [href substringFromIndex:18];
 
         if (Package *package = [database_ packageWithName:name]) {
@@ -4575,8 +4377,12 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
     [self reloadURL];
 }
 
+- (NSString *) _rightButtonTitle {
+    return @"Reload";
+}
+
 - (NSString *) rightButtonTitle {
-    return loading_ ? @"" : @"Reload";
+    return loading_ ? @"" : [self _rightButtonTitle];
 }
 
 - (NSString *) title {
@@ -5561,13 +5367,13 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 
     [self updateData];
 
-    /*if ([packages count] == 0);
-    else if (Loaded_)*/
+    if ([packages count] == 0);
+    else if (Loaded_)
         [self _loaded];
-    /*else {
+    else {
         Loaded_ = YES;
         [book_ update];
-    }*/
+    }
 
     /*[hud show:NO];
     [hud removeFromSuperview];*/