From dc53c38f27762575a7c7d8e2776e68ba44d3db4d Mon Sep 17 00:00:00 2001 From: Remy Date: Thu, 4 Aug 2011 14:53:31 -0700 Subject: [PATCH 01/22] initial commit for the new interface. includes: html5 boilerplate with modernizr, datatables, base.html and index.html --- data/css/data_table.css | 538 ++ data/css/style.css | 362 +- data/css/view.css | 827 -- data/images/back_disabled.jpg | Bin 0 -> 612 bytes data/images/back_enabled.jpg | Bin 0 -> 807 bytes data/images/forward_disabled.jpg | Bin 0 -> 635 bytes data/images/forward_enabled.jpg | Bin 0 -> 852 bytes data/images/sort_asc.png | Bin 0 -> 263 bytes data/images/sort_asc_disabled.png | Bin 0 -> 252 bytes data/images/sort_both.png | Bin 0 -> 282 bytes data/images/sort_desc.png | Bin 0 -> 260 bytes data/images/sort_desc_disabled.png | Bin 0 -> 251 bytes data/interfaces/default/_inc_footer.tmpl | 6 - data/interfaces/default/_inc_header.tmpl | 30 - data/interfaces/default/albumPage.tmpl | 45 - data/interfaces/default/artistInfo.tmpl | 13 - data/interfaces/default/artistPage.tmpl | 39 - data/interfaces/default/base.html | 76 + data/interfaces/default/config.tmpl | 238 - data/interfaces/default/history.tmpl | 3 - data/interfaces/default/index.html | 32 + data/interfaces/default/index.tmpl | 33 - data/interfaces/default/manage.tmpl | 28 - data/interfaces/default/search.tmpl | 31 - data/interfaces/default/upcoming.tmpl | 3 - data/js/libs/dd_belatedpng.js | 13 + data/js/libs/jquery-1.6.2.js | 8981 ++++++++++++++++++++++ data/js/libs/jquery-1.6.2.min.js | 18 + data/js/libs/jquery.dataTables.min.js | 151 + data/js/libs/modernizr-1.7.min.js | 2 + data/js/plugins.js | 9 + data/js/script.js | 9 + data/js/view.js | 1 - headphones/webserve.py | 24 +- 34 files changed, 9999 insertions(+), 1513 deletions(-) create mode 100644 data/css/data_table.css mode change 100644 => 100755 data/css/style.css delete mode 100755 data/css/view.css create mode 100644 data/images/back_disabled.jpg create mode 100644 data/images/back_enabled.jpg create mode 100644 data/images/forward_disabled.jpg create mode 100644 data/images/forward_enabled.jpg create mode 100644 data/images/sort_asc.png create mode 100644 data/images/sort_asc_disabled.png create mode 100644 data/images/sort_both.png create mode 100644 data/images/sort_desc.png create mode 100644 data/images/sort_desc_disabled.png delete mode 100644 data/interfaces/default/_inc_footer.tmpl delete mode 100644 data/interfaces/default/_inc_header.tmpl delete mode 100644 data/interfaces/default/albumPage.tmpl delete mode 100644 data/interfaces/default/artistInfo.tmpl delete mode 100644 data/interfaces/default/artistPage.tmpl create mode 100644 data/interfaces/default/base.html delete mode 100644 data/interfaces/default/config.tmpl delete mode 100644 data/interfaces/default/history.tmpl create mode 100644 data/interfaces/default/index.html delete mode 100644 data/interfaces/default/index.tmpl delete mode 100644 data/interfaces/default/manage.tmpl delete mode 100644 data/interfaces/default/search.tmpl delete mode 100644 data/interfaces/default/upcoming.tmpl create mode 100755 data/js/libs/dd_belatedpng.js create mode 100644 data/js/libs/jquery-1.6.2.js create mode 100644 data/js/libs/jquery-1.6.2.min.js create mode 100644 data/js/libs/jquery.dataTables.min.js create mode 100755 data/js/libs/modernizr-1.7.min.js create mode 100755 data/js/plugins.js create mode 100755 data/js/script.js delete mode 100755 data/js/view.js diff --git a/data/css/data_table.css b/data/css/data_table.css new file mode 100644 index 00000000..b230bea8 --- /dev/null +++ b/data/css/data_table.css @@ -0,0 +1,538 @@ +/* + * File: data_table.css + * CVS: $Id$ + * Description: CSS descriptions for DataTables demo pages + * Author: Allan Jardine + * Created: Tue May 12 06:47:22 BST 2009 + * Modified: $Date$ by $Author$ + * Language: CSS + * Project: DataTables + * + * Copyright 2009 Allan Jardine. All Rights Reserved. + * + * *************************************************************************** + * DESCRIPTION + * + * The styles given here are suitable for the demos that are used with the standard DataTables + * distribution (see www.datatables.net). You will most likely wish to modify these styles to + * meet the layout requirements of your site. + * + * Common issues: + * 'full_numbers' pagination - I use an extra selector on the body tag to ensure that there is + * no conflict between the two pagination types. If you want to use full_numbers pagination + * ensure that you either have "example_alt_pagination" as a body class name, or better yet, + * modify that selector. + * Note that the path used for Images is relative. All images are by default located in + * ../images/ - relative to this CSS file. + */ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * DataTables features + */ + +.dataTables_wrapper { + position: relative; + min-height: 302px; + clear: both; + _height: 302px; + zoom: 1; /* Feeling sorry for IE */ +} + +.dataTables_processing { + position: absolute; + top: 50%; + left: 50%; + width: 250px; + height: 30px; + margin-left: -125px; + margin-top: -15px; + padding: 14px 0 2px 0; + border: 1px solid #ddd; + text-align: center; + color: #999; + font-size: 14px; + background-color: white; +} + +.dataTables_length { + width: 40%; + float: left; +} + +.dataTables_filter { + width: 50%; + float: right; + text-align: right; +} + +.dataTables_info { + width: 60%; + float: left; +} + +.dataTables_paginate { + width: 44px; + * width: 50px; + float: right; + text-align: right; +} + +/* Pagination nested */ +.paginate_disabled_previous, .paginate_enabled_previous, .paginate_disabled_next, .paginate_enabled_next { + height: 19px; + width: 19px; + margin-left: 3px; + float: left; +} + +.paginate_disabled_previous { + background-image: url('../images/back_disabled.jpg'); +} + +.paginate_enabled_previous { + background-image: url('../images/back_enabled.jpg'); +} + +.paginate_disabled_next { + background-image: url('../images/forward_disabled.jpg'); +} + +.paginate_enabled_next { + background-image: url('../images/forward_enabled.jpg'); +} + + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * DataTables display + */ +table.display { + margin: 0 auto; + clear: both; + width: 100%; + + /* Note Firefox 3.5 and before have a bug with border-collapse + * ( https://bugzilla.mozilla.org/show%5Fbug.cgi?id=155955 ) + * border-spacing: 0; is one possible option. Conditional-css.com is + * useful for this kind of thing + * + * Further note IE 6/7 has problems when calculating widths with border width. + * It subtracts one px relative to the other browsers from the first column, and + * adds one to the end... + * + * If you want that effect I'd suggest setting a border-top/left on th/td's and + * then filling in the gaps with other borders. + */ +} + +table.display thead th { + padding: 3px 18px 3px 10px; + border-bottom: 1px solid black; + font-weight: bold; + cursor: pointer; + * cursor: hand; +} + +table.display tfoot th { + padding: 3px 18px 3px 10px; + border-top: 1px solid black; + font-weight: bold; +} + +table.display tr.heading2 td { + border-bottom: 1px solid #aaa; +} + +table.display td { + padding: 3px 10px; +} + +table.display td.center { + text-align: center; +} + + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * DataTables sorting + */ + +.sorting_asc { + background: url('../images/sort_asc.png') no-repeat center right; +} + +.sorting_desc { + background: url('../images/sort_desc.png') no-repeat center right; +} + +.sorting { + background: url('../images/sort_both.png') no-repeat center right; +} + +.sorting_asc_disabled { + background: url('../images/sort_asc_disabled.png') no-repeat center right; +} + +.sorting_desc_disabled { + background: url('../images/sort_desc_disabled.png') no-repeat center right; +} + + + + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * DataTables row classes + */ +table.display tr.odd.gradeA { + background-color: #ddffdd; +} + +table.display tr.even.gradeA { + background-color: #eeffee; +} + +table.display tr.odd.gradeC { + background-color: #ddddff; +} + +table.display tr.even.gradeC { + background-color: #eeeeff; +} + +table.display tr.odd.gradeX { + background-color: #ffdddd; +} + +table.display tr.even.gradeX { + background-color: #ffeeee; +} + +table.display tr.odd.gradeU { + background-color: #ddd; +} + +table.display tr.even.gradeU { + background-color: #eee; +} + + +tr.odd { + background-color: #E2E4FF; +} + +tr.even { + background-color: white; +} + + + + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Misc + */ +.dataTables_scroll { + clear: both; +} + +.dataTables_scrollBody { + *margin-top: -1px; +} + +.top, .bottom { + padding: 15px; + background-color: #F5F5F5; + border: 1px solid #CCCCCC; +} + +.top .dataTables_info { + float: none; +} + +.clear { + clear: both; +} + +.dataTables_empty { + text-align: center; +} + +tfoot input { + margin: 0.5em 0; + width: 100%; + color: #444; +} + +tfoot input.search_init { + color: #999; +} + +td.group { + background-color: #d1cfd0; + border-bottom: 2px solid #A19B9E; + border-top: 2px solid #A19B9E; +} + +td.details { + background-color: #d1cfd0; + border: 2px solid #A19B9E; +} + + +.example_alt_pagination div.dataTables_info { + width: 40%; +} + +.paging_full_numbers { + width: 400px; + height: 22px; + line-height: 22px; +} + +.paging_full_numbers span.paginate_button, + .paging_full_numbers span.paginate_active { + border: 1px solid #aaa; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + padding: 2px 5px; + margin: 0 3px; + cursor: pointer; + *cursor: hand; +} + +.paging_full_numbers span.paginate_button { + background-color: #ddd; +} + +.paging_full_numbers span.paginate_button:hover { + background-color: #ccc; +} + +.paging_full_numbers span.paginate_active { + background-color: #99B3FF; +} + +table.display tr.even.row_selected td { + background-color: #B0BED9; +} + +table.display tr.odd.row_selected td { + background-color: #9FAFD1; +} + + +/* + * Sorting classes for columns + */ +/* For the standard odd/even */ +tr.odd td.sorting_1 { + background-color: #D3D6FF; +} + +tr.odd td.sorting_2 { + background-color: #DADCFF; +} + +tr.odd td.sorting_3 { + background-color: #E0E2FF; +} + +tr.even td.sorting_1 { + background-color: #EAEBFF; +} + +tr.even td.sorting_2 { + background-color: #F2F3FF; +} + +tr.even td.sorting_3 { + background-color: #F9F9FF; +} + + +/* For the Conditional-CSS grading rows */ +/* + Colour calculations (based off the main row colours) + Level 1: + dd > c4 + ee > d5 + Level 2: + dd > d1 + ee > e2 + */ +tr.odd.gradeA td.sorting_1 { + background-color: #c4ffc4; +} + +tr.odd.gradeA td.sorting_2 { + background-color: #d1ffd1; +} + +tr.odd.gradeA td.sorting_3 { + background-color: #d1ffd1; +} + +tr.even.gradeA td.sorting_1 { + background-color: #d5ffd5; +} + +tr.even.gradeA td.sorting_2 { + background-color: #e2ffe2; +} + +tr.even.gradeA td.sorting_3 { + background-color: #e2ffe2; +} + +tr.odd.gradeC td.sorting_1 { + background-color: #c4c4ff; +} + +tr.odd.gradeC td.sorting_2 { + background-color: #d1d1ff; +} + +tr.odd.gradeC td.sorting_3 { + background-color: #d1d1ff; +} + +tr.even.gradeC td.sorting_1 { + background-color: #d5d5ff; +} + +tr.even.gradeC td.sorting_2 { + background-color: #e2e2ff; +} + +tr.even.gradeC td.sorting_3 { + background-color: #e2e2ff; +} + +tr.odd.gradeX td.sorting_1 { + background-color: #ffc4c4; +} + +tr.odd.gradeX td.sorting_2 { + background-color: #ffd1d1; +} + +tr.odd.gradeX td.sorting_3 { + background-color: #ffd1d1; +} + +tr.even.gradeX td.sorting_1 { + background-color: #ffd5d5; +} + +tr.even.gradeX td.sorting_2 { + background-color: #ffe2e2; +} + +tr.even.gradeX td.sorting_3 { + background-color: #ffe2e2; +} + +tr.odd.gradeU td.sorting_1 { + background-color: #c4c4c4; +} + +tr.odd.gradeU td.sorting_2 { + background-color: #d1d1d1; +} + +tr.odd.gradeU td.sorting_3 { + background-color: #d1d1d1; +} + +tr.even.gradeU td.sorting_1 { + background-color: #d5d5d5; +} + +tr.even.gradeU td.sorting_2 { + background-color: #e2e2e2; +} + +tr.even.gradeU td.sorting_3 { + background-color: #e2e2e2; +} + + +/* + * Row highlighting example + */ +.ex_highlight #example tbody tr.even:hover, #example tbody tr.even td.highlighted { + background-color: #ECFFB3; +} + +.ex_highlight #example tbody tr.odd:hover, #example tbody tr.odd td.highlighted { + background-color: #E6FF99; +} + +.ex_highlight_row #example tr.even:hover { + background-color: #ECFFB3; +} + +.ex_highlight_row #example tr.even:hover td.sorting_1 { + background-color: #DDFF75; +} + +.ex_highlight_row #example tr.even:hover td.sorting_2 { + background-color: #E7FF9E; +} + +.ex_highlight_row #example tr.even:hover td.sorting_3 { + background-color: #E2FF89; +} + +.ex_highlight_row #example tr.odd:hover { + background-color: #E6FF99; +} + +.ex_highlight_row #example tr.odd:hover td.sorting_1 { + background-color: #D6FF5C; +} + +.ex_highlight_row #example tr.odd:hover td.sorting_2 { + background-color: #E0FF84; +} + +.ex_highlight_row #example tr.odd:hover td.sorting_3 { + background-color: #DBFF70; +} + + +/* + * KeyTable + */ +table.KeyTable td { + border: 3px solid transparent; +} + +table.KeyTable td.focus { + border: 3px solid #3366FF; +} + +table.display tr.gradeA { + background-color: #eeffee; +} + +table.display tr.gradeC { + background-color: #ddddff; +} + +table.display tr.gradeX { + background-color: #ffdddd; +} + +table.display tr.gradeU { + background-color: #ddd; +} + +div.box { + height: 100px; + padding: 10px; + overflow: auto; + border: 1px solid #8080FF; + background-color: #E5E5FF; +} diff --git a/data/css/style.css b/data/css/style.css old mode 100644 new mode 100755 index 8462d8b9..7afda82b --- a/data/css/style.css +++ b/data/css/style.css @@ -1,216 +1,150 @@ -body{ - font-family:"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, - Helvetica, sans-serif; - font-size:16px; - background-color: #ebf4fb; - padding: 5px; - } - -h1{ - font-family:"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, - Helvetica, sans-serif; - size:30px; - color: #8B8989; - } -.container{ - width: 95%; - } -.header{ - background-color: #EEB4B4; - width: 80%; - } -.config{ - font-size:14px; - margin-left: 30px; - } -.configtable{ - background-color: #eeeeee; - width: 95%; - padding: 15px; - line-height: 12px; - -moz-border-radius: 10px; - border-radius: 10px; - } -.logo{ - font-family:"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, - Helvetica, sans-serif; - font-size:20px; - color: #8B8989; - margin-left: 30px; - margin-top: 0px; - } -.search{ - position: absolute; - top: 50px; - right: 70px; - } -.data{ - background-color: #ebf4fb; - font-size:24px; - } -.datanil{ - background-color: #ebf4fb; - font-size:36px; - text-align: center; - position: relative; - top:150px; - } -.table{ - padding: 10px; - font-size:24px; - background-color: #ffffff; - width: 95%; - margin-top: 25px; - margin-left: auto; - margin-right: 0px; - -moz-border-radius: 20px; - border-radius: 20px; - } -.tableleft{ - padding: 3px; - background-color: #ffffff; - float: left; - width: 46%; - height: 200px; - margin-top: 25px; - margin-left: 25px; - margin-right: auto; - -moz-border-radius: 20px; - border-radius: 20px; - } -.tableright{ - padding: 3px; - background-color: #ffffff; - width: 46%; - height: 200px; - margin-top: 25px; - margin-left: auto; - margin-right: 0px; - -moz-border-radius: 20px; - border-radius: 20px; - } -.nav{ - padding: 2px; - font-size:19px; - color: grey; - background-color: #bbbbbb; - width: 95%; - text-align: center; - margin-left: auto; - margin-right: 10px; - -moz-border-radius: 20px; - border-radius: 20px; - word-spacing: 18px; - } -.center{ - text-align: center; - font-size: 18px; - } -.smalltext{ - font-size: 11px; - } -.smalltext2{ - font-size: 11px; - margin-left: 45px; - } -.smalltext3{ - font-size: 11px; - margin-left: 30px; - color: #867970; - } -.mediumtext{ - font-size: 16px; - margin-left: 100px; - } -.mediumcentered{ - font-size: 18px; - text-align: center; - } -.bluecenter{ - color: #0000FF; - text-align: center; - font-size: 14px; - } -.logtext{ - font-size: 14px; - white-space: normal; - } -.bigtext{ - font-size: 22px; - } -.updatebar{ - text-align: center; - } -.version{ - font-size: 12px; - } -a:link { - color: #5E2612; - text-decoration: none; - } -a:visited { - color: #5E2612; - text-decoration: none; - } -a:hover { /*this effect is not shown in NN4.xx*/ - color: #999999; - text-decoration: underline; - } -a:active {/*colour in NN4.xx is red*/ - color: #5E2612; - text-decoration: underline; - } -a.gray { - color: #CFCFCF - } -a.external { - color: blue; - font-size:12px; - } -a.blue { - color: blue; - } -a.green { - color: #3ee343; - } -a.externalred { - color: red; - font-size:12px; - } -div.progress-container { - border: 1px solid #ccc; - width: 100px; - height: 12px; - margin: 2px 5px 2px 0; - padding: 1px; - float: left; - background: white; -} -a.center { - color: blue; - text-align: center; - } -div.progress-container > div { - background-color: #ACE97C; - height: 12px -} -.cloud{ - padding: 0px; - font-size:16px; - } -#cloud a.tag1 { font-size: 0.7em; font-weight: 100; } -#cloud a.tag2 { font-size: 0.8em; font-weight: 200; } -#cloud a.tag3 { font-size: 0.9em; font-weight: 300; } -#cloud a.tag4 { font-size: 1.0em; font-weight: 400; } -#cloud a.tag5 { font-size: 1.2em; font-weight: 500; } -#cloud a.tag6 { font-size: 1.4em; font-weight: 600; } -#cloud a.tag7 { font-size: 1.6em; font-weight: 700; } -#cloud a.tag8 { font-size: 1.8em; font-weight: 800; } -#cloud a.tag9 { font-size: 2.2em; font-weight: 900; } -#cloud a.tag10 { font-size: 2.5em; font-weight: 900; } +/* ==== Scroll down to find where to put your styles :) ==== */ -#cloud { padding: 2px; line-height: 1.5em; text-align: center; } -#cloud a { padding: 0px; } -#cloud { margin: 0; } -#cloud li { display: inline; } \ No newline at end of file +/* HTML5 ✰ Boilerplate */ + +html, body, div, span, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, +small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, figcaption, figure, +footer, header, hgroup, menu, nav, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} + +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} + +blockquote, q { quotes: none; } +blockquote:before, blockquote:after, +q:before, q:after { content: ''; content: none; } +ins { background-color: #ff9; color: #000; text-decoration: none; } +mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; } +del { text-decoration: line-through; } +abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; } +table { border-collapse: collapse; border-spacing: 0; } +hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; } +input, select { vertical-align: middle; } + +body { font:13px/1.231 sans-serif; *font-size:small; } +select, input, textarea, button { font:99% sans-serif; } +pre, code, kbd, samp { font-family: monospace, sans-serif; } + +html { overflow-y: scroll; } +a:hover, a:active { outline: none; } +ul, ol { margin-left: 2em; } +ol { list-style-type: decimal; } +nav ul, nav li { margin: 0; list-style:none; list-style-image: none; } +small { font-size: 85%; } +strong, th { font-weight: bold; } +td { vertical-align: top; } + +sub, sup { font-size: 75%; line-height: 0; position: relative; } +sup { top: -0.5em; } +sub { bottom: -0.25em; } + +pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; padding: 15px; } +textarea { overflow: auto; } +.ie6 legend, .ie7 legend { margin-left: -7px; } +input[type="radio"] { vertical-align: text-bottom; } +input[type="checkbox"] { vertical-align: bottom; } +.ie7 input[type="checkbox"] { vertical-align: baseline; } +.ie6 input { vertical-align: text-bottom; } +label, input[type="button"], input[type="submit"], input[type="image"], button { cursor: pointer; } +button, input, select, textarea { margin: 0; } +input:valid, textarea:valid { } +input:invalid, textarea:invalid { border-radius: 1px; -moz-box-shadow: 0px 0px 5px red; -webkit-box-shadow: 0px 0px 5px red; box-shadow: 0px 0px 5px red; } +.no-boxshadow input:invalid, .no-boxshadow textarea:invalid { background-color: #f0dddd; } + +::-moz-selection{ background: #FF5E99; color:#fff; text-shadow: none; } +::selection { background:#FF5E99; color:#fff; text-shadow: none; } +a:link { -webkit-tap-highlight-color: #FF5E99; } + +button { width: auto; overflow: visible; } +.ie7 img { -ms-interpolation-mode: bicubic; } + +body, select, input, textarea { color: #444; } +h1, h2, h3, h4, h5, h6 { font-weight: bold; } +a, a:active, a:visited { color: #607890; } +a:hover { color: #036; } + +/* + // ========================================== \\ + || || + || Your styles ! || + || || + \\ ========================================== // +*/ + +body { background-color: #EBF4FB } +div#logo { float: left; margin-left: 10px; } + +ul#nav { font-family: Arial, Verdana; font-size: 14px; padding: 0; list-style: none; float: left; } +ul#nav li { display: inline; margin-top: 25px; } +ul#nav li a { text-decoration: none; border-top: 1px solid #ffffff; padding: 5px 15px 5px 15px; margin-left: 1px; white-space: nowrap; } +ul#nav li a:hover { background: #22eeff } + +table#artist_table { margin-left: auto; margin-right: auto; } + +footer { margin-top: 40px; text-align: center; padding: 20px; } + + + + + + + + + + + + +.ir { display: block; text-indent: -999em; overflow: hidden; background-repeat: no-repeat; text-align: left; direction: ltr; } +.hidden { display: none; visibility: hidden; } +.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } +.visuallyhidden.focusable:active, +.visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; } +.invisible { visibility: hidden; } +.clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; } +.clearfix:after { clear: both; } +.clearfix { zoom: 1; } + + +@media all and (orientation:portrait) { + +} + +@media all and (orientation:landscape) { + +} + +@media screen and (max-device-width: 480px) { + + /* html { -webkit-text-size-adjust:none; -ms-text-size-adjust:none; } */ +} + + +@media print { + * { background: transparent !important; color: black !important; text-shadow: none !important; filter:none !important; + -ms-filter: none !important; } + a, a:visited { color: #444 !important; text-decoration: underline; } + a[href]:after { content: " (" attr(href) ")"; } + abbr[title]:after { content: " (" attr(title) ")"; } + .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } + pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } + thead { display: table-header-group; } + tr, img { page-break-inside: avoid; } + @page { margin: 0.5cm; } + p, h2, h3 { orphans: 3; widows: 3; } + h2, h3{ page-break-after: avoid; } +} \ No newline at end of file diff --git a/data/css/view.css b/data/css/view.css deleted file mode 100755 index 86c6efd4..00000000 --- a/data/css/view.css +++ /dev/null @@ -1,827 +0,0 @@ -#form_container -{ - background:#fff; - border:1px solid #ccc; - margin:0 auto; - text-align:left; - width:640px; -} - -#top -{ - display:block; - height:10px; - margin:10px auto 0; - width:650px; -} - -#footer -{ - width:640px; - clear:both; - color:#999999; - text-align:center; - width:640px; - padding-bottom: 15px; - font-size: 85%; -} - -#footer a{ - color:#999999; - text-decoration: none; - border-bottom: 1px dotted #999999; -} - -#bottom -{ - display:block; - height:10px; - margin:0 auto; - width:650px; -} - -form.appnitro -{ - margin:20px 20px 0; - padding:0 0 20px; -} - -/**** Form Section ****/ -.appnitro -{ - font-family:Lucida Grande, Tahoma, Arial, Verdana, sans-serif; - font-size:small; -} - -.appnitro li -{ - width:61%; -} - -form ul -{ - font-size:100%; - list-style-type:none; - margin:0; - padding:0; - width:100%; -} - -form li -{ - display:block; - margin:0; - padding:4px 5px 2px 9px; - position:relative; -} - -form li:after -{ - clear:both; - content:"."; - display:block; - height:0; - visibility:hidden; -} - -.buttons:after -{ - clear:both; - content:"."; - display:block; - height:0; - visibility:hidden; -} - -.buttons -{ - clear:both; - display:block; - margin-top:10px; -} - -* html form li -{ - height:1%; -} - -* html .buttons -{ - height:1%; -} - -* html form li div -{ - display:inline-block; -} - -form li div -{ - color:#444; - margin:0 4px 0 0; - padding:0 0 8px; -} - -form li span -{ - color:#444; - float:left; - margin:0 4px 0 0; - padding:0 0 8px; -} - -form li div.left -{ - display:inline; - float:left; - width:48%; -} - -form li div.right -{ - display:inline; - float:right; - width:48%; -} - -form li div.left .medium -{ - width:100%; -} - -form li div.right .medium -{ - width:100%; -} - -.clear -{ - clear:both; -} - -form li div label -{ - clear:both; - color:#444; - display:block; - font-size:9px; - line-height:9px; - margin:0; - padding-top:3px; -} - -form li span label -{ - clear:both; - color:#444; - display:block; - font-size:9px; - line-height:9px; - margin:0; - padding-top:3px; -} - -form li .datepicker -{ - cursor:pointer !important; - float:left; - height:16px; - margin:.1em 5px 0 0; - padding:0; - width:16px; -} - -.form_description -{ - border-bottom:1px dotted #ccc; - clear:both; - display:inline-block; - margin:0 0 1em; -} - -.form_description[class] -{ - display:block; -} - -.form_description h2 -{ - clear:left; - font-size:160%; - font-weight:400; - margin:0 0 3px; -} - -.form_description p -{ - font-size:95%; - line-height:130%; - margin:0 0 12px; -} - -form hr -{ - display:none; -} - -form li.section_break -{ - border-top:1px dotted #ccc; - margin-top:9px; - padding-bottom:0; - padding-left:9px; - padding-top:13px; - width:97% !important; -} - -form ul li.first -{ - border-top:none !important; - margin-top:0 !important; - padding-top:0 !important; -} - -form .section_break h3 -{ - font-size:110%; - font-weight:400; - line-height:130%; - margin:0 0 2px; -} - -form .section_break p -{ - font-size:85%; - - margin:0 0 10px; -} - -/**** Buttons ****/ -input.button_text -{ - overflow:visible; - padding:0 7px; - width:auto; -} - -.buttons input -{ - font-size:120%; - margin-right:5px; -} - -/**** Inputs and Labels ****/ -label.description -{ - border:none; - color:#222; - display:block; - font-size:95%; - font-weight:700; - line-height:150%; - padding:0 0 1px; -} - -span.symbol -{ - font-size:115%; - line-height:130%; -} - -input.text -{ - background:#fff url(../../../images/shadow.gif) repeat-x top; - border-bottom:1px solid #ddd; - border-left:1px solid #c3c3c3; - border-right:1px solid #c3c3c3; - border-top:1px solid #7c7c7c; - color:#333; - font-size:100%; - margin:0; - padding:2px 0; -} - -input.file -{ - color:#333; - font-size:100%; - margin:0; - padding:2px 0; -} - -textarea.textarea -{ - background:#fff url(../../../images/shadow.gif) repeat-x top; - border-bottom:1px solid #ddd; - border-left:1px solid #c3c3c3; - border-right:1px solid #c3c3c3; - border-top:1px solid #7c7c7c; - color:#333; - font-family:"Lucida Grande", Tahoma, Arial, Verdana, sans-serif; - font-size:100%; - margin:0; - width:99%; -} - -select.select -{ - color:#333; - font-size:100%; - margin:1px 0; - padding:1px 0 0; - background:#fff url(../../../images/shadow.gif) repeat-x top; - border-bottom:1px solid #ddd; - border-left:1px solid #c3c3c3; - border-right:1px solid #c3c3c3; - border-top:1px solid #7c7c7c; -} - - -input.currency -{ - text-align:right; -} - -input.checkbox -{ - display:block; - height:13px; - line-height:1.4em; - margin:6px 0 0 3px; - width:13px; -} - -input.radio -{ - display:block; - height:13px; - line-height:1.4em; - margin:6px 0 0 3px; - width:13px; -} - -label.choice -{ - color:#444; - display:block; - font-size:100%; - line-height:1.4em; - margin:-1.55em 0 0 25px; - padding:4px 0 5px; - width:90%; -} - -select.select[class] -{ - margin:0; - padding:1px 0; -} - -*:first-child+html select.select[class] -{ - margin:1px 0; -} - -.safari select.select -{ - font-size:120% !important; - margin-bottom:1px; -} - -input.small -{ - width:25%; -} - -select.small -{ - width:25%; -} - -input.medium -{ - width:50%; -} - -select.medium -{ - width:50%; -} - -input.large -{ - width:99%; -} - -select.large -{ - width:100%; -} - -textarea.small -{ - height:5.5em; -} - -textarea.medium -{ - height:10em; -} - -textarea.large -{ - height:20em; -} - -/**** Errors ****/ -#error_message -{ - background:#fff; - border:1px dotted red; - margin-bottom:1em; - padding-left:0; - padding-right:0; - padding-top:4px; - text-align:center; - width:99%; -} - -#error_message_title -{ - color:#DF0000; - font-size:125%; - margin:7px 0 5px; - padding:0; -} - -#error_message_desc -{ - color:#000; - font-size:100%; - margin:0 0 .8em; -} - -#error_message_desc strong -{ - background-color:#FFDFDF; - color:red; - padding:2px 3px; -} - -form li.error -{ - background-color:#FFDFDF !important; - border-bottom:1px solid #EACBCC; - border-right:1px solid #EACBCC; - margin:3px 0; -} - -form li.error label -{ - color:#DF0000 !important; -} - -form p.error -{ - clear:both; - color:red; - font-size:10px; - font-weight:700; - margin:0 0 5px; -} - -form .required -{ - color:red; - float:none; - font-weight:700; -} - -/**** Guidelines and Error Highlight ****/ -form li.highlighted -{ - background-color:#fff7c0; -} - -form .guidelines -{ - background:#f5f5f5; - border:1px solid #e6e6e6; - color:#444; - font-size:80%; - left:100%; - line-height:130%; - margin:0 0 0 8px; - padding:8px 10px 9px; - position:absolute; - top:0; - visibility:hidden; - width:42%; - z-index:1000; -} - -form .guidelines small -{ - font-size:105%; -} - -form li.highlighted .guidelines -{ - visibility:visible; -} - -form li:hover .guidelines -{ - visibility:visible; -} - -.no_guidelines .guidelines -{ - display:none !important; -} - -.no_guidelines form li -{ - width:97%; -} - -.no_guidelines li.section -{ - padding-left:9px; -} - -/*** Success Message ****/ -.form_success -{ - clear: both; - margin: 0; - padding: 90px 0pt 100px; - text-align: center -} - -.form_success h2 { - clear:left; - font-size:160%; - font-weight:normal; - margin:0pt 0pt 3px; -} - -/*** Password ****/ -ul.password{ - margin-top:60px; - margin-bottom: 60px; - text-align: center; -} -.password h2{ - color:#DF0000; - font-weight:bold; - margin:0pt auto 10px; -} - -.password input.text { - font-size:170% !important; - width:380px; - text-align: center; -} -.password label{ - display:block; - font-size:120% !important; - padding-top:10px; - font-weight:bold; -} - -#li_captcha{ - padding-left: 5px; -} - - -#li_captcha span{ - float:none; -} - -/** Embedded Form **/ - -.embed #form_container{ - border: none; -} - -.embed #top, .embed #bottom, .embed h1{ - display: none; -} - -.embed #form_container{ - width: 100%; -} - -.embed #footer{ - text-align: left; - padding-left: 10px; - width: 99%; -} - -.embed #footer.success{ - text-align: center; -} - -.embed form.appnitro -{ - margin:0px 0px 0; - -} - - - -/*** Calendar **********************/ -div.calendar { position: relative; } - -.calendar table { -cursor:pointer; -border:1px solid #ccc; -font-size: 11px; -color: #000; -background: #fff; -font-family:"Lucida Grande", Tahoma, Arial, Verdana, sans-serif; -} - -.calendar .button { -text-align: center; -padding: 2px; -} - -.calendar .nav { -background:#f5f5f5; -} - -.calendar thead .title { -font-weight: bold; -text-align: center; -background: #dedede; -color: #000; -padding: 2px 0 3px 0; -} - -.calendar thead .headrow { -background: #f5f5f5; -color: #444; -font-weight:bold; -} - -.calendar thead .daynames { -background: #fff; -color:#333; -font-weight:bold; -} - -.calendar thead .name { -border-bottom: 1px dotted #ccc; -padding: 2px; -text-align: center; -color: #000; -} - -.calendar thead .weekend { -color: #666; -} - -.calendar thead .hilite { -background-color: #444; -color: #fff; -padding: 1px; -} - -.calendar thead .active { -background-color: #d12f19; -color:#fff; -padding: 2px 0px 0px 2px; -} - - -.calendar tbody .day { -width:1.8em; -color: #222; -text-align: right; -padding: 2px 2px 2px 2px; -} -.calendar tbody .day.othermonth { -font-size: 80%; -color: #bbb; -} -.calendar tbody .day.othermonth.oweekend { -color: #fbb; -} - -.calendar table .wn { -padding: 2px 2px 2px 2px; -border-right: 1px solid #000; -background: #666; -} - -.calendar tbody .rowhilite td { -background: #FFF1AF; -} - -.calendar tbody .rowhilite td.wn { -background: #FFF1AF; -} - -.calendar tbody td.hilite { -padding: 1px 1px 1px 1px; -background:#444 !important; -color:#fff !important; -} - -.calendar tbody td.active { -color:#fff; -background: #529214 !important; -padding: 2px 2px 0px 2px; -} - -.calendar tbody td.selected { -font-weight: bold; -border: 1px solid #888; -padding: 1px 1px 1px 1px; -background: #f5f5f5 !important; -color: #222 !important; -} - -.calendar tbody td.weekend { -color: #666; -} - -.calendar tbody td.today { -font-weight: bold; -color: #529214; -background:#D9EFC2; -} - -.calendar tbody .disabled { color: #999; } - -.calendar tbody .emptycell { -visibility: hidden; -} - -.calendar tbody .emptyrow { -display: none; -} - -.calendar tfoot .footrow { -text-align: center; -background: #556; -color: #fff; -} - -.calendar tfoot .ttip { -background: #222; -color: #fff; -font-size:10px; -border-top: 1px solid #dedede; -padding: 3px; -} - -.calendar tfoot .hilite { -background: #aaf; -border: 1px solid #04f; -color: #000; -padding: 1px; -} - -.calendar tfoot .active { -background: #77c; -padding: 2px 0px 0px 2px; -} - -.calendar .combo { -position: absolute; -display: none; -top: 0px; -left: 0px; -width: 4em; -border: 1px solid #ccc; -background: #f5f5f5; -color: #222; -font-size: 90%; -z-index: 100; -} - -.calendar .combo .label, -.calendar .combo .label-IEfix { -text-align: center; -padding: 1px; -} - -.calendar .combo .label-IEfix { -width: 4em; -} - -.calendar .combo .hilite { -background: #444; -color:#fff; -} - -.calendar .combo .active { -border-top: 1px solid #999; -border-bottom: 1px solid #999; -background: #dedede; -font-weight: bold; -} - diff --git a/data/images/back_disabled.jpg b/data/images/back_disabled.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1e73a546e3609636f9cf4c543c2a4fe4050866c3 GIT binary patch literal 612 zcmex=C5UDGKfoZ!!63{a%*-grz$D1XEXer(2tyUn z+02YUSHb}sD?0}l&^EXLguy7lz`(-H!phDIlVN0HVHIR%V-ON%S2Pq6HFFG{Xryc+ zmRQ($@S#&sld*HEn>dFcBLmQ_$YwDLDjGT#PW*q1fd^^;oX+iLI)z|@;wX;U8Xr> zT8Qgxn;R!Cottl}#(8Ri${a3d=J*$R5qBRbK9bRJe*4BYwq9(uIM4mLS9%TSY?*y6 zE==owYH)vf>dq6(Uzum+IyVJqc3N&dRPD57vW(=l(=(DZ-K_!LSx zwPk@>bN@4B-OI9gCf>Wc_;CDAS%c@BJZ}kBlbDz~@y-*&Y^_U+@};q`xNUthN} On^Cj9W}Eu@|2F})$f79# literal 0 HcmV?d00001 diff --git a/data/images/back_enabled.jpg b/data/images/back_enabled.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a6d764c79c7a2047b6bf65bf9d96fcafd5161e38 GIT binary patch literal 807 zcmex=C5UDGKfoZ!!63{a%*-grz$D1XEXer(2*U&h z1}0{pBN2d|gO!bok%5r~RhW^Pm5qg+Lx6#Sm5Gs^od+h%$i&RTD#*spplB#0EW!~u z(MZ`u)XXulu<_tSr=TWdXR%Z_aiE1jZ7eM8Fl&IpCkWK1$Y$s$#2&a&N#sM~M5iWU z<^Q)Bc$gW1&SMs2uxGfw`}E06etb^r9k|jMc@Dlk(IlSD-Wwx*G{%4ThHhR%HnTsw zbu3z!=BtbC=-In5f5nt;je{+vrKZdWckk$U`c^FEYC6}Jn=lG)17QB>6z3khm7PHhg<-pEGSO0F>VA}N6`w53!8pr$>MbUW| zMEsFn=(%w+^5v0yp7Lmb+~3PpI4(#G~bQ$y_;t)oo+U5`bnSHZcn){CH~m7Ys2rz z2!R@xgGrCtcPz?eOPOzKr2P26p~(mTGjR1-c3?psA>uUGsyt|R2Ts3~!B zN71DGSrW%W*`w8#2{O$LX)%%8*<4Ycyr;tN(Hm8|M=_HtvR>P P+Rcr(b5Xx$|NkZcT0jT? literal 0 HcmV?d00001 diff --git a/data/images/forward_disabled.jpg b/data/images/forward_disabled.jpg new file mode 100644 index 0000000000000000000000000000000000000000..28a9dc53fa232919299ac980dc9b61167c1868bd GIT binary patch literal 635 zcmex=C5UDGKfoZ!!63{a%*-grz$D1XEXer(2tySE z1JEp>E8&2Nm4%I+g9~UMTnNHo6kuRrVP;|B;)Y2uGBL9Vva&GlzGLeh=CM*_u2I6%$1L~6;-1yGa|?*FvavCW+Av>{Flt>+*?d?iZ`9)^O?_H|D3t8;Og6lD<#WHQvW(mo1ETsD>A@mr8@sqg(?1R z%d@qrmWLWmIn1#pQuo}{brVW*C8t%EDXbOt_DabwXgGW3;wDX1;~7hWrYL+ZX=qre z&-y^2q0zm?-FJb#(AWJTHOJ-3`S`D|JEhzC{KvY_h0kZinXP-?w!g_L@p#JQO^!z$ mYCgVwS7#SryXs@?>&L&t^Z(SozHVt~@Z#>58}s)6zX<@qORf(9 literal 0 HcmV?d00001 diff --git a/data/images/forward_enabled.jpg b/data/images/forward_enabled.jpg new file mode 100644 index 0000000000000000000000000000000000000000..598c075f13ab0e4bbe05a4eeea7cf777ba73cf03 GIT binary patch literal 852 zcmex=C5UDGKfoZ!!63{a%*-grz$D1XEXer(2tywO z0~0eN(3Nn&%Fe>Z!NthH$ciG&$iU3P%Em6hz`z1zu=B#yGcqx=2(q#<2(fb*3M-3< zDh5t8G7&R#Oe}1C=oHjs?40T*F37+LbR`oj+(bq}CT0c}Rz*W0Hg?Cv#)(EB0yioN zi~PUEzyov)lOVGogFVA5dn3uE%Z<*rn&ugY^FIG9<<=87L*?mOy^~u`Rq(Eg+UDw^ z@^Uh}zJ$t~P2YpRT#sm2p`oEt8zJ-EBRfY;>b?DxZo!24Z)Q(O&}!Mbt}27`dKa(x zr)AHkZg5MjKa&4$syaupXWNXgp32dQs_rj-?|M=Q9pN^hZg^mV7=UWs>}D5 z$40(ME7Q1T4h8p}ZJ8O!G-Xk84;P~#ci!&o`A5I3kG@gLm*VlJbn%kvSqIh?3Y?HQ zAX0NL>XIkpf=MN83*!%}9-aN5)p7^py2Am>F9gW&pFSp#V0dxX#ZPW;UoD(gaI@8E z(rNQz1DBIiM13!LtZ|85lHB@$wW~n0fhRz-$?R&m{@z;uI}N|35As;}zdXB<I#+N1k=r*G-&mI78vJ_Ezs!yIV%J}l7d*c9>7Bn;xyLT84V&`$ z@9WMTAJ6DZyR)~=FS6P2e3MDv7lHB>{C&1H^7EJ9PIf=EdUwUYtGE9%$X9(m*ZWLf M_rC2^{;K~s0TRp{r2qf` literal 0 HcmV?d00001 diff --git a/data/images/sort_asc.png b/data/images/sort_asc.png new file mode 100644 index 0000000000000000000000000000000000000000..a56d0e21902ce620cd57f87fa58a6d3e804135cf GIT binary patch literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S3?yCqj{O5tEa{HEjtmUzPnffIy#(?lOI#yL zg7ec#$`gxH85~pclTsBta}(23gHjVyDhp4h+5i>l2Ka=y{`vF&J9*~+_wWBdfBFCW_y37A{>@tOZ^^2EJ(K_a|Nno&o$%d2jr=7+e!)O45@5J; zXU0OHl&hzUV@SoVE9Xw~GAQscA1s)^?0*rXqM}DeLv7?|BU8z@QCOGupVzP9Hek5&L+Gnl%BD>~0~tJB{an^LB{Ts5b0m7= literal 0 HcmV?d00001 diff --git a/data/images/sort_asc_disabled.png b/data/images/sort_asc_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..b7e621ef1c68da839ce077cec6e201a55c1ba168 GIT binary patch literal 252 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S3?yCqj{O5tEa{HEjtmUzPnffIy#(?lOI#yL zg7ec#$`gxH85~pclTsBta}(23gHjVyDhp4h+5i=)2l#}z{`>du$&)AV-@pIyd9;k)4B*-rq$b|z2+ZRf0KnW*L z7srr_TUXBQ7GeV6G?@}f$7k6Jhw{qGm$-QDzD`qrDaZ3s$b2XF;fBBPU nqjNDy&Nw~puHFA+-v&m8`?>-yUFVHK{$ucT^>bP0l+XkKhw^g= literal 0 HcmV?d00001 diff --git a/data/images/sort_both.png b/data/images/sort_both.png new file mode 100644 index 0000000000000000000000000000000000000000..839ac4bb5b0b414834c950de9deafff6dd94ed2d GIT binary patch literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S3?yCqj{O5tEa{HEjtmUzPnffIy#(?lOI#yL zg7ec#$`gxH85~pclTsBta}(23gHjVyDhp4h+5i=)2l#}z{`>du$&)AV-@pIyd9;k)4B*-rq$b|z2+ZRf0K#2rT z7srr_TRjsz`4|*ASOSl%eg6Ob+(JtRwX|O@S}a^IESQCgTe~DWM4fR9b+X literal 0 HcmV?d00001 diff --git a/data/images/sort_desc.png b/data/images/sort_desc.png new file mode 100644 index 0000000000000000000000000000000000000000..90b295159df995329b3a84340d32761f3d1dbade GIT binary patch literal 260 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S3?yCqj{O5tEa{HEjtmUzPnffIy#(?lOI#yL zg7ec#$`gxH85~pclTsBta}(23gHjVyDhp4h+5i>l2Ka=y{`vF&J9*~+_wWBdfBFCW_y37A{>@tOZ^^2EJ(K_a|Nno&o$%d2jr=7+e!)O45@5J; zXU0OHl#{26V@SoVqz8vtc$~v}dc~O{CLEF2anNavMpdMP)0v(X&o(k0opIq!LdvtJ wj~up7@0`wiSoe($&y07EkGxK6U|?nlJSz0{?wJWUfu=Edy85}Sb4q9e0C9MGu>b%7 literal 0 HcmV?d00001 diff --git a/data/images/sort_desc_disabled.png b/data/images/sort_desc_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..2409653dc94cd21a281a31c0e3819323b84704b7 GIT binary patch literal 251 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S3?yCqj{O5tEa{HEjtmUzPnffIy#(?lOI#yL zg7ec#$`gxH85~pclTsBta}(23gHjVyDhp4h+5i=)2l#}z{`>du$&)AV-@pIyd9;k)4B*-rq$b|z2+ZRf0KnX`r z7srr_TS*TNvhX;E^>j)zMNBv%VR68zN24n8K+>5N&kUJbBQCy4c=qgBiu3mAQ)hD? n)_oJhGvi(If!Apb49pB3GXz|UGFKJ=4P)?h^>bP0l+XkK*57ji literal 0 HcmV?d00001 diff --git a/data/interfaces/default/_inc_footer.tmpl b/data/interfaces/default/_inc_footer.tmpl deleted file mode 100644 index 9bf2eafe..00000000 --- a/data/interfaces/default/_inc_footer.tmpl +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/data/interfaces/default/_inc_header.tmpl b/data/interfaces/default/_inc_header.tmpl deleted file mode 100644 index c28e76cd..00000000 --- a/data/interfaces/default/_inc_header.tmpl +++ /dev/null @@ -1,30 +0,0 @@ -#attr title = "" -#attr rootPath = "" -#attr scripts = "" -#attr styles = "" - - - Headphones $title - - - - - - -
-
-
\ No newline at end of file diff --git a/data/interfaces/default/albumPage.tmpl b/data/interfaces/default/albumPage.tmpl deleted file mode 100644 index 66b9f4d1..00000000 --- a/data/interfaces/default/albumPage.tmpl +++ /dev/null @@ -1,45 +0,0 @@ -#attr $albumASIN = "" -#attr $albumTitle = "" -#attr $albumID = "" -#attr $artitstID = "" -#attr $artistName = "" -#import os.path -#import time -#include os.path.abspath($appPath+"/data/interfaces/default/_inc_header.tmpl") - - - - - - - - - - - - #for $track in $tracks - - - - - - #end for -
- #if $albumASIN: - -
- #end if -
- ${artistName} -${albumTitle}
- Download
-
Track #Track TitleDuration
${track[7]} - ${track[3]}(link) - - #try - #echo time.strftime("%M:%S", time.gmtime(int($track[4])/1000)) - #except - n/a - #end try -
- -#include os.path.abspath($appPath+"/data/interfaces/default/_inc_footer.tmpl") \ No newline at end of file diff --git a/data/interfaces/default/artistInfo.tmpl b/data/interfaces/default/artistInfo.tmpl deleted file mode 100644 index ae8480ae..00000000 --- a/data/interfaces/default/artistInfo.tmpl +++ /dev/null @@ -1,13 +0,0 @@ -#attr $artistName = "" -#attr $artistUuid = "" -#attr $releaseGroups = [] -#import os.path -#include os.path.abspath($appPath+"/data/interfaces/default/_inc_header.tmpl") - Artist Name: $artistName
- Unique ID: $artistUuid
-
- Albums:
- #for $releaseGroup in $releaseGroups: - $releaseGroup
- #end for -#include os.path.abspath($appPath+"/data/interfaces/default/_inc_footer.tmpl") \ No newline at end of file diff --git a/data/interfaces/default/artistPage.tmpl b/data/interfaces/default/artistPage.tmpl deleted file mode 100644 index 76af1721..00000000 --- a/data/interfaces/default/artistPage.tmpl +++ /dev/null @@ -1,39 +0,0 @@ -#attr $artistName = "" -#attr $artistID = "" -#import os.path -#include os.path.abspath($appPath+"/data/interfaces/default/_inc_header.tmpl") - - - - - - - - - - - #for $album in $albums - - - - - - - #end for -
-

$artistName

-
Album NameRelease DateStatus
- - $album[0] - (link)$album[1] - #if $album[3] == 'Skipped': - $album[3][want] - #elif $album[3] == 'Wanted': - $album[3][skip] - #elif $album[3] == 'Downloaded' or $album[3] == 'Snatched': - $album[3][retry] - #else: - $album[3] - #end if -
-#include os.path.abspath($appPath+"/data/interfaces/default/_inc_footer.tmpl") \ No newline at end of file diff --git a/data/interfaces/default/base.html b/data/interfaces/default/base.html new file mode 100644 index 00000000..a82ee1f6 --- /dev/null +++ b/data/interfaces/default/base.html @@ -0,0 +1,76 @@ +<% + import headphones +%> + + + + + + + + + + + Headphones - ${title} + + + + + + + + + ${next.headerIncludes()} + + + + +
+
+ + + +
+ +
+ ${next.body()} +
+ +
+
Version: ${headphones.CURRENT_VERSION}
+
+ + + + +
+
+ +
+ + + + ${next.javascriptIncludes()} + + + + + + + +<%def name="javascriptIncludes()"> +<%def name="headerIncludes()"> \ No newline at end of file diff --git a/data/interfaces/default/config.tmpl b/data/interfaces/default/config.tmpl deleted file mode 100644 index 0697a50d..00000000 --- a/data/interfaces/default/config.tmpl +++ /dev/null @@ -1,238 +0,0 @@ -#import os.path -#import config -#include os.path.abspath($appPath+"/data/interfaces/default/_inc_header.tmpl") -
- -
-
-
-

Web Interface

- - - - - - - - - - - - - - - - -
-

- HTTP Host:

-
- i.e. localhost or 0.0.0.0 -

-
-

- HTTP Username:

- -

-
-

- HTTP Port:

- -

-
-

- HTTP Password:

- -

-
-

Launch Browser on Startup:

-
- -

Download Settings

- - - - - - - - - - - - - - - - - - - - - - - -
-

SABnzbd Host:


- - usually localhost:8080 -
-

SABnzbd Username:

-
-
- -

SABnzbd API:

-
-
- -

SABnzbd Password:

-
-
- -

SABnzbd Category:

-
-
- -

Music Download Directory:


- - Absolute or relative path to the dir where SAB downloads your music
- i.e. Downloads/music or /Users/name/Downloads/music
-
-
- -

Usenet Retention:

-
- -

Search Providers

- - - - - - - - - - - - - - - - - - - - - - - - - -
-

NZBMatrix:

-
-

- NZBMatrix Username:
- -

-
-

- NZBMatrix API:
- -

-
-
- -

Newznab:

-
-
- -

- Newznab Host:
-
- i.e. http://nzb.su -

-
-
- -

- Newznab API:
- -

-
-
- -

NZBs.org:

-
-
- -

- NZBs.org UID:
- -

-
-
- -

- NZBs.org Hash:
- -

-
- -

Quality & Post Processing

- - - - - - - - - - - - - - - - -
-

Album Quality:

- Include lossless
- Convert lossless to mp3 -
-

-

iTunes:

- Move downloads to iTunes -

-
-
- -

Path to iTunes folder:
-
- i.e. Music/iTunes or /Users/name/Music/iTunes -

-
- Renaming & Metadata: -

- Rename & add metadata -
- Delete leftover files -

-
-
-

Album Art:

- Add album art -
- -


- (For now, all changes require a restart to take effect)

-
-
-#include os.path.abspath($appPath+"/data/interfaces/default/_inc_footer.tmpl") \ No newline at end of file diff --git a/data/interfaces/default/history.tmpl b/data/interfaces/default/history.tmpl deleted file mode 100644 index 0c03e2bf..00000000 --- a/data/interfaces/default/history.tmpl +++ /dev/null @@ -1,3 +0,0 @@ -#import os.path -#include os.path.abspath($appPath+"/data/interfaces/default/_inc_header.tmpl") -#include os.path.abspath($appPath+"/data/interfaces/default/_inc_footer.tmpl") \ No newline at end of file diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html new file mode 100644 index 00000000..e09afd23 --- /dev/null +++ b/data/interfaces/default/index.html @@ -0,0 +1,32 @@ +<%inherit file="base.html"/> + +<%def name="body()"> + + + + + + + + + + + %for artist in artists: + + + + + + + %endfor + +
Artist NameStatusLatest AlbumHave
${artist['ArtistName']}${artist['Status']}${artist['LatestAlbum']}${artist['HaveTracks']}/${artist['TotalTracks']}
+ + +<%def name="headerIncludes()"> + + + +<%def name="javascriptIncludes()"> + + \ No newline at end of file diff --git a/data/interfaces/default/index.tmpl b/data/interfaces/default/index.tmpl deleted file mode 100644 index 570c355a..00000000 --- a/data/interfaces/default/index.tmpl +++ /dev/null @@ -1,33 +0,0 @@ -#import os.path -#include os.path.abspath($appPath+"/data/interfaces/default/_inc_header.tmpl") - - - - - - - #for $artist in $artists - - - - - - #end for -
Artist NameStatusUpcoming Albums
- $artist[0] - (link) - [delete] - - #if $artist[2] == 'Active': - $artist[2] (pause) - #else: - $artist[2](resume) - #end if - - #try - $artist[3] $artist[4] - #except - None - #end try -
-#include os.path.abspath($appPath+"/data/interfaces/default/_inc_footer.tmpl") \ No newline at end of file diff --git a/data/interfaces/default/manage.tmpl b/data/interfaces/default/manage.tmpl deleted file mode 100644 index 92da4055..00000000 --- a/data/interfaces/default/manage.tmpl +++ /dev/null @@ -1,28 +0,0 @@ -#attr $path = "" -#import os.path -#include os.path.abspath($appPath+"/data/interfaces/default/_inc_header.tmpl") - -
-

Import or Sync Your iTunes Library/Music Folder

-

Enter the full path to your iTunes XML file or music folder - i.e. /Users/"username"/Music/iTunes/iTunes Music Library.xml - or /Users/"username"/Music/iTunes/iTunes Media/Music - (artists should have their own directories for folder import to work) - note: This process can take a LONG time! - Once you click "Submit" you can navigate away from this - page while the process runs.

-
- - -
-
- - -#include os.path.abspath($appPath+"/data/interfaces/default/_inc_footer.tmpl") \ No newline at end of file diff --git a/data/interfaces/default/search.tmpl b/data/interfaces/default/search.tmpl deleted file mode 100644 index 4ac94ebd..00000000 --- a/data/interfaces/default/search.tmpl +++ /dev/null @@ -1,31 +0,0 @@ -#import os.path -#include os.path.abspath($appPath+"/data/interfaces/default/_inc_header.tmpl") - -
- Results: -
    -
      -
-

-#include os.path.abspath($appPath+"/data/interfaces/default/_inc_footer.tmpl") \ No newline at end of file diff --git a/data/interfaces/default/upcoming.tmpl b/data/interfaces/default/upcoming.tmpl deleted file mode 100644 index 0c03e2bf..00000000 --- a/data/interfaces/default/upcoming.tmpl +++ /dev/null @@ -1,3 +0,0 @@ -#import os.path -#include os.path.abspath($appPath+"/data/interfaces/default/_inc_header.tmpl") -#include os.path.abspath($appPath+"/data/interfaces/default/_inc_footer.tmpl") \ No newline at end of file diff --git a/data/js/libs/dd_belatedpng.js b/data/js/libs/dd_belatedpng.js new file mode 100755 index 00000000..6062fb3c --- /dev/null +++ b/data/js/libs/dd_belatedpng.js @@ -0,0 +1,13 @@ +/** +* DD_belatedPNG: Adds IE6 support: PNG images for CSS background-image and HTML . +* Author: Drew Diller +* Email: drew.diller@gmail.com +* URL: http://www.dillerdesign.com/experiment/DD_belatedPNG/ +* Version: 0.0.8a +* Licensed under the MIT License: http://dillerdesign.com/experiment/DD_belatedPNG/#license +* +* Example usage: +* DD_belatedPNG.fix('.png_bg'); // argument is a CSS selector +* DD_belatedPNG.fixPng( someNode ); // argument is an HTMLDomElement +**/ +var DD_belatedPNG={ns:"DD_belatedPNG",imgSize:{},delay:10,nodesFixed:0,createVmlNameSpace:function(){if(document.namespaces&&!document.namespaces[this.ns]){document.namespaces.add(this.ns,"urn:schemas-microsoft-com:vml")}},createVmlStyleSheet:function(){var b,a;b=document.createElement("style");b.setAttribute("media","screen");document.documentElement.firstChild.insertBefore(b,document.documentElement.firstChild.firstChild);if(b.styleSheet){b=b.styleSheet;b.addRule(this.ns+"\\:*","{behavior:url(#default#VML)}");b.addRule(this.ns+"\\:shape","position:absolute;");b.addRule("img."+this.ns+"_sizeFinder","behavior:none; border:none; position:absolute; z-index:-1; top:-10000px; visibility:hidden;");this.screenStyleSheet=b;a=document.createElement("style");a.setAttribute("media","print");document.documentElement.firstChild.insertBefore(a,document.documentElement.firstChild.firstChild);a=a.styleSheet;a.addRule(this.ns+"\\:*","{display: none !important;}");a.addRule("img."+this.ns+"_sizeFinder","{display: none !important;}")}},readPropertyChange:function(){var b,c,a;b=event.srcElement;if(!b.vmlInitiated){return}if(event.propertyName.search("background")!=-1||event.propertyName.search("border")!=-1){DD_belatedPNG.applyVML(b)}if(event.propertyName=="style.display"){c=(b.currentStyle.display=="none")?"none":"block";for(a in b.vml){if(b.vml.hasOwnProperty(a)){b.vml[a].shape.style.display=c}}}if(event.propertyName.search("filter")!=-1){DD_belatedPNG.vmlOpacity(b)}},vmlOpacity:function(b){if(b.currentStyle.filter.search("lpha")!=-1){var a=b.currentStyle.filter;a=parseInt(a.substring(a.lastIndexOf("=")+1,a.lastIndexOf(")")),10)/100;b.vml.color.shape.style.filter=b.currentStyle.filter;b.vml.image.fill.opacity=a}},handlePseudoHover:function(a){setTimeout(function(){DD_belatedPNG.applyVML(a)},1)},fix:function(a){if(this.screenStyleSheet){var c,b;c=a.split(",");for(b=0;bn.H){i.B=n.H}d.vml.image.shape.style.clip="rect("+i.T+"px "+(i.R+a)+"px "+i.B+"px "+(i.L+a)+"px)"}else{d.vml.image.shape.style.clip="rect("+f.T+"px "+f.R+"px "+f.B+"px "+f.L+"px)"}},figurePercentage:function(d,c,f,a){var b,e;e=true;b=(f=="X");switch(a){case"left":case"top":d[f]=0;break;case"center":d[f]=0.5;break;case"right":case"bottom":d[f]=1;break;default:if(a.search("%")!=-1){d[f]=parseInt(a,10)/100}else{e=false}}d[f]=Math.ceil(e?((c[b?"W":"H"]*d[f])-(c[b?"w":"h"]*d[f])):parseInt(a,10));if(d[f]%2===0){d[f]++}return d[f]},fixPng:function(c){c.style.behavior="none";var g,b,f,a,d;if(c.nodeName=="BODY"||c.nodeName=="TD"||c.nodeName=="TR"){return}c.isImg=false;if(c.nodeName=="IMG"){if(c.src.toLowerCase().search(/\.png$/)!=-1){c.isImg=true;c.style.visibility="hidden"}else{return}}else{if(c.currentStyle.backgroundImage.toLowerCase().search(".png")==-1){return}}g=DD_belatedPNG;c.vml={color:{},image:{}};b={shape:{},fill:{}};for(a in c.vml){if(c.vml.hasOwnProperty(a)){for(d in b){if(b.hasOwnProperty(d)){f=g.ns+":"+d;c.vml[a][d]=document.createElement(f)}}c.vml[a].shape.stroked=false;c.vml[a].shape.appendChild(c.vml[a].fill);c.parentNode.insertBefore(c.vml[a].shape,c)}}c.vml.image.shape.fillcolor="none";c.vml.image.fill.type="tile";c.vml.color.fill.on=false;g.attachHandlers(c);g.giveLayout(c);g.giveLayout(c.offsetParent);c.vmlInitiated=true;g.applyVML(c)}};try{document.execCommand("BackgroundImageCache",false,true)}catch(r){}DD_belatedPNG.createVmlNameSpace();DD_belatedPNG.createVmlStyleSheet(); \ No newline at end of file diff --git a/data/js/libs/jquery-1.6.2.js b/data/js/libs/jquery-1.6.2.js new file mode 100644 index 00000000..f3201aac --- /dev/null +++ b/data/js/libs/jquery-1.6.2.js @@ -0,0 +1,8981 @@ +/*! + * jQuery JavaScript Library v1.6.2 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Thu Jun 30 14:16:56 2011 -0400 + */ +(function( window, undefined ) { + +// Use the correct document accordingly with window argument (sandbox) +var document = window.document, + navigator = window.navigator, + location = window.location; +var jQuery = (function() { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // (both of which we optimize for) + quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + + // Used for trimming whitespace + trimLeft = /^\s+/, + trimRight = /\s+$/, + + // Check for digits + rdigit = /\d/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, + rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + + // Useragent RegExp + rwebkit = /(webkit)[ \/]([\w.]+)/, + ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, + rmsie = /(msie) ([\w.]+)/, + rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, + + // Matches dashed string for camelizing + rdashAlpha = /-([a-z])/ig, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // The deferred used on DOM ready + readyList, + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + trim = String.prototype.trim, + indexOf = Array.prototype.indexOf, + + // [[Class]] -> type pairs + class2type = {}; + +jQuery.fn = jQuery.prototype = { + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context && document.body ) { + this.context = document; + this[0] = document.body; + this.selector = selector; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = quickExpr.exec( selector ); + } + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + doc = (context ? context.ownerDocument || context : document); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); + selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes; + } + + return jQuery.merge( this, selector ); + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return (context || rootjQuery).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if (selector.selector !== undefined) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.6.2", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = this.constructor(); + + if ( jQuery.isArray( elems ) ) { + push.apply( ret, elems ); + + } else { + jQuery.merge( ret, elems ); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + (this.selector ? " " : "") + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); + + // Add the callback + readyList.done( fn ); + + return this; + }, + + eq: function( i ) { + return i === -1 ? + this.slice( i ) : + this.slice( i, +i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + // Either a released hold or an DOMready/load event and not yet ready + if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 1 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).unbind( "ready" ); + } + } + }, + + bindReady: function() { + if ( readyList ) { + return; + } + + readyList = jQuery._Deferred(); + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + return setTimeout( jQuery.ready, 1 ); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", DOMContentLoaded ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + // A crude way of determining if an object is a window + isWindow: function( obj ) { + return obj && typeof obj === "object" && "setInterval" in obj; + }, + + isNaN: function( obj ) { + return obj == null || !rdigit.test( obj ) || isNaN( obj ); + }, + + type: function( obj ) { + return obj == null ? + String( obj ) : + class2type[ toString.call(obj) ] || "object"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw msg; + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { + + return (new Function( "return " + data ))(); + + } + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + // (xml & tmp used internally) + parseXML: function( data , xml , tmp ) { + + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + + tmp = xml.documentElement; + + if ( ! tmp || ! tmp.nodeName || tmp.nodeName === "parsererror" ) { + jQuery.error( "Invalid XML: " + data ); + } + + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && rnotwhite.test( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Converts a dashed string to camelCased string; + // Used by both the css and data modules + camelCase: function( string ) { + return string.replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction( object ); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { + break; + } + } + } + } + + return object; + }, + + // Use native String.trim function wherever possible + trim: trim ? + function( text ) { + return text == null ? + "" : + trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // The extra typeof function check is to prevent crashes + // in Safari 2 (See: #3039) + // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 + var type = jQuery.type( array ); + + if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( elem, array ) { + + if ( indexOf ) { + return indexOf.call( array, elem ); + } + + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; + } + } + + return -1; + }, + + merge: function( first, second ) { + var i = first.length, + j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var ret = [], retVal; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, key, ret = [], + i = 0, + length = elems.length, + // jquery objects are treated as arrays + isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; + + // Go through the array, translating each of the items to their + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( key in elems ) { + value = callback( elems[ key ], key, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + if ( typeof context === "string" ) { + var tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + var args = slice.call( arguments, 2 ), + proxy = function() { + return fn.apply( context, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + + return proxy; + }, + + // Mutifunctional method to get and set values to a collection + // The value/s can optionally be executed if it's a function + access: function( elems, key, value, exec, fn, pass ) { + var length = elems.length; + + // Setting many attributes + if ( typeof key === "object" ) { + for ( var k in key ) { + jQuery.access( elems, k, key[k], exec, fn, value ); + } + return elems; + } + + // Setting one attribute + if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = !pass && exec && jQuery.isFunction(value); + + for ( var i = 0; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + + return elems; + } + + // Getting an attribute + return length ? fn( elems[0], key ) : undefined; + }, + + now: function() { + return (new Date()).getTime(); + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = rwebkit.exec( ua ) || + ropera.exec( ua ) || + rmsie.exec( ua ) || + ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + sub: function() { + function jQuerySub( selector, context ) { + return new jQuerySub.fn.init( selector, context ); + } + jQuery.extend( true, jQuerySub, this ); + jQuerySub.superclass = this; + jQuerySub.fn = jQuerySub.prototype = this(); + jQuerySub.fn.constructor = jQuerySub; + jQuerySub.sub = this.sub; + jQuerySub.fn.init = function init( selector, context ) { + if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { + context = jQuerySub( context ); + } + + return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); + }; + jQuerySub.fn.init.prototype = jQuerySub.fn; + var rootjQuerySub = jQuerySub(document); + return jQuerySub; + }, + + browser: {} +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +// IE doesn't match non-breaking spaces with \s +if ( rnotwhite.test( "\xA0" ) ) { + trimLeft = /^[\s\xA0]+/; + trimRight = /[\s\xA0]+$/; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch(e) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +return jQuery; + +})(); + + +var // Promise methods + promiseMethods = "done fail isResolved isRejected promise then always pipe".split( " " ), + // Static reference to slice + sliceDeferred = [].slice; + +jQuery.extend({ + // Create a simple deferred (one callbacks list) + _Deferred: function() { + var // callbacks list + callbacks = [], + // stored [ context , args ] + fired, + // to avoid firing when already doing so + firing, + // flag to know if the deferred has been cancelled + cancelled, + // the deferred itself + deferred = { + + // done( f1, f2, ...) + done: function() { + if ( !cancelled ) { + var args = arguments, + i, + length, + elem, + type, + _fired; + if ( fired ) { + _fired = fired; + fired = 0; + } + for ( i = 0, length = args.length; i < length; i++ ) { + elem = args[ i ]; + type = jQuery.type( elem ); + if ( type === "array" ) { + deferred.done.apply( deferred, elem ); + } else if ( type === "function" ) { + callbacks.push( elem ); + } + } + if ( _fired ) { + deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] ); + } + } + return this; + }, + + // resolve with given context and args + resolveWith: function( context, args ) { + if ( !cancelled && !fired && !firing ) { + // make sure args are available (#8421) + args = args || []; + firing = 1; + try { + while( callbacks[ 0 ] ) { + callbacks.shift().apply( context, args ); + } + } + finally { + fired = [ context, args ]; + firing = 0; + } + } + return this; + }, + + // resolve with this as context and given arguments + resolve: function() { + deferred.resolveWith( this, arguments ); + return this; + }, + + // Has this deferred been resolved? + isResolved: function() { + return !!( firing || fired ); + }, + + // Cancel + cancel: function() { + cancelled = 1; + callbacks = []; + return this; + } + }; + + return deferred; + }, + + // Full fledged deferred (two callbacks list) + Deferred: function( func ) { + var deferred = jQuery._Deferred(), + failDeferred = jQuery._Deferred(), + promise; + // Add errorDeferred methods, then and promise + jQuery.extend( deferred, { + then: function( doneCallbacks, failCallbacks ) { + deferred.done( doneCallbacks ).fail( failCallbacks ); + return this; + }, + always: function() { + return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments ); + }, + fail: failDeferred.done, + rejectWith: failDeferred.resolveWith, + reject: failDeferred.resolve, + isRejected: failDeferred.isResolved, + pipe: function( fnDone, fnFail ) { + return jQuery.Deferred(function( newDefer ) { + jQuery.each( { + done: [ fnDone, "resolve" ], + fail: [ fnFail, "reject" ] + }, function( handler, data ) { + var fn = data[ 0 ], + action = data[ 1 ], + returned; + if ( jQuery.isFunction( fn ) ) { + deferred[ handler ](function() { + returned = fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise().then( newDefer.resolve, newDefer.reject ); + } else { + newDefer[ action ]( returned ); + } + }); + } else { + deferred[ handler ]( newDefer[ action ] ); + } + }); + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + if ( obj == null ) { + if ( promise ) { + return promise; + } + promise = obj = {}; + } + var i = promiseMethods.length; + while( i-- ) { + obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ]; + } + return obj; + } + }); + // Make sure only one callback list will be used + deferred.done( failDeferred.cancel ).fail( deferred.cancel ); + // Unexpose cancel + delete deferred.cancel; + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + return deferred; + }, + + // Deferred helper + when: function( firstParam ) { + var args = arguments, + i = 0, + length = args.length, + count = length, + deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? + firstParam : + jQuery.Deferred(); + function resolveFunc( i ) { + return function( value ) { + args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + if ( !( --count ) ) { + // Strange bug in FF4: + // Values changed onto the arguments object sometimes end up as undefined values + // outside the $.when method. Cloning the object into a fresh array solves the issue + deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) ); + } + }; + } + if ( length > 1 ) { + for( ; i < length; i++ ) { + if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) { + args[ i ].promise().then( resolveFunc(i), deferred.reject ); + } else { + --count; + } + } + if ( !count ) { + deferred.resolveWith( deferred, args ); + } + } else if ( deferred !== firstParam ) { + deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); + } + return deferred.promise(); + } +}); + + + +jQuery.support = (function() { + + var div = document.createElement( "div" ), + documentElement = document.documentElement, + all, + a, + select, + opt, + input, + marginDiv, + support, + fragment, + body, + testElementParent, + testElement, + testElementStyle, + tds, + events, + eventName, + i, + isSupported; + + // Preliminary tests + div.setAttribute("className", "t"); + div.innerHTML = "
a"; + + all = div.getElementsByTagName( "*" ); + a = div.getElementsByTagName( "a" )[ 0 ]; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return {}; + } + + // First batch of supports tests + select = document.createElement( "select" ); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName( "input" )[ 0 ]; + + support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: ( div.firstChild.nodeType === 3 ), + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName( "tbody" ).length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName( "link" ).length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style: /top/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: ( a.getAttribute( "href" ) === "/a" ), + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55$/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: ( input.value === "on" ), + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + + // Will be defined later + submitBubbles: true, + changeBubbles: true, + focusinBubbles: false, + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableMarginRight: true + }; + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { + div.attachEvent( "onclick", function() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + support.noCloneEvent = false; + }); + div.cloneNode( true ).fireEvent( "onclick" ); + } + + // Check if a radio maintains it's value + // after being appended to the DOM + input = document.createElement("input"); + input.value = "t"; + input.setAttribute("type", "radio"); + support.radioValue = input.value === "t"; + + input.setAttribute("checked", "checked"); + div.appendChild( input ); + fragment = document.createDocumentFragment(); + fragment.appendChild( div.firstChild ); + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + div.innerHTML = ""; + + // Figure out if the W3C box model works as expected + div.style.width = div.style.paddingLeft = "1px"; + + body = document.getElementsByTagName( "body" )[ 0 ]; + // We use our own, invisible, body unless the body is already present + // in which case we use a div (#9239) + testElement = document.createElement( body ? "div" : "body" ); + testElementStyle = { + visibility: "hidden", + width: 0, + height: 0, + border: 0, + margin: 0 + }; + if ( body ) { + jQuery.extend( testElementStyle, { + position: "absolute", + left: -1000, + top: -1000 + }); + } + for ( i in testElementStyle ) { + testElement.style[ i ] = testElementStyle[ i ]; + } + testElement.appendChild( div ); + testElementParent = body || documentElement; + testElementParent.insertBefore( testElement, testElementParent.firstChild ); + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + support.boxModel = div.offsetWidth === 2; + + if ( "zoom" in div.style ) { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.style.display = "inline"; + div.style.zoom = 1; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 ); + + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = ""; + div.innerHTML = "
"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 2 ); + } + + div.innerHTML = "
t
"; + tds = div.getElementsByTagName( "td" ); + + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Check if empty table cells still have offsetWidth/Height + // (IE < 8 fail this test) + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + div.innerHTML = ""; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. For more + // info see bug #3333 + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + if ( document.defaultView && document.defaultView.getComputedStyle ) { + marginDiv = document.createElement( "div" ); + marginDiv.style.width = "0"; + marginDiv.style.marginRight = "0"; + div.appendChild( marginDiv ); + support.reliableMarginRight = + ( parseInt( ( document.defaultView.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; + } + + // Remove the body element we added + testElement.innerHTML = ""; + testElementParent.removeChild( testElement ); + + // Technique from Juriy Zaytsev + // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ + // We only care about the case where non-standard event systems + // are used, namely in IE. Short-circuiting here helps us to + // avoid an eval call (in setAttribute) which can cause CSP + // to go haywire. See: https://developer.mozilla.org/en/Security/CSP + if ( div.attachEvent ) { + for( i in { + submit: 1, + change: 1, + focusin: 1 + } ) { + eventName = "on" + i; + isSupported = ( eventName in div ); + if ( !isSupported ) { + div.setAttribute( eventName, "return;" ); + isSupported = ( typeof div[ eventName ] === "function" ); + } + support[ i + "Bubbles" ] = isSupported; + } + } + + // Null connected elements to avoid leaks in IE + testElement = fragment = select = opt = body = marginDiv = div = input = null; + + return support; +})(); + +// Keep track of boxModel +jQuery.boxModel = jQuery.support.boxModel; + + + + +var rbrace = /^(?:\{.*\}|\[.*\])$/, + rmultiDash = /([a-z])([A-Z])/g; + +jQuery.extend({ + cache: {}, + + // Please use with caution + uuid: 0, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache, + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || (pvt && id && !cache[ id ][ internalKey ])) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ jQuery.expando ] = id = ++jQuery.uuid; + } else { + id = jQuery.expando; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery + // metadata on plain JS objects when the object is serialized using + // JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name); + } else { + cache[ id ] = jQuery.extend(cache[ id ], name); + } + } + + thisCache = cache[ id ]; + + // Internal jQuery data is stored in a separate object inside the object's data + // cache in order to avoid key collisions between internal data and user-defined + // data + if ( pvt ) { + if ( !thisCache[ internalKey ] ) { + thisCache[ internalKey ] = {}; + } + + thisCache = thisCache[ internalKey ]; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should + // not attempt to inspect the internal events object using jQuery.data, as this + // internal data object is undocumented and subject to change. + if ( name === "events" && !thisCache[name] ) { + return thisCache[ internalKey ] && thisCache[ internalKey ].events; + } + + return getByName ? + // Check for both converted-to-camel and non-converted data property names + thisCache[ jQuery.camelCase( name ) ] || thisCache[ name ] : + thisCache; + }, + + removeData: function( elem, name, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var internalKey = jQuery.expando, isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + + // See jQuery.data for more information + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + var thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ]; + + if ( thisCache ) { + delete thisCache[ name ]; + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !isEmptyDataObject(thisCache) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( pvt ) { + delete cache[ id ][ internalKey ]; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject(cache[ id ]) ) { + return; + } + } + + var internalCache = cache[ id ][ internalKey ]; + + // Browsers that fail expando deletion also refuse to delete expandos on + // the window, but it will allow it on all other JS objects; other browsers + // don't care + if ( jQuery.support.deleteExpando || cache != window ) { + delete cache[ id ]; + } else { + cache[ id ] = null; + } + + // We destroyed the entire user cache at once because it's faster than + // iterating through each key, but we need to continue to persist internal + // data if it existed + if ( internalCache ) { + cache[ id ] = {}; + // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery + // metadata on plain JS objects when the object is serialized using + // JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + + cache[ id ][ internalKey ] = internalCache; + + // Otherwise, we need to eliminate the expando on the node to avoid + // false lookups in the cache for entries that no longer exist + } else if ( isNode ) { + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( jQuery.support.deleteExpando ) { + delete elem[ jQuery.expando ]; + } else if ( elem.removeAttribute ) { + elem.removeAttribute( jQuery.expando ); + } else { + elem[ jQuery.expando ] = null; + } + } + }, + + // For internal use only. + _data: function( elem, name, data ) { + return jQuery.data( elem, name, data, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + if ( elem.nodeName ) { + var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; + + if ( match ) { + return !(match === true || elem.getAttribute("classid") !== match); + } + } + + return true; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var data = null; + + if ( typeof key === "undefined" ) { + if ( this.length ) { + data = jQuery.data( this[0] ); + + if ( this[0].nodeType === 1 ) { + var attr = this[0].attributes, name; + for ( var i = 0, l = attr.length; i < l; i++ ) { + name = attr[i].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.substring(5) ); + + dataAttr( this[0], name, data[ name ] ); + } + } + } + } + + return data; + + } else if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + var parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value === undefined ) { + data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + // Try to fetch any internally stored data first + if ( data === undefined && this.length ) { + data = jQuery.data( this[0], key ); + data = dataAttr( this[0], key, data ); + } + + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + + } else { + return this.each(function() { + var $this = jQuery( this ), + args = [ parts[0], value ]; + + $this.triggerHandler( "setData" + parts[1] + "!", args ); + jQuery.data( this, key, value ); + $this.triggerHandler( "changeData" + parts[1] + "!", args ); + }); + } + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + var name = "data-" + key.replace( rmultiDash, "$1-$2" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + !jQuery.isNaN( data ) ? parseFloat( data ) : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// TODO: This is a hack for 1.5 ONLY to allow objects with a single toJSON +// property to be considered empty objects; this property always exists in +// order to make sure JSON.stringify does not expose internal metadata +function isEmptyDataObject( obj ) { + for ( var name in obj ) { + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} + + + + +function handleQueueMarkDefer( elem, type, src ) { + var deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + defer = jQuery.data( elem, deferDataKey, undefined, true ); + if ( defer && + ( src === "queue" || !jQuery.data( elem, queueDataKey, undefined, true ) ) && + ( src === "mark" || !jQuery.data( elem, markDataKey, undefined, true ) ) ) { + // Give room for hard-coded callbacks to fire first + // and eventually mark/queue something else on the element + setTimeout( function() { + if ( !jQuery.data( elem, queueDataKey, undefined, true ) && + !jQuery.data( elem, markDataKey, undefined, true ) ) { + jQuery.removeData( elem, deferDataKey, true ); + defer.resolve(); + } + }, 0 ); + } +} + +jQuery.extend({ + + _mark: function( elem, type ) { + if ( elem ) { + type = (type || "fx") + "mark"; + jQuery.data( elem, type, (jQuery.data(elem,type,undefined,true) || 0) + 1, true ); + } + }, + + _unmark: function( force, elem, type ) { + if ( force !== true ) { + type = elem; + elem = force; + force = false; + } + if ( elem ) { + type = type || "fx"; + var key = type + "mark", + count = force ? 0 : ( (jQuery.data( elem, key, undefined, true) || 1 ) - 1 ); + if ( count ) { + jQuery.data( elem, key, count, true ); + } else { + jQuery.removeData( elem, key, true ); + handleQueueMarkDefer( elem, type, "mark" ); + } + } + }, + + queue: function( elem, type, data ) { + if ( elem ) { + type = (type || "fx") + "queue"; + var q = jQuery.data( elem, type, undefined, true ); + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !q || jQuery.isArray(data) ) { + q = jQuery.data( elem, type, jQuery.makeArray(data), true ); + } else { + q.push( data ); + } + } + return q || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + fn = queue.shift(), + defer; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift("inprogress"); + } + + fn.call(elem, function() { + jQuery.dequeue(elem, type); + }); + } + + if ( !queue.length ) { + jQuery.removeData( elem, type + "queue", true ); + handleQueueMarkDefer( elem, type, "queue" ); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + } + + if ( data === undefined ) { + return jQuery.queue( this[0], type ); + } + return this.each(function() { + var queue = jQuery.queue( this, type, data ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; + type = type || "fx"; + + return this.queue( type, function() { + var elem = this; + setTimeout(function() { + jQuery.dequeue( elem, type ); + }, time ); + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, object ) { + if ( typeof type !== "string" ) { + object = type; + type = undefined; + } + type = type || "fx"; + var defer = jQuery.Deferred(), + elements = this, + i = elements.length, + count = 1, + deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + tmp; + function resolve() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + } + while( i-- ) { + if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || + ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || + jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && + jQuery.data( elements[ i ], deferDataKey, jQuery._Deferred(), true ) )) { + count++; + tmp.done( resolve ); + } + } + resolve(); + return defer.promise(); + } +}); + + + + +var rclass = /[\n\t\r]/g, + rspace = /\s+/, + rreturn = /\r/g, + rtype = /^(?:button|input)$/i, + rfocusable = /^(?:button|input|object|select|textarea)$/i, + rclickable = /^a(?:rea)?$/i, + rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, + rinvalidChar = /\:|^on/, + formHook, boolHook; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.attr ); + }, + + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.prop ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, + + addClass: function( value ) { + var classNames, i, l, elem, + setClass, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).addClass( value.call(this, j, this.className) ); + }); + } + + if ( value && typeof value === "string" ) { + classNames = value.split( rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className && classNames.length === 1 ) { + elem.className = value; + + } else { + setClass = " " + elem.className + " "; + + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { + setClass += classNames[ c ] + " "; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classNames, i, l, elem, className, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).removeClass( value.call(this, j, this.className) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + classNames = (value || "").split( rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + className = (" " + elem.className + " ").replace( rclass, " " ); + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[ c ] + " ", " "); + } + elem.className = jQuery.trim( className ); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( i ) { + jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.split( rspace ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " "; + for ( var i = 0, l = this.length; i < l; i++ ) { + if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var hooks, ret, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return undefined; + } + + var isFunction = jQuery.isFunction( value ); + + return this.each(function( i ) { + var self = jQuery(this), val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, self.val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function( elem ) { + var value, + index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + // Don't return options that are disabled or in a disabled optgroup + if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && + (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + // Fixes Bug #2551 -- select.val() broken in IE after form.reset() + if ( one && !values.length && options.length ) { + return jQuery( options[ index ] ).val(); + } + + return values; + }, + + set: function( elem, value ) { + var values = jQuery.makeArray( value ); + + jQuery(elem).find("option").each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attrFix: { + // Always normalize to ensure hook usage + tabindex: "tabIndex" + }, + + attr: function( elem, name, value, pass ) { + var nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return undefined; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery( elem )[ name ]( value ); + } + + // Fallback to prop when attributes are not supported + if ( !("getAttribute" in elem) ) { + return jQuery.prop( elem, name, value ); + } + + var ret, hooks, + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // Normalize the name if needed + if ( notxml ) { + name = jQuery.attrFix[ name ] || name; + + hooks = jQuery.attrHooks[ name ]; + + if ( !hooks ) { + // Use boolHook for boolean attributes + if ( rboolean.test( name ) ) { + + hooks = boolHook; + + // Use formHook for forms and if the name contains certain characters + } else if ( formHook && name !== "className" && + (jQuery.nodeName( elem, "form" ) || rinvalidChar.test( name )) ) { + + hooks = formHook; + } + } + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return undefined; + + } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, "" + value ); + return value; + } + + } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + + ret = elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return ret === null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, name ) { + var propName; + if ( elem.nodeType === 1 ) { + name = jQuery.attrFix[ name ] || name; + + if ( jQuery.support.getSetAttribute ) { + // Use removeAttribute in browsers that support it + elem.removeAttribute( name ); + } else { + jQuery.attr( elem, name, "" ); + elem.removeAttributeNode( elem.getAttributeNode( name ) ); + } + + // Set corresponding property to false for boolean attributes + if ( rboolean.test( name ) && (propName = jQuery.propFix[ name ] || name) in elem ) { + elem[ propName ] = false; + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to it's default in case type is set after value + // This is for element creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + }, + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode("tabIndex"); + + return attributeNode && attributeNode.specified ? + parseInt( attributeNode.value, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + }, + // Use the value property for back compat + // Use the formHook for button elements in IE6/7 (#1954) + value: { + get: function( elem, name ) { + if ( formHook && jQuery.nodeName( elem, "button" ) ) { + return formHook.get( elem, name ); + } + return name in elem ? + elem.value : + null; + }, + set: function( elem, value, name ) { + if ( formHook && jQuery.nodeName( elem, "button" ) ) { + return formHook.set( elem, value, name ); + } + // Does not return so that setAttribute is also used + elem.value = value; + } + } + }, + + propFix: { + tabindex: "tabIndex", + readonly: "readOnly", + "for": "htmlFor", + "class": "className", + maxlength: "maxLength", + cellspacing: "cellSpacing", + cellpadding: "cellPadding", + rowspan: "rowSpan", + colspan: "colSpan", + usemap: "useMap", + frameborder: "frameBorder", + contenteditable: "contentEditable" + }, + + prop: function( elem, name, value ) { + var nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return undefined; + } + + var ret, hooks, + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + return (elem[ name ] = value); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== undefined ) { + return ret; + + } else { + return elem[ name ]; + } + } + }, + + propHooks: {} +}); + +// Hook for boolean attributes +boolHook = { + get: function( elem, name ) { + // Align boolean attributes with corresponding properties + return jQuery.prop( elem, name ) ? + name.toLowerCase() : + undefined; + }, + set: function( elem, value, name ) { + var propName; + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + // value is true since we know at this point it's type boolean and not false + // Set boolean attributes to the same name and set the DOM property + propName = jQuery.propFix[ name ] || name; + if ( propName in elem ) { + // Only set the IDL specifically if it already exists on the element + elem[ propName ] = true; + } + + elem.setAttribute( name, name.toLowerCase() ); + } + return name; + } +}; + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !jQuery.support.getSetAttribute ) { + + // propFix is more comprehensive and contains all fixes + jQuery.attrFix = jQuery.propFix; + + // Use this for any attribute on a form in IE6/7 + formHook = jQuery.attrHooks.name = jQuery.attrHooks.title = jQuery.valHooks.button = { + get: function( elem, name ) { + var ret; + ret = elem.getAttributeNode( name ); + // Return undefined if nodeValue is empty string + return ret && ret.nodeValue !== "" ? + ret.nodeValue : + undefined; + }, + set: function( elem, value, name ) { + // Check form objects in IE (multiple bugs related) + // Only use nodeValue if the attribute node exists on the form + var ret = elem.getAttributeNode( name ); + if ( ret ) { + ret.nodeValue = value; + return value; + } + } + }; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }); + }); +} + + +// Some attributes require a special call on IE +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + var ret = elem.getAttribute( name, 2 ); + return ret === null ? undefined : ret; + } + }); + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Normalize to lowercase since IE uppercases css property names + return elem.style.cssText.toLowerCase() || undefined; + }, + set: function( elem, value ) { + return (elem.style.cssText = "" + value); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }); +} + +// Radios and checkboxes getter/setter +if ( !jQuery.support.checkOn ) { + jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + get: function( elem ) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); +} +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return (elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0); + } + } + }); +}); + + + + +var rnamespaces = /\.(.*)$/, + rformElems = /^(?:textarea|input|select)$/i, + rperiod = /\./g, + rspaces = / /g, + rescape = /[^\w\s.|`]/g, + fcleanup = function( nm ) { + return nm.replace(rescape, "\\$&"); + }; + +/* + * A number of helper functions used for managing events. + * Many of the ideas behind this code originated from + * Dean Edwards' addEvent library. + */ +jQuery.event = { + + // Bind an event to an element + // Original by Dean Edwards + add: function( elem, types, handler, data ) { + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + if ( handler === false ) { + handler = returnFalse; + } else if ( !handler ) { + // Fixes bug #7229. Fix recommended by jdalton + return; + } + + var handleObjIn, handleObj; + + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + } + + // Make sure that the function being executed has a unique ID + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure + var elemData = jQuery._data( elem ); + + // If no elemData is found then we must be trying to bind to one of the + // banned noData elements + if ( !elemData ) { + return; + } + + var events = elemData.events, + eventHandle = elemData.handle; + + if ( !events ) { + elemData.events = events = {}; + } + + if ( !eventHandle ) { + elemData.handle = eventHandle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.handle.apply( eventHandle.elem, arguments ) : + undefined; + }; + } + + // Add elem as a property of the handle function + // This is to prevent a memory leak with non-native events in IE. + eventHandle.elem = elem; + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = types.split(" "); + + var type, i = 0, namespaces; + + while ( (type = types[ i++ ]) ) { + handleObj = handleObjIn ? + jQuery.extend({}, handleObjIn) : + { handler: handler, data: data }; + + // Namespaced event handlers + if ( type.indexOf(".") > -1 ) { + namespaces = type.split("."); + type = namespaces.shift(); + handleObj.namespace = namespaces.slice(0).sort().join("."); + + } else { + namespaces = []; + handleObj.namespace = ""; + } + + handleObj.type = type; + if ( !handleObj.guid ) { + handleObj.guid = handler.guid; + } + + // Get the current list of functions bound to this event + var handlers = events[ type ], + special = jQuery.event.special[ type ] || {}; + + // Init the event handler queue + if ( !handlers ) { + handlers = events[ type ] = []; + + // Check for a special event handler + // Only use addEventListener/attachEvent if the special + // events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add the function to the element's handler list + handlers.push( handleObj ); + + // Keep track of which events have been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, pos ) { + // don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + if ( handler === false ) { + handler = returnFalse; + } + + var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ), + events = elemData && elemData.events; + + if ( !elemData || !events ) { + return; + } + + // types is actually an event object here + if ( types && types.type ) { + handler = types.handler; + types = types.type; + } + + // Unbind all events for the element + if ( !types || typeof types === "string" && types.charAt(0) === "." ) { + types = types || ""; + + for ( type in events ) { + jQuery.event.remove( elem, type + types ); + } + + return; + } + + // Handle multiple events separated by a space + // jQuery(...).unbind("mouseover mouseout", fn); + types = types.split(" "); + + while ( (type = types[ i++ ]) ) { + origType = type; + handleObj = null; + all = type.indexOf(".") < 0; + namespaces = []; + + if ( !all ) { + // Namespaced event handlers + namespaces = type.split("."); + type = namespaces.shift(); + + namespace = new RegExp("(^|\\.)" + + jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + eventType = events[ type ]; + + if ( !eventType ) { + continue; + } + + if ( !handler ) { + for ( j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( all || namespace.test( handleObj.namespace ) ) { + jQuery.event.remove( elem, origType, handleObj.handler, j ); + eventType.splice( j--, 1 ); + } + } + + continue; + } + + special = jQuery.event.special[ type ] || {}; + + for ( j = pos || 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( handler.guid === handleObj.guid ) { + // remove the given handler for the given type + if ( all || namespace.test( handleObj.namespace ) ) { + if ( pos == null ) { + eventType.splice( j--, 1 ); + } + + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + + if ( pos != null ) { + break; + } + } + } + + // remove generic event handler if no more handlers exist + if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + ret = null; + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + var handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + delete elemData.events; + delete elemData.handle; + + if ( jQuery.isEmptyObject( elemData ) ) { + jQuery.removeData( elem, undefined, true ); + } + } + }, + + // Events that are safe to short-circuit if no handlers are attached. + // Native DOM events should not be added, they may have inline handlers. + customEvent: { + "getData": true, + "setData": true, + "changeData": true + }, + + trigger: function( event, data, elem, onlyHandlers ) { + // Event object or event type + var type = event.type || event, + namespaces = [], + exclusive; + + if ( type.indexOf("!") >= 0 ) { + // Exclusive events trigger only for the exact event (no namespaces) + type = type.slice(0, -1); + exclusive = true; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + + if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { + // No jQuery handlers for this event type, and it can't have inline handlers + return; + } + + // Caller can pass in an Event, Object, or just an event type string + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + new jQuery.Event( type, event ) : + // Just the event type (string) + new jQuery.Event( type ); + + event.type = type; + event.exclusive = exclusive; + event.namespace = namespaces.join("."); + event.namespace_re = new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)"); + + // triggerHandler() and global events don't bubble or run the default action + if ( onlyHandlers || !elem ) { + event.preventDefault(); + event.stopPropagation(); + } + + // Handle a global trigger + if ( !elem ) { + // TODO: Stop taunting the data cache; remove global events and always attach to document + jQuery.each( jQuery.cache, function() { + // internalKey variable is just used to make it easier to find + // and potentially change this stuff later; currently it just + // points to jQuery.expando + var internalKey = jQuery.expando, + internalCache = this[ internalKey ]; + if ( internalCache && internalCache.events && internalCache.events[ type ] ) { + jQuery.event.trigger( event, data, internalCache.handle.elem ); + } + }); + return; + } + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // Clean up the event in case it is being reused + event.result = undefined; + event.target = elem; + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data != null ? jQuery.makeArray( data ) : []; + data.unshift( event ); + + var cur = elem, + // IE doesn't like method names with a colon (#3533, #8272) + ontype = type.indexOf(":") < 0 ? "on" + type : ""; + + // Fire event on the current element, then bubble up the DOM tree + do { + var handle = jQuery._data( cur, "handle" ); + + event.currentTarget = cur; + if ( handle ) { + handle.apply( cur, data ); + } + + // Trigger an inline bound script + if ( ontype && jQuery.acceptData( cur ) && cur[ ontype ] && cur[ ontype ].apply( cur, data ) === false ) { + event.result = false; + event.preventDefault(); + } + + // Bubble up to document, then to window + cur = cur.parentNode || cur.ownerDocument || cur === event.target.ownerDocument && window; + } while ( cur && !event.isPropagationStopped() ); + + // If nobody prevented the default action, do it now + if ( !event.isDefaultPrevented() ) { + var old, + special = jQuery.event.special[ type ] || {}; + + if ( (!special._default || special._default.call( elem.ownerDocument, event ) === false) && + !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction)() check here because IE6/7 fails that test. + // IE<9 dies on focus to hidden element (#1486), may want to revisit a try/catch. + try { + if ( ontype && elem[ type ] ) { + // Don't re-trigger an onFOO event when we call its FOO() method + old = elem[ ontype ]; + + if ( old ) { + elem[ ontype ] = null; + } + + jQuery.event.triggered = type; + elem[ type ](); + } + } catch ( ieError ) {} + + if ( old ) { + elem[ ontype ] = old; + } + + jQuery.event.triggered = undefined; + } + } + + return event.result; + }, + + handle: function( event ) { + event = jQuery.event.fix( event || window.event ); + // Snapshot the handlers list since a called handler may add/remove events. + var handlers = ((jQuery._data( this, "events" ) || {})[ event.type ] || []).slice(0), + run_all = !event.exclusive && !event.namespace, + args = Array.prototype.slice.call( arguments, 0 ); + + // Use the fix-ed Event rather than the (read-only) native event + args[0] = event; + event.currentTarget = this; + + for ( var j = 0, l = handlers.length; j < l; j++ ) { + var handleObj = handlers[ j ]; + + // Triggered event must 1) be non-exclusive and have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event. + if ( run_all || event.namespace_re.test( handleObj.namespace ) ) { + // Pass in a reference to the handler function itself + // So that we can later remove it + event.handler = handleObj.handler; + event.data = handleObj.data; + event.handleObj = handleObj; + + var ret = handleObj.handler.apply( this, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + return event.result; + }, + + props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // store a copy of the original event object + // and "clone" to set read-only properties + var originalEvent = event; + event = jQuery.Event( originalEvent ); + + for ( var i = this.props.length, prop; i; ) { + prop = this.props[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary + if ( !event.target ) { + // Fixes #1925 where srcElement might not be defined either + event.target = event.srcElement || document; + } + + // check if target is a textnode (safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && event.fromElement ) { + event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; + } + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && event.clientX != null ) { + var eventDocument = event.target.ownerDocument || document, + doc = eventDocument.documentElement, + body = eventDocument.body; + + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } + + // Add which for key events + if ( event.which == null && (event.charCode != null || event.keyCode != null) ) { + event.which = event.charCode != null ? event.charCode : event.keyCode; + } + + // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) + if ( !event.metaKey && event.ctrlKey ) { + event.metaKey = event.ctrlKey; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && event.button !== undefined ) { + event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); + } + + return event; + }, + + // Deprecated, use jQuery.guid instead + guid: 1E8, + + // Deprecated, use jQuery.proxy instead + proxy: jQuery.proxy, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady, + teardown: jQuery.noop + }, + + live: { + add: function( handleObj ) { + jQuery.event.add( this, + liveConvert( handleObj.origType, handleObj.selector ), + jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); + }, + + remove: function( handleObj ) { + jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj ); + } + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( jQuery.isWindow( this ) ) { + this.onbeforeunload = eventHandle; + } + }, + + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + if ( elem.detachEvent ) { + elem.detachEvent( "on" + type, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !this.preventDefault ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // timeStamp is buggy for some events on Firefox(#3843) + // So we won't rely on the native value + this.timeStamp = jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // otherwise set the returnValue property of the original event to false (IE) + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Checks if an event happened on an element within another element +// Used in jQuery.event.special.mouseenter and mouseleave handlers +var withinElement = function( event ) { + + // Check if mouse(over|out) are still within the same parent element + var related = event.relatedTarget, + inside = false, + eventType = event.type; + + event.type = event.data; + + if ( related !== this ) { + + if ( related ) { + inside = jQuery.contains( this, related ); + } + + if ( !inside ) { + + jQuery.event.handle.apply( this, arguments ); + + event.type = eventType; + } + } +}, + +// In case of event delegation, we only need to rename the event.type, +// liveHandler will take care of the rest. +delegate = function( event ) { + event.type = event.data; + jQuery.event.handle.apply( this, arguments ); +}; + +// Create mouseenter and mouseleave events +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + setup: function( data ) { + jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); + }, + teardown: function( data ) { + jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); + } + }; +}); + +// submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function( data, namespaces ) { + if ( !jQuery.nodeName( this, "form" ) ) { + jQuery.event.add(this, "click.specialSubmit", function( e ) { + var elem = e.target, + type = elem.type; + + if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { + trigger( "submit", this, arguments ); + } + }); + + jQuery.event.add(this, "keypress.specialSubmit", function( e ) { + var elem = e.target, + type = elem.type; + + if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { + trigger( "submit", this, arguments ); + } + }); + + } else { + return false; + } + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialSubmit" ); + } + }; + +} + +// change delegation, happens here so we have bind. +if ( !jQuery.support.changeBubbles ) { + + var changeFilters, + + getVal = function( elem ) { + var type = elem.type, val = elem.value; + + if ( type === "radio" || type === "checkbox" ) { + val = elem.checked; + + } else if ( type === "select-multiple" ) { + val = elem.selectedIndex > -1 ? + jQuery.map( elem.options, function( elem ) { + return elem.selected; + }).join("-") : + ""; + + } else if ( jQuery.nodeName( elem, "select" ) ) { + val = elem.selectedIndex; + } + + return val; + }, + + testChange = function testChange( e ) { + var elem = e.target, data, val; + + if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) { + return; + } + + data = jQuery._data( elem, "_change_data" ); + val = getVal(elem); + + // the current data will be also retrieved by beforeactivate + if ( e.type !== "focusout" || elem.type !== "radio" ) { + jQuery._data( elem, "_change_data", val ); + } + + if ( data === undefined || val === data ) { + return; + } + + if ( data != null || val ) { + e.type = "change"; + e.liveFired = undefined; + jQuery.event.trigger( e, arguments[1], elem ); + } + }; + + jQuery.event.special.change = { + filters: { + focusout: testChange, + + beforedeactivate: testChange, + + click: function( e ) { + var elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : ""; + + if ( type === "radio" || type === "checkbox" || jQuery.nodeName( elem, "select" ) ) { + testChange.call( this, e ); + } + }, + + // Change has to be called before submit + // Keydown will be called before keypress, which is used in submit-event delegation + keydown: function( e ) { + var elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : ""; + + if ( (e.keyCode === 13 && !jQuery.nodeName( elem, "textarea" ) ) || + (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || + type === "select-multiple" ) { + testChange.call( this, e ); + } + }, + + // Beforeactivate happens also before the previous element is blurred + // with this event you can't trigger a change event, but you can store + // information + beforeactivate: function( e ) { + var elem = e.target; + jQuery._data( elem, "_change_data", getVal(elem) ); + } + }, + + setup: function( data, namespaces ) { + if ( this.type === "file" ) { + return false; + } + + for ( var type in changeFilters ) { + jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); + } + + return rformElems.test( this.nodeName ); + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialChange" ); + + return rformElems.test( this.nodeName ); + } + }; + + changeFilters = jQuery.event.special.change.filters; + + // Handle when the input is .focus()'d + changeFilters.focus = changeFilters.beforeactivate; +} + +function trigger( type, elem, args ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + // Don't pass args or remember liveFired; they apply to the donor event. + var event = jQuery.extend( {}, args[ 0 ] ); + event.type = type; + event.originalEvent = {}; + event.liveFired = undefined; + jQuery.event.handle.call( elem, event ); + if ( event.isDefaultPrevented() ) { + args[ 0 ].preventDefault(); + } +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + + function handler( donor ) { + // Donor event is always a native one; fix it and switch its type. + // Let focusin/out handler cancel the donor focus/blur event. + var e = jQuery.event.fix( donor ); + e.type = fix; + e.originalEvent = {}; + jQuery.event.trigger( e, null, e.target ); + if ( e.isDefaultPrevented() ) { + donor.preventDefault(); + } + } + }); +} + +jQuery.each(["bind", "one"], function( i, name ) { + jQuery.fn[ name ] = function( type, data, fn ) { + var handler; + + // Handle object literals + if ( typeof type === "object" ) { + for ( var key in type ) { + this[ name ](key, data, type[key], fn); + } + return this; + } + + if ( arguments.length === 2 || data === false ) { + fn = data; + data = undefined; + } + + if ( name === "one" ) { + handler = function( event ) { + jQuery( this ).unbind( event, handler ); + return fn.apply( this, arguments ); + }; + handler.guid = fn.guid || jQuery.guid++; + } else { + handler = fn; + } + + if ( type === "unload" && name !== "one" ) { + this.one( type, data, fn ); + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.add( this[i], type, handler, data ); + } + } + + return this; + }; +}); + +jQuery.fn.extend({ + unbind: function( type, fn ) { + // Handle object literals + if ( typeof type === "object" && !type.preventDefault ) { + for ( var key in type ) { + this.unbind(key, type[key]); + } + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.remove( this[i], type, fn ); + } + } + + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.live( types, data, fn, selector ); + }, + + undelegate: function( selector, types, fn ) { + if ( arguments.length === 0 ) { + return this.unbind( "live" ); + + } else { + return this.die( types, null, fn, selector ); + } + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + + triggerHandler: function( type, data ) { + if ( this[0] ) { + return jQuery.event.trigger( type, data, this[0], true ); + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, + guid = fn.guid || jQuery.guid++, + i = 0, + toggler = function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + }; + + // link all the functions, so any of them can unbind this click handler + toggler.guid = guid; + while ( i < args.length ) { + args[ i++ ].guid = guid; + } + + return this.click( toggler ); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +var liveMap = { + focus: "focusin", + blur: "focusout", + mouseenter: "mouseover", + mouseleave: "mouseout" +}; + +jQuery.each(["live", "die"], function( i, name ) { + jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { + var type, i = 0, match, namespaces, preType, + selector = origSelector || this.selector, + context = origSelector ? this : jQuery( this.context ); + + if ( typeof types === "object" && !types.preventDefault ) { + for ( var key in types ) { + context[ name ]( key, data, types[key], selector ); + } + + return this; + } + + if ( name === "die" && !types && + origSelector && origSelector.charAt(0) === "." ) { + + context.unbind( origSelector ); + + return this; + } + + if ( data === false || jQuery.isFunction( data ) ) { + fn = data || returnFalse; + data = undefined; + } + + types = (types || "").split(" "); + + while ( (type = types[ i++ ]) != null ) { + match = rnamespaces.exec( type ); + namespaces = ""; + + if ( match ) { + namespaces = match[0]; + type = type.replace( rnamespaces, "" ); + } + + if ( type === "hover" ) { + types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); + continue; + } + + preType = type; + + if ( liveMap[ type ] ) { + types.push( liveMap[ type ] + namespaces ); + type = type + namespaces; + + } else { + type = (liveMap[ type ] || type) + namespaces; + } + + if ( name === "live" ) { + // bind live handler + for ( var j = 0, l = context.length; j < l; j++ ) { + jQuery.event.add( context[j], "live." + liveConvert( type, selector ), + { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); + } + + } else { + // unbind live handler + context.unbind( "live." + liveConvert( type, selector ), fn ); + } + } + + return this; + }; +}); + +function liveHandler( event ) { + var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, + elems = [], + selectors = [], + events = jQuery._data( this, "events" ); + + // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911) + if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) { + return; + } + + if ( event.namespace ) { + namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + event.liveFired = this; + + var live = events.live.slice(0); + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { + selectors.push( handleObj.selector ); + + } else { + live.splice( j--, 1 ); + } + } + + match = jQuery( event.target ).closest( selectors, event.currentTarget ); + + for ( i = 0, l = match.length; i < l; i++ ) { + close = match[i]; + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) && !close.elem.disabled ) { + elem = close.elem; + related = null; + + // Those two events require additional checking + if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { + event.type = handleObj.preType; + related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; + + // Make sure not to accidentally match a child element with the same selector + if ( related && jQuery.contains( elem, related ) ) { + related = elem; + } + } + + if ( !related || related !== elem ) { + elems.push({ elem: elem, handleObj: handleObj, level: close.level }); + } + } + } + } + + for ( i = 0, l = elems.length; i < l; i++ ) { + match = elems[i]; + + if ( maxLevel && match.level > maxLevel ) { + break; + } + + event.currentTarget = match.elem; + event.data = match.handleObj.data; + event.handleObj = match.handleObj; + + ret = match.handleObj.origHandler.apply( match.elem, arguments ); + + if ( ret === false || event.isPropagationStopped() ) { + maxLevel = match.level; + + if ( ret === false ) { + stop = false; + } + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + + return stop; +} + +function liveConvert( type, selector ) { + return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspaces, "&"); +} + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + if ( fn == null ) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.bind( name, data, fn ) : + this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } +}); + + + +/*! + * Sizzle CSS Selector Engine + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true, + rBackslash = /\\/g, + rNonWord = /\W/; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function() { + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function( selector, context, results, seed ) { + results = results || []; + context = context || document; + + var origContext = context; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var m, set, checkSet, extra, ret, cur, pop, i, + prune = true, + contextXML = Sizzle.isXML( context ), + parts = [], + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec( "" ); + m = chunker.exec( soFar ); + + if ( m ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + } while ( m ); + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set ); + } + } + + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + + ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? + Sizzle.filter( ret.expr, ret.set )[0] : + ret.set[0]; + } + + if ( context ) { + ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + + set = ret.expr ? + Sizzle.filter( ret.expr, ret.set ) : + ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray( set ); + + } else { + prune = false; + } + + while ( parts.length ) { + cur = parts.pop(); + pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + + } else if ( context && context.nodeType === 1 ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + + } else { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function( results ) { + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[ i - 1 ] ) { + results.splice( i--, 1 ); + } + } + } + } + + return results; +}; + +Sizzle.matches = function( expr, set ) { + return Sizzle( expr, null, null, set ); +}; + +Sizzle.matchesSelector = function( node, expr ) { + return Sizzle( expr, null, null, [node] ).length > 0; +}; + +Sizzle.find = function( expr, context, isXML ) { + var set; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var match, + type = Expr.order[i]; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice( 1, 1 ); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace( rBackslash, "" ); + set = Expr.find[ type ]( match, context, isXML ); + + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = typeof context.getElementsByTagName !== "undefined" ? + context.getElementsByTagName( "*" ) : + []; + } + + return { set: set, expr: expr }; +}; + +Sizzle.filter = function( expr, set, inplace, not ) { + var match, anyFound, + old = expr, + result = [], + curLoop = set, + isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + var found, item, + filter = Expr.filter[ type ], + left = match[1]; + + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + + } else { + curLoop[i] = false; + } + + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw "Syntax error, unrecognized expression: " + msg; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + + match: { + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + + leftMatch: {}, + + attrMap: { + "class": "className", + "for": "htmlFor" + }, + + attrHandle: { + href: function( elem ) { + return elem.getAttribute( "href" ); + }, + type: function( elem ) { + return elem.getAttribute( "type" ); + } + }, + + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !rNonWord.test( part ), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + + ">": function( checkSet, part ) { + var elem, + isPartStr = typeof part === "string", + i = 0, + l = checkSet.length; + + if ( isPartStr && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + + } else { + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + + "": function(checkSet, part, isXML){ + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); + }, + + "~": function( checkSet, part, isXML ) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); + } + }, + + find: { + ID: function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }, + + NAME: function( match, context ) { + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], + results = context.getElementsByName( match[1] ); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + + TAG: function( match, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( match[1] ); + } + } + }, + preFilter: { + CLASS: function( match, curLoop, inplace, result, not, isXML ) { + match = " " + match[1].replace( rBackslash, "" ) + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + + ID: function( match ) { + return match[1].replace( rBackslash, "" ); + }, + + TAG: function( match, curLoop ) { + return match[1].replace( rBackslash, "" ).toLowerCase(); + }, + + CHILD: function( match ) { + if ( match[1] === "nth" ) { + if ( !match[2] ) { + Sizzle.error( match[0] ); + } + + match[2] = match[2].replace(/^\+|\s*/g, ''); + + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + else if ( match[2] ) { + Sizzle.error( match[0] ); + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + + ATTR: function( match, curLoop, inplace, result, not, isXML ) { + var name = match[1] = match[1].replace( rBackslash, "" ); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + // Handle if an un-quoted value was used + match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + + PSEUDO: function( match, curLoop, inplace, result, not ) { + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + + if ( !inplace ) { + result.push.apply( result, ret ); + } + + return false; + } + + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + + POS: function( match ) { + match.unshift( true ); + + return match; + } + }, + + filters: { + enabled: function( elem ) { + return elem.disabled === false && elem.type !== "hidden"; + }, + + disabled: function( elem ) { + return elem.disabled === true; + }, + + checked: function( elem ) { + return elem.checked === true; + }, + + selected: function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + parent: function( elem ) { + return !!elem.firstChild; + }, + + empty: function( elem ) { + return !elem.firstChild; + }, + + has: function( elem, i, match ) { + return !!Sizzle( match[3], elem ).length; + }, + + header: function( elem ) { + return (/h\d/i).test( elem.nodeName ); + }, + + text: function( elem ) { + var attr = elem.getAttribute( "type" ), type = elem.type; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); + }, + + radio: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; + }, + + checkbox: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; + }, + + file: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; + }, + + password: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; + }, + + submit: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "submit" === elem.type; + }, + + image: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; + }, + + reset: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "reset" === elem.type; + }, + + button: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && "button" === elem.type || name === "button"; + }, + + input: function( elem ) { + return (/input|select|textarea|button/i).test( elem.nodeName ); + }, + + focus: function( elem ) { + return elem === elem.ownerDocument.activeElement; + } + }, + setFilters: { + first: function( elem, i ) { + return i === 0; + }, + + last: function( elem, i, match, array ) { + return i === array.length - 1; + }, + + even: function( elem, i ) { + return i % 2 === 0; + }, + + odd: function( elem, i ) { + return i % 2 === 1; + }, + + lt: function( elem, i, match ) { + return i < match[3] - 0; + }, + + gt: function( elem, i, match ) { + return i > match[3] - 0; + }, + + nth: function( elem, i, match ) { + return match[3] - 0 === i; + }, + + eq: function( elem, i, match ) { + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function( elem, match, i, array ) { + var name = match[1], + filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; + + } else if ( name === "not" ) { + var not = match[3]; + + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { + return false; + } + } + + return true; + + } else { + Sizzle.error( name ); + } + }, + + CHILD: function( elem, match ) { + var type = match[1], + node = elem; + + switch ( type ) { + case "only": + case "first": + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + if ( type === "first" ) { + return true; + } + + node = elem; + + case "last": + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + return true; + + case "nth": + var first = match[2], + last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + + if ( first === 0 ) { + return diff === 0; + + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + + ID: function( elem, match ) { + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + + TAG: function( elem, match ) { + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; + }, + + CLASS: function( elem, match ) { + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + + ATTR: function( elem, match ) { + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + + POS: function( elem, match, i, array ) { + var name = match[2], + filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); +} + +var makeArray = function( array, results ) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch( e ) { + makeArray = function( array, results ) { + var i = 0, + ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + + } else { + if ( typeof array.length === "number" ) { + for ( var l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + + } else { + for ( ; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder, siblingCheck; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + return a.compareDocumentPosition ? -1 : 1; + } + + return a.compareDocumentPosition(b) & 4 ? -1 : 1; + }; + +} else { + sortOrder = function( a, b ) { + // The nodes are identical, we can exit early + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Fallback to using sourceIndex (in IE) if it's available on both nodes + } else if ( a.sourceIndex && b.sourceIndex ) { + return a.sourceIndex - b.sourceIndex; + } + + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; + + // If the nodes are siblings (or identical) we can do a quick check + if ( aup === bup ) { + return siblingCheck( a, b ); + + // If no parents were found then the nodes are disconnected + } else if ( !aup ) { + return -1; + + } else if ( !bup ) { + return 1; + } + + // Otherwise they're somewhere else in the tree so we need + // to build up a full list of the parentNodes for comparison + while ( cur ) { + ap.unshift( cur ); + cur = cur.parentNode; + } + + cur = bup; + + while ( cur ) { + bp.unshift( cur ); + cur = cur.parentNode; + } + + al = ap.length; + bl = bp.length; + + // Start walking down the tree looking for a discrepancy + for ( var i = 0; i < al && i < bl; i++ ) { + if ( ap[i] !== bp[i] ) { + return siblingCheck( ap[i], bp[i] ); + } + } + + // We ended someplace up the tree so do a sibling check + return i === al ? + siblingCheck( a, bp[i], -1 ) : + siblingCheck( ap[i], b, 1 ); + }; + + siblingCheck = function( a, b, ret ) { + if ( a === b ) { + return ret; + } + + var cur = a.nextSibling; + + while ( cur ) { + if ( cur === b ) { + return -1; + } + + cur = cur.nextSibling; + } + + return 1; + }; +} + +// Utility function for retreiving the text value of an array of DOM nodes +Sizzle.getText = function( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += Sizzle.getText( elem.childNodes ); + } + } + + return ret; +}; + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(), + root = document.documentElement; + + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + + return m ? + m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? + [m] : + undefined : + []; + } + }; + + Expr.filter.ID = function( elem, match ) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + + // release memory in IE + root = form = null; +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function( match, context ) { + var results = context.getElementsByTagName( match[1] ); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + + Expr.attrHandle.href = function( elem ) { + return elem.getAttribute( "href", 2 ); + }; + } + + // release memory in IE + div = null; +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, + div = document.createElement("div"), + id = "__sizzle__"; + + div.innerHTML = "

"; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function( query, context, extra, seed ) { + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && !Sizzle.isXML(context) ) { + // See if we find a selector to speed up + var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); + + if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { + // Speed-up: Sizzle("TAG") + if ( match[1] ) { + return makeArray( context.getElementsByTagName( query ), extra ); + + // Speed-up: Sizzle(".CLASS") + } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { + return makeArray( context.getElementsByClassName( match[2] ), extra ); + } + } + + if ( context.nodeType === 9 ) { + // Speed-up: Sizzle("body") + // The body element only exists once, optimize finding it + if ( query === "body" && context.body ) { + return makeArray( [ context.body ], extra ); + + // Speed-up: Sizzle("#ID") + } else if ( match && match[3] ) { + var elem = context.getElementById( match[3] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id === match[3] ) { + return makeArray( [ elem ], extra ); + } + + } else { + return makeArray( [], extra ); + } + } + + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(qsaError) {} + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + var oldContext = context, + old = context.getAttribute( "id" ), + nid = old || id, + hasParent = context.parentNode, + relativeHierarchySelector = /^\s*[+~]/.test( query ); + + if ( !old ) { + context.setAttribute( "id", nid ); + } else { + nid = nid.replace( /'/g, "\\$&" ); + } + if ( relativeHierarchySelector && hasParent ) { + context = context.parentNode; + } + + try { + if ( !relativeHierarchySelector || hasParent ) { + return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); + } + + } catch(pseudoError) { + } finally { + if ( !old ) { + oldContext.removeAttribute( "id" ); + } + } + } + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + // release memory in IE + div = null; + })(); +} + +(function(){ + var html = document.documentElement, + matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; + + if ( matches ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9 fails this) + var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), + pseudoWorks = false; + + try { + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( document.documentElement, "[test!='']:sizzle" ); + + } catch( pseudoError ) { + pseudoWorks = true; + } + + Sizzle.matchesSelector = function( node, expr ) { + // Make sure that attribute selectors are quoted + expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + if ( !Sizzle.isXML( node ) ) { + try { + if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { + var ret = matches.call( node, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || !disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9, so check for that + node.document && node.document.nodeType !== 11 ) { + return ret; + } + } + } catch(e) {} + } + + return Sizzle(expr, null, null, [node]).length > 0; + }; + } +})(); + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "
"; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function( match, context, isXML ) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + // release memory in IE + div = null; +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +if ( document.documentElement.contains ) { + Sizzle.contains = function( a, b ) { + return a !== b && (a.contains ? a.contains(b) : true); + }; + +} else if ( document.documentElement.compareDocumentPosition ) { + Sizzle.contains = function( a, b ) { + return !!(a.compareDocumentPosition(b) & 16); + }; + +} else { + Sizzle.contains = function() { + return false; + }; +} + +Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function( selector, context ) { + var match, + tmpSet = [], + later = "", + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})(); + + +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + isSimple = /^.[^:#\[\.,]*$/, + slice = Array.prototype.slice, + POS = jQuery.expr.match.POS, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var self = this, + i, l; + + if ( typeof selector !== "string" ) { + return jQuery( selector ).filter(function() { + for ( i = 0, l = self.length; i < l; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }); + } + + var ret = this.pushStack( "", "find", selector ), + length, n, r; + + for ( i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( n = length; n < ret.length; n++ ) { + for ( r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && ( typeof selector === "string" ? + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); + }, + + closest: function( selectors, context ) { + var ret = [], i, l, cur = this[0]; + + // Array + if ( jQuery.isArray( selectors ) ) { + var match, selector, + matches = {}, + level = 1; + + if ( cur && selectors.length ) { + for ( i = 0, l = selectors.length; i < l; i++ ) { + selector = selectors[i]; + + if ( !matches[ selector ] ) { + matches[ selector ] = POS.test( selector ) ? + jQuery( selector, context || this.context ) : + selector; + } + } + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( selector in matches ) { + match = matches[ selector ]; + + if ( match.jquery ? match.index( cur ) > -1 : jQuery( cur ).is( match ) ) { + ret.push({ selector: selector, elem: cur, level: level }); + } + } + + cur = cur.parentNode; + level++; + } + } + + return ret; + } + + // String + var pos = POS.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( i = 0, l = this.length; i < l; i++ ) { + cur = this[i]; + + while ( cur ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + + } else { + cur = cur.parentNode; + if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) { + break; + } + } + } + } + + ret = ret.length > 1 ? jQuery.unique( ret ) : ret; + + return this.pushStack( ret, "closest", selectors ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + if ( !elem || typeof elem === "string" ) { + return jQuery.inArray( this[0], + // If it receives a string, the selector is used + // If it receives nothing, the siblings are used + elem ? jQuery( elem ) : this.parent().children() ); + } + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( elem.parentNode.firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ), + // The variable 'args' was introduced in + // https://github.com/jquery/jquery/commit/52a0238 + // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed. + // http://code.google.com/p/v8/issues/detail?id=1050 + args = slice.call(arguments); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, args.join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return (elem === qualifier) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return (jQuery.inArray( elem, qualifier ) >= 0) === keep; + }); +} + + + + +var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, + rtagName = /<([\w:]+)/, + rtbody = /", "" ], + legend: [ 1, "
", "
" ], + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + col: [ 2, "", "
" ], + area: [ 1, "", "" ], + _default: [ 0, "", "" ] + }; + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize and
- - - + headphones
-
- ${next.body()} +
+ ${next.body()}
-
Version: ${headphones.CURRENT_VERSION}
-
- - - - -
-
+
${next.javascriptIncludes()} + diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index e09afd23..38c8ef3f 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -4,10 +4,10 @@ - - - - + + + + diff --git a/headphones/webserve.py b/headphones/webserve.py index 5af92dd0..35524d98 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -34,7 +34,7 @@ class WebInterface(object): def home(self): myDB = db.DBConnection() artists = myDB.select('SELECT * from artists order by ArtistSortName COLLATE NOCASE') - return serve_template(templatename="index.html", title='Home', artists=artists) + return serve_template(templatename="index.html", title="Home", artists=artists) home.exposed = True def homeold(self): From bb8b3730c48d28e03eedc8c7180ed62766433a68 Mon Sep 17 00:00:00 2001 From: Remy Date: Thu, 4 Aug 2011 19:30:38 -0700 Subject: [PATCH 03/22] Initial styling of index page --- data/css/data_table.css | 11 +++++++---- data/css/style.css | 18 +++++++++++++++--- data/interfaces/default/base.html | 31 +++++++++++++++++++++++++++++-- 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/data/css/data_table.css b/data/css/data_table.css index 1ee83731..cff31eee 100644 --- a/data/css/data_table.css +++ b/data/css/data_table.css @@ -1,8 +1,10 @@ .dataTables_wrapper { - border-radius: 10px; - width: 90%; - margin: 0 auto 0 auto; - padding: 20px; + border-radius: 20px; + -webkit-border-radius: 20px; + -moz-border-radius: 20px; + width: 88%; + margin: 20px auto 0 auto; + padding: 30px; background-color: white; position: relative; min-height: 302px; @@ -85,6 +87,7 @@ */ table.display { margin: 20px auto; + width: 95%; clear: both; /* Note Firefox 3.5 and before have a bug with border-collapse diff --git a/data/css/style.css b/data/css/style.css index e0a951d5..3cee7e1e 100755 --- a/data/css/style.css +++ b/data/css/style.css @@ -86,15 +86,27 @@ a:hover { color: #036; } \\ ========================================== // */ +container { } + body { background-color: #EBF4FB } -header { height: 68px; padding: 0 10px 0 10px; background-color: #F5F5F5; margin-bottom: 20px; } +header { height: 68px; padding: 0 10px 0 10px; background-color: #CDC9C9; } +div#logo { float: left; } + +ul#nav { margin-top: 25px; float: left; white-space: nowrap; list-style-type: none; } +ul#nav li { margin: 40px 5px auto 15px; display: inline; } +ul#nav li a { font-size: 16px; font-weight: bold; color: #330000; text-decoration: none; } +ul#nav li a:hover { background-color: #EE0000; } + +div#searchbar { margin: 24px auto auto 30px; float: left; } table#artist_table th#name { width: 30%; } table#artist_table th#status { width: 10%; } table#artist_table th#latestalbum { width: 35%; } table#artist_table th#have { width: 20%; } - +footer { margin: 20px auto 20px auto; } +div#version { text-align: center; font-weight: bold; } +div#donate { text-align: center; margin: 20px auto 20px auto; } @@ -122,7 +134,7 @@ table#artist_table th#have { width: 20%; } @media screen and (max-device-width: 480px) { - /* html { -webkit-text-size-adjust:none; -ms-text-size-adjust:none; } */ + html { -webkit-text-size-adjust:none; -ms-text-size-adjust:none; } } diff --git a/data/interfaces/default/base.html b/data/interfaces/default/base.html index 7fa334d5..e543c04a 100755 --- a/data/interfaces/default/base.html +++ b/data/interfaces/default/base.html @@ -27,7 +27,24 @@
- headphones + + +
@@ -35,7 +52,17 @@
- +
+ Version: ${headphones.CURRENT_VERSION} +
+
From a792e8614a428eed659c052d6ecc19b8eecd5bb1 Mon Sep 17 00:00:00 2001 From: Remy Date: Fri, 5 Aug 2011 00:22:30 -0700 Subject: [PATCH 04/22] Index page styling concept#1 --- data/css/data_table.css | 7 +++--- data/css/style.css | 39 ++++++++++++++++++++++------- data/images/sort_asc.png | Bin 263 -> 0 bytes data/images/sort_asc_disabled.png | Bin 252 -> 0 bytes data/images/sort_both.png | Bin 282 -> 0 bytes data/images/sort_desc.png | Bin 260 -> 0 bytes data/images/sort_desc_disabled.png | Bin 251 -> 0 bytes data/interfaces/default/index.html | 34 ++++++++++++++++++++----- data/js/script.js | 3 +++ 9 files changed, 64 insertions(+), 19 deletions(-) delete mode 100644 data/images/sort_asc.png delete mode 100644 data/images/sort_asc_disabled.png delete mode 100644 data/images/sort_both.png delete mode 100644 data/images/sort_desc.png delete mode 100644 data/images/sort_desc_disabled.png diff --git a/data/css/data_table.css b/data/css/data_table.css index cff31eee..a1c74dce 100644 --- a/data/css/data_table.css +++ b/data/css/data_table.css @@ -4,7 +4,7 @@ -moz-border-radius: 20px; width: 88%; margin: 20px auto 0 auto; - padding: 30px; + padding: 25px; background-color: white; position: relative; min-height: 302px; @@ -38,7 +38,7 @@ width: 50%; float: right; text-align: right; - margin-bottom: 25px; + margin-bottom: 15px; } .dataTables_info { @@ -87,7 +87,6 @@ */ table.display { margin: 20px auto; - width: 95%; clear: both; /* Note Firefox 3.5 and before have a bug with border-collapse @@ -203,7 +202,7 @@ tr.odd { } tr.even { - background-color: #F5F5F5; + background-color: white; } diff --git a/data/css/style.css b/data/css/style.css index 3cee7e1e..7a4b8b96 100755 --- a/data/css/style.css +++ b/data/css/style.css @@ -86,6 +86,23 @@ a:hover { color: #036; } \\ ========================================== // */ +a:link { + color: #5E2612; + text-decoration: none; + } +a:visited { + color: #5E2612; + text-decoration: none; + } +a:hover { /*this effect is not shown in NN4.xx*/ + color: #999999; + text-decoration: underline; + } +a:active {/*colour in NN4.xx is red*/ + color: #5E2612; + text-decoration: underline; + } + container { } body { background-color: #EBF4FB } @@ -99,20 +116,24 @@ ul#nav li a:hover { background-color: #EE0000; } div#searchbar { margin: 24px auto auto 30px; float: left; } -table#artist_table th#name { width: 30%; } -table#artist_table th#status { width: 10%; } -table#artist_table th#latestalbum { width: 35%; } -table#artist_table th#have { width: 20%; } +table#artist_table { background-color: white; } + +table#artist_table th#name { text-align: center; } +table#artist_table th#album { text-align: center; } +table#artist_table th#reldate { width: 125px; text-align: center; } +table#artist_table th#center { text-align: center; } +table#artist_table td#name { text-align: left; } +table#artist_table td#album { text-align: center; } +table#artist_table td#reldate { width: 125px; text-align: center; } + +div.progress-container { border: 1px solid #ccc; width: 100px; height: 14px; margin: 2px 5px 2px 0; padding: 1px; float: left; background: white; } +div.progress-container > div { background-color: #ACE97C; height: 14px; } +.havetracks { font-size: 13px; margin-left: 36px; } footer { margin: 20px auto 20px auto; } div#version { text-align: center; font-weight: bold; } div#donate { text-align: center; margin: 20px auto 20px auto; } - - - - - .ir { display: block; text-indent: -999em; overflow: hidden; background-repeat: no-repeat; text-align: left; direction: ltr; } .hidden { display: none; visibility: hidden; } .visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } diff --git a/data/images/sort_asc.png b/data/images/sort_asc.png deleted file mode 100644 index a56d0e21902ce620cd57f87fa58a6d3e804135cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S3?yCqj{O5tEa{HEjtmUzPnffIy#(?lOI#yL zg7ec#$`gxH85~pclTsBta}(23gHjVyDhp4h+5i>l2Ka=y{`vF&J9*~+_wWBdfBFCW_y37A{>@tOZ^^2EJ(K_a|Nno&o$%d2jr=7+e!)O45@5J; zXU0OHl&hzUV@SoVE9Xw~GAQscA1s)^?0*rXqM}DeLv7?|BU8z@QCOGupVzP9Hek5&L+Gnl%BD>~0~tJB{an^LB{Ts5b0m7= diff --git a/data/images/sort_asc_disabled.png b/data/images/sort_asc_disabled.png deleted file mode 100644 index b7e621ef1c68da839ce077cec6e201a55c1ba168..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 252 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S3?yCqj{O5tEa{HEjtmUzPnffIy#(?lOI#yL zg7ec#$`gxH85~pclTsBta}(23gHjVyDhp4h+5i=)2l#}z{`>du$&)AV-@pIyd9;k)4B*-rq$b|z2+ZRf0KnW*L z7srr_TUXBQ7GeV6G?@}f$7k6Jhw{qGm$-QDzD`qrDaZ3s$b2XF;fBBPU nqjNDy&Nw~puHFA+-v&m8`?>-yUFVHK{$ucT^>bP0l+XkKhw^g= diff --git a/data/images/sort_both.png b/data/images/sort_both.png deleted file mode 100644 index 839ac4bb5b0b414834c950de9deafff6dd94ed2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S3?yCqj{O5tEa{HEjtmUzPnffIy#(?lOI#yL zg7ec#$`gxH85~pclTsBta}(23gHjVyDhp4h+5i=)2l#}z{`>du$&)AV-@pIyd9;k)4B*-rq$b|z2+ZRf0K#2rT z7srr_TRjsz`4|*ASOSl%eg6Ob+(JtRwX|O@S}a^IESQCgTe~DWM4fR9b+X diff --git a/data/images/sort_desc.png b/data/images/sort_desc.png deleted file mode 100644 index 90b295159df995329b3a84340d32761f3d1dbade..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 260 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S3?yCqj{O5tEa{HEjtmUzPnffIy#(?lOI#yL zg7ec#$`gxH85~pclTsBta}(23gHjVyDhp4h+5i>l2Ka=y{`vF&J9*~+_wWBdfBFCW_y37A{>@tOZ^^2EJ(K_a|Nno&o$%d2jr=7+e!)O45@5J; zXU0OHl#{26V@SoVqz8vtc$~v}dc~O{CLEF2anNavMpdMP)0v(X&o(k0opIq!LdvtJ wj~up7@0`wiSoe($&y07EkGxK6U|?nlJSz0{?wJWUfu=Edy85}Sb4q9e0C9MGu>b%7 diff --git a/data/images/sort_desc_disabled.png b/data/images/sort_desc_disabled.png deleted file mode 100644 index 2409653dc94cd21a281a31c0e3819323b84704b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 251 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S3?yCqj{O5tEa{HEjtmUzPnffIy#(?lOI#yL zg7ec#$`gxH85~pclTsBta}(23gHjVyDhp4h+5i=)2l#}z{`>du$&)AV-@pIyd9;k)4B*-rq$b|z2+ZRf0KnX`r z7srr_TS*TNvhX;E^>j)zMNBv%VR68zN24n8K+>5N&kUJbBQCy4c=qgBiu3mAQ)hD? n)_oJhGvi(If!Apb49pB3GXz|UGFKJ=4P)?h^>bP0l+XkK*57ji diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index 38c8ef3f..8204c1f4 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -1,3 +1,6 @@ +<% + from headphones import helpers +%> <%inherit file="base.html"/> <%def name="body()"> @@ -5,18 +8,37 @@
- - + + %for artist in artists: + <% + totaltracks = artist['TotalTracks'] + havetracks = artist['HaveTracks'] + if not havetracks: + havetracks = 0 + try: + percent = (havetracks*100.0)/totaltracks + if percent > 100: + percent = 100 + except (ZeroDivisionError, TypeError): + percent = 0 + totaltracks = '?' + + if artist['ReleaseDate']: + releasedate = artist['ReleaseDate'] + else: + releasedate = '' + + %> - - - - + + + + %endfor diff --git a/data/js/script.js b/data/js/script.js index dbcf1638..3c2b3364 100755 --- a/data/js/script.js +++ b/data/js/script.js @@ -2,8 +2,11 @@ $(document).ready(function() { $('#artist_table').dataTable( { + "aoColumnDefs": [ + { "bSOtrable": false, "aTargets": [ 1 ] } ], "bStateSave": true, "iDisplayLength": 50, "sPaginationType": "full_numbers", + }); }); \ No newline at end of file From 3136439bab82a05755d5f82520f0a972c5bbfde3 Mon Sep 17 00:00:00 2001 From: Remy Date: Fri, 5 Aug 2011 13:37:14 -0700 Subject: [PATCH 05/22] Fixed header --- data/css/style.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/css/style.css b/data/css/style.css index 7a4b8b96..5b3e0bcc 100755 --- a/data/css/style.css +++ b/data/css/style.css @@ -106,7 +106,7 @@ a:active {/*colour in NN4.xx is red*/ container { } body { background-color: #EBF4FB } -header { height: 68px; padding: 0 10px 0 10px; background-color: #CDC9C9; } +header { height: 68px; width: 100%; padding: 0 10px 0 10px; background-color: #CDC9C9; position: fixed; top: 0; z-index: 999; } div#logo { float: left; } ul#nav { margin-top: 25px; float: left; white-space: nowrap; list-style-type: none; } @@ -116,6 +116,8 @@ ul#nav li a:hover { background-color: #EE0000; } div#searchbar { margin: 24px auto auto 30px; float: left; } +div#main { margin: 0; padding: 68px 0 0 0; } + table#artist_table { background-color: white; } table#artist_table th#name { text-align: center; } From f473a10b0bcf2d2e857bc806898e7ef719808354 Mon Sep 17 00:00:00 2001 From: Remy Date: Fri, 5 Aug 2011 13:53:43 -0700 Subject: [PATCH 06/22] Fixed min-widths on body and header --- data/css/style.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/css/style.css b/data/css/style.css index 5b3e0bcc..481c171f 100755 --- a/data/css/style.css +++ b/data/css/style.css @@ -105,11 +105,11 @@ a:active {/*colour in NN4.xx is red*/ container { } -body { background-color: #EBF4FB } -header { height: 68px; width: 100%; padding: 0 10px 0 10px; background-color: #CDC9C9; position: fixed; top: 0; z-index: 999; } +body { background-color: #EBF4FB; min-width: 906px; } +header { height: 68px; width: 100%; min-width: 906px; padding: 0 10px 0 10px; background-color: #CDC9C9; position: fixed; top: 0; z-index: 999; } div#logo { float: left; } -ul#nav { margin-top: 25px; float: left; white-space: nowrap; list-style-type: none; } +ul#nav { margin-top: 25px; float: left; list-style-type: none; } ul#nav li { margin: 40px 5px auto 15px; display: inline; } ul#nav li a { font-size: 16px; font-weight: bold; color: #330000; text-decoration: none; } ul#nav li a:hover { background-color: #EE0000; } From 51c151933f5e052df1615a59e3287d3037d30ebd Mon Sep 17 00:00:00 2001 From: Remy Date: Fri, 5 Aug 2011 14:31:23 -0700 Subject: [PATCH 07/22] Solid index layout, edited jquerydataTables for empty databases --- data/css/data_table.css | 9 +++++---- data/css/style.css | 14 +++++++------- data/js/libs/jquery.dataTables.min.js | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/data/css/data_table.css b/data/css/data_table.css index a1c74dce..352d8c8b 100644 --- a/data/css/data_table.css +++ b/data/css/data_table.css @@ -4,10 +4,10 @@ -moz-border-radius: 20px; width: 88%; margin: 20px auto 0 auto; - padding: 25px; + padding: 20px; background-color: white; position: relative; - min-height: 302px; + min-height: 125px; clear: both; _height: 302px; zoom: 1; /* Feeling sorry for IE */ @@ -198,7 +198,7 @@ table.display tr.even.gradeU { tr.odd { - background-color: white; + background-color: #eeeeee; } tr.even { @@ -238,7 +238,8 @@ tr.even { font-size: 24px; text-align: center; vertical-align: middle; - height: 200px; + background-color: white; + height: 50px; width: 90%; } diff --git a/data/css/style.css b/data/css/style.css index 481c171f..03d74326 100755 --- a/data/css/style.css +++ b/data/css/style.css @@ -105,14 +105,14 @@ a:active {/*colour in NN4.xx is red*/ container { } -body { background-color: #EBF4FB; min-width: 906px; } -header { height: 68px; width: 100%; min-width: 906px; padding: 0 10px 0 10px; background-color: #CDC9C9; position: fixed; top: 0; z-index: 999; } +body { background-color: #EBF4FB; min-width: 907px; } +header { height: 68px; width: 100%; min-width: 907px; padding: 0 10px 0 10px; background-color: #CDC9C9; position: fixed; top: 0; z-index: 999; } div#logo { float: left; } ul#nav { margin-top: 25px; float: left; list-style-type: none; } -ul#nav li { margin: 40px 5px auto 15px; display: inline; } +ul#nav li { margin: 40px 5px auto 12px; display: inline; } ul#nav li a { font-size: 16px; font-weight: bold; color: #330000; text-decoration: none; } -ul#nav li a:hover { background-color: #EE0000; } +ul#nav li a:hover { background-color: #E133A5; } div#searchbar { margin: 24px auto auto 30px; float: left; } @@ -120,9 +120,9 @@ div#main { margin: 0; padding: 68px 0 0 0; } table#artist_table { background-color: white; } -table#artist_table th#name { text-align: center; } -table#artist_table th#album { text-align: center; } -table#artist_table th#reldate { width: 125px; text-align: center; } +table#artist_table th#name { text-align: center; min-width: 225px; } +table#artist_table th#album { text-align: center; min-width: 200px; } +table#artist_table th#reldate { width: 175px; text-align: center; min-width: 100px; } table#artist_table th#center { text-align: center; } table#artist_table td#name { text-align: left; } table#artist_table td#album { text-align: center; } diff --git a/data/js/libs/jquery.dataTables.min.js b/data/js/libs/jquery.dataTables.min.js index fe738fc9..bbcf701f 100644 --- a/data/js/libs/jquery.dataTables.min.js +++ b/data/js/libs/jquery.dataTables.min.js @@ -32,7 +32,7 @@ l?-1:g>l?1:0},"html-desc":function(g,l){g=g.replace(/<.*?>/g,"").toLowerCase();l g},"numeric-asc":function(g,l){return(g=="-"||g===""?0:g*1)-(l=="-"||l===""?0:l*1)},"numeric-desc":function(g,l){return(l=="-"||l===""?0:l*1)-(g=="-"||g===""?0:g*1)}};o.aTypes=[function(g){if(typeof g=="number")return"numeric";else if(typeof g!="string")return null;var l,r=false;l=g.charAt(0);if("0123456789-".indexOf(l)==-1)return null;for(var s=1;s")!=-1)return"html";return null}];o.fnVersionCheck=function(g){var l=function(x,v){for(;x.length=parseInt(w,10)};o._oExternConfig={iNextUnique:0};i.fn.dataTable=function(g){function l(){this.fnRecordsTotal= function(){return this.oFeatures.bServerSide?parseInt(this._iRecordsTotal,10):this.aiDisplayMaster.length};this.fnRecordsDisplay=function(){return this.oFeatures.bServerSide?parseInt(this._iRecordsDisplay,10):this.aiDisplay.length};this.fnDisplayEnd=function(){return this.oFeatures.bServerSide?this.oFeatures.bPaginate===false||this._iDisplayLength==-1?this._iDisplayStart+this.aiDisplay.length:Math.min(this._iDisplayStart+this._iDisplayLength,this._iRecordsDisplay):this._iDisplayEnd};this.sInstance= -this.oInstance=null;this.oFeatures={bPaginate:true,bLengthChange:true,bFilter:true,bSort:true,bInfo:true,bAutoWidth:true,bProcessing:false,bSortClasses:true,bStateSave:false,bServerSide:false,bDeferRender:false};this.oScroll={sX:"",sXInner:"",sY:"",bCollapse:false,bInfinite:false,iLoadGap:100,iBarWidth:0,bAutoCss:true};this.aanFeatures=[];this.oLanguage={sProcessing:"Processing...",sLengthMenu:"Show _MENU_ artists",sZeroRecords:"No matching records found",sEmptyTable:"Add some artists to the database!", +this.oInstance=null;this.oFeatures={bPaginate:true,bLengthChange:true,bFilter:true,bSort:true,bInfo:true,bAutoWidth:true,bProcessing:false,bSortClasses:true,bStateSave:false,bServerSide:false,bDeferRender:false};this.oScroll={sX:"",sXInner:"",sY:"",bCollapse:false,bInfinite:false,iLoadGap:100,iBarWidth:0,bAutoCss:true};this.aanFeatures=[];this.oLanguage={sProcessing:"Processing...",sLengthMenu:"Show _MENU_ artists",sZeroRecords:"No matching records found",sEmptyTable:"", sLoadingRecords:"Loading...",sInfo:"Showing _START_ to _END_ of _TOTAL_ artists",sInfoEmpty:"Showing 0 to 0 of 0 artists",sInfoFiltered:"(filtered from _MAX_ total artists)",sInfoPostFix:"",sSearch:"Filter:",sUrl:"",oPaginate:{sFirst:"First",sPrevious:"Previous",sNext:"Next",sLast:"Last"},fnInfoCallback:null};this.aoData=[];this.aiDisplay=[];this.aiDisplayMaster=[];this.aoColumns=[];this.aoHeader=[];this.aoFooter=[];this.iNextId=0;this.asDataSearch=[];this.oPreviousSearch={sSearch:"",bRegex:false, bSmart:true};this.aoPreSearchCols=[];this.aaSorting=[[0,"asc",0]];this.aaSortingFixed=null;this.asStripClasses=[];this.asDestoryStrips=[];this.sDestroyWidth=0;this.fnFooterCallback=this.fnHeaderCallback=this.fnRowCallback=null;this.aoDrawCallback=[];this.fnInitComplete=this.fnPreDrawCallback=null;this.sTableId="";this.nTableWrapper=this.nTBody=this.nTFoot=this.nTHead=this.nTable=null;this.bInitialised=this.bDeferLoading=false;this.aoOpenRows=[];this.sDom="lfrtip";this.sPaginationType="two_button"; this.iCookieDuration=7200;this.sCookiePrefix="SpryMedia_DataTables_";this.fnCookieCallback=null;this.aoStateSave=[];this.aoStateLoad=[];this.sAjaxSource=this.oLoadedState=null;this.sAjaxDataProp="aaData";this.bAjaxDataGet=true;this.jqXHR=null;this.fnServerData=function(a,b,c,d){d.jqXHR=i.ajax({url:a,data:b,success:c,dataType:"json",cache:false,error:function(f,e){e=="parsererror"&&alert("DataTables warning: JSON data from server could not be parsed. This is caused by a JSON formatting error.")}})}; From 996ad22b6c7e103cf9ba84d287f73ccbf3492b8e Mon Sep 17 00:00:00 2001 From: Remy Date: Fri, 5 Aug 2011 14:49:19 -0700 Subject: [PATCH 08/22] Fixed data_table min height, changed artists -> artists per page and total artists (dataTables.min.js) --- data/css/data_table.css | 2 +- data/js/libs/jquery.dataTables.min.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/css/data_table.css b/data/css/data_table.css index 352d8c8b..eb3bc040 100644 --- a/data/css/data_table.css +++ b/data/css/data_table.css @@ -7,7 +7,7 @@ padding: 20px; background-color: white; position: relative; - min-height: 125px; + min-height: 155px; clear: both; _height: 302px; zoom: 1; /* Feeling sorry for IE */ diff --git a/data/js/libs/jquery.dataTables.min.js b/data/js/libs/jquery.dataTables.min.js index bbcf701f..adb45376 100644 --- a/data/js/libs/jquery.dataTables.min.js +++ b/data/js/libs/jquery.dataTables.min.js @@ -32,8 +32,8 @@ l?-1:g>l?1:0},"html-desc":function(g,l){g=g.replace(/<.*?>/g,"").toLowerCase();l g},"numeric-asc":function(g,l){return(g=="-"||g===""?0:g*1)-(l=="-"||l===""?0:l*1)},"numeric-desc":function(g,l){return(l=="-"||l===""?0:l*1)-(g=="-"||g===""?0:g*1)}};o.aTypes=[function(g){if(typeof g=="number")return"numeric";else if(typeof g!="string")return null;var l,r=false;l=g.charAt(0);if("0123456789-".indexOf(l)==-1)return null;for(var s=1;s")!=-1)return"html";return null}];o.fnVersionCheck=function(g){var l=function(x,v){for(;x.length=parseInt(w,10)};o._oExternConfig={iNextUnique:0};i.fn.dataTable=function(g){function l(){this.fnRecordsTotal= function(){return this.oFeatures.bServerSide?parseInt(this._iRecordsTotal,10):this.aiDisplayMaster.length};this.fnRecordsDisplay=function(){return this.oFeatures.bServerSide?parseInt(this._iRecordsDisplay,10):this.aiDisplay.length};this.fnDisplayEnd=function(){return this.oFeatures.bServerSide?this.oFeatures.bPaginate===false||this._iDisplayLength==-1?this._iDisplayStart+this.aiDisplay.length:Math.min(this._iDisplayStart+this._iDisplayLength,this._iRecordsDisplay):this._iDisplayEnd};this.sInstance= -this.oInstance=null;this.oFeatures={bPaginate:true,bLengthChange:true,bFilter:true,bSort:true,bInfo:true,bAutoWidth:true,bProcessing:false,bSortClasses:true,bStateSave:false,bServerSide:false,bDeferRender:false};this.oScroll={sX:"",sXInner:"",sY:"",bCollapse:false,bInfinite:false,iLoadGap:100,iBarWidth:0,bAutoCss:true};this.aanFeatures=[];this.oLanguage={sProcessing:"Processing...",sLengthMenu:"Show _MENU_ artists",sZeroRecords:"No matching records found",sEmptyTable:"", -sLoadingRecords:"Loading...",sInfo:"Showing _START_ to _END_ of _TOTAL_ artists",sInfoEmpty:"Showing 0 to 0 of 0 artists",sInfoFiltered:"(filtered from _MAX_ total artists)",sInfoPostFix:"",sSearch:"Filter:",sUrl:"",oPaginate:{sFirst:"First",sPrevious:"Previous",sNext:"Next",sLast:"Last"},fnInfoCallback:null};this.aoData=[];this.aiDisplay=[];this.aiDisplayMaster=[];this.aoColumns=[];this.aoHeader=[];this.aoFooter=[];this.iNextId=0;this.asDataSearch=[];this.oPreviousSearch={sSearch:"",bRegex:false, +this.oInstance=null;this.oFeatures={bPaginate:true,bLengthChange:true,bFilter:true,bSort:true,bInfo:true,bAutoWidth:true,bProcessing:false,bSortClasses:true,bStateSave:false,bServerSide:false,bDeferRender:false};this.oScroll={sX:"",sXInner:"",sY:"",bCollapse:false,bInfinite:false,iLoadGap:100,iBarWidth:0,bAutoCss:true};this.aanFeatures=[];this.oLanguage={sProcessing:"Processing...",sLengthMenu:"Show _MENU_ artists per page",sZeroRecords:"No matching records found",sEmptyTable:"", +sLoadingRecords:"Loading...",sInfo:"Showing _START_ to _END_ of _TOTAL_ total artists",sInfoEmpty:"Showing 0 to 0 of 0 artists",sInfoFiltered:"(filtered from _MAX_ total artists)",sInfoPostFix:"",sSearch:"Filter:",sUrl:"",oPaginate:{sFirst:"First",sPrevious:"Previous",sNext:"Next",sLast:"Last"},fnInfoCallback:null};this.aoData=[];this.aiDisplay=[];this.aiDisplayMaster=[];this.aoColumns=[];this.aoHeader=[];this.aoFooter=[];this.iNextId=0;this.asDataSearch=[];this.oPreviousSearch={sSearch:"",bRegex:false, bSmart:true};this.aoPreSearchCols=[];this.aaSorting=[[0,"asc",0]];this.aaSortingFixed=null;this.asStripClasses=[];this.asDestoryStrips=[];this.sDestroyWidth=0;this.fnFooterCallback=this.fnHeaderCallback=this.fnRowCallback=null;this.aoDrawCallback=[];this.fnInitComplete=this.fnPreDrawCallback=null;this.sTableId="";this.nTableWrapper=this.nTBody=this.nTFoot=this.nTHead=this.nTable=null;this.bInitialised=this.bDeferLoading=false;this.aoOpenRows=[];this.sDom="lfrtip";this.sPaginationType="two_button"; this.iCookieDuration=7200;this.sCookiePrefix="SpryMedia_DataTables_";this.fnCookieCallback=null;this.aoStateSave=[];this.aoStateLoad=[];this.sAjaxSource=this.oLoadedState=null;this.sAjaxDataProp="aaData";this.bAjaxDataGet=true;this.jqXHR=null;this.fnServerData=function(a,b,c,d){d.jqXHR=i.ajax({url:a,data:b,success:c,dataType:"json",cache:false,error:function(f,e){e=="parsererror"&&alert("DataTables warning: JSON data from server could not be parsed. This is caused by a JSON formatting error.")}})}; this.fnFormatNumber=function(a){if(a<1E3)return a;else{var b=a+"";a=b.split("");var c="";b=b.length;for(var d=0;d Date: Fri, 5 Aug 2011 16:13:53 -0700 Subject: [PATCH 09/22] css fixes for index --- data/css/data_table.css | 2 +- data/css/style.css | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/data/css/data_table.css b/data/css/data_table.css index eb3bc040..9e9b9320 100644 --- a/data/css/data_table.css +++ b/data/css/data_table.css @@ -4,7 +4,7 @@ -moz-border-radius: 20px; width: 88%; margin: 20px auto 0 auto; - padding: 20px; + padding: 22px; background-color: white; position: relative; min-height: 155px; diff --git a/data/css/style.css b/data/css/style.css index 03d74326..d26525d0 100755 --- a/data/css/style.css +++ b/data/css/style.css @@ -112,7 +112,7 @@ div#logo { float: left; } ul#nav { margin-top: 25px; float: left; list-style-type: none; } ul#nav li { margin: 40px 5px auto 12px; display: inline; } ul#nav li a { font-size: 16px; font-weight: bold; color: #330000; text-decoration: none; } -ul#nav li a:hover { background-color: #E133A5; } +ul#nav li a:hover { background-color: #a3e532; } div#searchbar { margin: 24px auto auto 30px; float: left; } @@ -120,16 +120,17 @@ div#main { margin: 0; padding: 68px 0 0 0; } table#artist_table { background-color: white; } -table#artist_table th#name { text-align: center; min-width: 225px; } -table#artist_table th#album { text-align: center; min-width: 200px; } +table#artist_table th#name { text-align: left; min-width: 225px; } +table#artist_table th#album { text-align: center; min-width: 250px; } table#artist_table th#reldate { width: 175px; text-align: center; min-width: 100px; } table#artist_table th#center { text-align: center; } -table#artist_table td#name { text-align: left; } -table#artist_table td#album { text-align: center; } -table#artist_table td#reldate { width: 125px; text-align: center; } +table#artist_table td#name { vertical-align: middle; text-align: left; } +table#artist_table td#album { vertical-align: middle; text-align: center; } +table#artist_table td#reldate { vertical-align: middle; width: 125px; text-align: center; } +table#artist_table td#have { vertical-align: middle; text-align: center; } div.progress-container { border: 1px solid #ccc; width: 100px; height: 14px; margin: 2px 5px 2px 0; padding: 1px; float: left; background: white; } -div.progress-container > div { background-color: #ACE97C; height: 14px; } +div.progress-container > div { background-color: #a3e532; height: 14px; } .havetracks { font-size: 13px; margin-left: 36px; } footer { margin: 20px auto 20px auto; } From fe1667a2e000eea6786ae60579de47e49c89d26e Mon Sep 17 00:00:00 2001 From: Remy Date: Fri, 5 Aug 2011 22:20:36 -0700 Subject: [PATCH 10/22] Added artist page and log page, fixed some css, added logline parse to helpers --- data/css/data_table.css | 6 +- data/css/style.css | 56 +++++++++---- data/interfaces/default/artist.html | 68 +++++++++++++++ data/interfaces/default/base.html | 7 +- data/interfaces/default/index.html | 18 +++- data/interfaces/default/logs.html | 56 +++++++++++++ data/js/libs/jquery.dataTables.min.js | 2 +- data/js/script.js | 12 --- headphones/helpers.py | 15 +++- headphones/templates.py | 2 +- headphones/webserve.py | 116 +------------------------- 11 files changed, 210 insertions(+), 148 deletions(-) create mode 100644 data/interfaces/default/artist.html create mode 100644 data/interfaces/default/logs.html diff --git a/data/css/data_table.css b/data/css/data_table.css index 9e9b9320..801352ac 100644 --- a/data/css/data_table.css +++ b/data/css/data_table.css @@ -4,7 +4,7 @@ -moz-border-radius: 20px; width: 88%; margin: 20px auto 0 auto; - padding: 22px; + padding: 25px; background-color: white; position: relative; min-height: 155px; @@ -42,7 +42,7 @@ } .dataTables_info { - width: 40%; + width: 50%; float: left; background-color: white; font-weight: bold; @@ -86,7 +86,7 @@ * DataTables display */ table.display { - margin: 20px auto; + margin: 20px auto; clear: both; /* Note Firefox 3.5 and before have a bug with border-collapse diff --git a/data/css/style.css b/data/css/style.css index d26525d0..3e7b5ba5 100755 --- a/data/css/style.css +++ b/data/css/style.css @@ -66,8 +66,8 @@ input:valid, textarea:valid { } input:invalid, textarea:invalid { border-radius: 1px; -moz-box-shadow: 0px 0px 5px red; -webkit-box-shadow: 0px 0px 5px red; box-shadow: 0px 0px 5px red; } .no-boxshadow input:invalid, .no-boxshadow textarea:invalid { background-color: #f0dddd; } -::-moz-selection{ background: #FF5E99; color:#fff; text-shadow: none; } -::selection { background:#FF5E99; color:#fff; text-shadow: none; } +::-moz-selection{ background: grey; color:#fff; text-shadow: none; } +::selection { background: grey; color:#fff; text-shadow: none; } a:link { -webkit-tap-highlight-color: #FF5E99; } button { width: auto; overflow: visible; } @@ -110,28 +110,56 @@ header { height: 68px; width: 100%; min-width: 907px; padding: 0 10px 0 10px; ba div#logo { float: left; } ul#nav { margin-top: 25px; float: left; list-style-type: none; } -ul#nav li { margin: 40px 5px auto 12px; display: inline; } -ul#nav li a { font-size: 16px; font-weight: bold; color: #330000; text-decoration: none; } +ul#nav li { margin: 40px 0px auto 10px; display: inline; } +ul#nav li a { padding: 5px; font-size: 16px; font-weight: bold; color: #330000; text-decoration: none; } ul#nav li a:hover { background-color: #a3e532; } -div#searchbar { margin: 24px auto auto 30px; float: left; } +div#subhead_container { margin-top: 68px; height: 30px; width:100%; min-width: 907px; background-color:#330000; position: fixed; float: left; list-style-type: none; top: 0; z-index: 999; overflow: hidden; } +ul#subhead_menu { margin-top: 5px; } +ul#subhead_menu li { width: 100%; height: 100%; display: inline; } +ul#subhead_menu li a { padding: 5px 15px 10px 15px; vertical-align: middle; color: white; font-size: 16px; text-decoration: none; } +ul#subhead_menu li a:hover { width: 100%; height: 100%; background-color: #grey; } -div#main { margin: 0; padding: 68px 0 0 0; } +div#searchbar { margin: 24px 30px auto auto; float: right; } + +div#main { margin: 0; padding: 80px 0 0 0; } table#artist_table { background-color: white; } -table#artist_table th#name { text-align: left; min-width: 225px; } -table#artist_table th#album { text-align: center; min-width: 250px; } +table#artist_table th#name { text-align: left; min-width: 200px; } +table#artist_table th#album { text-align: center; min-width: 300px; } table#artist_table th#reldate { width: 175px; text-align: center; min-width: 100px; } -table#artist_table th#center { text-align: center; } -table#artist_table td#name { vertical-align: middle; text-align: left; } -table#artist_table td#album { vertical-align: middle; text-align: center; } -table#artist_table td#reldate { vertical-align: middle; width: 125px; text-align: center; } -table#artist_table td#have { vertical-align: middle; text-align: center; } +table#artist_table th#have { text-align: center; } +table#artist_table td#name { vertical-align: middle; text-align: left; min-width:200px; } +table#artist_table td#album { vertical-align: middle; text-align: center; min-width: 300px; } +table#artist_table td#reldate { vertical-align: middle; text-align: center; min-width: 100px; } +table#artist_table td#have { vertical-align: middle; } + +div#artistheader { padding-top: 48px; font-size: 24px; font-weight: bold; text-align: center; } +table#album_table { background-color: white; } + +table#album_table th#albumart { text-align: left; min-width: 50px; } +table#album_table th#albumname { text-align: center; min-width: 150px; } +table#album_table th#reldate { width: 175px; text-align: center; min-width: 100px; } +table#album_table th#status { width: 175px; text-align: center; min-width: 100px; } +table#album_table th#type { width: 175px; text-align: center; min-width: 100px; } +table#album_table td#albumart { vertical-align: middle; text-align: left; } +table#album_table td#albumname { vertical-align: middle; text-align: center; } +table#album_table td#reldate { vertical-align: middle; text-align: center; } +table#album_table td#status { vertical-align: middle; text-align: center; } +table#album_table td#type { vertical-align: middle; text-align: center; } +table#album_table td#have { vertical-align: middle; } + +table#log_table { background-color: white; } + +table#log_table th#timestamp { text-align: left; min-width: 100px; } +table#log_table th#level { text-align: left; min-width: 100px; } +table#log_table th#thread { text-align: left; min-width: 100px; } +table#log_table th#message { text-align: left; min-width: 200px; } div.progress-container { border: 1px solid #ccc; width: 100px; height: 14px; margin: 2px 5px 2px 0; padding: 1px; float: left; background: white; } div.progress-container > div { background-color: #a3e532; height: 14px; } -.havetracks { font-size: 13px; margin-left: 36px; } +.havetracks { font-size: 13px; margin-left: 36px; padding-bottom: 3px; vertical-align: middle; } footer { margin: 20px auto 20px auto; } div#version { text-align: center; font-weight: bold; } diff --git a/data/interfaces/default/artist.html b/data/interfaces/default/artist.html new file mode 100644 index 00000000..f2f5cb8e --- /dev/null +++ b/data/interfaces/default/artist.html @@ -0,0 +1,68 @@ +<%inherit file="base.html"/> + +<%def name="subhead()"> + + + +<%def name="body()"> +
+ ${artist['ArtistName']} +
+
Artist NameStatusLatest AlbumHaveArtist NameStatusLatest AlbumHave
Artist NameStatusLatest AlbumLatest AlbumRelease Date Have
${artist['ArtistName']}${artist['Status']}${artist['LatestAlbum']}${artist['HaveTracks']}/${artist['TotalTracks']}${artist['ArtistName']} ${artist['LatestAlbum']}${releasedate}
${havetracks}/${totaltracks}
+ + + + + + + + + + + + %for album in albums: + + + + + + + + + %endfor + +
Album NameRelease DateRelease TypeStatusHave
${album['AlbumTitle']}${album['ReleaseDate']}${album['Type']}${album['Status']}
tracks
+ + +<%def name="headerIncludes()"> + + + +<%def name="javascriptIncludes()"> + + + \ No newline at end of file diff --git a/data/interfaces/default/base.html b/data/interfaces/default/base.html index e543c04a..bfc4cd60 100755 --- a/data/interfaces/default/base.html +++ b/data/interfaces/default/base.html @@ -46,7 +46,9 @@ - +
+ ${next.subhead()} +
${next.body()}
@@ -81,4 +83,5 @@ <%def name="javascriptIncludes()"> -<%def name="headerIncludes()"> \ No newline at end of file +<%def name="headerIncludes()"> +<%def name="subhead()"> \ No newline at end of file diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index 8204c1f4..9a2a89bc 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -35,8 +35,8 @@ %> - ${artist['ArtistName']} - ${artist['LatestAlbum']} + ${artist['ArtistName']} + ${artist['LatestAlbum']} ${releasedate}
${havetracks}/${totaltracks}
@@ -51,4 +51,18 @@ <%def name="javascriptIncludes()"> + \ No newline at end of file diff --git a/data/interfaces/default/logs.html b/data/interfaces/default/logs.html new file mode 100644 index 00000000..ec79540c --- /dev/null +++ b/data/interfaces/default/logs.html @@ -0,0 +1,56 @@ +<%inherit file="base.html"/> + +<%def name="body()"> + + + + + + + + + + + %for line in lineList: + <% + from headphones import helpers + timestamp, level, thread, message = helpers.extract_logline(line) + %> + %if timestamp and level and thread and message: + + + + + + + %endif + %endfor + +
TimestampLevelThreadMessage
${timestamp}${level}${thread}${message.decode('utf-8')}
+ + +<%def name="headerIncludes()"> + + + +<%def name="javascriptIncludes()"> + + + \ No newline at end of file diff --git a/data/js/libs/jquery.dataTables.min.js b/data/js/libs/jquery.dataTables.min.js index adb45376..e5b480bd 100644 --- a/data/js/libs/jquery.dataTables.min.js +++ b/data/js/libs/jquery.dataTables.min.js @@ -33,7 +33,7 @@ g},"numeric-asc":function(g,l){return(g=="-"||g===""?0:g*1)-(l=="-"||l===""?0:l* if(l!==null&&!isNaN(l)||typeof g=="string"&&g.length===0)return"date";return null},function(g){if(typeof g=="string"&&g.indexOf("<")!=-1&&g.indexOf(">")!=-1)return"html";return null}];o.fnVersionCheck=function(g){var l=function(x,v){for(;x.length=parseInt(w,10)};o._oExternConfig={iNextUnique:0};i.fn.dataTable=function(g){function l(){this.fnRecordsTotal= function(){return this.oFeatures.bServerSide?parseInt(this._iRecordsTotal,10):this.aiDisplayMaster.length};this.fnRecordsDisplay=function(){return this.oFeatures.bServerSide?parseInt(this._iRecordsDisplay,10):this.aiDisplay.length};this.fnDisplayEnd=function(){return this.oFeatures.bServerSide?this.oFeatures.bPaginate===false||this._iDisplayLength==-1?this._iDisplayStart+this.aiDisplay.length:Math.min(this._iDisplayStart+this._iDisplayLength,this._iRecordsDisplay):this._iDisplayEnd};this.sInstance= this.oInstance=null;this.oFeatures={bPaginate:true,bLengthChange:true,bFilter:true,bSort:true,bInfo:true,bAutoWidth:true,bProcessing:false,bSortClasses:true,bStateSave:false,bServerSide:false,bDeferRender:false};this.oScroll={sX:"",sXInner:"",sY:"",bCollapse:false,bInfinite:false,iLoadGap:100,iBarWidth:0,bAutoCss:true};this.aanFeatures=[];this.oLanguage={sProcessing:"Processing...",sLengthMenu:"Show _MENU_ artists per page",sZeroRecords:"No matching records found",sEmptyTable:"", -sLoadingRecords:"Loading...",sInfo:"Showing _START_ to _END_ of _TOTAL_ total artists",sInfoEmpty:"Showing 0 to 0 of 0 artists",sInfoFiltered:"(filtered from _MAX_ total artists)",sInfoPostFix:"",sSearch:"Filter:",sUrl:"",oPaginate:{sFirst:"First",sPrevious:"Previous",sNext:"Next",sLast:"Last"},fnInfoCallback:null};this.aoData=[];this.aiDisplay=[];this.aiDisplayMaster=[];this.aoColumns=[];this.aoHeader=[];this.aoFooter=[];this.iNextId=0;this.asDataSearch=[];this.oPreviousSearch={sSearch:"",bRegex:false, +sLoadingRecords:"Loading...",sInfo:"Showing _START_ to _END_ of _TOTAL_ artists",sInfoEmpty:"Showing 0 to 0 of 0 artists",sInfoFiltered:"(filtered from _MAX_ total artists)",sInfoPostFix:"",sSearch:"Filter:",sUrl:"",oPaginate:{sFirst:"First",sPrevious:"Previous",sNext:"Next",sLast:"Last"},fnInfoCallback:null};this.aoData=[];this.aiDisplay=[];this.aiDisplayMaster=[];this.aoColumns=[];this.aoHeader=[];this.aoFooter=[];this.iNextId=0;this.asDataSearch=[];this.oPreviousSearch={sSearch:"",bRegex:false, bSmart:true};this.aoPreSearchCols=[];this.aaSorting=[[0,"asc",0]];this.aaSortingFixed=null;this.asStripClasses=[];this.asDestoryStrips=[];this.sDestroyWidth=0;this.fnFooterCallback=this.fnHeaderCallback=this.fnRowCallback=null;this.aoDrawCallback=[];this.fnInitComplete=this.fnPreDrawCallback=null;this.sTableId="";this.nTableWrapper=this.nTBody=this.nTFoot=this.nTHead=this.nTable=null;this.bInitialised=this.bDeferLoading=false;this.aoOpenRows=[];this.sDom="lfrtip";this.sPaginationType="two_button"; this.iCookieDuration=7200;this.sCookiePrefix="SpryMedia_DataTables_";this.fnCookieCallback=null;this.aoStateSave=[];this.aoStateLoad=[];this.sAjaxSource=this.oLoadedState=null;this.sAjaxDataProp="aaData";this.bAjaxDataGet=true;this.jqXHR=null;this.fnServerData=function(a,b,c,d){d.jqXHR=i.ajax({url:a,data:b,success:c,dataType:"json",cache:false,error:function(f,e){e=="parsererror"&&alert("DataTables warning: JSON data from server could not be parsed. This is caused by a JSON formatting error.")}})}; this.fnFormatNumber=function(a){if(a<1E3)return a;else{var b=a+"";a=b.split("");var c="";b=b.length;for(var d=0;d.*?)\s\-\s(?P.*?)\s*\:\:\s(?P.*?)\s\:\s(?P.*)', re.VERBOSE) + match = pattern.match(s) + if match: + timestamp = match.group("timestamp") + level = match.group("level") + thread = match.group("thread") + message = match.group("message") + return (timestamp, level, thread, message) + else: + return None \ No newline at end of file diff --git a/headphones/templates.py b/headphones/templates.py index 8f2b3a77..4d399dc2 100644 --- a/headphones/templates.py +++ b/headphones/templates.py @@ -412,7 +412,7 @@ def displayAlbums(ArtistID, Type=None): else: newStatus = '%s' % (results[i][3]) page.append(''' - + %s (link) %s diff --git a/headphones/webserve.py b/headphones/webserve.py index 029805c4..08792d57 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -37,110 +37,11 @@ class WebInterface(object): return serve_template(templatename="index.html", title="Home", artists=artists) home.exposed = True - def homeold(self): - page = [templates._header] - if not headphones.CURRENT_VERSION: - page.append('''
You're running an unknown version of Headphones. Click here to update
''') - elif headphones.CURRENT_VERSION != headphones.LATEST_VERSION and headphones.INSTALL_TYPE != 'win': - page.append('''
A - newer version is available. You're %s commits behind. Click here to update
- ''' % (headphones.CURRENT_VERSION, headphones.LATEST_VERSION, headphones.COMMITS_BEHIND)) - page.append(templates._logobar) - page.append(templates._nav) - myDB = db.DBConnection() - results = myDB.select('SELECT ArtistName, ArtistID, Status, LatestAlbum, ReleaseDate, AlbumID, TotalTracks, HaveTracks from artists order by ArtistSortName collate nocase') - if len(results): - page.append('''
- - - - - - ''') - for artist in results: - totaltracks = artist['TotalTracks'] - havetracks = artist['HaveTracks'] - if not havetracks: - havetracks = 0 - try: - percent = (havetracks*100.0)/totaltracks - if percent > 100: - percent = 100 - except (ZeroDivisionError, TypeError): - percent = 0 - totaltracks = '?' - - if artist['LatestAlbum']: - if artist['ReleaseDate'] > helpers.today(): - newalbumName = '%s' % (artist['AlbumID'], artist['LatestAlbum']) - releaseDate = '(%s)' % artist['ReleaseDate'] - else: - newalbumName = '%s' % (artist['AlbumID'], artist['LatestAlbum']) - releaseDate = "" - else: - newalbumName = 'None' - releaseDate = "" - - if artist['Status'] == 'Paused': - newStatus = '''%s(resume)''' % (artist['Status'], artist['ArtistID']) - elif artist['Status'] == 'Loading': - newStatus = '''Loading...''' - else: - newStatus = '''%s(pause)''' % (artist['Status'], artist['ArtistID']) - - page.append(''' - - - - ''' % (artist['ArtistID'], artist['ArtistName'], artist['ArtistID'], - artist['ArtistID'], newStatus, newalbumName, releaseDate, - percent, havetracks, totaltracks)) - - page.append('''
Artist NameStatusUpcoming AlbumsHave
%s - (link) [delete]%s%s %s
%s/%s
''') - page.append(templates._footer % headphones.CURRENT_VERSION) - - else: - have = myDB.select('SELECT ArtistName from have') - if len(have): - page.append("""
Scanning...
""") - else: - page.append("""
Add some artists to the database!
""") - return page - home.exposed = True - - def artistPage(self, ArtistID): - page = [templates._header] - page.append(templates._logobar) - page.append(templates._nav) myDB = db.DBConnection() - - artist = myDB.select('SELECT ArtistName, IncludeExtras, Status from artists WHERE ArtistID=?', [ArtistID]) - while not artist: - time.sleep(1) - page.append('''

%s

- ''' % artist[0][0]) - if artist[0][2] == 'Loading': - page.append('

Loading...

') - - if templates.displayAlbums(ArtistID, 'Album'): - page.append(templates.displayAlbums(ArtistID, 'Album')) - - releasetypes = ['Compilation', 'EP', 'Single', 'Live', 'Remix'] - - for type in releasetypes: - if templates.displayAlbums(ArtistID, type): - page.append(templates.displayAlbums(ArtistID, type)) - - page.append('
') - - if not artist[0][1]: - page.append('''
''' - % (ArtistID, artist[0][0])) - - page.append(templates._footer % headphones.CURRENT_VERSION) - return page + artist = myDB.action('SELECT * FROM artists WHERE ArtistID=?', [ArtistID]).fetchone() + albums = myDB.select('SELECT * from albums WHERE ArtistID=? order by ReleaseDate DESC', [ArtistID]) + return serve_template(templatename="artist.html", title=artist['ArtistName'], artist=artist, albums=albums) artistPage.exposed = True @@ -524,22 +425,13 @@ class WebInterface(object): history.exposed = True def logs(self): - page = [templates._header] - page.append(templates._logobar) - page.append(templates._nav) - page.append('''

''') log_file = os.path.join(headphones.LOG_DIR, 'headphones.log') if os.path.isfile(log_file): fileHandle = open(log_file) lineList = fileHandle.readlines() fileHandle.close() lineList.reverse() - for line in lineList[1:200]: - page.append(line.decode('utf-8') + '

') - page.append('''

''') - page.append(templates._footer % headphones.CURRENT_VERSION) - return page - + return serve_template(templatename="logs.html", title="Log", lineList=lineList[0:1000]) logs.exposed = True def clearhistory(self): From 644344cb27ae7fc1b021af21f649cbe875c4ac0f Mon Sep 17 00:00:00 2001 From: Remy Date: Sat, 6 Aug 2011 01:25:47 -0700 Subject: [PATCH 11/22] Added log, history templates, changed some css, included updatebar, subheadings --- data/css/style.css | 28 ++++++------ data/interfaces/default/artist.html | 6 +-- data/interfaces/default/base.html | 22 ++++++--- data/interfaces/default/history.html | 64 ++++++++++++++++++++++++++ data/interfaces/default/index.html | 12 +++-- data/interfaces/default/logs.html | 6 ++- data/interfaces/default/restart.html | 9 ++++ data/interfaces/default/shutdown.html | 9 ++++ data/interfaces/default/update.html | 9 ++++ data/js/plugins.js | 14 ++++++ headphones/webserve.py | 65 +++------------------------ 11 files changed, 157 insertions(+), 87 deletions(-) create mode 100644 data/interfaces/default/history.html create mode 100644 data/interfaces/default/restart.html create mode 100644 data/interfaces/default/shutdown.html create mode 100644 data/interfaces/default/update.html diff --git a/data/css/style.css b/data/css/style.css index 3e7b5ba5..d367b209 100755 --- a/data/css/style.css +++ b/data/css/style.css @@ -1,6 +1,3 @@ - -/* ==== Scroll down to find where to put your styles :) ==== */ - /* HTML5 ✰ Boilerplate */ html, body, div, span, object, iframe, @@ -68,20 +65,17 @@ input:invalid, textarea:invalid { border-radius: 1px; -moz-box-shadow: 0px 0px 5 ::-moz-selection{ background: grey; color:#fff; text-shadow: none; } ::selection { background: grey; color:#fff; text-shadow: none; } -a:link { -webkit-tap-highlight-color: #FF5E99; } button { width: auto; overflow: visible; } .ie7 img { -ms-interpolation-mode: bicubic; } body, select, input, textarea { color: #444; } h1, h2, h3, h4, h5, h6 { font-weight: bold; } -a, a:active, a:visited { color: #607890; } -a:hover { color: #036; } /* // ========================================== \\ || || - || Your styles ! || + || Custom Styles || || || \\ ========================================== // */ @@ -102,19 +96,25 @@ a:active {/*colour in NN4.xx is red*/ color: #5E2612; text-decoration: underline; } +a.blue { + color: blue; + } container { } body { background-color: #EBF4FB; min-width: 907px; } -header { height: 68px; width: 100%; min-width: 907px; padding: 0 10px 0 10px; background-color: #CDC9C9; position: fixed; top: 0; z-index: 999; } -div#logo { float: left; } -ul#nav { margin-top: 25px; float: left; list-style-type: none; } +header { min-height: 68px; width: 100%; min-width: 907px; padding-left: 0px; padding-right: 10px; background-color: #CDC9C9; position: fixed; z-index: 998; } + +div#updatebar { text-align: center; min-width: 970px; width: 100%; background-color: light-blue; float: left; } +div#logo { float: left; padding-left: 10px; } + +ul#nav { margin: 25px 0 0 0; float: left; list-style-type: none; } ul#nav li { margin: 40px 0px auto 10px; display: inline; } ul#nav li a { padding: 5px; font-size: 16px; font-weight: bold; color: #330000; text-decoration: none; } ul#nav li a:hover { background-color: #a3e532; } -div#subhead_container { margin-top: 68px; height: 30px; width:100%; min-width: 907px; background-color:#330000; position: fixed; float: left; list-style-type: none; top: 0; z-index: 999; overflow: hidden; } +div#subhead_container { height: 30px; width:100%; min-width: 907px; background-color:#330000; float: left; list-style-type: none; z-index: 998; overflow: hidden; } ul#subhead_menu { margin-top: 5px; } ul#subhead_menu li { width: 100%; height: 100%; display: inline; } ul#subhead_menu li a { padding: 5px 15px 10px 15px; vertical-align: middle; color: white; font-size: 16px; text-decoration: none; } @@ -124,7 +124,7 @@ div#searchbar { margin: 24px 30px auto auto; float: right; } div#main { margin: 0; padding: 80px 0 0 0; } -table#artist_table { background-color: white; } +table#artist_table { background-color: white; width: 88%; padding: 20px; } table#artist_table th#name { text-align: left; min-width: 200px; } table#artist_table th#album { text-align: center; min-width: 300px; } @@ -135,7 +135,7 @@ table#artist_table td#album { vertical-align: middle; text-align: center; min-wi table#artist_table td#reldate { vertical-align: middle; text-align: center; min-width: 100px; } table#artist_table td#have { vertical-align: middle; } -div#artistheader { padding-top: 48px; font-size: 24px; font-weight: bold; text-align: center; } +div#paddingheader { padding-top: 48px; font-size: 24px; font-weight: bold; text-align: center; } table#album_table { background-color: white; } table#album_table th#albumart { text-align: left; min-width: 50px; } @@ -150,6 +150,8 @@ table#album_table td#status { vertical-align: middle; text-align: center; } table#album_table td#type { vertical-align: middle; text-align: center; } table#album_table td#have { vertical-align: middle; } +table#history_table { background-color: white; width: 88%; } + table#log_table { background-color: white; } table#log_table th#timestamp { text-align: left; min-width: 100px; } diff --git a/data/interfaces/default/artist.html b/data/interfaces/default/artist.html index f2f5cb8e..cf4d1d1a 100644 --- a/data/interfaces/default/artist.html +++ b/data/interfaces/default/artist.html @@ -1,6 +1,6 @@ <%inherit file="base.html"/> -<%def name="subhead()"> +<%def name="headerIncludes()">
  • Refresh Artist
  • @@ -11,7 +11,7 @@ <%def name="body()"> -
    +
    ${artist['ArtistName']}
    @@ -40,7 +40,7 @@
    -<%def name="headerIncludes()"> +<%def name="headIncludes()"> diff --git a/data/interfaces/default/base.html b/data/interfaces/default/base.html index bfc4cd60..10c0491a 100755 --- a/data/interfaces/default/base.html +++ b/data/interfaces/default/base.html @@ -20,13 +20,22 @@ - ${next.headerIncludes()} + ${next.headIncludes()}
    + % if not headphones.CURRENT_VERSION: +
    + You're running an unknown version of Headphones. Click here to update +
    + % elif headphones.CURRENT_VERSION != headphones.LATEST_VERSION and headphones.INSTALL_TYPE != 'win': +
    + A newer version is available. You're ${headphones.COMMITS_BEHIND} commits behind. Click here to update +
    + % endif @@ -45,10 +54,11 @@
    +
    + ${next.headerIncludes()} +
    -
    - ${next.subhead()} -
    +
    ${next.body()}
    @@ -83,5 +93,5 @@ <%def name="javascriptIncludes()"> -<%def name="headerIncludes()"> -<%def name="subhead()"> \ No newline at end of file +<%def name="headIncludes()"> +<%def name="headerIncludes()"> \ No newline at end of file diff --git a/data/interfaces/default/history.html b/data/interfaces/default/history.html new file mode 100644 index 00000000..db392757 --- /dev/null +++ b/data/interfaces/default/history.html @@ -0,0 +1,64 @@ +<%inherit file="base.html"/> + +<%def name="headerIncludes()"> + + + +<%def name="body()"> +
    + History +
    + + + + + + + + + + + %for item in history: + + + + + + + %endfor + +
    Date AddedFile NameSizeStatus
    ${item['DateAdded']}${item['Title']}${item['Size']}${item['Status']}
    + + +<%def name="headIncludes()"> + + + +<%def name="javascriptIncludes()"> + + + \ No newline at end of file diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index 9a2a89bc..7f687763 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -38,14 +38,14 @@ ${artist['ArtistName']} ${artist['LatestAlbum']} ${releasedate} -
    ${havetracks}/${totaltracks}
    +
    ${havetracks}/${totaltracks}
    %endfor -<%def name="headerIncludes()"> +<%def name="headIncludes()"> @@ -56,8 +56,12 @@ { $('#artist_table').dataTable( { - "aoColumnDefs": [ - { "bSOtrable": false, "aTargets": [ 1 ] } ], + "aoColumns": [ + { "sType": "anti-the" }, + null, + null, + { "sType": "title-numeric"} + ], "bStateSave": true, "iDisplayLength": 50, "sPaginationType": "full_numbers", diff --git a/data/interfaces/default/logs.html b/data/interfaces/default/logs.html index ec79540c..c1dacc9c 100644 --- a/data/interfaces/default/logs.html +++ b/data/interfaces/default/logs.html @@ -14,7 +14,9 @@ %for line in lineList: <% from headphones import helpers - timestamp, level, thread, message = helpers.extract_logline(line) + out_tuple = helpers.extract_logline(line) + if out_tuple: + timestamp, level, thread, message = out_tuple %> %if timestamp and level and thread and message: @@ -29,7 +31,7 @@ -<%def name="headerIncludes()"> +<%def name="headIncludes()"> diff --git a/data/interfaces/default/restart.html b/data/interfaces/default/restart.html new file mode 100644 index 00000000..b2aafaa7 --- /dev/null +++ b/data/interfaces/default/restart.html @@ -0,0 +1,9 @@ +<%inherit file="base.html"/> + +<%def name="headIncludes()"> + + + +<%def name="body()"> + Headphones is restarting + \ No newline at end of file diff --git a/data/interfaces/default/shutdown.html b/data/interfaces/default/shutdown.html new file mode 100644 index 00000000..f01313e1 --- /dev/null +++ b/data/interfaces/default/shutdown.html @@ -0,0 +1,9 @@ +<%inherit file="base.html"/> + +<%def name="headIncludes()"> + + + +<%def name="body()"> + Headphones is shutting down + \ No newline at end of file diff --git a/data/interfaces/default/update.html b/data/interfaces/default/update.html new file mode 100644 index 00000000..ac8c7aa6 --- /dev/null +++ b/data/interfaces/default/update.html @@ -0,0 +1,9 @@ +<%inherit file="base.html"/> + +<%def name="headIncludes()"> + + + +<%def name="body()"> + Headphones is updating + \ No newline at end of file diff --git a/data/js/plugins.js b/data/js/plugins.js index 8aecaa94..272d679b 100755 --- a/data/js/plugins.js +++ b/data/js/plugins.js @@ -6,4 +6,18 @@ window.log = function(){ }; (function(b){function c(){}for(var d="assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,time,timeEnd,trace,warn".split(","),a;a=d.pop();)b[a]=b[a]||c})(window.console=window.console||{}); +jQuery.fn.dataTableExt.oSort['title-numeric-asc'] = function(a,b) { + var x = a.match(/title="*(-?[0-9]+)/)[1]; + var y = b.match(/title="*(-?[0-9]+)/)[1]; + x = parseFloat( x ); + y = parseFloat( y ); + return ((x < y) ? -1 : ((x > y) ? 1 : 0)); +}; +jQuery.fn.dataTableExt.oSort['title-numeric-desc'] = function(a,b) { + var x = a.match(/title="*(-?[0-9]+)/)[1]; + var y = b.match(/title="*(-?[0-9]+)/)[1]; + x = parseFloat( x ); + y = parseFloat( y ); + return ((x < y) ? 1 : ((x > y) ? -1 : 0)); +}; \ No newline at end of file diff --git a/headphones/webserve.py b/headphones/webserve.py index 08792d57..765f6570 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -390,37 +390,9 @@ class WebInterface(object): checkGithub.exposed = True def history(self): - page = [templates._header] - page.append(templates._logobar) - page.append(templates._nav) myDB = db.DBConnection() - snatched = myDB.select('''SELECT AlbumID, Title TEXT, Size INTEGER, URL TEXT, DateAdded TEXT, Status TEXT from snatched order by DateAdded DESC''') - - page.append('''
    -

    History clear all

    - - - - - - ''') - if len(snatched) == 0: - page.append("""
    """) - - i = 0 - while i < len(snatched): - mb = snatched[i][2] / 1048576 - size = '%.2fM' % mb - page.append(''' - - - - - ''' % (snatched[i][5], snatched[i][1], size, snatched[i][4])) - i += 1 - page.append('''
    %s%s%s%s
    ''') - if len(snatched): - page.append(templates._footer % headphones.CURRENT_VERSION) + history = myDB.select('''SELECT * from snatched order by DateAdded DESC''') + return serve_template(templatename="history.html", title="History", history=history) return page history.exposed = True @@ -554,11 +526,7 @@ class WebInterface(object): def shutdown(self): logger.info(u"Headphones is shutting down...") threading.Timer(2, headphones.shutdown).start() - page = [templates._shutdownheader % 15] - page.append(templates._logobar) - page.append(templates._nav) - page.append('
    Shutting down Headphones...
    ') - page.append(templates._footer % headphones.CURRENT_VERSION) + return serve_template(templatename="shutdown.html", title="Shutting Down") return page shutdown.exposed = True @@ -566,23 +534,13 @@ class WebInterface(object): def restart(self): logger.info(u"Headphones is restarting...") threading.Timer(2, headphones.shutdown, [True]).start() - page = [templates._shutdownheader % 30] - page.append(templates._logobar) - page.append(templates._nav) - page.append('
    Restarting Headphones...
    ') - page.append(templates._footer % headphones.CURRENT_VERSION) - return page - + return serve_template(templatename="restart.html", title="Restarting") restart.exposed = True def update(self): logger.info('Headphones is updating...') threading.Timer(2, headphones.shutdown, [True, True]).start() - page = [templates._shutdownheader % 120] - page.append(templates._logobar) - page.append(templates._nav) - page.append('
    Updating Headphones...
    ') - page.append(templates._footer % headphones.CURRENT_VERSION) + return serve_template(templatename="update.html", title="Updating") return page update.exposed = True @@ -590,18 +548,7 @@ class WebInterface(object): def extras(self): myDB = db.DBConnection() cloudlist = myDB.select('SELECT * from lastfmcloud') - page = [templates._header] - page.append(templates._logobar) - page.append(templates._nav) - if len(cloudlist): - page.append(''' -

    Artists You Might Like:



    -
    -
      ''') - for item in cloudlist: - page.append('
    • %s
    • ' % (item['ArtistID'], item['Count'], item['ArtistName'])) - page.append('


    ') - page.append(templates._footer % headphones.CURRENT_VERSION) + return serve_template(templatename="extras.html", title="Extras", cloudlist=cloudlist) return page extras.exposed = True From 7199c610b893bf7fb06aef6d9599d576cd806e37 Mon Sep 17 00:00:00 2001 From: Remy Date: Sat, 6 Aug 2011 12:29:13 -0700 Subject: [PATCH 12/22] Fixed artist sort, fixed helpers import in logs --- data/interfaces/default/index.html | 4 ++-- data/interfaces/default/logs.html | 16 ++++++++-------- data/js/plugins.js | 12 ++++++++++++ headphones/webserve.py | 2 +- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index 7f687763..118830c9 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -35,7 +35,7 @@ %> - ${artist['ArtistName']} + ${artist['ArtistName']} ${artist['LatestAlbum']} ${releasedate}
    ${havetracks}/${totaltracks}
    @@ -57,7 +57,7 @@ $('#artist_table').dataTable( { "aoColumns": [ - { "sType": "anti-the" }, + { "sType": "title-string"}, null, null, { "sType": "title-numeric"} diff --git a/data/interfaces/default/logs.html b/data/interfaces/default/logs.html index c1dacc9c..c4525033 100644 --- a/data/interfaces/default/logs.html +++ b/data/interfaces/default/logs.html @@ -1,4 +1,7 @@ <%inherit file="base.html"/> +<%! + from headphones import helpers +%> <%def name="body()"> @@ -13,17 +16,14 @@ %for line in lineList: <% - from headphones import helpers out_tuple = helpers.extract_logline(line) - if out_tuple: - timestamp, level, thread, message = out_tuple %> - %if timestamp and level and thread and message: + %if out_tuple: - - - - + + + + %endif %endfor diff --git a/data/js/plugins.js b/data/js/plugins.js index 272d679b..7db34fad 100755 --- a/data/js/plugins.js +++ b/data/js/plugins.js @@ -6,6 +6,18 @@ window.log = function(){ }; (function(b){function c(){}for(var d="assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,time,timeEnd,trace,warn".split(","),a;a=d.pop();)b[a]=b[a]||c})(window.console=window.console||{}); +jQuery.fn.dataTableExt.oSort['title-string-asc'] = function(a,b) { + var x = a.match(/title="(.*?)"/)[1].toLowerCase(); + var y = b.match(/title="(.*?)"/)[1].toLowerCase(); + return ((x < y) ? -1 : ((x > y) ? 1 : 0)); +}; + +jQuery.fn.dataTableExt.oSort['title-string-desc'] = function(a,b) { + var x = a.match(/title="(.*?)"/)[1].toLowerCase(); + var y = b.match(/title="(.*?)"/)[1].toLowerCase(); + return ((x < y) ? 1 : ((x > y) ? -1 : 0)); +}; + jQuery.fn.dataTableExt.oSort['title-numeric-asc'] = function(a,b) { var x = a.match(/title="*(-?[0-9]+)/)[1]; var y = b.match(/title="*(-?[0-9]+)/)[1]; diff --git a/headphones/webserve.py b/headphones/webserve.py index 765f6570..4c76a26a 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -403,7 +403,7 @@ class WebInterface(object): lineList = fileHandle.readlines() fileHandle.close() lineList.reverse() - return serve_template(templatename="logs.html", title="Log", lineList=lineList[0:1000]) + return serve_template(templatename="logs.html", title="Log", lineList=lineList[0:500]) logs.exposed = True def clearhistory(self): From 548b606c0df4039edc546cb2e830f609083b4caf Mon Sep 17 00:00:00 2001 From: Remy Date: Sat, 6 Aug 2011 14:22:58 -0700 Subject: [PATCH 13/22] More styling on the index page - highlighting for paused artist --- data/css/data_table.css | 12 +++---- data/css/style.css | 8 ++--- data/interfaces/default/artist.html | 49 +++++++++++++++++++++++++--- data/interfaces/default/history.html | 12 ++++++- data/interfaces/default/index.html | 22 +++++++++---- 5 files changed, 80 insertions(+), 23 deletions(-) diff --git a/data/css/data_table.css b/data/css/data_table.css index 801352ac..5846855c 100644 --- a/data/css/data_table.css +++ b/data/css/data_table.css @@ -169,7 +169,7 @@ table.display tr.odd.gradeA { } table.display tr.even.gradeA { - background-color: #eeffee; + background-color: #ddffdd; } table.display tr.odd.gradeC { @@ -177,7 +177,7 @@ table.display tr.odd.gradeC { } table.display tr.even.gradeC { - background-color: #eeeeff; + background-color: #ddddff; } table.display tr.odd.gradeX { @@ -185,7 +185,7 @@ table.display tr.odd.gradeX { } table.display tr.even.gradeX { - background-color: #ffeeee; + background-color: #ffdddd; } table.display tr.odd.gradeU { @@ -197,11 +197,11 @@ table.display tr.even.gradeU { } -tr.odd { - background-color: #eeeeee; +table.display tr.odd.gradeZ { + background-color: #FCFAFA; } -tr.even { +table.display tr.even.gradeZ { background-color: white; } diff --git a/data/css/style.css b/data/css/style.css index d367b209..8f491b56 100755 --- a/data/css/style.css +++ b/data/css/style.css @@ -124,15 +124,15 @@ div#searchbar { margin: 24px 30px auto auto; float: right; } div#main { margin: 0; padding: 80px 0 0 0; } -table#artist_table { background-color: white; width: 88%; padding: 20px; } +table#artist_table { background-color: white; width: 100%; padding: 20px; } table#artist_table th#name { text-align: left; min-width: 200px; } +table#artist_table th#status { text-align: center; } table#artist_table th#album { text-align: center; min-width: 300px; } -table#artist_table th#reldate { width: 175px; text-align: center; min-width: 100px; } table#artist_table th#have { text-align: center; } table#artist_table td#name { vertical-align: middle; text-align: left; min-width:200px; } +table#artist_table td#status { vertical-align: middle; text-align: center; } table#artist_table td#album { vertical-align: middle; text-align: center; min-width: 300px; } -table#artist_table td#reldate { vertical-align: middle; text-align: center; min-width: 100px; } table#artist_table td#have { vertical-align: middle; } div#paddingheader { padding-top: 48px; font-size: 24px; font-weight: bold; text-align: center; } @@ -150,7 +150,7 @@ table#album_table td#status { vertical-align: middle; text-align: center; } table#album_table td#type { vertical-align: middle; text-align: center; } table#album_table td#have { vertical-align: middle; } -table#history_table { background-color: white; width: 88%; } +table#history_table { background-color: white; width: 100%; } table#log_table { background-color: white; } diff --git a/data/interfaces/default/artist.html b/data/interfaces/default/artist.html index cf4d1d1a..4dd0ca1e 100644 --- a/data/interfaces/default/artist.html +++ b/data/interfaces/default/artist.html @@ -1,11 +1,19 @@ <%inherit file="base.html"/> +<%! + from headphones import db +%> <%def name="headerIncludes()">
    @@ -27,13 +35,36 @@
    %for album in albums: - + <% + if album['Status'] == 'Skipped': + grade = 'Z' + elif album['Status'] == 'Wanted': + grade = 'X' + elif album['Status'] == 'Snatched': + grade = 'C' + else: + grade = 'A' + + myDB = db.DBConnection() + totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE AlbumID=?', [album['AlbumID']])) + havetracks = len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND AlbumTitle like ?', [album['ArtistName'], album['AlbumTitle']])) + + try: + percent = (havetracks*100.0)/totaltracks + if percent > 100: + percent = 100 + except (ZeroDivisionError, TypeError): + percent = 0 + totaltracks = '?' + + %> + - + %endfor @@ -51,6 +82,14 @@ { $('#album_table').dataTable( { + "aoColumns": [ + null, + null, + null, + null, + null, + { "sType": "title-numeric"} + ], "oLanguage": { "sLengthMenu":"Show _MENU_ albums per page", "sEmptyTable": "No album information available", diff --git a/data/interfaces/default/history.html b/data/interfaces/default/history.html index db392757..5db80fc3 100644 --- a/data/interfaces/default/history.html +++ b/data/interfaces/default/history.html @@ -26,7 +26,17 @@ %for item in history: - + <% + if item['Status'] == 'Processed': + grade = 'A' + elif item['Status'] == 'Snatched': + grade = 'C' + elif item['Status'] == 'Unprocessed': + grade = 'X' + else: + grade = 'U' + %> + diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index 118830c9..ee79b359 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -1,15 +1,15 @@ -<% +<%inherit file="base.html"/> +<%! from headphones import helpers %> -<%inherit file="base.html"/> <%def name="body()">
    ${timestamp}${level}${thread}${message.decode('utf-8')}${out_tuple[0]}${out_tuple[1]}${out_tuple[2]}${out_tuple[3].decode('utf-8')}
    ${album['AlbumTitle']} ${album['ReleaseDate']} ${album['Type']} ${album['Status']}
    tracks
    ${havetracks}/${totaltracks}
    ${item['DateAdded']} ${item['Title']} ${item['Size']}
    + - @@ -30,14 +30,22 @@ if artist['ReleaseDate']: releasedate = artist['ReleaseDate'] + if releasedate > helpers.today(): + grade = 'A' + else: + grade = 'Z' else: releasedate = '' + grade = 'Z' + + if artist['Status'] == 'Paused': + grade = 'X' %> - + - - + + %endfor @@ -59,7 +67,7 @@ "aoColumns": [ { "sType": "title-string"}, null, - null, + { "sType": "title-string"}, { "sType": "title-numeric"} ], "bStateSave": true, From 38e9af762be977140b31bdb68e272aead2a4ff5a Mon Sep 17 00:00:00 2001 From: Remy Date: Sat, 6 Aug 2011 18:23:58 -0700 Subject: [PATCH 14/22] Added album page, checkboxes to artist page, more interface fixes --- data/css/data_table.css | 2 ++ data/css/style.css | 32 ++++++++++++++++---- data/images/checkmark.png | Bin 3043 -> 1644 bytes data/interfaces/default/artist.html | 4 +++ data/interfaces/default/index.html | 10 +++++-- data/interfaces/default/logs.html | 8 ++++- data/js/plugins.js | 8 ++++- headphones/webserve.py | 45 +++------------------------- 8 files changed, 59 insertions(+), 50 deletions(-) diff --git a/data/css/data_table.css b/data/css/data_table.css index 5846855c..86161738 100644 --- a/data/css/data_table.css +++ b/data/css/data_table.css @@ -166,10 +166,12 @@ table.display td.center { */ table.display tr.odd.gradeA { background-color: #ddffdd; + font-weight: bold; } table.display tr.even.gradeA { background-color: #ddffdd; + font-weight: bold; } table.display tr.odd.gradeC { diff --git a/data/css/style.css b/data/css/style.css index 8f491b56..a5288327 100755 --- a/data/css/style.css +++ b/data/css/style.css @@ -106,6 +106,10 @@ body { background-color: #EBF4FB; min-width: 907px; } header { min-height: 68px; width: 100%; min-width: 907px; padding-left: 0px; padding-right: 10px; background-color: #CDC9C9; position: fixed; z-index: 998; } +h1 { font-size: 24px; } +h2 { font-size: 20px; } +h3 { font-size: 16px; } + div#updatebar { text-align: center; min-width: 970px; width: 100%; background-color: light-blue; float: left; } div#logo { float: left; padding-left: 10px; } @@ -124,25 +128,29 @@ div#searchbar { margin: 24px 30px auto auto; float: right; } div#main { margin: 0; padding: 80px 0 0 0; } +.table_wrapper { border-radius: 20px; -webkit-border-radius: 20px; -moz-border-radius: 20px; width: 88%; margin: 20px auto 0 auto; padding: 25px; background-color: white; position: relative; min-height: 200px; clear: both; _height: 302px; zoom: 1; } + table#artist_table { background-color: white; width: 100%; padding: 20px; } table#artist_table th#name { text-align: left; min-width: 200px; } -table#artist_table th#status { text-align: center; } +table#artist_table th#status { text-align: left; min-width: 50px; } table#artist_table th#album { text-align: center; min-width: 300px; } table#artist_table th#have { text-align: center; } table#artist_table td#name { vertical-align: middle; text-align: left; min-width:200px; } -table#artist_table td#status { vertical-align: middle; text-align: center; } +table#artist_table td#status { vertical-align: middle; text-align: left; min-width: 50px; } table#artist_table td#album { vertical-align: middle; text-align: center; min-width: 300px; } table#artist_table td#have { vertical-align: middle; } div#paddingheader { padding-top: 48px; font-size: 24px; font-weight: bold; text-align: center; } table#album_table { background-color: white; } +table#album_table th#checkbox { text-align: left; min-width: 25px; } table#album_table th#albumart { text-align: left; min-width: 50px; } table#album_table th#albumname { text-align: center; min-width: 150px; } table#album_table th#reldate { width: 175px; text-align: center; min-width: 100px; } table#album_table th#status { width: 175px; text-align: center; min-width: 100px; } table#album_table th#type { width: 175px; text-align: center; min-width: 100px; } +table#album_table td#checkbox { vertical-align: middle; text-align: left; } table#album_table td#albumart { vertical-align: middle; text-align: left; } table#album_table td#albumname { vertical-align: middle; text-align: center; } table#album_table td#reldate { vertical-align: middle; text-align: center; } @@ -150,13 +158,27 @@ table#album_table td#status { vertical-align: middle; text-align: center; } table#album_table td#type { vertical-align: middle; text-align: center; } table#album_table td#have { vertical-align: middle; } +img.albumArt { float: left; padding-right: 5px; } +div#albumheader { height: 200px; } +div#track_wrapper { padding-top: 20px; text-align: center; font-size: 16px; } + +table#track_table th#number { text-align: left; min-width: 50px; } +table#track_table th#name { text-align: center; min-width: 350px; } +table#track_table th#duration { width: 175px; text-align: center; min-width: 100px; } +table#track_table th#have { width: 175px; text-align: center; min-width: 100px; } + +table#track_table td#number { vertical-align: middle; text-align: left; } +table#track_table td#name { vertical-align: middle; text-align: center; } +table#track_table td#duration { vertical-align: middle; text-align: center; } +table#track_table td#have { vertical-align: middle; text-align: center; } + table#history_table { background-color: white; width: 100%; } table#log_table { background-color: white; } -table#log_table th#timestamp { text-align: left; min-width: 100px; } -table#log_table th#level { text-align: left; min-width: 100px; } -table#log_table th#thread { text-align: left; min-width: 100px; } +table#log_table th#timestamp { text-align: left; min-width: 200px; } +table#log_table th#level { text-align: left; min-width: 75px; } +table#log_table th#thread { text-align: left; min-width: 75px; } table#log_table th#message { text-align: left; min-width: 200px; } div.progress-container { border: 1px solid #ccc; width: 100px; height: 14px; margin: 2px 5px 2px 0; padding: 1px; float: left; background: white; } diff --git a/data/images/checkmark.png b/data/images/checkmark.png index d265c512545a48095195c0d1a4550174dda40080..11f2053056942cf01f37ae4d9664a58a0b7b48d9 100644 GIT binary patch literal 1644 zcmV-y29x=TP)4Tx0C)j~RL^S@K@|QrZmG~B2wH0nvUrdpNm;9CMbtL^5n^i$+aIn^?(HA4aZWV5ov6ELTdbo0FI&wK{O>*+w4vx20?>!`FrQsdJlnHR>OPy zcd~b_n$otK2Za4V;76L-DzNVtaSB-y0*E}{p()372;bw_^6ZZ}PI-92wGS&j#91PI zKs7DSe@(bk%_Y-7gGe}(^>I=@oY#w#*Bu9GZf3^F5WP>3rn}7Ut74&?PWBFvy`A)a zPP5)V!Xd&78LdA?xQ(9mjMYElVd13a#D+Z_7&Y|xU=_C-srWU*6kiZcC!$nw*)9$7 zn6CX+@=AhmkT}X@VSsa5NKe;HZuq)~1$`#h6R+ZTR#D-3j}vF!)ZOnz+5)dI4jl{{ z44Mr{P!L4~VVJN`K!!XTF*LGrKO?IK8z<8w`3e3jI8lUGNUta*C8 zn(P`s>{pjD=7Kek#B;Fw@hxAK%$F&Q6vg9J^Xf~4by_hu-=A!MJ3Znq&n~srbFGPs zH&&aMXZ>nO`|hf|ljc?VPhR!${AbO?W8x_>CU%PFA&Hm8F7cAsOREdwU~R_;ot1_u z(ruCYB-LPGn!NQdT|ZlRy+(fw^-+`=%+gee_kY4FWHg<*4sZI8+sFJD270UUORdLHO0nA4V) z%{fwsET5CQ>B?eK%uw4yQc~9?*JVo2}ze(;aRcp*ceL#HUJSllrgm5wQKR zQu+C;QrUh^8rFfA`ftFz{YAidi-`aL010qNS#tmY3ljhU3ljkVnw%H_00SgRL_t(I z5uKFVOVmLW$LGxDw-*=RDky>^kVw0jmJo!Sfe|bz(XN6o^qeKCE*2DN z4+SB_?n7HrP!#qOZl!e?!Db{ zP$}_5N^m5w18p^yI z+xok{j8F)YImWA2#era%!7T|Z0z2xQh^gK6AEDkONhoJyGO?;EYQ-ueq$&s zzfl;ZfFddgm6Z$T+9to|hid(~)@urf_zD!i2vp}pVnQ*^W~>n3)3IlKB1o9; z^5LEhXTni&4dcNub1qtkSh*CQt-qsz)!_(_VGVc|N}>Ow=q5TcEri+E+4Nx~fRQ() zvPqt;I2DeF8_l>bSFNPlo5;?r);KDAaa+d z`kbP7T4G&k!?-qvySCPz=LeQ!nUHej6!i>>ZCncG1M#MqT$X8qKDs#7XBWM0f+B{` z<%j_qa3m%IOQqntCf;-C z!+eDSy#&RZ^5FcpTdwVOTVc*3F&TDjQ_vb3FCCqnpdzOm6P#Q0rN?1@$ACj`FS%=2 z$F<7iCvH}boV>HC)}Qy+s`-1D8vg+cHlS4?a4@HDGRsQu-~9E3<5Jlyl8y`XP*hDa q^Jy~!y5mdjS-AFB;KI%SGXDiFKQlE0PieXU0000^uM4#YN z*$M`cML<9+E`SoGEmTC(qQwG2{Lp|_g%%W}RNl4U`}h4ZGvCZPXU;h@zq1?@`Fk>G z1~dQw!`sU}fLQU*1Emmim$Rf7fJJrQ?!v7pL$Awx&$6B`?GAsq?09H%&x^UNaJ`V| zxCi@+cj>uW$nr>Xu<{#PF=cmVPYgO;l;w#{8XjEhb!G)=DNq+m!0}QO91a>TUlz_ zR9Qm*XyVnK|BS_SQ{KenOIny|rDY!H{g>3wZS#0C)8Fv>p$&^rz#+U+f~K|*_I&WW z4Grf9Ry@ic`u2w?=PnXcFjf|is?s7-8}vT>SmKqKQMTnsVob=?Wos!|$*uK*WQyX4 zGeI7k!Q(fjA(PLO>kYt}ZNP1=dO~MvG0eCezR**P0ylscE(Vz_O99Uvv#ZtGuU$z} zVn7Ae`el;fcUFV|RVE*J&{0W{=n!ra@f{|<2y&F>NHyo?*O*;)YlmMw3c5(yAPG&Y z2J|KH+oloyxR#HH<;fYvnLgltXlC!{`!{-H533IPU3_F5sn8CtQ;?&O%sASJo)5$> zH%DLkhYzaekI~%#ryHux7R1o}{Byotus~mW3@@D9*4DU7Jm^k2IQ<(J@BG%plrG!5LE5b3!uvlFhi)QYdPB)5VH2_oWF~{^ReKTnw z<+a%j^F0GqXNUHs-kT?Z@#h#^LXz#pgBRmE&x+jl&YM?#(|R_>xbQa|6HrOsR#9(H zD(#S0!FK~_>H|)WD};uttGB}V-g!Hu)BzOI0bD?G{=yB${Vp#!R4oGuT`R13B=suf zeq&bTu^L?M^By}-bDVTbymB@VG&4+V6hMH$-vlUMI!*3gp2Vhj_6SWw4{g-1R@Y{KR|som9~|{F+)b^SzTxT z=zDg-^YW&`t;78_k6UK~HTq!s6e*}no!vXr+$)hzrt|gR=gg)>ZcR;3oUJ+Vv2sT9 z)^odt;a<99C~V!it%K#hwlHu2MI_F8#Jn*(rm(Z~2voW-QeOLaSY(o8$-y^u;P5^?^FO3MM3~7Z%bD@XYS1ylc)+mK@#P+`y4~K}?}ZePig|dx6yC=P%6N zP=ziqmbI)75X3G&>qL){*=KA!baB_a2bLReFY0xHr|X|SMxFPJ>JEA6#t(a)<^m<8 zWLXMDk0^Oq!#Z#1>36E}U8*|9>?o~szGnZa6}yu?XN{)^;vqEm699V5A+YeHnUvW*OCeAxnG&-D$Ugd95 zp$lqQ)9t!>VU*3o%0X_?LT_Ww*BGuNo@VJNm%}nEQ7!UuhuQlRM`r|~uZ2pLiOSxzSxctUeqnO-lQA6J>TN-)k>-W{fyG-hZMin zS)JE14T>T`lyCHPGa$RHjN`EUAaN4s0kUuae%PTi^p=(a2>3^{S+}TO ze^4wO;Pq=eP^DdiPC2)LhlO4Q*3RsHGx)XmoXykkTz(`{EEhD-dXbWd=Myusg7&JD z1bda5C(3tw*4vE%p`jk?#!=DQ8;)9hwo^Yt-!wd}xpld<_S-O`{M3gUVY0QLVF>?& zXSsigG=Xd~%7}V2n%21DhC9o;PnXoWO}!jxh=I(&X4K+}kImEPbL0P(AxlU^{1rcn zPnZ8np@WAxS~*J;!{SPZX&V{YZvsI?+M4q2_>32#w4u{;<3ze*5?NuVp@WIYur|eB z{r=m|Rmm~wOX2hzq%W>(l_8x!=E!-74cVZ3Wg&p~6OrUNsK8xZ1qARhTGK>5rmc7QU=Dv~KH7)4e1 zPD7~!Ewz};&#y7)N?VMMRO6*>c%P40MkqSYlE#w*Mj4MAvOIv(YWpIs2O6WtoxY}5 zQicDLtm@*i!wd`BncuWNRA>xGm--3SD-d{WaV|Y8VhA7KVp;2`9-!EF&}9#DH3#*k zTXhe|kt41{HGK7MrZS#6!)W?XJqII#qDygtuTEk8ZL1?5C9mceYvpV5EyX3VwA_Yl z*C`#NA?4|Nd{M8Xy3%6(7>jhK4WE$ka{1~Q}qDt%QBUnw;@z}LCG;c2{`i_3zs3VeD z>Zs7mQoMFC+^|{mQ4Szg!UV9-ipnh&3}1qa*SMj$48$Is18qXyiLVrYw?V7?Piyyc zCy%W}af`#)>FZRZcUP$e+DsG@Vc@G2Y_^s9UX0jMd*(SufhCG-b}eb%Ks^$U{0FrA zYjWVuH??bX+&EI*cdfL%JFK-ppvkfN6sg)77Z5DWPedX&_-h8;kD6AfGI9hzubh0% zcGnD&;h_2`^aikA+hMZxz43)RwR#pn9GxC6Y__lCfD)B$k3HB&5Z4#FsZ0mv!^mYI zc9W}Su!X3(OJTv8WajDxIj?vOD{;Ys2s{6kb`$d0fy9Oanqs5V-CiQhPLbb?)}!El zVswR;c20wQRv5w&0J}m^pzSmNRH;jNeU)-2E%$z+55cm{kTk6#;ZwGdo@<XRe^y zW+PS_KA(SaSl{$Siy3Sqc2Rtt^Z$F{|MkfMKm3`Ix`iEOUOL`JH1EKBv%ht6Pi diff --git a/data/interfaces/default/artist.html b/data/interfaces/default/artist.html index 4dd0ca1e..c299677a 100644 --- a/data/interfaces/default/artist.html +++ b/data/interfaces/default/artist.html @@ -22,9 +22,11 @@
    ${artist['ArtistName']}
    +
    Artist NameStatus Latest AlbumRelease Date Have
    ${artist['ArtistName']}${artist['LatestAlbum']}${releasedate}${artist['Status']}${artist['LatestAlbum']} ${releasedate}
    ${havetracks}/${totaltracks}
    + @@ -59,6 +61,7 @@ %> + @@ -88,6 +91,7 @@ null, null, null, + null, { "sType": "title-numeric"} ], "oLanguage": { diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index ee79b359..595aae3d 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -28,15 +28,21 @@ percent = 0 totaltracks = '?' - if artist['ReleaseDate']: + if artist['ReleaseDate'] and artist['LatestAlbum']: releasedate = artist['ReleaseDate'] + albumdisplay = '%s (%s)' % (artist['LatestAlbum'], artist['ReleaseDate']) if releasedate > helpers.today(): grade = 'A' else: grade = 'Z' + elif artist['LatestAlbum']: + releasedate = '' + grade = 'Z' + albumdisplay = artist['LatestAlbum'] else: releasedate = '' grade = 'Z' + albumdisplay = 'None' if artist['Status'] == 'Paused': grade = 'X' @@ -45,7 +51,7 @@ - + %endfor diff --git a/data/interfaces/default/logs.html b/data/interfaces/default/logs.html index c4525033..a4a146a6 100644 --- a/data/interfaces/default/logs.html +++ b/data/interfaces/default/logs.html @@ -19,7 +19,13 @@ out_tuple = helpers.extract_logline(line) %> %if out_tuple: - + <% + if out_tuple[1] == 'WARNING' or out_tuple[1] == 'ERROR': + grade = 'X' + else: + grade = 'Z' + %> + diff --git a/data/js/plugins.js b/data/js/plugins.js index 7db34fad..bafe8161 100755 --- a/data/js/plugins.js +++ b/data/js/plugins.js @@ -32,4 +32,10 @@ jQuery.fn.dataTableExt.oSort['title-numeric-desc'] = function(a,b) { x = parseFloat( x ); y = parseFloat( y ); return ((x < y) ? 1 : ((x > y) ? -1 : 0)); -}; \ No newline at end of file +}; + +function toggle(source) { + checkboxes = document.getElementsByName('mbid'); + for(var i in checkboxes) + checkboxes[i].checked = source.checked; +} \ No newline at end of file diff --git a/headphones/webserve.py b/headphones/webserve.py index 4c76a26a..8e3359e2 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -46,48 +46,11 @@ class WebInterface(object): def albumPage(self, AlbumID): - page = [templates._header] - page.append(templates._logobar) - page.append(templates._nav) myDB = db.DBConnection() - - results = myDB.select('SELECT ArtistID, ArtistName, AlbumTitle, TrackTitle, TrackDuration, TrackID, AlbumASIN from tracks WHERE AlbumID=?', [AlbumID]) - - if results[0][6]: - albumart = '''


    ''' % results[0][6] - else: - albumart = '' - i = 0 - page.append('''
    Album Name Release Date
    ${album['AlbumTitle']} ${album['ReleaseDate']}
    ${artist['ArtistName']} ${artist['Status']}${artist['LatestAlbum']} ${releasedate}${albumdisplay}
    ${havetracks}/${totaltracks}
    ${out_tuple[0]} ${out_tuple[1]} ${out_tuple[2]}
    - %s - %s
    - Download
    %s
    -
    - - - - - ''' % (results[0][0], results[0][1], results[0][2], AlbumID, results[0][0], albumart)) - while i < len(results): - trackmatches = myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND AlbumTitle like ? AND TrackTitle like ?', [results[i][1], results[i][2], results[i][3]]) - - if len(trackmatches): - have = '' - else: - have = '' - if results[i][4]: - duration = helpers.convert_milliseconds(int(results[i][4])) - else: - duration = 'n/a' - page.append(''' - - - ''' % (i+1, results[i][3], results[i][5], duration, have)) - i = i+1 - - page.append('''
    Track #Track TitleDuration
    %s%s (link)%s%s
    ''') - page.append(templates._footer % headphones.CURRENT_VERSION) - return page - + album = myDB.action('SELECT * from albums WHERE AlbumID=?', [AlbumID]).fetchone() + tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [AlbumID]) + title = album['ArtistName'] + ' - ' + album['AlbumTitle'] + return serve_template(templatename="album.html", title=title, album=album, tracks=tracks) albumPage.exposed = True From b23e1274bc8cc02d5c2bcabec1ce141e093f8225 Mon Sep 17 00:00:00 2001 From: Remy Date: Sat, 6 Aug 2011 22:40:31 -0700 Subject: [PATCH 15/22] Multiple album markings, select all javascript, better pp log message when unable to tag --- data/css/data_table.css | 2 -- data/css/style.css | 9 +++++---- data/interfaces/default/artist.html | 23 +++++++++++++++-------- data/interfaces/default/base.html | 10 +--------- data/interfaces/default/logs.html | 2 -- data/js/plugins.js | 2 +- headphones/postprocessor.py | 2 +- headphones/webserve.py | 18 ++++++++++++++++++ 8 files changed, 41 insertions(+), 27 deletions(-) diff --git a/data/css/data_table.css b/data/css/data_table.css index 86161738..5846855c 100644 --- a/data/css/data_table.css +++ b/data/css/data_table.css @@ -166,12 +166,10 @@ table.display td.center { */ table.display tr.odd.gradeA { background-color: #ddffdd; - font-weight: bold; } table.display tr.even.gradeA { background-color: #ddffdd; - font-weight: bold; } table.display tr.odd.gradeC { diff --git a/data/css/style.css b/data/css/style.css index a5288327..caeeb30a 100755 --- a/data/css/style.css +++ b/data/css/style.css @@ -110,6 +110,8 @@ h1 { font-size: 24px; } h2 { font-size: 20px; } h3 { font-size: 16px; } +p.indented { margin-left: 20px; font-size: 14px; } + div#updatebar { text-align: center; min-width: 970px; width: 100%; background-color: light-blue; float: left; } div#logo { float: left; padding-left: 10px; } @@ -144,13 +146,13 @@ table#artist_table td#have { vertical-align: middle; } div#paddingheader { padding-top: 48px; font-size: 24px; font-weight: bold; text-align: center; } table#album_table { background-color: white; } -table#album_table th#checkbox { text-align: left; min-width: 25px; } +table#album_table th#select { text-align: left; min-width: 25px; } table#album_table th#albumart { text-align: left; min-width: 50px; } table#album_table th#albumname { text-align: center; min-width: 150px; } table#album_table th#reldate { width: 175px; text-align: center; min-width: 100px; } table#album_table th#status { width: 175px; text-align: center; min-width: 100px; } table#album_table th#type { width: 175px; text-align: center; min-width: 100px; } -table#album_table td#checkbox { vertical-align: middle; text-align: left; } +table#album_table td#select { vertical-align: middle; text-align: left; } table#album_table td#albumart { vertical-align: middle; text-align: left; } table#album_table td#albumname { vertical-align: middle; text-align: center; } table#album_table td#reldate { vertical-align: middle; text-align: center; } @@ -176,9 +178,8 @@ table#history_table { background-color: white; width: 100%; } table#log_table { background-color: white; } -table#log_table th#timestamp { text-align: left; min-width: 200px; } +table#log_table th#timestamp { text-align: left; min-width: 165px; } table#log_table th#level { text-align: left; min-width: 75px; } -table#log_table th#thread { text-align: left; min-width: 75px; } table#log_table th#message { text-align: left; min-width: 200px; } div.progress-container { border: 1px solid #ccc; width: 100px; height: 14px; margin: 2px 5px 2px 0; padding: 1px; float: left; background: white; } diff --git a/data/interfaces/default/artist.html b/data/interfaces/default/artist.html index c299677a..0c02e83c 100644 --- a/data/interfaces/default/artist.html +++ b/data/interfaces/default/artist.html @@ -20,13 +20,21 @@ <%def name="body()">
    - ${artist['ArtistName']} +

    ${artist['ArtistName']}

    - + +

    Mark selected albums as + + +

    - + @@ -61,7 +69,7 @@ %> - @@ -72,6 +80,7 @@ %endfor
    Album Name Release Date
    + ${album['AlbumTitle']} ${album['ReleaseDate']}
    + <%def name="headIncludes()"> @@ -97,12 +106,10 @@ "oLanguage": { "sLengthMenu":"Show _MENU_ albums per page", "sEmptyTable": "No album information available", - "sInfo":"Showing _START_ to _END_ of _TOTAL_ albums", + "sInfo":"Showing _TOTAL_ albums", "sInfoEmpty":"Showing 0 to 0 of 0 albums", "sInfoFiltered":"(filtered from _MAX_ total albums)"}, - "bStateSave": true, - "iDisplayLength": 25, - "sPaginationType": "full_numbers", + "bPaginate": false, "aaSorting": [[3, 'asc'],[2,'desc']] }); diff --git a/data/interfaces/default/base.html b/data/interfaces/default/base.html index 10c0491a..f3a38847 100755 --- a/data/interfaces/default/base.html +++ b/data/interfaces/default/base.html @@ -27,15 +27,7 @@
    - % if not headphones.CURRENT_VERSION: - - % elif headphones.CURRENT_VERSION != headphones.LATEST_VERSION and headphones.INSTALL_TYPE != 'win': -
    - A newer version is available. You're ${headphones.COMMITS_BEHIND} commits behind. Click here to update -
    - % endif + diff --git a/data/interfaces/default/logs.html b/data/interfaces/default/logs.html index a4a146a6..d9aad901 100644 --- a/data/interfaces/default/logs.html +++ b/data/interfaces/default/logs.html @@ -9,7 +9,6 @@ Timestamp Level - Thread Message @@ -28,7 +27,6 @@ ${out_tuple[0]} ${out_tuple[1]} - ${out_tuple[2]} ${out_tuple[3].decode('utf-8')} %endif diff --git a/data/js/plugins.js b/data/js/plugins.js index bafe8161..da9863aa 100755 --- a/data/js/plugins.js +++ b/data/js/plugins.js @@ -35,7 +35,7 @@ jQuery.fn.dataTableExt.oSort['title-numeric-desc'] = function(a,b) { }; function toggle(source) { - checkboxes = document.getElementsByName('mbid'); + checkboxes = document.getElementsByClassName('checkbox'); for(var i in checkboxes) checkboxes[i].checked = source.checked; } \ No newline at end of file diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index e97d46b1..68966f63 100644 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -330,7 +330,7 @@ def correctMetadata(albumid, release, downloaded_track_list): cur_artist, cur_album, out_tuples, rec = autotag.tag_album(items, search_artist=release['ArtistName'], search_album=release['AlbumTitle']) if rec == 'RECOMMEND_NONE': - logger.warn('No accurate match found - not writing metadata') + logger.warn('No accurate album match found for %s, %s - not writing metadata' % (release['ArtistName'], release['AlbumTitle'])) return distance, items, info = out_tuples[0] diff --git a/headphones/webserve.py b/headphones/webserve.py index 8e3359e2..768d9fd1 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -162,6 +162,24 @@ class WebInterface(object): deleteArtist.exposed = True + def refreshArtist(self, ArtistID): + import importer + importer.addArtisttoDB(artistid) + refreshArtist.exposed=True + + def markAlbums(self, ArtistID=None, action=None, **args): + myDB = db.DBConnection() + for mbid in args: + controlValueDict = {'AlbumID': mbid} + newValueDict = {'Status': action} + myDB.upsert("albums", newValueDict, controlValueDict) + if action == 'Wanted': + import searcher + searcher.searchNZB(mbid, new=False) + raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID) + markAlbums.exposed = True + + def queueAlbum(self, AlbumID, ArtistID, new=False): logger.info(u"Marking album: " + AlbumID + "as wanted...") From 74149a6d3cbc145efbd482b8db3cc6fcdc6ea049 Mon Sep 17 00:00:00 2001 From: Remy Date: Sat, 6 Aug 2011 23:05:07 -0700 Subject: [PATCH 16/22] Center artist page css --- data/css/style.css | 2 +- data/interfaces/default/artist.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/css/style.css b/data/css/style.css index caeeb30a..91bf7b7f 100755 --- a/data/css/style.css +++ b/data/css/style.css @@ -146,7 +146,7 @@ table#artist_table td#have { vertical-align: middle; } div#paddingheader { padding-top: 48px; font-size: 24px; font-weight: bold; text-align: center; } table#album_table { background-color: white; } -table#album_table th#select { text-align: left; min-width: 25px; } +table#album_table th#select { vertical-align: middle; text-align: left; min-width: 25px; } table#album_table th#albumart { text-align: left; min-width: 50px; } table#album_table th#albumname { text-align: center; min-width: 150px; } table#album_table th#reldate { width: 175px; text-align: center; min-width: 100px; } diff --git a/data/interfaces/default/artist.html b/data/interfaces/default/artist.html index 0c02e83c..934d173a 100644 --- a/data/interfaces/default/artist.html +++ b/data/interfaces/default/artist.html @@ -34,7 +34,7 @@ - + From ca7526262c4048c8d528d0909b38e70e780ebf28 Mon Sep 17 00:00:00 2001 From: Remy Date: Sun, 7 Aug 2011 01:31:38 -0700 Subject: [PATCH 17/22] Added config, extras, manage, upcoming --- data/css/style.css | 23 ++- data/interfaces/default/album.html | 82 ++++++++++ data/interfaces/default/base.html | 12 +- data/interfaces/default/config.html | 224 ++++++++++++++++++++++++++ data/interfaces/default/extras.html | 13 ++ data/interfaces/default/history.html | 6 +- data/interfaces/default/index.html | 12 +- data/interfaces/default/manage.html | 33 ++++ data/interfaces/default/upcoming.html | 14 ++ headphones/webserve.py | 215 ++++++------------------ 10 files changed, 458 insertions(+), 176 deletions(-) create mode 100644 data/interfaces/default/album.html create mode 100644 data/interfaces/default/config.html create mode 100644 data/interfaces/default/extras.html create mode 100644 data/interfaces/default/manage.html create mode 100644 data/interfaces/default/upcoming.html diff --git a/data/css/style.css b/data/css/style.css index 91bf7b7f..9bd44217 100755 --- a/data/css/style.css +++ b/data/css/style.css @@ -136,11 +136,11 @@ table#artist_table { background-color: white; width: 100%; padding: 20px; } table#artist_table th#name { text-align: left; min-width: 200px; } table#artist_table th#status { text-align: left; min-width: 50px; } -table#artist_table th#album { text-align: center; min-width: 300px; } +table#artist_table th#album { text-align: left; min-width: 300px; } table#artist_table th#have { text-align: center; } table#artist_table td#name { vertical-align: middle; text-align: left; min-width:200px; } table#artist_table td#status { vertical-align: middle; text-align: left; min-width: 50px; } -table#artist_table td#album { vertical-align: middle; text-align: center; min-width: 300px; } +table#artist_table td#album { vertical-align: middle; text-align: left; min-width: 300px; } table#artist_table td#have { vertical-align: middle; } div#paddingheader { padding-top: 48px; font-size: 24px; font-weight: bold; text-align: center; } @@ -190,6 +190,25 @@ footer { margin: 20px auto 20px auto; } div#version { text-align: center; font-weight: bold; } div#donate { text-align: center; margin: 20px auto 20px auto; } +.cloudtag { padding-top: 30px; font-size:16px; } +#cloud a.tag1 { font-size: 0.7em; font-weight: 100; } +#cloud a.tag2 { font-size: 0.8em; font-weight: 200; } +#cloud a.tag3 { font-size: 0.9em; font-weight: 300; } +#cloud a.tag4 { font-size: 1.0em; font-weight: 400; } +#cloud a.tag5 { font-size: 1.2em; font-weight: 500; } +#cloud a.tag6 { font-size: 1.4em; font-weight: 600; } +#cloud a.tag7 { font-size: 1.6em; font-weight: 700; } +#cloud a.tag8 { font-size: 1.8em; font-weight: 800; } +#cloud a.tag9 { font-size: 2.2em; font-weight: 900; } +#cloud a.tag10 { font-size: 2.5em; font-weight: 900; } + +#cloud { padding: 2px; line-height: 1.5em; text-align: center; } +#cloud a { padding: 0px; } +#cloud { margin: 0; } +#cloud li { display: inline; } + + + .ir { display: block; text-indent: -999em; overflow: hidden; background-repeat: no-repeat; text-align: left; direction: ltr; } .hidden { display: none; visibility: hidden; } .visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } diff --git a/data/interfaces/default/album.html b/data/interfaces/default/album.html new file mode 100644 index 00000000..e242556d --- /dev/null +++ b/data/interfaces/default/album.html @@ -0,0 +1,82 @@ +<%inherit file="base.html" /> +<%! + from headphones import db, helpers + myDB = db.DBConnection() +%> + +<%def name="headerIncludes()"> +
    + +
    + + +<%def name="body()"> +
    +
    + albumart +

    ${album['AlbumTitle']}

    +

    ${album['ArtistName']}

    +
    +

    Description:

    +
    +
    +
    Album Name Release Date
    + + + + + + + + + + %for track in tracks: + <% + trackmatch = 'asd' + if len(trackmatch): + grade = 'A' + check = 'checkmark' + else: + grade = 'Z' + check = '' + %> + + + + + + + %endfor + +
    #Track TitleDurationHave
    #${track['TrackTitle']}${track['TrackDuration']}${check}
    +
    +
    + + +<%def name="headIncludes()"> + + + +<%def name="javascriptIncludes()"> + + + \ No newline at end of file diff --git a/data/interfaces/default/base.html b/data/interfaces/default/base.html index f3a38847..f9cea722 100755 --- a/data/interfaces/default/base.html +++ b/data/interfaces/default/base.html @@ -27,9 +27,17 @@
    - + % if not headphones.CURRENT_VERSION: +
    + You're running an unknown version of Headphones. Click here to update +
    + % elif headphones.CURRENT_VERSION != headphones.LATEST_VERSION and headphones.INSTALL_TYPE != 'win': +
    + A newer version is available. You're ${headphones.COMMITS_BEHIND} commits behind. Click here to update +
    + % endif
    +
+<%inherit file="base.html" /> \ No newline at end of file diff --git a/data/interfaces/default/extras.html b/data/interfaces/default/extras.html new file mode 100644 index 00000000..d9dbbe2f --- /dev/null +++ b/data/interfaces/default/extras.html @@ -0,0 +1,13 @@ +<%inherit file="base.html" /> +<%def name="body()"> +
+

Artists You Might Like

+
+ +
+
+ diff --git a/data/interfaces/default/history.html b/data/interfaces/default/history.html index 5db80fc3..f1002609 100644 --- a/data/interfaces/default/history.html +++ b/data/interfaces/default/history.html @@ -4,9 +4,9 @@ diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index 595aae3d..83a594b6 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -30,7 +30,7 @@ if artist['ReleaseDate'] and artist['LatestAlbum']: releasedate = artist['ReleaseDate'] - albumdisplay = '%s (%s)' % (artist['LatestAlbum'], artist['ReleaseDate']) + albumdisplay = '%s (%s)' % (artist['LatestAlbum'], artist['ReleaseDate']) if releasedate > helpers.today(): grade = 'A' else: @@ -38,21 +38,21 @@ elif artist['LatestAlbum']: releasedate = '' grade = 'Z' - albumdisplay = artist['LatestAlbum'] + albumdisplay = '%s' % artist['LatestAlbum'] else: releasedate = '' grade = 'Z' - albumdisplay = 'None' + albumdisplay = 'None' if artist['Status'] == 'Paused': grade = 'X' %> - ${artist['ArtistName']} + ${artist['ArtistName']} ${artist['Status']} - ${albumdisplay} -
${havetracks}/${totaltracks}
+ ${albumdisplay} +
${havetracks}/${totaltracks}
%endfor diff --git a/data/interfaces/default/manage.html b/data/interfaces/default/manage.html new file mode 100644 index 00000000..0d91e773 --- /dev/null +++ b/data/interfaces/default/manage.html @@ -0,0 +1,33 @@ +<%inherit file="base.html" /> +<%def name="body()"> +
+

Scan Music Library


+ Where do you keep your music?

+ You can put in any directory, and it will scan for audio files in that folder + (including all subdirectories)

For example: '/Users/name/Music' +

+ It may take a while depending on how many files you have. You can navigate away from the page
+ as soon as you click 'Submit' +

+
+
+
+
+

Import Last.FM Artists


+ Enter the username whose artists you want to import:

+
+ +


+

Placeholder :-)


+

+
+ +



+ ''' % (music_dir_input, lastfm_user_text)) + \ No newline at end of file diff --git a/data/interfaces/default/upcoming.html b/data/interfaces/default/upcoming.html new file mode 100644 index 00000000..3bdd64f8 --- /dev/null +++ b/data/interfaces/default/upcoming.html @@ -0,0 +1,14 @@ +<%inherit file="base.html" /> +<%def name="body()"> +
+

Upcoming Albums

+ %for album in wanted: + + %endfor + +
+
+

Wanted Albums

+ +
+ diff --git a/headphones/webserve.py b/headphones/webserve.py index 768d9fd1..73cb42bc 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -10,10 +10,11 @@ import threading import headphones -from headphones.mb import getReleaseGroup -from headphones import templates, logger, searcher, db, importer, helpers, mb, lastfm +from headphones import logger, searcher, db, importer, lastfm from headphones.helpers import checked, radio + + def serve_template(templatename, **kwargs): template_dir = os.path.join(str(headphones.PROG_DIR), 'data/interfaces/default/') @@ -181,18 +182,14 @@ class WebInterface(object): def queueAlbum(self, AlbumID, ArtistID, new=False): - logger.info(u"Marking album: " + AlbumID + "as wanted...") myDB = db.DBConnection() controlValueDict = {'AlbumID': AlbumID} newValueDict = {'Status': 'Wanted'} myDB.upsert("albums", newValueDict, controlValueDict) - import searcher searcher.searchNZB(AlbumID, new) - raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID) - queueAlbum.exposed = True def unqueueAlbum(self, AlbumID, ArtistID): @@ -208,112 +205,14 @@ class WebInterface(object): unqueueAlbum.exposed = True def upcoming(self): - page = [templates._header] - page.append(templates._logobar) - page.append(templates._nav) myDB = db.DBConnection() - albums = myDB.select("SELECT AlbumTitle, ReleaseDate, DateAdded, AlbumASIN, AlbumID, ArtistName, ArtistID from albums WHERE ReleaseDate > date('now') order by ReleaseDate DESC") - + upcoming = myDB.select("SELECT AlbumTitle, ReleaseDate, DateAdded, AlbumASIN, AlbumID, ArtistName, ArtistID from albums WHERE ReleaseDate > date('now') order by ReleaseDate DESC") wanted = myDB.select("SELECT AlbumTitle, ReleaseDate, DateAdded, AlbumASIN, AlbumID, ArtistName, ArtistID from albums WHERE Status='Wanted'") - - page.append('''
- - - - - - ''') - if len(albums) == 0: - page.append("""
Upcoming Albums

No albums are coming out soon :(
- (try adding some more artists!)
""") - - i = 0 - while i < len(albums): - - if albums[i][3]: - albumart = '''


''' % (albums[i][3], albums[i][3]) - else: - albumart = 'No Album Art... yet.' - - page.append(''' - - - ''' % (albumart, albums[i][6], albums[i][5], albums[i][4], albums[i][0], albums[i][1])) - i += 1 - page.append('''
%s%s%s (%s)
''') - if len(wanted): - page.append('''
- - - - - - ''') - i = 0 - while i < len(wanted): - - if wanted[i][3]: - albumart = '''


''' % (wanted[i][3], wanted[i][3]) - else: - albumart = 'No Album Art... yet.' - - page.append(''' - - - ''' % (albumart, wanted[i][6], wanted[i][5], wanted[i][4], wanted[i][0], wanted[i][1])) - i += 1 - page.append('''
Wanted Albums

%s%s%s (%s)
''') - if len(albums): - page.append(templates._footer % headphones.CURRENT_VERSION) - - return page + return serve_template(templatename="upcoming.html", title="Upcoming", upcoming=upcoming, wanted=wanted) upcoming.exposed = True def manage(self): - if headphones.LASTFM_USERNAME: - lastfm_user_text = headphones.LASTFM_USERNAME - else: - lastfm_user_text = 'Last.FM Username' - if headphones.MUSIC_DIR: - music_dir_input = '''''' % headphones.MUSIC_DIR - else: - music_dir_input = '''''' - page = [templates._header] - page.append(templates._logobar) - page.append(templates._nav) - page.append(''' -

Scan Music Library


- Where do you keep your music?

- You can put in any directory, and it will scan for audio files in that folder - (including all subdirectories)

For example: '/Users/name/Music' -

- It may take a while depending on how many files you have. You can navigate away from the page
- as soon as you click 'Submit' -

- -
- %s -


-

Import Last.FM Artists


- Enter the username whose artists you want to import:

-
- -


-

Placeholder :-)


-

-
- -



- ''' % (music_dir_input, lastfm_user_text)) - page.append(templates._footer % headphones.CURRENT_VERSION) - return page + return serve_template(templatename="manage.html", title="Manage") manage.exposed = True def importLastFM(self, username): @@ -387,67 +286,57 @@ class WebInterface(object): return serve_template(templatename="logs.html", title="Log", lineList=lineList[0:500]) logs.exposed = True - def clearhistory(self): - - logger.info(u"Clearing history") + def clearhistory(self, type=None): + logger.info(u"Clearing history where status is %s" % type) myDB = db.DBConnection() - myDB.action('''DELETE from snatched''') - + myDB.action('DELETE from snatched WHERE Status=?', [type]) raise cherrypy.HTTPRedirect("history") clearhistory.exposed = True def config(self): - page = [templates._header] - page.append(templates._logobar) - page.append(templates._nav) - page.append(templates.configform % ( - headphones.HTTP_HOST, - headphones.HTTP_USERNAME, - headphones.HTTP_PORT, - headphones.HTTP_PASSWORD, - checked(headphones.LAUNCH_BROWSER), - headphones.SAB_HOST, - headphones.SAB_USERNAME, - headphones.SAB_APIKEY, - headphones.SAB_PASSWORD, - headphones.SAB_CATEGORY, - headphones.DOWNLOAD_DIR, - checked(headphones.BLACKHOLE), - headphones.BLACKHOLE_DIR, - headphones.USENET_RETENTION, - checked(headphones.NZBMATRIX), - headphones.NZBMATRIX_USERNAME, - headphones.NZBMATRIX_APIKEY, - checked(headphones.NEWZNAB), - headphones.NEWZNAB_HOST, - headphones.NEWZNAB_APIKEY, - checked(headphones.NZBSORG), - headphones.NZBSORG_UID, - headphones.NZBSORG_HASH, - checked(headphones.NEWZBIN), - headphones.NEWZBIN_UID, - headphones.NEWZBIN_PASSWORD, - radio(headphones.PREFERRED_QUALITY, 0), - radio(headphones.PREFERRED_QUALITY, 1), - radio(headphones.PREFERRED_QUALITY, 3), - radio(headphones.PREFERRED_QUALITY, 2), - headphones.PREFERRED_BITRATE, - checked(headphones.DETECT_BITRATE), - checked(headphones.MOVE_FILES), - checked(headphones.RENAME_FILES), - checked(headphones.CORRECT_METADATA), - checked(headphones.CLEANUP_FILES), - checked(headphones.ADD_ALBUM_ART), - checked(headphones.EMBED_ALBUM_ART), - headphones.DESTINATION_DIR, - headphones.FOLDER_FORMAT, - headphones.FILE_FORMAT, - checked(headphones.INCLUDE_EXTRAS), - headphones.LOG_DIR - )) - page.append(templates._footer % headphones.CURRENT_VERSION) - return page - + config = { + "http_host" : headphones.HTTP_HOST, + "http_user" : headphones.HTTP_USERNAME, + "http_port" : headphones.HTTP_PORT, + "http_pass" : headphones.HTTP_PASSWORD, + "launch_browser" : checked(headphones.LAUNCH_BROWSER), + "sab_host" : headphones.SAB_HOST, + "sab_user" : headphones.SAB_USERNAME, + "sab_api" : headphones.SAB_APIKEY, + "sab_pass" : headphones.SAB_PASSWORD, + "sab_cat" : headphones.SAB_CATEGORY, + "download_dir" : headphones.DOWNLOAD_DIR, + "use_blackhole" : checked(headphones.BLACKHOLE), + "blackhole_dir" : headphones.BLACKHOLE_DIR, + "usenet_retention" : headphones.USENET_RETENTION, + "use_nzbmatrix" : checked(headphones.NZBMATRIX), + "nzbmatrix_user" : headphones.NZBMATRIX_USERNAME, + "nzbmatrix_api" : headphones.NZBMATRIX_APIKEY, + "use_newznab" : checked(headphones.NEWZNAB), + "newznab_host" : headphones.NEWZNAB_HOST, + "newznab_api" : headphones.NEWZNAB_APIKEY, + "use_nzbsorg" : checked(headphones.NZBSORG), + "nzbsorg_uid" : headphones.NZBSORG_UID, + "nzbsorg_hash" : headphones.NZBSORG_HASH, + "pref_qual_0" : radio(headphones.PREFERRED_QUALITY, 0), + "pref_qual_1" : radio(headphones.PREFERRED_QUALITY, 1), + "pref_qual_2" : radio(headphones.PREFERRED_QUALITY, 3), + "pref_qual_3" : radio(headphones.PREFERRED_QUALITY, 2), + "pref_bitrate" : headphones.PREFERRED_BITRATE, + "detect_bitrate" : checked(headphones.DETECT_BITRATE), + "move_files" : checked(headphones.MOVE_FILES), + "rename_files" : checked(headphones.RENAME_FILES), + "correct_metadata" : checked(headphones.CORRECT_METADATA), + "cleanup_files" : checked(headphones.CLEANUP_FILES), + "add_album_art" : checked(headphones.ADD_ALBUM_ART), + "embed_album_art" : checked(headphones.EMBED_ALBUM_ART), + "dest_dir" : headphones.DESTINATION_DIR, + "folder_format" : headphones.FOLDER_FORMAT, + "file_format" : headphones.FILE_FORMAT, + "include_extras" : checked(headphones.INCLUDE_EXTRAS), + "log_dir" : headphones.LOG_DIR + } + return serve_template(templatename="config.html", title="Settings", config=config) config.exposed = True From 1c736bf2283c12f4b73dfc30ffcbf8e187db24ce Mon Sep 17 00:00:00 2001 From: Remy Date: Sun, 7 Aug 2011 02:51:12 -0700 Subject: [PATCH 18/22] some more commits, extras.html, config.html (bad layout), otherstuff, changed log levels --- data/interfaces/default/config.html | 72 +++++++++++++-------------- data/interfaces/default/logs.html | 4 +- data/interfaces/default/restart.html | 9 ---- data/interfaces/default/shutdown.html | 4 +- data/interfaces/default/upcoming.html | 44 ++++++++++++++-- data/interfaces/default/update.html | 9 ---- headphones/searcher.py | 10 ++-- headphones/updater.py | 8 +-- headphones/webserve.py | 21 +++++--- 9 files changed, 101 insertions(+), 80 deletions(-) delete mode 100644 data/interfaces/default/restart.html delete mode 100644 data/interfaces/default/update.html diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index f523535e..d68bf011 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -1,46 +1,42 @@ <%inherit file="base.html"/>
- - - - - - - - - - - - - -
-

HTTP Host:
-
- e.g. localhost or 0.0.0.0 -

-
-

HTTP Username:
- -

-
-

HTTP Port:
- -

-
-

HTTP Password:
- -

-
-

Launch Browser on Startup:

-
+

Web Interface

+ + + + + + + + + + + + +
+

HTTP Host:
+
+ e.g. localhost or 0.0.0.0 +

+
+

HTTP Username:
+ +

+
+

HTTP Port:
+ +

+
+

HTTP Password:
+ +

+
+

Launch Browser on Startup:

+
- -

- Download Settings -

-
+

Download Settings

+ @@ -16,10 +17,11 @@ %for album in upcoming: + - - + + %endfor @@ -28,6 +30,30 @@

Wanted Albums

+
diff --git a/data/interfaces/default/logs.html b/data/interfaces/default/logs.html index d9aad901..8434e3b6 100644 --- a/data/interfaces/default/logs.html +++ b/data/interfaces/default/logs.html @@ -19,7 +19,9 @@ %> %if out_tuple: <% - if out_tuple[1] == 'WARNING' or out_tuple[1] == 'ERROR': + if out_tuple[1] == 'DEBUG': + continue + elif out_tuple[1] == 'WARNING' or out_tuple[1] == 'ERROR': grade = 'X' else: grade = 'Z' diff --git a/data/interfaces/default/restart.html b/data/interfaces/default/restart.html deleted file mode 100644 index b2aafaa7..00000000 --- a/data/interfaces/default/restart.html +++ /dev/null @@ -1,9 +0,0 @@ -<%inherit file="base.html"/> - -<%def name="headIncludes()"> - - - -<%def name="body()"> - Headphones is restarting - \ No newline at end of file diff --git a/data/interfaces/default/shutdown.html b/data/interfaces/default/shutdown.html index f01313e1..8a882e39 100644 --- a/data/interfaces/default/shutdown.html +++ b/data/interfaces/default/shutdown.html @@ -5,5 +5,7 @@ <%def name="body()"> - Headphones is shutting down +
+ Headphones is ${message} +
\ No newline at end of file diff --git a/data/interfaces/default/upcoming.html b/data/interfaces/default/upcoming.html index 3bdd64f8..4eb45d72 100644 --- a/data/interfaces/default/upcoming.html +++ b/data/interfaces/default/upcoming.html @@ -2,9 +2,28 @@ <%def name="body()">

Upcoming Albums

- %for album in wanted: - - %endfor + + + + + + + + + + + + %for album in upcoming: + + + + + + + + %endfor + +
Album NameRelease DateRelease TypeStatus
${album['AlbumTitle']}${album['ReleaseDate']}
@@ -12,3 +31,22 @@
+ +<%def name="headIncludes()"> + + + +<%def name="javascriptIncludes()"> + + + \ No newline at end of file diff --git a/data/interfaces/default/update.html b/data/interfaces/default/update.html deleted file mode 100644 index ac8c7aa6..00000000 --- a/data/interfaces/default/update.html +++ /dev/null @@ -1,9 +0,0 @@ -<%inherit file="base.html"/> - -<%def name="headIncludes()"> - - - -<%def name="body()"> - Headphones is updating - \ No newline at end of file diff --git a/headphones/searcher.py b/headphones/searcher.py index d7bdb4c5..cf143672 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -115,7 +115,7 @@ def searchNZB(albumid=None, new=False): } searchURL = "http://rss.nzbmatrix.com/rss.php?" + urllib.urlencode(params) - logger.info(u"Parsing results from "+searchURL) + logger.debug(u'Parsing results from NZBMatrix' % searchURL) try: data = urllib2.urlopen(searchURL, timeout=20).read() except urllib2.URLError, e: @@ -161,7 +161,7 @@ def searchNZB(albumid=None, new=False): searchURL = headphones.NEWZNAB_HOST + '/api?' + urllib.urlencode(params) - logger.info(u"Parsing results from "+searchURL) + logger.debug(u'Parsing results from %s' % (searchURL, headphones.NEWZNAB_HOST)) try: data = urllib2.urlopen(searchURL, timeout=20).read() @@ -215,7 +215,7 @@ def searchNZB(albumid=None, new=False): } searchURL = 'https://secure.nzbs.org/rss.php?' + urllib.urlencode(params) - logger.info(u"Parsing results from "+searchURL) + logger.debug(u'Parsing results from %s' % (searchURL, provider)) try: data = urllib2.urlopen(searchURL, timeout=20).read() @@ -293,7 +293,7 @@ def searchNZB(albumid=None, new=False): searchURL = providerurl + "search/?%s" % urllib.urlencode(params) data = getNewzbinURL(searchURL) if data: - logger.info(u"Parsing results from "+searchURL) + logger.debug(u'Parsing results from %s' % (searchURL, providerurl)) try: d = minidom.parseString(data) @@ -440,7 +440,7 @@ def searchNZB(albumid=None, new=False): linkparams["nzbname"] = nzb_folder_name saburl = 'http://' + headphones.SAB_HOST + '/sabnzbd/api?' + urllib.urlencode(linkparams) - logger.info(u"Sending link to SABNZBD: " + saburl) + logger.debug(u"Sending link to SABNZBD: " + saburl) try: urllib.urlopen(saburl) diff --git a/headphones/updater.py b/headphones/updater.py index 77fe5485..a7cd3b95 100644 --- a/headphones/updater.py +++ b/headphones/updater.py @@ -15,10 +15,4 @@ def dbUpdate(): artistid = artist[0] importer.addArtisttoDB(artistid) - logger.info('Update complete') - - - - - - + logger.info('Update complete') \ No newline at end of file diff --git a/headphones/webserve.py b/headphones/webserve.py index 73cb42bc..887354fb 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -206,8 +206,8 @@ class WebInterface(object): def upcoming(self): myDB = db.DBConnection() - upcoming = myDB.select("SELECT AlbumTitle, ReleaseDate, DateAdded, AlbumASIN, AlbumID, ArtistName, ArtistID from albums WHERE ReleaseDate > date('now') order by ReleaseDate DESC") - wanted = myDB.select("SELECT AlbumTitle, ReleaseDate, DateAdded, AlbumASIN, AlbumID, ArtistName, ArtistID from albums WHERE Status='Wanted'") + upcoming = myDB.select("SELECT * from albums WHERE ReleaseDate > date('now') order by ReleaseDate DESC") + wanted = myDB.select("SELECT * from albums WHERE Status='Wanted'") return serve_template(templatename="upcoming.html", title="Upcoming", upcoming=upcoming, wanted=wanted) upcoming.exposed = True @@ -287,9 +287,13 @@ class WebInterface(object): logs.exposed = True def clearhistory(self, type=None): - logger.info(u"Clearing history where status is %s" % type) myDB = db.DBConnection() - myDB.action('DELETE from snatched WHERE Status=?', [type]) + if type == 'all': + logger.info(u"Clearing all history") + myDB.action('DELETE from snatched') + else: + logger.info(u"Clearing history where status is %s" % type) + myDB.action('DELETE from snatched WHERE Status=?', [type]) raise cherrypy.HTTPRedirect("history") clearhistory.exposed = True @@ -396,7 +400,8 @@ class WebInterface(object): def shutdown(self): logger.info(u"Headphones is shutting down...") threading.Timer(2, headphones.shutdown).start() - return serve_template(templatename="shutdown.html", title="Shutting Down") + message = 'Shutting Down' + return serve_template(templatename="shutdown.html", title="Shutting Down", message=message) return page shutdown.exposed = True @@ -404,13 +409,15 @@ class WebInterface(object): def restart(self): logger.info(u"Headphones is restarting...") threading.Timer(2, headphones.shutdown, [True]).start() - return serve_template(templatename="restart.html", title="Restarting") + message = 'Restarting' + return serve_template(templatename="shutdown.html", title="Restarting", message=message) restart.exposed = True def update(self): logger.info('Headphones is updating...') threading.Timer(2, headphones.shutdown, [True, True]).start() - return serve_template(templatename="update.html", title="Updating") + message = 'Updating' + return serve_template(templatename="shutdown.html", title="Updating", message=message) return page update.exposed = True From 6c235732e2da0b1b558ca2838f5c48e81073e0fe Mon Sep 17 00:00:00 2001 From: Remy Date: Sun, 7 Aug 2011 12:33:41 -0700 Subject: [PATCH 19/22] Modified base.html to include album search --- data/interfaces/default/base.html | 6 +++++- data/interfaces/default/upcoming.html | 30 +++++++++++++++++++++++++-- headphones/webserve.py | 30 ++++++--------------------- 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/data/interfaces/default/base.html b/data/interfaces/default/base.html index f9cea722..dc89306c 100755 --- a/data/interfaces/default/base.html +++ b/data/interfaces/default/base.html @@ -51,7 +51,11 @@
diff --git a/data/interfaces/default/upcoming.html b/data/interfaces/default/upcoming.html index 4eb45d72..086d8c6f 100644 --- a/data/interfaces/default/upcoming.html +++ b/data/interfaces/default/upcoming.html @@ -6,6 +6,7 @@
Artist Album Name Release Date Release Type
${album['ArtistName']} ${album['AlbumTitle']} ${album['ReleaseDate']}${album['Type']}${album['Status']}
+ + + + + + + + + + + + %for album in wanted: + + + + + + + + + %endfor + +
ArtistAlbum NameRelease DateRelease TypeStatus
${album['ArtistName']}${album['AlbumTitle']}${album['ReleaseDate']}${album['Type']}${album['Status']}
diff --git a/headphones/webserve.py b/headphones/webserve.py index 887354fb..a812b1a9 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -55,29 +55,14 @@ class WebInterface(object): albumPage.exposed = True - def findArtist(self, name): + def search(self, name, type): - page = [templates._header] - page.append(templates._logobar) - page.append(templates._nav) - if len(name) == 0 or name == 'Add an artist': + if len(name) == 0: raise cherrypy.HTTPRedirect("home") + if type == 'artist': + searchresults = mb.findArtist(name, limit=10) else: - artistResults = mb.findArtist(name, limit=10) - if not artistResults: - logger.info(u"No results found for " + name) - page.append('''

No results! Go back

''') - return page - elif len(artistResults) > 1: - page.append('''

Search returned multiple artists. Click the artist you want to add:

''') - for result in artistResults: - page.append('''

%s (more info)

''' % (result['id'], result['uniquename'], result['id'])) - page.append('''
''') - return page - else: - for result in artistResults: - logger.info(u"Found one artist matching your search term: " + result['name'] +" ("+ result['id']+")") - raise cherrypy.HTTPRedirect("addArtist?artistid=%s" % result['id']) + searchresults = mb.findRelease(name, limit=10) findArtist.exposed = True @@ -164,8 +149,7 @@ class WebInterface(object): deleteArtist.exposed = True def refreshArtist(self, ArtistID): - import importer - importer.addArtisttoDB(artistid) + importer.addArtisttoDB(ArtistID) refreshArtist.exposed=True def markAlbums(self, ArtistID=None, action=None, **args): @@ -175,7 +159,6 @@ class WebInterface(object): newValueDict = {'Status': action} myDB.upsert("albums", newValueDict, controlValueDict) if action == 'Wanted': - import searcher searcher.searchNZB(mbid, new=False) raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID) markAlbums.exposed = True @@ -187,7 +170,6 @@ class WebInterface(object): controlValueDict = {'AlbumID': AlbumID} newValueDict = {'Status': 'Wanted'} myDB.upsert("albums", newValueDict, controlValueDict) - import searcher searcher.searchNZB(AlbumID, new) raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID) queueAlbum.exposed = True From 45563c821cfa9c9f3bbc9c3bc541b85c94f3f715 Mon Sep 17 00:00:00 2001 From: Remy Date: Sun, 7 Aug 2011 21:18:42 -0700 Subject: [PATCH 20/22] Added descriptions from last fm, formatted album page, added retry buttons --- data/css/style.css | 39 +++++++++-- data/interfaces/default/album.html | 38 ++++++---- data/interfaces/default/artist.html | 13 +++- data/interfaces/default/base.html | 2 +- data/interfaces/default/config.html | 2 +- data/interfaces/default/history.html | 7 +- data/interfaces/default/manage.html | 38 ++++++++-- data/interfaces/default/searchresults.html | 70 +++++++++++++++++++ data/interfaces/default/shutdown.html | 2 +- data/interfaces/default/upcoming.html | 18 +++-- headphones/__init__.py | 1 + headphones/importer.py | 2 + headphones/lastfm.py | 45 +++++++++++- headphones/mb.py | 41 +++++++++++ headphones/webserve.py | 80 +++++++--------------- 15 files changed, 306 insertions(+), 92 deletions(-) create mode 100644 data/interfaces/default/searchresults.html diff --git a/data/css/style.css b/data/css/style.css index 9bd44217..50614f91 100755 --- a/data/css/style.css +++ b/data/css/style.css @@ -102,15 +102,15 @@ a.blue { container { } -body { background-color: #EBF4FB; min-width: 907px; } +body { background-color: #EBF4FB; min-width: 930px; } -header { min-height: 68px; width: 100%; min-width: 907px; padding-left: 0px; padding-right: 10px; background-color: #CDC9C9; position: fixed; z-index: 998; } +header { min-height: 68px; width: 100%; min-width: 930px; padding-left: 0px; padding-right: 10px; background-color: #CDC9C9; position: fixed; z-index: 998; } h1 { font-size: 24px; } h2 { font-size: 20px; } h3 { font-size: 16px; } -p.indented { margin-left: 20px; font-size: 14px; } +p.indented { padding-top: 20px; margin-left: 20px; font-size: 14px; } div#updatebar { text-align: center; min-width: 970px; width: 100%; background-color: light-blue; float: left; } div#logo { float: left; padding-left: 10px; } @@ -120,7 +120,7 @@ ul#nav li { margin: 40px 0px auto 10px; display: inline; } ul#nav li a { padding: 5px; font-size: 16px; font-weight: bold; color: #330000; text-decoration: none; } ul#nav li a:hover { background-color: #a3e532; } -div#subhead_container { height: 30px; width:100%; min-width: 907px; background-color:#330000; float: left; list-style-type: none; z-index: 998; overflow: hidden; } +div#subhead_container { height: 30px; width:100%; min-width: 930px; background-color:#330000; float: left; list-style-type: none; z-index: 998; overflow: hidden; } ul#subhead_menu { margin-top: 5px; } ul#subhead_menu li { width: 100%; height: 100%; display: inline; } ul#subhead_menu li a { padding: 5px 15px 10px 15px; vertical-align: middle; color: white; font-size: 16px; text-decoration: none; } @@ -131,6 +131,9 @@ div#searchbar { margin: 24px 30px auto auto; float: right; } div#main { margin: 0; padding: 80px 0 0 0; } .table_wrapper { border-radius: 20px; -webkit-border-radius: 20px; -moz-border-radius: 20px; width: 88%; margin: 20px auto 0 auto; padding: 25px; background-color: white; position: relative; min-height: 200px; clear: both; _height: 302px; zoom: 1; } +.manage_wrapper { width: 88%; margin: 20px auto 0 auto; padding: 25px; min-height: 150px; clear: both; _height: 302px; zoom: 1; } +.table_wrapper_left { padding: 25px; background-color: #ffffff; float: left; width: 40%; height: 200px; margin-top: 25px; margin-left: 30px; margin-right: auto; -moz-border-radius: 20px; border-radius: 20px; } +.table_wrapper_right{ padding: 25px; background-color: #ffffff; width: 40%; height: 200px; margin-top: 25px; margin-left: auto; margin-right: 30px; -moz-border-radius: 20px; border-radius: 20px; } table#artist_table { background-color: white; width: 100%; padding: 20px; } @@ -144,6 +147,7 @@ table#artist_table td#album { vertical-align: middle; text-align: left; min-widt table#artist_table td#have { vertical-align: middle; } div#paddingheader { padding-top: 48px; font-size: 24px; font-weight: bold; text-align: center; } +div#nopaddingheader { font-size: 24px; font-weight: bold; text-align: center; } table#album_table { background-color: white; } table#album_table th#select { vertical-align: middle; text-align: left; min-width: 25px; } @@ -161,15 +165,15 @@ table#album_table td#type { vertical-align: middle; text-align: center; } table#album_table td#have { vertical-align: middle; } img.albumArt { float: left; padding-right: 5px; } -div#albumheader { height: 200px; } +div#albumheader { padding-top: 48px; height: 200px; } div#track_wrapper { padding-top: 20px; text-align: center; font-size: 16px; } -table#track_table th#number { text-align: left; min-width: 50px; } +table#track_table th#number { text-align: right; min-width: 20px; } table#track_table th#name { text-align: center; min-width: 350px; } table#track_table th#duration { width: 175px; text-align: center; min-width: 100px; } table#track_table th#have { width: 175px; text-align: center; min-width: 100px; } -table#track_table td#number { vertical-align: middle; text-align: left; } +table#track_table td#number { vertical-align: middle; text-align: right; } table#track_table td#name { vertical-align: middle; text-align: center; } table#track_table td#duration { vertical-align: middle; text-align: center; } table#track_table td#have { vertical-align: middle; text-align: center; } @@ -182,6 +186,27 @@ table#log_table th#timestamp { text-align: left; min-width: 165px; } table#log_table th#level { text-align: left; min-width: 75px; } table#log_table th#message { text-align: left; min-width: 200px; } +table#upcoming_table th#albumart { text-align: center; min-width: 50px; } +table#upcoming_table th#albumname { text-align: center; min-width: 200px; } +table#upcoming_table th#artistname { text-align: center; min-width: 150px; } +table#upcoming_table th#reldate { text-align: center; min-width: 100px; } +table#upcoming_table th#type { text-align: center; min-width: 75px; } + +table#upcoming_table td#albumart { vertical-align: middle; text-align: center; min-width: 50px; } +table#upcoming_table td#albumname { vertical-align: middle; text-align: center; min-width: 200px; } +table#upcoming_table td#artistname { vertical-align: middle; text-align: center; min-width: 150px; } +table#upcoming_table td#reldate { vertical-align: middle; text-align: center; min-width: 100px; } +table#upcoming_table td#type { vertical-align: middle; text-align: center; min-width: 75px; } +table#upcoming_table td#status { vertical-align: middle; text-align: center; } + +table#searchresults_table th#albumname { text-align: left; min-width: 225px; } +table#searchresults_table th#artistname { text-align: center; min-width: 325px; } +table#searchresults_table th#score { text-align: center; min-width: 75px; } + +table#searchresults_table td#albumname { vertical-align: middle; text-align: left; min-width: 200px; } +table#searchresults_table td#artistname { vertical-align: middle; text-align: left; min-width: 300px; } +table#searchresults_table td#score { vertical-align: middle; text-align: center; min-width: 75px; } + div.progress-container { border: 1px solid #ccc; width: 100px; height: 14px; margin: 2px 5px 2px 0; padding: 1px; float: left; background: white; } div.progress-container > div { background-color: #a3e532; height: 14px; } .havetracks { font-size: 13px; margin-left: 36px; padding-bottom: 3px; vertical-align: middle; } diff --git a/data/interfaces/default/album.html b/data/interfaces/default/album.html index e242556d..6fda2b27 100644 --- a/data/interfaces/default/album.html +++ b/data/interfaces/default/album.html @@ -7,26 +7,36 @@ <%def name="headerIncludes()">
<%def name="body()">
+

<- Back to ${album['ArtistName']}

albumart

${album['AlbumTitle']}

${album['ArtistName']}


+ <% + totalduration = myDB.action("SELECT SUM(TrackDuration) FROM tracks WHERE AlbumID=?", [album['AlbumID']]).fetchone()[0] + totaltracks = len(myDB.select("SELECT TrackTitle from tracks WHERE AlbumID=?", [album['AlbumID']])) + %> +

Tracks: ${totaltracks}

+

Duration: ${helpers.convert_milliseconds(totalduration)}

+ %if description:

Description:

+ ${description['Summary']} + %endif
@@ -35,14 +45,18 @@ - + + <% + i = 0 + %> %for track in tracks: <% - trackmatch = 'asd' - if len(trackmatch): + i += 1 + have = myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND AlbumTitle like ? AND TrackTitle like ?', [album['ArtistName'], album['AlbumTitle'], track['TrackTitle']]) + if len(have): grade = 'A' check = 'checkmark' else: @@ -50,9 +64,9 @@ check = '' %> - + - + %endfor diff --git a/data/interfaces/default/artist.html b/data/interfaces/default/artist.html index 934d173a..9ded40dc 100644 --- a/data/interfaces/default/artist.html +++ b/data/interfaces/default/artist.html @@ -26,6 +26,7 @@

Mark selected albums as @@ -74,7 +75,15 @@

- + %endfor @@ -110,7 +119,7 @@ "sInfoEmpty":"Showing 0 to 0 of 0 albums", "sInfoFiltered":"(filtered from _MAX_ total albums)"}, "bPaginate": false, - "aaSorting": [[3, 'asc'],[2,'desc']] + "aaSorting": [[4, 'asc'],[3,'desc']] }); }); diff --git a/data/interfaces/default/base.html b/data/interfaces/default/base.html index dc89306c..3704a6c2 100755 --- a/data/interfaces/default/base.html +++ b/data/interfaces/default/base.html @@ -49,7 +49,7 @@
  • settings
  • # Track Title DurationHave
    #${i} ${track['TrackTitle']}${track['TrackDuration']}${helpers.convert_milliseconds(track['TrackDuration'])} ${check}
    ${album['AlbumTitle']} ${album['ReleaseDate']} ${album['Type']}${album['Status']}${album['Status']} + %if album['Status'] == 'Skipped': + [want] + %elif album['Status'] == 'Wanted': + [skip] + %else: + [retry][new] + %endif +
    ${havetracks}/${totaltracks}
    @@ -216,5 +217,4 @@

    - <%inherit file="base.html" /> \ No newline at end of file diff --git a/data/interfaces/default/history.html b/data/interfaces/default/history.html index f1002609..77d80d87 100644 --- a/data/interfaces/default/history.html +++ b/data/interfaces/default/history.html @@ -1,4 +1,7 @@ <%inherit file="base.html"/> +<%! + from headphones import helpers +%> <%def name="headerIncludes()">
    @@ -22,6 +25,7 @@
    + @@ -39,8 +43,9 @@ - + + %endfor diff --git a/data/interfaces/default/manage.html b/data/interfaces/default/manage.html index 0d91e773..28d9e015 100644 --- a/data/interfaces/default/manage.html +++ b/data/interfaces/default/manage.html @@ -1,4 +1,8 @@ <%inherit file="base.html" /> +<%! + import headphones +%> + <%def name="body()">

    Scan Music Library


    @@ -10,24 +14,44 @@ as soon as you click 'Submit'

    + %if headphones.MUSIC_DIR: + + %else: + + %endif
    -
    + +

    Import Last.FM Artists


    Enter the username whose artists you want to import:

    - -

    -

    Placeholder :-)


    +

    +
    + +
    +

    Placeholder :-)




    -


    - ''' % (music_dir_input, lastfm_user_text)) + Check for Headphones Updates


    + \ No newline at end of file diff --git a/data/interfaces/default/searchresults.html b/data/interfaces/default/searchresults.html new file mode 100644 index 00000000..5109d10b --- /dev/null +++ b/data/interfaces/default/searchresults.html @@ -0,0 +1,70 @@ +<%inherit file="base.html" /> + +<%def name="body()"> + +
    +

    Search Results

    +

    +
    File Name Size Status
    ${item['DateAdded']} ${item['Title']}${item['Size']}${helpers.bytes_to_mb(item['Size'])} ${item['Status']}[retry][new]
    + + + %if type == 'album': + + %endif + + + + + + + %if searchresults: + %for result in searchresults: + <% + if result['score'] == 100: + grade = 'A' + else: + grade = 'Z' + %> + + %if type == 'album': + + %endif + + + %if type == 'album': + + %else: + + %endif + + %endfor + %endif + +
    Album NameArtist NameScore
    ${result['title']}${result['uniquename']}${result['score']}Add this albumAdd this artist
    + + +<%def name="headIncludes()"> + + + +<%def name="javascriptIncludes()"> + + + \ No newline at end of file diff --git a/data/interfaces/default/shutdown.html b/data/interfaces/default/shutdown.html index 8a882e39..917be6b2 100644 --- a/data/interfaces/default/shutdown.html +++ b/data/interfaces/default/shutdown.html @@ -6,6 +6,6 @@ <%def name="body()">
    - Headphones is ${message} +

    Headphones is ${message}

    \ No newline at end of file diff --git a/data/interfaces/default/upcoming.html b/data/interfaces/default/upcoming.html index 086d8c6f..ffde5a35 100644 --- a/data/interfaces/default/upcoming.html +++ b/data/interfaces/default/upcoming.html @@ -9,7 +9,7 @@ Artist Album Name Release Date - Release Type + Type Status @@ -26,30 +26,38 @@ %endfor -
    + +
    +

    Mark selected albums as + + +

    Wanted Albums

    + - - + %for album in wanted: + - %endfor diff --git a/headphones/__init__.py b/headphones/__init__.py index 84023254..112cb4ce 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -429,6 +429,7 @@ def dbcheck(): c.execute('CREATE TABLE IF NOT EXISTS snatched (AlbumID TEXT, Title TEXT, Size INTEGER, URL TEXT, DateAdded TEXT, Status TEXT, FolderName TEXT)') c.execute('CREATE TABLE IF NOT EXISTS have (ArtistName TEXT, AlbumTitle TEXT, TrackNumber TEXT, TrackTitle TEXT, TrackLength TEXT, BitRate TEXT, Genre TEXT, Date TEXT, TrackID TEXT)') c.execute('CREATE TABLE IF NOT EXISTS lastfmcloud (ArtistName TEXT, ArtistID TEXT, Count INTEGER)') + c.execute('CREATE TABLE IF NOT EXISTS descriptions (ReleaseGroupID TEXT, ReleaseID TEXT, Summary TEXT, Content TEXT)') try: c.execute('SELECT IncludeExtras from artists') diff --git a/headphones/importer.py b/headphones/importer.py index a64e959e..88d5b350 100644 --- a/headphones/importer.py +++ b/headphones/importer.py @@ -215,6 +215,8 @@ def addArtisttoDB(artistid, extrasonly=False): myDB.upsert("albums", newValueDict, controlValueDict) + lastfm.getAlbumDescription(rg['id'], release_dict['releaselist']) + # I changed the albumid from releaseid -> rgid, so might need to delete albums that have a releaseid for release in release_dict['releaselist']: myDB.action('DELETE from albums WHERE AlbumID=?', [release['releaseid']]) diff --git a/headphones/lastfm.py b/headphones/lastfm.py index a3e9c8ec..914bd2b8 100644 --- a/headphones/lastfm.py +++ b/headphones/lastfm.py @@ -4,7 +4,7 @@ from collections import defaultdict import random import headphones -from headphones import db +from headphones import db, logger api_key = '395e6ec6bb557382fc41fde867bce66f' @@ -91,4 +91,47 @@ def getArtists(): for artistid in artistlist: importer.addArtisttoDB(artistid) +def getAlbumDescription(rgid, releaselist): + + myDB = db.DBConnection() + result = myDB.select('SELECT Summary from descriptions WHERE ReleaseGroupID=?', [rgid]) + + if result: + return + + for release in releaselist: + + mbid = release['releaseid'] + url = 'http://ws.audioscrobbler.com/2.0/?method=album.getInfo&mbid=%s&api_key=%s' % (mbid, api_key) + logger.info('Checking last.fm for: ' + mbid) + data = urllib.urlopen(url).read() + + if data == 'Album not found': + logger.info('Release id not on last fm, skipping') + continue + + try: + d = minidom.parseString(data) + + albuminfo = d.getElementsByTagName("album") + + for item in albuminfo: + summarynode = item.getElementsByTagName("summary")[0].childNodes + contentnode = item.getElementsByTagName("content")[0].childNodes + for node in summarynode: + summary = node.data + for node in contentnode: + content = node.data + + controlValueDict = {'ReleaseGroupID': rgid} + newValueDict = {'ReleaseID': mbid, + 'Summary': summary, + 'Content': content} + myDB.upsert("descriptions", newValueDict, controlValueDict) + logger.info('Inserted description') + break + + except: + continue + \ No newline at end of file diff --git a/headphones/mb.py b/headphones/mb.py index 16a9da71..92723fda 100644 --- a/headphones/mb.py +++ b/headphones/mb.py @@ -66,6 +66,47 @@ def findArtist(name, limit=1): }) return artistlist + +def findRelease(name, limit=1): + + with mb_lock: + + releaselist = [] + attempt = 0 + releaseResults = None + + chars = set('!?') + if any((c in chars) for c in name): + name = '"'+name+'"' + + while attempt < 5: + + try: + releaseResults = q.getReleases(ws.ReleaseFilter(query=name, limit=limit)) + break + except WebServiceError, e: + logger.warn('Attempt to query MusicBrainz for %s failed: %s' % (name, e)) + attempt += 1 + time.sleep(5) + + time.sleep(1) + + if not releaseResults: + return False + + for result in releaseResults: + + releaselist.append({ + 'uniquename': result.release.artist.name, + 'title': result.release.title, + 'id': u.extractUuid(result.release.artist.id), + 'albumid': u.extractUuid(result.release.id), + 'url': result.release.artist.id, + 'albumurl': result.release.id, + 'score': result.score + }) + + return releaselist def getArtist(artistid, extrasonly=False): diff --git a/headphones/webserve.py b/headphones/webserve.py index a812b1a9..cb6a98f9 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -10,7 +10,7 @@ import threading import headphones -from headphones import logger, searcher, db, importer, lastfm +from headphones import logger, searcher, db, importer, mb, lastfm from headphones.helpers import checked, radio @@ -50,140 +50,112 @@ class WebInterface(object): myDB = db.DBConnection() album = myDB.action('SELECT * from albums WHERE AlbumID=?', [AlbumID]).fetchone() tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [AlbumID]) + description = myDB.action('SELECT * from descriptions WHERE ReleaseGroupID=?', [AlbumID]).fetchone() title = album['ArtistName'] + ' - ' + album['AlbumTitle'] - return serve_template(templatename="album.html", title=title, album=album, tracks=tracks) + return serve_template(templatename="album.html", title=title, album=album, tracks=tracks, description=description) albumPage.exposed = True def search(self, name, type): - if len(name) == 0: raise cherrypy.HTTPRedirect("home") if type == 'artist': - searchresults = mb.findArtist(name, limit=10) + searchresults = mb.findArtist(name, limit=20) else: - searchresults = mb.findRelease(name, limit=10) - - findArtist.exposed = True - - def artistInfo(self, artistid): - page = [templates._header] - page.append(templates._logobar) - page.append(templates._nav) - artist = mb.getArtist(artistid) - if artist['artist_begindate']: - begindate = artist['artist_begindate'] - else: - begindate = '' - if artist['artist_enddate']: - enddate = artist['artist_enddate'] - else: - enddate = '' - page.append('''

    Artist Information:

    ''') - page.append('''

    Artist Name: %s (%s)
    ''' % (artist['artist_name'], artist['artist_type'])) - page.append('''

    Years Active: %s - %s

    ''' % (begindate, enddate)) - page.append('''MusicBrainz Link: http://www.musicbrainz.org/artist/%s

    Albums:
    ''' % (artistid, artistid)) - for rg in artist['releasegroups']: - page.append('''%s
    ''' % rg['title']) - page.append('''

    ''' % artistid) - return page - - artistInfo.exposed = True + searchresults = mb.findRelease(name, limit=20) + return serve_template(templatename="searchresults.html", title='Search Results for: "' + name + '"', searchresults=searchresults, type=type) + search.exposed = True def addArtist(self, artistid): - threading.Thread(target=importer.addArtisttoDB, args=[artistid]).start() time.sleep(5) threading.Thread(target=lastfm.getSimilar).start() raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % artistid) - addArtist.exposed = True def getExtras(self, ArtistID): - myDB = db.DBConnection() controlValueDict = {'ArtistID': ArtistID} newValueDict = {'IncludeExtras': 1} myDB.upsert("artists", newValueDict, controlValueDict) - threading.Thread(target=importer.addArtisttoDB, args=[ArtistID, True]).start() time.sleep(10) raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID) - getExtras.exposed = True def pauseArtist(self, ArtistID): - logger.info(u"Pausing artist: " + ArtistID) myDB = db.DBConnection() controlValueDict = {'ArtistID': ArtistID} newValueDict = {'Status': 'Paused'} myDB.upsert("artists", newValueDict, controlValueDict) - - raise cherrypy.HTTPRedirect("home") - + raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID) pauseArtist.exposed = True def resumeArtist(self, ArtistID): - logger.info(u"Resuming artist: " + ArtistID) myDB = db.DBConnection() controlValueDict = {'ArtistID': ArtistID} newValueDict = {'Status': 'Active'} myDB.upsert("artists", newValueDict, controlValueDict) - - raise cherrypy.HTTPRedirect("home") - + raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID) resumeArtist.exposed = True def deleteArtist(self, ArtistID): - logger.info(u"Deleting all traces of artist: " + ArtistID) myDB = db.DBConnection() myDB.action('DELETE from artists WHERE ArtistID=?', [ArtistID]) myDB.action('DELETE from albums WHERE ArtistID=?', [ArtistID]) myDB.action('DELETE from tracks WHERE ArtistID=?', [ArtistID]) - raise cherrypy.HTTPRedirect("home") - deleteArtist.exposed = True def refreshArtist(self, ArtistID): importer.addArtisttoDB(ArtistID) + raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID) refreshArtist.exposed=True def markAlbums(self, ArtistID=None, action=None, **args): myDB = db.DBConnection() + if action == 'WantedNew': + newaction = 'Wanted' + else: + newaction = action for mbid in args: controlValueDict = {'AlbumID': mbid} - newValueDict = {'Status': action} + newValueDict = {'Status': newaction} myDB.upsert("albums", newValueDict, controlValueDict) if action == 'Wanted': searcher.searchNZB(mbid, new=False) - raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID) + if action == 'WantedNew': + searcher.searchNZB(mbid, new=True) + if ArtistID: + raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID) + else: + raise cherrypy.HTTPRedirect("upcoming") markAlbums.exposed = True - def queueAlbum(self, AlbumID, ArtistID, new=False): + def queueAlbum(self, AlbumID, ArtistID=None, new=False, redirect=None): logger.info(u"Marking album: " + AlbumID + "as wanted...") myDB = db.DBConnection() controlValueDict = {'AlbumID': AlbumID} newValueDict = {'Status': 'Wanted'} myDB.upsert("albums", newValueDict, controlValueDict) searcher.searchNZB(AlbumID, new) - raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID) + if ArtistID: + raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID) + else: + raise cherrypy.HTTPRedirect(redirect) queueAlbum.exposed = True def unqueueAlbum(self, AlbumID, ArtistID): - logger.info(u"Marking album: " + AlbumID + "as skipped...") myDB = db.DBConnection() controlValueDict = {'AlbumID': AlbumID} newValueDict = {'Status': 'Skipped'} myDB.upsert("albums", newValueDict, controlValueDict) - raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID) - unqueueAlbum.exposed = True def upcoming(self): From e42cacbe801c0214d9bfe9fcf8ff6320b83a4800 Mon Sep 17 00:00:00 2001 From: Remy Date: Sun, 7 Aug 2011 23:17:54 -0700 Subject: [PATCH 21/22] Config styling, cleaned up lastfm description logging --- data/css/style.css | 12 +- data/interfaces/default/config.html | 437 +++++++++++++++------------- data/interfaces/default/manage.html | 8 +- headphones/lastfm.py | 5 +- headphones/webserve.py | 7 +- 5 files changed, 251 insertions(+), 218 deletions(-) diff --git a/data/css/style.css b/data/css/style.css index 50614f91..b139cddc 100755 --- a/data/css/style.css +++ b/data/css/style.css @@ -111,6 +111,8 @@ h2 { font-size: 20px; } h3 { font-size: 16px; } p.indented { padding-top: 20px; margin-left: 20px; font-size: 14px; } +p.center { text-align: center; font-size: 18px; } +.smalltext2 { font-size: 11px; margin-left: 45px; } div#updatebar { text-align: center; min-width: 970px; width: 100%; background-color: light-blue; float: left; } div#logo { float: left; padding-left: 10px; } @@ -120,7 +122,7 @@ ul#nav li { margin: 40px 0px auto 10px; display: inline; } ul#nav li a { padding: 5px; font-size: 16px; font-weight: bold; color: #330000; text-decoration: none; } ul#nav li a:hover { background-color: #a3e532; } -div#subhead_container { height: 30px; width:100%; min-width: 930px; background-color:#330000; float: left; list-style-type: none; z-index: 998; overflow: hidden; } +div#subhead_container { height: 30px; width:100%; min-width: 1000px; background-color:#330000; float: left; list-style-type: none; z-index: 998; overflow: hidden; } ul#subhead_menu { margin-top: 5px; } ul#subhead_menu li { width: 100%; height: 100%; display: inline; } ul#subhead_menu li a { padding: 5px 15px 10px 15px; vertical-align: middle; color: white; font-size: 16px; text-decoration: none; } @@ -132,8 +134,12 @@ div#main { margin: 0; padding: 80px 0 0 0; } .table_wrapper { border-radius: 20px; -webkit-border-radius: 20px; -moz-border-radius: 20px; width: 88%; margin: 20px auto 0 auto; padding: 25px; background-color: white; position: relative; min-height: 200px; clear: both; _height: 302px; zoom: 1; } .manage_wrapper { width: 88%; margin: 20px auto 0 auto; padding: 25px; min-height: 150px; clear: both; _height: 302px; zoom: 1; } -.table_wrapper_left { padding: 25px; background-color: #ffffff; float: left; width: 40%; height: 200px; margin-top: 25px; margin-left: 30px; margin-right: auto; -moz-border-radius: 20px; border-radius: 20px; } -.table_wrapper_right{ padding: 25px; background-color: #ffffff; width: 40%; height: 200px; margin-top: 25px; margin-left: auto; margin-right: 30px; -moz-border-radius: 20px; border-radius: 20px; } +.table_wrapper_left { padding: 25px; background-color: #ffffff; float: left; width: 40%; min-height: 100px; margin-top: 25px; margin-left: 30px; margin-right: auto; -moz-border-radius: 20px; border-radius: 20px; } +.table_wrapper_right{ padding: 25px; background-color: #ffffff; width: 40%; min-height: 100px; margin-top: 25px; margin-left: auto; margin-right: 30px; -moz-border-radius: 20px; border-radius: 20px; } + +.configtable { font-size: 14px; line-height:18px; } +.configtable td { width: 350px; padding: 10px; vertical-align: middle; } +.configtable tr { vertical-align: text-top; } table#artist_table { background-color: white; width: 100%; padding: 20px; } diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index be26c7f4..bea006a8 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -1,220 +1,247 @@ <%inherit file="base.html"/> - -
    + +<%def name="body()">
    -

    Web Interface

    -
    Artist Album Name Release DateRelease TypeStatusType
    ${album['ArtistName']} ${album['AlbumTitle']} ${album['ReleaseDate']} ${album['Type']}${album['Status']}
    + +

    Web Interface

    + +
    + + + + + + + + + + + + + + +
    +

    HTTP Host:

    +
    + i.e. localhost or 0.0.0.0 +
    +

    HTTP Username:

    + +
    +

    HTTP Port:

    + +
    +

    HTTP Password:

    + +
    +

    Launch Browser on Startup:

    +
    +
    +
    +

    Download Settings

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    SABnzbd Host:


    + + usually localhost:8080 +
    +

    SABnzbd Username:

    +
    +

    SABnzbd API:

    +
    +

    SABnzbd Password:

    +
    +

    SABnzbd Category:

    +
    +

    Music Download Directory:


    + + Full path to the directory where SAB downloads your music
    + i.e. /Users/name/Downloads/music
    +
    +

    Use Black Hole:

    +
    +

    Black Hole Directory:


    + + Folder your Download program watches for NZBs +
    +

    Usenet Retention:

    +
    +
    +
    +

    Search Providers

    + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - + + + + + + +
    +

    NZBMatrix:

    +
    +

    NZBMatrix Username:

    + +
    +

    NZBMatrix API:

    + +
    +

    Newznab:

    +
    +

    Newznab Host:

    +
    + i.e. http://nzb.su +
    +
    + +

    + Newznab API:

    + + +
    + +

    NZBs.org:

    +
    + +

    NZBs.org UID:

    + + +
    + +

    NZBs.org Hash:

    + + +
    -

    HTTP Host:
    -
    - e.g. localhost or 0.0.0.0 -

    -
    -

    HTTP Username:
    - -

    -
    -

    HTTP Port:
    - -

    -
    -

    HTTP Password:
    - -

    -
    -

    Launch Browser on Startup:

    -
    +

    Newzbin:

    +
    +

    Newzbin UID:

    + + +
    +
    + +

    Newzbin Password:

    + +
    +
    +
    +

    Quality & Post Processing

    + + + + + + + + + +
    +

    Album Quality:


    + Highest Quality excluding Lossless
    + Highest Quality including Lossless
    + Lossless Only
    + Preferred Bitrate: + kbps
    + Auto-Detect Preferred Bitrate +
    +

    Post-Processing:

    + Move downloads to Destination Folder
    + Rename files
    + Correct metadata
    + Delete leftover files (.m3u, .nfo, .sfv, .nzb, etc.)
    + Add album art as 'folder.jpg' to album folder
    + Embed album art in each file +
    +
    + +

    Path to Destination folder:

    +
    + i.e. /Users/name/Music/iTunes or /Volumes/share/music +
    -

    Download Settings

    - - - - - - - - - - - - - - - - - - - - -
    -

    SABnzbd Host:

    -
    - e.g. localhost:8080 -
    -

    SABnzbd Username:

    - -
    -

    SABnzbd API:

    - -
    -

    SABnzbd Password:

    - -
    -

    SABnzbd Category:

    -
    -

    Music Download Directory:

    -
    - Full path to the directory where SAB downloads your music
    - e.g. Downloads/music or /Users/name/Downloads/music
    -
    -

    Use Black Hole:

    - -
    -

    Black Hole Directory:

    -
    - Folder your Download program watches for NZBs -
    -

    Usenet Retention:

    - -
    -
    -
    - -

    - Search Providers -

    -
    - - - - - - - - - - - - - - - - -
    -

    NZBMatrix:

    -
    -

    NZBMatrix Username:

    -
    -

    NZBMatrix API:

    -
    -

    Newznab:

    -
    -

    Newznab Host:

    - i.e. http://nzb.su

    -
    -

    Newznab API:

    -
    -

    NZBs.org:

    -
    -

    NZBs.org UID:

    -
    -

    NZBs.org Hash:

    -
    -
    -
    - -

    - Quality & Post Processing -

    -
    - - - - - - - - -
    - Album Quality: -

    - Highest Quality excluding Lossless
    - Highest Quality including Lossless
    - Lossless Only
    - Preferred Bitrate: kbps
    - Auto-Detect Preferred Bitrate -

    -
    - Post-Processing: -

    - Move downloads to Music Folder
    - Rename & add metadata
    - Correct metadata
    - Delete leftover files
    - Add album art
    - Embed album art in each file
    -

    -
    -

    - Path to Music folder:
    -
    - e.g. /Users/name/Music/iTunes or /Volumes/share/music -

    -
    -
    -
    - -

    - Advanced Settings -

    -
    - +

    Advanced Settings

    + +
    - Renaming Options: -

    - Folder Format:
    - -
    - Use: artist, album and year, '/' for directories.
    E.g.: artist/album [year]
    -

    -

    - File Format:
    - -
    - Use: tracknumber, title, artist, album and year -

    +

    Renaming Options:

    +
    +

    Folder Format:

    + Use: artist, album, year and first (first letter in artist name)
    + E.g.: first/artist/album [year] = G/Girl Talk/All Day [2010]
    +

    +

    File Format:

    +
    + Use: tracknumber, title, artist, album and year
    - Miscellaneous: -

    - Automatically Include Extras When Adding an Artist
    - Extras includes: EPs, Compilations, Live Albums, Remix Albums and Singles -

    - Log Directory: -

    - -

    +

    Miscellaneous:

    +
    +

    Automatically Include Extras When Adding an Artist


    + Extras includes: EPs, Compilations, Live Albums, Remix Albums and Singles +


    +

    Log Directory:

    -

    -
    - (Web Interface changes require a restart to take effect) -

    -
    -
    + +


    + (Web Interface changes require a restart to take effect) + + <%inherit file="base.html" /> \ No newline at end of file diff --git a/data/interfaces/default/manage.html b/data/interfaces/default/manage.html index 28d9e015..8b891317 100644 --- a/data/interfaces/default/manage.html +++ b/data/interfaces/default/manage.html @@ -49,9 +49,9 @@

    \ No newline at end of file diff --git a/headphones/lastfm.py b/headphones/lastfm.py index 914bd2b8..0e39a110 100644 --- a/headphones/lastfm.py +++ b/headphones/lastfm.py @@ -103,11 +103,9 @@ def getAlbumDescription(rgid, releaselist): mbid = release['releaseid'] url = 'http://ws.audioscrobbler.com/2.0/?method=album.getInfo&mbid=%s&api_key=%s' % (mbid, api_key) - logger.info('Checking last.fm for: ' + mbid) data = urllib.urlopen(url).read() - if data == 'Album not found': - logger.info('Release id not on last fm, skipping') + if data == 'Album not found': continue try: @@ -128,7 +126,6 @@ def getAlbumDescription(rgid, releaselist): 'Summary': summary, 'Content': content} myDB.upsert("descriptions", newValueDict, controlValueDict) - logger.info('Inserted description') break except: diff --git a/headphones/webserve.py b/headphones/webserve.py index cb6a98f9..d3ffc7f2 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -276,10 +276,13 @@ class WebInterface(object): "use_nzbsorg" : checked(headphones.NZBSORG), "nzbsorg_uid" : headphones.NZBSORG_UID, "nzbsorg_hash" : headphones.NZBSORG_HASH, + "use_newzbin" : checked(headphones.NEWZBIN), + "newzbin_uid" : headphones.NEWZBIN_UID, + "newzbin_pass" : headphones.NEWZBIN_PASSWORD, "pref_qual_0" : radio(headphones.PREFERRED_QUALITY, 0), "pref_qual_1" : radio(headphones.PREFERRED_QUALITY, 1), - "pref_qual_2" : radio(headphones.PREFERRED_QUALITY, 3), - "pref_qual_3" : radio(headphones.PREFERRED_QUALITY, 2), + "pref_qual_3" : radio(headphones.PREFERRED_QUALITY, 3), + "pref_qual_2" : radio(headphones.PREFERRED_QUALITY, 2), "pref_bitrate" : headphones.PREFERRED_BITRATE, "detect_bitrate" : checked(headphones.DETECT_BITRATE), "move_files" : checked(headphones.MOVE_FILES), From 4726c3c91f067bfad8f14d722d6de2cb9d34fee9 Mon Sep 17 00:00:00 2001 From: Remy Date: Mon, 8 Aug 2011 00:06:31 -0700 Subject: [PATCH 22/22] Finishing touches - adjusted css for config, fixed bug when no track duration, helpers now converts milliseconds to H:M:S if its more than an hour --- data/css/style.css | 2 ++ data/interfaces/default/album.html | 15 ++++++++++++--- data/interfaces/default/config.html | 12 +++++++++++- data/interfaces/default/shutdown.html | 4 +++- headphones/helpers.py | 6 ++++-- headphones/webserve.py | 13 +++++++------ 6 files changed, 39 insertions(+), 13 deletions(-) diff --git a/data/css/style.css b/data/css/style.css index b139cddc..04e1d56f 100755 --- a/data/css/style.css +++ b/data/css/style.css @@ -221,6 +221,8 @@ footer { margin: 20px auto 20px auto; } div#version { text-align: center; font-weight: bold; } div#donate { text-align: center; margin: 20px auto 20px auto; } +div#shutdown{ text-align: center; vertical-align: middle; } + .cloudtag { padding-top: 30px; font-size:16px; } #cloud a.tag1 { font-size: 0.7em; font-weight: 100; } #cloud a.tag2 { font-size: 0.8em; font-weight: 200; } diff --git a/data/interfaces/default/album.html b/data/interfaces/default/album.html index 6fda2b27..e8406f63 100644 --- a/data/interfaces/default/album.html +++ b/data/interfaces/default/album.html @@ -30,9 +30,14 @@ <% totalduration = myDB.action("SELECT SUM(TrackDuration) FROM tracks WHERE AlbumID=?", [album['AlbumID']]).fetchone()[0] totaltracks = len(myDB.select("SELECT TrackTitle from tracks WHERE AlbumID=?", [album['AlbumID']])) + try: + albumduration = helpers.convert_milliseconds(totalduration) + except: + albumduration = 'n/a' + %>

    Tracks: ${totaltracks}

    -

    Duration: ${helpers.convert_milliseconds(totalduration)}

    +

    Duration: ${albumduration}

    %if description:

    Description:

    ${description['Summary']} @@ -55,18 +60,22 @@ %for track in tracks: <% i += 1 - have = myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND AlbumTitle like ? AND TrackTitle like ?', [album['ArtistName'], album['AlbumTitle'], track['TrackTitle']]) + have = myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND AlbumTitle like ? AND TrackTitle like ?', [track['ArtistName'], track['AlbumTitle'], track['TrackTitle']]) if len(have): grade = 'A' check = 'checkmark' else: grade = 'Z' check = '' + try: + trackduration = helpers.convert_milliseconds(track['TrackDuration']) + except: + trackduration = 'n/a' %> ${i} ${track['TrackTitle']} - ${helpers.convert_milliseconds(track['TrackDuration'])} + ${trackduration} ${check} %endfor diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index bea006a8..dd8c0589 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -1,6 +1,16 @@ <%inherit file="base.html"/> - +<%def name="headerIncludes()"> +
    + +
    + <%def name="body()"> +
    +

    +

    Web Interface

    diff --git a/data/interfaces/default/shutdown.html b/data/interfaces/default/shutdown.html index 917be6b2..be7ca21d 100644 --- a/data/interfaces/default/shutdown.html +++ b/data/interfaces/default/shutdown.html @@ -1,11 +1,13 @@ <%inherit file="base.html"/> <%def name="headIncludes()"> - + <%def name="body()">
    +

    Headphones is ${message}

    +
    \ No newline at end of file diff --git a/headphones/helpers.py b/headphones/helpers.py index b2e6ca1f..9dc3aa10 100644 --- a/headphones/helpers.py +++ b/headphones/helpers.py @@ -79,8 +79,10 @@ def convert_milliseconds(ms): seconds = ms/1000 gmtime = time.gmtime(seconds) - - minutes = time.strftime("%M:%S", gmtime) + if seconds > 3600: + minutes = time.strftime("%H:%M:%S", gmtime) + else: + minutes = time.strftime("%M:%S", gmtime) return minutes diff --git a/headphones/webserve.py b/headphones/webserve.py index d3ffc7f2..fd406306 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -357,8 +357,8 @@ class WebInterface(object): def shutdown(self): logger.info(u"Headphones is shutting down...") threading.Timer(2, headphones.shutdown).start() - message = 'Shutting Down' - return serve_template(templatename="shutdown.html", title="Shutting Down", message=message) + message = 'Shutting Down...' + return serve_template(templatename="shutdown.html", title="Shutting Down", message=message, timer=15) return page shutdown.exposed = True @@ -366,15 +366,15 @@ class WebInterface(object): def restart(self): logger.info(u"Headphones is restarting...") threading.Timer(2, headphones.shutdown, [True]).start() - message = 'Restarting' - return serve_template(templatename="shutdown.html", title="Restarting", message=message) + message = 'Restarting...' + return serve_template(templatename="shutdown.html", title="Restarting", message=message, timer=30) restart.exposed = True def update(self): logger.info('Headphones is updating...') threading.Timer(2, headphones.shutdown, [True, True]).start() - message = 'Updating' - return serve_template(templatename="shutdown.html", title="Updating", message=message) + message = 'Updating...' + return serve_template(templatename="shutdown.html", title="Updating", message=message, timer=120) return page update.exposed = True @@ -388,6 +388,7 @@ class WebInterface(object): def addReleaseById(self, rid): threading.Thread(target=importer.addReleaseById, args=[rid]).start() + time.sleep(5) raise cherrypy.HTTPRedirect("home") addReleaseById.exposed = True