diff --git a/.gitignore b/.gitignore index ab4bf85..f00fed8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,5 @@ data/* !data/.gitkeep plugins/* !plugins/index.php -test/test-file1.txt tmp/* !tmp/index.php diff --git a/CodeMirror/.DS_Store b/CodeMirror/.DS_Store deleted file mode 100644 index 513cca5..0000000 Binary files a/CodeMirror/.DS_Store and /dev/null differ diff --git a/CodeMirror/addon/fold/foldgutter.css b/CodeMirror/addon/fold/foldgutter.css deleted file mode 100644 index ad19ae2..0000000 --- a/CodeMirror/addon/fold/foldgutter.css +++ /dev/null @@ -1,20 +0,0 @@ -.CodeMirror-foldmarker { - color: blue; - text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px; - font-family: arial; - line-height: .3; - cursor: pointer; -} -.CodeMirror-foldgutter { - width: .7em; -} -.CodeMirror-foldgutter-open, -.CodeMirror-foldgutter-folded { - cursor: pointer; -} -.CodeMirror-foldgutter-open:after { - content: "\25BE"; -} -.CodeMirror-foldgutter-folded:after { - content: "\25B8"; -} diff --git a/CodeMirror/addon/hint/show-hint.css b/CodeMirror/addon/hint/show-hint.css deleted file mode 100644 index 924e638..0000000 --- a/CodeMirror/addon/hint/show-hint.css +++ /dev/null @@ -1,38 +0,0 @@ -.CodeMirror-hints { - position: absolute; - z-index: 10; - overflow: hidden; - list-style: none; - - margin: 0; - padding: 2px; - - -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2); - -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2); - box-shadow: 2px 3px 5px rgba(0,0,0,.2); - border-radius: 3px; - border: 1px solid silver; - - background: white; - font-size: 90%; - font-family: monospace; - - max-height: 20em; - overflow-y: auto; -} - -.CodeMirror-hint { - margin: 0; - padding: 0 4px; - border-radius: 2px; - max-width: 19em; - overflow: hidden; - white-space: pre; - color: black; - cursor: pointer; -} - -li.CodeMirror-hint-active { - background: #08f; - color: white; -} diff --git a/CodeMirror/addon/lint/lint.css b/CodeMirror/addon/lint/lint.css deleted file mode 100644 index b6b42c2..0000000 --- a/CodeMirror/addon/lint/lint.css +++ /dev/null @@ -1,73 +0,0 @@ -/* The lint marker gutter */ -.CodeMirror-lint-markers { - width: 12px; margin-left: 2px; -} - -.CodeMirror-lint-tooltip { - background-color: infobackground; - border: 1px solid black; - border-radius: 4px 4px 4px 4px; - color: infotext; - font-family: monospace; - font-size: 10pt; - overflow: hidden; - padding: 2px 5px; - position: fixed; - white-space: pre; - white-space: pre-wrap; - z-index: 100; - max-width: 600px; - opacity: 0; - transition: opacity .4s; - -moz-transition: opacity .4s; - -webkit-transition: opacity .4s; - -o-transition: opacity .4s; - -ms-transition: opacity .4s; -} - -.CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning { - background-position: left bottom; - background-repeat: repeat-x; -} - -.CodeMirror-lint-mark-error { - background-image: - url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==") - ; -} - -.CodeMirror-lint-mark-warning { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII="); -} - -.CodeMirror-lint-marker-error, .CodeMirror-lint-marker-warning { - background-position: center center; - background-repeat: no-repeat; - cursor: help; - display: inline-block; - height: 16px; - width: 16px; - vertical-align: middle; - position: relative; -} - -.CodeMirror-lint-message-error, .CodeMirror-lint-message-warning { - padding-left: 18px; - background-position: top left; - background-repeat: no-repeat; -} - -.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII="); -} - -.CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII="); -} - -.CodeMirror-lint-marker-multiple { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC"); - background-repeat: no-repeat; - background-position: right bottom; - width: 100%; height: 100%; -} diff --git a/CodeMirror/addon/scroll/simplescrollbars.css b/CodeMirror/addon/scroll/simplescrollbars.css deleted file mode 100644 index d8a6d69..0000000 --- a/CodeMirror/addon/scroll/simplescrollbars.css +++ /dev/null @@ -1,68 +0,0 @@ -.CodeMirror-simplescroll-horizontal div, .CodeMirror-simplescroll-vertical div { - position: absolute; - background: #ccc; - -moz-box-sizing: border-box; - box-sizing: border-box; - border: 1px solid #bbb; - border-radius: 2px; -} - -.CodeMirror-simplescroll-horizontal, .CodeMirror-simplescroll-vertical { - position: absolute; - z-index: 6; - background: #eee; -} - -.CodeMirror-simplescroll-horizontal { - bottom: 0; left: 0; - height: 8px; -} -.CodeMirror-simplescroll-horizontal div { - bottom: 0; - height: 100%; -} - -.CodeMirror-simplescroll-vertical { - right: 0; top: 0; - width: 8px; -} -.CodeMirror-simplescroll-vertical div { - right: 0; - width: 100%; -} - - -.CodeMirror-overlayscroll .CodeMirror-scrollbar-filler, .CodeMirror-overlayscroll .CodeMirror-gutter-filler { - display: none; - background: transparent; -} - -.CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-vertical div { - position: absolute; - background: rgba(0,198,255,0.1); - border-radius: 0; -} - -.CodeMirror-overlayscroll-horizontal, .CodeMirror-overlayscroll-vertical { - position: absolute; - z-index: 6; -} - -.CodeMirror-overlayscroll-horizontal { - bottom: 0; left: 0; - height: 12px; -} -.CodeMirror-overlayscroll-horizontal div { - bottom: 0; - height: 100%; -} - -.CodeMirror-overlayscroll-vertical { - right: 0; top: 0; - width: 6px; -} -.CodeMirror-overlayscroll-vertical div { - display: none; - right: 0; - width: 100%; -} diff --git a/LICENSE.md b/LICENSE.md index 0858e74..d0a2395 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright: © 2015 ICEcoder Ltd +Copyright: © 2020 ICEcoder Ltd Website: icecoder.net Email: info@icecoder.net Twitter: @icecoder diff --git a/README.md b/README.md index 77e9bc1..12c53e3 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,13 @@ You can run ICEcoder either online or locally, on Linux, Windows or Mac based pl Either download the zip or clone from Github using: ``` -$ git clone git://github.com/mattpass/ICEcoder +$ git clone git://github.com/icecoder/ICEcoder ``` #### Step 2: Place in your document root (online or local) * Put in a new sub-dir URL such as yourdomain.com/ICEcoder or localhost/ICEcoder -* Set write permissions (757 or 775 depending on your system) on the 'backups', 'lib', 'plugins', 'test' and 'tmp' folders +* Set write permissions (757 or 775 depending on your system) on the 'data', 'lib', 'plugins' and 'tmp' folders *(Note: A small number of web servers give an internal server error here, if you get this, try 755 instead)* @@ -33,7 +33,7 @@ $ git clone git://github.com/mattpass/ICEcoder **Now you're setup, auto-logged in and ready to code!** -Suitable for commercial & non-commercial projects, just let me know if it's useful to you and any cool customisations you make to it. I take no responsibility for anything, your usage is all down to you. +Suitable for commercial & non-commercial projects, just let me know if it's useful to you and any cool customisations you make to it. I take no responsibility for anything, all usage is all down to you. It's fully open source and MIT licensed. I'm happy for you to take it, make it your own and customise to your hearts content and/or contribute to this main repo! :) diff --git a/assets/css/auto-logout-warning.css b/assets/css/auto-logout-warning.css new file mode 100644 index 0000000..51f0506 --- /dev/null +++ b/assets/css/auto-logout-warning.css @@ -0,0 +1,10 @@ +body {overflow: hidden; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + text-align: center; +} + +h1 {font-size: 36px; font-weight: normal; color: #888; margin-bottom: 20px} +.auto-logout-warning {background-color: #1c1c19; color: #fff; padding: 20px} \ No newline at end of file diff --git a/assets/css/backup-versions.css b/assets/css/backup-versions.css new file mode 100644 index 0000000..3296056 --- /dev/null +++ b/assets/css/backup-versions.css @@ -0,0 +1,29 @@ +body {overflow: hidden; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +h1 {font-size: 36px; font-weight: normal; color: #888; margin-bottom: 20px} +a {color: #fff; text-decoration: none} + +.backup-versions {background-color: #1c1c19; color: #fff; padding: 20px} +.button {padding: 5px 10px; font-size: 14px; background-color: rgba(0,198,255,0.7); margin-bottom: 20px; text-align: center; cursor: pointer} + +.cm-trailingspace { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAAB/qH1jAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QUXCToH00Y1UgAAACFJREFUCNdjPMDBUc/AwNDAAAFMTAwMDA0OP34wQgX/AQBYgwYEx4f9lQAAAABJRU5ErkJggg==); + background-position: bottom left; + background-repeat: repeat-x; +} +.CodeMirror-foldmarker {font-family: arial; line-height: .3; color: #b00; cursor: pointer; + text-shadow: #fff 1px 1px 2px, #fff -1px -1px 2px, #fff 1px -1px 2px, #fff -1px 1px 2px; +} +.CodeMirror-foldgutter {display: inline-block; width: 13px} +.CodeMirror-foldgutter-open, .CodeMirror-foldgutter-folded {position: absolute; display: inline-block; width: 13px; height: 13px; font-size: 14px; text-align: center; cursor: pointer} +.CodeMirror-foldgutter-open {background: rgba(255,255,255,0.04); color: #666} +.CodeMirror-foldgutter-open:after {position: relative; top: -2px} +.CodeMirror-foldgutter-folded {background: #800; color: #ddd} +.CodeMirror-foldgutter-folded:after {position: relative; top: -3px} + +.previewArea {display: inline-block; width: 480px; height: 550px; margin-left: 20px} diff --git a/CodeMirror/lib/codemirror.css b/assets/css/codemirror.css similarity index 55% rename from CodeMirror/lib/codemirror.css rename to assets/css/codemirror.css index ebfe4f6..65a7ef3 100644 --- a/CodeMirror/lib/codemirror.css +++ b/assets/css/codemirror.css @@ -1,39 +1,39 @@ /* BASICS */ .CodeMirror { - /* Set height, width, borders, and global font properties here */ - font-family: monospace; - height: 300px; - color: black; + /* Set height, width, borders, and global font properties here */ + font-family: monospace; + height: 300px; + color: black; } /* PADDING */ .CodeMirror-lines { - padding: 4px 0; /* Vertical padding around content */ + padding: 4px 0; /* Vertical padding around content */ } .CodeMirror pre { - padding: 0 4px; /* Horizontal padding of content */ + padding: 0 4px; /* Horizontal padding of content */ } .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { - background-color: white; /* The little square between H and V scrollbars */ + background-color: white; /* The little square between H and V scrollbars */ } /* GUTTER */ .CodeMirror-gutters { - border-right: 1px solid #ddd; - background-color: #f7f7f7; - white-space: nowrap; + border-right: 1px solid #ddd; + background-color: #f7f7f7; + white-space: nowrap; } .CodeMirror-linenumbers {} .CodeMirror-linenumber { - padding: 0 3px 0 5px; - min-width: 20px; - text-align: right; - color: #999; - white-space: nowrap; + padding: 0 3px 0 5px; + min-width: 20px; + text-align: right; + color: #999; + white-space: nowrap; } .CodeMirror-guttermarker { color: black; } @@ -42,45 +42,45 @@ /* CURSOR */ .CodeMirror-cursor { - border-left: 1px solid black; - border-right: none; - width: 0; + border-left: 1px solid black; + border-right: none; + width: 0; } /* Shown when moving in bi-directional text */ .CodeMirror div.CodeMirror-secondarycursor { - border-left: 1px solid silver; + border-left: 1px solid silver; } .cm-fat-cursor .CodeMirror-cursor { - width: auto; - border: 0; - background: #7e7; + width: auto; + border: 0; + background: #7e7; } .cm-fat-cursor div.CodeMirror-cursors { - z-index: 1; + z-index: 1; } .cm-animate-fat-cursor { - width: auto; - border: 0; - -webkit-animation: blink 1.06s steps(1) infinite; - -moz-animation: blink 1.06s steps(1) infinite; - animation: blink 1.06s steps(1) infinite; - background-color: #7e7; + width: auto; + border: 0; + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; + background-color: #7e7; } @-moz-keyframes blink { - 0% {} - 50% { background-color: transparent; } - 100% {} + 0% {} + 50% { background-color: transparent; } + 100% {} } @-webkit-keyframes blink { - 0% {} - 50% { background-color: transparent; } - 100% {} + 0% {} + 50% { background-color: transparent; } + 100% {} } @keyframes blink { - 0% {} - 50% { background-color: transparent; } - 100% {} + 0% {} + 50% { background-color: transparent; } + 100% {} } /* Can style cursor different in overwrite (non-insert) mode */ @@ -89,8 +89,8 @@ .cm-tab { display: inline-block; text-decoration: inherit; } .CodeMirror-ruler { - border-left: 1px solid #ccc; - position: absolute; + border-left: 1px solid #ccc; + position: absolute; } /* DEFAULT THEME */ @@ -144,130 +144,130 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} the editor. You probably shouldn't touch them. */ .CodeMirror { - position: relative; - overflow: hidden; - background: white; + position: relative; + overflow: hidden; + background: white; } .CodeMirror-scroll { - overflow: scroll !important; /* Things will break if this is overridden */ - /* 30px is the magic margin used to hide the element's real scrollbars */ - /* See overflow: hidden in .CodeMirror */ - margin-bottom: -30px; margin-right: -30px; - padding-bottom: 30px; - height: 100%; - outline: none; /* Prevent dragging from highlighting the element */ - position: relative; + overflow: scroll !important; /* Things will break if this is overridden */ + /* 30px is the magic margin used to hide the element's real scrollbars */ + /* See overflow: hidden in .CodeMirror */ + margin-bottom: -30px; margin-right: -30px; + padding-bottom: 30px; + height: 100%; + outline: none; /* Prevent dragging from highlighting the element */ + position: relative; } .CodeMirror-sizer { - position: relative; - border-right: 30px solid transparent; + position: relative; + border-right: 30px solid transparent; } /* The fake, visible scrollbars. Used to force redraw during scrolling before actual scrolling happens, thus preventing shaking and flickering artifacts. */ .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { - position: absolute; - z-index: 6; - display: none; + position: absolute; + z-index: 6; + display: none; } .CodeMirror-vscrollbar { - right: 0; top: 0; - overflow-x: hidden; - overflow-y: scroll; + right: 0; top: 0; + overflow-x: hidden; + overflow-y: scroll; } .CodeMirror-hscrollbar { - bottom: 0; left: 0; - overflow-y: hidden; - overflow-x: scroll; + bottom: 0; left: 0; + overflow-y: hidden; + overflow-x: scroll; } .CodeMirror-scrollbar-filler { - right: 0; bottom: 0; + right: 0; bottom: 0; } .CodeMirror-gutter-filler { - left: 0; bottom: 0; + left: 0; bottom: 0; } .CodeMirror-gutters { - position: absolute; left: 0; top: 0; - z-index: 3; + position: absolute; left: 0; top: 0; + z-index: 3; } .CodeMirror-gutter { - white-space: normal; - height: 100%; - display: inline-block; - vertical-align: top; - margin-bottom: -30px; - /* Hack to make IE7 behave */ - *zoom:1; - *display:inline; + white-space: normal; + height: 100%; + display: inline-block; + vertical-align: top; + margin-bottom: -30px; + /* Hack to make IE7 behave */ + *zoom:1; + *display:inline; } .CodeMirror-gutter-wrapper { - position: absolute; - z-index: 4; - background: none !important; - border: none !important; + position: absolute; + z-index: 4; + background: none !important; + border: none !important; } .CodeMirror-gutter-background { - position: absolute; - top: 0; bottom: 0; - z-index: 4; + position: absolute; + top: 0; bottom: 0; + z-index: 4; } .CodeMirror-gutter-elt { - position: absolute; - cursor: default; - z-index: 4; + position: absolute; + cursor: default; + z-index: 4; } .CodeMirror-gutter-wrapper { - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; } .CodeMirror-lines { - cursor: text; - min-height: 1px; /* prevents collapsing before first draw */ + cursor: text; + min-height: 1px; /* prevents collapsing before first draw */ } .CodeMirror pre { - /* Reset some styles that the rest of the page might have set */ - -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; - border-width: 0; - background: transparent; - font-family: inherit; - font-size: inherit; - margin: 0; - white-space: pre; - word-wrap: normal; - line-height: inherit; - color: inherit; - z-index: 2; - position: relative; - overflow: visible; - -webkit-tap-highlight-color: transparent; + /* Reset some styles that the rest of the page might have set */ + -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; + border-width: 0; + background: transparent; + font-family: inherit; + font-size: inherit; + margin: 0; + white-space: pre; + word-wrap: normal; + line-height: inherit; + color: inherit; + z-index: 2; + position: relative; + overflow: visible; + -webkit-tap-highlight-color: transparent; } .CodeMirror-wrap pre { - word-wrap: break-word; - white-space: pre-wrap; - word-break: normal; + word-wrap: break-word; + white-space: pre-wrap; + word-break: normal; } .CodeMirror-linebackground { - position: absolute; - left: 0; right: 0; top: 0; bottom: 0; - z-index: 0; + position: absolute; + left: 0; right: 0; top: 0; bottom: 0; + z-index: 0; } .CodeMirror-linewidget { - position: relative; - z-index: 2; - overflow: auto; + position: relative; + z-index: 2; + overflow: auto; } .CodeMirror-widget {} .CodeMirror-code { - outline: none; + outline: none; } /* Force content-box sizing for the elements where we expect it */ @@ -276,32 +276,32 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} .CodeMirror-gutter, .CodeMirror-gutters, .CodeMirror-linenumber { - -moz-box-sizing: content-box; - box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; } .CodeMirror-measure { - position: absolute; - width: 100%; - height: 0; - overflow: hidden; - visibility: hidden; + position: absolute; + width: 100%; + height: 0; + overflow: hidden; + visibility: hidden; } .CodeMirror-cursor { position: absolute; } .CodeMirror-measure pre { position: static; } div.CodeMirror-cursors { - visibility: hidden; - position: relative; - z-index: 3; + visibility: hidden; + position: relative; + z-index: 3; } div.CodeMirror-dragcursors { - visibility: visible; + visibility: visible; } .CodeMirror-focused div.CodeMirror-cursors { - visibility: visible; + visibility: visible; } .CodeMirror-selected { background: #d9d9d9; } @@ -311,8 +311,8 @@ div.CodeMirror-dragcursors { .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } .cm-searching { - background: #ffa; - background: rgba(255, 255, 0, .4); + background: #ffa; + background: rgba(255, 255, 0, .4); } /* IE7 hack to prevent it from returning funny offsetTops on the spans */ @@ -322,10 +322,10 @@ div.CodeMirror-dragcursors { .cm-force-border { padding-right: .1px; } @media print { - /* Hide the cursor when printing */ - .CodeMirror div.CodeMirror-cursors { - visibility: hidden; - } + /* Hide the cursor when printing */ + .CodeMirror div.CodeMirror-cursors { + visibility: hidden; + } } /* See issue #2901 */ diff --git a/lib/adminer.css b/assets/css/database.css similarity index 100% rename from lib/adminer.css rename to assets/css/database.css diff --git a/assets/css/editor.css b/assets/css/editor.css new file mode 100644 index 0000000..9d283e2 --- /dev/null +++ b/assets/css/editor.css @@ -0,0 +1,40 @@ +/* ICEcoder default theme by Matt Pass */ + +.cm-s-icecoder {color: #888; background: #1d1d1b} + +.cm-s-icecoder span.cm-keyword {color: #eee; font-weight: bold} /* off-white 1 */ +.cm-s-icecoder span.cm-atom {color: #e1c76e} /* yellow */ +.cm-s-icecoder span.cm-number {color: #6cb5d9} /* blue */ +.cm-s-icecoder span.cm-def {color: #b9ca4a} /* green */ + +.cm-s-icecoder span.cm-variable {color: #6cb5d9} /* blue */ +.cm-s-icecoder span.cm-variable-2 {color: #cc1e5c} /* pink */ +.cm-s-icecoder span.cm-variable-3 {color: #f9602c} /* orange */ + +.cm-s-icecoder span.cm-property {color: #eee} /* off-white 1 */ +.cm-s-icecoder span.cm-operator {color: #9179bb} /* purple */ +.cm-s-icecoder span.cm-comment {color: #444; font-style: italic} /* dark-grey */ + +.cm-s-icecoder span.cm-string {color: #b9ca4a} /* green */ +.cm-s-icecoder span.cm-string-2 {color: #6cb5d9} /* blue */ + +.cm-s-icecoder span.cm-meta {color: #888} /* grey */ + +.cm-s-icecoder span.cm-qualifier {color: #555} /* grey */ +.cm-s-icecoder span.cm-builtin {color: #214e7b} /* bright blue */ +.cm-s-icecoder span.cm-bracket {color: #cc7} /* grey-yellow */ + +.cm-s-icecoder span.cm-tag {color: #e8e8e8} /* off-white 2 */ +.cm-s-icecoder span.cm-attribute {color: #099} /* teal */ + +.cm-s-icecoder span.cm-header {color: #6a0d6a} /* purple-pink */ +.cm-s-icecoder span.cm-quote {color: #186718} /* dark green */ +.cm-s-icecoder span.cm-hr {color: #888} /* mid-grey */ +.cm-s-icecoder span.cm-link {color: #e1c76e} /* yellow */ +.cm-s-icecoder span.cm-error {color: #d00} /* red */ + +.cm-s-icecoder .CodeMirror-cursor {border-left: 1px solid #ee0 !important; box-shadow: -1px 0 0 0 #ee0} +.cm-s-icecoder .CodeMirror-selected {color: #fff !important; background: #037 !important} +.cm-s-icecoder .CodeMirror-gutters {background: #1d1d1b; min-width: 41px; border-right: 0} +.cm-s-icecoder .CodeMirror-linenumber {color: #555; cursor: default} +.cm-s-icecoder .CodeMirror-matchingbracket {color: #fff !important; background: #555 !important} diff --git a/assets/css/farbtastic.css b/assets/css/farbtastic.css new file mode 100644 index 0000000..b32cbd0 --- /dev/null +++ b/assets/css/farbtastic.css @@ -0,0 +1,49 @@ +.picker { + display: inline-block; +} +.farbtastic { + position: relative; +} +.farbtastic * { + position: absolute; + cursor: crosshair; +} +.farbtastic, .farbtastic .wheel { + width: 195px; + height: 195px; +} +.farbtastic .color, .farbtastic .overlay { + top: 47px; + left: 47px; + width: 101px; + height: 101px; +} +.farbtastic .wheel { + background: url('../images/wheel.png') no-repeat; + width: 195px; + height: 195px; +} +.farbtastic .overlay { + background: url('../images/mask.png') no-repeat; +} +.farbtastic .marker { + width: 17px; + height: 17px; + margin: -8px 0 0 -8px; + overflow: hidden; + background: url('../images/marker.png') no-repeat; +} +.colorValue { + border: 0; + width: 105px; + padding-left: 5px +} +.insertColorValue { + background: #888; + color: #fff; + height: 18px; + width: 60px; + border: 0; + margin-left: 5px; + cursor: pointer +} \ No newline at end of file diff --git a/lib/file-type-icons.css b/assets/css/file-type-icons.css similarity index 100% rename from lib/file-type-icons.css rename to assets/css/file-type-icons.css diff --git a/lib/file-types.css b/assets/css/file-types.css similarity index 65% rename from lib/file-types.css rename to assets/css/file-types.css index efda368..88a7912 100644 --- a/lib/file-types.css +++ b/assets/css/file-types.css @@ -1,19 +1,19 @@ .fileManager { - margin: 0 0 15px 20px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; + margin: 0 0 15px 20px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } .fileManager span {font-family: helvetica, arial, swiss, verdana; padding: 1px 3px; border-radius: 3px} .fileManager a {color: #eee; text-decoration: none; cursor: pointer} .fileManager .pft-directory, .fileManager .pft-file {list-style-image: url(../images/blank.gif)} .fileManager ul, .fileManager li {margin-left: 15px; white-space: nowrap} - + /* Default file */ .fileManager LI.pft-directory:before, .fileManager LI.pft-file:before { - position: absolute; width: 16px; height: 16px; content: ""; margin-top: -2px; margin-left: -20px; background:url(../images/file-folder-icons.png) no-repeat 0 0; + position: absolute; width: 16px; height: 16px; content: ""; margin-top: -2px; margin-left: -20px; background:url('../images/file-folder-icons.png') no-repeat 0 0; } .fileManager LI.dirOpen:before {background-position: -16px 0} .fileManager LI.pft-file:before {background-position: -32px 0} diff --git a/assets/css/files.css b/assets/css/files.css new file mode 100644 index 0000000..98ab7bc --- /dev/null +++ b/assets/css/files.css @@ -0,0 +1,22 @@ +::-webkit-scrollbar {width: 12px; height: 12px} +::-webkit-scrollbar-thumb {background: rgba(0,198,255,0.2)} +::-webkit-scrollbar-thumb:hover {background: rgba(0,198,255,0.8)} +::-webkit-scrollbar-corner, ::-webkit-scrollbar-resizer {background: transparent} + +body {margin: 0; overflow: auto; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.lock {position: fixed; display: inline-block; width: 12px; height: 16px; background: url('../images/file-manager-icons.png') 0 0 no-repeat; right: 0; margin-right: 16px; top: 0; cursor: pointer} +.refresh {position: fixed; display: inline-block; width: 14px; height: 14px; background: url('../images/file-manager-icons.png') -32px 0 no-repeat; right: 0; margin-right: 15px; top: 25px; cursor: pointer} +.plugins {position: fixed; display: inline-block; width: 16px; height: 16px; background: url('../images/file-manager-icons.png') -64px 0 no-repeat; right: 0; margin-right: 15px; top: 47px; cursor: pointer} +.fmDragBox {position: absolute; display: inline-block; width: 0; height: 0; top: 0; left: 0; background: rgba(0,198,255,0.3); + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +li a span:first-of-type {white-space: pre} diff --git a/assets/css/foldgutter.css b/assets/css/foldgutter.css new file mode 100644 index 0000000..4b87b6a --- /dev/null +++ b/assets/css/foldgutter.css @@ -0,0 +1,20 @@ +.CodeMirror-foldmarker { + color: blue; + text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px; + font-family: arial; + line-height: .3; + cursor: pointer; +} +.CodeMirror-foldgutter { + width: .7em; +} +.CodeMirror-foldgutter-open, +.CodeMirror-foldgutter-folded { + cursor: pointer; +} +.CodeMirror-foldgutter-open:after { + content: "\25BE"; +} +.CodeMirror-foldgutter-folded:after { + content: "\25B8"; +} diff --git a/assets/css/ftp-manager.css b/assets/css/ftp-manager.css new file mode 100644 index 0000000..c44357f --- /dev/null +++ b/assets/css/ftp-manager.css @@ -0,0 +1,16 @@ +body {overflow: hidden; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +h1 {font-size: 36px; font-weight: normal; color: #888; margin-bottom: 20px} +a {color: #2187e7; text-decoration: none} +input {padding: 4px; border: 0; background-color: #444; color: #fff} +select {padding: 3px 4px; border: 0; background-color: #444; color: #fff} +input:focus {outline: none; background: rgba(0,198,255,0.5); color: #fff} +select:focus {outline: none} + +.ftpManager {background-color: #1c1c19; color: #fff; padding: 20px} +.ftpManager .info {font-size: 10px; color: rgba(0,198,255,0.7); cursor: help} \ No newline at end of file diff --git a/assets/css/help.css b/assets/css/help.css new file mode 100644 index 0000000..af8cbd0 --- /dev/null +++ b/assets/css/help.css @@ -0,0 +1,15 @@ +body {overflow: hidden; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +h1 {font-size: 36px; font-weight: normal; color: #888; margin-bottom: 20px} +a {color: #fff; text-decoration: none} + +.help {background-color: #1c1c19; color: #fff; padding: 20px} +.key {display: inline-block; width: 175px; text-align: right; margin-right: 5px; float: left} +.key .plus {color: #888} +.shortcut {display: inline-block; width: 195px; color: #888; margin-left: 5px; float: left} +.info {font-size: 10px; color: rgba(0,198,255,0.7); cursor: help} \ No newline at end of file diff --git a/assets/css/icecoder.css b/assets/css/icecoder.css new file mode 100644 index 0000000..b8d66ce --- /dev/null +++ b/assets/css/icecoder.css @@ -0,0 +1,162 @@ +body {overflow: hidden; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-color: #1d1d1b; +} + +h1 {font-size: 36px; font-weight: normal; color: #888; margin-bottom: 20px} +h2 {font-size: 18px; font-weight: normal; color: #fff} + +.blackMask {position: fixed; display: table; width: 100%; height: 100%; top: 0; left: 0; visibility: hidden; background-color: rgba(0,0,0,0.8); text-align: center; z-index: 100} +.blackMask .popupVCenter {#position: absolute; display: table-cell; #top: 50%; vertical-align: middle; text-align: center} +.popupVCenter .popup {#position: relative; #top: -50%; text-align: center; color: #fff; font-size: 10px} +.floatingContainer {position: absolute; top: 0; left: 0; width: 55px; height: 55px; visibility: hidden; border: solid 1px #444; image-rendering: pixelated} +.floatingContainer:before {position: absolute; display: inline-block; width: 3px; height: 3px; left: 25px; top: 25px; content: ''; border: solid 1px #b00} +.whiteGlow {box-shadow: 0 0 8px 2px rgba(255,255,255,0.6)} + +.spinner {width: 50px; height: 50px; margin: 20px auto; background: #fff; + -webkit-animation: rotateplane 1.2s infinite ease-in-out; + animation: rotateplane 1.2s infinite ease-in-out; +} + +@-webkit-keyframes rotateplane { + 0% {-webkit-transform: perspective(120px)} + 50% {-webkit-transform: perspective(120px) rotateY(180deg)} + 100% {-webkit-transform: perspective(120px) rotateY(180deg) rotateX(180deg)} +} + +@keyframes rotateplane { + 0% {transform: perspective(120px) rotateX(0deg) rotateY(0deg)} + 50% {transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg)} + 100% {transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg)} +} + +.infoBlackMask {position: fixed; display: none; width: 0; height: 0; top: 0; left: 0; border: solid 10000px rgba(0,0,0,0); transition: all 0.5s ease; z-index: 100} +.infoMessageContainer {position: fixed; display: none; width: 100%; height: 100%; top: 0; left: 0; text-align: center; opacity: 0; transition: all 0.5s ease; z-index: 100} +.infoMessage {position: relative; margin: -80px auto 0 auto; height: 160px; width: 500px; font-size: 14px; line-height: 21px; color: #fff} +.infoMessage .title {margin-bottom: 18px; font-size: 18px; font-weight: bold; color: #f80} +.infoMessage .button {position: absolute; bottom: 0; left: 190px; width: 100px; padding: 5px 10px; font-size: 18px; background-color: rgba(0,198,255,0.7); cursor: pointer} + +.plugins {position: absolute; display: inline-block; width: 3px; height: 100%; top: 0; background: transparent; overflow: hidden; z-index: 3; + transition: all 0.07s ease-out; +} + +.header {position: absolute; display: inline-block; top: 0; left: 0; width: 100%; height: 15px; background-color: #fff; text-align: right; z-index: 1} + +.files {position: absolute; display: inline-block; top: 0; left: 0; height: 100%; width: 250px; background-color: #444; overflow: hidden; z-index: 2; + transition: background 0.2s ease-out; +} +.files .fileNav {display: block; height: 36px} +.files .fileNav ul {list-style-type: none; line-height: 20px; padding-left: 15px} +.files .fileNav li {display: inline-block; padding: 18px 15px 0 0} +.files .fileNav a {color: #666; text-decoration: none} +.files .fileNav a:hover {color: #fff; cursor: pointer} + +.files .options { + transition: opacity 0.15s ease-in-out; +} +.files .options .optionsList {position: absolute; display: inline-block; visibility: hidden; background: #383838; height: 100%; padding: 23px 15px 15px 15px} +.files .options .optionsList ul {list-style-type: none; line-height: 24px} +.files .options .optionsList a {color: #666; text-decoration: none} +.files .options .optionsList a:hover {color: #fff; cursor: pointer} +.files input:focus, .password:focus {outline: 0; box-shadow: 0 0 10px 1px rgba(0,198,255,0.7)} +.files .button {position: absolute; border: 0; background: #444; color: #eee; height: 20px; margin-top: 16px; margin-left: 5px; font-size: 11px; cursor: pointer} +.files .button:hover {background-color: #1d1d1b; color: #eee} +.files .frame {display: inline-block; width: 250px; margin-top: 24px} +.files .serverMessage {position: absolute; display: inline-block; width: 450px; bottom: 0; background-color: rgba(255,255,255,0.8); font-size: 10px; padding: 4px 12px 1px 12px; opacity: 0; + transition: opacity 0.2s; +} +.files .serverMessage b {font-size: 10px} + +.files .tools {position: absolute; display: inline-block; width: 250px; height: 30px; left: 0; bottom: 0} +.files .tools div {display: inline-block; margin: 8px 0 8px 15px; color: #666; cursor: pointer} +.files .tools div:hover {color: #fff; cursor: pointer} +.files .tools .highlight {color: #eee; padding: 1px 3px; margin: 7px -3px 7px 12px; border-radius: 3px} +.files .tools .error {background: #800} +.files .tools .info {background: #080} + +.editor {position: absolute; display: inline-block; top: 0; left: 15px; width: 2400px} +.editor .tabsBar {display: inline-block; height: 27px; width: 2400px; margin-top: 15px; padding-left: 53px; background: #fff} +.tabsBar .tab {position: absolute; display: none; height: 15px; padding: 6px 8px 6px 9px; border-right: 1px solid #ddd; color: #fff; white-space: nowrap; overflow: hidden; cursor: pointer; z-index: 1; + transition: width 0.15s ease-in-out; + transition: left 0.15s ease-in-out; +} +.tabsBar .tabSlide { + transition: left 0.15s ease-in-out; +} +.tabsBar .tabDrag { + transition: left 0s ease-in-out; +} +.tabsBar .tab .closeTab, .tabsBar .closeAllTabs {margin: 2px 0 0 5px; border-radius: 6px; cursor: pointer} +.tabsBar .tab .closeTab {position: absolute; right: 7px} +.tabsBar .alphaTabs {position: absolute; margin: 8px 0 0 -20px; border-radius: 6px; cursor: pointer} +.tabsBar .alphaTabs:hover {background: #ccc} +.tabsBar .closeAllTabs {position: absolute; margin: 8px 0 0 -36px} +.tabsBar .closeAllTabs:hover {background: #ccc} +.tabsBar .newTab {position: absolute; display: inline-block; height: 15px; padding: 7px 7px 5px 7px; cursor: pointer; z-index: 0; + transition: left 0.15s ease-in-out; +} +.editor .findBar {display: inline-block; height: 28px; width: 2400px; color: #fff; background-color: #1d1d1b} +.findBar .findReplace {position: absolute; padding-top: 2px; z-index: 1} +.findReplace .selectWrapper {position: relative; display: inline-block; margin-top: -6px} +.findReplace .selectWrapper select {position: relative; padding: 1px; border: 0; background: url('../images/select-arrow.gif') no-repeat right #1d1d1b; color: #fff; font-size: 10px; height: 28px; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} +.findReplace .selectWrapper select:focus {outline: 0} +.findReplace .findText {display: inline-block; height: 28px; font-size: 10px; margin: 7px 3px 0 27px} +.findReplace .find {position: relative; width: 167px; height: 28px; border: 0; top: -2px; font-size: 12px; padding-left: 5px; margin-right: 3px; background: #444; color: #eee} +.findReplace .replaceText {height: 28px; font-size: 10px; margin: 6px 3px 0 2px} +.findReplace .replace {position: relative; width: 167px; height: 28px; border: 0; top: -2px; font-size: 12px; padding-left: 5px; background: #444; color: #eee} +.findReplace .withText {display: inline-block; height: 28px; font-size: 10px; margin-top: 1px} +.findReplace .targetText {height: 28px; font-size: 10px; margin: 7px 3px 0 2px} +.findReplace .button {position: relative; top: -2px; height: 28px; padding-top: 2px; border: 0; background-color: #1d1d1b; color: #eee; font-size: 10px; margin-left: 2px; cursor: pointer} +.findReplace .button:hover {background-color: #2187e7; color: #eee} +.findReplace .results {position: relative; display: inline-block; width: 200px; height: 20px; font-size: 10px; margin: 7px 0 0 20px} +.findBar .goLine {position: fixed; display: inline-block; width: 120px; right: 65px; top: 49px; height: 21px; font-size: 10px; color: #eee; cursor: default; z-index: 1} +.goLine .goToLine {width: 50px; height: 28px; border: 0; font-size: 12px; margin: -7px 0 0 3px; padding-left: 4px; background: #333; color: #eee} +.findReplace input:focus, .goLine .goToLine:focus {outline: none; background: rgba(0,198,255,0.5); color: #fff} +.findBar .view {position: fixed; display: inline-block; width: 16px; height: 16px; right: 40px; top: 49px; background: url('../images/file-manager-icons.png') 0 0 no-repeat; cursor: pointer; background-position: -48px 0; opacity: 0.3} +.findBar .bug {position: fixed; display: inline-block; width: 16px; height: 16px; right: 15px; top: 48px; background: url('../images/bug-reporting-icons.png') 0 0 no-repeat; cursor: pointer; background-position: 0 0} +.editor .terminal {position: fixed; top: 10000px; transition: top 0.2s ease; z-index: 2} +.editor .output {position: fixed; top: 10000px; padding: 15px 18px 8px 13px; transition: top 0.2s ease; background: rgba(0,0,0,0.92); z-index: 2} +.editor .database {position: fixed; top: 10000px; transition: top 0.2s ease; z-index: 2} +.editor .git {position: fixed; top: 10000px; padding: 15px 18px 15px 13px; overflow-y: auto; transition: top 0.2s ease; background: rgba(0,0,0,0.92); z-index: 2} +.editor .git::-webkit-scrollbar {width: 12px; height: 12px} +.editor .git::-webkit-scrollbar-thumb {background: rgba(0,198,255,0.2)} +.editor .git::-webkit-scrollbar-thumb:hover {background: rgba(0,198,255,0.8)} +.editor .git::-webkit-scrollbar-corner, .editor .git::-webkit-scrollbar-resizer {background: transparent} +.editor .git .link {font-family: monospace; cursor: pointer} +.editor .git .link:hover {color: rgba(0,198,255,1)} +.editor .code {position: relative; display: inline-block; top: 28px; width: 600px; height: 600px; visibility: hidden} + +.footer {position: fixed; display: inline-block; width: 100%; height: 30px; bottom: 0; background-color: rgba(0,0,0,0.15); left: 0; z-index: 1} +.footer .versionsDisplay {position: absolute; display: inline-block; padding: 5px; margin-top: 3px; left: 275px; color: #fff; cursor: pointer} +.footer .splitPaneControls {position: absolute; display: inline-block; width: 50px; text-align: center; padding: 6px} +.footer .splitPaneControls .off {display: inline-block; width: 18px; height: 18px; margin-right: 10px; background: url('../images/split-pane-controls.gif') no-repeat 0 0; cursor: pointer} +.footer .splitPaneControls .on {display: inline-block; width: 19px; height: 18px; background: url('../images/split-pane-controls.gif') no-repeat -18px 0; cursor: pointer} +.footer .splitPaneNames {position: absolute; display: inline-block; width: 100px; text-align: center; margin-top: 9px; color: #555; opacity: 0; transition: opacity 0.3s ease-in-out} +.footer .charDisplay, .footer .byteDisplay {position: absolute; display: inline-block; padding: 5px 0 0 8px; margin-top: 3px; margin-right: 10px; right: 0; font-size: 12px; color: #fff; text-align: right; text-align: right; cursor: pointer} + +.fileMenu {position: absolute; display: none; left: 0; top: 0; background-color: #333; z-index: 10; + transition: opacity 0.15s; +} +.fileMenu a {display: block; padding: 2px 5px; background-color: #333; color: #eee; text-decoration: none} +.fileMenu a:hover {background-color: #666} +.fileMenu hr {border: 0; height: 1px; padding: 0; margin: 0; background: #444} + +.tooltip {position: absolute; top: 0; left: 0; padding: 5px; background: #444; color: #bbb} + +.screenContainer {position: absolute; display: table; width: 100%; height: 100%; top: 0; left: 0; text-align: center} +.screenContainer .screenVCenter {#position: absolute; display: table-cell; #top: 50%; vertical-align: middle; text-align: center} +.screenVCenter .screenCenter {#position: relative; #top: -50%; text-align: center; display: inline} +.screenCenter .version {position: relative; display: block; margin: 5px 0 15px 0; font-size: 10px; color: #bbb} +.screenCenter .text {position: relative; display: block; margin-top: 15px; font-size: 10px; color: #888} +.screenCenter .text input {margin-top: 1px} +.screenCenter .text a {position: relative; display: block; margin-top: 15px; font-size: 10px; color: #888; text-decoration: none} +.screenCenter .password {border: 0; background-color: #333; color: #fff; height: 20px} +.screenCenter .button {border: 0; background: #444; color: #eee; height: 22px; cursor: pointer} +.screenCenter .button:hover {background: #2187e7; color: #eee} diff --git a/assets/css/lint.css b/assets/css/lint.css new file mode 100644 index 0000000..079faf3 --- /dev/null +++ b/assets/css/lint.css @@ -0,0 +1,72 @@ +/* The lint marker gutter */ +.CodeMirror-lint-markers { + width: 12px; margin-left: 2px; +} + +.CodeMirror-lint-tooltip { + background-color: #444; + border: 0; + color: #bbb; + font-family: arial, verdana, helvetica, sans-serif; + font-size: 13px; + overflow: hidden; + padding: 5px; + position: fixed; + white-space: pre; + white-space: pre-wrap; + z-index: 100; + max-width: 600px; + opacity: 0; + transition: opacity .4s; + -moz-transition: opacity .4s; + -webkit-transition: opacity .4s; + -o-transition: opacity .4s; + -ms-transition: opacity .4s; +} + +.CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning { + background-position: left bottom; + background-repeat: repeat-x; +} + +.CodeMirror-lint-mark-error { + background-image: + url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==") +; +} + +.CodeMirror-lint-mark-warning { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII="); +} + +.CodeMirror-lint-marker-error, .CodeMirror-lint-marker-warning { + background-position: center center; + background-repeat: no-repeat; + cursor: help; + display: inline-block; + height: 16px; + width: 16px; + vertical-align: middle; + position: relative; +} + +.CodeMirror-lint-message-error, .CodeMirror-lint-message-warning { + padding-left: 18px; + background-position: top left; + background-repeat: no-repeat; +} + +.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII="); +} + +.CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII="); +} + +.CodeMirror-lint-marker-multiple { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC"); + background-repeat: no-repeat; + background-position: right bottom; + width: 100%; height: 100%; +} diff --git a/lib/multiple-results.css b/assets/css/multiple-results.css similarity index 53% rename from lib/multiple-results.css rename to assets/css/multiple-results.css index d642b49..aaffeaa 100644 --- a/lib/multiple-results.css +++ b/assets/css/multiple-results.css @@ -1,33 +1,15 @@ -/* First, reset everything to a standard */ -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, font, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td { - border: 0; - margin: 0; - padding: 0; - outline: 0; - font-size: 12px; - vertical-align: top; -} - body {overflow: hidden; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } h1 {font-size: 36px; font-weight: normal; color: #888; margin: 20px 20px 0 20px} h2 {font-size: 18px; font-weight: normal; color: #fff} hr {border: 0; height: 1px; background-color: #888} -.results {font-family: arial, verdana, helvetica, sans-serif; background-color: #1c1c19; color: #fff} +.results {background-color: #1c1c19; color: #fff} .results .resultsPane {position: relative; width: 660px; height: 340px; overflow: auto; font-size: 10px; padding: 20px; float: left} .results .resultsPane a {color: rgba(0,198,255,0.7); text-decoration: none} .results .resultsPane a:hover {text-decoration: underline} diff --git a/assets/css/plugins-manager.css b/assets/css/plugins-manager.css new file mode 100644 index 0000000..bf49b71 --- /dev/null +++ b/assets/css/plugins-manager.css @@ -0,0 +1,24 @@ +body {overflow: hidden; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +h1 {font-size: 36px; font-weight: normal; color: #888; margin-bottom: 20px} +a {color: #fff; text-decoration: none} +input {margin-top: 3px; padding: 4px; border: 0; background-color: #444; color: #fff} +input:focus {outline: none; background: rgba(0,198,255,0.5); color: #fff} + +.pluginsManager {background-color: #1c1c19; color: #fff; padding: 20px} + +.pluginsPane { + display: inline-block; + width: 760px; + height: 340px; + overflow-y: auto; +} +.pluginsPane::-webkit-scrollbar {width: 12px; height: 12px} +.pluginsPane::-webkit-scrollbar-thumb {background: rgba(0,198,255,0.2)} +.pluginsPane::-webkit-scrollbar-thumb:hover {background: rgba(0,198,255,0.8)} +.pluginsPane::-webkit-scrollbar-corner, .pluginsPane::-webkit-scrollbar-resizer {background: transparent} diff --git a/assets/css/properties.css b/assets/css/properties.css new file mode 100644 index 0000000..babdfbf --- /dev/null +++ b/assets/css/properties.css @@ -0,0 +1,12 @@ +body {overflow: hidden;} + +h1 {font-size: 36px; font-weight: normal; color: #888; margin-bottom: 20px} +th {padding-left: 23px; padding-bottom: 5px} +th, td {text-align: left; font-size: 10px} + +.properties {background-color: #1c1c19; color: #fff; padding: 20px} +.properties .column {display: inline-block; width: 210px; font-size: 10px; float: left} + +.properties .permText {margin-top: 3px; padding: 4px; border: 0; background-color: #444; color: #fff} +.properties .permText:focus {outline: none; background: rgba(0,198,255,0.5); color: #fff} +.properties .update {position: absolute; bottom: 0; right: 20px; padding: 5px 10px; font-size: 18px; background-color: rgba(0,198,255,0.7); opacity: 0.1; cursor: pointer} \ No newline at end of file diff --git a/assets/css/resets.css b/assets/css/resets.css new file mode 100644 index 0000000..41dc070 --- /dev/null +++ b/assets/css/resets.css @@ -0,0 +1,18 @@ +/* Reset everything to a standard */ +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, font, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td { + font-family: arial, verdana, helvetica, sans-serif; + border: 0; + margin: 0; + padding: 0; + outline: 0; + font-size: 12px; + vertical-align: top; +} \ No newline at end of file diff --git a/lib/settings-screen.css b/assets/css/settings-screen.css similarity index 81% rename from lib/settings-screen.css rename to assets/css/settings-screen.css index 532461c..ac2d72e 100644 --- a/lib/settings-screen.css +++ b/assets/css/settings-screen.css @@ -8,20 +8,20 @@ b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td { - border: 0; - margin: 0; - padding: 0; - outline: 0; - /*font-size: 12px;*/ - vertical-align: top; + border: 0; + margin: 0; + padding: 0; + outline: 0; + /*font-size: 12px;*/ + vertical-align: top; } body {overflow: hidden; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select:none; - user-select: none; - font-size: 12px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select:none; + user-select: none; + font-size: 12px; } h1 {font-size: 36px; font-weight: normal; color: #888; margin-bottom: 20px} @@ -39,10 +39,10 @@ h2 {font-size: 18px; font-weight: normal; color: #fff} .settings .section {width: 640px; height: 450px; padding: 20px; margin-top: 10px; float: left} .settings input, .settings textarea {border: 1px solid #555; background-color: #444; color: #fff} .settings input:focus, .settings textarea:focus { - outline: none; - -webkit-box-shadow: 0 0 10px 1px rgba(0,198,255,0.7); - -moz-box-shadow: 0 0 10px 1px rgba(0,198,255,0.7); - box-shadow: 0 0 10px 1px rgba(0,198,255,0.7); + outline: none; + -webkit-box-shadow: 0 0 10px 1px rgba(0,198,255,0.7); + -moz-box-shadow: 0 0 10px 1px rgba(0,198,255,0.7); + box-shadow: 0 0 10px 1px rgba(0,198,255,0.7); } .settings input[type="text"]:disabled {opacity: 0.5} .settings .info {font-size: 10px; color: rgba(0,198,255,0.7); cursor: help} diff --git a/assets/css/show-hint.css b/assets/css/show-hint.css new file mode 100644 index 0000000..dd26dd3 --- /dev/null +++ b/assets/css/show-hint.css @@ -0,0 +1,38 @@ +.CodeMirror-hints { + position: absolute; + z-index: 10; + overflow: hidden; + list-style: none; + + margin: 0; + padding: 2px; + + -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2); + -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2); + box-shadow: 2px 3px 5px rgba(0,0,0,.2); + border-radius: 3px; + border: 1px solid silver; + + background: white; + font-size: 90%; + font-family: monospace; + + max-height: 20em; + overflow-y: auto; +} + +.CodeMirror-hint { + margin: 0; + padding: 0 4px; + border-radius: 2px; + max-width: 19em; + overflow: hidden; + white-space: pre; + color: black; + cursor: pointer; +} + +li.CodeMirror-hint-active { + background: #08f; + color: white; +} diff --git a/assets/css/simplescrollbars.css b/assets/css/simplescrollbars.css new file mode 100644 index 0000000..bf1137d --- /dev/null +++ b/assets/css/simplescrollbars.css @@ -0,0 +1,69 @@ +/* General styles */ + +.CodeMirror-simplescroll-horizontal, +.CodeMirror-overlayscroll-horizontal { + bottom: 0; + left: 0; + height: 12px; +} +.CodeMirror-simplescroll-horizontal div, +.CodeMirror-overlayscroll-horizontal div { + bottom: 0; + height: 100%; +} + +.CodeMirror-simplescroll-vertical, +.CodeMirror-overlayscroll-vertical{ + right: 0; + top: 0; + width: 12px; +} +.CodeMirror-simplescroll-vertical div, +.CodeMirror-overlayscroll-vertical div { + right: 0; + width: 100%; +} + +/* Simple specific styles*/ + +.CodeMirror-simplescroll-horizontal, .CodeMirror-simplescroll-vertical { + position: absolute; + background: #222; + z-index: 6; +} + +.CodeMirror-simplescroll-horizontal div, .CodeMirror-simplescroll-vertical div { + position: absolute; + -moz-box-sizing: border-box; + box-sizing: border-box; + background: rgba(0,198,255,0.2); +} + +.CodeMirror-simplescroll-horizontal div:hover, .CodeMirror-simplescroll-vertical div:hover { + background: rgba(0,198,255,0.8); +} + +.CodeMirror-simplescroll .CodeMirror-scrollbar-filler, .CodeMirror-simplescroll .CodeMirror-gutter-filler { + background: #222; +} + +/* Overlay specific styles */ + +.CodeMirror-overlayscroll-horizontal, .CodeMirror-overlayscroll-vertical { + position: absolute; + z-index: 6; +} + +.CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-vertical div { + position: absolute; + background: rgba(0,198,255,0.2); +} + +.CodeMirror-overlayscroll-horizontal div:hover, .CodeMirror-overlayscroll-vertical div:hover { + background: rgba(0,198,255,0.8); +} + +.CodeMirror-overlayscroll .CodeMirror-scrollbar-filler, .CodeMirror-overlayscroll .CodeMirror-gutter-filler { + display: none; + background: transparent; +} diff --git a/lib/terminal.css b/assets/css/terminal.css similarity index 82% rename from lib/terminal.css rename to assets/css/terminal.css index b860b3b..ca5f7be 100644 --- a/lib/terminal.css +++ b/assets/css/terminal.css @@ -8,20 +8,21 @@ b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, input, label, legend, table, caption, tbody, tfoot, thead, tr, th, td { - font-family: monospace, courier, sans-serif; - border: 0; - margin: 0; - padding: 0; - outline: 0; - font-size: 12px; - vertical-align: top; + font-family: monospace, courier, sans-serif; + border: 0; + margin: 0; + padding: 0; + outline: 0; + font-size: 12px; + vertical-align: top; } /* Box sizing */ * {-moz-box-sizing: border-box; box-sizing: border-box} -::-webkit-scrollbar {width: 12px; height: 12px; background: #666} -::-webkit-scrollbar-thumb {background: rgba(0,198,255,1)} +::-webkit-scrollbar {width: 12px; height: 12px} +::-webkit-scrollbar-thumb {background: rgba(0,198,255,0.2)} +::-webkit-scrollbar-thumb:hover {background: rgba(0,198,255,0.8)} ::-webkit-scrollbar-corner, ::-webkit-scrollbar-resizer {background: transparent} html, body {width: 100%; height: 100%} diff --git a/CodeMirror/theme/2019-torres-digital-theme.css b/assets/css/theme/2019-torres-digital-theme.css similarity index 100% rename from CodeMirror/theme/2019-torres-digital-theme.css rename to assets/css/theme/2019-torres-digital-theme.css diff --git a/CodeMirror/theme/3024-day.css b/assets/css/theme/3024-day.css similarity index 100% rename from CodeMirror/theme/3024-day.css rename to assets/css/theme/3024-day.css diff --git a/CodeMirror/theme/3024-night.css b/assets/css/theme/3024-night.css similarity index 100% rename from CodeMirror/theme/3024-night.css rename to assets/css/theme/3024-night.css diff --git a/CodeMirror/theme/abcdef.css b/assets/css/theme/abcdef.css similarity index 100% rename from CodeMirror/theme/abcdef.css rename to assets/css/theme/abcdef.css diff --git a/CodeMirror/theme/ambiance-mobile.css b/assets/css/theme/ambiance-mobile.css similarity index 100% rename from CodeMirror/theme/ambiance-mobile.css rename to assets/css/theme/ambiance-mobile.css diff --git a/CodeMirror/theme/ambiance.css b/assets/css/theme/ambiance.css similarity index 100% rename from CodeMirror/theme/ambiance.css rename to assets/css/theme/ambiance.css diff --git a/CodeMirror/theme/base16-dark.css b/assets/css/theme/base16-dark.css similarity index 100% rename from CodeMirror/theme/base16-dark.css rename to assets/css/theme/base16-dark.css diff --git a/CodeMirror/theme/base16-light.css b/assets/css/theme/base16-light.css similarity index 100% rename from CodeMirror/theme/base16-light.css rename to assets/css/theme/base16-light.css diff --git a/CodeMirror/theme/bespin.css b/assets/css/theme/bespin.css similarity index 100% rename from CodeMirror/theme/bespin.css rename to assets/css/theme/bespin.css diff --git a/CodeMirror/theme/blackboard.css b/assets/css/theme/blackboard.css similarity index 100% rename from CodeMirror/theme/blackboard.css rename to assets/css/theme/blackboard.css diff --git a/CodeMirror/theme/cobalt.css b/assets/css/theme/cobalt.css similarity index 100% rename from CodeMirror/theme/cobalt.css rename to assets/css/theme/cobalt.css diff --git a/CodeMirror/theme/colorforth.css b/assets/css/theme/colorforth.css similarity index 100% rename from CodeMirror/theme/colorforth.css rename to assets/css/theme/colorforth.css diff --git a/CodeMirror/theme/dracula.css b/assets/css/theme/dracula.css similarity index 100% rename from CodeMirror/theme/dracula.css rename to assets/css/theme/dracula.css diff --git a/CodeMirror/theme/duotone-dark.css b/assets/css/theme/duotone-dark.css similarity index 100% rename from CodeMirror/theme/duotone-dark.css rename to assets/css/theme/duotone-dark.css diff --git a/CodeMirror/theme/duotone-light.css b/assets/css/theme/duotone-light.css similarity index 100% rename from CodeMirror/theme/duotone-light.css rename to assets/css/theme/duotone-light.css diff --git a/CodeMirror/theme/eclipse.css b/assets/css/theme/eclipse.css similarity index 100% rename from CodeMirror/theme/eclipse.css rename to assets/css/theme/eclipse.css diff --git a/CodeMirror/theme/elegant.css b/assets/css/theme/elegant.css similarity index 100% rename from CodeMirror/theme/elegant.css rename to assets/css/theme/elegant.css diff --git a/CodeMirror/theme/erlang-dark.css b/assets/css/theme/erlang-dark.css similarity index 100% rename from CodeMirror/theme/erlang-dark.css rename to assets/css/theme/erlang-dark.css diff --git a/CodeMirror/theme/hopscotch.css b/assets/css/theme/hopscotch.css similarity index 100% rename from CodeMirror/theme/hopscotch.css rename to assets/css/theme/hopscotch.css diff --git a/CodeMirror/theme/icecoder-classic.css b/assets/css/theme/icecoder-classic.css similarity index 100% rename from CodeMirror/theme/icecoder-classic.css rename to assets/css/theme/icecoder-classic.css diff --git a/CodeMirror/theme/icecoder.css b/assets/css/theme/icecoder.css similarity index 100% rename from CodeMirror/theme/icecoder.css rename to assets/css/theme/icecoder.css diff --git a/CodeMirror/theme/isotope.css b/assets/css/theme/isotope.css similarity index 100% rename from CodeMirror/theme/isotope.css rename to assets/css/theme/isotope.css diff --git a/CodeMirror/theme/lesser-dark.css b/assets/css/theme/lesser-dark.css similarity index 100% rename from CodeMirror/theme/lesser-dark.css rename to assets/css/theme/lesser-dark.css diff --git a/CodeMirror/theme/liquibyte.css b/assets/css/theme/liquibyte.css similarity index 69% rename from CodeMirror/theme/liquibyte.css rename to assets/css/theme/liquibyte.css index 9db8bde..a1cd3c7 100644 --- a/CodeMirror/theme/liquibyte.css +++ b/assets/css/theme/liquibyte.css @@ -57,39 +57,3 @@ .cm-s-liquibyte .CodeMirror span.CodeMirror-matchingbracket { color: #0f0; font-weight: bold; } .cm-s-liquibyte .CodeMirror span.CodeMirror-nonmatchingbracket { color: #f00; font-weight: bold; } .CodeMirror-matchingtag { background-color: rgba(150, 255, 0, .3); } -/* Scrollbars */ -/* Simple */ -.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div:hover, div.CodeMirror-simplescroll-vertical div:hover { - background-color: rgba(80, 80, 80, .7); -} -.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div, div.CodeMirror-simplescroll-vertical div { - background-color: rgba(80, 80, 80, .3); - border: 1px solid #404040; - border-radius: 5px; -} -.cm-s-liquibyte div.CodeMirror-simplescroll-vertical div { - border-top: 1px solid #404040; - border-bottom: 1px solid #404040; -} -.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div { - border-left: 1px solid #404040; - border-right: 1px solid #404040; -} -.cm-s-liquibyte div.CodeMirror-simplescroll-vertical { - background-color: #262626; -} -.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal { - background-color: #262626; - border-top: 1px solid #404040; -} -/* Overlay */ -.cm-s-liquibyte div.CodeMirror-overlayscroll-horizontal div, div.CodeMirror-overlayscroll-vertical div { - background-color: #404040; - border-radius: 5px; -} -.cm-s-liquibyte div.CodeMirror-overlayscroll-vertical div { - border: 1px solid #404040; -} -.cm-s-liquibyte div.CodeMirror-overlayscroll-horizontal div { - border: 1px solid #404040; -} diff --git a/CodeMirror/theme/material.css b/assets/css/theme/material.css similarity index 100% rename from CodeMirror/theme/material.css rename to assets/css/theme/material.css diff --git a/CodeMirror/theme/mbo.css b/assets/css/theme/mbo.css similarity index 100% rename from CodeMirror/theme/mbo.css rename to assets/css/theme/mbo.css diff --git a/CodeMirror/theme/mdn-like.css b/assets/css/theme/mdn-like.css similarity index 100% rename from CodeMirror/theme/mdn-like.css rename to assets/css/theme/mdn-like.css diff --git a/CodeMirror/theme/midnight.css b/assets/css/theme/midnight.css similarity index 100% rename from CodeMirror/theme/midnight.css rename to assets/css/theme/midnight.css diff --git a/CodeMirror/theme/monokai.css b/assets/css/theme/monokai.css similarity index 100% rename from CodeMirror/theme/monokai.css rename to assets/css/theme/monokai.css diff --git a/CodeMirror/theme/neat.css b/assets/css/theme/neat.css similarity index 100% rename from CodeMirror/theme/neat.css rename to assets/css/theme/neat.css diff --git a/CodeMirror/theme/neo.css b/assets/css/theme/neo.css similarity index 100% rename from CodeMirror/theme/neo.css rename to assets/css/theme/neo.css diff --git a/CodeMirror/theme/night.css b/assets/css/theme/night.css similarity index 100% rename from CodeMirror/theme/night.css rename to assets/css/theme/night.css diff --git a/CodeMirror/theme/panda-syntax.css b/assets/css/theme/panda-syntax.css similarity index 100% rename from CodeMirror/theme/panda-syntax.css rename to assets/css/theme/panda-syntax.css diff --git a/CodeMirror/theme/paraiso-dark.css b/assets/css/theme/paraiso-dark.css similarity index 100% rename from CodeMirror/theme/paraiso-dark.css rename to assets/css/theme/paraiso-dark.css diff --git a/CodeMirror/theme/paraiso-light.css b/assets/css/theme/paraiso-light.css similarity index 100% rename from CodeMirror/theme/paraiso-light.css rename to assets/css/theme/paraiso-light.css diff --git a/CodeMirror/theme/pastel-on-dark.css b/assets/css/theme/pastel-on-dark.css similarity index 100% rename from CodeMirror/theme/pastel-on-dark.css rename to assets/css/theme/pastel-on-dark.css diff --git a/CodeMirror/theme/railscasts.css b/assets/css/theme/railscasts.css similarity index 100% rename from CodeMirror/theme/railscasts.css rename to assets/css/theme/railscasts.css diff --git a/CodeMirror/theme/rubyblue.css b/assets/css/theme/rubyblue.css similarity index 100% rename from CodeMirror/theme/rubyblue.css rename to assets/css/theme/rubyblue.css diff --git a/CodeMirror/theme/seti.css b/assets/css/theme/seti.css similarity index 100% rename from CodeMirror/theme/seti.css rename to assets/css/theme/seti.css diff --git a/CodeMirror/theme/solarized.css b/assets/css/theme/solarized.css similarity index 100% rename from CodeMirror/theme/solarized.css rename to assets/css/theme/solarized.css diff --git a/CodeMirror/theme/the-matrix.css b/assets/css/theme/the-matrix.css similarity index 100% rename from CodeMirror/theme/the-matrix.css rename to assets/css/theme/the-matrix.css diff --git a/CodeMirror/theme/tomorrow-night-bright.css b/assets/css/theme/tomorrow-night-bright.css similarity index 100% rename from CodeMirror/theme/tomorrow-night-bright.css rename to assets/css/theme/tomorrow-night-bright.css diff --git a/CodeMirror/theme/tomorrow-night-eighties.css b/assets/css/theme/tomorrow-night-eighties.css similarity index 100% rename from CodeMirror/theme/tomorrow-night-eighties.css rename to assets/css/theme/tomorrow-night-eighties.css diff --git a/CodeMirror/theme/ttcn.css b/assets/css/theme/ttcn.css similarity index 100% rename from CodeMirror/theme/ttcn.css rename to assets/css/theme/ttcn.css diff --git a/CodeMirror/theme/twilight.css b/assets/css/theme/twilight.css similarity index 100% rename from CodeMirror/theme/twilight.css rename to assets/css/theme/twilight.css diff --git a/CodeMirror/theme/vibrant-ink.css b/assets/css/theme/vibrant-ink.css similarity index 100% rename from CodeMirror/theme/vibrant-ink.css rename to assets/css/theme/vibrant-ink.css diff --git a/CodeMirror/theme/xq-dark.css b/assets/css/theme/xq-dark.css similarity index 100% rename from CodeMirror/theme/xq-dark.css rename to assets/css/theme/xq-dark.css diff --git a/CodeMirror/theme/xq-light.css b/assets/css/theme/xq-light.css similarity index 100% rename from CodeMirror/theme/xq-light.css rename to assets/css/theme/xq-light.css diff --git a/CodeMirror/theme/yeti.css b/assets/css/theme/yeti.css similarity index 100% rename from CodeMirror/theme/yeti.css rename to assets/css/theme/yeti.css diff --git a/CodeMirror/theme/zenburn.css b/assets/css/theme/zenburn.css similarity index 100% rename from CodeMirror/theme/zenburn.css rename to assets/css/theme/zenburn.css diff --git a/images/blank.gif b/assets/images/blank.gif similarity index 100% rename from images/blank.gif rename to assets/images/blank.gif diff --git a/images/bug-reporting-icons.png b/assets/images/bug-reporting-icons.png similarity index 100% rename from images/bug-reporting-icons.png rename to assets/images/bug-reporting-icons.png diff --git a/images/checkbox.gif b/assets/images/checkbox.gif similarity index 100% rename from images/checkbox.gif rename to assets/images/checkbox.gif diff --git a/images/checkerboard.png b/assets/images/checkerboard.png similarity index 100% rename from images/checkerboard.png rename to assets/images/checkerboard.png diff --git a/images/color-picker.png b/assets/images/color-picker.png similarity index 100% rename from images/color-picker.png rename to assets/images/color-picker.png diff --git a/favicon.png b/assets/images/favicon.png similarity index 100% rename from favicon.png rename to assets/images/favicon.png diff --git a/images/file-folder-icons.png b/assets/images/file-folder-icons.png similarity index 100% rename from images/file-folder-icons.png rename to assets/images/file-folder-icons.png diff --git a/images/doc-explorer-icons.png b/assets/images/file-manager-icons.png similarity index 87% rename from images/doc-explorer-icons.png rename to assets/images/file-manager-icons.png index fddc3cb..928e213 100644 Binary files a/images/doc-explorer-icons.png and b/assets/images/file-manager-icons.png differ diff --git a/images/files-arrow.png b/assets/images/files-arrow.png similarity index 100% rename from images/files-arrow.png rename to assets/images/files-arrow.png diff --git a/images/ice-coder.png b/assets/images/icecoder.png similarity index 100% rename from images/ice-coder.png rename to assets/images/icecoder.png diff --git a/farbtastic/marker.png b/assets/images/marker.png similarity index 100% rename from farbtastic/marker.png rename to assets/images/marker.png diff --git a/farbtastic/mask.png b/assets/images/mask.png similarity index 100% rename from farbtastic/mask.png rename to assets/images/mask.png diff --git a/images/nav-alpha.png b/assets/images/nav-alpha.png similarity index 100% rename from images/nav-alpha.png rename to assets/images/nav-alpha.png diff --git a/images/nav-close-all.gif b/assets/images/nav-close-all.gif similarity index 100% rename from images/nav-close-all.gif rename to assets/images/nav-close-all.gif diff --git a/images/nav-close.gif b/assets/images/nav-close.gif similarity index 100% rename from images/nav-close.gif rename to assets/images/nav-close.gif diff --git a/images/select-arrow.gif b/assets/images/select-arrow.gif similarity index 100% rename from images/select-arrow.gif rename to assets/images/select-arrow.gif diff --git a/images/snake.png b/assets/images/snake.png similarity index 100% rename from images/snake.png rename to assets/images/snake.png diff --git a/images/split-pane-controls.gif b/assets/images/split-pane-controls.gif similarity index 100% rename from images/split-pane-controls.gif rename to assets/images/split-pane-controls.gif diff --git a/farbtastic/wheel.png b/assets/images/wheel.png similarity index 100% rename from farbtastic/wheel.png rename to assets/images/wheel.png diff --git a/CodeMirror/LICENSE b/assets/js/LICENSE-codemirror similarity index 100% rename from CodeMirror/LICENSE rename to assets/js/LICENSE-codemirror diff --git a/CodeMirror/lib/codemirror-compressed.js b/assets/js/codemirror-compressed.js similarity index 100% rename from CodeMirror/lib/codemirror-compressed.js rename to assets/js/codemirror-compressed.js diff --git a/assets/js/difflib.js b/assets/js/difflib.js new file mode 100644 index 0000000..b733c99 --- /dev/null +++ b/assets/js/difflib.js @@ -0,0 +1,412 @@ +/*** +This is part of jsdifflib v1.0. + +Copyright (c) 2007, Snowtide Informatics Systems, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the Snowtide Informatics Systems nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. +***/ +/* Author: Chas Emerick */ +var __whitespace = {" ":true, "\t":true, "\n":true, "\f":true, "\r":true}; + +var difflib = { + defaultJunkFunction: function (c) { + return __whitespace.hasOwnProperty(c); + }, + + stripLinebreaks: function (str) { return str.replace(/^[\n\r]*|[\n\r]*$/g, ""); }, + + stringAsLines: function (str) { + var lfpos = str.indexOf("\n"); + var crpos = str.indexOf("\r"); + var linebreak = ((lfpos > -1 && crpos > -1) || crpos < 0) ? "\n" : "\r"; + + var lines = str.split(linebreak); + for (var i = 0; i < lines.length; i++) { + lines[i] = difflib.stripLinebreaks(lines[i]); + } + + return lines; + }, + + // iteration-based reduce implementation + __reduce: function (func, list, initial) { + if (initial != null) { + var value = initial; + var idx = 0; + } else if (list) { + var value = list[0]; + var idx = 1; + } else { + return null; + } + + for (; idx < list.length; idx++) { + value = func(value, list[idx]); + } + + return value; + }, + + // comparison function for sorting lists of numeric tuples + __ntuplecomp: function (a, b) { + var mlen = Math.max(a.length, b.length); + for (var i = 0; i < mlen; i++) { + if (a[i] < b[i]) return -1; + if (a[i] > b[i]) return 1; + } + + return a.length == b.length ? 0 : (a.length < b.length ? -1 : 1); + }, + + __calculate_ratio: function (matches, length) { + return length ? 2.0 * matches / length : 1.0; + }, + + // returns a function that returns true if a key passed to the returned function + // is in the dict (js object) provided to this function; replaces being able to + // carry around dict.has_key in python... + __isindict: function (dict) { + return function (key) { return dict.hasOwnProperty(key); }; + }, + + // replacement for python's dict.get function -- need easy default values + __dictget: function (dict, key, defaultValue) { + return dict.hasOwnProperty(key) ? dict[key] : defaultValue; + }, + + SequenceMatcher: function (a, b, isjunk) { + this.set_seqs = function (a, b) { + this.set_seq1(a); + this.set_seq2(b); + } + + this.set_seq1 = function (a) { + if (a == this.a) return; + this.a = a; + this.matching_blocks = this.opcodes = null; + } + + this.set_seq2 = function (b) { + if (b == this.b) return; + this.b = b; + this.matching_blocks = this.opcodes = this.fullbcount = null; + this.__chain_b(); + } + + this.__chain_b = function () { + var b = this.b; + var n = b.length; + var b2j = this.b2j = {}; + var populardict = {}; + for (var i = 0; i < b.length; i++) { + var elt = b[i]; + if (b2j.hasOwnProperty(elt)) { + var indices = b2j[elt]; + if (n >= 200 && indices.length * 100 > n) { + populardict[elt] = 1; + delete b2j[elt]; + } else { + indices.push(i); + } + } else { + b2j[elt] = [i]; + } + } + + for (var elt in populardict) { + if (populardict.hasOwnProperty(elt)) { + delete b2j[elt]; + } + } + + var isjunk = this.isjunk; + var junkdict = {}; + if (isjunk) { + for (var elt in populardict) { + if (populardict.hasOwnProperty(elt) && isjunk(elt)) { + junkdict[elt] = 1; + delete populardict[elt]; + } + } + for (var elt in b2j) { + if (b2j.hasOwnProperty(elt) && isjunk(elt)) { + junkdict[elt] = 1; + delete b2j[elt]; + } + } + } + + this.isbjunk = difflib.__isindict(junkdict); + this.isbpopular = difflib.__isindict(populardict); + } + + this.find_longest_match = function (alo, ahi, blo, bhi) { + var a = this.a; + var b = this.b; + var b2j = this.b2j; + var isbjunk = this.isbjunk; + var besti = alo; + var bestj = blo; + var bestsize = 0; + var j = null; + var k; + + var j2len = {}; + var nothing = []; + for (var i = alo; i < ahi; i++) { + var newj2len = {}; + var jdict = difflib.__dictget(b2j, a[i], nothing); + for (var jkey in jdict) { + if (jdict.hasOwnProperty(jkey)) { + j = jdict[jkey]; + if (j < blo) continue; + if (j >= bhi) break; + newj2len[j] = k = difflib.__dictget(j2len, j - 1, 0) + 1; + if (k > bestsize) { + besti = i - k + 1; + bestj = j - k + 1; + bestsize = k; + } + } + } + j2len = newj2len; + } + + while (besti > alo && bestj > blo && !isbjunk(b[bestj - 1]) && a[besti - 1] == b[bestj - 1]) { + besti--; + bestj--; + bestsize++; + } + + while (besti + bestsize < ahi && bestj + bestsize < bhi && + !isbjunk(b[bestj + bestsize]) && + a[besti + bestsize] == b[bestj + bestsize]) { + bestsize++; + } + + while (besti > alo && bestj > blo && isbjunk(b[bestj - 1]) && a[besti - 1] == b[bestj - 1]) { + besti--; + bestj--; + bestsize++; + } + + while (besti + bestsize < ahi && bestj + bestsize < bhi && isbjunk(b[bestj + bestsize]) && + a[besti + bestsize] == b[bestj + bestsize]) { + bestsize++; + } + + return [besti, bestj, bestsize]; + } + + this.get_matching_blocks = function () { + if (this.matching_blocks != null) return this.matching_blocks; + var la = this.a.length; + var lb = this.b.length; + + var queue = [[0, la, 0, lb]]; + var matching_blocks = []; + var alo, ahi, blo, bhi, qi, i, j, k, x; + while (queue.length) { + qi = queue.pop(); + alo = qi[0]; + ahi = qi[1]; + blo = qi[2]; + bhi = qi[3]; + x = this.find_longest_match(alo, ahi, blo, bhi); + i = x[0]; + j = x[1]; + k = x[2]; + + if (k) { + matching_blocks.push(x); + if (alo < i && blo < j) + queue.push([alo, i, blo, j]); + if (i+k < ahi && j+k < bhi) + queue.push([i + k, ahi, j + k, bhi]); + } + } + + matching_blocks.sort(difflib.__ntuplecomp); + + var i1 = 0, j1 = 0, k1 = 0, block = 0; + var i2, j2, k2; + var non_adjacent = []; + for (var idx in matching_blocks) { + if (matching_blocks.hasOwnProperty(idx)) { + block = matching_blocks[idx]; + i2 = block[0]; + j2 = block[1]; + k2 = block[2]; + if (i1 + k1 == i2 && j1 + k1 == j2) { + k1 += k2; + } else { + if (k1) non_adjacent.push([i1, j1, k1]); + i1 = i2; + j1 = j2; + k1 = k2; + } + } + } + + if (k1) non_adjacent.push([i1, j1, k1]); + + non_adjacent.push([la, lb, 0]); + this.matching_blocks = non_adjacent; + return this.matching_blocks; + } + + this.get_opcodes = function () { + if (this.opcodes != null) return this.opcodes; + var i = 0; + var j = 0; + var answer = []; + this.opcodes = answer; + var block, ai, bj, size, tag; + var blocks = this.get_matching_blocks(); + for (var idx in blocks) { + if (blocks.hasOwnProperty(idx)) { + block = blocks[idx]; + ai = block[0]; + bj = block[1]; + size = block[2]; + tag = ''; + if (i < ai && j < bj) { + tag = 'replace'; + } else if (i < ai) { + tag = 'delete'; + } else if (j < bj) { + tag = 'insert'; + } + if (tag) answer.push([tag, i, ai, j, bj]); + i = ai + size; + j = bj + size; + + if (size) answer.push(['equal', ai, i, bj, j]); + } + } + + return answer; + } + + // this is a generator function in the python lib, which of course is not supported in javascript + // the reimplementation builds up the grouped opcodes into a list in their entirety and returns that. + this.get_grouped_opcodes = function (n) { + if (!n) n = 3; + var codes = this.get_opcodes(); + if (!codes) codes = [["equal", 0, 1, 0, 1]]; + var code, tag, i1, i2, j1, j2; + if (codes[0][0] == 'equal') { + code = codes[0]; + tag = code[0]; + i1 = code[1]; + i2 = code[2]; + j1 = code[3]; + j2 = code[4]; + codes[0] = [tag, Math.max(i1, i2 - n), i2, Math.max(j1, j2 - n), j2]; + } + if (codes[codes.length - 1][0] == 'equal') { + code = codes[codes.length - 1]; + tag = code[0]; + i1 = code[1]; + i2 = code[2]; + j1 = code[3]; + j2 = code[4]; + codes[codes.length - 1] = [tag, i1, Math.min(i2, i1 + n), j1, Math.min(j2, j1 + n)]; + } + + var nn = n + n; + var group = []; + var groups = []; + for (var idx in codes) { + if (codes.hasOwnProperty(idx)) { + code = codes[idx]; + tag = code[0]; + i1 = code[1]; + i2 = code[2]; + j1 = code[3]; + j2 = code[4]; + if (tag == 'equal' && i2 - i1 > nn) { + group.push([tag, i1, Math.min(i2, i1 + n), j1, Math.min(j2, j1 + n)]); + groups.push(group); + group = []; + i1 = Math.max(i1, i2-n); + j1 = Math.max(j1, j2-n); + } + + group.push([tag, i1, i2, j1, j2]); + } + } + + if (group && !(group.length == 1 && group[0][0] == 'equal')) groups.push(group) + + return groups; + } + + this.ratio = function () { + matches = difflib.__reduce( + function (sum, triple) { return sum + triple[triple.length - 1]; }, + this.get_matching_blocks(), 0); + return difflib.__calculate_ratio(matches, this.a.length + this.b.length); + } + + this.quick_ratio = function () { + var fullbcount, elt; + if (this.fullbcount == null) { + this.fullbcount = fullbcount = {}; + for (var i = 0; i < this.b.length; i++) { + elt = this.b[i]; + fullbcount[elt] = difflib.__dictget(fullbcount, elt, 0) + 1; + } + } + fullbcount = this.fullbcount; + + var avail = {}; + var availhas = difflib.__isindict(avail); + var matches = numb = 0; + for (var i = 0; i < this.a.length; i++) { + elt = this.a[i]; + if (availhas(elt)) { + numb = avail[elt]; + } else { + numb = difflib.__dictget(fullbcount, elt, 0); + } + avail[elt] = numb - 1; + if (numb > 0) matches++; + } + + return difflib.__calculate_ratio(matches, this.a.length + this.b.length); + } + + this.real_quick_ratio = function () { + var la = this.a.length; + var lb = this.b.length; + return _calculate_ratio(Math.min(la, lb), la + lb); + } + + this.isjunk = isjunk ? isjunk : difflib.defaultJunkFunction; + this.a = this.b = null; + this.set_seqs(a, b); + } +}; \ No newline at end of file diff --git a/assets/js/farbtastic.js b/assets/js/farbtastic.js new file mode 100644 index 0000000..93a2158 --- /dev/null +++ b/assets/js/farbtastic.js @@ -0,0 +1,253 @@ +/*! + * Farbtastic: NON jQuery color picker plug-in v1.4, based on v1.3u + * + * Licensed under the GPL license: + * http://www.gnu.org/licenses/gpl.html + */ + +farbtasticFunc = function (options) { + farbtastic(this, options); + return this; +}; + +farbtastic = function (cont, cb) { + var container = document.getElementById(cont); + var callback = document.getElementById(cb); + return container.farbtastic || (container.farbtastic = new _farbtastic(container, callback)); +}; + +_farbtastic = function (container, callback) { + // Store farbtastic object + var fb = this; + + // Insert markup + container.innerHTML = '
'; + fb.wheel = document.getElementsByClassName('wheel')[0]; + // Dimensions + fb.radius = 84; + fb.square = 100; + fb.width = 194; + + /** + * Link to the given element(s) or callback. + */ + fb.linkTo = function (callback) { + // Unbind previous nodes + if (typeof fb.callback == 'object') { + console.log("Doesn't do anything?"); + fb.callback.removeEventListener('keyup', fb.updateValue); + } + + // Reset color + fb.color = null; + + // Bind callback or elements + if (typeof callback == 'function') { + fb.callback = callback; + } + else if (typeof callback == 'object' || typeof callback == 'string') { + fb.callback = callback; + fb.callback.addEventListener('keyup',fb.updateValue); + if (fb.callback.value) { + fb.setColor(fb.callback.value); + } + } + return this; + }; + fb.updateValue = function (event) { + if (this.value && this.value != fb.color) { + fb.setColor(this.value); + } + }; + + /** + * Change color with HTML syntax #123456 + */ + fb.setColor = function (color) { + var unpack = fb.unpack(color); + if (fb.color != color && unpack) { + fb.color = color; + fb.rgb = unpack; + fb.hsl = fb.RGBToHSL(fb.rgb); + fb.updateDisplay(); + } + return this; + }; + + /** + * Change color with HSL triplet [0..1, 0..1, 0..1] + */ + fb.setHSL = function (hsl) { + fb.hsl = hsl; + fb.rgb = fb.HSLToRGB(hsl); + fb.color = fb.pack(fb.rgb); + fb.updateDisplay(); + return this; + }; + + ///////////////////////////////////////////////////// + + /** + * Retrieve the coordinates of the given event relative to the center + * of the widget. + */ + fb.widgetCoords = function (event) { + return { x: (event.pageX - fb.wheel.offsetParent.offsetLeft) - fb.width / 2, y: (event.pageY - fb.wheel.offsetParent.offsetTop) - fb.width / 2 }; + }; + + /** + * Mousedown handler + */ + fb.mousedown = function (event) { + // Capture mouse + if (!document.dragging) { + document.addEventListener('mousemove', fb.mousemove); + document.addEventListener('mouseup', fb.mouseup); + document.dragging = true; + } + + // Check which area is being dragged + var pos = fb.widgetCoords(event); + fb.circleDrag = Math.max(Math.abs(pos.x), Math.abs(pos.y)) * 2 > fb.square; + + // Process + fb.mousemove(event); + return false; + }; + + /** + * Mousemove handler + */ + fb.mousemove = function (event) { + // Get coordinates relative to color picker center + var pos = fb.widgetCoords(event); + + // Set new HSL parameters + if (fb.circleDrag) { + var hue = Math.atan2(pos.x, -pos.y) / 6.28; + if (hue < 0) hue += 1; + fb.setHSL([hue, fb.hsl[1], fb.hsl[2]]); + } + else { + var sat = Math.max(0, Math.min(1, -(pos.x / fb.square) + .5)); + var lum = Math.max(0, Math.min(1, -(pos.y / fb.square) + .5)); + fb.setHSL([fb.hsl[0], sat, lum]); + } + return false; + }; + + /** + * Mouseup handler + */ + fb.mouseup = function () { + // Uncapture mouse + document.removeEventListener('mousemove', fb.mousemove); + document.removeEventListener('mouseup', fb.mouseup); + document.dragging = false; + }; + + /** + * Update the markers and styles + */ + fb.updateDisplay = function () { + // Markers + var angle = fb.hsl[0] * 6.28; + document.getElementsByClassName('h-marker')[0].style.left = Math.round(Math.sin(angle) * fb.radius + fb.width / 2) + 'px'; + document.getElementsByClassName('h-marker')[0].style.top = Math.round(-Math.cos(angle) * fb.radius + fb.width / 2) + 'px'; + + document.getElementsByClassName('sl-marker')[0].style.left = Math.round(fb.square * (.5 - fb.hsl[1]) + fb.width / 2) + 'px'; + document.getElementsByClassName('sl-marker')[0].style.top = Math.round(fb.square * (.5 - fb.hsl[2]) + fb.width / 2) + 'px'; + + // Saturation/Luminance gradient + document.getElementsByClassName('color')[0].style.backgroundColor = fb.pack(fb.HSLToRGB([fb.hsl[0], 1, 0.5])); + + // Linked elements or callback + if (typeof fb.callback == 'object') { + // Set background/foreground color + document.getElementById(fb.callback.id).style.backgroundColor = document.getElementById(fb.callback.id + 'RGB').style.backgroundColor = fb.color; + document.getElementById(fb.callback.id).style.color = document.getElementById(fb.callback.id + 'RGB').style.color = fb.hsl[2] > 0.5 ? '#000' : '#fff'; + document.getElementById('colorRGB').value = document.getElementById(fb.callback.id).style.backgroundColor.replace(/\s/g,''); + + // Change linked value + if (callback.value && callback.value != fb.color) { + callback.value = fb.color; + } + } + else if (typeof fb.callback == 'function') { + fb.callback.call(fb, fb.color); + } + }; + + /* Various color utility functions */ + fb.pack = function (rgb) { + var r = Math.round(rgb[0] * 255); + var g = Math.round(rgb[1] * 255); + var b = Math.round(rgb[2] * 255); + return '#' + (r < 16 ? '0' : '') + r.toString(16) + + (g < 16 ? '0' : '') + g.toString(16) + + (b < 16 ? '0' : '') + b.toString(16); + }; + + fb.unpack = function (color) { + if (color.length == 7) { + return [parseInt('0x' + color.substring(1, 3)) / 255, + parseInt('0x' + color.substring(3, 5)) / 255, + parseInt('0x' + color.substring(5, 7)) / 255]; + } + else if (color.length == 4) { + return [parseInt('0x' + color.substring(1, 2)) / 15, + parseInt('0x' + color.substring(2, 3)) / 15, + parseInt('0x' + color.substring(3, 4)) / 15]; + } + }; + + fb.HSLToRGB = function (hsl) { + var m1, m2, r, g, b; + var h = hsl[0], s = hsl[1], l = hsl[2]; + m2 = (l <= 0.5) ? l * (s + 1) : l + s - l*s; + m1 = l * 2 - m2; + return [this.hueToRGB(m1, m2, h+0.33333), + this.hueToRGB(m1, m2, h), + this.hueToRGB(m1, m2, h-0.33333)]; + }; + + fb.hueToRGB = function (m1, m2, h) { + h = (h < 0) ? h + 1 : ((h > 1) ? h - 1 : h); + if (h * 6 < 1) return m1 + (m2 - m1) * h * 6; + if (h * 2 < 1) return m2; + if (h * 3 < 2) return m1 + (m2 - m1) * (0.66666 - h) * 6; + return m1; + }; + + fb.RGBToHSL = function (rgb) { + var min, max, delta, h, s, l; + var r = rgb[0], g = rgb[1], b = rgb[2]; + min = Math.min(r, Math.min(g, b)); + max = Math.max(r, Math.max(g, b)); + delta = max - min; + l = (min + max) / 2; + s = 0; + if (l > 0 && l < 1) { + s = delta / (l < 0.5 ? (2 * l) : (2 - 2 * l)); + } + h = 0; + if (delta > 0) { + if (max == r && max != g) h += (g - b) / delta; + if (max == g && max != b) h += (2 + (b - r) / delta); + if (max == b && max != r) h += (4 + (r - g) / delta); + h /= 6; + } + return [h, s, l]; + }; + + // Install mousedown handler (the others are set on the document on-demand) + document.getElementsByClassName('farbtastic')[0].onmousedown = fb.mousedown; + + // Init color + fb.setColor('#000000'); + + // Set linked elements/callback + if (callback) { + fb.linkTo(callback); + } +}; \ No newline at end of file diff --git a/assets/js/icecoder.js b/assets/js/icecoder.js new file mode 100644 index 0000000..d682abe --- /dev/null +++ b/assets/js/icecoder.js @@ -0,0 +1,5129 @@ +// Get any elem by ID +const get = function(elem) { + return document.getElementById(elem); +}; + +// URL we're viewing ICEcoder from +const iceLoc = window.location.origin + window.location.pathname.replace(/\/$/, ""); + +// Main ICEcoder object +var ICEcoder = { + + // ==== + // INIT + // ==== + + // Define settings + filesW: 250, // Width of files pane + minFilesW: 14, // Min width of files pane + maxFilesW: 250, // Max width of files pane + selectedTab: 0, // The tab that's currently selected + savedPoints: [], // Ints array to indicate save points for docs + savedContents: [], // Array of last known saved contents + canSwitchTabs: true, // Stops switching of tabs when trying to close + openFiles: [], // Array of open file URLs + openFileMDTs: [], // Array of open file modification datetimes + openFileVersions: [], // Array of open file version counts + cMInstances: [], // List of CodeMirror instance no's + nextcMInstance: 1, // Next available CodeMirror instance no + selectedFiles: [], // Array of selected files + thisFileFolderType: '', // The type of current item - file or folder + thisFileFolderLink: '', // The id value of the current item + results: [], // Array of find coords (line & char) + resultsLines: [], // Array of lines containing results (simpler version of results) + findResult: 0, // Array position of current find in results + scrollbarVisible: false, // Indicates if the main pane has a scrollbar + mouseDown: false, // If the mouse is down + mouseDownInCM: false, // If the mouse is down within CodeMirror instance (can be false, 'editor' or 'gutter') + draggingFilesW: false, // If we're dragging the file manager width + draggingTab: false, // If we're dragging a tab + draggingWithKey: false, // The key that's down while dragging, false if no key + tabLeftPos: [], // Array of left positions of tabs inside content area + colorCurrentBG: '#1d1d1b', // Current tab/file background color + colorCurrentText: '#fff', // Current tab/file text color + colorOpenBG: '#c3c3c3', // Open tab/file background color + colorOpenTextFile: '#000', // Open file text color + colorOpenTextTab: '#888', // Open tab text color + colorSelectedBG: '#49d', // Selected tab/file background color + colorSelectedText: '#fff', // Selected tab/file text color + colorDropTgtBGFile: '#f80', // Drop dir target background color + prevTab: 0, // Previous tab to current + serverQueueItems: [], // Array of URLs to call in order + previewWindow: false, // Target variable for the preview window + previewWindowLoading: false, // Loading state of preview window + pluginIntervalRefs: [], // Array of plugin interval refs + overPopup: false, // Indicates if we're over a popup or not + cmdKey: false, // Tracking apple Command key up/down state + codeZoomedOut: false, // If true, code on non declaration lines is zoomed out + showingTool: false, // Which tool is showing right now (terminal, output, database, git etc) + oppTagReplaceData: [], // Will contain data for automatic opposite tag replacement to sync them + fmReady: false, // Indicates if the file manager is ready for action + bugReportStatus: "off", // Values of: off, error, ok, bugs + bugReportPath: "", // Bug report file path + bugFilesSizesSeen: [], // Array of last seen sizes of bug files + bugFilesSizesActual: [], // Array of actual sizes of bug files + splitPane: false, // Single or split pane editing + splitPaneLeftPerc: 100, // Width of left pane as a percentage + renderLineStyle: [], // Array of styles to apply on renderLine event + renderPaneShiftAmount: 0, // Shift comparison main (negative) vs diff pane (positive) + editorFocusInstance: "", // Name of editor instance that has focus + openSeconds: 0, // Number of seconds ICEcoder has been open for + indexing: false, // Indicates if ICEcoder is currently indexing + ready: false, // Indicates if ICEcoder is ready for action + + // Set our aliases + initAliases: function() { + const aliasArray = ["header", "files", "fileOptions", "optionsFile", "optionsEdit", "optionsSettings", "optionsHelp", "filesFrame", "editor", "tabsBar", "findBar", "terminal", "output", "database", "git", "content", "tools", "footer", "versionsDisplay", "splitPaneControls", "splitPaneNamesMain", "splitPaneNamesDiff", "charDisplay", "byteDisplay"]; + + // Create our ID aliases + for (let i = 0; i < aliasArray.length; i++) { + this[aliasArray[i]] = get(aliasArray[i]); + } + }, + + // On load, set the layout + init: function() { + // Contract the file manager if the user has set to have it hidden + if (false === this.lockedNav) { + this.filesW = this.minFilesW; + } + + // Set layout + this.setLayout(); + + this.overFileFolder('folder', '|'); + this.selectFileFolder('init'); + this.filesFrame.contentWindow.focus(); + + // Hide the loading screen & auto open last files? + this.showHide('hide', get('loadingMask')); + this.autoOpenInt = setInterval(function(ic) { + if (ic.fmReady) { + if (ic.openLastFiles) {ic.autoOpenFiles();} + clearInterval(ic.autoOpenInt); + } + }, 4, this); + + // Start bug checking + this.startBugChecking(); + + // Set the time since last user interaction + this.autoLogoutTimer = 0; + + // Start our interval timer, runs every second + this.oneSecondInt = setInterval(function(ic) { + ic.autoLogoutTimer++; + let unsavedFiles = false; + + // Check if we have any unsaved files + for(let i = 1; i <= ic.savedPoints.length; i++) { + if (ic.savedPoints[i - 1] !== ic.getcMInstance(i).changeGeneration()) { + unsavedFiles = true; + } + } + + // Show an auto-logout warning 60 secs before a logout + if(false === unsavedFiles && ic.autoLogoutMins > 1 && ic.autoLogoutTimer == (ic.autoLogoutMins * 60) - 60) { + ic.autoLogoutWarningScreen(); + } + + if (get('autoLogoutIFrame') && get('autoLogoutIFrame').contentWindow.document.getElementById('timeRemaning')) { + get('autoLogoutIFrame').contentWindow.document.getElementById('timeRemaning').innerHTML = + ic.autoLogoutTimer > 0 + ? (ic.autoLogoutMins * 60) - ic.autoLogoutTimer + : 0; + } + + // If there aren't any unsaved files, we have a timeout period > 0 and the time is up, we can logout + if (false === unsavedFiles && ic.autoLogoutMins > 0 && ic.autoLogoutTimer >= ic.autoLogoutMins * 60) { + ic.logout('autoLogout'); + } + + // Increase number of seconds ICEcoder has been open for by 1 + ic.openSeconds++; + + // Every 5 mins, ping our file to keep the session alive + if (0 === ic.openSeconds % 300) { + ic.filesFrame.contentWindow.frames['pingActive'].location.href = iceLoc + "/lib/session-active-ping.php"; + } + + // Every 3 seconds, re-index if we're not already busy + if (false === ic.indexing && false === ic.loadingFile && 0 === ic.serverQueueItems.length && 0 === ic.openSeconds % 3) { + ic.indexing = true; + // Get new data + let timestampExtra = ic.indexData + ? "?timestamp=" + ic.indexData.timestamps.indexed + "&csrf=" + ic.csrf + : ""; + fetch(iceLoc + '/lib/indexer.php' + timestampExtra) + .then(function(response) { + // Convert to JSON + return response.json(); + }).then(function(data) { + if (data.timestamps.changed) { + ic.indexData = data; + // If we have git diff data + if (data.gitDiff) { + ic.updateGitDiffPane(); + } + // If we have git content data + if (data.gitContent) { + ic.highlightGitDiffs(); + } + } + ic.indexing = false; + }); + } + }, 1000, this); + + // ICEcoder is ready to start using + this.ready = true; + }, + +// ====== +// LAYOUT +// ====== + + // Set our layout according to the browser size + setLayout: function(setEditor) { + let winW, winH, headerH, fileNavH, tabsBarH, findBarH, toolsBarH; + + // Determine width & height available + winW = window.innerWidth; + winH = window.innerHeight; + + // Apply sizes to various elements of the page + headerH = 15, fileNavH = 38, tabsBarH = 27, findBarH = 28, toolsBarH = 30; + this.header.style.width = this.tabsBar.style.width = this.findBar.style.width = winW + "px"; + this.files.style.width = this.editor.style.left = this.filesW + "px"; + this.optionsFile.style.width = this.optionsEdit.style.width = this.optionsSettings.style.width = this.optionsHelp.style.width = this.filesW + "px"; + this.filesFrame.style.height = (winH - headerH - fileNavH - 7 - toolsBarH) + "px"; + this.versionsDisplay.style.left = (this.filesW + 10) + "px"; + this.splitPaneControls.style.left = + parseInt( + ((winW - this.filesW) / 2) + + this.filesW - + (get("splitPaneControls").getBoundingClientRect().width / 2) + , 10) + "px"; + this.splitPaneNamesMain.style.left = (parseInt((winW - this.filesW) * 0.25, 10) - 50 + this.filesW) + "px"; + this.splitPaneNamesDiff.style.left = (parseInt((winW - this.filesW) * 0.75, 10) - 50 + this.filesW) + "px"; + this.setTabWidths(); + + // If we need to set the editor sizes + if (false !== setEditor) { + this.editor.style.width = this.content.style.width = (winW - this.filesW) + "px"; + this.terminal.style.width = (winW - this.filesW) + "px"; + this.output.style.width = (winW - this.filesW - 31) + "px"; + this.database.style.width = (winW - this.filesW) + "px"; + this.git.style.width = (winW - this.filesW - 31) + "px"; + this.content.style.height = (winH - headerH - tabsBarH - findBarH - toolsBarH) + "px"; + this.terminal.style.height = + this.output.style.height = + this.database.style.height = + this.git.style.height = + this.terminal.style.top = + this.output.style.top = + this.database.style.top = + this.git.style.top = winH + "px"; + if (false !== this.showingTool) { + get(this.showingTool).style.top = 0; + } + + // Resize the CodeMirror instances to match the window size + setTimeout(function(ic){ + for (let i = 0; i < ic.openFiles.length; i++) { + // Done the long way here as we need to call them in specific order to stop showing background and so avoiding a flicker effect + if (false === ic.splitPane) { + ic.content.contentWindow['cM' + ic.cMInstances[i]].setSize(ic.splitPaneLeftPerc + "%", ic.content.style.height); + } + ic.content.contentWindow['cM' + ic.cMInstances[i] + 'diff'].setSize((100 - ic.splitPaneLeftPerc) + "%", ic.content.style.height); + ic.content.contentWindow['cM'+ ic.cMInstances[i] + 'diff'].getWrapperElement().style.left = ic.splitPaneLeftPerc + "%"; + if (true === ic.splitPane) { + ic.content.contentWindow['cM' + ic.cMInstances[i]].setSize(ic.splitPaneLeftPerc + "%", ic.content.style.height); + } + } + // Place resultsBar on-top scrollbar + ic.content.contentWindow.document.getElementById('resultsBar').style.right = false === ic.splitPane + ? 0 + : parseInt(parseInt(ic.content.style.width, 10) / 2, 10) + "px"; + }, 4, this); + } + }, + + // Set the layout as split pane or not + setSplitPane: function(onOff) { + let cM, cMdiff; + + this.splitPane = "on" === onOff ? true : false; + get('splitPaneControlsOff').style.opacity = this.splitPane ? 0.2 : 0.5; + get('splitPaneControlsOn').style.opacity = this.splitPane ? 0.5 : 0.2; + get('splitPaneNamesMain').style.opacity = get('splitPaneNamesDiff').style.opacity = this.splitPane ? 1 : 0; + this.setLayout(); + + // Also clear marks (if going to a single pane) or redo the marks (if split pane) + if (true === this.splitPane) { + this.updateDiffs(); + // Also set the scroll position to match + cM = this.getcMInstance(); + this.cMonScroll(cM, 'cM' + this.cMInstances[this.selectedTab - 1]); + } else { + cM = this.getcMInstance(); + cMdiff = this.getcMdiffInstance(); + + if (cM) { + // Clear all main pane marks + cMmarks = cM.getAllMarks(); + for (let i = 0; i < cMmarks.length; i++) { + cMmarks[i].clear(); + } + // Clear all diff pane marks + cMdiffMarks = cMdiff.getAllMarks(); + for (let i = 0; i < cMdiffMarks.length; i++) { + cMdiffMarks[i].clear(); + } + } + } + + // Animate in/out the split pane + // First, clear any existing split pane interval anim + if ("undefined" !== typeof this.animSplitPaneInt) { + clearInterval(this.animSplitPaneInt); + } + // Now set the interval to animate it in/out + this.animSplitPaneInt = setInterval(function(ic) { + // Animate split pane in + if (ic.splitPane && ic.splitPaneLeftPerc > 50.1) { + ic.splitPaneLeftPerc = ((ic.splitPaneLeftPerc - 50) / 1.8) + 50; + // Animate split pane out + } else if (!ic.splitPane && ic.splitPaneLeftPerc < 99.9) { + ic.splitPaneLeftPerc = (50 - ((100 - ic.splitPaneLeftPerc) / 1.8)) + 50; + // Finish animating split pane in/out + } else { + ic.splitPaneLeftPerc = ic.splitPane ? 50 : 100; + clearInterval(ic.animSplitPaneInt); + } + ic.setLayout(); + }, 4, this); + }, + + // Tool show/hide toggle + toolShowHideToggle: function(tool) { + let winH; + + winH = window.innerHeight; + + if (-1 < ["terminal", "output", "database", "git"].indexOf(tool)) { + // Set out of view as a start point + get('terminal').style.top = winH + "px"; + get('output').style.top = winH + "px"; + get('database').style.top = winH + "px"; + get('git').style.top = winH + "px"; + + // Now set tool requested, out of view, or in view + get(tool).style.top = tool === this.showingTool ? winH + "px" : 0; + + // Carry out any extras... + if (tool === "terminal") { + // Focus on command prompt + setTimeout(function(ic){ + ic.terminal.contentWindow.document.getElementById('command').focus(); + }, 0 ,this); + } + + // Note which tool we're showing + this.showingTool = this.showingTool !== tool ? tool : false; + } + }, + + // Set the width of the file manager on demand + changeFilesW: function(expandContract) { + if (false === this.lockedNav || this.filesW === this.minFilesW) { + if ("undefined" !== typeof this.changeFilesInt) {clearInterval(this.changeFilesInt)} + this.changeFilesInt = setInterval(function(ic) {ic.changeFilesWStep(expandContract)}, 10, this); + } + }, + + // Expand/contract the file manager in half-steps + changeFilesWStep: function (expandContract) { + if ("expand" === expandContract) { + this.filesW < this.maxFilesW - 1 ? this.filesW += Math.ceil((this.maxFilesW - this.filesW) / 2) : this.filesW = this.maxFilesW; + } else { + this.filesW > this.minFilesW + 1 ? this.filesW -= Math.ceil((this.filesW - this.minFilesW) / 2) : this.filesW = this.minFilesW; + } + if (("expand" === expandContract && this.filesW === this.maxFilesW) || ("contract" === expandContract && this.filesW === this.minFilesW)) { + clearInterval(this.changeFilesInt); + } + // Redo the layout to match + this.setLayout(); + }, + + // Can click-drag file manager width? + canResizeFilesW: function() { + // If we have the cursor set we must be able! + if (true === this.ready && "w-resize" === document.body.style.cursor) { + // If our mouse is down (and went down on the CM instance's gutter) and we're within a 250px - half of avail width range + if (true === this.mouseDown && "gutter" === this.mouseDownInCM) { + this.filesW = this.maxFilesW = this.mouseX >= 250 && this.mouseX <= window.innerWidth / 2 + ? this.mouseX : this.mouseX < 250 ? 250 : window.innerWidth / 2; + // Set various widths based on the new width + this.files.style.width = this.filesFrame.style.width = this.filesW + "px"; + this.setLayout(); + this.draggingFilesW = true; + } + } else { + this.draggingFilesW = false; + } + }, + + // Lock & unlock the file manager navigation on demand + lockUnlockNav: function() { + let lockIcon; + + lockIcon = this.filesFrame.contentWindow.document.getElementById('fmLock'); + this.lockedNav = false === this.lockedNav; + lockIcon.style.backgroundPosition = this.lockedNav ? "0 0" : "-16px 0"; + }, + + // Show/hide the plugins on demand + showHidePlugins: function(vis) { + get('plugins').style.width = "show" === vis ? '55px' : '3px'; + get('plugins').style.background = "show" === vis ? '#333' : 'transparent'; + if ("show" === vis) { + this.changeFilesW('expand'); + } + }, + +// ====== +// EDITOR +// ====== + + // Set editor stats + setEditorStats: function() { + this.getCaretPosition(); + this.updateCharDisplay(); + this.updateByteDisplay(); + }, + + // On focus + cMonFocus: function(thisCM, cMinstance) { + this.setEditorStats(); + this.editorFocusInstance = cMinstance; + this.getCaretPosition(); + }, + + // On blur + cMonBlur: function(thisCM, cMinstance) { + // Nothing as yet + }, + + // On key up + cMonKeyUp: function(thisCM, cMinstance) { + if (undefined !== typeof this.doFindTimeout) { + clearInterval(this.doFindTimeout); + } + // If we have something to find in this document, find in 50 ms (unless cancelled by another keypress) + if ("" !== get('find').value && t['this document'] === document.findAndReplace.target.value) { + this.doFindTimeout = setTimeout(function (ic) { + ic.findReplace(get('find').value, false, false, false); + }, 50, this); + } + this.setEditorStats(); + }, + + // On cursor activity + cMonCursorActivity: function(thisCM, cMinstance) { + let thisCMPrevLine; + + this.setEditorStats(); + + thisCM.removeLineClass(this['cMActiveLine'+cMinstance], "background"); + if(thisCM.getCursor('start').line === thisCM.getCursor().line) { + this['cMActiveLine' + cMinstance] = thisCM.addLineClass(thisCM.getCursor().line, "background", "cm-s-activeLine"); + } + if ("CSS" === this.caretLocType) { + this.cssColorPreview(); + } + + thisCMPrevLine = this.editorFocusInstance.indexOf('diff') > -1 ? this.prevLineDiff : this.prevLine; + if (thisCMPrevLine !== thisCM.getCursor().line && + thisCM.getLine(thisCMPrevLine) && + thisCM.getLine(thisCMPrevLine).length > 0 && + thisCM.getLine(thisCMPrevLine).replace(/\s/g, '').length === 0) { + thisCM.replaceRange("", {line: thisCMPrevLine, ch: 0}, {line: thisCMPrevLine, ch: 1000000}); + } + + // Set the cursor to text height, not line height + setTimeout(function(ic) { + let paneMatch; + + // Loop through styles to check if we have to adjust cursor height + for (let i = 0; i < ic.renderLineStyle.length; i++) { + + // We have no matching pane to start with + paneMatch = false; + + // Is the pane we need to set the cursor on this pane? + if ( + ("diff" !== ic.renderLineStyle[i][0] && 1 === cMinstance.indexOf("diff")) || + ("diff" === ic.renderLineStyle[i][0] && -1 < cMinstance.indexOf("diff")) + ) + {paneMatch = true;} + + // If the pane matches & also the line we're on is the line we have a style set for, set that cursor height + if (paneMatch && thisCM.getCursor().line + 1 === ic.renderLineStyle[i][1]) { + thisCM.setOption("cursorHeight", thisCM.defaultTextHeight() / thisCM.lineInfo(thisCM.getCursor().line).handle.height); + } else { + thisCM.setOption("cursorHeight", 1); + } + + } + }, 0, this); + }, + + // On before change + cMonBeforeChange: function(thisCM, cMinstance, changeObj, cM) { + let sels, tagInfo, tagOpp, thisData; + + // Get the selections + sels = thisCM.listSelections(); + + // For each of the user selections + for (let i = 0; i < sels.length; i++) { + // Get the matching tagInfo for current cursor position + tagInfo = cM.findMatchingTag(thisCM, sels[i].anchor); + // If we're not ending a tag (autocompletion) and we have tagInfo and not undoing/redoing (which handles changes itself) + if (0 !== changeObj.text[0].indexOf(">") && "undefined" !== typeof tagInfo && "undo" !== changeObj.origin && "redo" !== changeObj.origin) { + // If we also have both open and close tag info + if ("undefined" !== typeof tagInfo.open && "undefined" !== typeof tagInfo.close) { + // Log the opposite tag info + tagOpp = tagInfo.at === "open" ? "close" : "open"; + if (null !== tagInfo[tagOpp]) { + thisData = tagInfo[tagOpp].tag + ";" + tagInfo[tagOpp].from.line + ":" + tagInfo[tagOpp].from.ch; + // Check that string firstly isn't in array and if not, push it in + if (-1 === this.oppTagReplaceData.indexOf(thisData)) { + this.oppTagReplaceData.push(thisData); + } + } + } + } + } + }, + + // On change + cMonChange: function(thisCM, cMinstance, changeObj, cM) { + let sels, rData, theTag, thisLine, thisChar, tagInfo, charDiff, closeDiff, repl1, repl2, filepath, filename, fileExt; + + // Get the selections + sels = thisCM.listSelections(); + + // If we're not loading the file, it's a change, so update tab + if (false === this.loadingFile) { + this.redoTabHighlight(this.selectedTab); + } + + // Detect if we have a scrollbar & set layout again + setTimeout(function(ic) { + ic.scrollBarVisible = thisCM.getScrollInfo().height > thisCM.getScrollInfo().clientHeight; + ic.setLayout(); + }, 0, this); + + // If we're replacing opposite tag strings, do that + if ("undefined" !== typeof this.oppTagReplaceData[0]) { + + // For each one of them, grab our data to work with + for (let i = 0; i < this.oppTagReplaceData.length; i++) { + // Extract data from that string + rData = this.oppTagReplaceData[i].split(";"); + theTag = rData[0]; + thisLine = parseInt(rData[1].split(":")[0], 10); + thisChar = parseInt(rData[1].split(":")[1], 10); + + // Get the tag info for matching tag + if (sels[i]) { + tagInfo = cM.findMatchingTag(thisCM, sels[i].anchor); + } + + // If we have tagInfo + if ("undefined" !== typeof tagInfo) { + // Get the opposite tag string + theTag = "open" === tagInfo.at ? tagInfo.open.tag : tagInfo.close.tag; + // If we have changeObj.from info to work with + if ("undefined" !== typeof changeObj.from) { + // Same line changing needs a character pos shift + charDiff = thisLine === changeObj.from.line + ? changeObj.text[0].length - changeObj.removed[0].length + : 0; + // Also need to adjust if we're in the close tag on same line + closeDiff = "close" === tagInfo.at && thisLine === changeObj.from.line + ? changeObj.removed[0].length - changeObj.text[0].length + 1 + : 1; + // Work out the replace from and to positions + repl1 = {line: thisLine, ch: thisChar + charDiff+("open" === tagInfo.at ? 2 : closeDiff)}; + repl2 = {line: thisLine, ch: thisChar + charDiff+("open" === tagInfo.at ? 2 : closeDiff) + rData[0].length}; + } + } + + // Replace our string over the range, if this token string isn't blank and the end tag matches our original tag + if ("" !== theTag.trim() && "undefined" !== typeof repl1 && "undefined" !== typeof repl2 && thisCM.getRange(repl1,repl2) === rData[0]) { + thisCM.replaceRange(theTag, repl1, repl2); + // If at the close tag, don't autocomplete + if (tagInfo.at === "close") { + this.autocompleteSkip = true; + } + } + } + + } + // Reset our array for next time and redo editor stats + this.oppTagReplaceData = []; + this.setEditorStats(); + + // Update the list of functions and classes + this.updateFunctionClassList(); + filepath = this.openFiles[this.selectedTab - 1]; + if (filepath) { + filename = filepath.substr(filepath.lastIndexOf("/") + 1); + fileExt = filename.substr(filename.lastIndexOf(".") + 1); + } + + // Update diffs if we have a split pane + if (true === this.splitPane) { + // Need 0ms tickover so we handle char change first + setTimeout(function(ic){ic.updateDiffs();}, 0, this); + } + + // Highlight Git diff colors in gutter + if (this.indexData) { + this.highlightGitDiffs(); + } + + // Update HTML edited files live + if (filepath && this.previewWindow.location && filepath !== "/[NEW]") { + this.updatePreviewWindow(thisCM, filepath, filename, fileExt); + } + + // Update the title tag to indicate any changes + this.indicateChanges(); + }, + + // On update + cMonUpdate: function(thisCM, cMinstance) { + // Nothing as yet + }, + + // On scroll + cMonScroll: function(thisCM, cMinstance) { + let cM, cMdiff, otherCM; + + if (true === this.splitPane) { + // Get both main & diff instance and work out the instance we're not scrolling + cM = this.getcMInstance(); + cMdiff = this.getcMdiffInstance(); + otherCM = cMinstance.indexOf('diff') > -1 ? cM : cMdiff; + + if (cM) { + // Scroll other pane x & y to match this one we're scrolling, after a tickover to avoid judder + // 0ms = drag scrollbar, 50 = mouse wheel + setTimeout(function(){otherCM.scrollTo(thisCM.getScrollInfo().left, thisCM.getScrollInfo().top);}, true === this.mouseDown ? 0 : 50); + } + } + }, + + // On input read + cMonInputRead: function(thisCM, cMinstance) { + if ("kepypress" === this.autoComplete && this.codeAssist) { + if (!thisCM.state.completionActive) { + if (!this.autocompleteSkip) { + this.autocomplete(); + } else { + this.autocompleteSkip = false; + } + } + } + }, + + // On gutter click + cMonGutterClick: function(thisCM, line, gutter, evt, cMinstance) { + this.mouseDownInCM = "gutter"; + }, + + // On mouse down + cMonMouseDown: function(thisCM, cMinstance, evt) { + this.mouseDownInCM = "editor"; + }, + + // On context menu + cMonContextMenu: function(thisCM, cMinstance, evt) { + // Set cursor + const currCoords = thisCM.coordsChar({left: evt.pageX, top: evt.pageY}); + thisCM.setCursor(currCoords); + + // If CTRL key down + if (evt.ctrlKey) { + setTimeout(function(ic) { + // Get cM and word under mouse pointer + let cM = thisCM; + let word = (cM.getRange(cM.findWordAt(cM.getCursor()).anchor, cM.findWordAt(cM.getCursor()).head)); + + // Get result and number of results for word in functions and classes from index JSON object list + let result = null; + let numResults = 0; + let filePath = ic.openFiles[ic.selectedTab - 1]; + let filePathExt = filePath.substr(filePath.lastIndexOf(".") + 1); + + if ("undefined" !== typeof ic.indexData.functions) { + for(i in ic.indexData.functions[filePathExt]) { + if (i === word) { + result = ic.indexData.functions[filePathExt][i]; + numResults++; + } + } + } + + if ("undefined" !== typeof ic.indexData.class) { + for (i in ic.indexData.classes[filePathExt]) { + if (i === word) { + result = ic.indexData.classes[filePathExt][i]; + numResults++; + } + } + } + + // If we have a single result and the cursor isn't already on the definition of it we can jump to where it's defined + if (1 === numResults && -1 === [null, "def"].indexOf(cM.getTokenTypeAt(cM.getCursor()))) { + ic.openFile(result.filePath.replace(docRoot, "")); + ic.goFindAfterOpenInt = setInterval(function(result) { + if (ic.openFiles[ic.selectedTab - 1] == result.filePath.replace(docRoot, "") && !ic.loadingFile) { + cM = ic.getcMInstance(); + setTimeout(function(result) { + ic.goToLine(result.range.from.line + 1); + cM.setSelection({line: result.range.from.line, ch: result.range.from.ch}, {line: result.range.to.line, ch: result.range.to.ch}); + }, 20, result); + clearInterval(ic.goFindAfterOpenInt); + } + }, 20, result); + } + + ic.mouseDownInCM = "editor"; + }, 0, this); + } + }, + + // On drag over + cMonDragOver: function(thisCM, evt, cMinstance) { + this.setDragCursor(evt, 'editor'); + }, + + // On render line + cMonRenderLine: function(thisCM, cMinstance, line, element) { + let paneMatch; + + // Loop through styles to use when rendering lines + for (let i = 0; i < this.renderLineStyle.length; i++) { + + // We have no matching pane to start with + paneMatch = false; + + // Is the pane we need to style this pane? + if ( + ("diff" !== this.renderLineStyle[i][0] && -1 === cMinstance.indexOf("diff")) || + ("diff" === this.renderLineStyle[i][0] && -1 < cMinstance.indexOf("diff")) + ) + {paneMatch = true;} + + // If the pane matches & also the line we're rendering is the line we have a style set for, set that style + if (paneMatch && thisCM.lineInfo(line).line + 1 == this.renderLineStyle[i][1]) { + element.style[this.renderLineStyle[i][2]] = this.renderLineStyle[i][3]; + } + + } + }, + + // Show function args tooltip + functionArgsTooltip: function(e, area) { + if (this.indexData) { + // If we have no files open, return early + if (0 === this.openFiles.length) { + get('tooltip').style.display = "none"; + return true; + } + + let i; + // Get cM instance, and the word under mouse pointer + const cM = this.getcMInstance(); + const coordsChar = cM.coordsChar({left: this.mouseX - this.maxFilesW, top: this.mouseY - 72}); + const word = (cM.getRange(cM.findWordAt(coordsChar).anchor, cM.findWordAt(coordsChar).head)); + + // If it's not a word, return early + if ("" === word) { + get('tooltip').style.display = "none"; + return true; + } + + // Get result and number of results for word in functions from index JSON object list + let result = null; + let numResults = 0; + const filePath = this.openFiles[this.selectedTab - 1]; + const filePathExt = filePath.substr(filePath.lastIndexOf(".") + 1); + for(i in this.indexData.functions[filePathExt]) { + if (i === word) { + result = this.indexData.functions[filePathExt][i]; + numResults++; + } + } + + // If we have a single result and the mouse pointer is not over the definition of it (that would be pointless), show tooltip + if (1 === numResults && -1 === [null, "def"].indexOf(cM.getTokenTypeAt(coordsChar))) { + get('tooltip').style.display = "block"; + get('tooltip').style.left = (this.mouseX - this.maxFilesW + 10) + "px"; + get('tooltip').style.top = (this.mouseY - 30) + "px"; + get('tooltip').style.zIndex = "1"; + get('tooltip').innerHTML = result.params; + // Else hide it + } else { + get('tooltip').style.display = "none"; + } + } + }, + + // Update diffs shown to the user in each pane + updateDiffs: function() { + let cM, cMdiff, mainText, diffText, sm, opcodes, cMmarks, cMdiffMarks, amt, sDiffs; + + // Reset the style array container and main vs diff pane shift difference + this.renderLineStyle = []; + this.renderPaneShiftAmount = 0; + + cM = this.getcMInstance(); + cMdiff = this.getcMdiffInstance(); + + // Get the baseText and newText values from the two textboxes, and split them into lines + mainText = cM ? difflib.stringAsLines(cM.getValue()) : ""; + diffText = cMdiff ? difflib.stringAsLines(cMdiff.getValue()) : ""; + + // Create a SequenceMatcher instance that diffs the two sets of lines + sm = new difflib.SequenceMatcher(mainText, diffText); + + // Get the opcodes from the SequenceMatcher instance + // Opcodes is a list of 3-tuples describing what changes should be made to the base text in order to yield the new text + opcodes = sm.get_opcodes(); + + if (cM) { + // Clear all main pane marks + cMmarks = cM.getAllMarks(); + for (let i = 0; i < cMmarks.length; i++) { + cMmarks[i].clear(); + } + // Clear all diff pane marks + cMdiffMarks = cMdiff.getAllMarks(); + for (let i = 0; i < cMdiffMarks.length; i++) { + cMdiffMarks[i].clear(); + } + } + + if (cM && "" !== cMdiff.getValue()) { + // For each opcode returned by jsdifflib + for (let i = 0; i < opcodes.length; i++) { + // If not 'equal' status for the section, we have a 'replace', 'delete' or 'insert' status, so do something + if ("equal" !== opcodes[i][0]) { + + // ========= + // MAIN PANE + // ========= + + // Replacing? Pad out main pane line to match equivalent last line in diff pane + if ("replace" === opcodes[i][0]) { + // Line amount is diff between end of both panes at this point in our loop, plus 1 line and our overall document shift, multiplied by font size + amt = ((opcodes[i][4] - opcodes[i][2] + 1 + this.renderPaneShiftAmount) * cM.defaultTextHeight()); + // Add on the extra heights for any wrapped lines + for (let j = opcodes[i][4] - 1; j <= opcodes[i][2] - 1; j++) { + if (cMdiff.getLineHandle(j).height > cM.defaultTextHeight()) { + amt += cMdiff.getLineHandle(j).height - cM.defaultTextHeight(); + } + } + // If we have an height greater than the default text height, add a new style + if (amt > cM.defaultTextHeight()) { + this.renderLineStyle.push(["main", opcodes[i][2], "height", amt + "px"]); + } + // Mark text in 2 colours, for each line + for (let j = 0; j<(opcodes[i][2]) - (opcodes[i][1]); j++) { + sDiffs = (this.findStringDiffs(cM.getLine(opcodes[i][1] + j),cMdiff.getLine(opcodes[i][3] + j))); + cM.markText({line: opcodes[i][1]+j, ch: 0}, {line: opcodes[i][3] + j + this.renderPaneShiftAmount, ch: sDiffs[0]}, {className: "diffGreyLighter"}); + cM.markText({line: opcodes[i][1]+j, ch: sDiffs[0]}, {line: opcodes[i][3] + j + this.renderPaneShiftAmount, ch: sDiffs[0] + sDiffs[1]}, {className: "diffGrey"}); + cM.markText({line: opcodes[i][1]+j, ch: sDiffs[0] + sDiffs[1]}, {line: opcodes[i][3] + j + this.renderPaneShiftAmount, ch: 1000000}, {className: "diffGreyLighter"}); + } + // Inserting + } else { + cM.markText({line: opcodes[i][1], ch: 0}, {line: opcodes[i][2] - 1, ch: 1000000}, {className: "diffGreen"}); + } + + // If inserting or deleting and the main pane hasn't changed, we need to pad out the line in that pane + if ("replace" !== opcodes[i][0] && opcodes[i][1] === opcodes[i][2]) { + this.renderLineStyle.push(["main", opcodes[i][2], "height", ((opcodes[i][4] - opcodes[i][3] + 1) * cM.defaultTextHeight()) + "px"]); + // Mark the range with empty class + cM.markText({line: opcodes[i][2] - 1, ch: 0}, {line: opcodes[i][2]-1, ch: 1000000}, {className: "diffNone"}); + } + + // ========= + // DIFF PANE + // ========= + + // Replacing? Pad out diff pane line to match equivalent last line in main pane + if ("replace" === opcodes[i][0]) { + // Line amount is diff between end of both panes at this point in our loop, plus 1 line and our overall document shift, multiplied by font size + amt = ((opcodes[i][2] - opcodes[i][4] + 1 - this.renderPaneShiftAmount) * cM.defaultTextHeight()); + // Add on the extra heights for any wrapped lines + for (let j = opcodes[i][4] - 1; j <= opcodes[i][2] - 1; j++) { + if (cM.getLineHandle(j).height > cM.defaultTextHeight()) { + amt += cM.getLineHandle(j).height - cM.defaultTextHeight(); + } + } + // If we have an height greater than the default text height, add a new style + if (amt > cM.defaultTextHeight()) { + this.renderLineStyle.push(["diff", opcodes[i][4], "height", amt + "px"]); + } + // Mark text in 2 colours, for each line + for (let j = 0; j<(opcodes[i][4]) - (opcodes[i][3]); j++) { + sDiffs = (this.findStringDiffs(cM.getLine(opcodes[i][1] + j),cMdiff.getLine(opcodes[i][3] + j))); + cMdiff.markText({line: opcodes[i][1] + j - this.renderPaneShiftAmount, ch: 0}, {line: opcodes[i][3] + j, ch: sDiffs[0]}, {className: "diffGreyLighter"}); + cMdiff.markText({line: opcodes[i][1] + j - this.renderPaneShiftAmount, ch: sDiffs[0]}, {line: opcodes[i][3] + j, ch: sDiffs[0] + sDiffs[2]}, {className: "diffGrey"}); + cMdiff.markText({line: opcodes[i][1] + j - this.renderPaneShiftAmount, ch: sDiffs[0] + sDiffs[2]}, {line: opcodes[i][3] + j, ch: 1000000}, {className: "diffGreyLighter"}); + } + // Deleting + } else { + cMdiff.markText({line: opcodes[i][3], ch: 0}, {line: opcodes[i][4] - 1, ch: 1000000}, {className: "diffRed"}); + } + + // If inserting or deleting and the diff pane hasn't changed, we need to pad out the line in that pane + if ("replace" !== opcodes[i][0] && opcodes[i][3] === opcodes[i][4]) { + this.renderLineStyle.push(["diff", opcodes[i][4], "height", ((opcodes[i][2] - opcodes[i][1] + 1) * cM.defaultTextHeight()) + "px"]); + // Mark the range with empty class + cMdiff.markText({line: opcodes[i][4] - 1, ch: 0}, {line: opcodes[i][4] - 1, ch: 1000000}, {className: "diffNone"}); + } + + // Finally, set the last amount shifted for this change + this.renderPaneShiftAmount = (opcodes[i][2] - opcodes[i][4]); + } + } + } + }, + + // Find diffs between 2 strings + findStringDiffs: function(a, b) { + if ("undefined" == typeof a) {a = ""} + if ("undefined" == typeof b) {b = ""} + for (var c = 0, // start from the first character + d = a.length, e = b.length; // and from the last characters of both strings + a[c] && // if not at the end of the string and + a[c] === b[c]; // if both strings are equal at this position + c++); // go forward + for (; d > c & e > c & // stop at the position found by the first loop + a[d - 1] === b[e - 1]; // if both strings are equal at this position + d--) e--; // go backward + return[c, d - c, e - c] // return position and lengths of the two substrings found + }, + + // Highlight git diffs (between what is in browser and in Git commits) + highlightGitDiffs: function() { + // Clear the timeout if we have one already + if ("undefined" !== typeof highlightGitDiffTimeout) { + clearTimeout(highlightGitDiffTimeout); + } + // If we have index data & Git data, after a timeout, if we have a matching path in that Git data + if (this.indexData && this.indexData.gitContent) { + highlightGitDiffTimeout = setTimeout(function(ic) { + if (ic.indexData.gitContent[docRoot + ic.openFiles[ic.selectedTab - 1]]) { + // Get the CodeMirror instance and clear the gutter for it + cM = ic.getcMInstance(); + cM.clearGutter("CodeMirror-linenumbers"); + // Get the baseText and gitText values from the two sources, and split them into lines + const mainText = cM ? difflib.stringAsLines(cM.getValue()) : ""; + const gitText = difflib.stringAsLines(ic.indexData.gitContent[docRoot + ic.openFiles[ic.selectedTab - 1]].lastHashContent ?? ""); + + // Create a SequenceMatcher instance that diffs the two sets of lines + const sm = new difflib.SequenceMatcher(gitText, mainText); + + // Get the opcodes from the SequenceMatcher instance + // Opcodes is a list of 3-tuples describing what changes should be made to the base text in order to yield the new text + const opcodes = sm.get_opcodes(); + + // For each opcode returned by jsdifflib + for (let i = 0; i < opcodes.length; i++) { + // If not 'equal' status for the section, we have a 'replace', 'delete' or 'insert' status, so do something + if ("equal" !== opcodes[i][0]) { + // Replacing + if ("replace" === opcodes[i][0]) { + // Mark text in one of 2 colours, for each line + for (let j = opcodes[i][3]; j < opcodes[i][4]; j++) { + let elem = document.createElement("DIV"); + elem.className = "CodeMirror-linenumber"; + // Only trim whitespace is different, grey + if (gitText[j - (opcodes[i][4] - opcodes[i][2])] && mainText[j].trim() === gitText[j - (opcodes[i][4] - opcodes[i][2])].trim()) { + elem.style.background = "#888"; + // Something other than whitespace is different, orange + } else { + elem.style.background = "#f80"; + } + elem.style.color = "#111"; + elem.innerHTML = j + 1; + cM.setGutterMarker(j, "CodeMirror-linenumbers", elem); + } + // Inserting + } else if ("insert" === opcodes[i][0]) { + // Mark text in green for each line + for (let j = opcodes[i][3]; j < opcodes[i][4]; j++) { + let elem = document.createElement("DIV"); + elem.className = "CodeMirror-linenumber"; + elem.style.background = "#080"; + elem.style.color = "#fff"; + elem.innerHTML = j + 1; + cM.setGutterMarker(j, "CodeMirror-linenumbers", elem); + } + // Deleting + } else { + // Add a red line to indicate where lines where deleted + let elem = document.createElement("DIV"); + elem.className = "CodeMirror-linenumber"; + // If we haven't deleted content at end, line is above numbers + if (cM.lineCount() > opcodes[i][3]) { + elem.style.borderTop = "solid #b00 1px"; + elem.innerHTML = opcodes[i][3] + 1; + cM.setGutterMarker(opcodes[i][3], "CodeMirror-linenumbers", elem); + // Otherwise, line is below last number + } else { + elem.style.borderBottom = "solid #b00 1px"; + elem.innerHTML = opcodes[i][3]; + cM.setGutterMarker(opcodes[i][3] - 1, "CodeMirror-linenumbers", elem); + } + } + } + } + } + }, this.loadingFile ? 100 : 0, this); + } + }, + + // Update Git diff pane (the diffs between saved content and git commits) + updateGitDiffPane: function() { + let gitDiffList = ""; + get("toolLinkGit").className = 0 < this.indexData.gitDiff.paths.length + ? "highlight info" + : ""; + for (let i = 0; i < this.indexData.gitDiff.paths.length; i++) { + gitDiffList += + '" + + "\n"; + } + get("git").innerHTML = gitDiffList + "

"; + }, + + // Update preview window content + updatePreviewWindow: function(thisCM, filepath, filename, fileExt) { + if (this.previewWindow.location.pathname === filepath) { + if (-1 < ["htm", "html", "txt"].indexOf(fileExt)) { + this.previewWindow.document.documentElement.innerHTML = thisCM.getValue(); + } else if (-1 < ["md"].indexOf(fileExt)) { + this.previewWindow.document.documentElement.innerHTML = mmd(thisCM.getValue()); + } + } else if (-1 < ["css"].indexOf(fileExt)) { + if (-1 < this.previewWindow.document.documentElement.innerHTML.indexOf(filename)) { + let css = thisCM.getValue(); + let style = document.createElement('style'); + style.type = 'text/css'; + style.id = "ICEcoder" + filepath.replace(/\//g,"_"); + if (style.styleSheet){ + style.styleSheet.cssText = css; + } else { + style.appendChild(document.createTextNode(css)); + } + if (this.previewWindow.document.getElementById(style.id)) { + this.previewWindow.document.documentElement.removeChild(this.previewWindow.document.getElementById(style.id)); + } + this.previewWindow.document.documentElement.appendChild(style); + } + } + // Do the pesticide plugin if it exists + try {this.doPesticide();} catch(err) {} + // Do the stats.js plugin if it exists + try {this.doStatsJS('update');} catch(err) {} + // Do the responsive plugin if it exists + try {this.doResponsive();} catch(err) {} + }, + + // Clean up our loaded code + contentCleanUp: function() { + let thisCM, content; + + thisCM = this.getThisCM(); + + // Replace any temp /textarea value + content = thisCM.getValue(); + content = content.replace(//g, '<\/textarea>'); + + // Then set the content in the editor & clear the history + thisCM.setValue(content); + thisCM.clearHistory(); + this.savedPoints[this.selectedTab - 1] = thisCM.changeGeneration(); + this.savedContents[this.selectedTab - 1] = thisCM.getValue(); + }, + + // Undo last change + undo: function() { + this.getThisCM().undo(); + }, + + // Redo change + redo: function() { + this.getThisCM().redo(); + }, + + // Indent more/less + indent: function(moreLess) { + if ("more" === moreLess) { + this.content.contentWindow.CodeMirror.commands.indentMore(this.getThisCM()); + } else { + this.content.contentWindow.CodeMirror.commands.indentLess(this.getThisCM()); + } + }, + + // Move current line up/down + moveLines: function(dir) { + let thisCM, lineStart, lineEnd, swapLineNo, swapLine; + + thisCM = this.getThisCM(); + + // Get start & end lines plus the line we'll swap with + lineStart = thisCM.getCursor('start'); + lineEnd = thisCM.getCursor('end'); + if ("up" === dir && 0 < lineStart.line) {swapLineNo = lineStart.line - 1} + if ("down" === dir && lineEnd.line < thisCM.lineCount() - 1) {swapLineNo = lineEnd.line + 1} + + // If we have a line to swap with + if (!isNaN(swapLineNo)) { + // Get the content of the swap line and carry out the swap in a single operation + swapLine = thisCM.getLine(swapLineNo); + thisCM.operation(function() { + // Move lines in turn up + if ("up" === dir) { + for (let i = lineStart.line; i <= lineEnd.line; i++) { + thisCM.replaceRange(thisCM.getLine(i), {line: i - 1, ch: 0}, {line: i - 1, ch: 1000000}); + } + // ...or down + } else { + for (let i = lineEnd.line; i >= lineStart.line; i--) { + thisCM.replaceRange(thisCM.getLine(i), {line: i + 1, ch: 0}, {line: i + 1, ch: 1000000}); + } + } + // Now swap our final line with the swap line to complete the move + thisCM.replaceRange(swapLine, {line: "up" === dir ? lineEnd.line : lineStart.line, ch: 0}, {line: "up" === dir ? lineEnd.line : lineStart.line, ch: 1000000}); + // Finally set the moved selection + thisCM.setSelection( + {line: lineStart.line + ("up" === dir ? -1 : 1), ch: lineStart.ch}, + {line: lineEnd.line + ("up" === dir ? -1 : 1), ch: lineEnd.ch} + ); + }) + } + }, + + // Highlight specified line + highlightLine: function(line) { + let thisCM; + + thisCM = this.getThisCM(); + thisCM.setSelection({line: line, ch: 0}, {line: line, ch: thisCM.lineInfo(line).text.length}); + }, + + // Focus the editor + focus: function(diff) { + let cM, cMdiff, thisCM; + + if (!(/iPhone|iPad|iPod/i.test(navigator.userAgent))) { + cM = this.getcMInstance(); + cMdiff = this.getcMdiffInstance(); + thisCM = diff ? cMdiff : cM; + if (thisCM) { + thisCM.focus(); + } + } + }, + + // Go to a specific line number + goToLine: function(lineNo, charNo, noFocus) { + let thisCM; + + lineNo = lineNo ? lineNo - 1 : get('goToLineNo').value - 1; + charNo = charNo ? charNo : 0; + + thisCM = this.getThisCM(); + + this.scrollingOnLine = thisCM.getCursor().line; + + // Scroll cursor into middle of view + if ("undefined" !== typeof this.scrollInt) { + clearInterval(this.scrollInt); + } + + this.scrollInt = setInterval(function(ic) { + ic.scrollingOnLine = ic.scrollingOnLine + ((lineNo - ic.scrollingOnLine) / 5); + thisCM.scrollTo(0, (thisCM.defaultTextHeight() * ic.scrollingOnLine) - (thisCM.getScrollInfo().clientHeight / 10)); + if (lineNo === Math.round(ic.scrollingOnLine)) { + clearInterval(ic.scrollInt); + } + }, 10, this); + + thisCM.setCursor(lineNo, charNo); + if (!noFocus) { + this.focus(); + // Also do this after a 0ms tickover incase DOM wasn't ready + setTimeout(function(ic) {ic.focus();}, 0, this); + } + return false; + }, + + // Comment/uncomment line or selected range on keypress + lineCommentToggle: function() { + let thisCM, cursorPos, linePos, lineContent, lCLen; + + thisCM = this.getThisCM(); + + cursorPos = thisCM.getCursor().ch; + linePos = thisCM.getCursor().line; + lineContent = thisCM.getLine(linePos); + lCLen = lineContent.length; + + this.lineCommentToggleSub(thisCM, cursorPos, linePos, lineContent, lCLen); + }, + + // Wrap our selected text/cursor with tags + tagWrapper: function(tag) { + let thisCM, tagStart, tagEnd, startLine, endLine; + + thisCM = this.getThisCM(); + + tagStart = tag; + tagEnd = tag; + if ('div' === tag) { + startLine = thisCM.getCursor('start').line; + endLine = thisCM.getCursor().line; + thisCM.operation(function() { + thisCM.replaceSelection("
\n" + thisCM.getSelection() + "\n
", "around"); + for (let i = startLine + 1; i <= endLine + 1; i++) { + thisCM.indentLine(i); + } + thisCM.indentLine(endLine + 2, 'prev'); + thisCM.indentLine(endLine + 2, 'subtract'); + }); + } else { + if ( + -1 < ['p', 'a', 'h1', 'h2', 'h3'].indexOf(tag) && + thisCM.getSelection().substr(0,tag.length + 1) === "<" + tagStart && + thisCM.getSelection().substr(-(tag.length + 3)) === "") { + // Undo wrapper + thisCM.replaceSelection( + thisCM.getSelection().substr(thisCM.getSelection().indexOf(">") + 1, + thisCM.getSelection().length-thisCM.getSelection().indexOf(">") - 1 - tag.length - 3), + "around"); + } else { + if ("a" === tag) {tagStart = 'a href=""';} + // Do wrapper + thisCM.replaceSelection("<" + tagStart + ">" + thisCM.getSelection() + "", "around"); + if ("a" === tag) {thisCM.setCursor({line: thisCM.getCursor('start').line, ch: thisCM.getCursor('start').ch + 9})} + } + } + }, + + // Add a line break at end of current or specified line + addLineBreakAtEnd: function(line) { + let thisCM; + + thisCM = this.getThisCM(); + + if (!line) {line = thisCM.getCursor().line} + thisCM.replaceRange(thisCM.getLine(line) + "
", {line: line, ch: 0}, {line : line, ch: 1000000}); + }, + + // Insert a line before and auto-indent + insertLineBefore: function(line) { + let thisCM; + + thisCM = this.getThisCM(); + + if (!line) {line = thisCM.getCursor().line} + thisCM.operation(function() { + thisCM.replaceRange("\n" + thisCM.getLine(line), {line: line, ch: 0}, {line: line, ch: 1000000}); + thisCM.setCursor({line: thisCM.getCursor().line - 1, ch: 0}); + thisCM.execCommand('indentAuto'); + }); + }, + + // Insert a line after and auto-indent + insertLineAfter: function(line) { + let thisCM; + + thisCM = this.getThisCM(); + + if (!line) {line = thisCM.getCursor().line} + thisCM.operation(function() { + thisCM.replaceRange(thisCM.getLine(line) + "\n", {line: line, ch: 0}, {line: line, ch: 1000000}); + thisCM.execCommand('indentAuto'); + }); + }, + + // Duplicate line + duplicateLines: function(line) { + let thisCM, ch, lineExtra, userSelStart, userSelEnd; + + thisCM = this.getThisCM(); + + if (!line && thisCM.somethingSelected()) { + userSelStart = thisCM.getCursor('start'); + userSelEnd = thisCM.getCursor('end'); + lineExtra = userSelStart.line !== userSelEnd.line && userSelEnd.ch === thisCM.getLine(userSelEnd.line).length ? "\n" : ""; + thisCM.replaceSelection(thisCM.getSelection() + lineExtra+thisCM.getSelection(), "end"); + thisCM.setSelection(userSelStart, userSelEnd); + } else { + if (!line) {line = thisCM.getCursor().line} + ch = thisCM.getCursor().ch; + thisCM.replaceRange(thisCM.getLine(line) + "\n" + thisCM.getLine(line), {line: line, ch: 0}, {line: line, ch: 1000000}); + thisCM.setCursor(line + 1, ch); + } + }, + + // Remove line + removeLines: function(line) { + let thisCM, ch; + + thisCM = this.getThisCM(); + + if (!line && thisCM.somethingSelected()) { + thisCM.replaceSelection("", "end"); + } else { + if (!line) {line = thisCM.getCursor().line} + ch = thisCM.getCursor().ch; + thisCM.execCommand('deleteLine'); + thisCM.setCursor(line - 1, ch); + } + }, + + // Jump to and highlight the function definition current token + jumpToDefinition: function() { + let thisCM, tokenString, defVars; + + thisCM = this.getThisCM(); + + tokenString = thisCM.getTokenAt(thisCM.getCursor()).string; + + if (thisCM.somethingSelected() && this.origCurorPos) { + thisCM.setCursor(this.origCurorPos); + } else { + this.origCurorPos = thisCM.getCursor(); + defVars = [ + "var " + tokenString, + "function " + tokenString, + tokenString + "=function", tokenString + "= function", tokenString + " =function", tokenString + " = function", + tokenString + "=new function", tokenString + "= new function", tokenString + " =new function", tokenString + " = new function", + "window['" + tokenString + "']", "window[\"" + tokenString + "\"]", + "this['" + tokenString + "']", "this[\"" + tokenString + "\"]", + tokenString + ":", tokenString + " :", + "def " + tokenString, + "class " + tokenString + ]; + for (let i = 0; i < defVars.length; i++) { + if (this.findReplace(defVars[i], false, false, false)) { + break; + } + } + } + }, + + // Update function & class list { + updateFunctionClassList: function() { + let cM; + + cM = this.getcMInstance(); + this.functionClassList = []; + + if (cM) { + // For each line, establish if there's a function or class item on it + cM.doc.eachLine(function(handle){ICEcoder.updateFunctionClassListItems(handle)}); + } + }, + + // Update function/class list items + updateFunctionClassListItems: function(handle) { + let cM, functionClassText; + + cM = this.getcMInstance(); + functionClassText = ""; + + // Get function declaration lines + if (handle.text.indexOf("function ") > -1 && handle.text.replace(/\$function/g,"").indexOf("function ") > -1) { + functionClassText = handle.text.substring(handle.text.indexOf("function ") + 9); + } + // Get class declaration lines + if (handle.text.indexOf("class ") > -1 && handle.text.replace(/\$class/g,"").indexOf("class ") > -1) { + functionClassText = handle.text.substring(handle.text.indexOf("class ") + 6); + } + // Get just the name of the function/class + functionClassText = functionClassText.trim().split("{")[0].split("("); + + // Push items into array + if (functionClassText[0] !== "") { + this.functionClassList.push({ + line: cM.getLineNumber(handle), + name: functionClassText[0], + params: "(" + (functionClassText[1] ? functionClassText[1].replace(/[,]/g,", ") : ""), + verified: false + }); + // After a 0ms tickover, verify the item + setTimeout(function(ic) { + // If we're defining a function/class + if (!handle.styles || (-1 < handle.styles && handle.styles.indexOf('def') && cM.getLineNumber(handle))) { + // Find our item in the array and mark it as verified + for (let i = 0; i < ic.functionClassList.length; i++) { + if (ic.functionClassList[i]['line'] == cM.getLineNumber(handle)) { + ic.functionClassList[i]['verified'] = true; + } + }; + } + }, 0, this); + } + }, + + // Autocomplete + autocomplete: function() { + this.content.contentWindow.CodeMirror.commands.autocomplete(this.getThisCM()); + }, + + // Paste a URL, locally or absolutely if CTRL/Cmd key down + pasteURL: function(url) { + if("CTRL" === this.draggingWithKey) { + url = window.location.protocol + "//" + window.location.hostname + url; + } + this.getThisCM().replaceSelection(url, "around"); + }, + + // Search for selected text online + searchForSelected: function() { + let thisCM; + + thisCM = this.getThisCM(); + + if (this.caretLocType) { + if ("" !== thisCM.getSelection()) { + let searchPrefix = this.caretLocType.toLowerCase() + " "; + if (this.caretLocType === "Content") { + searchPrefix = ""; + } + window.open("http://www.google.com/#output=search&q=" + searchPrefix + thisCM.getSelection()); + } else { + this.message(t['No text selected...']); + } + } + }, + + // Determine which area of the document we're in + caretLocationType: function() { + let thisCM, caretLocType, caretChunk, fileName, fileExt; + + thisCM = this.getThisCM(); + caretLocType = "Unknown"; + caretChunk = thisCM.getValue().substr(0, this.caretPos + 1); + + if (caretChunk.lastIndexOf("<\?") > caretChunk.lastIndexOf("?\>") && "Unknown" === caretLocType) {caretLocType = "PHP";} + else if (caretChunk.lastIndexOf("<\%") > caretChunk.lastIndexOf("%\>") && "Unknown" === caretLocType) {caretLocType = "Ruby";} + else if (caretChunk.lastIndexOf("") > caretChunk.lastIndexOf("<\/script>") && "Unknown" === caretLocType) {caretLocType = "JavaScript";} + else if (caretChunk.lastIndexOf(" caretChunk.lastIndexOf("/style>") && "Unknown" === caretLocType) {caretLocType = "CSS";} + else if (caretChunk.lastIndexOf("<") > caretChunk.lastIndexOf(">") && "Unknown" === caretLocType) {caretLocType = "HTML";} + else if ("Unknown" === caretLocType) {caretLocType = "Content";} + + fileName = this.openFiles[this.selectedTab - 1]; + if ("Content" === caretLocType && fileName) { + fileExt = fileName.split("."); + fileExt = fileExt[fileExt.length - 1]; + caretLocType = + fileExt === "js" ? "JavaScript" + : fileExt === "coffee" ? "CoffeeScript" + : fileExt === "ts" ? "TypeScript" + : fileExt === "py" ? "Python" + : fileExt === "mpy" ? "Python" + : fileExt === "rb" ? "Ruby" + : fileExt === "css" ? "CSS" + : fileExt === "less" ? "LESS" + : fileExt === "md" ? "Markdown" + : fileExt === "xml" ? "XML" + : fileExt === "sql" ? "SQL" + : fileExt === "yaml" ? "YAML" + : fileExt === "java" ? "Java" + : fileExt === "erl" ? "Erlang" + : fileExt === "jl" ? "Julia" + : fileExt === "c" ? "C" + : fileExt === "cpp" ? "C++" + : fileExt === "ino" ? "C++" + : fileExt === "cs" ? "C#" + : fileExt === "go" ? "Go" + : fileExt === "lua" ? "Lua" + : fileExt === "pl" ? "Perl" + : fileExt === "scss" ? "Sass" + : "Content"; + } + + this.caretLocType = caretLocType; + }, + + // Comment/uncomment line or selected range on keypress + lineCommentToggleSub: function(cM, cursorPos, linePos, lineContent, lCLen) { + let comments, startLine, endLine, commentCH, commentBS, commentBE; + + // Language specific commenting + if (-1 < ["JavaScript", "CoffeeScript", "TypeScript", "PHP", "Python", "Ruby", "CSS", "SQL", "Erlang", "Julia", "Java", "YAML", "C", "C++", "C#", "Go", "Lua", "Perl", "Sass"].indexOf(this.caretLocType)) { + + comments = { + "JavaScript" : ["// ", "/* ", " */"], + "CoffeeScript" : ["# ", "### ", " ###"], + "TypeScript" : ["// ", "/* ", " */"], + "PHP" : ["// ", "/* ", " */"], + "Python" : ["# ", "/* ", " */"], + "Ruby" : ["# ", "/* ", " */"], + "CSS" : ["// ", "/* ", " */"], + "SQL" : ["// ", "/* ", " */"], + "Erlang" : ["% ", "/* ", " */"], + "Julia" : ["# ", "/* ", " */"], + "Java" : ["// ", "/* ", " */"], + "YAML" : ["# ", "/* ", " */"], + "C" : ["// ", "/* ", " */"], + "C++" : ["// ", "/* ", " */"], + "C#" : ["// ", "/* ", " */"], + "Go" : ["// ", "/* ", " */"], + "Lua" : ["-- ", "--[[ ", " ]]"], + "Perl" : ["# ", "/* ", " */"], + "Sass" : ["// ", "/* ", " */"] + } + + // Identify the single line, block start and block end comment chars + commentCH = comments[this.caretLocType][0]; + commentBS = comments[this.caretLocType][1]; + commentBE = comments[this.caretLocType][2]; + + // Block commenting + if (cM.somethingSelected()) { + // Language has no block commenting, so repeating singles are needed + if (-1 < ["Ruby", "Python", "Erlang", "Julia", "YAML", "Perl"].indexOf(this.caretLocType)) { + startLine = cM.getCursor(true).line; + endLine = cM.getCursor().line; + for (let i = startLine; i <= endLine; i++) { + cM.replaceRange(cM.getLine(i).slice(0, commentCH.length) != commentCH + ? commentCH + cM.getLine(i) + : cM.getLine(i).slice(commentCH.length, cM.getLine(i).length), {line:i, ch:0}, {line:i, ch:1000000}); + } + // Language has block commenting + } else { + cM.replaceSelection(cM.getSelection().slice(0,commentBS.length) != commentBS + ? commentBS + cM.getSelection() + commentBE + : cM.getSelection().slice(commentBS.length, cM.getSelection().length - commentBE.length), "around"); + } + // Single line commenting + } else { + if (-1 < ["CSS", "SQL"].indexOf(this.caretLocType)) { + cM.replaceRange(lineContent.slice(0,commentBS.length) != commentBS + ? commentBS + lineContent + commentBE + : lineContent.slice(commentBS.length, lCLen - commentBE.length), {line: linePos, ch: 0}, {line: linePos, ch: 1000000}); + adjustCursor = commentBS.length; + if (lineContent.slice(0,commentBS.length) == commentBS) {adjustCursor = -adjustCursor} + } else { + cM.replaceRange(lineContent.slice(0,commentCH.length) != commentCH + ? commentCH + lineContent + : lineContent.slice(commentCH.length,lCLen), {line: linePos, ch: 0}, {line: linePos, ch: 1000000}); + adjustCursor = commentCH.length; + if (lineContent.slice(0,commentCH.length) == commentCH) {adjustCursor = -adjustCursor} + } + } + // HTML style commenting + } else { + if (cM.somethingSelected()) { + cM.replaceSelection(cM.getSelection().slice(0,4) !== "<\!--" + ? "<\!--" + cM.getSelection() + "//-->" + : cM.getSelection().slice(4, cM.getSelection().length - 5),"around"); + } else { + cM.replaceRange(lineContent.slice(0,4) !== "<\!--" + ? "<\!--" + lineContent + "//-->" + : lineContent.slice(4, lCLen-5), {line: linePos, ch: 0}, {line: linePos, ch: 1000000}); + adjustCursor = lineContent.slice(0,4) === "<\!--" ? -4 : 4; + } + } + + if (!cM.somethingSelected()) {cM.setCursor(linePos, cursorPos + adjustCursor)} + }, + +// ===== +// FILES +// ===== + + // Actions on file manager + fmAction: function(evt, action) { + let selElem, sPN, fileFolder, goElem; + + // Get selected elem, the parent node of that, if it's a file/folder and set elem to go to next + selElem = get('filesFrame').contentWindow.document.getElementById(this.selectedFiles[this.selectedFiles.length - 1] + "_perms").parentNode; + sPN = selElem.parentNode; + fileFolder = selElem.onmouseover.toString().indexOf("'folder'") > -1 ? "folder" : "file"; + goElem = false; + + if ("up" === action) { + if (sPN.previousSibling && sPN.previousSibling.previousSibling) { + // Jump to previous sibling + goElem = sPN.previousSibling.previousSibling; + if ("UL" === goElem.tagName) { + // Jump to last item in previous sibling dir + goElem = goElem.childNodes[goElem.childNodes.length - 1]; + } + } else if (sPN.parentNode.previousSibling) { + // Jump to parent dir + goElem = sPN.parentNode.previousSibling; + } + if (goElem) {goElem = goElem.childNodes[0]} + } + if ("down" === action) { + if (sPN.nextSibling && sPN.nextSibling.childNodes[0]) { + // Jump to first item in dir + goElem = sPN.nextSibling.childNodes[0]; + } else if (sPN.nextSibling && sPN.nextSibling.nextSibling) { + // Jump to next sibling + goElem = sPN.nextSibling.nextSibling; + } else if (sPN.parentNode.nextSibling) { + // Jump to next parent sibling item + goElem = sPN.parentNode.nextSibling.nextSibling; + } + if (goElem) {goElem = goElem.childNodes[0]} + } + if (action == "left") { + if ("folder" === fileFolder && sPN.parentNode.previousSibling) { + // contract dir + this.openCloseDir(selElem,false); + } + } + if ("right" === action || "enter" === action) { + "folder" === fileFolder + // expand dir + ? this.openCloseDir(selElem,true) + // open file + : this.openFile(selElem.childNodes[1].id.replace(/\|/g, "/")); + } + if (goElem && goElem.childNodes[1]) { + // If we have an elem to go to, select it + this.overFileFolder(fileFolder, goElem.childNodes[1].id); + this.selectFileFolder(evt); + } + }, + + // Open/close dirs on demand + openCloseDir: function(dir, load) { + let node, d; + + dir.onclick = function(event) { + if(!event.ctrlKey && !this.cmdKey) { + ICEcoder.openCloseDir(this, !load); + } + }; + node = dir.parentNode; + if (node.nextSibling) {node = node.nextSibling} + dir.parentNode.className = dir.className = "pft-directory dirOpen"; + if (node && "UL" === node.tagName) { + d = "none" === node.style.display; + d ? load = true : node.style.display = "none"; + dir.parentNode.className = dir.className = "pft-directory"; + } + if (load) { + this.filesFrame.contentWindow.frames['fileControl'].location.href = iceLoc + "/lib/get-branch.php?location=" + dir.childNodes[1].id + "&csrf=" + this.csrf; + } else if("UL" === node.tagName) { + node.parentNode.removeChild(node); + } + return false; + }, + + // Note which files or folders we are over on mouseover/mouseout + overFileFolder: function(type, link) { + this.thisFileFolderType = type; + this.thisFileFolderLink = link; + }, + + // Note which files or folders we are over on mouseover/mouseout + highlightFileFolder: function(link, highlight) { + this.filesFrame.contentWindow.document.getElementById(link).style.background = true === highlight + ? this.colorDropTgtBGFile + : ''; + }, + + // Detect and return dir/file/false for this DOM ref (false for not found) + isFileFolder: function(ref) { + let domElem; + + domElem = get('filesFrame').contentWindow.document.getElementById(ref.replace(iceRoot,"").replace(/\/$/, "").replace(/\//g, "|")); + if (domElem) { + return domElem.parentNode.parentNode.className.indexOf("directory") > -1 + ? "folder" + : "file"; + } else { + return false; + } + }, + + // Select file or folder on demand + selectFileFolder: function(evt, ctrlSim, shiftSim) { + let tgtFile, shortURL, selecting, dirList, lastFileClicked, startFile, endFile, thisFileObj; + + // If we've clicked somewhere other than a file/folder + if ("" === this.thisFileFolderLink) { + if (!ctrlSim && !evt.ctrlKey && !this.cmdKey) { + this.deselectAllFiles(); + } + } else if (this.thisFileFolderLink) { + // Get file URL, with pipes instead of slashes & target DOM elem + shortURL = this.thisFileFolderLink.replace(/\//g,"|"); + tgtFile = this.filesFrame.contentWindow.document.getElementById(shortURL); + + // If we have the CTRL/Cmd key down + if (ctrlSim || evt.ctrlKey || this.cmdKey) { + // Deselect or select file + if (-1 < this.selectedFiles.indexOf(shortURL)) { + this.selectDeselectFile('deselect', tgtFile); + this.selectedFiles.splice(this.selectedFiles.indexOf(shortURL), 1); + } else { + this.selectDeselectFile('select', tgtFile); + this.selectedFiles.push(shortURL); + } + // Select from last click to this one + } else if (shiftSim || evt.shiftKey) { + selecting = false; + dirList = tgtFile.parentNode.parentNode.parentNode; + lastFileClicked = this.selectedFiles[this.selectedFiles.length - 1]; + + // Prefix numbers with up to 20 leading zeros + // This is so we can have some kind of natural comparison on the regex below + function prefixer(match, p1, offset, string) { + return ('00000000000000000000' + match).substr(-20); + } + + startFile = shortURL.replace(/\d+/g, prefixer) < lastFileClicked.replace(/\d+/g, prefixer) ? shortURL : lastFileClicked; + endFile = shortURL.replace(/\d+/g, prefixer) > lastFileClicked.replace(/\d+/g, prefixer) ? shortURL : lastFileClicked; + + if (0 < this.selectedFiles.length && startFile.substr(0, startFile.lastIndexOf("|")) === endFile.substr(0, endFile.lastIndexOf("|"))) { + for (let i = 0; i < 1000000; i += 2) { + // Something bad has happened with what we're trying to select, so break + if ("undefined" === typeof dirList.childNodes[i] || dirList.childNodes[i].nodeName !== "LI") {break;} + thisFileObj = dirList.childNodes[i].childNodes[0].childNodes[1]; + if (thisFileObj.id === startFile) { + selecting = true; + } + if (true === selecting && -1 === this.selectedFiles.indexOf(thisFileObj.id)) { + this.selectDeselectFile('select', thisFileObj); + this.selectedFiles.push(thisFileObj.id); + } + if (thisFileObj.id === endFile) { + break; + } + } + } else { + this.selectDeselectFile('select', tgtFile); + this.selectedFiles.push(shortURL); + } + // We are single clicking + } else { + this.deselectAllFiles(); + + // Add our URL and highlight the file + this.selectDeselectFile('select', tgtFile); + this.selectedFiles.push(shortURL); + } + } + + // Adjust the file & replace select dropdown values accordingly + document.findAndReplace.target[2].innerHTML = !this.selectedFiles[0] ? t['all files'] : t['selected files']; + document.findAndReplace.target[3].innerHTML = !this.selectedFiles[0] ? t['all filenames'] : t['selected filenames']; + + // Hide the file menu incase it's showing + this.hideFileMenu(); + }, + + // Deselect all files + deselectAllFiles: function() { + let tgtFile; + + for (let i = 0; i < this.selectedFiles.length; i++) { + tgtFile = this.filesFrame.contentWindow.document.getElementById(this.selectedFiles[i]); + this.selectDeselectFile('deselect', tgtFile); + } + this.selectedFiles.length = 0; + }, + + // Select or deselect file + selectDeselectFile: function(action, file) { + let isOpen, isCurrent; + + if (file) { + isOpen = -1 < this.openFiles.indexOf(file.id.replace(/\|/g, "/")); + isCurrent = this.openFiles[this.selectedTab-1] === file.id.replace(/\|/g, "/"); + + // Selected dir/file + if ("select" === action) { + file.style.backgroundColor = this.colorSelectedBG; + file.style.color = this.colorSelectedText; + // File is current tab + } else if (true === isCurrent) { + file.style.backgroundColor = this.colorCurrentBG; + file.style.color = this.colorCurrentText; + // File is open + } else if (true === isOpen) { + file.style.backgroundColor = this.colorOpenBG; + file.style.color = this.colorOpenTextFile; + // Dir/file isn't selected + } else { + file.style.backgroundColor = ''; + file.style.color = ''; + } + } + }, + + // Box select files + boxSelect: function(evt, mouseAction) { + let fmDragBox, positive; + + fmDragBox = this.filesFrame.contentWindow.document.getElementById('fmDragBox'); + + // On mouse down, set start X & Y and reset first and last items in box area select + if ("down" === mouseAction) { + this.fmDragBoxStartX = this.mouseX; + this.fmDragBoxStartY = this.mouseY; + this.fmDragSelectFirst = ""; + this.fmDragSelectLast = ""; + } + + // On mouse drag, state we're dragging, set the box size and position properties and select files + if(this.mouseDown && !this.mouseDownInCM && "drag" === mouseAction) { + this.fmDraggedBox = true; + + // Handle X-axis properties + positive = this.mouseX - 0 < this.fmDragBoxStartX; + fmDragBox.style.left = (positive ? this.fmDragBoxStartX : this.mouseX) + "px"; + fmDragBox.style.width = Math.abs(this.mouseX - this.fmDragBoxStartX) + "px"; + + // Handle Y-axis properties + positive = 0 < this.mouseY - this.fmDragBoxStartY; + fmDragBox.style.top = (positive ? this.fmDragBoxStartY - 70 : this.mouseY - 70) + "px"; + fmDragBox.style.height = Math.abs(this.mouseY - this.fmDragBoxStartY) + "px"; + + // Select the files + if (this.thisFileFolderLink !== "") { + if (this.fmDragSelectFirst == "") { + this.fmDragSelectFirst = this.thisFileFolderLink; + this.overFileFolder(this.thisFileFolderLink.indexOf('.') > 0 ? 'file' : 'folder', this.fmDragSelectFirst); + this.selectFileFolder(evt); + } else { + this.fmDragSelectLast = this.thisFileFolderLink; + this.overFileFolder(this.thisFileFolderLink.indexOf('.') > 0 ? 'file' : 'folder', this.fmDragSelectLast); + this.selectFileFolder(evt, false, 'shiftSim'); + } + } + } + + // On mouse up, set width and height to 0 to hide + if("up" === mouseAction) { + fmDragBox.style.width = 0; + fmDragBox.style.height = 0; + } + }, + + // Create a new file (start & trigger save) + newFile: function() { + this.newTab(true); + }, + + // Create a new folder + newFolder: function() { + let shortURL, newFolder; + + shortURL = this.selectedFiles[this.selectedFiles.length - 1].replace(/\|/g, "/"); + newFolder = this.getInput('Enter new folder name at ' + shortURL, ''); + if (newFolder) { + newFolder = (shortURL + "/" + newFolder).replace(/\/\//, "/"); + this.serverQueue("add", iceLoc + "/lib/file-control.php?action=newFolder&csrf=" + this.csrf, encodeURIComponent(newFolder.replace(/\//g, "|"))); + this.serverMessage('' + t['Creating Folder'] + '
' + newFolder); + } + }, + + // Provide a path and line ref and we return the separate pieces + returnFileAndLine: function(fileLink) { + let line = 1; + const re = /^([^ ]*)\s+(on\s+)?(line\s+)?(\d+)/; + const reMatch = re.exec(fileLink); + + if (null !== reMatch) { + line = reMatch[4]; + fileLink = reMatch[1]; + } else if (fileLink.indexOf('://') > 0){ + if (fileLink.lastIndexOf(':') !== fileLink.indexOf('://')) { + line = fileLink.split(':')[2]; + fileLink = fileLink.substr(0,fileLink.lastIndexOf(":")); + } + } else if (fileLink.indexOf(':') > 0){ + line = fileLink.split(':')[1]; + fileLink = fileLink.split(':')[0]; + } + if ((fileLink.indexOf('(') > 0) && (fileLink.indexOf(')') > 0)){ + line = fileLink.split('(')[1].split(')')[0]; + fileLink = fileLink.split('(')[0]; + } + return [fileLink, line]; + }, + + // Open a file + openFile: function(fileLink) { + let flSplit, line, shortURL, canOpenFile; + + if ("undefined" !== typeof fileLink) { + flSplit = this.returnFileAndLine(fileLink); + fileLink = flSplit[0]; + line = flSplit[1]; + } + + if (fileLink) { + this.thisFileFolderLink = fileLink; + this.thisFileFolderType = "file"; + } + if ("/[NEW]" !== this.thisFileFolderLink && false !== this.isOpen(this.thisFileFolderLink)) { + this.switchTab(this.isOpen(this.thisFileFolderLink) + 1); + if (1 < line){ + this.goToLine(line); + } + } else if ("" !== this.thisFileFolderLink && "file" === this.thisFileFolderType) { + + // work out a shortened URL for the file + shortURL = this.thisFileFolderLink.replace(/\|/g, "/"); + // No reason why we can't open a file (so far) + canOpenFile = true; + // Limit to 100 files open at a time + if (100 <= this.openFiles.length) { + this.message(t['Sorry you can...']); + canOpenFile = false; + } + + // if we're still OK to open it... + if (canOpenFile) { + this.shortURL = shortURL; + + if ("/[NEW]" !== shortURL) { + this.thisFileFolderLink = this.thisFileFolderLink.replace(/\//g, "|"); + this.serverQueue("add", iceLoc + "/lib/file-control.php?action=load&file=" + encodeURIComponent(this.thisFileFolderLink) + "&csrf=" + this.csrf + "&lineNumber=" + line); + this.serverMessage('' + t['Opening File'] + '
' + this.shortURL); + } else { + this.createNewTab('new'); + } + this.fMIconVis('fMView', 1); + } + } + }, + + // Open selected files + openFilesFromList: function(fileList) { + for (let i = 0; i < fileList.length; i++) { + this.thisFileFolderLink = fileList[i].replace('|', '/'); + this.thisFileFolderType = 'file'; + this.openFile(); + } + }, + + // Show file prompt to open file + openPrompt: function() { + let fileLink; + + if(fileLink = this.getInput(t['Enter relative file...'], '')) { + fileLink.indexOf("://") > -1 + ? this.getRemoteFile(fileLink) + : this.openFile(fileLink); + } + }, + + // Get remote file contents + getRemoteFile: function(remoteFile) { + let flSplit, line; + + if ("undefined" !== typeof remoteFile) { + flSplit = this.returnFileAndLine(remoteFile); + remoteFile = flSplit[0]; + line = flSplit[1]; + } + + this.serverQueue("add", iceLoc + "/lib/file-control.php?action=getRemoteFile&csrf=" + this.csrf + "&lineNumber=" + line, encodeURIComponent(remoteFile)); + this.serverMessage('' + t['Getting'] + '
' + remoteFile); + }, + + // Get changes to save (used when simply saving, gets diff changes between current and last known version) + getChangesToSave: function() { + let cM, savedText, newText, sm, opcodes; + + cM = this.getcMInstance(); + + // Get the last known saved version of file from array + savedText = this.savedContents[this.selectedTab - 1]; + + // Get the text values and split it into lines + newText = difflib.stringAsLines(cM.getValue()); + savedText = difflib.stringAsLines(savedText); + + // Create a SequenceMatcher instance that diffs the two sets of lines + sm = new difflib.SequenceMatcher(savedText, newText); + + // Get the opcodes from the SequenceMatcher instance + // Opcodes is a list of 3-tuples describing what changes should be made to the base text in order to yield the new text + opcodes = sm.get_opcodes(); + + for (let i = 0; i < opcodes.length; i++) { + // opcode events may be: + // equal = do nothing for this range + // replace = replace [1]-[2] with [3]-[4] + // insert = replace [1]-[2] with [3]-[4] + // delete = replace [1]-[2] with [3]-[4] + for (let j = opcodes[i][3]; j < opcodes[i][4]; j++) { + if ("equal" !== opcodes[i][0]) { + // Add a new array item if we don't have one yet + if ("undefined" === typeof opcodes[i][5]) { + opcodes[i][5] = ""; + } + // Add text line from newText to that array item along with line break + opcodes[i][5] += newText[j] + "\n"; + } + } + } + + return JSON.stringify(opcodes); + }, + + // Save a file + saveFile: function(saveAs, newFileAutoSave) { + let changes, saveType, filePath, fileExt, pathPrefix; + filePath = this.openFiles[this.selectedTab - 1]; + fileExt = filePath.substr(filePath.lastIndexOf(".") + 1); + if ("undefined" !== typeof prettier && ["js", "json", "ts", "css", "scss", "less", "html", "xml", "yaml", "md", "php"].indexOf(fileExt) > -1) { + switch (fileExt) { + case "js": parser = "babel"; break; + case "json": parser = "json"; break; + case "ts": parser = "typescript"; break; + case "css": parser = "css"; break; + case "scss": parser = "scss"; break; + case "less": parser = "less"; break; + case "html": parser = "html"; break; + case "xml": parser = "html"; break; + case "yaml": parser = "yaml"; break; + case "md": parser = "markdown"; break; + case "php": parser = "php"; break; + } + try { + this.getThisCM().setValue(prettier.format( + this.getThisCM().getValue(), + { + parser: parser, + plugins: prettierPlugins + } + )); + } catch(err) { + get("toolLinkOutput").className = "highlight error"; + this.outputMsg('
Syntax error in ' + this.openFiles[this.selectedTab - 1].replace(iceRoot, "") + '
\n' + err.message); + } + } + setTimeout(function() { + // If we're not 'saving as', establish changes between current and known saved version from array + if (false === saveAs) { + changes = ic.getChangesToSave(); + } + + saveType = saveAs ? "saveAs" : "save"; + filePath = ic.openFiles[ic.selectedTab - 1].replace(iceRoot, "").replace(/\//g, "|"); + if ("|[NEW]" === filePath && 0 < ic.selectedFiles.length) { + pathPrefix = ic.selectedFiles[0]; + filePath = -1 == pathPrefix.lastIndexOf(".") || pathPrefix.lastIndexOf(".") < pathPrefix.lastIndexOf("|") + ? pathPrefix + filePath + : "|[NEW]"; + } + filePath = filePath.replace("||", "|"); + ic.serverQueue("add", iceLoc + "/lib/file-control.php?action=save&fileMDT=" + ic.openFileMDTs[ic.selectedTab - 1] + "&fileVersion=" + ic.openFileVersions[ic.selectedTab - 1] + "&saveType=" + saveType + "&newFileAutoSave=" + newFileAutoSave + "&tabNum=" + ic.selectedTab + "&csrf=" + ic.csrf,encodeURIComponent(filePath), changes); + ic.serverMessage('' + t['Saving'] + '
' + ic.openFiles[ic.selectedTab - 1].replace(iceRoot, "")); + }, 0, ic); + }, + + // Prompt a rename dialog + renameFile: function(oldName, newName) { + let shortURL, fileName, i; + + if (!oldName) { + shortURL = this.selectedFiles[this.selectedFiles.length - 1].replace(/\|/g, "/"); + oldName = this.selectedFiles[this.selectedFiles.length - 1].replace(/\|/g, "/"); + } else { + shortURL = oldName.replace(/\|/g, "/"); + } + if (!newName) { + newName = this.getInput(t['Please enter the...'], shortURL); + } + if (newName) { + i = this.openFiles.indexOf(shortURL.replace(/\|/g, "/")); + if (-1 < i) { + // rename array item and the tab + this.openFiles[i] = newName; + closeTabLink = ''; + fileName = this.openFiles[i]; + get('tab' + (i + 1)).innerHTML = closeTabLink + " " + fileName.slice(fileName.lastIndexOf("/")).replace(/\//, ""); + get('tab' + (i + 1)).title = newName; + } + this.serverQueue("add", iceLoc + "/lib/file-control.php?action=rename&oldFileName=" + encodeURIComponent(oldName.replace(/\|/g, "/")) + "&csrf=" + this.csrf,encodeURIComponent(newName)); + this.serverMessage('' + t['Renaming to'] + '
' + newName); + + this.setPreviousFiles(); + } + }, + + // Move a file from old location to new + moveFile: function(oldName, newName) { + let i, closeTabLink, fileName; + + if (newName && newName !== oldName) { + i = this.openFiles.indexOf(oldName.replace(/\|/g, "/")); + if(-1 < i) { + // rename array item and the tab + this.openFiles[i] = newName; + closeTabLink = ''; + fileName = this.openFiles[i]; + get('tab' + (i + 1)).innerHTML = closeTabLink + " " + fileName.slice(fileName.lastIndexOf("/")).replace(/\//, ""); + get('tab' + (i + 1)).title = newName; + } + if (this.ask("Are you sure you want to move file " + oldName + " to " + newName + " ?")){ + this.serverQueue("add", iceLoc + "/lib/file-control.php?action=move&oldFileName=" + encodeURIComponent(oldName.replace(/\//g, "|")) + "&csrf=" + this.csrf, encodeURIComponent(newName.replace(/\//g, "|"))); + this.serverMessage('' + t['Moving to'] + '
' + newName); + } + + this.setPreviousFiles(); + } + }, + + // Delete a file + deleteFiles: function(fileList) { + let tgtFiles, tgtListDisplay; + + tgtFiles = fileList ? fileList : this.selectedFiles; + tgtListDisplay = tgtFiles.toString().replace(/\|/g, "/").replace(/,/g, "\n"); + if (0 < tgtFiles.length && this.ask('Delete:\n\n' + tgtListDisplay + '?')) { + this.serverQueue("add", iceLoc + "/lib/file-control.php?action=delete&csrf=" + this.csrf,encodeURIComponent(tgtFiles.join(";"))); + this.serverMessage('' + t['Deleting File'] + '
' + tgtListDisplay); + } + }, + + // Copy files + copyFiles: function(fileList, dontShowPaste, dontHide) { + this.copiedFiles = []; + for (let i = 0; i < fileList.length; i++) { + this.copiedFiles[i] = fileList[i]; + } + if (!dontShowPaste) { + get('fmMenuPasteOption').style.display = "block"; + } + if (!dontHide) { + this.hideFileMenu(); + } + }, + + // Paste files + pasteFiles: function(location) { + if (this.copiedFiles) { + for (let i = 0; i < this.copiedFiles.length; i++) { + if ("|" !== this.copiedFiles[i]) { + this.serverQueue("add", iceLoc + "/lib/file-control.php?action=paste&location=" + location + "&csrf=" + this.csrf, encodeURIComponent(this.copiedFiles[i])); + this.serverMessage('' + t['Pasting File'] + '
' + this.copiedFiles[i].toString().replace(/\|/g, "/").replace(/,/g, "\n")); + } else { + this.message(t['Sorry cannot paste...']); + } + } + } else { + this.message(t['Nothing to paste...']); + } + }, + + // Duplicate (copy & paste) files + duplicateFiles: function(fileList) { + let copiedFiles, location; + + // Take a snapshot of copied files + if (this.copiedFiles) { + copiedFiles = this.copiedFiles; + } + + this.copyFiles(fileList, 'dontShowPaste', 'dontHide'); + location = fileList[0].substr(0, fileList[0].lastIndexOf("|")); + this.pasteFiles(location); + + // Restore copied files back to the snapshot + if ("undefined" !== typeof copiedFiles) { + this.copiedFiles = copiedFiles; + } + }, + + // Upload file(s) - select & submit + uploadFilesSelect: function(location) { + get('uploadDir').value = location; + get("fileInput").click(); + }, + uploadFilesSubmit: function(obj) { + if ("" !== get('fileInput').value) { + this.showHide('show', get('loadingMask')); + get('uploadFilesForm').submit(); + event.preventDefault(); + } + }, + + // Show/hide file manager nav options + showHideFileNav: function(vis, elem) { + let options = ["optionsFile", "optionsEdit", "optionsSettings", "optionsHelp"]; + if ("hide" === vis) { + fileNavInt = setTimeout(function(ic) { + for (let i = 0; i < options.length; i++) { + ic.showHide('hide', get(options[i])); + get(options[i] + 'Nav').style.color = ''; + } + }, 150, this); + } else { + for (let i = 0; i < options.length; i++) { + this.showHide('hide', get(options[i])); + get(options[i] + 'Nav').style.color = ''; + } + } + get('fileOptions').style.opacity = "0"; + if ("show" === vis) { + if ("undefined" !== typeof fileNavInt) { + clearTimeout(fileNavInt); + } + this.showHide(vis, get(elem)); + get(elem + 'Nav').style.color = '#fff'; + get('fileOptions').style.opacity = "1"; + } + }, + + // Is a specified path a folder? (Note: path is string encoded path with / replaced with |) + isPathFolder: function(path) { + // let's enumerate all folders to find whether clicked file is a folder or not + const dir = this.filesFrame.contentDocument.getElementsByClassName("pft-directory"); + const thisFileId = this.selectedFiles[0]; + let liNode, aNode, spanNode; + for (let i = 0; i < dir.length; i++){ + liNode = dir[i]; + if ("undefined" !== typeof liNode){ + aNode = liNode.childNodes[0]; + if ("undefined" !== typeof aNode){ + spanNode = aNode.childNodes[1]; + if ("undefined" !== typeof spanNode){ + if (thisFileId === spanNode.getAttribute('id')){ + // It's a folder + return true; + } + } + } + } + } + // It's a file + return false; + }, + + // Check for existence of a file/dir + checkExists: function(path) { + let xhr, statusObj, timeStart; + + path = path.replace(/\|/g, "/"); + // Clear any prefixed iceRoot from path + if (0 === path.indexOf(iceRoot)) { + path = path.replace(iceRoot, ""); + } + + // Start a seperate XHR call. We run seperately rather than add into the serverQueue because we may need to run + // immediately, eg need to if a file/dir exists mid flow in 'Save As' function, so can't go into queue + xhr = this.xhrObj(); + xhr.onreadystatechange=function() { + if (4 === xhr.readyState) { + // OK response? + if (200 === xhr.status) { + // Parse the response as a JSON object + statusObj = JSON.parse(xhr.responseText); + + // Set the action end time and time taken in JSON object + statusObj.action.timeEnd = new Date().getTime(); + statusObj.action.timeTaken = statusObj.action.timeEnd - statusObj.action.timeStart; + + // User wanted raw (or both) output of the response? + if (0 <= ["raw", "both"].indexOf(ICEcoder.fileDirResOutput)) { + console.log(xhr.responseText); + } + // User wanted object (or both) output of the response? + if (0 <= ["object", "both"].indexOf(ICEcoder.fileDirResOutput)) { + console.log(statusObj); + } + + // Also store the statusObj + ICEcoder.lastFileDirCheckStatusObj = statusObj; + + // If error, show that, otherwise do whatever we're required to do next + if (statusObj.status.error) { + ICEcoder.message(statusObj.status.errorMsg); + console.log("ICEcoder error info for your request..."); + console.log(statusObj); + ICEcoder.serverMessage(); + ICEcoder.serverQueue('del', 0); + } else { + eval(statusObj.action.doNext); + } + // Some other response? Display a message about that + } else { + ICEcoder.message(t['Sorry there was...']); + console.log("ICEcoder error info for your request..."); + console.log(statusObj); + ICEcoder.serverMessage(); + ICEcoder.serverQueue('del', 0); + } + } + }; + xhr.open("POST", iceLoc + "/lib/file-control.php?action=checkExists&csrf=" + this.csrf, true); + xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + timeStart = new Date().getTime(); + xhr.send('timeStart=' + timeStart + '&file=' + encodeURIComponent(path)); + }, + + // Show menu on right clicking in file manager + showMenu: function(evt) { + let menuType, menuHeight, winH, fmXPos, fmYPos; + + if (0 === this.selectedFiles.length || + -1 === this.selectedFiles.indexOf(this.selectedFiles[this.selectedFiles.length-1].replace(/\//g, "|"))) { + this.selectFileFolder(evt); + } + + menuHeight = 124 + 5; // general options height in px plus 5px space + winH = window.innerHeight; + if ("undefined" !== typeof this.thisFileFolderLink && "" !== this.thisFileFolderLink) { + menuType = this.isPathFolder(this.selectedFiles[0]) ? "folder" : "file"; + get('folderMenuItems').style.display = "folder" === menuType && 1 === this.selectedFiles.length ? "block" : "none"; + if ("folder" === menuType && 1 === this.selectedFiles.length) { + menuHeight += 20 + 20 + 1 + 23 + 1 + 2; // new file, new folder, hr, upload files(s), hr, padding + if ("block" === get('fmMenuPasteOption').style.display) { + menuHeight += 19; + } + } + get('singleFileMenuItems').style.display = this.selectedFiles.length > 1 ? "none" : "block"; + if (1 === this.selectedFiles.length) { + menuHeight += 43; + } + get('fileMenu').style.display = "inline-block"; + setTimeout(function() {get('fileMenu').style.opacity = "1"}, 4); + fmXPos = this.mouseX - this.filesFrame.contentWindow.scrollX + 20; + fmYPos = this.mouseY - this.filesFrame.contentWindow.scrollY - 10; + if (fmYPos + menuHeight > winH) { + fmYPos -= (fmYPos + menuHeight - winH); + } + get('fileMenu').style.left = fmXPos + "px"; + get('fileMenu').style.top = fmYPos + "px"; + } + return false; + }, + + // Continue to show the file menu + showFileMenu: function() { + get('fileMenu').style.display = 'inline-block'; + setTimeout(function() {get('fileMenu').style.opacity = "1"}, 4); + }, + + // Hide the file menu + hideFileMenu: function() { + get('fileMenu').style.display = 'none'; + get('fileMenu').style.opacity = "0"; + }, + + // Update the file manager tree list on demand + updateFileManagerList: function(action, location, file, perms, oldName, uploaded, fileOrFolder) { + let actionElemType, cssStyle, targetElem, locNest, newText, innerLI, permColors, newUL, newLI, elemType, nameLI, shortURL; + + perms = parseInt(perms, 10); + + // Adding files + if ("add" === action && !get('filesFrame').contentWindow.document.getElementById(location.replace(iceRoot, "").replace(/\/$/, "").replace(/\//g, "|") + "|" + file)) { + // Is this is a file or folder and based on that, set the CSS styling & link + actionElemType = fileOrFolder; + cssStyle = "file" === actionElemType ? "pft-file ext-" + file.substr(file.indexOf(".") + 1) : "pft-directory"; + perms = "file" === actionElemType ? this.newFilePerms : this.newDirPerms; + + // Identify our target element & the first child element in it's location + if (!location) {location = "/"} + location = location.replace(iceRoot, "/").replace("//", "/"); + targetElem = get('filesFrame').contentWindow.document.getElementById(location.replace(/\//g, "|")); + locNest = targetElem.parentNode.parentNode.nextSibling; + newText = document.createTextNode("\n"); + permColors = 777 === perms ? 'background: #800; color: #eee' : 'color: #888'; + innerLI = '        '+file+' '+perms+''; + + // If we don't have a locNest or at least 3 DOM items in there, it's an empty folder + if(!locNest || 3 > locNest.childNodes.length) { + // We now need to begin a new UL list + newUL = document.createElement("ul"); + locNest = targetElem.parentNode.parentNode; + locNest.parentNode.insertBefore(newUL, locNest.nextSibling); + + // Now we can add the first LI for this file/folder we're adding + newLI = document.createElement("li"); + newLI.className = cssStyle; + newLI.draggable = false; + newLI.ondragstart = function(event) {parent.ICEcoder.addDefaultDragData(this, event)}; + newLI.ondrag = function(event) {parent.ICEcoder.draggingWithKeyTest(event);if(parent.ICEcoder.getcMInstance()){parent.ICEcoder.editorFocusInstance.indexOf('diff') == -1 ? parent.ICEcoder.getcMInstance().focus() : parent.ICEcoder.getcMdiffInstance().focus()}}; + newLI.ondragover = function(event) {parent.ICEcoder.setDragCursor(event, "folder" === actionElemType ? 'folder' : 'file')}; + newLI.ondragend = function() {parent.ICEcoder.dropFile(this)}; + newLI.innerHTML = innerLI; + locNest.nextSibling.appendChild(newLI); + locNest.nextSibling.appendChild(newText); + + // There are items in that location, so add our new item in the right position + } else { + for (let i = 0; i < locNest.childNodes.length; i++) { + if (locNest.childNodes[i].className) { + // Identify if the item we're considering is a file or folder + elemType = 0 < locNest.childNodes[i].className.indexOf('directory') ? "folder" : "file"; + + // Get the name of the item + nameLI = locNest.childNodes[i].getElementsByTagName('span')[0].innerHTML; + + // If it's of the same type & the name is greater, or we're adding a folder and it's a file or if we're at the end of the list + if ((elemType == actionElemType && nameLI > file) || ("folder" === actionElemType && "file" === elemType) || i == locNest.childNodes.length - 1) { + newLI = document.createElement("li"); + newLI.className = cssStyle; + newLI.draggable = false; + newLI.ondragstart = function(event) {parent.ICEcoder.addDefaultDragData(this, event)}; + newLI.ondrag = function(event) {parent.ICEcoder.draggingWithKeyTest(event);if(parent.ICEcoder.getcMInstance()){parent.ICEcoder.editorFocusInstance.indexOf('diff') == -1 ? parent.ICEcoder.getcMInstance().focus() : parent.ICEcoder.getcMdiffInstance().focus()}}; + newLI.ondragover = function(event) {parent.ICEcoder.setDragCursor(event, "folder" === actionElemType ? 'folder' : 'file')}; + newLI.ondragend = function() {parent.ICEcoder.dropFile(this)}; + newLI.innerHTML = innerLI; + // Append or insert depending on which of the above if statements is true + if (i == locNest.childNodes.length - 1) { + locNest.appendChild(newLI); + locNest.appendChild(newText); + } else { + locNest.insertBefore(newLI,locNest.childNodes[i]); + locNest.insertBefore(newText,locNest.childNodes[i + 1]); + } + break; + } + } + } + } + // If we added a new file, we've saved it under a new filename, so set that + if ("file" === actionElemType && !uploaded) { + this.openFiles[this.selectedTab - 1] = location + "/" + file; + } + } + + // Renaming files + if ("rename" === action) { + // If dir is the same as before, it's a simple rename + if (location === oldName.substr(0, oldName.lastIndexOf('/'))) { + // Get short URL of our right clicked file and get target elem based on this + shortURL = oldName.replace(/\//g, "|"); + targetElem = get('filesFrame').contentWindow.document.getElementById(shortURL); + // Set the name to be as per our new file/folder name + targetElem.innerHTML = file; + // Update the ID of the target & set a new title and perms ID + targetElem.id = location.replace(/\//g, "|") + "|" + file; + targetElem.parentNode.title = targetElem.id.replace(/\|/g, "/"); + targetElemPerms = get('filesFrame').contentWindow.document.getElementById(shortURL + "_perms"); + targetElemPerms.id = location.replace(/\//g, "|") + "|" + file + "_perms"; + // Rename in selected files + this.renameInSelectedFiles(shortURL, location.replace(/\//g, "|") + "|" + file); + // Finally, rename also within any children + this.renameInChildren(targetElem, oldName, location, file); + // If dir has changed, handle dir change and possibly also filename change + } else { + // Target is root, or another dir? + const tgtClass = location === "" + ? this.filesFrame.contentWindow.document.getElementById("|").parentNode.parentNode.className + : this.filesFrame.contentWindow.document.getElementById(location.replace(/\//g, "|")).parentNode.parentNode.className; + // Source is a dir or file? + const srcClass = this.filesFrame.contentWindow.document.getElementById(oldName.replace(/\//g, "|")).parentNode.parentNode.className; + fileOrFolder = srcClass.indexOf("pft-directory") > -1 ? "folder" : "file"; + // Only add file into view if the dir is open + if (-1 < tgtClass.indexOf('dirOpen')) { + this.updateFileManagerList("add", location, file, false, false, false, fileOrFolder); + } + this.updateFileManagerList("delete", oldName.substr(0, oldName.lastIndexOf("/")), oldName.substr(oldName.lastIndexOf("/")+1)); + this.selectedFiles = []; + } + } + + // Moving files + if ("move" === action) { + // Target is root, or another dir? + const tgtClass = location === "" + ? this.filesFrame.contentWindow.document.getElementById("|").parentNode.parentNode.className + : this.filesFrame.contentWindow.document.getElementById(location.replace(/\//g, "|")).parentNode.parentNode.className; + // Only add file into view if the dir is open + if (-1 < tgtClass.indexOf('dirOpen')) { + this.updateFileManagerList("add", location, file, false, false, false, fileOrFolder); + } + this.updateFileManagerList("delete", oldName.substr(0, oldName.lastIndexOf("/")), file); + } + + // Chmod on files + if ("chmod" === action) { + // Get short URL for our file and get our target elem based on this + shortURL = this.selectedFiles[this.selectedFiles.length - 1].replace(/\|/g, "/"); + targetElem = get('filesFrame').contentWindow.document.getElementById(shortURL.replace(/\//g, "|") + "_perms"); + // Set the color for the perms + targetElem.style.background = 777 === perms ? '#800' : 'none'; + targetElem.style.color = 777 === perms ? '#eee' : '#888'; + // Set the new perms + targetElem.innerHTML = perms; + } + + // Deleting files + if ("delete" === action) { + if (!location) {location = ""} + location = location.replace(iceRoot, "/"); + location = location.replace("//", "/"); + location = location.replace(/\/$/, "").replace(/\//g, "|"); + targetElem = (location + "|" + file).replace("||", "|"); + targetElem = get('filesFrame').contentWindow.document.getElementById(targetElem).parentNode.parentNode; + this.openCloseDir(targetElem.childNodes[0], false); + targetElem.parentNode.removeChild(targetElem); + } + + // Finally, switch to selectedTab to refresh items + this.switchTab(this.selectedTab); + }, + + // Rename in selected files + renameInSelectedFiles: function(oldName, newName) { + for (let i = 0; i < this.selectedFiles.length; i++) { + if (oldName === this.selectedFiles[i]) { + this.selectedFiles[i] = newName; + } + } + }, + + // Rename node attributes within any renamed dirs recursively + renameInChildren: function(elem, oldName, location, file) { + let innerItems, targetElem, targetElemPerms; + + // If our elem has a sibling and it's a UL, we renamed a dir + if(elem.parentNode.parentNode.nextSibling && "UL" === elem.parentNode.parentNode.nextSibling.nodeName) { + innerItems = elem.parentNode.parentNode.nextSibling; + + // For each one of the children in the UL, if it's a LI (may be a file or dir) + for (let i = 0; i < innerItems.childNodes.length; i++) { + if ("LI" === innerItems.childNodes[i].nodeName) { + // Get the span elem inside as our targetElem + targetElem = innerItems.childNodes[i].childNodes[0].childNodes[1]; + // Update the ID of the target & set a new title + targetElem.id = targetElem.id.replace(oldName.replace(/\//g, "|"),location.replace(/\//g, "|") + "|" + file); + targetElem.parentNode.title = targetElem.id.replace(/\|/g, "/"); + // Also update the perms ID + targetElemPerms = get('filesFrame').contentWindow.document.getElementById(targetElem.id).nextSibling.nextSibling; + targetElemPerms.id = targetElem.id + "_perms"; + // Finally, test this node for ULs next to it also, incase it's a dir + this.renameInChildren(targetElem, oldName, location, file); + } + } + } + }, + + // Refresh file manager + refreshFileManager: function() { + this.filesFrame.contentWindow.location.reload(true); + this.filesFrame.style.opacity = "0"; + this.filesFrame.onload = function() { + ICEcoder.filesFrame.style.opacity = "1"; + } + }, + + // Detect CTRL/Cmd key whilst dragging files + draggingWithKeyTest: function(evt) { + let key; + + key = evt.keyCode ? evt.keyCode : evt.which ? evt.which : evt.charCode; + key = parseInt(key, 10); + + // Mac command key handling (224 = Moz, 91/93 = Webkit Left/Right Apple) + if (-1 < [224, 91, 93].indexOf(key)) { + this.cmdKey = true; + } + + this.draggingWithKey = evt.ctrlKey || this.cmdKey ? "CTRL" : false; + }, + + // Add default drag data (dragging in Firefox on DOM elems not possible otherwise) + addDefaultDragData: function(elem, evt) { + evt.dataTransfer.setData('Text', elem.id); + }, + + // Set a copy, move or none drag cursor type + setDragCursor: function(evt, dropType) { + let cursorIcon; + + // Prevent the default and establish if CTRL key is down + evt.preventDefault(); + this.draggingWithKeyTest(evt); + // Establish the cursor to show + cursorIcon = + "editor" === dropType + ? "CTRL" === this.draggingWithKey + ? "copy" + : "link" + : "folder" === dropType + ? "CTRL" === this.draggingWithKey + ? "copy" + : "move" + : "none"; + + evt.dataTransfer.dropEffect = cursorIcon; + }, + + // On dropping a file, do something + dropFile: function(elem) { + let filePath, tgtPath; + + filePath = elem.childNodes[0].childNodes[1].id.replace(/\|/g, "/"); + fileName = filePath.substr(filePath.lastIndexOf("/") + 1); + if ('editor' === this.area) { + this.pasteURL(filePath); + } + if ('files' === this.area) { + setTimeout(function(ic) { + tgtPath = "folder" === ic.thisFileFolderType ? ic.thisFileFolderLink : ic.thisFileFolderLink.substr(0, ic.thisFileFolderLink.lastIndexOf("|")); + if("CTRL" === ic.draggingWithKey) { + ic.copyFiles(ic.selectedFiles); + ic.pasteFiles(tgtPath); + } else { + // Clear the background of item you just dropped onto + this.filesFrame.contentWindow.document.getElementById(tgtPath.replace(/\//g, "|")).style.background = ''; + // If the tgtPath is not the root, postfix the path with a pipe + if ("|" !== tgtPath) {tgtPath += "|"}; + ic.moveFile(filePath,tgtPath.replace(/\|/g, "/") + fileName); + } + }, 4, this); + } + this.mouseDown = false; + this.mouseDownInCM = false; + }, + +// ============== +// FIND & REPLACE +// ============== + + // Update find & replace options based on user selection + findReplaceOptions: function() { + get('rText').style.display = + get('replace').style.display = + get('rTarget').style.display = + document.findAndReplace.connector.value==t['and'] + ? "inline-block" : "none"; + }, + + findReplaceOnInput: function() { + // Realtime finding - only action for finding/replacing in current doc + if ("" !== get('find').value && t['this document'] === document.findAndReplace.target.value) { + ICEcoder.findReplace(get('find').value, true, false, false); + get("find").focus(); + return false; + } + }, + + // Find & replace text according to user selections + findReplace: function(find, selectNext, canActionChanges, findPrevious) { + let replace, results, thisCM, avgBlockH, addPadding, rBlocks, haveMatch, blockColor, replaceQS, targetQS, filesQS; + + // Determine our find rExp, replace value and results display + const rExp = new RegExp(find.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), "gi"); + replace = get('replace').value; + results = get('results'); + + // Get CM pane + thisCM = this.getThisCM(); + + if (thisCM && 0 < find.length && t['this document'] === document.findAndReplace.target.value) { + // Replacing? + if (t['and'] === document.findAndReplace.connector.value && true === canActionChanges) { + // Find & replace the next instance, or all? + if (t['replace'] === document.findAndReplace.replaceAction.value && thisCM.getSelection().toLowerCase() === find.toLowerCase()) { + thisCM.replaceSelection(replace, "around"); + } else if (t['replace all'] === document.findAndReplace.replaceAction.value) { + thisCM.setValue(thisCM.getValue().replace(rExp, replace)); + } + } + + // Set results, resultsLines and findResult back to defaults + this.results = []; + this.resultsLines = []; + this.findResult = 0; + + // Start new iterators for line & last line + let i = 0; + let lastLine = -1; + + // Get lineNum and chNum from cursor + const lineNum = thisCM.getCursor(true === selectNext ? "anchor" : "head").line + 1; + const chNum = thisCM.getCursor(true === selectNext ? "anchor" : "head").ch; + + // Work out thhe avg block is either line height or fraction of space available + avgBlockH = !this.scrollBarVisible ? thisCM.defaultTextHeight() : parseInt(this.content.style.height, 10) / thisCM.lineCount(); + + // Need to add padding if there's no scrollbar, so current line highlighting lines up with it + addPadding = !this.scrollBarVisible ? thisCM.heightAtLine(0) : 0; + + // Result blocks string empty to start, ready to hold DOM elems to show in results bar + rBlocks = ""; + + // Start looking for results + thisCM.eachLine(function(line) { + i++; + haveMatch = false; + // If we have matches for our regex for this line + while ((match = rExp.exec(line.text)) !== null) { + haveMatch = true; + // Not the same as last line, add to resultsLines + if (lastLine !== i) { + ICEcoder.resultsLines.push(match.index); + lastLine = i; + } + // If the line containing a result is less than than the cursors line or + // if the character position of the match is less than the cursor position, increment findResult + if (i < lineNum || (i === lineNum && match.index < chNum)) { + ICEcoder.findResult++; + } + // Push the line & char position coords into results + ICEcoder.results.push([i, match.index]); + } + // If the avg block height for results in results bar is above 0.5 pixels heigh, we can add a DOM elem + if (0.5 <= avgBlockH) { + // Red for current line, grey for another line, transparent if no match + blockColor = haveMatch ? thisCM.getCursor().line + 1 == i ? "rgba(192,0,0,0.3)" : "rgba(128,128,128,0.3)" : "transparent"; + // Add the DOM elem into our rBlocks string + rBlocks += '
'; + } + }); + + // Increment findResult one more if our selection is what we want to find and we want to find next + if (find.toLowerCase() === thisCM.getSelection().toLowerCase() && false === findPrevious) { + ICEcoder.findResult++; + } + + if (findPrevious) { + // Find & replace backwards using previous button = 1, else just find = 1 + this.findResult -= true === canActionChanges ? 1 : 2; + } + + // If we have results + if (this.results.length>0) { + + // Show results only + if (false === selectNext) { + results.innerHTML = this.results.length + " results"; + // We may want to take action instead + } else { + // Looking for next and hit end, loop round to start + if (false === findPrevious && this.findResult > this.results.length - 1) { + this.findResult = 0 + } + // Looking for previous and hit start, loop round to end + if (findPrevious && 0 > this.findResult) { + this.findResult = this.results.length - 1; + } + + // If we somehow ended up with a number under 0, set to 0 + if (this.findResult < 0) { + this.findResult = 0; + } + + // Update results display + results.innerHTML = "Highlighted result " + (this.findResult + 1) + " of " + this.results.length + " results"; + + // Scroll to that line in the editor + this.goToLine(this.results[this.findResult][0], this.results[this.findResult][1], true); + + // Finally, highlight our selection and focus on CM pane + thisCM.setSelection( + {"line": this.results[this.findResult][0]-1, "ch": this.results[this.findResult][1]}, + {"line": this.results[this.findResult][0]-1, "ch": this.results[this.findResult][1] + find.length} + ); + this.focus(); + } + + // Display the find results bar + this.content.contentWindow.document.getElementById('resultsBar').innerHTML = rBlocks; + this.content.contentWindow.document.getElementById('resultsBar').style.display = "inline-block"; + + return true; + + } else { + results.innerHTML = "No results"; + this.content.contentWindow.document.getElementById('resultsBar').innerHTML = ""; + this.content.contentWindow.document.getElementById('resultsBar').style.display = "none"; + + // Clear our selection and so also the match highlights + thisCM.setCursor(thisCM.getCursor("anchor")); + + return false; + } + } else { + // Show the relevant multiple results popup + if (find !== "" && true === canActionChanges) { + // Set replace, target and files query string to empty + replaceQS = ""; + targetQS = ""; + filesQS = ""; + + // Replacing? + if (t['and'] === document.findAndReplace.connector.value) { + replaceQS = "&replace=" + replace; + } + // Target? + + if (0 <= document.findAndReplace.target.value.indexOf(t['file'])) { + targetQS = "&target=" + document.findAndReplace.target.value.replace(/ /g, "-"); + } + + // Files? + if (t['selected files'] === document.findAndReplace.target.value) { + filesQS = "&selectedFiles="+this.selectedFiles.join(":"); + } + + // Establish find + find = find.replace(/\'/g, '\''); + find !== encodeURIComponent(find) ? find = 'ICEcoder:' + encodeURIComponent(find) : find; + + // Finally, show loading mask and open multiple results pane using QS params + this.showHide('show',get('loadingMask')); + get('mediaContainer').innerHTML = ''; + // We have nothing to search for, blank it all out + } else { + results.innerHTML = "No results"; + this.content.contentWindow.document.getElementById('resultsBar').innerHTML = ""; + this.content.contentWindow.document.getElementById('resultsBar').style.display = "none"; + + // Clear our selection and so also the match highlights + thisCM.setCursor(thisCM.getCursor("anchor")); + } + } + }, + + // Replace text in a file + replaceInFile: function(fileRef, find, replace) { + this.serverQueue( + "add", + iceLoc + + "/lib/file-control.php?action=replaceText&find=" + find + + "&replace=" + replace + + "&csrf=" + this.csrf, + encodeURIComponent(fileRef.replace(/\//g, "|"))); + this.serverMessage('' + t['Replacing text in'] + '
' + fileRef); + }, + + + + + + + + + + + + + + + + + + + + + + + +// ============== +// INFO & DISPLAY +// ============== + + // Get the caret position + getCaretPosition: function() { + var thisCM, line, ch, chPos; + + thisCM = this.getThisCM(); + + line = thisCM.getCursor().line; + ch = thisCM.getCursor().ch; + chPos = 0; + for (var i=0;i0) + ? 'cM'+ICEcoder.cMInstances[this.selectedTab-1] + // fallback to first tab + : 'cM1' + ]; + }, + + // Determine the CodeMirror instance we're using + getcMdiffInstance: function(tab, context) { + if ("undefined" === typeof context) { + context = ICEcoder; + } + return context.content.contentWindow[ + (// target specific tab + !isNaN(tab) + ? 'cM'+ICEcoder.cMInstances[tab-1] + // new tab or selected tab + : tab=="new"||(tab!="new" && this.openFiles.length>0) + ? 'cM'+ICEcoder.cMInstances[this.selectedTab-1] + // fallback to first tab + : 'cM1') + + 'diff' + ]; + }, + + // Get the mouse position + getMouseXY: function(e,area) { + var tempX, tempY; + + this.mouseX = e.pageX ? e.pageX : e.clientX + document.body.scrollLeft; + this.mouseY = e.pageY ? e.pageY : e.clientY + document.body.scrollTop; + + this.area = area; + if (area!="top") { + this.mouseY += 25 + 45; + } + if (area=="editor") { + this.mouseX += this.filesW; + } + this.dragCursorTest(); + if (this.mouseY>62) {this.setTabWidths();}; + }, + + // Test if we need to show a drag cursor or not + dragCursorTest: function() { + var diffX, winH, cursorName, zone; + + // Dragging tabs, started after dragging for 10px from origin + diffX = this.mouseX - this.diffStartX; + if (this.draggingTab!==false && this.diffStartX && (diffX <= -10 || diffX >= 10)) { + if (this.mouseX > parseInt(this.files.style.width,10)) { + this.tabDragMouseX = this.mouseX - parseInt(this.files.style.width,10) - this.tabDragMouseXStart; + this.tabDragMove(); + } + } + + // Dragging file manager, possible within 7px of file manager edge + if (this.ready) { + winH = window.innerHeight; + if (!this.mouseDown) {this.draggingFilesW = false}; + + cursorName = (!this.draggingTab && ((this.mouseX > this.filesW-7 && this.mouseX < this.filesW+7) || this.draggingFilesW)) + ? "w-resize" + : "auto"; + if (this.content.contentWindow.document && this.filesFrame.contentWindow) { + document.body.style.cursor = cursorName; + if (zone = this.content.contentWindow.document.body) {zone.style.cursor = cursorName}; + if (zone = this.filesFrame.contentWindow.document.body) {zone.style.cursor = cursorName}; + } + } + }, + + // Show or hide a server message + serverMessage: function(message) { + var serverMessage; + + serverMessage = get('serverMessage'); + if (message) { + serverMessage.innerHTML = this.xssClean(message).replace(/\<b\>/g,"").replace(/\<\/b\>/g,"").replace(/\<br\>/g,"
"); + serverMessage.style.left = "0"; + } else { + setTimeout(function() {serverMessage.style.left = "2000px";},200); + } + serverMessage.style.opacity = message ? 1 : 0; + }, + + // Show a CSS color block next to our text cursor + cssColorPreview: function() { + var thisCM, string, rx, match, oldBlock, newBlock; + + thisCM = this.getThisCM(); + + if (thisCM) { + string = thisCM.getLine(thisCM.getCursor().line); + rx = /(#[\da-f]{3}(?:[\da-f]{3})?\b|\b(?:rgb|hsl)a?\([\s\d%,.-]+\)|\b[a-z]+\b)/gi; + + while((match = rx.exec(string)) && thisCM.getCursor().ch > match.index+match[0].length); + + oldBlock = get('content').contentWindow.document.getElementById('cssColor'); + if (oldBlock) {oldBlock.parentNode.removeChild(oldBlock)}; + if (this.codeAssist && this.caretLocType=="CSS") { + newBlock = document.createElement("div"); + newBlock.id = "cssColor"; + newBlock.style.position = "absolute"; + newBlock.style.display = "block"; + newBlock.style.width = newBlock.style.height = "20px"; + newBlock.style.zIndex = "1000"; + newBlock.style.background = match ? match[0] : ''; + newBlock.style.cursor = "pointer"; + newBlock.onclick = function() {ICEcoder.showColorPicker(match[0])}; + if (newBlock.style.backgroundColor=="") {newBlock.style.display = "none"}; + get('header').appendChild(newBlock); + thisCM.addWidget(thisCM.getCursor(), get('cssColor'), true); + } + } + }, + + // Show color picker + showColorPicker: function(color) { + get('blackMask').style.visibility = "visible"; + get('mediaContainer').innerHTML = '


'+ + ''+ + '
'+ + ''+ + ''; + farbtastic('picker','color'); + if (color) { + get('picker').farbtastic.setColor(color); + } + }, + + // Init the canvas by drawing the image and setting the floating containers background size (5x zoom) + initCanvasImage: function (imgThis) { + var canvas, img; + + canvas = get('canvasPicker').getContext('2d'); + + img = new Image(); + img.crossOrigin = "Anonymous"; + img.src = imgThis.src; + + // Issue with loading, display CORS error info + img.onerror = function() { + get('floatingContainer').style.visibility = "hidden"; + get('canvasPickerColorInfo').style.display = "none"; + get('canvasPickerCORSInfo').style.display = "block"; + } + + // On image load + img.onload = function() { + // Get width and height and draw this image into the canvas + get('canvasPicker').width = imgThis.width; + get('canvasPicker').height = imgThis.height; + canvas.drawImage(img,0,0,imgThis.width,imgThis.height); + + // Display color picker info and hide CORS message + get('canvasPickerColorInfo').style.display = "block"; + get('canvasPickerCORSInfo').style.display = "none"; + + // Show image preview box on mouse over + get('canvasPicker').onmouseover = function(event) { + get('floatingContainer').style.visibility = "visible"; + }; + // Hide image preview box on mouse out + get('canvasPicker').onmouseout = function(event) { + get('floatingContainer').style.visibility = "hidden"; + }; + } + + document.getElementById('floatingContainer').style.backgroundSize = (imgThis.naturalWidth*5)+"px "+(imgThis.naturalHeight*5)+"px"; + }, + + // Interact with the canvas image + interactCanvasImage: function (imgThis) { + var canvas, x, y, imgData, R, G, B, rgb, hex, textColor, fcElem, fcBGX, fcBGY; + + canvas = get('canvasPicker').getContext('2d'); + + // Show pointer colors on mouse move over canvas + get('canvasPicker').onmousemove = function(event) { + // get mouse x & y + x = event.pageX - this.offsetLeft; + y = event.pageY - this.offsetTop; + // get image data & then RGB values + imgData = canvas.getImageData(x, y, 1, 1).data; + R = imgData[0]; + G = imgData[1]; + B = imgData[2]; + rgb = R+','+G+','+B; + // Get hex from RGB value + hex = ICEcoder.rgbToHex(R,G,B); + // set the values & BG colours of the input boxes + get('rgbMouseXY').value = rgb; + get('hexMouseXY').value = '#' + hex; + get('hexMouseXY').style.backgroundColor = get('rgbMouseXY').style.backgroundColor = '#' + hex; + textColor = R<128 || G<128 || B<128 && (R<200 && G<200 && B>50) ? '#fff' : '#000'; + get('hexMouseXY').style.color = get('rgbMouseXY').style.color = textColor; + + // Move the floating container to follow mouse pointer + fcElem = get('floatingContainer'); + fcElem.style.left = this.mouseX+20 + "px"; + fcElem.style.top = this.mouseY + "px"; + // Move the background image for the container to match also + // 5 x zoom, account for scaling down of large images and shift 25px of the hover div size + // (55px is the 11x11 grid of pixels), minus 5px for centre row/col + fcBGX = -((x*5)*(imgThis.naturalWidth/imgThis.width))+25; + fcBGY = -((y*5)*(imgThis.naturalHeight/imgThis.height))+25; + fcElem.style.backgroundPosition = fcBGX+"px "+fcBGY+"px"; + }; + + // Set pointer colors on clicking canvas + get('canvasPicker').onclick = function() { + get('rgb').value = get('rgbMouseXY').value; + get('hex').value = get('hexMouseXY').value; + get('hex').style.backgroundColor = get('rgb').style.backgroundColor = get('hex').value; + get('hex').style.color = get('rgb').style.color = textColor; + } + }, + + // Convert RGB values to Hex + rgbToHex: function(R,G,B) { + return this.toHex(R)+this.toHex(G)+this.toHex(B); + }, + + // Return numbers as hex equivalent + toHex: function(n) { + n = parseInt(n,10); + if (isNaN(n)) return "00"; + n = Math.max(0,Math.min(n,255)); + return "0123456789abcdef".charAt((n-n%16)/16) + "0123456789abcdef".charAt(n%16); + }, + + // Insert new color value + insertColorValue: function(color) { + var thisCM, cursor; + + thisCM = this.getThisCM(); + + cursor = thisCM.getTokenAt(thisCM.getCursor()); + thisCM.replaceRange(color,{line:thisCM.getCursor().line,ch:cursor.start},{line:thisCM.getCursor().line,ch:cursor.end}); + }, + + // Change opacity of the file manager icons + fMIconVis: function(icon, vis) { + var i; + + if (i = get(icon)) { + i.style.opacity = vis; + } + }, + + // Check if a file is already open + isOpen: function(file) { + var i; + + file = file.replace(/\|/g, "/").replace(docRoot+iceRoot,""); + i = this.openFiles.indexOf(file); + // return the array position or false + return i!=-1 ? i : false; + }, + +// ============== +// SYSTEM +// ============== + + getThisCM: function() { + return this.editorFocusInstance.indexOf('diff') > -1 + ? this.getcMdiffInstance() + : this.getcMInstance(); + }, + + // Start running plugin intervals according to given specifics + startPluginIntervals: function(plugRef,plugURL,plugTarget,plugTimer) { + // Add CSRF to URL if it has QS params + if (plugURL.indexOf("?") > -1) { + plugURL = plugURL+"&csrf="+this.csrf; + } + this['plugTimer'+plugRef] = + // This window instances + ["_parent","_top","_self",""].indexOf(plugTarget) > -1 + ? this['plugTimer'+plugRef] = setInterval('window.location=\''+plugURL+'\'',plugTimer*1000*60) + // fileControl iframe instances + : plugTarget.indexOf("fileControl") == 0 + ? this['plugTimer'+plugRef] = setInterval(function(ic) { + ic.serverQueue("add",plugURL);ic.serverMessage(plugTarget.split(":")[1]); + },plugTimer*1000*60,this) + // _blank or named target window instances + : this['plugTimer'+plugRef] = setInterval('window.open(\''+plugURL+'\',\''+plugTarget+'\')',plugTimer*1000*60); + + // push the plugin ref into our array + this.pluginIntervalRefs.push(plugRef); + }, + + // Turning on/off the Code Assist + codeAssistToggle: function() { + var cM, cMdiff, fileName, fileExt; + + this.codeAssist = !this.codeAssist; + this.cssColorPreview(); + this.focus(this.editorFocusInstance.indexOf('diff') > -1 ? 'diff' : false); + + for (i=0;i0) { + nextSaveID++; + } + } + nextSaveID++; + // Add to end of array or remove from beginning on demand, plus add or remove if necessary + if (action=="add") { + this.serverQueueItems.push(item); + if (item.indexOf('action=save')>0) { + txtArea = document.createElement('textarea'); + txtArea.setAttribute('id', 'saveTemp'+nextSaveID); + document.body.appendChild(txtArea); + // If we're saving as or the file version is undefined, set the temp save value as the contents + if (item.indexOf('saveType=saveAs')>0 || item.indexOf('fileVersion=undefined')>0) { + get('saveTemp'+nextSaveID).value = cM.getValue(); + // Else we can save the JSON version of the changes to implement + } else { + get('saveTemp'+nextSaveID).value = changes; + } + } + } else if (action=="del") { + // console.log(this); + if (this.serverQueueItems[0] && this.serverQueueItems[0].indexOf('action=save')>0) { + topSaveID = nextSaveID-1; + for (var i=1;i=1 || this.serverQueueItems.length==1) { + // If we have an item, we're not saving previous file refs and not loading + if (item && (item.indexOf('saveFiles=')==-1 && item.indexOf('action=load')==-1)) { + xhr = this.xhrObj(); + xhr.onreadystatechange=function() { + if (xhr.readyState==4) { + // OK reponse? + if (xhr.status==200) { + // Parse the response as a JSON object + statusObj = JSON.parse(xhr.responseText); + + // Set the action end time and time taken in JSON object + statusObj.action.timeEnd = new Date().getTime(); + statusObj.action.timeTaken = statusObj.action.timeEnd - statusObj.action.timeStart; + + // User wanted raw (or both) output of the response? + if (["raw","both"].indexOf(ICEcoder.fileDirResOutput) >= 0) { + console.log(xhr.responseText); + } + // User wanted object (or both) output of the response? + if (["object","both"].indexOf(ICEcoder.fileDirResOutput) >= 0) { + console.log(statusObj); + } + // If error, show that, otherwise do whatever we're required to do next + if (statusObj.status.error) { + ICEcoder.message(statusObj.status.errorMsg); + console.log("ICEcoder error info for your request..."); + console.log(statusObj); + ICEcoder.serverMessage(); + ICEcoder.serverQueue('del',0); + } else { + eval(statusObj.action.doNext); + } + // Some other response? Display a message about that + } else { + ICEcoder.message(t['Sorry there was...']); + console.log("ICEcoder error info for your request..."); + console.log(statusObj); + ICEcoder.serverMessage(); + ICEcoder.serverQueue('del',0); + } + } + }; + xhr.open("POST",this.serverQueueItems[0],true); + xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + timeStart = new Date().getTime(); + + // Save as events need to send all contents + if (item.indexOf('action=saveAs')>0) { + xhr.send('timeStart='+timeStart+'&file='+file+'&contents='+encodeURIComponent(document.getElementById('saveTemp1').value)); + // Save evens can just sent the changes + } else if (item.indexOf('action=save')>0) { + xhr.send('timeStart='+timeStart+'&file='+file+'&changes='+encodeURIComponent(document.getElementById('saveTemp1').value)); + // Another type of event + } else { + xhr.send('timeStart='+timeStart+'&file='+file); + } + } else { + + setTimeout(function(ic) { + if ("undefined" != typeof ic.serverQueueItems[0]) { + ic.filesFrame.contentWindow.frames['fileControl'].location.href=ic.serverQueueItems[0]; + } + },1,this); + + } + } + }, + + // Cancel all actions on pressing Esc in non content areas + cancelAllActions: function() { + // Stop whatever the parent may be loading and clear tasks other than the current one + window.stop(); + if (this.serverQueueItems.length>0) { + this.serverQueueItems.splice(1,this.serverQueueItems.length); + } + this.showHide('hide',get('loadingMask')); + this.serverMessage(''+t['Cancelled tasks']+''); + setTimeout(function(ic) {ic.serverMessage();},2000,this); + }, + + // Set the current previousFiles in the settings file + setPreviousFiles: function() { + var previousFiles; + + previousFiles = this.openFiles.join(',').replace(/\//g,"|").replace(/(\|\[NEW\])|(,\|\[NEW\])/g,"").replace(/(^,)|(,$)/g,""); + if (previousFiles=="") {previousFiles="CLEAR"}; + // Then send through to the settings page to update setting + this.serverQueue("add",iceLoc+"/lib/settings.php?saveFiles="+encodeURIComponent(previousFiles)+"&csrf="+this.csrf); + this.updateLast10List(previousFiles); + }, + + // Update the list of 10 previous files in browser + updateLast10List: function(previousFiles) { + var newFile, last10Files, last10FilesList; + + // Split our previous files string into an array + previousFiles = previousFiles.split(','); + // For each one of those, if it's not 'CLEAR' we can maybe rotate the list + for (var i=0; i"+previousFiles[i].replace(/\|/g,"/")+"\n"; + + // Get DOM elem for last 10 files + last10Files = this.content.contentWindow.document.getElementById('last10Files'); + + // If the innerHTML of that doesn't contain our new item, we can insert it + if(last10Files.innerHTML.indexOf(newFile) == -1) { + // Get the last 10 files list, pop the last one off and add newFile at start + last10FilesList = last10Files.innerHTML.split("\n"); + if ( + last10FilesList.length >= 10 || // No more than 10 + last10FilesList[0] == '
[none]


' || // Clear out placeholder + last10FilesList[last10FilesList.length-1] == "" // No empty array items + ) { + last10FilesList.pop(); + } + // Update the list + last10Files.innerHTML = newFile + (last10FilesList.join("\n")); + } + } + } + }, + + // Opens the last files we had open + autoOpenFiles: function() { + if (this.previousFiles.length>0 && this.ask(t['Open previous files']+'\n\n'+this.previousFiles.length+' files:\n'+this.previousFiles.join('\n').replace(/\|/g,"/").replace(new RegExp(docRoot+iceRoot,'gi'),""))) { + for (var i=0;i'; + } + this.showHide(hide?'hide':'show',get('blackMask')); + }, + + // Show the help screen + helpScreen: function() { + get('mediaContainer').innerHTML = ''; + this.showHide('show',get('blackMask')); + }, + + // Show the backup versions screen + versionsScreen: function(file,versions) { + get('mediaContainer').innerHTML = ''; + this.showHide('show',get('blackMask')); + }, + + // Show the ICEcoder manual, loaded remotely + showManual: function(version,section) { + var sectionExtra; + + sectionExtra = section ? "#"+section : ""; + get('mediaContainer').innerHTML = ''; + this.showHide('show',get('blackMask')); + }, + + // Show the properties screen + propertiesScreen: function(fileName) { + get('mediaContainer').innerHTML = ''; + this.showHide('show',get('blackMask')); + }, + + // Show the auto-logout warning screen + autoLogoutWarningScreen: function() { + get('mediaContainer').innerHTML = ''; + this.showHide('show',get('blackMask')); + }, + + // Show the plugins manager + pluginsManager: function() { + get('mediaContainer').innerHTML = ''; + this.showHide('show',get('blackMask')); + }, + + // Go to localhost root + goLocalhostRoot: function() { + this.filesFrame.contentWindow.frames['fileControl'].location.href = iceLoc+"/lib/go-localhost-root.php"; + }, + + // Show the FTP manager + ftpManager: function() { + get('mediaContainer').innerHTML = ''; + this.showHide('show',get('blackMask')); + }, + + // Update the settings used when we make a change to them + useNewSettings: function(themeURL,codeAssist,lockedNav,tagWrapperCommand,autoComplete,visibleTabs,fontSize,lineWrapping,lineNumbers,showTrailingSpace,matchBrackets,autoCloseTags,autoCloseBrackets,indentWithTabs,indentAuto,indentSize,pluginPanelAligned,scrollbarStyle,bugFilePaths,bugFileCheckTimer,bugFileMaxLines,updateDiffOnSave,autoLogoutMins,refreshFM) { + var styleNode, thisCSS, strCSS, activeLineBG; + + // cut out ?microtime= at the end + var cleanThemeUrl = themeURL.slice(0, themeURL.lastIndexOf("?")); + // find out new theme name without leading path and trailing ".css" + var newTheme = cleanThemeUrl.slice(cleanThemeUrl.lastIndexOf("/")+1,cleanThemeUrl.lastIndexOf(".")); + // if theme was not changed - no need to do all these tricks + if (this.theme !== newTheme){ + // Add new stylesheet for selected theme to editor + this.theme = newTheme; + if (this.theme=="editor") {this.theme="icecoder"}; + styleNode = document.createElement('link'); + styleNode.setAttribute('rel', 'stylesheet'); + styleNode.setAttribute('type', 'text/css'); + styleNode.setAttribute('href', themeURL); + this.content.contentWindow.document.getElementsByTagName('head')[0].appendChild(styleNode); + if (["3024-day","base16-light","eclipse","elegant","mdn-like","neat","neo","paraiso-light","solarized","the-matrix","xq-light"].indexOf(this.theme)>-1) { + activeLineBG = "#ccc"; + } else if (["3024-night","blackboard","colorforth","liquibyte","night","tomorrow-night-bright","tomorrow-night-eighties","vibrant-ink"].indexOf(this.theme)>-1) { + activeLineBG = "#888"; + } else { + activeLineBG = "#000"; + } + this.switchTab(this.selectedTab); + } + + // Check/uncheck Code Assist setting + if (codeAssist != this.codeAssist) { + this.codeAssistToggle(); + } + + // Unlock/lock the file manager + if (lockedNav != this.lockedNav) { + this.lockUnlockNav(); + this.changeFilesW(!lockedNav ? 'contract' : 'expand'); + this.hideFileMenu(); + }; + + // Update font size at top level + thisCSS = document.styleSheets[0]; + strCSS = thisCSS.rules ? 'rules' : 'cssRules'; + thisCSS[strCSS][0].style['fontSize'] = fontSize; + + // Update font size in file manager + thisCSS = this.filesFrame.contentWindow.document.styleSheets[3]; + strCSS = thisCSS.rules ? 'rules' : 'cssRules'; + thisCSS[strCSS][0].style['fontSize'] = fontSize; + + // Update styles in editor + thisCSS = this.content.contentWindow.document.styleSheets[6]; + strCSS = thisCSS.rules ? 'rules' : 'cssRules'; + thisCSS[strCSS][0].style['fontSize'] = fontSize; + thisCSS[strCSS][4].style['border-left-width'] = visibleTabs ? '1px' : '0'; + thisCSS[strCSS][4].style['margin-left'] = visibleTabs ? '-1px' : '0'; + thisCSS[strCSS][2].style.cssText = "background-color: " + activeLineBG + " !important"; + + this.lineWrapping = lineWrapping; + this.lineNumbers = lineNumbers; + this.showTrailingSpace = showTrailingSpace; + this.matchBrackets = matchBrackets; + this.autoCloseTags = autoCloseTags; + this.autoCloseBrackets = autoCloseBrackets; + this.indentWithTabs = indentWithTabs; + this.indentSize = indentSize; + this.indentAuto = indentAuto; + this.scrollbarStyle = scrollbarStyle; + for (var i=0;ichMod '+perms+' on
'+file.replace(/\|/g,"/")); + }, + + // Open/show the preview window + openPreviewWindow: function() { + if (this.openFiles.length>0) { + var filepath, filename, fileExt; + + filepath = this.openFiles[this.selectedTab-1]; + filename = filepath.substr(filepath.lastIndexOf("/")+1); + fileExt = filename.substr(filename.lastIndexOf(".")+1); + + this.previewWindowLoading = true; + this.previewWindow = window.open(filepath,"previewWindow",500,500); + if (["md"].indexOf(fileExt) > -1) { + this.previewWindow.addEventListener('load', function(ic, content) { + ic.previewWindowLoading = false; + ic.previewWindow.document.documentElement.innerHTML = "" + setTimeout(function() {ic.previewWindow.document.documentElement.innerHTML = content}, 100); + }(ic, mmd(ic.getThisCM().getValue())), false); + } else { + this.previewWindow.onload = function() { + this.previewWindowLoading = false; + // Do the pesticide plugin if it exists + try {this.doPesticide();} catch(err) {}; + // Do the stats.js plugin if it exists + try {this.doStatsJS('open');} catch(err) {}; + // Do the responsive plugin if it exists + try {this.doResponsive();} catch(err) {}; + } + } + } + }, + + // Reset auto-logout timer + resetAutoLogoutTimer: function() { + if(this.autoLogoutMins > 1 && this.autoLogoutTimer > (this.autoLogoutMins*60)-60) { + this.showHide('hide',get('blackMask')); + } + this.autoLogoutTimer = 0; + }, + + // Logout of ICEcoder + logout: function(type) { + window.location = window.location + "?logout&"+(type ? "type="+type+"&" : "")+"csrf="+this.csrf; + }, + + // Show a message + outputMsg: function(msg) { + this.output.innerHTML += msg + "
"; + }, + + // Show a message + message: function(msg) { + alert(msg); + }, + + // Ask for confirmation + ask: function(question) { + return confirm(question); + }, + + // Get the users input + getInput: function(question,defaultValue) { + return prompt(question,defaultValue); + }, + + // Show a data screen message + dataMessage: function(message) { + var dM; + + dM = this.content.contentWindow.document.getElementById('dataMessage'); + dM.style.display = "block"; + dM.innerHTML = message; + }, + + // Update ICEcoder + update: function() { + var autoUpdate; + + autoUpdate = confirm(t['Please note for...']); + if (autoUpdate) { + this.showHide('show',get('loadingMask')); + window.location = iceLoc+"/lib/updater.php"; + } else { + window.open("https://this.net"); + } + }, + + // ICEcoder just updated + updated: function() { + get('blackMask').style.visibility = "visible"; + get('mediaContainer').innerHTML = '

Thanks for updating to v'+this.versionNo+'!

' + + '

Click anywhere to continue using this...

'; + }, + + // XHR object + xhrObj: function(){ + try {return new XMLHttpRequest();}catch(e){} + try {return new ActiveXObject("Msxml3.XMLHTTP");}catch(e){} + try {return new ActiveXObject("Msxml2.XMLHTTP.6.0");}catch(e){} + try {return new ActiveXObject("Msxml2.XMLHTTP.3.0");}catch(e){} + try {return new ActiveXObject("Msxml2.XMLHTTP");}catch(e){} + try {return new ActiveXObject("Microsoft.XMLHTTP");}catch(e){} + return null; + }, + + // Open bug report + openBugReport: function() { + var bugReportOpenFilePos; + + if(this.bugReportStatus=="off") { + this.message(t['You can start...']); + } + if(this.bugReportStatus=="error") { + this.message(t['Error cannot find...']); + } + if(this.bugReportStatus=="ok") { + this.message(t['No new errors...']); + } + if(this.bugReportStatus=="bugs") { + // Close bug-report without saving previousFiles and without confirming close if we made changes on the bug report + var bugReportOpenFilePos = this.openFiles.indexOf(this.bugReportPath.replace(/\|/g,"/")); + if (bugReportOpenFilePos > -1) { + this.closeTab(bugReportOpenFilePos+1,'dontSetPV','dontAsk'); + } + this.openFile(this.bugReportPath); + this.bugFilesSizesSeen = this.bugFilesSizesActual; + } + }, + + // Start bug checking by looking in bug file paths on a timer + startBugChecking: function() { + var bugCheckURL; + + if (this.bugFileCheckTimer !== 0) { + // Clear any existing interval + if ("undefined" != typeof this.bugFileCheckInt) { + clearInterval(this.bugFileCheckInt); + } + // Start a new timer + this.bugFilesSizesSeen = []; + this.bugFileCheckInt = setInterval(function(ic) { + bugCheckURL = iceLoc+"/lib/bug-files-check.php?"; + bugCheckURL += "files="+(ic.bugFilePaths[0] !== "" ? ic.bugFilePaths.join() : "null").replace(/\//g,"|"); + bugCheckURL += "&filesSizesSeen="; + if (ic.bugFilesSizesSeen.length != ic.bugFilePaths.length) { + // Fill the array with nulls + for (var i=0; i/g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + }, + + // Print code of current tab + printCode: function() { + var thisCM, printIFrame; + + thisCM = this.getThisCM(); + + printIFrame = this.filesFrame.contentWindow.frames['fileControl']; + // Print page content injected into iFrame, escaped with pre and xssClean + printIFrame.window.document.body.innerHTML = 'ICEcoder code output
'+this.xssClean(thisCM.getValue())+'
'; + printIFrame.focus(); + printIFrame.print(); + // Focus back on code + thisCM.focus(); + }, + + // Update the title tag to indicate any changes + indicateChanges: function() { + var winTitle; + + if (!this.loadingFile) { + winTitle = "ICEcoder v "+this.versionNo; + for(var i=1;i<=this.savedPoints.length;i++) { + if (this.savedPoints[i-1]!=this.getcMInstance(i).changeGeneration()) { + // We have an unsaved tab, indicate that in the title + winTitle += " \u2744"; + break; + } + } + document.title = winTitle; + } + }, + +// ============== +// TABS +// ============== + + // Change tabs by switching visibility of instances + switchTab: function(newTab,noFocus) { + var cM, cMdiff, thisCM; + + // If we're not switching to same tab (for some reason), note the previous tab + if (newTab !== this.selectedTab) { + this.prevTab = this.selectedTab; + } + + // Identify tab that's currently selected & get the instance + this.selectedTab = newTab; + cM = this.getcMInstance(); + cMdiff = this.getcMdiffInstance(); + thisCM = this.editorFocusInstance.indexOf('diff') > -1 ? cMdiff : cM; + + if (thisCM) { + // Switch mode to HTML, PHP, CSS etc + this.switchMode(); + + // Set all cM instances to be hidden, then make our selected instance visible + for (var i=0;i thisCM.getScrollInfo().clientHeight; + ic.findReplace(get('find').value, false, false, false); + ic.setLayout(); + },0,this); + + // Finally, update the cursor display + this.getCaretPosition(); + this.updateCharDisplay(); + this.updateByteDisplay(); + } + }, + + // Starts a new file by setting a few vars & creating a new cM instance + newTab: function(autoSave) { + var cM; + + this.cMInstances.push(this.nextcMInstance); + this.selectedTab = this.cMInstances.length; + this.showHide('show',this.content); + this.content.contentWindow.createNewCMInstance(this.nextcMInstance); + this.setLayout(); + + this.thisFileFolderType='file'; + this.thisFileFolderLink='/[NEW]'; + this.openFile(); + + cM = this.getcMInstance('new'); + this.switchTab(this.openFiles.length); + + cM.removeLineClass(this['cMActiveLinecM'+this.cMInstances[this.selectedTab-1]], "background"); + this['cMActiveLinecM'+this.selectedTab] = cM.addLineClass(0, "background", "cm-s-activeLine"); + this.nextcMInstance++; + + // Also auto trigger save + if (true === autoSave) { + this.saveFile(false, true); + } + }, + + // Create a new tab for a file + createNewTab: function(isNew) { + var closeTabLink, fileName; + + // Push new file into array + this.openFiles.push(this.shortURL); + + // Setup a new tab + closeTabLink = ''; + get('tab'+(this.openFiles.length)).style.display = "inline-block"; + fileName = this.openFiles[this.openFiles.length-1]; + get('tab'+(this.openFiles.length)).innerHTML = closeTabLink + " " + fileName.slice(fileName.lastIndexOf("/")).replace(/\//,""); + get('tab'+(this.openFiles.length)).title = "/" + this.openFiles[this.openFiles.length-1].replace(/\//,""); + + // Set the widths + this.setTabWidths(); + + // Highlight it and state it's selected + this.redoTabHighlight(this.openFiles.length); + this.selectedTab=this.openFiles.length; + + // Add a new value ready to indicate if this content has been changed + this.savedPoints.push(0); + this.savedContents.push(""); + + if (!isNew) { + this.setPreviousFiles(); + } + }, + + // Cycle to next tab + nextTab: function() { + var goToTab; + + goToTab = this.selectedTab+1 <= this.openFiles.length ? this.selectedTab+1 : 1; + this.switchTab(goToTab,'noFocus'); + }, + + // Cycle to next tab + previousTab: function() { + var goToTab; + + goToTab = this.selectedTab-1 >= 1 ? this.selectedTab-1 : this.openFiles.length; + this.switchTab(goToTab,'noFocus'); + }, + + // Create a new tab for a file + renameTab: function(tabNum,newName) { + var closeTabLink, fileName; + + // Push new file into array + this.openFiles[tabNum-1] = newName; + + // Setup a new tab + closeTabLink = ''; + fileName = this.openFiles[tabNum-1]; + get('tab'+tabNum).innerHTML = closeTabLink + " " + fileName.slice(fileName.lastIndexOf("/")).replace(/\//,""); + get('tab'+tabNum).title = "/" + this.openFiles[tabNum-1].replace(/\//,""); + }, + + // Reset all tabs to be without a highlight and then highlight the selected + redoTabHighlight: function(selectedTab) { + var folderFileElems, fileLink; + + // For all open tabs... + for (var i = 1; i<= this.savedPoints.length; i++) { + // Set the close tab icon BG color according to save status + if (get('tab' + i).childNodes[0]) { + get('tab' + i).childNodes[0].childNodes[0].style.backgroundColor = this.savedPoints[i - 1] != this.getcMInstance(i).changeGeneration() + ? "#b00" : ""; + } + // Set the BG and text color for tabs according to if it's the current tab or not + get('tab'+i).style.color = i === selectedTab ? this.colorCurrentText : this.colorOpenTextTab; + get('tab'+i).style.background = i === selectedTab ? this.colorCurrentBG : this.colorOpenBG; + } + + // Now we can set about setting the coloring of dirs/files in the file manager + // First we clear the highlighing, then highlight the open dirs/files, then highlight the current + // file that's open as a tab (overides open highlighting) and finally highlight all of the + // user selected dirs/files (overrides previous highlighting too) + + // Clear all highlighting + folderFileElems = this.filesFrame.contentWindow.document.getElementsByTagName("SPAN"); + for (let i = 0; i < folderFileElems.length; i++) { + if (-1 === folderFileElems[i].id.indexOf("_perms") && "" !== folderFileElems[i].style.backgroundColor) { + folderFileElems[i].style.backgroundColor = ""; + folderFileElems[i].style.color = ""; + } + } + + // Highlight all open files + for (var i = 0; i < this.openFiles.length; i++) { + fileLink = this.filesFrame.contentWindow.document.getElementById(this.openFiles[i].replace(/\//g,"|")); + if (fileLink) { + fileLink.style.backgroundColor = this.colorOpenBG; + fileLink.style.color = this.colorOpenTextFile; + } + } + + // Highlight the file that's the current tab + if (1 <= this.selectedTab) { + fileLink = this.filesFrame.contentWindow.document.getElementById(this.openFiles[this.selectedTab - 1].replace(/\//g,"|")); + if (fileLink) { + fileLink.style.backgroundColor = this.colorCurrentBG; + fileLink.style.color = this.colorCurrentText; + } + } + + + // Highlight all user selected files + for (var i = 0; i < this.selectedFiles.length; i++) { + fileLink = this.filesFrame.contentWindow.document.getElementById(this.selectedFiles[i]); + if (fileLink) { + fileLink.style.backgroundColor = this.colorSelectedBG; + fileLink.style.color = this.colorSelectedText; + } + } + }, + + // Close the tab upon request + closeTab: function(closeTabNum, dontSetPV, dontAsk) { + var okToRemove, closeFileName; + + // If we haven't specified, close current tab + if (!closeTabNum) {closeTabNum = this.selectedTab}; + + okToRemove = true; + // Only confirm if we're OK to ask and... + if (!dontAsk && ( + ("/[NEW]" === this.openFiles[closeTabNum-1] + // ...it's a new file that's not empty + ? "" !== this.getcMInstance(closeTabNum).getValue() + // ...or it's not a new file and it's not saved + : this.savedPoints[closeTabNum-1] != this.getcMInstance(closeTabNum).changeGeneration() + ) + )) { + okToRemove = this.ask(t['You have made...']); + } + + if (okToRemove) { + // Get the filename of tab we're closing + closeFileName = this.openFiles[closeTabNum-1]; + + // recursively copy over all tabs & data from the tab to the right, if there is one + for (var i=closeTabNum;i0 ? this.selectedTab-=1 : this.selectedTab = 0; + } + if (this.openFiles.length>0 && this.selectedTab==0) {this.selectedTab=1}; + + // grey out the view icon + if (this.openFiles.length==0) { + this.fMIconVis('fMView',0.3); + } else { + // Switch the mode & the tab + this.switchMode(); + this.switchTab(this.selectedTab); + } + // Highlight the selected tab after splicing the change state out of the array + this.savedPoints.splice(closeTabNum-1,1); + this.savedContents.splice(closeTabNum-1,1); + this.redoTabHighlight(this.selectedTab); + + // Remove any highlighting from the file manager + this.selectDeselectFile('deselect',this.filesFrame.contentWindow.document.getElementById(closeFileName.replace(/\//g,"|"))); + + if (!dontSetPV) { + this.setPreviousFiles(); + } + + // Update the versions display + this.updateVersionsDisplay(); + + // Update the title tag to indicate any changes + this.indicateChanges(); + } + // Lastly, stop it from trying to also switch tab + this.canSwitchTabs=false; + // and set the widths + this.setTabWidths('posOnlyNewTab'); + setTimeout(function(ic) {ic.canSwitchTabs=true;},100,this); + }, + + // Close all tabs + closeAllTabs: function() { + if (this.cMInstances.length>0 && this.ask(t['Close all tabs'])) { + for (var i=this.cMInstances.length; i>0; i--) { + this.closeTab(i, i>1? true:false); + } + } + // Update the title tag to indicate any changes + this.indicateChanges(); + }, + + // Set the tabs width + setTabWidths: function(posOnlyNewTab) { + var availWidth, avgWidth, tabWidth, lastLeft, lastWidth; + + if (this.ready) { + availWidth = parseInt(this.content.style.width,10)-53-22-10; // - left margin - new tab - right margin + avgWidth = (availWidth/this.openFiles.length)-18; + tabWidth = -18; // Incl 18px offset + lastLeft = 53; + lastWidth = 0; + this.tabLeftPos = []; + for (var i=0;i availWidth ? parseInt(avgWidth*i,10) - parseInt(avgWidth*(i-1),10) : 150; + lastLeft = i==0 ? 53 : parseInt(get('tab'+(i)).style.left,10); + lastWidth = i==0 ? 0 : parseInt(get('tab'+(i)).style.width,10)+18; + if (!posOnlyNewTab) { + get('tab'+(i+1)).style.left = (lastLeft+lastWidth) + "px"; + get('tab'+(i+1)).style.width = tabWidth + "px"; + } else { + tabWidth = -18; + } + this.tabLeftPos.push(lastLeft+lastWidth); + } + get('newTab').style.left = (lastLeft+lastWidth+tabWidth+18) + "px"; + } + }, + + // Tab dragging start + tabDragStart: function(tab) { + this.draggingTab = tab; + this.diffStartX = this.mouseX; + this.tabDragMouseXStart = (this.mouseX - (parseInt(this.files.style.width,10)+53+18)) % 150; + // Put tab we're dragging over others + get('tab'+tab).style.zIndex = 2; + // Set classes for other tabs (tabSlide) and the one we're dragging (tabDrag) + for (var i=1; i<=this.openFiles.length; i++) { + get('tab'+i).className = i!==tab + ? "tab tabSlide" + : "tab tabDrag"; + } + }, + + // Tab dragging + tabDragMove: function() { + var lastTabWidth, thisLeft, dragTabNo, tabWidth; + + lastTabWidth = parseInt(get('tab'+this.openFiles.length).style.width,10)+18; + + // Set the left position but stay within left side (53) and new tab + this.thisLeft = thisLeft = this.tabDragMouseX >= 53 + ? this.tabDragMouseX <= parseInt(get('newTab').style.left,10) - lastTabWidth + ? this.tabDragMouseX : (parseInt(get('newTab').style.left,10) - lastTabWidth) : 53; + + get('tab'+this.draggingTab).style.left = thisLeft + "px"; + + this.dragTabNo = dragTabNo = this.draggingTab; + + // Set the opacities of tabs then positions of tabs we're not dragging + for (var i=1; i<=this.openFiles.length; i++) { + get('tab'+i).style.opacity = i == this.draggingTab ? 1 : 0.5; + tabWidth = this.tabLeftPos[i] ? this.tabLeftPos[i] - this.tabLeftPos[i-1] : tabWidth; + if (i!=this.draggingTab) { + if (i < this.draggingTab) { + get('tab'+i).style.left = thisLeft <= this.tabLeftPos[i-1] + ? this.tabLeftPos[i-1]+tabWidth + : this.tabLeftPos[i-1]; + } else { + get('tab'+i).style.left = thisLeft >= this.tabLeftPos[i-1] + ? this.tabLeftPos[i-1]-tabWidth + : this.tabLeftPos[i-1]; + } + } + } + }, + + // Tab dragging end + tabDragEnd: function() { + var swapWith, tempArray; + + // Set the tab widths + this.setTabWidths(); + // Determin what tabs we've swapped and reset classname, opacity & z-index for all + for (var i=1; i<=this.openFiles.length; i++) { + if (this.thisLeft >= this.tabLeftPos[i-1]) { + swapWith = this.thisLeft == this.tabLeftPos[0] ? 1 : this.dragTabNo > i ? i+1 : i; + } + get('tab'+i).className = "tab"; + get('tab'+i).style.opacity = 1; + if (i!=this.dragTabNo) { + get('tab'+i).style.zIndex = 1; + } else { + setTimeout(function() { + get('tab'+i).style.zIndex = 1; + },150); + } + } + if (this.thisLeft && this.thisLeft!==false) { + // Make a number ascending array + tempArray = []; + for (var i=1;i<=this.openFiles.length;i++) { + tempArray.push(i); + } + // Then swap our tab numbers + tempArray.splice(this.dragTabNo-1,1); + tempArray.splice(swapWith-1,0,this.dragTabNo); + // Now we have an order to sort against + this.sortTabs(tempArray); + } + this.setTabWidths(); + this.draggingTab = false; + this.thisLeft = false; + }, + + // Sort tabs into new order + sortTabs: function(newOrder) { + var a, b, savedPoints = [], savedContents = [], openFiles = [], openFileMDTs = [], openFileVersions = [], cMInstances = [], selectedTabWillBe; + + // Setup an array of our actual arrays and the blank ones + a = [this.savedPoints, this.savedContents, this.openFiles, this.openFileMDTs, this.openFileVersions, this.cMInstances]; + b = [savedPoints, savedContents, openFiles, openFileMDTs, openFileVersions, cMInstances]; + // Push the new order values into array b then set into array a + for (var i=0;i0) { + var currentArray, currentArrayFull, alphaArray, nextValue, nextPos; + + currentArray = []; + currentArrayFull = []; + alphaArray = []; + // Get filenames, full paths and set classname for sliding + for (var i=0;i0) { + nextValue = currentArray[0]; + nextValueFull = currentArrayFull[0]; + nextPos = 0; + for (var i=0;i startCG-5) { + cM.undo(); + undoCounts++; + } + // If we have content saved + if (ic.savedPoints[ic.selectedTab-1] == cM.changeGeneration()) { + // Start snake game + ic.startSnake(); + // If we don't, redo snake word + } else { + for (var i=1; i<=undoCounts; i++) { + cM.redo(); + } + } + },0,this); + } + + // Detect arrow keys if playing snake + if (this.snakePlaying) { + if (key==37) {this.snakeDir = 'left'} + if (key==39) {this.snakeDir = 'right'} + if (key==38) {this.snakeDir = 'up'} + if (key==40) {this.snakeDir = 'down'} + return false; + } + + // Mac command key handling (224 = Moz, 91/93 = Webkit Left/Right Apple) + if (key==224 || key==91 || key==93) { + this.cmdKey = true; + } + + // F1 (zoom code out non declaration lines) + if (key === 112) { + if (this.codeZoomedOut) { + return; + } + this.codeZoomedOut = true; + + cM = this.getcMInstance(); + // For every line in the current editor, add code-zoomed-out class if not a function/class declaration line + for (var i=0; i -1 ? true : false);return false;} + else {return key;} + // Alt+Enter (Insert Line After) + } else if (key==13) { + this.insertLineAfter(); + return false; + } else {return key;} + + } else { + + // Shift+Enter (Insert Line Before) + if(key==13 && evt.shiftKey) { + this.insertLineBefore(); + return false; + + // CTRL/Cmd+F (Find next) + // and + // CTRL/Cmd+G (Find previous) + } else if((key==70||key==71) && (evt.ctrlKey||this.cmdKey)) { + var find = get('find'); + var selections = this.getThisCM().getSelections(); + if (selections.length > 0){ + if (selections[0].length > 0){ + find.value = selections[0]; + } + } + find.select(); + // this is trick for Chrome - after you have used Ctrl-F once, when + // you try using Ctrl-F another time, somewhy Chrome still thinks, + // that find has focus and refuses to give it focus second time. + get('goToLineNo').focus(); + find.focus(); + // Trigger the find/replace operation (70 = F (next), 71 = G (prev)) + this.findReplace(find.value, true, true, 70 !== key); + return false; + + // CTRL/Cmd+L (Go to line) + } else if(key==76 && (evt.ctrlKey||this.cmdKey)) { + var goToLineInput = get('goToLineNo'); + goToLineInput.select(); + // this is trick for Chrome - after you have used Ctrl-F once, when + // you try using Ctrl-F another time, somewhy Chrome still thinks, + // that find has focus and refuses to give it focus second time. + get('find').focus(); + goToLineInput.focus(); + return false; + + // CTRL/Cmd+I (Get info) + } else if(key==73 && (evt.ctrlKey||this.cmdKey) && area == "content") { + this.searchForSelected(); + return false; + + // CTRL/Cmd+backspace arrow (Go to previous tab selected) + } else if(key==8 && (evt.ctrlKey||this.cmdKey)) { + if (this.prevTab !== 0) { + this.switchTab(this.prevTab); + } + return false; + + // CTRL/Cmd+right arrow (Tab to right) + } else if(key==39 && (evt.ctrlKey||this.cmdKey) && area!="content") { + this.nextTab(); + return false; + + // CTRL/Cmd+left arrow (Tab to left) + } else if(key==37 && (evt.ctrlKey||this.cmdKey) && area!="content") { + this.previousTab(); + return false; + + // CTRL/Cmd+up arrow (Move line up) + } else if(key==38 && (evt.ctrlKey||this.cmdKey) && area=="content") { + this.moveLines('up'); + return false; + + // CTRL/Cmd+down arrow (Move line down) + } else if(key==40 && (evt.ctrlKey||this.cmdKey) && area=="content") { + this.moveLines('down'); + return false; + + // CTRL/Cmd+numeric plus (New tab) + } else if((key==107 || key==187) && (evt.ctrlKey||this.cmdKey)) { + area=="content" + ? this.duplicateLines() + : this.newTab(false); + return false; + + // CTRL/Cmd+numeric minus (Close tab) + } else if((key==109 || key==189) && (evt.ctrlKey||this.cmdKey)) { + area=="content" + ? this.removeLines() + : this.closeTab(this.selectedTab); + return false; + + // CTRL/Cmd+S (Save), CTRL/Cmd+Shift+S (Save As) + } else if(key==83 && (evt.ctrlKey||this.cmdKey)) { + if(evt.shiftKey) { + this.saveFile(true, false); + } else { + this.saveFile(false, false); + } + return false; + + // CTRL/Cmd+Enter (Open Webpage) + } else if(key==13 && (evt.ctrlKey||this.cmdKey) && this.openFiles[this.selectedTab-1] != "/[NEW]") { + this.resetKeys(evt); + window.open(this.openFiles[this.selectedTab-1]); + return false; + + // Enter (Expand dir/open file) + } else if(key==13 && area=="files") { + if(!evt.ctrlKey && !this.cmdKey) { + if (this.selectedFiles.length == 0) { + this.overFileFolder('folder', '|'); + this.selectFileFolder('init'); + } + this.fmAction(evt,'enter'); + } + return false; + + // Up/down/left/right arrows (Traverse files) + } else if((key==38||key==40||key==37||key==39) && area=="files") { + if(!evt.ctrlKey && !this.cmdKey) { + if (this.selectedFiles.length == 0) { + this.overFileFolder('folder', '|'); + this.selectFileFolder('init'); + } + this.fmAction(evt, + key==38 ? 'up' : + key==40 ? 'down' : + key==37 ? 'left' : + 'right'); + } + return false; + + // CTRL/Cmd+O (Open Prompt) + } else if(key==79 && (evt.ctrlKey||this.cmdKey)) { + this.openPrompt(); + return false; + + // CTRL/Cmd+Space (Add snippet) + } else if(key==32 && (evt.ctrlKey||this.cmdKey) && area=="content") { + this.addSnippet(); + return false; + + // CTRL/Cmd+J (Jump to definition/back again) + } else if(key==74 && (evt.ctrlKey||this.cmdKey) && area=="content") { + this.jumpToDefinition(); + return false; + + // CTRL + Tab (lock/unlock file manager) + } else if(key==223 && (evt.ctrlKey||this.cmdKey)) { + this.lockUnlockNav(); + this.changeFilesW(this.lockedNav ? 'expand' : 'contract'); + return false; + + // CTRL + . (Fold/unfold current line) + } else if(key==190 && (evt.ctrlKey||this.cmdKey)) { + thisCM = this.getThisCM(); + thisCM.foldCode(thisCM.getCursor()); + return false; + + // ESC in content area (Comment/Uncomment line) + } else if(key==27 && area == "content") { + thisCM = this.getThisCM(); + + if (thisCM.getSelections().length > 1) { + thisCM.execCommand("singleSelection"); + } else { + this.lineCommentToggle(); + } + return false; + + // ESC not in content area (Cancel all actions) + } else if(key==27 && area != "content") { + this.cancelAllActions(); + return false; + + // Any other key + } else { + return key; + } + } + }, + + // Reset the state of keys back to the normal state + resetKeys: function(evt) { + var key, cM; + + key = evt.keyCode ? evt.keyCode : evt.which ? evt.which : evt.charCode; + + if (key == 112 && this.codeZoomedOut) { + cM = this.getcMInstance(); + // For every line in the current editor, remove code-zoomed-out class if not a function/class declaration line + for (var i=0; i-1) { + // Get text on the line from our target to the end + remainder = thisCM.getLine(lineNo); + strPos = remainder.indexOf(tgtString); + remainder = remainder.slice(remainder.indexOf(tgtString)+tgtString.length+1); + // Replace the function name if any + replaceString = replaceString.replace(/VAR/g,remainder); + // Get replaced string from start to our strPos + replacedLine = thisCM.getLine(lineNo).slice(0,strPos); + // Trim whitespace from start + whiteSpace = thisCM.getLine(lineNo).length - thisCM.getLine(lineNo).replace(/^\s\s*/, '').length; + whiteSpace = thisCM.getLine(lineNo).slice(0,whiteSpace); + // Replace indent with whatever whitespace we have + replaceString = replaceString.replace(/INDENT/g,whiteSpace); + replacedLine += replaceString; + // Get cursor position + curPos = replacedLine.indexOf("CURSOR"); + sPos = 0; + lineNoCount = lineNo; + for (i=0;i -1 ? true : false); + } + }, + + viewTutorial: function(step, delay) { + var winW, winH; + + winW = window.innerWidth; + winH = window.innerHeight; + + var steps = { + 0: { + "width": 250, + "height": 55, + "top": -55, + "left": 0, + "title": "

Code editor awesomeness ...in your browser", + "message": "View the quick start tutorial? (Well worthwhile!) or skip it", + "button": "view tutorial" + }, + 1: { + "width": 250, + "height": 55, + "top": 0, + "left": 0, + "title": "Options and settings", + "message": "Here you can perform file and editor content actions, plus also customise ICEcoders' settings, switch to other file manager sources, view help, search for info and more.", + "button": "next >" + }, + 2: { + "width": 250, + "height": winH - 85, + "top": 50, + "left": 0, + "title": "File manager", + "message": "This is the file manager. Click a dir to open/close, double click a file to open it and right click on dirs/files to get relevant options. You can drag and drop too.", + "button": "next >" + }, + 3: { + "width": 45, + "height": 85, + "top": 50, + "left": 205, + "title": "File manager options and plugins", + "message": "Here you can unlock/lock the file manager to collapse/expand it, refresh the file manager plus view and install plugins. (Also, move your mouse to left edge of file manager for quick access to the plugins).", + "button": "next >" + }, + 4: { + "width": 250, + "height": 35, + "top": winH - 35, + "left": 0, + "title": "Extra tools", + "message": "Get access to the terminal, output, database and Git interfaces here, displayed as an overlay to get the largest display, click option again to slide overlay out.", + "button": "next >" + }, + 5: { + "width": winW - 250, + "height": 42, + "top": 0, + "left": 250, + "title": "Editor tabs", + "message": "Your opened tabs will appear here. Icons displayed are to close all, alphabetize tabs and add new tab. You can drag your open tabs left/right to sort them too.", + "button": "next >" + }, + 6: { + "width": 440, + "height": 28, + "top": 42, + "left": 250, + "title": "Find and replace builder", + "message": "This is the find and replace builder. Here you can use the text fields and dropdown menus to build up sentences of what you'd like to do, such as find and replace in editor content, files and filenames.", + "button": "next >" + }, + 7: { + "width": 200, + "height": 28, + "top": 42, + "left": winW - 200, + "title": "Editor options and bug reporting", + "message": "Here you can specify the line to jump to (editor jumps as you type, hit Enter to focus on editor), plus options to view the current tab as a webpage in new browser window and view bugs as you code (once targeted at error logs).", + "button": "next >" + }, + 8: { + "width": 520, + "height": 380, + "top": 70, + "left": 250, + "title": "System info", + "message": "This is general info about your server, paths, browser and more. Worth noting to ensure settings seem correct.", + "button": "next >" + }, + 9: { + "width": 120, + "height": 30, + "top": winH - 30, + "left": 250, + "title": "Editor version control", + "message": "When you have a tab open, on every save, it makes a copy - click the number of backups it indicates, to view differences and options to restore old versions.", + "button": "next >" + }, + 10: { + "width": 100, + "height": 30, + "top": winH - 30, + "left": (((winW + 250) / 2) - 50), + "title": "Editor pane mode", + "message": "Switch between single pane and diff pane modes. The diff pane automatically sets a copy on each save, so you can undo/redo to cycle through those. The gutters indicate additions, changes and deletions on each line.", + "button": "next >" + }, + 11: { + "width": 100, + "height": 30, + "top": winH, + "left": (((winW + 250) / 2) - 50), + "title": "Let's get started!", + "message": "This really only scratches the surface of what ICEcoder can do. Have a look around, test out the features and don't forget to checkout the website for more advanced tips & tricks. You can get to this tutorial again under Help > Tutorial.", + "button": "start!" + }, + }; + + // Make both the info black mask and message display + get("infoBlackMask").style.display = "block"; + get("infoMessageContainer").style.display = "block"; + + // No step specified means starting from beginning + if (false === step) { + // Set margin-top to be above screen + get("infoMessageContainer").style.marginTop = -300 + "px"; + // After 100ms show border and message text (still above screen) + setTimeout(function() { + get("infoBlackMask").style.border = "solid 10000px rgba(0,0,0,0.8)"; + get("infoMessageContainer").style.opacity = "1"; + }, 100); + // After requested delay, slide in message but account for logo + setTimeout(function() { + get("infoMessageContainer").style.marginTop = (winH / 2) + 70 + "px"; + }, delay); + // Set message text and return to go no further + ICEcoder.viewTutorial(0); + return; + } + + if (9 === step) { + if ("" === get("versionsDisplay").innerText) { + get("versionsDisplay").innerText = "12345 backups"; + } + steps[9].width = get("versionsDisplay").innerText.length * 9; + } + if (10 === step) { + if ("12345 backups" === get("versionsDisplay").innerText) { + get("versionsDisplay").innerText = ""; + } + } + + // If we're going beyond the last step, we're finishing + if (11 < step) { + // Reset styles ready for next time + get("infoBlackMask").style.border = "solid 10000px rgba(0,0,0,0)"; + get("infoMessageContainer").style.opacity = "0"; + setTimeout(function() { + get("infoBlackMask").style.display = "none"; + get("infoMessageContainer").style.display = "none"; + }, 500); + // Mark tutorial as done in users settings and return + xhr = this.xhrObj(); + xhr.open("POST",iceLoc+"/lib/settings.php?action=turnOffTutorialOnLogin&csrf="+this.csrf,true); + xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + xhr.send(); + return; + } + + // Steps 1 onwards have no logo, normal margin-top needed + if (1 <= step) { + get("infoMessageContainer").style.marginTop = (winH / 2) + "px"; + } + + // We have something do display, so set info black mask to highlight item + get("infoBlackMask").style.height = steps[step].height; + get("infoBlackMask").style.width = steps[step].width; + get("infoBlackMask").style.top = -10000 + steps[step].top + "px"; + get("infoBlackMask").style.left = -10000 + steps[step].left + "px"; + // Set info title, message and button + get("infoMessage").innerHTML = '
' + steps[step].title + '
' + steps[step].message + '

' + steps[step].button + '
'; + }, + + // Snart snake + startSnake: function() { + this.snakePlaying = true; + this.showHide('show',get('blackMask')); + get('mediaContainer').innerHTML = 'Let\'s play
snake


Use arrow keys to eat your code

(it returns afterwards of course) :-)
'; + setTimeout(function(ic) { + ic.showHide('hide',get('blackMask')); + get('mediaContainer').innerHTML = ''; + ic.playSnake(); + },2000,this); + }, + + // Play snake + playSnake: function() { + var cM; + + cM = this.getcMInstance(); + cM.setOption('readOnly', 'nocursor'); + cM.focus(); + + // Get state of editor at present + this.snakePreHistory = cM.getHistory(); + this.snakePreContent = cM.getValue(); + this.snakePreCursor = cM.getCursor(); + + // Pick a random point for snake to come in and set head and 4 body parts off screen + var randPos = Math.floor(Math.random()*50); + this.snakePos = [ + [randPos,0], + [randPos,-1], + [randPos,-2], + [randPos,-3], + [randPos,-4] + ]; + + // Show game layer, set direction and do 1st frame of snake + this.content.contentWindow.document.getElementById('game').style.display = 'block'; + this.snakeDir = "down"; + this.doSnake(); + + // Every 0.1s, move snake + this.snakeInt = setInterval(function(ic) { + // Set new head X & Y pos according to direction + var newHead = []; + newHead[0] = ic.snakePos[0][0]+(ic.snakeDir == "right" ? 1 : ic.snakeDir == "left" ? -1 : 0); + newHead[1] = ic.snakePos[0][1]+(ic.snakeDir == "down" ? 1 : ic.snakeDir == "up" ? -1 : 0); + // Add new head and remove tail + ic.snakePos.unshift(newHead); + ic.snakePos.pop(); + // Do next frame of snake + ic.doSnake(); + },100,this); + }, + + doSnake: function() { + var cM, cW, cH, newInnerHTML, lineData, lineContent, spaceReplaceChars, collision, scrollInfo; + + // Get CodeMirror instance, plus char width and height + cM = this.getcMInstance(); + cW = cM.defaultCharWidth(); + cH = cM.defaultTextHeight(); + + // Clear content of game layer + this.content.contentWindow.document.getElementById('game').innerHTML = ""; + // Start a new set of contents + newInnerHTML = ""; + // For every part of snake, draw it's block in position + for (var i=0; i'; + } + // Set new content in game layer + this.content.contentWindow.document.getElementById('game').innerHTML = newInnerHTML; + + // Get line & ch value under snake head then line content + lineData = cM.coordsChar({top: ((this.snakePos[0][1]*cH)+4), left: ((this.snakePos[0][0]*cW)+60)}); + lineContent = cM.getLine(lineData.line); + + // If not the last char on the line + if (this.snakePos[0][0]-1 <= lineContent.length-2) { + spaceReplaceChars = ""; + // If char under snake head is a tab, replace string contains spaces of same width + if (lineContent.substr(lineData.ch,1) === "\t") { + for (var i=0; i= 5) { + this.snakePos.pop(); + } + } + // Detect if snake head has collided into itself + collision = false; + for (var i=1; i scrollInfo.clientWidth || ((this.snakePos[0][1]*cH)+4) > scrollInfo.clientHeight || + collision + ) { + // Clear interval and hide game layer + clearInterval(this.snakeInt); + this.content.contentWindow.document.getElementById('game').style.display = 'none'; + // Set content, saved point, saved contents and history back to what they were pre game + cM.setValue(this.snakePreContent); + this.savedPoints[this.selectedTab-1] = cM.changeGeneration(); + this.savedContents[this.selectedTab-1] = this.snakePreContent; + cM.setHistory(this.snakePreHistory); + // Redo changes indicator in title tag and tab highlight save indicator also to what they are now (pre game state) + this.indicateChanges(); + this.redoTabHighlight(this.selectedTab); + // Set editor to be editable again + cM.setOption('readOnly', false); + // Set cursor back to what it was pre game and focus on editor + cM.setCursor(this.snakePreCursor); + cM.focus(); + // State we are no longer playing snake + this.snakePlaying = false; + } + + } +}; diff --git a/assets/js/language-modes-partial.js b/assets/js/language-modes-partial.js new file mode 100644 index 0000000..4fc4d0b --- /dev/null +++ b/assets/js/language-modes-partial.js @@ -0,0 +1,31 @@ +// Provide a fileName and get fileExt and mode set based on supported languages + +fileExt = fileName.split("."); +fileExt = fileExt[fileExt.length - 1]; + +var mode = + fileExt == "js" ? "text/javascript" + : fileExt == "json" ? "text/javascript" + : fileExt == "coffee" ? "text/x-coffeescript" + : fileExt == "ts" ? "application/typescript" + : fileExt == "rb" ? "text/x-ruby" + : fileExt == "py" ? "text/x-python" + : fileExt == "mpy" ? "text/x-python" + : fileExt == "css" ? "text/css" + : fileExt == "less" ? "text/x-less" + : fileExt == "md" ? "text/x-markdown" + : fileExt == "xml" ? "application/xml" + : fileExt == "sql" ? "text/x-mysql" // also text/x-sql, text/x-mariadb, text/x-cassandra or text/x-plsql + : fileExt == "erl" ? "text/x-erlang" + : fileExt == "yaml" ? "text/x-yaml" + : fileExt == "java" ? "text/x-java" + : fileExt == "jl" ? "text/x-julia" + : fileExt == "c" ? "text/x-csrc" + : fileExt == "cpp" ? "text/x-c++src" + : fileExt == "ino" ? "text/x-c++src" + : fileExt == "cs" ? "text/x-csharp" + : fileExt == "go" ? "text/x-go" + : fileExt == "lua" ? "text/x-lua" + : fileExt == "pl" ? "text/x-perl" + : fileExt == "scss" ? "text/x-sass" + : "application/x-httpd-php"; diff --git a/lib/mmd.js b/assets/js/mmd.js similarity index 100% rename from lib/mmd.js rename to assets/js/mmd.js diff --git a/classes/Backup.php b/classes/Backup.php new file mode 100644 index 0000000..59c2102 --- /dev/null +++ b/classes/Backup.php @@ -0,0 +1,100 @@ +fileClass = new File(); + } + + public function makeBackup($fileLoc, $fileName, $contents) { + global $ftpSite, $t, $ICEcoder; + + $backupDirFormat = "Y-m-d"; + + // Establish the base, host and date dir parts... + $backupDirBase = str_replace("\\", "/", dirname(__FILE__)) . "/../data/backups/"; + $backupDirHost = isset($ftpSite) ? parse_url($ftpSite, PHP_URL_HOST) : "localhost"; + $backupDirDate = date($backupDirFormat); + + // Establish an array of dirs from base to our file location + $subDirsArray = explode("/", ltrim($fileLoc, "/")); + array_unshift($subDirsArray, $backupDirHost, $backupDirDate); + // Make any dirs that don't exist if full path isn't there + if (!is_dir($backupDirBase . implode("/", $subDirsArray))) { + $pathIncr = ""; + for ($i = 0; $i < count($subDirsArray); $i++) { + $pathIncr .= $subDirsArray[$i] . "/"; + // If this subdir isn't there, make it + if (!is_dir($backupDirBase . $pathIncr)) { + mkdir($backupDirBase . $pathIncr); + } + } + } + // We should have our dir path now so set that + $backupDir = $backupDirBase . implode("/", $subDirsArray); + // Work out an available filename (we postfix a number in parens) + for ($i = 1; $i < 1000000000; $i++) { + if (!file_exists($backupDir . '/' . $fileName . " (" . $i . ")")) { + $backupFileName = $fileName . " (" . $i . ")"; + $backupFileNum = $i; + $i = 1000000000; + } + } + + // Now save within that backup dir and clear the statcache + $fh = fopen($backupDir . "/" . $backupFileName, "w") or die($t['Sorry, cannot save...']); + fwrite($fh, $contents); + fclose($fh); + clearstatcache(); + + // Log the version count in an index file, which contains saved version counts + $backupIndex = $backupDirBase . $backupDirHost . "/" . $backupDirDate . "/.versions-index"; + // Have a version index already? Update contents + if (file_exists($backupIndex)) { + $versionsInfo = ""; + $versionsInfoOrig = getData($backupIndex); + $versionsInfoOrig = explode("\n", $versionsInfoOrig); + $replacedLine = false; + // For each line, either re-set number or simply include the line + for ($i = 0; $i < count($versionsInfoOrig); $i++) { + if (0 === strpos($versionsInfoOrig[$i], $fileLoc . "/" . $fileName . " = ")) { + $versionsInfo .= $fileLoc . "/" . $fileName . " = " . $backupFileNum . PHP_EOL; + $replacedLine = true; + } else { + $versionsInfo .= $versionsInfoOrig[$i] . PHP_EOL; + } + } + // Didn't find our line in the file? Add it to the end + if (!$replacedLine) { + $versionsInfo .= $fileLoc . "/" . $fileName . " = " . $backupFileNum . PHP_EOL; + } + // No version file yet, set the first line + } else { + $versionsInfo = $fileLoc . "/" . $fileName . " = " . $backupFileNum . PHP_EOL; + } + $versionsInfo = rtrim($versionsInfo, PHP_EOL); + $fh = fopen($backupIndex, 'w') or die($t['Sorry, cannot save...']); + fwrite($fh, $versionsInfo); + fclose($fh); + clearstatcache(); + + // Finally, clear any old backup dirs than user set X days (inclusive) + $backupDirsList = scandir($backupDirBase . $backupDirHost); + $backupDirsKeep = array(); + for ($i = 0; $i <= $ICEcoder["backupsDays"]; $i++) { + $backupDirsKeep[] = date($backupDirFormat, strtotime('-' . $i . ' day', strtotime($backupDirDate))); + } + for ($i = 0; $i < count($backupDirsList); $i++) { + if ("." !== $backupDirsList[$i] && ".." !== $backupDirsList[$i] && !in_array($backupDirsList[$i], $backupDirsKeep)) { + $this->fileClass->rrmdir($backupDirBase . $backupDirHost . "/" . $backupDirsList[$i]); + } + } + } +} diff --git a/classes/FTP.php b/classes/FTP.php new file mode 100644 index 0000000..cef29c5 --- /dev/null +++ b/classes/FTP.php @@ -0,0 +1,188 @@ +systemClass = new System(); + } + + public function writeFile() { + global $fileLoc, $fileName, $ftpConn, $ftpRoot, $ftpHost, $ftpMode, $ICEcoder, $doNext, $filemtime, $tabNum; + + $ftpFilepath = ltrim($fileLoc . "/" . $fileName, "/"); + if (isset($_POST['changes'])) { + // Get existing file contents as lines + $loadedFile = toUTF8noBOM($this->ftpGetContents($ftpConn, $ftpRoot . $fileLoc . "/" . $fileName, $ftpMode), false); + $fileLines = explode("\n", str_replace("\r", "", $loadedFile)); + // Need to add a new line at the end of each because explode will lose them, + // want want to end up with same array that 'file($file)' produces for a local file + // - it keeps the line endings at the end of each array item + for ($i = 0; $i < count($fileLines); $i++) { + if ($i < count($fileLines) - 1) { + $fileLines[$i] .= $ICEcoder["lineEnding"]; + } + } + // Stitch changes onto it + $contents = $this->systemClass->stitchChanges($fileLines, $_POST['changes']); + + // get old file contents and count stats on usage \n and \r there + // in this case we can keep line endings, which file had before, without + // making code version control systems going crazy about line endings change in whole file. + $unixNewLines = preg_match_all('/[^\r][\n]/u', $loadedFile); + $windowsNewLines = preg_match_all('/[\r][\n]/u', $loadedFile); + } else { + $contents = $_POST['contents']; + } + + // replace \r\n (Windows), \r (old Mac) and \n (Linux) line endings with whatever we chose to be lineEnding + $contents = str_replace("\r\n", $ICEcoder["lineEnding"], $contents); + $contents = str_replace("\r", $ICEcoder["lineEnding"], $contents); + $contents = str_replace("\n", $ICEcoder["lineEnding"], $contents); + if (isset($_POST['changes']) && ($unixNewLines > 0) || ($windowsNewLines > 0)) { + if ($unixNewLines > $windowsNewLines){ + $contents = str_replace($ICEcoder["lineEnding"], "\n", $contents); + } elseif ($windowsNewLines > $unixNewLines){ + $contents = str_replace($ICEcoder["lineEnding"], "\r\n", $contents); + } + } + // Write our file contents + if (!$this->ftpWriteFile($ftpConn, $ftpFilepath, $contents, $ftpMode)) { + $doNext .= 'ICEcoder.message("Sorry, could not write ' . $ftpFilepath . ' at ' . $ftpHost . '");'; + } else { + $doNext .= 'ICEcoder.openFileMDTs[' . ($tabNum - 1) .']="' . $filemtime . '";'; + $doNext .= '(function() {var x = ICEcoder.openFileVersions; var y = ' . ($tabNum - 1) .'; x[y] = "undefined" != typeof x[y] ? x[y] + 1 : 1})(); ICEcoder.updateVersionsDisplay();'; + } + } + + // Start a FTP connection + function ftpStart() + { + global $ftpConn, $ftpLogin, $ftpHost, $ftpUser, $ftpPass, $ftpPasv; + + // Establish connection, login and maybe use pasv + $ftpConn = ftp_connect($ftpHost); + $ftpLogin = ftp_login($ftpConn, $ftpUser, $ftpPass); + if ($ftpPasv) { + ftp_pasv($ftpConn, true); + } + } + + // End a FTP connection + function ftpEnd() + { + global $ftpConn; + + ftp_close($ftpConn); + } + + // Get dir/file lists (simple and detailed) from FTP detailed rawlist response + function ftpGetList($ftpConn, $directory = '.') + { + $simpleList = $detailedList = array(); + // If we have a FTP rawlist to work with + if (is_array($rows = @ftp_rawlist($ftpConn, $directory))) { + foreach ($rows as $row) { + // Split row up by spaces and set keys on $item array + $chunks = preg_split("/\s+/", $row); + list($item['rights'], $item['number'], $item['user'], $item['group'], $item['size'], $item['month'], $item['day'], $item['time']) = $chunks; + // Also set if this is a dir or file + $item['type'] = $chunks[0][0] === 'd' ? 'directory' : 'file'; + // Splice the array and finally work out $simpleList and $detailedList + array_splice($chunks, 0, 8); + $detailedList[implode(" ", $chunks)] = $item; + $simpleList[] = implode(" ", $chunks); + } + // Return simple array list and detailed items list also + return array('simpleList' => $simpleList, 'detailedList' => $detailedList); + } + return false; + } + + // Get detailed info on a file from returned info from ftpGetList + function ftpGetFileInfo($ftpConn, $directory = '.', $fileName) + { + // Get both sets of arrays back and get our detailed list + $ftpListArrays = $this->ftpGetList($ftpConn, $directory); + $detailedList = $ftpListArrays['detailedList']; + + // Now get the file info for our file + $fileInfo = $detailedList[$fileName]; + + // Return the info + return $fileInfo; + } + + // Get contents over FTP + function ftpGetContents($ftpConn, $filepath, $ftpMode) + { + // Create temp handler, this type needed for extended char set + $tempHandle = fopen('php://temp', 'r+'); + + // Get file from FTP assuming that it exists + ftp_fget($ftpConn, $tempHandle, $filepath, $ftpMode, 0); + + // Return our content + return stream_get_contents($tempHandle, -1, 0); + } + + // Write file contents over FTP + function ftpWriteFile($ftpConn, $filepath, $contents, $ftpMode) + { + // Create temp handler, this type needed for extended char set + $tempHandle = fopen('php://temp', 'r+'); + + // Write contents to handle and rewind head + fwrite($tempHandle, $contents); + rewind($tempHandle); + + // Write our content and return true/false + return ftp_fput($ftpConn, $filepath, $tempHandle, $ftpMode, 0); + } + + // Make a new dir over FTP + function ftpMkDir($ftpConn, $perms, $dir) + { + // Create the new dir + if (!ftp_mkdir($ftpConn, $dir)) { + return false; + } else { + // Also then set perms (we must be able to do that if we created dir, so can always return true) + $this->ftpPerms($ftpConn, $perms, $dir); + return true; + } + } + + // Rename a dir/dile over FTP + function ftpRename($ftpConn, $oldPath, $newPath) + { + // Return success status of rename + return ftp_rename($ftpConn, $oldPath, $newPath); + } + + // Change dir/file perms over FTP + function ftpPerms($ftpConn, $perms, $filePath) + { + // Return success status of perms change + return ftp_chmod($ftpConn, $perms, $filePath); + } + + // Delete dir/file over FTP + function ftpDelete($ftpConn, $type, $path) + { + if ($type == "file") { + // Delete our file and return true/false + return ftp_delete($ftpConn, $path); + } else { + // Delete our dir and return true/false + return ftp_rmdir($ftpConn, $path); + } + } + +} diff --git a/classes/File.php b/classes/File.php new file mode 100644 index 0000000..e9114af --- /dev/null +++ b/classes/File.php @@ -0,0 +1,762 @@ +ftpClass = new FTP(); + $this->systemClass = new System(); + } + + public function check() { + global $file, $fileOrig, $docRoot, $iceRoot, $fileLoc, $fileName, $error, $errorStr, $errorMsg; + // Replace pipes with slashes, then establish the actual name as we may have HTML entities in filename + $file = html_entity_decode(str_replace("|", "/", $file)); + + // Put the original $file var aside for use + $fileOrig = $file; + + // Trim any +'s or spaces from the end of file + $file = rtrim(rtrim($file, '+'), ' '); + + // Also remove [NEW] from $file, we can consider $_GET['action'] or $fileOrig to pick that up + $file = preg_replace('/\[NEW\]$/', '', $file); + + // Make each path in $file a full path (; separated list) + $allFiles = explode(";", $file); + for ($i = 0; $i < count($allFiles); $i++) { + if (false === strpos($allFiles[$i],$docRoot) && "getRemoteFile" !== $_GET['action']) { + $allFiles[$i] = str_replace("|", "/", $docRoot . $iceRoot . $allFiles[$i]); + } + }; + $file = implode(";", $allFiles); + + // Establish the $fileLoc and $fileName (used in single file cases, eg opening. Multiple file cases, eg deleting, is worked out in that loop) + $fileLoc = substr(str_replace($docRoot, "", $file), 0, strrpos(str_replace($docRoot, "", $file), "/")); + $fileName = basename($file); + + // Check through all files to make sure they're valid/safe + $allFiles = explode(";", $file); + for ($i = 0; $i < count($allFiles); $i++) { + + // Uncomment to alert and console.log the action and file, useful for debugging + // echo ";alert('" . xssClean($_GET['action'], "html") . " : " . $allFiles[$i] . "');console.log('" . xssClean($_GET['action'], "html") . " : " . $allFiles[$i] . "');"; + + $bannedFileFound = false; + for ($j = 0; $j < count($_SESSION['bannedFiles']); $j++) { + $thisFile = str_replace("*", "", $_SESSION['bannedFiles'][$j]); + if ("" != $thisFile && false !== strpos($allFiles[$i], $thisFile)) { + $bannedFileFound = true; + } + } + + // Die if the file requested isn't something we expect + if ( + // On the banned file/dir list + ($bannedFileFound) || + // A local folder that isn't the doc root or starts with the doc root + ("getRemoteFile" !== $_GET['action'] && !isset($ftpSite) && + rtrim($allFiles[$i], "/") !== rtrim($docRoot, "/") && + 0 !== strpos(realpath(rtrim(dirname($allFiles[$i]), "/")), realpath(rtrim($docRoot, "/"))) + ) || + // Or a remote URL that doesn't start http + ("getRemoteFile" === $_GET['action'] && 0 !== strpos($allFiles[$i], "http")) + ) { + $error = true; + $errorStr = "true"; + $errorMsg = "Sorry! - problem with file requested"; + }; + } + } + + public function updateUI() { + global $fileLoc, $fileName; + + $doNext = ""; + // Reload file manager, rename tab & remove old file highlighting if it was a new file + if (isset($_POST['newFileName']) && "" != $_POST['newFileName']) { + $doNext .= 'ICEcoder.selectedFiles=[];'; + $doNext .= 'ICEcoder.updateFileManagerList(\'add\', \'' . $fileLoc . '\', \'' . $fileName . '\', false, false, false, \'file\');'; + $doNext .= 'ICEcoder.renameTab(ICEcoder.selectedTab, \'' . $fileLoc . "/" . $fileName . '\');'; + } + + return $doNext; + } + + public function updateFileManager($action, $fileLoc, $fileName, $perms, $oldFile, $uploaded, $fileOrFolder) { + global $doNext; + $doNext .= "ICEcoder.updateFileManagerList('" . + $action . "', '" . + $fileLoc . "', '" . + $fileName . "', '" . + $perms . "', '" . + $oldFile . "', '" . + $uploaded . "', '" . + $fileOrFolder . "');"; + + return $doNext; + } + + public function load() { + global $file, $fileLoc, $fileName, $t, $ftpConn, $ftpHost, $ftpLogin, $ftpRoot, $ftpUser, $ftpMode; + echo 'action="load";'; + $lineNumber = max(isset($_REQUEST['lineNumber']) ? intval($_REQUEST['lineNumber']) : 1, 1); + // Check this file isn't on the banned list at all + $canOpen = true; + for ($i = 0; $i < count($_SESSION['bannedFiles']); $i++) { + if ("" !== str_replace("*", "", $_SESSION['bannedFiles'][$i]) && false !== strpos($file, str_replace("*", "", $_SESSION['bannedFiles'][$i]))) { + $canOpen = false; + } + } + + if (false === $canOpen) { + echo 'fileType="nothing"; parent.parent.ICEcoder.message(\'' . $t['Sorry, could not...'] . ' ' . $fileLoc . "/" . $fileName . '\');'; + } elseif (isset($ftpSite) || file_exists($file)) { + $finfo = "text"; + // Determine what to do based on mime type + if (!isset($ftpSite) && function_exists('finfo_open')) { + $finfoMIME = finfo_open(FILEINFO_MIME); + $finfo = finfo_file($finfoMIME, $file); + finfo_close($finfoMIME); + } else { + $fileExt = explode(" ", pathinfo($file, PATHINFO_EXTENSION)); + $fileExt = $fileExt[0]; + if (false !== array_search($fileExt, ["gif", "jpg", "jpeg", "png"])) { + $finfo = "image"; + }; + if (false !== array_search($fileExt, ["doc", "docx", "ppt", "rtf", "pdf", "zip", "tar", "gz", "swf", "asx", "asf", "midi", "mp3", "wav", "aiff", "mov", "qt", "wmv", "mp4", "odt", "odg", "odp"])) { + $finfo = "other"; + }; + } + if (0 === strpos($finfo, "text") || 0 === strpos($finfo, "application/xml") || false !== strpos($finfo, "empty")) { + echo 'fileType="text";'; + echo 'parent.parent.ICEcoder.shortURL = parent.parent.ICEcoder.thisFileFolderLink = "' . $fileLoc . "/" . $fileName . '";'; + + // Get file over FTP? + if (isset($ftpSite)) { + $this->ftpClass->ftpStart(); + // Show user warning if no good connection + if (!$ftpConn || !$ftpLogin) { + die('parent.parent.ICEcoder.message("Sorry, no FTP connection to ' . $ftpHost . ' for user ' . $ftpUser . '");parent.parent.ICEcoder.serverMessage();parent.parent.ICEcoder.serverQueue("del",0);'); + } + // Get our file contents and close the FTP connection + $loadedFile = toUTF8noBOM($this->ftpClass->ftpGetContents($ftpConn, $ftpRoot . $fileLoc . "/" . $fileName, $ftpMode), false); + $this->ftpClass->ftpEnd(); + // Get local file + } else { + $loadedFile = toUTF8noBOM(getData($file), true); + } + $encoding = ini_get("default_charset"); + if ("" == $encoding) { + $encoding = "UTF-8"; + } + // Get content and set HTML entities on it according to encoding + $loadedFile = htmlentities($loadedFile, ENT_COMPAT, $encoding); + // Remove \r chars and replace \n with carriage return HTML entity char + $loadedFile = preg_replace('/\\r/', '', $loadedFile); + $loadedFile = preg_replace('/\\n/', ' ', $loadedFile); + echo ' + '; -};?> - -/addon/fold/foldgutter.css?microtime="> -/addon/scroll/simplescrollbars.css?microtime="> -'; -};?> -'; -};?> -'; -};?> -'; +if (true === file_exists(dirname(__FILE__)."/plugins/jshint/jshint-2.5.6.min.js")) { + echo ''; +}; + +if (true === file_exists(dirname(__FILE__)."/plugins/emmet/emmet.min.js")) { + echo ''; +}; + +if (true === file_exists(dirname(__FILE__)."/plugins/pesticide/pesticide.js")) { + echo ''; +}; + +if (true === file_exists(dirname(__FILE__)."/plugins/stats.js/stats.min.js")) { + echo ''; +}; + +if (true === file_exists(dirname(__FILE__)."/plugins/responsive-helper/responsive-helper.js")) { + echo ''; };?> "> + + + - - + + - + @@ -101,7 +101,16 @@ h2 {color: rgba(0,198,255,0.7)}


-

+ " . + "(" . $_SERVER['SERVER_SOFTWARE'] . ")";?>





@@ -114,63 +123,66 @@ h2 {color: rgba(0,198,255,0.7)}



-

+


    '.$t['none'].'


'; + $last10FilesArray = explode(",", $ICEcoder["last10Files"]); + for ($i = 0; $i < count($last10FilesArray); $i++) { + if ($ICEcoder["last10Files"] == "") { + echo '
' . $t['none'] . '

'; } else { - $fileFolderName = str_replace("\\","/",$last10FilesArray[$i]); + $fileFolderName = str_replace("\\", "/", $last10FilesArray[$i]); // Get extension (prefix 'ext-' to prevent invalid classes from extensions that begin with numbers) - $ext = "ext-".pathinfo($docRoot.$iceRoot.$fileFolderName, PATHINFO_EXTENSION); - echo '
  • '; - echo ''; - echo str_replace($docRoot,"",str_replace("|","/",$last10FilesArray[$i])); + $ext = "ext-" . pathinfo($docRoot . $iceRoot . $fileFolderName, PATHINFO_EXTENSION); + echo '
  • '; + echo ''; + echo str_replace($docRoot, "", str_replace("|", "/", $last10FilesArray[$i])); echo '
  • '; - if ($i + +

    +
    +

    +
    -
    -

    - :
    - [?] -
    -
    - + - + diff --git a/farbtastic/farbtastic.css b/farbtastic/farbtastic.css deleted file mode 100644 index dbcd5a0..0000000 --- a/farbtastic/farbtastic.css +++ /dev/null @@ -1,49 +0,0 @@ -.picker { - display: inline-block; -} -.farbtastic { - position: relative; -} -.farbtastic * { - position: absolute; - cursor: crosshair; -} -.farbtastic, .farbtastic .wheel { - width: 195px; - height: 195px; -} -.farbtastic .color, .farbtastic .overlay { - top: 47px; - left: 47px; - width: 101px; - height: 101px; -} -.farbtastic .wheel { - background: url(wheel.png) no-repeat; - width: 195px; - height: 195px; -} -.farbtastic .overlay { - background: url(mask.png) no-repeat; -} -.farbtastic .marker { - width: 17px; - height: 17px; - margin: -8px 0 0 -8px; - overflow: hidden; - background: url(marker.png) no-repeat; -} -.colorValue { - border: 0; - width: 105px; - padding-left: 5px -} -.insertColorValue { - background: #888; - color: #fff; - height: 18px; - width: 60px; - border: 0; - margin-left: 5px; - cursor: pointer -} \ No newline at end of file diff --git a/farbtastic/farbtastic.js b/farbtastic/farbtastic.js deleted file mode 100644 index 9e1bb0d..0000000 --- a/farbtastic/farbtastic.js +++ /dev/null @@ -1,253 +0,0 @@ -/*! - * Farbtastic: NON jQuery color picker plug-in v1.4, based on v1.3u - * - * Licensed under the GPL license: - * http://www.gnu.org/licenses/gpl.html - */ - -farbtasticFunc = function (options) { - farbtastic(this, options); - return this; -}; - -farbtastic = function (container, callback) { - var container = document.getElementById(container); - var callback = document.getElementById(callback); - return container.farbtastic || (container.farbtastic = new _farbtastic(container, callback)); -}; - -_farbtastic = function (container, callback) { - // Store farbtastic object - var fb = this; - - // Insert markup - container.innerHTML = '
    '; - fb.wheel = document.getElementsByClassName('wheel')[0]; - // Dimensions - fb.radius = 84; - fb.square = 100; - fb.width = 194; - - /** - * Link to the given element(s) or callback. - */ - fb.linkTo = function (callback) { - // Unbind previous nodes - if (typeof fb.callback == 'object') { - console.log("Doesn't do anything?"); - fb.callback.removeEventListener('keyup', fb.updateValue); - } - - // Reset color - fb.color = null; - - // Bind callback or elements - if (typeof callback == 'function') { - fb.callback = callback; - } - else if (typeof callback == 'object' || typeof callback == 'string') { - fb.callback = callback; - fb.callback.addEventListener('keyup',fb.updateValue); - if (fb.callback.value) { - fb.setColor(fb.callback.value); - } - } - return this; - }; - fb.updateValue = function (event) { - if (this.value && this.value != fb.color) { - fb.setColor(this.value); - } - }; - - /** - * Change color with HTML syntax #123456 - */ - fb.setColor = function (color) { - var unpack = fb.unpack(color); - if (fb.color != color && unpack) { - fb.color = color; - fb.rgb = unpack; - fb.hsl = fb.RGBToHSL(fb.rgb); - fb.updateDisplay(); - } - return this; - }; - - /** - * Change color with HSL triplet [0..1, 0..1, 0..1] - */ - fb.setHSL = function (hsl) { - fb.hsl = hsl; - fb.rgb = fb.HSLToRGB(hsl); - fb.color = fb.pack(fb.rgb); - fb.updateDisplay(); - return this; - }; - - ///////////////////////////////////////////////////// - - /** - * Retrieve the coordinates of the given event relative to the center - * of the widget. - */ - fb.widgetCoords = function (event) { - return { x: (event.pageX - fb.wheel.offsetParent.offsetLeft) - fb.width / 2, y: (event.pageY - fb.wheel.offsetParent.offsetTop) - fb.width / 2 }; - }; - - /** - * Mousedown handler - */ - fb.mousedown = function (event) { - // Capture mouse - if (!document.dragging) { - document.addEventListener('mousemove', fb.mousemove); - document.addEventListener('mouseup', fb.mouseup); - document.dragging = true; - } - - // Check which area is being dragged - var pos = fb.widgetCoords(event); - fb.circleDrag = Math.max(Math.abs(pos.x), Math.abs(pos.y)) * 2 > fb.square; - - // Process - fb.mousemove(event); - return false; - }; - - /** - * Mousemove handler - */ - fb.mousemove = function (event) { - // Get coordinates relative to color picker center - var pos = fb.widgetCoords(event); - - // Set new HSL parameters - if (fb.circleDrag) { - var hue = Math.atan2(pos.x, -pos.y) / 6.28; - if (hue < 0) hue += 1; - fb.setHSL([hue, fb.hsl[1], fb.hsl[2]]); - } - else { - var sat = Math.max(0, Math.min(1, -(pos.x / fb.square) + .5)); - var lum = Math.max(0, Math.min(1, -(pos.y / fb.square) + .5)); - fb.setHSL([fb.hsl[0], sat, lum]); - } - return false; - }; - - /** - * Mouseup handler - */ - fb.mouseup = function () { - // Uncapture mouse - document.removeEventListener('mousemove', fb.mousemove); - document.removeEventListener('mouseup', fb.mouseup); - document.dragging = false; - }; - - /** - * Update the markers and styles - */ - fb.updateDisplay = function () { - // Markers - var angle = fb.hsl[0] * 6.28; - document.getElementsByClassName('h-marker')[0].style.left = Math.round(Math.sin(angle) * fb.radius + fb.width / 2) + 'px'; - document.getElementsByClassName('h-marker')[0].style.top = Math.round(-Math.cos(angle) * fb.radius + fb.width / 2) + 'px'; - - document.getElementsByClassName('sl-marker')[0].style.left = Math.round(fb.square * (.5 - fb.hsl[1]) + fb.width / 2) + 'px'; - document.getElementsByClassName('sl-marker')[0].style.top = Math.round(fb.square * (.5 - fb.hsl[2]) + fb.width / 2) + 'px'; - - // Saturation/Luminance gradient - document.getElementsByClassName('color')[0].style.backgroundColor = fb.pack(fb.HSLToRGB([fb.hsl[0], 1, 0.5])); - - // Linked elements or callback - if (typeof fb.callback == 'object') { - // Set background/foreground color - document.getElementById(fb.callback.id).style.backgroundColor = document.getElementById(fb.callback.id + 'RGB').style.backgroundColor = fb.color; - document.getElementById(fb.callback.id).style.color = document.getElementById(fb.callback.id + 'RGB').style.color = fb.hsl[2] > 0.5 ? '#000' : '#fff'; - document.getElementById('colorRGB').value = document.getElementById(fb.callback.id).style.backgroundColor.replace(/\s/g,''); - - // Change linked value - if (callback.value && callback.value != fb.color) { - callback.value = fb.color; - } - } - else if (typeof fb.callback == 'function') { - fb.callback.call(fb, fb.color); - } - }; - - /* Various color utility functions */ - fb.pack = function (rgb) { - var r = Math.round(rgb[0] * 255); - var g = Math.round(rgb[1] * 255); - var b = Math.round(rgb[2] * 255); - return '#' + (r < 16 ? '0' : '') + r.toString(16) + - (g < 16 ? '0' : '') + g.toString(16) + - (b < 16 ? '0' : '') + b.toString(16); - }; - - fb.unpack = function (color) { - if (color.length == 7) { - return [parseInt('0x' + color.substring(1, 3)) / 255, - parseInt('0x' + color.substring(3, 5)) / 255, - parseInt('0x' + color.substring(5, 7)) / 255]; - } - else if (color.length == 4) { - return [parseInt('0x' + color.substring(1, 2)) / 15, - parseInt('0x' + color.substring(2, 3)) / 15, - parseInt('0x' + color.substring(3, 4)) / 15]; - } - }; - - fb.HSLToRGB = function (hsl) { - var m1, m2, r, g, b; - var h = hsl[0], s = hsl[1], l = hsl[2]; - m2 = (l <= 0.5) ? l * (s + 1) : l + s - l*s; - m1 = l * 2 - m2; - return [this.hueToRGB(m1, m2, h+0.33333), - this.hueToRGB(m1, m2, h), - this.hueToRGB(m1, m2, h-0.33333)]; - }; - - fb.hueToRGB = function (m1, m2, h) { - h = (h < 0) ? h + 1 : ((h > 1) ? h - 1 : h); - if (h * 6 < 1) return m1 + (m2 - m1) * h * 6; - if (h * 2 < 1) return m2; - if (h * 3 < 2) return m1 + (m2 - m1) * (0.66666 - h) * 6; - return m1; - }; - - fb.RGBToHSL = function (rgb) { - var min, max, delta, h, s, l; - var r = rgb[0], g = rgb[1], b = rgb[2]; - min = Math.min(r, Math.min(g, b)); - max = Math.max(r, Math.max(g, b)); - delta = max - min; - l = (min + max) / 2; - s = 0; - if (l > 0 && l < 1) { - s = delta / (l < 0.5 ? (2 * l) : (2 - 2 * l)); - } - h = 0; - if (delta > 0) { - if (max == r && max != g) h += (g - b) / delta; - if (max == g && max != b) h += (2 + (b - r) / delta); - if (max == b && max != r) h += (4 + (r - g) / delta); - h /= 6; - } - return [h, s, l]; - }; - - // Install mousedown handler (the others are set on the document on-demand) - document.getElementsByClassName('farbtastic')[0].onmousedown = fb.mousedown; - - // Init color - fb.setColor('#000000'); - - // Set linked elements/callback - if (callback) { - fb.linkTo(callback); - } -}; \ No newline at end of file diff --git a/files.php b/files.php index 00ea4e8..724049f 100644 --- a/files.php +++ b/files.php @@ -1,63 +1,46 @@ - + -ICEcoder v <?php echo $ICEcoder["versionNo"];?> file manager - - - - - - - + ICEcoder v <?php echo $ICEcoder["versionNo"];?> file manager + + + + + + + - + -
    -
    -
    -top.ICEcoder.setSplitPane('on');"; - } - echo '
    '; -} -?> +
    +
    +
    - -
    - \ No newline at end of file + diff --git a/index.php b/index.php index 7952d89..b55daf5 100644 --- a/index.php +++ b/index.php @@ -1,14 +1,16 @@ $icv, 1 => substr($icvInfo, strlen($matches[0])) ]; - $icvI = str_replace('"','\\\'',$icvInfo[1]); + $icvI = str_replace('"', '\\\'', $icvInfo[1]); $thisV = $ICEcoder["versionNo"]; - if (strpos($thisV,"beta")>-1 && !strpos($icv,"beta") && str_replace(" beta","",$thisV) == $icv) {$thisV-=0.1;}; - if ($thisV<$icv) { - $updateMsg = ";top.ICEcoder.dataMessage('".$t['UPDATE INFO'].": ICEcoder v ".$icv." ".$t['now available'].". (".$t['Your version is']." v ".$ICEcoder["versionNo"].").

    ".$t['Update now']."

    ".$icvI."');"; + if (-1 < strpos($thisV, "beta") && false === strpos($icv, "beta") && str_replace(" beta", "", $thisV) === $icv) { + $thisV-=0.1; + }; + if ($thisV < $icv) { + $updateMsg = + ";ICEcoder.dataMessage('" . $t['UPDATE INFO'] . + ": ICEcoder v " . $icv." " . $t['now available'] . ". (" . $t['Your version is'] . " v " . $ICEcoder["versionNo"] . + ").

    " . + $t['Update now'] . "

    " . $icvI ."');"; } } -$isMac = strpos($_SERVER['HTTP_USER_AGENT'], "Macintosh")>-1 ? true : false; +$isMac = false !== strpos($_SERVER['HTTP_USER_AGENT'], "Macintosh") ? true : false; ?> - + ICEcoder v <?php echo $ICEcoder["versionNo"];?> @@ -39,19 +47,22 @@ $isMac = strpos($_SERVER['HTTP_USER_AGENT'], "Macintosh")>-1 ? true : false; - - + + - + - - - - - - + + + + + + + + + + + + + + + -ICEcoder.init();top.ICEcoder.content.style.visibility='visible';top.ICEcoder.filesFrame.contentWindow.frames['processControl'].location.href = 'processes/on-load.php';" onResize="ICEcoder.setLayout()" onKeyDown="return ICEcoder.interceptKeys('coder',event);" onKeyUp="parent.ICEcoder.resetKeys(event);" onBlur="parent.ICEcoder.resetKeys(event);"> + echo "ICEcoder.theme = '" . ("default" === $ICEcoder["theme"] ? 'icecoder' : $ICEcoder["theme"]) . "';" . + "ICEcoder.autoLogoutMins = " . $ICEcoder["autoLogoutMins"] . ";" . + "ICEcoder.fontSize = '" . $ICEcoder["fontSize"] . "';" . + "ICEcoder.openLastFiles = " . ($ICEcoder["openLastFiles"] ? 'true' : 'false') . ";" . + "ICEcoder.updateDiffOnSave = " . ($ICEcoder["updateDiffOnSave"] ? 'true' : 'false') . ";" . + "ICEcoder.languageUser = '".$ICEcoder["languageUser"] . "';" . + "ICEcoder.codeAssist = " . ($ICEcoder["codeAssist"] ? 'true' : 'false') . ";" . + "ICEcoder.lockedNav = " . ($ICEcoder["lockedNav"] ? 'true' : 'false') . ";" . + "ICEcoder.lineWrapping = " . ($ICEcoder["lineWrapping"] ? 'true' : 'false') . ";" . + "ICEcoder.lineNumbers = " . ($ICEcoder["lineNumbers"] ? 'true' : 'false') . ";" . + "ICEcoder.showTrailingSpace = " . ($ICEcoder["showTrailingSpace"] ? 'true' : 'false') . ";" . + "ICEcoder.matchBrackets = " . ($ICEcoder["matchBrackets"] ? 'true' : 'false') . ";" . + "ICEcoder.autoCloseTags = " . ($ICEcoder["autoCloseTags"] ? 'true' : 'false') . ";" . + "ICEcoder.autoCloseBrackets = " . ($ICEcoder["autoCloseBrackets"] ? 'true' : 'false') . ";" . + "ICEcoder.indentWithTabs = " . ($ICEcoder["indentWithTabs"] ? 'true' : 'false') . ";" . + "ICEcoder.indentAuto = " . ($ICEcoder["indentAuto"] ? 'true' : 'false') . ";" . + "ICEcoder.indentSize = " . $ICEcoder["indentSize"] . ";" . + "ICEcoder.scrollbarStyle = '" . $ICEcoder["scrollbarStyle"] . "';" . + "ICEcoder.demoMode = " . ($ICEcoder["demoMode"] ? 'true' : 'false') . ";" . + "ICEcoder.tagWrapperCommand = '" . $ICEcoder["tagWrapperCommand"] . "';" . + "ICEcoder.autoComplete = '" . $ICEcoder["autoComplete"] . "';" . + "ICEcoder.bugFilePaths = ['" . implode("','",$ICEcoder["bugFilePaths"]) . "'];" . + "ICEcoder.bugFileCheckTimer = ".$ICEcoder["bugFileCheckTimer"] . ";" . + "ICEcoder.bugFileMaxLines = " . $ICEcoder["bugFileMaxLines"] . ";" . + "ICEcoder.fileDirResOutput = '" . $ICEcoder["fileDirResOutput"] . "';" . + "ICEcoder.newDirPerms = " . $ICEcoder["newDirPerms"] . ";" . + "ICEcoder.newFilePerms = " . $ICEcoder["newFilePerms"] . ";"; + echo "ICEcoder.csrf = '" . $_SESSION["csrf"] . "';"; + if (true === $ICEcoder["tutorialOnLogin"]) { + echo "ICEcoder.viewTutorial(false, 700);"; + } + $extraProcessesClass = new ExtraProcesses(); + $onLoad = $extraProcessesClass->onLoad(); +?>ICEcoder.init();ICEcoder.content.style.visibility = 'visible';" onresize="ICEcoder.setLayout()" onkeydown="return ICEcoder.interceptKeys('coder', event);" onkeyup="if('visible' === get('blackMask').style.visibility) {ICEcoder.handleModalKeyUp(event, 'modalGeneralCatch')}; ICEcoder.resetKeys(event);" onblur="ICEcoder.resetKeys(event);"> -
    +
    -
    +
    -
    : 0" onMouseOver="top.ICEcoder.showHidePlugins('show')" onMouseOut="top.ICEcoder.showHidePlugins('hide')" onClick="top.ICEcoder.showHidePlugins('hide')"> +
    +
    +
    +
    + +
    : 0" onmouseover="ICEcoder.showHidePlugins('show')" onmouseout="ICEcoder.showHidePlugins('hide')" onclick="ICEcoder.showHidePlugins('hide')">
    - Color Picker

    + Color Picker

    - + / - + + / -
    -
    +
    - - -

    - + + +

    +
    -
    + - + ">
    - -

    + +

    - - - - + + + + - -

    - + +

    +
    -

    +

    Zip It!'.PHP_EOL; + if (true === file_exists(dirname(__FILE__) . "/plugins/zip-it/index.php")) { + echo 'Zip It!' . PHP_EOL; }; ?> - -

    - + +

    +
    - + -
    +
      -
    • -
    • -
    • -
    • -
    -
    -
    -
    Commit
    -
    Selected: 0
    -
    Pull
    +
  • +
  • +
  • +
  • +
    -
    +
    -
    +
    • @@ -233,25 +265,31 @@ $t = $text['index'];
    • -
    -
    + +
    -
    + //--> +
      +
    • Tutorial
    • ')">
    • @@ -259,37 +297,37 @@ $t = $text['index'];
    - +
    -
    Terminal
    -
    Output
    -
    Database
    -
    Git
    +
    Terminal
    +
    Output
    +
    Database
    +
    Git
    -
    - - +
    + +
    '; + for ($i = 1; $i <= 100; $i++) { + echo '
    '; } - ?>
    +
    + ?>
    +
    -
    -
    +
    +
    - +
    - with +
    with
    -
    - + + +
    ">
    -
    -
    - > - 0" onClick="top.ICEcoder.codeAssistToggle()"> -
    -
    -
    -
    - - + +
    +
    +
    ">
    - -
    Output
    via top.ICEcoder.output(message);

    - + +
    Output
    via ICEcoder.output(message);

    +

    sudo nohup php processes/system.php > data/nohup.log 2>&1 &

    ...to run as a background process"; + echo "To provide git diff data to ICEcoder, please run...

    sudo nohup php server/run-tasks.php > data/nohup.log 2>&1 &

    ...to run as a background process"; };?>
    - +
    -\n",top.get("git").innerHTML=a+"

    "},updatePreviewWindow:function(a,b,c,d){top.ICEcoder.previewWindow.location.pathname==b?-1<["htm","html","txt"].indexOf(d)?top.ICEcoder.previewWindow.document.documentElement.innerHTML= - a.getValue():-1<["md"].indexOf(d)&&(top.ICEcoder.previewWindow.document.documentElement.innerHTML=mmd(a.getValue())):-1<["css"].indexOf(d)&&-1/g,"");a.setValue(b);a.clearHistory();top.ICEcoder.savedPoints[top.ICEcoder.selectedTab-1]= - a.changeGeneration();top.ICEcoder.savedContents[top.ICEcoder.selectedTab-1]=a.getValue()},undo:function(){var a=ICEcoder.getcMInstance();var b=ICEcoder.getcMdiffInstance();(-1=f.line;b--)e.replaceRange(e.getLine(b),{line:b+1,ch:0},{line:b+1,ch:1E6});e.replaceRange(l,{line:"up"==a?g.line:f.line,ch:0},{line:"up"==a?g.line:f.line,ch:1E6});e.setSelection({line:f.line+("up"==a?-1:1),ch:f.ch},{line:g.line+("up"==a?-1:1),ch:g.ch})})}},highlightLine:function(a){var b=top.ICEcoder.getcMInstance();var c=top.ICEcoder.getcMdiffInstance();b=-1\n"+ - d.getSelection()+"\n
    ","around");for(var a=e+1;a<=f+1;a++)d.indentLine(a);d.indentLine(f+2,"prev");d.indentLine(f+2,"subtract")})}else-1<["p","a","h1","h2","h3"].indexOf(a)&&d.getSelection().substr(0,a.length+1)=="<"+b&&d.getSelection().substr(-(a.length+3))==""?d.replaceSelection(d.getSelection().substr(d.getSelection().indexOf(">")+1,d.getSelection().length-d.getSelection().indexOf(">")-1-a.length-3),"around"):("a"==a&&(b='a href=""'),d.replaceSelection("<"+b+">"+d.getSelection()+"","around"),"a"==a&&d.setCursor({line:d.getCursor("start").line,ch:d.getCursor("start").ch+9}))},addLineBreakAtEnd:function(a){var b=ICEcoder.getcMInstance();var c=ICEcoder.getcMdiffInstance();b=-1",{line:a,ch:0},{line:a,ch:1E6})},insertLineBefore:function(a){var b=ICEcoder.getcMInstance();var c=ICEcoder.getcMdiffInstance();var d=-1'+top.ICEcoder.functionClassList[b].name+'
    '+top.ICEcoder.functionClassList[b].params+"
    ");get("functionClassList").innerHTML=a},0))},updateFunctionClassListItems:function(a){var b=ICEcoder.getcMInstance(); - var c="";-1'+get("miniMapContent").innerHTML+"
    ";get("miniMapContent").innerHTML=get("miniMapContent").innerHTML.replace(/
    '; - var b=get("miniMapBox");b=new Draggabilly(b,{axis:"y",containment:!0});b.on("dragMove",function(b,d,e){yPos=this.position.y;maxHeight=parseInt(get("docExplorer").style.height,10)<=parseInt(get("miniMapContent").getBoundingClientRect().height,10)?parseInt(get("docExplorer").style.height,10):parseInt(get("miniMapContent").getBoundingClientRect().height,10);newPerc=this.position.y/(maxHeight-top.ICEcoder.miniMapBoxHeight);yPos=(a.getScrollInfo().height-a.getScrollInfo().clientHeight)*newPerc;a.scrollTo(0, - yPos)});b.on("pointerDown",function(a,b){top.ICEcoder.mouseDownMinimap=!0});b.on("pointerUp",function(a,b){top.ICEcoder.mouseDownMinimap=!1});top.ICEcoder.setMinimapLayout(a);get("docExplorer").style.right="-220px"}},setMinimapLayout:function(a,b){if(get("miniMapBox")&&a){var c=top.ICEcoder.getcMInstance();var d=a.getScrollInfo().top/(a.getScrollInfo().height-a.getScrollInfo().clientHeight);parseInt(get("miniMapContent").getBoundingClientRect().height,10)>parseInt(get("docExplorer").style.height, - 10)?(get("miniMapContainer").style.height=parseInt(get("docExplorer").style.height,10)+"px",top.ICEcoder.miniMapBoxHeight=parseInt(get("docExplorer").style.height,10)/c.defaultTextHeight()*2,get("miniMapBox").style.height=top.ICEcoder.miniMapBoxHeight+"px",top.ICEcoder.miniMapBoxTop=d*parseInt(get("docExplorer").style.height,10)-d*top.ICEcoder.miniMapBoxHeight,get("miniMapContent").style.marginTop=-(parseInt(get("miniMapContent").getBoundingClientRect().height,10)-parseInt(get("docExplorer").style.height, - 10))*d+"px"):(get("miniMapContainer").style.height=parseInt(get("miniMapContent").getBoundingClientRect().height,10)+"px",top.ICEcoder.miniMapBoxHeight=parseInt(get("docExplorer").style.height,10)/c.defaultTextHeight()*2,get("miniMapBox").style.height=top.ICEcoder.miniMapBoxHeight+"px",top.ICEcoder.miniMapBoxTop=d*parseInt(get("miniMapContainer").getBoundingClientRect().height,10)-d*top.ICEcoder.miniMapBoxHeight,get("miniMapContent").style.marginTop=0);top.ICEcoder.mouseDownMinimap||(get("miniMapBox").style.top= - top.ICEcoder.miniMapBoxTop+"px")}},autocomplete:function(){var a=ICEcoder.getcMInstance();var b=ICEcoder.getcMdiffInstance();a=-1g.replace(/\d+/g,f)?d:g;if(0d&&("LI"!=b.childNodes[d].nodeName&&d++,e=b.childNodes[d].childNodes[0].childNodes[1],e.id==c&&(a=!0),1==a&&-1==top.ICEcoder.selectedFiles.indexOf(e.id)&&(ICEcoder.selectDeselectFile("select",e),top.ICEcoder.selectedFiles.push(e.id)),e.id!=g);d+=2);else ICEcoder.selectDeselectFile("select", - e),top.ICEcoder.selectedFiles.push(d)}else top.ICEcoder.deselectAllFiles(),ICEcoder.selectDeselectFile("select",e),top.ICEcoder.selectedFiles.push(d)}top.ICEcoder.githubDiff&&(top.get("githubNavSelectedCount").innerHTML="Selected: "+top.ICEcoder.selectedFiles.length,top.get("githubNavCommit").style.color=0"+top.t["Creating Folder"]+ - "
    "+a)},returnFileAndLine:function(a){var b=1,c=/^([^ ]*)\s+(on\s+)?(line\s+)?(\d+)/.exec(a);null!==c?(b=c[4],a=c[1]):0"+top.t["Opening File"]+"
    "+top.ICEcoder.shortURL)):top.ICEcoder.createNewTab("new"),top.ICEcoder.fMIconVis("fMView",1))}},openFilesFromList:function(a){for(var b=0;b"+top.t.Getting+"
    "+a)},getChangesToSave:function(){var a=top.ICEcoder.getcMInstance();var b=top.ICEcoder.savedContents[top.ICEcoder.selectedTab-1];a=difflib.stringAsLines(a.getValue());b=difflib.stringAsLines(b);b=(new difflib.SequenceMatcher(b,a)).get_opcodes();for(var c=0;c"+top.t.Saving+"
    "+ICEcoder.openFiles[ICEcoder.selectedTab-1].replace(top.iceRoot,""))},renameFile:function(a,b){if(a)var c=a.replace(/\|/g,"/");else c=top.ICEcoder.selectedFiles[top.ICEcoder.selectedFiles.length-1].replace(/\|/g,"/"),a=top.ICEcoder.selectedFiles[top.ICEcoder.selectedFiles.length-1].replace(/\|/g,"/");b||(b=top.ICEcoder.getInput(top.t["Please enter the..."],c));if(b){var d=top.ICEcoder.openFiles.indexOf(c.replace(/\|/g, - "/"));-1',c=top.ICEcoder.openFiles[d],top.get("tab"+(d+1)).innerHTML=closeTabLink+" "+c.slice(c.lastIndexOf("/")).replace(/\//, - ""),top.get("tab"+(d+1)).title=b);top.ICEcoder.serverQueue("add","lib/file-control-xhr.php?action=rename&oldFileName="+encodeURIComponent(a.replace(/\|/g,"/"))+"&csrf="+top.ICEcoder.csrf,encodeURIComponent(b));top.ICEcoder.serverMessage(""+top.t["Renaming to"]+"
    "+b);top.ICEcoder.setPreviousFiles()}},moveFile:function(a,b){if(b&&b!=a){var c=top.ICEcoder.openFiles.indexOf(a.replace(/\|/g,"/"));if(-1'; - var d=top.ICEcoder.openFiles[c];top.get("tab"+(c+1)).innerHTML=closeTabLink+" "+d.slice(d.lastIndexOf("/")).replace(/\//,"");top.get("tab"+(c+1)).title=b}top.ICEcoder.ask("Are you sure you want to move file "+a+" to "+b+" ?")&&(top.ICEcoder.serverQueue("add","lib/file-control-xhr.php?action=move&oldFileName="+encodeURIComponent(a.replace(/\//g,"|"))+"&csrf="+top.ICEcoder.csrf,encodeURIComponent(b.replace(/\//g,"|"))),top.ICEcoder.serverMessage(""+top.t["Moving to"]+"
    "+b));top.ICEcoder.setPreviousFiles()}}, - deleteFiles:function(a){a=a?a:top.ICEcoder.selectedFiles;var b=a.toString().replace(/\|/g,"/").replace(/,/g,"\n");0"+top.t["Deleting File"]+"
    "+b))},copyFiles:function(a,b,c){top.ICEcoder.copiedFiles=[];for(var d=0;d"+top.t["Pasting File"]+"
    "+top.ICEcoder.copiedFiles[b].toString().replace(/\|/g,"/").replace(/,/g,"\n"))):top.ICEcoder.message(top.t["Sorry cannot paste..."]); - else top.ICEcoder.message(top.t["Nothing to paste..."])},duplicateFiles:function(a){if(top.ICEcoder.copiedFiles)var b=top.ICEcoder.copiedFiles;top.ICEcoder.copyFiles(a,"dontShowPaste","dontHide");a=a[0].substr(0,a[0].lastIndexOf("|"));top.ICEcoder.pasteFiles(a);"undefined"!=typeof b&&(top.ICEcoder.copiedFiles=b)},uploadFilesSelect:function(a){top.get("uploadDir").value=a;top.get("fileInput").click()},uploadFilesSubmit:function(a){""!=top.get("fileInput").value&&(top.ICEcoder.showHide("show",top.get("loadingMask")), - top.get("uploadFilesForm").submit(),event.preventDefault())},showHideFileNav:function(a,b){var c=["optionsFile","optionsEdit","optionsSource","optionsHelp"];if("hide"==a)fileNavInt=setTimeout(function(){for(var a=0;ab&&(c-=c+a-b);top.get("fileMenu").style.top=c+"px"}return!1},showFileMenu:function(){top.get("fileMenu").style.display="inline-block";setTimeout(function(){top.get("fileMenu").style.opacity="1"},4)},hideFileMenu:function(){top.get("fileMenu").style.display="none";top.get("fileMenu").style.opacity="0"},updateFileManagerList:function(a,b,c,d,e,f,g){if("add"==a&&!top.get("filesFrame").contentWindow.document.getElementById(b.replace(top.iceRoot, - "").replace(/\/$/,"").replace(/\//g,"|")+"|"+c)){var l="file"==g?"pft-file ext-"+c.substr(c.indexOf(".")+1):"pft-directory";d="file"==g?top.ICEcoder.newFilePerms:top.ICEcoder.newDirPerms;b||(b="/");b=b.replace(top.iceRoot,"/");b=b.replace("//","/");var h=top.get("filesFrame").contentWindow.document.getElementById(b.replace(/\//g,"|"));var m=h.parentNode.parentNode.nextSibling;var n=document.createTextNode("\n");var p=777==d?"background: #800; color: #eee":"color: #888";p='        '+c+' '+d+"";if(!m||3>m.childNodes.length){var k=document.createElement("ul");m=h.parentNode.parentNode; - m.parentNode.insertBefore(k,m.nextSibling);k=document.createElement("li");k.className=l;k.draggable=!1;k.ondragstart=function(a){top.ICEcoder.addDefaultDragData(this,a)};k.ondrag=function(a){top.ICEcoder.draggingWithKeyTest(a);top.ICEcoder.getcMInstance()&&(-1==top.ICEcoder.editorFocusInstance.indexOf("diff")?top.ICEcoder.getcMInstance().focus():top.ICEcoder.getcMdiffInstance().focus())};k.ondragover=function(a){top.ICEcoder.setDragCursor(a,"folder"==g?"folder":"file")};k.ondragend=function(){top.ICEcoder.dropFile(this)}; - k.innerHTML=p;m.nextSibling.appendChild(k);m.nextSibling.appendChild(n)}else for(h=0;hc||"folder"==g&&"file"==k||h==m.childNodes.length-1){k=document.createElement("li");k.className=l;k.draggable=!1;k.ondragstart=function(a){top.ICEcoder.addDefaultDragData(this,a)};k.ondrag=function(a){top.ICEcoder.draggingWithKeyTest(a); - top.ICEcoder.getcMInstance()&&(-1==top.ICEcoder.editorFocusInstance.indexOf("diff")?top.ICEcoder.getcMInstance().focus():top.ICEcoder.getcMdiffInstance().focus())};k.ondragover=function(a){top.ICEcoder.setDragCursor(a,"folder"==g?"folder":"file")};k.ondragend=function(){top.ICEcoder.dropFile(this)};k.innerHTML=p;h==m.childNodes.length-1?(m.appendChild(k),m.appendChild(n)):(m.insertBefore(k,m.childNodes[h]),m.insertBefore(n,m.childNodes[h+1]));break}}"file"!=g||f||(top.ICEcoder.openFiles[top.ICEcoder.selectedTab- - 1]=b+c)}"rename"==a&&(f=e.replace(/\//g,"|"),h=top.get("filesFrame").contentWindow.document.getElementById(f),h.innerHTML=c,h.id=b.replace(/\//g,"|")+"|"+c,h.parentNode.title=h.id.replace(/\|/g,"/"),targetElemPerms=top.get("filesFrame").contentWindow.document.getElementById(f+"_perms"),targetElemPerms.id=b.replace(/\//g,"|")+"|"+c+"_perms",top.ICEcoder.renameInChildren(h,e,b,c));"move"==a&&(top.ICEcoder.updateFileManagerList("add",b,c,!1,!1,!1,g),top.ICEcoder.updateFileManagerList("delete",e.substr(0, - e.lastIndexOf("/")),c));"chmod"==a&&(f=top.ICEcoder.selectedFiles[top.ICEcoder.selectedFiles.length-1].replace(/\|/g,"/"),h=top.get("filesFrame").contentWindow.document.getElementById(f.replace(/\//g,"|")+"_perms"),h.style.background=777==d?"#800":"none",h.style.color=777==d?"#eee":"#888",h.innerHTML=d);"delete"==a&&(b||(b=""),b=b.replace(top.iceRoot,"/"),b=b.replace("//","/"),b=b.replace(/\/$/,"").replace(/\//g,"|"),h=(b+"|"+c).replace("||","|"),h=top.get("filesFrame").contentWindow.document.getElementById(h).parentNode.parentNode, - top.ICEcoder.openCloseDir(h.childNodes[0],!1),h.parentNode.removeChild(h))},renameInChildren:function(a,b,c,d){if(a.parentNode.parentNode.nextSibling&&"UL"==a.parentNode.parentNode.nextSibling.nodeName){a=a.parentNode.parentNode.nextSibling;for(var e=0;ed.indexFromPos({ch:d.getCursor().ch- - 1,line:d.getCursor().line})&&ICEcoder.findResult--;else for(f=ICEcoder.findResult=0;fICEcoder.results.length-1&&(ICEcoder.findResult=0);e&&1==ICEcoder.findResult&&(ICEcoder.findResult=ICEcoder.results.length+1);g.innerHTML="Highlighted result "+(ICEcoder.findResult+(e?-1:1))+" of "+ICEcoder.results.length+" results";e?(b=d.getSearchCursor(a, - {ch:d.getCursor().ch-1,line:d.getCursor().line},!0),b.findPrevious(),b.from()||(b=d.getSearchCursor(a,{line:1E6,ch:1E6},!0),b.findPrevious())):(b=d.getSearchCursor(a,{ch:d.getCursor().ch+1,line:d.getCursor().line},!0),b.findNext(),b.from()||(b=d.getSearchCursor(a,{line:0,ch:0},!0),b.findNext()));d.setSelection(b.from(),b.to());top.ICEcoder.focus();top.ICEcoder.findMode=!0}a=top.ICEcoder.scrollBarVisible?parseInt(top.ICEcoder.content.style.height,10)/d.lineCount():d.defaultTextHeight();b=top.ICEcoder.scrollBarVisible? - 0:d.heightAtLine(0);e="";for(f=1;f<=d.lineCount();f++)g=-1
    ';top.ICEcoder.content.contentWindow.document.getElementById("resultsBar").innerHTML=e;top.ICEcoder.content.contentWindow.document.getElementById("resultsBar").style.display="inline-block";return!0}g.innerHTML="No results"; - top.ICEcoder.content.contentWindow.document.getElementById("resultsBar").innerHTML="";top.ICEcoder.content.contentWindow.document.getElementById("resultsBar").style.display="none";return!1}""!=a&&c?(e=b=d="",document.findAndReplace.connector.value==top.t.and&&(d="&replace="+f),0<=document.findAndReplace.target.value.indexOf(top.t.file)&&(b="&target="+document.findAndReplace.target.value.replace(/ /g,"-")),document.findAndReplace.target.value==top.t["selected files"]&&(e="&selectedFiles="+top.ICEcoder.selectedFiles.join(":")), - a=a.replace(/'/g,"'"),a!=encodeURIComponent(a)?a="ICEcoder:"+encodeURIComponent(a):a,top.ICEcoder.showHide("show",top.get("loadingMask")),top.get("mediaContainer").innerHTML=''):(g.innerHTML="No results",top.ICEcoder.content.contentWindow.document.getElementById("resultsBar").innerHTML="",top.ICEcoder.content.contentWindow.document.getElementById("resultsBar").style.display= - "none")}},replaceInFile:function(a,b,c){top.ICEcoder.serverQueue("add","lib/file-control-xhr.php?action=replaceText&find="+b+"&replace="+c+"&csrf="+top.ICEcoder.csrf,encodeURIComponent(a.replace(/\//g,"|")));top.ICEcoder.serverMessage(""+top.t["Replacing text in"]+"
    "+a)},getCaretPosition:function(){var a;var b=ICEcoder.getcMInstance();var c=ICEcoder.getcMdiffInstance();b=-1=b||10<=b)&&top.ICEcoder.mouseX>parseInt(top.ICEcoder.files.style.width,10)&&(top.ICEcoder.tabDragMouseX=top.ICEcoder.mouseX-parseInt(top.ICEcoder.files.style.width,10)-top.ICEcoder.tabDragMouseXStart,top.ICEcoder.tabDragMove());if(top.ICEcoder.ready&&(top.ICEcoder.mouseDown||(top.ICEcoder.draggingFilesW=!1),b=!ICEcoder.draggingTab&&(top.ICEcoder.mouseX>top.ICEcoder.filesW-7&&top.ICEcoder.mouseX").replace(/<\/b>/g,"").replace(/<br>/g,"
    "),b.style.left="0"):setTimeout(function(){b.style.left="2000px"},200);b.style.opacity=a?1:0},cssColorPreview:function(){var a, - b;var c=ICEcoder.getcMInstance();var d=ICEcoder.getcMdiffInstance();if(c=-1b.index+b[0].length;);(d=top.get("content").contentWindow.document.getElementById("cssColor"))&&d.parentNode.removeChild(d);top.ICEcoder.codeAssist&&"CSS"==top.ICEcoder.caretLocType&&(d=top.document.createElement("div"),d.id= - "cssColor",d.style.position="absolute",d.style.display="block",d.style.width=d.style.height="20px",d.style.zIndex="1000",d.style.background=b?b[0]:"",d.style.cursor="pointer",d.onclick=function(){top.ICEcoder.showColorPicker(b[0])},""==d.style.backgroundColor&&(d.style.display="none"),top.get("header").appendChild(d),c.addWidget(c.getCursor(),top.get("cssColor"),!0))}},showColorPicker:function(a){top.get("blackMask").style.visibility="visible";top.get("mediaContainer").innerHTML='



    '; - farbtastic("picker","color");a&&top.get("picker").farbtastic.setColor(a)},initCanvasImage:function(a){var b=top.get("canvasPicker").getContext("2d");var c=new Image;c.crossOrigin="Anonymous";c.src=a.src;c.onerror=function(){get("floatingContainer").style.visibility="hidden";get("canvasPickerColorInfo").style.display="none";get("canvasPickerCORSInfo").style.display="block"};c.onload=function(){top.get("canvasPicker").width=a.width;top.get("canvasPicker").height=a.height;b.drawImage(c,0,0,a.width,a.height); - get("canvasPickerColorInfo").style.display="block";get("canvasPickerCORSInfo").style.display="none";top.get("canvasPicker").onmouseover=function(a){get("floatingContainer").style.visibility="visible"};top.get("canvasPicker").onmouseout=function(a){get("floatingContainer").style.visibility="hidden"}};top.document.getElementById("floatingContainer").style.backgroundSize=5*a.naturalWidth+"px "+5*a.naturalHeight+"px"},interactCanvasImage:function(a){var b,c,d,e,f,g,l,h,m,n,p,k;var r=top.get("canvasPicker").getContext("2d"); - top.get("canvasPicker").onmousemove=function(t){b=t.pageX-this.offsetLeft;c=t.pageY-this.offsetTop;d=r.getImageData(b,c,1,1).data;e=d[0];f=d[1];g=d[2];l=e+","+f+","+g;h=top.ICEcoder.rgbToHex(e,f,g);top.get("rgbMouseXY").value=l;top.get("hexMouseXY").value="#"+h;top.get("hexMouseXY").style.backgroundColor=top.get("rgbMouseXY").style.backgroundColor="#"+h;m=128>e||128>f||128>g&&200>e&&200>f&&50'+top.t["Cancelled tasks"]+""); - setTimeout(function(){top.ICEcoder.serverMessage()},2E3)},setPreviousFiles:function(){var a=top.ICEcoder.openFiles.join(",").replace(/\//g,"|").replace(/(\|\[NEW\])|(,\|\[NEW\])/g,"").replace(/(^,)|(,$)/g,"");""==a&&(a="CLEAR");top.ICEcoder.serverQueue("add","lib/settings.php?saveFiles="+encodeURIComponent(a)+"&csrf="+top.ICEcoder.csrf);top.ICEcoder.updateLast10List(a)},updateLast10List:function(a){a=a.split(",");for(var b=0;b"+a[b].replace(/\|/g,"/")+"\n";var d=top.ICEcoder.content.contentWindow.document.getElementById("last10Files");if(-1==d.innerHTML.indexOf(c)){var e=d.innerHTML.split("\n");(10<=e.length||'
    [none]


    '==e[0]||""==e[e.length-1])&&e.pop();d.innerHTML=c+e.join("\n")}}},autoOpenFiles:function(){if(0< - top.ICEcoder.previousFiles.length&&top.ICEcoder.ask(top.t["Open previous files"]+"\n\n"+top.ICEcoder.previousFiles.length+" files:\n"+top.ICEcoder.previousFiles.join("\n").replace(/\|/g,"/").replace(new RegExp(top.docRoot+top.iceRoot,"gi"),"")))for(var a=0;a');top.ICEcoder.showHide(a?"hide":"show",top.get("blackMask"))},helpScreen:function(){top.get("mediaContainer").innerHTML='';top.ICEcoder.showHide("show",top.get("blackMask"))},versionsScreen:function(a,b){top.get("mediaContainer").innerHTML='';top.ICEcoder.showHide("show",top.get("blackMask"))},showManual:function(a,b){var c=b?"#"+b:"";top.get("mediaContainer").innerHTML='';top.ICEcoder.showHide("show",top.get("blackMask"))},propertiesScreen:function(a){top.get("mediaContainer").innerHTML='';top.ICEcoder.showHide("show",top.get("blackMask"))},autoLogoutWarningScreen:function(){top.get("mediaContainer").innerHTML='';top.ICEcoder.showHide("show",top.get("blackMask"))},pluginsManager:function(){top.get("mediaContainer").innerHTML=''; - top.ICEcoder.showHide("show",top.get("blackMask"))},goLocalhostRoot:function(){top.ICEcoder.filesFrame.contentWindow.frames.fileControl.location.href="lib/go-localhost-root.php"},githubAction:function(a){top.get("mediaContainer").innerHTML='';top.ICEcoder.showHide("show",top.get("blackMask"))},githubTokenAsk:function(a){if(githubAuthToken= - top.ICEcoder.getInput(top.t["Please enter your..."],""))top.ICEcoder.filesFrame.contentWindow.frames.fileControl.location.href="lib/github.php?action=auth&token="+githubAuthToken+"&goNext="+a+"&csrf="+top.ICEcoder.csrf,githubAuthToken=""},showHideGithubNav:function(a){top.get("githubNav").style.display="show"==a?"block":"none";top.get("fileNav").style.display="show"==a?"none":"block"},githubManager:function(){top.ICEcoder.githubAuthTokenSet?(top.get("mediaContainer").innerHTML='', - top.ICEcoder.showHide("show",top.get("blackMask"))):top.ICEcoder.githubTokenAsk("showManager")},githubDiffToggle:function(){if(!top.ICEcoder.githubAuthTokenSet)top.ICEcoder.githubTokenAsk("loadFiles");else if(top.ICEcoder.githubDiff||top.ICEcoder.ask(top.t["This will compare..."])){top.ICEcoder.githubDiff=!top.ICEcoder.githubDiff;var a=top.ICEcoder.githubDiff?"true":"false";top.ICEcoder.filesFrame.src="files.php?githubDiff="+a+"&csrf="+top.ICEcoder.csrf}},ftpManager:function(){top.get("mediaContainer").innerHTML= - '';top.ICEcoder.showHide("show",top.get("blackMask"))},useNewSettings:function(a,b,c,d,e,f,g,l,h,m,n,p,k,r,t,w,v,x,y,z,A,B,C,D){var u=a.slice(0,a.lastIndexOf("?"));u=u.slice(u.lastIndexOf("/")+1,u.lastIndexOf("."));if(top.ICEcoder.theme!==u){top.ICEcoder.theme=u;"editor"==top.ICEcoder.theme&&(top.ICEcoder.theme="icecoder");var q=document.createElement("link");q.setAttribute("rel","stylesheet");q.setAttribute("type", - "text/css");q.setAttribute("href",a);top.ICEcoder.content.contentWindow.document.getElementsByTagName("head")[0].appendChild(q);q=document.createElement("link");q.setAttribute("rel","stylesheet");q.setAttribute("type","text/css");q.setAttribute("href",a);top.document.getElementsByTagName("head")[0].appendChild(q);q=-1<"3024-day base16-light eclipse elegant mdn-like neat neo paraiso-light solarized the-matrix xq-light".split(" ").indexOf(top.ICEcoder.theme)?"#ccc":-1<"3024-night blackboard colorforth liquibyte night tomorrow-night-bright tomorrow-night-eighties vibrant-ink".split(" ").indexOf(top.ICEcoder.theme)? - "#888":"#000";top.ICEcoder.switchTab(top.ICEcoder.selectedTab)}b!=top.ICEcoder.codeAssist&&(top.get("codeAssist").checked=b,top.ICEcoder.codeAssistToggle());c!=top.ICEcoder.lockedNav&&(top.ICEcoder.lockUnlockNav(),ICEcoder.changeFilesW(c?"expand":"contract"),top.ICEcoder.hideFileMenu());a=top.document.styleSheets[0];b=a.rules?"rules":"cssRules";a[b][0].style.fontSize=g;a=ICEcoder.filesFrame.contentWindow.document.styleSheets[3];b=a.rules?"rules":"cssRules";a[b][0].style.fontSize=g;a=ICEcoder.content.contentWindow.document.styleSheets[6]; - b=a.rules?"rules":"cssRules";a[b][0].style.fontSize=g;a[b][4].style["border-left-width"]=f?"1px":"0";a[b][4].style["margin-left"]=f?"-1px":"0";a[b][2].style.cssText="background-color: "+q+" !important";top.ICEcoder.lineWrapping=l;top.ICEcoder.lineNumbers=h;top.ICEcoder.showTrailingSpace=m;top.ICEcoder.matchBrackets=n;top.ICEcoder.autoCloseTags=p;top.ICEcoder.autoCloseBrackets=k;top.ICEcoder.indentWithTabs=r;top.ICEcoder.indentSize=w;top.ICEcoder.indentAuto=t;for(f=0;fchMod "+ - b+" on
    "+a.replace(/\|/g,"/"))},openPreviewWindow:function(){if(060*top.ICEcoder.autoLogoutMins-60&&ICEcoder.showHide("hide",get("blackMask")); - top.ICEcoder.autoLogoutTimer=0},logout:function(a){window.location=window.location+"?logout&"+(a?"type="+a+"&":"")+"csrf="+top.ICEcoder.csrf},outputMsg:function(a){top.ICEcoder.output.innerHTML+=a+"
    "},message:function(a){alert(a)},ask:function(a){return confirm(a)},getInput:function(a,b){return prompt(a,b)},dataMessage:function(a){var b=top.ICEcoder.content.contentWindow.document.getElementById("dataMessage");b.style.display="block";b.innerHTML=a},update:function(){confirm(top.t["Please note for..."])? - (top.ICEcoder.showHide("show",top.get("loadingMask")),window.location="lib/updater.php"):window.open("https://icecoder.net")},updated:function(){top.get("blackMask").style.visibility="visible";top.get("mediaContainer").innerHTML='

    Thanks for updating to v'+top.ICEcoder.versionNo+'!

    Click anywhere to continue using ICEcoder...

    '},xhrObj:function(){try{return new XMLHttpRequest}catch(a){}try{return new ActiveXObject("Msxml3.XMLHTTP")}catch(a){}try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(a){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(a){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(a){}try{return new ActiveXObject("Microsoft.XMLHTTP")}catch(a){}return null}, - openBugReport:function(){"off"==top.ICEcoder.bugReportStatus&&top.ICEcoder.message(top.t["You can start..."]);"error"==top.ICEcoder.bugReportStatus&&top.ICEcoder.message(top.t["Error cannot find..."]);"ok"==top.ICEcoder.bugReportStatus&&top.ICEcoder.message(top.t["No new errors..."]);if("bugs"==top.ICEcoder.bugReportStatus){var a=top.ICEcoder.openFiles.indexOf(top.ICEcoder.bugReportPath.replace(/\|/g,"/"));-1/g,">").replace(/"/g, - """).replace(/'/g,"'")},printCode:function(){var a=top.ICEcoder.getcMInstance();var b=top.ICEcoder.getcMdiffInstance();a=-1ICEcoder code output
    '+top.ICEcoder.xssClean(a.getValue())+"
    ";b.focus();b.print();a.focus()},indicateChanges:function(){if(!top.ICEcoder.loadingFile){var a= - "ICEcoder v "+top.ICEcoder.versionNo;for(var b=1;b<=top.ICEcoder.savedPoints.length;b++)if(top.ICEcoder.savedPoints[b-1]!=top.ICEcoder.getcMInstance(b).changeGeneration()){a+=" \u2744";break}top.document.title=a}},switchTab:function(a,b){a!==top.ICEcoder.selectedTab&&(top.ICEcoder.prevTab=top.ICEcoder.selectedTab);ICEcoder.selectedTab=a;var c=ICEcoder.getcMInstance();var d=ICEcoder.getcMdiffInstance();if(-1 '+b.slice(b.lastIndexOf("/")).replace(/\//,"");top.get("tab"+top.ICEcoder.openFiles.length).title="/"+top.ICEcoder.openFiles[top.ICEcoder.openFiles.length-1].replace(/\//, - "");top.ICEcoder.setTabWidths();top.ICEcoder.redoTabHighlight(top.ICEcoder.openFiles.length);top.ICEcoder.selectedTab=top.ICEcoder.openFiles.length;top.ICEcoder.savedPoints.push(0);top.ICEcoder.savedContents.push("");a||top.ICEcoder.setPreviousFiles()},nextTab:function(){top.ICEcoder.switchTab(top.ICEcoder.selectedTab+1<=top.ICEcoder.openFiles.length?top.ICEcoder.selectedTab+1:1,"noFocus")},previousTab:function(){top.ICEcoder.switchTab(1<=top.ICEcoder.selectedTab-1?top.ICEcoder.selectedTab-1:top.ICEcoder.openFiles.length, - "noFocus")},renameTab:function(a,b){top.ICEcoder.openFiles[a-1]=b;var c=top.ICEcoder.openFiles[a-1];top.get("tab"+a).innerHTML=' '+c.slice(c.lastIndexOf("/")).replace(/\//, - "");top.get("tab"+a).title="/"+top.ICEcoder.openFiles[a-1].replace(/\//,"")},redoTabHighlight:function(a){for(var b,c,d=1;d<=ICEcoder.savedPoints.length;d++)top.get("tab"+d).childNodes[0]&&(top.get("tab"+d).childNodes[0].childNodes[0].style.backgroundColor=ICEcoder.savedPoints[d-1]!=top.ICEcoder.getcMInstance(d).changeGeneration()?"#b00":"transparent"),b=d==a?top.ICEcoder.tabFGselected:top.ICEcoder.tabFGnormalTab,"undefined"!=typeof top.ICEcoder.openFiles[d-1]&&"/[NEW]"!=top.ICEcoder.openFiles[d- - 1]&&(c=top.ICEcoder.filesFrame.contentWindow.document.getElementById(top.ICEcoder.openFiles[d-1].replace(/\//g,"|")))&&(c.style.backgroundColor=d==a?top.ICEcoder.tabBGcurrent:top.ICEcoder.tabBGopen,c.style.color=d==a?top.ICEcoder.tabFGcurrent:top.ICEcoder.tabFGopenFile),top.get("tab"+d).style.color=b,top.get("tab"+d).style.background=d==a?top.ICEcoder.tabBGcurrent:top.ICEcoder.tabBGopen},closeTab:function(a,b,c){a||(a=top.ICEcoder.selectedTab);ICEcoder.getcMInstance();ICEcoder.getcMdiffInstance(); - var d=!0;c||ICEcoder.savedPoints[a-1]==top.ICEcoder.getcMInstance(a).changeGeneration()||(d=top.ICEcoder.ask(top.t["You have made..."]));if(d){c=top.ICEcoder.openFiles[a-1];for(d=a;db?parseInt(c*g,10)-parseInt(c*(g-1),10):150,e=0==g?53:parseInt(top.get("tab"+g).style.left,10),f=0==g?0:parseInt(top.get("tab"+g).style.width, - 10)+18,a?d=-18:(top.get("tab"+(g+1)).style.left=e+f+"px",top.get("tab"+(g+1)).style.width=d+"px"),top.ICEcoder.tabLeftPos.push(e+f);top.get("newTab").style.left=e+f+d+18+"px"}},tabDragStart:function(a){top.ICEcoder.draggingTab=a;top.ICEcoder.diffStartX=top.ICEcoder.mouseX;top.ICEcoder.tabDragMouseXStart=(top.ICEcoder.mouseX-(parseInt(top.ICEcoder.files.style.width,10)+53+18))%150;top.get("tab"+a).style.zIndex=2;for(var b=1;b<=top.ICEcoder.openFiles.length;b++)top.get("tab"+b).className=b!==a?"tab tabSlide": - "tab tabDrag"},tabDragMove:function(){var a=parseInt(top.get("tab"+top.ICEcoder.openFiles.length).style.width,10)+18;top.ICEcoder.thisLeft=a=53<=top.ICEcoder.tabDragMouseX?top.ICEcoder.tabDragMouseX<=parseInt(top.get("newTab").style.left,10)-a?top.ICEcoder.tabDragMouseX:parseInt(top.get("newTab").style.left,10)-a:53;top.get("tab"+top.ICEcoder.draggingTab).style.left=a+"px";top.ICEcoder.dragTabNo=top.ICEcoder.draggingTab;for(var b=1;b<=top.ICEcoder.openFiles.length;b++){top.get("tab"+b).style.opacity= - b==top.ICEcoder.draggingTab?1:.5;var c=top.ICEcoder.tabLeftPos[b]?top.ICEcoder.tabLeftPos[b]-top.ICEcoder.tabLeftPos[b-1]:c;b!=top.ICEcoder.draggingTab&&(b=top.ICEcoder.tabLeftPos[b-1]?top.ICEcoder.tabLeftPos[b-1]-c:top.ICEcoder.tabLeftPos[b-1])}},tabDragEnd:function(){var a;top.ICEcoder.setTabWidths();for(var b=1;b<=top.ICEcoder.openFiles.length;b++)top.ICEcoder.thisLeft>= - top.ICEcoder.tabLeftPos[b-1]&&(a=top.ICEcoder.thisLeft==top.ICEcoder.tabLeftPos[0]?1:top.ICEcoder.dragTabNo>b?b+1:b),top.get("tab"+b).className="tab",top.get("tab"+b).style.opacity=1,b!=top.ICEcoder.dragTabNo?top.get("tab"+b).style.zIndex=1:setTimeout(function(){top.get("tab"+b).style.zIndex=1},150);if(top.ICEcoder.thisLeft&&!1!==top.ICEcoder.thisLeft){var c=[];for(b=1;b<=top.ICEcoder.openFiles.length;b++)c.push(b);c.splice(top.ICEcoder.dragTabNo-1,1);c.splice(a-1,0,top.ICEcoder.dragTabNo);ICEcoder.sortTabs(c)}top.ICEcoder.setTabWidths(); - top.ICEcoder.draggingTab=!1;top.ICEcoder.thisLeft=!1},sortTabs:function(a){var b;var c=[ICEcoder.savedPoints,ICEcoder.savedContents,ICEcoder.openFiles,ICEcoder.openFileMDTs,ICEcoder.openFileVersions,ICEcoder.cMInstances];var d=[[],[],[],[],[],[]];for(var e=0;eb-5;)e.undo(),a++;if(top.ICEcoder.savedPoints[top.ICEcoder.selectedTab-1]==e.changeGeneration())top.ICEcoder.startSnake();else for(b=1;b<=a;b++)e.redo()},0);if(top.ICEcoder.snakePlaying)return 37==d&&(top.ICEcoder.snakeDir= - "left"),39==d&&(top.ICEcoder.snakeDir="right"),38==d&&(top.ICEcoder.snakeDir="up"),40==d&&(top.ICEcoder.snakeDir="down"),!1;if(224==d||91==d||93==d)top.ICEcoder.cmdKey=!0;if(112===d){if(top.ICEcoder.codeZoomedOut)return;top.ICEcoder.codeZoomedOut=!0;var e=ICEcoder.getcMInstance();for(d=0;dLet\'s play
    snake


    Use arrow keys to eat your code

    (it returns afterwards of course) :-)';setTimeout(function(){top.ICEcoder.showHide("hide",top.get("blackMask"));top.get("mediaContainer").innerHTML= - "";top.ICEcoder.playSnake()},2E3)},playSnake:function(){var a=ICEcoder.getcMInstance();a.setOption("readOnly","nocursor");a.focus();top.ICEcoder.snakePreHistory=a.getHistory();top.ICEcoder.snakePreContent=a.getValue();top.ICEcoder.snakePreCursor=a.getCursor();a=Math.floor(50*Math.random());top.ICEcoder.snakePos=[[a,0],[a,-1],[a,-2],[a,-3],[a,-4]];top.ICEcoder.content.contentWindow.document.getElementById("game").style.display="block";top.ICEcoder.snakeDir="down";top.ICEcoder.doSnake();top.ICEcoder.snakeInt= - setInterval(function(){var a=[];a[0]=top.ICEcoder.snakePos[0][0]+("right"==top.ICEcoder.snakeDir?1:"left"==top.ICEcoder.snakeDir?-1:0);a[1]=top.ICEcoder.snakePos[0][1]+("down"==top.ICEcoder.snakeDir?1:"up"==top.ICEcoder.snakeDir?-1:0);top.ICEcoder.snakePos.unshift(a);top.ICEcoder.snakePos.pop();top.ICEcoder.doSnake()},100)},doSnake:function(){var a;var b=ICEcoder.getcMInstance();var c=b.defaultCharWidth();var d=b.defaultTextHeight();var e=top.ICEcoder.content.contentWindow.document.getElementById("game").innerHTML= - "";for(a=0;a
    ';top.ICEcoder.content.contentWindow.document.getElementById("game").innerHTML=e;e=b.coordsChar({top:top.ICEcoder.snakePos[0][1]*d+4,left:top.ICEcoder.snakePos[0][0]*c+60});var f=b.getLine(e.line);if(top.ICEcoder.snakePos[0][0]-1<=f.length-2){var g= - "";if("\t"===f.substr(e.ch,1))for(a=0;atop.ICEcoder.snakePos[0][0]||0>top.ICEcoder.snakePos[0][1]||top.ICEcoder.snakePos[0][0]*c+60>a.clientWidth||top.ICEcoder.snakePos[0][1]*d+4>a.clientHeight||e)clearInterval(top.ICEcoder.snakeInt),top.ICEcoder.content.contentWindow.document.getElementById("game").style.display="none", - b.setValue(top.ICEcoder.snakePreContent),top.ICEcoder.savedPoints[top.ICEcoder.selectedTab-1]=b.changeGeneration(),top.ICEcoder.savedContents[top.ICEcoder.selectedTab-1]=top.ICEcoder.snakePreContent,b.setHistory(top.ICEcoder.snakePreHistory),top.ICEcoder.indicateChanges(),top.ICEcoder.redoTabHighlight(top.ICEcoder.selectedTab),b.setOption("readOnly",!1),b.setCursor(top.ICEcoder.snakePreCursor),b.focus(),top.ICEcoder.snakePlaying=!1}}; \ No newline at end of file diff --git a/lib/icecoder.php b/lib/icecoder.php new file mode 100644 index 0000000..92640c2 --- /dev/null +++ b/lib/icecoder.php @@ -0,0 +1,13 @@ +invalidateOPCache($docRoot . $ICEcoderDir . "/data/index.php"); + $prevIndexData = file_get_contents($docRoot . $ICEcoderDir . "/data/index.php"); + if (false !== strpos($prevIndexData, "", "", $prevIndexData); $prevIndexData = unserialize($prevIndexData); @@ -31,25 +32,25 @@ function phpGrep($path, $base) { global $indexableFileExts, $prevIndexData, $indexData; $fp = opendir($path); - global $ICEcoder, $serverType, $docRoot, $ICEcoderDir; - if (!isset($ret)) {$ret="";}; - $slash = $serverType == strpos($path,"\\")>-1 ? "\\" : "/"; + global $ICEcoder, $docRoot, $ICEcoderDir; + if (!isset($ret)) {$ret = "";}; + $slash = -1 < strpos($path, "\\") ? "\\" : "/"; while($f = readdir($fp)) { // Ignore . and .. paths - if ($f == "." || $f == "..") continue; - $filePath = $path.$slash.$f; + if ("." === $f || ".." === $f) continue; + $filePath = $path . $slash . $f; $filePathExt = pathinfo($filePath, PATHINFO_EXTENSION); // Exclude the folder ICEcoder is running from - $rootPrefix = '/'.str_replace("/","\/",preg_quote(str_replace("\\","/",$docRoot))).'/'; + $rootPrefix = '/' . str_replace("/", "\/", preg_quote(str_replace("\\", "/", $docRoot))) . '/'; $localPath = preg_replace($rootPrefix, '', $filePath, 1); - if (strpos($localPath, $ICEcoderDir)===0) { + if (0 === strpos($localPath, $ICEcoderDir)) { continue; } - if(is_dir($filePath)) { + if (is_dir($filePath)) { $ret .= phpGrep($filePath, $base); } else { // Check if we should scan within this file, by only considering files that may contain functions & classes - if (in_array($filePathExt, $indexableFileExts) === false) { + if (false === in_array($filePathExt, $indexableFileExts)) { continue; } // Check if file appears to be the same (same size and mtime), if so, continue as we'll assume it's not changed @@ -69,14 +70,14 @@ function phpGrep($path, $base) { } $bFile = false; // Exclude banned files - for ($i=0;$iinvalidateOPCache($docRoot . $ICEcoderDir . "/data/git-diff.php"); + $gitDiffData = file_get_contents($docRoot . $ICEcoderDir . "/data/git-diff.php"); if (strpos($gitDiffData, "", "", $gitDiffData); @@ -218,9 +220,10 @@ if (!isset($_GET['timestamp']) || !isset($prevIndexData["timestamps"]) || $_GET[ } // If we have a data/git-content.php file - if (file_exists($docRoot.$ICEcoderDir."/data/git-content.php")) { + if (file_exists($docRoot . $ICEcoderDir . "/data/git-content.php")) { // Get serialized array back out of PHP file inside a comment block as git data for git content usage - $gitContent = file_get_contents($docRoot.$ICEcoderDir."/data/git-content.php"); + $systemClass->invalidateOPCache($docRoot . $ICEcoderDir . "/data/git-content.php"); + $gitContent = file_get_contents($docRoot . $ICEcoderDir . "/data/git-content.php"); if (strpos($gitContent, "", "", $gitContent); @@ -230,7 +233,7 @@ if (!isset($_GET['timestamp']) || !isset($prevIndexData["timestamps"]) || $_GET[ } // Store the serialized array in PHP comment block for next time - file_put_contents($docRoot.$ICEcoderDir."/data/index.php", ""); + file_put_contents($docRoot . $ICEcoderDir . "/data/index.php", ""); // Output what we have in our index... } else { $output = $prevIndexData; @@ -243,7 +246,7 @@ if (!isset($_GET['timestamp']) || !isset($prevIndexData["timestamps"]) || $_GET[ "browser" => (int) $_GET['timestamp'], "changed" => false ] - ]; + ]; } // Output the JSON diff --git a/lib/language-modes-partial.js b/lib/language-modes-partial.js deleted file mode 100644 index 5d520ec..0000000 --- a/lib/language-modes-partial.js +++ /dev/null @@ -1,29 +0,0 @@ -// Provide a fileName and get fileExt and mode set based on supported languages - -fileExt = fileName.split("."); -fileExt = fileExt[fileExt.length-1]; -var mode = - fileExt == "js" || fileExt == "json" ? "text/javascript" - : fileExt == "coffee" ? "text/x-coffeescript" - : fileExt == "ts" ? "application/typescript" - : fileExt == "rb" ? "text/x-ruby" - : fileExt == "py" ? "text/x-python" - : fileExt == "mpy" ? "text/x-python" - : fileExt == "css" ? "text/css" - : fileExt == "less" ? "text/x-less" - : fileExt == "md" ? "text/x-markdown" - : fileExt == "xml" ? "application/xml" - : fileExt == "sql" ? "text/x-mysql" // also text/x-sql, text/x-mariadb, text/x-cassandra or text/x-plsql - : fileExt == "erl" ? "text/x-erlang" - : fileExt == "yaml" ? "text/x-yaml" - : fileExt == "java" ? "text/x-java" - : fileExt == "jl" ? "text/x-julia" - : fileExt == "c" ? "text/x-csrc" - : fileExt == "cpp" ? "text/x-c++src" - : fileExt == "ino" ? "text/x-c++src" - : fileExt == "cs" ? "text/x-csharp" - : fileExt == "go" ? "text/x-go" - : fileExt == "lua" ? "text/x-lua" - : fileExt == "pl" ? "text/x-perl" - : fileExt == "scss" ? "text/x-sass" - : "application/x-httpd-php"; diff --git a/lib/login.php b/lib/login.php index 11833ae..905ba83 100644 --- a/lib/login.php +++ b/lib/login.php @@ -1,73 +1,74 @@ ICEcoder <?php -echo $ICEcoder["versionNo"]." : "; -echo $ICEcoder["password"] == "" && !$ICEcoder["multiUser"] ? "Setup" : "Login"; +echo $ICEcoder["versionNo"] . " : "; +echo $settingPW ? "Setup" : "Login"; ?> - - + + + -setTimeout(function(){document.getElementById('screenContainer').style.opacity=1},50)"> +setTimeout(function(){document.getElementById('screenContainer').style.opacity = '1'}, 50)">
    - ICEcoder + ICEcoder
    v
    -
    onsubmit="return checkCanSubmit();"> + onsubmit="return checkCanSubmit();">

    '.PHP_EOL;}; + if ($ICEcoder["multiUser"]) {echo '

    ';}; ?> - onkeyup="pwStrength(this.value)" onchange="pwStrength(this.value)" onpaste="pwStrength(this.value)">
    + onkeyup="pwStrength(this.value)" onchange="pwStrength(this.value)" onpaste="pwStrength(this.value)">
    '. - '
    10+
      '. - '
    upper
      '. - '
    lower
      '. - '
    number
      '. - '
    special
    '. - '
    '; + echo '
    '. + '
    10+
      ' . + '
    upper
      ' . + '
    lower
      ' . + '
    number
      ' . + '
    special
    ' . + '
    '; } ?>
    " class="button"> Using over non-https connection.
    SSL is recommended!
    '; + echo '
    Using over non-https connection.
    TLS is recommended!
    '; } if($ICEcoder["multiUser"] && $ICEcoderSettings["enableRegistration"]){ - echo ''; + echo ''; } ?> '.$t['disable further registrations'].'
    '; + if ("" === $ICEcoder["password"] && false === $ICEcoder["multiUser"]) { + echo '
    ' . $t['disable further registrations'] . '
    '; } - if ($ICEcoder["password"] == "" || $ICEcoder["multiUser"]) { - echo '
    '.$t['auto-check for updates'].'
    '; + if ("" === $ICEcoder["password"] || true === $ICEcoder["multiUser"]) { + echo '
    ' . $t['auto-check for updates'] . '
    '; } - if (!$ICEcoder["multiUser"]) { echo '';}; + if (false === $ICEcoder["multiUser"]) { echo '';}; ?> "> @@ -77,14 +78,15 @@ echo $ICEcoder["password"] == "" && !$ICEcoder["multiUser"] ? "Setup" : "Login"; - \ No newline at end of file + diff --git a/lib/plugins-display.php b/lib/plugins-display.php index 1a6e432..d426254 100644 --- a/lib/plugins-display.php +++ b/lib/plugins-display.php @@ -1,42 +1,54 @@ '.$ICEcoder[

    '; - }; + // Work out the plugins to display to the user + $pluginsDisplay = ""; + for ($i = 0; $i < count($ICEcoder["plugins"]); $i++) { + $target = explode(":", $ICEcoder["plugins"][$i][4]); + $pluginsDisplay .= + '' . $ICEcoder[

    '; + }; - // If we're updating plugins, update those shown - if (isset($_GET['updatedPlugins'])) { - echo ""; - } + // If we're updating plugins, update those shown + if (isset($_GET['updatedPlugins'])) { + echo ""; + } - // Work out what plugins we'll need to set on a setInterval - $onLoadExtras = ""; - for ($i=0;$i - - + + \ No newline at end of file +?> diff --git a/lib/plugins-manager.css b/lib/plugins-manager.css deleted file mode 100644 index 2ce6793..0000000 --- a/lib/plugins-manager.css +++ /dev/null @@ -1,36 +0,0 @@ -/* First, reset everything to a standard */ -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, font, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td { - border: 0; - margin: 0; - padding: 0; - outline: 0; - font-size: 12px; - vertical-align: top; -} - -body {overflow: hidden; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -h1 {font-size: 36px; font-weight: normal; color: #888; margin-bottom: 20px} -a {color: #fff; text-decoration: none} -input {padding: 4px; border: 1px solid #555; background-color: #444; color: #fff} -input:focus { - outline: none; - -webkit-box-shadow: 0 0 10px 1px rgba(0,198,255,0.7); - -moz-box-shadow: 0 0 10px 1px rgba(0,198,255,0.7); - box-shadow: 0 0 10px 1px rgba(0,198,255,0.7); -} - -.pluginsManager {font-family: arial, verdana, helvetica, sans-serif; background-color: #1c1c19; color: #fff; padding: 20px} \ No newline at end of file diff --git a/lib/plugins-manager.php b/lib/plugins-manager.php index 1301960..f6ad67b 100644 --- a/lib/plugins-manager.php +++ b/lib/plugins-manager.php @@ -1,172 +1,208 @@ open($zipFile); + // Now unpack the zip + $zip = new ZipArchive; + $zip->open($zipFile); - // Create all files & dirs, in 1kb chunks - for($i=0; $i<$zip->numFiles; $i++) { - $name = $zip->getNameIndex($i); + // Create all files & dirs, in 1kb chunks + for ($i = 0; $i < $zip->numFiles; $i++) { + $name = $zip->getNameIndex($i); - // Determine output filename - $file = $target.$name; + // Determine output filename + $file = $target . $name; - // Create the directories if necessary - $dir = dirname($file); - if (!is_dir($dir)) mkdir($dir, 0777, true); + // Create the directories if necessary + $dir = dirname($file); + if (false === is_dir($dir)) mkdir($dir, 0777, true); - // Read from zip and write to disk - $fpr = $zip->getStream($name); - if (!is_dir($file)) { - $fpw = fopen($file, 'w'); - while ($data = fread($fpr, 1024)) { - fwrite($fpw, $data); - } - fclose($fpw); - } - fclose($fpr); - } - $zip->close(); + // Read from zip and write to disk + $fpr = $zip->getStream($name); + if (false === is_dir($file)) { + $fpw = fopen($file, 'w'); + while ($data = fread($fpr, 1024)) { + fwrite($fpw, $data); + } + fclose($fpw); + } + fclose($fpr); + } + $zip->close(); - // Remove the tmp zip file - unlink($zipFile); + // Remove the tmp zip file + unlink($zipFile); - // Start creating a new chunk for the plugins settings - $settingsNew = '"plugins" => array('; + // Start creating a new chunk for the plugins settings + $settingsNew = '"plugins" => array('; - // Set all the old plugins - for ($i=0; $iif (top.confirm('".$t['ICEcoder needs to...']."')) {top.window.location.reload(true);} else {window.location='plugins-manager.php?updatedPlugins&csrf='+top.ICEcoder.csrf;}"; - } else { - header("Location: plugins-manager.php?updatedPlugins&csrf=".$_SESSION["csrf"]); - echo ""; - } - die("".$t['saving plugins'].""); - } else { - echo ""; - } + // Now update the config file + if (is_writeable("../data/".$settingsFile)) { + $fh = fopen("../data/".$settingsFile, 'w'); + fwrite($fh, $settingsContents); + fclose($fh); + // Finally, reload ICEcoder itself if plugin requires it or just the iFrame screen for the user if it doesn't + if ("install" === $_GET['action'] && "true" === $pluginsData[$_GET['plugin']]['reload']) { + echo ""; + } else { + header("Location: plugins-manager.php?updatedPlugins&csrf=" . $_SESSION["csrf"]); + echo ""; + } + die("" . $t['saving plugins'] . ""); + } else { + echo ""; + } } // Function to delete the plugin dir & files/dirs inside function deletePlugin($dir) { - $mydir = opendir($dir); - while(false !== ($file = readdir($mydir))) { - if($file != "." && $file != "..") { - chmod($dir.$file, 0777); - if(is_dir($dir.$file)) { + global $t; + $theDir = opendir($dir); + while(false !== ($file = readdir($theDir))) { + if($file !== "." && $file !== "..") { + chmod($dir . $file, 0777); + if(is_dir($dir . $file)) { chdir('.'); - deletePlugin($dir.$file.'/'); - if(is_dir($dir.$file)) { - rmdir($dir.$file) or DIE("".$t['couldnt delete dir'].": $dir$file
    "); - } + deletePlugin($dir . $file . '/'); + if(is_dir($dir . $file)) { + rmdir($dir . $file) or die("" . $t['couldnt delete dir'] . ": $dir$file
    "); + } + } + else { + unlink($dir . $file) or die("" . $t['couldnt delete file'] . ": $dir$file
    "); } - else - unlink($dir.$file) or DIE("".$t['couldnt delete file'].": $dir$file
    "); } } - closedir($mydir); + closedir($theDir); rmdir($dir); } ?> @@ -174,97 +210,107 @@ function deletePlugin($dir) { -ICEcoder <?php echo $ICEcoder["versionNo"];?> plugins manager - - - + ICEcoder <?php echo $ICEcoder["versionNo"];?> plugins manager + + + + - +

    -','writingPlugins')" style="position: absolute; top: 26px; right: 20px">
    -
    - 0) { - ?> -
    -


    +','plugins')" style="position: absolute; top: 26px; right: 20px">
    +
    + +
    +


    -
    - - - - - - - - '; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - } - echo ''; - echo ''; - echo ''; - echo ''; - ?> -
    '.$plugins[$i][0].''.$plugins[$i][0].'
    '.$t['Update'].'
    - "> -
    -
    - +
    + + + + + + + + '; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo ''; + echo ''; + echo ''; + echo ''; + ?> +
    ' . $plugins[$i][0] .
+                            '' . $plugins[$i][0] . '
    ' . $t['Update'] . '
    + "> +
    +
    + -
    -


    +
    +


    - 0) { - ?> - - '.PHP_EOL; - } + +
    + ' . PHP_EOL; + } - $installUninstallButton = '
    '.$t['Install'].'
    '; - for ($j=0; $j'.$t['Uninstall'].''; - } - } + $installUninstallButton = '
    ' . $t['Install'] . '
    '; + for ($j = 0; $j < count($plugins); $j++) { + if ($pluginsData[$i]['name'] == $plugins[$j][0]) { + $installUninstallButton = '
    ' . $t['Uninstall'] . '
    '; + } + } - $reloadExtra = $pluginsData[$i]['reload'] == 'true' ? '
    '.$t['Reload after install...'].'' : ''; - echo '
    '; - echo ''; - $styleExtra = ($i % 2 == 1 || $i == count($pluginsData)-1) ? "0" : "30px"; - echo ''; + $reloadExtra = "true" === $pluginsData[$i]['reload'] ? '
    ' . $t['Reload after install...'] . '' : ''; + echo ''; + echo ''; + $styleExtra = (1 === $i % 2 || $i === count($pluginsData) - 1) ? "0" : "30px"; + echo ''; - if ($i % 2 == 1 || $i == count($pluginsData)-1) { - echo PHP_EOL.''.PHP_EOL; - } - } - ?> -
    '.$pluginsData[$i]['name'].''.$pluginsData[$i]['name'].$reloadExtra.''.$installUninstallButton.''.$pluginsData[$i]['name'] . '' . $pluginsData[$i]['name'] . $reloadExtra . '' . $installUninstallButton . '
    + if (1 === $i % 2 || $i == count($pluginsData) - 1) { + echo PHP_EOL . '' . PHP_EOL; + } + } + ?> + - -
    + +
    diff --git a/lib/properties.css b/lib/properties.css deleted file mode 100644 index 13c8930..0000000 --- a/lib/properties.css +++ /dev/null @@ -1,28 +0,0 @@ -/* First, reset everything to a standard */ -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, font, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td { - border: 0; - margin: 0; - padding: 0; - outline: 0; - font-size: 12px; - vertical-align: top; -} - -body {overflow: hidden;} - -h1 {font-size: 36px; font-weight: normal; color: #888; margin-bottom: 20px} -th {padding-left: 23px; padding-bottom: 5px} -th, td {text-align: left; font-size: 10px} - -.properties {font-family: arial, verdana, helvetica, sans-serif; background-color: #1c1c19; color: #fff; padding: 20px} -.properties .column {display: inline-block; width: 210px; font-size: 10px; float: left} - -.properties .update {position: absolute; bottom: 0; right: 20px; padding: 5px 10px; font-size: 18px; background-color: rgba(0,198,255,0.7); opacity: 0.1; cursor: pointer} \ No newline at end of file diff --git a/lib/properties.php b/lib/properties.php index 06bce73..ced0430 100644 --- a/lib/properties.php +++ b/lib/properties.php @@ -1,26 +1,27 @@ alert('Sorry - problem with file/folder requested');window.history.back();"); +if (!file_exists($fileName) || 0 !== strpos(str_replace("\\", "/", $fileName),$docRoot)) { + die(""); } ?> - + ICEcoder <?php echo $ICEcoder["versionNo"];?> file/folder properties - + + - +

    @@ -29,19 +30,19 @@ if (!file_exists($fileName) || strpos(str_replace("\\","/",$fileName),$docRoot) $bytes = filesize($fileName); // If it's a dir, get the dir size if (is_dir($fileName)) { - $io = popen('/usr/bin/du -sb '.$fileName, 'r'); - $bytes = intval(fgets($io,80)); + $io = popen('/usr/bin/du -sb ' . $fileName, 'r'); + $bytes = intval(fgets($io, 80)); pclose($io); } // Change into kilobytes -$outputSize = ($bytes/1024); +$outputSize = ($bytes / 1024); $outputUnit = "kb"; // Maybe we should show in megabytes? if ($outputSize >= 1024) { - $outputSize = ($outputSize/1024); + $outputSize = ($outputSize / 1024); $outputUnit = "mb"; } -echo number_format($outputSize, 2, '.', '').$outputUnit." (".number_format($bytes)." bytes)"; +echo number_format($outputSize, 2, '.', '') . $outputUnit . " (" . number_format($bytes) . " bytes)"; ?> : : @@ -50,7 +51,7 @@ echo number_format($outputSize, 2, '.', '').$outputUnit." (".number_format($byte : / -: +:

    :
    @@ -58,9 +59,9 @@ echo number_format($outputSize, 2, '.', '').$outputUnit." (".number_format($byte $dirList = scandir($fileName); $dirCount = 0; $fileCount = 0; -for ($i=0; $i @@ -83,34 +84,34 @@ echo $chmodInfo; - - - + + + - - - + + + - - - + + +
    onClick="changePerms();showButton()"> onClick="changePerms();showButton()"> onClick="changePerms();showButton()"> onClick="changePerms(); showButton()"> onClick="changePerms(); showButton()"> onClick="changePerms(); showButton()">
    onClick="changePerms();showButton()"> onClick="changePerms();showButton()"> onClick="changePerms();showButton()"> onClick="changePerms(); showButton()"> onClick="changePerms(); showButton()"> onClick="changePerms(); showButton()">
    onClick="changePerms();showButton()"> onClick="changePerms();showButton()"> onClick="changePerms();showButton()"> onClick="changePerms(); showButton()"> onClick="changePerms(); showButton()"> onClick="changePerms(); showButton()">
    :
    -
    - + + ">
    @@ -118,58 +119,60 @@ $execVars = array(1,3,5,7);
    - \ No newline at end of file + diff --git a/lib/requirements.php b/lib/requirements.php index b02951f..5f90c73 100644 --- a/lib/requirements.php +++ b/lib/requirements.php @@ -1,5 +1,5 @@ @@ -42,47 +42,48 @@ if (!$reqsPassed) { - - + + + - +
    - ICEcoder + ICEcoder
    v
    Requirements problem!



    Sorry, but ICEcoder has a problem
    running on your server:



    and needs 7.0 or above

    "; + if (true === in_array("phpVersion", $reqsFailures)) { + echo "Your PHP version is " . phpversion() . "
    and needs 7.0 or above

    "; } - if (in_array("phpSession", $reqsFailures)) { + if (true === in_array("phpSession", $reqsFailures)) { echo "You don't appear to have a
    working PHP session

    "; } - if (in_array("phpReadFile", $reqsFailures)) { + if (true === in_array("phpReadFile", $reqsFailures)) { echo "You don't seem to be able
    to read the config file

    "; } - if (in_array("phpAllowURLFOpen", $reqsFailures)) { + if (true === in_array("phpAllowURLFOpen", $reqsFailures)) { echo "You don't seem to have
    allow_url_fopen enabled

    "; } - if (in_array("phpCreateConfig", $reqsFailures)) { + if (true === in_array("phpCreateConfig", $reqsFailures)) { echo "Cannot update config file:
    data/".$configSettings."
    Please check write permissions
    on data dir and reload page

    "; } - if (in_array("phpCreateSettings", $reqsFailures)) { + if (true === in_array("phpCreateSettings", $reqsFailures)) { echo "Couldn't create:
    data/$settingsFile
    Maybe you need write
    permissions on the data dir?

    "; } - if (in_array("phpUpdateSettings", $reqsFailures)) { + if (true === in_array("phpUpdateSettings", $reqsFailures)) { echo "Can't update config file:
    data/".$settingsFile."
    Please check write permissions
    on data dir and reload page

    "; } - if (in_array("phpCreateSettingsFileAddr", $reqsFailures)) { + if (true === in_array("phpCreateSettingsFileAddr", $reqsFailures)) { echo "Couldn't create:
    data/$settingsFileAddr
    Maybe you need write
    permissions on the data dir?

    "; } - if (in_array("systemIPRestriction", $reqsFailures)) { + if (true === in_array("systemIPRestriction", $reqsFailures)) { echo "Not in permitted IPs list

    "; } ?> diff --git a/lib/session-active-ping.php b/lib/session-active-ping.php index f0b08be..383e60c 100644 --- a/lib/session-active-ping.php +++ b/lib/session-active-ping.php @@ -1,5 +1,5 @@ - array( - 'timeout' => 60 // secs - ) -)); +$systemClass = new System; +$systemClass->setErrorHandling(); +$systemClass->setTimeZone(); +$context = $systemClass->setStreamContext(); // Start a session if we haven't already -if(!isset($_SESSION)) { - ini_set('session.use_cookies','1'); // Use cookies not URL parameters - ini_set('session.use_only_cookies','1'); // Force use of cookies and nothing else - ini_set('session.name','ICEcoder_Cookie'); // Set a seperate cookie session name - ini_set('session.cookie_lifetime','0'); // Until the browser restarts by default - ini_set('session.cookie_domain',''); // This domain only -// ini_set('session.cookie_path',str_replace($_SERVER['DOCUMENT_ROOT'],'',dirname(dirname(__FILE__)))); // ICEcoder path only, fails ON IE - ini_set('session.use_trans_sid','0'); // Ensure this insecure feature is disabled - ini_set('session.hash_function','sha512'); // Use Sha512 for session - ini_set('session.hash_bits_per_character','6'); // Specify hash scheme of 0-9,a-v,A-Z,-,, -// ini_set('session.use_strict_mode','1'); // Reject any session ID that was user provided and not generated by the session (since PHP 5.5.2) - ini_set('session.httponly','1'); // Only allow http protocol (ie, not JS) access to the cookie (since PHP 5.2.0) - ini_set('session.save_path',dirname(__FILE__).'/../tmp'); // Localise the session files to /tmp +if(false === isset($_SESSION)) { + ini_set('session.use_cookies', '1'); // Use cookies not URL parameters + ini_set('session.use_only_cookies', '1'); // Force use of cookies and nothing else + ini_set('session.name', 'ICEcoder_Cookie'); // Set a seperate cookie session name + ini_set('session.cookie_lifetime', '0'); // Until the browser restarts by default + ini_set('session.cookie_domain', ''); // This domain only +// ini_set('session.cookie_path', str_replace($_SERVER['DOCUMENT_ROOT'], '', dirname(dirname(__FILE__)))); // ICEcoder path only, fails ON IE + ini_set('session.use_trans_sid', '0'); // Ensure this insecure feature is disabled + ini_set('session.hash_function', 'sha512'); // Use Sha512 for session + ini_set('session.hash_bits_per_character', '6'); // Specify hash scheme of 0-9,a-v,A-Z,-,, +// ini_set('session.use_strict_mode', '1'); // Reject any session ID that was user provided and not generated by the session (since PHP 5.5.2) + ini_set('session.httponly', '1'); // Only allow http protocol (ie, not JS) access to the cookie (since PHP 5.2.0) + ini_set('session.save_path', dirname(__FILE__) . '/../tmp'); // Localise the session files to /tmp if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') { - ini_set('session.cookie_secure','1'); // Only allows access to session ID when protocol is HTTPS, switched on under 'if https' condition + ini_set('session.cookie_secure', '1'); // Only allows access to session ID when protocol is HTTPS, switched on under 'if https' condition } - session_start(); // Finally, start the session! - if (!isset($_SESSION['csrf'])){ - session_regenerate_id(true); // Create a new ID to help prevent fixation + session_start(); // Finally, start the session! + if (false === isset($_SESSION['csrf'])){ + session_regenerate_id(true); // Create a new ID to help prevent fixation } } // Set the language file, if now possible -if (isset($_SESSION['text'])) { +if (true === isset($_SESSION['text'])) { $text = $_SESSION['text']; $t = $text['settings-common']; } // Copy over backups if we've just updated to new version (TODO: can be moved to updater.php one day after 7.0 released) -if (isset($_GET['display']) && $_GET['display'] === "updated") { +if (true === isset($_GET['display']) && $_GET['display'] === "updated") { // If the backups dir doesn't exist, or it does but is empty if ( - !file_exists(dirname(__FILE__)."/../data/backups") || - count(array_diff(scandir(dirname(__FILE__)."/../data/backups"), ['.', '..'])) === 0 + false === file_exists(dirname(__FILE__) . "/../data/backups") || + count(array_diff(scandir(dirname(__FILE__) . "/../data/backups"), ['.', '..'])) === 0 ) { // If the old version has some backups to move over - if (count(array_diff(scandir(dirname(__FILE__)."/../tmp/oldVersion/backups"), ['.', '..'])) > 0) { + if (count(array_diff(scandir(dirname(__FILE__) . "/../tmp/oldVersion/backups"), ['.', '..'])) > 0) { // If the data dir is writable - if (is_writable(dirname(__FILE__)."/../data")) { + if (is_writable(dirname(__FILE__) . "/../data")) { // Remove the backups dir if it's there and writable - if (file_exists(dirname(__FILE__)."/../data/backups") && is_writable(dirname(__FILE__)."/../data")) { - rmdir(dirname(__FILE__)."/../data/backups"); + if (file_exists(dirname(__FILE__) . "/../data/backups") && is_writable(dirname(__FILE__) . "/../data")) { + rmdir(dirname(__FILE__) . "/../data/backups"); } // Move backups dir from old version to current version - rename(dirname(__FILE__)."/../tmp/oldVersion/backups", dirname(__FILE__)."/../data/backups"); + rename(dirname(__FILE__) . "/../tmp/oldVersion/backups", dirname(__FILE__) . "/../data/backups"); } } } } // Check requirements meet minimum spec -include(dirname(__FILE__)."/requirements.php"); +include dirname(__FILE__) . "/requirements.php"; // Create a backups dir in the data dir if it doesn't exist yet -if (!file_exists(dirname(__FILE__)."/../data/backups")) { - mkdir(dirname(__FILE__)."/../data/backups"); +if (false === file_exists(dirname(__FILE__) . "/../data/backups")) { + mkdir(dirname(__FILE__) . "/../data/backups"); +} + +// Walk through possibilities in the order we'd like to determine an user IP +function getUserIP() { + return $_SERVER['HTTP_CLIENT_IP'] + ?? $_SERVER['HTTP_X_FORWARDED_FOR'] + ?? $_SERVER['HTTP_X_FORWARDED'] + ?? $_SERVER['HTTP_FORWARDED_FOR'] + ?? $_SERVER['HTTP_FORWARDED'] + ?? $_SERVER['REMOTE_ADDR'] + ?? 'Unknown'; } // Get data from a fopen or CURL connection -function getData($url,$type='fopen',$dieMessage=false,$timeout=60) { - global $context; +function getData($url, $type='fopen', $dieMessage = false, $timeout = 60) { + global $context, $systemClass; // Request is to connect via CURL - if ($type == "curl" && function_exists('curl_init')) { + if ($type === "curl" && function_exists('curl_init')) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); @@ -100,13 +106,15 @@ function getData($url,$type='fopen',$dieMessage=false,$timeout=60) { 'timeout' => $timeout // secs ) )); - $data = @file_get_contents($url,false,$context); + $systemClass->invalidateOPCache($url); + $data = @file_get_contents($url, false, $context); if (!$data) { - $data = @file_get_contents(str_replace("https:","http:",$url), false, $context); + $data = @file_get_contents(str_replace("https:", "http:", $url), false, $context); } } elseif (file_exists($url)) { - $data = file_get_contents($url); - } + $systemClass->invalidateOPCache($url); + $data = file_get_contents($url); + } // Return data or die with message if ($data) { return $data; @@ -114,17 +122,18 @@ function getData($url,$type='fopen',$dieMessage=false,$timeout=60) { die($dieMessage); exit; } else { - return 'no data'; + return ''; } } // Require a re-index dir/file data next time we index function requireReIndexNextTime() { // If we have a data/index.php file - global $docRoot, $ICEcoderDir; - if (file_exists($docRoot.$ICEcoderDir."/data/index.php")) { + global $docRoot, $ICEcoderDir, $systemClass; + if (true === file_exists($docRoot . $ICEcoderDir . "/data/index.php")) { // Get serialized array back out of PHP file inside a comment block as prevIndexData - $prevIndexData = file_get_contents($docRoot.$ICEcoderDir."/data/index.php"); + $systemClass->invalidateOPCache($docRoot . $ICEcoderDir . "/data/index.php"); + $prevIndexData = file_get_contents($docRoot . $ICEcoderDir . "/data/index.php"); if (strpos($prevIndexData, "", "", $prevIndexData); @@ -133,22 +142,23 @@ function requireReIndexNextTime() { // Set timestamp back to epoch to force a re-index next time $prevIndexData['timestamps']['indexed'] = 0; - file_put_contents($docRoot.$ICEcoderDir."/data/index.php", ""); + file_put_contents($docRoot . $ICEcoderDir . "/data/index.php", ""); } } } // Logout if that's the action we're taking -if (isset($_GET['logout'])) { - include(dirname(__FILE__)."/../processes/on-user-logout.php"); - $_SESSION['loggedIn']=false; - $_SESSION['username']=false; +if (true === isset($_GET['logout'])) { + $extraProcessesClass = new ExtraProcesses(); + $extraProcessesClass->onUserLogout($_SESSION['username']); + $_SESSION['loggedIn'] = false; + $_SESSION['username'] = ""; session_destroy(); header("Location: ."); die("Logging you out..."); } -define('SALT_LENGTH',12); +define('SALT_LENGTH', 12); // Generate hash function generateHash($pw) { // Generate Bcrypt hash @@ -164,8 +174,8 @@ function verifyHash($pw, $orig) { : "NO MATCH"; } // Verify legacy sha1 hash - $origSalt = substr($orig,0,SALT_LENGTH); - return $origSalt.sha1($origSalt.$pw); + $origSalt = substr($orig, 0, SALT_LENGTH); + return $origSalt . sha1($origSalt . $pw); } // returns a number, whole or decimal or null @@ -202,7 +212,7 @@ function xssClean($data,$type) { // === url === if ($type == "url") { - if(preg_match("#^(?:(?:https?|ftp):{1})\/\/[^\"\s\\\\]*.[^\"\s\\\\]*$#iu",(string)$data,$match)) { + if(preg_match("#^(?:(?:https?|ftp):{1})\/\/[^\"\s\\\\]*.[^\"\s\\\\]*$#iu", (string)$data, $match)) { return $match[0]; } else { return 'javascript:void(0)'; @@ -221,7 +231,7 @@ function injClean($data) { } // returns a UTF8 based string with any UFT8 BOM removed -function toUTF8noBOM($string,$message=false) { +function toUTF8noBOM($string, $message = false) { global $text; $t = $text['settings-common']; @@ -234,15 +244,15 @@ function toUTF8noBOM($string,$message=false) { if (0 === strncmp($string, $bom, 3)) { // If there's a BOM followed by a Windows based (2 char) line ending // chop BOM off and prefix returned string with a PHP_EOL - if (0 === strncmp($string, $bom."\r\n", 3)) { - $string = PHP_EOL.substr($string, 3); + if (0 === strncmp($string, $bom . "\r\n", 3)) { + $string = PHP_EOL . substr($string, 3); // Else, simply chop off the BOM } else { $string = substr($string, 3); } } // Remove any other BOMs from view - $string = preg_replace('/'.$bom.'/','',$string); + $string = preg_replace('/' . $bom . '/', '', $string); // Test for any bad characters $teststring = $string; @@ -252,7 +262,7 @@ function toUTF8noBOM($string,$message=false) { if (!$strictUTF8 && strlen($teststringConverted) == strlen($teststringBroken)) { $string = utf8_encode($string); if ($message) { - echo "top.ICEcoder.message('".$t['Your document does...'].".');"; +// echo "parent.parent.ICEcoder.message('".$t['Your document does...'].".');"; } } } @@ -290,46 +300,46 @@ if (!function_exists('array_replace_recursive')) { } // Get number of versions total for a file -function getVersionsCount($fileLoc,$fileName) { +function getVersionsCount($fileLoc, $fileName) { global $context; $count = 0; $dateCounts = array(); $backupDateDirs = array(); // Establish the base, host and date dirs within... - $backupDirBase = str_replace("\\","/",dirname(__FILE__))."/../data/backups/"; - $backupDirHost = isset($ftpSite) ? parse_url($ftpSite,PHP_URL_HOST) : "localhost"; + $backupDirBase = str_replace("\\", "/", dirname(__FILE__)) . "/../data/backups/"; + $backupDirHost = isset($ftpSite) ? parse_url($ftpSite, PHP_URL_HOST) : "localhost"; // check if folder exists if local before enumerating contents if(!isset($ftpSite)) { - if(is_dir($backupDirBase.$backupDirHost)) { - $backupDateDirs = scandir($backupDirBase.$backupDirHost,1); + if(is_dir($backupDirBase . $backupDirHost)) { + $backupDateDirs = scandir($backupDirBase . $backupDirHost, 1); } } else { - $backupDateDirs = scandir($backupDirBase.$backupDirHost,1); + $backupDateDirs = scandir($backupDirBase . $backupDirHost, 1); } // Get rid of . and .. from date dirs array - for ($i=0; $iinvalidateOPCache($path); $data = file_get_contents($path); $data = str_replace("<"."?php\n/*\n\n", "", $data); $data = str_replace("\n\n*/\n?".">", "", $data); @@ -352,6 +365,6 @@ function serializedFileData($do, $path, $output=null) { return $data; } if ($do === "set") { - file_put_contents($path, "<"."?php\n/*\n\n".serialize($output)."\n\n*/\n?".">"); + file_put_contents($path, "<"."?php\n/*\n\n" . serialize($output) . "\n\n*/\n?" . ">"); } } diff --git a/lib/settings-save-current-files.php b/lib/settings-save-current-files.php index 1bc88b7..00392ab 100644 --- a/lib/settings-save-current-files.php +++ b/lib/settings-save-current-files.php @@ -31,7 +31,7 @@ if ($_SESSION['loggedIn'] && isset($_GET["saveFiles"]) && $_GET['saveFiles']) { fwrite($fh, $settingsContents); fclose($fh); } else { - echo ""; + echo ""; } // Update our last10Files var? @@ -50,10 +50,10 @@ if ($_SESSION['loggedIn'] && isset($_GET["saveFiles"]) && $_GET['saveFiles']) { fwrite($fh, $settingsContents); fclose($fh); } else { - echo ""; + echo ""; } } } } - echo ''; + echo ''; } diff --git a/lib/settings-screen.php b/lib/settings-screen.php index 5466389..54d6d76 100644 --- a/lib/settings-screen.php +++ b/lib/settings-screen.php @@ -10,9 +10,9 @@ $t = $text['settings-screen']; ICEcoder <?php echo $ICEcoder["versionNo"];?> settings screen - -/lib/codemirror.css?microtime="> - + + + - + '.PHP_EOL; + echo ''.PHP_EOL; +} + +// Do we have a tab to switch to? +$tabSwitchExtra = ""; +if (true === isset($_GET['tab'])) { + $tabSwitchExtra = "switchTab('" . $_GET['tab'] . "');"; } ?> + - +
    - +

    @@ -54,11 +61,7 @@ for ($i=0;$i
    :
    - https://github.com/mattpass/ICEcoder -

    - - :
    - + https://github.com/icecoder/ICEcoder

    :
    @@ -76,7 +79,7 @@ for ($i=0;$iTwitter
    Facebook
    Google Groups
    - GitHub
    + GitHub
    Email

    @@ -271,6 +274,16 @@ function findSequence(goal) {


    +
    +
    + +
    +

    +


    @@ -327,11 +340,6 @@ function findSequence(goal) { } echo ' onclick="showButton()" id="enableRegistration"> '.$t['Registration'].' '; ?> -

    - -

    github


    - [?]   Personal Access Token   Client/Secret Pair Token
    - " autocomplete="off">