Migrated from GitHub
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .eustia.js +9 -0
- .prettierignore +1 -0
- .prettierrc.json +5 -0
- CHANGELOG.md +489 -0
- LICENSE +21 -0
- ORIGINAL_README.md +85 -0
- build/build.js +13 -0
- build/loaders/handlebars-minifier-loader.js +3 -0
- build/webpack.analyser.js +8 -0
- build/webpack.base.js +112 -0
- build/webpack.dev.js +14 -0
- build/webpack.polyfill.js +10 -0
- build/webpack.prod.js +23 -0
- eruda.d.ts +536 -0
- eslint.config.mjs +34 -0
- karma.conf.js +61 -0
- package.json +88 -0
- src/Console/Console.js +399 -0
- src/Console/Console.scss +145 -0
- src/DevTools/DevTools.js +408 -0
- src/DevTools/DevTools.scss +37 -0
- src/DevTools/Tool.js +20 -0
- src/Elements/CssStore.js +112 -0
- src/Elements/Detail.js +518 -0
- src/Elements/Elements.js +325 -0
- src/Elements/Elements.scss +253 -0
- src/Elements/util.js +39 -0
- src/EntryBtn/EntryBtn.js +176 -0
- src/EntryBtn/EntryBtn.scss +22 -0
- src/Info/Info.js +123 -0
- src/Info/Info.scss +60 -0
- src/Info/defInfo.js +57 -0
- src/Network/Detail.js +166 -0
- src/Network/Network.js +385 -0
- src/Network/Network.scss +175 -0
- src/Network/util.js +108 -0
- src/Resources/Cookie.js +190 -0
- src/Resources/Resources.js +444 -0
- src/Resources/Resources.scss +87 -0
- src/Resources/Storage.js +229 -0
- src/Resources/util.js +40 -0
- src/Settings/Settings.js +150 -0
- src/Settings/Settings.scss +10 -0
- src/Snippets/Snippets.js +99 -0
- src/Snippets/Snippets.scss +44 -0
- src/Snippets/defSnippets.js +236 -0
- src/Snippets/searchText.scss +9 -0
- src/Sources/Sources.js +270 -0
- src/Sources/Sources.scss +65 -0
- src/eruda.js +322 -0
.eustia.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
module.exports = {
|
| 2 |
+
test: {
|
| 3 |
+
library: ['node_modules/eustia-module'],
|
| 4 |
+
files: ['test/*.js', 'test/*.html'],
|
| 5 |
+
exclude: ['js'],
|
| 6 |
+
namespace: 'util',
|
| 7 |
+
output: 'test/util.js',
|
| 8 |
+
},
|
| 9 |
+
}
|
.prettierignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
test/util.js
|
.prettierrc.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"singleQuote": true,
|
| 3 |
+
"tabWidth": 2,
|
| 4 |
+
"semi": false
|
| 5 |
+
}
|
CHANGELOG.md
ADDED
|
@@ -0,0 +1,489 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## 3.4.1 (10 Nov 2024)
|
| 2 |
+
|
| 3 |
+
* fix: no copy and delete for shadow root
|
| 4 |
+
* fix: fetch remains pending when error occurs
|
| 5 |
+
* fix: theme not updated if system theme changed
|
| 6 |
+
|
| 7 |
+
## 3.4.0 (27 Sep 2024)
|
| 8 |
+
|
| 9 |
+
* feat: support shadow dom [#158](https://github.com/liriliri/eruda/issues/158)
|
| 10 |
+
* fix: quirks mode table rendering [#459](https://github.com/liriliri/eruda/issues/459)
|
| 11 |
+
|
| 12 |
+
## 3.3.0 (9 Sep 2024)
|
| 13 |
+
|
| 14 |
+
* feat: add vue devtools plugin
|
| 15 |
+
|
| 16 |
+
## 3.2.3 (10 AUG 2024)
|
| 17 |
+
|
| 18 |
+
* fix: WebSocket message base64 encoded [#447](https://github.com/liriliri/eruda/issues/447)
|
| 19 |
+
|
| 20 |
+
## 3.2.2 (8 AUG 2024)
|
| 21 |
+
|
| 22 |
+
* chore: update plugin versions
|
| 23 |
+
|
| 24 |
+
## 3.2.1 (20 JUL 2024)
|
| 25 |
+
|
| 26 |
+
* fix: touches plugin [#344](https://github.com/liriliri/eruda/issues/344)
|
| 27 |
+
|
| 28 |
+
## 3.2.0 (16 JUL 2024)
|
| 29 |
+
|
| 30 |
+
* feat: support inline mode
|
| 31 |
+
* feat: allow spaces in plugin name
|
| 32 |
+
* fix: some typescript d.ts mistakes
|
| 33 |
+
* chore: remove elements set api
|
| 34 |
+
* chore: update monitor plugin version
|
| 35 |
+
|
| 36 |
+
## 3.1.0 (9 JUL 2024)
|
| 37 |
+
|
| 38 |
+
* feat: add AMOLED theme [#414](https://github.com/liriliri/eruda/pull/414)
|
| 39 |
+
* feat: support system preference theme config
|
| 40 |
+
* feat: add isDarkTheme, getTheme util
|
| 41 |
+
* fix: backers.svg lazy loading [#407](https://github.com/liriliri/eruda/issues/407)
|
| 42 |
+
|
| 43 |
+
## 3.0.1 (18 JUL 2023)
|
| 44 |
+
|
| 45 |
+
* fix: can not print string with %o [#336](https://github.com/liriliri/eruda/issues/336)
|
| 46 |
+
* fix: mouse event on touch device [#302](https://github.com/liriliri/eruda/issues/302)
|
| 47 |
+
* fix: unable to remove snippets [#349](https://github.com/liriliri/eruda/issues/349)
|
| 48 |
+
|
| 49 |
+
## 3.0.0 (2 Apr 2023)
|
| 50 |
+
|
| 51 |
+
* feat: replace fps and memory with monitor plugin
|
| 52 |
+
* fix: resource stylesheet show failed
|
| 53 |
+
* chore: remove licia utils
|
| 54 |
+
* chore: separate polyfill
|
| 55 |
+
|
| 56 |
+
## 2.11.3 (3 Mar 2023)
|
| 57 |
+
|
| 58 |
+
* fix: scale [#307](https://github.com/liriliri/eruda/issues/307)
|
| 59 |
+
|
| 60 |
+
## 2.11.2 (28 Jan 2023)
|
| 61 |
+
|
| 62 |
+
* fix: check safe area error
|
| 63 |
+
|
| 64 |
+
## 2.11.1 (28 Jan 2023)
|
| 65 |
+
|
| 66 |
+
* fix: bottom safe area
|
| 67 |
+
* fix(console): filter function support
|
| 68 |
+
* fix: click event stop propagation [#155](https://github.com/liriliri/eruda/issues/155)
|
| 69 |
+
* fix: worker null error [#152](https://github.com/liriliri/eruda/issues/152)
|
| 70 |
+
|
| 71 |
+
## 2.11.0 (19 Jan 2023)
|
| 72 |
+
|
| 73 |
+
* feat(network): filter
|
| 74 |
+
* feat(info): add backers
|
| 75 |
+
* feat(settings): use luna setting
|
| 76 |
+
* feat(resources): use luna data grid
|
| 77 |
+
* feat(resources): copy storage, cookie
|
| 78 |
+
* fix(sources): code not selectable
|
| 79 |
+
* fix(console): filter api
|
| 80 |
+
|
| 81 |
+
## 2.10.0 (24 Dec 2022)
|
| 82 |
+
|
| 83 |
+
* feat(sources): use luna text viewer
|
| 84 |
+
* feat(elements): split mode
|
| 85 |
+
* feat(network): split mode
|
| 86 |
+
* fix(resources): delete cookie
|
| 87 |
+
|
| 88 |
+
## 2.9.1 (20 Dec 2022)
|
| 89 |
+
|
| 90 |
+
* fix(elements): select element using touch events
|
| 91 |
+
|
| 92 |
+
## 2.9.0 (20 Dec 2022)
|
| 93 |
+
|
| 94 |
+
* feat(elements): integrate dom viewer
|
| 95 |
+
* feat(elements): element crumbs
|
| 96 |
+
* feat(elements): copy node and delete node
|
| 97 |
+
* feat(network): copy response
|
| 98 |
+
* feat(network): toggle recording
|
| 99 |
+
* chore: remove dom plugin snippet
|
| 100 |
+
|
| 101 |
+
## 2.8.3 (13 Dec 2022)
|
| 102 |
+
|
| 103 |
+
* fix(network): remove data grid ios outline
|
| 104 |
+
* chore: update luna console and luna object viewer
|
| 105 |
+
|
| 106 |
+
## 2.8.2 (12 Dec 2022)
|
| 107 |
+
|
| 108 |
+
* fix: some variables not reset when destroy
|
| 109 |
+
|
| 110 |
+
## 2.8.1 (12 Dec 2022)
|
| 111 |
+
|
| 112 |
+
* fix: remove luna syntax highlighter
|
| 113 |
+
|
| 114 |
+
## 2.8.0 (11 Dec 2022)
|
| 115 |
+
|
| 116 |
+
* feat(info): copy
|
| 117 |
+
* feat(sources): use luna syntax highlighter
|
| 118 |
+
* feat(network): use luna data grid
|
| 119 |
+
* feat(network): copy as curl [#220](https://github.com/liriliri/eruda/issues/220)
|
| 120 |
+
* fix(network): recognize JSON [#201](https://github.com/liriliri/eruda/issues/201)
|
| 121 |
+
* fix: init with shadow dom style error [#195](https://github.com/liriliri/eruda/issues/195)
|
| 122 |
+
|
| 123 |
+
## 2.7.4 (10 Dec 2022)
|
| 124 |
+
|
| 125 |
+
* fix: firefox document.body is null error [#293](https://github.com/liriliri/eruda/issues/293)
|
| 126 |
+
|
| 127 |
+
## 2.7.3 (8 Dec 2022)
|
| 128 |
+
|
| 129 |
+
* fix: remove tabs horizontal scrollbar [#236](https://github.com/liriliri/eruda/issues/236)
|
| 130 |
+
|
| 131 |
+
## 2.7.2 (7 Dec 2022)
|
| 132 |
+
|
| 133 |
+
* fix: luna modal style
|
| 134 |
+
|
| 135 |
+
## 2.7.1 (7 Dec 2022)
|
| 136 |
+
|
| 137 |
+
* fix: remove debug log
|
| 138 |
+
|
| 139 |
+
## 2.7.0 (7 Dec 2022)
|
| 140 |
+
|
| 141 |
+
* feat: drag to resize
|
| 142 |
+
* feat: update icons
|
| 143 |
+
* feat: use luna modal to replace browser prompt
|
| 144 |
+
|
| 145 |
+
## 2.6.2 (3 Dec 2022)
|
| 146 |
+
|
| 147 |
+
* feat: support android 5.0
|
| 148 |
+
* feat(sources): remove code beautify
|
| 149 |
+
* fix: code plugin theme
|
| 150 |
+
|
| 151 |
+
## 2.6.1 (26 Nov 2022)
|
| 152 |
+
|
| 153 |
+
* fix: dark mode scrollbar style
|
| 154 |
+
* fix: unable to load timing plugin
|
| 155 |
+
|
| 156 |
+
## 2.6.0 (25 Nov 2022)
|
| 157 |
+
|
| 158 |
+
* feat(console): select and copy
|
| 159 |
+
* chore: update luna console
|
| 160 |
+
* chore: update chobitsu
|
| 161 |
+
|
| 162 |
+
## 2.5.0 (9 Jul 2022)
|
| 163 |
+
|
| 164 |
+
* feat: add ts declaration [#187](https://github.com/liriliri/eruda/pull/187)
|
| 165 |
+
* refactor: use luna console
|
| 166 |
+
* refactor: use chobitsu for highlighting element
|
| 167 |
+
|
| 168 |
+
## 2.4.1 (28 Sep 2020)
|
| 169 |
+
|
| 170 |
+
* fix: remove arrow function [#160](https://github.com/liriliri/eruda/issues/160)
|
| 171 |
+
|
| 172 |
+
## 2.4.0 (14 Sep 2020)
|
| 173 |
+
|
| 174 |
+
* feat: default settings [#141](https://github.com/liriliri/eruda/issues/141)
|
| 175 |
+
* fix(elements): highlight
|
| 176 |
+
* fix(console): blinks frequently as it scroll to the border
|
| 177 |
+
* refactor: use chobitsu
|
| 178 |
+
|
| 179 |
+
## 2.3.3 (3 May 2020)
|
| 180 |
+
|
| 181 |
+
* fix: unsafe-eval CSP violation [#140](https://github.com/liriliri/eruda/issues/140)
|
| 182 |
+
|
| 183 |
+
## v2.3.2 (29 Apr 2020)
|
| 184 |
+
|
| 185 |
+
* fix(console): scroll performance
|
| 186 |
+
|
| 187 |
+
## v2.3.1 (28 Apr 2020)
|
| 188 |
+
|
| 189 |
+
* fix(elements): content highlight
|
| 190 |
+
|
| 191 |
+
## v2.3.0 (28 Apr 2020)
|
| 192 |
+
|
| 193 |
+
* feat: refresh notification
|
| 194 |
+
* fix(console): safari bounce effect
|
| 195 |
+
* fix(elements): highlight
|
| 196 |
+
|
| 197 |
+
## v2.2.2 (17 Apr 2020)
|
| 198 |
+
|
| 199 |
+
* fix(console): extra info from
|
| 200 |
+
* chore: update icons
|
| 201 |
+
|
| 202 |
+
## v2.2.1 (20 Mar 2020)
|
| 203 |
+
|
| 204 |
+
* fix: redundant evaluated style
|
| 205 |
+
* chore: use [luna-object-viewer](https://github.com/liriliri/luna) for viewing object
|
| 206 |
+
|
| 207 |
+
## v2.2.0 (9 Feb 2020)
|
| 208 |
+
|
| 209 |
+
* feat: use dark theme for dark mode
|
| 210 |
+
* feat(elements): computed style filter
|
| 211 |
+
* feat(resources): storage and cookie filter
|
| 212 |
+
* fix(snippet): error loading plugin for local page
|
| 213 |
+
* fix(console): unable to clear filter
|
| 214 |
+
|
| 215 |
+
## v2.1.0 (2 Feb 2020)
|
| 216 |
+
|
| 217 |
+
* feat: change navigation bar height
|
| 218 |
+
* feat: change default transparency to 1
|
| 219 |
+
* feat: change loaded plugin position
|
| 220 |
+
* feat(console): remove debug filter
|
| 221 |
+
* feat(console): improve input style
|
| 222 |
+
* feat(console): show filter text
|
| 223 |
+
* feat(network): add requests api [#132](https://github.com/liriliri/eruda/issues/132)
|
| 224 |
+
|
| 225 |
+
## v2.0.2 (9 Jan 2020)
|
| 226 |
+
|
| 227 |
+
* chore: reduce file size (452kb -> 418kb)
|
| 228 |
+
|
| 229 |
+
## v2.0.1 (6 Jan 2020)
|
| 230 |
+
|
| 231 |
+
* chore: update plugins
|
| 232 |
+
|
| 233 |
+
## v2.0.0 (3 Jan 2020)
|
| 234 |
+
|
| 235 |
+
* feat: theme support
|
| 236 |
+
* feat(console): $x utility
|
| 237 |
+
* feat(console): remove useWorker
|
| 238 |
+
* feat(sources): indent size configuration
|
| 239 |
+
* fix(console): url recognition
|
| 240 |
+
* fix(console): log style
|
| 241 |
+
* fix(sources): scrolling
|
| 242 |
+
* perf(console): large object expansion
|
| 243 |
+
* chore: reduce file size (472kb -> 452kb)
|
| 244 |
+
|
| 245 |
+
## v1.10.3 (8 Nov 2019)
|
| 246 |
+
|
| 247 |
+
* fix(info): escape location [#127](https://github.com/liriliri/eruda/issues/127)
|
| 248 |
+
* chore: update refresh icon
|
| 249 |
+
* chore: update timing plugin version
|
| 250 |
+
|
| 251 |
+
## v1.10.2 (5 Nov 2019)
|
| 252 |
+
|
| 253 |
+
* fix: must add .default if using require
|
| 254 |
+
|
| 255 |
+
## v1.10.1 (4 Nov 2019)
|
| 256 |
+
|
| 257 |
+
* fix(console): error display when js execution disabled
|
| 258 |
+
|
| 259 |
+
## v1.10.0 (4 Nov 2019)
|
| 260 |
+
|
| 261 |
+
* chore: updated to babel7, must add .default if using require
|
| 262 |
+
* feat(console): multiple console instance
|
| 263 |
+
* perf(console): rendering for a large number of logs
|
| 264 |
+
|
| 265 |
+
## v1.9.2 (1 Nov 2019)
|
| 266 |
+
|
| 267 |
+
* perf(console): rendering
|
| 268 |
+
|
| 269 |
+
## v1.9.1 (27 Oct 2019)
|
| 270 |
+
|
| 271 |
+
* perf(console): asynchronous log render
|
| 272 |
+
* perf(console): reduce memory usage, 50% drop
|
| 273 |
+
|
| 274 |
+
## v1.9.0 (20 Oct 2019)
|
| 275 |
+
|
| 276 |
+
* feat: add snippet for loading touches plugin
|
| 277 |
+
* feat: add fit screen snippet
|
| 278 |
+
* fix(console): filter shouldn't affect group
|
| 279 |
+
|
| 280 |
+
## v1.8.1 (14 Oct 2019)
|
| 281 |
+
|
| 282 |
+
* fix(network): style [#121](https://github.com/liriliri/eruda/issues/121)
|
| 283 |
+
|
| 284 |
+
## v1.8.0 (13 Oct 2019)
|
| 285 |
+
|
| 286 |
+
* feat(network): display optimization
|
| 287 |
+
* feat: move http view from sources to network
|
| 288 |
+
* fix(console): group object expansion
|
| 289 |
+
|
| 290 |
+
## v1.7.2 (11 Oct 2019)
|
| 291 |
+
|
| 292 |
+
* fix(console): blank bottom if js input is disabled
|
| 293 |
+
* chore: update eruda-dom version
|
| 294 |
+
|
| 295 |
+
## v1.7.1 (10 Oct 2019)
|
| 296 |
+
|
| 297 |
+
* fix: resize
|
| 298 |
+
|
| 299 |
+
## v1.7.0 (8 Oct 2019)
|
| 300 |
+
|
| 301 |
+
* feat: resize [#89](https://github.com/liriliri/eruda/issues/89)
|
| 302 |
+
* feat(console): replace help button with filter
|
| 303 |
+
* feat(console): disable js execution
|
| 304 |
+
* feat(console): [utilities api](https://developers.google.cn/web/tools/chrome-devtools/console/utilities)
|
| 305 |
+
* fix(console): disable log collapsing for group
|
| 306 |
+
* fix(elements): select not working for desktop
|
| 307 |
+
|
| 308 |
+
## v1.6.3 (1 Oct 2019)
|
| 309 |
+
|
| 310 |
+
* fix(console): log border style
|
| 311 |
+
|
| 312 |
+
## v1.6.2 (29 Sep 2019)
|
| 313 |
+
|
| 314 |
+
* fix: container style affected [#119](https://github.com/liriliri/eruda/issues/119)
|
| 315 |
+
* fix(console): log style, line-height should be normal
|
| 316 |
+
|
| 317 |
+
## v1.6.1 (27 Sep 2019)
|
| 318 |
+
|
| 319 |
+
* feat(network): catch fetch request headers
|
| 320 |
+
* feat(console): timeLog, countReset
|
| 321 |
+
* fix(console): clear not working
|
| 322 |
+
* fix(console): table
|
| 323 |
+
|
| 324 |
+
## v1.6.0 (26 Sep 2019)
|
| 325 |
+
|
| 326 |
+
* feat: console group
|
| 327 |
+
* fix: console style, width and height is forbidden
|
| 328 |
+
* fix: regexp json view
|
| 329 |
+
* chore: update fps and memory plugin version
|
| 330 |
+
|
| 331 |
+
## v1.5.8 (2 Aug 2019)
|
| 332 |
+
|
| 333 |
+
* fix: safeStorage undefined [#108](https://github.com/liriliri/eruda/issues/108)
|
| 334 |
+
|
| 335 |
+
## v1.5.7 (15 Jul 2019)
|
| 336 |
+
|
| 337 |
+
* Fix iOS max log number
|
| 338 |
+
* Disable calling init if already initialized
|
| 339 |
+
* Disable worker by default
|
| 340 |
+
* Support xhr blob response type [#104](https://github.com/liriliri/eruda/issues/100)
|
| 341 |
+
|
| 342 |
+
## v1.5.6 (17 Jun 2019)
|
| 343 |
+
|
| 344 |
+
* Disable log collapse for objects
|
| 345 |
+
|
| 346 |
+
## v1.5.5 (25 May 2019)
|
| 347 |
+
|
| 348 |
+
* Fix resources error when cookie has % [#100](https://github.com/liriliri/eruda/issues/100)
|
| 349 |
+
* Update dom plugin version
|
| 350 |
+
|
| 351 |
+
## v1.5.4 (23 Sep 2018)
|
| 352 |
+
|
| 353 |
+
* Fix network url start with //
|
| 354 |
+
* Smaller padding for logs
|
| 355 |
+
|
| 356 |
+
## v1.5.3 (2 Sep 2018)
|
| 357 |
+
|
| 358 |
+
* Add load dom plugin snippet
|
| 359 |
+
* Disable highlight for invisible elements
|
| 360 |
+
* Fix unexpected token \t in JSON
|
| 361 |
+
* Add load orientation plugin snippet
|
| 362 |
+
|
| 363 |
+
## v1.5.2 (23 Aug 2018)
|
| 364 |
+
|
| 365 |
+
* Fix console show in sources panel
|
| 366 |
+
* Fix log merge
|
| 367 |
+
* Support getting entryBtn instance
|
| 368 |
+
* Update timing plugin version
|
| 369 |
+
* Add remove setting api
|
| 370 |
+
* Fix safari merge log exception
|
| 371 |
+
|
| 372 |
+
## v1.5.1 (18 Aug 2018)
|
| 373 |
+
|
| 374 |
+
* Fix uglifyjs unicode escape [#69](https://github.com/liriliri/eruda/issues/69)
|
| 375 |
+
* Update icons, use [iconfont](http://www.iconfont.cn) instead of [icomoon](https://icomoon.io/)
|
| 376 |
+
* Show custom request headers [#78](https://github.com/liriliri/eruda/pull/78)
|
| 377 |
+
* Add get api to info panel [#83](https://github.com/liriliri/eruda/issues/83)
|
| 378 |
+
* Fix responseType json error [#82](https://github.com/liriliri/eruda/issues/82)
|
| 379 |
+
* Support console lazy evaluation
|
| 380 |
+
|
| 381 |
+
## v1.5.0 (19 Jun 2018)
|
| 382 |
+
|
| 383 |
+
* Use shadow dom to encapsulate css
|
| 384 |
+
* Enable sources copy [#71](https://github.com/liriliri/eruda/issues/71)
|
| 385 |
+
* Improve **borderAll** style
|
| 386 |
+
* Add **position** api [#74](https://github.com/liriliri/eruda/issues/74)
|
| 387 |
+
* Fix nav bottom bar wrong position when removed
|
| 388 |
+
|
| 389 |
+
## v1.4.4 (27 May 2018)
|
| 390 |
+
|
| 391 |
+
* Improve console line break display
|
| 392 |
+
* Add **rmCookie** util
|
| 393 |
+
* Add **Load Geolocation Plugin** snippet
|
| 394 |
+
* Fix Elements cssRules [#63](https://github.com/liriliri/eruda/issues/63)
|
| 395 |
+
* Support console events [#66](https://github.com/liriliri/eruda/issues/66)
|
| 396 |
+
* Fix Uc browser console worker [#62](https://github.com/liriliri/eruda/issues/62)
|
| 397 |
+
|
| 398 |
+
## v1.4.3 (7 Feb 2018)
|
| 399 |
+
|
| 400 |
+
* Dynamic info content support [#51](https://github.com/liriliri/eruda/issues/51)
|
| 401 |
+
* Fix console input covered by error log
|
| 402 |
+
* Add elements box model chart
|
| 403 |
+
* Fix source code white-space style [#53](https://github.com/liriliri/eruda/issues/53)
|
| 404 |
+
* Resources support iframe
|
| 405 |
+
* Add **Load Benchmark Plugin** snippet
|
| 406 |
+
|
| 407 |
+
## v1.4.2 (28 Jan 2018)
|
| 408 |
+
|
| 409 |
+
* Extract viewportScale util into [eris](https://github.com/liriliri/eris)
|
| 410 |
+
* Improve image list view using flex
|
| 411 |
+
* Add DevTools display event hooks [#50](https://github.com/liriliri/eruda/issues/50)
|
| 412 |
+
|
| 413 |
+
## v1.4.1 (13 Jan 2018)
|
| 414 |
+
|
| 415 |
+
* Update timing plugin version
|
| 416 |
+
* Fix viewportScale
|
| 417 |
+
* Optimize console performance for big data
|
| 418 |
+
* Expose snippets run api
|
| 419 |
+
* Delete desktop scrollbar style
|
| 420 |
+
* Add code plugin to snippets
|
| 421 |
+
|
| 422 |
+
## v1.4.0 (7 Jan 2018)
|
| 423 |
+
|
| 424 |
+
* Remove network timing into external plugin
|
| 425 |
+
* Add system info
|
| 426 |
+
* Add memory plugin snippet
|
| 427 |
+
* Monitor fetch requests [#24](https://github.com/liriliri/eruda/issues/24)
|
| 428 |
+
* Reduce json viewer click area
|
| 429 |
+
* Use resource timing for image capture
|
| 430 |
+
|
| 431 |
+
## v1.3.2 (14 Dec 2017)
|
| 432 |
+
|
| 433 |
+
* Fix restore settings snippet
|
| 434 |
+
* Extract *features* into an external plugin
|
| 435 |
+
|
| 436 |
+
## v1.3.1 (19 Nov 2017)
|
| 437 |
+
|
| 438 |
+
* Observe elements in resources panel
|
| 439 |
+
* Fix performance timing not supported [#40](https://github.com/liriliri/eruda/issues/40)
|
| 440 |
+
|
| 441 |
+
## v1.3.0 (5 Nov 2017)
|
| 442 |
+
|
| 443 |
+
* Remove log margin
|
| 444 |
+
* Fix css custom properties [#33](https://github.com/liriliri/eruda/issues/33)
|
| 445 |
+
* Add version info
|
| 446 |
+
* Change icomoon generated font name
|
| 447 |
+
* Improve snippets style
|
| 448 |
+
* Add *Load Fps Plugin* and *Restore Settings* snippets
|
| 449 |
+
* Support navbar color customization
|
| 450 |
+
* Support range in settings panel
|
| 451 |
+
* Support auto scale [#32](https://github.com/liriliri/eruda/issues/32)
|
| 452 |
+
* Improve *Border All* snippet
|
| 453 |
+
* Use high resolution time for console time
|
| 454 |
+
|
| 455 |
+
## v1.2.6 (31 Aug 2017)
|
| 456 |
+
|
| 457 |
+
* Fix catch global errors
|
| 458 |
+
|
| 459 |
+
## v1.2.5 (20 Aug 2017)
|
| 460 |
+
|
| 461 |
+
* Fix cookie URI malformed
|
| 462 |
+
* Fix single string argument unescaped
|
| 463 |
+
* Update util library and dependencies
|
| 464 |
+
* Fix catch event listeners [#31](https://github.com/liriliri/eruda/issues/31)
|
| 465 |
+
* Console log scroll automatically only at bottom
|
| 466 |
+
* Fix unformatted html tag
|
| 467 |
+
|
| 468 |
+
## v1.2.4 (1 Jul 2017)
|
| 469 |
+
|
| 470 |
+
* Fix uncaught promise error [#29](https://github.com/liriliri/eruda/issues/23)
|
| 471 |
+
* Fix bad classes [#28](https://github.com/liriliri/eruda/issues/23)
|
| 472 |
+
|
| 473 |
+
## v1.2.3 (15 May 2017)
|
| 474 |
+
|
| 475 |
+
* Disable modernizr classes
|
| 476 |
+
* Update eustia util
|
| 477 |
+
* Fix console resize [#23](https://github.com/liriliri/eruda/issues/23)
|
| 478 |
+
* Improve object log
|
| 479 |
+
* Use outline for borderAll snippet
|
| 480 |
+
|
| 481 |
+
## v1.2.2 (11 Mar 2017)
|
| 482 |
+
|
| 483 |
+
* Fix log url recognition
|
| 484 |
+
* Fix error log stack url and style
|
| 485 |
+
* Fix table log ouput
|
| 486 |
+
* Fix storage initialization [#20](https://github.com/liriliri/eruda/issues/20)
|
| 487 |
+
* Update eustia lib
|
| 488 |
+
* Elements auto refresh
|
| 489 |
+
* Add pc scrollbar style
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
The MIT License (MIT)
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2016-present liriliri
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
ORIGINAL_README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<div align="center">
|
| 2 |
+
<a href="https://eruda.liriliri.io/" target="_blank">
|
| 3 |
+
<img src="https://eruda.liriliri.io/icon.png" width="400">
|
| 4 |
+
</a>
|
| 5 |
+
</div>
|
| 6 |
+
|
| 7 |
+
<h1 align="center">Eruda</h1>
|
| 8 |
+
|
| 9 |
+
<div align="center">
|
| 10 |
+
|
| 11 |
+
Console for Mobile Browsers.
|
| 12 |
+
|
| 13 |
+
[![NPM version][npm-image]][npm-url]
|
| 14 |
+
[![Build status][ci-image]][ci-url]
|
| 15 |
+
[![Test coverage][codecov-image]][codecov-url]
|
| 16 |
+
[![Downloads][jsdelivr-image]][jsdelivr-url]
|
| 17 |
+
[![License][license-image]][npm-url]
|
| 18 |
+
|
| 19 |
+
</div>
|
| 20 |
+
|
| 21 |
+
[npm-image]: https://img.shields.io/npm/v/eruda?style=flat-square
|
| 22 |
+
[npm-url]: https://npmjs.org/package/eruda
|
| 23 |
+
[jsdelivr-image]: https://img.shields.io/jsdelivr/npm/hm/eruda?style=flat-square
|
| 24 |
+
[jsdelivr-url]: https://www.jsdelivr.com/package/npm/eruda
|
| 25 |
+
[ci-image]: https://img.shields.io/github/actions/workflow/status/liriliri/eruda/main.yml?branch=master&style=flat-square
|
| 26 |
+
[ci-url]: https://github.com/liriliri/eruda/actions/workflows/main.yml
|
| 27 |
+
[codecov-image]: https://img.shields.io/codecov/c/github/liriliri/eruda?style=flat-square
|
| 28 |
+
[codecov-url]: https://codecov.io/github/liriliri/eruda?branch=master
|
| 29 |
+
[license-image]: https://img.shields.io/npm/l/eruda?style=flat-square
|
| 30 |
+
[donate-image]: https://img.shields.io/badge/$-donate-0070ba.svg?style=flat-square
|
| 31 |
+
|
| 32 |
+
<img src="https://eruda.liriliri.io/screenshot.jpg" style="width:100%">
|
| 33 |
+
|
| 34 |
+
## Demo
|
| 35 |
+
|
| 36 |
+

|
| 37 |
+
|
| 38 |
+
Browse it on your phone: [eruda.liriliri.io](https://eruda.liriliri.io/)
|
| 39 |
+
|
| 40 |
+
## Install
|
| 41 |
+
|
| 42 |
+
You can get it on npm.
|
| 43 |
+
|
| 44 |
+
```bash
|
| 45 |
+
npm install eruda --save-dev
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
Add this script to your page.
|
| 49 |
+
|
| 50 |
+
```html
|
| 51 |
+
<script src="node_modules/eruda/eruda.js"></script>
|
| 52 |
+
<script>eruda.init();</script>
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
It's also available on [jsDelivr](http://www.jsdelivr.com/projects/eruda) and [cdnjs](https://cdnjs.com/libraries/eruda).
|
| 56 |
+
|
| 57 |
+
```html
|
| 58 |
+
<script src="https://cdn.jsdelivr.net/npm/eruda"></script>
|
| 59 |
+
<script>eruda.init();</script>
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
For more detailed usage instructions, please read the documentation at [eruda.liriliri.io](https://eruda.liriliri.io/docs/)!
|
| 63 |
+
|
| 64 |
+
## Related Projects
|
| 65 |
+
|
| 66 |
+
* [eruda-android](https://github.com/liriliri/eruda-android): Simple webview with eruda loaded automatically.
|
| 67 |
+
* [chii](https://github.com/liriliri/chii): Remote debugging tool.
|
| 68 |
+
* [chobitsu](https://github.com/liriliri/chobitsu): Chrome devtools protocol JavaScript implementation.
|
| 69 |
+
* [licia](https://github.com/liriliri/licia): Utility library used by eruda.
|
| 70 |
+
* [luna](https://github.com/liriliri/luna): UI components used by eruda.
|
| 71 |
+
* [vivy](https://github.com/liriliri/vivy-docs): Icon image generation.
|
| 72 |
+
|
| 73 |
+
## Third Party
|
| 74 |
+
|
| 75 |
+
* [eruda-pixel](https://github.com/Faithree/eruda-pixel): UI pixel restoration tool.
|
| 76 |
+
* [eruda-webpack-plugin](https://github.com/huruji/eruda-webpack-plugin): Eruda webpack plugin.
|
| 77 |
+
* [eruda-vue-devtools](https://github.com/Zippowxk/vue-devtools-plugin): Eruda Vue-devtools plugin.
|
| 78 |
+
|
| 79 |
+
## Backers
|
| 80 |
+
|
| 81 |
+
<a rel="noreferrer noopener" href="https://opencollective.com/eruda" target="_blank"><img src="https://opencollective.com/eruda/backers.svg?width=890"></a>
|
| 82 |
+
|
| 83 |
+
## Contribution
|
| 84 |
+
|
| 85 |
+
Read [Contributing Guide](https://eruda.liriliri.io/docs/contributing.html) for development setup instructions.
|
build/build.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const path = require('path')
|
| 2 |
+
const fs = require('licia/fs')
|
| 3 |
+
|
| 4 |
+
const pkg = require('../package.json')
|
| 5 |
+
|
| 6 |
+
delete pkg.scripts
|
| 7 |
+
delete pkg.devDependencies
|
| 8 |
+
|
| 9 |
+
fs.writeFile(
|
| 10 |
+
path.resolve(__dirname, '../dist/package.json'),
|
| 11 |
+
JSON.stringify(pkg, null, 2),
|
| 12 |
+
'utf8'
|
| 13 |
+
)
|
build/loaders/handlebars-minifier-loader.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
module.exports = function (src) {
|
| 2 |
+
return src.replace(/"loc":\{"start":\{"line":\d+,"column":\d+},"end":\{"line":\d+,"column":\d+\}\}/g, '')
|
| 3 |
+
}
|
build/webpack.analyser.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const BundleAnalyzerPlugin =
|
| 2 |
+
require('webpack-bundle-analyzer').BundleAnalyzerPlugin
|
| 3 |
+
|
| 4 |
+
exports = require('./webpack.prod')
|
| 5 |
+
|
| 6 |
+
exports.plugins.push(new BundleAnalyzerPlugin())
|
| 7 |
+
|
| 8 |
+
module.exports = exports
|
build/webpack.base.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const autoprefixer = require('autoprefixer')
|
| 2 |
+
const prefixer = require('postcss-prefixer')
|
| 3 |
+
const clean = require('postcss-clean')
|
| 4 |
+
const webpack = require('webpack')
|
| 5 |
+
const pkg = require('../package.json')
|
| 6 |
+
const path = require('path')
|
| 7 |
+
const ESLintPlugin = require('eslint-webpack-plugin')
|
| 8 |
+
|
| 9 |
+
process.traceDeprecation = true
|
| 10 |
+
|
| 11 |
+
const banner = pkg.name + ' v' + pkg.version + ' ' + pkg.homepage
|
| 12 |
+
|
| 13 |
+
const postcssLoader = {
|
| 14 |
+
loader: 'postcss-loader',
|
| 15 |
+
options: {
|
| 16 |
+
plugins: [
|
| 17 |
+
prefixer({
|
| 18 |
+
prefix: '_',
|
| 19 |
+
ignore: [/luna-*/],
|
| 20 |
+
}),
|
| 21 |
+
autoprefixer,
|
| 22 |
+
clean(),
|
| 23 |
+
],
|
| 24 |
+
},
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
const rawLoader = {
|
| 28 |
+
loader: 'raw-loader',
|
| 29 |
+
options: {
|
| 30 |
+
esModule: false,
|
| 31 |
+
},
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
module.exports = {
|
| 35 |
+
entry: './src/index',
|
| 36 |
+
resolve: {
|
| 37 |
+
symlinks: false,
|
| 38 |
+
alias: {
|
| 39 |
+
axios: path.resolve(__dirname, '../src/lib/empty.js'),
|
| 40 |
+
micromark: path.resolve(__dirname, '../src/lib/micromark.js'),
|
| 41 |
+
},
|
| 42 |
+
},
|
| 43 |
+
devServer: {
|
| 44 |
+
static: {
|
| 45 |
+
directory: path.join(__dirname, '../test'),
|
| 46 |
+
},
|
| 47 |
+
port: 8080,
|
| 48 |
+
},
|
| 49 |
+
output: {
|
| 50 |
+
path: path.resolve(__dirname, '../dist'),
|
| 51 |
+
publicPath: '/assets/',
|
| 52 |
+
library: 'eruda',
|
| 53 |
+
libraryTarget: 'umd',
|
| 54 |
+
},
|
| 55 |
+
module: {
|
| 56 |
+
rules: [
|
| 57 |
+
{
|
| 58 |
+
test: /\.js$/,
|
| 59 |
+
include: [
|
| 60 |
+
path.resolve(__dirname, '../src'),
|
| 61 |
+
path.resolve(__dirname, '../node_modules/luna-console'),
|
| 62 |
+
path.resolve(__dirname, '../node_modules/luna-modal'),
|
| 63 |
+
path.resolve(__dirname, '../node_modules/luna-tab'),
|
| 64 |
+
path.resolve(__dirname, '../node_modules/luna-data-grid'),
|
| 65 |
+
path.resolve(__dirname, '../node_modules/luna-object-viewer'),
|
| 66 |
+
path.resolve(__dirname, '../node_modules/luna-dom-viewer'),
|
| 67 |
+
path.resolve(__dirname, '../node_modules/luna-text-viewer'),
|
| 68 |
+
path.resolve(__dirname, '../node_modules/luna-setting'),
|
| 69 |
+
path.resolve(__dirname, '../node_modules/luna-box-model'),
|
| 70 |
+
path.resolve(__dirname, '../node_modules/luna-notification'),
|
| 71 |
+
],
|
| 72 |
+
use: [
|
| 73 |
+
{
|
| 74 |
+
loader: 'babel-loader',
|
| 75 |
+
options: {
|
| 76 |
+
sourceType: 'unambiguous',
|
| 77 |
+
presets: ['@babel/preset-env'],
|
| 78 |
+
plugins: [
|
| 79 |
+
'@babel/plugin-transform-runtime',
|
| 80 |
+
'@babel/plugin-proposal-class-properties',
|
| 81 |
+
],
|
| 82 |
+
},
|
| 83 |
+
},
|
| 84 |
+
],
|
| 85 |
+
},
|
| 86 |
+
{
|
| 87 |
+
test: /\.scss$/,
|
| 88 |
+
use: [
|
| 89 |
+
'css-loader',
|
| 90 |
+
postcssLoader,
|
| 91 |
+
{ loader: 'sass-loader', options: { api: 'modern' } },
|
| 92 |
+
],
|
| 93 |
+
},
|
| 94 |
+
{
|
| 95 |
+
test: /\.css$/,
|
| 96 |
+
exclude: /luna-dom-highlighter/,
|
| 97 |
+
use: ['css-loader', postcssLoader],
|
| 98 |
+
},
|
| 99 |
+
{
|
| 100 |
+
test: /luna-dom-highlighter\.css$/,
|
| 101 |
+
use: [rawLoader],
|
| 102 |
+
},
|
| 103 |
+
],
|
| 104 |
+
},
|
| 105 |
+
plugins: [
|
| 106 |
+
new webpack.BannerPlugin(banner),
|
| 107 |
+
new webpack.DefinePlugin({
|
| 108 |
+
VERSION: '"' + pkg.version + '"',
|
| 109 |
+
}),
|
| 110 |
+
new ESLintPlugin(),
|
| 111 |
+
],
|
| 112 |
+
}
|
build/webpack.dev.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const webpack = require('webpack')
|
| 2 |
+
|
| 3 |
+
exports = require('./webpack.base')
|
| 4 |
+
|
| 5 |
+
exports.mode = 'development'
|
| 6 |
+
exports.output.filename = 'eruda.js'
|
| 7 |
+
exports.devtool = 'source-map'
|
| 8 |
+
exports.plugins = exports.plugins.concat([
|
| 9 |
+
new webpack.DefinePlugin({
|
| 10 |
+
ENV: '"development"',
|
| 11 |
+
}),
|
| 12 |
+
])
|
| 13 |
+
|
| 14 |
+
module.exports = exports
|
build/webpack.polyfill.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const path = require('path')
|
| 2 |
+
|
| 3 |
+
module.exports = {
|
| 4 |
+
mode: 'production',
|
| 5 |
+
entry: './src/polyfill',
|
| 6 |
+
output: {
|
| 7 |
+
path: path.resolve(__dirname, '../dist'),
|
| 8 |
+
filename: 'eruda-polyfill.js',
|
| 9 |
+
},
|
| 10 |
+
}
|
build/webpack.prod.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const webpack = require('webpack')
|
| 2 |
+
const TerserPlugin = require('terser-webpack-plugin')
|
| 3 |
+
|
| 4 |
+
exports = require('./webpack.base')
|
| 5 |
+
|
| 6 |
+
exports.mode = 'production'
|
| 7 |
+
exports.output.filename = 'eruda.js'
|
| 8 |
+
exports.devtool = 'source-map'
|
| 9 |
+
exports.plugins = exports.plugins.concat([
|
| 10 |
+
new webpack.DefinePlugin({
|
| 11 |
+
ENV: '"production"',
|
| 12 |
+
}),
|
| 13 |
+
])
|
| 14 |
+
exports.optimization = {
|
| 15 |
+
minimize: true,
|
| 16 |
+
minimizer: [
|
| 17 |
+
new TerserPlugin({
|
| 18 |
+
extractComments: false,
|
| 19 |
+
}),
|
| 20 |
+
],
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
module.exports = exports
|
eruda.d.ts
ADDED
|
@@ -0,0 +1,536 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Type definitions for Eruda
|
| 3 |
+
* @see https://github.com/liriliri/eruda
|
| 4 |
+
*/
|
| 5 |
+
declare module 'eruda' {
|
| 6 |
+
export interface InitDefaults {
|
| 7 |
+
/**
|
| 8 |
+
* Transparency, 0 to 1
|
| 9 |
+
*/
|
| 10 |
+
transparency?: number
|
| 11 |
+
/**
|
| 12 |
+
* Display size, 0 to 100
|
| 13 |
+
*/
|
| 14 |
+
displaySize?: number
|
| 15 |
+
/**
|
| 16 |
+
* Theme, defaults to Light or Dark in dark mode
|
| 17 |
+
*/
|
| 18 |
+
theme?: string
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
export interface InitOptions {
|
| 22 |
+
/**
|
| 23 |
+
* Container element. If not set, it will append an element directly under html root element
|
| 24 |
+
*/
|
| 25 |
+
container?: HTMLElement
|
| 26 |
+
/**
|
| 27 |
+
* Choose which default tools you want, by default all will be added
|
| 28 |
+
*/
|
| 29 |
+
tool?: string[]
|
| 30 |
+
/**
|
| 31 |
+
* Auto scale eruda for different viewport settings
|
| 32 |
+
*/
|
| 33 |
+
autoScale?: boolean
|
| 34 |
+
/**
|
| 35 |
+
* Use shadow dom for css encapsulation
|
| 36 |
+
*/
|
| 37 |
+
useShadowDom?: boolean
|
| 38 |
+
/**
|
| 39 |
+
* Enable inline mode
|
| 40 |
+
*/
|
| 41 |
+
inline?: boolean
|
| 42 |
+
/**
|
| 43 |
+
* Default settings
|
| 44 |
+
*/
|
| 45 |
+
defaults?: InitDefaults
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
export interface Position {
|
| 49 |
+
x: number
|
| 50 |
+
y: number
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
type AnyFn = (...args: any[]) => any
|
| 54 |
+
|
| 55 |
+
export interface Emitter {
|
| 56 |
+
on(event: string, listener: AnyFn): Emitter
|
| 57 |
+
off(event: string, listener: AnyFn): Emitter
|
| 58 |
+
once(event: string, listener: AnyFn): Emitter
|
| 59 |
+
emit(event: string, ...args: any[]): Emitter
|
| 60 |
+
removeAllListeners(event?: string): Emitter
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
/**
|
| 64 |
+
* Eruda Plugin
|
| 65 |
+
* @see https://eruda.liriliri.io/docs/plugin.html
|
| 66 |
+
*/
|
| 67 |
+
export interface Tool {
|
| 68 |
+
/**
|
| 69 |
+
* Every plugin must have a unique name, which will be shown as the tab name on the top.
|
| 70 |
+
*/
|
| 71 |
+
name: string
|
| 72 |
+
/**
|
| 73 |
+
* Called when plugin is added, and a document element used to display content is passed in.
|
| 74 |
+
* The element is wrapped as a jQuery like object, provided by the licia utility library.
|
| 75 |
+
*/
|
| 76 |
+
init(el: unknown): void
|
| 77 |
+
/**
|
| 78 |
+
* Called when switch to the panel. Usually all you need to do is to show the container element.
|
| 79 |
+
*/
|
| 80 |
+
show(): Tool | undefined
|
| 81 |
+
/**
|
| 82 |
+
* Called when switch to other panel. You should at least hide the container element here.
|
| 83 |
+
*/
|
| 84 |
+
hide(): Tool | undefined
|
| 85 |
+
/**
|
| 86 |
+
* Called when plugin is removed using `eruda.remove('plugin name')`.
|
| 87 |
+
*/
|
| 88 |
+
destroy(): void
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
export interface ToolConstructor {
|
| 92 |
+
new (): Tool
|
| 93 |
+
readonly prototype: Tool
|
| 94 |
+
|
| 95 |
+
extend(tool: Tool): ToolConstructor
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
export interface ConsoleConfig {
|
| 99 |
+
/**
|
| 100 |
+
* Asynchronous rendering
|
| 101 |
+
*/
|
| 102 |
+
asyncRender?: boolean
|
| 103 |
+
/**
|
| 104 |
+
* Enable JavaScript execution
|
| 105 |
+
*/
|
| 106 |
+
jsExecution?: boolean
|
| 107 |
+
/**
|
| 108 |
+
* Catch global errors
|
| 109 |
+
*/
|
| 110 |
+
catchGlobalErr?: boolean
|
| 111 |
+
/**
|
| 112 |
+
* Override console
|
| 113 |
+
*/
|
| 114 |
+
overrideConsole?: boolean
|
| 115 |
+
/**
|
| 116 |
+
* Display extra information
|
| 117 |
+
*/
|
| 118 |
+
displayExtraInfo?: boolean
|
| 119 |
+
/**
|
| 120 |
+
* Display unenumerable properties
|
| 121 |
+
*/
|
| 122 |
+
displayUnenumerable?: boolean
|
| 123 |
+
/**
|
| 124 |
+
* Access getter value
|
| 125 |
+
*/
|
| 126 |
+
displayGetterVal?: boolean
|
| 127 |
+
/**
|
| 128 |
+
* Stringify object when clicked
|
| 129 |
+
*/
|
| 130 |
+
lazyEvaluation?: boolean
|
| 131 |
+
/**
|
| 132 |
+
* Auto display if error occurs
|
| 133 |
+
*/
|
| 134 |
+
displayIfErr?: boolean
|
| 135 |
+
/**
|
| 136 |
+
* Max log number
|
| 137 |
+
*/
|
| 138 |
+
maxLogNum?: string
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
export interface Log {
|
| 142 |
+
type: string
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
export interface ErudaConsole extends Tool, Console {
|
| 146 |
+
config: {
|
| 147 |
+
set<K extends keyof ConsoleConfig>(name: K, value: ConsoleConfig[K]): void
|
| 148 |
+
}
|
| 149 |
+
/**
|
| 150 |
+
* Custom filter
|
| 151 |
+
*/
|
| 152 |
+
filter(pattern: string | RegExp | ((log: Log) => boolean)): void
|
| 153 |
+
/**
|
| 154 |
+
* Html string
|
| 155 |
+
*/
|
| 156 |
+
html(htmlStr: string): void
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
export interface ErudaConsoleConstructor {
|
| 160 |
+
new (): ErudaConsole
|
| 161 |
+
readonly prototype: ErudaConsole
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
export interface ElementsConfig {
|
| 165 |
+
/**
|
| 166 |
+
* Catch Event Listeners
|
| 167 |
+
*/
|
| 168 |
+
overrideEventTarget?: boolean
|
| 169 |
+
/**
|
| 170 |
+
* Auto Refresh
|
| 171 |
+
*/
|
| 172 |
+
observeElement?: boolean
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
export interface Elements extends Tool {
|
| 176 |
+
config: {
|
| 177 |
+
set<K extends keyof ElementsConfig>(
|
| 178 |
+
name: K,
|
| 179 |
+
value: ElementsConfig[K]
|
| 180 |
+
): void
|
| 181 |
+
}
|
| 182 |
+
/**
|
| 183 |
+
* Element to display
|
| 184 |
+
*/
|
| 185 |
+
select(el: HTMLElement): void
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
export interface ElementsConstructor {
|
| 189 |
+
new (): Elements
|
| 190 |
+
readonly prototype: Elements
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
export interface Network extends Tool {
|
| 194 |
+
/**
|
| 195 |
+
* Clear requests
|
| 196 |
+
*/
|
| 197 |
+
clear(): void
|
| 198 |
+
/**
|
| 199 |
+
* Get request data
|
| 200 |
+
*/
|
| 201 |
+
requests(): object[]
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
export interface NetworkConstructor {
|
| 205 |
+
new (): Network
|
| 206 |
+
readonly prototype: Network
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
export interface ResourcesConfig {
|
| 210 |
+
/**
|
| 211 |
+
* Hide Eruda Setting
|
| 212 |
+
*/
|
| 213 |
+
hideErudaSetting?: boolean
|
| 214 |
+
/**
|
| 215 |
+
* Auto Refresh Elements
|
| 216 |
+
*/
|
| 217 |
+
observeElement?: boolean
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
export interface Resources extends Tool {
|
| 221 |
+
config: {
|
| 222 |
+
set<K extends keyof ResourcesConfig>(
|
| 223 |
+
name: K,
|
| 224 |
+
value: ResourcesConfig[K]
|
| 225 |
+
): void
|
| 226 |
+
}
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
export interface ResourcesConstructor {
|
| 230 |
+
new (): Resources
|
| 231 |
+
readonly prototype: Resources
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
export interface SourcesConfig {
|
| 235 |
+
/**
|
| 236 |
+
* Show Line Numbers
|
| 237 |
+
*/
|
| 238 |
+
showLineNum?: boolean
|
| 239 |
+
/**
|
| 240 |
+
* Beautify Code
|
| 241 |
+
*/
|
| 242 |
+
formatCode?: boolean
|
| 243 |
+
/**
|
| 244 |
+
* Indent Size
|
| 245 |
+
*/
|
| 246 |
+
indentSize?: string
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
export interface Sources extends Tool {
|
| 250 |
+
config: {
|
| 251 |
+
set<K extends keyof SourcesConfig>(name: K, value: SourcesConfig[K]): void
|
| 252 |
+
}
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
export interface SourcesConstructor {
|
| 256 |
+
new (): Sources
|
| 257 |
+
readonly prototype: Sources
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
export interface InfoItem {
|
| 261 |
+
name: string
|
| 262 |
+
val: string
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
export interface Info extends Tool {
|
| 266 |
+
/**
|
| 267 |
+
* Clear infos
|
| 268 |
+
*/
|
| 269 |
+
clear(): void
|
| 270 |
+
/**
|
| 271 |
+
* Add info
|
| 272 |
+
*/
|
| 273 |
+
add(name: string, content: string | (() => void)): void
|
| 274 |
+
/**
|
| 275 |
+
* Get info or infos
|
| 276 |
+
*/
|
| 277 |
+
get(): InfoItem[]
|
| 278 |
+
get(name: string): string
|
| 279 |
+
/**
|
| 280 |
+
* Remove specified info
|
| 281 |
+
*/
|
| 282 |
+
remove(name: string): void
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
export interface InfoConstructor {
|
| 286 |
+
new (): Info
|
| 287 |
+
readonly prototype: Info
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
export interface Snippets extends Tool {
|
| 291 |
+
/**
|
| 292 |
+
* Clear snippets
|
| 293 |
+
*/
|
| 294 |
+
clear(): void
|
| 295 |
+
/**
|
| 296 |
+
* Add snippet
|
| 297 |
+
* @param name Snippet name
|
| 298 |
+
* @param fn Function to be triggered
|
| 299 |
+
* @param desc Snippet description
|
| 300 |
+
*/
|
| 301 |
+
add(name: string, fn: Function, desc: string): void
|
| 302 |
+
/**
|
| 303 |
+
* Remove specified snippet
|
| 304 |
+
* @param name Snippet name
|
| 305 |
+
*/
|
| 306 |
+
remove(name: string): void
|
| 307 |
+
/**
|
| 308 |
+
* Run specified snippet
|
| 309 |
+
* @param name Snippet name
|
| 310 |
+
*/
|
| 311 |
+
run(name: string): void
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
export interface SnippetsConstructor {
|
| 315 |
+
new (): Snippets
|
| 316 |
+
readonly prototype: Snippets
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
export interface SettingsRangeOptions {
|
| 320 |
+
min?: number
|
| 321 |
+
max?: number
|
| 322 |
+
step?: number
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
export interface Settings extends Tool {
|
| 326 |
+
/**
|
| 327 |
+
* Clear settings
|
| 328 |
+
*/
|
| 329 |
+
clear(): void
|
| 330 |
+
/**
|
| 331 |
+
* Remove setting
|
| 332 |
+
* @param cfg Config object
|
| 333 |
+
* @param name Option name
|
| 334 |
+
*/
|
| 335 |
+
remove(cfj: object, name: string): void
|
| 336 |
+
/**
|
| 337 |
+
* Add text
|
| 338 |
+
*/
|
| 339 |
+
text(str: string): void
|
| 340 |
+
/**
|
| 341 |
+
* Add switch to toggle a boolean value
|
| 342 |
+
* @param cfg Config object created by util.createCfg
|
| 343 |
+
* @param name Option name
|
| 344 |
+
* @param desc Option description
|
| 345 |
+
*/
|
| 346 |
+
switch(cfg: object, name: string, desc: string): void
|
| 347 |
+
/**
|
| 348 |
+
* Add select to select a number of string values
|
| 349 |
+
* @param cfg Config object
|
| 350 |
+
* @param name Option name
|
| 351 |
+
* @param desc Option description
|
| 352 |
+
* @param values Array of strings to select
|
| 353 |
+
*/
|
| 354 |
+
select(cfg: object, name: string, desc: string, values: string[]): void
|
| 355 |
+
/**
|
| 356 |
+
* Add range to input a number
|
| 357 |
+
* @param cfg Config object
|
| 358 |
+
* @param name Option name
|
| 359 |
+
* @param desc Option description
|
| 360 |
+
* @param options Min, max, step
|
| 361 |
+
*/
|
| 362 |
+
range(
|
| 363 |
+
cfg: object,
|
| 364 |
+
name: string,
|
| 365 |
+
desc: string,
|
| 366 |
+
options?: SettingsRangeOptions
|
| 367 |
+
): void
|
| 368 |
+
/**
|
| 369 |
+
* Add a separator
|
| 370 |
+
*/
|
| 371 |
+
separator(): void
|
| 372 |
+
}
|
| 373 |
+
|
| 374 |
+
export interface SettingsConstructor {
|
| 375 |
+
new (): Settings
|
| 376 |
+
readonly prototype: Settings
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
export interface EntryBtn extends Emitter {
|
| 380 |
+
show(): void
|
| 381 |
+
hide(): void
|
| 382 |
+
getPos(): Position
|
| 383 |
+
setPos(pos: Position): void
|
| 384 |
+
destroy(): void
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
export interface EntryBtnConstructor {
|
| 388 |
+
new (): EntryBtn
|
| 389 |
+
readonly prototype: EntryBtn
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
export interface DevTools extends Emitter {
|
| 393 |
+
show(): DevTools
|
| 394 |
+
hide(): DevTools
|
| 395 |
+
toggle(): void
|
| 396 |
+
add(tool: Tool | object): DevTools
|
| 397 |
+
remove(name: string): DevTools
|
| 398 |
+
removeAll(): DevTools
|
| 399 |
+
get<T extends ToolConstructor>(name: string): InstanceType<T> | undefined
|
| 400 |
+
showTool(name: string): DevTools
|
| 401 |
+
initCfg(settings: Settings): void
|
| 402 |
+
notify(content: string, options: object): void
|
| 403 |
+
destroy(): void
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
export interface DevToolsConstructor {
|
| 407 |
+
new (): DevTools
|
| 408 |
+
readonly prototype: DevTools
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
/**
|
| 412 |
+
* Eruda Util
|
| 413 |
+
* @see https://eruda.liriliri.io/docs/plugin.html#utility
|
| 414 |
+
*/
|
| 415 |
+
export interface Util {
|
| 416 |
+
evalCss(css: string): HTMLStyleElement
|
| 417 |
+
isErudaEl(val: any): boolean
|
| 418 |
+
isDarkTheme(theme?: string): boolean
|
| 419 |
+
getTheme(): string
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
+
interface IToolNameMap {
|
| 423 |
+
console: InstanceType<ErudaConsoleConstructor>
|
| 424 |
+
elements: InstanceType<ElementsConstructor>
|
| 425 |
+
info: InstanceType<InfoConstructor>
|
| 426 |
+
network: InstanceType<NetworkConstructor>
|
| 427 |
+
resources: InstanceType<ResourcesConstructor>
|
| 428 |
+
settings: InstanceType<SettingsConstructor>
|
| 429 |
+
snippets: InstanceType<SnippetsConstructor>
|
| 430 |
+
sources: InstanceType<SourcesConstructor>
|
| 431 |
+
entryBtn: InstanceType<EntryBtnConstructor>
|
| 432 |
+
}
|
| 433 |
+
|
| 434 |
+
/**
|
| 435 |
+
* Eruda APIs
|
| 436 |
+
* @see https://eruda.liriliri.io/docs/api.html
|
| 437 |
+
*/
|
| 438 |
+
export interface ErudaApis {
|
| 439 |
+
/**
|
| 440 |
+
* Initialize eruda.
|
| 441 |
+
*/
|
| 442 |
+
init(options?: InitOptions): void
|
| 443 |
+
/**
|
| 444 |
+
* Destory eruda.
|
| 445 |
+
* Note: You can call `init` method again after destruction.
|
| 446 |
+
*/
|
| 447 |
+
destroy(): void
|
| 448 |
+
/**
|
| 449 |
+
* Set or get scale.
|
| 450 |
+
*/
|
| 451 |
+
scale(): number
|
| 452 |
+
scale(s: number): Eruda
|
| 453 |
+
/**
|
| 454 |
+
* Set or get entry button position.
|
| 455 |
+
* It will not take effect if given pos is out of range.
|
| 456 |
+
*/
|
| 457 |
+
position(): Position
|
| 458 |
+
position(p: Position): Eruda
|
| 459 |
+
/**
|
| 460 |
+
* Get tool, eg. console, elements panels.
|
| 461 |
+
*/
|
| 462 |
+
get<K extends keyof IToolNameMap>(name: K): IToolNameMap[K]
|
| 463 |
+
get<T extends ToolConstructor>(name: string): InstanceType<T> | undefined
|
| 464 |
+
get(): InstanceType<DevToolsConstructor>
|
| 465 |
+
/**
|
| 466 |
+
* Add tool.
|
| 467 |
+
*/
|
| 468 |
+
add<T extends ToolConstructor>(
|
| 469 |
+
tool: InstanceType<T> | ((eruda: Eruda) => InstanceType<T>)
|
| 470 |
+
): Eruda | undefined
|
| 471 |
+
/**
|
| 472 |
+
* Remove tool.
|
| 473 |
+
*/
|
| 474 |
+
remove(name: string): Eruda | undefined
|
| 475 |
+
/**
|
| 476 |
+
* Show eruda panel.
|
| 477 |
+
*/
|
| 478 |
+
show(name?: string): Eruda | undefined
|
| 479 |
+
/**
|
| 480 |
+
* Hide eruda panel.
|
| 481 |
+
*/
|
| 482 |
+
hide(): Eruda | undefined
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
+
export interface Eruda extends ErudaApis {
|
| 486 |
+
/**
|
| 487 |
+
* Display console logs. Implementation detail follows the console api spec.
|
| 488 |
+
*/
|
| 489 |
+
Console: ErudaConsoleConstructor
|
| 490 |
+
/**
|
| 491 |
+
* Check dom element status.
|
| 492 |
+
*/
|
| 493 |
+
Elements: ElementsConstructor
|
| 494 |
+
/**
|
| 495 |
+
* Display special information, could be used for displaying user info to track user logs.
|
| 496 |
+
* By default, page url and browser user agent is shown.
|
| 497 |
+
*/
|
| 498 |
+
Info: InfoConstructor
|
| 499 |
+
/**
|
| 500 |
+
* Display requests.
|
| 501 |
+
*/
|
| 502 |
+
Network: NetworkConstructor
|
| 503 |
+
/**
|
| 504 |
+
* LocalStorage, sessionStorage, cookies, scripts, styleSheets and images.
|
| 505 |
+
*/
|
| 506 |
+
Resources: ResourcesConstructor
|
| 507 |
+
/**
|
| 508 |
+
* Customization for all tools.
|
| 509 |
+
*/
|
| 510 |
+
Settings: SettingsConstructor
|
| 511 |
+
/**
|
| 512 |
+
* Allow you to register small functions that can be triggered multiple times.
|
| 513 |
+
*/
|
| 514 |
+
Snippets: SnippetsConstructor
|
| 515 |
+
/**
|
| 516 |
+
* View object, html, js, and css.
|
| 517 |
+
*/
|
| 518 |
+
Sources: SourcesConstructor
|
| 519 |
+
/**
|
| 520 |
+
* Eruda Tool
|
| 521 |
+
*/
|
| 522 |
+
Tool: ToolConstructor
|
| 523 |
+
/**
|
| 524 |
+
* Eruda Util
|
| 525 |
+
*/
|
| 526 |
+
util: Util
|
| 527 |
+
/**
|
| 528 |
+
* Eruda version
|
| 529 |
+
*/
|
| 530 |
+
readonly version: string
|
| 531 |
+
}
|
| 532 |
+
|
| 533 |
+
const eruda: Eruda
|
| 534 |
+
|
| 535 |
+
export default eruda
|
| 536 |
+
}
|
eslint.config.mjs
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import babelEslintParser from '@babel/eslint-parser'
|
| 2 |
+
import eslintJs from '@eslint/js'
|
| 3 |
+
import globals from 'globals'
|
| 4 |
+
|
| 5 |
+
export default [
|
| 6 |
+
eslintJs.configs.recommended,
|
| 7 |
+
{
|
| 8 |
+
languageOptions: {
|
| 9 |
+
parser: babelEslintParser,
|
| 10 |
+
parserOptions: {
|
| 11 |
+
requireConfigFile: false,
|
| 12 |
+
babelOptions: {
|
| 13 |
+
babelrc: false,
|
| 14 |
+
configFile: false,
|
| 15 |
+
},
|
| 16 |
+
},
|
| 17 |
+
globals: {
|
| 18 |
+
...globals.builtin,
|
| 19 |
+
...globals.browser,
|
| 20 |
+
...globals.commonjs,
|
| 21 |
+
VERSION: true,
|
| 22 |
+
ENV: true,
|
| 23 |
+
},
|
| 24 |
+
},
|
| 25 |
+
rules: {
|
| 26 |
+
quotes: ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }],
|
| 27 |
+
'prefer-const': 2,
|
| 28 |
+
},
|
| 29 |
+
},
|
| 30 |
+
{files: ['build/**/*.js'], languageOptions:{globals: {...globals.node}}},
|
| 31 |
+
{
|
| 32 |
+
ignores: ['test','dist','coverage'],
|
| 33 |
+
}
|
| 34 |
+
]
|
karma.conf.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const webpackCfg = require('./build/webpack.dev')
|
| 2 |
+
webpackCfg.devtool = 'inline-source-map'
|
| 3 |
+
webpackCfg.module.rules.push({
|
| 4 |
+
test: /\.js$/,
|
| 5 |
+
exclude: /node_modules|lib\/util\.js/,
|
| 6 |
+
loader: '@jsdevtools/coverage-istanbul-loader',
|
| 7 |
+
enforce: 'post',
|
| 8 |
+
options: {
|
| 9 |
+
esModules: true,
|
| 10 |
+
},
|
| 11 |
+
})
|
| 12 |
+
|
| 13 |
+
module.exports = function (config) {
|
| 14 |
+
config.set({
|
| 15 |
+
basePath: '',
|
| 16 |
+
frameworks: ['jquery-1.8.3'],
|
| 17 |
+
files: [
|
| 18 |
+
'src/index.js',
|
| 19 |
+
'test/init.js',
|
| 20 |
+
'node_modules/jasmine-core/lib/jasmine-core/jasmine.js',
|
| 21 |
+
'node_modules/karma-jasmine/lib/boot.js',
|
| 22 |
+
'node_modules/karma-jasmine/lib/adapter.js',
|
| 23 |
+
'node_modules/jasmine-jquery/lib/jasmine-jquery.js',
|
| 24 |
+
'test/util.js',
|
| 25 |
+
'test/console.js',
|
| 26 |
+
'test/elements.js',
|
| 27 |
+
'test/info.js',
|
| 28 |
+
'test/network.js',
|
| 29 |
+
'test/resources.js',
|
| 30 |
+
'test/snippets.js',
|
| 31 |
+
'test/sources.js',
|
| 32 |
+
'test/settings.js',
|
| 33 |
+
'test/eruda.js',
|
| 34 |
+
],
|
| 35 |
+
plugins: [
|
| 36 |
+
'karma-jasmine',
|
| 37 |
+
'karma-jquery',
|
| 38 |
+
'karma-chrome-launcher',
|
| 39 |
+
'karma-webpack',
|
| 40 |
+
'karma-sourcemap-loader',
|
| 41 |
+
'karma-coverage-istanbul-reporter',
|
| 42 |
+
],
|
| 43 |
+
webpackServer: {
|
| 44 |
+
noInfo: true,
|
| 45 |
+
},
|
| 46 |
+
preprocessors: {
|
| 47 |
+
'src/index.js': ['webpack', 'sourcemap'],
|
| 48 |
+
},
|
| 49 |
+
webpack: webpackCfg,
|
| 50 |
+
coverageIstanbulReporter: {
|
| 51 |
+
reports: ['html', 'lcovonly', 'text', 'text-summary'],
|
| 52 |
+
},
|
| 53 |
+
reporters: ['progress', 'coverage-istanbul'],
|
| 54 |
+
port: 9876,
|
| 55 |
+
colors: true,
|
| 56 |
+
logLevel: config.LOG_INFO,
|
| 57 |
+
browsers: ['ChromeHeadless'],
|
| 58 |
+
singleRun: true,
|
| 59 |
+
concurrency: Infinity,
|
| 60 |
+
})
|
| 61 |
+
}
|
package.json
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "eruda",
|
| 3 |
+
"version": "3.4.1",
|
| 4 |
+
"description": "Console for Mobile Browsers",
|
| 5 |
+
"main": "eruda.js",
|
| 6 |
+
"browserslist": [
|
| 7 |
+
"since 2015",
|
| 8 |
+
"not dead"
|
| 9 |
+
],
|
| 10 |
+
"scripts": {
|
| 11 |
+
"ci": "npm run lint && npm run test && npm run build && npm run es5",
|
| 12 |
+
"build": "lsla shx rm -rf dist && webpack --config build/webpack.prod.js && webpack --config build/webpack.polyfill.js && node build/build && lsla shx cp README.md eruda.d.ts dist",
|
| 13 |
+
"build:analyser": "webpack --config build/webpack.analyser.js",
|
| 14 |
+
"dev": "webpack-dev-server --config build/webpack.dev.js --host 0.0.0.0",
|
| 15 |
+
"test": "karma start",
|
| 16 |
+
"format": "lsla prettier \"*.{js,ts}\" \"src/**/*.{js,scss,css}\" \"build/*.js\" \"test/*.{js,html}\" --write",
|
| 17 |
+
"lint": "eslint .",
|
| 18 |
+
"lint:fix": "npm run lint -- --fix",
|
| 19 |
+
"es5": "es-check es5 dist/eruda.js dist/eruda-polyfill.js",
|
| 20 |
+
"setup": "lsla shx mkdir -p test/lib && lsla shx cp node_modules/jasmine-core/lib/jasmine-core/{jasmine.css,jasmine.js,jasmine-html.js,boot.js} test/lib && lsla shx cp node_modules/jasmine-jquery/lib/jasmine-jquery.js test/lib && lsla shx cp node_modules/jquery/dist/jquery.js test/lib",
|
| 21 |
+
"genIcon": "lsla genIcon --input src/style/icon --output src/style/icon.css --name eruda-icon && lsla prettier src/**/*.css --write"
|
| 22 |
+
},
|
| 23 |
+
"repository": {
|
| 24 |
+
"type": "git",
|
| 25 |
+
"url": "git+https://github.com/liriliri/eruda.git"
|
| 26 |
+
},
|
| 27 |
+
"keywords": [
|
| 28 |
+
"console",
|
| 29 |
+
"mobile",
|
| 30 |
+
"debug"
|
| 31 |
+
],
|
| 32 |
+
"author": "redhoodsu",
|
| 33 |
+
"license": "MIT",
|
| 34 |
+
"bugs": {
|
| 35 |
+
"url": "https://github.com/liriliri/eruda/issues"
|
| 36 |
+
},
|
| 37 |
+
"homepage": "https://eruda.liriliri.io/",
|
| 38 |
+
"devDependencies": {
|
| 39 |
+
"@babel/core": "^7.18.6",
|
| 40 |
+
"@babel/eslint-parser": "^7.26.10",
|
| 41 |
+
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
| 42 |
+
"@babel/plugin-transform-runtime": "^7.18.6",
|
| 43 |
+
"@babel/preset-env": "^7.18.6",
|
| 44 |
+
"@babel/runtime": "^7.18.6",
|
| 45 |
+
"@eslint/js": "^9.22.0",
|
| 46 |
+
"@jsdevtools/coverage-istanbul-loader": "^3.0.5",
|
| 47 |
+
"autoprefixer": "^9.7.4",
|
| 48 |
+
"babel-loader": "^8.2.5",
|
| 49 |
+
"chobitsu": "^1.8.4",
|
| 50 |
+
"core-js": "^3.37.1",
|
| 51 |
+
"css-loader": "^3.4.2",
|
| 52 |
+
"es-check": "^6.2.1",
|
| 53 |
+
"eslint": "^9.22.0",
|
| 54 |
+
"eslint-webpack-plugin": "^5.0.0",
|
| 55 |
+
"globals": "^16.0.0",
|
| 56 |
+
"jasmine-core": "^2.99.1",
|
| 57 |
+
"jasmine-jquery": "^2.1.1",
|
| 58 |
+
"jquery": "^3.4.1",
|
| 59 |
+
"karma": "^6.4.0",
|
| 60 |
+
"karma-chrome-launcher": "^3.1.0",
|
| 61 |
+
"karma-coverage-istanbul-reporter": "^2.1.1",
|
| 62 |
+
"karma-jasmine": "^1.1.2",
|
| 63 |
+
"karma-jquery": "^0.2.4",
|
| 64 |
+
"karma-sourcemap-loader": "^0.3.7",
|
| 65 |
+
"karma-webpack": "^5.0.0",
|
| 66 |
+
"licia": "^1.44.0",
|
| 67 |
+
"luna-box-model": "^1.0.0",
|
| 68 |
+
"luna-console": "^1.3.6",
|
| 69 |
+
"luna-data-grid": "^1.4.2",
|
| 70 |
+
"luna-dom-viewer": "^1.8.3",
|
| 71 |
+
"luna-modal": "^1.3.1",
|
| 72 |
+
"luna-notification": "^0.3.3",
|
| 73 |
+
"luna-object-viewer": "^0.3.2",
|
| 74 |
+
"luna-setting": "^2.0.2",
|
| 75 |
+
"luna-tab": "^0.3.4",
|
| 76 |
+
"luna-text-viewer": "^0.2.1",
|
| 77 |
+
"postcss-clean": "^1.2.2",
|
| 78 |
+
"postcss-loader": "^3.0.0",
|
| 79 |
+
"postcss-prefixer": "^2.1.3",
|
| 80 |
+
"raw-loader": "^4.0.2",
|
| 81 |
+
"sass": "^1.77.6",
|
| 82 |
+
"sass-loader": "^14.2.1",
|
| 83 |
+
"webpack": "^5.92.1",
|
| 84 |
+
"webpack-bundle-analyzer": "^4.7.0",
|
| 85 |
+
"webpack-cli": "^5.1.4",
|
| 86 |
+
"webpack-dev-server": "^5.0.4"
|
| 87 |
+
}
|
| 88 |
+
}
|
src/Console/Console.js
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import Tool from '../DevTools/Tool'
|
| 2 |
+
import noop from 'licia/noop'
|
| 3 |
+
import $ from 'licia/$'
|
| 4 |
+
import toStr from 'licia/toStr'
|
| 5 |
+
import isFn from 'licia/isFn'
|
| 6 |
+
import Emitter from 'licia/Emitter'
|
| 7 |
+
import isStr from 'licia/isStr'
|
| 8 |
+
import isRegExp from 'licia/isRegExp'
|
| 9 |
+
import uncaught from 'licia/uncaught'
|
| 10 |
+
import trim from 'licia/trim'
|
| 11 |
+
import upperFirst from 'licia/upperFirst'
|
| 12 |
+
import isHidden from 'licia/isHidden'
|
| 13 |
+
import isNull from 'licia/isNull'
|
| 14 |
+
import isArr from 'licia/isArr'
|
| 15 |
+
import extend from 'licia/extend'
|
| 16 |
+
import evalCss from '../lib/evalCss'
|
| 17 |
+
import Settings from '../Settings/Settings'
|
| 18 |
+
import LunaConsole from 'luna-console'
|
| 19 |
+
import LunaModal from 'luna-modal'
|
| 20 |
+
import { classPrefix as c } from '../lib/util'
|
| 21 |
+
|
| 22 |
+
uncaught.start()
|
| 23 |
+
|
| 24 |
+
export default class Console extends Tool {
|
| 25 |
+
constructor({ name = 'console' } = {}) {
|
| 26 |
+
super()
|
| 27 |
+
|
| 28 |
+
Emitter.mixin(this)
|
| 29 |
+
|
| 30 |
+
this.name = name
|
| 31 |
+
this._selectedLog = null
|
| 32 |
+
}
|
| 33 |
+
init($el, container) {
|
| 34 |
+
super.init($el)
|
| 35 |
+
this._container = container
|
| 36 |
+
|
| 37 |
+
this._appendTpl()
|
| 38 |
+
|
| 39 |
+
this._initCfg()
|
| 40 |
+
|
| 41 |
+
this._initLogger()
|
| 42 |
+
this._exposeLogger()
|
| 43 |
+
this._bindEvent()
|
| 44 |
+
}
|
| 45 |
+
show() {
|
| 46 |
+
super.show()
|
| 47 |
+
this._handleShow()
|
| 48 |
+
}
|
| 49 |
+
overrideConsole() {
|
| 50 |
+
const origConsole = (this._origConsole = {})
|
| 51 |
+
const winConsole = window.console
|
| 52 |
+
|
| 53 |
+
CONSOLE_METHOD.forEach((name) => {
|
| 54 |
+
let origin = (origConsole[name] = noop)
|
| 55 |
+
if (winConsole[name]) {
|
| 56 |
+
origin = origConsole[name] = winConsole[name].bind(winConsole)
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
winConsole[name] = (...args) => {
|
| 60 |
+
this[name](...args)
|
| 61 |
+
origin(...args)
|
| 62 |
+
}
|
| 63 |
+
})
|
| 64 |
+
|
| 65 |
+
return this
|
| 66 |
+
}
|
| 67 |
+
setGlobal(name, val) {
|
| 68 |
+
this._logger.setGlobal(name, val)
|
| 69 |
+
}
|
| 70 |
+
restoreConsole() {
|
| 71 |
+
if (!this._origConsole) return this
|
| 72 |
+
|
| 73 |
+
CONSOLE_METHOD.forEach(
|
| 74 |
+
(name) => (window.console[name] = this._origConsole[name])
|
| 75 |
+
)
|
| 76 |
+
delete this._origConsole
|
| 77 |
+
|
| 78 |
+
return this
|
| 79 |
+
}
|
| 80 |
+
catchGlobalErr() {
|
| 81 |
+
uncaught.addListener(this._handleErr)
|
| 82 |
+
|
| 83 |
+
return this
|
| 84 |
+
}
|
| 85 |
+
ignoreGlobalErr() {
|
| 86 |
+
uncaught.rmListener(this._handleErr)
|
| 87 |
+
|
| 88 |
+
return this
|
| 89 |
+
}
|
| 90 |
+
filter(filter) {
|
| 91 |
+
const $filterText = this._$filterText
|
| 92 |
+
const logger = this._logger
|
| 93 |
+
|
| 94 |
+
if (isStr(filter)) {
|
| 95 |
+
$filterText.text(filter)
|
| 96 |
+
logger.setOption('filter', trim(filter))
|
| 97 |
+
} else if (isRegExp(filter)) {
|
| 98 |
+
$filterText.text(toStr(filter))
|
| 99 |
+
logger.setOption('filter', filter)
|
| 100 |
+
} else if (isFn(filter)) {
|
| 101 |
+
$filterText.text('ƒ')
|
| 102 |
+
logger.setOption('filter', filter)
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
destroy() {
|
| 106 |
+
this._logger.destroy()
|
| 107 |
+
super.destroy()
|
| 108 |
+
|
| 109 |
+
this._container.off('show', this._handleShow)
|
| 110 |
+
|
| 111 |
+
if (this._style) {
|
| 112 |
+
evalCss.remove(this._style)
|
| 113 |
+
}
|
| 114 |
+
this.ignoreGlobalErr()
|
| 115 |
+
this.restoreConsole()
|
| 116 |
+
this._rmCfg()
|
| 117 |
+
}
|
| 118 |
+
_handleShow = () => {
|
| 119 |
+
if (isHidden(this._$el.get(0))) return
|
| 120 |
+
this._logger.renderViewport()
|
| 121 |
+
}
|
| 122 |
+
_handleErr = (err) => {
|
| 123 |
+
this._logger.error(err)
|
| 124 |
+
}
|
| 125 |
+
_enableJsExecution(enabled) {
|
| 126 |
+
const $el = this._$el
|
| 127 |
+
const $jsInput = $el.find(c('.js-input'))
|
| 128 |
+
|
| 129 |
+
if (enabled) {
|
| 130 |
+
$jsInput.show()
|
| 131 |
+
$el.rmClass(c('js-input-hidden'))
|
| 132 |
+
} else {
|
| 133 |
+
$jsInput.hide()
|
| 134 |
+
$el.addClass(c('js-input-hidden'))
|
| 135 |
+
}
|
| 136 |
+
}
|
| 137 |
+
_appendTpl() {
|
| 138 |
+
const $el = this._$el
|
| 139 |
+
|
| 140 |
+
this._style = evalCss(require('./Console.scss'))
|
| 141 |
+
$el.append(
|
| 142 |
+
c(`
|
| 143 |
+
<div class="control">
|
| 144 |
+
<span class="icon-clear clear-console"></span>
|
| 145 |
+
<span class="level active" data-level="all">All</span>
|
| 146 |
+
<span class="level" data-level="info">Info</span>
|
| 147 |
+
<span class="level" data-level="warning">Warning</span>
|
| 148 |
+
<span class="level" data-level="error">Error</span>
|
| 149 |
+
<span class="filter-text"></span>
|
| 150 |
+
<span class="icon-filter filter"></span>
|
| 151 |
+
<span class="icon-copy icon-disabled copy"></span>
|
| 152 |
+
</div>
|
| 153 |
+
<div class="logs-container"></div>
|
| 154 |
+
<div class="js-input">
|
| 155 |
+
<div class="buttons">
|
| 156 |
+
<div class="button cancel">Cancel</div>
|
| 157 |
+
<div class="button execute">Execute</div>
|
| 158 |
+
</div>
|
| 159 |
+
<span class="icon-arrow-right"></span>
|
| 160 |
+
<textarea></textarea>
|
| 161 |
+
</div>
|
| 162 |
+
`)
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
const _$inputContainer = $el.find(c('.js-input'))
|
| 166 |
+
const _$input = _$inputContainer.find('textarea')
|
| 167 |
+
const _$inputBtns = _$inputContainer.find(c('.buttons'))
|
| 168 |
+
|
| 169 |
+
extend(this, {
|
| 170 |
+
_$control: $el.find(c('.control')),
|
| 171 |
+
_$logs: $el.find(c('.logs-container')),
|
| 172 |
+
_$inputContainer,
|
| 173 |
+
_$input,
|
| 174 |
+
_$inputBtns,
|
| 175 |
+
_$filterText: $el.find(c('.filter-text')),
|
| 176 |
+
})
|
| 177 |
+
}
|
| 178 |
+
_initLogger() {
|
| 179 |
+
const cfg = this.config
|
| 180 |
+
let maxLogNum = cfg.get('maxLogNum')
|
| 181 |
+
maxLogNum = maxLogNum === 'infinite' ? 0 : +maxLogNum
|
| 182 |
+
|
| 183 |
+
const $level = this._$control.find(c('.level'))
|
| 184 |
+
const logger = new LunaConsole(this._$logs.get(0), {
|
| 185 |
+
asyncRender: cfg.get('asyncRender'),
|
| 186 |
+
maxNum: maxLogNum,
|
| 187 |
+
showHeader: cfg.get('displayExtraInfo'),
|
| 188 |
+
unenumerable: cfg.get('displayUnenumerable'),
|
| 189 |
+
accessGetter: cfg.get('displayGetterVal'),
|
| 190 |
+
lazyEvaluation: cfg.get('lazyEvaluation'),
|
| 191 |
+
})
|
| 192 |
+
|
| 193 |
+
logger.on('optionChange', (name, val) => {
|
| 194 |
+
switch (name) {
|
| 195 |
+
case 'level':
|
| 196 |
+
$level.each(function () {
|
| 197 |
+
const $this = $(this)
|
| 198 |
+
const level = $this.data('level')
|
| 199 |
+
const isMatch = level === val || (level === 'all' && isArr(val))
|
| 200 |
+
|
| 201 |
+
$this[isMatch ? 'addClass' : 'rmClass'](c('active'))
|
| 202 |
+
})
|
| 203 |
+
break
|
| 204 |
+
}
|
| 205 |
+
})
|
| 206 |
+
|
| 207 |
+
if (cfg.get('overrideConsole')) this.overrideConsole()
|
| 208 |
+
|
| 209 |
+
this._logger = logger
|
| 210 |
+
}
|
| 211 |
+
_exposeLogger() {
|
| 212 |
+
const logger = this._logger
|
| 213 |
+
const methods = ['html'].concat(CONSOLE_METHOD)
|
| 214 |
+
|
| 215 |
+
methods.forEach(
|
| 216 |
+
(name) =>
|
| 217 |
+
(this[name] = (...args) => {
|
| 218 |
+
logger[name](...args)
|
| 219 |
+
this.emit(name, ...args)
|
| 220 |
+
|
| 221 |
+
return this
|
| 222 |
+
})
|
| 223 |
+
)
|
| 224 |
+
}
|
| 225 |
+
_bindEvent() {
|
| 226 |
+
const container = this._container
|
| 227 |
+
const $input = this._$input
|
| 228 |
+
const $inputBtns = this._$inputBtns
|
| 229 |
+
const $control = this._$control
|
| 230 |
+
|
| 231 |
+
const logger = this._logger
|
| 232 |
+
const config = this.config
|
| 233 |
+
|
| 234 |
+
$control
|
| 235 |
+
.on('click', c('.clear-console'), () => logger.clear(true))
|
| 236 |
+
.on('click', c('.level'), function () {
|
| 237 |
+
let level = $(this).data('level')
|
| 238 |
+
if (level === 'all') {
|
| 239 |
+
level = ['verbose', 'info', 'warning', 'error']
|
| 240 |
+
}
|
| 241 |
+
logger.setOption('level', level)
|
| 242 |
+
})
|
| 243 |
+
.on('click', c('.filter'), () => {
|
| 244 |
+
LunaModal.prompt('Filter').then((filter) => {
|
| 245 |
+
if (isNull(filter)) return
|
| 246 |
+
this.filter(filter)
|
| 247 |
+
})
|
| 248 |
+
})
|
| 249 |
+
.on('click', c('.copy'), () => {
|
| 250 |
+
this._selectedLog.copy()
|
| 251 |
+
container.notify('Copied', { icon: 'success' })
|
| 252 |
+
})
|
| 253 |
+
|
| 254 |
+
$inputBtns
|
| 255 |
+
.on('click', c('.cancel'), () => this._hideInput())
|
| 256 |
+
.on('click', c('.execute'), () => {
|
| 257 |
+
const jsInput = $input.val().trim()
|
| 258 |
+
if (jsInput === '') return
|
| 259 |
+
|
| 260 |
+
logger.evaluate(jsInput)
|
| 261 |
+
$input.val('').get(0).blur()
|
| 262 |
+
this._hideInput()
|
| 263 |
+
})
|
| 264 |
+
|
| 265 |
+
$input.on('focusin', () => this._showInput())
|
| 266 |
+
|
| 267 |
+
logger.on('insert', (log) => {
|
| 268 |
+
const autoShow = log.type === 'error' && config.get('displayIfErr')
|
| 269 |
+
|
| 270 |
+
if (autoShow) container.showTool('console').show()
|
| 271 |
+
})
|
| 272 |
+
|
| 273 |
+
logger.on('select', (log) => {
|
| 274 |
+
this._selectedLog = log
|
| 275 |
+
$control.find(c('.icon-copy')).rmClass(c('icon-disabled'))
|
| 276 |
+
})
|
| 277 |
+
|
| 278 |
+
logger.on('deselect', () => {
|
| 279 |
+
this._selectedLog = null
|
| 280 |
+
$control.find(c('.icon-copy')).addClass(c('icon-disabled'))
|
| 281 |
+
})
|
| 282 |
+
|
| 283 |
+
container.on('show', this._handleShow)
|
| 284 |
+
}
|
| 285 |
+
_hideInput() {
|
| 286 |
+
this._$inputContainer.rmClass(c('active'))
|
| 287 |
+
this._$inputBtns.css('display', 'none')
|
| 288 |
+
}
|
| 289 |
+
_showInput() {
|
| 290 |
+
this._$inputContainer.addClass(c('active'))
|
| 291 |
+
this._$inputBtns.css('display', 'flex')
|
| 292 |
+
}
|
| 293 |
+
_rmCfg() {
|
| 294 |
+
const cfg = this.config
|
| 295 |
+
|
| 296 |
+
const settings = this._container.get('settings')
|
| 297 |
+
if (!settings) return
|
| 298 |
+
|
| 299 |
+
settings
|
| 300 |
+
.remove(cfg, 'asyncRender')
|
| 301 |
+
.remove(cfg, 'jsExecution')
|
| 302 |
+
.remove(cfg, 'catchGlobalErr')
|
| 303 |
+
.remove(cfg, 'overrideConsole')
|
| 304 |
+
.remove(cfg, 'displayExtraInfo')
|
| 305 |
+
.remove(cfg, 'displayUnenumerable')
|
| 306 |
+
.remove(cfg, 'displayGetterVal')
|
| 307 |
+
.remove(cfg, 'lazyEvaluation')
|
| 308 |
+
.remove(cfg, 'displayIfErr')
|
| 309 |
+
.remove(cfg, 'maxLogNum')
|
| 310 |
+
.remove(upperFirst(this.name))
|
| 311 |
+
}
|
| 312 |
+
_initCfg() {
|
| 313 |
+
const container = this._container
|
| 314 |
+
|
| 315 |
+
const cfg = (this.config = Settings.createCfg(this.name, {
|
| 316 |
+
asyncRender: true,
|
| 317 |
+
catchGlobalErr: true,
|
| 318 |
+
jsExecution: true,
|
| 319 |
+
overrideConsole: true,
|
| 320 |
+
displayExtraInfo: false,
|
| 321 |
+
displayUnenumerable: true,
|
| 322 |
+
displayGetterVal: true,
|
| 323 |
+
lazyEvaluation: true,
|
| 324 |
+
displayIfErr: false,
|
| 325 |
+
maxLogNum: 'infinite',
|
| 326 |
+
}))
|
| 327 |
+
|
| 328 |
+
this._enableJsExecution(cfg.get('jsExecution'))
|
| 329 |
+
if (cfg.get('catchGlobalErr')) this.catchGlobalErr()
|
| 330 |
+
|
| 331 |
+
cfg.on('change', (key, val) => {
|
| 332 |
+
const logger = this._logger
|
| 333 |
+
switch (key) {
|
| 334 |
+
case 'asyncRender':
|
| 335 |
+
return logger.setOption('asyncRender', val)
|
| 336 |
+
case 'jsExecution':
|
| 337 |
+
return this._enableJsExecution(val)
|
| 338 |
+
case 'catchGlobalErr':
|
| 339 |
+
return val ? this.catchGlobalErr() : this.ignoreGlobalErr()
|
| 340 |
+
case 'overrideConsole':
|
| 341 |
+
return val ? this.overrideConsole() : this.restoreConsole()
|
| 342 |
+
case 'maxLogNum':
|
| 343 |
+
return logger.setOption('maxNum', val === 'infinite' ? 0 : +val)
|
| 344 |
+
case 'displayExtraInfo':
|
| 345 |
+
return logger.setOption('showHeader', val)
|
| 346 |
+
case 'displayUnenumerable':
|
| 347 |
+
return logger.setOption('unenumerable', val)
|
| 348 |
+
case 'displayGetterVal':
|
| 349 |
+
return logger.setOption('accessGetter', val)
|
| 350 |
+
case 'lazyEvaluation':
|
| 351 |
+
return logger.setOption('lazyEvaluation', val)
|
| 352 |
+
}
|
| 353 |
+
})
|
| 354 |
+
|
| 355 |
+
const settings = container.get('settings')
|
| 356 |
+
if (!settings) return
|
| 357 |
+
|
| 358 |
+
settings
|
| 359 |
+
.text(upperFirst(this.name))
|
| 360 |
+
.switch(cfg, 'asyncRender', 'Asynchronous Rendering')
|
| 361 |
+
.switch(cfg, 'jsExecution', 'Enable JavaScript Execution')
|
| 362 |
+
.switch(cfg, 'catchGlobalErr', 'Catch Global Errors')
|
| 363 |
+
.switch(cfg, 'overrideConsole', 'Override Console')
|
| 364 |
+
.switch(cfg, 'displayIfErr', 'Auto Display If Error Occurs')
|
| 365 |
+
.switch(cfg, 'displayExtraInfo', 'Display Extra Information')
|
| 366 |
+
.switch(cfg, 'displayUnenumerable', 'Display Unenumerable Properties')
|
| 367 |
+
.switch(cfg, 'displayGetterVal', 'Access Getter Value')
|
| 368 |
+
.switch(cfg, 'lazyEvaluation', 'Lazy Evaluation')
|
| 369 |
+
.select(cfg, 'maxLogNum', 'Max Log Number', [
|
| 370 |
+
'infinite',
|
| 371 |
+
'250',
|
| 372 |
+
'125',
|
| 373 |
+
'100',
|
| 374 |
+
'50',
|
| 375 |
+
'10',
|
| 376 |
+
])
|
| 377 |
+
.separator()
|
| 378 |
+
}
|
| 379 |
+
}
|
| 380 |
+
|
| 381 |
+
const CONSOLE_METHOD = [
|
| 382 |
+
'log',
|
| 383 |
+
'error',
|
| 384 |
+
'info',
|
| 385 |
+
'warn',
|
| 386 |
+
'dir',
|
| 387 |
+
'time',
|
| 388 |
+
'timeLog',
|
| 389 |
+
'timeEnd',
|
| 390 |
+
'clear',
|
| 391 |
+
'table',
|
| 392 |
+
'assert',
|
| 393 |
+
'count',
|
| 394 |
+
'countReset',
|
| 395 |
+
'debug',
|
| 396 |
+
'group',
|
| 397 |
+
'groupCollapsed',
|
| 398 |
+
'groupEnd',
|
| 399 |
+
]
|
src/Console/Console.scss
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@use '../style/variable' as *;
|
| 2 |
+
@use '../style/mixin' as *;
|
| 3 |
+
|
| 4 |
+
#console {
|
| 5 |
+
padding-top: 40px;
|
| 6 |
+
padding-bottom: 24px;
|
| 7 |
+
width: 100%;
|
| 8 |
+
height: 100%;
|
| 9 |
+
&.js-input-hidden {
|
| 10 |
+
padding-bottom: 0;
|
| 11 |
+
}
|
| 12 |
+
.control {
|
| 13 |
+
padding: 10px 10px 10px 35px;
|
| 14 |
+
@include control();
|
| 15 |
+
.icon-clear {
|
| 16 |
+
padding-right: 0px;
|
| 17 |
+
left: 0;
|
| 18 |
+
}
|
| 19 |
+
.icon-copy {
|
| 20 |
+
right: 0;
|
| 21 |
+
}
|
| 22 |
+
.icon-filter {
|
| 23 |
+
right: 23px;
|
| 24 |
+
}
|
| 25 |
+
.level {
|
| 26 |
+
cursor: pointer;
|
| 27 |
+
font-size: $font-size-s;
|
| 28 |
+
height: 20px;
|
| 29 |
+
display: inline-block;
|
| 30 |
+
margin: 0 2px;
|
| 31 |
+
padding: 0 4px;
|
| 32 |
+
line-height: 20px;
|
| 33 |
+
transition: background-color $anim-duration, color $anim-duration;
|
| 34 |
+
&.active {
|
| 35 |
+
background: var(--highlight);
|
| 36 |
+
color: var(--select-foreground);
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
.filter-text {
|
| 40 |
+
white-space: nowrap;
|
| 41 |
+
position: absolute;
|
| 42 |
+
line-height: 20px;
|
| 43 |
+
max-width: 80px;
|
| 44 |
+
overflow: hidden;
|
| 45 |
+
right: 55px;
|
| 46 |
+
font-size: $font-size;
|
| 47 |
+
text-overflow: ellipsis;
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
.js-input {
|
| 51 |
+
pointer-events: none;
|
| 52 |
+
position: absolute;
|
| 53 |
+
z-index: 100;
|
| 54 |
+
left: 0;
|
| 55 |
+
bottom: 0;
|
| 56 |
+
width: 100%;
|
| 57 |
+
border-top: 1px solid var(--border);
|
| 58 |
+
height: 24px;
|
| 59 |
+
.icon-arrow-right {
|
| 60 |
+
line-height: 23px;
|
| 61 |
+
color: var(--accent);
|
| 62 |
+
position: absolute;
|
| 63 |
+
left: 10px;
|
| 64 |
+
top: 0;
|
| 65 |
+
z-index: 10;
|
| 66 |
+
}
|
| 67 |
+
&.active {
|
| 68 |
+
height: 100%;
|
| 69 |
+
padding-top: 40px;
|
| 70 |
+
padding-bottom: 40px;
|
| 71 |
+
border-top: none;
|
| 72 |
+
.icon-arrow-right {
|
| 73 |
+
display: none;
|
| 74 |
+
}
|
| 75 |
+
textarea {
|
| 76 |
+
overflow: auto;
|
| 77 |
+
padding-left: 10px;
|
| 78 |
+
}
|
| 79 |
+
}
|
| 80 |
+
.buttons {
|
| 81 |
+
display: none;
|
| 82 |
+
position: absolute;
|
| 83 |
+
left: 0;
|
| 84 |
+
bottom: 0;
|
| 85 |
+
width: 100%;
|
| 86 |
+
height: 40px;
|
| 87 |
+
color: var(--primary);
|
| 88 |
+
background: var(--darker-background);
|
| 89 |
+
font-size: $font-size-s;
|
| 90 |
+
border-top: 1px solid var(--border);
|
| 91 |
+
.button {
|
| 92 |
+
pointer-events: all;
|
| 93 |
+
cursor: pointer;
|
| 94 |
+
flex: 1;
|
| 95 |
+
text-align: center;
|
| 96 |
+
border-right: 1px solid var(--border);
|
| 97 |
+
height: 40px;
|
| 98 |
+
line-height: 40px;
|
| 99 |
+
transition: background-color $anim-duration, color $anim-duration;
|
| 100 |
+
&:last-child {
|
| 101 |
+
border-right: none;
|
| 102 |
+
}
|
| 103 |
+
&:active {
|
| 104 |
+
color: var(--select-foreground);
|
| 105 |
+
background: var(--highlight);
|
| 106 |
+
}
|
| 107 |
+
}
|
| 108 |
+
}
|
| 109 |
+
textarea {
|
| 110 |
+
overflow: hidden;
|
| 111 |
+
pointer-events: all;
|
| 112 |
+
padding: 3px 10px;
|
| 113 |
+
padding-left: 25px;
|
| 114 |
+
outline: none;
|
| 115 |
+
border: none;
|
| 116 |
+
font-size: $font-size;
|
| 117 |
+
width: 100%;
|
| 118 |
+
height: 100%;
|
| 119 |
+
user-select: text;
|
| 120 |
+
resize: none;
|
| 121 |
+
color: var(--primary);
|
| 122 |
+
background: var(--background);
|
| 123 |
+
}
|
| 124 |
+
}
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
.safe-area #console {
|
| 128 |
+
@include safe-area(padding-bottom, 24px);
|
| 129 |
+
&.js-input-hidden {
|
| 130 |
+
padding-bottom: 0;
|
| 131 |
+
}
|
| 132 |
+
.js-input {
|
| 133 |
+
@include safe-area(height, 24px);
|
| 134 |
+
&.active {
|
| 135 |
+
height: 100%;
|
| 136 |
+
@include safe-area(padding-bottom, 40px);
|
| 137 |
+
}
|
| 138 |
+
.buttons {
|
| 139 |
+
@include safe-area(height, 40px);
|
| 140 |
+
.button {
|
| 141 |
+
@include safe-area(height, 40px);
|
| 142 |
+
}
|
| 143 |
+
}
|
| 144 |
+
}
|
| 145 |
+
}
|
src/DevTools/DevTools.js
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logger from '../lib/logger'
|
| 2 |
+
import Tool from './Tool'
|
| 3 |
+
import Settings from '../Settings/Settings'
|
| 4 |
+
import Emitter from 'licia/Emitter'
|
| 5 |
+
import defaults from 'licia/defaults'
|
| 6 |
+
import keys from 'licia/keys'
|
| 7 |
+
import last from 'licia/last'
|
| 8 |
+
import each from 'licia/each'
|
| 9 |
+
import isNum from 'licia/isNum'
|
| 10 |
+
import nextTick from 'licia/nextTick'
|
| 11 |
+
import $ from 'licia/$'
|
| 12 |
+
import toNum from 'licia/toNum'
|
| 13 |
+
import extend from 'licia/extend'
|
| 14 |
+
import isStr from 'licia/isStr'
|
| 15 |
+
import theme from 'licia/theme'
|
| 16 |
+
import upperFirst from 'licia/upperFirst'
|
| 17 |
+
import startWith from 'licia/startWith'
|
| 18 |
+
import ready from 'licia/ready'
|
| 19 |
+
import pointerEvent from 'licia/pointerEvent'
|
| 20 |
+
import evalCss from '../lib/evalCss'
|
| 21 |
+
import emitter from '../lib/emitter'
|
| 22 |
+
import { isDarkTheme } from '../lib/themes'
|
| 23 |
+
import LunaNotification from 'luna-notification'
|
| 24 |
+
import LunaModal from 'luna-modal'
|
| 25 |
+
import LunaTab from 'luna-tab'
|
| 26 |
+
import {
|
| 27 |
+
classPrefix as c,
|
| 28 |
+
eventClient,
|
| 29 |
+
hasSafeArea,
|
| 30 |
+
safeStorage,
|
| 31 |
+
} from '../lib/util'
|
| 32 |
+
|
| 33 |
+
export default class DevTools extends Emitter {
|
| 34 |
+
constructor($container, { defaults = {}, inline = false } = {}) {
|
| 35 |
+
super()
|
| 36 |
+
|
| 37 |
+
this._defCfg = extend(
|
| 38 |
+
{
|
| 39 |
+
transparency: 1,
|
| 40 |
+
displaySize: 80,
|
| 41 |
+
theme: 'System preference',
|
| 42 |
+
},
|
| 43 |
+
defaults
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
this._style = evalCss(require('./DevTools.scss'))
|
| 47 |
+
|
| 48 |
+
this.$container = $container
|
| 49 |
+
this._isShow = false
|
| 50 |
+
this._opacity = 1
|
| 51 |
+
this._tools = {}
|
| 52 |
+
this._isResizing = false
|
| 53 |
+
this._resizeTimer = null
|
| 54 |
+
this._resizeStartY = 0
|
| 55 |
+
this._resizeStartSize = 0
|
| 56 |
+
this._inline = inline
|
| 57 |
+
|
| 58 |
+
this._initTpl()
|
| 59 |
+
this._initTab()
|
| 60 |
+
this._initNotification()
|
| 61 |
+
this._initModal()
|
| 62 |
+
|
| 63 |
+
ready(() => this._checkSafeArea())
|
| 64 |
+
this._bindEvent()
|
| 65 |
+
}
|
| 66 |
+
show() {
|
| 67 |
+
this._isShow = true
|
| 68 |
+
|
| 69 |
+
this._$el.show()
|
| 70 |
+
this._tab.updateSlider()
|
| 71 |
+
|
| 72 |
+
// Need a delay after show to enable transition effect.
|
| 73 |
+
setTimeout(() => {
|
| 74 |
+
this._$el.css('opacity', this._opacity)
|
| 75 |
+
}, 50)
|
| 76 |
+
|
| 77 |
+
this.emit('show')
|
| 78 |
+
|
| 79 |
+
return this
|
| 80 |
+
}
|
| 81 |
+
hide() {
|
| 82 |
+
if (this._inline) {
|
| 83 |
+
return
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
this._isShow = false
|
| 87 |
+
this.emit('hide')
|
| 88 |
+
|
| 89 |
+
this._$el.css({ opacity: 0 })
|
| 90 |
+
setTimeout(() => this._$el.hide(), 300)
|
| 91 |
+
|
| 92 |
+
return this
|
| 93 |
+
}
|
| 94 |
+
toggle() {
|
| 95 |
+
return this._isShow ? this.hide() : this.show()
|
| 96 |
+
}
|
| 97 |
+
add(tool) {
|
| 98 |
+
const tab = this._tab
|
| 99 |
+
|
| 100 |
+
if (!(tool instanceof Tool)) {
|
| 101 |
+
const { init, show, hide, destroy } = new Tool()
|
| 102 |
+
defaults(tool, { init, show, hide, destroy })
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
const name = tool.name
|
| 106 |
+
if (!name) {
|
| 107 |
+
return logger.error('You must specify a name for a tool')
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
if (this._tools[name]) {
|
| 111 |
+
return logger.warn(`Tool ${name} already exists`)
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
const id = name.replace(/\s+/g, '-')
|
| 115 |
+
this._$tools.prepend(`<div id="${c(id)}" class="${c(id + ' tool')}"></div>`)
|
| 116 |
+
tool.init(this._$tools.find(`.${c(id)}.${c('tool')}`), this)
|
| 117 |
+
tool.active = false
|
| 118 |
+
this._tools[name] = tool
|
| 119 |
+
|
| 120 |
+
if (name === 'settings') {
|
| 121 |
+
tab.append({
|
| 122 |
+
id: name,
|
| 123 |
+
title: name,
|
| 124 |
+
})
|
| 125 |
+
} else {
|
| 126 |
+
tab.insert(tab.length - 1, {
|
| 127 |
+
id: name,
|
| 128 |
+
title: name,
|
| 129 |
+
})
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
return this
|
| 133 |
+
}
|
| 134 |
+
remove(name) {
|
| 135 |
+
const tools = this._tools
|
| 136 |
+
|
| 137 |
+
if (!tools[name]) return logger.warn(`Tool ${name} doesn't exist`)
|
| 138 |
+
|
| 139 |
+
this._tab.remove(name)
|
| 140 |
+
|
| 141 |
+
const tool = tools[name]
|
| 142 |
+
delete tools[name]
|
| 143 |
+
if (tool.active) {
|
| 144 |
+
const toolKeys = keys(tools)
|
| 145 |
+
if (toolKeys.length > 0) this.showTool(tools[last(toolKeys)].name)
|
| 146 |
+
}
|
| 147 |
+
tool.destroy()
|
| 148 |
+
|
| 149 |
+
return this
|
| 150 |
+
}
|
| 151 |
+
removeAll() {
|
| 152 |
+
each(this._tools, (tool) => this.remove(tool.name))
|
| 153 |
+
|
| 154 |
+
return this
|
| 155 |
+
}
|
| 156 |
+
get(name) {
|
| 157 |
+
const tool = this._tools[name]
|
| 158 |
+
|
| 159 |
+
if (tool) return tool
|
| 160 |
+
}
|
| 161 |
+
showTool(name) {
|
| 162 |
+
if (this._curTool === name) {
|
| 163 |
+
return this
|
| 164 |
+
}
|
| 165 |
+
this._curTool = name
|
| 166 |
+
|
| 167 |
+
const tools = this._tools
|
| 168 |
+
|
| 169 |
+
const tool = tools[name]
|
| 170 |
+
if (!tool) return
|
| 171 |
+
|
| 172 |
+
let lastTool = {}
|
| 173 |
+
|
| 174 |
+
each(tools, (tool) => {
|
| 175 |
+
if (tool.active) {
|
| 176 |
+
lastTool = tool
|
| 177 |
+
tool.active = false
|
| 178 |
+
tool.hide()
|
| 179 |
+
}
|
| 180 |
+
})
|
| 181 |
+
|
| 182 |
+
tool.active = true
|
| 183 |
+
tool.show()
|
| 184 |
+
|
| 185 |
+
this._tab.select(name)
|
| 186 |
+
|
| 187 |
+
this.emit('showTool', name, lastTool)
|
| 188 |
+
|
| 189 |
+
return this
|
| 190 |
+
}
|
| 191 |
+
initCfg(settings) {
|
| 192 |
+
const cfg = (this.config = Settings.createCfg('dev-tools', this._defCfg))
|
| 193 |
+
|
| 194 |
+
this._setTransparency(cfg.get('transparency'))
|
| 195 |
+
this._setDisplaySize(cfg.get('displaySize'))
|
| 196 |
+
this._setTheme(cfg.get('theme'))
|
| 197 |
+
|
| 198 |
+
cfg.on('change', (key, val) => {
|
| 199 |
+
switch (key) {
|
| 200 |
+
case 'transparency':
|
| 201 |
+
return this._setTransparency(val)
|
| 202 |
+
case 'displaySize':
|
| 203 |
+
return this._setDisplaySize(val)
|
| 204 |
+
case 'theme':
|
| 205 |
+
return this._setTheme(val)
|
| 206 |
+
}
|
| 207 |
+
})
|
| 208 |
+
|
| 209 |
+
settings
|
| 210 |
+
.separator()
|
| 211 |
+
.select(cfg, 'theme', 'Theme', [
|
| 212 |
+
'System preference',
|
| 213 |
+
...keys(evalCss.getThemes()),
|
| 214 |
+
])
|
| 215 |
+
|
| 216 |
+
if (!this._inline) {
|
| 217 |
+
settings
|
| 218 |
+
.range(cfg, 'transparency', 'Transparency', {
|
| 219 |
+
min: 0.2,
|
| 220 |
+
max: 1,
|
| 221 |
+
step: 0.01,
|
| 222 |
+
})
|
| 223 |
+
.range(cfg, 'displaySize', 'Display Size', {
|
| 224 |
+
min: 40,
|
| 225 |
+
max: 100,
|
| 226 |
+
step: 1,
|
| 227 |
+
})
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
settings
|
| 231 |
+
.button('Restore defaults and reload', function () {
|
| 232 |
+
const store = safeStorage('local')
|
| 233 |
+
|
| 234 |
+
const data = JSON.parse(JSON.stringify(store))
|
| 235 |
+
each(data, (val, key) => {
|
| 236 |
+
if (!isStr(val)) {
|
| 237 |
+
return
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
if (startWith(key, 'eruda')) {
|
| 241 |
+
store.removeItem(key)
|
| 242 |
+
}
|
| 243 |
+
})
|
| 244 |
+
|
| 245 |
+
window.location.reload()
|
| 246 |
+
})
|
| 247 |
+
.separator()
|
| 248 |
+
}
|
| 249 |
+
notify(content, options) {
|
| 250 |
+
this._notification.notify(content, options)
|
| 251 |
+
}
|
| 252 |
+
destroy() {
|
| 253 |
+
evalCss.remove(this._style)
|
| 254 |
+
this.removeAll()
|
| 255 |
+
this._tab.destroy()
|
| 256 |
+
this._$el.remove()
|
| 257 |
+
window.removeEventListener('resize', this._checkSafeArea)
|
| 258 |
+
emitter.off(emitter.SCALE, this._updateTabHeight)
|
| 259 |
+
}
|
| 260 |
+
_checkSafeArea = () => {
|
| 261 |
+
const { $container } = this
|
| 262 |
+
|
| 263 |
+
if (hasSafeArea()) {
|
| 264 |
+
$container.addClass(c('safe-area'))
|
| 265 |
+
} else {
|
| 266 |
+
$container.rmClass(c('safe-area'))
|
| 267 |
+
}
|
| 268 |
+
}
|
| 269 |
+
_setTheme(t) {
|
| 270 |
+
const { $container } = this
|
| 271 |
+
|
| 272 |
+
if (t === 'System preference') {
|
| 273 |
+
t = upperFirst(theme.get())
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
if (isDarkTheme(t)) {
|
| 277 |
+
$container.addClass(c('dark'))
|
| 278 |
+
} else {
|
| 279 |
+
$container.rmClass(c('dark'))
|
| 280 |
+
}
|
| 281 |
+
evalCss.setTheme(t)
|
| 282 |
+
}
|
| 283 |
+
_setTransparency(opacity) {
|
| 284 |
+
if (!isNum(opacity)) return
|
| 285 |
+
|
| 286 |
+
this._opacity = opacity
|
| 287 |
+
if (this._isShow) this._$el.css({ opacity })
|
| 288 |
+
}
|
| 289 |
+
_setDisplaySize(height) {
|
| 290 |
+
if (this._inline) {
|
| 291 |
+
height = 100
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
if (!isNum(height)) return
|
| 295 |
+
|
| 296 |
+
this._$el.css({ height: height + '%' })
|
| 297 |
+
}
|
| 298 |
+
_initTpl() {
|
| 299 |
+
const $container = this.$container
|
| 300 |
+
|
| 301 |
+
$container.append(
|
| 302 |
+
c(`
|
| 303 |
+
<div class="dev-tools">
|
| 304 |
+
<div class="resizer"></div>
|
| 305 |
+
<div class="tab"></div>
|
| 306 |
+
<div class="tools"></div>
|
| 307 |
+
<div class="notification"></div>
|
| 308 |
+
<div class="modal"></div>
|
| 309 |
+
</div>
|
| 310 |
+
`)
|
| 311 |
+
)
|
| 312 |
+
|
| 313 |
+
this._$el = $container.find(c('.dev-tools'))
|
| 314 |
+
this._$tools = this._$el.find(c('.tools'))
|
| 315 |
+
}
|
| 316 |
+
_initTab() {
|
| 317 |
+
this._tab = new LunaTab(this._$el.find(c('.tab')).get(0), {
|
| 318 |
+
height: 40,
|
| 319 |
+
})
|
| 320 |
+
this._tab.on('select', (id) => this.showTool(id))
|
| 321 |
+
}
|
| 322 |
+
_updateTabHeight = (scale) => {
|
| 323 |
+
this._tab.setOption('height', 40 * scale)
|
| 324 |
+
nextTick(() => {
|
| 325 |
+
this._tab.updateSlider()
|
| 326 |
+
})
|
| 327 |
+
}
|
| 328 |
+
_initNotification() {
|
| 329 |
+
this._notification = new LunaNotification(
|
| 330 |
+
this._$el.find(c('.notification')).get(0),
|
| 331 |
+
{
|
| 332 |
+
position: {
|
| 333 |
+
x: 'center',
|
| 334 |
+
y: 'top',
|
| 335 |
+
},
|
| 336 |
+
}
|
| 337 |
+
)
|
| 338 |
+
}
|
| 339 |
+
_initModal() {
|
| 340 |
+
LunaModal.setContainer(this._$el.find(c('.modal')).get(0))
|
| 341 |
+
}
|
| 342 |
+
_bindEvent() {
|
| 343 |
+
const $resizer = this._$el.find(c('.resizer'))
|
| 344 |
+
const $navBar = this._$el.find(c('.nav-bar'))
|
| 345 |
+
const $document = $(document)
|
| 346 |
+
|
| 347 |
+
if (this._inline) {
|
| 348 |
+
$resizer.hide()
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
const startListener = (e) => {
|
| 352 |
+
e.preventDefault()
|
| 353 |
+
e.stopPropagation()
|
| 354 |
+
|
| 355 |
+
e = e.origEvent
|
| 356 |
+
this._isResizing = true
|
| 357 |
+
this._resizeStartSize = this.config.get('displaySize')
|
| 358 |
+
this._resizeStartY = eventClient('y', e)
|
| 359 |
+
|
| 360 |
+
$resizer.css('height', '100%')
|
| 361 |
+
|
| 362 |
+
$document.on(pointerEvent('move'), moveListener)
|
| 363 |
+
$document.on(pointerEvent('up'), endListener)
|
| 364 |
+
}
|
| 365 |
+
const moveListener = (e) => {
|
| 366 |
+
if (!this._isResizing) {
|
| 367 |
+
return
|
| 368 |
+
}
|
| 369 |
+
e.preventDefault()
|
| 370 |
+
e.stopPropagation()
|
| 371 |
+
|
| 372 |
+
e = e.origEvent
|
| 373 |
+
const deltaY =
|
| 374 |
+
((this._resizeStartY - eventClient('y', e)) / window.innerHeight) * 100
|
| 375 |
+
let displaySize = this._resizeStartSize + deltaY
|
| 376 |
+
if (displaySize < 40) {
|
| 377 |
+
displaySize = 40
|
| 378 |
+
} else if (displaySize > 100) {
|
| 379 |
+
displaySize = 100
|
| 380 |
+
}
|
| 381 |
+
this.config.set('displaySize', toNum(displaySize.toFixed(2)))
|
| 382 |
+
}
|
| 383 |
+
const endListener = () => {
|
| 384 |
+
clearTimeout(this._resizeTimer)
|
| 385 |
+
this._isResizing = false
|
| 386 |
+
|
| 387 |
+
$resizer.css('height', 10)
|
| 388 |
+
|
| 389 |
+
$document.off(pointerEvent('move'), moveListener)
|
| 390 |
+
$document.off(pointerEvent('up'), endListener)
|
| 391 |
+
}
|
| 392 |
+
$resizer.css('height', 10)
|
| 393 |
+
$resizer.on(pointerEvent('down'), startListener)
|
| 394 |
+
|
| 395 |
+
$navBar.on('contextmenu', (e) => e.preventDefault())
|
| 396 |
+
this.$container.on('click', (e) => e.stopPropagation())
|
| 397 |
+
window.addEventListener('resize', this._checkSafeArea)
|
| 398 |
+
|
| 399 |
+
emitter.on(emitter.SCALE, this._updateTabHeight)
|
| 400 |
+
|
| 401 |
+
theme.on('change', () => {
|
| 402 |
+
const t = this.config.get('theme')
|
| 403 |
+
if (t === 'System preference') {
|
| 404 |
+
this._setTheme(t)
|
| 405 |
+
}
|
| 406 |
+
})
|
| 407 |
+
}
|
| 408 |
+
}
|
src/DevTools/DevTools.scss
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@use '../style/variable' as *;
|
| 2 |
+
@use '../style/mixin' as *;
|
| 3 |
+
|
| 4 |
+
.dev-tools {
|
| 5 |
+
position: absolute;
|
| 6 |
+
width: 100%;
|
| 7 |
+
height: 100%;
|
| 8 |
+
left: 0;
|
| 9 |
+
bottom: 0;
|
| 10 |
+
background: var(--background);
|
| 11 |
+
z-index: 500;
|
| 12 |
+
display: none;
|
| 13 |
+
padding-top: 40px !important;
|
| 14 |
+
opacity: 0;
|
| 15 |
+
transition: opacity $anim-duration;
|
| 16 |
+
border-top: 1px solid var(--border);
|
| 17 |
+
.resizer {
|
| 18 |
+
position: absolute;
|
| 19 |
+
width: 100%;
|
| 20 |
+
touch-action: none;
|
| 21 |
+
left: 0;
|
| 22 |
+
top: -8px;
|
| 23 |
+
cursor: row-resize;
|
| 24 |
+
z-index: 120;
|
| 25 |
+
}
|
| 26 |
+
.tools {
|
| 27 |
+
@include overflow-auto();
|
| 28 |
+
height: 100%;
|
| 29 |
+
width: 100%;
|
| 30 |
+
position: relative;
|
| 31 |
+
.tool {
|
| 32 |
+
@include absolute();
|
| 33 |
+
overflow: hidden;
|
| 34 |
+
display: none;
|
| 35 |
+
}
|
| 36 |
+
}
|
| 37 |
+
}
|
src/DevTools/Tool.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import Class from 'licia/Class'
|
| 2 |
+
|
| 3 |
+
export default Class({
|
| 4 |
+
init($el) {
|
| 5 |
+
this._$el = $el
|
| 6 |
+
},
|
| 7 |
+
show() {
|
| 8 |
+
this._$el.show()
|
| 9 |
+
|
| 10 |
+
return this
|
| 11 |
+
},
|
| 12 |
+
hide() {
|
| 13 |
+
this._$el.hide()
|
| 14 |
+
|
| 15 |
+
return this
|
| 16 |
+
},
|
| 17 |
+
destroy() {
|
| 18 |
+
this._$el.remove()
|
| 19 |
+
},
|
| 20 |
+
})
|
src/Elements/CssStore.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import each from 'licia/each'
|
| 2 |
+
import sortKeys from 'licia/sortKeys'
|
| 3 |
+
|
| 4 |
+
function formatStyle(style) {
|
| 5 |
+
const ret = {}
|
| 6 |
+
|
| 7 |
+
for (let i = 0, len = style.length; i < len; i++) {
|
| 8 |
+
const name = style[i]
|
| 9 |
+
|
| 10 |
+
if (style[name] === 'initial') continue
|
| 11 |
+
|
| 12 |
+
ret[name] = style[name]
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
return sortStyleKeys(ret)
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
const elProto = Element.prototype
|
| 19 |
+
|
| 20 |
+
let matchesSel = function () {
|
| 21 |
+
return false
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
if (elProto.webkitMatchesSelector) {
|
| 25 |
+
matchesSel = (el, selText) => el.webkitMatchesSelector(selText)
|
| 26 |
+
} else if (elProto.mozMatchesSelector) {
|
| 27 |
+
matchesSel = (el, selText) => el.mozMatchesSelector(selText)
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
export default class CssStore {
|
| 31 |
+
constructor(el) {
|
| 32 |
+
this._el = el
|
| 33 |
+
}
|
| 34 |
+
getComputedStyle() {
|
| 35 |
+
const computedStyle = window.getComputedStyle(this._el)
|
| 36 |
+
|
| 37 |
+
return formatStyle(computedStyle)
|
| 38 |
+
}
|
| 39 |
+
getMatchedCSSRules() {
|
| 40 |
+
const ret = []
|
| 41 |
+
|
| 42 |
+
each(document.styleSheets, (styleSheet) => {
|
| 43 |
+
try {
|
| 44 |
+
// Started with version 64, Chrome does not allow cross origin script to access this property.
|
| 45 |
+
if (!styleSheet.cssRules) return
|
| 46 |
+
} catch {
|
| 47 |
+
return
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
each(styleSheet.cssRules, (cssRule) => {
|
| 51 |
+
let matchesEl = false
|
| 52 |
+
|
| 53 |
+
// Mobile safari will throw DOM Exception 12 error, need to try catch it.
|
| 54 |
+
try {
|
| 55 |
+
matchesEl = this._elMatchesSel(cssRule.selectorText)
|
| 56 |
+
} catch {
|
| 57 |
+
// No op
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
if (!matchesEl) return
|
| 61 |
+
|
| 62 |
+
ret.push({
|
| 63 |
+
selectorText: cssRule.selectorText,
|
| 64 |
+
style: formatStyle(cssRule.style),
|
| 65 |
+
})
|
| 66 |
+
})
|
| 67 |
+
})
|
| 68 |
+
|
| 69 |
+
return ret
|
| 70 |
+
}
|
| 71 |
+
_elMatchesSel(selText) {
|
| 72 |
+
return matchesSel(this._el, selText)
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
function sortStyleKeys(style) {
|
| 77 |
+
return sortKeys(style, {
|
| 78 |
+
comparator: (a, b) => {
|
| 79 |
+
const lenA = a.length
|
| 80 |
+
const lenB = b.length
|
| 81 |
+
const len = lenA > lenB ? lenB : lenA
|
| 82 |
+
|
| 83 |
+
for (let i = 0; i < len; i++) {
|
| 84 |
+
const codeA = a.charCodeAt(i)
|
| 85 |
+
const codeB = b.charCodeAt(i)
|
| 86 |
+
const cmpResult = cmpCode(codeA, codeB)
|
| 87 |
+
|
| 88 |
+
if (cmpResult !== 0) return cmpResult
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
if (lenA > lenB) return 1
|
| 92 |
+
if (lenA < lenB) return -1
|
| 93 |
+
|
| 94 |
+
return 0
|
| 95 |
+
},
|
| 96 |
+
})
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
function cmpCode(a, b) {
|
| 100 |
+
a = transCode(a)
|
| 101 |
+
b = transCode(b)
|
| 102 |
+
|
| 103 |
+
if (a > b) return 1
|
| 104 |
+
if (a < b) return -1
|
| 105 |
+
return 0
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
function transCode(code) {
|
| 109 |
+
// - should be placed after lowercase chars.
|
| 110 |
+
if (code === 45) return 123
|
| 111 |
+
return code
|
| 112 |
+
}
|
src/Elements/Detail.js
ADDED
|
@@ -0,0 +1,518 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import isEmpty from 'licia/isEmpty'
|
| 2 |
+
import lowerCase from 'licia/lowerCase'
|
| 3 |
+
import pick from 'licia/pick'
|
| 4 |
+
import toStr from 'licia/toStr'
|
| 5 |
+
import map from 'licia/map'
|
| 6 |
+
import isEl from 'licia/isEl'
|
| 7 |
+
import escape from 'licia/escape'
|
| 8 |
+
import startWith from 'licia/startWith'
|
| 9 |
+
import contain from 'licia/contain'
|
| 10 |
+
import unique from 'licia/unique'
|
| 11 |
+
import each from 'licia/each'
|
| 12 |
+
import keys from 'licia/keys'
|
| 13 |
+
import isNull from 'licia/isNull'
|
| 14 |
+
import trim from 'licia/trim'
|
| 15 |
+
import isFn from 'licia/isFn'
|
| 16 |
+
import isBool from 'licia/isBool'
|
| 17 |
+
import safeGet from 'licia/safeGet'
|
| 18 |
+
import $ from 'licia/$'
|
| 19 |
+
import h from 'licia/h'
|
| 20 |
+
import extend from 'licia/extend'
|
| 21 |
+
import MutationObserver from 'licia/MutationObserver'
|
| 22 |
+
import CssStore from './CssStore'
|
| 23 |
+
import Settings from '../Settings/Settings'
|
| 24 |
+
import LunaModal from 'luna-modal'
|
| 25 |
+
import LunaBoxModel from 'luna-box-model'
|
| 26 |
+
import chobitsu from '../lib/chobitsu'
|
| 27 |
+
import { formatNodeName } from './util'
|
| 28 |
+
import { isErudaEl, classPrefix as c } from '../lib/util'
|
| 29 |
+
|
| 30 |
+
export default class Detail {
|
| 31 |
+
constructor($container, devtools) {
|
| 32 |
+
this._$container = $container
|
| 33 |
+
this._devtools = devtools
|
| 34 |
+
this._curEl = document.documentElement
|
| 35 |
+
this._initObserver()
|
| 36 |
+
this._initCfg()
|
| 37 |
+
this._initTpl()
|
| 38 |
+
this._bindEvent()
|
| 39 |
+
}
|
| 40 |
+
show(el) {
|
| 41 |
+
this._curEl = el
|
| 42 |
+
this._rmDefComputedStyle = true
|
| 43 |
+
this._computedStyleSearchKeyword = ''
|
| 44 |
+
this._enableObserver()
|
| 45 |
+
this._render()
|
| 46 |
+
this._highlight()
|
| 47 |
+
}
|
| 48 |
+
hide = () => {
|
| 49 |
+
this._$container.hide()
|
| 50 |
+
this._disableObserver()
|
| 51 |
+
chobitsu.domain('Overlay').hideHighlight()
|
| 52 |
+
}
|
| 53 |
+
destroy() {
|
| 54 |
+
this._disableObserver()
|
| 55 |
+
this.restoreEventTarget()
|
| 56 |
+
this._rmCfg()
|
| 57 |
+
}
|
| 58 |
+
overrideEventTarget() {
|
| 59 |
+
const winEventProto = getWinEventProto()
|
| 60 |
+
|
| 61 |
+
const origAddEvent = (this._origAddEvent = winEventProto.addEventListener)
|
| 62 |
+
const origRmEvent = (this._origRmEvent = winEventProto.removeEventListener)
|
| 63 |
+
|
| 64 |
+
winEventProto.addEventListener = function (type, listener, useCapture) {
|
| 65 |
+
addEvent(this, type, listener, useCapture)
|
| 66 |
+
origAddEvent.apply(this, arguments)
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
winEventProto.removeEventListener = function (type, listener, useCapture) {
|
| 70 |
+
rmEvent(this, type, listener, useCapture)
|
| 71 |
+
origRmEvent.apply(this, arguments)
|
| 72 |
+
}
|
| 73 |
+
}
|
| 74 |
+
restoreEventTarget() {
|
| 75 |
+
const winEventProto = getWinEventProto()
|
| 76 |
+
|
| 77 |
+
if (this._origAddEvent) winEventProto.addEventListener = this._origAddEvent
|
| 78 |
+
if (this._origRmEvent) winEventProto.removeEventListener = this._origRmEvent
|
| 79 |
+
}
|
| 80 |
+
_highlight = (type) => {
|
| 81 |
+
const el = this._curEl
|
| 82 |
+
|
| 83 |
+
const highlightConfig = {
|
| 84 |
+
showInfo: false,
|
| 85 |
+
}
|
| 86 |
+
if (!type || type === 'all') {
|
| 87 |
+
extend(highlightConfig, {
|
| 88 |
+
showInfo: true,
|
| 89 |
+
contentColor: 'rgba(111, 168, 220, .66)',
|
| 90 |
+
paddingColor: 'rgba(147, 196, 125, .55)',
|
| 91 |
+
borderColor: 'rgba(255, 229, 153, .66)',
|
| 92 |
+
marginColor: 'rgba(246, 178, 107, .66)',
|
| 93 |
+
})
|
| 94 |
+
} else if (type === 'margin') {
|
| 95 |
+
highlightConfig.marginColor = 'rgba(246, 178, 107, .66)'
|
| 96 |
+
} else if (type === 'border') {
|
| 97 |
+
highlightConfig.borderColor = 'rgba(255, 229, 153, .66)'
|
| 98 |
+
} else if (type === 'padding') {
|
| 99 |
+
highlightConfig.paddingColor = 'rgba(147, 196, 125, .55)'
|
| 100 |
+
} else if (type === 'content') {
|
| 101 |
+
highlightConfig.contentColor = 'rgba(111, 168, 220, .66)'
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
const { nodeId } = chobitsu.domain('DOM').getNodeId({ node: el })
|
| 105 |
+
chobitsu.domain('Overlay').highlightNode({
|
| 106 |
+
nodeId,
|
| 107 |
+
highlightConfig,
|
| 108 |
+
})
|
| 109 |
+
}
|
| 110 |
+
_initTpl() {
|
| 111 |
+
const $container = this._$container
|
| 112 |
+
|
| 113 |
+
const html = `<div class="${c('control')}">
|
| 114 |
+
<span class="${c('icon-arrow-left back')}"></span>
|
| 115 |
+
<span class="${c('element-name')}"></span>
|
| 116 |
+
<span class="${c('icon-refresh refresh')}"></span>
|
| 117 |
+
</div>
|
| 118 |
+
<div class="${c('element')}">
|
| 119 |
+
<div class="${c('attributes section')}"></div>
|
| 120 |
+
<div class="${c('styles section')}"></div>
|
| 121 |
+
<div class="${c('computed-style section')}"></div>
|
| 122 |
+
<div class="${c('listeners section')}"></div>
|
| 123 |
+
</div>`
|
| 124 |
+
|
| 125 |
+
$container.html(html)
|
| 126 |
+
|
| 127 |
+
this._$elementName = $container.find(c('.element-name'))
|
| 128 |
+
this._$attributes = $container.find(c('.attributes'))
|
| 129 |
+
this._$styles = $container.find(c('.styles'))
|
| 130 |
+
this._$listeners = $container.find(c('.listeners'))
|
| 131 |
+
this._$computedStyle = $container.find(c('.computed-style'))
|
| 132 |
+
|
| 133 |
+
const boxModelContainer = h('div')
|
| 134 |
+
this._$boxModel = $(boxModelContainer)
|
| 135 |
+
this._boxModel = new LunaBoxModel(boxModelContainer)
|
| 136 |
+
}
|
| 137 |
+
_toggleAllComputedStyle() {
|
| 138 |
+
this._rmDefComputedStyle = !this._rmDefComputedStyle
|
| 139 |
+
|
| 140 |
+
this._render()
|
| 141 |
+
}
|
| 142 |
+
_render() {
|
| 143 |
+
const data = this._getData(this._curEl)
|
| 144 |
+
const $attributes = this._$attributes
|
| 145 |
+
const $elementName = this._$elementName
|
| 146 |
+
const $styles = this._$styles
|
| 147 |
+
const $computedStyle = this._$computedStyle
|
| 148 |
+
const $listeners = this._$listeners
|
| 149 |
+
|
| 150 |
+
$elementName.html(data.name)
|
| 151 |
+
|
| 152 |
+
let attributes = '<tr><td>Empty</td></tr>'
|
| 153 |
+
if (!isEmpty(data.attributes)) {
|
| 154 |
+
attributes = map(data.attributes, ({ name, value }) => {
|
| 155 |
+
return `<tr>
|
| 156 |
+
<td class="${c('attribute-name-color')}">${escape(name)}</td>
|
| 157 |
+
<td class="${c('string-color')}">${value}</td>
|
| 158 |
+
</tr>`
|
| 159 |
+
}).join('')
|
| 160 |
+
}
|
| 161 |
+
attributes = `<h2>Attributes</h2>
|
| 162 |
+
<div class="${c('table-wrapper')}">
|
| 163 |
+
<table>
|
| 164 |
+
<tbody>
|
| 165 |
+
${attributes}
|
| 166 |
+
</tbody>
|
| 167 |
+
</table>
|
| 168 |
+
</div>`
|
| 169 |
+
$attributes.html(attributes)
|
| 170 |
+
|
| 171 |
+
let styles = ''
|
| 172 |
+
if (!isEmpty(data.styles)) {
|
| 173 |
+
const style = map(data.styles, ({ selectorText, style }) => {
|
| 174 |
+
style = map(style, (val, key) => {
|
| 175 |
+
return `<div class="${c('rule')}"><span>${escape(
|
| 176 |
+
key
|
| 177 |
+
)}</span>: ${val};</div>`
|
| 178 |
+
}).join('')
|
| 179 |
+
return `<div class="${c('style-rules')}">
|
| 180 |
+
<div>${escape(selectorText)} {</div>
|
| 181 |
+
${style}
|
| 182 |
+
<div>}</div>
|
| 183 |
+
</div>`
|
| 184 |
+
}).join('')
|
| 185 |
+
styles = `<h2>Styles</h2>
|
| 186 |
+
<div class="${c('style-wrapper')}">
|
| 187 |
+
${style}
|
| 188 |
+
</div>`
|
| 189 |
+
$styles.html(styles).show()
|
| 190 |
+
} else {
|
| 191 |
+
$styles.hide()
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
let computedStyle = ''
|
| 195 |
+
if (data.computedStyle) {
|
| 196 |
+
let toggleButton = c(`<div class="btn toggle-all-computed-style">
|
| 197 |
+
<span class="icon-expand"></span>
|
| 198 |
+
</div>`)
|
| 199 |
+
if (data.rmDefComputedStyle) {
|
| 200 |
+
toggleButton = c(`<div class="btn toggle-all-computed-style">
|
| 201 |
+
<span class="icon-compress"></span>
|
| 202 |
+
</div>`)
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
computedStyle = `<h2>
|
| 206 |
+
Computed Style
|
| 207 |
+
${toggleButton}
|
| 208 |
+
<div class="${c('btn computed-style-search')}">
|
| 209 |
+
<span class="${c('icon-filter')}"></span>
|
| 210 |
+
</div>
|
| 211 |
+
${
|
| 212 |
+
data.computedStyleSearchKeyword
|
| 213 |
+
? `<div class="${c('btn filter-text')}">${escape(
|
| 214 |
+
data.computedStyleSearchKeyword
|
| 215 |
+
)}</div>`
|
| 216 |
+
: ''
|
| 217 |
+
}
|
| 218 |
+
</h2>
|
| 219 |
+
<div class="${c('box-model')}"></div>
|
| 220 |
+
<div class="${c('table-wrapper')}">
|
| 221 |
+
<table>
|
| 222 |
+
<tbody>
|
| 223 |
+
${map(data.computedStyle, (val, key) => {
|
| 224 |
+
return `<tr>
|
| 225 |
+
<td class="${c('key')}">${escape(key)}</td>
|
| 226 |
+
<td>${val}</td>
|
| 227 |
+
</tr>`
|
| 228 |
+
}).join('')}
|
| 229 |
+
</tbody>
|
| 230 |
+
</table>
|
| 231 |
+
</div>`
|
| 232 |
+
|
| 233 |
+
$computedStyle.html(computedStyle).show()
|
| 234 |
+
this._boxModel.setOption('element', this._curEl)
|
| 235 |
+
$computedStyle.find(c('.box-model')).append(this._$boxModel.get(0))
|
| 236 |
+
} else {
|
| 237 |
+
$computedStyle.text('').hide()
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
let listeners = ''
|
| 241 |
+
if (data.listeners) {
|
| 242 |
+
listeners = map(data.listeners, (listeners, key) => {
|
| 243 |
+
listeners = map(listeners, ({ useCapture, listenerStr }) => {
|
| 244 |
+
return `<li ${useCapture ? `class="${c('capture')}"` : ''}>${escape(
|
| 245 |
+
listenerStr
|
| 246 |
+
)}</li>`
|
| 247 |
+
}).join('')
|
| 248 |
+
return `<div class="${c('listener')}">
|
| 249 |
+
<div class="${c('listener-type')}">${escape(key)}</div>
|
| 250 |
+
<ul class="${c('listener-content')}">
|
| 251 |
+
${listeners}
|
| 252 |
+
</ul>
|
| 253 |
+
</div>`
|
| 254 |
+
}).join('')
|
| 255 |
+
listeners = `<h2>Event Listeners</h2>
|
| 256 |
+
<div class="${c('listener-wrapper')}">
|
| 257 |
+
${listeners}
|
| 258 |
+
</div>`
|
| 259 |
+
$listeners.html(listeners).show()
|
| 260 |
+
} else {
|
| 261 |
+
$listeners.hide()
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
this._$container.show()
|
| 265 |
+
}
|
| 266 |
+
_getData(el) {
|
| 267 |
+
const ret = {}
|
| 268 |
+
|
| 269 |
+
const cssStore = new CssStore(el)
|
| 270 |
+
|
| 271 |
+
const { className, id, attributes, tagName } = el
|
| 272 |
+
|
| 273 |
+
ret.computedStyleSearchKeyword = this._computedStyleSearchKeyword
|
| 274 |
+
ret.attributes = formatAttr(attributes)
|
| 275 |
+
ret.name = formatNodeName({ tagName, id, className, attributes })
|
| 276 |
+
|
| 277 |
+
const events = el.erudaEvents
|
| 278 |
+
if (events && keys(events).length !== 0) ret.listeners = events
|
| 279 |
+
|
| 280 |
+
if (needNoStyle(tagName)) {
|
| 281 |
+
return ret
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
let computedStyle = cssStore.getComputedStyle()
|
| 285 |
+
|
| 286 |
+
const styles = cssStore.getMatchedCSSRules()
|
| 287 |
+
styles.unshift(getInlineStyle(el.style))
|
| 288 |
+
styles.forEach((style) => processStyleRules(style.style))
|
| 289 |
+
ret.styles = styles
|
| 290 |
+
|
| 291 |
+
if (this._rmDefComputedStyle) {
|
| 292 |
+
computedStyle = rmDefComputedStyle(computedStyle, styles)
|
| 293 |
+
}
|
| 294 |
+
ret.rmDefComputedStyle = this._rmDefComputedStyle
|
| 295 |
+
const computedStyleSearchKeyword = lowerCase(ret.computedStyleSearchKeyword)
|
| 296 |
+
if (computedStyleSearchKeyword) {
|
| 297 |
+
computedStyle = pick(computedStyle, (val, property) => {
|
| 298 |
+
return (
|
| 299 |
+
contain(property, computedStyleSearchKeyword) ||
|
| 300 |
+
contain(val, computedStyleSearchKeyword)
|
| 301 |
+
)
|
| 302 |
+
})
|
| 303 |
+
}
|
| 304 |
+
processStyleRules(computedStyle)
|
| 305 |
+
ret.computedStyle = computedStyle
|
| 306 |
+
|
| 307 |
+
return ret
|
| 308 |
+
}
|
| 309 |
+
_bindEvent() {
|
| 310 |
+
const devtools = this._devtools
|
| 311 |
+
|
| 312 |
+
this._$container
|
| 313 |
+
.on('click', c('.toggle-all-computed-style'), () =>
|
| 314 |
+
this._toggleAllComputedStyle()
|
| 315 |
+
)
|
| 316 |
+
.on('click', c('.computed-style-search'), () => {
|
| 317 |
+
LunaModal.prompt('Filter').then((filter) => {
|
| 318 |
+
if (isNull(filter)) return
|
| 319 |
+
filter = trim(filter)
|
| 320 |
+
this._computedStyleSearchKeyword = filter
|
| 321 |
+
this._render()
|
| 322 |
+
})
|
| 323 |
+
})
|
| 324 |
+
.on('click', '.eruda-listener-content', function () {
|
| 325 |
+
const text = $(this).text()
|
| 326 |
+
const sources = devtools.get('sources')
|
| 327 |
+
|
| 328 |
+
if (sources) {
|
| 329 |
+
sources.set('js', text)
|
| 330 |
+
devtools.showTool('sources')
|
| 331 |
+
}
|
| 332 |
+
})
|
| 333 |
+
.on('click', c('.element-name'), () => {
|
| 334 |
+
const sources = devtools.get('sources')
|
| 335 |
+
|
| 336 |
+
if (sources) {
|
| 337 |
+
sources.set('object', this._curEl)
|
| 338 |
+
devtools.showTool('sources')
|
| 339 |
+
}
|
| 340 |
+
})
|
| 341 |
+
.on('click', c('.back'), this.hide)
|
| 342 |
+
.on('click', c('.refresh'), () => {
|
| 343 |
+
this._render()
|
| 344 |
+
devtools.notify('Refreshed', { icon: 'success' })
|
| 345 |
+
})
|
| 346 |
+
|
| 347 |
+
this._boxModel.on('highlight', this._highlight)
|
| 348 |
+
}
|
| 349 |
+
_initObserver() {
|
| 350 |
+
this._observer = new MutationObserver((mutations) => {
|
| 351 |
+
each(mutations, (mutation) => this._handleMutation(mutation))
|
| 352 |
+
})
|
| 353 |
+
}
|
| 354 |
+
_enableObserver() {
|
| 355 |
+
this._observer.observe(document.documentElement, {
|
| 356 |
+
attributes: true,
|
| 357 |
+
childList: true,
|
| 358 |
+
subtree: true,
|
| 359 |
+
})
|
| 360 |
+
}
|
| 361 |
+
_disableObserver() {
|
| 362 |
+
this._observer.disconnect()
|
| 363 |
+
}
|
| 364 |
+
_handleMutation(mutation) {
|
| 365 |
+
if (isErudaEl(mutation.target)) return
|
| 366 |
+
|
| 367 |
+
if (mutation.type === 'attributes') {
|
| 368 |
+
if (mutation.target !== this._curEl) return
|
| 369 |
+
this._render()
|
| 370 |
+
}
|
| 371 |
+
}
|
| 372 |
+
_rmCfg() {
|
| 373 |
+
const cfg = this.config
|
| 374 |
+
|
| 375 |
+
const settings = this._devtools.get('settings')
|
| 376 |
+
|
| 377 |
+
if (!settings) return
|
| 378 |
+
|
| 379 |
+
settings
|
| 380 |
+
.remove(cfg, 'overrideEventTarget')
|
| 381 |
+
.remove(cfg, 'observeElement')
|
| 382 |
+
.remove('Elements')
|
| 383 |
+
}
|
| 384 |
+
_initCfg() {
|
| 385 |
+
const cfg = (this.config = Settings.createCfg('elements', {
|
| 386 |
+
overrideEventTarget: true,
|
| 387 |
+
}))
|
| 388 |
+
|
| 389 |
+
if (cfg.get('overrideEventTarget')) this.overrideEventTarget()
|
| 390 |
+
|
| 391 |
+
cfg.on('change', (key, val) => {
|
| 392 |
+
switch (key) {
|
| 393 |
+
case 'overrideEventTarget':
|
| 394 |
+
return val ? this.overrideEventTarget() : this.restoreEventTarget()
|
| 395 |
+
}
|
| 396 |
+
})
|
| 397 |
+
|
| 398 |
+
const settings = this._devtools.get('settings')
|
| 399 |
+
if (!settings) return
|
| 400 |
+
|
| 401 |
+
settings
|
| 402 |
+
.text('Elements')
|
| 403 |
+
.switch(cfg, 'overrideEventTarget', 'Catch Event Listeners')
|
| 404 |
+
|
| 405 |
+
settings.separator()
|
| 406 |
+
}
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
function processStyleRules(style) {
|
| 410 |
+
each(style, (val, key) => (style[key] = processStyleRule(val)))
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
const formatAttr = (attributes) =>
|
| 414 |
+
map(attributes, (attr) => {
|
| 415 |
+
let { value } = attr
|
| 416 |
+
const { name } = attr
|
| 417 |
+
value = escape(value)
|
| 418 |
+
|
| 419 |
+
const isLink =
|
| 420 |
+
(name === 'src' || name === 'href') && !startWith(value, 'data')
|
| 421 |
+
if (isLink) value = wrapLink(value)
|
| 422 |
+
if (name === 'style') value = processStyleRule(value)
|
| 423 |
+
|
| 424 |
+
return { name, value }
|
| 425 |
+
})
|
| 426 |
+
|
| 427 |
+
const regColor = /rgba?\((.*?)\)/g
|
| 428 |
+
const regCssUrl = /url\("?(.*?)"?\)/g
|
| 429 |
+
|
| 430 |
+
function processStyleRule(val) {
|
| 431 |
+
// For css custom properties, val is unable to retrieved.
|
| 432 |
+
val = toStr(val)
|
| 433 |
+
|
| 434 |
+
return val
|
| 435 |
+
.replace(
|
| 436 |
+
regColor,
|
| 437 |
+
'<span class="eruda-style-color" style="background-color: $&"></span>$&'
|
| 438 |
+
)
|
| 439 |
+
.replace(regCssUrl, (match, url) => `url("${wrapLink(url)}")`)
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
function getInlineStyle(style) {
|
| 443 |
+
const ret = {
|
| 444 |
+
selectorText: 'element.style',
|
| 445 |
+
style: {},
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
for (let i = 0, len = style.length; i < len; i++) {
|
| 449 |
+
const s = style[i]
|
| 450 |
+
|
| 451 |
+
ret.style[s] = style[s]
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
return ret
|
| 455 |
+
}
|
| 456 |
+
|
| 457 |
+
function rmDefComputedStyle(computedStyle, styles) {
|
| 458 |
+
const ret = {}
|
| 459 |
+
|
| 460 |
+
let keepStyles = ['display', 'width', 'height']
|
| 461 |
+
each(styles, (style) => {
|
| 462 |
+
keepStyles = keepStyles.concat(keys(style.style))
|
| 463 |
+
})
|
| 464 |
+
keepStyles = unique(keepStyles)
|
| 465 |
+
|
| 466 |
+
each(computedStyle, (val, key) => {
|
| 467 |
+
if (!contain(keepStyles, key)) return
|
| 468 |
+
|
| 469 |
+
ret[key] = val
|
| 470 |
+
})
|
| 471 |
+
|
| 472 |
+
return ret
|
| 473 |
+
}
|
| 474 |
+
|
| 475 |
+
const NO_STYLE_TAG = ['script', 'style', 'meta', 'title', 'link', 'head']
|
| 476 |
+
|
| 477 |
+
const needNoStyle = (tagName) => {
|
| 478 |
+
NO_STYLE_TAG.indexOf(tagName.toLowerCase()) > -1
|
| 479 |
+
}
|
| 480 |
+
|
| 481 |
+
const wrapLink = (link) => `<a href="${link}" target="_blank">${link}</a>`
|
| 482 |
+
|
| 483 |
+
function addEvent(el, type, listener, useCapture = false) {
|
| 484 |
+
if (!isEl(el) || !isFn(listener) || !isBool(useCapture)) return
|
| 485 |
+
|
| 486 |
+
const events = (el.erudaEvents = el.erudaEvents || {})
|
| 487 |
+
|
| 488 |
+
events[type] = events[type] || []
|
| 489 |
+
events[type].push({
|
| 490 |
+
listener: listener,
|
| 491 |
+
listenerStr: listener.toString(),
|
| 492 |
+
useCapture: useCapture,
|
| 493 |
+
})
|
| 494 |
+
}
|
| 495 |
+
|
| 496 |
+
function rmEvent(el, type, listener, useCapture = false) {
|
| 497 |
+
if (!isEl(el) || !isFn(listener) || !isBool(useCapture)) return
|
| 498 |
+
|
| 499 |
+
const events = el.erudaEvents
|
| 500 |
+
|
| 501 |
+
if (!(events && events[type])) return
|
| 502 |
+
|
| 503 |
+
const listeners = events[type]
|
| 504 |
+
|
| 505 |
+
for (let i = 0, len = listeners.length; i < len; i++) {
|
| 506 |
+
if (listeners[i].listener === listener) {
|
| 507 |
+
listeners.splice(i, 1)
|
| 508 |
+
break
|
| 509 |
+
}
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
if (listeners.length === 0) delete events[type]
|
| 513 |
+
if (keys(events).length === 0) delete el.erudaEvents
|
| 514 |
+
}
|
| 515 |
+
|
| 516 |
+
const getWinEventProto = () => {
|
| 517 |
+
return safeGet(window, 'EventTarget.prototype') || window.Node.prototype
|
| 518 |
+
}
|
src/Elements/Elements.js
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import Tool from '../DevTools/Tool'
|
| 2 |
+
import $ from 'licia/$'
|
| 3 |
+
import isEl from 'licia/isEl'
|
| 4 |
+
import nextTick from 'licia/nextTick'
|
| 5 |
+
import Emitter from 'licia/Emitter'
|
| 6 |
+
import map from 'licia/map'
|
| 7 |
+
import MediaQuery from 'licia/MediaQuery'
|
| 8 |
+
import isEmpty from 'licia/isEmpty'
|
| 9 |
+
import toNum from 'licia/toNum'
|
| 10 |
+
import copy from 'licia/copy'
|
| 11 |
+
import isMobile from 'licia/isMobile'
|
| 12 |
+
import isShadowRoot from 'licia/isShadowRoot'
|
| 13 |
+
import LunaDomViewer from 'luna-dom-viewer'
|
| 14 |
+
import { isErudaEl, classPrefix as c, isChobitsuEl } from '../lib/util'
|
| 15 |
+
import evalCss from '../lib/evalCss'
|
| 16 |
+
import Detail from './Detail'
|
| 17 |
+
import chobitsu from '../lib/chobitsu'
|
| 18 |
+
import emitter from '../lib/emitter'
|
| 19 |
+
import { formatNodeName } from './util'
|
| 20 |
+
|
| 21 |
+
export default class Elements extends Tool {
|
| 22 |
+
constructor() {
|
| 23 |
+
super()
|
| 24 |
+
|
| 25 |
+
this._style = evalCss(require('./Elements.scss'))
|
| 26 |
+
|
| 27 |
+
this.name = 'elements'
|
| 28 |
+
this._selectElement = false
|
| 29 |
+
this._observeElement = true
|
| 30 |
+
this._history = []
|
| 31 |
+
|
| 32 |
+
Emitter.mixin(this)
|
| 33 |
+
}
|
| 34 |
+
init($el, container) {
|
| 35 |
+
super.init($el)
|
| 36 |
+
|
| 37 |
+
this._container = container
|
| 38 |
+
|
| 39 |
+
this._initTpl()
|
| 40 |
+
this._htmlEl = document.documentElement
|
| 41 |
+
this._detail = new Detail(this._$detail, container)
|
| 42 |
+
this.config = this._detail.config
|
| 43 |
+
this._splitMediaQuery = new MediaQuery('screen and (min-width: 680px)')
|
| 44 |
+
this._splitMode = this._splitMediaQuery.isMatch()
|
| 45 |
+
this._domViewer = new LunaDomViewer(this._$domViewer.get(0), {
|
| 46 |
+
node: this._htmlEl,
|
| 47 |
+
ignore: (node) => isErudaEl(node) || isChobitsuEl(node),
|
| 48 |
+
})
|
| 49 |
+
this._domViewer.expand()
|
| 50 |
+
this._bindEvent()
|
| 51 |
+
chobitsu.domain('Overlay').enable()
|
| 52 |
+
|
| 53 |
+
nextTick(() => this._updateHistory())
|
| 54 |
+
}
|
| 55 |
+
show() {
|
| 56 |
+
super.show()
|
| 57 |
+
this._isShow = true
|
| 58 |
+
|
| 59 |
+
if (!this._curNode) {
|
| 60 |
+
this.select(document.body)
|
| 61 |
+
} else if (this._splitMode) {
|
| 62 |
+
this._showDetail()
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
hide() {
|
| 66 |
+
super.hide()
|
| 67 |
+
this._isShow = false
|
| 68 |
+
|
| 69 |
+
chobitsu.domain('Overlay').hideHighlight()
|
| 70 |
+
}
|
| 71 |
+
select(node) {
|
| 72 |
+
this._domViewer.select(node)
|
| 73 |
+
this._setNode(node)
|
| 74 |
+
this.emit('change', node)
|
| 75 |
+
return this
|
| 76 |
+
}
|
| 77 |
+
destroy() {
|
| 78 |
+
super.destroy()
|
| 79 |
+
|
| 80 |
+
emitter.off(emitter.SCALE, this._updateScale)
|
| 81 |
+
evalCss.remove(this._style)
|
| 82 |
+
this._detail.destroy()
|
| 83 |
+
chobitsu
|
| 84 |
+
.domain('Overlay')
|
| 85 |
+
.off('inspectNodeRequested', this._inspectNodeRequested)
|
| 86 |
+
chobitsu.domain('Overlay').disable()
|
| 87 |
+
this._splitMediaQuery.removeAllListeners()
|
| 88 |
+
}
|
| 89 |
+
_updateButtons() {
|
| 90 |
+
const $control = this._$control
|
| 91 |
+
const $showDetail = $control.find(c('.show-detail'))
|
| 92 |
+
const $copyNode = $control.find(c('.copy-node'))
|
| 93 |
+
const $deleteNode = $control.find(c('.delete-node'))
|
| 94 |
+
const iconDisabled = c('icon-disabled')
|
| 95 |
+
|
| 96 |
+
$showDetail.addClass(iconDisabled)
|
| 97 |
+
$copyNode.addClass(iconDisabled)
|
| 98 |
+
$deleteNode.addClass(iconDisabled)
|
| 99 |
+
|
| 100 |
+
const node = this._curNode
|
| 101 |
+
|
| 102 |
+
if (!node || isShadowRoot(node)) {
|
| 103 |
+
return
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
if (node !== document.documentElement && node !== document.body) {
|
| 107 |
+
$deleteNode.rmClass(iconDisabled)
|
| 108 |
+
}
|
| 109 |
+
$copyNode.rmClass(iconDisabled)
|
| 110 |
+
|
| 111 |
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
| 112 |
+
$showDetail.rmClass(iconDisabled)
|
| 113 |
+
}
|
| 114 |
+
}
|
| 115 |
+
_showDetail = () => {
|
| 116 |
+
if (!this._isShow || !this._curNode) {
|
| 117 |
+
return
|
| 118 |
+
}
|
| 119 |
+
if (this._curNode.nodeType === Node.ELEMENT_NODE) {
|
| 120 |
+
this._detail.show(this._curNode)
|
| 121 |
+
} else {
|
| 122 |
+
this._detail.show(this._curNode.parentNode || this._curNode.host)
|
| 123 |
+
}
|
| 124 |
+
}
|
| 125 |
+
_initTpl() {
|
| 126 |
+
const $el = this._$el
|
| 127 |
+
|
| 128 |
+
$el.html(
|
| 129 |
+
c(`<div class="elements">
|
| 130 |
+
<div class="control">
|
| 131 |
+
<span class="icon icon-select select"></span>
|
| 132 |
+
<span class="icon icon-eye show-detail"></span>
|
| 133 |
+
<span class="icon icon-copy copy-node"></span>
|
| 134 |
+
<span class="icon icon-delete delete-node"></span>
|
| 135 |
+
</div>
|
| 136 |
+
<div class="dom-viewer-container">
|
| 137 |
+
<div class="dom-viewer"></div>
|
| 138 |
+
</div>
|
| 139 |
+
<div class="crumbs"></div>
|
| 140 |
+
</div>
|
| 141 |
+
<div class="detail"></div>`)
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
this._$detail = $el.find(c('.detail'))
|
| 145 |
+
this._$domViewer = $el.find(c('.dom-viewer'))
|
| 146 |
+
this._$control = $el.find(c('.control'))
|
| 147 |
+
this._$crumbs = $el.find(c('.crumbs'))
|
| 148 |
+
}
|
| 149 |
+
_renderCrumbs() {
|
| 150 |
+
const crumbs = getCrumbs(this._curNode)
|
| 151 |
+
let html = ''
|
| 152 |
+
if (!isEmpty(crumbs)) {
|
| 153 |
+
html = map(crumbs, ({ text, idx }) => {
|
| 154 |
+
return `<li class="${c('crumb')}" data-idx="${idx}">${text}</div></li>`
|
| 155 |
+
}).join('')
|
| 156 |
+
}
|
| 157 |
+
this._$crumbs.html(html)
|
| 158 |
+
}
|
| 159 |
+
_back = () => {
|
| 160 |
+
if (this._curNode === this._htmlEl) return
|
| 161 |
+
|
| 162 |
+
const parentQueue = this._curParentQueue
|
| 163 |
+
let parent = parentQueue.shift()
|
| 164 |
+
|
| 165 |
+
while (!isElExist(parent)) {
|
| 166 |
+
parent = parentQueue.shift()
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
this.set(parent)
|
| 170 |
+
}
|
| 171 |
+
_bindEvent() {
|
| 172 |
+
const self = this
|
| 173 |
+
|
| 174 |
+
this._$el.on('click', c('.crumb'), function () {
|
| 175 |
+
let idx = toNum($(this).data('idx'))
|
| 176 |
+
let node = self._curNode
|
| 177 |
+
|
| 178 |
+
while (idx-- && node.parentElement) {
|
| 179 |
+
node = node.parentElement
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
if (isElExist(node)) {
|
| 183 |
+
self.select(node)
|
| 184 |
+
}
|
| 185 |
+
})
|
| 186 |
+
|
| 187 |
+
this._$control
|
| 188 |
+
.on('click', c('.select'), this._toggleSelect)
|
| 189 |
+
.on('click', c('.show-detail'), this._showDetail)
|
| 190 |
+
.on('click', c('.copy-node'), this._copyNode)
|
| 191 |
+
.on('click', c('.delete-node'), this._deleteNode)
|
| 192 |
+
|
| 193 |
+
this._domViewer.on('select', this._setNode).on('deselect', this._back)
|
| 194 |
+
|
| 195 |
+
chobitsu
|
| 196 |
+
.domain('Overlay')
|
| 197 |
+
.on('inspectNodeRequested', this._inspectNodeRequested)
|
| 198 |
+
|
| 199 |
+
this._splitMediaQuery.on('match', () => {
|
| 200 |
+
this._splitMode = true
|
| 201 |
+
this._showDetail()
|
| 202 |
+
})
|
| 203 |
+
this._splitMediaQuery.on('unmatch', () => {
|
| 204 |
+
this._splitMode = false
|
| 205 |
+
this._detail.hide()
|
| 206 |
+
})
|
| 207 |
+
|
| 208 |
+
emitter.on(emitter.SCALE, this._updateScale)
|
| 209 |
+
}
|
| 210 |
+
_updateScale = (scale) => {
|
| 211 |
+
this._splitMediaQuery.setQuery(`screen and (min-width: ${680 * scale}px)`)
|
| 212 |
+
}
|
| 213 |
+
_deleteNode = () => {
|
| 214 |
+
const node = this._curNode
|
| 215 |
+
|
| 216 |
+
if (node.parentNode) {
|
| 217 |
+
node.parentNode.removeChild(node)
|
| 218 |
+
}
|
| 219 |
+
}
|
| 220 |
+
_copyNode = () => {
|
| 221 |
+
const node = this._curNode
|
| 222 |
+
|
| 223 |
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
| 224 |
+
copy(node.outerHTML)
|
| 225 |
+
} else {
|
| 226 |
+
copy(node.nodeValue)
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
this._container.notify('Copied', { icon: 'success' })
|
| 230 |
+
}
|
| 231 |
+
_toggleSelect = () => {
|
| 232 |
+
this._$el.find(c('.select')).toggleClass(c('active'))
|
| 233 |
+
this._selectElement = !this._selectElement
|
| 234 |
+
|
| 235 |
+
if (this._selectElement) {
|
| 236 |
+
chobitsu.domain('Overlay').setInspectMode({
|
| 237 |
+
mode: 'searchForNode',
|
| 238 |
+
highlightConfig: {
|
| 239 |
+
showInfo: !isMobile(),
|
| 240 |
+
showRulers: false,
|
| 241 |
+
showAccessibilityInfo: !isMobile(),
|
| 242 |
+
showExtensionLines: false,
|
| 243 |
+
contrastAlgorithm: 'aa',
|
| 244 |
+
contentColor: 'rgba(111, 168, 220, .66)',
|
| 245 |
+
paddingColor: 'rgba(147, 196, 125, .55)',
|
| 246 |
+
borderColor: 'rgba(255, 229, 153, .66)',
|
| 247 |
+
marginColor: 'rgba(246, 178, 107, .66)',
|
| 248 |
+
},
|
| 249 |
+
})
|
| 250 |
+
this._container.hide()
|
| 251 |
+
} else {
|
| 252 |
+
chobitsu.domain('Overlay').setInspectMode({
|
| 253 |
+
mode: 'none',
|
| 254 |
+
})
|
| 255 |
+
chobitsu.domain('Overlay').hideHighlight()
|
| 256 |
+
}
|
| 257 |
+
}
|
| 258 |
+
_inspectNodeRequested = ({ backendNodeId }) => {
|
| 259 |
+
this._container.show()
|
| 260 |
+
this._toggleSelect()
|
| 261 |
+
try {
|
| 262 |
+
const { node } = chobitsu.domain('DOM').getNode({ nodeId: backendNodeId })
|
| 263 |
+
this.select(node)
|
| 264 |
+
} catch {
|
| 265 |
+
// No op
|
| 266 |
+
}
|
| 267 |
+
}
|
| 268 |
+
_setNode = (node) => {
|
| 269 |
+
if (node === this._curNode) return
|
| 270 |
+
|
| 271 |
+
this._curNode = node
|
| 272 |
+
this._renderCrumbs()
|
| 273 |
+
|
| 274 |
+
const parentQueue = []
|
| 275 |
+
|
| 276 |
+
let parent = node.parentNode
|
| 277 |
+
while (parent) {
|
| 278 |
+
parentQueue.push(parent)
|
| 279 |
+
parent = parent.parentNode
|
| 280 |
+
}
|
| 281 |
+
this._curParentQueue = parentQueue
|
| 282 |
+
|
| 283 |
+
if (this._splitMode) {
|
| 284 |
+
this._showDetail()
|
| 285 |
+
}
|
| 286 |
+
this._updateButtons()
|
| 287 |
+
this._updateHistory()
|
| 288 |
+
}
|
| 289 |
+
_updateHistory() {
|
| 290 |
+
const console = this._container.get('console')
|
| 291 |
+
if (!console) return
|
| 292 |
+
|
| 293 |
+
const history = this._history
|
| 294 |
+
history.unshift(this._curNode)
|
| 295 |
+
if (history.length > 5) history.pop()
|
| 296 |
+
for (let i = 0; i < 5; i++) {
|
| 297 |
+
console.setGlobal(`$${i}`, history[i])
|
| 298 |
+
}
|
| 299 |
+
}
|
| 300 |
+
}
|
| 301 |
+
|
| 302 |
+
const isElExist = (val) => isEl(val) && val.parentNode
|
| 303 |
+
|
| 304 |
+
function getCrumbs(el) {
|
| 305 |
+
const ret = []
|
| 306 |
+
let i = 0
|
| 307 |
+
|
| 308 |
+
while (el) {
|
| 309 |
+
ret.push({
|
| 310 |
+
text: formatNodeName(el, { noAttr: true }),
|
| 311 |
+
idx: i++,
|
| 312 |
+
})
|
| 313 |
+
|
| 314 |
+
if (isShadowRoot(el)) {
|
| 315 |
+
el = el.host
|
| 316 |
+
}
|
| 317 |
+
if (!el.parentElement && isShadowRoot(el.parentNode)) {
|
| 318 |
+
el = el.parentNode
|
| 319 |
+
} else {
|
| 320 |
+
el = el.parentElement
|
| 321 |
+
}
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
return ret.reverse()
|
| 325 |
+
}
|
src/Elements/Elements.scss
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@use '../style/variable' as *;
|
| 2 |
+
@use '../style/mixin' as *;
|
| 3 |
+
|
| 4 |
+
#elements {
|
| 5 |
+
.elements {
|
| 6 |
+
@include absolute();
|
| 7 |
+
padding-top: 40px;
|
| 8 |
+
padding-bottom: 24px;
|
| 9 |
+
font-size: 14px;
|
| 10 |
+
}
|
| 11 |
+
.control {
|
| 12 |
+
padding: 10px 0;
|
| 13 |
+
@include control();
|
| 14 |
+
.icon-eye {
|
| 15 |
+
right: 0;
|
| 16 |
+
}
|
| 17 |
+
.icon-copy {
|
| 18 |
+
right: 23px;
|
| 19 |
+
}
|
| 20 |
+
.icon-delete {
|
| 21 |
+
right: 46px;
|
| 22 |
+
}
|
| 23 |
+
}
|
| 24 |
+
.dom-viewer-container {
|
| 25 |
+
@include overflow-auto();
|
| 26 |
+
height: 100%;
|
| 27 |
+
padding: 5px 0;
|
| 28 |
+
}
|
| 29 |
+
.crumbs {
|
| 30 |
+
@include absolute(100%, 24px);
|
| 31 |
+
top: initial;
|
| 32 |
+
line-height: 24px;
|
| 33 |
+
bottom: 0;
|
| 34 |
+
border-top: 1px solid var(--border);
|
| 35 |
+
background: var(--darker-background);
|
| 36 |
+
color: var(--primary);
|
| 37 |
+
font-size: $font-size-s;
|
| 38 |
+
white-space: nowrap;
|
| 39 |
+
overflow: hidden;
|
| 40 |
+
text-overflow: ellipsis;
|
| 41 |
+
li {
|
| 42 |
+
cursor: pointer;
|
| 43 |
+
padding: 0 7px;
|
| 44 |
+
display: inline-block;
|
| 45 |
+
&:hover,
|
| 46 |
+
&:last-child {
|
| 47 |
+
background: var(--highlight);
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
.icon-arrow-right {
|
| 51 |
+
font-size: $font-size-s;
|
| 52 |
+
position: relative;
|
| 53 |
+
top: 1px;
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
.detail {
|
| 57 |
+
@include absolute();
|
| 58 |
+
z-index: 10;
|
| 59 |
+
padding-top: 40px;
|
| 60 |
+
display: none;
|
| 61 |
+
background: var(--background);
|
| 62 |
+
.control {
|
| 63 |
+
padding: 10px 35px;
|
| 64 |
+
.element-name {
|
| 65 |
+
font-size: $font-size-s;
|
| 66 |
+
overflow: hidden;
|
| 67 |
+
white-space: nowrap;
|
| 68 |
+
text-overflow: ellipsis;
|
| 69 |
+
width: 100%;
|
| 70 |
+
display: inline-block;
|
| 71 |
+
}
|
| 72 |
+
.icon-arrow-left {
|
| 73 |
+
left: 0;
|
| 74 |
+
}
|
| 75 |
+
.icon-refresh {
|
| 76 |
+
right: 0;
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
.element {
|
| 80 |
+
@include overflow-auto(y);
|
| 81 |
+
height: 100%;
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
.section {
|
| 85 |
+
border-bottom: 1px solid var(--border);
|
| 86 |
+
color: var(--foreground);
|
| 87 |
+
margin: 10px 0;
|
| 88 |
+
h2 {
|
| 89 |
+
color: var(--primary);
|
| 90 |
+
background: var(--darker-background);
|
| 91 |
+
border-top: 1px solid var(--border);
|
| 92 |
+
padding: $padding;
|
| 93 |
+
line-height: 18px;
|
| 94 |
+
font-size: $font-size;
|
| 95 |
+
transition: background-color $anim-duration;
|
| 96 |
+
@include right-btn();
|
| 97 |
+
&.active-effect {
|
| 98 |
+
cursor: pointer;
|
| 99 |
+
}
|
| 100 |
+
&.active-effect:active {
|
| 101 |
+
background: var(--highlight);
|
| 102 |
+
color: var(--select-foreground);
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
}
|
| 106 |
+
.attributes {
|
| 107 |
+
font-size: $font-size-s;
|
| 108 |
+
a {
|
| 109 |
+
color: var(--link-color);
|
| 110 |
+
}
|
| 111 |
+
.table-wrapper {
|
| 112 |
+
@include overflow-auto(x);
|
| 113 |
+
}
|
| 114 |
+
table {
|
| 115 |
+
td {
|
| 116 |
+
padding: 5px 10px;
|
| 117 |
+
}
|
| 118 |
+
}
|
| 119 |
+
}
|
| 120 |
+
.text-content {
|
| 121 |
+
background: #fff;
|
| 122 |
+
.content {
|
| 123 |
+
@include overflow-auto(x);
|
| 124 |
+
padding: $padding;
|
| 125 |
+
}
|
| 126 |
+
}
|
| 127 |
+
.style-color {
|
| 128 |
+
position: relative;
|
| 129 |
+
top: 1px;
|
| 130 |
+
width: 10px;
|
| 131 |
+
height: 10px;
|
| 132 |
+
border-radius: 50%;
|
| 133 |
+
margin-right: 2px;
|
| 134 |
+
border: 1px solid var(--border);
|
| 135 |
+
display: inline-block;
|
| 136 |
+
}
|
| 137 |
+
.box-model {
|
| 138 |
+
@include overflow-auto(x);
|
| 139 |
+
padding: $padding;
|
| 140 |
+
text-align: center;
|
| 141 |
+
border-bottom: 1px solid var(--color);
|
| 142 |
+
}
|
| 143 |
+
.computed-style {
|
| 144 |
+
font-size: $font-size-s;
|
| 145 |
+
a {
|
| 146 |
+
color: var(--link-color);
|
| 147 |
+
}
|
| 148 |
+
.table-wrapper {
|
| 149 |
+
@include overflow-auto(y);
|
| 150 |
+
max-height: 200px;
|
| 151 |
+
border-top: 1px solid var(--border);
|
| 152 |
+
}
|
| 153 |
+
table {
|
| 154 |
+
td {
|
| 155 |
+
padding: 5px 10px;
|
| 156 |
+
&.key {
|
| 157 |
+
white-space: nowrap;
|
| 158 |
+
color: var(--var-color);
|
| 159 |
+
}
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
}
|
| 163 |
+
.styles {
|
| 164 |
+
font-size: $font-size-s;
|
| 165 |
+
.style-wrapper {
|
| 166 |
+
padding: $padding;
|
| 167 |
+
.style-rules {
|
| 168 |
+
border: 1px solid var(--border);
|
| 169 |
+
padding: $padding;
|
| 170 |
+
margin-bottom: 10px;
|
| 171 |
+
.rule {
|
| 172 |
+
padding-left: 2em;
|
| 173 |
+
word-break: break-all;
|
| 174 |
+
a {
|
| 175 |
+
color: var(--link-color);
|
| 176 |
+
}
|
| 177 |
+
span {
|
| 178 |
+
color: var(--var-color);
|
| 179 |
+
}
|
| 180 |
+
}
|
| 181 |
+
&:last-child {
|
| 182 |
+
margin-bottom: 0;
|
| 183 |
+
}
|
| 184 |
+
}
|
| 185 |
+
}
|
| 186 |
+
}
|
| 187 |
+
.listeners {
|
| 188 |
+
font-size: $font-size-s;
|
| 189 |
+
.listener-wrapper {
|
| 190 |
+
padding: $padding;
|
| 191 |
+
.listener {
|
| 192 |
+
margin-bottom: 10px;
|
| 193 |
+
overflow: hidden;
|
| 194 |
+
border: 1px solid var(--border);
|
| 195 |
+
.listener-type {
|
| 196 |
+
padding: $padding;
|
| 197 |
+
background: var(--darker-background);
|
| 198 |
+
color: var(--primary);
|
| 199 |
+
}
|
| 200 |
+
.listener-content {
|
| 201 |
+
li {
|
| 202 |
+
@include overflow-auto(x);
|
| 203 |
+
padding: $padding;
|
| 204 |
+
border-top: none;
|
| 205 |
+
}
|
| 206 |
+
}
|
| 207 |
+
}
|
| 208 |
+
}
|
| 209 |
+
}
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
.safe-area #elements {
|
| 213 |
+
.elements {
|
| 214 |
+
@include safe-area(padding-bottom, 24px);
|
| 215 |
+
}
|
| 216 |
+
.crumbs {
|
| 217 |
+
@include safe-area(height, 24px);
|
| 218 |
+
}
|
| 219 |
+
.element {
|
| 220 |
+
@include safe-area(padding-bottom, 0px);
|
| 221 |
+
}
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
@media screen and (min-width: 680px) {
|
| 225 |
+
#elements {
|
| 226 |
+
.elements {
|
| 227 |
+
width: 50%;
|
| 228 |
+
.control {
|
| 229 |
+
.icon-eye {
|
| 230 |
+
display: none;
|
| 231 |
+
}
|
| 232 |
+
.icon-copy {
|
| 233 |
+
right: 0;
|
| 234 |
+
}
|
| 235 |
+
.icon-delete {
|
| 236 |
+
right: 23px;
|
| 237 |
+
}
|
| 238 |
+
}
|
| 239 |
+
}
|
| 240 |
+
.detail {
|
| 241 |
+
width: 50%;
|
| 242 |
+
left: initial;
|
| 243 |
+
right: 0;
|
| 244 |
+
border-left: 1px solid var(--border);
|
| 245 |
+
.control {
|
| 246 |
+
padding-left: 10px;
|
| 247 |
+
.icon-arrow-left {
|
| 248 |
+
display: none;
|
| 249 |
+
}
|
| 250 |
+
}
|
| 251 |
+
}
|
| 252 |
+
}
|
| 253 |
+
}
|
src/Elements/util.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import each from 'licia/each'
|
| 2 |
+
import isStr from 'licia/isStr'
|
| 3 |
+
import isShadowRoot from 'licia/isShadowRoot'
|
| 4 |
+
import { classPrefix as c } from '../lib/util'
|
| 5 |
+
|
| 6 |
+
export function formatNodeName(node, { noAttr = false } = {}) {
|
| 7 |
+
if (node.nodeType === Node.TEXT_NODE) {
|
| 8 |
+
return `<span class="${c('tag-name-color')}">(text)</span>`
|
| 9 |
+
} else if (node.nodeType === Node.COMMENT_NODE) {
|
| 10 |
+
return `<span class="${c('tag-name-color')}"><!--></span>`
|
| 11 |
+
} else if (isShadowRoot(node)) {
|
| 12 |
+
return `<span class="${c('tag-name-color')}">#shadow-root</span>`
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
const { id, className, attributes } = node
|
| 16 |
+
|
| 17 |
+
let ret = `<span class="eruda-tag-name-color">${node.tagName.toLowerCase()}</span>`
|
| 18 |
+
|
| 19 |
+
if (id !== '') ret += `<span class="eruda-function-color">#${id}</span>`
|
| 20 |
+
|
| 21 |
+
if (isStr(className)) {
|
| 22 |
+
let classes = ''
|
| 23 |
+
each(className.split(/\s+/g), (val) => {
|
| 24 |
+
if (val.trim() === '') return
|
| 25 |
+
classes += `.${val}`
|
| 26 |
+
})
|
| 27 |
+
ret += `<span class="eruda-attribute-name-color">${classes}</span>`
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
if (!noAttr) {
|
| 31 |
+
each(attributes, (attr) => {
|
| 32 |
+
const name = attr.name
|
| 33 |
+
if (name === 'id' || name === 'class' || name === 'style') return
|
| 34 |
+
ret += ` <span class="eruda-attribute-name-color">${name}</span><span class="eruda-operator-color">="</span><span class="eruda-string-color">${attr.value}</span><span class="eruda-operator-color">"</span>`
|
| 35 |
+
})
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
return ret
|
| 39 |
+
}
|
src/EntryBtn/EntryBtn.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import emitter from '../lib/emitter'
|
| 2 |
+
import Settings from '../Settings/Settings'
|
| 3 |
+
import Emitter from 'licia/Emitter'
|
| 4 |
+
import $ from 'licia/$'
|
| 5 |
+
import nextTick from 'licia/nextTick'
|
| 6 |
+
import orientation from 'licia/orientation'
|
| 7 |
+
import pointerEvent from 'licia/pointerEvent'
|
| 8 |
+
import { pxToNum, classPrefix as c, eventClient } from '../lib/util'
|
| 9 |
+
import evalCss from '../lib/evalCss'
|
| 10 |
+
|
| 11 |
+
const $document = $(document)
|
| 12 |
+
|
| 13 |
+
export default class EntryBtn extends Emitter {
|
| 14 |
+
constructor($container) {
|
| 15 |
+
super()
|
| 16 |
+
|
| 17 |
+
this._style = evalCss(require('./EntryBtn.scss'))
|
| 18 |
+
|
| 19 |
+
this._$container = $container
|
| 20 |
+
this._initTpl()
|
| 21 |
+
this._bindEvent()
|
| 22 |
+
this._registerListener()
|
| 23 |
+
}
|
| 24 |
+
hide() {
|
| 25 |
+
this._$el.hide()
|
| 26 |
+
}
|
| 27 |
+
show() {
|
| 28 |
+
this._$el.show()
|
| 29 |
+
}
|
| 30 |
+
setPos(pos) {
|
| 31 |
+
if (this._isOutOfRange(pos)) {
|
| 32 |
+
pos = this._getDefPos()
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
this._$el.css({
|
| 36 |
+
left: pos.x,
|
| 37 |
+
top: pos.y,
|
| 38 |
+
})
|
| 39 |
+
|
| 40 |
+
this.config.set('pos', pos)
|
| 41 |
+
}
|
| 42 |
+
getPos() {
|
| 43 |
+
return this.config.get('pos')
|
| 44 |
+
}
|
| 45 |
+
destroy() {
|
| 46 |
+
evalCss.remove(this._style)
|
| 47 |
+
this._unregisterListener()
|
| 48 |
+
this._$el.remove()
|
| 49 |
+
}
|
| 50 |
+
_isOutOfRange(pos) {
|
| 51 |
+
pos = pos || this.config.get('pos')
|
| 52 |
+
const defPos = this._getDefPos()
|
| 53 |
+
|
| 54 |
+
return (
|
| 55 |
+
pos.x > defPos.x + 10 || pos.x < 0 || pos.y < 0 || pos.y > defPos.y + 10
|
| 56 |
+
)
|
| 57 |
+
}
|
| 58 |
+
_registerListener() {
|
| 59 |
+
this._scaleListener = () =>
|
| 60 |
+
nextTick(() => {
|
| 61 |
+
if (this._isOutOfRange()) this._resetPos()
|
| 62 |
+
})
|
| 63 |
+
emitter.on(emitter.SCALE, this._scaleListener)
|
| 64 |
+
}
|
| 65 |
+
_unregisterListener() {
|
| 66 |
+
emitter.off(emitter.SCALE, this._scaleListener)
|
| 67 |
+
}
|
| 68 |
+
_initTpl() {
|
| 69 |
+
const $container = this._$container
|
| 70 |
+
|
| 71 |
+
$container.append(
|
| 72 |
+
c('<div class="entry-btn"><span class="icon-tool"></span></div>')
|
| 73 |
+
)
|
| 74 |
+
this._$el = $container.find('.eruda-entry-btn')
|
| 75 |
+
}
|
| 76 |
+
_resetPos(orientationChanged) {
|
| 77 |
+
const cfg = this.config
|
| 78 |
+
let pos = cfg.get('pos')
|
| 79 |
+
const defPos = this._getDefPos()
|
| 80 |
+
|
| 81 |
+
if (!cfg.get('rememberPos') || orientationChanged) {
|
| 82 |
+
pos = defPos
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
this.setPos(pos)
|
| 86 |
+
}
|
| 87 |
+
_onDragStart = (e) => {
|
| 88 |
+
const $el = this._$el
|
| 89 |
+
$el.addClass(c('active'))
|
| 90 |
+
|
| 91 |
+
this._isClick = true
|
| 92 |
+
e = e.origEvent
|
| 93 |
+
this._startX = eventClient('x', e)
|
| 94 |
+
this._oldX = pxToNum($el.css('left'))
|
| 95 |
+
this._oldY = pxToNum($el.css('top'))
|
| 96 |
+
this._startY = eventClient('y', e)
|
| 97 |
+
$document.on(pointerEvent('move'), this._onDragMove)
|
| 98 |
+
$document.on(pointerEvent('up'), this._onDragEnd)
|
| 99 |
+
}
|
| 100 |
+
_onDragMove = (e) => {
|
| 101 |
+
const btnSize = this._$el.get(0).offsetWidth
|
| 102 |
+
const maxWidth = this._$container.get(0).offsetWidth
|
| 103 |
+
const maxHeight = this._$container.get(0).offsetHeight
|
| 104 |
+
|
| 105 |
+
e = e.origEvent
|
| 106 |
+
const deltaX = eventClient('x', e) - this._startX
|
| 107 |
+
const deltaY = eventClient('y', e) - this._startY
|
| 108 |
+
if (Math.abs(deltaX) > 3 || Math.abs(deltaY) > 3) {
|
| 109 |
+
this._isClick = false
|
| 110 |
+
}
|
| 111 |
+
let newX = this._oldX + deltaX
|
| 112 |
+
let newY = this._oldY + deltaY
|
| 113 |
+
if (newX < 0) {
|
| 114 |
+
newX = 0
|
| 115 |
+
} else if (newX > maxWidth - btnSize) {
|
| 116 |
+
newX = maxWidth - btnSize
|
| 117 |
+
}
|
| 118 |
+
if (newY < 0) {
|
| 119 |
+
newY = 0
|
| 120 |
+
} else if (newY > maxHeight - btnSize) {
|
| 121 |
+
newY = maxHeight - btnSize
|
| 122 |
+
}
|
| 123 |
+
this._$el.css({
|
| 124 |
+
left: newX,
|
| 125 |
+
top: newY,
|
| 126 |
+
})
|
| 127 |
+
}
|
| 128 |
+
_onDragEnd = (e) => {
|
| 129 |
+
const $el = this._$el
|
| 130 |
+
|
| 131 |
+
if (this._isClick) {
|
| 132 |
+
this.emit('click')
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
this._onDragMove(e)
|
| 136 |
+
$document.off(pointerEvent('move'), this._onDragMove)
|
| 137 |
+
$document.off(pointerEvent('up'), this._onDragEnd)
|
| 138 |
+
|
| 139 |
+
const cfg = this.config
|
| 140 |
+
|
| 141 |
+
if (cfg.get('rememberPos')) {
|
| 142 |
+
cfg.set('pos', {
|
| 143 |
+
x: pxToNum($el.css('left')),
|
| 144 |
+
y: pxToNum($el.css('top')),
|
| 145 |
+
})
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
$el.rmClass('eruda-active')
|
| 149 |
+
}
|
| 150 |
+
_bindEvent() {
|
| 151 |
+
const $el = this._$el
|
| 152 |
+
|
| 153 |
+
$el.on(pointerEvent('down'), this._onDragStart)
|
| 154 |
+
|
| 155 |
+
orientation.on('change', () => this._resetPos(true))
|
| 156 |
+
window.addEventListener('resize', () => this._resetPos())
|
| 157 |
+
}
|
| 158 |
+
initCfg(settings) {
|
| 159 |
+
const cfg = (this.config = Settings.createCfg('entry-button', {
|
| 160 |
+
rememberPos: true,
|
| 161 |
+
pos: this._getDefPos(),
|
| 162 |
+
}))
|
| 163 |
+
|
| 164 |
+
settings.switch(cfg, 'rememberPos', 'Remember Entry Button Position')
|
| 165 |
+
|
| 166 |
+
this._resetPos()
|
| 167 |
+
}
|
| 168 |
+
_getDefPos() {
|
| 169 |
+
const minWidth = this._$el.get(0).offsetWidth + 10
|
| 170 |
+
|
| 171 |
+
return {
|
| 172 |
+
x: window.innerWidth - minWidth,
|
| 173 |
+
y: window.innerHeight - minWidth,
|
| 174 |
+
}
|
| 175 |
+
}
|
| 176 |
+
}
|
src/EntryBtn/EntryBtn.scss
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.container {
|
| 2 |
+
.entry-btn {
|
| 3 |
+
touch-action: none;
|
| 4 |
+
width: 40px;
|
| 5 |
+
height: 40px;
|
| 6 |
+
display: flex;
|
| 7 |
+
background: #000;
|
| 8 |
+
opacity: 0.3;
|
| 9 |
+
border-radius: 10px;
|
| 10 |
+
position: relative;
|
| 11 |
+
z-index: 1000;
|
| 12 |
+
transition: opacity 0.3s;
|
| 13 |
+
color: #fff;
|
| 14 |
+
font-size: 25px;
|
| 15 |
+
align-items: center;
|
| 16 |
+
justify-content: center;
|
| 17 |
+
&.active,
|
| 18 |
+
&:active {
|
| 19 |
+
opacity: 0.8;
|
| 20 |
+
}
|
| 21 |
+
}
|
| 22 |
+
}
|
src/Info/Info.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import Tool from '../DevTools/Tool'
|
| 2 |
+
import defInfo from './defInfo'
|
| 3 |
+
import each from 'licia/each'
|
| 4 |
+
import isFn from 'licia/isFn'
|
| 5 |
+
import isUndef from 'licia/isUndef'
|
| 6 |
+
import cloneDeep from 'licia/cloneDeep'
|
| 7 |
+
import evalCss from '../lib/evalCss'
|
| 8 |
+
import map from 'licia/map'
|
| 9 |
+
import escape from 'licia/escape'
|
| 10 |
+
import copy from 'licia/copy'
|
| 11 |
+
import $ from 'licia/$'
|
| 12 |
+
import { classPrefix as c } from '../lib/util'
|
| 13 |
+
|
| 14 |
+
export default class Info extends Tool {
|
| 15 |
+
constructor() {
|
| 16 |
+
super()
|
| 17 |
+
|
| 18 |
+
this._style = evalCss(require('./Info.scss'))
|
| 19 |
+
|
| 20 |
+
this.name = 'info'
|
| 21 |
+
this._infos = []
|
| 22 |
+
}
|
| 23 |
+
init($el, container) {
|
| 24 |
+
super.init($el)
|
| 25 |
+
this._container = container
|
| 26 |
+
|
| 27 |
+
this._addDefInfo()
|
| 28 |
+
this._bindEvent()
|
| 29 |
+
}
|
| 30 |
+
destroy() {
|
| 31 |
+
super.destroy()
|
| 32 |
+
|
| 33 |
+
evalCss.remove(this._style)
|
| 34 |
+
}
|
| 35 |
+
add(name, val) {
|
| 36 |
+
const infos = this._infos
|
| 37 |
+
let isUpdate = false
|
| 38 |
+
|
| 39 |
+
each(infos, (info) => {
|
| 40 |
+
if (name !== info.name) return
|
| 41 |
+
|
| 42 |
+
info.val = val
|
| 43 |
+
isUpdate = true
|
| 44 |
+
})
|
| 45 |
+
|
| 46 |
+
if (!isUpdate) infos.push({ name, val })
|
| 47 |
+
|
| 48 |
+
this._render()
|
| 49 |
+
|
| 50 |
+
return this
|
| 51 |
+
}
|
| 52 |
+
get(name) {
|
| 53 |
+
const infos = this._infos
|
| 54 |
+
|
| 55 |
+
if (isUndef(name)) {
|
| 56 |
+
return cloneDeep(infos)
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
let result
|
| 60 |
+
|
| 61 |
+
each(infos, (info) => {
|
| 62 |
+
if (name === info.name) result = info.val
|
| 63 |
+
})
|
| 64 |
+
|
| 65 |
+
return result
|
| 66 |
+
}
|
| 67 |
+
remove(name) {
|
| 68 |
+
const infos = this._infos
|
| 69 |
+
|
| 70 |
+
for (let i = infos.length - 1; i >= 0; i--) {
|
| 71 |
+
if (infos[i].name === name) infos.splice(i, 1)
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
this._render()
|
| 75 |
+
|
| 76 |
+
return this
|
| 77 |
+
}
|
| 78 |
+
clear() {
|
| 79 |
+
this._infos = []
|
| 80 |
+
|
| 81 |
+
this._render()
|
| 82 |
+
|
| 83 |
+
return this
|
| 84 |
+
}
|
| 85 |
+
_addDefInfo() {
|
| 86 |
+
each(defInfo, (info) => this.add(info.name, info.val))
|
| 87 |
+
}
|
| 88 |
+
_render() {
|
| 89 |
+
const infos = []
|
| 90 |
+
|
| 91 |
+
each(this._infos, ({ name, val }) => {
|
| 92 |
+
if (isFn(val)) val = val()
|
| 93 |
+
|
| 94 |
+
infos.push({ name, val })
|
| 95 |
+
})
|
| 96 |
+
|
| 97 |
+
const html = `<ul>${map(
|
| 98 |
+
infos,
|
| 99 |
+
(info) =>
|
| 100 |
+
`<li><h2 class="${c('title')}">${escape(info.name)}<span class="${c(
|
| 101 |
+
'icon-copy copy'
|
| 102 |
+
)}"></span></h2><div class="${c('content')}">${info.val}</div></li>`
|
| 103 |
+
).join('')}</ul>`
|
| 104 |
+
|
| 105 |
+
this._renderHtml(html)
|
| 106 |
+
}
|
| 107 |
+
_bindEvent() {
|
| 108 |
+
const container = this._container
|
| 109 |
+
|
| 110 |
+
this._$el.on('click', c('.copy'), function () {
|
| 111 |
+
const $li = $(this).parent().parent()
|
| 112 |
+
const name = $li.find(c('.title')).text()
|
| 113 |
+
const content = $li.find(c('.content')).text()
|
| 114 |
+
copy(`${name}: ${content}`)
|
| 115 |
+
container.notify('Copied', { icon: 'success' })
|
| 116 |
+
})
|
| 117 |
+
}
|
| 118 |
+
_renderHtml(html) {
|
| 119 |
+
if (html === this._lastHtml) return
|
| 120 |
+
this._lastHtml = html
|
| 121 |
+
this._$el.html(html)
|
| 122 |
+
}
|
| 123 |
+
}
|
src/Info/Info.scss
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@use '../style/variable' as *;
|
| 2 |
+
@use '../style/mixin' as *;
|
| 3 |
+
|
| 4 |
+
#info {
|
| 5 |
+
@include overflow-auto(y);
|
| 6 |
+
li {
|
| 7 |
+
margin: 10px;
|
| 8 |
+
border: 1px solid var(--border);
|
| 9 |
+
.title,
|
| 10 |
+
.content {
|
| 11 |
+
padding: $padding;
|
| 12 |
+
}
|
| 13 |
+
.title {
|
| 14 |
+
position: relative;
|
| 15 |
+
padding-bottom: 0;
|
| 16 |
+
color: var(--accent);
|
| 17 |
+
.icon-copy {
|
| 18 |
+
position: absolute;
|
| 19 |
+
right: 10px;
|
| 20 |
+
top: 14px;
|
| 21 |
+
color: var(--primary);
|
| 22 |
+
cursor: pointer;
|
| 23 |
+
transition: color $anim-duration;
|
| 24 |
+
&:active {
|
| 25 |
+
color: var(--accent);
|
| 26 |
+
}
|
| 27 |
+
}
|
| 28 |
+
}
|
| 29 |
+
.content {
|
| 30 |
+
margin: 0;
|
| 31 |
+
user-select: text;
|
| 32 |
+
color: var(--foreground);
|
| 33 |
+
font-size: $font-size-s;
|
| 34 |
+
word-break: break-all;
|
| 35 |
+
table {
|
| 36 |
+
width: 100%;
|
| 37 |
+
border-collapse: collapse;
|
| 38 |
+
th,
|
| 39 |
+
td {
|
| 40 |
+
border: 1px solid var(--border);
|
| 41 |
+
padding: 10px;
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
* {
|
| 45 |
+
user-select: text;
|
| 46 |
+
}
|
| 47 |
+
a {
|
| 48 |
+
color: var(--link-color);
|
| 49 |
+
}
|
| 50 |
+
}
|
| 51 |
+
.device-key,
|
| 52 |
+
.system-key {
|
| 53 |
+
width: 100px;
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
.safe-area #info {
|
| 59 |
+
@include safe-area(padding-bottom, 10px);
|
| 60 |
+
}
|
src/Info/defInfo.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import detectBrowser from 'licia/detectBrowser'
|
| 2 |
+
import detectOs from 'licia/detectOs'
|
| 3 |
+
import escape from 'licia/escape'
|
| 4 |
+
|
| 5 |
+
const browser = detectBrowser()
|
| 6 |
+
|
| 7 |
+
export default [
|
| 8 |
+
{
|
| 9 |
+
name: 'Location',
|
| 10 |
+
val() {
|
| 11 |
+
return escape(location.href)
|
| 12 |
+
},
|
| 13 |
+
},
|
| 14 |
+
{
|
| 15 |
+
name: 'User Agent',
|
| 16 |
+
val: navigator.userAgent,
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
name: 'Device',
|
| 20 |
+
val: [
|
| 21 |
+
'<table><tbody>',
|
| 22 |
+
`<tr><td class="eruda-device-key">screen</td><td>${screen.width} * ${screen.height}</td></tr>`,
|
| 23 |
+
`<tr><td>viewport</td><td>${window.innerWidth} * ${window.innerHeight}</td></tr>`,
|
| 24 |
+
`<tr><td>pixel ratio</td><td>${window.devicePixelRatio}</td></tr>`,
|
| 25 |
+
'</tbody></table>',
|
| 26 |
+
].join(''),
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
name: 'System',
|
| 30 |
+
val: [
|
| 31 |
+
'<table><tbody>',
|
| 32 |
+
`<tr><td class="eruda-system-key">os</td><td>${detectOs()}</td></tr>`,
|
| 33 |
+
`<tr><td>browser</td><td>${
|
| 34 |
+
browser.name + ' ' + browser.version
|
| 35 |
+
}</td></tr>`,
|
| 36 |
+
'</tbody></table>',
|
| 37 |
+
].join(''),
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
name: 'About',
|
| 41 |
+
val:
|
| 42 |
+
'<a href="https://eruda.liriliri.io" target="_blank">Eruda v' +
|
| 43 |
+
VERSION +
|
| 44 |
+
'</a>',
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
name: 'Backers',
|
| 48 |
+
val() {
|
| 49 |
+
return `
|
| 50 |
+
<a rel="noreferrer noopener" href="https://opencollective.com/eruda" target="_blank">
|
| 51 |
+
<img data-exclude="true" style="width: 100%;" loading="lazy" src="https://opencollective.com/eruda/backers.svg?width=${
|
| 52 |
+
window.innerWidth * 1.5
|
| 53 |
+
}&exclude=true">
|
| 54 |
+
</a>`
|
| 55 |
+
},
|
| 56 |
+
},
|
| 57 |
+
]
|
src/Network/Detail.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import trim from 'licia/trim'
|
| 2 |
+
import isEmpty from 'licia/isEmpty'
|
| 3 |
+
import map from 'licia/map'
|
| 4 |
+
import each from 'licia/each'
|
| 5 |
+
import escape from 'licia/escape'
|
| 6 |
+
import copy from 'licia/copy'
|
| 7 |
+
import isJson from 'licia/isJson'
|
| 8 |
+
import Emitter from 'licia/Emitter'
|
| 9 |
+
import truncate from 'licia/truncate'
|
| 10 |
+
import { classPrefix as c } from '../lib/util'
|
| 11 |
+
|
| 12 |
+
export default class Detail extends Emitter {
|
| 13 |
+
constructor($container, devtools) {
|
| 14 |
+
super()
|
| 15 |
+
this._$container = $container
|
| 16 |
+
this._devtools = devtools
|
| 17 |
+
|
| 18 |
+
this._detailData = {}
|
| 19 |
+
this._bindEvent()
|
| 20 |
+
}
|
| 21 |
+
show(data) {
|
| 22 |
+
if (data.resTxt && trim(data.resTxt) === '') {
|
| 23 |
+
delete data.resTxt
|
| 24 |
+
}
|
| 25 |
+
if (isEmpty(data.resHeaders)) {
|
| 26 |
+
delete data.resHeaders
|
| 27 |
+
}
|
| 28 |
+
if (isEmpty(data.reqHeaders)) {
|
| 29 |
+
delete data.reqHeaders
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
let postData = ''
|
| 33 |
+
if (data.data) {
|
| 34 |
+
postData = `<pre class="${c('data')}">${escape(data.data)}</pre>`
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
let reqHeaders = '<tr><td>Empty</td></tr>'
|
| 38 |
+
if (data.reqHeaders) {
|
| 39 |
+
reqHeaders = map(data.reqHeaders, (val, key) => {
|
| 40 |
+
return `<tr>
|
| 41 |
+
<td class="${c('key')}">${escape(key)}</td>
|
| 42 |
+
<td>${escape(val)}</td>
|
| 43 |
+
</tr>`
|
| 44 |
+
}).join('')
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
let resHeaders = '<tr><td>Empty</td></tr>'
|
| 48 |
+
if (data.resHeaders) {
|
| 49 |
+
resHeaders = map(data.resHeaders, (val, key) => {
|
| 50 |
+
return `<tr>
|
| 51 |
+
<td class="${c('key')}">${escape(key)}</td>
|
| 52 |
+
<td>${escape(val)}</td>
|
| 53 |
+
</tr>`
|
| 54 |
+
}).join('')
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
let resTxt = ''
|
| 58 |
+
if (data.resTxt) {
|
| 59 |
+
let text = data.resTxt
|
| 60 |
+
if (text.length > MAX_RES_LEN) {
|
| 61 |
+
text = truncate(text, MAX_RES_LEN)
|
| 62 |
+
}
|
| 63 |
+
resTxt = `<pre class="${c('response')}">${escape(text)}</pre>`
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
const html = `<div class="${c('control')}">
|
| 67 |
+
<span class="${c('icon-arrow-left back')}"></span>
|
| 68 |
+
<span class="${c('icon-delete back')}"></span>
|
| 69 |
+
<span class="${c('url')}">${escape(data.url)}</span>
|
| 70 |
+
<span class="${c('icon-copy copy-res')}"></span>
|
| 71 |
+
</div>
|
| 72 |
+
<div class="${c('http')}">
|
| 73 |
+
${postData}
|
| 74 |
+
<div class="${c('section')}">
|
| 75 |
+
<h2>Response Headers</h2>
|
| 76 |
+
<table class="${c('headers')}">
|
| 77 |
+
<tbody>
|
| 78 |
+
${resHeaders}
|
| 79 |
+
</tbody>
|
| 80 |
+
</table>
|
| 81 |
+
</div>
|
| 82 |
+
<div class="${c('section')}">
|
| 83 |
+
<h2>Request Headers</h2>
|
| 84 |
+
<table class="${c('headers')}">
|
| 85 |
+
<tbody>
|
| 86 |
+
${reqHeaders}
|
| 87 |
+
</tbody>
|
| 88 |
+
</table>
|
| 89 |
+
</div>
|
| 90 |
+
${resTxt}
|
| 91 |
+
</div>`
|
| 92 |
+
|
| 93 |
+
this._$container.html(html).show()
|
| 94 |
+
this._detailData = data
|
| 95 |
+
}
|
| 96 |
+
hide() {
|
| 97 |
+
this._$container.hide()
|
| 98 |
+
this.emit('hide')
|
| 99 |
+
}
|
| 100 |
+
_copyRes = () => {
|
| 101 |
+
const detailData = this._detailData
|
| 102 |
+
|
| 103 |
+
let data = `${detailData.method} ${detailData.url} ${detailData.status}\n`
|
| 104 |
+
if (!isEmpty(detailData.data)) {
|
| 105 |
+
data += '\nRequest Data\n\n'
|
| 106 |
+
data += `${detailData.data}\n`
|
| 107 |
+
}
|
| 108 |
+
if (!isEmpty(detailData.reqHeaders)) {
|
| 109 |
+
data += '\nRequest Headers\n\n'
|
| 110 |
+
each(detailData.reqHeaders, (val, key) => (data += `${key}: ${val}\n`))
|
| 111 |
+
}
|
| 112 |
+
if (!isEmpty(detailData.resHeaders)) {
|
| 113 |
+
data += '\nResponse Headers\n\n'
|
| 114 |
+
each(detailData.resHeaders, (val, key) => (data += `${key}: ${val}\n`))
|
| 115 |
+
}
|
| 116 |
+
if (detailData.resTxt) {
|
| 117 |
+
data += `\n${detailData.resTxt}\n`
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
copy(data)
|
| 121 |
+
this._devtools.notify('Copied', { icon: 'success' })
|
| 122 |
+
}
|
| 123 |
+
_bindEvent() {
|
| 124 |
+
const devtools = this._devtools
|
| 125 |
+
|
| 126 |
+
this._$container
|
| 127 |
+
.on('click', c('.back'), () => this.hide())
|
| 128 |
+
.on('click', c('.copy-res'), this._copyRes)
|
| 129 |
+
.on('click', c('.http .response'), () => {
|
| 130 |
+
const data = this._detailData
|
| 131 |
+
const resTxt = data.resTxt
|
| 132 |
+
|
| 133 |
+
if (isJson(resTxt)) {
|
| 134 |
+
return showSources('object', resTxt)
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
switch (data.subType) {
|
| 138 |
+
case 'css':
|
| 139 |
+
return showSources('css', resTxt)
|
| 140 |
+
case 'html':
|
| 141 |
+
return showSources('html', resTxt)
|
| 142 |
+
case 'javascript':
|
| 143 |
+
return showSources('js', resTxt)
|
| 144 |
+
case 'json':
|
| 145 |
+
return showSources('object', resTxt)
|
| 146 |
+
}
|
| 147 |
+
switch (data.type) {
|
| 148 |
+
case 'image':
|
| 149 |
+
return showSources('img', data.url)
|
| 150 |
+
}
|
| 151 |
+
})
|
| 152 |
+
|
| 153 |
+
const showSources = (type, data) => {
|
| 154 |
+
const sources = devtools.get('sources')
|
| 155 |
+
if (!sources) {
|
| 156 |
+
return
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
sources.set(type, data)
|
| 160 |
+
|
| 161 |
+
devtools.showTool('sources')
|
| 162 |
+
}
|
| 163 |
+
}
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
const MAX_RES_LEN = 100000
|
src/Network/Network.js
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import Tool from '../DevTools/Tool'
|
| 2 |
+
import $ from 'licia/$'
|
| 3 |
+
import ms from 'licia/ms'
|
| 4 |
+
import each from 'licia/each'
|
| 5 |
+
import map from 'licia/map'
|
| 6 |
+
import Detail from './Detail'
|
| 7 |
+
import throttle from 'licia/throttle'
|
| 8 |
+
import { getFileName, classPrefix as c } from '../lib/util'
|
| 9 |
+
import evalCss from '../lib/evalCss'
|
| 10 |
+
import chobitsu from '../lib/chobitsu'
|
| 11 |
+
import emitter from '../lib/emitter'
|
| 12 |
+
import LunaDataGrid from 'luna-data-grid'
|
| 13 |
+
import ResizeSensor from 'licia/ResizeSensor'
|
| 14 |
+
import MediaQuery from 'licia/MediaQuery'
|
| 15 |
+
import { getType } from './util'
|
| 16 |
+
import copy from 'licia/copy'
|
| 17 |
+
import extend from 'licia/extend'
|
| 18 |
+
import trim from 'licia/trim'
|
| 19 |
+
import isNull from 'licia/isNull'
|
| 20 |
+
import LunaModal from 'luna-modal'
|
| 21 |
+
import { curlStr } from './util'
|
| 22 |
+
|
| 23 |
+
export default class Network extends Tool {
|
| 24 |
+
constructor() {
|
| 25 |
+
super()
|
| 26 |
+
|
| 27 |
+
this._style = evalCss(require('./Network.scss'))
|
| 28 |
+
|
| 29 |
+
this.name = 'network'
|
| 30 |
+
this._requests = {}
|
| 31 |
+
this._selectedRequest = null
|
| 32 |
+
this._isRecording = true
|
| 33 |
+
}
|
| 34 |
+
init($el, container) {
|
| 35 |
+
super.init($el)
|
| 36 |
+
|
| 37 |
+
this._container = container
|
| 38 |
+
this._initTpl()
|
| 39 |
+
this._detail = new Detail(this._$detail, container)
|
| 40 |
+
this._splitMediaQuery = new MediaQuery('screen and (min-width: 680px)')
|
| 41 |
+
this._splitMode = this._splitMediaQuery.isMatch()
|
| 42 |
+
this._requestDataGrid = new LunaDataGrid(this._$requests.get(0), {
|
| 43 |
+
columns: [
|
| 44 |
+
{
|
| 45 |
+
id: 'name',
|
| 46 |
+
title: 'Name',
|
| 47 |
+
sortable: true,
|
| 48 |
+
weight: 30,
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
id: 'method',
|
| 52 |
+
title: 'Method',
|
| 53 |
+
sortable: true,
|
| 54 |
+
weight: 14,
|
| 55 |
+
},
|
| 56 |
+
{
|
| 57 |
+
id: 'status',
|
| 58 |
+
title: 'Status',
|
| 59 |
+
sortable: true,
|
| 60 |
+
weight: 14,
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
id: 'type',
|
| 64 |
+
title: 'Type',
|
| 65 |
+
sortable: true,
|
| 66 |
+
weight: 14,
|
| 67 |
+
},
|
| 68 |
+
{
|
| 69 |
+
id: 'size',
|
| 70 |
+
title: 'Size',
|
| 71 |
+
sortable: true,
|
| 72 |
+
weight: 14,
|
| 73 |
+
},
|
| 74 |
+
{
|
| 75 |
+
id: 'time',
|
| 76 |
+
title: 'Time',
|
| 77 |
+
sortable: true,
|
| 78 |
+
weight: 14,
|
| 79 |
+
},
|
| 80 |
+
],
|
| 81 |
+
})
|
| 82 |
+
this._resizeSensor = new ResizeSensor($el.get(0))
|
| 83 |
+
this._bindEvent()
|
| 84 |
+
}
|
| 85 |
+
show() {
|
| 86 |
+
super.show()
|
| 87 |
+
this._updateDataGridHeight()
|
| 88 |
+
}
|
| 89 |
+
clear() {
|
| 90 |
+
this._requests = {}
|
| 91 |
+
this._requestDataGrid.clear()
|
| 92 |
+
}
|
| 93 |
+
requests() {
|
| 94 |
+
const ret = []
|
| 95 |
+
each(this._requests, (request) => {
|
| 96 |
+
ret.push(request)
|
| 97 |
+
})
|
| 98 |
+
return ret
|
| 99 |
+
}
|
| 100 |
+
_updateDataGridHeight() {
|
| 101 |
+
const height = this._$el.offset().height - this._$control.offset().height
|
| 102 |
+
this._requestDataGrid.setOption({
|
| 103 |
+
minHeight: height,
|
| 104 |
+
maxHeight: height,
|
| 105 |
+
})
|
| 106 |
+
}
|
| 107 |
+
_reqWillBeSent = (params) => {
|
| 108 |
+
if (!this._isRecording) {
|
| 109 |
+
return
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
const request = {
|
| 113 |
+
name: getFileName(params.request.url),
|
| 114 |
+
url: params.request.url,
|
| 115 |
+
status: 'pending',
|
| 116 |
+
type: 'unknown',
|
| 117 |
+
subType: 'unknown',
|
| 118 |
+
size: 0,
|
| 119 |
+
data: params.request.postData,
|
| 120 |
+
method: params.request.method,
|
| 121 |
+
startTime: params.timestamp * 1000,
|
| 122 |
+
time: 0,
|
| 123 |
+
resTxt: '',
|
| 124 |
+
done: false,
|
| 125 |
+
reqHeaders: params.request.headers || {},
|
| 126 |
+
resHeaders: {},
|
| 127 |
+
}
|
| 128 |
+
let node
|
| 129 |
+
request.render = () => {
|
| 130 |
+
const data = {
|
| 131 |
+
name: request.name,
|
| 132 |
+
method: request.method,
|
| 133 |
+
status: request.status,
|
| 134 |
+
type: request.subType,
|
| 135 |
+
size: request.size,
|
| 136 |
+
time: request.displayTime,
|
| 137 |
+
}
|
| 138 |
+
if (node) {
|
| 139 |
+
node.data = data
|
| 140 |
+
node.render()
|
| 141 |
+
} else {
|
| 142 |
+
node = this._requestDataGrid.append(data, { selectable: true })
|
| 143 |
+
$(node.container).data('id', params.requestId)
|
| 144 |
+
}
|
| 145 |
+
if (request.hasErr) {
|
| 146 |
+
$(node.container).addClass(c('request-error'))
|
| 147 |
+
}
|
| 148 |
+
}
|
| 149 |
+
request.render()
|
| 150 |
+
this._requests[params.requestId] = request
|
| 151 |
+
}
|
| 152 |
+
_resReceivedExtraInfo = (params) => {
|
| 153 |
+
const request = this._requests[params.requestId]
|
| 154 |
+
if (!this._isRecording || !request) {
|
| 155 |
+
return
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
request.resHeaders = params.headers
|
| 159 |
+
|
| 160 |
+
this._updateType(request)
|
| 161 |
+
request.render()
|
| 162 |
+
}
|
| 163 |
+
_updateType(request) {
|
| 164 |
+
const contentType = request.resHeaders['content-type'] || ''
|
| 165 |
+
const { type, subType } = getType(contentType)
|
| 166 |
+
request.type = type
|
| 167 |
+
request.subType = subType
|
| 168 |
+
}
|
| 169 |
+
_resReceived = (params) => {
|
| 170 |
+
const request = this._requests[params.requestId]
|
| 171 |
+
if (!this._isRecording || !request) {
|
| 172 |
+
return
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
const { response } = params
|
| 176 |
+
const { status, headers } = response
|
| 177 |
+
request.status = status
|
| 178 |
+
if (status < 200 || status >= 300) {
|
| 179 |
+
request.hasErr = true
|
| 180 |
+
}
|
| 181 |
+
if (headers) {
|
| 182 |
+
request.resHeaders = headers
|
| 183 |
+
this._updateType(request)
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
request.render()
|
| 187 |
+
}
|
| 188 |
+
_loadingFinished = (params) => {
|
| 189 |
+
const request = this._requests[params.requestId]
|
| 190 |
+
if (!this._isRecording || !request) {
|
| 191 |
+
return
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
const time = params.timestamp * 1000
|
| 195 |
+
request.time = time - request.startTime
|
| 196 |
+
request.displayTime = ms(request.time)
|
| 197 |
+
|
| 198 |
+
request.size = params.encodedDataLength
|
| 199 |
+
request.done = true
|
| 200 |
+
request.resTxt = chobitsu.domain('Network').getResponseBody({
|
| 201 |
+
requestId: params.requestId,
|
| 202 |
+
}).body
|
| 203 |
+
|
| 204 |
+
request.render()
|
| 205 |
+
}
|
| 206 |
+
_loadingFailed = (params) => {
|
| 207 |
+
const request = this._requests[params.requestId]
|
| 208 |
+
if (!this._isRecording || !request) {
|
| 209 |
+
return
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
const time = params.timestamp * 1000
|
| 213 |
+
request.time = time - request.startTime
|
| 214 |
+
request.displayTime = ms(request.time)
|
| 215 |
+
|
| 216 |
+
request.hasErr = true
|
| 217 |
+
request.status = 0
|
| 218 |
+
request.done = true
|
| 219 |
+
|
| 220 |
+
request.render()
|
| 221 |
+
}
|
| 222 |
+
_copyCurl = () => {
|
| 223 |
+
const request = this._selectedRequest
|
| 224 |
+
|
| 225 |
+
copy(
|
| 226 |
+
curlStr({
|
| 227 |
+
requestMethod: request.method,
|
| 228 |
+
url() {
|
| 229 |
+
return request.url
|
| 230 |
+
},
|
| 231 |
+
requestFormData() {
|
| 232 |
+
return request.data
|
| 233 |
+
},
|
| 234 |
+
requestHeaders() {
|
| 235 |
+
const reqHeaders = request.reqHeaders || {}
|
| 236 |
+
extend(reqHeaders, {
|
| 237 |
+
'User-Agent': navigator.userAgent,
|
| 238 |
+
Referer: location.href,
|
| 239 |
+
})
|
| 240 |
+
|
| 241 |
+
return map(reqHeaders, (value, name) => {
|
| 242 |
+
return {
|
| 243 |
+
name,
|
| 244 |
+
value,
|
| 245 |
+
}
|
| 246 |
+
})
|
| 247 |
+
},
|
| 248 |
+
})
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
this._container.notify('Copied', { icon: 'success' })
|
| 252 |
+
}
|
| 253 |
+
_updateButtons() {
|
| 254 |
+
const $control = this._$control
|
| 255 |
+
const $showDetail = $control.find(c('.show-detail'))
|
| 256 |
+
const $copyCurl = $control.find(c('.copy-curl'))
|
| 257 |
+
const iconDisabled = c('icon-disabled')
|
| 258 |
+
|
| 259 |
+
$showDetail.addClass(iconDisabled)
|
| 260 |
+
$copyCurl.addClass(iconDisabled)
|
| 261 |
+
|
| 262 |
+
if (this._selectedRequest) {
|
| 263 |
+
$showDetail.rmClass(iconDisabled)
|
| 264 |
+
$copyCurl.rmClass(iconDisabled)
|
| 265 |
+
}
|
| 266 |
+
}
|
| 267 |
+
_toggleRecording = () => {
|
| 268 |
+
this._$control.find(c('.record')).toggleClass(c('recording'))
|
| 269 |
+
this._isRecording = !this._isRecording
|
| 270 |
+
}
|
| 271 |
+
_showDetail = () => {
|
| 272 |
+
if (this._selectedRequest) {
|
| 273 |
+
if (this._splitMode) {
|
| 274 |
+
this._$network.css('width', '50%')
|
| 275 |
+
}
|
| 276 |
+
this._detail.show(this._selectedRequest)
|
| 277 |
+
}
|
| 278 |
+
}
|
| 279 |
+
_bindEvent() {
|
| 280 |
+
const $control = this._$control
|
| 281 |
+
const $filterText = this._$filterText
|
| 282 |
+
const requestDataGrid = this._requestDataGrid
|
| 283 |
+
|
| 284 |
+
const self = this
|
| 285 |
+
|
| 286 |
+
$control
|
| 287 |
+
.on('click', c('.clear-request'), () => this.clear())
|
| 288 |
+
.on('click', c('.show-detail'), this._showDetail)
|
| 289 |
+
.on('click', c('.copy-curl'), this._copyCurl)
|
| 290 |
+
.on('click', c('.record'), this._toggleRecording)
|
| 291 |
+
.on('click', c('.filter'), () => {
|
| 292 |
+
LunaModal.prompt('Filter').then((filter) => {
|
| 293 |
+
if (isNull(filter)) return
|
| 294 |
+
|
| 295 |
+
$filterText.text(filter)
|
| 296 |
+
requestDataGrid.setOption('filter', trim(filter))
|
| 297 |
+
})
|
| 298 |
+
})
|
| 299 |
+
|
| 300 |
+
requestDataGrid.on('select', (node) => {
|
| 301 |
+
const id = $(node.container).data('id')
|
| 302 |
+
const request = self._requests[id]
|
| 303 |
+
this._selectedRequest = request
|
| 304 |
+
this._updateButtons()
|
| 305 |
+
if (this._splitMode) {
|
| 306 |
+
this._showDetail()
|
| 307 |
+
}
|
| 308 |
+
})
|
| 309 |
+
|
| 310 |
+
requestDataGrid.on('deselect', () => {
|
| 311 |
+
this._selectedRequest = null
|
| 312 |
+
this._updateButtons()
|
| 313 |
+
this._detail.hide()
|
| 314 |
+
})
|
| 315 |
+
|
| 316 |
+
this._resizeSensor.addListener(
|
| 317 |
+
throttle(() => this._updateDataGridHeight(), 15)
|
| 318 |
+
)
|
| 319 |
+
|
| 320 |
+
this._splitMediaQuery.on('match', () => {
|
| 321 |
+
this._detail.hide()
|
| 322 |
+
this._splitMode = true
|
| 323 |
+
})
|
| 324 |
+
this._splitMediaQuery.on('unmatch', () => {
|
| 325 |
+
this._detail.hide()
|
| 326 |
+
this._splitMode = false
|
| 327 |
+
})
|
| 328 |
+
this._detail.on('hide', () => {
|
| 329 |
+
if (this._splitMode) {
|
| 330 |
+
this._$network.css('width', '100%')
|
| 331 |
+
}
|
| 332 |
+
})
|
| 333 |
+
|
| 334 |
+
chobitsu.domain('Network').enable()
|
| 335 |
+
|
| 336 |
+
const network = chobitsu.domain('Network')
|
| 337 |
+
network.on('requestWillBeSent', this._reqWillBeSent)
|
| 338 |
+
network.on('responseReceivedExtraInfo', this._resReceivedExtraInfo)
|
| 339 |
+
network.on('responseReceived', this._resReceived)
|
| 340 |
+
network.on('loadingFinished', this._loadingFinished)
|
| 341 |
+
network.on('loadingFailed', this._loadingFailed)
|
| 342 |
+
|
| 343 |
+
emitter.on(emitter.SCALE, this._updateScale)
|
| 344 |
+
}
|
| 345 |
+
_updateScale = (scale) => {
|
| 346 |
+
this._splitMediaQuery.setQuery(`screen and (min-width: ${680 * scale}px)`)
|
| 347 |
+
}
|
| 348 |
+
destroy() {
|
| 349 |
+
super.destroy()
|
| 350 |
+
|
| 351 |
+
this._resizeSensor.destroy()
|
| 352 |
+
evalCss.remove(this._style)
|
| 353 |
+
this._splitMediaQuery.removeAllListeners()
|
| 354 |
+
|
| 355 |
+
const network = chobitsu.domain('Network')
|
| 356 |
+
network.off('requestWillBeSent', this._reqWillBeSent)
|
| 357 |
+
network.off('responseReceivedExtraInfo', this._resReceivedExtraInfo)
|
| 358 |
+
network.off('responseReceived', this._resReceived)
|
| 359 |
+
network.off('loadingFinished', this._loadingFinished)
|
| 360 |
+
|
| 361 |
+
emitter.off(emitter.SCALE, this._updateScale)
|
| 362 |
+
}
|
| 363 |
+
_initTpl() {
|
| 364 |
+
const $el = this._$el
|
| 365 |
+
$el.html(
|
| 366 |
+
c(`<div class="network">
|
| 367 |
+
<div class="control">
|
| 368 |
+
<span class="icon-record record recording"></span>
|
| 369 |
+
<span class="icon-clear clear-request"></span>
|
| 370 |
+
<span class="icon-eye icon-disabled show-detail"></span>
|
| 371 |
+
<span class="icon-copy icon-disabled copy-curl"></span>
|
| 372 |
+
<span class="filter-text"></span>
|
| 373 |
+
<span class="icon-filter filter"></span>
|
| 374 |
+
</div>
|
| 375 |
+
<div class="requests"></div>
|
| 376 |
+
</div>
|
| 377 |
+
<div class="detail"></div>`)
|
| 378 |
+
)
|
| 379 |
+
this._$network = $el.find(c('.network'))
|
| 380 |
+
this._$detail = $el.find(c('.detail'))
|
| 381 |
+
this._$requests = $el.find(c('.requests'))
|
| 382 |
+
this._$control = $el.find(c('.control'))
|
| 383 |
+
this._$filterText = $el.find(c('.filter-text'))
|
| 384 |
+
}
|
| 385 |
+
}
|
src/Network/Network.scss
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@use '../style/variable' as *;
|
| 2 |
+
@use '../style/mixin' as *;
|
| 3 |
+
|
| 4 |
+
#network {
|
| 5 |
+
.network {
|
| 6 |
+
@include absolute();
|
| 7 |
+
padding-top: 39px;
|
| 8 |
+
}
|
| 9 |
+
.control {
|
| 10 |
+
padding: 10px;
|
| 11 |
+
border-bottom: none;
|
| 12 |
+
@include control();
|
| 13 |
+
.title {
|
| 14 |
+
font-size: $font-size;
|
| 15 |
+
}
|
| 16 |
+
.icon-clear {
|
| 17 |
+
left: 23px;
|
| 18 |
+
}
|
| 19 |
+
.icon-eye {
|
| 20 |
+
right: 0;
|
| 21 |
+
}
|
| 22 |
+
.icon-copy {
|
| 23 |
+
right: 23px;
|
| 24 |
+
}
|
| 25 |
+
.icon-filter {
|
| 26 |
+
right: 46px;
|
| 27 |
+
}
|
| 28 |
+
.filter-text {
|
| 29 |
+
white-space: nowrap;
|
| 30 |
+
position: absolute;
|
| 31 |
+
line-height: 20px;
|
| 32 |
+
max-width: 80px;
|
| 33 |
+
overflow: hidden;
|
| 34 |
+
right: 88px;
|
| 35 |
+
font-size: $font-size;
|
| 36 |
+
text-overflow: ellipsis;
|
| 37 |
+
}
|
| 38 |
+
.icon-record {
|
| 39 |
+
left: 0;
|
| 40 |
+
&.recording {
|
| 41 |
+
color: var(--console-error-foreground);
|
| 42 |
+
text-shadow: 0 0 4px var(--console-error-foreground);
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
.request-error {
|
| 47 |
+
color: var(--console-error-foreground);
|
| 48 |
+
}
|
| 49 |
+
.luna-data-grid:focus {
|
| 50 |
+
.luna-data-grid-data-container {
|
| 51 |
+
.request-error.luna-data-grid-selected {
|
| 52 |
+
background: var(--console-error-background);
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
.luna-data-grid {
|
| 57 |
+
border-left: none;
|
| 58 |
+
border-right: none;
|
| 59 |
+
}
|
| 60 |
+
.detail {
|
| 61 |
+
@include absolute();
|
| 62 |
+
z-index: 10;
|
| 63 |
+
display: none;
|
| 64 |
+
padding-top: 40px;
|
| 65 |
+
background: var(--background);
|
| 66 |
+
.control {
|
| 67 |
+
padding: 10px 35px;
|
| 68 |
+
border-bottom: 1px solid var(--border);
|
| 69 |
+
.url {
|
| 70 |
+
font-size: $font-size-s;
|
| 71 |
+
overflow: hidden;
|
| 72 |
+
white-space: nowrap;
|
| 73 |
+
text-overflow: ellipsis;
|
| 74 |
+
width: 100%;
|
| 75 |
+
display: inline-block;
|
| 76 |
+
}
|
| 77 |
+
.icon-arrow-left {
|
| 78 |
+
left: 0;
|
| 79 |
+
}
|
| 80 |
+
.icon-delete {
|
| 81 |
+
left: 0;
|
| 82 |
+
display: none;
|
| 83 |
+
}
|
| 84 |
+
.icon-copy {
|
| 85 |
+
right: 0;
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
.http {
|
| 89 |
+
@include overflow-auto(y);
|
| 90 |
+
height: 100%;
|
| 91 |
+
.section {
|
| 92 |
+
border-top: 1px solid var(--border);
|
| 93 |
+
border-bottom: 1px solid var(--border);
|
| 94 |
+
margin-top: 10px;
|
| 95 |
+
margin-bottom: 10px;
|
| 96 |
+
h2 {
|
| 97 |
+
background: var(--darker-background);
|
| 98 |
+
color: var(--primary);
|
| 99 |
+
padding: $padding;
|
| 100 |
+
line-height: 18px;
|
| 101 |
+
font-size: $font-size;
|
| 102 |
+
}
|
| 103 |
+
table {
|
| 104 |
+
color: var(--foreground);
|
| 105 |
+
* {
|
| 106 |
+
user-select: text;
|
| 107 |
+
}
|
| 108 |
+
td {
|
| 109 |
+
font-size: $font-size-s;
|
| 110 |
+
padding: 5px 10px;
|
| 111 |
+
word-break: break-all;
|
| 112 |
+
}
|
| 113 |
+
.key {
|
| 114 |
+
white-space: nowrap;
|
| 115 |
+
font-weight: bold;
|
| 116 |
+
color: var(--accent);
|
| 117 |
+
}
|
| 118 |
+
}
|
| 119 |
+
}
|
| 120 |
+
.response,
|
| 121 |
+
.data {
|
| 122 |
+
user-select: text;
|
| 123 |
+
@include overflow-auto(x);
|
| 124 |
+
padding: $padding;
|
| 125 |
+
font-size: $font-size-s;
|
| 126 |
+
margin: 10px 0;
|
| 127 |
+
white-space: pre-wrap;
|
| 128 |
+
border-top: 1px solid var(--border);
|
| 129 |
+
color: var(--foreground);
|
| 130 |
+
border-bottom: 1px solid var(--border);
|
| 131 |
+
}
|
| 132 |
+
}
|
| 133 |
+
}
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
.safe-area #network {
|
| 137 |
+
.http {
|
| 138 |
+
@include safe-area(padding-bottom, 0px);
|
| 139 |
+
}
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
@media screen and (min-width: 680px) {
|
| 143 |
+
#network {
|
| 144 |
+
.network {
|
| 145 |
+
.control {
|
| 146 |
+
.icon-eye {
|
| 147 |
+
display: none;
|
| 148 |
+
}
|
| 149 |
+
.icon-copy {
|
| 150 |
+
right: 0;
|
| 151 |
+
}
|
| 152 |
+
.icon-filter {
|
| 153 |
+
right: 23px;
|
| 154 |
+
}
|
| 155 |
+
.filter-text {
|
| 156 |
+
right: 55px;
|
| 157 |
+
}
|
| 158 |
+
}
|
| 159 |
+
}
|
| 160 |
+
.detail {
|
| 161 |
+
width: 50%;
|
| 162 |
+
left: initial;
|
| 163 |
+
right: 0;
|
| 164 |
+
border-left: 1px solid var(--border);
|
| 165 |
+
.control {
|
| 166 |
+
.icon-arrow-left {
|
| 167 |
+
display: none;
|
| 168 |
+
}
|
| 169 |
+
.icon-delete {
|
| 170 |
+
display: block;
|
| 171 |
+
}
|
| 172 |
+
}
|
| 173 |
+
}
|
| 174 |
+
}
|
| 175 |
+
}
|
src/Network/util.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import last from 'licia/last'
|
| 2 |
+
import detectOs from 'licia/detectOs'
|
| 3 |
+
import arrToMap from 'licia/arrToMap'
|
| 4 |
+
|
| 5 |
+
export function getType(contentType) {
|
| 6 |
+
if (!contentType) return 'unknown'
|
| 7 |
+
|
| 8 |
+
const type = contentType.split(';')[0].split('/')
|
| 9 |
+
|
| 10 |
+
return {
|
| 11 |
+
type: type[0],
|
| 12 |
+
subType: last(type),
|
| 13 |
+
}
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
export function curlStr(request) {
|
| 17 |
+
let platform = detectOs()
|
| 18 |
+
if (platform === 'windows') {
|
| 19 |
+
platform = 'win'
|
| 20 |
+
}
|
| 21 |
+
let command = []
|
| 22 |
+
const ignoredHeaders = arrToMap([
|
| 23 |
+
'accept-encoding',
|
| 24 |
+
'host',
|
| 25 |
+
'method',
|
| 26 |
+
'path',
|
| 27 |
+
'scheme',
|
| 28 |
+
'version',
|
| 29 |
+
])
|
| 30 |
+
|
| 31 |
+
function escapeStringWin(str) {
|
| 32 |
+
const encapsChars = /[\r\n]/.test(str) ? '^"' : '"'
|
| 33 |
+
return (
|
| 34 |
+
encapsChars +
|
| 35 |
+
str
|
| 36 |
+
.replace(/\\/g, '\\\\')
|
| 37 |
+
.replace(/"/g, '\\"')
|
| 38 |
+
.replace(/[^a-zA-Z0-9\s_\-:=+~'/.',?;()*`&]/g, '^$&')
|
| 39 |
+
.replace(/%(?=[a-zA-Z0-9_])/g, '%^')
|
| 40 |
+
.replace(/\r?\n/g, '^\n\n') +
|
| 41 |
+
encapsChars
|
| 42 |
+
)
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
function escapeStringPosix(str) {
|
| 46 |
+
function escapeCharacter(x) {
|
| 47 |
+
const code = x.charCodeAt(0)
|
| 48 |
+
let hexString = code.toString(16)
|
| 49 |
+
while (hexString.length < 4) {
|
| 50 |
+
hexString = '0' + hexString
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
return '\\u' + hexString
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
// eslint-disable-next-line no-control-regex
|
| 57 |
+
if (/[\0-\x1F\x7F-\x9F!]|'/.test(str)) {
|
| 58 |
+
return (
|
| 59 |
+
"$'" +
|
| 60 |
+
str
|
| 61 |
+
.replace(/\\/g, '\\\\')
|
| 62 |
+
.replace(/'/g, "\\'")
|
| 63 |
+
.replace(/\n/g, '\\n')
|
| 64 |
+
.replace(/\r/g, '\\r')
|
| 65 |
+
// eslint-disable-next-line no-control-regex
|
| 66 |
+
.replace(/[\0-\x1F\x7F-\x9F!]/g, escapeCharacter) +
|
| 67 |
+
"'"
|
| 68 |
+
)
|
| 69 |
+
}
|
| 70 |
+
return "'" + str + "'"
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
const escapeString = platform === 'win' ? escapeStringWin : escapeStringPosix
|
| 74 |
+
|
| 75 |
+
command.push(escapeString(request.url()).replace(/[[{}\]]/g, '\\$&'))
|
| 76 |
+
|
| 77 |
+
let inferredMethod = 'GET'
|
| 78 |
+
const data = []
|
| 79 |
+
const formData = request.requestFormData()
|
| 80 |
+
if (formData) {
|
| 81 |
+
data.push('--data-raw ' + escapeString(formData))
|
| 82 |
+
ignoredHeaders['content-length'] = true
|
| 83 |
+
inferredMethod = 'POST'
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
if (request.requestMethod !== inferredMethod) {
|
| 87 |
+
command.push('-X ' + escapeString(request.requestMethod))
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
const requestHeaders = request.requestHeaders()
|
| 91 |
+
for (let i = 0; i < requestHeaders.length; i++) {
|
| 92 |
+
const header = requestHeaders[i]
|
| 93 |
+
const name = header.name.replace(/^:/, '')
|
| 94 |
+
if (ignoredHeaders[name.toLowerCase()]) {
|
| 95 |
+
continue
|
| 96 |
+
}
|
| 97 |
+
command.push('-H ' + escapeString(name + ': ' + header.value))
|
| 98 |
+
}
|
| 99 |
+
command = command.concat(data)
|
| 100 |
+
command.push('--compressed')
|
| 101 |
+
|
| 102 |
+
return (
|
| 103 |
+
'curl ' +
|
| 104 |
+
command.join(
|
| 105 |
+
command.length >= 3 ? (platform === 'win' ? ' ^\n ' : ' \\\n ') : ' '
|
| 106 |
+
)
|
| 107 |
+
)
|
| 108 |
+
}
|
src/Resources/Cookie.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import map from 'licia/map'
|
| 2 |
+
import trim from 'licia/trim'
|
| 3 |
+
import isNull from 'licia/isNull'
|
| 4 |
+
import each from 'licia/each'
|
| 5 |
+
import copy from 'licia/copy'
|
| 6 |
+
import LunaModal from 'luna-modal'
|
| 7 |
+
import LunaDataGrid from 'luna-data-grid'
|
| 8 |
+
import { setState, getState } from './util'
|
| 9 |
+
import chobitsu from '../lib/chobitsu'
|
| 10 |
+
import { classPrefix as c } from '../lib/util'
|
| 11 |
+
|
| 12 |
+
export default class Cookie {
|
| 13 |
+
constructor($container, devtools) {
|
| 14 |
+
this._$container = $container
|
| 15 |
+
this._devtools = devtools
|
| 16 |
+
this._selectedItem = null
|
| 17 |
+
|
| 18 |
+
this._initTpl()
|
| 19 |
+
this._dataGrid = new LunaDataGrid(this._$dataGrid.get(0), {
|
| 20 |
+
columns: [
|
| 21 |
+
{
|
| 22 |
+
id: 'key',
|
| 23 |
+
title: 'Key',
|
| 24 |
+
weight: 30,
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
id: 'value',
|
| 28 |
+
title: 'Value',
|
| 29 |
+
weight: 90,
|
| 30 |
+
},
|
| 31 |
+
],
|
| 32 |
+
minHeight: 60,
|
| 33 |
+
maxHeight: 223,
|
| 34 |
+
})
|
| 35 |
+
|
| 36 |
+
this._bindEvent()
|
| 37 |
+
}
|
| 38 |
+
refresh() {
|
| 39 |
+
const $container = this._$container
|
| 40 |
+
const dataGrid = this._dataGrid
|
| 41 |
+
|
| 42 |
+
const { cookies } = chobitsu.domain('Network').getCookies()
|
| 43 |
+
const cookieData = map(cookies, ({ name, value }) => ({
|
| 44 |
+
key: name,
|
| 45 |
+
val: value,
|
| 46 |
+
}))
|
| 47 |
+
|
| 48 |
+
dataGrid.clear()
|
| 49 |
+
each(cookieData, ({ key, val }) => {
|
| 50 |
+
dataGrid.append(
|
| 51 |
+
{
|
| 52 |
+
key,
|
| 53 |
+
value: val,
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
selectable: true,
|
| 57 |
+
}
|
| 58 |
+
)
|
| 59 |
+
})
|
| 60 |
+
|
| 61 |
+
const cookieState = getState('cookie', cookieData.length)
|
| 62 |
+
setState($container, cookieState)
|
| 63 |
+
}
|
| 64 |
+
_initTpl() {
|
| 65 |
+
const $container = this._$container
|
| 66 |
+
|
| 67 |
+
$container.html(
|
| 68 |
+
c(`<h2 class="title">
|
| 69 |
+
Cookie
|
| 70 |
+
<div class="btn refresh-cookie">
|
| 71 |
+
<span class="icon-refresh"></span>
|
| 72 |
+
</div>
|
| 73 |
+
<div class="btn show-detail btn-disabled">
|
| 74 |
+
<span class="icon icon-eye"></span>
|
| 75 |
+
</div>
|
| 76 |
+
<div class="btn copy-cookie btn-disabled">
|
| 77 |
+
<span class="icon icon-copy"></span>
|
| 78 |
+
</div>
|
| 79 |
+
<div class="btn delete-cookie btn-disabled">
|
| 80 |
+
<span class="icon icon-delete"></span>
|
| 81 |
+
</div>
|
| 82 |
+
<div class="btn clear-cookie">
|
| 83 |
+
<span class="icon-clear"></span>
|
| 84 |
+
</div>
|
| 85 |
+
<div class="btn filter" data-type="cookie">
|
| 86 |
+
<span class="icon-filter"></span>
|
| 87 |
+
</div>
|
| 88 |
+
<div class="btn filter-text"></div>
|
| 89 |
+
</h2>
|
| 90 |
+
<div class="data-grid"></div>`)
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
this._$dataGrid = $container.find(c('.data-grid'))
|
| 94 |
+
this._$filterText = $container.find(c('.filter-text'))
|
| 95 |
+
}
|
| 96 |
+
_updateButtons() {
|
| 97 |
+
const $container = this._$container
|
| 98 |
+
const $showDetail = $container.find(c('.show-detail'))
|
| 99 |
+
const $deleteCookie = $container.find(c('.delete-cookie'))
|
| 100 |
+
const $copyCookie = $container.find(c('.copy-cookie'))
|
| 101 |
+
const btnDisabled = c('btn-disabled')
|
| 102 |
+
|
| 103 |
+
$showDetail.addClass(btnDisabled)
|
| 104 |
+
$deleteCookie.addClass(btnDisabled)
|
| 105 |
+
$copyCookie.addClass(btnDisabled)
|
| 106 |
+
|
| 107 |
+
if (this._selectedItem) {
|
| 108 |
+
$showDetail.rmClass(btnDisabled)
|
| 109 |
+
$deleteCookie.rmClass(btnDisabled)
|
| 110 |
+
$copyCookie.rmClass(btnDisabled)
|
| 111 |
+
}
|
| 112 |
+
}
|
| 113 |
+
_getVal(key) {
|
| 114 |
+
const { cookies } = chobitsu.domain('Network').getCookies()
|
| 115 |
+
|
| 116 |
+
for (let i = 0, len = cookies.length; i < len; i++) {
|
| 117 |
+
if (cookies[i].name === key) {
|
| 118 |
+
return cookies[i].value
|
| 119 |
+
}
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
return ''
|
| 123 |
+
}
|
| 124 |
+
_bindEvent() {
|
| 125 |
+
const devtools = this._devtools
|
| 126 |
+
|
| 127 |
+
this._$container
|
| 128 |
+
.on('click', c('.refresh-cookie'), () => {
|
| 129 |
+
devtools.notify('Refreshed', { icon: 'success' })
|
| 130 |
+
this.refresh()
|
| 131 |
+
})
|
| 132 |
+
.on('click', c('.clear-cookie'), () => {
|
| 133 |
+
chobitsu.domain('Storage').clearDataForOrigin({
|
| 134 |
+
storageTypes: 'cookies',
|
| 135 |
+
})
|
| 136 |
+
this.refresh()
|
| 137 |
+
})
|
| 138 |
+
.on('click', c('.delete-cookie'), () => {
|
| 139 |
+
const key = this._selectedItem
|
| 140 |
+
|
| 141 |
+
chobitsu.domain('Network').deleteCookies({ name: key })
|
| 142 |
+
this.refresh()
|
| 143 |
+
})
|
| 144 |
+
.on('click', c('.show-detail'), () => {
|
| 145 |
+
const key = this._selectedItem
|
| 146 |
+
const val = this._getVal(key)
|
| 147 |
+
|
| 148 |
+
try {
|
| 149 |
+
showSources('object', JSON.parse(val))
|
| 150 |
+
} catch {
|
| 151 |
+
showSources('raw', val)
|
| 152 |
+
}
|
| 153 |
+
})
|
| 154 |
+
.on('click', c('.copy-cookie'), () => {
|
| 155 |
+
const key = this._selectedItem
|
| 156 |
+
copy(this._getVal(key))
|
| 157 |
+
devtools.notify('Copied', { icon: 'success' })
|
| 158 |
+
})
|
| 159 |
+
.on('click', c('.filter'), () => {
|
| 160 |
+
LunaModal.prompt('Filter').then((filter) => {
|
| 161 |
+
if (isNull(filter)) return
|
| 162 |
+
filter = trim(filter)
|
| 163 |
+
this._filter = filter
|
| 164 |
+
this._$filterText.text(filter)
|
| 165 |
+
this._dataGrid.setOption('filter', filter)
|
| 166 |
+
})
|
| 167 |
+
})
|
| 168 |
+
|
| 169 |
+
function showSources(type, data) {
|
| 170 |
+
const sources = devtools.get('sources')
|
| 171 |
+
if (!sources) return
|
| 172 |
+
|
| 173 |
+
sources.set(type, data)
|
| 174 |
+
|
| 175 |
+
devtools.showTool('sources')
|
| 176 |
+
|
| 177 |
+
return true
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
this._dataGrid
|
| 181 |
+
.on('select', (node) => {
|
| 182 |
+
this._selectedItem = node.data.key
|
| 183 |
+
this._updateButtons()
|
| 184 |
+
})
|
| 185 |
+
.on('deselect', () => {
|
| 186 |
+
this._selectedItem = null
|
| 187 |
+
this._updateButtons()
|
| 188 |
+
})
|
| 189 |
+
}
|
| 190 |
+
}
|
src/Resources/Resources.js
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import Tool from '../DevTools/Tool'
|
| 2 |
+
import Settings from '../Settings/Settings'
|
| 3 |
+
import $ from 'licia/$'
|
| 4 |
+
import escape from 'licia/escape'
|
| 5 |
+
import isEmpty from 'licia/isEmpty'
|
| 6 |
+
import contain from 'licia/contain'
|
| 7 |
+
import unique from 'licia/unique'
|
| 8 |
+
import each from 'licia/each'
|
| 9 |
+
import sameOrigin from 'licia/sameOrigin'
|
| 10 |
+
import ajax from 'licia/ajax'
|
| 11 |
+
import MutationObserver from 'licia/MutationObserver'
|
| 12 |
+
import toArr from 'licia/toArr'
|
| 13 |
+
import concat from 'licia/concat'
|
| 14 |
+
import map from 'licia/map'
|
| 15 |
+
import { isErudaEl, classPrefix as c } from '../lib/util'
|
| 16 |
+
import evalCss from '../lib/evalCss'
|
| 17 |
+
import Storage from './Storage'
|
| 18 |
+
import Cookie from './Cookie'
|
| 19 |
+
import { setState, getState } from './util'
|
| 20 |
+
|
| 21 |
+
export default class Resources extends Tool {
|
| 22 |
+
constructor() {
|
| 23 |
+
super()
|
| 24 |
+
|
| 25 |
+
this._style = evalCss(require('./Resources.scss'))
|
| 26 |
+
|
| 27 |
+
this.name = 'resources'
|
| 28 |
+
this._hideErudaSetting = false
|
| 29 |
+
this._observeElement = true
|
| 30 |
+
}
|
| 31 |
+
init($el, container) {
|
| 32 |
+
super.init($el)
|
| 33 |
+
|
| 34 |
+
this._container = container
|
| 35 |
+
|
| 36 |
+
this._initTpl()
|
| 37 |
+
this._localStorage = new Storage(
|
| 38 |
+
this._$localStorage,
|
| 39 |
+
container,
|
| 40 |
+
this,
|
| 41 |
+
'local'
|
| 42 |
+
)
|
| 43 |
+
this._sessionStorage = new Storage(
|
| 44 |
+
this._$sessionStorage,
|
| 45 |
+
container,
|
| 46 |
+
this,
|
| 47 |
+
'session'
|
| 48 |
+
)
|
| 49 |
+
this._cookie = new Cookie(this._$cookie, container)
|
| 50 |
+
|
| 51 |
+
this._bindEvent()
|
| 52 |
+
this._initObserver()
|
| 53 |
+
this._initCfg()
|
| 54 |
+
}
|
| 55 |
+
refresh() {
|
| 56 |
+
return this.refreshLocalStorage()
|
| 57 |
+
.refreshSessionStorage()
|
| 58 |
+
.refreshCookie()
|
| 59 |
+
.refreshScript()
|
| 60 |
+
.refreshStylesheet()
|
| 61 |
+
.refreshIframe()
|
| 62 |
+
.refreshImage()
|
| 63 |
+
}
|
| 64 |
+
destroy() {
|
| 65 |
+
super.destroy()
|
| 66 |
+
|
| 67 |
+
this._localStorage.destroy()
|
| 68 |
+
this._sessionStorage.destroy()
|
| 69 |
+
this._disableObserver()
|
| 70 |
+
evalCss.remove(this._style)
|
| 71 |
+
this._rmCfg()
|
| 72 |
+
}
|
| 73 |
+
refreshScript() {
|
| 74 |
+
let scriptData = []
|
| 75 |
+
|
| 76 |
+
$('script').each(function () {
|
| 77 |
+
const src = this.src
|
| 78 |
+
|
| 79 |
+
if (src !== '') scriptData.push(src)
|
| 80 |
+
})
|
| 81 |
+
|
| 82 |
+
scriptData = unique(scriptData)
|
| 83 |
+
|
| 84 |
+
const scriptState = getState('script', scriptData.length)
|
| 85 |
+
let scriptDataHtml = '<li>Empty</li>'
|
| 86 |
+
if (!isEmpty(scriptData)) {
|
| 87 |
+
scriptDataHtml = map(scriptData, (script) => {
|
| 88 |
+
script = escape(script)
|
| 89 |
+
return `<li><a href="${script}" target="_blank" class="${c(
|
| 90 |
+
'js-link'
|
| 91 |
+
)}">${script}</a></li>`
|
| 92 |
+
}).join('')
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
const scriptHtml = `<h2 class="${c('title')}">
|
| 96 |
+
Script
|
| 97 |
+
<div class="${c('btn refresh-script')}">
|
| 98 |
+
<span class="${c('icon-refresh')}"></span>
|
| 99 |
+
</div>
|
| 100 |
+
</h2>
|
| 101 |
+
<ul class="${c('link-list')}">
|
| 102 |
+
${scriptDataHtml}
|
| 103 |
+
</ul>`
|
| 104 |
+
|
| 105 |
+
const $script = this._$script
|
| 106 |
+
setState($script, scriptState)
|
| 107 |
+
$script.html(scriptHtml)
|
| 108 |
+
|
| 109 |
+
return this
|
| 110 |
+
}
|
| 111 |
+
refreshStylesheet() {
|
| 112 |
+
let stylesheetData = []
|
| 113 |
+
|
| 114 |
+
$('link').each(function () {
|
| 115 |
+
if (this.rel !== 'stylesheet') return
|
| 116 |
+
|
| 117 |
+
stylesheetData.push(this.href)
|
| 118 |
+
})
|
| 119 |
+
|
| 120 |
+
stylesheetData = unique(stylesheetData)
|
| 121 |
+
|
| 122 |
+
const stylesheetState = getState('stylesheet', stylesheetData.length)
|
| 123 |
+
let stylesheetDataHtml = '<li>Empty</li>'
|
| 124 |
+
if (!isEmpty(stylesheetData)) {
|
| 125 |
+
stylesheetDataHtml = map(stylesheetData, (stylesheet) => {
|
| 126 |
+
stylesheet = escape(stylesheet)
|
| 127 |
+
return ` <li><a href="${stylesheet}" target="_blank" class="${c(
|
| 128 |
+
'css-link'
|
| 129 |
+
)}">${stylesheet}</a></li>`
|
| 130 |
+
}).join('')
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
const stylesheetHtml = `<h2 class="${c('title')}">
|
| 134 |
+
Stylesheet
|
| 135 |
+
<div class="${c('btn refresh-stylesheet')}">
|
| 136 |
+
<span class="${c('icon-refresh')}"></span>
|
| 137 |
+
</div>
|
| 138 |
+
</h2>
|
| 139 |
+
<ul class="${c('link-list')}">
|
| 140 |
+
${stylesheetDataHtml}
|
| 141 |
+
</ul>`
|
| 142 |
+
|
| 143 |
+
const $stylesheet = this._$stylesheet
|
| 144 |
+
setState($stylesheet, stylesheetState)
|
| 145 |
+
$stylesheet.html(stylesheetHtml)
|
| 146 |
+
|
| 147 |
+
return this
|
| 148 |
+
}
|
| 149 |
+
refreshIframe() {
|
| 150 |
+
let iframeData = []
|
| 151 |
+
|
| 152 |
+
$('iframe').each(function () {
|
| 153 |
+
const $this = $(this)
|
| 154 |
+
const src = $this.attr('src')
|
| 155 |
+
|
| 156 |
+
if (src) iframeData.push(src)
|
| 157 |
+
})
|
| 158 |
+
|
| 159 |
+
iframeData = unique(iframeData)
|
| 160 |
+
|
| 161 |
+
let iframeDataHtml = '<li>Empty</li>'
|
| 162 |
+
if (!isEmpty(iframeData)) {
|
| 163 |
+
iframeDataHtml = map(iframeData, (iframe) => {
|
| 164 |
+
iframe = escape(iframe)
|
| 165 |
+
return `<li><a href="${iframe}" target="_blank" class="${c(
|
| 166 |
+
'iframe-link'
|
| 167 |
+
)}">${iframe}</a></li>`
|
| 168 |
+
}).join('')
|
| 169 |
+
}
|
| 170 |
+
const iframeHtml = `<h2 class="${c('title')}">
|
| 171 |
+
Iframe
|
| 172 |
+
<div class="${c('btn refresh-iframe')}">
|
| 173 |
+
<span class="${c('icon-refresh')}"></span>
|
| 174 |
+
</div>
|
| 175 |
+
</h2>
|
| 176 |
+
<ul class="${c('link-list')}">
|
| 177 |
+
${iframeDataHtml}
|
| 178 |
+
</ul>`
|
| 179 |
+
|
| 180 |
+
this._$iframe.html(iframeHtml)
|
| 181 |
+
|
| 182 |
+
return this
|
| 183 |
+
}
|
| 184 |
+
refreshLocalStorage() {
|
| 185 |
+
this._localStorage.refresh()
|
| 186 |
+
|
| 187 |
+
return this
|
| 188 |
+
}
|
| 189 |
+
refreshSessionStorage() {
|
| 190 |
+
this._sessionStorage.refresh()
|
| 191 |
+
|
| 192 |
+
return this
|
| 193 |
+
}
|
| 194 |
+
refreshCookie() {
|
| 195 |
+
this._cookie.refresh()
|
| 196 |
+
|
| 197 |
+
return this
|
| 198 |
+
}
|
| 199 |
+
refreshImage() {
|
| 200 |
+
let imageData = []
|
| 201 |
+
|
| 202 |
+
const performance = (this._performance =
|
| 203 |
+
window.webkitPerformance || window.performance)
|
| 204 |
+
if (performance && performance.getEntries) {
|
| 205 |
+
const entries = this._performance.getEntries()
|
| 206 |
+
entries.forEach((entry) => {
|
| 207 |
+
if (entry.initiatorType === 'img' || isImg(entry.name)) {
|
| 208 |
+
if (contain(entry.name, 'exclude=true')) {
|
| 209 |
+
return
|
| 210 |
+
}
|
| 211 |
+
imageData.push(entry.name)
|
| 212 |
+
}
|
| 213 |
+
})
|
| 214 |
+
} else {
|
| 215 |
+
$('img').each(function () {
|
| 216 |
+
const $this = $(this)
|
| 217 |
+
const src = $this.attr('src')
|
| 218 |
+
|
| 219 |
+
if ($this.data('exclude') === 'true') {
|
| 220 |
+
return
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
imageData.push(src)
|
| 224 |
+
})
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
imageData = unique(imageData)
|
| 228 |
+
imageData.sort()
|
| 229 |
+
|
| 230 |
+
const imageState = getState('image', imageData.length)
|
| 231 |
+
let imageDataHtml = '<li>Empty</li>'
|
| 232 |
+
if (!isEmpty(imageData)) {
|
| 233 |
+
// prettier-ignore
|
| 234 |
+
imageDataHtml = map(imageData, (image) => {
|
| 235 |
+
return `<li class="${c('image')}">
|
| 236 |
+
<img src="${escape(image)}" data-exclude="true" class="${c('img-link')}"/>
|
| 237 |
+
</li>`
|
| 238 |
+
}).join('')
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
const imageHtml = `<h2 class="${c('title')}">
|
| 242 |
+
Image
|
| 243 |
+
<div class="${c('btn refresh-image')}">
|
| 244 |
+
<span class="${c('icon-refresh')}"></span>
|
| 245 |
+
</div>
|
| 246 |
+
</h2>
|
| 247 |
+
<ul class="${c('image-list')}">
|
| 248 |
+
${imageDataHtml}
|
| 249 |
+
</ul>`
|
| 250 |
+
|
| 251 |
+
const $image = this._$image
|
| 252 |
+
setState($image, imageState)
|
| 253 |
+
$image.html(imageHtml)
|
| 254 |
+
|
| 255 |
+
return this
|
| 256 |
+
}
|
| 257 |
+
show() {
|
| 258 |
+
super.show()
|
| 259 |
+
if (this._observeElement) this._enableObserver()
|
| 260 |
+
|
| 261 |
+
return this.refresh()
|
| 262 |
+
}
|
| 263 |
+
hide() {
|
| 264 |
+
this._disableObserver()
|
| 265 |
+
|
| 266 |
+
return super.hide()
|
| 267 |
+
}
|
| 268 |
+
_initTpl() {
|
| 269 |
+
const $el = this._$el
|
| 270 |
+
$el.html(
|
| 271 |
+
c(`<div class="section local-storage"></div>
|
| 272 |
+
<div class="section session-storage"></div>
|
| 273 |
+
<div class="section cookie"></div>
|
| 274 |
+
<div class="section script"></div>
|
| 275 |
+
<div class="section stylesheet"></div>
|
| 276 |
+
<div class="section iframe"></div>
|
| 277 |
+
<div class="section image"></div>`)
|
| 278 |
+
)
|
| 279 |
+
this._$localStorage = $el.find(c('.local-storage'))
|
| 280 |
+
this._$sessionStorage = $el.find(c('.session-storage'))
|
| 281 |
+
this._$cookie = $el.find(c('.cookie'))
|
| 282 |
+
this._$script = $el.find(c('.script'))
|
| 283 |
+
this._$stylesheet = $el.find(c('.stylesheet'))
|
| 284 |
+
this._$iframe = $el.find(c('.iframe'))
|
| 285 |
+
this._$image = $el.find(c('.image'))
|
| 286 |
+
}
|
| 287 |
+
_bindEvent() {
|
| 288 |
+
const $el = this._$el
|
| 289 |
+
const container = this._container
|
| 290 |
+
|
| 291 |
+
$el
|
| 292 |
+
.on('click', '.eruda-refresh-script', () => {
|
| 293 |
+
container.notify('Refreshed', { icon: 'success' })
|
| 294 |
+
this.refreshScript()
|
| 295 |
+
})
|
| 296 |
+
.on('click', '.eruda-refresh-stylesheet', () => {
|
| 297 |
+
container.notify('Refreshed', { icon: 'success' })
|
| 298 |
+
this.refreshStylesheet()
|
| 299 |
+
})
|
| 300 |
+
.on('click', '.eruda-refresh-iframe', () => {
|
| 301 |
+
container.notify('Refreshed', { icon: 'success' })
|
| 302 |
+
this.refreshIframe()
|
| 303 |
+
})
|
| 304 |
+
.on('click', '.eruda-refresh-image', () => {
|
| 305 |
+
container.notify('Refreshed', { icon: 'success' })
|
| 306 |
+
this.refreshImage()
|
| 307 |
+
})
|
| 308 |
+
.on('click', '.eruda-img-link', function () {
|
| 309 |
+
const src = $(this).attr('src')
|
| 310 |
+
|
| 311 |
+
showSources('img', src)
|
| 312 |
+
})
|
| 313 |
+
.on('click', '.eruda-css-link', linkFactory('css'))
|
| 314 |
+
.on('click', '.eruda-js-link', linkFactory('js'))
|
| 315 |
+
.on('click', '.eruda-iframe-link', linkFactory('iframe'))
|
| 316 |
+
|
| 317 |
+
function showSources(type, data) {
|
| 318 |
+
const sources = container.get('sources')
|
| 319 |
+
if (!sources) return
|
| 320 |
+
|
| 321 |
+
sources.set(type, data)
|
| 322 |
+
|
| 323 |
+
container.showTool('sources')
|
| 324 |
+
|
| 325 |
+
return true
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
function linkFactory(type) {
|
| 329 |
+
return function (e) {
|
| 330 |
+
if (!container.get('sources')) return
|
| 331 |
+
e.preventDefault()
|
| 332 |
+
|
| 333 |
+
const url = $(this).attr('href')
|
| 334 |
+
|
| 335 |
+
if (type === 'iframe' || !sameOrigin(location.href, url)) {
|
| 336 |
+
showSources('iframe', url)
|
| 337 |
+
} else {
|
| 338 |
+
ajax({
|
| 339 |
+
url,
|
| 340 |
+
success: (data) => {
|
| 341 |
+
showSources(type, data)
|
| 342 |
+
},
|
| 343 |
+
dataType: 'raw',
|
| 344 |
+
})
|
| 345 |
+
}
|
| 346 |
+
}
|
| 347 |
+
}
|
| 348 |
+
}
|
| 349 |
+
_rmCfg() {
|
| 350 |
+
const cfg = this.config
|
| 351 |
+
|
| 352 |
+
const settings = this._container.get('settings')
|
| 353 |
+
|
| 354 |
+
if (!settings) return
|
| 355 |
+
|
| 356 |
+
settings
|
| 357 |
+
.remove(cfg, 'hideErudaSetting')
|
| 358 |
+
.remove(cfg, 'observeElement')
|
| 359 |
+
.remove('Resources')
|
| 360 |
+
}
|
| 361 |
+
_initCfg() {
|
| 362 |
+
const cfg = (this.config = Settings.createCfg('resources', {
|
| 363 |
+
hideErudaSetting: true,
|
| 364 |
+
observeElement: true,
|
| 365 |
+
}))
|
| 366 |
+
|
| 367 |
+
if (cfg.get('hideErudaSetting')) this._hideErudaSetting = true
|
| 368 |
+
if (!cfg.get('observeElement')) this._observeElement = false
|
| 369 |
+
|
| 370 |
+
cfg.on('change', (key, val) => {
|
| 371 |
+
switch (key) {
|
| 372 |
+
case 'hideErudaSetting':
|
| 373 |
+
this._hideErudaSetting = val
|
| 374 |
+
return
|
| 375 |
+
case 'observeElement':
|
| 376 |
+
this._observeElement = val
|
| 377 |
+
return val ? this._enableObserver() : this._disableObserver()
|
| 378 |
+
}
|
| 379 |
+
})
|
| 380 |
+
|
| 381 |
+
const settings = this._container.get('settings')
|
| 382 |
+
settings
|
| 383 |
+
.text('Resources')
|
| 384 |
+
.switch(cfg, 'hideErudaSetting', 'Hide Eruda Setting')
|
| 385 |
+
.switch(cfg, 'observeElement', 'Auto Refresh Elements')
|
| 386 |
+
.separator()
|
| 387 |
+
}
|
| 388 |
+
_initObserver() {
|
| 389 |
+
this._observer = new MutationObserver((mutations) => {
|
| 390 |
+
each(mutations, (mutation) => {
|
| 391 |
+
this._handleMutation(mutation)
|
| 392 |
+
})
|
| 393 |
+
})
|
| 394 |
+
}
|
| 395 |
+
_handleMutation(mutation) {
|
| 396 |
+
if (isErudaEl(mutation.target)) return
|
| 397 |
+
|
| 398 |
+
const checkEl = (el) => {
|
| 399 |
+
const tagName = getLowerCaseTagName(el)
|
| 400 |
+
switch (tagName) {
|
| 401 |
+
case 'script':
|
| 402 |
+
this.refreshScript()
|
| 403 |
+
break
|
| 404 |
+
case 'img':
|
| 405 |
+
this.refreshImage()
|
| 406 |
+
break
|
| 407 |
+
case 'link':
|
| 408 |
+
this.refreshStylesheet()
|
| 409 |
+
break
|
| 410 |
+
}
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
if (mutation.type === 'attributes') {
|
| 414 |
+
checkEl(mutation.target)
|
| 415 |
+
} else if (mutation.type === 'childList') {
|
| 416 |
+
checkEl(mutation.target)
|
| 417 |
+
let nodes = toArr(mutation.addedNodes)
|
| 418 |
+
nodes = concat(nodes, toArr(mutation.removedNodes))
|
| 419 |
+
|
| 420 |
+
for (const node of nodes) {
|
| 421 |
+
checkEl(node)
|
| 422 |
+
}
|
| 423 |
+
}
|
| 424 |
+
}
|
| 425 |
+
_enableObserver() {
|
| 426 |
+
this._observer.observe(document.documentElement, {
|
| 427 |
+
attributes: true,
|
| 428 |
+
childList: true,
|
| 429 |
+
subtree: true,
|
| 430 |
+
})
|
| 431 |
+
}
|
| 432 |
+
_disableObserver() {
|
| 433 |
+
this._observer.disconnect()
|
| 434 |
+
}
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
function getLowerCaseTagName(el) {
|
| 438 |
+
if (!el.tagName) return ''
|
| 439 |
+
return el.tagName.toLowerCase()
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
const regImg = /\.(jpeg|jpg|gif|png)$/
|
| 443 |
+
|
| 444 |
+
const isImg = (url) => regImg.test(url)
|
src/Resources/Resources.scss
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@use '../style/variable' as *;
|
| 2 |
+
@use '../style/mixin' as *;
|
| 3 |
+
|
| 4 |
+
#resources {
|
| 5 |
+
@include overflow-auto(y);
|
| 6 |
+
padding: 10px;
|
| 7 |
+
font-size: 14px;
|
| 8 |
+
.section {
|
| 9 |
+
margin-bottom: 10px;
|
| 10 |
+
overflow: hidden;
|
| 11 |
+
border: 1px solid var(--border);
|
| 12 |
+
&.warn {
|
| 13 |
+
border: 1px solid var(--console-warn-border);
|
| 14 |
+
.title {
|
| 15 |
+
background: var(--console-warn-background);
|
| 16 |
+
color: var(--console-warn-foreground);
|
| 17 |
+
}
|
| 18 |
+
}
|
| 19 |
+
&.danger {
|
| 20 |
+
border: 1px solid var(--console-error-border);
|
| 21 |
+
.title {
|
| 22 |
+
background: var(--console-error-background);
|
| 23 |
+
color: var(--console-error-foreground);
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
&.local-storage,
|
| 27 |
+
&.session-storage,
|
| 28 |
+
&.cookie {
|
| 29 |
+
border: none;
|
| 30 |
+
.title {
|
| 31 |
+
border: 1px solid var(--border);
|
| 32 |
+
border-bottom: none;
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
}
|
| 36 |
+
.title {
|
| 37 |
+
padding: $padding;
|
| 38 |
+
line-height: 18px;
|
| 39 |
+
color: var(--primary);
|
| 40 |
+
background: var(--darker-background);
|
| 41 |
+
@include right-btn();
|
| 42 |
+
}
|
| 43 |
+
.link-list {
|
| 44 |
+
font-size: $font-size-s;
|
| 45 |
+
color: var(--foreground);
|
| 46 |
+
li {
|
| 47 |
+
padding: 10px;
|
| 48 |
+
word-break: break-all;
|
| 49 |
+
a {
|
| 50 |
+
color: var(--link-color) !important;
|
| 51 |
+
}
|
| 52 |
+
}
|
| 53 |
+
}
|
| 54 |
+
.image-list {
|
| 55 |
+
color: var(--foreground);
|
| 56 |
+
font-size: $font-size-s;
|
| 57 |
+
display: flex;
|
| 58 |
+
flex-wrap: wrap;
|
| 59 |
+
padding-left: $padding;
|
| 60 |
+
padding-top: $padding;
|
| 61 |
+
&::after {
|
| 62 |
+
content: '';
|
| 63 |
+
flex-grow: 1000;
|
| 64 |
+
}
|
| 65 |
+
li {
|
| 66 |
+
flex-grow: 1;
|
| 67 |
+
cursor: pointer;
|
| 68 |
+
overflow-y: hidden;
|
| 69 |
+
margin-right: $padding;
|
| 70 |
+
margin-bottom: $padding;
|
| 71 |
+
border: 1px solid var(--border);
|
| 72 |
+
&.image {
|
| 73 |
+
height: 100px;
|
| 74 |
+
font-size: 0;
|
| 75 |
+
}
|
| 76 |
+
img {
|
| 77 |
+
height: 100px;
|
| 78 |
+
min-width: 100%;
|
| 79 |
+
object-fit: cover;
|
| 80 |
+
}
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
.safe-area #resources {
|
| 86 |
+
@include safe-area(padding-bottom, 10px);
|
| 87 |
+
}
|
src/Resources/Storage.js
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import each from 'licia/each'
|
| 2 |
+
import isStr from 'licia/isStr'
|
| 3 |
+
import startWith from 'licia/startWith'
|
| 4 |
+
import truncate from 'licia/truncate'
|
| 5 |
+
import LunaModal from 'luna-modal'
|
| 6 |
+
import LunaDataGrid from 'luna-data-grid'
|
| 7 |
+
import isNull from 'licia/isNull'
|
| 8 |
+
import trim from 'licia/trim'
|
| 9 |
+
import copy from 'licia/copy'
|
| 10 |
+
import emitter from '../lib/emitter'
|
| 11 |
+
import { safeStorage, classPrefix as c } from '../lib/util'
|
| 12 |
+
|
| 13 |
+
export default class Storage {
|
| 14 |
+
constructor($container, devtools, resources, type) {
|
| 15 |
+
this._type = type
|
| 16 |
+
this._$container = $container
|
| 17 |
+
this._devtools = devtools
|
| 18 |
+
this._resources = resources
|
| 19 |
+
this._selectedItem = null
|
| 20 |
+
this._storeData = []
|
| 21 |
+
|
| 22 |
+
this._initTpl()
|
| 23 |
+
this._dataGrid = new LunaDataGrid(this._$dataGrid.get(0), {
|
| 24 |
+
columns: [
|
| 25 |
+
{
|
| 26 |
+
id: 'key',
|
| 27 |
+
title: 'Key',
|
| 28 |
+
weight: 30,
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
id: 'value',
|
| 32 |
+
title: 'Value',
|
| 33 |
+
weight: 90,
|
| 34 |
+
},
|
| 35 |
+
],
|
| 36 |
+
minHeight: 60,
|
| 37 |
+
maxHeight: 223,
|
| 38 |
+
})
|
| 39 |
+
|
| 40 |
+
this._bindEvent()
|
| 41 |
+
}
|
| 42 |
+
destroy() {
|
| 43 |
+
emitter.off(emitter.SCALE, this._updateGridHeight)
|
| 44 |
+
}
|
| 45 |
+
refresh() {
|
| 46 |
+
const dataGrid = this._dataGrid
|
| 47 |
+
|
| 48 |
+
this._refreshStorage()
|
| 49 |
+
dataGrid.clear()
|
| 50 |
+
|
| 51 |
+
each(this._storeData, ({ key, val }) => {
|
| 52 |
+
dataGrid.append(
|
| 53 |
+
{
|
| 54 |
+
key,
|
| 55 |
+
value: val,
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
selectable: true,
|
| 59 |
+
}
|
| 60 |
+
)
|
| 61 |
+
})
|
| 62 |
+
}
|
| 63 |
+
_refreshStorage() {
|
| 64 |
+
const resources = this._resources
|
| 65 |
+
|
| 66 |
+
let store = safeStorage(this._type, false)
|
| 67 |
+
|
| 68 |
+
if (!store) return
|
| 69 |
+
|
| 70 |
+
const storeData = []
|
| 71 |
+
|
| 72 |
+
// Mobile safari is not able to loop through localStorage directly.
|
| 73 |
+
store = JSON.parse(JSON.stringify(store))
|
| 74 |
+
|
| 75 |
+
each(store, (val, key) => {
|
| 76 |
+
// According to issue 20, not all values are guaranteed to be string.
|
| 77 |
+
if (!isStr(val)) return
|
| 78 |
+
|
| 79 |
+
if (resources.config.get('hideErudaSetting')) {
|
| 80 |
+
if (startWith(key, 'eruda') || key === 'active-eruda') return
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
storeData.push({
|
| 84 |
+
key: key,
|
| 85 |
+
val: truncate(val, 200),
|
| 86 |
+
})
|
| 87 |
+
})
|
| 88 |
+
|
| 89 |
+
this._storeData = storeData
|
| 90 |
+
}
|
| 91 |
+
_updateButtons() {
|
| 92 |
+
const $container = this._$container
|
| 93 |
+
const $showDetail = $container.find(c('.show-detail'))
|
| 94 |
+
const $deleteStorage = $container.find(c('.delete-storage'))
|
| 95 |
+
const $copyStorage = $container.find(c('.copy-storage'))
|
| 96 |
+
const btnDisabled = c('btn-disabled')
|
| 97 |
+
|
| 98 |
+
$showDetail.addClass(btnDisabled)
|
| 99 |
+
$deleteStorage.addClass(btnDisabled)
|
| 100 |
+
$copyStorage.addClass(btnDisabled)
|
| 101 |
+
|
| 102 |
+
if (this._selectedItem) {
|
| 103 |
+
$showDetail.rmClass(btnDisabled)
|
| 104 |
+
$deleteStorage.rmClass(btnDisabled)
|
| 105 |
+
$copyStorage.rmClass(btnDisabled)
|
| 106 |
+
}
|
| 107 |
+
}
|
| 108 |
+
_initTpl() {
|
| 109 |
+
const $container = this._$container
|
| 110 |
+
const type = this._type
|
| 111 |
+
|
| 112 |
+
$container.html(
|
| 113 |
+
c(`<h2 class="title">
|
| 114 |
+
${type === 'local' ? 'Local' : 'Session'} Storage
|
| 115 |
+
<div class="btn refresh-storage">
|
| 116 |
+
<span class="icon icon-refresh"></span>
|
| 117 |
+
</div>
|
| 118 |
+
<div class="btn show-detail btn-disabled">
|
| 119 |
+
<span class="icon icon-eye"></span>
|
| 120 |
+
</div>
|
| 121 |
+
<div class="btn copy-storage btn-disabled">
|
| 122 |
+
<span class="icon icon-copy"></span>
|
| 123 |
+
</div>
|
| 124 |
+
<div class="btn delete-storage btn-disabled">
|
| 125 |
+
<span class="icon icon-delete"></span>
|
| 126 |
+
</div>
|
| 127 |
+
<div class="btn clear-storage">
|
| 128 |
+
<span class="icon icon-clear"></span>
|
| 129 |
+
</div>
|
| 130 |
+
<div class="btn filter">
|
| 131 |
+
<span class="icon icon-filter"></span>
|
| 132 |
+
</div>
|
| 133 |
+
<div class="btn filter-text"></div>
|
| 134 |
+
</h2>
|
| 135 |
+
<div class="data-grid"></div>`)
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
this._$dataGrid = $container.find(c('.data-grid'))
|
| 139 |
+
this._$filterText = $container.find(c('.filter-text'))
|
| 140 |
+
}
|
| 141 |
+
_getVal(key) {
|
| 142 |
+
return this._type === 'local'
|
| 143 |
+
? localStorage.getItem(key)
|
| 144 |
+
: sessionStorage.getItem(key)
|
| 145 |
+
}
|
| 146 |
+
_updateGridHeight = (scale) => {
|
| 147 |
+
this._dataGrid.setOption({
|
| 148 |
+
minHeight: 60 * scale,
|
| 149 |
+
maxHeight: 223 * scale,
|
| 150 |
+
})
|
| 151 |
+
}
|
| 152 |
+
_bindEvent() {
|
| 153 |
+
const type = this._type
|
| 154 |
+
const devtools = this._devtools
|
| 155 |
+
|
| 156 |
+
this._$container
|
| 157 |
+
.on('click', c('.refresh-storage'), () => {
|
| 158 |
+
devtools.notify('Refreshed', { icon: 'success' })
|
| 159 |
+
this.refresh()
|
| 160 |
+
})
|
| 161 |
+
.on('click', c('.clear-storage'), () => {
|
| 162 |
+
each(this._storeData, (val) => {
|
| 163 |
+
if (type === 'local') {
|
| 164 |
+
localStorage.removeItem(val.key)
|
| 165 |
+
} else {
|
| 166 |
+
sessionStorage.removeItem(val.key)
|
| 167 |
+
}
|
| 168 |
+
})
|
| 169 |
+
this.refresh()
|
| 170 |
+
})
|
| 171 |
+
.on('click', c('.show-detail'), () => {
|
| 172 |
+
const key = this._selectedItem
|
| 173 |
+
const val = this._getVal(key)
|
| 174 |
+
|
| 175 |
+
try {
|
| 176 |
+
showSources('object', JSON.parse(val))
|
| 177 |
+
} catch {
|
| 178 |
+
showSources('raw', val)
|
| 179 |
+
}
|
| 180 |
+
})
|
| 181 |
+
.on('click', c('.copy-storage'), () => {
|
| 182 |
+
const key = this._selectedItem
|
| 183 |
+
copy(this._getVal(key))
|
| 184 |
+
devtools.notify('Copied', { icon: 'success' })
|
| 185 |
+
})
|
| 186 |
+
.on('click', c('.filter'), () => {
|
| 187 |
+
LunaModal.prompt('Filter').then((filter) => {
|
| 188 |
+
if (isNull(filter)) return
|
| 189 |
+
filter = trim(filter)
|
| 190 |
+
this._$filterText.text(filter)
|
| 191 |
+
this._dataGrid.setOption('filter', filter)
|
| 192 |
+
})
|
| 193 |
+
})
|
| 194 |
+
.on('click', c('.delete-storage'), () => {
|
| 195 |
+
const key = this._selectedItem
|
| 196 |
+
|
| 197 |
+
if (type === 'local') {
|
| 198 |
+
localStorage.removeItem(key)
|
| 199 |
+
} else {
|
| 200 |
+
sessionStorage.removeItem(key)
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
this.refresh()
|
| 204 |
+
})
|
| 205 |
+
|
| 206 |
+
function showSources(type, data) {
|
| 207 |
+
const sources = devtools.get('sources')
|
| 208 |
+
if (!sources) return
|
| 209 |
+
|
| 210 |
+
sources.set(type, data)
|
| 211 |
+
|
| 212 |
+
devtools.showTool('sources')
|
| 213 |
+
|
| 214 |
+
return true
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
this._dataGrid
|
| 218 |
+
.on('select', (node) => {
|
| 219 |
+
this._selectedItem = node.data.key
|
| 220 |
+
this._updateButtons()
|
| 221 |
+
})
|
| 222 |
+
.on('deselect', () => {
|
| 223 |
+
this._selectedItem = null
|
| 224 |
+
this._updateButtons()
|
| 225 |
+
})
|
| 226 |
+
|
| 227 |
+
emitter.on(emitter.SCALE, this._updateGridHeight)
|
| 228 |
+
}
|
| 229 |
+
}
|
src/Resources/util.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { classPrefix as c } from '../lib/util'
|
| 2 |
+
|
| 3 |
+
export function setState($el, state) {
|
| 4 |
+
$el
|
| 5 |
+
.rmClass(c('ok'))
|
| 6 |
+
.rmClass(c('danger'))
|
| 7 |
+
.rmClass(c('warn'))
|
| 8 |
+
.addClass(c(state))
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
export function getState(type, len) {
|
| 12 |
+
if (len === 0) return ''
|
| 13 |
+
|
| 14 |
+
let warn = 0
|
| 15 |
+
let danger = 0
|
| 16 |
+
|
| 17 |
+
switch (type) {
|
| 18 |
+
case 'cookie':
|
| 19 |
+
warn = 30
|
| 20 |
+
danger = 60
|
| 21 |
+
break
|
| 22 |
+
case 'script':
|
| 23 |
+
warn = 5
|
| 24 |
+
danger = 10
|
| 25 |
+
break
|
| 26 |
+
case 'stylesheet':
|
| 27 |
+
warn = 4
|
| 28 |
+
danger = 8
|
| 29 |
+
break
|
| 30 |
+
case 'image':
|
| 31 |
+
warn = 50
|
| 32 |
+
danger = 100
|
| 33 |
+
break
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
if (len >= danger) return 'danger'
|
| 37 |
+
if (len >= warn) return 'warn'
|
| 38 |
+
|
| 39 |
+
return 'ok'
|
| 40 |
+
}
|
src/Settings/Settings.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import Tool from '../DevTools/Tool'
|
| 2 |
+
import $ from 'licia/$'
|
| 3 |
+
import LocalStore from 'licia/LocalStore'
|
| 4 |
+
import uniqId from 'licia/uniqId'
|
| 5 |
+
import each from 'licia/each'
|
| 6 |
+
import filter from 'licia/filter'
|
| 7 |
+
import isStr from 'licia/isStr'
|
| 8 |
+
import contain from 'licia/contain'
|
| 9 |
+
import clone from 'licia/clone'
|
| 10 |
+
import evalCss from '../lib/evalCss'
|
| 11 |
+
import LunaSetting from 'luna-setting'
|
| 12 |
+
|
| 13 |
+
export default class Settings extends Tool {
|
| 14 |
+
constructor() {
|
| 15 |
+
super()
|
| 16 |
+
|
| 17 |
+
this._style = evalCss(require('./Settings.scss'))
|
| 18 |
+
|
| 19 |
+
this.name = 'settings'
|
| 20 |
+
this._settings = []
|
| 21 |
+
}
|
| 22 |
+
init($el) {
|
| 23 |
+
super.init($el)
|
| 24 |
+
|
| 25 |
+
this._setting = new LunaSetting($el.get(0))
|
| 26 |
+
|
| 27 |
+
this._bindEvent()
|
| 28 |
+
}
|
| 29 |
+
remove(config, key) {
|
| 30 |
+
if (isStr(config)) {
|
| 31 |
+
const self = this
|
| 32 |
+
this._$el.find('.luna-setting-item-title').each(function () {
|
| 33 |
+
const $this = $(this)
|
| 34 |
+
if ($this.text() === config) {
|
| 35 |
+
self._setting.remove(this.settingItem)
|
| 36 |
+
}
|
| 37 |
+
})
|
| 38 |
+
} else {
|
| 39 |
+
this._settings = filter(this._settings, (setting) => {
|
| 40 |
+
if (setting.config === config && setting.key === key) {
|
| 41 |
+
this._setting.remove(setting.item)
|
| 42 |
+
return false
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
return true
|
| 46 |
+
})
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
this._cleanSeparator()
|
| 50 |
+
|
| 51 |
+
return this
|
| 52 |
+
}
|
| 53 |
+
destroy() {
|
| 54 |
+
this._setting.destroy()
|
| 55 |
+
super.destroy()
|
| 56 |
+
|
| 57 |
+
evalCss.remove(this._style)
|
| 58 |
+
}
|
| 59 |
+
clear() {
|
| 60 |
+
this._settings = []
|
| 61 |
+
this._setting.clear()
|
| 62 |
+
}
|
| 63 |
+
switch(config, key, desc) {
|
| 64 |
+
const id = this._genId()
|
| 65 |
+
|
| 66 |
+
const item = this._setting.appendCheckbox(id, !!config.get(key), desc)
|
| 67 |
+
this._settings.push({ config, key, id, item })
|
| 68 |
+
|
| 69 |
+
return this
|
| 70 |
+
}
|
| 71 |
+
select(config, key, desc, selections) {
|
| 72 |
+
const id = this._genId()
|
| 73 |
+
|
| 74 |
+
const selectOptions = {}
|
| 75 |
+
each(selections, (selection) => (selectOptions[selection] = selection))
|
| 76 |
+
const item = this._setting.appendSelect(
|
| 77 |
+
id,
|
| 78 |
+
config.get(key),
|
| 79 |
+
'',
|
| 80 |
+
desc,
|
| 81 |
+
selectOptions
|
| 82 |
+
)
|
| 83 |
+
this._settings.push({ config, key, id, item })
|
| 84 |
+
|
| 85 |
+
return this
|
| 86 |
+
}
|
| 87 |
+
range(config, key, desc, { min = 0, max = 1, step = 0.1 }) {
|
| 88 |
+
const id = this._genId()
|
| 89 |
+
|
| 90 |
+
const item = this._setting.appendNumber(id, config.get(key), desc, {
|
| 91 |
+
max,
|
| 92 |
+
min,
|
| 93 |
+
step,
|
| 94 |
+
range: true,
|
| 95 |
+
})
|
| 96 |
+
this._settings.push({ config, key, min, max, step, id, item })
|
| 97 |
+
|
| 98 |
+
return this
|
| 99 |
+
}
|
| 100 |
+
button(text, handler) {
|
| 101 |
+
this._setting.appendButton(text, handler)
|
| 102 |
+
|
| 103 |
+
return this
|
| 104 |
+
}
|
| 105 |
+
separator() {
|
| 106 |
+
this._setting.appendSeparator()
|
| 107 |
+
|
| 108 |
+
return this
|
| 109 |
+
}
|
| 110 |
+
text(text) {
|
| 111 |
+
this._setting.appendTitle(text)
|
| 112 |
+
|
| 113 |
+
return this
|
| 114 |
+
}
|
| 115 |
+
// Merge adjacent separators
|
| 116 |
+
_cleanSeparator() {
|
| 117 |
+
const children = clone(this._$el.get(0).children)
|
| 118 |
+
|
| 119 |
+
function isSeparator(node) {
|
| 120 |
+
return contain(node.getAttribute('class'), 'luna-setting-item-separator')
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
for (let i = 0, len = children.length; i < len - 1; i++) {
|
| 124 |
+
if (isSeparator(children[i]) && isSeparator(children[i + 1])) {
|
| 125 |
+
$(children[i]).remove()
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
}
|
| 129 |
+
_genId() {
|
| 130 |
+
return uniqId('eruda-settings')
|
| 131 |
+
}
|
| 132 |
+
_getSetting(id) {
|
| 133 |
+
let ret
|
| 134 |
+
|
| 135 |
+
each(this._settings, (setting) => {
|
| 136 |
+
if (setting.id === id) ret = setting
|
| 137 |
+
})
|
| 138 |
+
|
| 139 |
+
return ret
|
| 140 |
+
}
|
| 141 |
+
_bindEvent() {
|
| 142 |
+
this._setting.on('change', (id, val) => {
|
| 143 |
+
const setting = this._getSetting(id)
|
| 144 |
+
setting.config.set(setting.key, val)
|
| 145 |
+
})
|
| 146 |
+
}
|
| 147 |
+
static createCfg(name, data) {
|
| 148 |
+
return new LocalStore('eruda-' + name, data)
|
| 149 |
+
}
|
| 150 |
+
}
|
src/Settings/Settings.scss
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@use '../style/variable' as *;
|
| 2 |
+
@use '../style/mixin' as *;
|
| 3 |
+
|
| 4 |
+
#settings {
|
| 5 |
+
@include overflow-auto(y);
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
.safe-area #settings {
|
| 9 |
+
@include safe-area(padding-bottom, 0px);
|
| 10 |
+
}
|
src/Snippets/Snippets.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import Tool from '../DevTools/Tool'
|
| 2 |
+
import defSnippets from './defSnippets'
|
| 3 |
+
import $ from 'licia/$'
|
| 4 |
+
import each from 'licia/each'
|
| 5 |
+
import escape from 'licia/escape'
|
| 6 |
+
import map from 'licia/map'
|
| 7 |
+
import remove from 'licia/remove'
|
| 8 |
+
import evalCss from '../lib/evalCss'
|
| 9 |
+
import { classPrefix as c } from '../lib/util'
|
| 10 |
+
|
| 11 |
+
export default class Snippets extends Tool {
|
| 12 |
+
constructor() {
|
| 13 |
+
super()
|
| 14 |
+
|
| 15 |
+
this._style = evalCss(require('./Snippets.scss'))
|
| 16 |
+
|
| 17 |
+
this.name = 'snippets'
|
| 18 |
+
|
| 19 |
+
this._snippets = []
|
| 20 |
+
}
|
| 21 |
+
init($el) {
|
| 22 |
+
super.init($el)
|
| 23 |
+
|
| 24 |
+
this._bindEvent()
|
| 25 |
+
this._addDefSnippets()
|
| 26 |
+
}
|
| 27 |
+
destroy() {
|
| 28 |
+
super.destroy()
|
| 29 |
+
|
| 30 |
+
evalCss.remove(this._style)
|
| 31 |
+
}
|
| 32 |
+
add(name, fn, desc) {
|
| 33 |
+
this._snippets.push({ name, fn, desc })
|
| 34 |
+
|
| 35 |
+
this._render()
|
| 36 |
+
|
| 37 |
+
return this
|
| 38 |
+
}
|
| 39 |
+
remove(name) {
|
| 40 |
+
remove(this._snippets, (snippet) => snippet.name === name)
|
| 41 |
+
|
| 42 |
+
this._render()
|
| 43 |
+
|
| 44 |
+
return this
|
| 45 |
+
}
|
| 46 |
+
run(name) {
|
| 47 |
+
const snippets = this._snippets
|
| 48 |
+
|
| 49 |
+
for (let i = 0, len = snippets.length; i < len; i++) {
|
| 50 |
+
if (snippets[i].name === name) this._run(i)
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
return this
|
| 54 |
+
}
|
| 55 |
+
clear() {
|
| 56 |
+
this._snippets = []
|
| 57 |
+
this._render()
|
| 58 |
+
|
| 59 |
+
return this
|
| 60 |
+
}
|
| 61 |
+
_bindEvent() {
|
| 62 |
+
const self = this
|
| 63 |
+
|
| 64 |
+
this._$el.on('click', '.eruda-run', function () {
|
| 65 |
+
const idx = $(this).data('idx')
|
| 66 |
+
|
| 67 |
+
self._run(idx)
|
| 68 |
+
})
|
| 69 |
+
}
|
| 70 |
+
_run(idx) {
|
| 71 |
+
this._snippets[idx].fn.call(null)
|
| 72 |
+
}
|
| 73 |
+
_addDefSnippets() {
|
| 74 |
+
each(defSnippets, (snippet) => {
|
| 75 |
+
this.add(snippet.name, snippet.fn, snippet.desc)
|
| 76 |
+
})
|
| 77 |
+
}
|
| 78 |
+
_render() {
|
| 79 |
+
const html = map(this._snippets, (snippet, idx) => {
|
| 80 |
+
return `<div class="${c('section run')}" data-idx="${idx}">
|
| 81 |
+
<h2 class="${c('name')}">${escape(snippet.name)}
|
| 82 |
+
<div class="${c('btn')}">
|
| 83 |
+
<span class="${c('icon-play')}"></span>
|
| 84 |
+
</div>
|
| 85 |
+
</h2>
|
| 86 |
+
<div class="${c('description')}">
|
| 87 |
+
${escape(snippet.desc)}
|
| 88 |
+
</div>
|
| 89 |
+
</div>`
|
| 90 |
+
}).join('')
|
| 91 |
+
|
| 92 |
+
this._renderHtml(html)
|
| 93 |
+
}
|
| 94 |
+
_renderHtml(html) {
|
| 95 |
+
if (html === this._lastHtml) return
|
| 96 |
+
this._lastHtml = html
|
| 97 |
+
this._$el.html(html)
|
| 98 |
+
}
|
| 99 |
+
}
|
src/Snippets/Snippets.scss
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@use '../style/variable' as *;
|
| 2 |
+
@use '../style/mixin' as *;
|
| 3 |
+
|
| 4 |
+
#snippets {
|
| 5 |
+
@include overflow-auto(y);
|
| 6 |
+
padding: $padding;
|
| 7 |
+
.section {
|
| 8 |
+
margin-bottom: 10px;
|
| 9 |
+
border: 1px solid var(--border);
|
| 10 |
+
overflow: hidden;
|
| 11 |
+
cursor: pointer;
|
| 12 |
+
&:active {
|
| 13 |
+
.name {
|
| 14 |
+
background: var(--highlight);
|
| 15 |
+
color: var(--select-foreground);
|
| 16 |
+
}
|
| 17 |
+
}
|
| 18 |
+
.name {
|
| 19 |
+
padding: $padding;
|
| 20 |
+
line-height: 18px;
|
| 21 |
+
color: var(--primary);
|
| 22 |
+
background: var(--darker-background);
|
| 23 |
+
transition: background-color $anim-duration;
|
| 24 |
+
.btn {
|
| 25 |
+
margin-left: 10px;
|
| 26 |
+
float: right;
|
| 27 |
+
text-align: center;
|
| 28 |
+
width: 18px;
|
| 29 |
+
height: 18px;
|
| 30 |
+
font-size: $font-size-s;
|
| 31 |
+
}
|
| 32 |
+
}
|
| 33 |
+
.description {
|
| 34 |
+
font-size: $font-size-s;
|
| 35 |
+
color: var(--foreground);
|
| 36 |
+
padding: $padding;
|
| 37 |
+
transition: background-color $anim-duration;
|
| 38 |
+
}
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
.safe-area #snippets {
|
| 43 |
+
@include safe-area(padding-bottom, 10px);
|
| 44 |
+
}
|
src/Snippets/defSnippets.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logger from '../lib/logger'
|
| 2 |
+
import emitter from '../lib/emitter'
|
| 3 |
+
import Url from 'licia/Url'
|
| 4 |
+
import now from 'licia/now'
|
| 5 |
+
import startWith from 'licia/startWith'
|
| 6 |
+
import $ from 'licia/$'
|
| 7 |
+
import upperFirst from 'licia/upperFirst'
|
| 8 |
+
import loadJs from 'licia/loadJs'
|
| 9 |
+
import trim from 'licia/trim'
|
| 10 |
+
import LunaModal from 'luna-modal'
|
| 11 |
+
import { isErudaEl } from '../lib/util'
|
| 12 |
+
import evalCss from '../lib/evalCss'
|
| 13 |
+
|
| 14 |
+
let style = null
|
| 15 |
+
|
| 16 |
+
export default [
|
| 17 |
+
{
|
| 18 |
+
name: 'Border All',
|
| 19 |
+
fn() {
|
| 20 |
+
if (style) {
|
| 21 |
+
evalCss.remove(style)
|
| 22 |
+
style = null
|
| 23 |
+
return
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
style = evalCss(
|
| 27 |
+
'* { outline: 2px dashed #707d8b; outline-offset: -3px; }',
|
| 28 |
+
document.head
|
| 29 |
+
)
|
| 30 |
+
},
|
| 31 |
+
desc: 'Add color borders to all elements',
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
name: 'Refresh Page',
|
| 35 |
+
fn() {
|
| 36 |
+
const url = new Url()
|
| 37 |
+
url.setQuery('timestamp', now())
|
| 38 |
+
|
| 39 |
+
window.location.replace(url.toString())
|
| 40 |
+
},
|
| 41 |
+
desc: 'Add timestamp to url and refresh',
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
name: 'Search Text',
|
| 45 |
+
fn() {
|
| 46 |
+
LunaModal.prompt('Enter the text').then((keyword) => {
|
| 47 |
+
if (!keyword || trim(keyword) === '') {
|
| 48 |
+
return
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
search(keyword)
|
| 52 |
+
})
|
| 53 |
+
},
|
| 54 |
+
desc: 'Highlight given text on page',
|
| 55 |
+
},
|
| 56 |
+
{
|
| 57 |
+
name: 'Edit Page',
|
| 58 |
+
fn() {
|
| 59 |
+
const body = document.body
|
| 60 |
+
|
| 61 |
+
body.contentEditable = body.contentEditable !== 'true'
|
| 62 |
+
},
|
| 63 |
+
desc: 'Toggle body contentEditable',
|
| 64 |
+
},
|
| 65 |
+
{
|
| 66 |
+
name: 'Fit Screen',
|
| 67 |
+
// https://achrafkassioui.com/birdview/
|
| 68 |
+
fn() {
|
| 69 |
+
const body = document.body
|
| 70 |
+
const html = document.documentElement
|
| 71 |
+
const $body = $(body)
|
| 72 |
+
if ($body.data('scaled')) {
|
| 73 |
+
window.scrollTo(0, +$body.data('scaled'))
|
| 74 |
+
$body.rmAttr('data-scaled')
|
| 75 |
+
$body.css('transform', 'none')
|
| 76 |
+
} else {
|
| 77 |
+
const documentHeight = Math.max(
|
| 78 |
+
body.scrollHeight,
|
| 79 |
+
body.offsetHeight,
|
| 80 |
+
html.clientHeight,
|
| 81 |
+
html.scrollHeight,
|
| 82 |
+
html.offsetHeight
|
| 83 |
+
)
|
| 84 |
+
const viewportHeight = Math.max(
|
| 85 |
+
document.documentElement.clientHeight,
|
| 86 |
+
window.innerHeight || 0
|
| 87 |
+
)
|
| 88 |
+
const scaleVal = viewportHeight / documentHeight
|
| 89 |
+
$body.css('transform', `scale(${scaleVal})`)
|
| 90 |
+
$body.data('scaled', window.scrollY)
|
| 91 |
+
window.scrollTo(0, documentHeight / 2 - viewportHeight / 2)
|
| 92 |
+
}
|
| 93 |
+
},
|
| 94 |
+
desc: 'Scale down the whole page to fit screen',
|
| 95 |
+
},
|
| 96 |
+
{
|
| 97 |
+
name: 'Load Vue Plugin',
|
| 98 |
+
fn() {
|
| 99 |
+
loadPlugin('vue')
|
| 100 |
+
},
|
| 101 |
+
desc: 'Vue devtools',
|
| 102 |
+
},
|
| 103 |
+
{
|
| 104 |
+
name: 'Load Monitor Plugin',
|
| 105 |
+
fn() {
|
| 106 |
+
loadPlugin('monitor')
|
| 107 |
+
},
|
| 108 |
+
desc: 'Display page fps, memory and dom nodes',
|
| 109 |
+
},
|
| 110 |
+
{
|
| 111 |
+
name: 'Load Features Plugin',
|
| 112 |
+
fn() {
|
| 113 |
+
loadPlugin('features')
|
| 114 |
+
},
|
| 115 |
+
desc: 'Browser feature detections',
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
name: 'Load Timing Plugin',
|
| 119 |
+
fn() {
|
| 120 |
+
loadPlugin('timing')
|
| 121 |
+
},
|
| 122 |
+
desc: 'Show performance and resource timing',
|
| 123 |
+
},
|
| 124 |
+
{
|
| 125 |
+
name: 'Load Code Plugin',
|
| 126 |
+
fn() {
|
| 127 |
+
loadPlugin('code')
|
| 128 |
+
},
|
| 129 |
+
desc: 'Edit and run JavaScript',
|
| 130 |
+
},
|
| 131 |
+
{
|
| 132 |
+
name: 'Load Benchmark Plugin',
|
| 133 |
+
fn() {
|
| 134 |
+
loadPlugin('benchmark')
|
| 135 |
+
},
|
| 136 |
+
desc: 'Run JavaScript benchmarks',
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
name: 'Load Geolocation Plugin',
|
| 140 |
+
fn() {
|
| 141 |
+
loadPlugin('geolocation')
|
| 142 |
+
},
|
| 143 |
+
desc: 'Test geolocation',
|
| 144 |
+
},
|
| 145 |
+
{
|
| 146 |
+
name: 'Load Orientation Plugin',
|
| 147 |
+
fn() {
|
| 148 |
+
loadPlugin('orientation')
|
| 149 |
+
},
|
| 150 |
+
desc: 'Test orientation api',
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
name: 'Load Touches Plugin',
|
| 154 |
+
fn() {
|
| 155 |
+
loadPlugin('touches')
|
| 156 |
+
},
|
| 157 |
+
desc: 'Visualize screen touches',
|
| 158 |
+
},
|
| 159 |
+
]
|
| 160 |
+
|
| 161 |
+
evalCss(require('./searchText.scss'), document.head)
|
| 162 |
+
|
| 163 |
+
function search(text) {
|
| 164 |
+
const root = document.body
|
| 165 |
+
const regText = new RegExp(text, 'ig')
|
| 166 |
+
|
| 167 |
+
traverse(root, (node) => {
|
| 168 |
+
const $node = $(node)
|
| 169 |
+
|
| 170 |
+
if (!$node.hasClass('eruda-search-highlight-block')) return
|
| 171 |
+
|
| 172 |
+
return document.createTextNode($node.text())
|
| 173 |
+
})
|
| 174 |
+
|
| 175 |
+
traverse(root, (node) => {
|
| 176 |
+
if (node.nodeType !== 3) return
|
| 177 |
+
|
| 178 |
+
let val = node.nodeValue
|
| 179 |
+
val = val.replace(
|
| 180 |
+
regText,
|
| 181 |
+
(match) => `<span class="eruda-keyword">${match}</span>`
|
| 182 |
+
)
|
| 183 |
+
if (val === node.nodeValue) return
|
| 184 |
+
|
| 185 |
+
const $ret = $(document.createElement('div'))
|
| 186 |
+
|
| 187 |
+
$ret.html(val)
|
| 188 |
+
$ret.addClass('eruda-search-highlight-block')
|
| 189 |
+
|
| 190 |
+
return $ret.get(0)
|
| 191 |
+
})
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
function traverse(root, processor) {
|
| 195 |
+
const childNodes = root.childNodes
|
| 196 |
+
|
| 197 |
+
if (isErudaEl(root)) return
|
| 198 |
+
|
| 199 |
+
for (let i = 0, len = childNodes.length; i < len; i++) {
|
| 200 |
+
const newNode = traverse(childNodes[i], processor)
|
| 201 |
+
if (newNode) root.replaceChild(newNode, childNodes[i])
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
return processor(root)
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
function loadPlugin(name) {
|
| 208 |
+
const globalName = 'eruda' + upperFirst(name)
|
| 209 |
+
if (window[globalName]) return
|
| 210 |
+
|
| 211 |
+
let protocol = location.protocol
|
| 212 |
+
if (!startWith(protocol, 'http')) protocol = 'http:'
|
| 213 |
+
|
| 214 |
+
loadJs(
|
| 215 |
+
`${protocol}//cdn.jsdelivr.net/npm/eruda-${name}@${pluginVersion[name]}`,
|
| 216 |
+
(isLoaded) => {
|
| 217 |
+
if (!isLoaded || !window[globalName])
|
| 218 |
+
return logger.error('Fail to load plugin ' + name)
|
| 219 |
+
|
| 220 |
+
emitter.emit(emitter.ADD, window[globalName])
|
| 221 |
+
emitter.emit(emitter.SHOW, name)
|
| 222 |
+
}
|
| 223 |
+
)
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
const pluginVersion = {
|
| 227 |
+
monitor: '1.1.1',
|
| 228 |
+
features: '2.1.0',
|
| 229 |
+
timing: '2.0.1',
|
| 230 |
+
code: '2.2.0',
|
| 231 |
+
benchmark: '2.0.1',
|
| 232 |
+
geolocation: '2.1.0',
|
| 233 |
+
orientation: '2.1.1',
|
| 234 |
+
touches: '2.1.0',
|
| 235 |
+
vue: '1.1.1',
|
| 236 |
+
}
|
src/Snippets/searchText.scss
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@use '../style/variable' as *;
|
| 2 |
+
|
| 3 |
+
.search-highlight-block {
|
| 4 |
+
display: inline;
|
| 5 |
+
.keyword {
|
| 6 |
+
background: var(--console-warn-background);
|
| 7 |
+
color: var(--console-warn-foreground);
|
| 8 |
+
}
|
| 9 |
+
}
|
src/Sources/Sources.js
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import Tool from '../DevTools/Tool'
|
| 2 |
+
import LunaObjectViewer from 'luna-object-viewer'
|
| 3 |
+
import Settings from '../Settings/Settings'
|
| 4 |
+
import ajax from 'licia/ajax'
|
| 5 |
+
import each from 'licia/each'
|
| 6 |
+
import isStr from 'licia/isStr'
|
| 7 |
+
import escape from 'licia/escape'
|
| 8 |
+
import truncate from 'licia/truncate'
|
| 9 |
+
import replaceAll from 'licia/replaceAll'
|
| 10 |
+
import highlight from 'licia/highlight'
|
| 11 |
+
import LunaTextViewer from 'luna-text-viewer'
|
| 12 |
+
import evalCss from '../lib/evalCss'
|
| 13 |
+
import { classPrefix as c } from '../lib/util'
|
| 14 |
+
|
| 15 |
+
export default class Sources extends Tool {
|
| 16 |
+
constructor() {
|
| 17 |
+
super()
|
| 18 |
+
|
| 19 |
+
this._style = evalCss(require('./Sources.scss'))
|
| 20 |
+
|
| 21 |
+
this.name = 'sources'
|
| 22 |
+
this._showLineNum = true
|
| 23 |
+
}
|
| 24 |
+
init($el, container) {
|
| 25 |
+
super.init($el)
|
| 26 |
+
|
| 27 |
+
this._container = container
|
| 28 |
+
this._bindEvent()
|
| 29 |
+
this._initCfg()
|
| 30 |
+
}
|
| 31 |
+
destroy() {
|
| 32 |
+
super.destroy()
|
| 33 |
+
|
| 34 |
+
evalCss.remove(this._style)
|
| 35 |
+
this._rmCfg()
|
| 36 |
+
}
|
| 37 |
+
set(type, val) {
|
| 38 |
+
if (type === 'img') {
|
| 39 |
+
this._isFetchingData = true
|
| 40 |
+
|
| 41 |
+
const img = new Image()
|
| 42 |
+
|
| 43 |
+
const self = this
|
| 44 |
+
|
| 45 |
+
img.onload = function () {
|
| 46 |
+
self._isFetchingData = false
|
| 47 |
+
self._data = {
|
| 48 |
+
type: 'img',
|
| 49 |
+
val: {
|
| 50 |
+
width: this.width,
|
| 51 |
+
height: this.height,
|
| 52 |
+
src: val,
|
| 53 |
+
},
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
self._render()
|
| 57 |
+
}
|
| 58 |
+
img.onerror = function () {
|
| 59 |
+
self._isFetchingData = false
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
img.src = val
|
| 63 |
+
|
| 64 |
+
return
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
this._data = { type, val }
|
| 68 |
+
|
| 69 |
+
this._render()
|
| 70 |
+
|
| 71 |
+
return this
|
| 72 |
+
}
|
| 73 |
+
show() {
|
| 74 |
+
super.show()
|
| 75 |
+
|
| 76 |
+
if (!this._data && !this._isFetchingData) {
|
| 77 |
+
this._renderDef()
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
return this
|
| 81 |
+
}
|
| 82 |
+
_renderDef() {
|
| 83 |
+
if (this._html) {
|
| 84 |
+
this._data = {
|
| 85 |
+
type: 'html',
|
| 86 |
+
val: this._html,
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
return this._render()
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
if (this._isGettingHtml) return
|
| 93 |
+
this._isGettingHtml = true
|
| 94 |
+
|
| 95 |
+
ajax({
|
| 96 |
+
url: location.href,
|
| 97 |
+
success: (data) => (this._html = data),
|
| 98 |
+
error: () => (this._html = 'Sorry, unable to fetch source code:('),
|
| 99 |
+
complete: () => {
|
| 100 |
+
this._isGettingHtml = false
|
| 101 |
+
this._renderDef()
|
| 102 |
+
},
|
| 103 |
+
dataType: 'raw',
|
| 104 |
+
})
|
| 105 |
+
}
|
| 106 |
+
_bindEvent() {
|
| 107 |
+
this._container.on('showTool', (name, lastTool) => {
|
| 108 |
+
if (name !== this.name && lastTool.name === this.name) {
|
| 109 |
+
delete this._data
|
| 110 |
+
}
|
| 111 |
+
})
|
| 112 |
+
}
|
| 113 |
+
_rmCfg() {
|
| 114 |
+
const cfg = this.config
|
| 115 |
+
|
| 116 |
+
const settings = this._container.get('settings')
|
| 117 |
+
|
| 118 |
+
if (!settings) return
|
| 119 |
+
|
| 120 |
+
settings.remove(cfg, 'showLineNum').remove('Sources')
|
| 121 |
+
}
|
| 122 |
+
_initCfg() {
|
| 123 |
+
const cfg = (this.config = Settings.createCfg('sources', {
|
| 124 |
+
showLineNum: true,
|
| 125 |
+
}))
|
| 126 |
+
|
| 127 |
+
if (!cfg.get('showLineNum')) this._showLineNum = false
|
| 128 |
+
|
| 129 |
+
cfg.on('change', (key, val) => {
|
| 130 |
+
switch (key) {
|
| 131 |
+
case 'showLineNum':
|
| 132 |
+
this._showLineNum = val
|
| 133 |
+
return
|
| 134 |
+
}
|
| 135 |
+
})
|
| 136 |
+
|
| 137 |
+
const settings = this._container.get('settings')
|
| 138 |
+
settings
|
| 139 |
+
.text('Sources')
|
| 140 |
+
.switch(cfg, 'showLineNum', 'Show Line Numbers')
|
| 141 |
+
.separator()
|
| 142 |
+
}
|
| 143 |
+
_render() {
|
| 144 |
+
this._isInit = true
|
| 145 |
+
|
| 146 |
+
const data = this._data
|
| 147 |
+
|
| 148 |
+
switch (data.type) {
|
| 149 |
+
case 'html':
|
| 150 |
+
case 'js':
|
| 151 |
+
case 'css':
|
| 152 |
+
return this._renderCode()
|
| 153 |
+
case 'img':
|
| 154 |
+
return this._renderImg()
|
| 155 |
+
case 'object':
|
| 156 |
+
return this._renderObj()
|
| 157 |
+
case 'raw':
|
| 158 |
+
return this._renderRaw()
|
| 159 |
+
case 'iframe':
|
| 160 |
+
return this._renderIframe()
|
| 161 |
+
}
|
| 162 |
+
}
|
| 163 |
+
_renderImg() {
|
| 164 |
+
const { width, height, src } = this._data.val
|
| 165 |
+
|
| 166 |
+
this._renderHtml(`<div class="${c('image')}">
|
| 167 |
+
<div class="${c('breadcrumb')}">${escape(src)}</div>
|
| 168 |
+
<div class="${c('img-container')}" data-exclude="true">
|
| 169 |
+
<img src="${escape(src)}">
|
| 170 |
+
</div>
|
| 171 |
+
<div class="${c('img-info')}">${escape(width)} × ${escape(height)}</div>
|
| 172 |
+
</div>`)
|
| 173 |
+
}
|
| 174 |
+
_renderCode() {
|
| 175 |
+
const data = this._data
|
| 176 |
+
|
| 177 |
+
this._renderHtml(
|
| 178 |
+
`<div class="${c('code')}" data-type="${data.type}"></div>`,
|
| 179 |
+
false
|
| 180 |
+
)
|
| 181 |
+
|
| 182 |
+
let code = data.val
|
| 183 |
+
const len = data.val.length
|
| 184 |
+
|
| 185 |
+
if (len > MAX_RAW_LEN) {
|
| 186 |
+
code = truncate(code, MAX_RAW_LEN)
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
// If source code too big, don't process it.
|
| 190 |
+
if (len < MAX_BEAUTIFY_LEN) {
|
| 191 |
+
code = highlight(code, data.type, {
|
| 192 |
+
comment: '',
|
| 193 |
+
string: '',
|
| 194 |
+
number: '',
|
| 195 |
+
keyword: '',
|
| 196 |
+
operator: '',
|
| 197 |
+
})
|
| 198 |
+
each(['comment', 'string', 'number', 'keyword', 'operator'], (type) => {
|
| 199 |
+
code = replaceAll(code, `class="${type}"`, `class="${c(type)}"`)
|
| 200 |
+
})
|
| 201 |
+
} else {
|
| 202 |
+
code = escape(code)
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
const container = this._$el.find(c('.code')).get(0)
|
| 206 |
+
new LunaTextViewer(container, {
|
| 207 |
+
text: code,
|
| 208 |
+
escape: false,
|
| 209 |
+
wrapLongLines: true,
|
| 210 |
+
showLineNumbers: data.val.length < MAX_LINE_NUM_LEN && this._showLineNum,
|
| 211 |
+
})
|
| 212 |
+
}
|
| 213 |
+
_renderObj() {
|
| 214 |
+
// Using cache will keep binding events to the same elements.
|
| 215 |
+
this._renderHtml(`<ul class="${c('json')}"></ul>`, false)
|
| 216 |
+
|
| 217 |
+
let val = this._data.val
|
| 218 |
+
|
| 219 |
+
try {
|
| 220 |
+
if (isStr(val)) {
|
| 221 |
+
val = JSON.parse(val)
|
| 222 |
+
}
|
| 223 |
+
} catch {
|
| 224 |
+
// No op
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
const objViewer = new LunaObjectViewer(
|
| 228 |
+
this._$el.find('.eruda-json').get(0),
|
| 229 |
+
{
|
| 230 |
+
unenumerable: true,
|
| 231 |
+
accessGetter: true,
|
| 232 |
+
prototype: false,
|
| 233 |
+
}
|
| 234 |
+
)
|
| 235 |
+
objViewer.set(val)
|
| 236 |
+
}
|
| 237 |
+
_renderRaw() {
|
| 238 |
+
const data = this._data
|
| 239 |
+
|
| 240 |
+
this._renderHtml(`<div class="${c('raw-wrapper')}">
|
| 241 |
+
<div class="${c('raw')}"></div>
|
| 242 |
+
</div>`)
|
| 243 |
+
|
| 244 |
+
let val = data.val
|
| 245 |
+
const container = this._$el.find(c('.raw')).get(0)
|
| 246 |
+
if (val.length > MAX_RAW_LEN) {
|
| 247 |
+
val = truncate(val, MAX_RAW_LEN)
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
new LunaTextViewer(container, {
|
| 251 |
+
text: val,
|
| 252 |
+
wrapLongLines: true,
|
| 253 |
+
showLineNumbers: val.length < MAX_LINE_NUM_LEN && this._showLineNum,
|
| 254 |
+
})
|
| 255 |
+
}
|
| 256 |
+
_renderIframe() {
|
| 257 |
+
this._renderHtml(`<iframe src="${escape(this._data.val)}"></iframe>`)
|
| 258 |
+
}
|
| 259 |
+
_renderHtml(html, cache = true) {
|
| 260 |
+
if (cache && html === this._lastHtml) return
|
| 261 |
+
this._lastHtml = html
|
| 262 |
+
this._$el.html(html)
|
| 263 |
+
// Need setTimeout to make it work
|
| 264 |
+
setTimeout(() => (this._$el.get(0).scrollTop = 0), 0)
|
| 265 |
+
}
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
const MAX_BEAUTIFY_LEN = 30000
|
| 269 |
+
const MAX_LINE_NUM_LEN = 80000
|
| 270 |
+
const MAX_RAW_LEN = 100000
|
src/Sources/Sources.scss
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@use '../style/variable' as *;
|
| 2 |
+
@use '../style/mixin' as *;
|
| 3 |
+
|
| 4 |
+
#sources {
|
| 5 |
+
font-size: 0;
|
| 6 |
+
@include overflow-auto(y);
|
| 7 |
+
color: var(--foreground);
|
| 8 |
+
.code-wrapper,
|
| 9 |
+
.raw-wrapper {
|
| 10 |
+
@include overflow-auto(x);
|
| 11 |
+
width: 100%;
|
| 12 |
+
min-height: 100%;
|
| 13 |
+
}
|
| 14 |
+
.raw,
|
| 15 |
+
.code {
|
| 16 |
+
height: 100%;
|
| 17 |
+
.keyword {
|
| 18 |
+
color: var(--keyword-color);
|
| 19 |
+
}
|
| 20 |
+
.comment {
|
| 21 |
+
color: var(--comment-color);
|
| 22 |
+
}
|
| 23 |
+
.number {
|
| 24 |
+
color: var(--number-color);
|
| 25 |
+
}
|
| 26 |
+
.string {
|
| 27 |
+
color: var(--string-color);
|
| 28 |
+
}
|
| 29 |
+
.operator {
|
| 30 |
+
color: var(--operator-color);
|
| 31 |
+
}
|
| 32 |
+
&[data-type='html'] {
|
| 33 |
+
.keyword {
|
| 34 |
+
color: var(--tag-name-color);
|
| 35 |
+
}
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
.image {
|
| 39 |
+
font-size: $font-size-s;
|
| 40 |
+
.breadcrumb {
|
| 41 |
+
@include breadcrumb();
|
| 42 |
+
}
|
| 43 |
+
.img-container {
|
| 44 |
+
text-align: center;
|
| 45 |
+
img {
|
| 46 |
+
max-width: 100%;
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
.img-info {
|
| 50 |
+
text-align: center;
|
| 51 |
+
margin: 20px 0;
|
| 52 |
+
color: var(--foreground);
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
.json {
|
| 56 |
+
padding: 0 $padding;
|
| 57 |
+
* {
|
| 58 |
+
user-select: text;
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
iframe {
|
| 62 |
+
width: 100%;
|
| 63 |
+
height: 100%;
|
| 64 |
+
}
|
| 65 |
+
}
|
src/eruda.js
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import EntryBtn from './EntryBtn/EntryBtn'
|
| 2 |
+
import DevTools from './DevTools/DevTools'
|
| 3 |
+
import Tool from './DevTools/Tool'
|
| 4 |
+
import Console from './Console/Console'
|
| 5 |
+
import Network from './Network/Network'
|
| 6 |
+
import Elements from './Elements/Elements'
|
| 7 |
+
import Snippets from './Snippets/Snippets'
|
| 8 |
+
import Resources from './Resources/Resources'
|
| 9 |
+
import Info from './Info/Info'
|
| 10 |
+
import Sources from './Sources/Sources'
|
| 11 |
+
import Settings from './Settings/Settings'
|
| 12 |
+
import emitter from './lib/emitter'
|
| 13 |
+
import logger from './lib/logger'
|
| 14 |
+
import * as util from './lib/util'
|
| 15 |
+
import { isDarkTheme } from './lib/themes'
|
| 16 |
+
import themes from './lib/themes'
|
| 17 |
+
import isFn from 'licia/isFn'
|
| 18 |
+
import isNum from 'licia/isNum'
|
| 19 |
+
import isObj from 'licia/isObj'
|
| 20 |
+
import each from 'licia/each'
|
| 21 |
+
import isMobile from 'licia/isMobile'
|
| 22 |
+
import viewportScale from 'licia/viewportScale'
|
| 23 |
+
import detectBrowser from 'licia/detectBrowser'
|
| 24 |
+
import $ from 'licia/$'
|
| 25 |
+
import toArr from 'licia/toArr'
|
| 26 |
+
import upperFirst from 'licia/upperFirst'
|
| 27 |
+
import nextTick from 'licia/nextTick'
|
| 28 |
+
import isEqual from 'licia/isEqual'
|
| 29 |
+
import extend from 'licia/extend'
|
| 30 |
+
import evalCss from './lib/evalCss'
|
| 31 |
+
import chobitsu from './lib/chobitsu'
|
| 32 |
+
|
| 33 |
+
export default {
|
| 34 |
+
init({
|
| 35 |
+
container,
|
| 36 |
+
tool,
|
| 37 |
+
autoScale = true,
|
| 38 |
+
useShadowDom = true,
|
| 39 |
+
inline = false,
|
| 40 |
+
defaults = {},
|
| 41 |
+
} = {}) {
|
| 42 |
+
if (this._isInit) {
|
| 43 |
+
return
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
this._isInit = true
|
| 47 |
+
this._scale = 1
|
| 48 |
+
|
| 49 |
+
this._initContainer(container, useShadowDom)
|
| 50 |
+
this._initStyle()
|
| 51 |
+
this._initDevTools(defaults, inline)
|
| 52 |
+
this._initEntryBtn()
|
| 53 |
+
this._initSettings()
|
| 54 |
+
this._initTools(tool)
|
| 55 |
+
this._registerListener()
|
| 56 |
+
|
| 57 |
+
if (autoScale) {
|
| 58 |
+
this._autoScale()
|
| 59 |
+
}
|
| 60 |
+
if (inline) {
|
| 61 |
+
this._entryBtn.hide()
|
| 62 |
+
this._$el.addClass('eruda-inline')
|
| 63 |
+
this.show()
|
| 64 |
+
}
|
| 65 |
+
},
|
| 66 |
+
_isInit: false,
|
| 67 |
+
version: VERSION,
|
| 68 |
+
util: {
|
| 69 |
+
isErudaEl: util.isErudaEl,
|
| 70 |
+
evalCss,
|
| 71 |
+
isDarkTheme(theme) {
|
| 72 |
+
if (!theme) {
|
| 73 |
+
theme = this.getTheme()
|
| 74 |
+
}
|
| 75 |
+
return isDarkTheme(theme)
|
| 76 |
+
},
|
| 77 |
+
getTheme: () => {
|
| 78 |
+
const curTheme = evalCss.getCurTheme()
|
| 79 |
+
|
| 80 |
+
let result = 'Light'
|
| 81 |
+
each(themes, (theme, name) => {
|
| 82 |
+
if (isEqual(theme, curTheme)) {
|
| 83 |
+
result = name
|
| 84 |
+
}
|
| 85 |
+
})
|
| 86 |
+
|
| 87 |
+
return result
|
| 88 |
+
},
|
| 89 |
+
},
|
| 90 |
+
chobitsu,
|
| 91 |
+
Tool,
|
| 92 |
+
Console,
|
| 93 |
+
Elements,
|
| 94 |
+
Network,
|
| 95 |
+
Sources,
|
| 96 |
+
Resources,
|
| 97 |
+
Info,
|
| 98 |
+
Snippets,
|
| 99 |
+
Settings,
|
| 100 |
+
get(name) {
|
| 101 |
+
if (!this._checkInit()) return
|
| 102 |
+
|
| 103 |
+
if (name === 'entryBtn') return this._entryBtn
|
| 104 |
+
|
| 105 |
+
const devTools = this._devTools
|
| 106 |
+
|
| 107 |
+
return name ? devTools.get(name) : devTools
|
| 108 |
+
},
|
| 109 |
+
add(tool) {
|
| 110 |
+
if (!this._checkInit()) return
|
| 111 |
+
|
| 112 |
+
if (isFn(tool)) tool = tool(this)
|
| 113 |
+
|
| 114 |
+
this._devTools.add(tool)
|
| 115 |
+
|
| 116 |
+
return this
|
| 117 |
+
},
|
| 118 |
+
remove(name) {
|
| 119 |
+
this._devTools.remove(name)
|
| 120 |
+
|
| 121 |
+
return this
|
| 122 |
+
},
|
| 123 |
+
show(name) {
|
| 124 |
+
if (!this._checkInit()) return
|
| 125 |
+
|
| 126 |
+
const devTools = this._devTools
|
| 127 |
+
|
| 128 |
+
name ? devTools.showTool(name) : devTools.show()
|
| 129 |
+
|
| 130 |
+
return this
|
| 131 |
+
},
|
| 132 |
+
hide() {
|
| 133 |
+
if (!this._checkInit()) return
|
| 134 |
+
|
| 135 |
+
this._devTools.hide()
|
| 136 |
+
|
| 137 |
+
return this
|
| 138 |
+
},
|
| 139 |
+
destroy() {
|
| 140 |
+
this._devTools.destroy()
|
| 141 |
+
delete this._devTools
|
| 142 |
+
this._entryBtn.destroy()
|
| 143 |
+
delete this._entryBtn
|
| 144 |
+
this._unregisterListener()
|
| 145 |
+
$(this._container).remove()
|
| 146 |
+
evalCss.clear()
|
| 147 |
+
this._isInit = false
|
| 148 |
+
this._container = null
|
| 149 |
+
this._shadowRoot = null
|
| 150 |
+
},
|
| 151 |
+
scale(s) {
|
| 152 |
+
if (isNum(s)) {
|
| 153 |
+
this._scale = s
|
| 154 |
+
emitter.emit(emitter.SCALE, s)
|
| 155 |
+
return this
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
return this._scale
|
| 159 |
+
},
|
| 160 |
+
position(p) {
|
| 161 |
+
const entryBtn = this._entryBtn
|
| 162 |
+
|
| 163 |
+
if (isObj(p)) {
|
| 164 |
+
entryBtn.setPos(p)
|
| 165 |
+
return this
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
return entryBtn.getPos()
|
| 169 |
+
},
|
| 170 |
+
_autoScale() {
|
| 171 |
+
if (!isMobile()) return
|
| 172 |
+
|
| 173 |
+
this.scale(1 / viewportScale())
|
| 174 |
+
},
|
| 175 |
+
_registerListener() {
|
| 176 |
+
this._addListener = (...args) => this.add(...args)
|
| 177 |
+
this._showListener = (...args) => this.show(...args)
|
| 178 |
+
|
| 179 |
+
emitter.on(emitter.ADD, this._addListener)
|
| 180 |
+
emitter.on(emitter.SHOW, this._showListener)
|
| 181 |
+
emitter.on(emitter.SCALE, evalCss.setScale)
|
| 182 |
+
},
|
| 183 |
+
_unregisterListener() {
|
| 184 |
+
emitter.off(emitter.ADD, this._addListener)
|
| 185 |
+
emitter.off(emitter.SHOW, this._showListener)
|
| 186 |
+
emitter.off(emitter.SCALE, evalCss.setScale)
|
| 187 |
+
},
|
| 188 |
+
_checkInit() {
|
| 189 |
+
if (!this._isInit) logger.error('Please call "eruda.init()" first')
|
| 190 |
+
return this._isInit
|
| 191 |
+
},
|
| 192 |
+
_initContainer(container, useShadowDom) {
|
| 193 |
+
if (!container) {
|
| 194 |
+
container = document.createElement('div')
|
| 195 |
+
document.documentElement.appendChild(container)
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
container.id = 'eruda'
|
| 199 |
+
container.style.all = 'initial'
|
| 200 |
+
this._container = container
|
| 201 |
+
|
| 202 |
+
let shadowRoot
|
| 203 |
+
let el
|
| 204 |
+
if (useShadowDom) {
|
| 205 |
+
if (container.attachShadow) {
|
| 206 |
+
shadowRoot = container.attachShadow({ mode: 'open' })
|
| 207 |
+
} else if (container.createShadowRoot) {
|
| 208 |
+
shadowRoot = container.createShadowRoot()
|
| 209 |
+
}
|
| 210 |
+
if (shadowRoot) {
|
| 211 |
+
// font-face doesn't work inside shadow dom.
|
| 212 |
+
evalCss.container = document.head
|
| 213 |
+
evalCss(
|
| 214 |
+
require('./style/icon.css') +
|
| 215 |
+
require('luna-console/luna-console.css') +
|
| 216 |
+
require('luna-object-viewer/luna-object-viewer.css') +
|
| 217 |
+
require('luna-dom-viewer/luna-dom-viewer.css') +
|
| 218 |
+
require('luna-text-viewer/luna-text-viewer.css') +
|
| 219 |
+
require('luna-notification/luna-notification.css')
|
| 220 |
+
)
|
| 221 |
+
|
| 222 |
+
el = document.createElement('div')
|
| 223 |
+
shadowRoot.appendChild(el)
|
| 224 |
+
this._shadowRoot = shadowRoot
|
| 225 |
+
}
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
if (!this._shadowRoot) {
|
| 229 |
+
el = document.createElement('div')
|
| 230 |
+
container.appendChild(el)
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
extend(el, {
|
| 234 |
+
className: 'eruda-container __chobitsu-hide__',
|
| 235 |
+
contentEditable: false,
|
| 236 |
+
})
|
| 237 |
+
|
| 238 |
+
// http://stackoverflow.com/questions/3885018/active-pseudo-class-doesnt-work-in-mobile-safari
|
| 239 |
+
if (detectBrowser().name === 'ios') el.setAttribute('ontouchstart', '')
|
| 240 |
+
|
| 241 |
+
this._$el = $(el)
|
| 242 |
+
},
|
| 243 |
+
_initDevTools(defaults, inline) {
|
| 244 |
+
this._devTools = new DevTools(this._$el, {
|
| 245 |
+
defaults,
|
| 246 |
+
inline,
|
| 247 |
+
})
|
| 248 |
+
},
|
| 249 |
+
_initStyle() {
|
| 250 |
+
const className = 'eruda-style-container'
|
| 251 |
+
const $el = this._$el
|
| 252 |
+
|
| 253 |
+
if (this._shadowRoot) {
|
| 254 |
+
evalCss.container = this._shadowRoot
|
| 255 |
+
evalCss(':host { all: initial }')
|
| 256 |
+
} else {
|
| 257 |
+
$el.append(`<div class="${className}"></div>`)
|
| 258 |
+
evalCss.container = $el.find(`.${className}`).get(0)
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
evalCss(
|
| 262 |
+
require('./style/reset.scss') +
|
| 263 |
+
require('luna-object-viewer/luna-object-viewer.css') +
|
| 264 |
+
require('luna-console/luna-console.css') +
|
| 265 |
+
require('luna-notification/luna-notification.css') +
|
| 266 |
+
require('luna-data-grid/luna-data-grid.css') +
|
| 267 |
+
require('luna-dom-viewer/luna-dom-viewer.css') +
|
| 268 |
+
require('luna-modal/luna-modal.css') +
|
| 269 |
+
require('luna-tab/luna-tab.css') +
|
| 270 |
+
require('luna-text-viewer/luna-text-viewer.css') +
|
| 271 |
+
require('luna-setting/luna-setting.css') +
|
| 272 |
+
require('luna-box-model/luna-box-model.css') +
|
| 273 |
+
require('./style/style.scss') +
|
| 274 |
+
require('./style/icon.css')
|
| 275 |
+
)
|
| 276 |
+
},
|
| 277 |
+
_initEntryBtn() {
|
| 278 |
+
this._entryBtn = new EntryBtn(this._$el)
|
| 279 |
+
this._entryBtn.on('click', () => this._devTools.toggle())
|
| 280 |
+
},
|
| 281 |
+
_initSettings() {
|
| 282 |
+
const devTools = this._devTools
|
| 283 |
+
const settings = new Settings()
|
| 284 |
+
|
| 285 |
+
devTools.add(settings)
|
| 286 |
+
|
| 287 |
+
this._entryBtn.initCfg(settings)
|
| 288 |
+
devTools.initCfg(settings)
|
| 289 |
+
},
|
| 290 |
+
_initTools(
|
| 291 |
+
tool = [
|
| 292 |
+
'console',
|
| 293 |
+
'elements',
|
| 294 |
+
'network',
|
| 295 |
+
'resources',
|
| 296 |
+
'sources',
|
| 297 |
+
'info',
|
| 298 |
+
'snippets',
|
| 299 |
+
]
|
| 300 |
+
) {
|
| 301 |
+
tool = toArr(tool)
|
| 302 |
+
|
| 303 |
+
const devTools = this._devTools
|
| 304 |
+
|
| 305 |
+
tool.forEach((name) => {
|
| 306 |
+
const Tool = this[upperFirst(name)]
|
| 307 |
+
try {
|
| 308 |
+
if (Tool) devTools.add(new Tool())
|
| 309 |
+
} catch (e) {
|
| 310 |
+
// Use nextTick to make sure it is possible to be caught by console panel.
|
| 311 |
+
nextTick(() => {
|
| 312 |
+
logger.error(
|
| 313 |
+
`Something wrong when initializing tool ${name}:`,
|
| 314 |
+
e.message
|
| 315 |
+
)
|
| 316 |
+
})
|
| 317 |
+
}
|
| 318 |
+
})
|
| 319 |
+
|
| 320 |
+
devTools.showTool(tool[0] || 'settings')
|
| 321 |
+
},
|
| 322 |
+
}
|