.\n\n.list-group {\n // No need to set list-style: none; since .list-group-item is block level\n padding-left: 0; // reset padding because ul and ol\n margin-bottom: 20px;\n}\n\n\n// Individual list items\n//\n// Use on `li`s or `div`s within the `.list-group` parent.\n\n.list-group-item {\n position: relative;\n display: block;\n padding: 10px 15px;\n // Place the border on the list items and negative margin up for better styling\n margin-bottom: -1px;\n background-color: @list-group-bg;\n border: 1px solid @list-group-border;\n\n // Round the first and last items\n &:first-child {\n .border-top-radius(@list-group-border-radius);\n }\n &:last-child {\n margin-bottom: 0;\n .border-bottom-radius(@list-group-border-radius);\n }\n\n // Disabled state\n &.disabled,\n &.disabled:hover,\n &.disabled:focus {\n color: @list-group-disabled-color;\n cursor: @cursor-disabled;\n background-color: @list-group-disabled-bg;\n\n // Force color to inherit for custom content\n .list-group-item-heading {\n color: inherit;\n }\n .list-group-item-text {\n color: @list-group-disabled-text-color;\n }\n }\n\n // Active class on item itself, not parent\n &.active,\n &.active:hover,\n &.active:focus {\n z-index: 2; // Place active items above their siblings for proper border styling\n color: @list-group-active-color;\n background-color: @list-group-active-bg;\n border-color: @list-group-active-border;\n\n // Force color to inherit for custom content\n .list-group-item-heading,\n .list-group-item-heading > small,\n .list-group-item-heading > .small {\n color: inherit;\n }\n .list-group-item-text {\n color: @list-group-active-text-color;\n }\n }\n}\n\n\n// Interactive list items\n//\n// Use anchor or button elements instead of `li`s or `div`s to create interactive items.\n// Includes an extra `.active` modifier class for showing selected items.\n\na.list-group-item,\nbutton.list-group-item {\n color: @list-group-link-color;\n\n .list-group-item-heading {\n color: @list-group-link-heading-color;\n }\n\n // Hover state\n &:hover,\n &:focus {\n color: @list-group-link-hover-color;\n text-decoration: none;\n background-color: @list-group-hover-bg;\n }\n}\n\nbutton.list-group-item {\n width: 100%;\n text-align: left;\n}\n\n\n// Contextual variants\n//\n// Add modifier classes to change text and background color on individual items.\n// Organizationally, this must come after the `:hover` states.\n\n.list-group-item-variant(success; @state-success-bg; @state-success-text);\n.list-group-item-variant(info; @state-info-bg; @state-info-text);\n.list-group-item-variant(warning; @state-warning-bg; @state-warning-text);\n.list-group-item-variant(danger; @state-danger-bg; @state-danger-text);\n\n\n// Custom content options\n//\n// Extra classes for creating well-formatted content within `.list-group-item`s.\n\n.list-group-item-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.list-group-item-text {\n margin-bottom: 0;\n line-height: 1.3;\n}\n","// List Groups\n\n.list-group-item-variant(@state; @background; @color) {\n .list-group-item-@{state} {\n color: @color;\n background-color: @background;\n\n a&,\n button& {\n color: @color;\n\n .list-group-item-heading {\n color: inherit;\n }\n\n &:hover,\n &:focus {\n color: @color;\n background-color: darken(@background, 5%);\n }\n &.active,\n &.active:hover,\n &.active:focus {\n color: #fff;\n background-color: @color;\n border-color: @color;\n }\n }\n }\n}\n","// stylelint-disable selector-max-type, selector-max-compound-selectors, selector-max-combinators, no-duplicate-selectors\n\n//\n// Panels\n// --------------------------------------------------\n\n\n// Base class\n.panel {\n margin-bottom: @line-height-computed;\n background-color: @panel-bg;\n border: 1px solid transparent;\n border-radius: @panel-border-radius;\n .box-shadow(0 1px 1px rgba(0, 0, 0, .05));\n}\n\n// Panel contents\n.panel-body {\n padding: @panel-body-padding;\n &:extend(.clearfix all);\n}\n\n// Optional heading\n.panel-heading {\n padding: @panel-heading-padding;\n border-bottom: 1px solid transparent;\n .border-top-radius((@panel-border-radius - 1));\n\n > .dropdown .dropdown-toggle {\n color: inherit;\n }\n}\n\n// Within heading, strip any `h*` tag of its default margins for spacing.\n.panel-title {\n margin-top: 0;\n margin-bottom: 0;\n font-size: ceil((@font-size-base * 1.125));\n color: inherit;\n\n > a,\n > small,\n > .small,\n > small > a,\n > .small > a {\n color: inherit;\n }\n}\n\n// Optional footer (stays gray in every modifier class)\n.panel-footer {\n padding: @panel-footer-padding;\n background-color: @panel-footer-bg;\n border-top: 1px solid @panel-inner-border;\n .border-bottom-radius((@panel-border-radius - 1));\n}\n\n\n// List groups in panels\n//\n// By default, space out list group content from panel headings to account for\n// any kind of custom content between the two.\n\n.panel {\n > .list-group,\n > .panel-collapse > .list-group {\n margin-bottom: 0;\n\n .list-group-item {\n border-width: 1px 0;\n border-radius: 0;\n }\n\n // Add border top radius for first one\n &:first-child {\n .list-group-item:first-child {\n border-top: 0;\n .border-top-radius((@panel-border-radius - 1));\n }\n }\n\n // Add border bottom radius for last one\n &:last-child {\n .list-group-item:last-child {\n border-bottom: 0;\n .border-bottom-radius((@panel-border-radius - 1));\n }\n }\n }\n > .panel-heading + .panel-collapse > .list-group {\n .list-group-item:first-child {\n .border-top-radius(0);\n }\n }\n}\n// Collapse space between when there's no additional content.\n.panel-heading + .list-group {\n .list-group-item:first-child {\n border-top-width: 0;\n }\n}\n.list-group + .panel-footer {\n border-top-width: 0;\n}\n\n// Tables in panels\n//\n// Place a non-bordered `.table` within a panel (not within a `.panel-body`) and\n// watch it go full width.\n\n.panel {\n > .table,\n > .table-responsive > .table,\n > .panel-collapse > .table {\n margin-bottom: 0;\n\n caption {\n padding-right: @panel-body-padding;\n padding-left: @panel-body-padding;\n }\n }\n // Add border top radius for first one\n > .table:first-child,\n > .table-responsive:first-child > .table:first-child {\n .border-top-radius((@panel-border-radius - 1));\n\n > thead:first-child,\n > tbody:first-child {\n > tr:first-child {\n border-top-left-radius: (@panel-border-radius - 1);\n border-top-right-radius: (@panel-border-radius - 1);\n\n td:first-child,\n th:first-child {\n border-top-left-radius: (@panel-border-radius - 1);\n }\n td:last-child,\n th:last-child {\n border-top-right-radius: (@panel-border-radius - 1);\n }\n }\n }\n }\n // Add border bottom radius for last one\n > .table:last-child,\n > .table-responsive:last-child > .table:last-child {\n .border-bottom-radius((@panel-border-radius - 1));\n\n > tbody:last-child,\n > tfoot:last-child {\n > tr:last-child {\n border-bottom-right-radius: (@panel-border-radius - 1);\n border-bottom-left-radius: (@panel-border-radius - 1);\n\n td:first-child,\n th:first-child {\n border-bottom-left-radius: (@panel-border-radius - 1);\n }\n td:last-child,\n th:last-child {\n border-bottom-right-radius: (@panel-border-radius - 1);\n }\n }\n }\n }\n > .panel-body + .table,\n > .panel-body + .table-responsive,\n > .table + .panel-body,\n > .table-responsive + .panel-body {\n border-top: 1px solid @table-border-color;\n }\n > .table > tbody:first-child > tr:first-child th,\n > .table > tbody:first-child > tr:first-child td {\n border-top: 0;\n }\n > .table-bordered,\n > .table-responsive > .table-bordered {\n border: 0;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th:first-child,\n > td:first-child {\n border-left: 0;\n }\n > th:last-child,\n > td:last-child {\n border-right: 0;\n }\n }\n }\n > thead,\n > tbody {\n > tr:first-child {\n > td,\n > th {\n border-bottom: 0;\n }\n }\n }\n > tbody,\n > tfoot {\n > tr:last-child {\n > td,\n > th {\n border-bottom: 0;\n }\n }\n }\n }\n > .table-responsive {\n margin-bottom: 0;\n border: 0;\n }\n}\n\n\n// Collapsible panels (aka, accordion)\n//\n// Wrap a series of panels in `.panel-group` to turn them into an accordion with\n// the help of our collapse JavaScript plugin.\n\n.panel-group {\n margin-bottom: @line-height-computed;\n\n // Tighten up margin so it's only between panels\n .panel {\n margin-bottom: 0;\n border-radius: @panel-border-radius;\n\n + .panel {\n margin-top: 5px;\n }\n }\n\n .panel-heading {\n border-bottom: 0;\n\n + .panel-collapse > .panel-body,\n + .panel-collapse > .list-group {\n border-top: 1px solid @panel-inner-border;\n }\n }\n\n .panel-footer {\n border-top: 0;\n + .panel-collapse .panel-body {\n border-bottom: 1px solid @panel-inner-border;\n }\n }\n}\n\n\n// Contextual variations\n.panel-default {\n .panel-variant(@panel-default-border; @panel-default-text; @panel-default-heading-bg; @panel-default-border);\n}\n.panel-primary {\n .panel-variant(@panel-primary-border; @panel-primary-text; @panel-primary-heading-bg; @panel-primary-border);\n}\n.panel-success {\n .panel-variant(@panel-success-border; @panel-success-text; @panel-success-heading-bg; @panel-success-border);\n}\n.panel-info {\n .panel-variant(@panel-info-border; @panel-info-text; @panel-info-heading-bg; @panel-info-border);\n}\n.panel-warning {\n .panel-variant(@panel-warning-border; @panel-warning-text; @panel-warning-heading-bg; @panel-warning-border);\n}\n.panel-danger {\n .panel-variant(@panel-danger-border; @panel-danger-text; @panel-danger-heading-bg; @panel-danger-border);\n}\n","// Panels\n\n.panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border) {\n border-color: @border;\n\n & > .panel-heading {\n color: @heading-text-color;\n background-color: @heading-bg-color;\n border-color: @heading-border;\n\n + .panel-collapse > .panel-body {\n border-top-color: @border;\n }\n .badge {\n color: @heading-bg-color;\n background-color: @heading-text-color;\n }\n }\n & > .panel-footer {\n + .panel-collapse > .panel-body {\n border-bottom-color: @border;\n }\n }\n}\n","// Embeds responsive\n//\n// Credit: Nicolas Gallagher and SUIT CSS.\n\n.embed-responsive {\n position: relative;\n display: block;\n height: 0;\n padding: 0;\n overflow: hidden;\n\n .embed-responsive-item,\n iframe,\n embed,\n object,\n video {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border: 0;\n }\n}\n\n// Modifier class for 16:9 aspect ratio\n.embed-responsive-16by9 {\n padding-bottom: 56.25%;\n}\n\n// Modifier class for 4:3 aspect ratio\n.embed-responsive-4by3 {\n padding-bottom: 75%;\n}\n","//\n// Wells\n// --------------------------------------------------\n\n\n// Base class\n.well {\n min-height: 20px;\n padding: 19px;\n margin-bottom: 20px;\n background-color: @well-bg;\n border: 1px solid @well-border;\n border-radius: @border-radius-base;\n .box-shadow(inset 0 1px 1px rgba(0, 0, 0, .05));\n blockquote {\n border-color: #ddd;\n border-color: rgba(0, 0, 0, .15);\n }\n}\n\n// Sizes\n.well-lg {\n padding: 24px;\n border-radius: @border-radius-large;\n}\n.well-sm {\n padding: 9px;\n border-radius: @border-radius-small;\n}\n","// stylelint-disable property-no-vendor-prefix\n\n//\n// Close icons\n// --------------------------------------------------\n\n\n.close {\n float: right;\n font-size: (@font-size-base * 1.5);\n font-weight: @close-font-weight;\n line-height: 1;\n color: @close-color;\n text-shadow: @close-text-shadow;\n .opacity(.2);\n\n &:hover,\n &:focus {\n color: @close-color;\n text-decoration: none;\n cursor: pointer;\n .opacity(.5);\n }\n\n // Additional properties for button version\n // iOS requires the button element instead of an anchor tag.\n // If you want the anchor version, it requires `href=\"#\"`.\n // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile\n button& {\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n appearance: none;\n }\n}\n","//\n// Modals\n// --------------------------------------------------\n\n// .modal-open - body class for killing the scroll\n// .modal - container to scroll within\n// .modal-dialog - positioning shell for the actual modal\n// .modal-content - actual modal w/ bg and corners and shit\n\n// Kill the scroll on the body\n.modal-open {\n overflow: hidden;\n}\n\n// Container that the modal scrolls within\n.modal {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: @zindex-modal;\n display: none;\n overflow: hidden;\n -webkit-overflow-scrolling: touch;\n\n // Prevent Chrome on Windows from adding a focus outline. For details, see\n // https://github.com/twbs/bootstrap/pull/10951.\n outline: 0;\n\n // When fading in the modal, animate it to slide down\n &.fade .modal-dialog {\n .translate(0, -25%);\n .transition-transform(~\"0.3s ease-out\");\n }\n &.in .modal-dialog { .translate(0, 0) }\n}\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n// Shell div to position the modal with bottom padding\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 10px;\n}\n\n// Actual modal\n.modal-content {\n position: relative;\n background-color: @modal-content-bg;\n background-clip: padding-box;\n border: 1px solid @modal-content-fallback-border-color; //old browsers fallback (ie8 etc)\n border: 1px solid @modal-content-border-color;\n border-radius: @border-radius-large;\n .box-shadow(0 3px 9px rgba(0, 0, 0, .5));\n // Remove focus outline from opened modal\n outline: 0;\n}\n\n// Modal background\n.modal-backdrop {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: @zindex-modal-background;\n background-color: @modal-backdrop-bg;\n // Fade for backdrop\n &.fade { .opacity(0); }\n &.in { .opacity(@modal-backdrop-opacity); }\n}\n\n// Modal header\n// Top section of the modal w/ title and dismiss\n.modal-header {\n padding: @modal-title-padding;\n border-bottom: 1px solid @modal-header-border-color;\n &:extend(.clearfix all);\n}\n// Close icon\n.modal-header .close {\n margin-top: -2px;\n}\n\n// Title text within header\n.modal-title {\n margin: 0;\n line-height: @modal-title-line-height;\n}\n\n// Modal body\n// Where all modal content resides (sibling of .modal-header and .modal-footer)\n.modal-body {\n position: relative;\n padding: @modal-inner-padding;\n}\n\n// Footer (for actions)\n.modal-footer {\n padding: @modal-inner-padding;\n text-align: right; // right align buttons\n border-top: 1px solid @modal-footer-border-color;\n &:extend(.clearfix all); // clear it in case folks use .pull-* classes on buttons\n\n // Properly space out buttons\n .btn + .btn {\n margin-bottom: 0; // account for input[type=\"submit\"] which gets the bottom margin like all other inputs\n margin-left: 5px;\n }\n // but override that for button groups\n .btn-group .btn + .btn {\n margin-left: -1px;\n }\n // and override it for block buttons as well\n .btn-block + .btn-block {\n margin-left: 0;\n }\n}\n\n// Measure scrollbar width for padding body during modal show/hide\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n\n// Scale up the modal\n@media (min-width: @screen-sm-min) {\n // Automatically set modal's width for larger viewports\n .modal-dialog {\n width: @modal-md;\n margin: 30px auto;\n }\n .modal-content {\n .box-shadow(0 5px 15px rgba(0, 0, 0, .5));\n }\n\n // Modal sizes\n .modal-sm { width: @modal-sm; }\n}\n\n@media (min-width: @screen-md-min) {\n .modal-lg { width: @modal-lg; }\n}\n","//\n// Tooltips\n// --------------------------------------------------\n\n\n// Base class\n.tooltip {\n position: absolute;\n z-index: @zindex-tooltip;\n display: block;\n // Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element.\n // So reset our font and text properties to avoid inheriting weird values.\n .reset-text();\n font-size: @font-size-small;\n\n .opacity(0);\n\n &.in { .opacity(@tooltip-opacity); }\n &.top {\n padding: @tooltip-arrow-width 0;\n margin-top: -3px;\n }\n &.right {\n padding: 0 @tooltip-arrow-width;\n margin-left: 3px;\n }\n &.bottom {\n padding: @tooltip-arrow-width 0;\n margin-top: 3px;\n }\n &.left {\n padding: 0 @tooltip-arrow-width;\n margin-left: -3px;\n }\n\n // Note: Deprecated .top-left, .top-right, .bottom-left, and .bottom-right as of v3.3.1\n &.top .tooltip-arrow {\n bottom: 0;\n left: 50%;\n margin-left: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.top-left .tooltip-arrow {\n right: @tooltip-arrow-width;\n bottom: 0;\n margin-bottom: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.top-right .tooltip-arrow {\n bottom: 0;\n left: @tooltip-arrow-width;\n margin-bottom: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.right .tooltip-arrow {\n top: 50%;\n left: 0;\n margin-top: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width @tooltip-arrow-width 0;\n border-right-color: @tooltip-arrow-color;\n }\n &.left .tooltip-arrow {\n top: 50%;\n right: 0;\n margin-top: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-left-color: @tooltip-arrow-color;\n }\n &.bottom .tooltip-arrow {\n top: 0;\n left: 50%;\n margin-left: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n &.bottom-left .tooltip-arrow {\n top: 0;\n right: @tooltip-arrow-width;\n margin-top: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n &.bottom-right .tooltip-arrow {\n top: 0;\n left: @tooltip-arrow-width;\n margin-top: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n}\n\n// Wrapper for the tooltip content\n.tooltip-inner {\n max-width: @tooltip-max-width;\n padding: 3px 8px;\n color: @tooltip-color;\n text-align: center;\n background-color: @tooltip-bg;\n border-radius: @border-radius-base;\n}\n\n// Arrows\n.tooltip-arrow {\n position: absolute;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n",".reset-text() {\n font-family: @font-family-base;\n // We deliberately do NOT reset font-size.\n font-style: normal;\n font-weight: 400;\n line-height: @line-height-base;\n line-break: auto;\n text-align: left; // Fallback for where `start` is not supported\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n word-wrap: normal;\n white-space: normal;\n}\n","//\n// Popovers\n// --------------------------------------------------\n\n\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: @zindex-popover;\n display: none;\n max-width: @popover-max-width;\n padding: 1px;\n // Our parent element can be arbitrary since popovers are by default inserted as a sibling of their target element.\n // So reset our font and text properties to avoid inheriting weird values.\n .reset-text();\n font-size: @font-size-base;\n background-color: @popover-bg;\n background-clip: padding-box;\n border: 1px solid @popover-fallback-border-color;\n border: 1px solid @popover-border-color;\n border-radius: @border-radius-large;\n .box-shadow(0 5px 10px rgba(0, 0, 0, .2));\n\n // Offset the popover to account for the popover arrow\n &.top { margin-top: -@popover-arrow-width; }\n &.right { margin-left: @popover-arrow-width; }\n &.bottom { margin-top: @popover-arrow-width; }\n &.left { margin-left: -@popover-arrow-width; }\n\n // Arrows\n // .arrow is outer, .arrow:after is inner\n > .arrow {\n border-width: @popover-arrow-outer-width;\n\n &,\n &:after {\n position: absolute;\n display: block;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n }\n\n &:after {\n content: \"\";\n border-width: @popover-arrow-width;\n }\n }\n\n &.top > .arrow {\n bottom: -@popover-arrow-outer-width;\n left: 50%;\n margin-left: -@popover-arrow-outer-width;\n border-top-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-top-color: @popover-arrow-outer-color;\n border-bottom-width: 0;\n &:after {\n bottom: 1px;\n margin-left: -@popover-arrow-width;\n content: \" \";\n border-top-color: @popover-arrow-color;\n border-bottom-width: 0;\n }\n }\n &.right > .arrow {\n top: 50%;\n left: -@popover-arrow-outer-width;\n margin-top: -@popover-arrow-outer-width;\n border-right-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-right-color: @popover-arrow-outer-color;\n border-left-width: 0;\n &:after {\n bottom: -@popover-arrow-width;\n left: 1px;\n content: \" \";\n border-right-color: @popover-arrow-color;\n border-left-width: 0;\n }\n }\n &.bottom > .arrow {\n top: -@popover-arrow-outer-width;\n left: 50%;\n margin-left: -@popover-arrow-outer-width;\n border-top-width: 0;\n border-bottom-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-bottom-color: @popover-arrow-outer-color;\n &:after {\n top: 1px;\n margin-left: -@popover-arrow-width;\n content: \" \";\n border-top-width: 0;\n border-bottom-color: @popover-arrow-color;\n }\n }\n\n &.left > .arrow {\n top: 50%;\n right: -@popover-arrow-outer-width;\n margin-top: -@popover-arrow-outer-width;\n border-right-width: 0;\n border-left-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-left-color: @popover-arrow-outer-color;\n &:after {\n right: 1px;\n bottom: -@popover-arrow-width;\n content: \" \";\n border-right-width: 0;\n border-left-color: @popover-arrow-color;\n }\n }\n}\n\n.popover-title {\n padding: 8px 14px;\n margin: 0; // reset heading margin\n font-size: @font-size-base;\n background-color: @popover-title-bg;\n border-bottom: 1px solid darken(@popover-title-bg, 5%);\n border-radius: (@border-radius-large - 1) (@border-radius-large - 1) 0 0;\n}\n\n.popover-content {\n padding: 9px 14px;\n}\n","// stylelint-disable media-feature-name-no-unknown\n\n//\n// Carousel\n// --------------------------------------------------\n\n\n// Wrapper for the slide container and indicators\n.carousel {\n position: relative;\n}\n\n.carousel-inner {\n position: relative;\n width: 100%;\n overflow: hidden;\n\n > .item {\n position: relative;\n display: none;\n .transition(.6s ease-in-out left);\n\n // Account for jankitude on images\n > img,\n > a > img {\n &:extend(.img-responsive);\n line-height: 1;\n }\n\n // WebKit CSS3 transforms for supported devices\n @media all and (transform-3d), (-webkit-transform-3d) {\n .transition-transform(~\"0.6s ease-in-out\");\n .backface-visibility(~\"hidden\");\n .perspective(1000px);\n\n &.next,\n &.active.right {\n .translate3d(100%, 0, 0);\n left: 0;\n }\n &.prev,\n &.active.left {\n .translate3d(-100%, 0, 0);\n left: 0;\n }\n &.next.left,\n &.prev.right,\n &.active {\n .translate3d(0, 0, 0);\n left: 0;\n }\n }\n }\n\n > .active,\n > .next,\n > .prev {\n display: block;\n }\n\n > .active {\n left: 0;\n }\n\n > .next,\n > .prev {\n position: absolute;\n top: 0;\n width: 100%;\n }\n\n > .next {\n left: 100%;\n }\n > .prev {\n left: -100%;\n }\n > .next.left,\n > .prev.right {\n left: 0;\n }\n\n > .active.left {\n left: -100%;\n }\n > .active.right {\n left: 100%;\n }\n\n}\n\n// Left/right controls for nav\n// ---------------------------\n\n.carousel-control {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: @carousel-control-width;\n font-size: @carousel-control-font-size;\n color: @carousel-control-color;\n text-align: center;\n text-shadow: @carousel-text-shadow;\n background-color: rgba(0, 0, 0, 0); // Fix IE9 click-thru bug\n .opacity(@carousel-control-opacity);\n // We can't have this transition here because WebKit cancels the carousel\n // animation if you trip this while in the middle of another animation.\n\n // Set gradients for backgrounds\n &.left {\n #gradient > .horizontal(@start-color: rgba(0, 0, 0, .5); @end-color: rgba(0, 0, 0, .0001));\n }\n &.right {\n right: 0;\n left: auto;\n #gradient > .horizontal(@start-color: rgba(0, 0, 0, .0001); @end-color: rgba(0, 0, 0, .5));\n }\n\n // Hover/focus state\n &:hover,\n &:focus {\n color: @carousel-control-color;\n text-decoration: none;\n outline: 0;\n .opacity(.9);\n }\n\n // Toggles\n .icon-prev,\n .icon-next,\n .glyphicon-chevron-left,\n .glyphicon-chevron-right {\n position: absolute;\n top: 50%;\n z-index: 5;\n display: inline-block;\n margin-top: -10px;\n }\n .icon-prev,\n .glyphicon-chevron-left {\n left: 50%;\n margin-left: -10px;\n }\n .icon-next,\n .glyphicon-chevron-right {\n right: 50%;\n margin-right: -10px;\n }\n .icon-prev,\n .icon-next {\n width: 20px;\n height: 20px;\n font-family: serif;\n line-height: 1;\n }\n\n .icon-prev {\n &:before {\n content: \"\\2039\";// SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039)\n }\n }\n .icon-next {\n &:before {\n content: \"\\203a\";// SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A)\n }\n }\n}\n\n// Optional indicator pips\n//\n// Add an unordered list with the following class and add a list item for each\n// slide your carousel holds.\n\n.carousel-indicators {\n position: absolute;\n bottom: 10px;\n left: 50%;\n z-index: 15;\n width: 60%;\n padding-left: 0;\n margin-left: -30%;\n text-align: center;\n list-style: none;\n\n li {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin: 1px;\n text-indent: -999px;\n cursor: pointer;\n // IE8-9 hack for event handling\n //\n // Internet Explorer 8-9 does not support clicks on elements without a set\n // `background-color`. We cannot use `filter` since that's not viewed as a\n // background color by the browser. Thus, a hack is needed.\n // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Internet_Explorer\n //\n // For IE8, we set solid black as it doesn't support `rgba()`. For IE9, we\n // set alpha transparency for the best results possible.\n background-color: #000 \\9; // IE8\n background-color: rgba(0, 0, 0, 0); // IE9\n\n border: 1px solid @carousel-indicator-border-color;\n border-radius: 10px;\n }\n\n .active {\n width: 12px;\n height: 12px;\n margin: 0;\n background-color: @carousel-indicator-active-bg;\n }\n}\n\n// Optional captions\n// -----------------------------\n// Hidden by default for smaller viewports\n.carousel-caption {\n position: absolute;\n right: 15%;\n bottom: 20px;\n left: 15%;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: @carousel-caption-color;\n text-align: center;\n text-shadow: @carousel-text-shadow;\n\n & .btn {\n text-shadow: none; // No shadow for button elements in carousel-caption\n }\n}\n\n\n// Scale up controls for tablets and up\n@media screen and (min-width: @screen-sm-min) {\n\n // Scale up the controls a smidge\n .carousel-control {\n .glyphicon-chevron-left,\n .glyphicon-chevron-right,\n .icon-prev,\n .icon-next {\n width: (@carousel-control-font-size * 1.5);\n height: (@carousel-control-font-size * 1.5);\n margin-top: (@carousel-control-font-size / -2);\n font-size: (@carousel-control-font-size * 1.5);\n }\n .glyphicon-chevron-left,\n .icon-prev {\n margin-left: (@carousel-control-font-size / -2);\n }\n .glyphicon-chevron-right,\n .icon-next {\n margin-right: (@carousel-control-font-size / -2);\n }\n }\n\n // Show and left align the captions\n .carousel-caption {\n right: 20%;\n left: 20%;\n padding-bottom: 30px;\n }\n\n // Move up the indicators\n .carousel-indicators {\n bottom: 20px;\n }\n}\n","// Clearfix\n//\n// For modern browsers\n// 1. The space content is one way to avoid an Opera bug when the\n// contenteditable attribute is included anywhere else in the document.\n// Otherwise it causes space to appear at the top and bottom of elements\n// that are clearfixed.\n// 2. The use of `table` rather than `block` is only necessary if using\n// `:before` to contain the top-margins of child elements.\n//\n// Source: http://nicolasgallagher.com/micro-clearfix-hack/\n\n.clearfix() {\n &:before,\n &:after {\n display: table; // 2\n content: \" \"; // 1\n }\n &:after {\n clear: both;\n }\n}\n","// Center-align a block level element\n\n.center-block() {\n display: block;\n margin-right: auto;\n margin-left: auto;\n}\n","// stylelint-disable font-family-name-quotes, font-family-no-missing-generic-family-keyword\n\n// CSS image replacement\n//\n// Heads up! v3 launched with only `.hide-text()`, but per our pattern for\n// mixins being reused as classes with the same name, this doesn't hold up. As\n// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`.\n//\n// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757\n\n// Deprecated as of v3.0.1 (has been removed in v4)\n.hide-text() {\n font: ~\"0/0\" a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n\n// New mixin to use as of v3.0.1\n.text-hide() {\n .hide-text();\n}\n","// stylelint-disable declaration-no-important, at-rule-no-vendor-prefix\n\n//\n// Responsive: Utility classes\n// --------------------------------------------------\n\n\n// IE10 in Windows (Phone) 8\n//\n// Support for responsive views via media queries is kind of borked in IE10, for\n// Surface/desktop in split view and for Windows Phone 8. This particular fix\n// must be accompanied by a snippet of JavaScript to sniff the user agent and\n// apply some conditional CSS to *only* the Surface/desktop Windows 8. Look at\n// our Getting Started page for more information on this bug.\n//\n// For more information, see the following:\n//\n// Issue: https://github.com/twbs/bootstrap/issues/10497\n// Docs: https://getbootstrap.com/docs/3.4/getting-started/#support-ie10-width\n// Source: https://timkadlec.com/2013/01/windows-phone-8-and-device-width/\n// Source: https://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/\n\n@-ms-viewport {\n width: device-width;\n}\n\n\n// Visibility utilities\n// Note: Deprecated .visible-xs, .visible-sm, .visible-md, and .visible-lg as of v3.2.0\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n .responsive-invisibility();\n}\n\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n display: none !important;\n}\n\n.visible-xs {\n @media (max-width: @screen-xs-max) {\n .responsive-visibility();\n }\n}\n.visible-xs-block {\n @media (max-width: @screen-xs-max) {\n display: block !important;\n }\n}\n.visible-xs-inline {\n @media (max-width: @screen-xs-max) {\n display: inline !important;\n }\n}\n.visible-xs-inline-block {\n @media (max-width: @screen-xs-max) {\n display: inline-block !important;\n }\n}\n\n.visible-sm {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n .responsive-visibility();\n }\n}\n.visible-sm-block {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: block !important;\n }\n}\n.visible-sm-inline {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: inline !important;\n }\n}\n.visible-sm-inline-block {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: inline-block !important;\n }\n}\n\n.visible-md {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n .responsive-visibility();\n }\n}\n.visible-md-block {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: block !important;\n }\n}\n.visible-md-inline {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: inline !important;\n }\n}\n.visible-md-inline-block {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: inline-block !important;\n }\n}\n\n.visible-lg {\n @media (min-width: @screen-lg-min) {\n .responsive-visibility();\n }\n}\n.visible-lg-block {\n @media (min-width: @screen-lg-min) {\n display: block !important;\n }\n}\n.visible-lg-inline {\n @media (min-width: @screen-lg-min) {\n display: inline !important;\n }\n}\n.visible-lg-inline-block {\n @media (min-width: @screen-lg-min) {\n display: inline-block !important;\n }\n}\n\n.hidden-xs {\n @media (max-width: @screen-xs-max) {\n .responsive-invisibility();\n }\n}\n.hidden-sm {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n .responsive-invisibility();\n }\n}\n.hidden-md {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n .responsive-invisibility();\n }\n}\n.hidden-lg {\n @media (min-width: @screen-lg-min) {\n .responsive-invisibility();\n }\n}\n\n\n// Print utilities\n//\n// Media queries are placed on the inside to be mixin-friendly.\n\n// Note: Deprecated .visible-print as of v3.2.0\n.visible-print {\n .responsive-invisibility();\n\n @media print {\n .responsive-visibility();\n }\n}\n.visible-print-block {\n display: none !important;\n\n @media print {\n display: block !important;\n }\n}\n.visible-print-inline {\n display: none !important;\n\n @media print {\n display: inline !important;\n }\n}\n.visible-print-inline-block {\n display: none !important;\n\n @media print {\n display: inline-block !important;\n }\n}\n\n.hidden-print {\n @media print {\n .responsive-invisibility();\n }\n}\n","// stylelint-disable declaration-no-important\n\n.responsive-visibility() {\n display: block !important;\n table& { display: table !important; }\n tr& { display: table-row !important; }\n th&,\n td& { display: table-cell !important; }\n}\n\n.responsive-invisibility() {\n display: none !important;\n}\n"]}
\ No newline at end of file
diff --git a/cps/static/css/images/annotation-check.svg b/cps/static/css/libs/images/annotation-check.svg
similarity index 100%
rename from cps/static/css/images/annotation-check.svg
rename to cps/static/css/libs/images/annotation-check.svg
diff --git a/cps/static/css/images/annotation-comment.svg b/cps/static/css/libs/images/annotation-comment.svg
similarity index 100%
rename from cps/static/css/images/annotation-comment.svg
rename to cps/static/css/libs/images/annotation-comment.svg
diff --git a/cps/static/css/images/annotation-help.svg b/cps/static/css/libs/images/annotation-help.svg
similarity index 100%
rename from cps/static/css/images/annotation-help.svg
rename to cps/static/css/libs/images/annotation-help.svg
diff --git a/cps/static/css/images/annotation-insert.svg b/cps/static/css/libs/images/annotation-insert.svg
similarity index 100%
rename from cps/static/css/images/annotation-insert.svg
rename to cps/static/css/libs/images/annotation-insert.svg
diff --git a/cps/static/css/images/annotation-key.svg b/cps/static/css/libs/images/annotation-key.svg
similarity index 100%
rename from cps/static/css/images/annotation-key.svg
rename to cps/static/css/libs/images/annotation-key.svg
diff --git a/cps/static/css/images/annotation-newparagraph.svg b/cps/static/css/libs/images/annotation-newparagraph.svg
similarity index 100%
rename from cps/static/css/images/annotation-newparagraph.svg
rename to cps/static/css/libs/images/annotation-newparagraph.svg
diff --git a/cps/static/css/images/annotation-noicon.svg b/cps/static/css/libs/images/annotation-noicon.svg
similarity index 100%
rename from cps/static/css/images/annotation-noicon.svg
rename to cps/static/css/libs/images/annotation-noicon.svg
diff --git a/cps/static/css/images/annotation-note.svg b/cps/static/css/libs/images/annotation-note.svg
similarity index 100%
rename from cps/static/css/images/annotation-note.svg
rename to cps/static/css/libs/images/annotation-note.svg
diff --git a/cps/static/css/images/annotation-paragraph.svg b/cps/static/css/libs/images/annotation-paragraph.svg
similarity index 100%
rename from cps/static/css/images/annotation-paragraph.svg
rename to cps/static/css/libs/images/annotation-paragraph.svg
diff --git a/cps/static/css/images/findbarButton-next-rtl.png b/cps/static/css/libs/images/findbarButton-next-rtl.png
similarity index 100%
rename from cps/static/css/images/findbarButton-next-rtl.png
rename to cps/static/css/libs/images/findbarButton-next-rtl.png
diff --git a/cps/static/css/images/findbarButton-next-rtl@2x.png b/cps/static/css/libs/images/findbarButton-next-rtl@2x.png
similarity index 100%
rename from cps/static/css/images/findbarButton-next-rtl@2x.png
rename to cps/static/css/libs/images/findbarButton-next-rtl@2x.png
diff --git a/cps/static/css/images/findbarButton-next.png b/cps/static/css/libs/images/findbarButton-next.png
similarity index 100%
rename from cps/static/css/images/findbarButton-next.png
rename to cps/static/css/libs/images/findbarButton-next.png
diff --git a/cps/static/css/images/findbarButton-next@2x.png b/cps/static/css/libs/images/findbarButton-next@2x.png
similarity index 100%
rename from cps/static/css/images/findbarButton-next@2x.png
rename to cps/static/css/libs/images/findbarButton-next@2x.png
diff --git a/cps/static/css/images/findbarButton-previous-rtl.png b/cps/static/css/libs/images/findbarButton-previous-rtl.png
similarity index 100%
rename from cps/static/css/images/findbarButton-previous-rtl.png
rename to cps/static/css/libs/images/findbarButton-previous-rtl.png
diff --git a/cps/static/css/images/findbarButton-previous-rtl@2x.png b/cps/static/css/libs/images/findbarButton-previous-rtl@2x.png
similarity index 100%
rename from cps/static/css/images/findbarButton-previous-rtl@2x.png
rename to cps/static/css/libs/images/findbarButton-previous-rtl@2x.png
diff --git a/cps/static/css/images/findbarButton-previous.png b/cps/static/css/libs/images/findbarButton-previous.png
similarity index 100%
rename from cps/static/css/images/findbarButton-previous.png
rename to cps/static/css/libs/images/findbarButton-previous.png
diff --git a/cps/static/css/images/findbarButton-previous@2x.png b/cps/static/css/libs/images/findbarButton-previous@2x.png
similarity index 100%
rename from cps/static/css/images/findbarButton-previous@2x.png
rename to cps/static/css/libs/images/findbarButton-previous@2x.png
diff --git a/cps/static/css/images/grab.cur b/cps/static/css/libs/images/grab.cur
similarity index 100%
rename from cps/static/css/images/grab.cur
rename to cps/static/css/libs/images/grab.cur
diff --git a/cps/static/css/images/grabbing.cur b/cps/static/css/libs/images/grabbing.cur
similarity index 100%
rename from cps/static/css/images/grabbing.cur
rename to cps/static/css/libs/images/grabbing.cur
diff --git a/cps/static/css/images/loading-icon.gif b/cps/static/css/libs/images/loading-icon.gif
similarity index 100%
rename from cps/static/css/images/loading-icon.gif
rename to cps/static/css/libs/images/loading-icon.gif
diff --git a/cps/static/css/images/loading-small.png b/cps/static/css/libs/images/loading-small.png
similarity index 100%
rename from cps/static/css/images/loading-small.png
rename to cps/static/css/libs/images/loading-small.png
diff --git a/cps/static/css/images/loading-small@2x.png b/cps/static/css/libs/images/loading-small@2x.png
similarity index 100%
rename from cps/static/css/images/loading-small@2x.png
rename to cps/static/css/libs/images/loading-small@2x.png
diff --git a/cps/static/css/images/secondaryToolbarButton-documentProperties.png b/cps/static/css/libs/images/secondaryToolbarButton-documentProperties.png
similarity index 100%
rename from cps/static/css/images/secondaryToolbarButton-documentProperties.png
rename to cps/static/css/libs/images/secondaryToolbarButton-documentProperties.png
diff --git a/cps/static/css/images/secondaryToolbarButton-documentProperties@2x.png b/cps/static/css/libs/images/secondaryToolbarButton-documentProperties@2x.png
similarity index 100%
rename from cps/static/css/images/secondaryToolbarButton-documentProperties@2x.png
rename to cps/static/css/libs/images/secondaryToolbarButton-documentProperties@2x.png
diff --git a/cps/static/css/images/secondaryToolbarButton-firstPage.png b/cps/static/css/libs/images/secondaryToolbarButton-firstPage.png
similarity index 100%
rename from cps/static/css/images/secondaryToolbarButton-firstPage.png
rename to cps/static/css/libs/images/secondaryToolbarButton-firstPage.png
diff --git a/cps/static/css/images/secondaryToolbarButton-firstPage@2x.png b/cps/static/css/libs/images/secondaryToolbarButton-firstPage@2x.png
similarity index 100%
rename from cps/static/css/images/secondaryToolbarButton-firstPage@2x.png
rename to cps/static/css/libs/images/secondaryToolbarButton-firstPage@2x.png
diff --git a/cps/static/css/images/secondaryToolbarButton-handTool.png b/cps/static/css/libs/images/secondaryToolbarButton-handTool.png
similarity index 100%
rename from cps/static/css/images/secondaryToolbarButton-handTool.png
rename to cps/static/css/libs/images/secondaryToolbarButton-handTool.png
diff --git a/cps/static/css/images/secondaryToolbarButton-handTool@2x.png b/cps/static/css/libs/images/secondaryToolbarButton-handTool@2x.png
similarity index 100%
rename from cps/static/css/images/secondaryToolbarButton-handTool@2x.png
rename to cps/static/css/libs/images/secondaryToolbarButton-handTool@2x.png
diff --git a/cps/static/css/images/secondaryToolbarButton-lastPage.png b/cps/static/css/libs/images/secondaryToolbarButton-lastPage.png
similarity index 100%
rename from cps/static/css/images/secondaryToolbarButton-lastPage.png
rename to cps/static/css/libs/images/secondaryToolbarButton-lastPage.png
diff --git a/cps/static/css/images/secondaryToolbarButton-lastPage@2x.png b/cps/static/css/libs/images/secondaryToolbarButton-lastPage@2x.png
similarity index 100%
rename from cps/static/css/images/secondaryToolbarButton-lastPage@2x.png
rename to cps/static/css/libs/images/secondaryToolbarButton-lastPage@2x.png
diff --git a/cps/static/css/images/secondaryToolbarButton-rotateCcw.png b/cps/static/css/libs/images/secondaryToolbarButton-rotateCcw.png
similarity index 100%
rename from cps/static/css/images/secondaryToolbarButton-rotateCcw.png
rename to cps/static/css/libs/images/secondaryToolbarButton-rotateCcw.png
diff --git a/cps/static/css/images/secondaryToolbarButton-rotateCcw@2x.png b/cps/static/css/libs/images/secondaryToolbarButton-rotateCcw@2x.png
similarity index 100%
rename from cps/static/css/images/secondaryToolbarButton-rotateCcw@2x.png
rename to cps/static/css/libs/images/secondaryToolbarButton-rotateCcw@2x.png
diff --git a/cps/static/css/images/secondaryToolbarButton-rotateCw.png b/cps/static/css/libs/images/secondaryToolbarButton-rotateCw.png
similarity index 100%
rename from cps/static/css/images/secondaryToolbarButton-rotateCw.png
rename to cps/static/css/libs/images/secondaryToolbarButton-rotateCw.png
diff --git a/cps/static/css/images/secondaryToolbarButton-rotateCw@2x.png b/cps/static/css/libs/images/secondaryToolbarButton-rotateCw@2x.png
similarity index 100%
rename from cps/static/css/images/secondaryToolbarButton-rotateCw@2x.png
rename to cps/static/css/libs/images/secondaryToolbarButton-rotateCw@2x.png
diff --git a/cps/static/css/libs/images/secondaryToolbarButton-scrollHorizontal.png b/cps/static/css/libs/images/secondaryToolbarButton-scrollHorizontal.png
new file mode 100644
index 00000000..cb702fc4
Binary files /dev/null and b/cps/static/css/libs/images/secondaryToolbarButton-scrollHorizontal.png differ
diff --git a/cps/static/css/libs/images/secondaryToolbarButton-scrollHorizontal@2x.png b/cps/static/css/libs/images/secondaryToolbarButton-scrollHorizontal@2x.png
new file mode 100644
index 00000000..7f05289b
Binary files /dev/null and b/cps/static/css/libs/images/secondaryToolbarButton-scrollHorizontal@2x.png differ
diff --git a/cps/static/css/libs/images/secondaryToolbarButton-scrollVertical.png b/cps/static/css/libs/images/secondaryToolbarButton-scrollVertical.png
new file mode 100644
index 00000000..0b8427a1
Binary files /dev/null and b/cps/static/css/libs/images/secondaryToolbarButton-scrollVertical.png differ
diff --git a/cps/static/css/libs/images/secondaryToolbarButton-scrollVertical@2x.png b/cps/static/css/libs/images/secondaryToolbarButton-scrollVertical@2x.png
new file mode 100644
index 00000000..72ab55eb
Binary files /dev/null and b/cps/static/css/libs/images/secondaryToolbarButton-scrollVertical@2x.png differ
diff --git a/cps/static/css/libs/images/secondaryToolbarButton-scrollWrapped.png b/cps/static/css/libs/images/secondaryToolbarButton-scrollWrapped.png
new file mode 100644
index 00000000..165fc8bc
Binary files /dev/null and b/cps/static/css/libs/images/secondaryToolbarButton-scrollWrapped.png differ
diff --git a/cps/static/css/libs/images/secondaryToolbarButton-scrollWrapped@2x.png b/cps/static/css/libs/images/secondaryToolbarButton-scrollWrapped@2x.png
new file mode 100644
index 00000000..42461411
Binary files /dev/null and b/cps/static/css/libs/images/secondaryToolbarButton-scrollWrapped@2x.png differ
diff --git a/cps/static/css/libs/images/secondaryToolbarButton-selectTool.png b/cps/static/css/libs/images/secondaryToolbarButton-selectTool.png
new file mode 100644
index 00000000..25520a6f
Binary files /dev/null and b/cps/static/css/libs/images/secondaryToolbarButton-selectTool.png differ
diff --git a/cps/static/css/libs/images/secondaryToolbarButton-selectTool@2x.png b/cps/static/css/libs/images/secondaryToolbarButton-selectTool@2x.png
new file mode 100644
index 00000000..a58aaef4
Binary files /dev/null and b/cps/static/css/libs/images/secondaryToolbarButton-selectTool@2x.png differ
diff --git a/cps/static/css/libs/images/secondaryToolbarButton-spreadEven.png b/cps/static/css/libs/images/secondaryToolbarButton-spreadEven.png
new file mode 100644
index 00000000..3fa07e70
Binary files /dev/null and b/cps/static/css/libs/images/secondaryToolbarButton-spreadEven.png differ
diff --git a/cps/static/css/libs/images/secondaryToolbarButton-spreadEven@2x.png b/cps/static/css/libs/images/secondaryToolbarButton-spreadEven@2x.png
new file mode 100644
index 00000000..32e5033d
Binary files /dev/null and b/cps/static/css/libs/images/secondaryToolbarButton-spreadEven@2x.png differ
diff --git a/cps/static/css/libs/images/secondaryToolbarButton-spreadNone.png b/cps/static/css/libs/images/secondaryToolbarButton-spreadNone.png
new file mode 100644
index 00000000..16114735
Binary files /dev/null and b/cps/static/css/libs/images/secondaryToolbarButton-spreadNone.png differ
diff --git a/cps/static/css/libs/images/secondaryToolbarButton-spreadNone@2x.png b/cps/static/css/libs/images/secondaryToolbarButton-spreadNone@2x.png
new file mode 100644
index 00000000..8e51cf3b
Binary files /dev/null and b/cps/static/css/libs/images/secondaryToolbarButton-spreadNone@2x.png differ
diff --git a/cps/static/css/libs/images/secondaryToolbarButton-spreadOdd.png b/cps/static/css/libs/images/secondaryToolbarButton-spreadOdd.png
new file mode 100644
index 00000000..5126313a
Binary files /dev/null and b/cps/static/css/libs/images/secondaryToolbarButton-spreadOdd.png differ
diff --git a/cps/static/css/libs/images/secondaryToolbarButton-spreadOdd@2x.png b/cps/static/css/libs/images/secondaryToolbarButton-spreadOdd@2x.png
new file mode 100644
index 00000000..5996b74d
Binary files /dev/null and b/cps/static/css/libs/images/secondaryToolbarButton-spreadOdd@2x.png differ
diff --git a/cps/static/css/images/shadow.png b/cps/static/css/libs/images/shadow.png
similarity index 100%
rename from cps/static/css/images/shadow.png
rename to cps/static/css/libs/images/shadow.png
diff --git a/cps/static/css/libs/images/texture.png b/cps/static/css/libs/images/texture.png
new file mode 100644
index 00000000..12bae83a
Binary files /dev/null and b/cps/static/css/libs/images/texture.png differ
diff --git a/cps/static/css/images/toolbarButton-bookmark.png b/cps/static/css/libs/images/toolbarButton-bookmark.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-bookmark.png
rename to cps/static/css/libs/images/toolbarButton-bookmark.png
diff --git a/cps/static/css/images/toolbarButton-bookmark@2x.png b/cps/static/css/libs/images/toolbarButton-bookmark@2x.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-bookmark@2x.png
rename to cps/static/css/libs/images/toolbarButton-bookmark@2x.png
diff --git a/cps/static/css/images/toolbarButton-download.png b/cps/static/css/libs/images/toolbarButton-download.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-download.png
rename to cps/static/css/libs/images/toolbarButton-download.png
diff --git a/cps/static/css/images/toolbarButton-download@2x.png b/cps/static/css/libs/images/toolbarButton-download@2x.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-download@2x.png
rename to cps/static/css/libs/images/toolbarButton-download@2x.png
diff --git a/cps/static/css/libs/images/toolbarButton-menuArrows.png b/cps/static/css/libs/images/toolbarButton-menuArrows.png
new file mode 100644
index 00000000..e50ca4ee
Binary files /dev/null and b/cps/static/css/libs/images/toolbarButton-menuArrows.png differ
diff --git a/cps/static/css/images/toolbarButton-menuArrows@2x.png b/cps/static/css/libs/images/toolbarButton-menuArrows@2x.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-menuArrows@2x.png
rename to cps/static/css/libs/images/toolbarButton-menuArrows@2x.png
diff --git a/cps/static/css/images/toolbarButton-openFile.png b/cps/static/css/libs/images/toolbarButton-openFile.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-openFile.png
rename to cps/static/css/libs/images/toolbarButton-openFile.png
diff --git a/cps/static/css/images/toolbarButton-openFile@2x.png b/cps/static/css/libs/images/toolbarButton-openFile@2x.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-openFile@2x.png
rename to cps/static/css/libs/images/toolbarButton-openFile@2x.png
diff --git a/cps/static/css/images/toolbarButton-pageDown-rtl.png b/cps/static/css/libs/images/toolbarButton-pageDown-rtl.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-pageDown-rtl.png
rename to cps/static/css/libs/images/toolbarButton-pageDown-rtl.png
diff --git a/cps/static/css/images/toolbarButton-pageDown-rtl@2x.png b/cps/static/css/libs/images/toolbarButton-pageDown-rtl@2x.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-pageDown-rtl@2x.png
rename to cps/static/css/libs/images/toolbarButton-pageDown-rtl@2x.png
diff --git a/cps/static/css/images/toolbarButton-pageDown.png b/cps/static/css/libs/images/toolbarButton-pageDown.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-pageDown.png
rename to cps/static/css/libs/images/toolbarButton-pageDown.png
diff --git a/cps/static/css/images/toolbarButton-pageDown@2x.png b/cps/static/css/libs/images/toolbarButton-pageDown@2x.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-pageDown@2x.png
rename to cps/static/css/libs/images/toolbarButton-pageDown@2x.png
diff --git a/cps/static/css/images/toolbarButton-pageUp-rtl.png b/cps/static/css/libs/images/toolbarButton-pageUp-rtl.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-pageUp-rtl.png
rename to cps/static/css/libs/images/toolbarButton-pageUp-rtl.png
diff --git a/cps/static/css/images/toolbarButton-pageUp-rtl@2x.png b/cps/static/css/libs/images/toolbarButton-pageUp-rtl@2x.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-pageUp-rtl@2x.png
rename to cps/static/css/libs/images/toolbarButton-pageUp-rtl@2x.png
diff --git a/cps/static/css/images/toolbarButton-pageUp.png b/cps/static/css/libs/images/toolbarButton-pageUp.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-pageUp.png
rename to cps/static/css/libs/images/toolbarButton-pageUp.png
diff --git a/cps/static/css/images/toolbarButton-pageUp@2x.png b/cps/static/css/libs/images/toolbarButton-pageUp@2x.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-pageUp@2x.png
rename to cps/static/css/libs/images/toolbarButton-pageUp@2x.png
diff --git a/cps/static/css/images/toolbarButton-presentationMode.png b/cps/static/css/libs/images/toolbarButton-presentationMode.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-presentationMode.png
rename to cps/static/css/libs/images/toolbarButton-presentationMode.png
diff --git a/cps/static/css/images/toolbarButton-presentationMode@2x.png b/cps/static/css/libs/images/toolbarButton-presentationMode@2x.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-presentationMode@2x.png
rename to cps/static/css/libs/images/toolbarButton-presentationMode@2x.png
diff --git a/cps/static/css/images/toolbarButton-print.png b/cps/static/css/libs/images/toolbarButton-print.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-print.png
rename to cps/static/css/libs/images/toolbarButton-print.png
diff --git a/cps/static/css/images/toolbarButton-print@2x.png b/cps/static/css/libs/images/toolbarButton-print@2x.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-print@2x.png
rename to cps/static/css/libs/images/toolbarButton-print@2x.png
diff --git a/cps/static/css/images/toolbarButton-search.png b/cps/static/css/libs/images/toolbarButton-search.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-search.png
rename to cps/static/css/libs/images/toolbarButton-search.png
diff --git a/cps/static/css/images/toolbarButton-search@2x.png b/cps/static/css/libs/images/toolbarButton-search@2x.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-search@2x.png
rename to cps/static/css/libs/images/toolbarButton-search@2x.png
diff --git a/cps/static/css/images/toolbarButton-secondaryToolbarToggle-rtl.png b/cps/static/css/libs/images/toolbarButton-secondaryToolbarToggle-rtl.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-secondaryToolbarToggle-rtl.png
rename to cps/static/css/libs/images/toolbarButton-secondaryToolbarToggle-rtl.png
diff --git a/cps/static/css/images/toolbarButton-secondaryToolbarToggle-rtl@2x.png b/cps/static/css/libs/images/toolbarButton-secondaryToolbarToggle-rtl@2x.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-secondaryToolbarToggle-rtl@2x.png
rename to cps/static/css/libs/images/toolbarButton-secondaryToolbarToggle-rtl@2x.png
diff --git a/cps/static/css/images/toolbarButton-secondaryToolbarToggle.png b/cps/static/css/libs/images/toolbarButton-secondaryToolbarToggle.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-secondaryToolbarToggle.png
rename to cps/static/css/libs/images/toolbarButton-secondaryToolbarToggle.png
diff --git a/cps/static/css/images/toolbarButton-secondaryToolbarToggle@2x.png b/cps/static/css/libs/images/toolbarButton-secondaryToolbarToggle@2x.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-secondaryToolbarToggle@2x.png
rename to cps/static/css/libs/images/toolbarButton-secondaryToolbarToggle@2x.png
diff --git a/cps/static/css/images/toolbarButton-sidebarToggle-rtl.png b/cps/static/css/libs/images/toolbarButton-sidebarToggle-rtl.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-sidebarToggle-rtl.png
rename to cps/static/css/libs/images/toolbarButton-sidebarToggle-rtl.png
diff --git a/cps/static/css/images/toolbarButton-sidebarToggle-rtl@2x.png b/cps/static/css/libs/images/toolbarButton-sidebarToggle-rtl@2x.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-sidebarToggle-rtl@2x.png
rename to cps/static/css/libs/images/toolbarButton-sidebarToggle-rtl@2x.png
diff --git a/cps/static/css/images/toolbarButton-sidebarToggle.png b/cps/static/css/libs/images/toolbarButton-sidebarToggle.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-sidebarToggle.png
rename to cps/static/css/libs/images/toolbarButton-sidebarToggle.png
diff --git a/cps/static/css/images/toolbarButton-sidebarToggle@2x.png b/cps/static/css/libs/images/toolbarButton-sidebarToggle@2x.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-sidebarToggle@2x.png
rename to cps/static/css/libs/images/toolbarButton-sidebarToggle@2x.png
diff --git a/cps/static/css/images/toolbarButton-viewAttachments.png b/cps/static/css/libs/images/toolbarButton-viewAttachments.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-viewAttachments.png
rename to cps/static/css/libs/images/toolbarButton-viewAttachments.png
diff --git a/cps/static/css/libs/images/toolbarButton-viewAttachments@2x.png b/cps/static/css/libs/images/toolbarButton-viewAttachments@2x.png
new file mode 100644
index 00000000..4a5e2b8a
Binary files /dev/null and b/cps/static/css/libs/images/toolbarButton-viewAttachments@2x.png differ
diff --git a/cps/static/css/images/toolbarButton-viewOutline-rtl.png b/cps/static/css/libs/images/toolbarButton-viewOutline-rtl.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-viewOutline-rtl.png
rename to cps/static/css/libs/images/toolbarButton-viewOutline-rtl.png
diff --git a/cps/static/css/images/toolbarButton-viewOutline-rtl@2x.png b/cps/static/css/libs/images/toolbarButton-viewOutline-rtl@2x.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-viewOutline-rtl@2x.png
rename to cps/static/css/libs/images/toolbarButton-viewOutline-rtl@2x.png
diff --git a/cps/static/css/images/toolbarButton-viewOutline.png b/cps/static/css/libs/images/toolbarButton-viewOutline.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-viewOutline.png
rename to cps/static/css/libs/images/toolbarButton-viewOutline.png
diff --git a/cps/static/css/images/toolbarButton-viewOutline@2x.png b/cps/static/css/libs/images/toolbarButton-viewOutline@2x.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-viewOutline@2x.png
rename to cps/static/css/libs/images/toolbarButton-viewOutline@2x.png
diff --git a/cps/static/css/images/toolbarButton-viewThumbnail.png b/cps/static/css/libs/images/toolbarButton-viewThumbnail.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-viewThumbnail.png
rename to cps/static/css/libs/images/toolbarButton-viewThumbnail.png
diff --git a/cps/static/css/libs/images/toolbarButton-viewThumbnail@2x.png b/cps/static/css/libs/images/toolbarButton-viewThumbnail@2x.png
new file mode 100644
index 00000000..a0208b41
Binary files /dev/null and b/cps/static/css/libs/images/toolbarButton-viewThumbnail@2x.png differ
diff --git a/cps/static/css/images/toolbarButton-zoomIn.png b/cps/static/css/libs/images/toolbarButton-zoomIn.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-zoomIn.png
rename to cps/static/css/libs/images/toolbarButton-zoomIn.png
diff --git a/cps/static/css/images/toolbarButton-zoomIn@2x.png b/cps/static/css/libs/images/toolbarButton-zoomIn@2x.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-zoomIn@2x.png
rename to cps/static/css/libs/images/toolbarButton-zoomIn@2x.png
diff --git a/cps/static/css/images/toolbarButton-zoomOut.png b/cps/static/css/libs/images/toolbarButton-zoomOut.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-zoomOut.png
rename to cps/static/css/libs/images/toolbarButton-zoomOut.png
diff --git a/cps/static/css/images/toolbarButton-zoomOut@2x.png b/cps/static/css/libs/images/toolbarButton-zoomOut@2x.png
similarity index 100%
rename from cps/static/css/images/toolbarButton-zoomOut@2x.png
rename to cps/static/css/libs/images/toolbarButton-zoomOut@2x.png
diff --git a/cps/static/css/libs/images/treeitem-collapsed-rtl.png b/cps/static/css/libs/images/treeitem-collapsed-rtl.png
new file mode 100644
index 00000000..0496b357
Binary files /dev/null and b/cps/static/css/libs/images/treeitem-collapsed-rtl.png differ
diff --git a/cps/static/css/libs/images/treeitem-collapsed-rtl@2x.png b/cps/static/css/libs/images/treeitem-collapsed-rtl@2x.png
new file mode 100644
index 00000000..6ad9ebcd
Binary files /dev/null and b/cps/static/css/libs/images/treeitem-collapsed-rtl@2x.png differ
diff --git a/cps/static/css/images/treeitem-collapsed.png b/cps/static/css/libs/images/treeitem-collapsed.png
similarity index 100%
rename from cps/static/css/images/treeitem-collapsed.png
rename to cps/static/css/libs/images/treeitem-collapsed.png
diff --git a/cps/static/css/images/treeitem-collapsed@2x.png b/cps/static/css/libs/images/treeitem-collapsed@2x.png
similarity index 100%
rename from cps/static/css/images/treeitem-collapsed@2x.png
rename to cps/static/css/libs/images/treeitem-collapsed@2x.png
diff --git a/cps/static/css/images/treeitem-expanded.png b/cps/static/css/libs/images/treeitem-expanded.png
similarity index 100%
rename from cps/static/css/images/treeitem-expanded.png
rename to cps/static/css/libs/images/treeitem-expanded.png
diff --git a/cps/static/css/images/treeitem-expanded@2x.png b/cps/static/css/libs/images/treeitem-expanded@2x.png
similarity index 100%
rename from cps/static/css/images/treeitem-expanded@2x.png
rename to cps/static/css/libs/images/treeitem-expanded@2x.png
diff --git a/cps/static/css/libs/normalize.css b/cps/static/css/libs/normalize.css
index c3e014d9..b0c1902d 100644
--- a/cps/static/css/libs/normalize.css
+++ b/cps/static/css/libs/normalize.css
@@ -1,505 +1,349 @@
-/*! normalize.css v1.0.1 | MIT License | git.io/normalize */
+/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
-/* ==========================================================================
- HTML5 display definitions
+/* Document
========================================================================== */
-/*
- * Corrects `block` display not defined in IE 6/7/8/9 and Firefox 3.
- */
-
-article,
-aside,
-details,
-figcaption,
-figure,
-footer,
-header,
-hgroup,
-nav,
-section,
-summary {
- display: block;
-}
-
-/*
- * Corrects `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
- */
-
-audio,
-canvas,
-video {
- display: inline-block;
- *display: inline;
- *zoom: 1;
-}
-
-/*
- * Prevents modern browsers from displaying `audio` without controls.
- * Remove excess height in iOS 5 devices.
+/**
+ * 1. Correct the line height in all browsers.
+ * 2. Prevent adjustments of font size after orientation changes in iOS.
*/
-audio:not([controls]) {
- display: none;
- height: 0;
-}
-
-/*
- * Addresses styling for `hidden` attribute not present in IE 7/8/9, Firefox 3,
- * and Safari 4.
- * Known issue: no IE 6 support.
- */
-
-[hidden] {
- display: none;
+html {
+ line-height: 1.15; /* 1 */
+ -webkit-text-size-adjust: 100%; /* 2 */
}
-/* ==========================================================================
- Base
+/* Sections
========================================================================== */
-/*
- * 1. Corrects text resizing oddly in IE 6/7 when body `font-size` is set using
- * `em` units.
- * 2. Prevents iOS text size adjust after orientation change, without disabling
- * user zoom.
+/**
+ * Remove the margin in all browsers.
*/
-html {
- font-size: 100%; /* 1 */
- -webkit-text-size-adjust: 100%; /* 2 */
- -ms-text-size-adjust: 100%; /* 2 */
+body {
+ margin: 0;
}
-/*
- * Addresses `font-family` inconsistency between `textarea` and other form
- * elements.
+/**
+ * Render the `main` element consistently in IE.
*/
-html,
-button,
-input,
-select,
-textarea {
- font-family: sans-serif;
+main {
+ display: block;
}
-/*
- * Addresses margins handled incorrectly in IE 6/7.
+/**
+ * Correct the font size and margin on `h1` elements within `section` and
+ * `article` contexts in Chrome, Firefox, and Safari.
*/
-body {
- margin: 0;
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
}
-/* ==========================================================================
- Links
+/* Grouping content
========================================================================== */
-/*
- * Addresses `outline` inconsistency between Chrome and other browsers.
+/**
+ * 1. Add the correct box sizing in Firefox.
+ * 2. Show the overflow in Edge and IE.
*/
-a:focus {
- outline: thin dotted;
+hr {
+ box-sizing: content-box; /* 1 */
+ height: 0; /* 1 */
+ overflow: visible; /* 2 */
}
-/*
- * Improves readability when focused and also mouse hovered in all browsers.
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
*/
-a:active,
-a:hover {
- outline: 0;
+pre {
+ font-family: monospace, monospace; /* 1 */
+ font-size: 1em; /* 2 */
}
-/* ==========================================================================
- Typography
+/* Text-level semantics
========================================================================== */
-/*
- * Addresses font sizes and margins set differently in IE 6/7.
- * Addresses font sizes within `section` and `article` in Firefox 4+, Safari 5,
- * and Chrome.
+/**
+ * Remove the gray background on active links in IE 10.
*/
-h1 {
- font-size: 2em;
- margin: 0.67em 0;
-}
-
-h2 {
- font-size: 1.5em;
- margin: 0.83em 0;
+a {
+ background-color: transparent;
}
-h3 {
- font-size: 1.17em;
- margin: 1em 0;
-}
-
-h4 {
- font-size: 1em;
- margin: 1.33em 0;
-}
-
-h5 {
- font-size: 0.83em;
- margin: 1.67em 0;
-}
-
-h6 {
- font-size: 0.75em;
- margin: 2.33em 0;
-}
-
-/*
- * Addresses styling not present in IE 7/8/9, Safari 5, and Chrome.
+/**
+ * 1. Remove the bottom border in Chrome 57-
+ * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
- border-bottom: 1px dotted;
+ border-bottom: none; /* 1 */
+ text-decoration: underline; /* 2 */
+ text-decoration: underline dotted; /* 2 */
}
-/*
- * Addresses style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome.
+/**
+ * Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
- font-weight: bold;
-}
-
-blockquote {
- margin: 1em 40px;
+ font-weight: bolder;
}
-/*
- * Addresses styling not present in Safari 5 and Chrome.
- */
-
-dfn {
- font-style: italic;
-}
-
-/*
- * Addresses styling not present in IE 6/7/8/9.
- */
-
-mark {
- background: #ff0;
- color: #000;
-}
-
-/*
- * Addresses margins set differently in IE 6/7.
- */
-
-p,
-pre {
- margin: 1em 0;
-}
-
-/*
- * Corrects font family set oddly in IE 6, Safari 4/5, and Chrome.
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
-pre,
samp {
- font-family: monospace, serif;
- _font-family: 'courier new', monospace;
- font-size: 1em;
+ font-family: monospace, monospace; /* 1 */
+ font-size: 1em; /* 2 */
}
-/*
- * Improves readability of pre-formatted text in all browsers.
- */
-
-pre {
- white-space: pre;
- white-space: pre-wrap;
- word-wrap: break-word;
-}
-
-/*
- * Addresses CSS quotes not supported in IE 6/7.
- */
-
-q {
- quotes: none;
-}
-
-/*
- * Addresses `quotes` property not supported in Safari 4.
- */
-
-q:before,
-q:after {
- content: '';
- content: none;
-}
-
-/*
- * Addresses inconsistent and variable font size in all browsers.
+/**
+ * Add the correct font size in all browsers.
*/
small {
- font-size: 80%;
+ font-size: 80%;
}
-/*
- * Prevents `sub` and `sup` affecting `line-height` in all browsers.
+/**
+ * Prevent `sub` and `sup` elements from affecting the line height in
+ * all browsers.
*/
sub,
sup {
- font-size: 75%;
- line-height: 0;
- position: relative;
- vertical-align: baseline;
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
}
-sup {
- top: -0.5em;
+sub {
+ bottom: -0.25em;
}
-sub {
- bottom: -0.25em;
+sup {
+ top: -0.5em;
}
-/* ==========================================================================
- Lists
+/* Embedded content
========================================================================== */
-/*
- * Addresses margins set differently in IE 6/7.
+/**
+ * Remove the border on images inside links in IE 10.
*/
-dl,
-menu,
-ol,
-ul {
- margin: 1em 0;
+img {
+ border-style: none;
}
-dd {
- margin: 0 0 0 40px;
-}
+/* Forms
+ ========================================================================== */
-/*
- * Addresses paddings set differently in IE 6/7.
+/**
+ * 1. Change the font styles in all browsers.
+ * 2. Remove the margin in Firefox and Safari.
*/
-menu,
-ol,
-ul {
- padding: 0 0 0 40px;
+button,
+input,
+optgroup,
+select,
+textarea {
+ font-family: inherit; /* 1 */
+ font-size: 100%; /* 1 */
+ line-height: 1.15; /* 1 */
+ margin: 0; /* 2 */
}
-/*
- * Corrects list images handled incorrectly in IE 7.
+/**
+ * Show the overflow in IE.
+ * 1. Show the overflow in Edge.
*/
-nav ul,
-nav ol {
- list-style: none;
- list-style-image: none;
+button,
+input { /* 1 */
+ overflow: visible;
}
-/* ==========================================================================
- Embedded content
- ========================================================================== */
-
-/*
- * 1. Removes border when inside `a` element in IE 6/7/8/9 and Firefox 3.
- * 2. Improves image quality when scaled in IE 7.
+/**
+ * Remove the inheritance of text transform in Edge, Firefox, and IE.
+ * 1. Remove the inheritance of text transform in Firefox.
*/
-img {
- border: 0; /* 1 */
- -ms-interpolation-mode: bicubic; /* 2 */
+button,
+select { /* 1 */
+ text-transform: none;
}
-/*
- * Corrects overflow displayed oddly in IE 9.
+/**
+ * Correct the inability to style clickable types in iOS and Safari.
*/
-svg:not(:root) {
- overflow: hidden;
+button,
+[type="button"],
+[type="reset"],
+[type="submit"] {
+ -webkit-appearance: button;
}
-/* ==========================================================================
- Figures
- ========================================================================== */
-
-/*
- * Addresses margin not present in IE 6/7/8/9, Safari 5, and Opera 11.
+/**
+ * Remove the inner border and padding in Firefox.
*/
-figure {
- margin: 0;
+button::-moz-focus-inner,
+[type="button"]::-moz-focus-inner,
+[type="reset"]::-moz-focus-inner,
+[type="submit"]::-moz-focus-inner {
+ border-style: none;
+ padding: 0;
}
-/* ==========================================================================
- Forms
- ========================================================================== */
-
-/*
- * Corrects margin displayed oddly in IE 6/7.
+/**
+ * Restore the focus styles unset by the previous rule.
*/
-form {
- margin: 0;
+button:-moz-focusring,
+[type="button"]:-moz-focusring,
+[type="reset"]:-moz-focusring,
+[type="submit"]:-moz-focusring {
+ outline: 1px dotted ButtonText;
}
-/*
- * Define consistent border, margin, and padding.
+/**
+ * Correct the padding in Firefox.
*/
fieldset {
- border: 1px solid #c0c0c0;
- margin: 0 2px;
- padding: 0.35em 0.625em 0.75em;
+ padding: 0.35em 0.75em 0.625em;
}
-/*
- * 1. Corrects color not being inherited in IE 6/7/8/9.
- * 2. Corrects text not wrapping in Firefox 3.
- * 3. Corrects alignment displayed oddly in IE 6/7.
+/**
+ * 1. Correct the text wrapping in Edge and IE.
+ * 2. Correct the color inheritance from `fieldset` elements in IE.
+ * 3. Remove the padding so developers are not caught out when they zero out
+ * `fieldset` elements in all browsers.
*/
legend {
- border: 0; /* 1 */
- padding: 0;
- white-space: normal; /* 2 */
- *margin-left: -7px; /* 3 */
+ box-sizing: border-box; /* 1 */
+ color: inherit; /* 2 */
+ display: table; /* 1 */
+ max-width: 100%; /* 1 */
+ padding: 0; /* 3 */
+ white-space: normal; /* 1 */
}
-/*
- * 1. Corrects font size not being inherited in all browsers.
- * 2. Addresses margins set differently in IE 6/7, Firefox 3+, Safari 5,
- * and Chrome.
- * 3. Improves appearance and consistency in all browsers.
+/**
+ * Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
-button,
-input,
-select,
-textarea {
- font-size: 100%; /* 1 */
- margin: 0; /* 2 */
- vertical-align: baseline; /* 3 */
- *vertical-align: middle; /* 3 */
+progress {
+ vertical-align: baseline;
}
-/*
- * Addresses Firefox 3+ setting `line-height` on `input` using `!important` in
- * the UA stylesheet.
+/**
+ * Remove the default vertical scrollbar in IE 10+.
*/
-button,
-input {
- line-height: normal;
+textarea {
+ overflow: auto;
}
-/*
- * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
- * and `video` controls.
- * 2. Corrects inability to style clickable `input` types in iOS.
- * 3. Improves usability and consistency of cursor style between image-type
- * `input` and others.
- * 4. Removes inner spacing in IE 7 without affecting normal text inputs.
- * Known issue: inner spacing remains in IE 6.
+/**
+ * 1. Add the correct box sizing in IE 10.
+ * 2. Remove the padding in IE 10.
*/
-button,
-html input[type="button"], /* 1 */
-input[type="reset"],
-input[type="submit"] {
- -webkit-appearance: button; /* 2 */
- cursor: pointer; /* 3 */
- *overflow: visible; /* 4 */
+[type="checkbox"],
+[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
}
-/*
- * Re-set default cursor for disabled elements.
+/**
+ * Correct the cursor style of increment and decrement buttons in Chrome.
*/
-button[disabled],
-input[disabled] {
- cursor: default;
+[type="number"]::-webkit-inner-spin-button,
+[type="number"]::-webkit-outer-spin-button {
+ height: auto;
}
-/*
- * 1. Addresses box sizing set to content-box in IE 8/9.
- * 2. Removes excess padding in IE 8/9.
- * 3. Removes excess padding in IE 7.
- * Known issue: excess padding remains in IE 6.
+/**
+ * 1. Correct the odd appearance in Chrome and Safari.
+ * 2. Correct the outline style in Safari.
*/
-input[type="checkbox"],
-input[type="radio"] {
- box-sizing: border-box; /* 1 */
- padding: 0; /* 2 */
- *height: 13px; /* 3 */
- *width: 13px; /* 3 */
+[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ outline-offset: -2px; /* 2 */
}
-/*
- * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome.
- * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome
- * (include `-moz` to future-proof).
+/**
+ * Remove the inner padding in Chrome and Safari on macOS.
*/
-/*
-input[type="search"] {
- -webkit-appearance: textfield;
- -moz-box-sizing: content-box;
- -webkit-box-sizing: content-box;
- box-sizing: content-box;
+
+[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
}
-*/
-/*
- * Removes inner padding and search cancel button in Safari 5 and Chrome
- * on OS X.
+/**
+ * 1. Correct the inability to style clickable types in iOS and Safari.
+ * 2. Change font properties to `inherit` in Safari.
*/
-/* input[type="search"]::-webkit-search-cancel-button,
-input[type="search"]::-webkit-search-decoration {
- -webkit-appearance: none;
-} */
+::-webkit-file-upload-button {
+ -webkit-appearance: button; /* 1 */
+ font: inherit; /* 2 */
+}
+
+/* Interactive
+ ========================================================================== */
/*
- * Removes inner padding and border in Firefox 3+.
+ * Add the correct display in Edge, IE 10+, and Firefox.
*/
-button::-moz-focus-inner,
-input::-moz-focus-inner {
- border: 0;
- padding: 0;
+details {
+ display: block;
}
/*
- * 1. Removes default vertical scrollbar in IE 6/7/8/9.
- * 2. Improves readability and alignment in all browsers.
+ * Add the correct display in all browsers.
*/
-textarea {
- overflow: auto; /* 1 */
- vertical-align: top; /* 2 */
+summary {
+ display: list-item;
}
-/* ==========================================================================
- Tables
+/* Misc
========================================================================== */
-/*
- * Remove most spacing between table cells.
+/**
+ * Add the correct display in IE 10+.
*/
-table {
- border-collapse: collapse;
- border-spacing: 0;
+template {
+ display: none;
}
+
+/**
+ * Add the correct display in IE 10.
+ */
+
+[hidden] {
+ display: none;
+}
\ No newline at end of file
diff --git a/cps/static/css/libs/viewer.css b/cps/static/css/libs/viewer.css
index b593d119..82766d66 100644
--- a/cps/static/css/libs/viewer.css
+++ b/cps/static/css/libs/viewer.css
@@ -24,16 +24,13 @@
line-height: 1.0;
}
-.textLayer > div {
+.textLayer > span {
color: transparent;
position: absolute;
white-space: pre;
cursor: text;
-webkit-transform-origin: 0% 0%;
- -moz-transform-origin: 0% 0%;
- -o-transform-origin: 0% 0%;
- -ms-transform-origin: 0% 0%;
- transform-origin: 0% 0%;
+ transform-origin: 0% 0%;
}
.textLayer .highlight {
@@ -60,9 +57,10 @@
background-color: rgb(0, 100, 0);
}
-.textLayer ::selection { background: rgb(0,0,255); }
.textLayer ::-moz-selection { background: rgb(0,0,255); }
+.textLayer ::selection { background: rgb(0,0,255); }
+
.textLayer .endOfContent {
display: block;
position: absolute;
@@ -73,8 +71,9 @@
z-index: -1;
cursor: default;
-webkit-user-select: none;
- -ms-user-select: none;
- -moz-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
}
.textLayer .endOfContent.active {
@@ -86,7 +85,8 @@
position: absolute;
}
-.annotationLayer .linkAnnotation > a {
+.annotationLayer .linkAnnotation > a,
+.annotationLayer .buttonWidgetAnnotation.pushButton > a {
position: absolute;
font-size: 1em;
top: 0;
@@ -95,11 +95,8 @@
height: 100%;
}
-.annotationLayer .linkAnnotation > a /* -ms-a */ {
- background: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7") 0 0 repeat;
-}
-
-.annotationLayer .linkAnnotation > a:hover {
+.annotationLayer .linkAnnotation > a:hover,
+.annotationLayer .buttonWidgetAnnotation.pushButton > a:hover {
opacity: 0.2;
background: #ff0;
box-shadow: 0px 2px 10px #ff0;
@@ -110,6 +107,119 @@
cursor: pointer;
}
+.annotationLayer .textWidgetAnnotation input,
+.annotationLayer .textWidgetAnnotation textarea,
+.annotationLayer .choiceWidgetAnnotation select,
+.annotationLayer .buttonWidgetAnnotation.checkBox input,
+.annotationLayer .buttonWidgetAnnotation.radioButton input {
+ background-color: rgba(0, 54, 255, 0.13);
+ border: 1px solid transparent;
+ box-sizing: border-box;
+ font-size: 9px;
+ height: 100%;
+ margin: 0;
+ padding: 0 3px;
+ vertical-align: top;
+ width: 100%;
+}
+
+.annotationLayer .choiceWidgetAnnotation select option {
+ padding: 0;
+}
+
+.annotationLayer .buttonWidgetAnnotation.radioButton input {
+ border-radius: 50%;
+}
+
+.annotationLayer .textWidgetAnnotation textarea {
+ font: message-box;
+ font-size: 9px;
+ resize: none;
+}
+
+.annotationLayer .textWidgetAnnotation input[disabled],
+.annotationLayer .textWidgetAnnotation textarea[disabled],
+.annotationLayer .choiceWidgetAnnotation select[disabled],
+.annotationLayer .buttonWidgetAnnotation.checkBox input[disabled],
+.annotationLayer .buttonWidgetAnnotation.radioButton input[disabled] {
+ background: none;
+ border: 1px solid transparent;
+ cursor: not-allowed;
+}
+
+.annotationLayer .textWidgetAnnotation input:hover,
+.annotationLayer .textWidgetAnnotation textarea:hover,
+.annotationLayer .choiceWidgetAnnotation select:hover,
+.annotationLayer .buttonWidgetAnnotation.checkBox input:hover,
+.annotationLayer .buttonWidgetAnnotation.radioButton input:hover {
+ border: 1px solid #000;
+}
+
+.annotationLayer .textWidgetAnnotation input:focus,
+.annotationLayer .textWidgetAnnotation textarea:focus,
+.annotationLayer .choiceWidgetAnnotation select:focus {
+ background: none;
+ border: 1px solid transparent;
+}
+
+.annotationLayer .buttonWidgetAnnotation.checkBox input:checked:before,
+.annotationLayer .buttonWidgetAnnotation.checkBox input:checked:after,
+.annotationLayer .buttonWidgetAnnotation.radioButton input:checked:before {
+ background-color: #000;
+ content: '';
+ display: block;
+ position: absolute;
+}
+
+.annotationLayer .buttonWidgetAnnotation.checkBox input:checked:before,
+.annotationLayer .buttonWidgetAnnotation.checkBox input:checked:after {
+ height: 80%;
+ left: 45%;
+ width: 1px;
+}
+
+.annotationLayer .buttonWidgetAnnotation.checkBox input:checked:before {
+ -webkit-transform: rotate(45deg);
+ transform: rotate(45deg);
+}
+
+.annotationLayer .buttonWidgetAnnotation.checkBox input:checked:after {
+ -webkit-transform: rotate(-45deg);
+ transform: rotate(-45deg);
+}
+
+.annotationLayer .buttonWidgetAnnotation.radioButton input:checked:before {
+ border-radius: 50%;
+ height: 50%;
+ left: 30%;
+ top: 20%;
+ width: 50%;
+}
+
+.annotationLayer .textWidgetAnnotation input.comb {
+ font-family: monospace;
+ padding-left: 2px;
+ padding-right: 0;
+}
+
+.annotationLayer .textWidgetAnnotation input.comb:focus {
+ /*
+ * Letter spacing is placed on the right side of each character. Hence, the
+ * letter spacing of the last character may be placed outside the visible
+ * area, causing horizontal scrolling. We avoid this by extending the width
+ * when the element has focus and revert this when it loses focus.
+ */
+ width: 115%;
+}
+
+.annotationLayer .buttonWidgetAnnotation.checkBox input,
+.annotationLayer .buttonWidgetAnnotation.radioButton input {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ padding: 0;
+}
+
.annotationLayer .popupWrapper {
position: absolute;
width: 20em;
@@ -125,16 +235,19 @@
padding: 0.6em;
margin-left: 5px;
cursor: pointer;
+ font: message-box;
word-wrap: break-word;
}
.annotationLayer .popup h1 {
font-size: 1em;
border-bottom: 1px solid #000000;
+ margin: 0;
padding-bottom: 0.2em;
}
.annotationLayer .popup p {
+ margin: 0;
padding-top: 0.2em;
}
@@ -142,6 +255,13 @@
.annotationLayer .underlineAnnotation,
.annotationLayer .squigglyAnnotation,
.annotationLayer .strikeoutAnnotation,
+.annotationLayer .lineAnnotation svg line,
+.annotationLayer .squareAnnotation svg rect,
+.annotationLayer .circleAnnotation svg ellipse,
+.annotationLayer .polylineAnnotation svg polyline,
+.annotationLayer .polygonAnnotation svg polygon,
+.annotationLayer .inkAnnotation svg polyline,
+.annotationLayer .stampAnnotation,
.annotationLayer .fileAttachmentAnnotation {
cursor: pointer;
}
@@ -159,7 +279,8 @@
overflow: visible;
border: 9px solid transparent;
background-clip: content-box;
- border-image: url(images/shadow.png) 9 9 repeat;
+ -o-border-image: url(images/shadow.png) 9 9 repeat;
+ border-image: url(images/shadow.png) 9 9 repeat;
background-color: white;
}
@@ -168,11 +289,64 @@
border: none;
}
+.pdfViewer.singlePageView {
+ display: inline-block;
+}
+
+.pdfViewer.singlePageView .page {
+ margin: 0;
+ border: none;
+}
+
+.pdfViewer.scrollHorizontal, .pdfViewer.scrollWrapped, .spread {
+ margin-left: 3.5px;
+ margin-right: 3.5px;
+ text-align: center;
+}
+
+.pdfViewer.scrollHorizontal, .spread {
+ white-space: nowrap;
+}
+
+.pdfViewer.removePageBorders,
+.pdfViewer.scrollHorizontal .spread,
+.pdfViewer.scrollWrapped .spread {
+ margin-left: 0;
+ margin-right: 0;
+}
+
+.spread .page,
+.pdfViewer.scrollHorizontal .page,
+.pdfViewer.scrollWrapped .page,
+.pdfViewer.scrollHorizontal .spread,
+.pdfViewer.scrollWrapped .spread {
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.spread .page,
+.pdfViewer.scrollHorizontal .page,
+.pdfViewer.scrollWrapped .page {
+ margin-left: -3.5px;
+ margin-right: -3.5px;
+}
+
+.pdfViewer.removePageBorders .spread .page,
+.pdfViewer.removePageBorders.scrollHorizontal .page,
+.pdfViewer.removePageBorders.scrollWrapped .page {
+ margin-left: 5px;
+ margin-right: 5px;
+}
+
.pdfViewer .page canvas {
margin: 0;
display: block;
}
+.pdfViewer .page canvas[hidden] {
+ display: none;
+}
+
.pdfViewer .page .loadingIcon {
position: absolute;
display: block;
@@ -183,6 +357,26 @@
background: url('images/loading-icon.gif') center no-repeat;
}
+.pdfPresentationMode .pdfViewer {
+ margin-left: 0;
+ margin-right: 0;
+}
+
+.pdfPresentationMode .pdfViewer .page,
+.pdfPresentationMode .pdfViewer .spread {
+ display: block;
+}
+
+.pdfPresentationMode .pdfViewer .page,
+.pdfPresentationMode .pdfViewer.removePageBorders .page {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.pdfPresentationMode:-ms-fullscreen .pdfViewer .page {
+ margin-bottom: 100% !important;
+}
+
.pdfPresentationMode:-webkit-full-screen .pdfViewer .page {
margin-bottom: 100%;
border: 0;
@@ -193,16 +387,15 @@
border: 0;
}
-.pdfPresentationMode:-ms-fullscreen .pdfViewer .page {
- margin-bottom: 100% !important;
- border: 0;
-}
-
.pdfPresentationMode:fullscreen .pdfViewer .page {
margin-bottom: 100%;
border: 0;
}
+:root {
+ --sidebar-width: 200px;
+}
+
* {
padding: 0;
margin: 0;
@@ -237,6 +430,15 @@ select {
display: none !important;
}
+#viewerContainer.pdfPresentationMode:-ms-fullscreen {
+ top: 0px !important;
+ overflow: hidden !important;
+}
+
+#viewerContainer.pdfPresentationMode:-ms-fullscreen::-ms-backdrop {
+ background-color: #000;
+}
+
#viewerContainer.pdfPresentationMode:-webkit-full-screen {
top: 0px;
border-top: 2px solid transparent;
@@ -246,6 +448,7 @@ select {
overflow: hidden;
cursor: none;
-webkit-user-select: none;
+ user-select: none;
}
#viewerContainer.pdfPresentationMode:-moz-full-screen {
@@ -257,20 +460,19 @@ select {
overflow: hidden;
cursor: none;
-moz-user-select: none;
+ user-select: none;
}
#viewerContainer.pdfPresentationMode:-ms-fullscreen {
- top: 0px !important;
+ top: 0px;
border-top: 2px solid transparent;
+ background-color: #000;
width: 100%;
height: 100%;
- overflow: hidden !important;
+ overflow: hidden;
cursor: none;
-ms-user-select: none;
-}
-
-#viewerContainer.pdfPresentationMode:-ms-fullscreen::-ms-backdrop {
- background-color: #000;
+ user-select: none;
}
#viewerContainer.pdfPresentationMode:fullscreen {
@@ -282,8 +484,9 @@ select {
overflow: hidden;
cursor: none;
-webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
}
.pdfPresentationMode:-webkit-full-screen a:not(.internalLink) {
@@ -295,60 +498,34 @@ select {
}
.pdfPresentationMode:-ms-fullscreen a:not(.internalLink) {
- display: none !important;
+ display: none;
}
.pdfPresentationMode:fullscreen a:not(.internalLink) {
display: none;
}
-.pdfPresentationMode:-webkit-full-screen .textLayer > div {
+.pdfPresentationMode:-webkit-full-screen .textLayer > span {
cursor: none;
}
-.pdfPresentationMode:-moz-full-screen .textLayer > div {
+.pdfPresentationMode:-moz-full-screen .textLayer > span {
cursor: none;
}
-.pdfPresentationMode:-ms-fullscreen .textLayer > div {
+.pdfPresentationMode:-ms-fullscreen .textLayer > span {
cursor: none;
}
-.pdfPresentationMode:fullscreen .textLayer > div {
+.pdfPresentationMode:fullscreen .textLayer > span {
cursor: none;
}
.pdfPresentationMode.pdfPresentationModeControls > *,
-.pdfPresentationMode.pdfPresentationModeControls .textLayer > div {
+.pdfPresentationMode.pdfPresentationModeControls .textLayer > span {
cursor: default;
}
-/* outer/inner center provides horizontal center */
-.outerCenter {
- pointer-events: none;
- position: relative;
-}
-html[dir='ltr'] .outerCenter {
- float: right;
- right: 50%;
-}
-html[dir='rtl'] .outerCenter {
- float: left;
- left: 50%;
-}
-.innerCenter {
- pointer-events: auto;
- position: relative;
-}
-html[dir='ltr'] .innerCenter {
- float: right;
- right: -50%;
-}
-html[dir='rtl'] .innerCenter {
- float: left;
- left: -50%;
-}
-
#outerContainer {
width: 100%;
height: 100%;
@@ -357,35 +534,51 @@ html[dir='rtl'] .innerCenter {
#sidebarContainer {
position: absolute;
- top: 0;
+ top: 32px;
bottom: 0;
- width: 200px;
+ width: 200px; /* Here, and elsewhere below, keep the constant value for compatibility
+ with older browsers that lack support for CSS variables. */
+ width: var(--sidebar-width);
visibility: hidden;
- -webkit-transition-duration: 200ms;
- -webkit-transition-timing-function: ease;
+ z-index: 100;
+ border-top: 1px solid #333;
+
transition-duration: 200ms;
transition-timing-function: ease;
-
}
html[dir='ltr'] #sidebarContainer {
- -webkit-transition-property: left;
transition-property: left;
left: -200px;
+ left: calc(-1 * var(--sidebar-width));
}
html[dir='rtl'] #sidebarContainer {
- -webkit-transition-property: right;
transition-property: right;
right: -200px;
+ right: calc(-1 * var(--sidebar-width));
}
-#outerContainer.sidebarMoving > #sidebarContainer,
-#outerContainer.sidebarOpen > #sidebarContainer {
+.loadingInProgress #sidebarContainer {
+ top: 36px;
+}
+
+#outerContainer.sidebarResizing #sidebarContainer {
+ /* Improve responsiveness and avoid visual glitches when the sidebar is resized. */
+ transition-duration: 0s;
+ /* Prevent e.g. the thumbnails being selected when the sidebar is resized. */
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+#outerContainer.sidebarMoving #sidebarContainer,
+#outerContainer.sidebarOpen #sidebarContainer {
visibility: visible;
}
-html[dir='ltr'] #outerContainer.sidebarOpen > #sidebarContainer {
+html[dir='ltr'] #outerContainer.sidebarOpen #sidebarContainer {
left: 0px;
}
-html[dir='rtl'] #outerContainer.sidebarOpen > #sidebarContainer {
+html[dir='rtl'] #outerContainer.sidebarOpen #sidebarContainer {
right: 0px;
}
@@ -396,20 +589,6 @@ html[dir='rtl'] #outerContainer.sidebarOpen > #sidebarContainer {
bottom: 0;
left: 0;
min-width: 320px;
- -webkit-transition-duration: 200ms;
- -webkit-transition-timing-function: ease;
- transition-duration: 200ms;
- transition-timing-function: ease;
-}
-html[dir='ltr'] #outerContainer.sidebarOpen > #mainContainer {
- -webkit-transition-property: left;
- transition-property: left;
- left: 200px;
-}
-html[dir='rtl'] #outerContainer.sidebarOpen > #mainContainer {
- -webkit-transition-property: right;
- transition-property: right;
- right: 200px;
}
#sidebarContent {
@@ -418,7 +597,7 @@ html[dir='rtl'] #outerContainer.sidebarOpen > #mainContainer {
overflow: auto;
-webkit-overflow-scrolling: touch;
position: absolute;
- width: 200px;
+ width: 100%;
background-color: hsla(0,0%,0%,.1);
}
html[dir='ltr'] #sidebarContent {
@@ -440,6 +619,10 @@ html[dir='rtl'] #sidebarContent {
left: 0;
outline: none;
}
+#viewerContainer:not(.pdfPresentationMode) {
+ transition-duration: 200ms;
+ transition-timing-function: ease;
+}
html[dir='ltr'] #viewerContainer {
box-shadow: inset 1px 0 0 hsla(0,0%,100%,.05);
}
@@ -447,6 +630,22 @@ html[dir='rtl'] #viewerContainer {
box-shadow: inset -1px 0 0 hsla(0,0%,100%,.05);
}
+#outerContainer.sidebarResizing #viewerContainer {
+ /* Improve responsiveness and avoid visual glitches when the sidebar is resized. */
+ transition-duration: 0s;
+}
+
+html[dir='ltr'] #outerContainer.sidebarOpen #viewerContainer:not(.pdfPresentationMode) {
+ transition-property: left;
+ left: 200px;
+ left: var(--sidebar-width);
+}
+html[dir='rtl'] #outerContainer.sidebarOpen #viewerContainer:not(.pdfPresentationMode) {
+ transition-property: right;
+ right: 200px;
+ right: var(--sidebar-width);
+}
+
.toolbar {
position: relative;
left: 0;
@@ -460,7 +659,7 @@ html[dir='rtl'] #viewerContainer {
}
#toolbarSidebar {
- width: 200px;
+ width: 100%;
height: 32px;
background-color: #424242; /* fallback */
background-image: url(images/texture.png),
@@ -479,6 +678,21 @@ html[dir='rtl'] #toolbarSidebar {
0 0 1px hsla(0,0%,0%,.1);
}
+#sidebarResizer {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 6px;
+ z-index: 200;
+ cursor: ew-resize;
+}
+html[dir='ltr'] #sidebarResizer {
+ right: -6px;
+}
+html[dir='rtl'] #sidebarResizer {
+ left: -6px;
+}
+
#toolbarContainer, .findbar, .secondaryToolbar {
position: relative;
height: 32px;
@@ -487,15 +701,13 @@ html[dir='rtl'] #toolbarSidebar {
linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
}
html[dir='ltr'] #toolbarContainer, .findbar, .secondaryToolbar {
- box-shadow: inset 1px 0 0 hsla(0,0%,100%,.08),
- inset 0 1px 1px hsla(0,0%,0%,.15),
+ box-shadow: inset 0 1px 1px hsla(0,0%,0%,.15),
inset 0 -1px 0 hsla(0,0%,100%,.05),
0 1px 0 hsla(0,0%,0%,.15),
0 1px 1px hsla(0,0%,0%,.1);
}
html[dir='rtl'] #toolbarContainer, .findbar, .secondaryToolbar {
- box-shadow: inset -1px 0 0 hsla(0,0%,100%,.08),
- inset 0 1px 1px hsla(0,0%,0%,.15),
+ box-shadow: inset 0 1px 1px hsla(0,0%,0%,.15),
inset 0 -1px 0 hsla(0,0%,100%,.05),
0 1px 0 hsla(0,0%,0%,.15),
0 1px 1px hsla(0,0%,0%,.1);
@@ -521,7 +733,6 @@ html[dir='rtl'] #toolbarContainer, .findbar, .secondaryToolbar {
height: 100%;
background-color: #ddd;
overflow: hidden;
- -webkit-transition: width 200ms;
transition: width 200ms;
}
@@ -537,7 +748,6 @@ html[dir='rtl'] #toolbarContainer, .findbar, .secondaryToolbar {
#loadingBar .progress.indeterminate {
background-color: #999;
- -webkit-transition: none;
transition: none;
}
@@ -554,15 +764,15 @@ html[dir='rtl'] #toolbarContainer, .findbar, .secondaryToolbar {
#ddd 95px, #bbb 100px);
-webkit-animation: progressIndeterminate 950ms linear infinite;
- animation: progressIndeterminate 950ms linear infinite;
+
+ animation: progressIndeterminate 950ms linear infinite;
}
.findbar, .secondaryToolbar {
top: 32px;
position: absolute;
z-index: 10000;
- height: 32px;
-
+ height: auto;
min-width: 16px;
padding: 0px 6px 0px 6px;
margin: 4px 2px 4px 2px;
@@ -573,19 +783,47 @@ html[dir='rtl'] #toolbarContainer, .findbar, .secondaryToolbar {
cursor: default;
}
+.findbar {
+ min-width: 300px;
+}
+.findbar > div {
+ height: 32px;
+}
+.findbar.wrapContainers > div {
+ clear: both;
+}
+.findbar.wrapContainers > div#findbarMessageContainer {
+ height: auto;
+}
html[dir='ltr'] .findbar {
left: 68px;
}
-
html[dir='rtl'] .findbar {
right: 68px;
}
.findbar label {
-webkit-user-select: none;
- -moz-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
}
+#findInput {
+ width: 200px;
+}
+#findInput::-webkit-input-placeholder {
+ color: hsl(0, 0%, 75%);
+}
+#findInput:-ms-input-placeholder {
+ font-style: italic;
+}
+#findInput::-ms-input-placeholder {
+ font-style: italic;
+}
+#findInput::placeholder {
+ font-style: italic;
+}
#findInput[data-status="pending"] {
background-image: url(images/loading-small.png);
background-repeat: no-repeat;
@@ -615,6 +853,11 @@ html[dir='rtl'] .secondaryToolbar {
margin-bottom: -4px;
}
+#secondaryToolbarButtonContainer.hiddenScrollModeButtons > .scrollModeButtons,
+#secondaryToolbarButtonContainer.hiddenSpreadModeButtons > .spreadModeButtons {
+ display: none !important;
+}
+
.doorHanger,
.doorHangerRight {
border: 1px solid hsla(0,0%,0%,.5);
@@ -677,41 +920,40 @@ html[dir='ltr'] .doorHangerRight:before {
font-style: italic;
color: #A6B7D0;
}
+#findMsg:empty {
+ display: none;
+}
#findInput.notFound {
background-color: rgb(255, 102, 102);
}
-html[dir='ltr'] #toolbarViewerLeft {
- margin-left: -1px;
-}
-html[dir='rtl'] #toolbarViewerRight {
- margin-right: -1px;
+#toolbarViewerMiddle {
+ position: absolute;
+ left: 50%;
+ -webkit-transform: translateX(-50%);
+ transform: translateX(-50%);
}
html[dir='ltr'] #toolbarViewerLeft,
html[dir='rtl'] #toolbarViewerRight {
- position: absolute;
- top: 0;
- left: 0;
+ float: left;
}
html[dir='ltr'] #toolbarViewerRight,
html[dir='rtl'] #toolbarViewerLeft {
- position: absolute;
- top: 0;
- right: 0;
+ float: right;
}
html[dir='ltr'] #toolbarViewerLeft > *,
html[dir='ltr'] #toolbarViewerMiddle > *,
html[dir='ltr'] #toolbarViewerRight > *,
-html[dir='ltr'] .findbar > * {
+html[dir='ltr'] .findbar * {
position: relative;
float: left;
}
html[dir='rtl'] #toolbarViewerLeft > *,
html[dir='rtl'] #toolbarViewerMiddle > *,
html[dir='rtl'] #toolbarViewerRight > *,
-html[dir='rtl'] .findbar > * {
+html[dir='rtl'] .findbar * {
position: relative;
float: right;
}
@@ -755,10 +997,6 @@ html[dir='rtl'] .splitToolbarButton > .toolbarButton {
opacity: .5;
}
-.toolbarButton.group {
- margin-right: 0;
-}
-
.splitToolbarButton.toggled .toolbarButton {
margin: 0;
}
@@ -775,9 +1013,6 @@ html[dir='rtl'] .splitToolbarButton > .toolbarButton {
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
0 0 1px hsla(0,0%,100%,.15) inset,
0 1px 0 hsla(0,0%,100%,.05);
- -webkit-transition-property: background-color, border-color, box-shadow;
- -webkit-transition-duration: 150ms;
- -webkit-transition-timing-function: ease;
transition-property: background-color, border-color, box-shadow;
transition-duration: 150ms;
transition-timing-function: ease;
@@ -837,9 +1072,6 @@ html[dir='rtl'] .splitToolbarButtonSeparator {
padding: 12px 0;
margin: 1px 0;
box-shadow: 0 0 0 1px hsla(0,0%,100%,.03);
- -webkit-transition-property: padding;
- -webkit-transition-duration: 10ms;
- -webkit-transition-timing-function: ease;
transition-property: padding;
transition-duration: 10ms;
transition-timing-function: ease;
@@ -857,13 +1089,11 @@ html[dir='rtl'] .splitToolbarButtonSeparator {
font-size: 12px;
line-height: 14px;
-webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
/* Opera does not support user-select, use <... unselectable="on"> instead */
cursor: default;
- -webkit-transition-property: background-color, border-color, box-shadow;
- -webkit-transition-duration: 150ms;
- -webkit-transition-timing-function: ease;
transition-property: background-color, border-color, box-shadow;
transition-duration: 150ms;
transition-timing-function: ease;
@@ -906,9 +1136,6 @@ html[dir='rtl'] .dropdownToolbarButton {
box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset,
0 0 1px hsla(0,0%,0%,.2) inset,
0 1px 0 hsla(0,0%,100%,.05);
- -webkit-transition-property: background-color, border-color, box-shadow;
- -webkit-transition-duration: 10ms;
- -webkit-transition-timing-function: linear;
transition-property: background-color, border-color, box-shadow;
transition-duration: 10ms;
transition-timing-function: linear;
@@ -923,9 +1150,6 @@ html[dir='rtl'] .dropdownToolbarButton {
box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset,
0 0 1px hsla(0,0%,0%,.2) inset,
0 1px 0 hsla(0,0%,100%,.05);
- -webkit-transition-property: background-color, border-color, box-shadow;
- -webkit-transition-duration: 10ms;
- -webkit-transition-timing-function: linear;
transition-property: background-color, border-color, box-shadow;
transition-duration: 10ms;
transition-timing-function: linear;
@@ -996,12 +1220,6 @@ html[dir='rtl'] .toolbarButton:first-child {
height: 1px;
}
-.toolbarButtonFlexibleSpacer {
- -webkit-box-flex: 1;
- -moz-box-flex: 1;
- min-width: 30px;
-}
-
html[dir='ltr'] #findPrevious {
margin-left: 3px;
}
@@ -1105,8 +1323,6 @@ html[dir='rtl'] .toolbarButton.pageDown::before {
.toolbarButton.bookmark,
.secondaryToolbarButton.bookmark {
- -webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
box-sizing: border-box;
outline: none;
padding-top: 4px;
@@ -1145,6 +1361,24 @@ html[dir="rtl"] #viewOutline.toolbarButton::before {
content: url(images/toolbarButton-search.png);
}
+.toolbarButton.pdfSidebarNotification::after {
+ position: absolute;
+ display: inline-block;
+ top: 1px;
+ /* Create a filled circle, with a diameter of 9 pixels, using only CSS: */
+ content: '';
+ background-color: #70DB55;
+ height: 9px;
+ width: 9px;
+ border-radius: 50%;
+}
+html[dir='ltr'] .toolbarButton.pdfSidebarNotification::after {
+ left: 17px;
+}
+html[dir='rtl'] .toolbarButton.pdfSidebarNotification::after {
+ right: 17px;
+}
+
.secondaryToolbarButton {
position: relative;
margin: 0 0 4px 0;
@@ -1193,10 +1427,38 @@ html[dir="rtl"] .secondaryToolbarButton > span {
content: url(images/secondaryToolbarButton-rotateCw.png);
}
+.secondaryToolbarButton.selectTool::before {
+ content: url(images/secondaryToolbarButton-selectTool.png);
+}
+
.secondaryToolbarButton.handTool::before {
content: url(images/secondaryToolbarButton-handTool.png);
}
+.secondaryToolbarButton.scrollVertical::before {
+ content: url(images/secondaryToolbarButton-scrollVertical.png);
+}
+
+.secondaryToolbarButton.scrollHorizontal::before {
+ content: url(images/secondaryToolbarButton-scrollHorizontal.png);
+}
+
+.secondaryToolbarButton.scrollWrapped::before {
+ content: url(images/secondaryToolbarButton-scrollWrapped.png);
+}
+
+.secondaryToolbarButton.spreadNone::before {
+ content: url(images/secondaryToolbarButton-spreadNone.png);
+}
+
+.secondaryToolbarButton.spreadOdd::before {
+ content: url(images/secondaryToolbarButton-spreadOdd.png);
+}
+
+.secondaryToolbarButton.spreadEven::before {
+ content: url(images/secondaryToolbarButton-spreadEven.png);
+}
+
.secondaryToolbarButton.documentProperties::before {
content: url(images/secondaryToolbarButton-documentProperties.png);
}
@@ -1291,23 +1553,35 @@ html[dir='rtl'] .verticalToolbarSeparator {
line-height: 14px;
text-align: left;
-webkit-user-select: none;
- -moz-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
cursor: default;
}
#thumbnailView {
position: absolute;
- width: 120px;
+ width: calc(100% - 60px);
top: 0;
bottom: 0;
- padding: 10px 40px 0;
+ padding: 10px 30px 0;
overflow: auto;
-webkit-overflow-scrolling: touch;
}
+#thumbnailView > a:active,
+#thumbnailView > a:focus {
+ outline: 0;
+}
+
.thumbnail {
+ margin: 0 10px 5px 10px;
+}
+html[dir='ltr'] .thumbnail {
float: left;
- margin-bottom: 5px;
+}
+html[dir='rtl'] .thumbnail {
+ float: right;
}
#thumbnailView > a:last-of-type > .thumbnail {
@@ -1320,7 +1594,7 @@ html[dir='rtl'] .verticalToolbarSeparator {
.thumbnail:not([data-loaded]) {
border: 1px dashed rgba(255, 255, 255, 0.5);
- margin: -1px -1px 4px -1px;
+ margin: -1px 9px 4px 9px;
}
.thumbnailImage {
@@ -1371,13 +1645,15 @@ a:focus > .thumbnail > .thumbnailSelectionRing,
#outlineView,
#attachmentsView {
position: absolute;
- width: 192px;
+ width: calc(100% - 8px);
top: 0;
bottom: 0;
overflow: auto;
-webkit-overflow-scrolling: touch;
-webkit-user-select: none;
- -moz-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
}
#outlineView {
@@ -1410,7 +1686,10 @@ html[dir='rtl'] .outlineItem > .outlineItems {
color: hsla(0,0%,100%,.8);
font-size: 13px;
line-height: 15px;
- -moz-user-select: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
white-space: normal;
}
@@ -1505,8 +1784,8 @@ html[dir='rtl'] .outlineItemToggler::before {
/* TODO: file FF bug to support ::-moz-selection:window-inactive
so we can override the opaque grey background when the window is inactive;
see https://bugzilla.mozilla.org/show_bug.cgi?id=706209 */
-::selection { background: rgba(0,0,255,0.3); }
::-moz-selection { background: rgba(0,0,255,0.3); }
+::selection { background: rgba(0,0,255,0.3); }
#errorWrapper {
background: none repeat scroll 0 0 #FF5555;
@@ -1717,21 +1996,19 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * {
mix-blend-mode: screen;
}
-#viewer.textLayer-visible .textLayer > div {
+#viewer.textLayer-visible .textLayer > span {
background-color: rgba(255, 255, 0, 0.1);
color: black;
border: solid 1px rgba(255, 0, 0, 0.5);
- -webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
box-sizing: border-box;
}
-#viewer.textLayer-hover .textLayer > div:hover {
+#viewer.textLayer-hover .textLayer > span:hover {
background-color: white;
color: black;
}
-#viewer.textLayer-shadow .textLayer > div {
+#viewer.textLayer-shadow .textLayer > span {
background-color: rgba(255,255,255, .6);
color: black;
}
@@ -1739,7 +2016,6 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * {
.grab-to-pan-grab {
cursor: url("images/grab.cur"), move !important;
cursor: -webkit-grab !important;
- cursor: -moz-grab !important;
cursor: grab !important;
}
.grab-to-pan-grab *:not(input):not(textarea):not(button):not(select):not(:link) {
@@ -1749,7 +2025,6 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * {
.grab-to-pan-grabbing {
cursor: url("images/grabbing.cur"), move !important;
cursor: -webkit-grabbing !important;
- cursor: -moz-grabbing !important;
cursor: grabbing !important;
position: fixed;
@@ -1771,17 +2046,17 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * {
display: none;
}
-@media screen and (min-resolution: 2dppx) {
+@media screen and (-webkit-min-device-pixel-ratio: 1.1), screen and (min-resolution: 1.1dppx) {
/* Rules for Retina screens */
.toolbarButton::before {
-webkit-transform: scale(0.5);
- transform: scale(0.5);
+ transform: scale(0.5);
top: -5px;
}
.secondaryToolbarButton::before {
-webkit-transform: scale(0.5);
- transform: scale(0.5);
+ transform: scale(0.5);
top: -4px;
}
@@ -1918,17 +2193,45 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * {
content: url(images/secondaryToolbarButton-rotateCw@2x.png);
}
+ .secondaryToolbarButton.selectTool::before {
+ content: url(images/secondaryToolbarButton-selectTool@2x.png);
+ }
+
.secondaryToolbarButton.handTool::before {
content: url(images/secondaryToolbarButton-handTool@2x.png);
}
+ .secondaryToolbarButton.scrollVertical::before {
+ content: url(images/secondaryToolbarButton-scrollVertical@2x.png);
+ }
+
+ .secondaryToolbarButton.scrollHorizontal::before {
+ content: url(images/secondaryToolbarButton-scrollHorizontal@2x.png);
+ }
+
+ .secondaryToolbarButton.scrollWrapped::before {
+ content: url(images/secondaryToolbarButton-scrollWrapped@2x.png);
+ }
+
+ .secondaryToolbarButton.spreadNone::before {
+ content: url(images/secondaryToolbarButton-spreadNone@2x.png);
+ }
+
+ .secondaryToolbarButton.spreadOdd::before {
+ content: url(images/secondaryToolbarButton-spreadOdd@2x.png);
+ }
+
+ .secondaryToolbarButton.spreadEven::before {
+ content: url(images/secondaryToolbarButton-spreadEven@2x.png);
+ }
+
.secondaryToolbarButton.documentProperties::before {
content: url(images/secondaryToolbarButton-documentProperties@2x.png);
}
.outlineItemToggler::before {
-webkit-transform: scale(0.5);
- transform: scale(0.5);
+ transform: scale(0.5);
top: -1px;
content: url(images/treeitem-expanded@2x.png);
}
@@ -1983,11 +2286,11 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * {
display: none;
}
- /* Rules for browsers that support mozPrintCallback */
- body[data-mozPrintCallback] #outerContainer {
+ /* Rules for browsers that support PDF.js printing */
+ body[data-pdfjsprinting] #outerContainer {
display: none;
}
- body[data-mozPrintCallback] #printContainer {
+ body[data-pdfjsprinting] #printContainer {
display: block;
}
#printContainer {
@@ -1998,10 +2301,14 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * {
position: relative;
top: 0;
left: 0;
- height: 100%;
- overflow: hidden;
+ width: 1px;
+ height: 1px;
+ overflow: visible;
+ page-break-after: always;
+ page-break-inside: avoid;
}
- #printContainer canvas {
+ #printContainer canvas,
+ #printContainer img {
display: block;
}
}
@@ -2012,64 +2319,27 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * {
display: none;
}
-@media all and (max-width: 960px) {
- html[dir='ltr'] #outerContainer.sidebarMoving .outerCenter,
- html[dir='ltr'] #outerContainer.sidebarOpen .outerCenter {
- float: left;
- left: 205px;
- }
- html[dir='rtl'] #outerContainer.sidebarMoving .outerCenter,
- html[dir='rtl'] #outerContainer.sidebarOpen .outerCenter {
- float: right;
- right: 205px;
- }
-}
-
@media all and (max-width: 900px) {
- .sidebarOpen .hiddenLargeView {
- display: none;
- }
- .sidebarOpen .visibleLargeView {
- display: inherit;
- }
-}
-
-@media all and (max-width: 860px) {
- .sidebarOpen .hiddenMediumView {
- display: none;
- }
- .sidebarOpen .visibleMediumView {
- display: inherit;
+ #toolbarViewerMiddle {
+ display: table;
+ margin: auto;
+ left: auto;
+ position: inherit;
+ -webkit-transform: none;
+ transform: none;
}
}
-@media all and (max-width: 770px) {
- #sidebarContainer {
- top: 32px;
- z-index: 100;
- }
- .loadingInProgress #sidebarContainer {
- top: 37px;
- }
+@media all and (max-width: 840px) {
#sidebarContent {
- top: 32px;
background-color: hsla(0,0%,0%,.7);
}
- html[dir='ltr'] #outerContainer.sidebarOpen > #mainContainer {
- left: 0px;
+ html[dir='ltr'] #outerContainer.sidebarOpen #viewerContainer {
+ left: 0px !important;
}
- html[dir='rtl'] #outerContainer.sidebarOpen > #mainContainer {
- right: 0px;
- }
-
- html[dir='ltr'] .outerCenter {
- float: left;
- left: 205px;
- }
- html[dir='rtl'] .outerCenter {
- float: right;
- right: 205px;
+ html[dir='rtl'] #outerContainer.sidebarOpen #viewerContainer {
+ right: 0px !important;
}
#outerContainer .hiddenLargeView,
@@ -2082,7 +2352,7 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * {
}
}
-@media all and (max-width: 700px) {
+@media all and (max-width: 770px) {
#outerContainer .hiddenLargeView {
display: none;
}
@@ -2091,7 +2361,7 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * {
}
}
-@media all and (max-width: 660px) {
+@media all and (max-width: 700px) {
#outerContainer .hiddenMediumView {
display: none;
}
@@ -2100,30 +2370,26 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * {
}
}
-@media all and (max-width: 600px) {
- .hiddenSmallView {
+@media all and (max-width: 640px) {
+ .hiddenSmallView, .hiddenSmallView * {
display: none;
}
.visibleSmallView {
display: inherit;
}
- html[dir='ltr'] #outerContainer.sidebarMoving .outerCenter,
- html[dir='ltr'] #outerContainer.sidebarOpen .outerCenter,
- html[dir='ltr'] .outerCenter {
- left: 156px;
- }
- html[dir='rtl'] #outerContainer.sidebarMoving .outerCenter,
- html[dir='rtl'] #outerContainer.sidebarOpen .outerCenter,
- html[dir='rtl'] .outerCenter {
- right: 156px;
- }
.toolbarButtonSpacer {
width: 0;
}
+ html[dir='ltr'] .findbar {
+ left: 38px;
+ }
+ html[dir='rtl'] .findbar {
+ right: 38px;
+ }
}
-@media all and (max-width: 510px) {
- #scaleSelectContainer, #pageNumberLabel {
+@media all and (max-width: 535px) {
+ #scaleSelectContainer {
display: none;
}
}
diff --git a/cps/static/css/listen.css b/cps/static/css/listen.css
new file mode 100644
index 00000000..b08cc33c
--- /dev/null
+++ b/cps/static/css/listen.css
@@ -0,0 +1,114 @@
+.sm2-bar-ui {
+ font-size: 20px;
+ }
+
+ .sm2-bar-ui.compact {
+ max-width: 90%;
+ }
+
+ .sm2-progress .sm2-progress-ball {
+ width: .5333em;
+ height: 1.9333em;
+ border-radius: 0em;
+ }
+
+ .sm2-progress .sm2-progress-track {
+ height: 0.15em;
+ background: white;
+ }
+
+ .sm2-bar-ui .sm2-main-controls,
+ .sm2-bar-ui .sm2-playlist-drawer {
+ background-color: transparent;
+ }
+
+ .sm2-bar-ui .sm2-inline-texture {
+ background: transparent;
+ }
+
+ .rating .glyphicon-star {
+ color: gray;
+ }
+
+ .rating .glyphicon-star.good {
+ color: white;
+ }
+
+ body {
+ overflow: hidden;
+ background: #272B30;
+ color: #aaa;
+ }
+
+ #main {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ }
+
+ #area {
+ width: 80%;
+ height: 80%;
+ margin: 5% auto;
+ max-width: 1250px;
+ }
+
+ #area iframe {
+ border: none;
+ }
+
+ #prev {
+ left: 40px;
+ }
+
+ #next {
+ right: 40px;
+ }
+
+ .arrow {
+ position: absolute;
+ top: 50%;
+ margin-top: -32px;
+ font-size: 64px;
+ color: #E2E2E2;
+ font-family: arial, sans-serif;
+ font-weight: bold;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ }
+
+ .arrow:hover {
+ color: #777;
+ }
+
+ .arrow:active {
+ color: #000;
+ }
+
+ xmp,
+ pre,
+ plaintext {
+ display: block;
+ font-family: -moz-fixed;
+ white-space: pre;
+ margin: 1em 0;
+ }
+
+ #area {
+ overflow: hidden;
+ }
+
+ pre {
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ font-family: -moz-fixed;
+ column-count: 2;
+ -webkit-columns: 2;
+ -moz-columns: 2;
+ column-gap: 20px;
+ -moz-column-gap: 20px;
+ -webkit-column-gap: 20px;
+ position: relative;
+ }
\ No newline at end of file
diff --git a/cps/static/css/main.css b/cps/static/css/main.css
index 4e3ac86f..4afb28f7 100644
--- a/cps/static/css/main.css
+++ b/cps/static/css/main.css
@@ -104,7 +104,7 @@ body {
height: 80%;
/* margin-left: 10%; */
margin: 0 auto;
- max-width: 1250px;
+ /* max-width: 1250px; */
z-index: 2;
position: relative;
overflow: hidden;
@@ -652,10 +652,10 @@ input:-ms-placeholder {
padding: 4px;
}
-@media only screen and (max-width: 1040px) {
+@media only screen and (max-width: 1040px) and (orientation: portrait) {
#viewer{
- width: 50%;
- margin-left: 25%;
+ width: 80%;
+ margin-left: 10%;
}
#divider,
@@ -745,15 +745,63 @@ input:-ms-placeholder {
height: 740px;
}
}
- /*For iPad landscape layouts only */
+ /*For iPad landscape layouts only *//*
@media only screen and (min-device-width: 481px) and (max-device-width: 1024px) and (orientation: landscape) {
#viewer iframe {
width: 460px;
height: 415px;
}
+}*/
+
+@media only screen
+and (min-device-width : 768px)
+and (max-device-width : 1024px)
+and (orientation : landscape)
+/*and (-webkit-min-device-pixel-ratio: 2)*/ {
+ #viewer{
+ width: 80%;
+ margin-left: 10%;
+ }
+ #divider,
+ #divider.show {
+ display: none;
+ }
+}
+
+ /*For iPad landscape layouts only */
+@media only screen and (min-device-width: 481px) and (max-device-width: 1024px) and (orientation: landscape) {
+ #viewer iframe {
+ width: 960px;
+ height: 515px;
+ }
+}
+
+/* For iPhone 6\6s portrait layouts only */
+@media only screen and (min-device-width : 375px) and (max-device-width : 667px) and (orientation: portrait) {
+ #viewer {
+ width: 300px;
+ height: 480px;
+ }
+ #viewer iframe {
+ width: 300px;
+ height: 480px;
+ }
}
+
+/* For iPhone 6\6s landscape layouts only */
+@media only screen and (min-device-width : 375px) and (max-device-width : 667px) and (orientation: landscape) {
+ #viewer {
+ width: 450px;
+ height: 300px;
+ }
+ #viewer iframe {
+ width: 450px;
+ height: 300px;
+ }
+}
+
/* For iPhone portrait layouts only */
-@media only screen and (max-device-width: 480px) and (orientation: portrait) {
+@media only screen and (max-device-width: 374px) and (orientation: portrait) {
#viewer {
width: 256px;
height: 432px;
@@ -763,8 +811,9 @@ input:-ms-placeholder {
height: 432px;
}
}
+
/* For iPhone landscape layouts only */
-@media only screen and (max-device-width: 480px) and (orientation: landscape) {
+@media only screen and (max-device-width: 374px) and (orientation: landscape) {
#viewer iframe {
width: 256px;
height: 124px;
diff --git a/cps/static/css/style.css b/cps/static/css/style.css
index 921a35ff..aaa7503e 100644
--- a/cps/static/css/style.css
+++ b/cps/static/css/style.css
@@ -1,3 +1,6 @@
+
+.tooltip.bottom .tooltip-inner{font-size:13px;font-family:Open Sans Semibold,Helvetica Neue,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;padding:3px 10px;border-radius:4px;background-color:#fff;-webkit-box-shadow:0 4px 10px 0 rgba(0,0,0,.35);box-shadow:0 4px 10px 0 rgba(0,0,0,.35);opacity:1;white-space:nowrap;margin-top:-16px!important;line-height:1.71428571;color:#ddd}
+
@font-face {
font-family: 'Grand Hotel';
font-style: normal;
@@ -5,6 +8,22 @@
src: local('Grand Hotel'), local('GrandHotel-Regular'), url("fonts/GrandHotel-Regular.ttf") format('truetype');
}
+html.http-error {
+ margin: 0;
+ height: 100%;
+}
+.http-error body {
+ margin: 0;
+ height: 100%;
+ display: table;
+ width: 100%;
+}
+.http-error body > div {
+ display: table-cell;
+ vertical-align: middle;
+ text-align: center;
+}
+
body{background:#f2f2f2}body h2{font-weight:normal;color:#444}
body { margin-bottom: 40px;}
a{color: #45b29d}a:hover{color: #444;}
@@ -20,6 +39,11 @@ a{color: #45b29d}a:hover{color: #444;}
.container-fluid .book .meta .title{font-weight:bold;font-size:15px;color:#444}
.container-fluid .book .meta .author{font-size:12px;color:#999}
.container-fluid .book .meta .rating{margin-top:5px}.rating .glyphicon-star{color:#999}.rating .glyphicon-star.good{color:#45b29d}
+
+.container-fluid .author .author-hidden, .container-fluid .author .author-hidden-divider {
+ display: none;
+}
+
.navbar-brand{font-family: 'Grand Hotel', cursive; font-size: 35px; color: #45b29d !important;}
.more-stuff{margin-top: 20px; padding-top: 20px; border-top: 1px solid #ccc}
.more-stuff>li{margin-bottom: 10px;}
@@ -36,18 +60,100 @@ span.glyphicon.glyphicon-tags {padding-right: 5px;color: #999;vertical-align: te
-moz-box-shadow: 0 5px 8px -6px #777;
box-shadow: 0 5px 8px -6px #777;
}
+
.navbar-default .navbar-toggle .icon-bar {background-color: #000;}
.navbar-default .navbar-toggle {border-color: #000;}
+.cover { margin-bottom: 10px;}
.btn-file {position: relative; overflow: hidden;}
.btn-file input[type=file] {position: absolute; top: 0; right: 0; min-width: 100%; min-height: 100%; font-size: 100px; text-align: right; filter: alpha(opacity=0); opacity: 0; outline: none; background: white; cursor: inherit; display: block;}
-.btn-toolbar .btn { margin-bottom: 5px; }
-
+.btn-toolbar .btn,.discover .btn { margin-bottom: 5px; }
+.button-link {color:#fff;}
.btn-primary:hover, .btn-primary:focus, .btn-primary:active, .btn-primary.active, .open .dropdown-toggle.btn-primary{ background-color: #1C5484; }
.btn-primary.disabled, .btn-primary[disabled], fieldset[disabled] .btn-primary, .btn-primary.disabled:hover, .btn-primary[disabled]:hover, fieldset[disabled] .btn-primary:hover, .btn-primary.disabled:focus, .btn-primary[disabled]:focus, fieldset[disabled] .btn-primary:focus, .btn-primary.disabled:active, .btn-primary[disabled]:active, fieldset[disabled] .btn-primary:active, .btn-primary.disabled.active, .btn-primary[disabled].active, fieldset[disabled] .btn-primary.active { background-color: #89B9E2; }
.btn-toolbar>.btn+.btn, .btn-toolbar>.btn-group+.btn, .btn-toolbar>.btn+.btn-group, .btn-toolbar>.btn-group+.btn-group { margin-left:0px; }
-
+.panel-body {background-color: #f5f5f5;}
.spinner {margin:0 41%;}
.spinner2 {margin:0 41%;}
+.block-label {display: block;}
+.fake-input {position: absolute; pointer-events: none; top: 0;}
+
+input.pill { position: absolute; opacity: 0; }
+input.pill + label {
+ border: 2px solid #45b29d;
+ border-radius: 15px;
+ color: #45b29d;
+ cursor: pointer;
+ display: inline-block;
+ padding: 3px 15px;
+ user-select: none;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+input.pill:checked + label {
+ background-color: #45b29d;
+ border-color: #fff;
+ color: #fff;
+}
+input.pill:not(:checked) + label .glyphicon {
+ display: none;
+}
+
+.author-bio img {margin: 0 1em 1em 0;}
+.author-link {display: inline-block; margin-top: 10px; width: 100px;}
+.author-link img {display: block; height: 100%;}
+
+#remove-from-shelves .btn,
+#shelf-action-errors {
+ margin-left: 5px;
+}
+
+.tags_click, .serie_click, .language_click {margin-right: 5px;}
+
+#meta-info {
+ height:600px;
+ overflow-y:scroll;
+}
+.media-list {
+ padding-right:15px;
+}
+.media-body p {
+ text-align: justify;
+}
+#meta-info img { max-height: 150px; max-width: 100px; cursor: pointer; }
+
+.padded-bottom { margin-bottom: 15px; }
+
+.upload-format-input-text {display: initial;}
+#btn-upload-format {display: none;}
+
+.upload-cover-input-text {display: initial;}
+#btn-upload-cover {display: none;}
+
+.panel-title > a { text-decoration: none;}
+
+.editable-buttons { display:inline-block; margin-left: 7px ;}
+.editable-input { display:inline-block;}
+.editable-cancel { margin-bottom: 0px !important; margin-left: 7px !important;}
+.editable-submit { margin-bottom: 0px !important;}
+
+.filterheader { margin-bottom: 20px; }
+
+.errorlink {margin-top: 20px;}
+.modal-body .comments {
+ max-height:300px;
+ overflow-y: auto;
+}
+
+div.log {
+ font-family: Courier New;
+ font-size: 12px;
+ box-sizing: border-box;
+ height: 700px;
+ overflow-y: scroll;
+ border: 1px solid #ddd;
+ white-space: nowrap;
+ padding: 0.5em;
+}
diff --git a/cps/static/css/upload.css b/cps/static/css/upload.css
new file mode 100644
index 00000000..a56dde2c
--- /dev/null
+++ b/cps/static/css/upload.css
@@ -0,0 +1,8 @@
+@media (min-device-width: 768px) {
+ .upload-modal-dialog {
+ position: absolute;
+ top: 45%;
+ left: 50%;
+ transform: translate(-50%, -50%) !important;
+ }
+}
diff --git a/cps/static/favicon.ico b/cps/static/favicon.ico
index 46339365..0774d0f9 100644
Binary files a/cps/static/favicon.ico and b/cps/static/favicon.ico differ
diff --git a/cps/static/img/.gitignore b/cps/static/img/.gitignore
deleted file mode 100644
index e69de29b..00000000
diff --git a/cps/static/img/goodreads.svg b/cps/static/img/goodreads.svg
new file mode 100644
index 00000000..32695526
--- /dev/null
+++ b/cps/static/img/goodreads.svg
@@ -0,0 +1 @@
+
diff --git a/cps/static/js/archive/archive.js b/cps/static/js/archive/archive.js
new file mode 100644
index 00000000..13e1d183
--- /dev/null
+++ b/cps/static/js/archive/archive.js
@@ -0,0 +1,447 @@
+/* alphanum.js (C) Brian Huisman
+ * Based on the Alphanum Algorithm by David Koelle
+ * The Alphanum Algorithm is discussed at http://www.DaveKoelle.com
+ *
+ * Distributed under same license as original
+ *
+ * Released under the MIT License - https://opensource.org/licenses/MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+/* ********************************************************************
+* Alphanum sort() function version - case insensitive
+* - Slower, but easier to modify for arrays of objects which contain
+* string properties
+*
+*/
+/* exported alphanumCase */
+
+
+function alphanumCase(a, b) {
+ function chunkify(t) {
+ var tz = new Array();
+ var x = 0, y = -1, n = 0, i, j;
+
+ while (i = (j = t.charAt(x++)).charCodeAt(0)) {
+ var m = (i === 46 || (i >= 48 && i <= 57));
+ if (m !== n) {
+ tz[++y] = "";
+ n = m;
+ }
+ tz[y] += j;
+ }
+ return tz;
+ }
+
+ var aa = chunkify(a.filename.toLowerCase());
+ var bb = chunkify(b.filename.toLowerCase());
+
+ for (var x = 0; aa[x] && bb[x]; x++) {
+ if (aa[x] !== bb[x]) {
+ var c = Number(aa[x]), d = Number(bb[x]);
+ if (c === aa[x] && d === bb[x]) {
+ return c - d;
+ } else {
+ return (aa[x] > bb[x]) ? 1 : -1;
+ }
+ }
+ }
+ return aa.length - bb.length;
+}
+// ===========================================================================
+
+
+/**
+ * archive.js
+ *
+ * Provides base functionality for unarchiving.
+ *
+ * Licensed under the MIT License
+ *
+ * Copyright(c) 2011 Google Inc.
+ */
+
+/* global bitjs, Uint8Array */
+
+var bitjs = bitjs || {};
+bitjs.archive = bitjs.archive || {};
+
+(function() {
+
+ // ===========================================================================
+ // Stolen from Closure because it's the best way to do Java-like inheritance.
+ bitjs.base = function(me, optMethodName, varArgs) {
+ var caller = arguments.callee.caller;
+ if (caller.superClass_) {
+ // This is a constructor. Call the superclass constructor.
+ return caller.superClass_.constructor.apply(
+ me, Array.prototype.slice.call(arguments, 1));
+ }
+
+ var args = Array.prototype.slice.call(arguments, 2);
+ var foundCaller = false;
+ for (var ctor = me.constructor; ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) {
+ if (ctor.prototype[optMethodName] === caller) {
+ foundCaller = true;
+ } else if (foundCaller) {
+ return ctor.prototype[optMethodName].apply(me, args);
+ }
+ }
+
+ // If we did not find the caller in the prototype chain,
+ // then one of two things happened:
+ // 1) The caller is an instance method.
+ // 2) This method was not called by the right caller.
+ if (me[optMethodName] === caller) {
+ return me.constructor.prototype[optMethodName].apply(me, args);
+ } else {
+ throw Error(
+ "goog.base called from a method of one name " +
+ "to a method of a different name");
+ }
+ };
+ bitjs.inherits = function(childCtor, parentCtor) {
+ /** @constructor */
+ function TempCtor() {}
+ TempCtor.prototype = parentCtor.prototype;
+ childCtor.superClass_ = parentCtor.prototype;
+ childCtor.prototype = new TempCtor();
+ childCtor.prototype.constructor = childCtor;
+ };
+ // ===========================================================================
+
+ /**
+ * An unarchive event.
+ *
+ * @param {string} type The event type.
+ * @constructor
+ */
+ bitjs.archive.UnarchiveEvent = function(type) {
+ /**
+ * The event type.
+ *
+ * @type {string}
+ */
+ this.type = type;
+ };
+
+ /**
+ * The UnarchiveEvent types.
+ */
+ bitjs.archive.UnarchiveEvent.Type = {
+ START: "start",
+ PROGRESS: "progress",
+ EXTRACT: "extract",
+ FINISH: "finish",
+ INFO: "info",
+ ERROR: "error"
+ };
+
+ /**
+ * Useful for passing info up to the client (for debugging).
+ *
+ * @param {string} msg The info message.
+ */
+ bitjs.archive.UnarchiveInfoEvent = function(msg) {
+ bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.INFO);
+
+ /**
+ * The information message.
+ *
+ * @type {string}
+ */
+ this.msg = msg;
+ };
+ bitjs.inherits(bitjs.archive.UnarchiveInfoEvent, bitjs.archive.UnarchiveEvent);
+
+ /**
+ * An unrecoverable error has occured.
+ *
+ * @param {string} msg The error message.
+ */
+ bitjs.archive.UnarchiveErrorEvent = function(msg) {
+ bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.ERROR);
+
+ /**
+ * The information message.
+ *
+ * @type {string}
+ */
+ this.msg = msg;
+ };
+ bitjs.inherits(bitjs.archive.UnarchiveErrorEvent, bitjs.archive.UnarchiveEvent);
+
+ /**
+ * Start event.
+ *
+ * @param {string} msg The info message.
+ */
+ bitjs.archive.UnarchiveStartEvent = function() {
+ bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.START);
+ };
+ bitjs.inherits(bitjs.archive.UnarchiveStartEvent, bitjs.archive.UnarchiveEvent);
+
+ /**
+ * Finish event.
+ *
+ * @param {string} msg The info message.
+ */
+ bitjs.archive.UnarchiveFinishEvent = function() {
+ bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.FINISH);
+ };
+ bitjs.inherits(bitjs.archive.UnarchiveFinishEvent, bitjs.archive.UnarchiveEvent);
+
+ /**
+ * Progress event.
+ */
+ bitjs.archive.UnarchiveProgressEvent = function(
+ currentFilename,
+ currentFileNumber,
+ currentBytesUnarchivedInFile,
+ currentBytesUnarchived,
+ totalUncompressedBytesInArchive,
+ totalFilesInArchive) {
+ bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.PROGRESS);
+
+ this.currentFilename = currentFilename;
+ this.currentFileNumber = currentFileNumber;
+ this.currentBytesUnarchivedInFile = currentBytesUnarchivedInFile;
+ this.totalFilesInArchive = totalFilesInArchive;
+ this.currentBytesUnarchived = currentBytesUnarchived;
+ this.totalUncompressedBytesInArchive = totalUncompressedBytesInArchive;
+ };
+ bitjs.inherits(bitjs.archive.UnarchiveProgressEvent, bitjs.archive.UnarchiveEvent);
+
+ /**
+ * All extracted files returned by an Unarchiver will implement
+ * the following interface:
+ *
+ * interface UnarchivedFile {
+ * string filename
+ * TypedArray fileData
+ * }
+ *
+ */
+
+ /**
+ * Extract event.
+ */
+ bitjs.archive.UnarchiveExtractEvent = function(unarchivedFile) {
+ bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.EXTRACT);
+
+ /**
+ * @type {UnarchivedFile}
+ */
+ this.unarchivedFile = unarchivedFile;
+ };
+ bitjs.inherits(bitjs.archive.UnarchiveExtractEvent, bitjs.archive.UnarchiveEvent);
+
+
+ /**
+ * Base class for all Unarchivers.
+ *
+ * @param {ArrayBuffer} arrayBuffer The Array Buffer.
+ * @param {string} optPathToBitJS Optional string for where the BitJS files are located.
+ * @constructor
+ */
+ bitjs.archive.Unarchiver = function(arrayBuffer, optPathToBitJS) {
+ /**
+ * The ArrayBuffer object.
+ * @type {ArrayBuffer}
+ * @protected
+ */
+ this.ab = arrayBuffer;
+
+ /**
+ * The path to the BitJS files.
+ * @type {string}
+ * @private
+ */
+ this.pathToBitJS_ = optPathToBitJS || "/";
+
+ /**
+ * A map from event type to an array of listeners.
+ * @type {Map.
}
+ */
+ this.listeners_ = {};
+ for (var type in bitjs.archive.UnarchiveEvent.Type) {
+ this.listeners_[bitjs.archive.UnarchiveEvent.Type[type]] = [];
+ }
+ };
+
+ /**
+ * Private web worker initialized during start().
+ * @type {Worker}
+ * @private
+ */
+ bitjs.archive.Unarchiver.prototype.worker_ = null;
+
+ /**
+ * This method must be overridden by the subclass to return the script filename.
+ * @return {string} The script filename.
+ * @protected.
+ */
+ bitjs.archive.Unarchiver.prototype.getScriptFileName = function() {
+ throw "Subclasses of AbstractUnarchiver must overload getScriptFileName()";
+ };
+
+ /**
+ * Adds an event listener for UnarchiveEvents.
+ *
+ * @param {string} Event type.
+ * @param {function} An event handler function.
+ */
+ bitjs.archive.Unarchiver.prototype.addEventListener = function(type, listener) {
+ if (type in this.listeners_) {
+ if (this.listeners_[type].indexOf(listener) === -1) {
+ this.listeners_[type].push(listener);
+ }
+ }
+ };
+
+ /**
+ * Removes an event listener.
+ *
+ * @param {string} Event type.
+ * @param {EventListener|function} An event listener or handler function.
+ */
+ bitjs.archive.Unarchiver.prototype.removeEventListener = function(type, listener) {
+ if (type in this.listeners_) {
+ var index = this.listeners_[type].indexOf(listener);
+ if (index !== -1) {
+ this.listeners_[type].splice(index, 1);
+ }
+ }
+ };
+
+ /**
+ * Receive an event and pass it to the listener functions.
+ *
+ * @param {bitjs.archive.UnarchiveEvent} e
+ * @private
+ */
+ bitjs.archive.Unarchiver.prototype.handleWorkerEvent_ = function(e) {
+ if ((e instanceof bitjs.archive.UnarchiveEvent || e.type) &&
+ this.listeners_[e.type] instanceof Array) {
+ this.listeners_[e.type].forEach(function (listener) {
+ listener(e);
+ });
+ if (e.type === bitjs.archive.UnarchiveEvent.Type.FINISH) {
+ this.worker_.terminate();
+ }
+ }
+ };
+
+ /**
+ * Starts the unarchive in a separate Web Worker thread and returns immediately.
+ */
+ bitjs.archive.Unarchiver.prototype.start = function() {
+ var me = this;
+ var scriptFileName = this.pathToBitJS_ + this.getScriptFileName();
+ if (scriptFileName) {
+ this.worker_ = new Worker(scriptFileName);
+
+ this.worker_.onerror = function(e) {
+ throw e;
+ };
+
+ this.worker_.onmessage = function(e) {
+ if (typeof e.data !== "string") {
+ // Assume that it is an UnarchiveEvent. Some browsers preserve the 'type'
+ // so that instanceof UnarchiveEvent returns true, but others do not.
+ me.handleWorkerEvent_(e.data);
+ }
+ };
+
+ this.worker_.postMessage({file: this.ab});
+ }
+ };
+
+ /**
+ * Terminates the Web Worker for this Unarchiver and returns immediately.
+ */
+ bitjs.archive.Unarchiver.prototype.stop = function() {
+ if (this.worker_) {
+ this.worker_.terminate();
+ }
+ };
+
+
+ /**
+ * Unzipper
+ * @extends {bitjs.archive.Unarchiver}
+ * @constructor
+ */
+ bitjs.archive.Unzipper = function(arrayBuffer, optPathToBitJS) {
+ bitjs.base(this, arrayBuffer, optPathToBitJS);
+ };
+ bitjs.inherits(bitjs.archive.Unzipper, bitjs.archive.Unarchiver);
+ bitjs.archive.Unzipper.prototype.getScriptFileName = function() {
+ return "unzip.js";
+ };
+
+ /**
+ * Unrarrer
+ * @extends {bitjs.archive.Unarchiver}
+ * @constructor
+ */
+ bitjs.archive.Unrarrer = function(arrayBuffer, optPathToBitJS) {
+ bitjs.base(this, arrayBuffer, optPathToBitJS);
+ };
+ bitjs.inherits(bitjs.archive.Unrarrer, bitjs.archive.Unarchiver);
+ bitjs.archive.Unrarrer.prototype.getScriptFileName = function() {
+ return "unrar.js";
+ };
+
+ /**
+ * Untarrer
+ * @extends {bitjs.archive.Unarchiver}
+ * @constructor
+ */
+ bitjs.archive.Untarrer = function(arrayBuffer, optPathToBitJS) {
+ bitjs.base(this, arrayBuffer, optPathToBitJS);
+ };
+ bitjs.inherits(bitjs.archive.Untarrer, bitjs.archive.Unarchiver);
+ bitjs.archive.Untarrer.prototype.getScriptFileName = function() {
+ return "untar.js";
+ };
+
+ /**
+ * Factory method that creates an unarchiver based on the byte signature found
+ * in the arrayBuffer.
+ * @param {ArrayBuffer} ab
+ * @param {string=} optPathToBitJS Path to the unarchiver script files.
+ * @return {bitjs.archive.Unarchiver}
+ */
+ bitjs.archive.GetUnarchiver = function(ab, optPathToBitJS) {
+ var unarchiver = null;
+ var pathToBitJS = optPathToBitJS || "";
+ var h = new Uint8Array(ab, 0, 10);
+
+ if (h[0] === 0x52 && h[1] === 0x61 && h[2] === 0x72 && h[3] === 0x21) { // Rar!
+ unarchiver = new bitjs.archive.Unrarrer(ab, pathToBitJS);
+ } else if (h[0] === 80 && h[1] === 75) { // PK (Zip)
+ unarchiver = new bitjs.archive.Unzipper(ab, pathToBitJS);
+ } else { // Try with tar
+ unarchiver = new bitjs.archive.Untarrer(ab, pathToBitJS);
+ }
+ return unarchiver;
+ };
+
+})();
diff --git a/cps/static/js/archive/rarvm.js b/cps/static/js/archive/rarvm.js
new file mode 100644
index 00000000..44e09330
--- /dev/null
+++ b/cps/static/js/archive/rarvm.js
@@ -0,0 +1,864 @@
+/**
+ * rarvm.js
+ *
+ * Licensed under the MIT License
+ *
+ * Copyright(c) 2017 Google Inc.
+ */
+
+/**
+ * CRC Implementation.
+ */
+/* global Uint8Array, Uint32Array, bitjs, DataView */
+/* exported MAXWINMASK, UnpackFilter */
+
+var CRCTab = new Array(256).fill(0);
+
+function initCRC() {
+ for (var i = 0; i < 256; ++i) {
+ var c = i;
+ for (var j = 0; j < 8; ++j) {
+ // Read http://stackoverflow.com/questions/6798111/bitwise-operations-on-32-bit-unsigned-ints
+ // for the bitwise operator issue (JS interprets operands as 32-bit signed
+ // integers and we need to deal with unsigned ones here).
+ c = ((c & 1) ? ((c >>> 1) ^ 0xEDB88320) : (c >>> 1)) >>> 0;
+ }
+ CRCTab[i] = c;
+ }
+}
+
+/**
+ * @param {number} startCRC
+ * @param {Uint8Array} arr
+ * @return {number}
+ */
+function CRC(startCRC, arr) {
+ if (CRCTab[1] === 0) {
+ initCRC();
+ }
+
+ /*
+ #if defined(LITTLE_ENDIAN) && defined(PRESENT_INT32) && defined(ALLOW_NOT_ALIGNED_INT)
+ while (Size>0 && ((long)Data & 7))
+ {
+ StartCRC=CRCTab[(byte)(StartCRC^Data[0])]^(StartCRC>>8);
+ Size--;
+ Data++;
+ }
+ while (Size>=8)
+ {
+ StartCRC^=*(uint32 *)Data;
+ StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
+ StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
+ StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
+ StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
+ StartCRC^=*(uint32 *)(Data+4);
+ StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
+ StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
+ StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
+ StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
+ Data+=8;
+ Size-=8;
+ }
+ #endif
+ */
+
+ for (var i = 0; i < arr.length; ++i) {
+ var byte = ((startCRC ^ arr[i]) >>> 0) & 0xff;
+ startCRC = (CRCTab[byte] ^ (startCRC >>> 8)) >>> 0;
+ }
+
+ return startCRC;
+}
+
+// ============================================================================================== //
+
+
+/**
+ * RarVM Implementation.
+ */
+var VM_MEMSIZE = 0x40000;
+var VM_MEMMASK = (VM_MEMSIZE - 1);
+var VM_GLOBALMEMADDR = 0x3C000;
+var VM_GLOBALMEMSIZE = 0x2000;
+var VM_FIXEDGLOBALSIZE = 64;
+var MAXWINSIZE = 0x400000;
+var MAXWINMASK = (MAXWINSIZE - 1);
+
+/**
+ */
+var VmCommands = {
+ VM_MOV: 0,
+ VM_CMP: 1,
+ VM_ADD: 2,
+ VM_SUB: 3,
+ VM_JZ: 4,
+ VM_JNZ: 5,
+ VM_INC: 6,
+ VM_DEC: 7,
+ VM_JMP: 8,
+ VM_XOR: 9,
+ VM_AND: 10,
+ VM_OR: 11,
+ VM_TEST: 12,
+ VM_JS: 13,
+ VM_JNS: 14,
+ VM_JB: 15,
+ VM_JBE: 16,
+ VM_JA: 17,
+ VM_JAE: 18,
+ VM_PUSH: 19,
+ VM_POP: 20,
+ VM_CALL: 21,
+ VM_RET: 22,
+ VM_NOT: 23,
+ VM_SHL: 24,
+ VM_SHR: 25,
+ VM_SAR: 26,
+ VM_NEG: 27,
+ VM_PUSHA: 28,
+ VM_POPA: 29,
+ VM_PUSHF: 30,
+ VM_POPF: 31,
+ VM_MOVZX: 32,
+ VM_MOVSX: 33,
+ VM_XCHG: 34,
+ VM_MUL: 35,
+ VM_DIV: 36,
+ VM_ADC: 37,
+ VM_SBB: 38,
+ VM_PRINT: 39,
+
+ /*
+ #ifdef VM_OPTIMIZE
+ VM_MOVB, VM_MOVD, VM_CMPB, VM_CMPD,
+
+ VM_ADDB, VM_ADDD, VM_SUBB, VM_SUBD, VM_INCB, VM_INCD, VM_DECB, VM_DECD,
+ VM_NEGB, VM_NEGD,
+ #endif
+ */
+
+ // TODO: This enum value would be much larger if VM_OPTIMIZE.
+ VM_STANDARD: 40,
+};
+
+/**
+ */
+var VmStandardFilters = {
+ VMSF_NONE: 0,
+ VMSF_E8: 1,
+ VMSF_E8E9: 2,
+ VMSF_ITANIUM: 3,
+ VMSF_RGB: 4,
+ VMSF_AUDIO: 5,
+ VMSF_DELTA: 6,
+ VMSF_UPCASE: 7,
+};
+
+/**
+ */
+var VmFlags = {
+ VM_FC: 1,
+ VM_FZ: 2,
+ VM_FS: 0x80000000,
+};
+
+/**
+ */
+var VmOpType = {
+ VM_OPREG: 0,
+ VM_OPINT: 1,
+ VM_OPREGMEM: 2,
+ VM_OPNONE: 3,
+};
+
+/**
+ * Finds the key that maps to a given value in an object. This function is useful in debugging
+ * variables that use the above enums.
+ * @param {Object} obj
+ * @param {number} val
+ * @return {string} The key/enum value as a string.
+ */
+function findKeyForValue(obj, val) {
+ for (var key in obj) {
+ if (obj[key] === val) {
+ return key;
+ }
+ }
+ return null;
+}
+
+function getDebugString(obj, val) {
+ var s = "Unknown.";
+ if (obj === VmCommands) {
+ s = "VmCommands.";
+ } else if (obj === VmStandardFilters) {
+ s = "VmStandardFilters.";
+ } else if (obj === VmFlags) {
+ s = "VmOpType.";
+ } else if (obj === VmOpType) {
+ s = "VmOpType.";
+ }
+
+ return s + findKeyForValue(obj, val);
+}
+
+/**
+ * @struct
+ * @constructor
+ */
+var VmPreparedOperand = function() {
+ /** @type {VmOpType} */
+ this.Type;
+
+ /** @type {number} */
+ this.Data = 0;
+
+ /** @type {number} */
+ this.Base = 0;
+
+ // TODO: In C++ this is a uint*
+ /** @type {Array} */
+ this.Addr = null;
+};
+
+/** @return {string} */
+VmPreparedOperand.prototype.toString = function() {
+ if (this.Type === null) {
+ return "Error: Type was null in VmPreparedOperand";
+ }
+ return "{ " +
+ "Type: " + getDebugString(VmOpType, this.Type) +
+ ", Data: " + this.Data +
+ ", Base: " + this.Base +
+ " }";
+};
+
+/**
+ * @struct
+ * @constructor
+ */
+var VmPreparedCommand = function() {
+ /** @type {VmCommands} */
+ this.OpCode;
+
+ /** @type {boolean} */
+ this.ByteMode = false;
+
+ /** @type {VmPreparedOperand} */
+ this.Op1 = new VmPreparedOperand();
+
+ /** @type {VmPreparedOperand} */
+ this.Op2 = new VmPreparedOperand();
+};
+
+/** @return {string} */
+VmPreparedCommand.prototype.toString = function(indent) {
+ if (this.OpCode === null) {
+ return "Error: OpCode was null in VmPreparedCommand";
+ }
+ indent = indent || "";
+ return indent + "{\n" +
+ indent + " OpCode: " + getDebugString(VmCommands, this.OpCode) + ",\n" +
+ indent + " ByteMode: " + this.ByteMode + ",\n" +
+ indent + " Op1: " + this.Op1.toString() + ",\n" +
+ indent + " Op2: " + this.Op2.toString() + ",\n" +
+ indent + "}";
+};
+
+/**
+ * @struct
+ * @constructor
+ */
+var VmPreparedProgram = function() {
+ /** @type {Array} */
+ this.Cmd = [];
+
+ /** @type {Array} */
+ this.AltCmd = null;
+
+ /** @type {Uint8Array} */
+ this.GlobalData = new Uint8Array();
+
+ /** @type {Uint8Array} */
+ this.StaticData = new Uint8Array(); // static data contained in DB operators
+
+ /** @type {Uint32Array} */
+ this.InitR = new Uint32Array(7);
+
+ /**
+ * A pointer to bytes that have been filtered by a program.
+ * @type {Uint8Array}
+ */
+ this.FilteredData = null;
+};
+
+/** @return {string} */
+VmPreparedProgram.prototype.toString = function() {
+ var s = "{\n Cmd: [\n";
+ for (var i = 0; i < this.Cmd.length; ++i) {
+ s += this.Cmd[i].toString(" ") + ",\n";
+ }
+ s += "],\n";
+ // TODO: Dump GlobalData, StaticData, InitR?
+ s += " }\n";
+ return s;
+};
+
+/**
+ * @struct
+ * @constructor
+ */
+var UnpackFilter = function() {
+ /** @type {number} */
+ this.BlockStart = 0;
+
+ /** @type {number} */
+ this.BlockLength = 0;
+
+ /** @type {number} */
+ this.ExecCount = 0;
+
+ /** @type {boolean} */
+ this.NextWindow = false;
+
+ // position of parent filter in Filters array used as prototype for filter
+ // in PrgStack array. Not defined for filters in Filters array.
+ /** @type {number} */
+ this.ParentFilter = null;
+
+ /** @type {VmPreparedProgram} */
+ this.Prg = new VmPreparedProgram();
+};
+
+var VMCF_OP0 = 0;
+var VMCF_OP1 = 1;
+var VMCF_OP2 = 2;
+var VMCF_OPMASK = 3;
+var VMCF_BYTEMODE = 4;
+var VMCF_JUMP = 8;
+var VMCF_PROC = 16;
+var VMCF_USEFLAGS = 32;
+var VMCF_CHFLAGS = 64;
+
+var VmCmdFlags = [
+ /* VM_MOV */
+ VMCF_OP2 | VMCF_BYTEMODE,
+ /* VM_CMP */
+ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
+ /* VM_ADD */
+ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
+ /* VM_SUB */
+ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
+ /* VM_JZ */
+ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
+ /* VM_JNZ */
+ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
+ /* VM_INC */
+ VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS,
+ /* VM_DEC */
+ VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS,
+ /* VM_JMP */
+ VMCF_OP1 | VMCF_JUMP,
+ /* VM_XOR */
+ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
+ /* VM_AND */
+ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
+ /* VM_OR */
+ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
+ /* VM_TEST */
+ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
+ /* VM_JS */
+ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
+ /* VM_JNS */
+ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
+ /* VM_JB */
+ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
+ /* VM_JBE */
+ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
+ /* VM_JA */
+ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
+ /* VM_JAE */
+ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
+ /* VM_PUSH */
+ VMCF_OP1,
+ /* VM_POP */
+ VMCF_OP1,
+ /* VM_CALL */
+ VMCF_OP1 | VMCF_PROC,
+ /* VM_RET */
+ VMCF_OP0 | VMCF_PROC,
+ /* VM_NOT */
+ VMCF_OP1 | VMCF_BYTEMODE,
+ /* VM_SHL */
+ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
+ /* VM_SHR */
+ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
+ /* VM_SAR */
+ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
+ /* VM_NEG */
+ VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS,
+ /* VM_PUSHA */
+ VMCF_OP0,
+ /* VM_POPA */
+ VMCF_OP0,
+ /* VM_PUSHF */
+ VMCF_OP0 | VMCF_USEFLAGS,
+ /* VM_POPF */
+ VMCF_OP0 | VMCF_CHFLAGS,
+ /* VM_MOVZX */
+ VMCF_OP2,
+ /* VM_MOVSX */
+ VMCF_OP2,
+ /* VM_XCHG */
+ VMCF_OP2 | VMCF_BYTEMODE,
+ /* VM_MUL */
+ VMCF_OP2 | VMCF_BYTEMODE,
+ /* VM_DIV */
+ VMCF_OP2 | VMCF_BYTEMODE,
+ /* VM_ADC */
+ VMCF_OP2 | VMCF_BYTEMODE | VMCF_USEFLAGS | VMCF_CHFLAGS,
+ /* VM_SBB */
+ VMCF_OP2 | VMCF_BYTEMODE | VMCF_USEFLAGS | VMCF_CHFLAGS,
+ /* VM_PRINT */
+ VMCF_OP0,
+];
+
+
+/**
+ * @param {number} length
+ * @param {number} crc
+ * @param {VmStandardFilters} type
+ * @struct
+ * @constructor
+ */
+var StandardFilterSignature = function(length, crc, type) {
+ /** @type {number} */
+ this.Length = length;
+
+ /** @type {number} */
+ this.CRC = crc;
+
+ /** @type {VmStandardFilters} */
+ this.Type = type;
+};
+
+/**
+ * @type {Array}
+ */
+var StdList = [
+ new StandardFilterSignature(53, 0xad576887, VmStandardFilters.VMSF_E8),
+ new StandardFilterSignature(57, 0x3cd7e57e, VmStandardFilters.VMSF_E8E9),
+ new StandardFilterSignature(120, 0x3769893f, VmStandardFilters.VMSF_ITANIUM),
+ new StandardFilterSignature(29, 0x0e06077d, VmStandardFilters.VMSF_DELTA),
+ new StandardFilterSignature(149, 0x1c2c5dc8, VmStandardFilters.VMSF_RGB),
+ new StandardFilterSignature(216, 0xbc85e701, VmStandardFilters.VMSF_AUDIO),
+ new StandardFilterSignature(40, 0x46b9c560, VmStandardFilters.VMSF_UPCASE),
+];
+
+/**
+ * @constructor
+ */
+var RarVM = function() {
+ /** @private {Uint8Array} */
+ this.mem_ = null;
+
+ /** @private {Uint32Array} */
+ this.R_ = new Uint32Array(8);
+
+ /** @private {number} */
+ this.flags_ = 0;
+};
+
+/**
+ * Initializes the memory of the VM.
+ */
+RarVM.prototype.init = function() {
+ if (!this.mem_) {
+ this.mem_ = new Uint8Array(VM_MEMSIZE);
+ }
+};
+
+/**
+ * @param {Uint8Array} code
+ * @return {VmStandardFilters}
+ */
+RarVM.prototype.isStandardFilter = function(code) {
+ var codeCRC = (CRC(0xffffffff, code, code.length) ^ 0xffffffff) >>> 0;
+ for (var i = 0; i < StdList.length; ++i) {
+ if (StdList[i].CRC === codeCRC && StdList[i].Length === code.length) {
+ return StdList[i].Type;
+ }
+ }
+
+ return VmStandardFilters.VMSF_NONE;
+};
+
+/**
+ * @param {VmPreparedOperand} op
+ * @param {boolean} byteMode
+ * @param {bitjs.io.BitStream} bstream A rtl bit stream.
+ */
+RarVM.prototype.decodeArg = function(op, byteMode, bstream) {
+ var data = bstream.peekBits(16);
+ if (data & 0x8000) {
+ op.Type = VmOpType.VM_OPREG; // Operand is register (R[0]..R[7])
+ bstream.readBits(1); // 1 flag bit and...
+ op.Data = bstream.readBits(3); // ... 3 register number bits
+ op.Addr = [this.R_[op.Data]]; // TODO &R[Op.Data] // Register address
+ } else {
+ if ((data & 0xc000) === 0) {
+ op.Type = VmOpType.VM_OPINT; // Operand is integer
+ bstream.readBits(2); // 2 flag bits
+ if (byteMode) {
+ op.Data = bstream.readBits(8); // Byte integer.
+ } else {
+ op.Data = RarVM.readData(bstream); // 32 bit integer.
+ }
+ } else {
+ // Operand is data addressed by register data, base address or both.
+ op.Type = VmOpType.VM_OPREGMEM;
+ if ((data & 0x2000) === 0) {
+ bstream.readBits(3); // 3 flag bits
+ // Base address is zero, just use the address from register.
+ op.Data = bstream.readBits(3); // (Data>>10)&7
+ op.Addr = [this.R_[op.Data]]; // TODO &R[op.Data]
+ op.Base = 0;
+ } else {
+ bstream.readBits(4); // 4 flag bits
+ if ((data & 0x1000) === 0) {
+ // Use both register and base address.
+ op.Data = bstream.readBits(3);
+ op.Addr = [this.R_[op.Data]]; // TODO &R[op.Data]
+ } else {
+ // Use base address only. Access memory by fixed address.
+ op.Data = 0;
+ }
+ op.Base = RarVM.readData(bstream); // Read base address.
+ }
+ }
+ }
+};
+
+/**
+ * @param {VmPreparedProgram} prg
+ */
+RarVM.prototype.execute = function(prg) {
+ this.R_.set(prg.InitR);
+
+ var globalSize = Math.min(prg.GlobalData.length, VM_GLOBALMEMSIZE);
+ if (globalSize) {
+ this.mem_.set(prg.GlobalData.subarray(0, globalSize), VM_GLOBALMEMADDR);
+ }
+
+ var staticSize = Math.min(prg.StaticData.length, VM_GLOBALMEMSIZE - globalSize);
+ if (staticSize) {
+ this.mem_.set(prg.StaticData.subarray(0, staticSize), VM_GLOBALMEMADDR + globalSize);
+ }
+
+ this.R_[7] = VM_MEMSIZE;
+ this.flags_ = 0;
+
+ var preparedCodes = prg.AltCmd ? prg.AltCmd : prg.Cmd;
+ if (prg.Cmd.length > 0 && !this.executeCode(preparedCodes)) {
+ // Invalid VM program. Let's replace it with 'return' command.
+ preparedCodes.OpCode = VmCommands.VM_RET;
+ }
+
+ var dataView = new DataView(this.mem_.buffer, VM_GLOBALMEMADDR);
+ var newBlockPos = dataView.getUint32(0x20, true /* little endian */ ) & VM_MEMMASK;
+ var newBlockSize = dataView.getUint32(0x1c, true /* little endian */ ) & VM_MEMMASK;
+ if (newBlockPos + newBlockSize >= VM_MEMSIZE) {
+ newBlockPos = newBlockSize = 0;
+ }
+ prg.FilteredData = this.mem_.subarray(newBlockPos, newBlockPos + newBlockSize);
+
+ prg.GlobalData = new Uint8Array(0);
+
+ var dataSize = Math.min(dataView.getUint32(0x30),
+ (VM_GLOBALMEMSIZE - VM_FIXEDGLOBALSIZE));
+ if (dataSize !== 0) {
+ var len = dataSize + VM_FIXEDGLOBALSIZE;
+ prg.GlobalData = new Uint8Array(len);
+ prg.GlobalData.set(mem.subarray(VM_GLOBALMEMADDR, VM_GLOBALMEMADDR + len));
+ }
+};
+
+/**
+ * @param {Array} preparedCodes
+ * @return {boolean}
+ */
+RarVM.prototype.executeCode = function(preparedCodes) {
+ var codeIndex = 0;
+ var cmd = preparedCodes[codeIndex];
+ // TODO: Why is this an infinite loop instead of just returning
+ // when a VM_RET is hit?
+ while (1) {
+ switch (cmd.OpCode) {
+ case VmCommands.VM_RET:
+ if (this.R_[7] >= VM_MEMSIZE) {
+ return true;
+ }
+ //SET_IP(GET_VALUE(false,(uint *)&Mem[R[7] & VM_MEMMASK]));
+ this.R_[7] += 4;
+ continue;
+
+ case VmCommands.VM_STANDARD:
+ this.executeStandardFilter(cmd.Op1.Data);
+ break;
+
+ default:
+ console.error("RarVM OpCode not supported: " + getDebugString(VmCommands, cmd.OpCode));
+ break;
+ } // switch (cmd.OpCode)
+ codeIndex++;
+ cmd = preparedCodes[codeIndex];
+ }
+};
+
+/**
+ * @param {number} filterType
+ */
+RarVM.prototype.executeStandardFilter = function(filterType) {
+ switch (filterType) {
+ case VmStandardFilters.VMSF_DELTA:
+ var dataSize = this.R_[4];
+ var channels = this.R_[0];
+ var srcPos = 0;
+ var border = dataSize * 2;
+
+ //SET_VALUE(false,&Mem[VM_GLOBALMEMADDR+0x20],DataSize);
+ var dataView = new DataView(this.mem_.buffer, VM_GLOBALMEMADDR);
+ dataView.setUint32(0x20, dataSize, true /* little endian */ );
+
+ if (dataSize >= VM_GLOBALMEMADDR / 2) {
+ break;
+ }
+
+ // Bytes from same channels are grouped to continual data blocks,
+ // so we need to place them back to their interleaving positions.
+ for (var curChannel = 0; curChannel < channels; ++curChannel) {
+ var prevByte = 0;
+ for (var destPos = dataSize + curChannel; destPos < border; destPos += channels) {
+ prevByte = (prevByte - this.mem_[srcPos++]) & 0xff;
+ this.mem_[destPos] = prevByte;
+ }
+ }
+
+ break;
+
+ default:
+ console.error("RarVM Standard Filter not supported: " + getDebugString(VmStandardFilters, filterType));
+ break;
+ }
+};
+
+/**
+ * @param {Uint8Array} code
+ * @param {VmPreparedProgram} prg
+ */
+RarVM.prototype.prepare = function(code, prg) {
+ var codeSize = code.length;
+ var i;
+ var curCmd;
+
+ //InitBitInput();
+ //memcpy(InBuf,Code,Min(CodeSize,BitInput::MAX_SIZE));
+ var bstream = new bitjs.io.BitStream(code.buffer, true /* rtl */ );
+
+ // Calculate the single byte XOR checksum to check validity of VM code.
+ var xorSum = 0;
+ for (i = 1; i < codeSize; ++i) {
+ xorSum ^= code[i];
+ }
+
+ bstream.readBits(8);
+
+ prg.Cmd = []; // TODO: Is this right? I don't see it being done in rarvm.cpp.
+
+ // VM code is valid if equal.
+ if (xorSum === code[0]) {
+ var filterType = this.isStandardFilter(code);
+ if (filterType !== VmStandardFilters.VMSF_NONE) {
+ // VM code is found among standard filters.
+ curCmd = new VmPreparedCommand();
+ prg.Cmd.push(curCmd);
+
+ curCmd.OpCode = VmCommands.VM_STANDARD;
+ curCmd.Op1.Data = filterType;
+ // TODO: Addr=&CurCmd->Op1.Data
+ curCmd.Op1.Addr = [curCmd.Op1.Data];
+ curCmd.Op2.Addr = [null]; // &CurCmd->Op2.Data;
+ curCmd.Op1.Type = VmOpType.VM_OPNONE;
+ curCmd.Op2.Type = VmOpType.VM_OPNONE;
+ codeSize = 0;
+ }
+
+ var dataFlag = bstream.readBits(1);
+
+ // Read static data contained in DB operators. This data cannot be
+ // changed, it is a part of VM code, not a filter parameter.
+
+ if (dataFlag & 0x8000) {
+ var dataSize = RarVM.readData(bstream) + 1;
+ // TODO: This accesses the byte pointer of the bstream directly. Is that ok?
+ for (i = 0; i < bstream.bytePtr < codeSize && i < dataSize; ++i) {
+ // Append a byte to the program's static data.
+ var newStaticData = new Uint8Array(prg.StaticData.length + 1);
+ newStaticData.set(prg.StaticData);
+ newStaticData[newStaticData.length - 1] = bstream.readBits(8);
+ prg.StaticData = newStaticData;
+ }
+ }
+
+ while (bstream.bytePtr < codeSize) {
+ curCmd = new VmPreparedCommand();
+ prg.Cmd.push(curCmd); // Prg->Cmd.Add(1)
+ var flag = bstream.peekBits(1);
+ if (!flag) { // (Data&0x8000)==0
+ curCmd.OpCode = bstream.readBits(4);
+ } else {
+ curCmd.OpCode = (bstream.readBits(6) - 24);
+ }
+
+ if (VmCmdFlags[curCmd.OpCode] & VMCF_BYTEMODE) {
+ curCmd.ByteMode = (bstream.readBits(1) !== 0);
+ } else {
+ curCmd.ByteMode = 0;
+ }
+ curCmd.Op1.Type = VmOpType.VM_OPNONE;
+ curCmd.Op2.Type = VmOpType.VM_OPNONE;
+ var opNum = (VmCmdFlags[curCmd.OpCode] & VMCF_OPMASK);
+ curCmd.Op1.Addr = null;
+ curCmd.Op2.Addr = null;
+ if (opNum > 0) {
+ this.decodeArg(curCmd.Op1, curCmd.ByteMode, bstream); // reading the first operand
+ if (opNum === 2) {
+ this.decodeArg(curCmd.Op2, curCmd.ByteMode, bstream); // reading the second operand
+ } else {
+ if (curCmd.Op1.Type === VmOpType.VM_OPINT && (VmCmdFlags[curCmd.OpCode] & (VMCF_JUMP | VMCF_PROC))) {
+ // Calculating jump distance.
+ var distance = curCmd.Op1.Data;
+ if (distance >= 256) {
+ distance -= 256;
+ } else {
+ if (distance >= 136) {
+ distance -= 264;
+ } else {
+ if (distance >= 16) {
+ distance -= 8;
+ } else {
+ if (distance >= 8) {
+ distance -= 16;
+ }
+ }
+ }
+ distance += prg.Cmd.length;
+ }
+ curCmd.Op1.Data = distance;
+ }
+ }
+ } // if (OpNum>0)
+ } // while ((uint)InAddrOp1.Data
+ curCmd.Op1.Addr = [curCmd.Op1.Data];
+ curCmd.Op2.Addr = [curCmd.Op2.Data];
+ curCmd.Op1.Type = VmOpType.VM_OPNONE;
+ curCmd.Op2.Type = VmOpType.VM_OPNONE;
+
+ // If operand 'Addr' field has not been set by DecodeArg calls above,
+ // let's set it to point to operand 'Data' field. It is necessary for
+ // VM_OPINT type operands (usual integers) or maybe if something was
+ // not set properly for other operands. 'Addr' field is required
+ // for quicker addressing of operand data.
+ for (i = 0; i < prg.Cmd.length; ++i) {
+ var cmd = prg.Cmd[i];
+ if (cmd.Op1.Addr === null) {
+ cmd.Op1.Addr = [cmd.Op1.Data];
+ }
+ if (cmd.Op2.Addr === null) {
+ cmd.Op2.Addr = [cmd.Op2.Data];
+ }
+ }
+
+ /*
+ #ifdef VM_OPTIMIZE
+ if (CodeSize!=0)
+ Optimize(Prg);
+ #endif
+ */
+};
+
+/**
+ * @param {Uint8Array} arr The byte array to set a value in.
+ * @param {number} value The unsigned 32-bit value to set.
+ * @param {number} offset Offset into arr to start setting the value, defaults to 0.
+ */
+RarVM.prototype.setLowEndianValue = function(arr, value, offset) {
+ var i = offset || 0;
+ arr[i] = value & 0xff;
+ arr[i + 1] = (value >>> 8) & 0xff;
+ arr[i + 2] = (value >>> 16) & 0xff;
+ arr[i + 3] = (value >>> 24) & 0xff;
+};
+
+/**
+ * Sets a number of bytes of the VM memory at the given position from a
+ * source buffer of bytes.
+ * @param {number} pos The position in the VM memory to start writing to.
+ * @param {Uint8Array} buffer The source buffer of bytes.
+ * @param {number} dataSize The number of bytes to set.
+ */
+RarVM.prototype.setMemory = function(pos, buffer, dataSize) {
+ if (pos < VM_MEMSIZE) {
+ var numBytes = Math.min(dataSize, VM_MEMSIZE - pos);
+ for (var i = 0; i < numBytes; ++i) {
+ this.mem_[pos + i] = buffer[i];
+ }
+ }
+};
+
+/**
+ * Static function that reads in the next set of bits for the VM
+ * (might return 4, 8, 16 or 32 bits).
+ * @param {bitjs.io.BitStream} bstream A RTL bit stream.
+ * @return {number} The value of the bits read.
+ */
+RarVM.readData = function(bstream) {
+ // Read in the first 2 bits.
+ var flags = bstream.readBits(2);
+ switch (flags) { // Data&0xc000
+ // Return the next 4 bits.
+ case 0:
+ return bstream.readBits(4); // (Data>>10)&0xf
+
+ case 1: // 0x4000
+ // 0x3c00 => 0011 1100 0000 0000
+ if (bstream.peekBits(4) === 0) { // (Data&0x3c00)==0
+ // Skip the 4 zero bits.
+ bstream.readBits(4);
+ // Read in the next 8 and pad with 1s to 32 bits.
+ return (0xffffff00 | bstream.readBits(8)) >>> 0; // ((Data>>2)&0xff)
+ }
+
+ // Else, read in the next 8.
+ return bstream.readBits(8);
+
+ // Read in the next 16.
+ case 2: // 0x8000
+ var val = bstream.getBits();
+ bstream.readBits(16);
+ return val; //bstream.readBits(16);
+
+ // case 3
+ default:
+ return (bstream.readBits(16) << 16) | bstream.readBits(16);
+ }
+};
+
+// ============================================================================================== //
diff --git a/cps/static/js/archive/unrar.js b/cps/static/js/archive/unrar.js
new file mode 100644
index 00000000..fadb791e
--- /dev/null
+++ b/cps/static/js/archive/unrar.js
@@ -0,0 +1,1368 @@
+/**
+ * unrar.js
+ *
+ * Licensed under the MIT License
+ *
+ * Copyright(c) 2011 Google Inc.
+ * Copyright(c) 2011 antimatter15
+ *
+ * Reference Documentation:
+ *
+ * http://kthoom.googlecode.com/hg/docs/unrar.html
+ */
+/* global bitjs, importScripts, RarVM, Uint8Array, UnpackFilter */
+/* global VM_FIXEDGLOBALSIZE, VM_GLOBALMEMSIZE, MAXWINMASK, VM_GLOBALMEMADDR, MAXWINSIZE */
+
+// This file expects to be invoked as a Worker (see onmessage below).
+importScripts("../io/bitstream.js");
+importScripts("../io/bytebuffer.js");
+importScripts("archive.js");
+importScripts("rarvm.js");
+
+// Progress variables.
+var currentFilename = "";
+var currentFileNumber = 0;
+var currentBytesUnarchivedInFile = 0;
+var currentBytesUnarchived = 0;
+var totalUncompressedBytesInArchive = 0;
+var totalFilesInArchive = 0;
+
+// Helper functions.
+var info = function(str) {
+ postMessage(new bitjs.archive.UnarchiveInfoEvent(str));
+};
+var err = function(str) {
+ postMessage(new bitjs.archive.UnarchiveErrorEvent(str));
+};
+var postProgress = function() {
+ postMessage(new bitjs.archive.UnarchiveProgressEvent(
+ currentFilename,
+ currentFileNumber,
+ currentBytesUnarchivedInFile,
+ currentBytesUnarchived,
+ totalUncompressedBytesInArchive,
+ totalFilesInArchive));
+};
+
+// shows a byte value as its hex representation
+var nibble = "0123456789ABCDEF";
+var byteValueToHexString = function(num) {
+ return nibble[num >> 4] + nibble[num & 0xF];
+};
+var twoByteValueToHexString = function(num) {
+ return nibble[(num >> 12) & 0xF] + nibble[(num >> 8) & 0xF] + nibble[(num >> 4) & 0xF] + nibble[num & 0xF];
+};
+
+
+// Volume Types
+// MARK_HEAD = 0x72;
+var MAIN_HEAD = 0x73,
+ FILE_HEAD = 0x74,
+ // COMM_HEAD = 0x75,
+ // AV_HEAD = 0x76,
+ // SUB_HEAD = 0x77,
+ // PROTECT_HEAD = 0x78,
+ // SIGN_HEAD = 0x79,
+ // NEWSUB_HEAD = 0x7a,
+ ENDARC_HEAD = 0x7b;
+
+// ============================================================================================== //
+
+/**
+ * @param {bitjs.io.BitStream} bstream
+ * @constructor
+ */
+var RarVolumeHeader = function(bstream) {
+ var headPos = bstream.bytePtr;
+ // byte 1,2
+ info("Rar Volume Header @" + bstream.bytePtr);
+
+ this.crc = bstream.readBits(16);
+ info(" crc=" + this.crc);
+
+ // byte 3
+ this.headType = bstream.readBits(8);
+ info(" headType=" + this.headType);
+
+ // Get flags
+ // bytes 4,5
+ this.flags = {};
+ this.flags.value = bstream.peekBits(16);
+
+ info(" flags=" + twoByteValueToHexString(this.flags.value));
+ switch (this.headType) {
+ case MAIN_HEAD:
+ this.flags.MHD_VOLUME = !!bstream.readBits(1);
+ this.flags.MHD_COMMENT = !!bstream.readBits(1);
+ this.flags.MHD_LOCK = !!bstream.readBits(1);
+ this.flags.MHD_SOLID = !!bstream.readBits(1);
+ this.flags.MHD_PACK_COMMENT = !!bstream.readBits(1);
+ this.flags.MHD_NEWNUMBERING = this.flags.MHD_PACK_COMMENT;
+ this.flags.MHD_AV = !!bstream.readBits(1);
+ this.flags.MHD_PROTECT = !!bstream.readBits(1);
+ this.flags.MHD_PASSWORD = !!bstream.readBits(1);
+ this.flags.MHD_FIRSTVOLUME = !!bstream.readBits(1);
+ this.flags.MHD_ENCRYPTVER = !!bstream.readBits(1);
+ bstream.readBits(6); // unused
+ break;
+ case FILE_HEAD:
+ this.flags.LHD_SPLIT_BEFORE = !!bstream.readBits(1); // 0x0001
+ this.flags.LHD_SPLIT_AFTER = !!bstream.readBits(1); // 0x0002
+ this.flags.LHD_PASSWORD = !!bstream.readBits(1); // 0x0004
+ this.flags.LHD_COMMENT = !!bstream.readBits(1); // 0x0008
+ this.flags.LHD_SOLID = !!bstream.readBits(1); // 0x0010
+ bstream.readBits(3); // unused
+ this.flags.LHD_LARGE = !!bstream.readBits(1); // 0x0100
+ this.flags.LHD_UNICODE = !!bstream.readBits(1); // 0x0200
+ this.flags.LHD_SALT = !!bstream.readBits(1); // 0x0400
+ this.flags.LHD_VERSION = !!bstream.readBits(1); // 0x0800
+ this.flags.LHD_EXTTIME = !!bstream.readBits(1); // 0x1000
+ this.flags.LHD_EXTFLAGS = !!bstream.readBits(1); // 0x2000
+ bstream.readBits(2); // unused
+ info(" LHD_SPLIT_BEFORE = " + this.flags.LHD_SPLIT_BEFORE);
+ break;
+ default:
+ bstream.readBits(16);
+ }
+
+ // byte 6,7
+ this.headSize = bstream.readBits(16);
+ info(" headSize=" + this.headSize);
+ switch (this.headType) {
+ case MAIN_HEAD:
+ this.highPosAv = bstream.readBits(16);
+ this.posAv = bstream.readBits(32);
+ if (this.flags.MHD_ENCRYPTVER) {
+ this.encryptVer = bstream.readBits(8);
+ }
+ info("Found MAIN_HEAD with highPosAv=" + this.highPosAv + ", posAv=" + this.posAv);
+ break;
+ case FILE_HEAD:
+ this.packSize = bstream.readBits(32);
+ this.unpackedSize = bstream.readBits(32);
+ this.hostOS = bstream.readBits(8);
+ this.fileCRC = bstream.readBits(32);
+ this.fileTime = bstream.readBits(32);
+ this.unpVer = bstream.readBits(8);
+ this.method = bstream.readBits(8);
+ this.nameSize = bstream.readBits(16);
+ this.fileAttr = bstream.readBits(32);
+
+ if (this.flags.LHD_LARGE) {
+ info("Warning: Reading in LHD_LARGE 64-bit size values");
+ this.HighPackSize = bstream.readBits(32);
+ this.HighUnpSize = bstream.readBits(32);
+ } else {
+ this.HighPackSize = 0;
+ this.HighUnpSize = 0;
+ if (this.unpackedSize === 0xffffffff) {
+ this.HighUnpSize = 0x7fffffff;
+ this.unpackedSize = 0xffffffff;
+ }
+ }
+ this.fullPackSize = 0;
+ this.fullUnpackSize = 0;
+ this.fullPackSize |= this.HighPackSize;
+ this.fullPackSize <<= 32;
+ this.fullPackSize |= this.packSize;
+
+ // read in filename
+
+ this.filename = bstream.readBytes(this.nameSize);
+ var _s = "";
+ for (var _i = 0; _i < this.filename.length ; _i++) {
+ _s += String.fromCharCode(this.filename[_i]);
+ }
+
+ this.filename = _s;
+
+ if (this.flags.LHD_SALT) {
+ info("Warning: Reading in 64-bit salt value");
+ this.salt = bstream.readBits(64); // 8 bytes
+ }
+
+ if (this.flags.LHD_EXTTIME) {
+ // 16-bit flags
+ var extTimeFlags = bstream.readBits(16);
+
+ // this is adapted straight out of arcread.cpp, Archive::ReadHeader()
+ for (var I = 0; I < 4; ++I) {
+ var rmode = extTimeFlags >> ((3 - I) * 4);
+ if ((rmode & 8) === 0) {
+ continue;
+ }
+ if (I !== 0) {
+ bstream.readBits(16);
+ }
+ var count = (rmode & 3);
+ for (var J = 0; J < count; ++J) {
+ bstream.readBits(8);
+ }
+ }
+ }
+
+ if (this.flags.LHD_COMMENT) {
+ info("Found a LHD_COMMENT");
+ }
+
+
+ while (headPos + this.headSize > bstream.bytePtr) {
+ bstream.readBits(1);
+ }
+
+ // If Info line is commented in firefox fails if server on same computer than browser with error "expected expression, got default"
+ //info("Found FILE_HEAD with packSize=" + this.packSize + ", unpackedSize= " + this.unpackedSize + ", hostOS=" + this.hostOS + ", unpVer=" + this.unpVer + ", method=" + this.method + ", filename=" + this.filename);
+
+ break;
+ default:
+ info("Found a header of type 0x" + byteValueToHexString(this.headType));
+ // skip the rest of the header bytes (for now)
+ bstream.readBytes(this.headSize - 7);
+ break;
+ }
+};
+
+//var BLOCK_LZ = 0;
+
+var rLDecode = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224],
+ rLBits = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5],
+ rDBitLengthCounts = [4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 14, 0, 12],
+ rSDDecode = [0, 4, 8, 16, 32, 64, 128, 192],
+ rSDBits = [2, 2, 3, 4, 5, 6, 6, 6];
+
+var rDDecode = [0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32,
+ 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072,
+ 4096, 6144, 8192, 12288, 16384, 24576, 32768, 49152, 65536, 98304,
+ 131072, 196608, 262144, 327680, 393216, 458752, 524288, 589824,
+ 655360, 720896, 786432, 851968, 917504, 983040
+];
+
+var rDBits = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5,
+ 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14,
+ 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16
+];
+
+var rLowDistRepCount = 16;
+
+var rNC = 299,
+ rDC = 60,
+ rLDC = 17,
+ rRC = 28,
+ rBC = 20,
+ rHuffTableSize = (rNC + rDC + rRC + rLDC);
+
+//var UnpBlockType = BLOCK_LZ;
+var UnpOldTable = new Array(rHuffTableSize);
+
+var BD = { //bitdecode
+ DecodeLen: new Array(16),
+ DecodePos: new Array(16),
+ DecodeNum: new Array(rBC)
+};
+var LD = { //litdecode
+ DecodeLen: new Array(16),
+ DecodePos: new Array(16),
+ DecodeNum: new Array(rNC)
+};
+var DD = { //distdecode
+ DecodeLen: new Array(16),
+ DecodePos: new Array(16),
+ DecodeNum: new Array(rDC)
+};
+var LDD = { //low dist decode
+ DecodeLen: new Array(16),
+ DecodePos: new Array(16),
+ DecodeNum: new Array(rLDC)
+};
+var RD = { //rep decode
+ DecodeLen: new Array(16),
+ DecodePos: new Array(16),
+ DecodeNum: new Array(rRC)
+};
+
+/**
+ * @type {Array}
+ */
+var rOldBuffers = [];
+
+/**
+ * The current buffer we are unpacking to.
+ * @type {bitjs.io.ByteBuffer}
+ */
+var rBuffer;
+
+/**
+ * The buffer of the final bytes after filtering (only used in Unpack29).
+ * @type {bitjs.io.ByteBuffer}
+ */
+var wBuffer;
+
+var lowDistRepCount = 0;
+var prevLowDist = 0;
+
+var rOldDist = [0, 0, 0, 0];
+var lastDist;
+var lastLength;
+
+/**
+ * In unpack.cpp, UnpPtr keeps track of what bytes have been unpacked
+ * into the Window buffer and WrPtr keeps track of what bytes have been
+ * actually written to disk after the unpacking and optional filtering
+ * has been done.
+ *
+ * In our case, rBuffer is the buffer for the unpacked bytes and wBuffer is
+ * the final output bytes.
+ */
+
+
+/**
+ * Read in Huffman tables for RAR
+ * @param {bitjs.io.BitStream} bstream
+ */
+function rarReadTables(bstream) {
+ var BitLength = new Array(rBC);
+ var Table = new Array(rHuffTableSize);
+ var i;
+ // before we start anything we need to get byte-aligned
+ bstream.readBits((8 - bstream.bitPtr) & 0x7);
+
+ if (bstream.readBits(1)) {
+ info("Error! PPM not implemented yet");
+ return;
+ }
+
+ if (!bstream.readBits(1)) { //discard old table
+ for (i = UnpOldTable.length; i--;) {
+ UnpOldTable[i] = 0;
+ }
+ }
+
+ // read in bit lengths
+ for (var I = 0; I < rBC; ++I) {
+ var Length = bstream.readBits(4);
+ if (Length === 15) {
+ var ZeroCount = bstream.readBits(4);
+ if (ZeroCount === 0) {
+ BitLength[I] = 15;
+ } else {
+ ZeroCount += 2;
+ while (ZeroCount-- > 0 && I < rBC) {
+ BitLength[I++] = 0;
+ }
+ --I;
+ }
+ } else {
+ BitLength[I] = Length;
+ }
+ }
+
+ // now all 20 bit lengths are obtained, we construct the Huffman Table:
+
+ rarMakeDecodeTables(BitLength, 0, BD, rBC);
+
+ var TableSize = rHuffTableSize;
+ //console.log(DecodeLen, DecodePos, DecodeNum);
+ for (i = 0; i < TableSize;) {
+ var N;
+ var num = rarDecodeNumber(bstream, BD);
+ if (num < 16) {
+ Table[i] = (num + UnpOldTable[i]) & 0xf;
+ i++;
+ } else if (num < 18) {
+ N = (num === 16) ? (bstream.readBits(3) + 3) : (bstream.readBits(7) + 11);
+
+ while (N-- > 0 && i < TableSize) {
+ Table[i] = Table[i - 1];
+ i++;
+ }
+ } else {
+ N = (num === 18) ? (bstream.readBits(3) + 3) : (bstream.readBits(7) + 11);
+
+ while (N-- > 0 && i < TableSize) {
+ Table[i++] = 0;
+ }
+ }
+ }
+
+ rarMakeDecodeTables(Table, 0, LD, rNC);
+ rarMakeDecodeTables(Table, rNC, DD, rDC);
+ rarMakeDecodeTables(Table, rNC + rDC, LDD, rLDC);
+ rarMakeDecodeTables(Table, rNC + rDC + rLDC, RD, rRC);
+
+ for (i = UnpOldTable.length; i--;) {
+ UnpOldTable[i] = Table[i];
+ }
+ return true;
+}
+
+
+function rarDecodeNumber(bstream, dec) {
+ var DecodeLen = dec.DecodeLen,
+ DecodePos = dec.DecodePos,
+ DecodeNum = dec.DecodeNum;
+ var bitField = bstream.getBits() & 0xfffe;
+ //some sort of rolled out binary search
+ var bits = ((bitField < DecodeLen[8]) ?
+ ((bitField < DecodeLen[4]) ?
+ ((bitField < DecodeLen[2]) ?
+ ((bitField < DecodeLen[1]) ? 1 : 2) :
+ ((bitField < DecodeLen[3]) ? 3 : 4)) :
+ (bitField < DecodeLen[6]) ?
+ ((bitField < DecodeLen[5]) ? 5 : 6) :
+ ((bitField < DecodeLen[7]) ? 7 : 8)) :
+ ((bitField < DecodeLen[12]) ?
+ ((bitField < DecodeLen[10]) ?
+ ((bitField < DecodeLen[9]) ? 9 : 10) :
+ ((bitField < DecodeLen[11]) ? 11 : 12)) :
+ (bitField < DecodeLen[14]) ?
+ ((bitField < DecodeLen[13]) ? 13 : 14) :
+ 15));
+ bstream.readBits(bits);
+ var N = DecodePos[bits] + ((bitField - DecodeLen[bits - 1]) >>> (16 - bits));
+
+ return DecodeNum[N];
+}
+
+
+function rarMakeDecodeTables(BitLength, offset, dec, size) {
+ var DecodeLen = dec.DecodeLen;
+ var DecodePos = dec.DecodePos;
+ var DecodeNum = dec.DecodeNum;
+ var LenCount = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+ var TmpPos = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+ var N = 0;
+ var M = 0;
+ var i;
+ for (i = DecodeNum.length; i--;) {
+ DecodeNum[i] = 0;
+ }
+ for (i = 0; i < size; i++) {
+ LenCount[BitLength[i + offset] & 0xF]++;
+ }
+ LenCount[0] = 0;
+ TmpPos[0] = 0;
+ DecodePos[0] = 0;
+ DecodeLen[0] = 0;
+
+ var I;
+ for (I = 1; I < 16; ++I) {
+ N = 2 * (N + LenCount[I]);
+ M = (N << (15 - I));
+ if (M > 0xFFFF) {
+ M = 0xFFFF;
+ }
+ DecodeLen[I] = M;
+ DecodePos[I] = DecodePos[I - 1] + LenCount[I - 1];
+ TmpPos[I] = DecodePos[I];
+ }
+ for (I = 0; I < size; ++I) {
+ if (BitLength[I + offset] !== 0) {
+ DecodeNum[TmpPos[BitLength[offset + I] & 0xF]++] = I;
+ }
+ }
+
+}
+
+// TODO: implement
+/**
+ * @param {bitjs.io.BitStream} bstream
+ * @param {boolean} Solid
+ */
+function unpack15() { //bstream, Solid) {
+ info("ERROR! RAR 1.5 compression not supported");
+}
+
+/**
+ * Unpacks the bit stream into rBuffer using the Unpack20 algorithm.
+ * @param {bitjs.io.BitStream} bstream
+ * @param {boolean} Solid
+ */
+function unpack20(bstream) { //, Solid) {
+ var destUnpSize = rBuffer.data.length;
+ var oldDistPtr = 0;
+ var Length;
+ var Distance;
+ rarReadTables20(bstream);
+ while (destUnpSize > rBuffer.ptr) {
+ var num = rarDecodeNumber(bstream, LD);
+ var Bits;
+ if (num < 256) {
+ rBuffer.insertByte(num);
+ continue;
+ }
+ if (num > 269) {
+ Length = rLDecode[num -= 270] + 3;
+ if ((Bits = rLBits[num]) > 0) {
+ Length += bstream.readBits(Bits);
+ }
+ var DistNumber = rarDecodeNumber(bstream, DD);
+ Distance = rDDecode[DistNumber] + 1;
+ if ((Bits = rDBits[DistNumber]) > 0) {
+ Distance += bstream.readBits(Bits);
+ }
+ if (Distance >= 0x2000) {
+ Length++;
+ if (Distance >= 0x40000) {
+ Length++;
+ }
+ }
+ lastLength = Length;
+ lastDist = rOldDist[oldDistPtr++ & 3] = Distance;
+ rarCopyString(Length, Distance);
+ continue;
+ }
+ if (num === 269) {
+ rarReadTables20(bstream);
+ rarUpdateProgress();
+ continue;
+ }
+ if (num === 256) {
+ lastDist = rOldDist[oldDistPtr++ & 3] = lastDist;
+ rarCopyString(lastLength, lastDist);
+ continue;
+ }
+ if (num < 261) {
+ Distance = rOldDist[(oldDistPtr - (num - 256)) & 3];
+ var LengthNumber = rarDecodeNumber(bstream, RD);
+ Length = rLDecode[LengthNumber] + 2;
+ if ((Bits = rLBits[LengthNumber]) > 0) {
+ Length += bstream.readBits(Bits);
+ }
+ if (Distance >= 0x101) {
+ Length++;
+ if (Distance >= 0x2000) {
+ Length++;
+ if (Distance >= 0x40000) {
+ Length++;
+ }
+ }
+ }
+ lastLength = Length;
+ lastDist = rOldDist[oldDistPtr++ & 3] = Distance;
+ rarCopyString(Length, Distance);
+ continue;
+ }
+ if (num < 270) {
+ Distance = rSDDecode[num -= 261] + 1;
+ if ((Bits = rSDBits[num]) > 0) {
+ Distance += bstream.readBits(Bits);
+ }
+ lastLength = 2;
+ lastDist = rOldDist[oldDistPtr++ & 3] = Distance;
+ rarCopyString(2, Distance);
+ continue;
+ }
+ }
+ rarUpdateProgress();
+}
+
+function rarUpdateProgress() {
+ var change = rBuffer.ptr - currentBytesUnarchivedInFile;
+ currentBytesUnarchivedInFile = rBuffer.ptr;
+ currentBytesUnarchived += change;
+ postProgress();
+}
+
+var rNC20 = 298,
+ rDC20 = 48,
+ rRC20 = 28,
+ rBC20 = 19,
+ rMC20 = 257;
+
+var UnpOldTable20 = new Array(rMC20 * 4);
+
+function rarReadTables20(bstream) {
+ var BitLength = new Array(rBC20);
+ var Table = new Array(rMC20 * 4);
+ var TableSize, N, I;
+ var i;
+ bstream.readBits(1);
+ if (!bstream.readBits(1)) {
+ for (i = UnpOldTable20.length; i--;) {
+ UnpOldTable20[i] = 0;
+ }
+ }
+ TableSize = rNC20 + rDC20 + rRC20;
+ for (I = 0; I < rBC20; I++) {
+ BitLength[I] = bstream.readBits(4);
+ }
+ rarMakeDecodeTables(BitLength, 0, BD, rBC20);
+ I = 0;
+ while (I < TableSize) {
+ var num = rarDecodeNumber(bstream, BD);
+ if (num < 16) {
+ Table[I] = num + UnpOldTable20[I] & 0xf;
+ I++;
+ } else if (num === 16) {
+ N = bstream.readBits(2) + 3;
+ while (N-- > 0 && I < TableSize) {
+ Table[I] = Table[I - 1];
+ I++;
+ }
+ } else {
+ if (num === 17) {
+ N = bstream.readBits(3) + 3;
+ } else {
+ N = bstream.readBits(7) + 11;
+ }
+ while (N-- > 0 && I < TableSize) {
+ Table[I++] = 0;
+ }
+ }
+ }
+ rarMakeDecodeTables(Table, 0, LD, rNC20);
+ rarMakeDecodeTables(Table, rNC20, DD, rDC20);
+ rarMakeDecodeTables(Table, rNC20 + rDC20, RD, rRC20);
+ for (i = UnpOldTable20.length; i--;) {
+ UnpOldTable20[i] = Table[i];
+ }
+}
+
+// ============================================================================================== //
+
+// Unpack code specific to RarVM
+var VM = new RarVM();
+
+/**
+ * Filters code, one entry per filter.
+ * @type {Array}
+ */
+var Filters = [];
+
+/**
+ * Filters stack, several entrances of same filter are possible.
+ * @type {Array}
+ */
+var PrgStack = [];
+
+/**
+ * Lengths of preceding blocks, one length per filter. Used to reduce
+ * size required to write block length if lengths are repeating.
+ * @type {Array}
+ */
+var OldFilterLengths = [];
+
+var LastFilter = 0;
+
+function initFilters() {
+ OldFilterLengths = [];
+ LastFilter = 0;
+ Filters = [];
+ PrgStack = [];
+}
+
+
+/**
+ * @param {number} firstByte The first byte (flags).
+ * @param {Uint8Array} vmCode An array of bytes.
+ */
+function rarAddVMCode(firstByte, vmCode) {
+ VM.init();
+ var i;
+ var bstream = new bitjs.io.BitStream(vmCode.buffer, true /* rtl */ );
+
+ var filtPos;
+ if (firstByte & 0x80) {
+ filtPos = RarVM.readData(bstream);
+ if (filtPos === 0) {
+ initFilters();
+ } else {
+ filtPos--;
+ }
+ } else {
+ filtPos = LastFilter;
+ }
+
+ if (filtPos > Filters.length || filtPos > OldFilterLengths.length) {
+ return false;
+ }
+
+ LastFilter = filtPos;
+ var newFilter = (filtPos === Filters.length);
+
+ // new filter for PrgStack
+ var stackFilter = new UnpackFilter();
+ var filter = null;
+ // new filter code, never used before since VM reset
+ if (newFilter) {
+ // too many different filters, corrupt archive
+ if (filtPos > 1024) {
+ return false;
+ }
+
+ filter = new UnpackFilter();
+ Filters.push(filter);
+ stackFilter.ParentFilter = (Filters.length - 1);
+ OldFilterLengths.push(0); // OldFilterLengths.Add(1)
+ filter.ExecCount = 0;
+ } else { // filter was used in the past
+ filter = Filters[filtPos];
+ stackFilter.ParentFilter = filtPos;
+ filter.ExecCount++;
+ }
+
+ var emptyCount = 0;
+ for (i = 0; i < PrgStack.length; ++i) {
+ PrgStack[i - emptyCount] = PrgStack[i];
+
+ if (PrgStack[i] === null) {
+ emptyCount++;
+ }
+ if (emptyCount > 0) {
+ PrgStack[i] = null;
+ }
+ }
+
+ if (emptyCount === 0) {
+ PrgStack.push(null); //PrgStack.Add(1);
+ emptyCount = 1;
+ }
+
+ var stackPos = PrgStack.length - emptyCount;
+ PrgStack[stackPos] = stackFilter;
+ stackFilter.ExecCount = filter.ExecCount;
+
+ var blockStart = RarVM.readData(bstream);
+ if (firstByte & 0x40) {
+ blockStart += 258;
+ }
+ stackFilter.BlockStart = (blockStart + rBuffer.ptr) & MAXWINMASK;
+
+ if (firstByte & 0x20) {
+ stackFilter.BlockLength = RarVM.readData(bstream);
+ } else {
+ stackFilter.BlockLength = filtPos < OldFilterLengths.length ?
+ OldFilterLengths[filtPos] :
+ 0;
+ }
+ stackFilter.NextWindow = (wBuffer.ptr !== rBuffer.ptr) &&
+ (((wBuffer.ptr - rBuffer.ptr) & MAXWINMASK) <= blockStart);
+
+ OldFilterLengths[filtPos] = stackFilter.BlockLength;
+
+ for (i = 0; i < 7; ++i) {
+ stackFilter.Prg.InitR[i] = 0;
+ }
+ stackFilter.Prg.InitR[3] = VM_GLOBALMEMADDR;
+ stackFilter.Prg.InitR[4] = stackFilter.BlockLength;
+ stackFilter.Prg.InitR[5] = stackFilter.ExecCount;
+
+ // set registers to optional parameters if any
+ if (firstByte & 0x10) {
+ var initMask = bstream.readBits(7);
+ for (i = 0; i < 7; ++i) {
+ if (initMask & (1 << i)) {
+ stackFilter.Prg.InitR[i] = RarVM.readData(bstream);
+ }
+ }
+ }
+
+ if (newFilter) {
+ var vmCodeSize = RarVM.readData(bstream);
+ if (vmCodeSize >= 0x10000 || vmCodeSize === 0) {
+ return false;
+ }
+ vmCode = new Uint8Array(vmCodeSize);
+ for (i = 0; i < vmCodeSize; ++i) {
+ //if (Inp.Overflow(3))
+ // return(false);
+ vmCode[i] = bstream.readBits(8);
+ }
+ VM.prepare(vmCode, filter.Prg);
+ }
+ stackFilter.Prg.Cmd = filter.Prg.Cmd;
+ stackFilter.Prg.AltCmd = filter.Prg.Cmd;
+
+ var staticDataSize = filter.Prg.StaticData.length;
+ if (staticDataSize > 0 && staticDataSize < VM_GLOBALMEMSIZE) {
+ // read statically defined data contained in DB commands
+ for (i = 0; i < staticDataSize; ++i) {
+ stackFilter.Prg.StaticData[i] = filter.Prg.StaticData[i];
+ }
+ }
+
+ if (stackFilter.Prg.GlobalData.length < VM_FIXEDGLOBALSIZE) {
+ stackFilter.Prg.GlobalData = new Uint8Array(VM_FIXEDGLOBALSIZE);
+ }
+
+ var globalData = stackFilter.Prg.GlobalData;
+ for (i = 0; i < 7; ++i) {
+ VM.setLowEndianValue(globalData, stackFilter.Prg.InitR[i], i * 4);
+ }
+
+ VM.setLowEndianValue(globalData, stackFilter.BlockLength, 0x1c);
+ VM.setLowEndianValue(globalData, 0, 0x20);
+ VM.setLowEndianValue(globalData, stackFilter.ExecCount, 0x2c);
+ for (i = 0; i < 16; ++i) {
+ globalData[0x30 + i] = 0;
+ }
+
+ // put data block passed as parameter if any
+ if (firstByte & 8) {
+ //if (Inp.Overflow(3))
+ // return(false);
+ var dataSize = RarVM.readData(bstream);
+ if (dataSize > (VM_GLOBALMEMSIZE - VM_FIXEDGLOBALSIZE)) {
+ return (false);
+ }
+
+ var curSize = stackFilter.Prg.GlobalData.length;
+ if (curSize < dataSize + VM_FIXEDGLOBALSIZE) {
+ // Resize global data and update the stackFilter and local variable.
+ var numBytesToAdd = dataSize + VM_FIXEDGLOBALSIZE - curSize;
+ var newGlobalData = new Uint8Array(globalData.length + numBytesToAdd);
+ newGlobalData.set(globalData);
+
+ stackFilter.Prg.GlobalData = newGlobalData;
+ globalData = newGlobalData;
+ }
+ //byte *GlobalData=&StackFilter->Prg.GlobalData[VM_FIXEDGLOBALSIZE];
+ for (i = 0; i < dataSize; ++i) {
+ //if (Inp.Overflow(3))
+ // return(false);
+ globalData[VM_FIXEDGLOBALSIZE + i] = bstream.readBits(8);
+ }
+ }
+
+ return true;
+}
+
+
+/**
+ * @param {!bitjs.io.BitStream} bstream
+ */
+function rarReadVMCode(bstream) {
+ var firstByte = bstream.readBits(8);
+ var length = (firstByte & 7) + 1;
+ if (length === 7) {
+ length = bstream.readBits(8) + 7;
+ } else if (length === 8) {
+ length = bstream.readBits(16);
+ }
+
+ // Read all bytes of VM code into an array.
+ var vmCode = new Uint8Array(length);
+ for (var i = 0; i < length; i++) {
+ // Do something here with checking readbuf.
+ vmCode[i] = bstream.readBits(8);
+ }
+ return rarAddVMCode(firstByte, vmCode);
+}
+
+/**
+ * Unpacks the bit stream into rBuffer using the Unpack29 algorithm.
+ * @param {bitjs.io.BitStream} bstream
+ * @param {boolean} Solid
+ */
+function unpack29(bstream) {
+ // lazy initialize rDDecode and rDBits
+
+ var DDecode = new Array(rDC);
+ var DBits = new Array(rDC);
+ var Distance = 0;
+ var Length = 0;
+ var Dist = 0, BitLength = 0, Slot = 0;
+ var I;
+ for (I = 0; I < rDBitLengthCounts.length; I++, BitLength++) {
+ for (var J = 0; J < rDBitLengthCounts[I]; J++, Slot++, Dist += (1 << BitLength)) {
+ DDecode[Slot] = Dist;
+ DBits[Slot] = BitLength;
+ }
+ }
+
+ var Bits;
+ //tablesRead = false;
+
+ rOldDist = [0, 0, 0, 0];
+
+ lastDist = 0;
+ lastLength = 0;
+ var i;
+ for (i = UnpOldTable.length; i--;) {
+ UnpOldTable[i] = 0;
+ }
+
+ // read in Huffman tables
+ rarReadTables(bstream);
+
+ while (true) {
+ var num = rarDecodeNumber(bstream, LD);
+
+ if (num < 256) {
+ rBuffer.insertByte(num);
+ continue;
+ }
+ if (num >= 271) {
+ Length = rLDecode[num -= 271] + 3;
+ if ((Bits = rLBits[num]) > 0) {
+ Length += bstream.readBits(Bits);
+ }
+ var DistNumber = rarDecodeNumber(bstream, DD);
+ Distance = DDecode[DistNumber] + 1;
+ if ((Bits = DBits[DistNumber]) > 0) {
+ if (DistNumber > 9) {
+ if (Bits > 4) {
+ Distance += ((bstream.getBits() >>> (20 - Bits)) << 4);
+ bstream.readBits(Bits - 4);
+ //todo: check this
+ }
+ if (lowDistRepCount > 0) {
+ lowDistRepCount--;
+ Distance += prevLowDist;
+ } else {
+ var LowDist = rarDecodeNumber(bstream, LDD);
+ if (LowDist === 16) {
+ lowDistRepCount = rLowDistRepCount - 1;
+ Distance += prevLowDist;
+ } else {
+ Distance += LowDist;
+ prevLowDist = LowDist;
+ }
+ }
+ } else {
+ Distance += bstream.readBits(Bits);
+ }
+ }
+ if (Distance >= 0x2000) {
+ Length++;
+ if (Distance >= 0x40000) {
+ Length++;
+ }
+ }
+ rarInsertOldDist(Distance);
+ rarInsertLastMatch(Length, Distance);
+ rarCopyString(Length, Distance);
+ continue;
+ }
+ if (num === 256) {
+ if (!rarReadEndOfBlock(bstream)) {
+ break;
+ }
+ continue;
+ }
+ if (num === 257) {
+ if (!rarReadVMCode(bstream)) {
+ break;
+ }
+ continue;
+ }
+ if (num === 258) {
+ if (lastLength !== 0) {
+ rarCopyString(lastLength, lastDist);
+ }
+ continue;
+ }
+ if (num < 263) {
+ var DistNum = num - 259;
+ Distance = rOldDist[DistNum];
+
+ for (var I2 = DistNum; I2 > 0; I2--) {
+ rOldDist[I2] = rOldDist[I2 - 1];
+ }
+ rOldDist[0] = Distance;
+
+ var LengthNumber = rarDecodeNumber(bstream, RD);
+ Length = rLDecode[LengthNumber] + 2;
+ if ((Bits = rLBits[LengthNumber]) > 0) {
+ Length += bstream.readBits(Bits);
+ }
+ rarInsertLastMatch(Length, Distance);
+ rarCopyString(Length, Distance);
+ continue;
+ }
+ if (num < 272) {
+ Distance = rSDDecode[num -= 263] + 1;
+ if ((Bits = rSDBits[num]) > 0) {
+ Distance += bstream.readBits(Bits);
+ }
+ rarInsertOldDist(Distance);
+ rarInsertLastMatch(2, Distance);
+ rarCopyString(2, Distance);
+ continue;
+ }
+ } // while (true)
+ rarUpdateProgress();
+ rarWriteBuf();
+}
+
+/**
+ * Does stuff to the current byte buffer (rBuffer) based on
+ * the filters loaded into the RarVM and writes out to wBuffer.
+ */
+function rarWriteBuf() {
+ var writeSize = (rBuffer.ptr & MAXWINMASK);
+ var j;
+ var flt;
+ for (var i = 0; i < PrgStack.length; ++i) {
+ flt = PrgStack[i];
+ if (flt === null) {
+ continue;
+ }
+
+ if (flt.NextWindow) {
+ flt.NextWindow = false;
+ continue;
+ }
+
+ var blockStart = flt.BlockStart;
+ var blockLength = flt.BlockLength;
+ var parentPrg;
+
+ // WrittenBorder = wBuffer.ptr
+ if (((blockStart - wBuffer.ptr) & MAXWINMASK) < writeSize) {
+ if (wBuffer.ptr !== blockStart) {
+ // Copy blockStart bytes from rBuffer into wBuffer.
+ rarWriteArea(wBuffer.ptr, blockStart);
+ writeSize = (rBuffer.ptr - wBuffer.ptr) & MAXWINMASK;
+ }
+ if (blockLength <= writeSize) {
+ var blockEnd = (blockStart + blockLength) & MAXWINMASK;
+ if (blockStart < blockEnd || blockEnd === 0) {
+ VM.setMemory(0, rBuffer.data.subarray(blockStart, blockStart + blockLength), blockLength);
+ } else {
+ var firstPartLength = MAXWINSIZE - blockStart;
+ VM.setMemory(0, rBuffer.data.subarray(blockStart, blockStart + firstPartLength), firstPartLength);
+ VM.setMemory(firstPartLength, rBuffer.data, blockEnd);
+ }
+
+ parentPrg = Filters[flt.ParentFilter].Prg;
+ var prg = flt.Prg;
+
+ if (parentPrg.GlobalData.length > VM_FIXEDGLOBALSIZE) {
+ // Copy global data from previous script execution if any.
+ prg.GlobalData = new Uint8Array(parentPrg.GlobalData);
+ }
+
+ rarExecuteCode(prg);
+ var globalDataLen;
+
+ if (prg.GlobalData.length > VM_FIXEDGLOBALSIZE) {
+ // Save global data for next script execution.
+ globalDataLen = prg.GlobalData.length;
+ if (parentPrg.GlobalData.length < globalDataLen) {
+ parentPrg.GlobalData = new Uint8Array(globalDataLen);
+ }
+ parentPrg.GlobalData.set(
+ this.mem_.subarray(VM_FIXEDGLOBALSIZE, VM_FIXEDGLOBALSIZE + globalDataLen),
+ VM_FIXEDGLOBALSIZE);
+ } else {
+ parentPrg.GlobalData = new Uint8Array(0);
+ }
+
+ var filteredData = prg.FilteredData;
+
+ PrgStack[i] = null;
+ while (i + 1 < PrgStack.length) {
+ var nextFilter = PrgStack[i + 1];
+ if (nextFilter === null || nextFilter.BlockStart !== blockStart ||
+ nextFilter.BlockLength !== filteredData.length || nextFilter.NextWindow) {
+ break;
+ }
+
+ // Apply several filters to same data block.
+
+ VM.setMemory(0, filteredData, filteredData.length);
+
+ parentPrg = Filters[nextFilter.ParentFilter].Prg;
+ var nextPrg = nextFilter.Prg;
+
+ globalDataLen = parentPrg.GlobalData.length;
+ if (globalDataLen > VM_FIXEDGLOBALSIZE) {
+ // Copy global data from previous script execution if any.
+ nextPrg.GlobalData = new Uint8Array(globalDataLen);
+ nextPrg.GlobalData.set(parentPrg.GlobalData.subarray(VM_FIXEDGLOBALSIZE, VM_FIXEDGLOBALSIZE + globalDataLen), VM_FIXEDGLOBALSIZE);
+ }
+
+ rarExecuteCode(nextPrg);
+
+ if (nextPrg.GlobalData.length > VM_GLOBALMEMSIZE) {
+ // Save global data for next script execution.
+ globalDataLen = nextPrg.GlobalData.length;
+ if (parentPrg.GlobalData.length < globalDataLen) {
+ parentPrg.GlobalData = new Uint8Array(globalDataLen);
+ }
+ parentPrg.GlobalData.set(
+ this.mem_.subarray(VM_FIXEDGLOBALSIZE, VM_FIXEDGLOBALSIZE + globalDataLen),
+ VM_FIXEDGLOBALSIZE);
+ } else {
+ parentPrg.GlobalData = new Uint8Array(0);
+ }
+
+ filteredData = nextPrg.FilteredData;
+ i++;
+ PrgStack[i] = null;
+ } // while (i + 1 < PrgStack.length)
+
+ for (j = 0; j < filteredData.length; ++j) {
+ wBuffer.insertByte(filteredData[j]);
+ }
+ writeSize = (rBuffer.ptr - wBuffer.ptr) & MAXWINMASK;
+ } else { // if (blockLength <= writeSize)
+ for (j = i; j < PrgStack.length; ++j) {
+ flt = PrgStack[j];
+ if (flt !== null && flt.NextWindow) {
+ flt.NextWindow = false;
+ }
+ }
+ //WrPtr=WrittenBorder;
+ return;
+ }
+ } // if (((blockStart - wBuffer.ptr) & MAXWINMASK) < writeSize)
+ } // for (var i = 0; i < PrgStack.length; ++i)
+
+ // Write any remaining bytes from rBuffer to wBuffer;
+ rarWriteArea(wBuffer.ptr, rBuffer.ptr);
+
+ // Now that the filtered buffer has been written, swap it back to rBuffer.
+ rBuffer = wBuffer;
+}
+
+/**
+ * Copy bytes from rBuffer to wBuffer.
+ * @param {number} startPtr The starting point to copy from rBuffer.
+ * @param {number} endPtr The ending point to copy from rBuffer.
+ */
+function rarWriteArea(startPtr, endPtr) {
+ if (endPtr < startPtr) {
+ console.error("endPtr < startPtr, endPtr=" + endPtr + ", startPtr=" + startPtr);
+ // rarWriteData(startPtr, -(int)StartPtr & MAXWINMASK);
+ // RarWriteData(0, endPtr);
+ return;
+ } else if (startPtr < endPtr) {
+ rarWriteData(startPtr, endPtr - startPtr);
+ }
+}
+
+/**
+ * Writes bytes into wBuffer from rBuffer.
+ * @param {number} offset The starting point to copy bytes from rBuffer.
+ * @param {number} numBytes The number of bytes to copy.
+ */
+function rarWriteData(offset, numBytes) {
+ if (wBuffer.ptr >= rBuffer.data.length) {
+ return;
+ }
+ var leftToWrite = rBuffer.data.length - wBuffer.ptr;
+ if (numBytes > leftToWrite) {
+ numBytes = leftToWrite;
+ }
+ for (var i = 0; i < numBytes; ++i) {
+ wBuffer.insertByte(rBuffer.data[offset + i]);
+ }
+}
+
+/**
+ * @param {VM_PreparedProgram} prg
+ */
+function rarExecuteCode(prg) {
+ if (prg.GlobalData.length > 0) {
+ var writtenFileSize = wBuffer.ptr;
+ prg.InitR[6] = writtenFileSize;
+ VM.setLowEndianValue(prg.GlobalData, writtenFileSize, 0x24);
+ VM.setLowEndianValue(prg.GlobalData, (writtenFileSize >>> 32) >> 0, 0x28);
+ VM.execute(prg);
+ }
+}
+
+function rarReadEndOfBlock(bstream) {
+ rarUpdateProgress();
+
+ var NewTable = false,
+ NewFile = false;
+ if (bstream.readBits(1)) {
+ NewTable = true;
+ } else {
+ NewFile = true;
+ NewTable = !!bstream.readBits(1);
+ }
+ //tablesRead = !NewTable;
+ return !(NewFile || (NewTable && !rarReadTables(bstream)));
+}
+
+function rarInsertLastMatch(length, distance) {
+ lastDist = distance;
+ lastLength = length;
+}
+
+function rarInsertOldDist(distance) {
+ rOldDist.splice(3, 1);
+ rOldDist.splice(0, 0, distance);
+}
+
+/**
+ * Copies len bytes from distance bytes ago in the buffer to the end of the
+ * current byte buffer.
+ * @param {number} length How many bytes to copy.
+ * @param {number} distance How far back in the buffer from the current write
+ * pointer to start copying from.
+ */
+function rarCopyString(length, distance) {
+ var srcPtr = rBuffer.ptr - distance;
+ if (srcPtr < 0) {
+ var l = rOldBuffers.length;
+ while (srcPtr < 0) {
+ srcPtr = rOldBuffers[--l].data.length + srcPtr;
+ }
+ // TODO: lets hope that it never needs to read beyond file boundaries
+ while (length--) {
+ rBuffer.insertByte(rOldBuffers[l].data[srcPtr++]);
+ }
+ }
+ if (length > distance) {
+ while (length--) {
+ rBuffer.insertByte(rBuffer.data[srcPtr++]);
+ }
+ } else {
+ rBuffer.insertBytes(rBuffer.data.subarray(srcPtr, srcPtr + length));
+ }
+}
+
+/**
+ * @param {RarLocalFile} v
+ */
+function unpack(v) {
+ // TODO: implement what happens when unpVer is < 15
+ var Ver = v.header.unpVer <= 15 ? 15 : v.header.unpVer;
+ // var Solid = v.header.LHD_SOLID;
+ var bstream = new bitjs.io.BitStream(v.fileData.buffer, true /* rtl */, v.fileData.byteOffset, v.fileData.byteLength);
+
+ rBuffer = new bitjs.io.ByteBuffer(v.header.unpackedSize);
+
+ info("Unpacking " + v.filename + " RAR v" + Ver);
+
+ switch (Ver) {
+ case 15: // rar 1.5 compression
+ unpack15(); //(bstream, Solid);
+ break;
+ case 20: // rar 2.x compression
+ case 26: // files larger than 2GB
+ unpack20(bstream); //, Solid);
+ break;
+ case 29: // rar 3.x compression
+ case 36: // alternative hash
+ wBuffer = new bitjs.io.ByteBuffer(rBuffer.data.length);
+ unpack29(bstream);
+ break;
+ } // switch(method)
+
+ rOldBuffers.push(rBuffer);
+ // TODO: clear these old buffers when there's over 4MB of history
+ return rBuffer.data;
+}
+
+// bstream is a bit stream
+var RarLocalFile = function(bstream) {
+ this.header = new RarVolumeHeader(bstream);
+ this.filename = this.header.filename;
+
+ if (this.header.headType !== FILE_HEAD && this.header.headType !== ENDARC_HEAD) {
+ this.isValid = false;
+ info("Error! RAR Volume did not include a FILE_HEAD header ");
+ } else {
+ // read in the compressed data
+ this.fileData = null;
+ if (this.header.packSize > 0) {
+ this.fileData = bstream.readBytes(this.header.packSize);
+ this.isValid = true;
+ }
+ }
+};
+
+RarLocalFile.prototype.unrar = function() {
+ if (!this.header.flags.LHD_SPLIT_BEFORE) {
+ // unstore file
+ if (this.header.method === 0x30) {
+ info("Unstore " + this.filename);
+ this.isValid = true;
+
+ currentBytesUnarchivedInFile += this.fileData.length;
+ currentBytesUnarchived += this.fileData.length;
+
+ // Create a new buffer and copy it over.
+ var len = this.header.packSize;
+ var newBuffer = new bitjs.io.ByteBuffer(len);
+ newBuffer.insertBytes(this.fileData);
+ this.fileData = newBuffer.data;
+ } else {
+ this.isValid = true;
+ this.fileData = unpack(this);
+ }
+ }
+};
+
+var unrar = function(arrayBuffer) {
+ currentFilename = "";
+ currentFileNumber = 0;
+ currentBytesUnarchivedInFile = 0;
+ currentBytesUnarchived = 0;
+ totalUncompressedBytesInArchive = 0;
+ totalFilesInArchive = 0;
+
+ postMessage(new bitjs.archive.UnarchiveStartEvent());
+ var bstream = new bitjs.io.BitStream(arrayBuffer, false /* rtl */);
+
+ var header = new RarVolumeHeader(bstream);
+ if (header.crc === 0x6152 &&
+ header.headType === 0x72 &&
+ header.flags.value === 0x1A21 &&
+ header.headSize === 7) {
+ info("Found RAR signature");
+
+ var mhead = new RarVolumeHeader(bstream);
+ if (mhead.headType !== MAIN_HEAD) {
+ info("Error! RAR did not include a MAIN_HEAD header");
+ } else {
+ var localFiles = [];
+ var localFile = null;
+ do {
+ try {
+ localFile = new RarLocalFile(bstream);
+ info("RAR localFile isValid=" + localFile.isValid + ", volume packSize=" + localFile.header.packSize);
+ if (localFile && localFile.isValid && localFile.header.packSize > 0) {
+ totalUncompressedBytesInArchive += localFile.header.unpackedSize;
+ localFiles.push(localFile);
+ } else if (localFile.header.packSize === 0 && localFile.header.unpackedSize === 0) {
+ localFile.isValid = true;
+ }
+ } catch (err) {
+ break;
+ }
+ //info("bstream" + bstream.bytePtr+"/"+bstream.bytes.length);
+ } while (localFile.isValid);
+ totalFilesInArchive = localFiles.length;
+
+ // now we have all information but things are unpacked
+ localFiles.sort(alphanumCase);
+
+ info(localFiles.map(function(a) {
+ return a.filename;
+ }).join(", "));
+ for (var i = 0; i < localFiles.length; ++i) {
+ var localfile = localFiles[i];
+
+ // update progress
+ currentFilename = localfile.header.filename;
+ currentBytesUnarchivedInFile = 0;
+
+ // actually do the unzipping
+ localfile.unrar();
+
+ if (localfile.isValid) {
+ postMessage(new bitjs.archive.UnarchiveExtractEvent(localfile));
+ postProgress();
+ }
+ }
+
+ postProgress();
+ }
+ } else {
+ err("Invalid RAR file");
+ }
+ postMessage(new bitjs.archive.UnarchiveFinishEvent());
+};
+
+// event.data.file has the ArrayBuffer.
+onmessage = function(event) {
+ var ab = event.data.file;
+ unrar(ab, true);
+};
diff --git a/cps/static/js/archive/untar.js b/cps/static/js/archive/untar.js
new file mode 100644
index 00000000..cc1499ef
--- /dev/null
+++ b/cps/static/js/archive/untar.js
@@ -0,0 +1,179 @@
+/**
+ * untar.js
+ *
+ * Licensed under the MIT License
+ *
+ * Copyright(c) 2011 Google Inc.
+ *
+ * Reference Documentation:
+ *
+ * TAR format: http://www.gnu.org/software/automake/manual/tar/Standard.html
+ */
+
+/* global bitjs, importScripts, Uint8Array */
+
+// This file expects to be invoked as a Worker (see onmessage below).
+importScripts("../io/bytestream.js");
+importScripts("archive.js");
+
+// Progress variables.
+var currentFilename = "";
+var currentFileNumber = 0;
+var currentBytesUnarchivedInFile = 0;
+var currentBytesUnarchived = 0;
+var totalUncompressedBytesInArchive = 0;
+var totalFilesInArchive = 0;
+var allLocalFiles = [];
+
+// Helper functions.
+var info = function(str) {
+ postMessage(new bitjs.archive.UnarchiveInfoEvent(str));
+};
+var err = function(str) {
+ postMessage(new bitjs.archive.UnarchiveErrorEvent(str));
+};
+
+// Removes all characters from the first zero-byte in the string onwards.
+var readCleanString = function(bstr, numBytes) {
+ var str = bstr.readString(numBytes);
+ var zIndex = str.indexOf(String.fromCharCode(0));
+ return zIndex != -1 ? str.substr(0, zIndex) : str;
+};
+
+var postProgress = function() {
+ postMessage(new bitjs.archive.UnarchiveProgressEvent(
+ currentFilename,
+ currentFileNumber,
+ currentBytesUnarchivedInFile,
+ currentBytesUnarchived,
+ totalUncompressedBytesInArchive,
+ totalFilesInArchive
+ ));
+};
+
+// takes a ByteStream and parses out the local file information
+var TarLocalFile = function(bstream) {
+ this.isValid = false;
+
+ var bytesRead = 0;
+
+ // Read in the header block
+ this.name = readCleanString(bstream, 100);
+ this.mode = readCleanString(bstream, 8);
+ this.uid = readCleanString(bstream, 8);
+ this.gid = readCleanString(bstream, 8);
+ this.size = parseInt(readCleanString(bstream, 12), 8);
+ this.mtime = readCleanString(bstream, 12);
+ this.chksum = readCleanString(bstream, 8);
+ this.typeflag = readCleanString(bstream, 1);
+ this.linkname = readCleanString(bstream, 100);
+ this.maybeMagic = readCleanString(bstream, 6);
+
+ if (this.maybeMagic === "ustar") {
+ this.version = readCleanString(bstream, 2);
+ this.uname = readCleanString(bstream, 32);
+ this.gname = readCleanString(bstream, 32);
+ this.devmajor = readCleanString(bstream, 8);
+ this.devminor = readCleanString(bstream, 8);
+ this.prefix = readCleanString(bstream, 155);
+
+ if (this.prefix.length) {
+ this.name = this.prefix + this.name;
+ }
+ bstream.readBytes(12); // 512 - 500
+ } else {
+ bstream.readBytes(255); // 512 - 257
+ }
+
+ bytesRead += 512;
+
+ // Done header, now rest of blocks are the file contents.
+ this.filename = this.name;
+ this.fileData = null;
+
+ info("Untarring file '" + this.filename + "'");
+ info(" size = " + this.size);
+ info(" typeflag = " + this.typeflag);
+
+ // A regular file.
+ if (this.typeflag == 0) {
+ info(" This is a regular file.");
+ var sizeInBytes = parseInt(this.size);
+ this.fileData = new Uint8Array(bstream.readBytes(sizeInBytes));
+ bytesRead += sizeInBytes;
+ if (this.name.length > 0 && this.size > 0 && this.fileData && this.fileData.buffer) {
+ this.isValid = true;
+ }
+
+ // Round up to 512-byte blocks.
+ var remaining = 512 - (bytesRead % 512);
+ if (remaining > 0 && remaining < 512) {
+ bstream.readBytes(remaining);
+ }
+ } else if (this.typeflag == 5) {
+ info(" This is a directory.");
+ }
+};
+
+
+var untar = function(arrayBuffer) {
+ postMessage(new bitjs.archive.UnarchiveStartEvent());
+ currentFilename = "";
+ currentFileNumber = 0;
+ currentBytesUnarchivedInFile = 0;
+ currentBytesUnarchived = 0;
+ totalUncompressedBytesInArchive = 0;
+ totalFilesInArchive = 0;
+ allLocalFiles = [];
+
+ var bstream = new bitjs.io.ByteStream(arrayBuffer);
+ postProgress();
+ /*
+ // go through whole file, read header of each block and memorize, filepointer
+ */
+ while (bstream.peekNumber(4) !== 0) {
+ var localFile = new TarLocalFile(bstream);
+ allLocalFiles.push(localFile);
+ postProgress();
+ }
+ // got all local files, now sort them
+ allLocalFiles.sort(alphanumCase);
+
+ allLocalFiles.forEach(function(oneLocalFile) {
+ // While we don't encounter an empty block, keep making TarLocalFiles.
+ if (oneLocalFile && oneLocalFile.isValid) {
+ // If we make it to this point and haven't thrown an error, we have successfully
+ // read in the data for a local file, so we can update the actual bytestream.
+ totalUncompressedBytesInArchive += oneLocalFile.size;
+
+ // update progress
+ currentFilename = oneLocalFile.filename;
+ currentFileNumber = totalFilesInArchive++;
+ currentBytesUnarchivedInFile = oneLocalFile.size;
+ currentBytesUnarchived += oneLocalFile.size;
+ postMessage(new bitjs.archive.UnarchiveExtractEvent(oneLocalFile));
+ postProgress();
+ }
+ });
+ totalFilesInArchive = allLocalFiles.length;
+
+ postProgress();
+ postMessage(new bitjs.archive.UnarchiveFinishEvent());
+};
+
+// event.data.file has the first ArrayBuffer.
+// event.data.bytes has all subsequent ArrayBuffers.
+onmessage = function(event) {
+ try {
+ untar(event.data.file, true);
+ } catch (e) {
+ if (typeof e === "string" && e.startsWith("Error! Overflowed")) {
+ // Overrun the buffer.
+ // unarchiveState = UnarchiveState.WAITING;
+ } else {
+ err("Found an error while untarring");
+ err(e);
+ throw e;
+ }
+ }
+};
diff --git a/cps/static/js/archive/unzip.js b/cps/static/js/archive/unzip.js
new file mode 100644
index 00000000..886f4b80
--- /dev/null
+++ b/cps/static/js/archive/unzip.js
@@ -0,0 +1,660 @@
+/**
+ * unzip.js
+ *
+ * Licensed under the MIT License
+ *
+ * Copyright(c) 2011 Google Inc.
+ * Copyright(c) 2011 antimatter15
+ *
+ * Reference Documentation:
+ *
+ * ZIP format: http://www.pkware.com/documents/casestudies/APPNOTE.TXT
+ * DEFLATE format: http://tools.ietf.org/html/rfc1951
+ */
+/* global bitjs, importScripts, Uint8Array*/
+
+// This file expects to be invoked as a Worker (see onmessage below).
+importScripts("../io/bitstream.js");
+importScripts("../io/bytebuffer.js");
+importScripts("../io/bytestream.js");
+importScripts("archive.js");
+
+// Progress variables.
+var currentFilename = "";
+var currentFileNumber = 0;
+var currentBytesUnarchivedInFile = 0;
+var currentBytesUnarchived = 0;
+var totalUncompressedBytesInArchive = 0;
+var totalFilesInArchive = 0;
+
+// Helper functions.
+var info = function(str) {
+ postMessage(new bitjs.archive.UnarchiveInfoEvent(str));
+};
+var err = function(str) {
+ postMessage(new bitjs.archive.UnarchiveErrorEvent(str));
+};
+var postProgress = function() {
+ postMessage(new bitjs.archive.UnarchiveProgressEvent(
+ currentFilename,
+ currentFileNumber,
+ currentBytesUnarchivedInFile,
+ currentBytesUnarchived,
+ totalUncompressedBytesInArchive,
+ totalFilesInArchive));
+};
+
+var zLocalFileHeaderSignature = 0x04034b50;
+var zArchiveExtraDataSignature = 0x08064b50;
+var zCentralFileHeaderSignature = 0x02014b50;
+var zDigitalSignatureSignature = 0x05054b50;
+
+// takes a ByteStream and parses out the local file information
+var ZipLocalFile = function(bstream) {
+ if (typeof bstream !== typeof {} || !bstream.readNumber || typeof bstream.readNumber !== typeof function() {}) {
+ return null;
+ }
+
+ bstream.readNumber(4); // swallow signature
+ this.version = bstream.readNumber(2);
+ this.generalPurpose = bstream.readNumber(2);
+ this.compressionMethod = bstream.readNumber(2);
+ this.lastModFileTime = bstream.readNumber(2);
+ this.lastModFileDate = bstream.readNumber(2);
+ this.crc32 = bstream.readNumber(4);
+ this.compressedSize = bstream.readNumber(4);
+ this.uncompressedSize = bstream.readNumber(4);
+ this.fileNameLength = bstream.readNumber(2);
+ this.extraFieldLength = bstream.readNumber(2);
+
+ this.filename = null;
+ if (this.fileNameLength > 0) {
+ this.filename = bstream.readString(this.fileNameLength);
+ }
+
+ this.extraField = null;
+ if (this.extraFieldLength > 0) {
+ this.extraField = bstream.readString(this.extraFieldLength);
+ info(" extra field=" + this.extraField);
+ }
+
+ // read in the compressed data
+ this.fileData = null;
+ if (this.compressedSize > 0) {
+ this.fileData = new Uint8Array(bstream.bytes.buffer, bstream.ptr, this.compressedSize);
+ bstream.ptr += this.compressedSize;
+ }
+
+ // TODO: deal with data descriptor if present (we currently assume no data descriptor!)
+ // "This descriptor exists only if bit 3 of the general purpose bit flag is set"
+ // But how do you figure out how big the file data is if you don't know the compressedSize
+ // from the header?!?
+ if ((this.generalPurpose & bitjs.BIT[3]) !== 0) {
+ this.crc32 = bstream.readNumber(4);
+ this.compressedSize = bstream.readNumber(4);
+ this.uncompressedSize = bstream.readNumber(4);
+ }
+
+ // Now that we have all the bytes for this file, we can print out some information.
+ info("Zip Local File Header:");
+ info(" version=" + this.version);
+ info(" general purpose=" + this.generalPurpose);
+ info(" compression method=" + this.compressionMethod);
+ info(" last mod file time=" + this.lastModFileTime);
+ info(" last mod file date=" + this.lastModFileDate);
+ info(" crc32=" + this.crc32);
+ info(" compressed size=" + this.compressedSize);
+ info(" uncompressed size=" + this.uncompressedSize);
+ info(" file name length=" + this.fileNameLength);
+ info(" extra field length=" + this.extraFieldLength);
+ info(" filename = '" + this.filename + "'");
+
+};
+
+// determine what kind of compressed data we have and decompress
+ZipLocalFile.prototype.unzip = function() {
+
+ // Zip Version 1.0, no compression (store only)
+ if (this.compressionMethod === 0 ) {
+ info("ZIP v" + this.version + ", store only: " + this.filename + " (" + this.compressedSize + " bytes)");
+ currentBytesUnarchivedInFile = this.compressedSize;
+ currentBytesUnarchived += this.compressedSize;
+ this.fileData = zeroCompression(this.fileData, this.uncompressedSize);
+ } else if (this.compressionMethod === 8) {
+ // version == 20, compression method == 8 (DEFLATE)
+ info("ZIP v2.0, DEFLATE: " + this.filename + " (" + this.compressedSize + " bytes)");
+ this.fileData = inflate(this.fileData, this.uncompressedSize);
+ } else {
+ err("UNSUPPORTED VERSION/FORMAT: ZIP v" + this.version + ", compression method=" + this.compressionMethod + ": " + this.filename + " (" + this.compressedSize + " bytes)");
+ this.fileData = null;
+ }
+};
+
+
+// Takes an ArrayBuffer of a zip file in
+// returns null on error
+// returns an array of DecompressedFile objects on success
+// ToDo This function differs
+var unzip = function(arrayBuffer) {
+ postMessage(new bitjs.archive.UnarchiveStartEvent());
+
+ currentFilename = "";
+ currentFileNumber = 0;
+ currentBytesUnarchivedInFile = 0;
+ currentBytesUnarchived = 0;
+ totalUncompressedBytesInArchive = 0;
+ totalFilesInArchive = 0;
+ currentBytesUnarchived = 0;
+
+ var bstream = new bitjs.io.ByteStream(arrayBuffer);
+ // detect local file header signature or return null
+ if (bstream.peekNumber(4) === zLocalFileHeaderSignature) {
+ var localFiles = [];
+ // loop until we don't see any more local files
+ while (bstream.peekNumber(4) === zLocalFileHeaderSignature) {
+ var oneLocalFile = new ZipLocalFile(bstream);
+ // this should strip out directories/folders
+ if (oneLocalFile && oneLocalFile.uncompressedSize > 0 && oneLocalFile.fileData) {
+ localFiles.push(oneLocalFile);
+ totalUncompressedBytesInArchive += oneLocalFile.uncompressedSize;
+ }
+ }
+ totalFilesInArchive = localFiles.length;
+
+ // got all local files, now sort them
+ localFiles.sort(alphanumCase);
+
+ // archive extra data record
+ if (bstream.peekNumber(4) === zArchiveExtraDataSignature) {
+ info(" Found an Archive Extra Data Signature");
+
+ // skipping this record for now
+ bstream.readNumber(4);
+ var archiveExtraFieldLength = bstream.readNumber(4);
+ bstream.readString(archiveExtraFieldLength);
+ }
+
+ // central directory structure
+ // TODO: handle the rest of the structures (Zip64 stuff)
+ if (bstream.peekNumber(4) === zCentralFileHeaderSignature) {
+ info(" Found a Central File Header");
+
+ // read all file headers
+ while (bstream.peekNumber(4) === zCentralFileHeaderSignature) {
+ bstream.readNumber(4); // signature
+ bstream.readNumber(2); // version made by
+ bstream.readNumber(2); // version needed to extract
+ bstream.readNumber(2); // general purpose bit flag
+ bstream.readNumber(2); // compression method
+ bstream.readNumber(2); // last mod file time
+ bstream.readNumber(2); // last mod file date
+ bstream.readNumber(4); // crc32
+ bstream.readNumber(4); // compressed size
+ bstream.readNumber(4); // uncompressed size
+ var fileNameLength = bstream.readNumber(2); // file name length
+ var extraFieldLength = bstream.readNumber(2); // extra field length
+ var fileCommentLength = bstream.readNumber(2); // file comment length
+ bstream.readNumber(2); // disk number start
+ bstream.readNumber(2); // internal file attributes
+ bstream.readNumber(4); // external file attributes
+ bstream.readNumber(4); // relative offset of local header
+
+ bstream.readString(fileNameLength); // file name
+ bstream.readString(extraFieldLength); // extra field
+ bstream.readString(fileCommentLength); // file comment
+ }
+ }
+
+ // digital signature
+ if (bstream.peekNumber(4) === zDigitalSignatureSignature) {
+ info(" Found a Digital Signature");
+
+ bstream.readNumber(4);
+ var sizeOfSignature = bstream.readNumber(2);
+ bstream.readString(sizeOfSignature); // digital signature data
+ }
+
+ // report # files and total length
+ if (localFiles.length > 0) {
+ postProgress();
+ }
+
+ // now do the unzipping of each file
+ for (var i = 0; i < localFiles.length; ++i) {
+ var localfile = localFiles[i];
+
+ // update progress
+ currentFilename = localfile.filename;
+ currentFileNumber = i;
+ currentBytesUnarchivedInFile = 0;
+
+ // actually do the unzipping
+ localfile.unzip();
+
+ if (localfile.fileData !== null) {
+ postMessage(new bitjs.archive.UnarchiveExtractEvent(localfile));
+ postProgress();
+ }
+ }
+ postProgress();
+ postMessage(new bitjs.archive.UnarchiveFinishEvent());
+ }
+};
+
+// returns a table of Huffman codes
+// each entry's index is its code and its value is a JavaScript object
+// containing {length: 6, symbol: X}
+function getHuffmanCodes(bitLengths) {
+ // ensure bitLengths is an array containing at least one element
+ if (typeof bitLengths !== typeof [] || bitLengths.length < 1) {
+ err("Error! getHuffmanCodes() called with an invalid array");
+ return null;
+ }
+
+ // Reference: http://tools.ietf.org/html/rfc1951#page-8
+ var numLengths = bitLengths.length;
+ var blCount = [];
+ var MAX_BITS = 1;
+
+ // Step 1: count up how many codes of each length we have
+ for (var i = 0; i < numLengths; ++i) {
+ var length = bitLengths[i];
+ // test to ensure each bit length is a positive, non-zero number
+ if (typeof length !== typeof 1 || length < 0) {
+ err("bitLengths contained an invalid number in getHuffmanCodes(): " + length + " of type " + (typeof length));
+ return null;
+ }
+ // increment the appropriate bitlength count
+ if (typeof blCount[length] === "undefined") blCount[length] = 0;
+ // a length of zero means this symbol is not participating in the huffman coding
+ if (length > 0) blCount[length]++;
+
+ if (length > MAX_BITS) MAX_BITS = length;
+ }
+
+ // Step 2: Find the numerical value of the smallest code for each code length
+ var nextCode = [];
+ var code = 0;
+ for (var bits = 1; bits <= MAX_BITS; ++bits) {
+ var length2 = bits - 1;
+ // ensure undefined lengths are zero
+ if (typeof blCount[length2] === "undefined") blCount[length2] = 0;
+ code = (code + blCount[bits - 1]) << 1;
+ nextCode [bits] = code;
+ }
+
+ // Step 3: Assign numerical values to all codes
+ var table = {};
+ var tableLength = 0;
+ for (var n = 0; n < numLengths; ++n) {
+ var len = bitLengths[n];
+ if (len !== 0) {
+ table[nextCode [len]] = { length: len, symbol: n }; //, bitstring: binaryValueToString(nextCode [len],len) };
+ tableLength++;
+ nextCode [len]++;
+ }
+ }
+ table.maxLength = tableLength;
+
+ return table;
+}
+
+/*
+ The Huffman codes for the two alphabets are fixed, and are not
+ represented explicitly in the data. The Huffman code lengths
+ for the literal/length alphabet are:
+
+ Lit Value Bits Codes
+ --------- ---- -----
+ 0 - 143 8 00110000 through
+ 10111111
+ 144 - 255 9 110010000 through
+ 111111111
+ 256 - 279 7 0000000 through
+ 0010111
+ 280 - 287 8 11000000 through
+ 11000111
+*/
+// fixed Huffman codes go from 7-9 bits, so we need an array whose index can hold up to 9 bits
+var fixedHCtoLiteral = null;
+var fixedHCtoDistance = null;
+
+function getFixedLiteralTable() {
+ // create once
+ if (!fixedHCtoLiteral) {
+ var bitlengths = new Array(288);
+ var i;
+ for (i = 0; i <= 143; ++i) bitlengths[i] = 8;
+ for (i = 144; i <= 255; ++i) bitlengths[i] = 9;
+ for (i = 256; i <= 279; ++i) bitlengths[i] = 7;
+ for (i = 280; i <= 287; ++i) bitlengths[i] = 8;
+
+ // get huffman code table
+ fixedHCtoLiteral = getHuffmanCodes(bitlengths);
+ }
+ return fixedHCtoLiteral;
+}
+
+function getFixedDistanceTable() {
+ // create once
+ if (!fixedHCtoDistance) {
+ var bitlengths = new Array(32);
+ for (var i = 0; i < 32; ++i) {
+ bitlengths[i] = 5;
+ }
+
+ // get huffman code table
+ fixedHCtoDistance = getHuffmanCodes(bitlengths);
+ }
+ return fixedHCtoDistance;
+}
+
+// extract one bit at a time until we find a matching Huffman Code
+// then return that symbol
+function decodeSymbol(bstream, hcTable) {
+ var code = 0;
+ var len = 0;
+
+ // loop until we match
+ for (;;) {
+ // read in next bit
+ var bit = bstream.readBits(1);
+ code = (code << 1) | bit;
+ ++len;
+
+ // check against Huffman Code table and break if found
+ if (hcTable.hasOwnProperty(code) && hcTable[code].length === len) {
+ break;
+ }
+ if (len > hcTable.maxLength) {
+ err("Bit stream out of sync, didn't find a Huffman Code, length was " + len +
+ " and table only max code length of " + hcTable.maxLength);
+ break;
+ }
+ }
+ return hcTable[code].symbol;
+}
+
+
+var CodeLengthCodeOrder = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
+/*
+ Extra Extra Extra
+Code Bits Length(s) Code Bits Lengths Code Bits Length(s)
+---- ---- ------ ---- ---- ------- ---- ---- -------
+ 257 0 3 267 1 15,16 277 4 67-82
+ 258 0 4 268 1 17,18 278 4 83-98
+ 259 0 5 269 2 19-22 279 4 99-114
+ 260 0 6 270 2 23-26 280 4 115-130
+ 261 0 7 271 2 27-30 281 5 131-162
+ 262 0 8 272 2 31-34 282 5 163-194
+ 263 0 9 273 3 35-42 283 5 195-226
+ 264 0 10 274 3 43-50 284 5 227-257
+ 265 1 11,12 275 3 51-58 285 0 258
+ 266 1 13,14 276 3 59-66
+*/
+var LengthLookupTable = [
+ [0, 3],
+ [0, 4],
+ [0, 5],
+ [0, 6],
+ [0, 7],
+ [0, 8],
+ [0, 9],
+ [0, 10],
+ [1, 11],
+ [1, 13],
+ [1, 15],
+ [1, 17],
+ [2, 19],
+ [2, 23],
+ [2, 27],
+ [2, 31],
+ [3, 35],
+ [3, 43],
+ [3, 51],
+ [3, 59],
+ [4, 67],
+ [4, 83],
+ [4, 99],
+ [4, 115],
+ [5, 131],
+ [5, 163],
+ [5, 195],
+ [5, 227],
+ [0, 258]
+];
+/*
+ Extra Extra Extra
+ Code Bits Dist Code Bits Dist Code Bits Distance
+ ---- ---- ---- ---- ---- ------ ---- ---- --------
+ 0 0 1 10 4 33-48 20 9 1025-1536
+ 1 0 2 11 4 49-64 21 9 1537-2048
+ 2 0 3 12 5 65-96 22 10 2049-3072
+ 3 0 4 13 5 97-128 23 10 3073-4096
+ 4 1 5,6 14 6 129-192 24 11 4097-6144
+ 5 1 7,8 15 6 193-256 25 11 6145-8192
+ 6 2 9-12 16 7 257-384 26 12 8193-12288
+ 7 2 13-16 17 7 385-512 27 12 12289-16384
+ 8 3 17-24 18 8 513-768 28 13 16385-24576
+ 9 3 25-32 19 8 769-1024 29 13 24577-32768
+*/
+var DistLookupTable = [
+ [0, 1],
+ [0, 2],
+ [0, 3],
+ [0, 4],
+ [1, 5],
+ [1, 7],
+ [2, 9],
+ [2, 13],
+ [3, 17],
+ [3, 25],
+ [4, 33],
+ [4, 49],
+ [5, 65],
+ [5, 97],
+ [6, 129],
+ [6, 193],
+ [7, 257],
+ [7, 385],
+ [8, 513],
+ [8, 769],
+ [9, 1025],
+ [9, 1537],
+ [10, 2049],
+ [10, 3073],
+ [11, 4097],
+ [11, 6145],
+ [12, 8193],
+ [12, 12289],
+ [13, 16385],
+ [13, 24577]
+];
+
+function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) {
+ /*
+ loop (until end of block code recognized)
+ decode literal/length value from input stream
+ if value < 256
+ copy value (literal byte) to output stream
+ otherwise
+ if value = end of block (256)
+ break from loop
+ otherwise (value = 257..285)
+ decode distance from input stream
+
+ move backwards distance bytes in the output
+ stream, and copy length bytes from this
+ position to the output stream.
+ */
+ var blockSize = 0;
+ for (;;) {
+ var symbol = decodeSymbol(bstream, hcLiteralTable);
+ if (symbol < 256) {
+ // copy literal byte to output
+ buffer.insertByte(symbol);
+ blockSize++;
+ } else {
+ // end of block reached
+ if (symbol === 256) {
+ break;
+ } else {
+ var lengthLookup = LengthLookupTable[symbol - 257];
+ var length = lengthLookup[1] + bstream.readBits(lengthLookup[0]);
+ var distLookup = DistLookupTable[decodeSymbol(bstream, hcDistanceTable)];
+ var distance = distLookup[1] + bstream.readBits(distLookup[0]);
+
+ // now apply length and distance appropriately and copy to output
+
+ // TODO: check that backward distance < data.length?
+
+ // http://tools.ietf.org/html/rfc1951#page-11
+ // "Note also that the referenced string may overlap the current
+ // position; for example, if the last 2 bytes decoded have values
+ // X and Y, a string reference with
+ // adds X,Y,X,Y,X to the output stream."
+ //
+ // loop for each character
+ var ch = buffer.ptr - distance;
+ blockSize += length;
+ if (length > distance) {
+ var data = buffer.data;
+ while (length--) {
+ buffer.insertByte(data[ch++]);
+ }
+ } else {
+ buffer.insertBytes(buffer.data.subarray(ch, ch + length));
+ }
+ } // length-distance pair
+ } // length-distance pair or end-of-block
+ } // loop until we reach end of block
+ return blockSize;
+}
+
+function zeroCompression(compressedData, numDecompressedBytes) {
+ var bstream = new bitjs.io.BitStream(compressedData.buffer,
+ false /* rtl */,
+ compressedData.byteOffset,
+ compressedData.byteLength);
+ var buffer = new bitjs.io.ByteBuffer(numDecompressedBytes);
+ buffer.insertBytes(bstream.readBytes(numDecompressedBytes));
+ return buffer.data;
+}
+
+// {Uint8Array} compressedData A Uint8Array of the compressed file data.
+// compression method 8
+// deflate: http://tools.ietf.org/html/rfc1951
+function inflate(compressedData, numDecompressedBytes) {
+ // Bit stream representing the compressed data.
+ var bstream = new bitjs.io.BitStream(compressedData.buffer,
+ false /* rtl */,
+ compressedData.byteOffset,
+ compressedData.byteLength);
+ var buffer = new bitjs.io.ByteBuffer(numDecompressedBytes);
+ var blockSize = 0;
+
+ // block format: http://tools.ietf.org/html/rfc1951#page-9
+ var bFinal = 0;
+ do {
+ bFinal = bstream.readBits(1);
+ var bType = bstream.readBits(2);
+ blockSize = 0;
+ // ++numBlocks;
+ // no compression
+ if (bType === 0) {
+ // skip remaining bits in this byte
+ while (bstream.bitPtr !== 0) bstream.readBits(1);
+ var len = bstream.readBits(16);
+ bstream.readBits(16);
+ // TODO: check if nlen is the ones-complement of len?
+
+ if (len > 0) buffer.insertBytes(bstream.readBytes(len));
+ blockSize = len;
+ } else if (bType === 1) {
+ // fixed Huffman codes
+ blockSize = inflateBlockData(bstream, getFixedLiteralTable(), getFixedDistanceTable(), buffer);
+ } else if (bType === 2) {
+ // dynamic Huffman codes
+ var numLiteralLengthCodes = bstream.readBits(5) + 257;
+ var numDistanceCodes = bstream.readBits(5) + 1,
+ numCodeLengthCodes = bstream.readBits(4) + 4;
+
+ // populate the array of code length codes (first de-compaction)
+ var codeLengthsCodeLengths = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+ for (var i = 0; i < numCodeLengthCodes; ++i) {
+ codeLengthsCodeLengths[ CodeLengthCodeOrder[i] ] = bstream.readBits(3);
+ }
+
+ // get the Huffman Codes for the code lengths
+ var codeLengthsCodes = getHuffmanCodes(codeLengthsCodeLengths);
+
+ // now follow this mapping
+ /*
+ 0 - 15: Represent code lengths of 0 - 15
+ 16: Copy the previous code length 3 - 6 times.
+ The next 2 bits indicate repeat length
+ (0 = 3, ... , 3 = 6)
+ Example: Codes 8, 16 (+2 bits 11),
+ 16 (+2 bits 10) will expand to
+ 12 code lengths of 8 (1 + 6 + 5)
+ 17: Repeat a code length of 0 for 3 - 10 times.
+ (3 bits of length)
+ 18: Repeat a code length of 0 for 11 - 138 times
+ (7 bits of length)
+ */
+ // to generate the true code lengths of the Huffman Codes for the literal
+ // and distance tables together
+ var literalCodeLengths = [];
+ var prevCodeLength = 0;
+ while (literalCodeLengths.length < numLiteralLengthCodes + numDistanceCodes) {
+ var symbol = decodeSymbol(bstream, codeLengthsCodes);
+ if (symbol <= 15) {
+ literalCodeLengths.push(symbol);
+ prevCodeLength = symbol;
+ } else if (symbol === 16) {
+ var repeat = bstream.readBits(2) + 3;
+ while (repeat--) {
+ literalCodeLengths.push(prevCodeLength);
+ }
+ } else if (symbol === 17) {
+ var repeat1 = bstream.readBits(3) + 3;
+ while (repeat1--) {
+ literalCodeLengths.push(0);
+ }
+ } else if (symbol === 18) {
+ var repeat2 = bstream.readBits(7) + 11;
+ while (repeat2--) {
+ literalCodeLengths.push(0);
+ }
+ }
+ }
+
+ // now split the distance code lengths out of the literal code array
+ var distanceCodeLengths = literalCodeLengths.splice(numLiteralLengthCodes, numDistanceCodes);
+
+ // now generate the true Huffman Code tables using these code lengths
+ var hcLiteralTable = getHuffmanCodes(literalCodeLengths);
+ var hcDistanceTable = getHuffmanCodes(distanceCodeLengths);
+ blockSize = inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer);
+ } else {
+ // error
+ err("Error! Encountered deflate block of type 3");
+ return null;
+ }
+
+ // update progress
+ currentBytesUnarchivedInFile += blockSize;
+ currentBytesUnarchived += blockSize;
+ postProgress();
+
+ } while (bFinal !== 1);
+ // we are done reading blocks if the bFinal bit was set for this block
+
+ // return the buffer data bytes
+ return buffer.data;
+}
+
+// event.data.file has the ArrayBuffer.
+onmessage = function(event) {
+ unzip(event.data.file, true);
+};
diff --git a/cps/static/js/caliBlur.js b/cps/static/js/caliBlur.js
new file mode 100644
index 00000000..53f033ba
--- /dev/null
+++ b/cps/static/js/caliBlur.js
@@ -0,0 +1,723 @@
+/* This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
+ * Copyright (C) 2018-2019 hexeth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+// Move advanced search to side-menu
+$( 'a[href*="advanced"]' ).parent().insertAfter( '#nav_new' );
+$( 'body' ).addClass('blur');
+$( 'body.stat' ).addClass( 'stats' );
+$( 'body.config' ).addClass( 'admin');
+$( 'body.uiconfig' ).addClass( 'admin');
+$( 'body.advsearch' ).addClass( 'advanced_search' );
+$( 'body.newuser' ).addClass( 'admin' );
+$( 'body.mailset' ).addClass( 'admin' );
+
+// Back button
+curHref = window.location.href.split('/');
+prevHref = document.referrer.split('/');
+$( '.navbar-form.navbar-left' )
+ .before( '' );
+if ( history.length === 1 ||
+ curHref[0] +
+ curHref[1] +
+ curHref[2] !=
+ prevHref[0] +
+ prevHref[1] +
+ prevHref[2] ||
+ $( 'body.root' )>length > 0 ) {
+ $( '.plexBack' ).addClass( 'noBack' );
+}
+
+//Weird missing a after pressing back from edit.
+setTimeout(function() {
+ if ( $( '.plexBack a').length < 1 ) {
+ $( '.plexBack' ).append('');
+ }
+},10);
+
+// Home button
+$( '.plexBack' ).before( '' );
+$( 'a.navbar-brand' ).clone().appendTo( '.home-btn' ).empty().removeClass('navbar-brand');
+/////////////////////////////////
+// Start of Book Details Work //
+///////////////////////////////
+
+// Wrap book description in div container
+if ( $( 'body.book' ).length > 0 ) {
+
+ description = $( '.comments' );
+ bookInfo = $( ".author" ).nextUntil("#decription");
+ $("#decription").detach();
+ $( '.comments' ).detach();
+ $( bookInfo ).wrapAll( '' );
+// $( 'h3:contains("Description:")' ).after( '' );
+ $( '.languages' ).appendTo( '.bookinfo' );
+ $('.hr').detach();
+ if ( $( '.identifiers ').length > 0 ) {
+ console.log(".identifiers length " + $( '.identifiers ').length );
+ $( '.identifiers' ).before( '' );
+ } else {
+ if ( $( '.bookinfo > p:first-child' ).length > 0 ) {
+ console.log(".bookinfo > p:first-child length " + $( '.bookinfo > p' ).length );
+ $( '.bookinfo > p:first-child' ).first().after( '' );
+ } else{
+ if ( $( '.bookinfo a[href*="/series/"]' ).length > 0 ) {
+ console.log( 'series text found; placing hr below series' );
+ $( '.bookinfo a[href*="/series/"]' ).parent().after( '' );
+ } else {
+ console.log("prepending hr div to top of .bookinfo");
+ $( '.bookinfo' ).prepend( '' );
+ }
+ }
+ }
+ $( '.rating' ).insertBefore( '.hr' );
+ $( '#remove-from-shelves' ).insertAfter( '.hr' );
+ $( description ).appendTo('.bookinfo')
+ /* if book description is not in html format, Remove extra line breaks
+ Remove blank lines/unnecessary spaces, split by line break to array
+ Push array into .description div. If there is still a wall of text,
+ find sentences and split wall into groups of three sentence paragraphs.
+ If the book format is in html format, Keep html, but strip away inline
+ styles and empty elements */
+
+ // If text is sitting in div as text node
+ if ( $('.comments:has(p)' ).length === 0 ) {
+ newdesc = description.text()
+ .replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm,"").split(/\n/);
+ $('.comments' ).empty();
+ $.each(newdesc, function(i, val) {
+ $( 'div.comments' ).append( '' + newdesc[i] + '
' );
+ });
+ $( '.comments' ).fadeIn(100);
+ } //If still a wall of text create 3 sentence paragraphs.
+ if( $( '.comments p' ).length === 1 ) {
+ if ( description.context != undefined ) {
+ newdesc = description.text()
+ .replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm,"").split(/\n/);
+ }
+ else {
+ newdesc = description.text();
+ }
+ doc = nlp ( newdesc.toString() );
+ sentences = doc.map((m)=> m.out( 'text' ));
+ sentences[0] = sentences[0].replace(",","");
+ $( '.comments p' ).remove();
+ let size = 3; let sentenceChunks = [];
+ for (var i=0; i' + preOutput + '';
+ });
+ $( 'div.comments' ).append( output );
+ }
+ else {
+ $.each(description, function(i, val) {
+// $( description[i].outerHTML ).appendTo( '.comments' );
+ $( 'div.comments :empty' ).remove();
+ $( 'div.comments ').attr( 'style', '' );
+ });
+ $( 'div.comments' ).fadeIn( 100 );
+ }
+
+ // Sexy blurred backgrounds
+ cover = $( '.cover img' ).attr( 'src' );
+ $( '#loader + .container-fluid' )
+ .prepend( ' ' );
+
+ // Fix-up book detail headings
+ publisher = $( '.publishers p span' ).text().split( ':' );
+ $( '.publishers p span' ).remove();
+ $.each(publisher, function(i, val) {
+ $( '.publishers' ).append( '