├── .gitignore ├── Gruntfile.js ├── LICENSE ├── NOTICE ├── README.md ├── TODO.md ├── _locales └── en │ └── messages.json ├── background.html ├── content_script.js ├── css ├── common │ ├── fancytree.css │ ├── pagetree.css │ ├── pane.css │ ├── reset.css │ └── ui.jqgrid.css ├── jqueryui │ └── redmond │ │ ├── images │ │ ├── ui-bg_flat_0_aaaaaa_40x100.png │ │ ├── ui-bg_flat_55_fbec88_40x100.png │ │ ├── ui-bg_glass_75_d0e5f5_1x400.png │ │ ├── ui-bg_glass_85_dfeffc_1x400.png │ │ ├── ui-bg_glass_95_fef1ec_1x400.png │ │ ├── ui-bg_gloss-wave_55_5c9ccc_500x100.png │ │ ├── ui-bg_inset-hard_100_f5f8f9_1x100.png │ │ ├── ui-bg_inset-hard_100_fcfdfd_1x100.png │ │ ├── ui-icons_217bc0_256x240.png │ │ ├── ui-icons_2e83ff_256x240.png │ │ ├── ui-icons_469bdd_256x240.png │ │ ├── ui-icons_6da8d5_256x240.png │ │ ├── ui-icons_cd0a0a_256x240.png │ │ ├── ui-icons_d8e7f3_256x240.png │ │ └── ui-icons_f9bd01_256x240.png │ │ └── jquery-ui-1.8.23.custom.css ├── options.css ├── panes │ ├── closed.css │ ├── external-site.css │ ├── notepad.css │ ├── pages.css │ └── whatsnew.css └── sidebar.css ├── detect-monitor.html ├── images ├── clear_highlight.png ├── close.png ├── close_branch.png ├── cloud1.png ├── copy_url.png ├── create_tab.png ├── favicon │ ├── bookmarks.png │ ├── downloads.png │ ├── extensions.png │ ├── history.png │ ├── newtab.png │ └── settings.png ├── folder.png ├── help.png ├── hibernate.png ├── hibernate_branch.png ├── hibernate_wake.png ├── highlight.png ├── incognito-16.png ├── incognito-32.png ├── install_indicator_arrow.png ├── label.png ├── nav │ ├── closed.png │ ├── grooveshark.ico │ ├── notepad.png │ ├── pages.png │ ├── reddit.gif │ ├── reddit.png │ ├── settings.png │ ├── tribunal.png │ ├── whatsnew.gif │ └── whatsnew.png ├── options │ ├── facebook.png │ └── twitter.png ├── panty-pull-128.png ├── panty-pull-48.png ├── pinned.png ├── reload.png ├── sidewise.ico ├── sidewise_icon_128.png ├── sidewise_icon_16.png ├── sidewise_icon_256.png ├── sidewise_icon_32.png ├── sidewise_icon_48.png ├── tab-single-16.png ├── tab-single-32.png ├── tab-stack-16.png ├── tab-stack-32.png ├── text_indent_promote.png ├── text_indent_remove.png ├── throbber-slow.gif ├── toggle_arrow_collapsed.png ├── toggle_arrow_collapsed_hover.png ├── toggle_arrow_expanded.png ├── toggle_arrow_expanded_hover.png ├── unpin.png ├── wake.png ├── wake_branch.png ├── warning.png └── x.gif ├── js ├── bg │ ├── background.js │ ├── classes │ │ ├── ChromeWindowFocusTracker.js │ │ ├── DataTree.js │ │ ├── DataTreeNode.js │ │ ├── DataTreeRootNode.js │ │ ├── FolderNode.js │ │ ├── GhostNode.js │ │ ├── HeaderNode.js │ │ ├── IconTester.js │ │ ├── MonitorInfo.js │ │ ├── PageNode.js │ │ ├── PageTree.js │ │ ├── PageTreeNode.js │ │ ├── SidebarHandler.js │ │ ├── SidebarPaneCatalog.js │ │ ├── UiDataTree.js │ │ └── WindowNode.js │ ├── config.js │ ├── events │ │ ├── browseraction-events.js │ │ ├── omnibox-events.js │ │ ├── request-events.js │ │ ├── runtime-events.js │ │ ├── snapin-events.js │ │ ├── tab-events.js │ │ ├── webnav-events.js │ │ └── window-events.js │ └── functions │ │ └── associate.js ├── jquery │ ├── impromptu │ │ ├── README.md │ │ ├── default.css │ │ ├── jquery-impromptu.js │ │ └── themes │ │ │ └── clean-blue.css │ ├── jqgrid │ │ ├── grid.locale-en.js │ │ └── jquery.jqGrid.min.js │ ├── jquery-1.7.2.min.js │ ├── jquery-helpers.js │ ├── jquery-ui-1.8.23.complete.min.js │ ├── jquery.contextMenu.js │ ├── jquery.easing.js │ ├── jquery.highlight.js │ ├── jquery.scrollTo-min.js │ └── jquery.tools.min.js ├── lib │ └── ga.js ├── ui │ ├── classes │ │ ├── FancyTree.contextMenu.js │ │ ├── FancyTree.dragDrop.js │ │ ├── FancyTree.effects.js │ │ ├── FancyTree.events.body.js │ │ ├── FancyTree.events.row.js │ │ ├── FancyTree.events.rowClick.js │ │ ├── FancyTree.filtering.js │ │ ├── FancyTree.init.js │ │ ├── FancyTree.js │ │ ├── FancyTree.misc.js │ │ ├── FancyTree.multiSelect.js │ │ ├── FancyTree.rowType.js │ │ ├── FancyTree.rows.helpers.js │ │ ├── FancyTree.rows.html.js │ │ ├── FancyTree.rows.ops.js │ │ ├── FancyTree.tooltips.js │ │ ├── SidebarNavManager.js │ │ └── SidebarPaneFancyTreeBinder.js │ ├── common │ │ ├── pagetree.js │ │ └── pane.js │ ├── options │ │ ├── common.js │ │ ├── full.js │ │ └── install.js │ ├── panes │ │ ├── closed.js │ │ ├── external-site.js │ │ ├── notepad.js │ │ ├── pages.js │ │ └── whatsnew.js │ ├── sidebar.js │ └── test_icons │ │ └── test_icons.js └── util │ ├── chrome-functions.js │ ├── classes │ ├── Catalog.js │ └── TimeoutManager.js │ ├── i18n.js │ ├── logging.js │ ├── marked.js │ ├── settings.js │ ├── ui-util.js │ └── util.js ├── manifest.json ├── options.html ├── options_install.html ├── package-lock.json ├── package.json ├── panes ├── 404.html ├── bookmarks.html ├── closed.html ├── external-site.html ├── notepad.html ├── pages.html ├── reddit.html ├── tribunal.html └── whatsnew.html ├── sidebar.html └── test_icons.html /.gitignore: -------------------------------------------------------------------------------- 1 | /fliptest.html 2 | /todo.txt 3 | /TEST.js 4 | node_modules 5 | dist 6 | sidewise.code-workspace 7 | .idea 8 | .DS_Store -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | var distDir = "./dist/"; 4 | var distArchive = "./dist/ext.zip"; 5 | 6 | // Project configuration. 7 | grunt.initConfig({ 8 | pkg: grunt.file.readJSON('package.json'), 9 | clean: { 10 | dist: [distDir + '**', distArchive] 11 | }, 12 | copy: { 13 | dist: { 14 | files: [ 15 | { 16 | expand: true, 17 | src: [ 18 | '{js,images,_locales,panes,css}/**', 19 | '*.{js,html}', 20 | '!Gruntfile.js', 21 | 'manifest.json', 22 | 'LICENSE', 23 | 'NOTICE'], 24 | dest: distDir 25 | } 26 | ] 27 | } 28 | }, 29 | usebanner: { 30 | dist: { 31 | options: { 32 | position: 'top', 33 | banner: '/*\n' + 34 | ' * Licensed under the Creative Commons Zero (CC0) license.\n' + 35 | ' * See LICENSE for details.\n' + 36 | ' */', 37 | linebreak: true 38 | }, 39 | files: { 40 | src: [distDir + '**/*.js', '!**/jquery/*', ] 41 | } 42 | } 43 | }, 44 | compress: { 45 | dist: { 46 | options: { 47 | archive: distArchive 48 | }, 49 | files: [ 50 | { 51 | expand: true, 52 | cwd: distDir, 53 | src: ['**/*' ], 54 | dest: '/' 55 | } 56 | ] 57 | } 58 | 59 | } 60 | }); 61 | 62 | grunt.loadNpmTasks('grunt-contrib-clean'); 63 | grunt.loadNpmTasks('grunt-contrib-compress'); 64 | grunt.loadNpmTasks('grunt-contrib-copy'); 65 | grunt.loadNpmTasks('grunt-banner'); 66 | 67 | // Default task(s). 68 | grunt.registerTask('default', ['clean', 'copy', 'usebanner', /* 'compress' */]); 69 | 70 | }; 71 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | This extension uses icons based on the famfamfam silk series. 2 | http://www.famfamfam.com/lab/icons/silk/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sidewise Tree Style Tabs 2 | Persistent sidebar extension for Chrome featuring tree style tabs and tab hibernation. 3 | 4 | This is the initial public commit to Github of the Sidewise codebase. 5 | 6 | ## Building 7 | 8 | ``` 9 | npm install 10 | npm install -g grunt 11 | grunt 12 | ``` 13 | 14 | The output is in `./dist`. You can open this folder directly using the "Load unpacked" button on chrome://extensions/ . 15 | 16 | ## License history 17 | 18 | * Apr 4 2021: convert to Creative Commons Zero (CC0) license. All previously held copyrights released to public domain. 19 | * Before Apr 4 2021: licensed under a modified MIT license by Joel Thornton . 20 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ## Todos: top known issues to fix 2 | 3 | * Remove donation flows and associated code/HTML/CSS 4 | * Remove specialized super() behavior. See `js/util/util.js:extendClass()` implementation and notes at line 362 (from "TODO drop smart surrogate logic"). I believe this implementation is overwrought and arose because of a misunderstanding by me (original author) around how calling should work in an OO class hierarchy. Note that just removing the surrogate behavior will cause some parts of the code to break where this special behavior is expected. Possible fix strategy: tentatively replace each usage of `extendClass()` with a version that does NOT perform the surrogate behavior, and test the extension. If it breaks, then one of the classes involved is affected by the surrogate behavior. 5 | * Remove old video player tracking code from content-script.js and related files and adopt use of Chrome extension api for detecting which tabs are outputting audio instead, e.g. `tabs.query({ audible: true })` 6 | * In some cases, Sidewise can get 'out of sync' between Chrome's tab bar and Sidewise's tree. This may be connected to a problem in onTabCreated() where an error accessing tab.id, tab.windowId, or tab.openerTabId. 7 | * The MonitorInfo class and 'detect-monitor.html' file exist solely to workaround a deficiency Chrome had with regard to providing accurate multiple-monitor metrics data when this was implemented (ca. 2012). It is probable (but untested) that this code is no longer necessary; see https://developer.chrome.com/docs/extensions/reference/system_display/ 8 | * The ChromeWindowFocusTracker class exhibits problematic behavior on some Linux window managers. This most likely is related to the particular sequence of window events raised on Windows vs Linux. 9 | * The right-click menu within Sidewise does not work properly on MacOS: the menu closes immediately upon mouse release. Most likely related to the particular sequence of mouse events raised on Windows vs MacOS. 10 | -------------------------------------------------------------------------------- /background.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /css/common/pane.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Segoe UI', 'MS Sans Serif', sans-serif; 3 | -webkit-user-select: none; 4 | user-select: none; 5 | font-size: 16px; 6 | overflow: hidden; 7 | } 8 | 9 | #heading { 10 | font-weight: bold; 11 | font-size: 16px; 12 | } 13 | 14 | .hint { 15 | font-size: 11px; 16 | margin-top: 0.5em; 17 | color: #999; 18 | } 19 | -------------------------------------------------------------------------------- /css/common/reset.css: -------------------------------------------------------------------------------- 1 | /* v2.0 | 20110126 2 | http://meyerweb.com/eric/tools/css/reset/ 3 | License: none (public domain) 4 | */ 5 | html, body, div, span, applet, object, iframe, 6 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 7 | a, abbr, acronym, address, big, cite, code, 8 | del, dfn, em, img, ins, kbd, q, s, samp, 9 | small, strike, strong, sub, sup, tt, var, 10 | b, u, i, center, 11 | dl, dt, dd, ol, ul, li, 12 | fieldset, form, label, legend, 13 | table, caption, tbody, tfoot, thead, tr, th, td, 14 | article, aside, canvas, details, embed, 15 | figure, figcaption, footer, header, hgroup, 16 | menu, nav, output, ruby, section, summary, 17 | time, mark, audio, video { 18 | margin: 0; 19 | padding: 0; 20 | border: 0; 21 | font-size: 100%; 22 | font: inherit; 23 | vertical-align: baseline; 24 | } 25 | /* HTML5 display-role reset for older browsers */ 26 | article, aside, details, figcaption, figure, 27 | footer, header, hgroup, menu, nav, section { 28 | display: block; 29 | } 30 | body { 31 | line-height: 1; 32 | } 33 | ol, ul { 34 | list-style: none; 35 | } 36 | blockquote, q { 37 | quotes: none; 38 | } 39 | blockquote:before, blockquote:after, 40 | q:before, q:after { 41 | content: ''; 42 | content: none; 43 | } 44 | table { 45 | border-collapse: collapse; 46 | border-spacing: 0; 47 | } -------------------------------------------------------------------------------- /css/jqueryui/redmond/images/ui-bg_flat_0_aaaaaa_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/css/jqueryui/redmond/images/ui-bg_flat_0_aaaaaa_40x100.png -------------------------------------------------------------------------------- /css/jqueryui/redmond/images/ui-bg_flat_55_fbec88_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/css/jqueryui/redmond/images/ui-bg_flat_55_fbec88_40x100.png -------------------------------------------------------------------------------- /css/jqueryui/redmond/images/ui-bg_glass_75_d0e5f5_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/css/jqueryui/redmond/images/ui-bg_glass_75_d0e5f5_1x400.png -------------------------------------------------------------------------------- /css/jqueryui/redmond/images/ui-bg_glass_85_dfeffc_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/css/jqueryui/redmond/images/ui-bg_glass_85_dfeffc_1x400.png -------------------------------------------------------------------------------- /css/jqueryui/redmond/images/ui-bg_glass_95_fef1ec_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/css/jqueryui/redmond/images/ui-bg_glass_95_fef1ec_1x400.png -------------------------------------------------------------------------------- /css/jqueryui/redmond/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/css/jqueryui/redmond/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png -------------------------------------------------------------------------------- /css/jqueryui/redmond/images/ui-bg_inset-hard_100_f5f8f9_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/css/jqueryui/redmond/images/ui-bg_inset-hard_100_f5f8f9_1x100.png -------------------------------------------------------------------------------- /css/jqueryui/redmond/images/ui-bg_inset-hard_100_fcfdfd_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/css/jqueryui/redmond/images/ui-bg_inset-hard_100_fcfdfd_1x100.png -------------------------------------------------------------------------------- /css/jqueryui/redmond/images/ui-icons_217bc0_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/css/jqueryui/redmond/images/ui-icons_217bc0_256x240.png -------------------------------------------------------------------------------- /css/jqueryui/redmond/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/css/jqueryui/redmond/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /css/jqueryui/redmond/images/ui-icons_469bdd_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/css/jqueryui/redmond/images/ui-icons_469bdd_256x240.png -------------------------------------------------------------------------------- /css/jqueryui/redmond/images/ui-icons_6da8d5_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/css/jqueryui/redmond/images/ui-icons_6da8d5_256x240.png -------------------------------------------------------------------------------- /css/jqueryui/redmond/images/ui-icons_cd0a0a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/css/jqueryui/redmond/images/ui-icons_cd0a0a_256x240.png -------------------------------------------------------------------------------- /css/jqueryui/redmond/images/ui-icons_d8e7f3_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/css/jqueryui/redmond/images/ui-icons_d8e7f3_256x240.png -------------------------------------------------------------------------------- /css/jqueryui/redmond/images/ui-icons_f9bd01_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/css/jqueryui/redmond/images/ui-icons_f9bd01_256x240.png -------------------------------------------------------------------------------- /css/panes/closed.css: -------------------------------------------------------------------------------- 1 | header { 2 | height: 70px; 3 | } 4 | 5 | footer.debugEnabled { 6 | height: 20px; 7 | } 8 | 9 | section#main { 10 | top: 70px; 11 | bottom: 0px; 12 | } 13 | 14 | section#main.debugEnabled { 15 | top: 70px; 16 | bottom: 20px; 17 | } 18 | 19 | .ftItemRowContent { 20 | margin-left: 3px; 21 | } 22 | 23 | .ftItemTextAffix { 24 | font-style: italic; 25 | color: green; 26 | } 27 | 28 | 29 | /*.ftRoot > .ftChildren > .ftRowNode > .ftItemRow > .ftNode, 30 | .ftRowNode[rowtype=header] > .ftChildren > .ftRowNode > .ftItemRow > .ftNode, 31 | .ftRoot > .ftChildren > .ftRowNode > .ftItemRow > .ftExpander, 32 | .ftRowNode[rowtype=header] > .ftChildren > .ftRowNode > .ftItemRow > .ftExpander 33 | { 34 | width: 0px; 35 | margin-right: 2px; 36 | } 37 | 38 | .ftChildren .ftRowNode .ftChildren .ftRowNode .ftChildren 39 | { 40 | margin-left: -7px; 41 | }*/ 42 | 43 | .ftNode, .ftExpander { 44 | position: relative; 45 | left: 21px; 46 | margin-left: -16px; 47 | opacity: 0.0; 48 | } 49 | 50 | .ftExpander:hover { 51 | opacity: 1.0; 52 | } 53 | 54 | .ftRowNode.ftCollapsed > .ftItemRow > .ftExpander + .ftItemRowContent > .ftInnerRow > .ftRowIcon { 55 | opacity: 0.25; 56 | } 57 | 58 | .ftExpander:hover + .ftItemRowContent > .ftInnerRow > .ftRowIcon { 59 | visibility: hidden; 60 | } 61 | 62 | .ftExpander + .ftItemRowContent > .ftInnerRow > .ftRowIcon:hover { 63 | visibility: hidden; 64 | } 65 | 66 | /*.ftExpander + .ftItemRowContent > .ftInnerRow > .ftRowIcon { 67 | visibility: hidden !important; 68 | opacity: 0.0; 69 | z-index: -255; 70 | } 71 | */ 72 | .ftRowIcon { 73 | opacity: 1.0; 74 | } 75 | 76 | .ftRowNode.ftCollapsed > .ftItemRow > .ftExpander { 77 | opacity: 1.0; 78 | } 79 | 80 | .ftRowNode.ftCollapsed > .ftItemRow:hover > .ftExpander { 81 | opacity: 1.0; 82 | } 83 | 84 | /*.ftRowNode.ftCollapsed > .ftItemRow:hover > .ftItemRowContent > .ftInnerRow > .ftRowIcon { 85 | visibility: hidden; 86 | } 87 | */ -------------------------------------------------------------------------------- /css/panes/external-site.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | width: 100%; 4 | } 5 | 6 | #contentFrame { 7 | width: 100%; 8 | height: 100%; 9 | } 10 | 11 | #loadingHint { 12 | display: none; 13 | position: fixed; 14 | z-index: -1000; 15 | font-family: 'Segoe UI', sans-serif; 16 | font-size: 16px; 17 | margin: 2em 1em 0em 1em; 18 | padding-left: 1.5em; 19 | background-image: url('/images/throbber-slow.gif'); 20 | background-repeat: no-repeat; 21 | font-style: italic; 22 | } 23 | 24 | #loadingURL { 25 | font-size: 10pt; 26 | font-family: monospace; 27 | font-style: normal; 28 | color: blue; 29 | margin-top: 0.5em; 30 | } 31 | 32 | #loadingError { 33 | font-size: 10pt; 34 | font-style: normal; 35 | font-weight: bold; 36 | color: red; 37 | margin-top: 1.5em; 38 | } 39 | -------------------------------------------------------------------------------- /css/panes/notepad.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Segoe UI', 'MS Sans Serif', sans-serif; 3 | margin: 5px; 4 | } 5 | 6 | #main { 7 | height: 100%; 8 | width: 100%; 9 | } 10 | 11 | #header, #footer { 12 | height: 1px; 13 | } 14 | 15 | #header { 16 | padding-bottom: 6px; 17 | } 18 | 19 | #notepad { 20 | width: 100%; 21 | height: 100%; 22 | font-family: 'Consolas', 'Lucida Console', monospace; 23 | font-size: 12px; 24 | } 25 | 26 | #footer { 27 | font-size: 11px; 28 | color: #666; 29 | } -------------------------------------------------------------------------------- /css/panes/pages.css: -------------------------------------------------------------------------------- 1 | header { 2 | height: 30px; 3 | } 4 | 5 | footer.debugEnabled { 6 | height: 20px; 7 | } 8 | 9 | section#main { 10 | top: 30px; 11 | bottom: 0px; 12 | } 13 | 14 | section#main.debugEnabled { 15 | bottom: 20px; 16 | } 17 | -------------------------------------------------------------------------------- /css/panes/whatsnew.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Segoe UI', 'MS Sans Serif', sans-serif; 3 | font-size: 12px; 4 | margin: 5px; 5 | } 6 | 7 | a { 8 | font-weight: bold; 9 | } 10 | 11 | ul { 12 | padding-left: 1.5em; 13 | } 14 | 15 | hr { 16 | color: #000; 17 | background-color: #000; 18 | height: 1px; 19 | border: none; 20 | } 21 | 22 | #heading { 23 | font-weight: bold; 24 | font-size: 16px; 25 | } 26 | 27 | input[type=checkbox] { 28 | position: relative; 29 | top: 2px; 30 | } 31 | -------------------------------------------------------------------------------- /css/sidebar.css: -------------------------------------------------------------------------------- 1 | body { 2 | overflow: hidden; 3 | font-size: 16px; 4 | } 5 | 6 | #main { width: 100%; height: 100%;} 7 | 8 | #main td { width: 1px; } 9 | 10 | #sidebars iframe { width: 100%; height: 100%; } 11 | 12 | #header { 13 | height: 2.25em; 14 | -webkit-user-select: none; 15 | user-select: none; 16 | width: 1000%; 17 | } 18 | 19 | #headerBox { 20 | background: -webkit-linear-gradient(top, rgba(181,189,200,1) 0%,rgba(130,140,149,1) 73%); 21 | padding: 0.25em; 22 | position: fixed; 23 | height: 1.625em; 24 | width: 100%; 25 | border-bottom: 1px solid #222; 26 | overflow: hidden; 27 | } 28 | 29 | #headerRight { 30 | float: right; 31 | padding-right: 0.5em; 32 | } 33 | 34 | #sidebarButtons { 35 | overflow: hidden; 36 | } 37 | 38 | #sidebarButtons > li { 39 | display: inline-block; 40 | margin-right: 3px; 41 | margin-bottom: 1em; 42 | } 43 | 44 | #sidebarButtons > li > div > img, #optionsButton > img { 45 | width: 16px; 46 | height: 16px; 47 | margin: 3px 5px; 48 | } 49 | 50 | /*#sidebarButtons > li.selected > div { 51 | background: -webkit-linear-gradient(top, rgba(249,215,129,1) 0%,rgba(251,193,33,1) 50%,rgba(214,157,0,1) 51%,rgba(249,207,93,1) 100%); 52 | border-color: #444; 53 | border-width: 2px; 54 | margin: 0px; 55 | } 56 | */ 57 | 58 | /*#sidebarButtons > li.selected:hover > div { 59 | background: -webkit-linear-gradient(top, rgba(252,234,188,1) 0%,rgba(252,206,78,1) 50%,rgba(248,182,0,1) 51%,rgba(251,223,147,1) 100%); 60 | } 61 | */ 62 | 63 | #sidebarButtons > li > div, #optionsButton { 64 | display: block; 65 | background: -webkit-linear-gradient(top, rgba(196,203,212,1) 0%,rgba(155,164,171,1) 73%); 66 | border-radius: 5px; 67 | border: 1px solid #666; 68 | height: 22px; 69 | width: 26px; 70 | text-decoration: none; 71 | color: black; 72 | font-family: sans-serif; 73 | font-size: 14px; 74 | margin: 1px; 75 | box-shadow: -1px -1px 2px 1px rgba(0, 0, 0, 0.15) inset, 1px 1px 2px 1px rgba(255, 255, 255, 0.15) inset; 76 | } 77 | 78 | #sidebarButtons > li > div:hover, #optionsButton:hover { 79 | background: -webkit-linear-gradient(top, rgba(207,212,219,1) 0%,rgba(174,180,186,1) 73%); 80 | } 81 | 82 | #sidebarButtons > li.selected > div { 83 | background: -webkit-linear-gradient(top, rgba(217,222,229,1) 0%,rgba(184,190,196,1) 73%); 84 | border: 1px solid black; 85 | } 86 | 87 | #sidebarButtons > li.selected > div:hover { 88 | background: -webkit-linear-gradient(top, rgba(227,232,239,1) 0%,rgba(194,200,206,1) 73%); 89 | } 90 | 91 | #sidebarButtons > li.mousedown > div, #optionsButton.mousedown { 92 | box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.15) inset, -1px -1px 2px 1px rgba(255, 255, 255, 0.15) inset; 93 | } 94 | 95 | #sidebarButtons > li.mousedown > div > img, #optionsButton.mousedown > img { 96 | position: relative; 97 | top: 1px; 98 | left: 1px; 99 | } 100 | 101 | #optionsButtonIcon { 102 | height: 16px; 103 | width: 16px; 104 | } 105 | 106 | .tooltip { 107 | display: none; 108 | background: -webkit-linear-gradient(top, rgba(242,245,246,1) 0%,rgba(227,234,237,1) 100%); 109 | border: 1px solid #333; 110 | border-radius: 3px; 111 | padding: 3px 5px 3px 5px; 112 | font-size: 12px; 113 | font-family: 'segoe ui', 'ms sans serif', sans-serif; 114 | box-shadow: 2px 2px 4px rgba(0,0,0,0.4); 115 | white-space: nowrap; 116 | } -------------------------------------------------------------------------------- /detect-monitor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Sidewise Monitor Detection 4 | 5 | 6 |
7 | 8 |
9 | -------------------------------------------------------------------------------- /images/clear_highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/clear_highlight.png -------------------------------------------------------------------------------- /images/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/close.png -------------------------------------------------------------------------------- /images/close_branch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/close_branch.png -------------------------------------------------------------------------------- /images/cloud1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/cloud1.png -------------------------------------------------------------------------------- /images/copy_url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/copy_url.png -------------------------------------------------------------------------------- /images/create_tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/create_tab.png -------------------------------------------------------------------------------- /images/favicon/bookmarks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/favicon/bookmarks.png -------------------------------------------------------------------------------- /images/favicon/downloads.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/favicon/downloads.png -------------------------------------------------------------------------------- /images/favicon/extensions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/favicon/extensions.png -------------------------------------------------------------------------------- /images/favicon/history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/favicon/history.png -------------------------------------------------------------------------------- /images/favicon/newtab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/favicon/newtab.png -------------------------------------------------------------------------------- /images/favicon/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/favicon/settings.png -------------------------------------------------------------------------------- /images/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/folder.png -------------------------------------------------------------------------------- /images/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/help.png -------------------------------------------------------------------------------- /images/hibernate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/hibernate.png -------------------------------------------------------------------------------- /images/hibernate_branch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/hibernate_branch.png -------------------------------------------------------------------------------- /images/hibernate_wake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/hibernate_wake.png -------------------------------------------------------------------------------- /images/highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/highlight.png -------------------------------------------------------------------------------- /images/incognito-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/incognito-16.png -------------------------------------------------------------------------------- /images/incognito-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/incognito-32.png -------------------------------------------------------------------------------- /images/install_indicator_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/install_indicator_arrow.png -------------------------------------------------------------------------------- /images/label.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/label.png -------------------------------------------------------------------------------- /images/nav/closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/nav/closed.png -------------------------------------------------------------------------------- /images/nav/grooveshark.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/nav/grooveshark.ico -------------------------------------------------------------------------------- /images/nav/notepad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/nav/notepad.png -------------------------------------------------------------------------------- /images/nav/pages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/nav/pages.png -------------------------------------------------------------------------------- /images/nav/reddit.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/nav/reddit.gif -------------------------------------------------------------------------------- /images/nav/reddit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/nav/reddit.png -------------------------------------------------------------------------------- /images/nav/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/nav/settings.png -------------------------------------------------------------------------------- /images/nav/tribunal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/nav/tribunal.png -------------------------------------------------------------------------------- /images/nav/whatsnew.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/nav/whatsnew.gif -------------------------------------------------------------------------------- /images/nav/whatsnew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/nav/whatsnew.png -------------------------------------------------------------------------------- /images/options/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/options/facebook.png -------------------------------------------------------------------------------- /images/options/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/options/twitter.png -------------------------------------------------------------------------------- /images/panty-pull-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/panty-pull-128.png -------------------------------------------------------------------------------- /images/panty-pull-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/panty-pull-48.png -------------------------------------------------------------------------------- /images/pinned.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/pinned.png -------------------------------------------------------------------------------- /images/reload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/reload.png -------------------------------------------------------------------------------- /images/sidewise.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/sidewise.ico -------------------------------------------------------------------------------- /images/sidewise_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/sidewise_icon_128.png -------------------------------------------------------------------------------- /images/sidewise_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/sidewise_icon_16.png -------------------------------------------------------------------------------- /images/sidewise_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/sidewise_icon_256.png -------------------------------------------------------------------------------- /images/sidewise_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/sidewise_icon_32.png -------------------------------------------------------------------------------- /images/sidewise_icon_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/sidewise_icon_48.png -------------------------------------------------------------------------------- /images/tab-single-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/tab-single-16.png -------------------------------------------------------------------------------- /images/tab-single-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/tab-single-32.png -------------------------------------------------------------------------------- /images/tab-stack-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/tab-stack-16.png -------------------------------------------------------------------------------- /images/tab-stack-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/tab-stack-32.png -------------------------------------------------------------------------------- /images/text_indent_promote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/text_indent_promote.png -------------------------------------------------------------------------------- /images/text_indent_remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/text_indent_remove.png -------------------------------------------------------------------------------- /images/throbber-slow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/throbber-slow.gif -------------------------------------------------------------------------------- /images/toggle_arrow_collapsed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/toggle_arrow_collapsed.png -------------------------------------------------------------------------------- /images/toggle_arrow_collapsed_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/toggle_arrow_collapsed_hover.png -------------------------------------------------------------------------------- /images/toggle_arrow_expanded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/toggle_arrow_expanded.png -------------------------------------------------------------------------------- /images/toggle_arrow_expanded_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/toggle_arrow_expanded_hover.png -------------------------------------------------------------------------------- /images/unpin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/unpin.png -------------------------------------------------------------------------------- /images/wake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/wake.png -------------------------------------------------------------------------------- /images/wake_branch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/wake_branch.png -------------------------------------------------------------------------------- /images/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/warning.png -------------------------------------------------------------------------------- /images/x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelpt/sidewise/a1c43b99b01060b44f7268467db48dbecc6c8881/images/x.gif -------------------------------------------------------------------------------- /js/bg/classes/ChromeWindowFocusTracker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @constructor 3 | */ 4 | var ChromeWindowFocusTracker = function(onInitialized) 5 | { 6 | this.init(onInitialized); 7 | }; 8 | 9 | ChromeWindowFocusTracker.prototype = { 10 | 11 | init: function(onInitialized) { 12 | this.windowIds = []; 13 | this.chromeHasFocus = false; 14 | 15 | var self = this; 16 | chrome.windows.getAll(null, function(wins) { 17 | if (!wins) { 18 | // try again in a bit 19 | setTimeout(function() { self.init(onInitialized); }, 5000); 20 | return; 21 | } 22 | 23 | // collect all existing windows 24 | for (var i in wins) { 25 | self.windowIds.push(wins[i].id); 26 | } 27 | 28 | function _retry() { 29 | // try again in a bit 30 | setTimeout(function() { self.init(onInitialized); }, 5000); 31 | return; 32 | } 33 | 34 | function _gotLastFocused(win) { 35 | if (!win) { 36 | _retry(); 37 | return; 38 | } 39 | self.setFocused(win.id); 40 | self.chromeHasFocus = true; 41 | if (onInitialized) { 42 | onInitialized(win); 43 | } 44 | return; 45 | } 46 | 47 | // set currently focused window 48 | chrome.windows.getAll(function(wins) { // use .getAll() first so we can avoid exception in .getLastFocused 49 | if (!wins || wins.length == 0) { // when .getAll() returns no windows 50 | _retry(); 51 | return; 52 | } 53 | chrome.windows.getLastFocused(null, _gotLastFocused); 54 | }); 55 | }); 56 | }, 57 | 58 | getFocused: function(topIndex) { 59 | if (this.windowIds.length == 0) { 60 | return null; 61 | } 62 | topIndex = topIndex || 0; 63 | var index = Math.max(0, this.windowIds.length - topIndex - 1); 64 | return this.windowIds[index]; 65 | }, 66 | 67 | setFocused: function(windowId) { 68 | if (windowId == sidebarHandler.windowId) { 69 | return; 70 | } 71 | this.remove(windowId); 72 | this.windowIds.push(windowId); 73 | return true; 74 | }, 75 | 76 | remove: function(windowId) { 77 | var index = this.windowIds.indexOf(windowId); 78 | if (index == -1) { 79 | log('Did not find windowId to remove', windowId, this.windowIds); 80 | return false; 81 | } 82 | this.windowIds.splice(index, 1); 83 | return true; 84 | }, 85 | 86 | getTopFocusableWindow: function(callback, topIndex) { 87 | var length = this.windowIds.length; 88 | 89 | if (length == 0 || topIndex >= length) { 90 | callback(null); 91 | return; 92 | } 93 | 94 | topIndex = topIndex || 0; 95 | var index = length - topIndex - 1; 96 | var windowId = this.windowIds[index]; 97 | var tracker = this; 98 | chrome.windows.get(windowId, function(win) { 99 | if (win && win.state != 'minimized' && win.type != 'popup') { 100 | callback(win); // found a focusable window 101 | return; 102 | } 103 | tracker.getTopFocusableWindow(callback, topIndex + 1); 104 | }); 105 | } 106 | } -------------------------------------------------------------------------------- /js/bg/classes/DataTreeNode.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * @class 5 | * Barebones node object for use as a node in DataTree. 6 | * @see DataTree 7 | * @constructor 8 | */ 9 | var DataTreeNode = function() 10 | { 11 | this.UUID = generateGuid(); 12 | this.id = this.UUID; 13 | this.elemType = 'node'; 14 | this.children = []; 15 | this.parent = null; 16 | this.root = null; 17 | }; 18 | 19 | DataTreeNode.prototype = { 20 | 21 | toString: function() { 22 | return this.elemType + ':' + this.id; 23 | }, 24 | 25 | parents: function() { 26 | var parents = []; 27 | var parent = this.parent; 28 | while (parent) { 29 | parents.push(parent); 30 | parent = parent.parent; 31 | } 32 | return parents; 33 | }, 34 | 35 | topParent: function() { 36 | var top = this; 37 | while (top.parent && !top.parent.isRoot) { 38 | top = top.parent; 39 | } 40 | if (top === this) { 41 | return null; 42 | } 43 | return top; 44 | }, 45 | 46 | siblings: function() { 47 | return this.parent.children; 48 | }, 49 | 50 | siblingIndex: function() { 51 | return this.parent.children.indexOf(this); 52 | }, 53 | 54 | beforeSibling: function() { 55 | var index = this.siblingIndex() - 1; 56 | if (index < 0) { 57 | return; 58 | } 59 | return this.parent.children[index]; 60 | }, 61 | 62 | beforeSiblings: function() { 63 | return this.siblings().slice(0, this.siblingIndex()).reverse(); 64 | }, 65 | 66 | afterSibling: function() { 67 | var index = this.siblingIndex() + 1; 68 | if (index >= this.parent.children.length) { 69 | return; 70 | } 71 | return this.parent.children[index]; 72 | }, 73 | 74 | afterSiblings: function() { 75 | return this.siblings().slice(this.siblingIndex() + 1); 76 | }, 77 | 78 | precedingNodes: function(topmostParent) { 79 | var found = false; 80 | var self = this; 81 | if (!topmostParent) { 82 | topmostParent = this.root; 83 | } 84 | return this.root.hostTree.filter(function(e) { 85 | if (e === self) { 86 | found = true; 87 | return false; 88 | } 89 | if (found) { 90 | return false; 91 | } 92 | return true; 93 | }, topmostParent.children); 94 | }, 95 | 96 | preceding: function(matchFn, topmostParent) { 97 | var p = this.precedingNodes(topmostParent); 98 | if (p.length == 0) { 99 | return; 100 | } 101 | 102 | if (!matchFn) { 103 | return p[p.length - 1]; 104 | } 105 | 106 | var r = first(p.reverse(), function(e) { return matchFn(e); }); 107 | 108 | if (r) { 109 | return r[1]; 110 | } 111 | return; 112 | }, 113 | 114 | followingNodes: function(topmostParent) { 115 | var found = false; 116 | var self = this; 117 | if (!topmostParent) { 118 | topmostParent = this.root; 119 | } 120 | return this.root.hostTree.filter(function(e) { 121 | if (e === self) { 122 | found = true; 123 | return false; 124 | } 125 | if (!found) { 126 | return false; 127 | } 128 | return true; 129 | }, topmostParent.children); 130 | }, 131 | 132 | following: function(matchFn, topmostParent) { 133 | var p = this.followingNodes(topmostParent); 134 | if (p.length == 0) { 135 | return; 136 | } 137 | 138 | if (!matchFn) { 139 | return p[0]; 140 | } 141 | 142 | var r = first(p, function(e) { return matchFn(e); }); 143 | 144 | if (r) { 145 | return r[1]; 146 | } 147 | return; 148 | } 149 | 150 | 151 | }; 152 | 153 | extendClass(DataTreeNode, Object, DataTreeNode.prototype); -------------------------------------------------------------------------------- /js/bg/classes/DataTreeRootNode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @constructor 3 | * @extends DataTreeNode 4 | */ 5 | var DataTreeRootNode = function(hostTree) 6 | { 7 | this.$base(); 8 | this.type = 'root'; 9 | this.isRoot = true; 10 | this.hostTree = hostTree; 11 | }; 12 | 13 | extendClass(DataTreeRootNode, DataTreeNode, {}); 14 | -------------------------------------------------------------------------------- /js/bg/classes/FolderNode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @constructor 3 | * @extends PageTreeNode 4 | */ 5 | var FolderNode = function(label) 6 | { 7 | this.$base(); 8 | 9 | this.elemType = 'folder'; 10 | this.id = 'f' + this.UUID; 11 | this.title = ''; 12 | this.label = label; 13 | }; 14 | 15 | extendClass(FolderNode, PageTreeNode, {}); 16 | 17 | -------------------------------------------------------------------------------- /js/bg/classes/GhostNode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @constructor 3 | * @extends DataTreeNode 4 | */ 5 | var GhostNode = function(id, elemType) 6 | { 7 | this.$base(); 8 | 9 | this.elemType = 'ghost'; 10 | this.id = id; 11 | this.ghostType = elemType; 12 | this.alive = true; 13 | }; 14 | 15 | GhostNode.prototype = { 16 | }; 17 | 18 | extendClass(GhostNode, DataTreeNode, GhostNode.prototype); 19 | -------------------------------------------------------------------------------- /js/bg/classes/HeaderNode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @constructor 3 | * @extends PageTreeNode 4 | */ 5 | var HeaderNode = function() 6 | { 7 | this.$base(); 8 | 9 | this.elemType = 'header'; 10 | this.id = 'h' + this.UUID; 11 | this.collecting = false; // Set to true when a HeaderNode in the recently closed tree is 'collecting' tab closures 12 | }; 13 | 14 | HeaderNode.prototype = { 15 | }; 16 | 17 | extendClass(HeaderNode, PageTreeNode, HeaderNode.prototype); 18 | -------------------------------------------------------------------------------- /js/bg/classes/IconTester.js: -------------------------------------------------------------------------------- 1 | // Used to detect when Chrome is showing a malware warning page, which can cause the Sidewide sidebar to also 2 | // show as a malware warning page due to its loading of favicons from malware-warning sites. 3 | 4 | var IconTester = function() { 5 | this.testTab = undefined; 6 | this.testResetTime = undefined; 7 | this.testOnFinished = undefined; 8 | this.badNodesFound = []; 9 | }; 10 | 11 | var IconTesterDomWindow = undefined; 12 | 13 | IconTester.prototype = { 14 | testIcons: function() { 15 | preventTestIconsCheck = true; 16 | var msg = 'Sidewise\'s sidebar appears to be showing a Chrome malware warning page. This can happen when you visit a ' + 17 | 'page which Chrome believes to contain malware. Because Sidewise shows the favicon of such pages in the sidebar, ' + 18 | 'this also triggers Chrome\'s malware warning within the sidebar itself.\n\n' + 19 | 'Sidewise can try to fix this by identifying the bad favicon and removing it from the sidebar.\n\n' + 20 | 'This process will take about a minute. DO NOT interact with Chrome until it is complete.'; 21 | if (!confirm(msg)) { 22 | alert('You declined to do the favicon malware test. Sidewise won\'t ask again until you restart Chrome.'); 23 | return; 24 | } 25 | 26 | this.testTab = undefined; 27 | this.testOnFinished = undefined; 28 | 29 | try { 30 | if (sidebarHandler.sidebarExists()) { 31 | sidebarHandler.remove(); 32 | } 33 | this.doTestIcons(this.onTestIconsFinished); 34 | } 35 | catch (ex) { 36 | this.destroyTestIconsPage(); 37 | alert('Sorry, but something went wrong during the testing process. No changes have been made to your tree.'); 38 | } 39 | }, 40 | 41 | onTestIconsFinished: function(badNodes) { 42 | var self = this; 43 | 44 | if (badNodes.length > 0) { 45 | badNodes.forEach(function(e) { 46 | e.favicon = 'chrome://favicon'; 47 | }); 48 | savePageTreeToLocalStorage(tree, 'pageTree', true); 49 | savePageTreeToLocalStorage(recentlyClosedTree, 'recentlyClosedTree', true); 50 | 51 | // Remember what we have found so far and run the test suite again 52 | // until we're all clear 53 | this.badNodesFound.push(badNodes); 54 | this.doTestIcons(this.onTestIconsFinished); 55 | return; 56 | } 57 | this.destroyTestIconsPage(); 58 | 59 | setTimeout(function() { 60 | sidebarHandler.createWithDockState(settings.get('dockState')); 61 | if (self.badNodesFound.length == 0) { 62 | setTimeout(function() { 63 | alert('Sidewise did not find any favicons that caused the malware page problem. Sorry!\n\n' + 64 | 'If you are still seeing the malware page in the sidebar, restarting Chrome and rerunning this test will usually fix it.'); 65 | }, 100); 66 | return; 67 | } 68 | setTimeout(function() { 69 | alert('The testing process is complete and Sidewise has detected ' + self.badNodesFound.length.toString() + ' favicon(s) that caused the malware page problem.' + 70 | '\n\nSidewise has removed these favicons from the sidebar and the problem should now be resolved.'); 71 | }, 100); 72 | preventTestIconsCheck = false; 73 | }, 500); 74 | }, 75 | 76 | doTestIcons: function(onFinished) { 77 | this.testTab = undefined; 78 | this.testOnFinished = onFinished; 79 | this.startTestIconsLoops(); 80 | }, 81 | 82 | createTestIconsPage: function(onCreated) { 83 | var self = this; 84 | IconTesterDomWindow = undefined; 85 | chrome.tabs.create({ url: 'test_icons.html' }, function(tab) { 86 | self.onTestIconsTabCreated(tab, onCreated); 87 | }); 88 | }, 89 | 90 | resetTestIconsPage: function(onResetDone) { 91 | var self = this; 92 | if (this.testTab) { 93 | this.destroyTestIconsPage(function() { 94 | self.createTestIconsPage(onResetDone); 95 | }); 96 | return; 97 | } 98 | this.createTestIconsPage(onResetDone); 99 | }, 100 | 101 | destroyTestIconsPage: function(onDestroyed) { 102 | var self = this; 103 | onDestroyed = onDestroyed || function() { }; 104 | if (this.testTab) { 105 | chrome.tabs.remove(this.testTab.id, function() { 106 | self.testTab = undefined; 107 | IconTesterDomWindow = undefined; 108 | onDestroyed(); 109 | }); 110 | return; 111 | } 112 | onDestroyed(); 113 | }, 114 | 115 | onTestIconsTabCreated: function(tab, onReady) { 116 | var self = this; 117 | if (!tab) { 118 | throw new Error('Test icons tab failed to load.'); 119 | } 120 | this.testTab = tab; 121 | setTimeout(function() { 122 | if (!IconTesterDomWindow) { 123 | self.onTestIconsTabCreated(tab, onReady); 124 | return; 125 | } 126 | onReady(); 127 | }, 250); 128 | }, 129 | 130 | startTestIconsLoops: function() { 131 | var self = this; 132 | var badNodes = []; 133 | this.testIconsInTree(tree, function(badNode) { 134 | if (badNode) { 135 | console.warn('GOT IT', badNode.id, badNode.favicon); 136 | badNodes.push(badNode); 137 | } 138 | self.testIconsInTree(recentlyClosedTree, function(badNode) { 139 | if (badNode) { 140 | console.warn('RC GOT IT', badNode.id, badNode.favicon); 141 | badNodes.push(badNode); 142 | } 143 | self.testOnFinished(badNodes); 144 | }); 145 | }); 146 | }, 147 | 148 | testIconsInTree: function(tree, onFinished) { 149 | var self = this; 150 | var nodes = tree.filter(function(e) { 151 | return e instanceof PageNode && e.favicon; 152 | }); 153 | this.testResetTime = 5000; 154 | this.testIconBatch(nodes, function() { 155 | self.destroyTestIconsPage(function() { onFinished(); }); 156 | }, function(badNode) { 157 | self.destroyTestIconsPage(function() { onFinished(badNode); }); 158 | }); 159 | }, 160 | 161 | testIconBatch: function(nodes, onAllValid, onFoundInvalid) { 162 | var self = this; 163 | this.resetTestIconsPage(function() { 164 | for (var i = 0; i < nodes.length; i++) { 165 | var node = nodes[i]; 166 | IconTesterDomWindow.testIcon(node.favicon); 167 | }; 168 | setTimeout(function() { 169 | chrome.tabs.get(self.testTab.id, function(t) { 170 | if (t.title.toLowerCase().indexOf('malware') >= 0) { 171 | // one of the tested icons had a problem 172 | if (nodes.length == 1) { 173 | // found the bad icon 174 | onFoundInvalid(nodes[0]); 175 | return; 176 | } 177 | // more than one tested icon had a problem, 178 | // so split the test batch in half and test 179 | // each half separately 180 | var batch1 = nodes.slice(0, nodes.length / 2); 181 | var batch2 = nodes.slice(nodes.length / 2); 182 | 183 | self.testResetTime = 2000; 184 | self.testIconBatch(batch1, function() { 185 | // all the icons in the first batch are valid 186 | // so test the second batch 187 | self.testIconBatch(batch2, function() { 188 | // all the icons in the second batch are valid 189 | // but this should be impossible 190 | throw new Error('Found a bad icon in an earlier iteration but did not find it in subdivision process!'); 191 | }, onFoundInvalid); 192 | }, onFoundInvalid); 193 | } 194 | else { 195 | // no icon came up on error 196 | onAllValid(); 197 | } 198 | }); 199 | }, self.testResetTime); 200 | }); 201 | } 202 | }; 203 | -------------------------------------------------------------------------------- /js/bg/classes/PageNode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @constructor 3 | * @extends PageTreeNode 4 | */ 5 | var PageNode = function(tab, overrideStatus) 6 | { 7 | this.$base(); 8 | 9 | this.id = 'p' + this.UUID; 10 | this.elemType = 'page'; 11 | this.referrer = ''; 12 | this.historylength = 1; 13 | this.placed = false; 14 | this.unread = false; 15 | this.smartFocusParentTabId = null; 16 | this.initialCreation = false; 17 | this.restored = false; 18 | this.incognito = tab.incognito || false; 19 | this.sessionGuid = null; 20 | this.mediaState = null; 21 | this.mediaTime = null; 22 | this.restored = false; 23 | this.restorable = false; 24 | 25 | if (tab) { 26 | var url = tab.url ? dropUrlHash(tab.url) : ''; 27 | this.chromeId = tab.id; 28 | this.windowId = tab.windowId; 29 | this.openerTabId = tab.openerTabId; 30 | this.index = tab.index; 31 | this.url = tab.url; 32 | this.favicon = getBestFavIconUrl(tab.favIconUrl, url); 33 | this.title = getBestPageTitle(tab.title, tab.url); 34 | this.status = overrideStatus || tab.status; 35 | this.pinned = tab.pinned; 36 | } 37 | else { 38 | this.hibernated = true; 39 | this.chromeId = null; 40 | this.windowId = null; 41 | this.openerTabId = null; 42 | this.index = null; 43 | this.url = null; 44 | this.favicon = null; 45 | this.title = null; 46 | this.status = overrideStatus || 'complete'; 47 | this.pinned = false; 48 | } 49 | }; 50 | 51 | PageNode.prototype = { 52 | isTab: function() { 53 | return !this.hibernated; 54 | } 55 | }; 56 | 57 | extendClass(PageNode, PageTreeNode, PageNode.prototype); 58 | -------------------------------------------------------------------------------- /js/bg/classes/PageTreeNode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @constructor 3 | * @extends DataTreeNode 4 | */ 5 | var PageTreeNode = function() 6 | { 7 | this.$base(); 8 | 9 | this.title = ''; 10 | this.label = ''; 11 | this.highlighted = false; 12 | this.collapsed = false; 13 | this.hibernated = false; 14 | this.restorable = false; 15 | this.createdOn = Date.now(); 16 | }; 17 | 18 | PageTreeNode.prototype = { 19 | isTab: function() { 20 | return false; 21 | } 22 | }; 23 | 24 | extendClass(PageTreeNode, DataTreeNode, PageTreeNode.prototype); 25 | -------------------------------------------------------------------------------- /js/bg/classes/SidebarPaneCatalog.js: -------------------------------------------------------------------------------- 1 | var SidebarPaneCatalog = function() { 2 | this.$base(config.AVAILABLE_PANES); 3 | }; 4 | 5 | SidebarPaneCatalog.prototype = { 6 | 7 | getPane: function(id) { 8 | return this.getItem(id); 9 | }, 10 | 11 | getPaneIds: function() { 12 | return this.getIds(); 13 | }, 14 | 15 | loadState: function() { 16 | var saved = settings.get('sidebarPanesState', []); 17 | 18 | this.items = []; 19 | var seenPanes = []; 20 | 21 | var availPanes = clone(config.AVAILABLE_PANES); 22 | var availPaneIds = availPanes.map(function(e) { return e.id; }); 23 | var lastEnabledPaneIndex = -1; 24 | 25 | // add panes with a known state from settings to this.panes 26 | for (var i = 0; i < saved.length; i++) { 27 | var s = saved[i]; 28 | seenPanes.push(s.id); 29 | var index = availPaneIds.indexOf(s.id); 30 | if (index == -1) { 31 | log('State data found for pane that is not in available panes', s, availPanes); 32 | continue; 33 | } 34 | var pane = availPanes[index]; 35 | pane.enabled = s.enabled; 36 | 37 | if (pane.enabled) { 38 | lastEnabledPaneIndex++; 39 | } 40 | 41 | this.items.push(pane); 42 | } 43 | 44 | // add panes from the available panes which did not have a known state from settings 45 | // (allows us to add new panes to the built in list over time) 46 | for (var i = 0; i < availPanes.length; i++) { 47 | var pane = availPanes[i]; 48 | if (seenPanes.indexOf(pane.id) > -1) { 49 | // already added 50 | continue; 51 | } 52 | // add new default panes at the end 53 | this.items.push(pane); 54 | } 55 | }, 56 | 57 | saveState: function() { 58 | return this.$super('saveState')('sidebarPanesState'); 59 | }, 60 | 61 | addPane: function(id, enabled, url, label, icon) { 62 | var pane = { id: id, enabled: enabled, url: url, label: label, icon: icon }; 63 | return this.appendItem(pane); 64 | }, 65 | 66 | removePane: function(id) { 67 | return this.removeItem(id); 68 | }, 69 | 70 | reorderPane: function(id, newIndex) { 71 | return this.reorderItem(id, newIndex); 72 | } 73 | 74 | }; 75 | 76 | extendClass(SidebarPaneCatalog, Catalog, SidebarPaneCatalog.prototype); 77 | -------------------------------------------------------------------------------- /js/bg/classes/WindowNode.js: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////// 2 | // Constants 3 | /////////////////////////////////////////////////////////// 4 | 5 | var WINDOW_DEFAULT_TITLE = getMessage('text_Window'); 6 | 7 | 8 | /////////////////////////////////////////////////////////// 9 | // WindowNode 10 | /////////////////////////////////////////////////////////// 11 | 12 | /** 13 | * @constructor 14 | * @extends PageTreeNode 15 | */ 16 | var WindowNode = function(win) 17 | { 18 | this.$base(); 19 | 20 | this.elemType = 'window'; 21 | this.title = WINDOW_DEFAULT_TITLE; 22 | this.id = 'w' + this.UUID; 23 | 24 | if (win) { 25 | this.chromeId = win.id; 26 | this.incognito = win.incognito; 27 | this.type = win.type; 28 | return; 29 | } 30 | 31 | this.incognito = false; 32 | this.type = 'normal'; 33 | this.hibernated = true; 34 | 35 | this.restored = false; 36 | this.restorable = false; 37 | this.old = false; 38 | }; 39 | 40 | extendClass(WindowNode, PageTreeNode, {}); -------------------------------------------------------------------------------- /js/bg/config.js: -------------------------------------------------------------------------------- 1 | var config = config || {}; 2 | 3 | config.AVAILABLE_PANES = [ 4 | { enabled: true, id: 'pages', url: 'panes/pages.html', label: getMessage('sidebarLabel_Pages'), icon: 'images/nav/pages.png' }, 5 | { enabled: true, id: 'closed', url: 'panes/closed.html', label: 'Recently closed', icon: 'images/nav/closed.png' }, 6 | { enabled: true, id: 'notepad', url: 'panes/notepad.html', label: getMessage('sidebarLabel_Notepad'), icon: 'images/nav/notepad.png' }, 7 | { enabled: false, id: 'reddit', url: 'panes/external-site.html#http://i.reddit.com', label: 'Reddit', icon: 'images/nav/reddit.png' }, 8 | { enabled: false, id: 'grooveshark', url: 'panes/external-site.html#http://html5.grooveshark.com/#!/music/stations', label: 'Grooveshark', icon: 'images/nav/grooveshark.ico' }, 9 | { enabled: false, id: 'whatsnew', url: 'panes/whatsnew.html', label: 'What\'s New', icon: '/images/nav/whatsnew.gif' } 10 | ]; 11 | 12 | config.TREE_ONMODIFIED_DELAY_ON_STARTUP_MS = 2500; 13 | config.TREE_ONMODIFIED_DELAY_AFTER_STARTUP_MS = 1000; 14 | config.TREE_ONMODIFIED_STARTUP_DURATION_MS = 20000; 15 | config.TREE_ONMODIFIED_SAVE_AFTER_TAB_CLOSE_MS = 5000; 16 | config.TREE_ONMODIFIED_SAVE_AFTER_WINDOW_CLOSE_MS = 10000; 17 | 18 | config.DENIED_SAVE_TREE_RETRY_MS = 2000; // how soon to retry saving the page tree when it is temporariliy disallowed 19 | config.SAVE_TREE_BACKUP_EVERY_MS = 1000 * 60 * 15; // how often to save a backup of the page tree (15 minutes) 20 | config.MIN_NODES_TO_BACKUP_TREE = 6; // skip backups when we have fewer than this many nodes in the tree 21 | config.SAVE_TREE_INITIAL_BACKUP_AFTER_MS = 15000; // save the initial backup when none yet exists this soon after startup 22 | 23 | config.PAGETREE_NODE_TYPES = { 24 | 'window': WindowNode, 25 | 'page': PageNode, 26 | 'folder': FolderNode, 27 | 'header': HeaderNode 28 | }; 29 | 30 | config.GHOSTTREE_NODE_TYPES = { 31 | 'ghost': GhostNode 32 | }; 33 | 34 | config.PREPEND_RECENTLY_CLOSED_GROUP_HEADER_INTERVAL_MS = 750; 35 | config.GROUPING_ROW_COUNT_THRESHOLD = 7; 36 | config.GROUPING_ROW_COUNT_WAIT_THRESHOLD = 8; 37 | config.GROUPING_ROW_COUNT_WAIT_ITERATIONS = 4; 38 | 39 | // Nodes in the recently closed tree must be at most no older than this many 40 | // ms to qualify for having another node matched to an associated position vs. 41 | // them, e.g. a new recently-closed node being made a child of an existing 42 | // recently-closed node. When a recently-closed node can't be placed in 43 | // relative positioning to another node due to exceeding this timeout or just 44 | // not finding any qualifying nodes, the fallback behavior is simply to 45 | // prepend it to the top .collecting=true HeaderNode in the recently closed 46 | // tree. 47 | config.RECENTLY_CLOSED_ALLOW_RESTRUCTURING_MS = MINUTE_MS * 10; 48 | 49 | config.RECENTLY_CLOSED_GROUP_AFTER_REMOVE_IDLE_MS = HOUR_MS * 3; -------------------------------------------------------------------------------- /js/bg/events/browseraction-events.js: -------------------------------------------------------------------------------- 1 | function registerBrowserActionEvents() 2 | { 3 | chrome.browserAction.onClicked.addListener(onBrowserActionClicked); 4 | } 5 | 6 | function onBrowserActionClicked() 7 | { 8 | var action = settings.get('browserActionButtonBehavior'); 9 | log('browser action button clicked', 'configured action:', action); 10 | 11 | if (sidebarHandler.creatingSidebar || sidebarHandler.removeInProgress) { 12 | return; 13 | } 14 | 15 | if (sidebarHandler.sidebarExists()) { 16 | if (action == 'toggle') { 17 | // sidebar exists, so remove it 18 | sidebarHandler.remove(); 19 | return; 20 | } 21 | 22 | // sidebar exists, is it undocked? 23 | if (sidebarHandler.dockState == 'undocked') { 24 | // it's undocked so just raise it 25 | chrome.windows.update(sidebarHandler.windowId, { focused: true }); 26 | return; 27 | } 28 | 29 | // sidebar exists, are we changing its dock target? 30 | if (sidebarHandler.dockWindowId != focusTracker.getFocused()) { 31 | // changing dock target 32 | sidebarHandler.redock(focusTracker.getFocused()); 33 | return; 34 | } 35 | 36 | // sidebar exists and it's docked to the current focused window, so just raise the sidebar 37 | chrome.windows.update(sidebarHandler.windowId, { focused: true }); 38 | return; 39 | } 40 | 41 | // sidebar doesn't exist so create it with user's choice of docking mode from settings 42 | sidebarHandler.createWithDockState(settings.get('dockState')); 43 | } 44 | -------------------------------------------------------------------------------- /js/bg/events/omnibox-events.js: -------------------------------------------------------------------------------- 1 | function registerOmniboxEvents() { 2 | chrome.omnibox.onInputChanged.addListener(onOmniboxInputChanged); 3 | chrome.omnibox.onInputEntered.addListener(onOmniboxInputEntered); 4 | chrome.omnibox.setDefaultSuggestion({ description: getMessage('omniboxDefaultSuggestion') }); 5 | } 6 | 7 | function onOmniboxInputChanged(text, suggest) { 8 | // console.log('inputChanged: ' + text); 9 | var matches = tree.filter(function(e) { 10 | return e.elemType == 'page' && e.title && e.title.toLowerCase().indexOf(text) == 0; 11 | }); 12 | 13 | matches = matches.concat(tree.filter(function(e) { 14 | if (e.elemType != 'page') { 15 | return false; 16 | } 17 | 18 | var match = false; 19 | if (e.title && e.title.toLowerCase().indexOf(text) > 0) { 20 | match = true; 21 | } 22 | else if (e.label && e.label.toLowerCase().indexOf(text) > -1) { 23 | match = true; 24 | } 25 | 26 | if (match) { 27 | if (matches.indexOf(e) == -1) { 28 | return true; 29 | } 30 | } 31 | 32 | return false; 33 | })); 34 | 35 | var re = new RegExp('(' + text + ')', 'i'); 36 | 37 | var suggestions = matches.map(function(e) { 38 | if (e.label) { 39 | var label = escapeOmniboxText(e.label) + ': '; 40 | } 41 | else { 42 | var label = ''; 43 | } 44 | var url = escapeOmniboxText(e.url); 45 | var title = escapeOmniboxText(e.title); 46 | 47 | label = label.replace(re, '$1'); 48 | title = title.replace(re, '$1'); 49 | 50 | var description = 51 | '' + getMessage('omniboxSuggestionPrefix') + ' ' 52 | + label 53 | + (e.hibernated ? '(' + getMessage('text_hibernated') + ') ' : '') 54 | + title 55 | + ' - ' + url + ''; 56 | 57 | return { content: e.id, description: description }; 58 | }); 59 | 60 | suggest(suggestions); 61 | } 62 | 63 | function escapeOmniboxText(text) { 64 | return text.replace(/;/g, ';') 65 | .replace(/&(?!#59;)/g, '&'); 66 | } 67 | 68 | // This event is fired with the user accepts the input in the omnibox. 69 | function onOmniboxInputEntered(text) { 70 | var page = tree.getNode(text); 71 | if (page.hibernated) { 72 | tree.awakenPages([page], true); 73 | return; 74 | } 75 | chrome.tabs.update(page.chromeId, { active: true }); 76 | } 77 | -------------------------------------------------------------------------------- /js/bg/events/runtime-events.js: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////// 2 | // Initialization 3 | /////////////////////////////////////////////////////////// 4 | 5 | function registerRuntimeEvents() 6 | { 7 | chrome.runtime.onSuspend.addListener(onRuntimeSuspend); 8 | chrome.runtime.onSuspendCanceled.addListener(onRuntimeSuspendCanceled); 9 | } 10 | 11 | 12 | /////////////////////////////////////////////////////////// 13 | // Event handlers 14 | /////////////////////////////////////////////////////////// 15 | 16 | // Called when Chrome is about to exit; prohibit saving any changes to the 17 | // tree or updating the sidebar during this time 18 | function onRuntimeSuspend() { 19 | shutdownSidewise(); 20 | } 21 | 22 | 23 | // Called when Chrome "cancels" an exit; for this we just restart Sidewise entirely 24 | // to avoid issues with the existing pre-restart state messing with post-restart state 25 | function onRuntimeSuspendCanceled() { 26 | restartSidewise(); 27 | } 28 | 29 | 30 | -------------------------------------------------------------------------------- /js/bg/events/snapin-events.js: -------------------------------------------------------------------------------- 1 | function registerSnapInEvents() { 2 | chrome.extension.onRequestExternal.addListener(onRequestExternal); 3 | } 4 | 5 | function onRequestExternal(request, sender, sendResponse) { 6 | sidebarHandler.sidebarPanes['sidebarHost'].manager.addSidebarPane( 7 | sender.id, request.label, request.icon, request.url); 8 | } 9 | 10 | -------------------------------------------------------------------------------- /js/jquery/impromptu/README.md: -------------------------------------------------------------------------------- 1 | jQuery Impromptu 2 | ================ 3 | 4 | About 5 | ----- 6 | - Author: [Trent Richardson](http://trentrichardson.com) 7 | - Documentation: [http://trentrichardson.com/Impromptu/](http://trentrichardson.com/Impromptu/) 8 | - Twitter: [@practicalweb](http://twitter.com/practicalweb) 9 | 10 | Use 11 | --- 12 | - Include jQuery and Impromptu into your page 13 | - Add the CSS file or copy it to your CSS 14 | - call using $.prompt('hello world!'); 15 | - Visit the [Impromptu Documentation](http://trentrichardson.com/Impromptu/) for MUCH more advanced usage 16 | 17 | 18 | License 19 | ------- 20 | Copyright 2011 Trent Richardson 21 | 22 | Dual licensed under the MIT and GPL licenses. 23 | 24 | http://trentrichardson.com/Impromptu/GPL-LICENSE.txt 25 | 26 | http://trentrichardson.com/Impromptu/MIT-LICENSE.txt 27 | -------------------------------------------------------------------------------- /js/jquery/impromptu/default.css: -------------------------------------------------------------------------------- 1 | /* 2 | ------------------------------ 3 | Impromptu 4 | ------------------------------ 5 | */ 6 | .jqifade{ 7 | position: absolute; 8 | background-color: #777777; 9 | } 10 | div.jqi{ 11 | width: 400px; 12 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 13 | position: absolute; 14 | background-color: #ffffff; 15 | font-size: 11px; 16 | text-align: left; 17 | border: solid 1px #eeeeee; 18 | border-radius: 10px; 19 | -moz-border-radius: 10px; 20 | -webkit-border-radius: 10px; 21 | padding: 7px; 22 | } 23 | div.jqi .jqicontainer{ 24 | } 25 | div.jqi .jqiclose{ 26 | position: absolute; 27 | top: 4px; right: -2px; 28 | width: 18px; 29 | cursor: default; 30 | color: #bbbbbb; 31 | font-weight: bold; 32 | } 33 | div.jqi .jqititle{ 34 | padding: 5px 10px; 35 | font-size: 16px; 36 | line-height: 20px; 37 | border-bottom: solid 1px #eeeeee; 38 | } 39 | div.jqi .jqimessage{ 40 | padding: 10px; 41 | line-height: 20px; 42 | color: #444444; 43 | } 44 | div.jqi .jqibuttons{ 45 | text-align: right; 46 | padding: 5px 0 5px 0; 47 | border: solid 1px #eeeeee; 48 | background-color: #f4f4f4; 49 | } 50 | div.jqi button{ 51 | padding: 3px 10px; 52 | margin: 0 10px; 53 | background-color: #2F6073; 54 | border: solid 1px #f4f4f4; 55 | color: #ffffff; 56 | font-weight: bold; 57 | font-size: 12px; 58 | } 59 | div.jqi button:hover{ 60 | background-color: #728A8C; 61 | } 62 | div.jqi button.jqidefaultbutton{ 63 | background-color: #BF5E26; 64 | } 65 | .jqiwarning .jqi .jqibuttons{ 66 | background-color: #BF5E26; 67 | } 68 | 69 | .jqi .jqiarrow{ position: absolute; height: 0; width:0; line-height: 0; font-size: 0; border: solid 10px transparent;} 70 | 71 | .jqi .jqiarrowtl{ left: 10px; top: -20px; border-bottom-color: #ffffff; } 72 | .jqi .jqiarrowtc{ left: 50%; top: -20px; border-bottom-color: #ffffff; margin-left: -10px; } 73 | .jqi .jqiarrowtr{ right: 10px; top: -20px; border-bottom-color: #ffffff; } 74 | 75 | .jqi .jqiarrowbl{ left: 10px; bottom: -20px; border-top-color: #ffffff; } 76 | .jqi .jqiarrowbc{ left: 50%; bottom: -20px; border-top-color: #ffffff; margin-left: -10px; } 77 | .jqi .jqiarrowbr{ right: 10px; bottom: -20px; border-top-color: #ffffff; } 78 | 79 | .jqi .jqiarrowlt{ left: -20px; top: 10px; border-right-color: #ffffff; } 80 | .jqi .jqiarrowlm{ left: -20px; top: 50%; border-right-color: #ffffff; margin-top: -10px; } 81 | .jqi .jqiarrowlb{ left: -20px; bottom: 10px; border-right-color: #ffffff; } 82 | 83 | .jqi .jqiarrowrt{ right: -20px; top: 10px; border-left-color: #ffffff; } 84 | .jqi .jqiarrowrm{ right: -20px; top: 50%; border-left-color: #ffffff; margin-top: -10px; } 85 | .jqi .jqiarrowrb{ right: -20px; bottom: 10px; border-left-color: #ffffff; } 86 | 87 | -------------------------------------------------------------------------------- /js/jquery/impromptu/themes/clean-blue.css: -------------------------------------------------------------------------------- 1 | /* 2 | ------------------------------ 3 | clean blue 4 | ------------------------------ 5 | */ 6 | .cleanbluewarning .cleanblue{ 7 | background-color: #acb4c4; 8 | } 9 | .cleanbluefade{ 10 | position: absolute; 11 | background-color: #aaaaaa; 12 | } 13 | div.cleanblue{ 14 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 15 | position: absolute; 16 | background-color: #ffffff; 17 | width: 300px; 18 | font-size: 11px; 19 | text-align: left; 20 | border: solid 1px #213e80; 21 | } 22 | div.cleanblue .cleanbluecontainer{ 23 | background-color: #ffffff; 24 | border-top: solid 14px #213e80; 25 | padding: 5px; 26 | font-weight: bold; 27 | } 28 | div.cleanblue .cleanblueclose{ 29 | float: right; 30 | width: 18px; 31 | cursor: default; 32 | margin: -19px -12px 0 0; 33 | color: #ffffff; 34 | font-weight: bold; 35 | } 36 | div.cleanblue .cleanbluemessage{ 37 | padding: 10px; 38 | line-height: 20px; 39 | font-size: 11px; 40 | color: #333333; 41 | } 42 | div.cleanblue .cleanbluebuttons{ 43 | text-align: right; 44 | padding: 5px 0 5px 0; 45 | border: solid 1px #eeeeee; 46 | background-color: #f4f4f4; 47 | } 48 | div.cleanblue button{ 49 | padding: 3px 10px; 50 | margin: 0 10px; 51 | background-color: #314e90; 52 | border: solid 1px #f4f4f4; 53 | color: #ffffff; 54 | font-weight: bold; 55 | font-size: 12px; 56 | } 57 | div.cleanblue button:hover{ 58 | border: solid 1px #d4d4d4; 59 | } 60 | -------------------------------------------------------------------------------- /js/jquery/jqgrid/grid.locale-en.js: -------------------------------------------------------------------------------- 1 | ;(function($){ 2 | /** 3 | * jqGrid English Translation 4 | * Tony Tomov tony@trirand.com 5 | * http://trirand.com/blog/ 6 | * Dual licensed under the MIT and GPL licenses: 7 | * http://www.opensource.org/licenses/mit-license.php 8 | * http://www.gnu.org/licenses/gpl.html 9 | **/ 10 | $.jgrid = $.jgrid || {}; 11 | $.extend($.jgrid,{ 12 | defaults : { 13 | recordtext: "View {0} - {1} of {2}", 14 | emptyrecords: "No records to view", 15 | loadtext: "Loading...", 16 | pgtext : "Page {0} of {1}" 17 | }, 18 | search : { 19 | caption: "Search...", 20 | Find: "Find", 21 | Reset: "Reset", 22 | odata : ['equal', 'not equal', 'less', 'less or equal','greater','greater or equal', 'begins with','does not begin with','is in','is not in','ends with','does not end with','contains','does not contain'], 23 | groupOps: [ { op: "AND", text: "all" }, { op: "OR", text: "any" } ], 24 | matchText: " match", 25 | rulesText: " rules" 26 | }, 27 | edit : { 28 | addCaption: "Add Record", 29 | editCaption: "Edit Record", 30 | bSubmit: "Submit", 31 | bCancel: "Cancel", 32 | bClose: "Close", 33 | saveData: "Data has been changed! Save changes?", 34 | bYes : "Yes", 35 | bNo : "No", 36 | bExit : "Cancel", 37 | msg: { 38 | required:"Field is required", 39 | number:"Please, enter valid number", 40 | minValue:"value must be greater than or equal to ", 41 | maxValue:"value must be less than or equal to", 42 | email: "is not a valid e-mail", 43 | integer: "Please, enter valid integer value", 44 | date: "Please, enter valid date value", 45 | url: "is not a valid URL. Prefix required ('http://' or 'https://')", 46 | nodefined : " is not defined!", 47 | novalue : " return value is required!", 48 | customarray : "Custom function should return array!", 49 | customfcheck : "Custom function should be present in case of custom checking!" 50 | 51 | } 52 | }, 53 | view : { 54 | caption: "View Record", 55 | bClose: "Close" 56 | }, 57 | del : { 58 | caption: "Delete", 59 | msg: "Delete selected record(s)?", 60 | bSubmit: "Delete", 61 | bCancel: "Cancel" 62 | }, 63 | nav : { 64 | edittext: "", 65 | edittitle: "Edit selected row", 66 | addtext:"", 67 | addtitle: "Add new row", 68 | deltext: "", 69 | deltitle: "Delete selected row", 70 | searchtext: "", 71 | searchtitle: "Find records", 72 | refreshtext: "", 73 | refreshtitle: "Reload Grid", 74 | alertcap: "Warning", 75 | alerttext: "Please, select row", 76 | viewtext: "", 77 | viewtitle: "View selected row" 78 | }, 79 | col : { 80 | caption: "Select columns", 81 | bSubmit: "Ok", 82 | bCancel: "Cancel" 83 | }, 84 | errors : { 85 | errcap : "Error", 86 | nourl : "No url is set", 87 | norecords: "No records to process", 88 | model : "Length of colNames <> colModel!" 89 | }, 90 | formatter : { 91 | integer : {thousandsSeparator: ",", defaultValue: '0'}, 92 | number : {decimalSeparator:".", thousandsSeparator: ",", decimalPlaces: 2, defaultValue: '0.00'}, 93 | currency : {decimalSeparator:".", thousandsSeparator: ",", decimalPlaces: 2, prefix: "", suffix:"", defaultValue: '0.00'}, 94 | date : { 95 | dayNames: [ 96 | "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat", 97 | "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" 98 | ], 99 | monthNames: [ 100 | "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 101 | "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" 102 | ], 103 | AmPm : ["am","pm","AM","PM"], 104 | S: function (j) {return j < 11 || j > 13 ? ['st', 'nd', 'rd', 'th'][Math.min((j - 1) % 10, 3)] : 'th';}, 105 | srcformat: 'Y-m-d', 106 | newformat: 'n/j/Y', 107 | masks : { 108 | // see http://php.net/manual/en/function.date.php for PHP format used in jqGrid 109 | // and see http://docs.jquery.com/UI/Datepicker/formatDate 110 | // and https://github.com/jquery/globalize#dates for alternative formats used frequently 111 | // one can find on https://github.com/jquery/globalize/tree/master/lib/cultures many 112 | // information about date, time, numbers and currency formats used in different countries 113 | // one should just convert the information in PHP format 114 | ISO8601Long:"Y-m-d H:i:s", 115 | ISO8601Short:"Y-m-d", 116 | // short date: 117 | // n - Numeric representation of a month, without leading zeros 118 | // j - Day of the month without leading zeros 119 | // Y - A full numeric representation of a year, 4 digits 120 | // example: 3/1/2012 which means 1 March 2012 121 | ShortDate: "n/j/Y", // in jQuery UI Datepicker: "M/d/yyyy" 122 | // long date: 123 | // l - A full textual representation of the day of the week 124 | // F - A full textual representation of a month 125 | // d - Day of the month, 2 digits with leading zeros 126 | // Y - A full numeric representation of a year, 4 digits 127 | LongDate: "l, F d, Y", // in jQuery UI Datepicker: "dddd, MMMM dd, yyyy" 128 | // long date with long time: 129 | // l - A full textual representation of the day of the week 130 | // F - A full textual representation of a month 131 | // d - Day of the month, 2 digits with leading zeros 132 | // Y - A full numeric representation of a year, 4 digits 133 | // g - 12-hour format of an hour without leading zeros 134 | // i - Minutes with leading zeros 135 | // s - Seconds, with leading zeros 136 | // A - Uppercase Ante meridiem and Post meridiem (AM or PM) 137 | FullDateTime: "l, F d, Y g:i:s A", // in jQuery UI Datepicker: "dddd, MMMM dd, yyyy h:mm:ss tt" 138 | // month day: 139 | // F - A full textual representation of a month 140 | // d - Day of the month, 2 digits with leading zeros 141 | MonthDay: "F d", // in jQuery UI Datepicker: "MMMM dd" 142 | // short time (without seconds) 143 | // g - 12-hour format of an hour without leading zeros 144 | // i - Minutes with leading zeros 145 | // A - Uppercase Ante meridiem and Post meridiem (AM or PM) 146 | ShortTime: "g:i A", // in jQuery UI Datepicker: "h:mm tt" 147 | // long time (with seconds) 148 | // g - 12-hour format of an hour without leading zeros 149 | // i - Minutes with leading zeros 150 | // s - Seconds, with leading zeros 151 | // A - Uppercase Ante meridiem and Post meridiem (AM or PM) 152 | LongTime: "g:i:s A", // in jQuery UI Datepicker: "h:mm:ss tt" 153 | SortableDateTime: "Y-m-d\\TH:i:s", 154 | UniversalSortableDateTime: "Y-m-d H:i:sO", 155 | // month with year 156 | // Y - A full numeric representation of a year, 4 digits 157 | // F - A full textual representation of a month 158 | YearMonth: "F, Y" // in jQuery UI Datepicker: "MMMM, yyyy" 159 | }, 160 | reformatAfterEdit : false 161 | }, 162 | baseLinkUrl: '', 163 | showAction: '', 164 | target: '', 165 | checkbox : {disabled:true}, 166 | idName : 'id' 167 | } 168 | }); 169 | })(jQuery); 170 | -------------------------------------------------------------------------------- /js/jquery/jquery-helpers.js: -------------------------------------------------------------------------------- 1 | // Add $.reverse() 2 | jQuery.fn.reverse = [].reverse; 3 | 4 | // Adds an :icontains jQuery selector, which does a case-insensitive match of elements' contained text 5 | $.expr[':'].icontains = function(obj, index, meta, stack){ 6 | return (obj.textContent || obj.innerText || jQuery(obj).text() || '') 7 | .toLowerCase() 8 | .indexOf(meta[3].toLowerCase()) >= 0; 9 | }; 10 | 11 | // Adds a :regexicontains jQuery selector, which does a case-insensitive regex match of elements' contained text 12 | $.expr[':'].regexicontains = function(obj, index, meta, stack) { 13 | var re = new RegExp(meta[3], 'i'); 14 | return re.test(obj.textContent || obj.innerText || jQuery(obj).text() || ''); 15 | }; 16 | 17 | // Adds an insertAtCaret jQuery method to insert myValue at the current 18 | // insert point in a textarea/input=text 19 | jQuery.fn.extend({ 20 | insertAtCaret: function(myValue) { 21 | return this.each(function(i) { 22 | if (document.selection) { 23 | //For browsers like Internet Explorer 24 | this.focus(); 25 | sel = document.selection.createRange(); 26 | sel.text = myValue; 27 | this.focus(); 28 | } 29 | else if (this.selectionStart || this.selectionStart == '0') { 30 | //For browsers like Firefox and Webkit based 31 | var startPos = this.selectionStart; 32 | var endPos = this.selectionEnd; 33 | var scrollTop = this.scrollTop; 34 | this.value = this.value.substring(0, startPos)+myValue+this.value.substring(endPos,this.value.length); 35 | this.focus(); 36 | this.selectionStart = startPos + myValue.length; 37 | this.selectionEnd = startPos + myValue.length; 38 | this.scrollTop = scrollTop; 39 | } else { 40 | this.value += myValue; 41 | this.focus(); 42 | } 43 | }); 44 | } 45 | }); 46 | 47 | // Insert element at the requested index 48 | jQuery.fn.insertAt = function(index, element) { 49 | var lastIndex = this.children().size(); 50 | if (index < 0) { 51 | index = Math.max(0, lastIndex + 1 + index); 52 | } 53 | this.append(element); 54 | if (index < lastIndex) { 55 | this.children().eq(index).before(this.children().last()); 56 | } 57 | return this; 58 | }; 59 | 60 | jQuery.fn.following = function(selector, topParent) { 61 | if (topParent && !this.parentsUntil(topParent).parent().is(topParent)) { 62 | return $(); 63 | } 64 | 65 | if (!selector) { 66 | selector = '*'; 67 | } 68 | var firstSelector = selector.replace(',', ':first,') + ':first'; 69 | 70 | var $child = this.find(firstSelector); 71 | if ($child.length > 0) { 72 | return $child; 73 | } 74 | 75 | var $next = this.next(); 76 | 77 | if ($next.length == 1) { 78 | if ($next.is(selector)) { 79 | return $next; 80 | } 81 | 82 | $next = $next.following(selector, topParent); 83 | if ($next.length == 1) { 84 | return $next; 85 | } 86 | } 87 | 88 | var parent = this.parentsUntil(selector).parent(); 89 | while (parent.length > 0 && !parent.is(topParent)) { 90 | var $afters = parent.nextAll(); 91 | for (var i = 0; i < $afters.length; i++) { 92 | var $after = $($afters[i]); 93 | if ($after.is(selector)) { 94 | return $after; 95 | } 96 | var $child = $after.find(firstSelector); 97 | if ($child.length > 0) { 98 | return $child; 99 | } 100 | } 101 | parent = parent.parent(); 102 | } 103 | return $(); 104 | }; 105 | 106 | jQuery.fn.preceding = function(selector, topParent) { 107 | if (topParent && !this.parentsUntil(topParent).parent().is(topParent)) { 108 | return $(); 109 | } 110 | 111 | if (!selector) { 112 | selector = '*'; 113 | } 114 | 115 | var $befores = this.prevAll(); 116 | 117 | for (var i = 0; i < $befores.length; i++) { 118 | var $before = $($befores[i]); 119 | var $child = $before.find(selector).last(); 120 | if ($child.length > 0) { 121 | return $child; 122 | } 123 | if ($before.is(selector)) { 124 | return $before; 125 | } 126 | } 127 | 128 | var $parent = this.parentsUntil(selector).parent(); 129 | 130 | if ($parent.is(topParent)) { 131 | return $(); 132 | } 133 | 134 | if ($parent.length > 0) { 135 | if ($parent.is(selector)) { 136 | return $parent; 137 | } 138 | 139 | var $before = $parent.preceding(selector); 140 | if ($before.length > 0) { 141 | return $before; 142 | } 143 | } 144 | 145 | return $(); 146 | }; -------------------------------------------------------------------------------- /js/jquery/jquery.contextMenu.js: -------------------------------------------------------------------------------- 1 | // jQuery Context Menu Plugin 2 | // 3 | // Version 1.01 4 | // 5 | // Cory S.N. LaViska 6 | // A Beautiful Site (http://abeautifulsite.net/) 7 | // 8 | // More info: http://abeautifulsite.net/2008/09/jquery-context-menu-plugin/ 9 | // 10 | // Terms of Use 11 | // 12 | // This plugin is dual-licensed under the GNU General Public License 13 | // and the MIT License and is copyright A Beautiful Site, LLC. 14 | // 15 | if(jQuery)( function() { 16 | $.extend($.fn, { 17 | 18 | contextMenu: function(o, callback) { 19 | // Defaults 20 | if( o.menu == undefined ) return false; 21 | if( o.inSpeed == undefined ) o.inSpeed = 150; 22 | if( o.outSpeed == undefined ) o.outSpeed = 75; 23 | // 0 needs to be -1 for expected results (no fade) 24 | if( o.inSpeed == 0 ) o.inSpeed = -1; 25 | if( o.outSpeed == 0 ) o.outSpeed = -1; 26 | // Loop each context menu 27 | $(this).each( function() { 28 | var el = $(this); 29 | var offset = $(el).offset(); 30 | // Add contextMenu class 31 | $('#' + o.menu).addClass('contextMenu'); 32 | // Simulate a true right click 33 | $(this).mousedown( function(e) { 34 | var evt = e; 35 | evt.stopPropagation(); 36 | $(this).mouseup( function(e) { 37 | e.stopPropagation(); 38 | var srcElement = $(this); 39 | $(this).unbind('mouseup'); 40 | if( evt.button == 2 ) { 41 | // Hide context menus that may be showing 42 | $(".contextMenu").hide(); 43 | // Get this context menu 44 | var menu = $('#' + o.menu); 45 | 46 | if( $(el).hasClass('disabled') ) return false; 47 | 48 | // Detect mouse position 49 | var d = {}, x, y; 50 | if( self.innerHeight ) { 51 | d.pageYOffset = self.pageYOffset; 52 | d.pageXOffset = self.pageXOffset; 53 | d.innerHeight = self.innerHeight; 54 | d.innerWidth = self.innerWidth; 55 | } else if( document.documentElement && 56 | document.documentElement.clientHeight ) { 57 | d.pageYOffset = document.documentElement.scrollTop; 58 | d.pageXOffset = document.documentElement.scrollLeft; 59 | d.innerHeight = document.documentElement.clientHeight; 60 | d.innerWidth = document.documentElement.clientWidth; 61 | } else if( document.body ) { 62 | d.pageYOffset = document.body.scrollTop; 63 | d.pageXOffset = document.body.scrollLeft; 64 | d.innerHeight = document.body.clientHeight; 65 | d.innerWidth = document.body.clientWidth; 66 | } 67 | (e.pageX) ? x = e.pageX : x = e.clientX + d.scrollLeft; 68 | (e.pageY) ? y = e.pageY : y = e.clientY + d.scrollTop; 69 | 70 | // Show the menu 71 | $(document).unbind('click'); 72 | $(menu).css({ top: y, left: x }).fadeIn(o.inSpeed); 73 | // Hover events 74 | $(menu).find('A').mouseover( function() { 75 | $(menu).find('LI.hover').removeClass('hover'); 76 | $(this).parent().addClass('hover'); 77 | }).mouseout( function() { 78 | $(menu).find('LI.hover').removeClass('hover'); 79 | }); 80 | 81 | // Keyboard 82 | $(document).keypress( function(e) { 83 | switch( e.keyCode ) { 84 | case 38: // up 85 | if( $(menu).find('LI.hover').size() == 0 ) { 86 | $(menu).find('LI:last').addClass('hover'); 87 | } else { 88 | $(menu).find('LI.hover').removeClass('hover').prevAll('LI:not(.disabled)').eq(0).addClass('hover'); 89 | if( $(menu).find('LI.hover').size() == 0 ) $(menu).find('LI:last').addClass('hover'); 90 | } 91 | break; 92 | case 40: // down 93 | if( $(menu).find('LI.hover').size() == 0 ) { 94 | $(menu).find('LI:first').addClass('hover'); 95 | } else { 96 | $(menu).find('LI.hover').removeClass('hover').nextAll('LI:not(.disabled)').eq(0).addClass('hover'); 97 | if( $(menu).find('LI.hover').size() == 0 ) $(menu).find('LI:first').addClass('hover'); 98 | } 99 | break; 100 | case 13: // enter 101 | $(menu).find('LI.hover A').trigger('click'); 102 | break; 103 | case 27: // esc 104 | $(document).trigger('click'); 105 | break 106 | } 107 | }); 108 | 109 | // When items are selected 110 | $('#' + o.menu).find('A').unbind('click'); 111 | $('#' + o.menu).find('LI:not(.disabled) A').click( function() { 112 | $(document).unbind('click').unbind('keypress'); 113 | $(".contextMenu").hide(); 114 | // Callback 115 | if( callback ) callback( $(this).attr('href').substr(1), $(srcElement), {x: x - offset.left, y: y - offset.top, docX: x, docY: y} ); 116 | return false; 117 | }); 118 | 119 | // Hide bindings 120 | setTimeout( function() { // Delay for Mozilla 121 | $(document).click( function() { 122 | $(document).unbind('click').unbind('keypress'); 123 | $(menu).fadeOut(o.outSpeed); 124 | return false; 125 | }); 126 | }, 0); 127 | } 128 | }); 129 | }); 130 | 131 | // Disable text selection 132 | if( $.browser.mozilla ) { 133 | $('#' + o.menu).each( function() { $(this).css({ 'MozUserSelect' : 'none' }); }); 134 | } else if( $.browser.msie ) { 135 | $('#' + o.menu).each( function() { $(this).bind('selectstart.disableTextSelect', function() { return false; }); }); 136 | } else { 137 | $('#' + o.menu).each(function() { $(this).bind('mousedown.disableTextSelect', function() { return false; }); }); 138 | } 139 | // Disable browser context menu (requires both selectors to work in IE/Safari + FF/Chrome) 140 | $(el).add($('UL.contextMenu')).bind('contextmenu', function() { return false; }); 141 | 142 | }); 143 | return $(this); 144 | }, 145 | 146 | // Disable context menu items on the fly 147 | disableContextMenuItems: function(o) { 148 | if( o == undefined ) { 149 | // Disable all 150 | $(this).find('LI').addClass('disabled'); 151 | return( $(this) ); 152 | } 153 | $(this).each( function() { 154 | if( o != undefined ) { 155 | var d = o.split(','); 156 | for( var i = 0; i < d.length; i++ ) { 157 | $(this).find('A[href="' + d[i] + '"]').parent().addClass('disabled'); 158 | 159 | } 160 | } 161 | }); 162 | return( $(this) ); 163 | }, 164 | 165 | // Enable context menu items on the fly 166 | enableContextMenuItems: function(o) { 167 | if( o == undefined ) { 168 | // Enable all 169 | $(this).find('LI.disabled').removeClass('disabled'); 170 | return( $(this) ); 171 | } 172 | $(this).each( function() { 173 | if( o != undefined ) { 174 | var d = o.split(','); 175 | for( var i = 0; i < d.length; i++ ) { 176 | $(this).find('A[href="' + d[i] + '"]').parent().removeClass('disabled'); 177 | 178 | } 179 | } 180 | }); 181 | return( $(this) ); 182 | }, 183 | 184 | // Disable context menu(s) 185 | disableContextMenu: function() { 186 | $(this).each( function() { 187 | $(this).addClass('disabled'); 188 | }); 189 | return( $(this) ); 190 | }, 191 | 192 | // Enable context menu(s) 193 | enableContextMenu: function() { 194 | $(this).each( function() { 195 | $(this).removeClass('disabled'); 196 | }); 197 | return( $(this) ); 198 | }, 199 | 200 | // Destroy context menu(s) 201 | destroyContextMenu: function() { 202 | // Destroy specified context menus 203 | $(this).each( function() { 204 | // Disable action 205 | $(this).unbind('mousedown').unbind('mouseup'); 206 | }); 207 | return( $(this) ); 208 | } 209 | 210 | }); 211 | })(jQuery); -------------------------------------------------------------------------------- /js/jquery/jquery.highlight.js: -------------------------------------------------------------------------------- 1 | /* BEGIN jquery color */ 2 | (function(jQuery) { 3 | jQuery.each(['backgroundColor', 'borderBottomColor', 'borderLeftColor', 'borderRightColor', 'borderTopColor', 'color', 'outlineColor'], function(i, attr) { 4 | jQuery.fx.step[attr] = function(fx) { 5 | if (!fx.colorInit) { 6 | fx.start = getColor(fx.elem, attr); 7 | fx.end = getRGB(fx.end); 8 | fx.colorInit = true; 9 | } 10 | fx.elem.style[attr] = "rgb(" + [Math.max(Math.min(parseInt((fx.pos * (fx.end[0] - fx.start[0])) + fx.start[0]), 255), 0), Math.max(Math.min(parseInt((fx.pos * (fx.end[1] - fx.start[1])) + fx.start[1]), 255), 0), Math.max(Math.min(parseInt((fx.pos * (fx.end[2] - fx.start[2])) + fx.start[2]), 255), 0)].join(",") + ")"; 11 | } 12 | }); 13 | 14 | function getRGB(color) { 15 | var result; 16 | if (color && color.constructor == Array && color.length == 3) return color; 17 | if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color)) return [parseInt(result[1]), parseInt(result[2]), parseInt(result[3])]; 18 | if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color)) return [parseFloat(result[1]) * 2.55, parseFloat(result[2]) * 2.55, parseFloat(result[3]) * 2.55]; 19 | if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color)) return [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)]; 20 | if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color)) return [parseInt(result[1] + result[1], 16), parseInt(result[2] + result[2], 16), parseInt(result[3] + result[3], 16)]; 21 | if (result = /rgba\(0, 0, 0, 0\)/.exec(color)) return colors['transparent']; 22 | return colors[jQuery.trim(color).toLowerCase()]; 23 | } 24 | 25 | function getColor(elem, attr) { 26 | var color; 27 | do { 28 | color = jQuery.curCSS(elem, attr); 29 | if (color != '' && color != 'transparent' || jQuery.nodeName(elem, "body")) break; 30 | attr = "backgroundColor"; 31 | } while (elem = elem.parentNode); 32 | return getRGB(color); 33 | }; 34 | var colors = { 35 | aqua: [0, 255, 255], 36 | azure: [240, 255, 255], 37 | beige: [245, 245, 220], 38 | black: [0, 0, 0], 39 | blue: [0, 0, 255], 40 | brown: [165, 42, 42], 41 | cyan: [0, 255, 255], 42 | darkblue: [0, 0, 139], 43 | darkcyan: [0, 139, 139], 44 | darkgrey: [169, 169, 169], 45 | darkgreen: [0, 100, 0], 46 | darkkhaki: [189, 183, 107], 47 | darkmagenta: [139, 0, 139], 48 | darkolivegreen: [85, 107, 47], 49 | darkorange: [255, 140, 0], 50 | darkorchid: [153, 50, 204], 51 | darkred: [139, 0, 0], 52 | darksalmon: [233, 150, 122], 53 | darkviolet: [148, 0, 211], 54 | fuchsia: [255, 0, 255], 55 | gold: [255, 215, 0], 56 | green: [0, 128, 0], 57 | indigo: [75, 0, 130], 58 | khaki: [240, 230, 140], 59 | lightblue: [173, 216, 230], 60 | lightcyan: [224, 255, 255], 61 | lightgreen: [144, 238, 144], 62 | lightgrey: [211, 211, 211], 63 | lightpink: [255, 182, 193], 64 | lightyellow: [255, 255, 224], 65 | lime: [0, 255, 0], 66 | magenta: [255, 0, 255], 67 | maroon: [128, 0, 0], 68 | navy: [0, 0, 128], 69 | olive: [128, 128, 0], 70 | orange: [255, 165, 0], 71 | pink: [255, 192, 203], 72 | purple: [128, 0, 128], 73 | violet: [128, 0, 128], 74 | red: [255, 0, 0], 75 | silver: [192, 192, 192], 76 | white: [255, 255, 255], 77 | yellow: [255, 255, 0], 78 | transparent: [255, 255, 255] 79 | }; 80 | })(jQuery); /* END jquery color */ 81 | 82 | 83 | /* BEGIN highlight */ 84 | jQuery(function() { 85 | $.fn.highlight = function(options) { 86 | options = (options) ? options : { 87 | start_color: "#ff0", 88 | end_color: "#fff", 89 | delay: 1500 90 | }; 91 | $(this).each(function() { 92 | $(this).stop().css({ 93 | "background-color": options.start_color 94 | }).animate({ 95 | "background-color": options.end_color 96 | }, options.delay); 97 | }); 98 | } 99 | }); /* END highlight */ 100 | -------------------------------------------------------------------------------- /js/jquery/jquery.scrollTo-min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jQuery.ScrollTo - Easy element scrolling using jQuery. 3 | * Copyright (c) 2007-2009 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com 4 | * Dual licensed under MIT and GPL. 5 | * Date: 5/25/2009 6 | * @author Ariel Flesler 7 | * @version 1.4.2 8 | * 9 | * http://flesler.blogspot.com/2007/10/jqueryscrollto.html 10 | */ 11 | ;(function(d){var k=d.scrollTo=function(a,i,e){d(window).scrollTo(a,i,e)};k.defaults={axis:'xy',duration:parseFloat(d.fn.jquery)>=1.3?0:1};k.window=function(a){return d(window)._scrollable()};d.fn._scrollable=function(){return this.map(function(){var a=this,i=!a.nodeName||d.inArray(a.nodeName.toLowerCase(),['iframe','#document','html','body'])!=-1;if(!i)return a;var e=(a.contentWindow||a).document||a.ownerDocument||a;return d.browser.safari||e.compatMode=='BackCompat'?e.body:e.documentElement})};d.fn.scrollTo=function(n,j,b){if(typeof j=='object'){b=j;j=0}if(typeof b=='function')b={onAfter:b};if(n=='max')n=9e9;b=d.extend({},k.defaults,b);j=j||b.speed||b.duration;b.queue=b.queue&&b.axis.length>1;if(b.queue)j/=2;b.offset=p(b.offset);b.over=p(b.over);return this._scrollable().each(function(){var q=this,r=d(q),f=n,s,g={},u=r.is('html,body');switch(typeof f){case'number':case'string':if(/^([+-]=)?\d+(\.\d+)?(px|%)?$/.test(f)){f=p(f);break}f=d(f,this);case'object':if(f.is||f.style)s=(f=d(f)).offset()}d.each(b.axis.split(''),function(a,i){var e=i=='x'?'Left':'Top',h=e.toLowerCase(),c='scroll'+e,l=q[c],m=k.max(q,i);if(s){g[c]=s[h]+(u?0:l-r.offset()[h]);if(b.margin){g[c]-=parseInt(f.css('margin'+e))||0;g[c]-=parseInt(f.css('border'+e+'Width'))||0}g[c]+=b.offset[h]||0;if(b.over[h])g[c]+=f[i=='x'?'width':'height']()*b.over[h]}else{var o=f[h];g[c]=o.slice&&o.slice(-1)=='%'?parseFloat(o)/100*m:o}if(/^\d+$/.test(g[c]))g[c]=g[c]<=0?0:Math.min(g[c],m);if(!a&&b.queue){if(l!=g[c])t(b.onAfterFirst);delete g[c]}});t(b.onAfter);function t(a){r.animate(g,j,b.easing,a&&function(){a.call(this,n,b)})}}).end()};k.max=function(a,i){var e=i=='x'?'Width':'Height',h='scroll'+e;if(!d(a).is('html,body'))return a[h]-d(a)[e.toLowerCase()]();var c='client'+e,l=a.ownerDocument.documentElement,m=a.ownerDocument.body;return Math.max(l[h],m[h])-Math.min(l[c],m[c])};function p(a){return typeof a=='object'?a:{top:a,left:a}}})(jQuery); -------------------------------------------------------------------------------- /js/lib/ga.js: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////// 2 | // ga.js 3 | // Google Analytics integration 4 | /////////////////////////////////////////////////////////// 5 | 6 | /////////////////////////////////////////////////////////// 7 | // Initialization 8 | /////////////////////////////////////////////////////////// 9 | 10 | // respect the user 11 | if (localStorage['reportUsageStatistics'] != 'false') { 12 | var _gaq = _gaq || []; 13 | initGA(); 14 | reportPageView(); 15 | } 16 | 17 | function initGA() { 18 | // initialize google analytics object 19 | _gaq.push(['_setAccount', 'UA-33231974-1']); 20 | _gaq.push(['_gat._anonymizeIp']); 21 | 22 | (function() { 23 | var ga = document.createElement('script'); ga.type = 'text/javascript'; 24 | ga.async = true; 25 | ga.src = 'https://ssl.google-analytics.com/ga.js'; 26 | var s = document.getElementsByTagName('script')[0]; 27 | s.parentNode.insertBefore(ga, s); 28 | })(); 29 | } 30 | 31 | 32 | /////////////////////////////////////////////////////////// 33 | // Functions 34 | /////////////////////////////////////////////////////////// 35 | 36 | // Report an event with the given details to Google Analytics. 37 | function reportEvent(category, action, label, intValue, nonInteraction) { 38 | if (localStorage['reportUsageStatistics'] != 'true') { 39 | // respect the user 40 | return; 41 | } 42 | 43 | if (localStorage['loggingEnabled'] == 'true') { 44 | // log it 45 | console.log('reporting event', category, action, label, intValue, nonInteraction); 46 | } 47 | 48 | // send it 49 | _gaq.push(['_trackEvent', category, action, label, intValue, nonInteraction || false]); 50 | } 51 | 52 | // Report a page view to Google Analytics. 53 | function reportPageView(url) { 54 | if (localStorage['reportUsageStatistics'] != 'true') { 55 | // respect the user 56 | return; 57 | } 58 | 59 | url = url || location.pathname; 60 | 61 | if (localStorage['loggingEnabled'] == 'true') { 62 | console.log('reporting page view', url); 63 | } 64 | 65 | _gaq.push(['_trackPageview', url]); 66 | } -------------------------------------------------------------------------------- /js/ui/classes/FancyTree.contextMenu.js: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////// 2 | // FancyTree.contextMenu.js 3 | // Context menu implementation 4 | /////////////////////////////////////////////////////////// 5 | 6 | 7 | /////////////////////////////////////////////////////////// 8 | // Event handlers 9 | /////////////////////////////////////////////////////////// 10 | 11 | FancyTree.prototype.onContextMenu = function(evt) { 12 | var treeObj = evt.data.treeObj; 13 | 14 | if (treeObj.contextMenuShown) { 15 | treeObj.disableContextMenu.call(treeObj); 16 | } 17 | 18 | var row = treeObj.getParentRowNode($(evt.target)); 19 | 20 | if (!row) { 21 | // didn't click a row 22 | return false; 23 | } 24 | 25 | treeObj.contextMenuTarget = row; 26 | 27 | if (treeObj.multiSelection.length == 0 || !row.hasClass('ftSelected')) { 28 | treeObj.clearMultiSelection.call(treeObj); 29 | treeObj.toggleMultiSelectionSingle.call(treeObj, row); 30 | } 31 | 32 | treeObj.contextMenuSelectionData = treeObj.multiSelection; 33 | 34 | treeObj.enableContextMenu.call(treeObj, evt.pageX, evt.pageY); 35 | return false; 36 | }; 37 | 38 | FancyTree.prototype.onContextMenuItemClick = function(evt) { 39 | var treeObj = evt.data.treeObj; 40 | var id = this.attributes.contextMenuId.value; 41 | var contextMenuItem = treeObj.contextMenuItems[id]; 42 | var callback = contextMenuItem.callback; 43 | var $rows = contextMenuItem.$rows; 44 | 45 | treeObj.disableContextMenu(); 46 | treeObj.sortMultiSelection(); 47 | 48 | if (!contextMenuItem.preserveSelectionAfter) { 49 | treeObj.clearMultiSelection(); 50 | } 51 | 52 | treeObj.resetDragDropState(function() { 53 | // Perform context menu after a short delay to allow for sidebar to 54 | // do its visual updates first 55 | setTimeout(function() { callback($rows); }, 50); 56 | }); 57 | 58 | return false; 59 | }; 60 | 61 | 62 | /////////////////////////////////////////////////////////// 63 | // Context menu functions 64 | /////////////////////////////////////////////////////////// 65 | 66 | // show context menu positioned at mouse click 67 | FancyTree.prototype.enableContextMenu = function(x, y) 68 | { 69 | if (!this.onContextMenuShow) { 70 | return; 71 | } 72 | 73 | var items = this.onContextMenuShow(this.contextMenuSelectionData); 74 | 75 | if (items.length == 0) { 76 | return; 77 | } 78 | 79 | var menu = $('