Pārlūkot izejas kodu

FeJson版本合并到FeHelper;第一步,先合并文件夹

[email protected] 3 gadi atpakaļ
vecāks
revīzija
b988f91641
100 mainītis faili ar 11902 papildinājumiem un 8442 dzēšanām
  1. 0 638
      apps/_locales/zh_CN/messages.json
  2. 0 12
      apps/ajax-debugger/index.html
  3. 0 120
      apps/ajax-debugger/index.js
  4. 111 0
      apps/background/crx-download.js
  5. 0 22
      apps/background/detect.js
  6. 274 757
      apps/background/index.js
  7. 142 0
      apps/background/menu.js
  8. 0 132
      apps/background/network.js
  9. 0 132
      apps/code-beautify/automatic.js
  10. 2 2
      apps/code-beautify/beautify-css.js
  11. 1 1
      apps/code-beautify/beautify-html.js
  12. 0 0
      apps/code-beautify/beautify-vk.js
  13. 1 1
      apps/code-beautify/beautify.js
  14. 20 2
      apps/code-beautify/content-script.css
  15. 219 0
      apps/code-beautify/content-script.js
  16. 8 2
      apps/code-beautify/index.html
  17. 40 16
      apps/code-beautify/index.js
  18. 6 0
      apps/code-compress/htmlminifier.min.js
  19. 29 1
      apps/code-compress/index.css
  20. 35 9
      apps/code-compress/index.html
  21. 123 10
      apps/code-compress/index.js
  22. 3 3
      apps/code-compress/uglifyjs3.js
  23. 0 293
      apps/code-standards/css/fcp-css-analytic.js
  24. 0 648
      apps/code-standards/css/fcp-css.js
  25. 0 308
      apps/code-standards/fcp-fl.js
  26. 0 901
      apps/code-standards/fcp-main.js
  27. 0 294
      apps/code-standards/fcp-tabs.js
  28. 0 506
      apps/code-standards/html/fcp-html-analytic.js
  29. 0 211
      apps/code-standards/html/fcp-html-doctype.js
  30. 0 488
      apps/code-standards/html/fcp-html.js
  31. 0 348
      apps/code-standards/index.css
  32. 0 49
      apps/code-standards/index.js
  33. 0 324
      apps/code-standards/js/fcp-js-analytic.js
  34. 0 435
      apps/code-standards/js/fcp-js.js
  35. BIN
      apps/code-standards/pbar-ani.gif
  36. 561 0
      apps/color-picker/content-script.js
  37. 13 0
      apps/color-picker/index.html
  38. 0 570
      apps/color-picker/index.js
  39. 0 7
      apps/content-script/fehelper-only.js
  40. 0 96
      apps/content-script/index.js
  41. 3 2
      apps/crontab/index.css
  42. 1 0
      apps/crontab/index.html
  43. 0 0
      apps/crontab/index.js
  44. 69 0
      apps/devtools/file-tpl.js
  45. 279 0
      apps/devtools/index.css
  46. 162 0
      apps/devtools/index.html
  47. 809 0
      apps/devtools/index.js
  48. 791 0
      apps/dynamic/awesome.js
  49. 14 0
      apps/dynamic/index.html
  50. 82 0
      apps/dynamic/index.js
  51. 81 1
      apps/en-decode/endecode-lib.js
  52. 30 0
      apps/en-decode/he.js
  53. 41 0
      apps/en-decode/index.css
  54. 0 0
      apps/en-decode/index.html
  55. 42 10
      apps/en-decode/index.js
  56. 286 0
      apps/excel2json/CSVParser.js
  57. 538 0
      apps/excel2json/DataGridRenderer.js
  58. 190 0
      apps/excel2json/converter.js
  59. 143 0
      apps/excel2json/index.css
  60. 78 0
      apps/excel2json/index.html
  61. 104 0
      apps/excel2json/index.js
  62. 0 0
      apps/grid-ruler/content-script.css
  63. 28 23
      apps/grid-ruler/content-script.js
  64. 11 0
      apps/grid-ruler/index.html
  65. 41 0
      apps/hello-world/content-script.js
  66. 23 0
      apps/hello-world/fh-config.js
  67. 4 0
      apps/hello-world/index.css
  68. 14 0
      apps/hello-world/index.html
  69. 1 0
      apps/hello-world/index.js
  70. 201 0
      apps/html2markdown/demo-tpl.js
  71. 77 0
      apps/html2markdown/editor.css
  72. 146 0
      apps/html2markdown/index.css
  73. 45 45
      apps/html2markdown/index.html
  74. 193 237
      apps/html2markdown/index.js
  75. 1072 0
      apps/html2markdown/libs/marked.js
  76. 1671 0
      apps/html2markdown/libs/rawdeflate.js
  77. 753 0
      apps/html2markdown/libs/rawinflate.js
  78. 0 3
      apps/image-base64/index.css
  79. 84 84
      apps/image-base64/index.html
  80. 13 15
      apps/image-base64/index.js
  81. 137 0
      apps/index/fh-config.js
  82. 395 0
      apps/index/index.css
  83. 148 0
      apps/index/index.html
  84. 85 0
      apps/index/index.js
  85. 90 0
      apps/index/json-demo.json
  86. 14 14
      apps/json-diff/index.css
  87. 5 1
      apps/json-diff/index.html
  88. 14 5
      apps/json-diff/index.js
  89. 0 275
      apps/json-format/automatic.js
  90. 479 0
      apps/json-format/content-script.css
  91. 417 0
      apps/json-format/content-script.js
  92. 96 25
      apps/json-format/format-lib.js
  93. 70 98
      apps/json-format/index.css
  94. 40 29
      apps/json-format/index.html
  95. 93 47
      apps/json-format/index.js
  96. 2 2
      apps/json-format/json-abc.js
  97. 0 0
      apps/json-format/json-bigint.js
  98. 103 0
      apps/json-format/json-decode.js
  99. 6 2
      apps/json-format/json-lint.js
  100. 0 186
      apps/json-format/without-ui.css

+ 0 - 638
apps/_locales/zh_CN/messages.json

@@ -1,638 +0,0 @@
-{
-	"extensionName": { "message": "前端(FE辅助)侦测工具" },
-	"extensionDescription": { "message": "部分idear来自compatibility-detecotor!" },
-	
-	"normalTitleText": { "message": "点击此按钮开始检测本页面的兼容性问题" },
-	"okTitleText": { "message": "暂时没有发现问题" },
-	"warningTitleText": { "message": "发现警告级别的问题\n点击查看" },
-	"errorTitleText": { "message": "发现错误级别的问题\n点击查看" },
-	
-	"msg0001":{"message":"HTML侦测结果"},
-	"msg0002":{"message":"CSS侦测结果"},
-	"msg0003":{"message":"Javascript侦测结果"},
-	"msg0004":{"message":"共<span class=x-num>$1</span>个,"},
-	"msg0005":{"message":"建议替换它。"},
-	"msg0006":{"message":"HTML5不再支持的标签"},
-	"msg0007":{"message":"<span class=x-num>$1</span>个位于<span class=x-tag>&lt;$2&gt;</span>标签内,"},
-	"msg0008":{"message":"HTML5不再支持的属性"},
-	"msg0009":{"message":"本页设置了DTD,"},
-	"msg0010":{"message":"这个 DTD 使 Chrome 处于<span class=x-num>$1</span>,但有可能使 IE 进入<span class=x-num>$2</span>。"},
-	"msg0011":{"message":"这个 DTD 使 Chrome 处于<span class=x-num>$1</span>,但 DTD 之前存在 IE 条件注释,有可能使 IE 进入$2"},
-	"msg0012":{"message":"本页在 IE 和 Chrome 中将触发 相同的 渲染模式,均为<span class=x-num>$1</span>"},
-	"msg0013":{"message":"建议使用在所有浏览器中均触发标准模式的 DTD,以减少出现兼容性问题的可能"},
-	"msg0014":{"message":"本页在 IE 和 Chrome 中将触发 <strong>不同的</strong> 渲染模式,其中 IE 中为$1,Chrome为$2"},
-	"msg0015":{"message":"建议删除 DTD 之前的内容,以减少出现兼容性问题的可能"},
-	"msg0016":{"message":"本页没有设置 DTD,在所有浏览器中将以<span class=x-num>$1</span>渲染;"},
-	"msg0017":{"message":"文档类型定义(DTD)"},
-	"msg0018":{"message":"发现仅 IE 支持的条件注释<span class=x-num>$1</span>个"},
-	"msg0019":{"message":"建议删掉页面上无用的注释"},
-	"msg0020":{"message":"其他HTML相关检测结果"},
-	"msg0021":{"message":"有<span class=x-num>$1</span>个 <span class=x-tag>&lt;link&gt;</span> 标记未放置在<span class=x-tag>&lt;$2&gt;</span>标签内,如果是动态创建的此标签,可能会导致它失效"},
-	"msg0022":{"message":"link标签"},
-	"msg0023":{"message":"没有发现异常"},
-	"msg0024":{"message":"ID重复性检测"},
-	"msg0025":{"message":"发现如下重复性的ID"},
-	"msg0026":{"message":"发现如下<span class=x-num>$1</span>个ID被重复使用,$2"},
-	"msg0027":{"message":"在文档中没有发现注释"},
-	"msg0028":{"message":"在文档中发现<span class=x-num>$1</span>个DOM节点"},
-	"msg0029":{"message":"发现有<strong>$1</strong>个<span class=x-tag>&lt;input&gt;</span>设置了size属性,size属性不能精确的控制其尺寸,请使用CSS的width替换它"},
-	"msg0030":{"message":"在文档中共发现<span class=x-num>$1</span>个HTML注释,注释总大小为 <span class=x-num>$2</span>字节;"},
-	"msg0031":{"message":"所有处于存活期的cookie"},
-	"msg0032":{"message":"其他Script相关检测结果"},
-	"msg0033":{"message":"在页面上发现独立的<span class=x-tag>&lt;script&gt;</span>块<span class=x-num>$1</span>个"},
-	"msg0034":{"message":"在页面上发现通过<span class=x-tag>&lt;script&gt;</span>标签引入外部js文件<span class=x-num>$1</span>个"},
-	"msg0035":{"message":"发现内联元素<span class=x-tag>$1</span>包含了块级元素<span class=x-tag>$2</span>"},
-	"msg0036":{"message":"内联元素包含块级元素"},
-	"msg0037":{"message":"未闭合的标签"},
-	"msg0038":{"message":"发现有$1未闭合,各浏览器对这些未闭合标签的容错方式不同"},
-	"msg0039":{"message":"冗余的CSS选择器"},
-	"msg0040":{"message":"可能用到的CSS伪类选择器"},
-	"msg0041":{"message":"实际用到的CSS选择器"},
-	"msg0042":{"message":"本页的cookie总大小为<span class=x-num>$1</span>字节"},
-	"msg0043":{"message":"本页的HTML代码没有经过压缩"},
-	"msg0044":{"message":"发现$1内的CSS代码没有经过压缩"},
-	"msg0045":{"message":"发现$1内的Javascript代码没有经过压缩"},
-	"msg0046":{"message":"<span class=x-tag>$1</span>标签"},
-	"msg0047":{"message":"<span class=x-num>$1</span>"},
-	"msg0048":{"message":"所有CSS检测结果"},
-	"msg0049":{"message":"当前页面没有设置<span class=x-tag>&lt;title&gt;</span>标签"},
-	"msg0050":{"message":"当前页面的<span class=x-tag>&lt;title&gt;</span>标签不在<span class=x-tag>&lt;head&gt;</span>标签内"},
-	"msg0051":{"message":"当前页面设置了多个<span class=x-tag>&lt;title&gt;</span>标签"},
-	"msg0052":{"message":",不利于SEO"},
-	"msg0053":{"message":"检测到<span class=x-tag>&lt;img&gt;</span>标签<span class=x-num>$1</span>的src为空"},
-	"msg0054":{"message":"当前页面引入的tangram文件为<span class=x-num>$1</span>"},
-	"msg0055":{"message":"当前页面依次引入了如下tangram文件:<span class=x-num>$1</span>"},
-	"msg0056":{"message":"绑定到window对象上的全局属性或方法"},
-	"msg0057":{"message":"<div><span class=x-tag>$1</span>中引用了<span class=x-num>$2</span>个css背景图片<span class='x-expand -x-expand-$3'>展开</span></div>"},
-	"msg0058":{"message":"CSS中共检测到<span class=x-num>$1</span>个expression,分布在$2"},
-	"msg0059":{"message":"<span class=x-tag>$1</span>中<span class=x-num>$2</span>个"},
-	"msg0060":{"message":"Firefox的注释中不能包含'--',但当前页面上已检测出<span class=x-num>$1</span>个这样的注释"},
-	"msg0061":{"message":"$1相关参考问题及其建议"},
-	"msg0062":{"message":"<a href='$1' target='_blank'>$2</a>"},
-	"msg0063":{"message":"当前页面的最大嵌套深度为<span class=x-num>$1</span>,路径为:<span class=x-tag>$2</span>"},
-	"msg0064":{"message":"<span class=x-tag>$1</span>被使用<span class=x-num>$2</span>次"},
-	"msg0065":{"message":"CSS中使用到的背景图片"},
-	"msg0066":{"message":"如下文件被多次引入,请确认:$1"},
-	"msg0067":{"message":"如下文件路径不同,但内容一样,请确认:$1"},
-	"msg0068":{"message":"<div><a href='$1' target='_blank' class=x-file>$2</a>被引入<span class=x-num>$3</span>次</div>"},
-	"msg0069":{"message":"<a href='$1' target='_blank' class=x-file>$2</a>文件"},
-	"msg0070":{"message":"<div>$1的内容重复</div>"},
-	
-	"html_0001": { "message": "在 Firefox 中注释内容中如果包含 '--' 字符在某些情况下会使该注释解析错误" },
-	"html_0002": { "message": "各浏览器对于字符编码别名支持的宽泛程度存在差异" },
-	"html_0003": { "message": "各浏览器对 URI 中非 ASCII 字符的处理有差异" },
-	"html_0004": { "message": "DTD 之前的非空白字符在某些情况下会使该 DTD 失效" },
-	"html_0005": { "message": "Chrome 和 Safari 中标签紧密相邻的行内元素在折行显示时存在错误" },
-	"html_0006": { "message": "META 标签的 content 属性中使用非 ';' 的符号做数据分割时在某些浏览器中不会被识别" },
-	"html_0007": { "message": "IE6 IE7 IE8(Q) 会忽略 OBJECT 和 IFRAME 标签之后的空白符" },
-	"html_0008": { "message": "IE Opera 对 OBJECT 元素之前的 'non-breaking space' 处理有误" },
-	"html_0009": { "message": "Chrome 和 Safari 中 BR 元素前的空白符不会被忽略" },
-	"html_0010": { "message": "IE 中单元格的 colspan 属性在某些情况下会影响 TABLE 元素的自动布局" },
-	"html_0011": { "message": "Firefox 在 TABLE 元素的宽度属性 (Attribute) 值大于 100% 时会以 100% 计算" },
-	"html_0012": { "message": "IE 支持某些非 TD 和 TH 元素上的 noWrap 属性" },
-	"html_0013": { "message": "各浏览器下使用 OBJECT 元素和 EMBED 元素嵌入 Flash 存在差异" },
-	"html_0014": { "message": "IMG 元素的 src 属性为空时其尺寸在各浏览器中不一致" },
-	"html_0015": { "message": "IE6 IE7 IE8(Q) 中 IMG 元素的 alt 属性在没有 title 属性的情况下会被当作提示信息使用" },
-	"html_0016": { "message": "IE6 不支持 PNG24 格式图片的半透明效果" },
-	"html_0017": { "message": "只有 IE6 中给 IMG 设置相同的 src 时会重载图片" },
-	"html_0018": { "message": "Firefox Opera 不支持通过为 OBJECT 元素设置 classid 引入 Windows 下的 Media Player 或 Flash 插件" },
-	"html_0019": { "message": "Firefox Opera 中 OBJECT 元素的默认尺寸为不可视" },
-	"html_0020": { "message": "在 IE6 IE7 IE8(Q) 中 CENTER 元素自身也会居中对齐,在 IE(S) 与 Opera(S) 中还会使其包含的表格中的文本居中对齐" },
-	"html_0021": { "message": "各浏览器对 align='middle' 的理解有差异" },
-	"html_0022": { "message": "Chrome Safari Firefox 中 IFRAME 元素在文档树中发生变化后父子页面间的某些交互方式会失效" },
-	"html_0023": { "message": "不同内核的浏览器中文件选择控件的外观也不相同" },
-	"html_0024": { "message": "不能以 size 属性精确控制 INPUT 文本框或密码框的宽度" },
-	"html_0025": { "message": "各浏览器中密码框掩码的外观不完全一致" },
-	"html_0026": { "message": "IE6 IE7 IE8(Q) 中 FILEDSET 元素的宽度不是 'auto' 时的表现与行内元素相同" },
-	"html_0027": { "message": "仅在 IE6 IE7 IE8中 \"disabled\" 属性可以作用于非表单控件元素" },
-	"html_0028": { "message": "Chrome Safari Opera 中 INPUT、TEXTAREA 元素的 disabled 属性值为 true 时其前景颜色会发生变化,但在 Chrome Safari 中某些情况下 disabled 属性值由 true 转为 false 后,其前景颜色不会更新至最初的设定色" },
-	"html_0029": { "message": "Firefox 中 TEXTAREA 元素根据 \"rows\" 设置值生成的实际行数为设置值 + 1" },
-	"html_0030": { "message": "Firefox Opera 中 BUTTON 元素的子元素可以溢出按钮渲染" },
-	"html_0031": { "message": "IE Opera 中 src 属性为空的 SCRIPT 元素内的脚本不会被忽略,且内联脚本可以通过修改其所在 SCRIPT 元素的 src 属性引入新的外部脚本文件" },
-	"html_0032": { "message": "Safari 中错误的注释将导致部分 JavaScript 代码无法执行" },
-	"html_0033": { "message": "IE6 IE7 IE8(Q) 中同一元素重复定义的 style 属性会被合并" },
-	"html_0034": { "message": "IE6 IE7 IE8(Q) Firefox(Q) Opera(Q) 会自动修复被大括号包含的 style 属性的值" },
-	"html_0035": { "message": "IE6 IE7 IE8(Q) 中对 UL OL DL 内包含非列表元素的处理跟其他浏览器不同" },
-	"html_0036": { "message": "各浏览器对未闭合标签的容错方式不同" },
-	"html_0037": {"message": "IE6 Firefox Chrome(Q) Safari(Q) 不支持 OPTION 元素的 label 属性"},
-	"html_0038": {"message": "IE6 IE7 不支持 OPTION 和 OPTGROUP 元素的 disabled 属性"},
-	"html_0039": { "message": "IE Opera 中可以通过 MAP 元素的 id 属性与 IMG 元素相关联" },
-	"html_0040": { "message": "各浏览器对表单元素单选按钮组设置非 CDATA 标准的 name 属性值解析不同"},
-	"html_0041": { "message": "各浏览器对 BASE 元素前后的超链接的默认 target 处理存在差异"},
-	"html_0042": { "message": "只有 IE 支持 IMG INPUT[type=image] 标记内的 lowsrc 属性"},
-		
-	"html_0001_suggestion": { "message": "按标准推荐的方法写注释标签" },
-	"html_0002_suggestion": { "message": "首先,对于动态页面必须确保 HTTP \"Content-Type\" 头字段的 \"charset\" 参数与页面自身编码相符,且务必在页面的 META 元素中也声明相符的字符编码信息。对于静态页面,必须保证页面中 META 元素声明中 \"http-equiv\" 为 \"Content-Type\" 对应的值中的 \"charset\" 的值与页面自身编码相符。" },
-	"html_0003_suggestion": { "message": "当 URI 中含有非 ASCII 字符时,不要依赖浏览器对 URI 的编码方式,以避免产生差异。" },
-	"html_0004_suggestion": { "message": "声明 DTD 时,确保 DTD 之前没有其他字符,即便有,也只能是空格符、换行符和制表符。" },
-	"html_0005_suggestion": { "message": "避免出现紧密连接的内联元素标签,可以在每个标记之间加入空格或者换行符来避免这个问题。" },
-	"html_0006_suggestion": { "message": "参照 W3C 的建议,使用英文半角分号“;”。" },
-	"html_0007_suggestion": { "message": "若不希望出现空格,可以将 IFRAME OBJECT 元素设置为块级元素。" },
-	"html_0008_suggestion": { "message": "合理的设置容器及 OBJECT 元素的宽度。" },
-	"html_0009_suggestion": { "message": "删除 BR 元素之前多余的空白符。" },
-	"html_0010_suggestion": { "message": "1. 设置 TABLE 的 'table-layout' 特性值为 fixed,使用固定布局的表格元素可避免此问题。" },
-	"html_0011_suggestion": { "message": "给 TABLE 元素设置宽度的时候,不要使用 HTML 属性 'width',请使用 CSS 特性 'width'。" },
-	"html_0012_suggestion": { "message": "nowrap 属性是被废弃的属性,使用 CSS 规则 white-space:nowrap 代替这个属性。" },
-	"html_0013_suggestion": { "message": "若不考虑 W3C 校验,可统一使用 EMBED 元素嵌入 Flash,这样可以避免因参数不统一导致的兼容性问题。" },
-	"html_0014_suggestion": { "message": "为了防止这种无 \"src\" 的 IMG 元素对页面产生布局影响,需要设置这种 IMG 的 ‘display’ 特性为 'none'。" },
-	"html_0015_suggestion": { "message": "若用户需要显示提示框,则指定 title 属性;" },
-	"html_0016_suggestion": { "message": "使用 IE 专有滤镜 AlphaImageLoader Filter 来修复 IE6 透明通道问题,详情请参考 MSDN 说明:  http://msdn.microsoft.com/en-us/library/ms532969(VS.85).aspx " },
-	"html_0017_suggestion": { "message": "如果需要重复设置相同的 src 值时,均触发 IMG 的 onload 事件,或者需要每次均从服务器端下载图片数据的时候,可以采用图片地址后加上随机数或当前时间戳参数的手段,避免内容被缓存。" },
-	"html_0018_suggestion": { "message": "由于某些浏览器原生无法支持 OBJECT 元素使用 classid 属性引入 Media Player 插件,所以为保证最大的兼容性,应避免使用此方式在页面中播放媒体文件。" },
-	"html_0019_suggestion": { "message": "OBJECT 元素为替换元素,应为 OBJECT 元素设置一个明确的宽度和高度。" },
-	"html_0020_suggestion": { "message": "避免使用 CENTER 标签,使用 CSS 的 'text-align' 特性来代替。" },
-	"html_0021_suggestion": { "message": "align=\"middle\" 仅在 IMG、OBJECT、APPLET 元素上的 align 属性中是合法值,对于其他元素的 align 属性均为非法。各浏览器在上述三个元素之外的元素上遇到 align=\"middle\" 均按照自己的理解方式解释。同时除单元格元素的 align 属性之外,其他的 align 属性均被 W3C 官方废弃(Deprecated.),所以应避免使用此属性。" },
-	"html_0022_suggestion": { "message": "根据上面所得的结果,推荐使用 document.getElementById(\"IFRAME\").contentWindow.document 获取 IFRAME 元素内页面的 document 对象,且对于在文档树中移动位置后的 IFRAME 元素也有很好的兼容性。同时应避免对跨域的父子页面交互。" },
-	"html_0023_suggestion": { "message": "在 Chrome  Safari 中对于文件选择控件的特殊呈现方式为 WebKit 内核特有,且其渲染方式也符合 W3C 对文件选择控件的规定。一般来说这种差异不会带来严重的兼容性问题。" },
-	"html_0024_suggestion": { "message": "不要试图通过设置 \"size\" 属性使 INPUT[type=text/password] 元素在所有浏览器中的宽度一致,这是不可能的。在需要对这类元素做精确的控制时,应使用 CSS 的 'width' 和 'height' 特性。" },
-	"html_0025_suggestion": { "message": "由于掩码字符无法被修改,因此仅通过 CSS 统一密码输入框的掩码样式是不可能的。且这种差异可以忽略不计。" },
-	"html_0026_suggestion": { "message": "这是一个 Bug,通过显式设置 FIELDSET 的样式为 'display:block' 来修复。" },
-	"html_0027_suggestion": { "message": "应避免在非表单控件类元素上使用 \"disabled\" 属性。" },
-	"html_0028_suggestion": { "message": "避免为 INPUT、TEXTAREA 元素设置比较深的背景色,或者在需要使一个文本框在 \"disabled=true\" 及 \"disabled=false\" 之间切换时,尽量通过标准的 DOM 方式创建此元素。" },
-	"html_0029_suggestion": { "message": "当我们仅仅为 TEXTAREA 元素设置 \"rows\" 属性以控制其高度时,在 Firefox 中无法得到我们预期的效果。且其他浏览器对 \"rows\" 属性设置的元素高度也不尽相同,这一点 W3C 没有明确规范 \"rows\" 属性计算高度时的具体算法。" },
-	"html_0030_suggestion": { "message": "合理的设置 BUTTON 及其子元素的宽度及高度,避免出现子元素溢出 BUTTON 的情况。" },
-	"html_0031_suggestion": { "message": "不在设定了 src 属性的 SCRIPT 元素内编写脚本。" },
-	"html_0032_suggestion": { "message": "SCRIPT 标记内如有 HTML 注释标记,请仔细检查,注意他们的闭合情况。" },
-	"html_0033_suggestion": { "message": "不要依赖 IE 的容错机制,避免重复定义 HTML 元素属性。" },
-	"html_0034_suggestion": { "message": "不要在 'style' 的属性值中使用大括号等符号包含样式特性,直接书写样式代码即可避免此类问题。" },
-	"html_0035_suggestion": { "message": "虽然 HTML 4.01 规范中没有明确指出列表元素的兄弟元素必须是同一列表元素;但是根据 HTML 语义化理念,建议不要在列表元素 LI DD DT 之后插入其他元素。" },
-	"html_0036_suggestion": { "message": "编写代码时要注意,不要遗失应有的闭合标签,以确保在各浏览器中都能按照预期的文档结构来渲染页面。" },
-	"html_0037_suggestion": { "message": "尽量避免在 OPTION 元素上使用 label 属性,如果想替代 OPTION 原有的内容值,请使用 Javascript。" },
-	"html_0038_suggestion": { "message": "使用其他方式使想要 disabled 的 OPTION 和 OPTGROUP 无效,比如使用脚本动态删除 OPTION 或 OPTGROUP。" },
-	"html_0039_suggestion": { "message": "若需要 IMG 元素与 MAP 元素相关联,注意通过 IMG 元素的 usemap 属性关联的 MAP 元素的 name 属性的值。" },
-	"html_0040_suggestion": { "message": "单选按钮的 \"name\" 属性值必须以字母 ([A-Za-z])和数字([0-9])开头 ,其后由任何字母、数字、连字符(\"-\")、下划线(\"_\")、冒号(\":\")和句号 (\".\") 组成。" },
-	"html_0041_suggestion": { "message": "不要在 HEAD 元素之外定义 BASE 元素,保证各浏览器兼容。" },
-	"html_0042_suggestion": { "message": "如无特殊应用需求,应避免使用 lowsrc 为属性名,防止他与 IE 扩展属性混淆。"},
-	
-	"css_0001": { "message": "Chrome Safari 中 '@charset' 声明的位置错误将导致其后的一个规则集无效" },
-	"css_0002": { "message": "各浏览器在 HTML 页面与页面中引入的外部 CSS 文件编码不一致时表现不同" },
-	"css_0003": { "message": "IE6 IE7(Q) IE8(Q) 不支持子选择器" },
-	"css_0004": { "message": "IE6 IE7(Q) IE8(Q) 不支持相邻兄弟选择器" },
-	"css_0005": { "message": "IE6 IE7(Q) IE8(Q) 不支持属性选择器" },
-	"css_0006": { "message": "IE6 IE7(Q) IE8(Q) 不支持 ':first-child' 伪元素" },
-	"css_0007": { "message": "IE6 IE7(Q) IE8(Q) 中 A 元素的 :visited :hover :active 伪类未按规范要求的算法来计算针对性" },
-	"css_0008": { "message": "IE6 IE7(Q) IE8(Q) 不支持 A 元素以外的其他元素的 ':hover' 伪类" },
-	"css_0009": { "message": "IE6 IE7 IE8(Q) 不支持 A 元素以外的其他元素的 ':active' 伪类" },
-	"css_0010": { "message": "IE6 IE7 IE8(Q) 不支持 ':focus' 伪类" },
-	"css_0011": { "message": "IE6 IE7 IE8(Q) 不支持 ':before' 和 ':after' 伪元素" },
-	"css_0012": { "message": "IE6 IE7 IE8(Q) 不支持 CSS 特性的 'inherit' 值" },
-	"css_0013": { "message": "IE8  Chrome Safari 中具有加粗效果的 HTML 元素的 'font-weight' 特性会受到其祖先元素的影响" },
-	"css_0014": { "message": "IE6 IE7(Q) IE8(Q) 不完全支持 !important 规则" },
-	"css_0015": { "message": "IE6 IE7 IE8(Q) 负边距 (margin) 导致元素溢出 hasLayout 容器时显示异常" },
-	"css_0016": { "message": "Chrome Safari 中为处于浮动元素后创建了 in-flow 的 Block Formatting Context 的元素设置的 'margin-left' 或 'margin-right' 特性会出错" },
-	"css_0017": { "message": "Firefox 中 'display:table '的元素的外边距不会与包含块的外边距折叠" },
-	"css_0018": { "message": "IE6 IE7 IE8(Q) 中浮动元素和定位元素某些情况下会影响普通流中毗邻 'margin' 的折叠" },
-	"css_0019": { "message": "IE6 IE7 IE8(Q) 中父元素或子元素触发 hasLayout 时子元素的 margin 值与期望值不符" },
-	"css_0020": { "message": "IE6 IE7 IE8(Q) 不支持 'display' 的替代值" },
-	"css_0021": { "message": "不能在 IE6 IE7 IE8(Q) 中触发 hasLayout 并在其他浏览器中创建 Block Formatting Context 的元素的表现会有差异" },
-	"css_0022": { "message": "各浏览器中浮动元素与相邻的 Block Formatting Context 之间的关系有差异" },
-	"css_0023": { "message": "IE6 IE7 IE8(Q) 中零高度的浮动元素会阻挡其兄弟浮动元素" },
-	"css_0024": { "message": "IE6 IE7 IE8(Q) 中行内元素后相邻的浮动元素在某些情况下会折行放置在之前行内元素所在行框的底部" },
-	"css_0025": { "message": "IE6 IE7 IE8(Q)中设置了 'clear' 特性的元素 'margin-top' 特性处理有误,某些情况下会与浮动元素重叠" },
-	"css_0026": { "message": "在 IE6 IE7 IE8(Q) 中包含被清除浮动的浮动元素的块级元素的背景在某些情况下不是设置值" },
-	"css_0027": { "message": "IE6 IE7 IE8(Q) 中对浮动元素上 'clear' 特性的解释出现错误,使其自身位置和其后浮动元素的位置与其他浏览器中不同" },
-	"css_0028": { "message": "IE6 IE7 IE8(Q) 中一些块级元素的默认上下外边距会在其浮动或触碰到 hasLayout 的容器后消失" },
-	"css_0029": { "message": "IE6 IE7 IE8(Q) 中元素的 'padding-top' 遇到 'clear' 特性在某些情况下复制到 'padding-bottom'" },
-	"css_0030": { "message": "Firefox 浏览器对 TABLE 中绝对定位元素包含块的判定有错误,某些情况下会导致绝对定位元素位置跟其他浏览器中有差异" },
-	"css_0031": { "message": "IE6 IE7 IE8(Q) Firefox Opera 中绝对定位元素的静态位置计算某些情况下会出错" },
-	"css_0032": { "message": "IE6 IE7(Q) IE8(Q) 不支持固定定位(position:fixed)" },
-	"css_0033": { "message": "IE6 IE7 IE8(Q) 中某些情况下浮动元素会在其浮动方向溢出其包含块" },
-	"css_0034": { "message": "IE6 IE7 IE8(Q) 中定位元素 'z-index' 为默认值在某些情况下会产生新的层叠上下文" },
-	"css_0035": { "message": "IE6 IE7 IE8(Q) 中溢出容器的浮动元素导致容器兄弟元素文本定位错误" },
-	"css_0036": { "message": "IE7(S) 中元素溢出包含块的部分会被 hasLayout 元素遮住" },
-	"css_0037": { "message": "IE6 IE7(Q) IE8(Q) 中包含块若未触发 hasLayout 则会影响参照其定位的绝对定位元素的偏移位置计算" },
-	"css_0038": { "message": "各浏览器中 'width' 和 'height' 在某些元素上的作用位置有差异" },
-	"css_0039": { "message": "IE6 IE7(Q) IE8(Q) 中 'width' 或 'height' 的设定值在不足以容纳其内容时将被撑大" },
-	"css_0040": { "message": "零宽高的 IFRAME 元素的实际尺寸在不同浏览器中有差异" },
-	"css_0041": { "message": "IE6 IE7 IE8(Q) 中浮动元素宽度的 shrink-to-fit 算法与标准规定的算法不同" },
-	"css_0042": { "message": "Chrome Safari 中浮动元素之前的非 inline 级元素会对包含块的 shrink-to-fit 宽度计算有影响" },
-	"css_0043": { "message": "IE6 IE7 IE8(Q) 中右浮动元素会撑大其祖先级元素的宽度" },
-	"css_0044": { "message": "IE Opera 计算 shrink-to-fit 的宽度时会考虑浮动元素的宽度" },
-	"css_0045": { "message": "IE6 IE7(Q) IE8(Q) 绝对定位元素无法根据其四个方向的偏移量自动计算其尺寸" },
-	"css_0046": { "message": "IE6 IE7 IE8(Q) 中行框的顶边与底边位置识别有误" },
-	"css_0047": { "message": "IE6 IE7(Q) IE8(Q) 不支持 'min-width' 和 'max-width' 特性" },
-	"css_0048": { "message": "IE6 IE7 IE8(Q) 中行内非替换元素中的非文本文字会撑高其高度" },
-	"css_0049": { "message": "不同浏览器内 'line-height' 样式设置会影响不同行内替换元素的显示高度" },
-	"css_0050": { "message": "IE6 IE7 IE8(Q) 中触发 hasLayout 的空块级非替换元素的高度不是0" },
-	"css_0051": { "message": "行内非替换元素的高度和宽度的设置仅在 IE 混杂模式中生效" },
-	"css_0052": { "message": "IE6 IE7(Q) IE8(Q)不支持 'min-height' 和 'max-height' 特性" },
-	"css_0053": { "message": "各浏览器对常用行内替换元素的 'baseline' 位置理解不同" },
-	"css_0054": { "message": "Safari Chrome 中行框高度计算有误" },
-	"css_0055": { "message": "Safari Chrome 中元素 'overflow' 值为非默认值时其最后一个内联子元素的半差异高度被忽略" },
-	"css_0056": { "message": "各浏览器对行内元素绝对定位后的静态位置的 'top' 特性计算存在差异,IE6 IE7 IE8(Q) 还会考虑半差异的高度" },
-	"css_0057": { "message": "在不同的文档模式中,当唯一的非表单控件类行内替换元素存在于其包容块中时,其父框的行高并不一定会计算文本基线高度。" },
-	"css_0058": { "message": "各浏览器中当容器元素 shrink-to-fit 时容器内 MARQUEE 标签的计算宽度不一致" },
-	"css_0059": { "message": "IE 中当 'overflow-x' 或 'overflow-y' 的值被设置成非 'visible' 时,另一个特性的计算值为 'visible' 而非 'auto'" },
-	"css_0060": { "message": "IE6 IE7 IE8(Q) 中 'overflow' 特性不为 'visible' 的非定位元素内包含溢出的定位元素时的渲染效果有误" },
-	"css_0061": { "message": "IE6 IE7 IE8(Q) 中 UL 和 OL 标记为实现放置 LI 元素标记框 'outside' 设置时所使用的样式设定不同于其他浏览器" },
-	"css_0062": { "message": "IE6 IE7 IE8(Q) 中如果列表元素设置 'list-style-type:none' 时不影响 ‘list-style-position:inside’ 设置产生的标记块  " },
-	"css_0063": { "message": "LI 元素设置标记类型的特性 'style-list-type' 的 'disc | circle | square' 三个特性值在各浏览器中的渲染方式不同" },
-	"css_0064": { "message": "IE6 IE7(Q) IE8(Q) 中 IFRAME 元素 'background-color' 特性默认值不是 transparent" },
-	"css_0065": { "message": "IE6 不支持 HTML、BODY 以外元素的 background-attachment:fixed" },
-	"css_0066": { "message": "各浏览器对 '@font-face' 规则支持的字体格式不同,IE 支持 EOT 字体,Firefox Safari Opera 支持 TrueType 等字体" },
-	"css_0067": { "message": "在某些条件下 Firefox Chrome Safari 的标准模式中 'text-decoration' 会作用于 IMG 元素上" },
-	"css_0068": { "message": "元素和其子孙元素的 'text-decoration' 特性对其内文本的渲染在各浏览器中不同" },
-	"css_0069": { "message": "'text-align' 特性在 IE6 IE7 IE8(Q) 中可以影响块级元素的对齐方式,并且在所有浏览器的混杂模式中均不能被 TABLE 元素继承" },
-	"css_0070": { "message": "WebKit 浏览器中 'white-space:nowrap' 使表格内的浮动元素不折行" },
-	"css_0071": { "message": "'text-overflow:ellipsis' 会引起兼容性问题" },
-	"css_0072": { "message": "某些条件下 IE6 IE7 IE8(Q) 中 'word-wrap:break-word' 作用于 TD 时导致其中 IMG 元素不换行" },
-	"css_0073": { "message": "'word-wrap:break-word' 只有在 WebKit 浏览器中对 SELECT 元素上的文字有效" },
-	"css_0074": { "message": "'word-wrap:break-word' 导致在 IE6, IE7 及 IE8(Q) 中空格不被忽略" },
-	"css_0075": { "message": "仅 Firefox 支持小数数值的 'letter-spacing' 特性" },
-	"css_0076": { "message": "Firefox 中 A 元素的 ':hover' 伪类中设置的下划线某些情况下会失效" },
-	"css_0077": { "message": "固定表格布局下的各浏览器对与表格宽度计算算法不同" },
-	"css_0078": { "message": "WebKit 中某些条件下 empty cell 的宽度和高度为 0" },
-	"css_0079": { "message": "IE6 IE7 IE8(Q) 中自动布局的表格在其中包含无内容的左浮动元素时的宽度计算在某些情况下有误" },
-	"css_0080": { "message": "IE 混杂模式下 TR 元素的最终高度始终不会超过其所有 TD 子元素的高度" },
-	"css_0081": { "message": "单元格的高度计算受其 'padding' 和 'line-height' 的影响 " },
-	"css_0082": { "message": "IE6(Q) IE7(Q) IE8(Q) Chrome(S) Safari(S) Firefox 对多层嵌套的表格级元素百分比高度计算错误" },
-	"css_0083": { "message": "IE6 IE7 IE8(Q) Firefox(Q) Opera(Q) 中空单元格的边框某些情况下会消失" },
-	"css_0084": { "message": "IE6 IE7 IE8(Q) 中空单元格的上下 'padding' 失效" },
-	"css_0085": { "message": "IE6 IE7 IE8(Q) 中单元格对齐依据的是元素的原始宽度而不是其因某些原因被拉大后的宽度" },
-	"css_0086": { "message": "IE Firefox Opera 的混杂模式对于百分比单位高度的单元格内子元素的百分比高度计算错误" },
-	"css_0087": { "message": "只有 IE 和 Opera 支持 'cursor:hand'" },
-	"css_0088": { "message": "IE6 IE7 IE8(Q) 不支持 'outline' 特性" },
-	"css_0089": { "message": "IE6 IE7 IE8(Q) 会自动修复 'font-family' 特性的值是由一个引号包括了整个字体家族时的错误书写的代码" },
-	"css_0090": { "message": "各浏览器对 CSS 代码及 CSS 相关 DOM 操作中长度 \"&lt;length&gt;\" 类型的值缺失单位的容错程度存在差异" },
-	"css_0091": { "message": "各浏览器对 CSS 错误解析规则的差异及 CSS hack" },
-	"css_0092": { "message": "各浏览器对不合法的类选择器名称的容错程度存在差异" },
-	"css_0093": { "message": "各浏览器对于计算后宽度和高度为含有小数的长度值时,其最终值会不一致" },
-	"css_0094": { "message": "IE5.0 IE5.5 IE6 中浮动元素在某些情况下会有双倍外边距" },
-	"css_0095": { "message": "IE6 IE7 IE8(Q) 中从单元格溢出的内容会被自动剪裁" },
-	"css_0096": { "message": "IE6 IE7 IE8(Q) 中 'white-space' 特性在某些情况下不会自动继承" },
-	"css_0097": { "message": "非 IE 浏览器中 'margin' 特性对 TABLE 元素的 align 属性会有影响" },
-	"css_0098": { "message": "IE6 IE7(Q) IE8(Q) 中 A 元素的 ':hover' 伪类以及 IE6 IE7 IE8(Q) 中的 ':active' 伪类 在当缺少 href 属性时会失效" },
-	"css_0099": { "message": "IE(Q) Firefox(Q) Opera 中 BR 元素的 'line-height' 特性的计算值在某些情况下会小于其继承值" },
-	"css_0100": { "message": "字体样式标签(U、B、S等)对某些 CSS 特性有影响" },
-	"css_0101": { "message": "IE6 IE7 IE8(Q) 中 cellspacing 属性在重合的边框模型的表格中仍然有效" },
-	"css_0102": { "message": "各浏览器中 IFRAME 元素的 scrolling 属性与其子页面 HTML 与 BODY 元素 'overflow' 特性的制约关系有差异" },
-	"css_0103": { "message": "IE6(Q) IE7(Q) IE8(Q) 中给 IMG 元素设置 'padding' 无效" },
-	"css_0104": { "message": "各浏览器中 FONT 元素的颜色设置在某些情况下会作用到由其祖先级元素设定的装饰线的颜色" },
-	"css_0105": { "message": "不同浏览器中 Flash 与其他元素发生覆盖时有差异" },
-	"css_0106": { "message": "IE6 IE7 IE8(Q) 没有完全正确地将 IMG、OBJECT、IFRAME、TABLE 元素的 align='left|right' 理解为浮动" },
-	"css_0107": { "message": "IE 对浮动非替换元素内包含宽度单位为百分比的元素时的 \"shrink-to-fit\" 宽度算法有误" },
-	"css_0108": {"message": "IE6 IE7 IE8(Q) 下 'background-attachment : scroll' 时背景图片会随着元素内容滚动" },
-	"css_0109": {"message": "各浏览器禁止内容选中的方式不同" },
-	"css_0110": {"message": "IE6 IE7 IE8(Q) 不支持 border-spacing 特性" },
-	"css_0111": { "message": "各浏览器对于未明确设定高度的包含块内包含百分比单位高度的块级元素或行内块元素的高度计算存在差异" },
-	"css_0112": { "message": "IE6 IE7 IE8(Q) 不支持 border-spacing 特性" },
-	"css_0113": { "message": "IE6 IE7 IE8(Q) 中空 TABLE 的宽度和高度均为 0" },
-	"css_0114": { "message": "IE6 IE7 IE8(Q) 中对浮动元素上 'clear' 特性的解释出现错误,使其自身位置和其后浮动元素的位置与其他浏览器中不同" },
-	"css_0115": { "message": "IE6 IE7 IE8(Q) 中零高度的浮动元素会阻挡其兄弟浮动元素" },
-	"css_0116": { "message": "只有 IE Chrome Safari 支持 'zoom' 特性并且他们的具体实现方式不同"},
-	"css_0117": { "message": "Chrome Safari 认为 'float:center' 是合法值且其计算值为 'none'"},
-	"css_0118": { "message": "各浏览器对 'marginwidth' 和 'marginheight' 属性的错误设定值的处理不同"},
-	"css_0119": { "message": "各主流浏览器均不支持非标准的 LAYER 元素" },
-	
-	"css_0001_suggestion": { "message": "要使用 '@charset' 规则,请确保将其放在样式表的最开始,前边不能有任何字符。" },
-	"css_0002_suggestion": { "message": "当 HTML 文件或 CSS 文件要引入一个不同编码的 CSS 文件时,要明确声明将被引入的 CSS 文件的编码,以避免发生上述问题。" },
-	"css_0003_suggestion": { "message": "避免在 IE6 IE7(Q) IE8(Q) 中使用子选择器。" },
-	"css_0004_suggestion": { "message": "避免在非标准模式中对 IE7 以下版本使相邻兄弟选择器。" },
-	"css_0005_suggestion": { "message": "避免在 IE6 IE7(Q) IE8(Q) 中使用属性选择器。" },
-	"css_0006_suggestion": { "message": "避免在 IE6 IE7(Q) IE8(Q) 中使用 ‘:first-child’ 伪元素。" },
-	"css_0007_suggestion": { "message": "严格按照标准的建议,以 L-V-H-A 的顺序声明 A 标签的伪类,以保证在各浏览器中兼容。" },
-	"css_0008_suggestion": { "message": "使用 JavaScript 绑定 'onmouseover' 和 'onmouseout'(模仿 ':hover');" },
-	"css_0009_suggestion": { "message": "使用 JavaScript 绑定 onmousedown onmouseup 事件模仿 ‘:active’ 伪类效果;" },
-	"css_0010_suggestion": { "message": "使用 JavaScript 绑定 onfocus onblur 事件模仿 ‘:focus’ 效果;" },
-	"css_0011_suggestion": { "message": "使用 JavaScript 判断在 IE6 IE7 IE8(Q) 中通过脚本实现 ':before' 及 ':after' 伪元素的效果;" },
-	"css_0012_suggestion": { "message": "使用标准模式渲染页面;" },
-	"css_0013_suggestion": { "message": "最好为元素设置绝对明确的 'font-weight' 特性的值,避免使用 bolder、lighter 这类相对量以及浏览器的默认样式。" },
-	"css_0014_suggestion": { "message": "这是浏览器的 Bug 导致,无法通过常规方式解决。不过,一般 '!important' 规则常常用于 CSS hack 以区分 IE6 与其他浏览器,其作为 hack 存在的意义已大于其本身的含义。" },
-	"css_0015_suggestion": { "message": "首先需要保证容器在IE中触发 hasLayout 属性,可以通过zoom:1实现。" },
-	"css_0016_suggestion": { "message": "为该创建了 BFC 的元素设置一个明确的宽度。" },
-	"css_0017_suggestion": { "message": "由于 IE6 IE7 IE8(Q) Firefox 元素的 'margin' 处理与 W3C 规范中的差异,若我们需要利用 \"margin collapse\" 达到某些布局效果时,在这几种浏览器中可能会由于没有发生 \"margin collapse\" 而出现 \"额外边距\" 的情况。所以应避免为表格设置上下边距,以及导致其 \"margin collapse\" 的发生,可以使用为表格的父元素使用 'padding' 代替表格元素的 'margin' 。" },
-	"css_0018_suggestion": { "message": "1. 根据具体需求,调整 'margin' 的位置和大小;" },
-	"css_0019_suggestion": { "message": "为容器显式地设置高度。若容器高度不定,则要避免在触发了 hasLayout 的容器内的浮动子元素上设置 'margin-bottom' 特性,可以通过为容器设置 'padding-bottom' 达到相似的效果。" },
-	"css_0020_suggestion": { "message": "尽量仅使用所有浏览器都支持的 'display' 特性值:'inline'、'block'、'list-item'、'none'。" },
-	"css_0021_suggestion": { "message": "仅当一个元素即在 IE 早期版本中触发了 hasLayout,又在其他浏览器中创建了 block formatting context 时,才能避免上述问题的发生。即同时启用上述两者以保证各浏览器的兼容,或者相反,两者皆不启用:" },
-	"css_0022_suggestion": { "message": "合理地设置容器的宽度、浮动元素的宽度、BFC 的宽度的值,尽量保证 BFC 的宽度小于 \"容器的剩余空间宽度\" 。若需要 BFC 折行显示在新的一行上,可以通过 BFC 设置 'clear' 特性等手段使其换行。" },
-	"css_0023_suggestion": { "message": "如果希望一个浮动元素能阻挡与其向相同方向浮动的兄弟元素,请确保其高度不为零,以使页面布局在各浏览器中的表现一致。" },
-	"css_0024_suggestion": { "message": "使用绝对定位(position:absolute)模拟右浮动(float:right)。" },
-	"css_0025_suggestion": { "message": "尽量避免为 'clear' 特性不为 none 的元素(即清理元素)设置 'margin-top' 特性,尤其是负值。" },
-	"css_0026_suggestion": { "message": "使丢失背景的容器触发 IE 浏览器特有的 hasLayout,如 'zoom:1',或者设置宽度和高度。" },
-	"css_0027_suggestion": { "message": "不要将 'clear' 特性应用在浮动元素上,以免出现上述不兼容的问题。" },
-	"css_0028_suggestion": { "message": "用自定义的 'margin' 取代浏览器的默认外边距样式。" },
-	"css_0029_suggestion": { "message": "方案1.不触发容器的 hasLayout 特性;" },
-	"css_0030_suggestion": { "message": "这是 Firefox 的一个 bug,绝对定位元素无法根据 'display' 特性是 'table' 且是绝对定位的祖先元素定位。" },
-	"css_0031_suggestion": { "message": "首先对于绝对定位元素,应尽可能避免使其 'top'、'right'、'bottom'、'left' 特性的值均为 'auto'。若必须这么做,则尽可能的保证绝对定位元素之前的兄弟元素为非浮动的块级元素。" },
-	"css_0032_suggestion": { "message": "在 IE6 IE7(Q) IE8(Q) 中为固定定位元素设置 'position:absolute',再通过 JavaScript 脚本或者 CSS Expression 动态设置其偏移量。如:" },
-	"css_0033_suggestion": { "message": "当文字方向为 'ltr' 时应避免使右浮动元素的宽度超出其包含块的宽度。同样地,当文字方向为 'rtl' 时应避免使左浮动元素的宽度超出其包含块的宽度。" },
-	"css_0034_suggestion": { "message": "理解层叠上下文、层叠级别与 'z-index' 之间的关系。" },
-	"css_0035_suggestion": { "message": "及时地为容器清除浮动,并且确保浮动元素没有溢出容器。" },
-	"css_0036_suggestion": { "message": "合理设置元素的 'width'、'height' 和 'overflow' 特性,避免内容溢出容器。 " },
-	"css_0037_suggestion": { "message": "使包含块触发 hasLayout 特性。如 'zoom:1' 或者设置明确的宽度、高度。" },
-	"css_0038_suggestion": { "message": "1. 使用能触发标准模式 (S) 的 DTD" },
-	"css_0039_suggestion": { "message": "使用能触发标准模式 (S) 的 DTD,以将受此问题影响的浏览器范围缩小到仅 IE6(S)。" },
-	"css_0040_suggestion": { "message": "根据实际情况选择使用 'visibility:hidden' 或者 'display:none' 隐藏 IFRAME 。" },
-	"css_0041_suggestion": { "message": "这个问题的影响较大,避免该问题的最直接的方式是给浮动非替换元素指定一个宽度,而不使用其默认值 'auto',从而避免其宽度为 shrink-to-fit,以使页面布局在各浏览器中的表现一致。" },
-	"css_0042_suggestion": { "message": "在容器为绝对定位、浮动或行内块元素且没有明确设定宽度时,若浮动元素之前出现非 inline 级元素,则要小心这个元素对容器 shrink-to-fit 宽度的影响。可以为容器明确的设定一个宽度。" },
-	"css_0043_suggestion": { "message": "如果有一个右浮动元素,应注意避免其祖先级元素的宽度为 shrink-to-fit,即给它们设定一个明确的宽度。以使页面布局在各浏览器中的表现一致。" },
-	"css_0044_suggestion": { "message": "尽量为非替换浮动元素、非替换绝对定位元素、非替换行内块元素显式地设置一个宽度,防止浏览器在 'width:auto' 时对于 shrink-to-fit 的宽度计算方式不同造成布局上的差异。" },
-	"css_0045_suggestion": { "message": "若能为非替换绝对定位元素设定固定的宽度及高度,则尽量不使用此方式自动计算绝对定位元素的 'width' 及 'height'。" },
-	"css_0046_suggestion": { "message": "为了取得正常布局,建议 'line-height' 计算值设置永远大于 'font-size' 计算值设置。 " },
-	"css_0047_suggestion": { "message": "使用 Javascript 实现 'min-width' 和 'max-width' 特性功能。" },
-	"css_0048_suggestion": { "message": "针对不同的需求,可以采取如下方式来避免此问题:" },
-	"css_0049_suggestion": { "message": "尽量避免使用 'line-height' 为 INPUT[type=text]、INPUT[type=password]、INPUT[type=button]、INPUT[type=file]、input[type=submit] 和 BUTTON 标记设置 ‘line-height’,而应改用规范内说明的 'height' 属性。 " },
-	"css_0050_suggestion": { "message": "如果想让一个触发了 hasLayout 的块级非替换元素的高度为0,可以给这个空的块级非替换元素增加一个空的注释块:" },
-	"css_0051_suggestion": { "message": "在页面上添加&lt;!DOCTYPE HTML&gt;,使页面工作在标准模式下。" },
-	"css_0052_suggestion": { "message": "使用 Javascript 实现 'min-height' 和 'max-height' 特性功能。" },
-	"css_0053_suggestion": { "message": "出现这种情况时,应避免使用 'baseline' 对齐方式,需为元素指定 'vertical-align' 值非 'baseline',推荐使用 'vertical-align:bottom' 或  'vertical-align:top'。" },
-	"css_0054_suggestion": { "message": "为了取得正常布局,建议 'line-height' 计算值永远大于 'font-size' 计算值设置。" },
-	"css_0055_suggestion": { "message": "为了准确得到容器高度,建议避免使用行高为行内元素指定高度,而应改用块标记,并且明确的指定其 'height' 值。" },
-	"css_0056_suggestion": { "message": "在使行内元素绝对定位的时候,要注意其行高对静态位置带来的影响,明确设置定位的元素偏移位置。" },
-	"css_0057_suggestion": { "message": "如果在 非标准模式 中,需要父容器在仅有行内替换元素的情况下计算出包含文本基线高度的行高值,则必须加入其他行内文本元素。" },
-	"css_0058_suggestion": { "message": "给 MARQUEE 元素定义具体的宽度,保证各浏览器兼容。" },
-	"css_0059_suggestion": { "message": "同时设置 'overflow-x' 和 'overflow-y' 的值,不要出现其中之一为 'hidden' 时,而另一个是 'visible' 的情况;" },
-	"css_0060_suggestion": { "message": "根据实际需求可以去掉包含块的 'overflow:hidden' 或采用其他定位方案,避免在 IE 中触发此问题,实现在各浏览器表现一致。" },
-	"css_0061_suggestion": { "message": "此种情况时可以将 IE6 IE7 IE8(Q) 浏览器的 UL OL 标记样式更改为与其他浏览器样式一致,即:" },
-	"css_0062_suggestion": { "message": "为了避免此问题,建议在设置 'list-style-type:none' 时仅将 'list-style-position' 属性值设置为默认的 'outside'。 " },
-	"css_0063_suggestion": { "message": "建议弃使用 'disc | circle | square' 这三个样式,改用背景图片代替样式显示,如: " },
-	"css_0064_suggestion": { "message": "IFRAME 透明是一个常见的问题,有时候我们需要其透明,这时需要为 IFRAME 元素添加属性 allowtransparency=\"true\"," },
-	"css_0065_suggestion": { "message": "避免为非 BODY、HTML 元素设置 'background-attachment:fixed' 特性。" },
-	"css_0066_suggestion": { "message": "由于各浏览器对 '@font-face' 规则字体格式支持存在差异,若仅通过定义一个 '@font-face' 规则,可以通过 CSS hack 的方式在 IE、Firefox、Chrome、Safari、Opera 中得到相同的字体效果:" },
-	"css_0067_suggestion": { "message": "尽量给需要修饰的文本单独设置 'text-decoration' 特性;若需要给图片添加上划线或下划线,则使用 'border-top' 和 'border-bottom' 来模拟 'text-decoration'。" },
-	"css_0068_suggestion": { "message": "根据具体应用环境,参照“问题分析”中各种情况在各浏览器下的结果汇总使用兼容性较好的方案。" },
-	"css_0069_suggestion": { "message": "避免在包含宽度比自身小的块级元素或表格的元素上设置 'text-align' 特性。" },
-	"css_0070_suggestion": { "message": "不要在 TD 标记中使用 'white-space:nowrap' 样式,或者为 TABLE 标记设置 'table-layout:fixed' 样式严格计算其内部布局。" },
-	"css_0071_suggestion": { "message": "不要在包含块级元素的元素上使用 'text-overflow:ellipsis';" },
-	"css_0072_suggestion": { "message": "限制 TD 元素宽度并设置 'word-wrap' 特性值为默认值。" },
-	"css_0073_suggestion": { "message": "不在 SELECT 元素上设置 'word-wrap:break-word;'" },
-	"css_0074_suggestion": { "message": "删除不必要的空格。" },
-	"css_0075_suggestion": { "message": "避免在使用 px 单位时为 'letter-spacing' 特性设置小数数值。" },
-	"css_0076_suggestion": { "message": "选用合适的文档头,让该页面使用标准模式;如:" },
-	"css_0077_suggestion": { "message": "在 'table-layout:fixed' 这种固定布局算法下的表格中,可以为表格最后一列不设置宽度,尽量消除由算法差异带来的列的宽度差异。" },
-	"css_0078_suggestion": { "message": "在空的 TD、TH 元素内添加 ' ';" },
-	"css_0079_suggestion": { "message": "避免在 TD 元素内出现左浮动、内容为空的元素。" },
-	"css_0080_suggestion": { "message": "避免为 TR 设置高度。 " },
-	"css_0081_suggestion": { "message": "避免为 TD 元素设置 'padding-top' 'padding-bottom' 特性" },
-	"css_0082_suggestion": { "message": "尽量避免为 TABLE 级别的元素设置百分比为单位的 'height' 特性,以避免 Firefox 对 TABLE 级别元素的高度算法 Bug。" },
-	"css_0083_suggestion": { "message": "避免出现空单元格,使用 “ ” 代替空单元格。" },
-	"css_0084_suggestion": { "message": "在需要留空的单元格中加一个空格: " },
-	"css_0085_suggestion": { "message": "不要同时为 TABLE 元素及其内部各列显式设置宽度,当需要应用各列设定宽度时,TABLE 元素的宽度应保持默认的 \"auto\" ,当需要限定 TABLE的宽度时,应至少保证有一列的宽度为默认的  \"auto\" 。" },
-	"css_0086_suggestion": { "message": "使用标准模式——&lt;!DOCTYPE html&gt;;尽量避免在表格及块级元素上使用百分比高度。" },
-	"css_0087_suggestion": { "message": "使用 CSS 规范中定义的 'cursor:  pointer' 样式代替 'cursor:hand' 样式。" },
-	"css_0088_suggestion": { "message": "由于 'outline' 特性不影响盒模型及文本流,所以使用此特性时,在不支持的 IE6 IE7 IE8(Q) 中不会出现影响布局的兼容性问题,只会在较小的程度上影响视觉效果,暂时没有好的替代方案。" },
-	"css_0089_suggestion": { "message": "这是由于疏忽笔误造成的错误,首先应按照 W3C 规范中的标准写法定义 'font-family' 。而不能利用浏览器对错误代码的容错机制。" },
-	"css_0090_suggestion": { "message": "对于那些长度类型的 CSS 特性,在 CSS 代码及使用 DOM 操作相关样式时,应明确为其标注单位字符。 " },
-	"css_0091_suggestion": { "message": "合理运用各浏览器对 CSS 错误解析规则的差异及 CSS hack 为不同浏览器赋予不同样式。" },
-	"css_0092_suggestion": { "message": "坚持以字母开头命名选择器,这样可保证在所有浏览器下都能兼容。" },
-	"css_0093_suggestion": { "message": "如果页面需要精确到像素级的话,建议为宽高属性指定整型值。" },
-	"css_0094_suggestion": { "message": "尽量避免同时使用 'margin-left' 与 float:left,及 'margin-right' 与 float:right;" },
-	"css_0095_suggestion": { "message": "遵照 W3C 规范所描述的,为单元格元素设置 \"overflow:hidden\" ,在所有浏览器中均会裁切溢出单元格的内容。" },
-	"css_0096_suggestion": { "message": "明确地为 TD 的子元素设置 'white-space' 特性,避免使 TD 元素自动继承父元素的 'white-space' 特性。或者避免同时为 TD 元素设置宽度及 white-space:nowrap(或是 nowrap 属性)。" },
-	"css_0097_suggestion": { "message": "由于 TABLE 元素的 align 属性已经被 W3C 废弃,所以在考虑 TABLE 元素对齐问题上应避免使用 align 属性,而改用 CSS。" },
-	"css_0098_suggestion": { "message": "如果需要一个 A 元素能根据用户的行为改变样式,请添加 A 元素的 'href' 属性使伪类生效。" },
-	"css_0099_suggestion": { "message": "尽可能不为 BR 元素设定一个可能小于其继承值的值。" },
-	"css_0100_suggestion": { "message": "U、S 元素本身是废弃元素,应避免使用,而用 CSS 相关特性来替代这些元素产生的效果。" },
-	"css_0101_suggestion": { "message": "在使用 border-collapse:collapse 时应保证 TABLE 元素的 cellspacing 属性值为 0。" },
-	"css_0102_suggestion": { "message": "W3C 规范并没有说明 scrolling 属性应该控制子页面哪个元素的滚动条的生成或者 'overflow' 特性,为防止在某些情况下 Chrome Safari 的 IFRAME 子页面中出现多余滚动条,应避免为 HTML 或者 BODY 元素设置 overflow:scroll。" },
-	"css_0103_suggestion": { "message": "使用标准模式。" },
-	"css_0104_suggestion": { "message": "FONT 元素本身是废弃元素,并且其所有属性均已不推荐使用,应避免使用 FONT 元素。" },
-	"css_0105_suggestion": { "message": "若有页面需求是要求其他元素遮挡 Flash " },
-	"css_0106_suggestion": { "message": "align 属性已被废弃,应避免使用这类 HTML 属性。为 IMG、OBJECT、IFRAME、TABLE、APPLET、EMBED 元素使用 CSS 的 'float' 特性以达到相同的效果。" },
-	"css_0107_suggestion": { "message": "1. 明确为浮动元素设置一个宽度值,避免其在进行 \"shrink-to-fit\" 计算时在不同浏览器之间出现的宽度计算差异。" },
-	"css_0108_suggestion": {"message": "若需要在所有浏览器下元素图片不随元素内容滚动,则不给元素设置任何背景,并且在元素的父元素上设置背景图,例如:"},
-	"css_0109_suggestion": {"message": "给标签设置样式 -moz-user-select:none ;-webkit-user-select:none 同时标签设置 unselectable=\"on\" ,保证各浏览器都可以禁止内容选中。"},
-	"css_0110_suggestion": {"message": "'border-spacing' 不是所有浏览器都能很好的支持,若水平和垂直方向的空间相等,可以用 TABLE 的 cellspacing 属性代替 'border-spacing' 特性。"},
-	"css_0111_suggestion": { "message": "要对设置有百分比高度的块级元素的包含块设置明确的 height 属性值。" },
-	"css_0112_suggestion": { "message": "'border-spacing' 不是所有浏览器都能很好的支持,若水平和垂直方向的空间相等,可以用 TABLE 的 cellspacing 属性代替 'border-spacing' 特性。"},
-	"css_0113_suggestion": { "message": "避免出现空 TABLE ,保证各浏览器兼容。" },
-	"css_0114_suggestion": { "message": "不要将 'clear' 特性应用在浮动元素上,以免出现上述不兼容的问题。" },
-	"css_0115_suggestion": { "message": "如果希望一个浮动元素能阻挡与其向相同方向浮动的兄弟元素,请确保其高度不为零,以使页面布局在各浏览器中的表现一致。反之,如果不希望零高度的浮动元素阻挡其兄弟浮动元素,请隐藏该元素,如使用 'display:none'。但要注意,这样做也会使其可能存在的绝对定位的内容也不可见。" },
-	"css_0116_suggestion": { "message": "由于各浏览器对 'zoom' 的实现上存在差异,并且Firefox Opera 并不支持 'zoom',因此不建议为 'zoom' 设定值大于 1 的值来对元素进行缩放应用。" },
-	"css_0117_suggestion": { "message": "避免使用非法的 'float' 特性值。"},
-	"css_0118_suggestion": { "message": "在使用 \"marginwidth\" 和 \"marginheight\" 时应严格遵照规范中的描述,要对其设定大于等于零的整数值。"},
-	"css_0119_suggestion": { "message": "放弃使用 LAYER 元素,改用 CSS 中的绝对定位样式 'position:absloute' 处理相关情况。" },
-
-	"javascript_0001": { "message": "IE6 IE7 IE8 的函数声明和函数表达式的实现与其他浏览器有差异" },
-	"javascript_0002": { "message": "Firefox 对条件判断语句块内的函数声明的处理与其他浏览器有差异" },
-	"javascript_0003": { "message": "Safari Chrome 中用 for in 可以遍历出 Date Array String 对象中被更新的原型方法" },
-	"javascript_0004": { "message": "各浏览器中 Date 对象的 toLocalString 方法的返回值不一致" },
-	"javascript_0005": { "message": "IE6 IE7 IE8 会忽略 JavaScript 代码中大括号之后的第一个分号" },
-	"javascript_0006": { "message": "在 IE6 IE7 IE8(Q) 中不能在 JSON 直接量的最后一个键值对后加 ','" },
-	"javascript_0007": { "message": "IE6 IE7 IE8 不会忽略数组直接量的末尾空元素" },
-	"javascript_0008": { "message": "仅 IE 支持使用含中文标点符号的变量名" },
-	"javascript_0009": { "message": "元素的内联事件处理函数的特殊作用域在各浏览器中存在差异" },
-	"javascript_0010": { "message": "各浏览器中 Date 对象的 getYear 方法的返回值不一致" },
-	"javascript_0011": { "message": "IE6 IE7 IE8(Q) 中的 getElementById 方法能以 name 属性为参数获取某些元素" },
-	"javascript_0012": { "message": "IE6 IE7 IE8(Q) 中的 getElementById 方法的参数不区分大小写" },
-	"javascript_0013": { "message": "IE 在创建 DOM 树时会忽略某些空白字符" },
-	"javascript_0014": { "message": "各浏览器中的 NodeList 接口存在差异" },
-	"javascript_0015": { "message": "IE 中一个对象的 native 方法是跟该对象绑定的" },
-	"javascript_0016": { "message": "IE 混淆了 DOM 对象属性(property)及 HTML 标签属性(attribute),造成了对 setAttribute、getAttribute 的不正确实现" },
-	"javascript_0017": { "message": "IE 对 DOMImplementation 接口的支持程度停留在 DOM 1 Core 阶段" },
-	"javascript_0018": { "message": "已经被废弃的 DocumentLS 接口目前仅在 Firefox 和 Opera 下被部分支持" },
-	"javascript_0019": { "message": "Firefox 和 Opera 不支持 \"document.styleSheets\" 通过 STYLE 元素 id 获取 CSSStyleSheet 对象" },
-	"javascript_0020": { "message": "仅 IE 中的 createElement 方法支持传入 \"HTML String\" 做参数" },
-	"javascript_0021": { "message": "事件模型在各浏览器中存在差异" },
-	"javascript_0022": { "message": "IE6 IE7 IE8 中 getElementsByName 方法的参数不区分大小写" },
-	"javascript_0023": { "message": "各浏览器对于 document、document.body、document.documentElement 对象的 onscroll 事件的支持存在差异" },
-	"javascript_0024": { "message": "IE 标准模式中 BODY 元素的高度变化和 IE6(S) 中 BODY 元素的宽度变化会触发 window.onresize 事件" },
-	"javascript_0025": { "message": "各浏览器对于 DOM 对象的鼠标滚轮事件事件存在差异" },
-	"javascript_0026": { "message": "Firefox 不支持 DOM 对象的 insertAdjacentHTML 和 insertAdjacentText 方法" },
-	"javascript_0027": { "message": "Firefox 不支持 DOM 对象的 outerHTML、innerText、outerText 属性" },
-	"javascript_0028": { "message": "IE6 IE7 IE8(Q) 中 DOM 元素的 offsetParent 在某些情况下为距离其最近的触发了 hasLayout 的祖先级元素" },
-	"javascript_0029": { "message": "插入空白页面 IFRAME 元素时 Chrome Safari Opera 浏览器中会触发 load 事件" },
-	"javascript_0030": { "message": "仅 IE 和 Firefox 支持 window 对象的 onerror 事件" },
-	"javascript_0031": { "message": "各浏览器对元素在没有设置 tabindex 属性时触发 onfocus 事件以及通过其 focus() 方法获得焦点的情况有差异" },
-	"javascript_0032": { "message": "各浏览器对页面 onload 事件处理方式不一致" },
-	"javascript_0033": { "message": "IE Chrome Safari 在计算 'overflow' 特性值为 visible 的容器的 scrollHeight 的值时会考虑其内脱离了文本流的元素" },
-	"javascript_0034": { "message": "Firefox Safari 中对 cookie 中未经编码的中文汉字处理有问题" },
-	"javascript_0035": { "message": "不能保证带有 name 属性的 BUTTON / INPUT [ type = submit / button / image ] 元素的 value 属性值在所有浏览器中都可以被提交到服务端" },
-	"javascript_0036": { "message": "IE 未按预期方式处理 content-type 为 text/plain 的内容" },
-	"javascript_0037": { "message": "IE6 和 Chrome 未按预期方式处理 content-type 为 application/rss+xml 的内容" },
-	"javascript_0038": { "message": "WebKit 中依赖文本流定位的绝对定位元素某些情况下不会更新其位置" },
-	"javascript_0039": { "message": "Webkit 中 NOBR 标签与其他标签紧密相连在某些情况下会导致部分内容无法换行" },
-	"javascript_0040": { "message": "Webkit 浏览器会压缩 OPTION 元素中的全角空格" },
-	"javascript_0041": { "message": "Chrome 中文版配置文件中对最小字体限制为 12px" },
-	"javascript_0042": { "message": "Chrome Safari 中的表单在某些情况下不能够重复提交" },
-	"javascript_0043": { "message": "WebKit 中 MARQUEE 元素 的 behavior 属性值为 alternate 时如果其中包含块级元素则会影响其滚动效果" },
-	"javascript_0044": { "message": "WebKit 中 HR 元素 noshade 属性会影响其 'color' 特性" },
-	"javascript_0045": { "message": "只有 IE 的 TABLE、THEAD、TBODY、TFOOT 有 moveRow 方法" },
-	"javascript_0046": { "message": "只有 IE 支持 TABLE 元素的 'bordercolordark' 和 'bordercolorlight' 属性" },
-	"javascript_0047": { "message": "只有 IE 支持条件注释" },
-	"javascript_0048": { "message": "只有 IE 的脚本引擎支持 VBScript" },
-	"javascript_0049": { "message": "只有 IE 的脚本引擎支持 JScript.Encode" },
-	"javascript_0050": { "message": "Element.all 属性方法只有 IE 和 Opera 支持" },
-	"javascript_0051": { "message": "只有 IE 和 Opera 支持使用 currentStyle 获取 HTMLElement 的计算后的样式" },
-	"javascript_0052": { "message": "只有 IE 和 Opear 支持 FORM.item() 方法" },
-	"javascript_0053": { "message": "只有 IE 支持 CSS Expression" },
-	"javascript_0054": { "message": "只有 IE 支持 CSS Filter" },
-	"javascript_0055": { "message": "IE 的 external 对象提供的方法是 IE 特有的" },
-	"javascript_0056": { "message": "只有 IE 可以设置滚动条样式" },
-	"javascript_0057": { "message": "只有 IE 支持 'writing-mode' 特性" },
-	"javascript_0058": { "message": "只有 IE 和 Opera 支持 TABLE 元素的 cells 属性" },
-	"javascript_0059": { "message": "只有 IE 和 Opera 支持 document.frames" },
-	"javascript_0060": { "message": "只有 IE 支持 mouseenter 和 mouseleave 事件" },
-	"javascript_0061": { "message": "只有 IE 的脚本引擎支持 CollectGarbage ScriptEngine 等 JScript 特有的方法" },
-	"javascript_0062": { "message": "IE 中可以使用 ActiveXObject 创建 Automation 对象,同时该对象的方法及属性名称大小写不敏感" },
-	"javascript_0063": { "message": "只有 IE 支持 OBJECT 元素的 onerror 事件" },
-	"javascript_0064": { "message": "IE 中可以使用 classid 与 codebase 属性结合完成下载、安装和使用 ActiveX 插件" },
-	"javascript_0065": { "message": "IE 会忽略触发 hasLayout 的元素内尾部的全角空格" },
-	"javascript_0066": { "message": "只有 IE 支持 DHTML Behaviors 及相关方法" },
-	"javascript_0067": { "message": "只有 IE 支持 window.createPopup 方法" },
-	"javascript_0068": { "message": "在 IE 中被透明元素遮挡的元素仍能响应鼠标事件" },
-	"javascript_0069": { "message": "只有 IE 的 HTMLElement 有 mergeAttributes 与 clearAttributes 方法" },
-	"javascript_0070": { "message": "只有 IE 的 HTMLElement 有 applyElement 方法" },
-	"javascript_0071": { "message": "只有 IE 的 HTMLDOMNode 有 replaceNode 与 swapNode 方法" },
-	"javascript_0072": { "message": "只有 IE 支持 BGSOUND 标签" },
-	"javascript_0073": { "message": "仅 IE 和 Opera 支持 HTMLFrameElement 和 HTMLIFrameElement 的 document 属性" },
-	"javascript_0074": { "message": "只有 IE 和 Opera 的 HTMLDOMNode 有 removeNode 方法" },
-	"javascript_0075": { "message": "只有 IE 支持 XML 数据岛" },
-	"javascript_0076": { "message": "IE 中页面内 OBJECT 对象相对其他浏览器额外包含了其引入的 ActiveX 插件的部分私有属性" },
-	"javascript_0077": { "message": "Firefox 中块级元素高度或宽度过小会导致滚动条消失" },
-	"javascript_0078": { "message": "Firefox 不识别 type 或 language 被设置为 JScript 的 SCRIPT 标签" },
-	"javascript_0079": { "message": "Firefox 中 Date.now 方法被重写后 MARQUEE 元素不再滚动" },
-	"javascript_0080": { "message": "IE 支持使用 window.clipboardData 访问系统剪贴板,Chrome 和 Safari 中存在类似的 Clipboard 对象但尚未实现,Firefox 和 Opera 不支持这类对象" },
-	"javascript_0081": { "message": "document.all 在各浏览器中的支持不同" },
-	"javascript_0082": { "message": "特定的 URL 伪协议需安装提供该协议的特定软件才有效" },
-	"javascript_0083": { "message": "浏览器后退按钮在各浏览器中的行为在某种情况下不一致" },
-	"javascript_0084": { "message": "各浏览器对 TABLE、TH、TD 元素的 bordercolor 属性的处理有差异" },
-	"javascript_0085": { "message": "获取文档可视尺寸(视口)时在各浏览器中的参考元素有差异" },
-	"javascript_0086": { "message": "各浏览器对于获取文档水平及垂直方向滚动条位置(scrollLeft、scrollTop)时的参考元素存在差异" },
-	"javascript_0087": { "message": "各浏览器在宽度不够时不会对连续的全角空格进行折行处理存在差异" },
-	"javascript_0088": { "message": "各浏览器中对直接以 id 或者 name 属性值获取元素存在差异" },
-	"javascript_0089": { "message": "某些情况下除了 IE 和 Opera 之外的浏览器中 window.close 方法无法关闭由直接输入 URL 或 Ctrl + 点击链接方式打开的窗口" },
-	"javascript_0090": { "message": "动态引入的外部 JS 文件在各浏览器中的加载顺序不一致" },
-	"javascript_0091": { "message": "document.write 方式引入外部 JavaScript 文件导致脚本程序执行顺序不同以及 DOM 树更新延迟问题" },
-	"javascript_0092": { "message": "Webkit 浏览器中 TD 的 \"noWrap\" 属性会引起的它里面 MARQUEE 元素宽度计算错误" },
-	"javascript_0093": { "message": "非 IE 浏览器中为 MARQUEE 元素设置某些 CSS 特性会导致其失去滚动效果" },
-	"javascript_0094": { "message": "Chrome Safari 中设置了 'bordercolor' 属性的 TABLE 元素,会为其自动设置3px的边框宽度" },
-	"javascript_0095": { "message": "IE6 IE7 IE8 对 onreadystatechange 事件的扩充" },
-	"javascript_0096": { "message": "各浏览器对遇到中文标点符号时折行的处理有差异" },
-	"javascript_0097": { "message": "在 IE 中一些特殊字符的最终呈现字体不是设定值" },
-	"javascript_0098": { "message": "Firefox 不支持 DOM 元素 style 属性中的 pixel* 属性,并且某些情况下 Webkit 浏览器 pixel* 属性的返回值和 IE 中不同" },
-	"javascript_0099": { "message": "新版本的浏览器都已经不支持古老的 document.layers" },
-	"javascript_0100": { "message": "各浏览器对使用 document.id 和 document.name 获取对象的支持存在差异" },
-	"javascript_0101": { "message": "IE 和 Firefox 可以通过特定方法使 innerHTML 方法载入的 SCRIPT 标签中的 JavaScript 代码在页面加载后也可以执行" },
-	"javascript_0102": { "message": "IE8(S) Firefox Opera Chrome Safari 在某些情况下计算 MARQUEE 元素的宽度时会参考其子元素的宽度" },
-	"javascript_0103": { "message": "IE 和 Chrome 支持通过 EMBED 元素嵌入 MP3 格式文件" },
-	"javascript_0104": { "message": "判断浏览器类型或版本时使用的方法不当将导致代码不能按照预期的效果执行" },
-	"javascript_0105": { "message": "IE 中 location=\"\" 或 location.href=\"\" 将使页面跳转至当前页面所在根目录" },
-	"javascript_0106": { "message": "Opera 和 Chrome 对模态对话框(showModalDialog)的支持有缺陷,且非 IE 浏览器均不支持非模态对话框(showModelessDialog)" },
-	"javascript_0107": { "message": "溢出定位流的不可视元素,其包含块的滚动条生成差异" },
-	"javascript_0108": { "message": "IE8 标准模式及 Opera 不支持 WBR 元素" },
-	"javascript_0109": { "message": "各浏览器对 navigator 对象中几个与语言相关的属性的返回值存在差异" },
-	"javascript_0110": { "message": "各浏览器创建 XMLHttpRequest 对象的方式不同" },
-	"javascript_0111": { "message": "各浏览器对 window.execScript 方法的支持不同"},
-    "javascript_0112": { "message": "各浏览器下在向文档树中插入通过 cloneNode(true) 创建的节点时,其内的 SCRIPT 元素中的脚本执行有差异" },
-	"javascript_0113": { "message": "IE6 IE7 IE8 Opera 支持除 INPUT 和 BUTTON 元素以外的其他元素的 click 方法" },
-	"javascript_0114": {"message": "为 SELECT 对象增加或删除选项的方法在各浏览器中的支持情况不同"},
-	
-	
-	"javascript_0001_suggestion": { "message": "避免使用 IE 的这些“特性”,以保证兼容所有浏览器。" },
-	"javascript_0002_suggestion": { "message": "将条件语句中的函数声明替换为函数表达式,如:" },
-	"javascript_0003_suggestion": { "message": "对于数组,避免用 for...in 方式而采用索引即数字下标的形式枚举数组成员。" },
-	"javascript_0004_suggestion": { "message": "要获得相同格式的时间字符串,请不要使用 Date.prototype.toLocaleString() 方法,可以通过分别使用 getFullYear、getMonth、getDate 和 getDay 分别获得各关键字符串并拼装。" },
-	"javascript_0005_suggestion": { "message": "按照规范书写正确的代码。" },
-	"javascript_0006_suggestion": { "message": "即便规范没有强调最后一个键值对的后边不能出现 ',',也要确保最后一个键值对之后没有多余的 ',',以兼容各浏览器。" },
-	"javascript_0007_suggestion": { "message": "数组直接量的最后不要出现 ',',以保证兼容各浏览器。" },
-	"javascript_0008_suggestion": { "message": "避免在变量名(即标识符)中出现中文标点,以保证兼容各浏览器。" },
-	"javascript_0009_suggestion": { "message": "1. 尽量不要使用内联事件处理函数,使用 DOM 标准的事件注册方式为该元素注册事件处理函数,如:" },
-	"javascript_0010_suggestion": { "message": "要获得一个具体时间的年份,请不要使用 Date.prototype.getYear() 方法,使用 Date.prototype.getFullYear() 代替,以在各浏览器下获得相同的表现。" },
-	"javascript_0011_suggestion": { "message": "在使用 document.getElementById 方法获取页面元素时,应传入元素的 id 属性值,而不能使用元素的 name 属性值。" },
-	"javascript_0012_suggestion": { "message": "在使用 document.getElementById 获取页面元素时,应保证作为参数的 id 与目标元素的实际 id 值完全一致。" },
-	"javascript_0013_suggestion": { "message": "1. 没有必要时尽量去掉各标签之间的空白字符。" },
-	"javascript_0014_suggestion": { "message": "要从 NodeList 中获取元素,请使用 NodeList[index]、NodeList[name] 或 NodeList.item(index),以保证兼容各浏览器。" },
-	"javascript_0015_suggestion": { "message": "使用方式 1 达到简写一些 DOM 和 BOM 对象原生方法的目的。" },
-	"javascript_0016_suggestion": { "message": "避免使用 \"Element.setAttribute(\"style\", \"XXX\")\" 在所有浏览器中设置元素的 style 属性,可以改用符合规范的 \"Element.style.cssText = \"XXX\"\";" },
-	"javascript_0017_suggestion": { "message": "避免使用各浏览器支持程度不同的 DOMImplementation 的接口。" },
-	"javascript_0018_suggestion": { "message": "由于 W3C 推荐的标准中,已经放弃了 DocumentLS 接口,为更好的兼容各浏览器,建议采用 XMLHttpRequest 方式载入xml文件,示例代码如下:" },
-	"javascript_0019_suggestion": { "message": "避免使用 \"document.styleSheets\" 通过 STYLE 元素 id 获取 CSSStyleSheet 对象,使用 W3C 规范中的整数下标方式获取。" },
-	"javascript_0020_suggestion": { "message": "对于一般的非替换元素,在各浏览器中均使用 W3C 规范中的标准的为 createElement 方法传入标签名的做法。" },
-	"javascript_0021_suggestion": { "message": "1. 使用特性判断创建无兼容性问题的事件监听器绑定和解绑函数" },
-	"javascript_0022_suggestion": { "message": "在使用 document.getElementsByName 方法获取页面元素时,应保证作为参数的 name 与目标元素的实际 name 值完全一致。" },
-	"javascript_0023_suggestion": { "message": "在给整个浏览器窗口绑定滚动事件 (scroll) 的时候,绑定到 window 对象上。" },
-	"javascript_0024_suggestion": { "message": "1. 不期望触发 window.onresize 事件时:" },
-	"javascript_0025_suggestion": { "message": "利用浏览器类型判断,给各浏览器绑定各自支持的鼠标滚轮事件。如,res.html" },
-	"javascript_0026_suggestion": { "message": "在 Firefox 中,可通过扩展 HTMLElement 的原型 (prototype) 来实现这两个方法:" },
-	"javascript_0027_suggestion": { "message": "在 Firefox 中,可通过扩展 HTMLElement 的原型 (prototype) 来实现相关属性。" },
-	"javascript_0028_suggestion": { "message": "使得元素的 offsetParent 在各浏览器中一致。例如,对于以上测试样例,可考虑对于 DIV[id=\"div\"] 设置 position 为(absolute | relative | fixed)。" },
-	"javascript_0029_suggestion": { "message": "为 IFRAME 标签的 src 属性指定具体 URL 后再将节点插入 DOM 树中 。" },
-	"javascript_0030_suggestion": { "message": "放弃使用 window.onerror,通过合理使用 try-catch 来达到近似的效果。" },
-	"javascript_0031_suggestion": { "message": "对于一般常见的可视元素,若需要元素可触发 onfocus 事件以及通过其 focus() 方法获得焦点,则应为其设置 tabindex 属性。" },
-	"javascript_0032_suggestion": { "message": "统一为 window 对象的 onload 事件绑定函数,避免在 Firefox 中产生 document.body.onload 事件理解歧义。" },
-	"javascript_0033_suggestion": { "message": "确保读取 scrollHeight 属性的元素均创建了新的 block formatting context,或者此容器与内部子容器处于同一文档流中,以此避免各浏览器中读数不同。" },
-	"javascript_0034_suggestion": { "message": "在设置和读取 cookie 时,始终为字符进行编解码操作,推荐使用 \"encodeURIComponent\" 和 \"decodeURIComponent\" 方法做相应编解码工作。" },
-	"javascript_0035_suggestion": { "message": "通常情况下,服务器端不需要按钮的 key/value 信息,建议删除按钮的 name 属性,不使其成为 successful control。" },
-	"javascript_0036_suggestion": { "message": "这是由于 IE 浏览器的特有内容嗅探机制导致,故在不修改服务器端代码的情况下,无法通过常规办法解决此 IE 特性问题。更多关于此问题的资料参考官方信息:Internet Explorer 未按预期方式处理“Text/Plain”内容类型。" },
-	"javascript_0037_suggestion": { "message": "各浏览器对于 RSS 的支持及渲染方式为浏览器各自实现导致,故无法通过常规办法使各浏览器达到一致的效果,对于暂不支持 RSS 的浏览器应给予提示。Chrome 可通过安装扩展插件实现此功能。" },
-	"javascript_0038_suggestion": { "message": "在绝对定位元素的包含块可能发生改变时(即该绝对定位元素的“静态位置”可能发生变化时),不要让该绝对定位元素依赖其“静态位置”来定位,给该元素设定明确的 left、right、top 或 bottom 值。" },
-	"javascript_0039_suggestion": { "message": "&lt;div style=\"width:150px;background:gold\"&gt;" },
-	"javascript_0040_suggestion": { "message": "避免在 OPTION 元素中使用全角空格,必要时可使用两个 \" \" 代替。" },
-	"javascript_0041_suggestion": { "message": "避免使用小于 12px 的字号,过小的文字建议用图片代替。" },
-	"javascript_0042_suggestion": { "message": "每次提交表单后都修改 action 属性内的 URL(加入时间戳)。" },
-	"javascript_0043_suggestion": { "message": "在 MARQUEE 标记内避免使用普通流中的块级元素,或用脚本程序模拟 MARQUEE 标记的效果。" },
-	"javascript_0044_suggestion": { "message": "去除 HR 元素的 noshade 属性或者将 noshade 属性放置在 color 属性之前。" },
-	"javascript_0045_suggestion": { "message": "不要使用 IE 的特性设计页面代码。" },
-	"javascript_0046_suggestion": { "message": "这两个属性为 IE 私有属性,并且 MSDN 官方文档上也不再推荐使用它们。所以,若要达到这两个属性的效果,可以在 TABLE 和  TD 元素上使用 \"border-color\" 。" },
-	"javascript_0047_suggestion": { "message": "在没有特殊需求的情况下,尽量避免使用 IE 特有的条件注释。" },
-	"javascript_0048_suggestion": { "message": "避免使用 VBScript,使用 JavaScript 书写页面上的脚本。" },
-	"javascript_0049_suggestion": { "message": "避免使用 IE 特有脚本扩展方式书写代码。" },
-	"javascript_0050_suggestion": { "message": "使用 Node 接口的 'childNodes' 属性,注意在非 IE 浏览器中使用该属性会返回文本节点,使用 Node.nodeType 判断节点类型以获取想要的节点;" },
-	"javascript_0051_suggestion": { "message": "要获取元素的某种样式计算后的值,请考虑所有浏览器的兼容性情况。如使用以下代码给不支持 getComputedStyle 的 IE 提供与其他浏览器相同的函数:" },
-	"javascript_0052_suggestion": { "message": "当使用 item 方法遍历 HTMLCollection 时,只有在IE Opera 浏览器下可以直接获取 HTMLFormElement 元素内所有标记引用,其他浏览器并不支持这种方法。" },
-	"javascript_0053_suggestion": { "message": "CSS Expression 是一个已经过时的技术,微软官方日后可能不会再对其进行开发及支持。 除非为了利用 CSS hack 去弥补某些 IE 中不支持的 CSS 特性,否则应尽量避免使用 CSS Expression。" },
-	"javascript_0054_suggestion": { "message": "由于 CSS Filter 是 IE 特有的技术,其他浏览器均不支持,所以为了最大的兼容性及标准化,应尽量避免为 IE 单独使用 Filter,如需要其某些特效,应同时考虑其他浏览器,利用非 IE 浏览器对 CSS3 草案的良好支持保证最好的兼容性。" },
-	"javascript_0055_suggestion": { "message": "不要使用 IE 的特性设计页面代码。" },
-	"javascript_0056_suggestion": { "message": "尽量不要去修改滚动条的样式。以免造成 IE 跟其他浏览器之间的差异。" },
-	"javascript_0057_suggestion": { "message": "其他浏览器中不支持文字的垂直排列,所以,尽量避免使用 IE 中特有的 'writing-mode' 特性。" },
-	"javascript_0058_suggestion": { "message": "避免使用 TABLE 的 cells 属性获取元素,而对 table row(TR) 使用 cells 属性,或用 document.getElementsByTagName 等方式获取元素。" },
-	"javascript_0059_suggestion": { "message": "用 window.frames 代替 document.frames。" },
-	"javascript_0060_suggestion": { "message": "1. 避免使用 mouseenter 和 mouseleave 事件。" },
-	"javascript_0061_suggestion": { "message": "避免使用此类由浏览器厂商提供的私有方法和扩展。" },
-	"javascript_0062_suggestion": { "message": "为了最好的兼容性,应尽量避免使用这种技术,或者在非 IE 浏览器中使用其他的替代技术,如 NPAPI 插件。" },
-	"javascript_0063_suggestion": { "message": "避免使用非标准的 onerror 事件。对于 OBJECT 元素可以通过对客户端浏览器类型进行判断,来达到有好地提示错误的效果。" },
-	"javascript_0064_suggestion": { "message": "ActiveX 为微软专有技术,仅被 Windows 中的 IE 支持。所以若希望这类组件可以运行于多种浏览器,则需要使用其他非 IE 支持的 NPAPI 重新设计在这种接口下的插件。以保证最好的兼容性。" },
-	"javascript_0065_suggestion": { "message": "避免使用全角空格占位,使用“ ”代替全角空格。" },
-	"javascript_0066_suggestion": { "message": "1. 避免使用 IE 的特性。" },
-	"javascript_0067_suggestion": { "message": "尽量不要使用 IE 的特性,必须使用弹出窗口时,可以通过脚本使用兼容各浏览器的方式来实现。" },
-	"javascript_0068_suggestion": { "message": "如果想遮盖某些元素,请为其设置一个非 'transparent' 的背景色。如果需要让这个遮掩层“隐形”,使用 'opacity:0' 和 'filter:alpha(opacity=0)' 来实现。" },
-	"javascript_0069_suggestion": { "message": "尽量避免使用 IE 独有的这两个方法编写代码,改用标准方法 setAttribute 和 removeAttribute 单独设置和删除属性来实现需求。" },
-	"javascript_0070_suggestion": { "message": "尽量避免使用 applyElement 方法编写代码,可以使用标准方法实现同样功能,如 appendChild、insertBefore、removeChild、replaceChild、cloneChild 等。" },
-	"javascript_0071_suggestion": { "message": "不要使用 IE 的特性设计页面代码。" },
-	"javascript_0072_suggestion": { "message": "如果背景音乐是必须的,请考虑使用被广泛使用的 Flash 插件来实现,以兼容所有浏览器。" },
-	"javascript_0073_suggestion": { "message": "使用 HTMLFrameElement 或 HTMLIFrameElement 对象的 contentWindow 属性得到该框架页的 window 对象应用,再访问其下的 document 对象。" },
-	"javascript_0074_suggestion": { "message": "不要使用 IE 的特性设计页面代码。" },
-	"javascript_0075_suggestion": { "message": "应考虑其他标准的、符合 W3C 规范的、各浏览器均支持的 XML 相关技术解析 XML 文档。" },
-	"javascript_0076_suggestion": { "message": "ActiveX 技术为微软的专有技术,仅在Windows 下的 IE 浏览器中可以得到支持。" },
-	"javascript_0077_suggestion": { "message": "针对 Firefox 中的这种特有现象,在确定不需要滚动条时为元素设置 'overflow:hidden',并且合理地为需要携带滚动条元素设置其宽度及高度。" },
-	"javascript_0078_suggestion": { "message": "为了保证脚本程序可以正常执行,除非特意使用仅 IE 支持的 VBScript 和 Script Encoder 机制外,应当将 SCRIPT 标记的 \"type\" 属性设置为 \"javascript\",并且不要设置已经废弃的 \"Languange\" 属性。" },
-	"javascript_0079_suggestion": { "message": "MARQUEE 元素为非 W3C 标准元素,应尽量避免使用。同时应尽可能不对原生的 JavaScript 进行修改甚至删除。" },
-	"javascript_0080_suggestion": { "message": "判断浏览器类型,若不是 IE 则弹出提示,告诉用户当前浏览器不能访问剪贴板。如:" },
-	"javascript_0081_suggestion": { "message": "由于 document.all 方法存在支持程度问题,获取元素还是推荐用 W3C DOM 规范中提供的 document.getElementById、document.getElementsByTagName 等标准方法。" },
-	"javascript_0082_suggestion": { "message": "某些协议是由特定的厂商自定义的,如:\"tencent://\" 和 \"msnim:chat\" 之类,需安装特定的客户端软件以注册该类伪协议,方可正常使用。例如,安装 QQ 或 MSN 应用程序。" },
-	"javascript_0083_suggestion": { "message": "以上所提到的情况,我们应该尽量避免,我们期望随着各浏览器新版本的发布,相应的问题也能得到修复,如 Chrome 的新版本就修复了 IFRAME 地址改变不记录历史记录的问题。" },
-	"javascript_0084_suggestion": { "message": "在 W3C HTML4 属性表中没有找到 bordercolor 属性,可见这并不是 W3C 规范中的标准属性,各浏览器根据各自的理解实现了该属性的渲染方式,应避免使用这种非标准属性。" },
-	"javascript_0085_suggestion": { "message": "当出现页面垂直滚动条的时候尽量使用 'document.body.scrollHeight' 获取页面滚动条的高度。" },
-	"javascript_0086_suggestion": { "message": "使用 \"||\" 逻辑语句将这两种获取方式相连,以保证兼容性。如:" },
-	"javascript_0087_suggestion": { "message": "使用两个半角空格代替一个全角空格。" },
-	"javascript_0088_suggestion": { "message": "通过上面的测试可以看到,仅仅在通过 name 属性获取 IFRAME 和 FRAME 元素内子页面的 window 对象时,所有浏览器没有差异。" },
-	"javascript_0089_suggestion": { "message": "使用 window.close() 来关闭窗口时,需注意以下两点:" },
-	"javascript_0090_suggestion": { "message": "对于必须动态附加到文档的外部 js 文件,要保证动态引入的脚本全部执行完成后,才能执行后续代码。" },
-	"javascript_0091_suggestion": { "message": "如果外部引用的 JavaScript 文件内程序,要求在执行顺序上一致,请避免使用 document.write 语句引入的 JS 程序文件中再次使用他来加载外部 JavaScript 文件。" },
-	"javascript_0092_suggestion": { "message": "HTML 4.01 中表示 \"nowrap\" 属性已经被废弃,使用 'white-space' 样式代替他 。" },
-	"javascript_0093_suggestion": { "message": "首先 MARQUEE 元素为非 W3C 标准元素,应尽量避免使用。若必须使用,则应尽量避免为 MARQUEE 元素设置其默认值之外的 'overflow'、 'display'、'-moz-bind' 特性值。" },
-	"javascript_0094_suggestion": { "message": "避免给 TABLE 设置 'bordercolor' ,使用 CSS 中 'border' 实现其效果,保证各浏览器兼容。" },
-	"javascript_0095_suggestion": { "message": "在现行 W3C 标准规范中仅有 XmlHttpRequest 对象中存在 onreadystatechange 事件 ( 请参考  XMLHttpRequest 规范 )。" },
-	"javascript_0096_suggestion": { "message": "对段落文字排版时须谨慎,注意标点符号的位置。如果不好控制文字内容,可以使用Javascript根据文字内容所占的高度动态缩改文字内容。" },
-	"javascript_0097_suggestion": { "message": "针对问题 1,避免使用可以触发这种特殊现象的字体,如 \"宋体\" ,为特殊字符设置其他字体。" },
-	"javascript_0098_suggestion": { "message": "避免使用 IE6 IE7 IE8  浏览器私有 DOM 属性值,改用标准 DOM 规范中定义的有度量单位的 \"width \"、\"height\"、\"top\"、 \"right\"、 \"left\"、 \"bottom\" 属性来代替它们。" },
-	"javascript_0099_suggestion": { "message": "用其他方式判断浏览器类型,避免使用 document.layers 属性;" },
-	"javascript_0100_suggestion": { "message": "使用 W3C 标准中的 document.getElementById(id) 方法获取对象。" },
-	"javascript_0101_suggestion": { "message": "上面提到的 IE 及 Firefox 中使通过 innerHTML 方法动态插入的 SCRIPT 元素中脚本执行的方法均比较特殊,是利用了浏览器的 Bug,或者是利用了浏览器提供的特性。而 innerHTML 方法只是用来插入 HTML 代码,并不能使其中包含的脚本代码执行。" },
-	"javascript_0102_suggestion": { "message": "总是为 MARQUEE 元素显式地设定一个宽度。" },
-	"javascript_0103_suggestion": { "message": "在 HTML5 尚未制定完成且普及时,使用 Flash 播放 MP3 文件,以保证最大的兼容性。" },
-	"javascript_0104_suggestion": { "message": "浏览器类型或版本判别推荐采用分析 navigator.userAgent 属性的方式。" },
-	"javascript_0105_suggestion": { "message": "在为 location 或 location.href 赋值做页面跳转时必须保证所赋的字符串地址的合法性,不要使用空字符串。" },
-	"javascript_0106_suggestion": { "message": "showModalDialog 方法与 showModelessDialog 方法均不被 W3C 支持,虽然 showModalDialog 方法目前已比较广泛的被支持,但还是应避免使用它。因为模态对话框会阻塞父窗口的输入,使其是去焦点,这将对用户体验不利。" },
-	"javascript_0107_suggestion": { "message": "避免在定位流内出现不可视元素且溢出父容器。" },
-	"javascript_0108_suggestion": { "message": "1. 可以为WBR元素添加一个 :after 伪元素,强迫其后插入一个软换行符。" },
-	"javascript_0109_suggestion": { "message": "可以使用下面的代码获取当前浏览器语言:" },
-	"javascript_0110_suggestion": { "message": "判断浏览器版本,使用不同的 XMLHttpRequest 对象创建方式。如:" },
-	"javascript_0111_suggestion": { "message": "window.execScript 方法不是所有浏览器都支持,同时所支持的脚本语言种类并不尽相同,请确定在目标浏览器均支持此方法的情况下再使用它。"},
-	"javascript_0112_suggestion": { "message": "避免深度复制 cloneNode(true) 包含 SCRIPT 元素的节点。" },
-	"javascript_0113_suggestion": { "message": "建议尽量避免对除 INPUT 和 BUTTON 元素以外的其他元素通过 click 方法模拟鼠标点击事件。" },
-	"javascript_0114_suggestion": { "message": "1. 在添加 OPTION 元素时,如果需要向指定索引前插入 OPTION,可以使用 options.add(option, index)。" }
-	
-}

+ 0 - 12
apps/ajax-debugger/index.html

@@ -1,12 +0,0 @@
-<!doctype html>
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <title>Ajax接口调试</title>
-    <script src="../static/vendor/require/require.js"></script>
-    <script src="index.js"></script>
-</head>
-<body>
-
-</body>
-</html>

+ 0 - 120
apps/ajax-debugger/index.js

@@ -1,120 +0,0 @@
-/**
- * Ajax接口调试功能
- *
- * 原理:
- *
- *  devtools → background-page → content-page → console.*
- *
- * @author [email protected]
- */
-let AjaxDebugger = (function () {
-
-    let MSG_TYPE = Tarp.require('../static/js/msg_type');
-
-    /**
-     * 自定义Console
-     */
-    let FeHelperConsole = (function () {
-        let Types = 'log,debug,info,warn,error,group,groupCollapsed,groupEnd'.split(',');
-
-        let sendMessage = function (type, format, args) {
-            chrome.runtime.sendMessage({
-                type: MSG_TYPE.AJAX_DEBUGGER_CONSOLE,
-                content: escape(JSON.stringify(Array.prototype.slice.call(arguments, 0)))
-            });
-        };
-
-        let that = new Function();
-        Types.forEach(function (tp) {
-            that[tp] = sendMessage.bind(that, tp);
-        });
-
-        return that;
-    })();
-
-    /**
-     * 分析Request
-     * @param request
-     */
-    let analyticRequest = function (request) {
-        let url = request.request.url || "",
-            urlSeparator = (url.indexOf("?") > -1) ? "&" : "?",
-            requestParams = (request.request.postData && request.request.postData.params) || [],
-            responseStatus = request.response.status + " " + request.response.statusText,
-            responseSize = request.response.bodySize + request.response.headersSize;
-
-        let queryString = '';
-        requestParams.forEach(function (param, index) {
-            queryString += (index === 0 ? urlSeparator : '&') + param.name + '=' + param.value;
-        });
-
-        let requestPath = '/' + (url.split('?') || [''])[0].replace('://', '').split('/').splice(1).join('/');
-        let responseTime = request.time > 1000 ? Math.ceil(request.time / 1000) + 's' : Math.ceil(request.time) + 'ms';
-        responseSize = responseSize > 1024 ? Math.ceil(responseSize / 1024) + 'KB' : Math.ceil(responseSize) + 'B';
-
-        // 获取Response的数据
-        request.getContent(function (content, encoding) {
-
-            if (content) {
-                try {
-                    request.response.responseData = JSON.parse(content);
-                }
-                catch (e) {
-                    request.response.responseData = content;
-                }
-            }
-
-            let header = "Ajax请求加载完毕 (" + [requestPath, responseStatus, responseTime, responseSize].join(" - ") + ") " + ' -- By FeHelper';
-
-            FeHelperConsole.group(header);
-            FeHelperConsole.log('AjaxURL  :', {url: url + queryString});
-            FeHelperConsole.log("Request  :", {request: request.request});
-            FeHelperConsole.log("Response :", {response: request.response});
-            FeHelperConsole.log("OtherInfo:", {
-                timeConsuming: responseTime,
-                timings: request.timings,
-                time: request.startedDateTime,
-                server: request.serverIPAddress
-            });
-            FeHelperConsole.groupEnd();
-        });
-    };
-
-
-    /**
-     * 监控Network中的请求
-     */
-    chrome.devtools.network.onRequestFinished.addListener(function (request) {
-
-        let reqUrl = request.request.url.split('?')[0];
-        if (/\.js$/.test(reqUrl)) {
-            return false;
-        }
-        let isXHR = /\.json$/.test(reqUrl) || (request.request.headers.concat(request.response.headers)).some(function (header) {
-            return (
-                (header.name === "X-Requested-With" && header.value === "XMLHttpRequest") ||
-                (header.name === "Content-Type" && (
-                    header.value === "application/x-www-form-urlencoded" ||
-                    /application\/json/.test(header.value) ||
-                    /application\/javascript/.test(header.value) ||
-                    /text\/javascript/.test(header.value)
-                ))
-            );
-        });
-
-        if (isXHR) {
-            chrome.runtime.sendMessage({
-                type: MSG_TYPE.AJAX_DEBUGGER_SWITCH
-            }, function (debuggerSwitchOn) {
-                debuggerSwitchOn && analyticRequest(request);
-            });
-        }
-
-    });
-
-    // 与background保持心跳
-    chrome.runtime.connect({
-        name: MSG_TYPE.DEV_TOOLS
-    });
-
-})();

+ 111 - 0
apps/background/crx-download.js

@@ -0,0 +1,111 @@
+/**
+ * FeHelper:从chrome webstore下载extension文件的工具
+ * @author zhaoxianlie
+ */
+
+import MSG_TYPE from '../static/js/common.js';
+
+export default (function () {
+
+    /**
+     * 检测Google chrome服务能不能访问,在2s内检测心跳
+     * @param success
+     * @param failure
+     */
+    let detectGoogleDotCom = function (success, failure) {
+        Promise.race([
+            fetch('https://clients2.google.com/service/update2/crx'),
+            new Promise(function (resolve, reject) {
+                setTimeout(() => reject(new Error('request timeout')), 2000)
+            })])
+            .then((data) => {
+                success && success();
+            }).catch(() => {
+            failure && failure();
+        });
+    };
+
+    /**
+     * 从google官方渠道下载chrome扩展
+     * @param crxId 需要下载的extension id
+     * @param crxName 扩展名称
+     * @param callback 下载动作结束后的回调
+     */
+    let downloadCrxFileByCrxId = function (crxId, crxName, callback) {
+        detectGoogleDotCom(() => {
+            // google可以正常访问,则正常下载
+            let url = "https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D"
+                + crxId + "%26uc&prodversion=" + navigator.userAgent.split("Chrome/")[1].split(" ")[0];
+            if (!chrome.downloads) {
+                let a = document.createElement('a');
+                a.href = url;
+                a.download = crxName || (crxId + '.crx');
+                (document.body || document.documentElement).appendChild(a);
+                a.click();
+                a.remove();
+            } else {
+                chrome.downloads.download({
+                    url: url,
+                    filename: crxName || crxId,
+                    conflictAction: 'overwrite',
+                    saveAs: true
+                }, function (downloadId) {
+                    if (chrome.runtime.lastError) {
+                        alert('抱歉,下载失败!错误信息:' + chrome.runtime.lastError.message);
+                    }
+                });
+            }
+        }, () => {
+            // google不能正常访问
+            callback ? callback() : alert('抱歉,下载失败!');
+        });
+
+    };
+
+    /**
+     * 从chrome webstore下载crx文件
+     * 在chrome extension详情页使用
+     */
+    let downloadCrxFileFromWebStoreDetailPage = function (callback) {
+
+        chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
+            let tab = tabs[0];
+            let crxId = tab.url.split("/")[6].split('?')[0];
+            let crxName = tab.title.split(" - Chrome")[0] + ".crx";
+            crxName = crxName.replace(/[&\/\\:"*<>|?]/g, '');
+
+            downloadCrxFileByCrxId(crxId, crxName, callback);
+        });
+    };
+
+    /**
+     * 通过右键菜单下载或者分享crx
+     * @param tab
+     * @private
+     */
+    let _downloadCrx = function (tab) {
+        let isWebStoreDetailPage = tab.url.indexOf('https://chrome.google.com/webstore/detail/') === 0;
+        if (isWebStoreDetailPage) {
+            // 如果是某个chrome extension的详情页面了,直接下载当前crx文件
+            downloadCrxFileFromWebStoreDetailPage(() => {
+                alert('下载失败,可能是当前网络无法访问Google站点!');
+            });
+        } else {
+            // 否则,下载FeHelper并分享出去
+            if (confirm('下载最新版【FeHelper.JSON】并分享给其他小伙伴儿,走你~~~')) {
+                let crxId = MSG_TYPE.STABLE_EXTENSION_ID;
+                let crxName = chrome.runtime.getManifest().name + '-latestVersion.crx';
+
+                downloadCrxFileByCrxId(crxId, crxName, () => {
+                    chrome.tabs.create({
+                        url: MSG_TYPE.DOWNLOAD_FROM_GITHUB
+                    });
+                });
+            }
+        }
+    };
+
+    return {
+        downloadCrx: _downloadCrx
+    };
+})();

+ 0 - 22
apps/background/detect.js

@@ -1,22 +0,0 @@
-/**
- * 检测浏览器是否支持部分ES6的语法,比如 let、=> 之类
- * FeHelper坚持一个原则:不在低版本的Chrome上运行
- * @returns {boolean}
- * @private
- */
-var _browserSupport = function () {
-    var support = true;
-    try {
-        new Function('let a = () => {}');
-    } catch (e) {
-        support = false;
-    }
-    return support;
-};
-if (!_browserSupport()) {
-    chrome.browserAction.onClicked.addListener(function () {
-        alert('检测到当前浏览器版本较低,FeHelper可能无法正常运行,建议升级到最新版浏览器以正常使用,谢谢!');
-    });
-    chrome.browserAction.setBadgeText({text: '异常'});
-    chrome.browserAction.setPopup({popup: ''});
-}

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 274 - 757
apps/background/index.js


+ 142 - 0
apps/background/menu.js

@@ -0,0 +1,142 @@
+/**
+ * FeHelper 右键菜单管理
+ * @type {{manage}}
+ * @author zhaoxianlie
+ */
+
+import CrxDownloader from './crx-download.js';
+import Awesome from '../dynamic/awesome.js';
+
+export default (function () {
+
+    let FeJson = {
+        contextMenuId:"fhm_main"
+    };
+
+    // 邮件菜单配置项
+    let defaultMenuOptions = {
+        'download-crx': {
+            icon: '♥',
+            text: '插件下载分享',
+            onClick: function (info, tab) {
+                CrxDownloader.downloadCrx(tab);
+            }
+        },
+        'fehelper-setting': {
+            icon: '❂',
+            text: 'FeHelper设置',
+            onClick: function (info, tab) {
+                chrome.runtime.openOptionsPage();
+            }
+        }
+    };
+
+    /**
+     * 创建一个menu 菜单
+     * @param toolName
+     * @param menuList
+     * @returns {boolean}
+     * @private
+     */
+    let _createItem = (toolName, menuList) => {
+        menuList && menuList.forEach && menuList.forEach(menu => {
+            let _menu_id = 'fhm_c' + escape(menu.text).replace(/\W/g,'');
+            chrome.contextMenus.create({
+                id: _menu_id,
+                title: menu.icon + '  ' + menu.text,
+                contexts: menu.contexts || ['all'],
+                parentId: FeJson.contextMenuId
+            });
+
+            chrome.contextMenus.onClicked.addListener(((tName,mId,mFunc) => (info, tab) => {
+                if(info.menuItemId === mId) {
+                    if(mFunc) {
+                        mFunc(info,tab);
+                    }else{
+                        chrome.DynamicToolRunner({
+                            query: `tool=${tName}`
+                        });
+                    }
+                }
+            })(toolName,_menu_id,menu.onClick));
+        });
+    };
+
+    /**
+     * 绘制一条分割线
+     * @private
+     */
+    let _createSeparator = function () {
+        chrome.contextMenus.create({
+            id: 'fhm_s' + Math.ceil(Math.random()*10e9),
+            type: 'separator',
+            parentId: FeJson.contextMenuId
+        });
+    };
+
+    /**
+     * 创建扩展专属的右键菜单
+     */
+    let _createContextMenu = function () {
+        _removeContextMenu();
+        chrome.contextMenus.create({
+            id: FeJson.contextMenuId,
+            title: "FeHelper",
+            contexts: ['page', 'selection', 'editable', 'link', 'image'],
+            documentUrlPatterns: ['http://*/*', 'https://*/*', 'file://*/*']
+        });
+
+        // 绘制用户安装的菜单,放在前面
+        Awesome.getInstalledTools().then(tools => {
+            let allMenus = Object.keys(tools).filter(tool => tools[tool].installed && tools[tool].menu);
+            let onlineTools = allMenus.filter(tool => tool !== 'devtools' && !tools[tool].hasOwnProperty('_devTool'));
+            let devTools = allMenus.filter(tool => tool === 'devtools' || tools[tool].hasOwnProperty('_devTool'));
+
+            // 绘制FH提供的工具菜单
+            onlineTools.forEach(tool => _createItem(tool, tools[tool].menuConfig));
+            // 如果有本地工具的菜单需要绘制,则需要加一条分割线
+            devTools.length && _createSeparator();
+            // 绘制本地工具的菜单
+            devTools.forEach(tool => _createItem(tool, tools[tool].menuConfig));
+        });
+
+        // 绘制两个系统提供的菜单,放到最后
+        let sysMenu = ['download-crx', 'fehelper-setting'];
+        let arrPromises = sysMenu.map(menu => Awesome.menuMgr(menu, 'get'));
+        Promise.all(arrPromises).then(values => {
+            let needDraw = String(values[0]) === '1' || String(values[1]) !== '0';
+
+            // 绘制一条分割线
+            _createSeparator();
+
+            // 绘制菜单
+            String(values[0]) === '1' && _createItem(sysMenu[0], [defaultMenuOptions[sysMenu[0]]]);
+            String(values[1]) !== '0' && _createItem(sysMenu[1], [defaultMenuOptions[sysMenu[1]]]);
+        });
+    };
+
+    /**
+     * 移除扩展专属的右键菜单
+     */
+    let _removeContextMenu = function () {
+        chrome.contextMenus.removeAll();
+    };
+
+    /**
+     * 创建或移除扩展专属的右键菜单
+     */
+    let _createOrRemoveContextMenu = function (_settings) {
+        _settings.getOptions((opts) => {
+            console.log(String(opts['OPT_ITEM_CONTEXTMENUS']))
+            if (String(opts['OPT_ITEM_CONTEXTMENUS']) !== 'false') {
+                _createContextMenu();
+            } else {
+                _removeContextMenu();
+            }
+        });
+    };
+
+    return {
+        manage: _createOrRemoveContextMenu
+    };
+})();

+ 0 - 132
apps/background/network.js

@@ -1,132 +0,0 @@
-/**
- * 后端网络处理,ajax等
- * @author zhaoxianlie
- */
-module.exports = (() => {
-    /**
-     * 通过这个方法来读取服务器端的CSS文件内容,要这样做,前提是在manifest.json中配置permissions为:http://
-     * @param {String} link 需要读取的css文件
-     * @param {Function} callback 回调方法,格式为:function(respData){}
-     * @config {Object} respData 输出到客户端的内容,格式为{success:BooleanValue,content:StringValue}
-     * @return {Undefined} 无返回值
-     */
-    let _readFileContent = function (link, callback) {
-        //创建XMLHttpRequest对象,用原生的AJAX方式读取内容
-        let xhr = new XMLHttpRequest();
-        //处理细节
-        xhr.onreadystatechange = function () {
-            //后端已经处理完成,并已将请求response回来了
-            if (xhr.readyState === 4) {
-                //输出到客户端的内容,格式为{success:BooleanValue,content:StringValue}
-                let respData;
-                //判断status是否为OK
-                if (xhr.status === 200 && xhr.responseText) {
-                    //OK时回送给客户端的内容
-                    respData = {
-                        success: true,	//成功
-                        path: link,	//文件路径
-                        content: xhr.responseText	//文件内容
-                    };
-                } else {	//失败
-                    respData = {
-                        success: false,	//失败
-                        path: link,	//文件路径
-                        content: "FcpHelper can't load such file."	//失败信息
-                    };
-                }
-                //触发回调,并将结果回送
-                callback(respData);
-            }
-        };
-        //打开读通道
-        xhr.open('GET', link, true);
-        //设置HTTP-HEADER
-        xhr.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
-        xhr.setRequestHeader("Access-Control-Allow-Origin", "*");
-        //开始进行数据读取
-        xhr.send();
-    };
-
-    /**
-     * 从cookie中获取url
-     * @param {Object} cookie
-     */
-    let _urlFromCookie = function (cookie) {
-        return "http" + (cookie.secure ? "s" : "") + "://" + cookie.domain + cookie.path;
-    };
-
-    /**
-     * 获取页面上的所有cookie
-     * @param {Object} callback
-     */
-    let _getCookies = function (request, callback) {
-        let arrCookies = [];
-        chrome.cookies.getAll({}, function (cookies) {
-            for (let i = 0, le = cookies.length; i < le; i++) {
-                if (request.url.indexOf(cookies[i].domain.substring(1)) > -1) {
-                    cookies[i].url = _urlFromCookie(cookies[i]);
-                    arrCookies.push(cookies[i]);
-                }
-            }
-
-            //排序
-            cookies.sort(function (a, b) {
-                return a.domain.localeCompare(b.domain);
-            });
-
-            callback.call(null, {
-                cookie: arrCookies
-            });
-        });
-    };
-
-    /**
-     * 移除某个cookie
-     * @param {Object} request
-     * @param {Object} callback
-     */
-    let _removeCookie = function (request, callback) {
-        chrome.cookies.getAll({}, function (cookies) {
-            for (let i = 0, le = cookies.length; i < le; i++) {
-                let url = _urlFromCookie(cookies[i]);
-                let name = cookies[i].name;
-                if (url == request.url && name == request.name) {
-                    chrome.cookies.remove({"url": url, "name": name});
-                    if (callback && typeof callback == "function") {
-                        callback.call(null);
-                    }
-                    return;
-                }
-            }
-        });
-    };
-
-    /**
-     * 设置某个cookie
-     * @param {Object} request
-     * @param {Object} callback
-     */
-    let _setCookie = function (request, callback) {
-        chrome.cookies.getAll({}, function (cookies) {
-            for (let i = 0, le = cookies.length; i < le; i++) {
-                let url = _urlFromCookie(cookies[i]);
-                let name = cookies[i].name;
-                if (url == request.url && name == request.name) {
-                    chrome.cookies.set(request);
-                    if (callback && typeof callback == "function") {
-                        callback.call(null);
-                    }
-                    return;
-                }
-            }
-        });
-    };
-
-    return {
-        readFileContent: _readFileContent,
-        getCookies: _getCookies,
-        removeCookie: _removeCookie,
-        setCookie: _setCookie
-    };
-})();
-

+ 0 - 132
apps/code-beautify/automatic.js

@@ -1,132 +0,0 @@
-module.exports = (() => {
-
-    let formattedCodes = '';
-
-    /**
-     * 代码美化
-     */
-    let format = (fileType, source, callback) => {
-
-        let beauty = txtResult => {
-
-            formattedCodes = txtResult;
-            txtResult = txtResult.replace(/>/g, '&gt;').replace(/</g, '&lt;');
-            txtResult = '<pre class="language-' + fileType.toLowerCase() + ' line-numbers"><code>' + txtResult + '</code></pre>';
-            $('#fehelper_tips').siblings().remove().end().after(txtResult);
-
-            Tarp.require('../static/vendor/prism/prism.js', true).then(Prism => {
-                Prism.highlightAll();
-                callback && callback();
-            });
-
-        };
-
-        switch (fileType) {
-            case 'javascript':
-                let opts = {
-                    brace_style: "collapse",
-                    break_chained_methods: false,
-                    indent_char: " ",
-                    indent_scripts: "keep",
-                    indent_size: "4",
-                    keep_array_indentation: true,
-                    preserve_newlines: true,
-                    space_after_anon_function: true,
-                    space_before_conditional: true,
-                    unescape_strings: false,
-                    wrap_line_length: "120"
-                };
-                Tarp.require('../code-beautify/beautify.js');
-                beauty(js_beautify(source, opts));
-                break;
-            case 'css':
-                Tarp.require('../code-beautify/beautify-css.js');
-                css_beautify(source, {}, resp => beauty(resp));
-                break;
-        }
-
-    };
-
-    /**
-     * 检测
-     * @returns {boolean}
-     */
-    let detect = (fileType) => {
-
-        let source = document.body.textContent;
-
-        let cssUrl = chrome.extension.getURL('code-beautify/automatic.css');
-        $('<link href="' + cssUrl + '" rel="stylesheet" type="text/css" />').appendTo(document.head);
-        $(document.body).addClass('show-tipsbar');
-
-        let tipsBar = $('<div id="fehelper_tips">' +
-            '<span class="desc">FeHelper检测到这可能是<i>' + fileType + '</i>代码,<span class="ask">是否进行美化处理?</span></span>' +
-            '<a class="encoding">有乱码?点击修正!</a>' +
-            '<button class="yes">代码美化</button>' +
-            '<button class="no">放弃!</button>' +
-            '<button class="copy hide">复制美化过的代码</button>' +
-            '<button class="close"><span></span></button>' +
-            '<a class="forbid">彻底关闭这个功能!&gt;&gt;</a>' +
-            '</div>').prependTo('body');
-
-        tipsBar.find('button.yes').click((evt) => {
-            tipsBar.find('button.yes,button.no').hide();
-            let elAsk = tipsBar.find('span.ask').text('正在努力美化,请稍候...');
-            format(fileType, source, () => {
-                elAsk.text('已为您美化完毕!');
-                $(document.body).removeClass('show-tipsbar').addClass('show-beautified');
-            });
-        });
-
-        tipsBar.find('a.forbid').click((evt) => {
-            evt.preventDefault();
-            chrome.runtime.sendMessage({
-                type: MSG_TYPE.OPEN_OPTIONS_PAGE
-            });
-        });
-
-        tipsBar.find('button.no,button.close').click((evt) => {
-            $(document.body).removeClass('show-tipsbar').removeClass('show-beautified');
-            tipsBar.remove();
-        });
-
-        tipsBar.find('button.copy').click((evt) => {
-            _copyToClipboard(formattedCodes);
-        });
-
-        tipsBar.find('a.encoding').click((evt) => {
-            evt.preventDefault();
-            fetch(location.href).then(res => res.text()).then(text => {
-                source = text;
-                if ($(document.body).hasClass('show-beautified')) {
-                    tipsBar.find('button.yes').trigger('click');
-                } else {
-                    $('#fehelper_tips+pre').text(text);
-                }
-            });
-        });
-    };
-
-
-    /**
-     * chrome 下复制到剪贴板
-     * @param text
-     */
-    let _copyToClipboard = function (text) {
-        let input = document.createElement('textarea');
-        input.style.position = 'fixed';
-        input.style.opacity = 0;
-        input.value = text;
-        document.body.appendChild(input);
-        input.select();
-        document.execCommand('Copy');
-        document.body.removeChild(input);
-
-        alert('代码复制成功,随处粘贴可用!')
-    };
-
-    return {
-        detect: detect
-    }
-
-})();

+ 2 - 2
apps/code-beautify/beautify-css.js

@@ -64,7 +64,7 @@
             callback && callback(evt.data);
         };
         worker.postMessage({
-            source_text: source_text,
+            source_text: (source_text || '').trim(),
             options: options
         });
     }
@@ -266,5 +266,5 @@
         };
     }
 
-    module.exports.css_beautify = window.css_beautify = css_beautify;
+    window.css_beautify = css_beautify;
 }());

+ 1 - 1
apps/code-beautify/beautify-html.js

@@ -610,7 +610,7 @@
     }
 
     // If we're running a web page and don't have either of the above, add our one global
-    module.exports.html_beautify = window.html_beautify = function(html_source, options) {
+    window.html_beautify = function(html_source, options) {
         return style_html(html_source, options, window.js_beautify, window.css_beautify);
     };
 }());

+ 0 - 0
apps/code-beautify/vkbeautify.js → apps/code-beautify/beautify-vk.js


+ 1 - 1
apps/code-beautify/beautify.js

@@ -3466,5 +3466,5 @@ let JsBeautifier = (function() {
 
 }());
 
-module.exports.js_beautify = window.js_beautify = JsBeautifier;
+window.js_beautify = JsBeautifier;
 

+ 20 - 2
apps/code-beautify/automatic.css → apps/code-beautify/content-script.css

@@ -1,4 +1,5 @@
-@import url("../static/vendor/prism/prism.css");
+@import url("../static/vendor/highlight/github.css");
+
 body {
     font-size: 14px;
 }
@@ -12,7 +13,7 @@ body {
     width: 100%;
     height: 37px;
     border-bottom: 1px solid #AAAAAB;
-    background-image: url('/static/img/fe-16.png'), linear-gradient(to bottom, #FEEFAE, #FAE692);
+    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA7UlEQVQ4EWN4KSwb8EJO7fxzbon/6Lg+Yc5/3wlvseLgKW/v+/a/CWB4xi/9AF0jjL9dzxurZpihfhPfnmeAKcZGP+SX/h/S+QivIXgNABlalboUqwF+EyFegxvwnwwA8gr1DUAPizOyBnC3wQIPmSbogtvCinADkBkwQwga8JRHElkfnI3TAHQvgPgwANOETGO4AN2Ax7xSMP0YNMggggZcF1PB0AgTQDEA3WYY/4SCKdaEBPMG3AUwDej0Tl1P/Aa8lFHBmZlAhi1wzMZpQODktxcYXkopBzzjl76AbjOMjytLh09/+yBmzvsAAD5azgb97zJPAAAAAElFTkSuQmCC), linear-gradient(to bottom, #FEEFAE, #FAE692);
     background-position: 10px 50%, 0 0;
     background-repeat: no-repeat, repeat-x;
     font: 15px/32px 'Helvetica', 'Segoe UI', Arial, 'Microsoft Yahei', Simsun, sans-serif;
@@ -132,4 +133,21 @@ body.processing > :not(#fehelper_tips) {
 }
 pre>code[class*="language"] {
     overflow: initial;
+}
+
+pre ol {
+    background: #f9f9f9;
+    color:#ccc;
+}
+pre ol li {
+    margin: 0 0 0 2px;
+    padding: 1px 0 1px 5px;
+    background: #fff;
+    border-left: 1px solid #ddd;
+}
+pre ol li>span {
+    color: #444;
+}
+pre ol li:hover {
+    background:#f5f5f5;
 }

+ 219 - 0
apps/code-beautify/content-script.js

@@ -0,0 +1,219 @@
+let __importScript = (filename) => {
+    fetch(filename).then(resp => resp.text()).then(jsText => eval(jsText));
+};
+
+__importScript('beautify.js');
+__importScript('beautify-css.js');
+
+let highlightWebWorker = () => {
+
+    __importScript('../static/vendor/highlight/highlight.js');
+
+    self.onmessage = (event) => {
+        const result = self.hljs.highlightAuto(event.data);
+        postMessage(result.value);
+    };
+};
+
+window.codebeautifyContentScript = (() => {
+
+    let formattedCodes = '';
+
+    /**
+     * 代码美化
+     */
+    let format = (fileType, source, callback) => {
+
+        let beauty = txtResult => {
+
+            let code = document.getElementsByTagName('pre')[0];
+            formattedCodes = txtResult;
+            code.textContent = txtResult;
+            code.classList.add('language-' + fileType.toLowerCase());
+
+            // 用webwork的方式来进行格式化,效率更高
+            let worker = new Worker(URL.createObjectURL(new Blob(["(" + highlightWebWorker.toString() + ")()"], {type: 'text/javascript'})));
+            worker.onmessage = (event) => {
+                code.innerHTML = "<ol><li><span>" + event.data.replace(/\n/gm, '</span></li><li><span>') + '</span></li></ol>';
+                callback && callback();
+            };
+            worker.postMessage(txtResult);
+        };
+
+        switch (fileType) {
+            case 'javascript':
+                let opts = {
+                    brace_style: "collapse",
+                    break_chained_methods: false,
+                    indent_char: " ",
+                    indent_scripts: "keep",
+                    indent_size: "4",
+                    keep_array_indentation: true,
+                    preserve_newlines: true,
+                    space_after_anon_function: true,
+                    space_before_conditional: true,
+                    unescape_strings: false,
+                    wrap_line_length: "120"
+                };
+                beauty(js_beautify(source, opts));
+                break;
+            case 'css':
+                css_beautify(source, {}, resp => beauty(resp));
+                break;
+        }
+
+    };
+
+    /**
+     * 检测
+     * @returns {boolean}
+     */
+    window._codebutifydetect_ = (fileType) => {
+
+        if (!document.getElementsByTagName('pre')[0]) {
+            return;
+        }
+        let source = document.getElementsByTagName('pre')[0].textContent;
+
+        if (window.codebeautifyContentScriptCssInject) {
+            window.codebeautifyContentScriptCssInject();
+        } else {
+            // 注入css and html fragment
+            chrome.runtime.sendMessage({
+                type: 'fh-dynamic-any-thing',
+                func: ((params, callback) => {
+
+                    let injectFn = (cssText) => {
+                        chrome.tabs.insertCSS({
+                            code: cssText,
+                            runAt: 'document_end'
+                        });
+                    };
+
+                    let cssText = Awesome.getContentScript('code-beautify', true);
+                    if (typeof cssText === 'string') {
+                        injectFn(cssText);
+                    } else if (cssText instanceof Promise) {
+                        cssText.then(css => injectFn(css));
+                    }
+                    callback && callback();
+                    return true;
+                }).toString()
+            });
+        }
+
+        $(document.body).addClass('show-tipsbar');
+
+        let tipsBar = $('<div id="fehelper_tips">' +
+            '<span class="desc">FeHelper检测到这可能是<i>' + fileType + '</i>代码,<span class="ask">是否进行美化处理?</span></span>' +
+            '<a class="encoding">有乱码?点击修正!</a>' +
+            '<button class="yes">代码美化</button>' +
+            '<button class="no">放弃!</button>' +
+            '<button class="copy hide">复制美化过的代码</button>' +
+            '<button class="close"><span></span></button>' +
+            '<a class="forbid">彻底关闭这个功能!&gt;&gt;</a>' +
+            '</div>').prependTo('body');
+
+        tipsBar.find('button.yes').click((evt) => {
+            tipsBar.find('button.yes,button.no').hide();
+            let elAsk = tipsBar.find('span.ask').text('正在努力美化,请稍候...');
+            format(fileType, source, () => {
+                elAsk.text('已为您美化完毕!');
+                $(document.body).removeClass('show-tipsbar').addClass('show-beautified');
+            });
+        });
+
+        tipsBar.find('a.forbid').click((evt) => {
+            evt.preventDefault();
+            if (confirm('一旦彻底关闭,不可恢复,请确认?')) {
+                chrome.runtime.sendMessage({
+                    type: 'fh-dynamic-any-thing',
+                    func: ((params, callback) => {
+                        localStorage.setItem('JS_CSS_PAGE_BEAUTIFY', 0);
+                    }).toString()
+                }, () => {
+                    alert('已关闭,如果要恢复,请在FeHelper「设置页」重新安装「代码美化工具」!');
+                });
+            }
+        });
+
+        tipsBar.find('button.no,button.close').click((evt) => {
+            $(document.body).removeClass('show-tipsbar').removeClass('show-beautified');
+            tipsBar.remove();
+        });
+
+        tipsBar.find('button.copy').click((evt) => {
+            _copyToClipboard(formattedCodes);
+        });
+
+        tipsBar.find('a.encoding').click((evt) => {
+            evt.preventDefault();
+            fetch(location.href).then(res => res.text()).then(text => {
+                source = text;
+                if ($(document.body).hasClass('show-beautified')) {
+                    tipsBar.find('button.yes').trigger('click');
+                } else {
+                    $('#fehelper_tips+pre').text(text);
+                }
+            });
+        });
+    };
+
+
+    /**
+     * chrome 下复制到剪贴板
+     * @param text
+     */
+    let _copyToClipboard = function (text) {
+        let input = document.createElement('textarea');
+        input.style.position = 'fixed';
+        input.style.opacity = 0;
+        input.value = text;
+        document.body.appendChild(input);
+        input.select();
+        document.execCommand('Copy');
+        document.body.removeChild(input);
+
+        alert('代码复制成功,随处粘贴可用!')
+    };
+
+    return function () {
+        chrome.runtime.sendMessage({
+            type: 'fh-dynamic-any-thing',
+            params: {
+                tabId: window.__FH_TAB_ID__ || null
+            },
+            func: ((params, callback) => {
+                chrome.tabs.executeScript(params.tabId, {
+                    code: '(' + (() => {
+                        let ext = location.pathname.substring(location.pathname.lastIndexOf(".") + 1).toLowerCase();
+                        let fileType = ({'js': 'javascript', 'css': 'css'})[ext];
+                        let contentType = document.contentType.toLowerCase();
+
+                        if (!fileType) {
+                            if (/\/javascript$/.test(contentType)) {
+                                fileType = 'javascript';
+                            } else if (/\/css$/.test(contentType)) {
+                                fileType = 'css';
+                            }
+                        } else if (contentType === 'text/html') {
+                            fileType = undefined;
+                        }
+                        return fileType;
+                    }).toString() + ')()'
+                }, function (fileType) {
+                    if (['javascript', 'css'].includes(fileType[0])) {
+                        if (localStorage.getItem('JS_CSS_PAGE_BEAUTIFY') !== '0') {
+                            chrome.tabs.executeScript(params.tabId, {
+                                code: `window._codebutifydetect_('${fileType[0]}')`
+                            });
+                        }
+                    }
+                });
+                callback && callback();
+                return true;
+            }).toString()
+        });
+    };
+
+})();

+ 8 - 2
apps/code-beautify/index.html

@@ -3,9 +3,9 @@
 <head>
     <title>代码美化工具</title>
     <meta charset="UTF-8">
+    <link rel="shortcut icon" href="../static/img/favicon.ico">
     <link rel="stylesheet" href="index.css"/>
     <script type="text/javascript" src="../static/vendor/vue/vue.js"></script>
-    <script src="../static/vendor/require/require.js"></script>
 </head>
 <body>
 <div class="wrapper" id="pageContainer">
@@ -50,7 +50,13 @@
         <div class="row" id="jfContent" ref="jfContentBox" v-html="resultContent"></div>
     </div>
 </div>
-<script src="../static/vendor/prism/prism.js"></script>
+<script type="text/javascript" src="beautify-html.js"></script>
+<script type="text/javascript" src="beautify-vk.js"></script>
+<script type="text/javascript" src="content-script.js"></script>
+<script type="text/javascript" src="../static/vendor/prism/prism.js"></script>
 <script type="text/javascript" src="index.js"></script>
+
+<script src="../static/vendor/jquery/jquery-3.3.1.min.js"></script>
+<script src="../static/js/navbar.js"></script>
 </body>
 </html>

+ 40 - 16
apps/code-beautify/index.js

@@ -11,17 +11,17 @@ new Vue({
     },
 
     mounted: function () {
-
         // 在tab创建或者更新时候,监听事件,看看是否有参数传递过来
-        chrome.runtime.onMessage.addListener((request, sender, callback) => {
-            let MSG_TYPE = Tarp.require('../static/js/msg_type');
-            if (request.type === MSG_TYPE.TAB_CREATED_OR_UPDATED && request.event === MSG_TYPE.CODE_BEAUTIFY) {
-                if (request.content) {
+        if (location.protocol === 'chrome-extension:') {
+            chrome.runtime.onMessage.addListener((request, sender, callback) => {
+                if (request.type === 'TAB_CREATED_OR_UPDATED' && request.content && request.event === location.pathname.split('/')[1]) {
                     this.sourceContent = request.content;
                     this.format();
                 }
-            }
-        });
+                callback && callback();
+                return true;
+            });
+        }
 
         //输入框聚焦
         this.$refs.codeSource.focus();
@@ -29,8 +29,10 @@ new Vue({
 
     methods: {
         format: function () {
-            if(!this.sourceContent.trim()) {
+            if (!this.sourceContent.trim()) {
                 return alert('内容为空,不需要美化处理!');
+            }else{
+                this.toast('格式化进行中...');
             }
 
             let beauty = (result) => {
@@ -42,6 +44,7 @@ new Vue({
                 this.$nextTick(() => {
                     Prism.highlightAll();
                     this.showCopyBtn = true;
+                    this.toast('格式化完成!');
                 });
             };
 
@@ -66,27 +69,24 @@ new Vue({
                         "comma_first": false,
                         "e4x": false
                     };
-                    beauty(Tarp.require('../code-beautify/beautify.js').js_beautify(this.sourceContent, opts));
+                    beauty(js_beautify(this.sourceContent, opts));
                     break;
                 case 'CSS':
-                    Tarp.require('../code-beautify/beautify-css.js').css_beautify(this.sourceContent, {}, result => beauty(result));
+                    css_beautify(this.sourceContent, {}, result => beauty(result));
                     break;
                 case 'HTML':
-                    Tarp.require('../code-beautify/beautify-html.js');
-                    beauty(html_beautify(this.sourceContent));
+                    beauty(html_beautify(this.sourceContent,{indent_size:15}));
                     break;
                 case 'SQL':
-                    Tarp.require('../code-beautify/vkbeautify.js');
                     beauty(vkbeautify.sql(this.sourceContent, 4));
                     break;
                 default:
-                    Tarp.require('../code-beautify/vkbeautify.js');
                     beauty(vkbeautify.xml(this.sourceContent));
             }
 
         },
 
-        copy: function(){
+        copy: function () {
 
             let _copyToClipboard = function (text) {
                 let input = document.createElement('textarea');
@@ -103,6 +103,30 @@ new Vue({
 
             let txt = this.$refs.jfContentBox.textContent;
             _copyToClipboard(txt);
+        },
+
+        /**
+         * 自动消失的Alert弹窗
+         * @param content
+         */
+        toast (content) {
+            window.clearTimeout(window.feHelperAlertMsgTid);
+            let elAlertMsg = document.querySelector("#fehelper_alertmsg");
+            if (!elAlertMsg) {
+                let elWrapper = document.createElement('div');
+                elWrapper.innerHTML = '<div id="fehelper_alertmsg" style="position:fixed;bottom:5px;left:5px;z-index:1000000">' +
+                    '<p style="background:#000;display:inline-block;color:#fff;text-align:center;' +
+                    'padding:10px 10px;margin:0 auto;font-size:14px;border-radius:4px;">' + content + '</p></div>';
+                elAlertMsg = elWrapper.childNodes[0];
+                document.body.appendChild(elAlertMsg);
+            } else {
+                elAlertMsg.querySelector('p').innerHTML = content;
+                elAlertMsg.style.display = 'block';
+            }
+
+            window.feHelperAlertMsgTid = window.setTimeout(function () {
+                elAlertMsg.style.display = 'none';
+            }, 3000);
         }
     }
-});
+});

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 6 - 0
apps/code-compress/htmlminifier.min.js


+ 29 - 1
apps/code-compress/index.css

@@ -1,7 +1,7 @@
 @import url("../static/vendor/codemirror/codemirror.css");
 @import url("../static/css/bootstrap.min.css");
 
-#jfContent {
+.mod-textarea {
     min-height: 200px;
 }
 .panel-title .x-xother {
@@ -14,3 +14,31 @@
 .panel-title .x-xother:hover {
     color:red;
 }
+.x-error {
+    color:red;
+}
+[v-cloak] {
+    display: none;
+}
+.box-infos {
+    color:green;
+    margin-right: 20px;
+}
+.x-right-info {
+    margin-top:5px;
+}
+#fehelper_alertmsg {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    z-index: 1000000;
+    background: #000;
+    display: inline-block;
+    color: #fff;
+    text-align: center;
+    padding: 10px 10px;
+    margin: 0 auto;
+    font-size: 14px;
+    border-bottom: 1px solid #aaa;
+}

+ 35 - 9
apps/code-compress/index.html

@@ -1,11 +1,11 @@
 <!DOCTYPE HTML>
 <html lang="zh-CN">
     <head>
-        <title>代码压缩工具</title>
+        <title>FeHelper-代码压缩工具(支持HTML/Javascript/CSS)</title>
         <meta charset="UTF-8">
+        <link rel="shortcut icon" href="../static/img/favicon.ico">
         <link rel="stylesheet" href="index.css" />
         <script type="text/javascript" src="../static/vendor/vue/vue.js"></script>
-        <script src="../static/vendor/require/require.js"></script>
     </head>
     <body>
     <div class="wrapper" id="pageContainer">
@@ -13,8 +13,7 @@
             <div class="panel-heading">
                 <h3 class="panel-title">
                     <a href="http://www.baidufe.com/fehelper/feedback.html" target="_blank" class="x-a-high">
-                        <img src="../static/img/fe-16.png" alt="fehelper"/> FeHelper</a>:Javascript代码压缩
-                    <a class="x-xother" href="https://www.baidufe.com/fehelper/codecompress.html" target="_blank">CSS+HTML代码在线压缩&gt;&gt;</a>
+                        <img src="../static/img/fe-16.png" alt="fehelper"/> FeHelper</a>:代码压缩工具
                 </h3>
             </div>
         </div>
@@ -23,12 +22,32 @@
             <div class="row">
                 <textarea class="form-control mod-textarea" id="codeSource" ref="codeSource" v-model="sourceContent" placeholder="在这里粘贴需要进行压缩的代码"></textarea>
             </div>
-            <div class="row ui-mt-10 ui-mb-10 text-right">
-                <button id="btnFormat" class="btn btn-success" @click="compress()">压缩</button>
+
+            <div class="row ui-mt-10 ui-mb-10">
+
+                <button id="btnFormat" class="btn btn-success btn-sm ui-mr-20" @click="compress()">压缩</button>
+
+                <div class="radio ui-d-ib">
+                    <label><input id="codeTypeHtml" name="codeType" type="radio" value="html" v-model="codeType" @click="changeCodeType('html')">HTML压缩</label>
+                </div>
+
+                <div class="radio ui-d-ib ui-ml-20">
+                    <label><input id="codeTypeJs" name="codeType" type="radio" value="js" v-model="codeType" @click="changeCodeType('js')">Javascript压缩</label>
+                </div>
+
+                <div class="radio ui-d-ib ui-ml-20">
+                    <label><input id="codeTypeCss" name="codeType" type="radio" value="css" v-model="codeType" @click="changeCodeType('css')">CSS压缩</label>
+                </div>
+
+                <div class="ui-fl-r x-right-info" v-cloak v-show="!!resultContent">
+                    <span class="box-infos" v-html="compressInfo"></span>
+                    <button class="btn btn-sm btn-primary" @click="copyToClipboard(resultContent)">复制结果</button>
+                </div>
             </div>
 
-            <div class="row rst-item">
-                <textarea class="form-control mod-textarea" id="jfContent" ref="jfContent" placeholder="压缩后的代码将在这里显示" v-model="resultContent" @mouseover="getResult()"></textarea>
+            <div class="row rst-item" v-cloak v-show="!!resultContent">
+                <textarea class="form-control mod-textarea" :class="hasError?'x-error':''" ref="jfContent" disabled
+                          placeholder="压缩后的代码将在这里显示" v-model="resultContent"></textarea>
             </div>
         </div>
     </div>
@@ -41,13 +60,20 @@
     <script src="../static/vendor/uglifyjs3/scope.js"></script>
     <script src="../static/vendor/uglifyjs3/output.js"></script>
     <script src="../static/vendor/uglifyjs3/compress.js"></script>
+    <script type="text/javascript" src="uglifyjs3.js"></script>
 
     <script src="../static/vendor/codemirror/codemirror.js"></script>
     <script src="../static/vendor/codemirror/javascript.js"></script>
+    <script src="../static/vendor/codemirror/css.js"></script>
+    <script src="../static/vendor/codemirror/xml.js"></script>
+    <script src="../static/vendor/codemirror/htmlmixed.js"></script>
     <script src="../static/vendor/codemirror/active-line.js"></script>
     <script src="../static/vendor/codemirror/matchbrackets.js"></script>
     <script src="../static/vendor/codemirror/placeholder.js"></script>
-
+    <script type="text/javascript" src="htmlminifier.min.js"></script>
     <script type="text/javascript" src="index.js"></script>
+
+    <script src="../static/vendor/jquery/jquery-3.3.1.min.js"></script>
+    <script src="../static/js/navbar.js"></script>
     </body>
 </html>

+ 123 - 10
apps/code-compress/index.js

@@ -6,14 +6,17 @@ let editor = {};
 new Vue({
     el: '#pageContainer',
     data: {
+        codeType: 'html',
         sourceContent: '',
-        resultContent: ''
+        resultContent: '',
+        hasError: false,
+        compressInfo: ''
     },
 
     mounted: function () {
 
         editor = CodeMirror.fromTextArea(this.$refs.codeSource, {
-            mode: "text/javascript",
+            mode: "htmlmixed",
             lineNumbers: true,
             matchBrackets: true,
             styleActiveLine: true,
@@ -26,22 +29,132 @@ new Vue({
 
     methods: {
         compress: function () {
-
+            this.hasError = false;
+            this.compressInfo = '';
             this.sourceContent = editor.getValue().trim();
 
             if (!this.sourceContent) {
                 alert('请先粘贴您需要压缩的代码');
             } else {
-                // 用uglifyjs3进行在线压缩
-                let UglifyJs3 = Tarp.require('./uglifyjs3');
-                let result = UglifyJs3.compress(this.sourceContent);
-                this.resultContent = result.out || result.error;
-                this.$refs.jfContent.style.color = result.error ? 'red' : 'black';
+                if (this.codeType === 'js') {
+                    this.jsCompress(this.sourceContent);
+                } else if (this.codeType === 'css') {
+                    this.cssCompress(this.sourceContent);
+                } else {
+                    this.htmlCompress(this.sourceContent);
+                }
             }
         },
 
-        getResult: function () {
-            this.$refs.jfContent.select();
+        changeCodeType(ctype) {
+            let editorMode = {
+                css: 'text/css',
+                js: {name: 'javascript', json: true},
+                html: 'htmlmixed'
+            };
+            editor.setOption('mode', editorMode[ctype]);
+            if (editor.getValue().trim()) {
+                this.$nextTick(this.compress);
+            }
+        },
+
+        buildCompressInfo(original, minified) {
+            let commify = str => String(str).split('').reverse().join('').replace(/(...)(?!$)/g, '$1,').split('').reverse().join('');
+            let diff = original.length - minified.length;
+            let savings = original.length ? (100 * diff / minified.length).toFixed(2) : 0;
+            this.compressInfo = '压缩前: <strong>' + commify(original.length) + '</strong>' +
+                ',压缩后: <strong>' + commify(minified.length) + '</strong>' +
+                ',压缩率: <strong>' + commify(diff) + ' (' + savings + '%)</strong>';
+        },
+
+        jsCompress(js) {
+            let result = UglifyJs3.compress(js);
+            this.hasError = !!result.error;
+            this.resultContent = result.out || result.error;
+            !this.hasError && this.buildCompressInfo(this.sourceContent, this.resultContent);
+        },
+
+        cssCompress(css) {
+            let res = css.replace(/\/\*(.|\n)*?\*\//g, "")
+                .replace(/\s*([\{\}\:\;\,])\s*/g, "$1")
+                .replace(/\,[\s\.\#\d]*\{/g, "{")
+                .replace(/;\s*;/g, ";")
+                .match(/^\s*(\S+(\s+\S+)*)\s*$/);
+            this.resultContent = (res === null) ? css : res[1];
+            this.buildCompressInfo(this.sourceContent, this.resultContent);
+        },
+
+        htmlCompress(html) {
+            let options = {
+                "caseSensitive": false,
+                "collapseBooleanAttributes": true,
+                "collapseInlineTagWhitespace": false,
+                "collapseWhitespace": true,
+                "conservativeCollapse": false,
+                "decodeEntities": true,
+                "html5": true,
+                "includeAutoGeneratedTags": false,
+                "keepClosingSlash": false,
+                "minifyCSS": true,
+                "minifyJS": true,
+                "preserveLineBreaks": false,
+                "preventAttributesEscaping": false,
+                "processConditionalComments": true,
+                "processScripts": ["text/html"],
+                "removeAttributeQuotes": true,
+                "removeComments": true,
+                "removeEmptyAttributes": true,
+                "removeEmptyElements": false,
+                "removeOptionalTags": true,
+                "removeRedundantAttributes": true,
+                "removeScriptTypeAttributes": true,
+                "removeStyleLinkTypeAttributes": true,
+                "removeTagWhitespace": true,
+                "sortAttributes": true,
+                "sortClassName": true,
+                "trimCustomFragments": true,
+                "useShortDoctype": true
+            };
+            options.log = console.log;
+            try {
+                this.resultContent = require('html-minifier').minify(html, options);
+                this.buildCompressInfo(this.sourceContent, this.resultContent);
+            } catch (err) {
+                this.hasError = true;
+                this.resultContent = err;
+            }
+        },
+
+        toast(content) {
+            window.clearTimeout(window.feHelperAlertMsgTid);
+            let elAlertMsg = document.querySelector("#fehelper_alertmsg");
+            if (!elAlertMsg) {
+                let elWrapper = document.createElement('div');
+                elWrapper.innerHTML = '<div id="fehelper_alertmsg">' + content + '</div>';
+                elAlertMsg = elWrapper.childNodes[0];
+                document.body.appendChild(elAlertMsg);
+            } else {
+                elAlertMsg.innerHTML = content;
+                elAlertMsg.style.display = 'block';
+            }
+
+            window.feHelperAlertMsgTid = window.setTimeout(function () {
+                elAlertMsg.style.display = 'none';
+            }, 3000);
+        },
+        copyToClipboard(text) {
+            if (this.hasError) return false;
+
+            let input = document.createElement('textarea');
+            input.style.position = 'fixed';
+            input.style.opacity = 0;
+            input.value = text;
+            document.body.appendChild(input);
+            input.select();
+            document.execCommand('Copy');
+            document.body.removeChild(input);
+
+            this.toast('压缩结果已复制成功,随处粘贴可用!');
         }
     }
 });

+ 3 - 3
apps/code-compress/uglifyjs3.js

@@ -2,7 +2,7 @@
  * Uglifyjs3 Js压缩
  * @type {{compress}}
  */
-module.exports = (() => {
+var UglifyJs3 = (() => {
 
     function get_options() {
         return {
@@ -25,8 +25,8 @@ module.exports = (() => {
                 dead_code: true,
                 drop_console: false,
                 drop_debugger: true,
-                ecma: 5,
-                evaluate: true,
+                ecma: 6,
+                evaluate: false,
                 expression: false,
                 global_defs: {},
                 hoist_funs: false,

+ 0 - 293
apps/code-standards/css/fcp-css-analytic.js

@@ -1,293 +0,0 @@
-/**
- * 注册命名空间:baidu.cssAnalytic
- */
-baidu.namespace.register("baidu.cssAnalytic");
-
-/**
- * 
- * css分析器
- * 支持一下css规则:
- * css规则
- * 	1、@charset "utf-8"; //设置字符集
- * 	2、@import url("a.css"); //import
- * 	3、_property:value //ie6
- * 	4、*property:value //ie6,7
- * 	5、property:value\9; //ie6,7,8,9
- * 	6、property//:value  //非ie6
- * 	7、* html selector{} //各种选择符
- * 	8、@media all and (-webkit-min-device-pixel-ratio:10000),not all and (-webkit-min-device-pixel-ratio:0) { ... } //设备
- * 	9、@-moz-xxx  //firefox
- * 	10、property:value !important; //important
- * 	11、property:expression(onmouseover=function(){})  //expression,值里有可能有 { 和 } 
- * 	12、-webkit-border-radious:value //浏览器私有,减号开头
- * 	13、filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="ds_laby.png", sizingMethod='crop')  //ie下的filter,有(和)
- * 
- * @author lichengyin (FCP:PHP代码)
- * @cover zhaoxianlie (FCPHelper:将PHP代码重写为Javascript代码)
- */
-baidu.cssAnalytic = function(){
-	/**
-	 * 当前解析到的位置
-	 * @var int
-	 */
-	this.parsePos = 0;
-	
-	this.content = '';
-	
-	this.contentLength = 0;
-	
-	this._output = [];
-	
-	this._pre_type = ''; //上一个特殊类型
-	
-	this.run = function($content){
-		this.content = $content.trim().replace(/\n/g, "\n");
-		this.contentLength = this.content.length;
-		this.tokenAnalytic();
-		return this._output;
-	};
-	this.tokenAnalytic = function(){
-		var $token;
-		while (true){
-			$token = this.getNextToken();
-			if ($token){
-				if ($token[1] === baidu.FL.FL_EOF) break;
-				this._output.push($token);
-				if ($token[1] === baidu.FL.CSS_PROPERTY) {
-					this._output.push([':', baidu.FL.CSS_COLON]);
-				} else if ($token[1] === baidu.FL.CSS_VALUE) {
-					this._output.push([';', baidu.FL.CSS_SEMICOLON]);
-				}
-			}
-		}
-	};
-	this.getNextToken = function(){
-		if (this.parsePos >= this.contentLength){
-			return ['', baidu.FL.FL_EOF];
-		}
-		$char = this.content[this.parsePos];
-		this.parsePos++;
-		
-		if ($char === "\x0d") return ''; //\r
-		if ($char === "\x0a") return [$char, baidu.FL.FL_NEW_LINE];
-		//避免出现多个空格在一起的情况
-		if ($char.trim() === '' || $char === ';') return '';
-		
-		var $result ;
-		
-		//处理@开头的;如:@charset "utf-8";@import url("a.css"), @media xxx{}
-		if ($char === '@'){
-			$result = this._getAtToken($char);
-			if ($result) return $result;
-		}else if ($char === '{'){
-			switch (this._pre_type){
-				case baidu.FL.CSS_DEVICE_DESC : 
-					this._pre_type = baidu.FL.CSS_DEVICE_START;
-					return [$char, baidu.FL.CSS_DEVICE_START];
-				default : 
-					this._pre_type = baidu.FL.CSS_SELECTOER_START;
-					return [$char, baidu.FL.CSS_SELECTOER_START];
-			}
-		}else if ($char === '}'){
-			switch (this._pre_type){
-				case baidu.FL.CSS_SELECTOER_END:
-					this._pre_type = baidu.FL.CSS_DEVICE_END;
-					return [$char, baidu.FL.CSS_DEVICE_END];
-				default: 
-					for(var $i=this._output.length-1;$i>=0;$i--){
-						var $item = this._output[$i];
-						if($item[1] === baidu.FL.CSS_SELECTOER_START){
-							this._pre_type = baidu.FL.CSS_SELECTOER_END;
-							return [$char, baidu.FL.CSS_SELECTOER_END];
-						}else if($item[1] === baidu.FL.CSS_DEVICE_START){
-							this._pre_type = baidu.FL.CSS_DEVICE_END;
-							return [$char, baidu.FL.CSS_DEVICE_END];
-						}
-					}
-					this._pre_type = baidu.FL.CSS_SELECTOER_END;
-					return [$char, baidu.FL.CSS_SELECTOER_END];
-			}
-		}else if (this.content.substr( this.parsePos - 1, 2) === '/*'){
-			$result = this._getCommentToken($char);
-			if ($result) return $result;
-		}else if ($char === "\x0d" || $char === "\x0a"){
-			return [$char, baidu.FL.FL_NEW_LINE];
-		}
-
-		switch (this._pre_type){
-			case baidu.FL.CSS_SELECTOER_START : 
-			case baidu.FL.CSS_VALUE : 
-				$result = this._getPropertyToken($char);
-				this._pre_type = baidu.FL.CSS_PROPERTY;
-				return $result;
-			case baidu.FL.CSS_PROPERTY : 
-				$result = this._getValueToken($char);
-				this._pre_type = baidu.FL.CSS_VALUE;
-				return $result;
-			case baidu.FL.CSS_DEVICE_START:
-				var $pos = this.parsePos;
-				$result = this._getPropertyToken($char);
-				var $str = $result[0];
-				if($str.indexOf('{') > -1){
-					this.parsePos = $pos;
-					$result = this._getSelectorToken($char);
-					this._pre_type = baidu.FL.CSS_DEVICE_START;
-					if ($result) return $result;
-				}else{
-					this._pre_type = baidu.FL.CSS_PROPERTY;
-					return $result;
-				}
-			default:
-				$result = this._getSelectorToken($char);
-				if ($result) return $result;
-			
-		}
-		return [$char, baidu.FL.CSS_NORMAL];
-	};
-	
-	/**
-	 * 处理@开头的;如:@charset "utf-8";@import url("a.css"), @media xxx{}
-	 * @param {Object} $char
-	 */
-	this._getAtToken = function($char){
-		$resultString = $char;
-		while (this.content[this.parsePos] !== ';' 
-			&& this.content[this.parsePos] !== '{' 
-			&& this.parsePos < this.contentLength){
-			
-			$resultString += this.content[this.parsePos];
-			this.parsePos++;
-		}
-		if (this.content[this.parsePos] === ';'){
-			$resultString += ';';
-			this.parsePos++;
-			return [$resultString.trim(), baidu.FL.CSS_AT];
-		}
-		this._pre_type = baidu.FL.CSS_DEVICE_DESC;
-		return [$resultString.trim(), baidu.FL.CSS_DEVICE_DESC];
-	};
-
-	/**
-	 * comment
-	 * @param {Object} $char
-	 * @param {Object} $fromSelector=false
-	 */
-	this._getCommentToken = function($char, $fromSelector){
-		this.parsePos++;
-		$resultString = '';
-		while (!(this.content[this.parsePos] === '*' 
-			&& this.content[this.parsePos + 1] 
-			&& this.content[this.parsePos + 1] === '/') 
-			&& this.parsePos < this.contentLength){
-			
-			$resultString += this.content[this.parsePos];
-			this.parsePos++;
-		}
-		this.parsePos += 2;
-		if ($fromSelector){
-			return '/*' + $resultString + '*/';
-		}
-		return ['/*' + $resultString + '*/' , baidu.FL.CSS_COMMENT];
-	};
-	
-	/**
-	 * selector content
-	 * 选择符里可能还有注释,注释里可能含有{}等字符
-	 */
-	this._getSelectorToken = function($char){
-		var $resultString = $char;
-		while (this.content[this.parsePos] !== '{' 
-				&& this.content[this.parsePos] !== '}' 
-				&& this.parsePos < this.contentLength){
-			//如果选择符中含有注释
-			if (this.content[this.parsePos] === '/' &&
-				this.content[this.parsePos+1] &&
-				this.content[this.parsePos+1] === '*'){
-				$resultString += this._getCommentToken('/', true);
-			}else{
-				$resultString += this.content[this.parsePos];
-				this.parsePos++;
-			}
-		}
-		return [$resultString.trim(), baidu.FL.CSS_SELECTOER];
-	};
-	
-	/**
-	 * css property
-	 * @param {Object} $char
-	 */
-	this._getPropertyToken = function($char){
-		$resultString = $char;
-		while (this.content[this.parsePos] !== ':' && 
-				this.content[this.parsePos] !== ';' && 
-				this.content[this.parsePos] !== '}' && 
-				this.parsePos < this.contentLength){
-			$resultString += this.content[this.parsePos];
-			this.parsePos++;
-		}
-		//增加对div{color}的容错机制
-		if (this.content[this.parsePos] !== '}'){
-			this.parsePos++;
-		}
-		return [$resultString.trim().toLowerCase(), baidu.FL.CSS_PROPERTY];
-	};
-	
-	/**
-	 * css value
-	 * @param {Object} $char
-	 */
-	this._getValueToken = function($char){
-		var $resultString = $char;
-		var $isExpression = false;
-		while (this.content[this.parsePos] !== ';' 
-			&& this.content[this.parsePos] !== '}' 
-			&& this.parsePos < this.contentLength){
-			
-			$char = this.content[this.parsePos];
-			this.parsePos++;
-			$resultString += $char;
-			if (!$isExpression && $resultString.toLowerCase() === 'expression('){
-				$isExpression = true;
-				$resultString += this._getJSToken();
-			}
-		}
-		if (this.content[this.parsePos] === ';'){
-			this.parsePos++;
-		}
-		//将多个空格变成一个空格
-		$resultString = $resultString.trim().replace(/\s+/ig, " ");
-		return [$resultString, baidu.FL.CSS_VALUE];
-	};
-	
-	/**
-	 * 处理expression里的javascript
-	 */
-	this._getJSToken = function(){
-		var $string = '',$char;
-		while (this.parsePos < this.contentLength){
-			$char = this.content[this.parsePos];
-			this.parsePos++;
-			$string += $char;
-			//这里使用js分析器,然后判断(和) 个数是否相等
-			if ($char === ')' && this._checkJSToken('(' + $string)){
-				break;
-			}
-		}
-		return $string;
-	};
-	
-	/**
-	 * check js for expression
-	 * @param array $output
-	 */
-	this._checkJSToken = function($output){
-		var $expr_start = 0;
-		var $expr_end = 0;
-		for (var $i=0,$count=$output.length;$i<$count;$i++){
-			var $item = $output[$i];
-			if ($item[0] === '(') $expr_start++;
-			else if ($item[0] === ')') $expr_end++;
-		}
-		return $expr_start === $expr_end;
-	};
-};

+ 0 - 648
apps/code-standards/css/fcp-css.js

@@ -1,648 +0,0 @@
-/**
- * 注册命名空间:baidu.css
- */
-baidu.namespace.register("baidu.css");
-
-/**
- * css相关处理
- * @author zhaoxianlie 
- */
-baidu.css = (function(){
-	
-	var _readyQueen = null;
-	var _localdata = null;
-	var _stats = null;
-	var _styleBlockCount  = 0;
-	var _rootPath = null;
-	
-	/**
-	 * 存储页面上的css源代码
-	 * @item {fileName:'',fileContent:''}
-	 */
-	var _rawCssSource = [];
-		
-	var _summaryInformation = null;
-	
-	
-	/**
-	 * 初始化css文件的读取队列
-	 * @param {Object} isFinished 是否读取完成
-	 */
-	var _initReadyQueen = function(isFinished){
-		_readyQueen = {
-			curIndex : 0,	//当前正处于读取的index
-			queen : [],	//css文件队列,格式为:{link:"",style:""},其中link和style不可能同时有值
-			callback : new Function(),	//回调方法
-			finished : isFinished	//是否读取完成
-		};
-		_localdata = {};
-	};
-	
-	
-	/**
-	 * 初始化侦测结果
-	 */
-	var _initSummaryInformation = function(){		
-	    _summaryInformation = {
-			styles : [],				//所有的style标签和所有的link[rel=stylesheet]
-			cssMinified : {				//css文件是否被压缩
-				files : [],
-				count : 0
-			},
-			backgroundImages : [],		//css背景图片统计
-			expressions : [],			//css expression统计
-			duplicatedFiles : []		//重复引入的文件
-	    };
-		
-	};
-	
-	/**
-	 * 初始化Stats
-	 */
-	var _initStats = function(){
-	    _stats = {
-	        matched: {count:0,selectors:[]},	//匹配上的
-	        unmatched: {count:0,selectors:[]},	//未匹配上的
-	        ignored: {count:0,selectors:[]}		//忽略的
-	    };
-	};
-	
-	/**
-	 * 增加一项读取项
-	 * @param {Object} readyItem 格式为:{link:Object,style:Object}
-	 */
-	var _addReadyItem = function(readyItem){
-		_readyQueen.queen.push(readyItem);
-	};
-	
-	/**
-	 * 获取当前正在解析的Style块
-	 */
-	var _getCurrentReadyItem = function(){
-		return _readyQueen.queen[_readyQueen.curIndex];
-	};
-	
-	/**
-	 * 判断当前读取的是否为最后一个Style块
-	 */
-	var _isLastReadyItem = function(){
-		return (_readyQueen.curIndex == _readyQueen.queen.length);
-	};
-	
-	/**
-	 * 读取队列移动到下一个元素
-	 */
-	var _moveToNextReadyItem = function(){
-		_readyQueen.curIndex += 1;
-	};
-	
-	/**
-	 * 判断当前队列是否读取完毕
-	 */
-	var _isDealFinished = function(){
-		return _readyQueen.finished;
-	};
-	
-	/**
-	 * 设置当前文件的根路径
-	 * @param {Object} path
-	 */
-	var _setCurRootPath = function(path) {
-		var reg = /(.*\/)([^\/]+\.css)/;
-		var p = reg.exec((path || '').replace(/\?.*/,''));
-		_rootPath = p ? p[1] : '';
-	};
-	
-	/**
-	 * 获取当前正在解析的文件的根路径
-	 */
-	var _getCurRootPath = function(){
-		return _rootPath || '';
-	};
-	
-	/**
-	 * 根据文件路径,提取文件名
-	 * @param {Object} path 文件url
-	 */
-	var _getFileName = function(path){
-		var reg = /(.*\/)([^\/]+\.css)/;
-		var p = reg.exec((path || '').replace(/\?.*/,''));
-		return p ? p[2] : "style块" + ++_styleBlockCount;
-	};
-	
-	/**
-	 * 保存CSS代码
-	 * @param {Object} _filePath 文件完整路径
-	 * @param {Object} _fileContent 内容
-	 */
-	var _saveCssSource = function(_filePath,_fileContent){
-		
-		//过滤CSS注释
-		_fileContent = _fileContent.replace(/\/\*[\S\s]*?\*\//g,'');
-		
-		//提取文件名
-		var _fileName = _getFileName(_filePath);
-		
-		_rawCssSource.push({
-			href : _filePath ? _filePath : '#',
-			fileName : _fileName,
-			fileContent : _fileContent
-		});
-		
-		//对@import处理
-		try{
-			var reg = /@import\s+url\(\s*(\"|\')(.*)\1\s*\)(\s*;)?/ig;
-			_fileContent.replace(reg,function($0,$1,$2){
-				_addReadyItem({link:{href:_getCurRootPath() + $2},style:null});
-			});
-		}catch(err){
-		}
-	};
-	
-	/**
-	 * 获取一个比较标准的图片地址
-	 * @param {Object} bgUrl
-	 */
-	var _getBgImageUrl = function(bgUrl){
-		if(bgUrl.indexOf('http://') != 0) {
-			bgUrl = bgUrl.replace(/['"]/g,"");
-			var __rp = _getCurRootPath();
-			if(bgUrl.indexOf('/') == 0){
-				__rp = '';
-			} else if(bgUrl.indexOf('./') == 0) {
-				bgUrl = bgUrl.substr(2);
-			} else if(bgUrl.indexOf('../') == 0) {
-				bgUrl = bgUrl.substr(3);
-				if(__rp.lastIndexOf('/') == __rp.length - 1) {
-					__rp = __rp.substr(0,__rp.length - 1);
-				}
-				__rp = __rp.substr(0,__rp.lastIndexOf('/') + 1);
-			}
-			bgUrl = __rp + bgUrl
-		}
-		return bgUrl;
-	};
-	
-	/**
-	 * 寻找并统计CSS背景图片
-	 * @param {Object} _fileName
-	 * @param {Object} _fileContent
-	 */
-	var _findBackgroundImage = function(_fileName,_fileContent){
-
-		var reg = /(background|background-image):(?:[\#\w]+\s+)?url\(([^\)]*)\)/ig;
-		var arr = [];
-		
-		_fileContent.replace(/\/\*[\S\s]*?\*\//g,'').replace(/\r?\n/,'')
-			.replace(/\s+\'|\"/g,'').replace(reg,function($0,$1,$2){
-				$2 = $2.replace(/\?.*/,'');
-				arr.push(_getBgImageUrl($2));
-		});
-		if(arr.length) {
-			_summaryInformation.backgroundImages.push({
-				fileName:_fileName,
-				bgImages : arr
-			});
-		}
-	};
-	
-	/**
-	 * 寻找并统计css中的expression
-	 * @param {Object} _fileName
-	 * @param {Object} _fileContent
-	 */
-	var _findExpression = function(_fileName,_fileContent){
-		var reg = /:expression\(/ig;
-		var arr = _fileContent.replace(/\/\*[\S\s]*?\*\//g,'').replace(/\r?\n/,'')
-			.replace(/\s+/g,'').split(reg);
-		if(arr.length - 1) {
-			_summaryInformation.expressions.push({
-				fileName : _fileName,
-				count : arr.length - 1
-			});
-		}
-	};
-	
-	/**
-	 * 检测某个css文件是否被压缩
-	 * @param {Object} cssObj css文件对象
-	 * @config {String} href 文件路径
-	 * @config {String} fileName 文件名
-	 * @config {String} fileContent 文件内容
-	 */
-	var _detectCssMinify = function(cssObj){
-		var lines = cssObj.fileContent.split(/\n/);
-		var average_length_perline = cssObj.fileContent.length / lines.length;
-		if (average_length_perline < 150 && lines.length > 1) {
-			_summaryInformation.cssMinified.count++;
-			_summaryInformation.cssMinified.files.push({
-				href : cssObj.href,
-				fileName : cssObj.fileName
-			});
-		}
-	};
-	
-	/**
-	 * 将stylesheet归类进行检测
-	 * @param {Array} styleheets CSSstyleheet对象数组
-	 */
-	var _getCssData = function(styleheets){
-		//从页面上获取<link> 或者 <style> 内容
-		var cssdata = (function(){
-			var ss = {link:[],style:[]};
-			jQuery.each(styleheets,function(i,styleheet){
-				//通过 <link> 标签引用的css样式
-				if(!!styleheet.href) { ss.link.push(styleheet); }
-				//通过 <style> 标签定义的css
-				else { ss.style.push(styleheet); }
-			});
-			return ss;
-		})();
-		
-		return cssdata;
-	};
-	
-	/**
-	 * 提取CSS文件内容
-	 * @param {String} link 需要读取的css文件
-	 * @see 具体可参见css-background.js文件中定义的Function: _readFileContent
-	 */
-	var _getCssSourceByServer = function(link){
-		_setCurRootPath(link.href);
-
-		//向background发送一个消息,要求其加载并处理css文件内容
-		chrome.runtime.sendMessage(null,{
-			type : MSG_TYPE.GET_CSS,
-			link : link.href
-		},function(respData){
-			//保存源代码
-			_saveCssSource(respData.path,respData.content)
-			//继续读取(是从就绪队列中进行读取)
-			_readRawCss();
-		});
-	};
-	
-	
-	/**
-	 * 读取页面上已经存在的<style>标签内的样式
-	 * @param {StyleSheet} stylesheet 样式
-	 * @return {Undefined} 无返回值 
-	 */
-	var _readStyleTagContent = function(stylesheet){
-		//保存源代码
-		_saveCssSource('',stylesheet.ownerNode.innerText)
-		
-		//继续读取(是从就绪队列中进行读取)
-		_readRawCss();
-	};
-	
-	
-	/**
-	 * 从就绪队列中,逐个加载css
-	 */
-	var _readRawCss = function(){
-		
-		//取得当前需要读取的数据
-		var curData = _getCurrentReadyItem();
-
-		//是否读取完成
-		if(_isDealFinished() || !curData) {
-			chrome.runtime.sendMessage({
-				type : MSG_TYPE.CSS_READY
-			});
-			return;
-		}
-	
-		//_readyQueen.curIndex是会被累加的
-		_moveToNextReadyItem();
-		
-		//清空队列
-		if(_isLastReadyItem()) {
-			_initReadyQueen(true);
-		}
-		
-		//如果是<style>标签
-		if(!!curData.style) {
-			_readStyleTagContent(curData.style);
-		}
-		//如果是<link>标签
-		else if(!!curData.link){
-			_getCssSourceByServer(curData.link);
-		}
-	};
-	
-	/**
-	 * 初始化CSS数据
-	 */
-	var _initCssData = function(){
-		styleheets = _getCssData(document.styleSheets);
-		
-		//处理<style>定义的样式
-		if(styleheets.style && styleheets.style.length >0) {
-			jQuery.each(styleheets.style,function(i,style){
-				//加入读取队列
-				_addReadyItem({link:null,style:style});
-			});
-		}
-		
-		//处理<link>引入的css文件
-		if(styleheets.link && styleheets.link.length >0) {
-			jQuery.each(styleheets.link,function(i,link){
-				//加入读取队列
-				_addReadyItem({link:link,style:null});
-			});
-		}
-
-		//开始读取
-		_readRawCss();
-	};
-	
-	/**
-	 * 输出结果
-	 */
-	var _outputResult = function(){
-		
-	    return [{
-				type : 0,	//"冗余的CSS选择器"
-				count : _stats.unmatched.count,
-				content : _stats.unmatched.selectors
-			}, {
-				type : 1,	//"可能用到的CSS伪类选择器"
-				count : _stats.ignored.count,
-				content : _stats.ignored.selectors
-			}, {
-				type : 2,	//"实际用到的CSS选择器"
-				count : _stats.matched.count,
-				content : _stats.matched.selectors
-			}];
-	};
-	
-	/**
-	 * 对以存储的css代码进行解析
-	 */
-	var _dealCssFile = function(){
-		var isStop = false;
-		jQuery.each(_rawCssSource,function(i,fileObj){
-			_dealCssRule(fileObj);
-		});
-	};
-	
-	/**
-	 * 处理某个css文件内的css rules
-	 * @param {Object} _fileObj
-	 * @config {String} fileName
-	 * @config {String} fileContent
-	 */
-	var _dealCssRule = function(_fileObj){
-		var fileName = _fileObj.fileName;
-		var fileContent = _fileObj.fileContent;
-		
-		//检测css是否压缩
-		_detectCssMinify(_fileObj);
-		
-		//检测css expression
-		_findExpression(fileName,fileContent);
-		
-		//检测css background-image
-		_findBackgroundImage(fileName,fileContent);
-		
-		//css源码分析
-		var _cssAnalyticRst = (new baidu.cssAnalytic()).run(fileContent);
-		//获得所有选择器
-		var _selectors = _getSelectors(_cssAnalyticRst);
-		
-		//初始化结果集
-		_initStats();
-		
-		//检测
-		jQuery.each(_selectors,function(i,item){
-			if(item.selector) {
-				_detectSelector(item.selector,item.csstext);
-			}
-		});
-	
-		_summaryInformation.styles.push({
-			path : fileName,
-			content : _outputResult()
-	    });
-	};
-	
-	/**
-	 * 从css分析结果中汇总每个独立的selector和rule的对应关系
-	 * @param {Object} _cssAnalyticRst
-	 */
-	var _getSelectors = function(_cssAnalyticRst){
-		var rst = [],_selector='',_csstext = [],_pre_type_start;
-		for(var i = 0,len = _cssAnalyticRst.length;i < len;i++){
-			var item = _cssAnalyticRst[i];
-			//new line
-			if(item[1] == baidu.FL.FL_NEW_LINE) {
-				continue;
-			}
-			//device description
-			else if(item[1] == baidu.FL.CSS_DEVICE_DESC) {
-				_selector = item[0];
-				_pre_type_start = baidu.FL.CSS_DEVICE_START;
-				continue;
-			}
-			//selector
-			else if(item[1] == baidu.FL.CSS_SELECTOER) {
-				_selector = item[0];
-				_pre_type_start = baidu.FL.CSS_SELECTOER_START;
-				continue;
-			} 
-			//@import、@charset
-			else if(item[1] == baidu.FL.CSS_AT) {
-				_selector = item[0];
-			}
-			//csstext
-			else {
-				var j = i ;
-				for(;j < len;j++) {
-					var jtem = _cssAnalyticRst[j];
-					//@import、@charset
-					if(item[1] == baidu.FL.CSS_AT) {
-						j--;
-						break;
-					} 
-					//device description
-					else if(jtem[1] == baidu.FL.CSS_DEVICE_END && _pre_type_start == baidu.FL.CSS_DEVICE_START) {
-						_csstext.push(jtem[0]);
-						break;
-					}
-					//selector
-					else if(jtem[1] == baidu.FL.CSS_SELECTOER_END && _pre_type_start == baidu.FL.CSS_SELECTOER_START) {
-						_csstext.push(jtem[0]);
-						break;
-					}
-					//csstext 
-					else {
-						_csstext.push(jtem[0]);
-					}
-				}
-				i = j ;
-			}
-		
-			//结果记录
-			rst.push({
-				selector : _selector,
-				csstext : _csstext.join('')
-			});
-			//清空结果集
-			_csstext = [];
-			_pre_type_start = '';
-		}
-		return rst;
-	};
-	
-	/**
-	 * 检测某个selector是否用到
-	 * @param {Object} _selector
-	 * @param {Object} _csstext
-	 */
-	var _detectSelector = function(_selector,_csstext){
-		
-		//用‘,’分割多个selector
-		var _selectors = _selector.replace(/\r?\n/g,'').trim().split(',');
-		
-		var rawSelector = '',arr;
-		var reg = /^([\*\+]+[^ ]+)[ ]+(.*)$/;
-		var vreg = /([^:]+)(:hover|:focus|:visited|:link|:active|:before|:after|::)/;
-		var type;
-		
-		jQuery.each(_selectors,function(i,currSelector){
-			rawSelector = currSelector;
-			arr = reg.exec(currSelector);
-			if(arr && arr[1] && arr[2]) {
-				currSelector = arr[2];
-			}
-			
-			//是否为CSS伪类
-			var isVirtual = false;
-			if(currSelector.indexOf('@') > -1 || currSelector.indexOf('-moz-') > -1) {
-	            _localdata[currSelector] = 1;
-			} else if (!_localdata[currSelector]) {
-				//检测是否是伪类
-		        var virtualCss = vreg.exec(currSelector);
-				if(virtualCss && virtualCss[1]) {
-					isVirtual = true;
-					currSelector = virtualCss[1];
-				}
-				try{
-					//核心:试图根据该选择器去渲染节点,看能找到几个节点,如果没有找到,则表示匹配失败
-		            _localdata[currSelector] = jQuery(currSelector + ':not("[id^=fe-helper-],[id^=fe-helper-] ' + currSelector + '")').length;
-				}catch(err){
-					_localdata[currSelector] = 0;
-				}
-	        }
-			
-			type = _localdata[currSelector] ? isVirtual ? "ignored" : "matched" : "unmatched";
-			
-			//记录结果
-	        _stats[type].count++;
-			_stats[type].selectors.push({
-				selector : rawSelector,
-				cssText : _csstext
-			});
-		});
-	};
-	
-	/**
-	 * 检测css选择器
-	 */
-	var _detectCSS = function(){
-		//处理css文件以及style块
-		_dealCssFile();
-	};
-	
-	/**
-	 * 检测是否引入的重复的文件
-	 */
-	var _detectDuplicatedFile = function(){
-		styleheets = _getCssData(document.styleSheets);
-		
-		var files = {};
-		var duplicatedFiles = [];
-		var dealedFiles = {};
-		
-		//处理<link>引入的css文件
-		if(styleheets.link && styleheets.link.length >0) {
-			
-			jQuery.each(styleheets.link,function(i,link){
-				files[link.href] = parseInt(files[link.href] || 0,10) + 1;
-			});
-			jQuery.each(files,function(href,count){
-				//文件href重复
-				if(count > 1) {
-					duplicatedFiles.push({
-						href : href,
-						count : count
-					});
-				} else {	//href不重复的情况下,检测文件内容是否相同
-					var _fileContent = '';
-					var _dupFiles = [];
-					jQuery.each(_rawCssSource,function(i,file){
-						if(file.href == href) {
-							_fileContent = file.fileContent.replace(/\s+/g,'');
-							return false;
-						}
-					});
-					jQuery.each(_rawCssSource,function(i,file){
-						if(_fileContent == file.fileContent.replace(/\s+/g,'') && !dealedFiles[file.href] && _dupFiles.join(',').indexOf(file.href) == -1) {
-							_dupFiles.push(file.href);
-						}
-					});
-					if(_dupFiles.length > 1) {
-						duplicatedFiles.push({
-							href : href,
-							dupFiles : _dupFiles
-						});
-						dealedFiles[href] = true;
-					}
-				}
-			});
-		}
-		
-		_summaryInformation.duplicatedFiles = duplicatedFiles;
-	};
-	
-	/**
-	 * 初始化
-	 */
-	var _init = function(){
-		//初始化就绪队列
-		_initReadyQueen(false);
-		//初始化CSS数据
-		_initCssData();
-	};
-	
-	/**
-	 * css相关处理
-	 * @param {Function} callback 侦测完毕后的回调方法,形如:function(data){}
-	 * @config {Object} data 就是_summaryInformation
-	 */
-	var _detect = function(callback){
-		
-		//初始化结果集
-		_initSummaryInformation();
-		
-		//执行侦测
-		_detectCSS();
-		
-		//检测重复引入的文件
-		_detectDuplicatedFile();
-		
-		//执行回调
-		if(callback && typeof callback == "function") {
-			callback.call(null,_summaryInformation);
-		}
-	};
-	
-	
-	return {
-		init : _init,
-		detect : _detect
-	};
-	
-})();
-

+ 0 - 308
apps/code-standards/fcp-fl.js

@@ -1,308 +0,0 @@
-/**
- * 注册命名空间:baidu.FL
- */
-baidu.namespace.register("baidu.FL");
-
-
-/**
- * FL常量
- * @author lichengyin (FCP:PHP代码)
- * @cover zhaoxianlie (FCPHelper:将PHP代码重写为Javascript代码)
- */
-baidu.FL = new (function() {
-	this.FL_EOF 						= 1;   //结束
-	this.FL_TPL_DELIMITER 				= 2;   //模板语法
-	this.FL_NEW_LINE 					= 3;   //new line
-	this.FL_NORMAL 						= 4;   //normal,一般很少出现这个
-	
-	this.HTML_CONTENT 					= 111; //文本
-	this.HTML_TAG 						= 112; //一般标签
-	this.HTML_JS_START 					= 113; //js start
-	this.HTML_JS_CONTENT 				= 114; //js content,要手工调用js analytic
-	this.HTML_JS_END 					= 115; //js end
-	this.HTML_CSS_START 				= 116; //css start
-	this.HTML_CSS_CONTENT 				= 117; //css content,要手工调用css analytic
-	this.HTML_CSS_END 					= 118; //css end
-	this.HTML_IE_HACK_START 			= 119; //ie hack start
-	this.HTML_IE_HACK_EDN 				= 120; //ie hack end
-	this.HTML_DOC_TYPE 					= 121; //doc type
-	this.HTML_COMMENT 					= 122; //html comment
-	this.HTML_STATUS_OK					= 123; //status ok
-	this.HTML_TAG_START					= 124; //tag start
-	this.HTML_TAG_END					= 125; //tag end
-	this.HTML_TPL_ATTR_NAME				= 126; //tpl attributes name
-	this.HTML_XML						= 127; //is xml
-	this.HTML_TEXTAREA_START			= 128; //textarea tag start
-	this.HTML_TEXTAREA_CONTENT			= 129; //textarea tag end
-	this.HTML_TEXTAREA_END				= 130; //textarea tag end
-	this.HTML_PRE_START					= 131; //pre tag start
-	this.HTML_PRE_CONTENT				= 132; //pre tag end
-	this.HTML_PRE_END					= 133; //pre tag end
-	
-	this.JS_START_EXPR 					= 211; //start expression
-	this.JS_END_EXPR 					= 212; //end expression
-	this.JS_START_BLOCK 				= 213; //start block
-	this.JS_END_BLOCK 					= 214; //end block
-	this.JS_SEMICOLON 					= 215; //分号
-	this.JS_WORD						= 216; //单词
-	this.JS_OPERATOR					= 217; //操作符
-	this.JS_EQUALS						= 218; //等号
-	this.JS_INLINE_COMMENT				= 219; //行内注释
-	this.JS_BLOCK_COMMENT				= 220; //跨级注释
-	this.JS_COMMENT						= 221; //注释
-	this.JS_STRING						= 222; //字符串	
-	this.JS_IE_CC						= 223; //条件编译	
-	this.JS_REGEXP						= 224; //正则
-	
-	this.JS_MODE_EXPRESSION				= 250; //
-	this.JS_MODE_INDENT_EXPRESSION		= 251; //
-	this.JS_MODE_DO_BLOCK				= 252; //
-	this.JS_MODE_BLOCK					= 253; //
-	this.JS_MODE_ARRAY					= 254;
-	
-
-	this.CSS_AT							= 311; //@
-	this.CSS_NORMAL						= 312; //
-	this.CSS_DEVICE_DESC				= 313; //设备描述内容
-	this.CSS_DEVICE_START				= 314; //设备开始符,为{
-	this.CSS_DEVICE_END					= 315; //设备结束符,为}
-	this.CSS_SELECTOER					= 316; //选择器
-	this.CSS_SELECTOER_START			= 317; //选择器开始符,为{
-	this.CSS_SELECTOER_END				= 318; //选择器结束符,为}
-	this.CSS_COMMENT					= 319; //评论
-	this.CSS_PROPERTY					= 320; //属性
-	this.CSS_VALUE						= 321; //值
-	this.CSS_SEMICOLON 					= 322; //分号
-	this.CSS_COLON						= 323; //冒号
-})();
-
-// FL常量
-baidu.FlConst = {};
-//首先配一个DTD中的白名单
-baidu.FlConst.PUBLIC_ID_WHITE_LIST = {
-    '': {
-        systemIds: {
-            '': true
-        }
-    },
-    '-//W3C//DTD HTML 3.2 Final//EN': {
-        systemIds: {
-            '': true
-        }
-    },
-    '-//W3C//DTD HTML 4.0//EN': {
-        systemIds: {
-            '': true,
-            'http://www.w3.org/TR/html4/strict.dtd': true
-        }
-    },
-    '-//W3C//DTD HTML 4.01//EN': {
-        systemIds: {
-            '': true,
-            'http://www.w3.org/TR/html4/strict.dtd': true
-        }
-    },
-    '-//W3C//DTD HTML 4.0 Transitional//EN': {
-        systemIds: {
-            '': true,
-            'http://www.w3.org/TR/html4/loose.dtd': true
-        }
-    },
-    '-//W3C//DTD HTML 4.01 Transitional//EN': {
-        systemIds: {
-            '': true,
-            'http://www.w3.org/TR/html4/loose.dtd': true,
-            'http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd': true
-        }
-    },
-    '-//W3C//DTD XHTML 1.1//EN': {
-        systemIds: {
-            'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd': true
-        }
-    },
-    '-//W3C//DTD XHTML Basic 1.0//EN': {
-        systemIds: {
-            'http://www.w3.org/TR/xhtml-basic/xhtml-basic10.dtd': true
-        }
-    },
-    '-//W3C//DTD XHTML 1.0 Strict//EN': {
-        systemIds: {
-            'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd': true
-        }
-    },
-    '-//W3C//DTD XHTML 1.0 Transitional//EN': {
-        systemIds: {
-            'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd': true
-        }
-    },
-    'ISO/IEC 15445:1999//DTD HyperText Markup Language//EN': {
-        systemIds: {
-            '': true
-        }
-    },
-    'ISO/IEC 15445:2000//DTD HTML//EN': {
-        systemIds: {
-            '': true
-        }
-    },
-    'ISO/IEC 15445:1999//DTD HTML//EN': {
-        systemIds: {
-            '': true
-        }
-    }
-};
-
-/**
- * IE和Webkit对Doctype的解析差异
- */
-baidu.FlConst.COMPAT_MODE_DIFF_PUBLIC_ID_MAP = {
-    '-//W3C//DTD HTML 4.0 Transitional//EN': {
-        systemIds: {
-            'http://www.w3.org/TR/html4/loose.dtd': {
-                IE: 'S',
-                WebKit: 'Q'
-            }
-        }
-    },
-    'ISO/IEC 15445:2000//DTD HTML//EN': {
-        systemIds: {
-            '': {
-                IE: 'Q',
-                WebKit: 'S'
-            }
-        }
-    },
-    'ISO/IEC 15445:1999//DTD HTML//EN': {
-        systemIds: {
-            '': {
-                IE: 'Q',
-                WebKit: 'S'
-            }
-        }
-    }
-};
-
-
-/**
- * 过时的HTML标签,HTML5已经不再支持
- */
-baidu.FlConst.HTML_DEPRECATED_TAGS = {
-    acronym: "定义首字母缩写",
-    applet: "定义Java Applet",
-    basefont: "定义Font定义",
-    big: "定义大号文本",
-    center: "定义居中的文本",
-    dir: "定义目录列表",
-    font: "定义文字相关",
-    frame: "定义框架",
-    frameset: "定义框架集",
-    isindex: "定义单行的输入域",
-    noframes: "定义noframe 部分",
-    s: "定义加删除线的文本",
-    strike: "定义加删除线的文本",
-    tt: "定义打字机文本",
-    u: "定义下划线文本",
-    xmp: "定义预格式文本",
-    layer: "定义层"
-};
-
-/**
- * 过时的HTML属性,HTML5已经不再支持
- */
-baidu.FlConst.HTML_DEPRECATED_ATTRIBUTES = {
-    align: {
-        iframe: true,
-        img: true,
-        object: true,
-        table: true
-    },
-    color: {
-        font: true
-    },
-    height: {
-        td: true,
-        th: true
-    },
-    language: {
-        script: true
-    },
-    noshade: {
-        hr: true
-    },
-    nowrap: {
-        td: true,
-        th: true
-    },
-    size: {
-        hr: true,
-        font: true,
-        basefont: true
-    }
-};
-
-/**
- * 块级元素
- */
-baidu.FlConst.BLOCK_HTML_ELEMENT = [
-    'address','blockquote','center','dir',
-    'div','dl','fieldset','form','h1','h2',
-    'h3','h4','h5','h6','hr','isindex','menu',
-    'noframes','noscript','ol','p','pre','table','ul'
-];
-
-/**
- * 内联元素
- */
-baidu.FlConst.INLINE_HTML_ELEMENT = [
-    'a','acronym','b','bdo','big','br','cite','code',
-    'dfn','em','font','i','img','input','kbd','label',
-    'q','s','samp','select','small','span','strike','strong',
-    'sub','sup','textarea','tt','u','var'
-];
-
-
-/**
- * 可变元素:为根据上下文语境决定该元素为块元素或者内联元素。
- */
-baidu.FlConst.CHANGE_ABLE_HTML_ELEMENT = [
-    'applet','button','del','iframe',
-    'ins','map','object','script'
-];
-
-//关于IE的条件注释,可以参考这里:http://msdn.microsoft.com/en-us/library/ms537512(v=vs.85).aspx
-
-//条件注释的正则匹配规则
-baidu.FlConst.CONDITIONAL_COMMENT_REGEXP = /\[\s*if\s+[^\]][\s\w]*\]/i;
-
-// 非IE条件注释开始:<![if !IE]> or <![if false]>
-baidu.FlConst.NOT_IE_REVEALED_OPENING_CONDITIONAL_COMMENT_REGEXP = /^\[if\s+(!IE|false)\]$/i;
-
-// IE条件注释结束:<![endif]>
-baidu.FlConst.REVEALED_CLOSING_CONDITIONAL_COMMENT_REGEXP = /^\[endif\s*\]$/i;
-
-// 非IE的条件注释整体:  <!--[if !IE]> HTML <![endif]--> or  <!--[if false]> HTML <![endif]-->
-baidu.FlConst.NOT_IE_HIDDEN_CONDITIONAL_COMMENT_REGEXP = /^\[if\s+(!IE|false)\]>.*<!\[endif\]$/i;
-
-
-/* 正则 */
-baidu.FlConst.REG = {
-    //script标签
-    SCRIPT: /<script[^>]*>[\s\S]*?<\/[^>]*script>/gi,
-    //注释
-    COMMENT: /<!--[\s\S]*?--\>/g,
-    //cssExpression
-    CSS_EXPRESSION: /expression[\s\r\n ]?\(/gi,
-    //textarea
-    TEXTAREA:/<textarea[^>]*>[\s\S]*?<\/[^>]*textarea>/gi,
-    //不合法的标签
-    INVALID_TAG:/<\W+>/gi
-};
-
-/**
- * 能够自动闭合的标签,就算不闭合也不影响兄弟节点的布局
- */
-baidu.FlConst.SELF_CLOSING_TAGS = [
-    'meta','link','area','base',
-    'col','input','img','br',
-    'hr','param','embed'
-];

+ 0 - 901
apps/code-standards/fcp-main.js

@@ -1,901 +0,0 @@
-/**
- * 注册命名空间
- */
-
-baidu.namespace.register("baidu.fcphelper");
-
-baidu.fcphelper = (function(){
-	/**
-	 * 创建一个Accordion
-	 * @param {Object} data
-	 */
-	var _creatResultItem = function(data){
-		return '\
-			<h3 class="rst-title">\
-				<a href="#">' + data.title + ':\
-					<span class="rst-count">' + data.count + '</span>\
-				</a>\
-			</h3>\
-			<div class="rst-content">' + data.content + '</div>\
-			';
-	};
-	
-	/**
-	 * 获取message.json中定义的相关问题以及建议
-	 * @param {Object} perfix
-	 * @param {Object} start
-	 * @param {Object} end
-	 */
-	var _get_issue_suggestion = function(perfix,start,end){
-		
-		var tempArr = [];
-		var tempInt = 0;
-		
-		tempArr.push('<table>');
-		tempArr.push('<thead><tr>'+
-				'<td>序号(Num)</td>'+
-				'<td class="td-content-title">&nbsp;</td>'+
-				'<td class="td-content-content">描述(Description)</td>' +
-				'</tr></thead><tbody>');
-		var addTr = function(_t_title,_t_content) {
-			var _cls = tempInt % 2 == 0 ? 'tr-content-even' : '';
-			tempArr.push('<tr class="' + _cls + '"><th class="td-linenum" rowspan="2">' + (++tempInt) + '</th>' +
-					'<td class="td-content-title"><span class="-x-issue">问题</span></td>' + 
-					'<td class="td-content-content -c-x-issue">' + _t_title + '</td></tr>');
-			tempArr.push('<tr class="' + _cls + '">' +
-					'<td class="td-content-title"><span class="-x-suggestion">建议</span></td>' + 
-					'<td class="td-content-content">' + _t_content + '</td></tr>');
-		};
-		
-		var key;
-		for(var i = start;i <= end;i++) {
-			key = ('0000' + i);
-			key = perfix + '_' + key.substr(key.length - 4);
-			addTr(baidu.i18n.getMessage(key),baidu.i18n.getMessage(key + '_suggestion'));
-		}
-		
-		tempArr.push('</tbody></table>');
-				
-		return tempArr.join('');
-	};
-	
-	/**
-	 * 显示页面上有效的cookie
-	 * @param {Object} respData
-	 * @param {Object} title
-	 * @param {Object} allContent
-	 */
-	var _js_getCookie = function(respData,title,allContent) {
-		var tempArr = [];
-		var tempInt = 0;
-		
-		tempArr.push('<div>' + baidu.i18n.getMessage('msg0042',[document.cookie.getBytes()]) + '。如下是和整个站点相关的cookie:</div><br />');
-		
-		tempArr.push('<table>');
-		tempArr.push('<thead><tr>'+
-				'<td>序号</td>'+
-				'<td class="td-cookie-name">名称(name)</td>' +
-				'<td class="td-cookie-value">值(value)</td>' +
-				'<td class="td-cookie-domain">所在域(domain)</td>' +
-				'<td class="td-cookie-expires">过期时间(expires)</td>' +
-				'<td class="td-cookie-op">操作</td>' +
-				'</tr></thead><tbody>');
-		var d = new Date() - 1;
-		jQuery.each(respData.cookies,function(i,cookie){
-			tempInt++;
-			tempArr.push('<tr>'+
-				'<td class="td-cookie-linenum">' + (i+1) + '</td>'+
-				'<td class="td-cookie-name">' + cookie.name + '</td>' +
-				'<td class="td-cookie-value" id="td-cookie-value-"' + cookie.name + '>' + cookie.value + '</td>' +
-				'<td class="td-cookie-domain">' + cookie.domain + '</td>' +
-				'<td class="td-cookie-expires">' + ( cookie.expirationDate ? new Date(cookie.expirationDate * 1000).format('yyyy年MM月dd日 HH时mm分ss秒') : '-')  + '</td>' +
-				'<td class="td-cookie-op">' +
-					'<button class="fe-a-cookie-delete -f-a-c-d-' + d + ' ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only"><span class="ui-button-text">删除</span></button>' +
-					'<input type="hidden" id="hid-url-' + cookie.name + '" value="' + cookie.url + '" />' +
-					'<input type="hidden" id="hid-domain-' + cookie.name + '" value="' + cookie.domain + '" />' +
-					'<input type="hidden" id="hid-storeId-' + cookie.name + '" value="' + cookie.storeId + '" />' +
-					'<input type="hidden" id="hid-expires-' + cookie.name + '" value="' + (cookie.expirationDate ? cookie.expirationDate : '') + '" />' +
-				'</td>' +
-				'</tr>');
-		});
-		tempArr.push('</tbody></table>');
-        
-		allContent.push(_creatResultItem({
-			title : title,
-			count : tempInt,
-			content : tempArr.join('')
-		}));
-		
-		//-------------------------------------------
-		jQuery('#fe-helper-box .-f-a-c-d-' + d).on('click',function(e){
-			var $this = jQuery(this);
-			
-			var $tr = $this.parent().parent();
-			var key = $tr.children('.td-cookie-name').html().trim();
-			var storeId = jQuery('#hid-storeId-' + key).val().trim();
-			
-			//remove cookie
-			chrome.runtime.sendMessage({
-					"type" : MSG_TYPE.REMOVE_COOKIE,
-					"name" : key,
-					"url" : jQuery('#hid-url-' + key).val().trim(),
-					"storeId" : storeId
-				},function(cookie){
-					var $table = $this.parent().parent().parent();
-					var idx = $table.index($tr) - 1;
-					var x = 0;
-					$tr.remove();
-					//更改序号
-					$table.find('td.td-cookie-linenum').each(function(i,td){
-						jQuery(td).html((i + 1));
-						x++;
-					});
-					//总数
-					$table.parent().parent().prev().find('.rst-count').html(x);
-				});
-		});
-	};
-	
-	/**
-	 * 显示页面上的script标签
-	 * @param {Object} respData
-	 * @param {Object} title
-	 * @param {Object} allContent
-	 */
-	var _js_getScriptTag = function(respData,title,allContent) {
-		
-		var tempArr = [];
-		var tempInt = 0;
-		
-		tempArr.push('<table>');
-		tempArr.push('<thead><tr>'+
-				'<td>序号(Num)</td>'+
-				'<td class="td-content">描述(Description)</td>' +
-				'</tr></thead><tbody>');
-		var addTr = function(_content){
-			tempArr.push('<tr>'+
-				'<th class="td-linenum">' + ++tempInt + '</th>'+
-				'<td class="td-content">' + _content + '</td></tr>');
-			
-		};
-		//script block
-		if(respData.scriptTag.scriptBlock) {
-			addTr(baidu.i18n.getMessage('msg0033',[respData.scriptTag.scriptBlock]));
-		}
-		
-		//script src
-		if(respData.scriptTag.scriptSrc) {
-			addTr(baidu.i18n.getMessage('msg0034',[respData.scriptTag.scriptSrc]));
-		}
-		
-		//JS文件被压缩?
-		if(respData.jsMinified.count > 0) {
-			var t = [];
-			jQuery.each(respData.jsMinified.files,function(k,item){
-				if(item.href == '#') {
-					t.push(baidu.i18n.getMessage('msg0047',[item.fileName]));
-				} else {
-					t.push(baidu.i18n.getMessage('msg0062',[item.href,item.fileName]));
-				}
-			});
-			addTr(baidu.i18n.getMessage('msg0045',[t.join('、')]));
-		}
-
-		//tangram
-		if(respData.tangram.length > 0) {
-			var t = [];
-			jQuery.each(respData.tangram,function(i,item){
-				t.push(item);
-			});
-			if(t.length == 1) {
-				addTr(baidu.i18n.getMessage('msg0054',[t.join('')]));
-			} else {
-				addTr(baidu.i18n.getMessage('msg0055',[t.join('、')]));
-			}
-		}
-		
-		//重复引入的文件
-		if(respData.duplicatedFiles.length) {
-			var dupFile = [];
-			var dupHref = [];
-			var txt = [];
-			jQuery.each(respData.duplicatedFiles,function(i,item){
-				if(item.dupFiles) {	//不同地址的文件,但内容重复
-					var t = [];
-					jQuery.each(item.dupFiles,function(j,f){
-						t.push(baidu.i18n.getMessage('msg0069',[f,f]));
-					});
-					dupFile.push(baidu.i18n.getMessage('msg0070',[t.join('、')]));
-				} else {	//同一个地址的文件,被多次引入
-					dupHref.push(baidu.i18n.getMessage('msg0068',[item.href,item.href,item.count]));
-				}
-			});
-			if(dupHref.length) {
-				txt.push(baidu.i18n.getMessage('msg0066',['<div style="margin-left:30px;">' + dupHref.join('') + '</div>']));
-			}
-			if(dupFile.length) {
-				txt.push(baidu.i18n.getMessage('msg0067',['<div style="margin-left:30px;">' + dupFile.join('') + '</div>']));
-			}
-			addTr(txt.join(''));
-		}
-
-		tempArr.push('</tbody></table>');
-		allContent.push(_creatResultItem({
-			title : title,
-			count : tempInt,
-			content : tempArr.join('')
-		}));
-	};
-	
-	/**
-	 * 显示过期标签的检测结果
-	 * @param {Object} respData
-	 * @param {Object} title
-	 * @param {Object} allContent
-	 */
-	var _html_getHTMLDeprecatedTag = function(respData,title,allContent) {
-		var isEmpty = true;
-		for(var k in respData.HTMLBase.HTMLDeprecatedTag) {
-			isEmpty = false;
-			break;
-		}
-		if(isEmpty) {
-			return;
-		}
-		
-		var tempArr = [];
-		var tempInt = 0;
-		
-		tempArr.push('<table>');
-		tempArr.push('<thead><tr>'+
-				'<td>标签(Tag)</td>'+
-				'<td class="td-content">描述(Description)</td>' +
-				'</tr></thead><tbody>');
-		jQuery.each(respData.HTMLBase.HTMLDeprecatedTag,function(key,item){
-			tempInt++;
-			tempArr.push('<tr>'+
-				'<th class="td-linenum">' + key + '</th>'+
-				'<td class="td-content">' + 
-					baidu.i18n.getMessage('msg0004',[item]) +
-					baidu.i18n.getMessage('msg0005') + '</td></tr>');
-		});
-		tempArr.push('</tbody></table>');
-		allContent.push(_creatResultItem({
-			title : title,
-			count : tempInt,
-			content : tempArr.join('')
-		}));
-	};
-	
-	/**
-	 * 显示过期的属性
-	 * @param {Object} HTMLDeprecatedAttributes
-	 * @param {Object} title
-	 * @param {Object} allContent
-	 */
-	var _html_getHTMLDeprecatedAttribute = function(respData,title,allContent){
-		var isEmpty = true;
-		for(var k in respData.HTMLBase.HTMLDeprecatedAttribute) {
-			isEmpty = false;
-			break;
-		}
-		if(isEmpty) {
-			return;
-		}
-		
-		var tempArr = [];
-		var tempInt = 0; 
-		
-		tempArr.push('<table>');
-		tempArr.push('<thead><tr>'+
-				'<td>属性(Attr)</td>'+
-				'<td class="td-content">描述(Description)</td>' +
-				'</tr></thead><tbody>');
-		jQuery.each(respData.HTMLBase.HTMLDeprecatedAttribute,function(key,item){
-			tempInt++;
-			tempArr.push('<tr>' +
-				'<th class="td-linenum">' + key + '</th>' +
-				'<td class="td-content"><span class="x-detail">' +
-				(function(){
-					var arr = [];
-					jQuery.each(item,function(k,v){
-						arr.push(baidu.i18n.getMessage('msg0007',[ v,k ]));
-					});
-					return arr.join('');
-				})() + baidu.i18n.getMessage('msg0005') + '</span></td></tr>');
-		});
-		tempArr.push('</tbody></table>');
-		allContent.push(_creatResultItem({
-			title : title,
-			count : tempInt,
-			content : tempArr.join('')
-		}));
-	};
-	
-	/**
-	 * 获取Link标签的解析结果
-	 * @param {Object} respData
-	 * @param {Object} title
-	 * @param {Object} allContent
-	 */
-	var _html_getLink = function(respData,title,allContent){
-		var notInHead = respData.LINK.notInHead;
-		
-		if(notInHead.length == 0) return;
-		
-		var ct = '';
-		ct = '<div>' + baidu.i18n.getMessage('msg0021',[notInHead.length,
-				'head']) + '</div>';
-		ct += (function(d){
-			var arr = ['<table>'];
-			arr.push('<thead><tr>'+
-					'<td>序号(Num)</td>'+
-					'<td class="td-content">描述(Description)</td>' +
-					'</tr></thead><tbody>');
-			if(notInHead.length) {
-				jQuery.each(d,function(i,link){
-					arr.push('<tr>\
-						<th class="td-linenum">' + (i + 1) + '</th>\
-						<td class="td-content">' + link.outerHTML.replace(/</g,'&lt;').replace(/>/g,'&gt;') + '</td>\
-					</tr>');
-				});
-			}
-			arr.push('</tbody></table>');
-			return arr.join('');
-		})(notInHead);
-		allContent.push(_creatResultItem({
-			title : title,
-			count : notInHead.length,
-			content : ct
-		}));
-	};
-		
-	/**
-	 * 获取标签包含关系
-	 * @param {Object} respData
-	 * @param {Object} title
-	 * @param {Object} allContent
-	 */
-	var _html_getTagIncludeCase = function(respData,title,allContent){
-		
-		if(respData.tagInclude.length == 0) return;
-		
-		var ct = '';
-		ct += (function(d){
-			var arr = ['<table>'];
-			arr.push('<thead><tr>'+
-					'<td>序号(Num)</td>'+
-					'<td class="td-content">描述(Description)</td>' +
-					'</tr></thead><tbody>');
-			if(respData.tagInclude.length > 0) {
-				jQuery.each(d,function(id,v){
-					arr.push('<tr>\
-						<th class="td-linenum">' + (id + 1) + '</th>\
-						<td class="td-content">' + baidu.i18n.getMessage('msg0035',[v.inline,v.block]) + '</td>\
-					</tr>');
-				});
-			}
-			arr.push('</tbody></table>');
-			return arr.join('');
-		})(respData.tagInclude);
-		allContent.push(_creatResultItem({
-			title : title,
-			count : respData.tagInclude.length,
-			content : ct
-		}));
-	};
-	
-	/**
-	 * DocumentMode
-	 * @param {Object} respData
-	 */
-	var _html_getDocMode = function(respData){
-		var tempArr = [];
-	    var quirksMode = '<strong>混杂模式</strong>';
-	    var standardsMode = '<em>标准模式</em>';
-	    var mode = (respData.documentMode.WebKit == 'S') ? standardsMode : quirksMode;
-	
-	    if (respData.documentMode.hasDocType) {
-			tempArr.push(baidu.i18n.getMessage('msg0009'));
-	        if (respData.documentMode.isUnusualDocType) {
-	            tempArr.push(baidu.i18n.getMessage('msg0010',[mode,quirksMode]));
-	        }
-	        if (respData.documentMode.hasConditionalCommentBeforeDTD || 
-					respData.documentMode.hasCommentBeforeDTD) {
-	            tempArr.push(baidu.i18n.getMessage('msg0011',[mode,quirksMode]));
-	        }
-	        
-	        if (respData.documentMode.IE == respData.documentMode.WebKit) {
-	            tempArr.push(baidu.i18n.getMessage('msg0012',[mode]));
-	            if (respData.documentMode.WebKit == 'Q') {
-	            	tempArr.push(baidu.i18n.getMessage('msg0013'));
-	            }
-	        } else {
-	            if (!respData.documentMode.isUnusualDocType) {
-	                if (respData.documentMode.IE) {
-						var $1 = (respData.documentMode.IE == 'Q') ? quirksMode : standardsMode;
-						var $2 = (respData.documentMode.WebKit == 'Q') ? quirksMode : standardsMode;
-	            		tempArr.push(baidu.i18n.getMessage('msg0014',[$1,$2]));
-	            		tempArr.push(baidu.i18n.getMessage('msg0013'));
-	                } else {
-	                    tempArr.push(baidu.i18n.getMessage('msg0015'));
-	                }
-	            } else {
-	            	tempArr.push(baidu.i18n.getMessage('msg0013'));
-	            }
-	        }
-	    } else {
-	        // 没有设置DTD
-	        tempArr.push(baidu.i18n.getMessage('msg0016',[mode]));
-	        tempArr.push(baidu.i18n.getMessage('msg0013'));
-	    }
-		
-		return tempArr.join('');
-	};
-	
-	/**
-	 * 获取DOM解析的结果
-	 * @param {Object} respData
-	 * @param {Object} title
-	 * @param {Object} allContent
-	 */
-	var _html_getDom = function(respData,title,allContent) {
-		var tempArr = [];
-		var tempInt = 0;
-		
-		tempArr.push('<table>');
-		tempArr.push('<thead><tr>'+
-				'<td>序号(Num)</td>'+
-				'<td class="td-content">描述(Description)</td>' +
-				'</tr></thead><tbody>');
-		var addTr = function(text) {
-			tempArr.push('<tr><th class="td-linenum">' + (++tempInt) + '</th>' +
-					'<td class="td-content">' + text + '</td></tr>');
-		};
-		
-		//doctype
-		addTr(_html_getDocMode(respData));
-		
-		//总数量
-		addTr(baidu.i18n.getMessage('msg0028',[respData.DOM.count]));
-		
-		//dom节点的最大嵌套深度
-		addTr(baidu.i18n.getMessage('msg0063',[respData.DOM.maxDepth.depth,respData.DOM.maxDepth.xpath]));
-		
-		//title标签检测
-		if(respData.title.length == 0) {
-			addTr(baidu.i18n.getMessage('msg0049') + baidu.i18n.getMessage('msg0052'));
-		} else if(respData.title.length > 1) {
-			addTr(baidu.i18n.getMessage('msg0051') + baidu.i18n.getMessage('msg0052'));
-		} else if(!respData.title[0].isInHead){
-			addTr(baidu.i18n.getMessage('msg0050') + baidu.i18n.getMessage('msg0052'));
-		}
-		
-		//img标签的src=''
-		if(respData.imgTag.length > 0) {
-			var t = '';
-			jQuery.each(respData.imgTag,function(i,k){
-				t = '';
-				t += k.id ? '#' + k.id : '';
-				t += t.className ? '.' + t.className.replace(/\s+/g,'.') : '';
-				t = t ? t : k.outerHTML.replace(/</g,'&lt;').replace(/>/g,'&gt;');
-				addTr(baidu.i18n.getMessage('msg0053',[t]));
-			});
-		}
-		
-		//所有注释数量
-		if(respData.DOM.allComm.length) {
-			//IE条件注释
-		    if (respData.DOM.IECondComm.length) {
-				addTr(baidu.i18n.getMessage('msg0018',[respData.DOM.IECondComm.length]));
-			}
-			//Firefox中不支持的注释:不能出现‘--’
-		    if (respData.DOM.FFNotSptComm.length) {
-				addTr(baidu.i18n.getMessage('msg0060',[respData.DOM.FFNotSptComm.length]));
-			}
-			
-			//所有注释
-			addTr(baidu.i18n.getMessage('msg0030',[respData.DOM.allComm.length,respData.DOM.allComm.join('').getBytes()]) + baidu.i18n.getMessage('msg0019'));
-		}
-		
-		//重复性的ID
-		if(respData.ID.count) {
-			var id_arr = [];
-			jQuery.each(respData.ID.ids,function(id,v){
-				id_arr.push(baidu.i18n.getMessage('msg0064',[id,v]));
-			});
-			addTr(baidu.i18n.getMessage('msg0026',[respData.ID.count,id_arr.join(',')]));
-		}
-		
-		//问题Input
-		if(respData.DOM.invalidInput.count) {
-			addTr(baidu.i18n.getMessage('msg0029',[respData.DOM.invalidInput.count]));
-		}
-		
-		//HTML是否压缩
-		if(!respData.htmlMinified) {
-			addTr(baidu.i18n.getMessage('msg0043'));
-		}
-		
-		//未闭合的标签
-		if(respData.unClosedTags.length > 0) {
-			var t = [];
-			jQuery.each(respData.unClosedTags,function(k,item){
-				t.push(baidu.i18n.getMessage('msg0046',[item]));
-			});
-			addTr(baidu.i18n.getMessage('msg0038',[t.join('、')]));
-		}
-		
-		tempArr.push('</tbody></table>');
-		
-		allContent.push(_creatResultItem({
-			title : title,
-			count : tempInt,
-			content : tempArr.join('')
-		}));
-	};
-	
-	/**
-	 * 检测CSS的使用情况
-	 * @param {Object} respData
-	 * @param {Object} title
-	 * @param {Object} allContent
-	 */
-	var _css_getCssUsage = function(respData){
-		
-		var _cssTitles = [
-			baidu.i18n.getMessage('msg0039'),
-			baidu.i18n.getMessage('msg0040'),
-			baidu.i18n.getMessage('msg0041')
-		];
-		
-		var _getCssTable = function(d){
-			var arr = ['<table>'];
-			arr.push('<thead><tr>'+
-					'<td>序号(Num)</td>'+
-					'<td class="td-content">选择器(CSS Selector)</td>' +
-					'</tr></thead><tbody>');
-			jQuery.each(d,function(i,item){
-				arr.push('<tr>' +
-					'<th class="td-linenum">' + (i + 1) + '</th>' +
-					'<td class="td-content">' +
-						'<span class="x-selector">' + item.selector + '</span>' +
-						'<span class="x-css-text">' + item.cssText + '</span>' +
-					'</td>' +
-				'</tr>');
-			});
-			arr.push('</tbody></table>');
-			return arr.join('');
-		};
-		
-		//style标签内的css检测、link引入的css检测
-		jQuery.each(respData.styles,function(i,style){
-			var allContent = [];
-			jQuery.each(style.content,function(j,v){
-				allContent.push(_creatResultItem({
-					title : _cssTitles[j],
-					count : v.count,
-					content : _getCssTable(v.content)
-				}));
-			});
-			//创建tab
-			baidu.fcptabs.addCssTab( style.path, allContent.join('') );
-		});
-	};
-	
-	/**
-	 * 汇总css文件中用到的所有背景图片
-	 * @param {Object} respData
-	 */
-	var _css_getBackgroundImages = function(respData,allContent){
-		var tempArr = [];
-		var lineNum = 0;
-		var tempInt = 0;
-		tempArr.push('<table>');
-		tempArr.push('<thead><tr>'+
-				'<td>序号(Num)</td>'+
-				'<td class="td-content">描述(Description)</td>' +
-				'</tr></thead><tbody>');
-		var addTr = function(text) {
-			tempArr.push('<tr><th class="td-linenum">' + (++lineNum) + '</th>' +
-					'<td class="td-content">' + text + '</td></tr>');
-		};
-		//CSS背景图片的使用情况
-		if(respData.backgroundImages.length) {
-			var a = [];
-			var d = new Date() - 1;
-			jQuery.each(respData.backgroundImages,function(i,item){
-				a = ['<div class="table-css-bg -t-c-b-' + d + '" style="display:none;">'];
-				jQuery.each(item.bgImages,function(j,b){
-					a.push('<div>' + b + '</div>');
-				});
-				a.push('</div>');
-				addTr(baidu.i18n.getMessage('msg0057',[item.fileName,item.bgImages.length,d]) + a.join(''));
-				tempInt += item.bgImages.length;
-			});
-		
-			jQuery('#fe-helper-box .-x-expand-' + d).on('click',function(){
-				var _this = jQuery(this);
-				var _table = _this.parent().next();
-				
-				if(_table.css('display') == 'none') {
-					_table.slideDown(300);
-					_this.html('收起');
-				} else {
-					_table.slideUp(300);
-					_this.html('展开');
-				}
-			});
-
-			jQuery('#fe-helper-box .-t-c-b-' + d + ' div').on('mouseover',function(e){
-				var imgTooltip = jQuery('#fe-img-tootip');
-				if(!imgTooltip[0]) {
-					imgTooltip = jQuery('<img id="fe-img-tootip" src="' + jQuery(this).html().trim() + '" alt="load image failed" />').appendTo('body');
-				} else {
-					imgTooltip.attr('src',jQuery(this).html().trim());
-				}
-				imgTooltip.css({
-					top : e.pageY + 20,
-					left : e.pageX + 20,
-					'max-width' : 500,
-					'max-height' : 500
-				}).show();
-			}).on('mouseout',function(e){
-				var imgTooltip = jQuery('#fe-img-tootip');
-				imgTooltip.hide();
-			})
-		}
-		
-		tempArr.push('</tbody></table>');
-		
-		allContent.push(_creatResultItem({
-			title : baidu.i18n.getMessage('msg0065'),
-			count : tempInt,
-			content : tempArr.join('')
-		}));
-	};
-	
-	
-	/**
-	 * css检测结果汇总
-	 * @param {Object} respData
-	 */
-	var _css_totalDetectResult = function(respData){
-		var tempArr = [];
-		var tempInt = 0;
-		var allContent = [];
-		tempArr.push('<table>');
-		tempArr.push('<thead><tr>'+
-				'<td>序号(Num)</td>'+
-				'<td class="td-content">描述(Description)</td>' +
-				'</tr></thead><tbody>');
-		var addTr = function(text) {
-			tempArr.push('<tr><th class="td-linenum">' + (++tempInt) + '</th>' +
-					'<td class="td-content">' + text + '</td></tr>');
-		};
-		
-		//重复引入的文件
-		if(respData.duplicatedFiles.length) {
-			var dupFile = [];
-			var dupHref = [];
-			var txt = [];
-			jQuery.each(respData.duplicatedFiles,function(i,item){
-				if(item.dupFiles) {	//不同地址的文件,但内容重复
-					var t = [];
-					jQuery.each(item.dupFiles,function(j,f){
-						t.push(baidu.i18n.getMessage('msg0069',[f,f]));
-					});
-					dupFile.push(baidu.i18n.getMessage('msg0070',[t.join('、')]));
-				} else {	//同一个地址的文件,被多次引入
-					dupHref.push(baidu.i18n.getMessage('msg0068',[item.href,item.href,item.count]));
-				}
-			});
-			if(dupHref.length) {
-				txt.push(baidu.i18n.getMessage('msg0066',['<div style="margin-left:30px;">' + dupHref.join('') + '</div>']));
-			}
-			if(dupFile.length) {
-				txt.push(baidu.i18n.getMessage('msg0067',['<div style="margin-left:30px;">' + dupFile.join('') + '</div>']));
-			}
-			addTr(txt.join(''));
-		}
-		
-		//CSS文件压缩
-		if(respData.cssMinified.count > 0) {
-			var t = [];
-			jQuery.each(respData.cssMinified.files,function(k,item){
-				if(item.href == '#') {
-					t.push(baidu.i18n.getMessage('msg0047',[item.fileName]));
-				} else {
-					t.push(baidu.i18n.getMessage('msg0062',[item.href,item.fileName]));
-				}
-			});
-			addTr(baidu.i18n.getMessage('msg0044',[t.join('、')]));
-		}
-		
-		//css expression
-		if(respData.expressions.length) {
-			var a = [],t = 0;
-			jQuery.each(respData.expressions,function(i,item){
-				a.push(baidu.i18n.getMessage('msg0059',[item.fileName,item.count]));
-				t += item.count;
-			});
-			addTr(baidu.i18n.getMessage('msg0058',[t,a.join('、')]));
-		}
-		
-		tempArr.push('</tbody></table>');
-		
-		allContent.push(_creatResultItem({
-			title : baidu.i18n.getMessage('msg0048'),
-			count : tempInt,
-			content : tempArr.join('')
-		}));
-		
-		//背景图片
-		_css_getBackgroundImages(respData,allContent);
-		
-		//创建tab
-		baidu.fcptabs.addCssTab( baidu.i18n.getMessage('msg0048'), allContent.join('') );
-	};
-
-	
-	/**
-	 * HTML侦测,通过调用baidu.html.detect方法进行
-	 */
-	var _detectHTML = function(){
-		baidu.html.detect(function(respData){
-			var content = [];
-			
-			//html.HTMLDeprecatedTag
-			_html_getHTMLDeprecatedTag(respData,baidu.i18n.getMessage('msg0006'),content);
-	
-			//html.HTMLDeprecatedAttribute
-			_html_getHTMLDeprecatedAttribute(respData,baidu.i18n.getMessage('msg0008'),content);
-	
-			//LINK
-			_html_getLink(respData,baidu.i18n.getMessage('msg0022'),content);
-	        
-			//获取标签包含关系
-			_html_getTagIncludeCase(respData,baidu.i18n.getMessage('msg0036'),content);
-			
-	        //其他DOM相关处理
-			_html_getDom(respData,baidu.i18n.getMessage('msg0020'),content);
-			
-			//创建tab 
-			baidu.fcptabs.addHtmlTab(content.join(''));
-		});
-	};
-	
-	/**
-	 * CSS侦测,通过调用baidu.css.detect方法进行
-	 */
-	var _detectCSS = function(){
-		baidu.css.detect(function(respData){
-			
-			//执行检测并查创建tab
-			_css_getCssUsage(respData);
-			
-			//所有检测结果
-			_css_totalDetectResult(respData);
-			
-		});
-	};
-	
-	
-	/**
-	 * Javascript侦测,通过调用baidu.js.detect方法进行
-	 */
-	var _detectJavascript = function(){
-		baidu.js.detect(function(respData){
-			var allContent = [];
-			
-			//cookies
-			_js_getCookie(respData,baidu.i18n.getMessage('msg0031'),allContent);
-			
-			//script相关检测
-			_js_getScriptTag(respData,baidu.i18n.getMessage('msg0032'),allContent);
-			
-			//创建tab
-			baidu.fcptabs.addJavascriptTab(allContent.join(''));
-		});
-	};
-	
-	/**
-	 * 增加HTML、CSS、Javascript的问题及建议tab
-	 */
-	var _addIssueSuggestionTab = function(){
-		
-		var _getBtnString = function(_text){
-			return '<a class="-f-h-get-more-" href="#" onclick="return false;">' + _text + '&gt;&gt;</a>';
-		};
-		
-		//HTML问题及建议
-		jQuery(_getBtnString('查看更多HTML帮助')).appendTo('#fe-helper-tab-html').click(function(e){
-
-			if(!jQuery('#fe-helper-tab-HTML-issue-sug')[0]){
-				baidu.fcptabs.addIssueSuggestionTab('HTML',_get_issue_suggestion('html',1,42));
-			} 
-			
-			var $allTabs = jQuery( "#fe-helper-main-tab>div");
-			var index = $allTabs.index( jQuery( '#fe-helper-tab-HTML-issue-sug' ) );
-			jQuery('#fe-helper-main-tab').tabs( "select" , index );
-		});
-		
-		//CSS问题及建议
-		jQuery(_getBtnString('查看更多CSS帮助')).appendTo('#fe-helper-tab-css').click(function(e){
-
-			if(!jQuery('#fe-helper-tab-CSS-issue-sug')[0]){
-				baidu.fcptabs.addIssueSuggestionTab('CSS',_get_issue_suggestion('css',1,119));
-			} 
-
-			var $allTabs = jQuery( "#fe-helper-main-tab>div");
-			var index = $allTabs.index( jQuery( '#fe-helper-tab-CSS-issue-sug' ) );
-			jQuery('#fe-helper-main-tab').tabs( "select" , index );
-		});
-		
-		//Javascript问题及建议
-		jQuery(_getBtnString('查看更多Javascript帮助')).appendTo('#fe-helper-tab-js').click(function(e){
-
-			if(!jQuery('#fe-helper-tab-Javascript-issue-sug')[0]){
-				baidu.fcptabs.addIssueSuggestionTab('Javascript',_get_issue_suggestion('javascript',1,114));
-			}
-
-			var $allTabs = jQuery( "#fe-helper-main-tab>div");
-			var index = $allTabs.index( jQuery( '#fe-helper-tab-Javascript-issue-sug' ) );
-			jQuery('#fe-helper-main-tab').tabs( "select" , index );
-		});
-	};
-	
-	
-	/**
-	 * 初始化静态文件
-	 */
-	var _initStaticFile = function(){
-		//////////先做一些准备工作/////////////////////
-		//css初始化
-		baidu.css.init();
-		//js初始化
-		baidu.js.init();
-	};
-
-	/**
-	 * 初始化页面
-	 * @return {[type]}
-	 */
-	var _initHtml = function(callback){
-		//html初始化
-		baidu.html.init(callback);
-	};
-	
-	/**
-	 * 执行FCPHelper检测
-	 */
-	var _detect = function(){
-		//显示进度条
-		baidu.fcptabs.createProgressBar();
-		
-		window.setTimeout(function(){
-			//HTML侦测
-			_detectHTML();
-			
-			//CSS侦测
-			_detectCSS();
-			
-			//Javascript侦测
-			_detectJavascript();
-			
-			//增加问题及建议Tab
-			_addIssueSuggestionTab();
-			
-			//检测完毕更新进度条
-			baidu.fcptabs.updateProgressBar(100);
-		},100);
-	};
-	
-	return {
-		initStaticFile : _initStaticFile,
-		initHtml : _initHtml,
-		detect : _detect
-	};
-	
-})();
-

+ 0 - 294
apps/code-standards/fcp-tabs.js

@@ -1,294 +0,0 @@
-/**
- * 注册命名空间
- */
-baidu.namespace.register("baidu.fcptabs");
-
-/**
- * tab相关处理
- * @author zhaoxianlie 
- */
-baidu.fcptabs = (function(){
-	
-	/**
-	 * 存储临时变量
-	 */
-	var _tempvar = {};
-	
-	/**
-	 * 创建主面板
-	 */
-	var _createMainTab = function(){
-		//首先检测面板箱是否存在
-		var $tabBox = jQuery("#fe-helper-box");
-		var $mainTab = jQuery("#fe-helper-main-tab");
-		
-		//有了则删掉
-		if($tabBox[0]){
-			$tabBox.remove();
-		}
-		
-		$tabBox = jQuery('<div id="fe-helper-box" class="fe-helper-hide"></div>').appendTo("body");
-		jQuery('<iframe id="fe-helper-main-ifr" src="about:blank" frameborder="0"></iframe>').appendTo($tabBox);
-		$mainTab = jQuery('<div id="fe-helper-main-tab"></div>').appendTo($tabBox).html('\
-		<ul id="fe-helper-main-ul">\
-			<li id="fe-helper-closethick"><span class="ui-icon ui-icon-closethick" title="关闭面板">Close</span></li>\
-			<li id="fe-helper-plusthick" class="fe-helper-hide"><span class="ui-icon ui-icon-plusthick" title="最大化面板">Maximun</span></li>\
-			<li id="fe-helper-minusthick"><span class="ui-icon ui-icon-minusthick" title="最小化面板">Minimun</span></li>\
-		</ul>\
-		');
-		
-		//最大化显示
-		$tabBox.css({
-			height : jQuery(window).height()
-		});
-	
-		// 初始化mainTab,并增加tab方法
-		$mainTab.tabs({
-			tabTemplate: "<li><a href='#{href}'>#{label}</a> <span class='ui-icon ui-icon-close'>Remove Tab</span></li>",
-			add: function( event, ui ) {
-							
-				//设置子面板大小
-				jQuery('#fe-helper-tab-' + _tempvar.type).css({
-					height : jQuery(window).height() - 80
-				});
-				
-				var _t_id = 'fe-helper-acc-' + _tempvar.type;
-				
-				jQuery( ui.panel ).append( "<div id='" + _t_id + "'>" + _tempvar.tabContent + "</div>" );
-									
-				//设置子面板大小
-				jQuery('#' + _t_id).css({
-					height : jQuery(window).height() - 80
-				});
-						
-				//是否需要将内容部分以Accordion形式展现
-				if(_tempvar.isAccordion) {
-					jQuery("#" + _t_id).accordion({
-						collapsible: true,
-						active:false
-					});
-					
-					//.rst-content
-					var _rst_content = jQuery("#" + _t_id + " .rst-content");
-					var _height = jQuery(window).height() - 120 - _rst_content.length * 30;
-					_rst_content.css({
-						height : _height
-					});
-				}
-			}
-		});
-		
-		//对新产生的tab,增加移除事件
-		jQuery( "#fe-helper-main-ul span.ui-icon-close" ).on( "click", function() {
-			var $allTabs = jQuery( "#fe-helper-main-ul li");
-			var index = $allTabs.index( jQuery( this ).parent() ) - 3;
-			$mainTab.tabs( "remove", index );
-			
-			//如果所有tab都关闭了,则关闭整个tabBox
-			if($allTabs.length == 4) {
-				_tempvar.tabBox.remove();
-			}
-		});
-		
-		//三个按钮的事件
-		jQuery("#fe-helper-closethick").click(function(e){
-			_tempvar.tabBox.hide("slide");
-		});
-		jQuery("#fe-helper-plusthick").click(function(e){
-			_tempvar.tabBox.css({
-				height : jQuery(window).height()
-			});
-			jQuery(this).hide().next().show();
-		});
-		jQuery("#fe-helper-minusthick").click(function(e){
-			_tempvar.tabBox.css({
-				height : 38
-			});
-			jQuery(this).hide().prev().show();
-		});
-		
-		//window大小改变时候
-		jQuery(window).resize(function(e){
-			_tempvar.tabBox.css({
-				height : jQuery(window).height()
-			});
-		});
-	
-		//保存mainTab
-		_tempvar.tabBox = $tabBox;
-		_tempvar.mainTab = $mainTab;
-		
-		return $tabBox;
-	};
-	
-	/**
-	 * 根据不同的标题,在页面上增加面板
-	 * @param {Object} type 面板的类型:HTML,CSS,Javascript
-	 * @param {Object} tabTitle 面板标题
-	 * @param {Object} tabContent 面板内容
-	 * @param {Object} isAccordion 是否生成Accordion
-	 */
-	var _addTab = function(type,tabTitle,tabContent,isAccordion) {
-		//保存这个值,创建tab时用到
-		_tempvar.type = type;
-		_tempvar.tabContent = tabContent;
-		_tempvar.isAccordion = isAccordion;
-		
-		//创建新的面板
-		return _tempvar.mainTab.tabs(
-			"add",
-			"#fe-helper-tab-" + _tempvar.type,
-			tabTitle);
-	};
-	
-	/**
-	 * 根据不同的标题,在页面上增加HTML面板
-	 * @param {Object} type 面板的类型:HTML,CSS,Javascript
-	 * @param {Object} tabContent HTML面板内容
-	 */
-	var _addIssueSuggestionTab = function(type,tabContent) {
-		//创建面板
-		return _addTab(type + '-issue-sug',baidu.i18n.getMessage('msg0061',[type]),tabContent,false);
-	};
-	
-	/**
-	 * 根据不同的标题,在页面上增加HTML面板
-	 * @param {Object} tabContent HTML面板内容
-	 */
-	var _addHtmlTab = function(tabContent) {
-		//创建面板
-		return _addTab('html',baidu.i18n.getMessage('msg0001'),tabContent,true);
-	};
-	
-	/**
-	 * 在页面上创建Javascript面板
-	 * @param {Object} tabContent HTML面板内容
-	 */
-	var _addJavascriptTab = function(tabContent) {
-		//创建新的面板
-		return _addTab('js',baidu.i18n.getMessage('msg0003'),tabContent,true);
-	};
-	
-	/**
-	 * 根据不同的标题,在页面上增加CSS面板
-	 * @param {Object} tabTitle 面板标题
-	 * @param {Object} tabContent 面板内容
-	 */
-	var _addCssTab = function(tabTitle,tabContent) {
-		//保存这个值,创建tab时用到
-		_tempvar.cssTabContent = tabContent;
-		_tempvar.cssTabCount = _tempvar.cssTabCount || 0;
-		_tempvar.cssTabCount++
-		
-		_tempvar.cssTab = jQuery('#fe-helper-tab-css');
-		
-		if(!_tempvar.cssTab[0]) {
-			//创建面板
-			_addTab('css',baidu.i18n.getMessage('msg0002'),'',false);
-			_tempvar.cssTab = jQuery('#fe-helper-tab-css').html('<ul id="fe-helper-css-ul"></ul>');
-			
-			// 初始化mainTab,并增加tab方法
-			_tempvar.cssTab.tabs({
-				tabTemplate: "<li><a href='#{href}'>#{label}</a> <span class='ui-icon ui-icon-close'>Remove Tab</span></li>",
-				add: function( event, ui ) {
-					var _t_id = 'fe-helper-css-acc-' + _tempvar.cssTabCount;
-					jQuery( ui.panel ).append( "<div id='" + _t_id + "'>" + _tempvar.cssTabContent + "</div>" );
-					jQuery("#" + _t_id).accordion({
-						collapsible: true,
-						active:false
-					});
-					
-					//.rst-content
-					var _rst_content = jQuery("#" + _t_id + " .rst-content");
-					var _height = jQuery(window).height() - 180 - _rst_content.length * 30;
-					_rst_content.css({
-						height : _height
-					});
-				}
-			});
-				
-			//对新产生的tab,增加移除事件
-			jQuery( "#fe-helper-css-ul span.ui-icon-close" ).on( "click", function() {
-				var $allTabs = jQuery( "#fe-helper-css-ul li");
-				var index = $allTabs.index( jQuery( this ).parent() );
-				_tempvar.cssTab.tabs( "remove", index );
-			});
-		}
-		
-		//创建新的面板
-		return _tempvar.cssTab.tabs(
-			"add",
-			"#fe-helper-tab-css-" + _tempvar.cssTabCount,
-			tabTitle);
-	};
-	
-	/**
-	 * 创建进度条
-	 */
-	var _createProgressBar = function(){
-		
-		var _startTime = new Date();
-
-		//先创建主面板
-		baidu.fcptabs.createMainTab();
-		
-		if(_tempvar.progressbar) {
-			_tempvar.mask.remove();
-		}
-		//创建遮罩面板
-		_tempvar.mask = jQuery('<div id="fe-helper-pb-mask">' +
-									'<div id="f-h-p-m"></div>' +
-									'<div id="fe-helper-progress-bar-img">正在进行页面检测,请稍后...</div>' +
-									'<div id="fe-helper-progress-bar"></div>' +
-								'</div>').appendTo('body');
-		//遮罩层大小
-		jQuery('#f-h-p-m').css({
-			height : jQuery(window).height(),
-			width : jQuery(window).width()
-		});
-		
-		//进度条背景
-		var pbarGif = chrome.extension.getURL('code-standards/pbar-ani.gif');
-		jQuery('#fe-helper-progress-bar-img').css({
-			'background' : 'url(' + pbarGif + ') repeat-x'
-		});
-		
-		//产生滚动条,初始化进度为0
-		_tempvar.progressbar = jQuery('#fe-helper-progress-bar')
-				.progressbar({
-					value : 0,
-					complete : function(event,ui){
-						var _pbImg = jQuery('#fe-helper-progress-bar-img').html('页面检测完成,共计耗时:' + (new Date() - _startTime) / 1000 + ' s');
-						//完成以后展示检测结果
-						_tempvar.tabBox.show('slide',{},500);
-						jQuery('#f-h-p-m').fadeOut(500);
-						_pbImg.fadeOut(3000);
-					}
-				});
-		jQuery('#fe-helper-progress-bar-img').css({
-					top : jQuery(window).height() / 2 - 40,
-					left : ( jQuery(window).width() - 800) / 2
-				});
-	};
-	
-	/**
-	 * 更新进度条
-	 * @param {Object} _value
-	 */
-	var _updateProgressBar = function(_value){
-		_tempvar.progressbar.progressbar('value',_value);
-	};
-	
-	
-	return {
-		createMainTab : _createMainTab,
-		addHtmlTab : _addHtmlTab,
-		addJavascriptTab : _addJavascriptTab,
-		addCssTab : _addCssTab,
-		addIssueSuggestionTab : _addIssueSuggestionTab,
-		createProgressBar : _createProgressBar,
-		updateProgressBar : _updateProgressBar
-	};
-	
-})();
-

+ 0 - 506
apps/code-standards/html/fcp-html-analytic.js

@@ -1,506 +0,0 @@
-/**
- * 注册命名空间:baidu.htmlAnalytic
- */
-baidu.namespace.register("baidu.htmlAnalytic");
-
-/**
- * 
- * html词法分析类
- * 
- * @author lichengyin (FCP:PHP代码)
- * @cover zhaoxianlie (FCPHelper:将PHP代码重写为Javascript代码)
- */
-baidu.htmlAnalytic = function(){
-	
-	/**
-	 * 
-	 * 当前解析到的位置
-	 * @var int
-	 */
-	this.parsePos = 0;
-	
-	/**
-	 * 
-	 * 要解析的内容
-	 * @var string
-	 */
-	this.content = '';
-	/**
-	 * 
-	 * 要解析的内容长度
-	 * @var int
-	 */
-	this.contentLength = 0;
-	
-	/**
-	 * 
-	 * 单个标签
-	 * @var array
-	 */
-	this.singleTag = [
-		"br", "input", "link", "meta", "!doctype", "basefont", "base", "col",
-		"area", "hr", "wbr", "param", "img", "isindex", "?xml", "embed"
-	];
-	
-	/**
-	 * 闭合标签检测时候的白名单
-	 */
-	this.closeTagWhiteList = [
-		'html','body','li','tr','td'
-	];
-	
-	/**
-	 * 
-	 * 解析后的token存放处
-	 * @var array
-	 */
-	this._output = [];
-	
-	this.__construct = function(){
-		
-	};
-	
-	/**
-	 * 
-	 * 默认是进行html分析
-	 * type不为1的时候进行tag属性分析
-	 * @param string $content
-	 * @param int $type
-	 */
-	this.run = function($content, $type){
-		if($type == undefined) $type = 1;
-		this.content = $content.trim().replace(/\r\n/g, "\n");
-		if (this.content.indexOf('<?xml') > -1){
-			return [[$content, baidu.FL.HTML_XML]];
-		}
-		this.contentLength = this.content.length;
-		if ($type === 1){
-			this.tokenAnalytic();
-			return this._output;
-		}
-		return this.getTagAttributes($content);
-	};
-	
-	/**
-	 * 
-	 * 使用特征值进行分析
-	 */
-	this.tokenAnalytic = function(){
-		var $token;
-		while (true){
-			$token = this.getNextToken();
-			if ($token){
-				if ($token[1] === baidu.FL.FL_EOF) break;
-				this._output.push($token);
-			}
-		}
-	};
-	
-	/**
-	 * 
-	 * 解析下一个特征值
-	 */
-	this.getNextToken = function(){
-		if (this.parsePos >= this.contentLength){
-			return ['', baidu.FL.FL_EOF];
-		}
-		
-		var $char = this.content[this.parsePos];
-		this.parsePos++;
-		var $outputCount = this._output.length;
-		var $result;
-		if ($outputCount){
-			var $tokenType = this._output[$outputCount - 1][1];
-			if ( $tokenType === baidu.FL.HTML_JS_START){
-				//js标签里任何内容都直接通过,不做任何处理
-				$result = this._getScriptOrStyleContent($char, 1);
-				if ($result) return $result;
-			}else if ($tokenType === baidu.FL.HTML_CSS_START){
-				//style标签里任何内容都直接通过,不做任何处理
-				$result = this._getScriptOrStyleContent($char, 2);
-				if ($result) return $result;
-			}else if($tokenType === baidu.FL.HTML_TEXTAREA_START){
-				//textarea标签里任何内容都直接通过,不做任何处理
-				$result = this._getTextareaOrPreContent($char, 1);
-				if ($result) return $result;
-			}else if($tokenType === baidu.FL.HTML_PRE_START){
-				//pre标签里任何内容都直接通过,不做任何处理
-				$result = this._getTextareaOrPreContent($char, 2);
-				if ($result) return $result;
-			}
-		}
-		if ($char === "\x0d") return ''; // \r
-		if ($char === "\x0a"){
-			return [$char, baidu.FL.FL_NEW_LINE];
-		}
-		
-		//处理一般性的标签,当前字符为<并且下一个字符不为<
-		if ($char === '<' && this.content[this.parsePos] !== '<'){
-			$result = this._getTagToken($char);
-			if ($result) return $result;
-		}
-		$result = this._getContentToken($char);
-		if ($result) return $result;
-		return [$char, baidu.FL.FL_NORMAL];
-	};
-	
-	/**
-	 * 标签
-	 * @param {Object} $char
-	 */
-	this._getTagToken = function($char){
-		var $resultString = $char;
-		do {
-			if (this.parsePos >= this.contentLength){
-				break;
-			}
-			$char = this.content[this.parsePos];
-			this.parsePos++;
-			
-			if ($char === '"' || $char === "'"){
-				if ($resultString[1] !== '!'){
-					$resultString += $char;
-					$resultString += this._getUnformated($char);
-				}
-			}else {
-				$resultString += $char;
-			}
-		}while ($char !== '>');
-		//注释或者ie hack
-		if ($resultString[1] === '!'){
-			if ($resultString.indexOf('[if') > -1){
-				if ($resultString.indexOf('!IE') > -1){
-					$resultString += this._getUnformated('-->', $resultString);
-				}
-				return [$resultString, baidu.FL.HTML_IE_HACK_START];
-			}else if ($resultString.indexOf('[[endif') > -1){
-				return [$resultString, baidu.FL.HTML_IE_HACK_EDN];
-			}else if (this._checkEqual($resultString, 2, 7, 'doctype')){
-				return [$resultString, baidu.FL.HTML_DOC_TYPE];
-			}else if(this._checkEqual($resultString, 4, 6, 'status')){
-				$resultString += this._getUnformated('-->', $resultString);
-				return [$resultString, baidu.FL.HTML_STATUS_OK];
-			}else {
-				$resultString += this._getUnformated('-->', $resultString);
-				return [$resultString, baidu.FL.HTML_COMMENT];
-			}
-		}
-		if (this._checkEqual($resultString, 0, 7, '<script')){
-			return [$resultString, baidu.FL.HTML_JS_START];
-		}else if (this._checkEqual($resultString, 0, 9, '</script>')){
-			return [$resultString, baidu.FL.HTML_JS_END];
-		}else if (this._checkEqual($resultString, 0, 6, '<style')){
-			return [$resultString, baidu.FL.HTML_CSS_START];
-		}else if (this._checkEqual($resultString, 0, 8, '</style>')){
-			return [$resultString, baidu.FL.HTML_CSS_END];
-		}else if (this._checkEqual($resultString, 0, 9, '<textarea')){
-			return [$resultString, baidu.FL.HTML_TEXTAREA_START];
-		}else if (this._checkEqual($resultString, 0, 11, '</textarea>')){
-			return [$resultString, baidu.FL.HTML_TEXTAREA_END];
-		}else if (this._checkEqual($resultString, 0, 4, '<pre')){
-			return [$resultString, baidu.FL.HTML_PRE_START];
-		}else if (this._checkEqual($resultString, 0, 6, '</pre>')){
-			return [$resultString, baidu.FL.HTML_PRE_END];
-		}
-		if (this._checkEqual($resultString, 0, 2, '</')){
-			return [$resultString, baidu.FL.HTML_TAG_END];
-		}
-		return [$resultString, baidu.FL.HTML_TAG_START];
-	};
-	
-	/**
-	 * 
-	 * 检测一个字符串的截取部分是否等于一个特定的字符串
-	 * @param string $str
-	 * @param int $start
-	 * @param int $len
-	 * @param string $result
-	 */
-	this._checkEqual = function($str, $start, $len, $result){
-		return $str.substr($start, $len).toLowerCase() === $result.toLowerCase();
-	};
-	
-	/**
-	 * 
-	 * 解析文本节点
-	 * @param string $char
-	 */
-	this._getContentToken = function($char){
-		var $resultString = $char;
-		while (true){
-			if (this.parsePos >= this.contentLength){
-				break;
-			}
-			//增加对<a href=""><<<</a>的兼容,此时内容为<<<
-			if (this.content[this.parsePos] === '<' 
-				&& this.content[this.parsePos+1] 
-				&& this.content[this.parsePos+1] !== '<' && this.content[this.parsePos+1] !== '>'){
-				break;
-			}
-			$resultString += this.content[this.parsePos];
-			this.parsePos++;
-		}
-		return [$resultString, baidu.FL.HTML_CONTENT];
-	};
-	
-	/**
-	 * 获取需要的字符
-	 * @param {Object} $char
-	 * @param {Object} $orign
-	 */
-	this._getUnformated = function($char, $orign){
-		if($orign == undefined) $orign = '';
-		if ($orign.indexOf($char) > -1) return '';
-		var $resultString = '';
-		do {
-			if (this.parsePos >= this.contentLength){
-				break;
-			}
-			$c = this.content[this.parsePos];
-			$resultString += $c;
-			this.parsePos++;
-		}while ($resultString.indexOf($char) == -1);
-		//增加一个字符的容错机制,如:value=""",这里一不小心多写了个引号
-		if ($char.length === 1){
-			while ($char === this.content[this.parsePos]){
-				$resultString += this.content[this.parsePos];
-				this.parsePos++;
-			}
-		}
-		return $resultString;
-	};
-	
-	/**
-	 * 获取script或者style里的内容
-	 * @param {String} $char
-	 * @param {Integer} $type 0:script,1:style
-	 */
-	this._getScriptOrStyleContent = function($char, $type){
-		var $tokenText = $type == 1 ? '</script>' : '</style>';
-		var $tokenLength = $tokenText.length;
-		if (this.content.substr( this.parsePos - 1, $tokenLength).toLowerCase() === $tokenText){
-			return '';
-		}
-		var $resultString = $char;
-		while (this.parsePos < this.contentLength){
-			if (this.content.substr( this.parsePos, $tokenLength).toLowerCase() === $tokenText){
-				break;
-			}else {
-				$resultString += this.content[this.parsePos];
-				this.parsePos++;
-			}
-		}
-		$resultString = $resultString.trim();
-		var $startEscape = ['<!--', '/*<![CDATA[*/', '//<![CDATA['];
-		var $endEscape = ['//-->', '/*]]>*/', '//]]>'];
-		for (var $escape in $startEscape){
-			if ($resultString.indexOf($escape) === 0){
-				$resultString = $resultString.substr($escape.length);
-				break;
-			}
-		}
-		for (var $escape in $endEscape ){
-			if ($resultString.indexOf($escape) === ($resultString.length - $escape.length)){
-				$resultString = $resultString.substr(0, $resultString.length - $escape.length);
-				break;
-			}
-		}
-		return [$resultString.trim(), $type === 1 ? baidu.FL.HTML_JS_CONTENT : baidu.FL.HTML_CSS_CONTENT];
-	};
-	
-	/**
-	 * 获取Textarea或者pre标签的内容
-	 * @param {String} $char
-	 * @param {Integer} $type 0:textarea,1:pre
-	 */
-	this._getTextareaOrPreContent = function($char, $type){
-		var $tokenText = $type == 1 ? '</textarea>' : '</pre>';
-		var $tokenLength = $tokenText.length;
-		if (this.content.substr( this.parsePos - 1, $tokenLength).toLowerCase() === $tokenText){
-			return '';
-		}
-		var $resultString = $char;
-		while (this.parsePos < this.contentLength){
-			if (this.content.substr( this.parsePos, $tokenLength).toLowerCase() === $tokenText){
-				break;
-			}else {
-				$resultString += this.content[this.parsePos];
-				this.parsePos++;
-			}
-		}
-		
-		return [$resultString.trim(), $type === 1 ? baidu.FL.HTML_TEXTAREA_CONTENT : baidu.FL.HTML_PRE_CONTENT];
-	};
-	
-	/**
-	 * 
-	 * 分析tag标签的属性名和属性值
-	 * @param string $tagContent
-	 */
-	this.getTagAttributes = function($tagContent){
-		//tag end
-		var $tagContent = $tagContent.trim();
-		if ($tagContent.substr( 0, 2) === '</') {
-			return [
-				baidu.FL.HTML_TAG_END, 
-				$tagContent.substr(2, $tagContent.length - 3).trim()
-			];
-		}
-		//tag start
-		var $result = [baidu.FL.HTML_TAG_START, '', []];
-
-		this.parsePos = 1;
-		this.contentLength = $tagContent.replace(/^>\/|>\/$/g,'').length;
-		var $tagName = '';
-		while (true){
-			if (this.parsePos >= this.contentLength){
-				break;
-			}
-			$char = this.content[this.parsePos];
-			this.parsePos++;
-			if (!/^[a-z0-9]{1}$/g.test($char)){
-				this.parsePos--;
-				break;
-			}else{
-				$tagName += $char;
-			}
-		}
-		//get tag name
-		$result[1] = $tagName;
-		var $attr = $name = '';
-		while (true){
-			if (this.parsePos >= this.contentLength){
-				break;
-			}
-			$char = this.content[this.parsePos];
-			this.parsePos++;
-			var $re;
-			if ($char === '"' || $char === "'"){
-				$re = $char + this._getUnformated($char);
-				$result[2].push([$name, $re]);
-			}else if ($char === '='){
-				$name = $attr;
-				$attr = '';
-			}else if ($char === ' '){
-				if ($attr){
-					if ($name){
-						$result[2].push([$name, $attr]);
-					}else{
-						$result[2].push([$attr, '']);
-					}
-				}
-				$name = $attr = '';
-			}else{
-				if ($char !== ' ' && $char != "\n" && $char != "\r" && $char != "\t") $attr += $char;
-			}
-		}
-		if ($attr){
-			if ($name){
-				$result[2].push([$name, $attr]);
-			}else{
-				$result[2].push([$attr, '']);
-			}
-		}
-		return $result;
-	};
-	
-	/**
-	 * 模拟PHP中的in_array
-	 * @param {Object} $tag
-	 */
-	this._in_array = function($array,$tag){
-		for(var i = 0,len = $array.length;i < len;i++){
-			if($tag.trim() == $array[i]) return true;
-		}
-		return false;
-	};
-	
-	/**
-	 * 从给定的html代码中提取tagName
-	 * @param {Object} $tagOuterHtml
-	 * @param {Object} $type 0:开始标签,1:结束标签
-	 */
-	this._getTagName = function($tagOuterHtml,$type){
-		var reg_start = /^<([^\s\/>]+)\s*\/?/g;
-		var reg_end = /^<\/([^\s]+)>/g;
-		var tagAttrs = ($type == 0 ? reg_start : reg_end).exec($tagOuterHtml);
-		return tagAttrs ? tagAttrs[1] : '';
-	};
-	
-	/**
-	 * 获取某段html中未闭合的标签
-	 * @param {Object} str 待检测的html片段
-	 */
-	this.getUnclosedTags = function(str){
-		
-		//给Array增加remove方法
-	    Array.prototype.remove = function(str){
-	        for (var index = this.length - 1; index >= 0; index--) {
-	            if (str == this[index].tagName) {
-	                this.splice(index,1);
-	                return true;
-	            }
-	        }
-	        return false;
-	    };
-	    
-	    //HTML词法分析
-	    var analyticRst = this.run(str);
-	    var rawHtml = [];
-	    for(var i = 0,len = analyticRst.length;i < len;i++){
-	    	if(analyticRst[i][1] === baidu.FL.HTML_PRE_START ||
-	    		analyticRst[i][1] === baidu.FL.HTML_PRE_END ||
-	    		analyticRst[i][1] === baidu.FL.HTML_TEXTAREA_START ||
-	    		analyticRst[i][1] === baidu.FL.HTML_TEXTAREA_END ||
-	    		analyticRst[i][1] === baidu.FL.HTML_TAG_START ||
-	    		analyticRst[i][1] === baidu.FL.HTML_TAG_END ||
-	    		analyticRst[i][1] === baidu.FL.HTML_XML) {
-	    			rawHtml.push(analyticRst[i]);
-	    		}
-	    }
-	    
-	    var tag = ''; // 标签
-	    var startUncloseTags = []; // "开始标签栈",前不闭合,如有</div>而前面没有<div>
-	    var endUncloseTags = []; // "结束标签栈",后不闭合,如有<div>而后面没有</div>
-	    
-	    //开始分析
-	    for(var i = 0,len = rawHtml.length;i < len;i++) {
-	    	//开始标签
-	    	if(rawHtml[i][1] !== baidu.FL.HTML_PRE_END && 
-	    			rawHtml[i][1] !== baidu.FL.HTML_TEXTAREA_END && 
-	    			rawHtml[i][1] !== baidu.FL.HTML_TAG_END) {
-		    	tag = this._getTagName(rawHtml[i][0],0);
-	    		endUncloseTags.push({
-					tagName : tag,
-					outerHTML : rawHtml[i][0],
-					type : 1			//1表示标签后不闭合
-				});
-	    	} 
-	    	//结束标签
-	    	else {
-		    	tag = this._getTagName(rawHtml[i][0],1);
-	    		// 从"结束标签栈"移除一个闭合的标签
-                if (!endUncloseTags.remove(tag)) { // 若移除失败,说明前面没有需要闭合的标签
-                    startUncloseTags.push({ 	// 此标签需要前闭合
-						tagName : tag,
-						outerHTML : rawHtml[i][0],
-						type : 0			//0表示标签前不闭合
-					});
-                }
-	    	}
-	    }
-	    
-		//结果	    
-		var rst = [],temp = endUncloseTags.concat(startUncloseTags);
-		//后不闭合\前不闭合,此处过滤自动闭合的标签
-		for(var i = 0,len = temp.length;i < len;i++) {
-			if((!this._in_array(this.singleTag ,temp[i].tagName.toLowerCase()) || temp[i].type == 0)
-				&& !this._in_array(this.closeTagWhiteList ,temp[i].tagName.toLowerCase())) {
-				rst.push(temp[i]);
-			}
-		}
-		
-	    return rst;
-	};
-	
-};

+ 0 - 211
apps/code-standards/html/fcp-html-doctype.js

@@ -1,211 +0,0 @@
-/**
- * 注册命名空间
- */
-baidu.namespace.register("baidu.doctype");
-
-/**
- * 计算documentMode
- * @author zhaoxianlie 
- */
-baidu.doctype = (function(){
-	var documentMode = {
-	    //在未定义DTD的时候,浏览器默认将以混杂模式进行渲染
-	    IE: 'Q',
-	    WebKit: 'Q',
-	    // 在DTD前是否存在注释(在我们的所有模板前面,都有一段注释:STATUS OK)
-	    hasCommentBeforeDTD: false,
-	    // 在DTD钱是否存在IE条件注释
-	    hasConditionalCommentBeforeDTD: false,
-	    // 是否为一个奇怪的DTD
-	    isUnusualDocType: false,
-	    // 是否有DTD
-	    hasDocType: false
-	};
-
-	/**
-	 * <!DOCTYPE "xmlns:xsl='http://www.w3.org/1999/XSL/Transform'">
-	 * 这个DTD将使页面在IE下以标准模式渲染,在Webkit下以混杂模式渲染
-	 * 比如这个站点: http://www.nasa.gov/
-	 * @param {string} name 值为:document.doctype.name.toLowerCase()
-	 * @param {string} publicId 值为:document.doctype.publicId
-	 * @param {string} systemId 值为:document.doctype.systemId
-	 */
-	function fixDocTypeOfNASA(name, publicId, systemId){
-	    if (name.toLowerCase() == "\"xmlns:xsl='http://www.w3.org/1999/xsl/transform'\"" &&
-	    publicId == '' &&
-	    systemId == '') {
-	        documentMode.IE = 'S';
-	        documentMode.WebKit = 'Q';
-	        documentMode.isUnusualDocType = false;
-	    }
-	}
-	
-	/**
-	 * 判断一个Comment是否为IE的条件注释
-	 * @param {string} Comment内容
-	 */
-	function isConditionalComment(nodeValue){
-	    return baidu.FlConst.CONDITIONAL_COMMENT_REGEXP.test(nodeValue);
-	}
-	
-	/**
-	 * 判断一个Comment内容是否为非IE的注释
-	 * @param {string} Comment内容
-	 */
-	function isNotIEHiddenConditionalComment(nodeValue){
-	    return baidu.FlConst.NOT_IE_HIDDEN_CONDITIONAL_COMMENT_REGEXP.test(nodeValue);
-	}
-	
-	/**
-	 * 判断一个Comment是否为注释的结束部分
-	 * @param {string} Comment内容
-	 */
-	function isRevealedClosingConditionalComment(nodeValue){
-	    return baidu.FlConst.REVEALED_CLOSING_CONDITIONAL_COMMENT_REGEXP.test(nodeValue);
-	}
-	
-	/**
-	 * 判断一个Comment是否为非IE注释的开始
-	 * @param {string} Comment内容
-	 */
-	function isNotIERevealedOpeningConditionalComment(nodeValue){
-	    return baidu.FlConst.NOT_IE_REVEALED_OPENING_CONDITIONAL_COMMENT_REGEXP.test(nodeValue);
-	}
-	
-	/**
-	 * 试图在某个Comment前面找到一个非IE注释的开始
-	 */
-	function getPreviousRevealedOpeningConditionalComment(node){
-	    var prev = node.previousSibling;
-	    for (; prev; prev = prev.previousSibling) {
-	        if (isNotIERevealedOpeningConditionalComment(prev.nodeValue)) {
-	            return prev;
-	        }
-	    }
-	    return null;
-	}
-	
-	/**
-	 * 检测文档头DTD前面是否有注释
-	 */
-	function checkForCommentBeforeDTD(){
-	    var result = {
-	        hasCommentBeforeDTD: false,
-	        hasConditionalCommentBeforeDTD: false
-	    };
-	    var doctype = document.doctype;
-	    if (!doctype) {
-	        return result;
-		}
-	    
-		//从<html>向上搜索,进行检测
-	    var prev = doctype.previousSibling;
-	    for (; prev; prev = prev.previousSibling) {
-	        if (prev.nodeType == Node.COMMENT_NODE) {
-	            var nodeValue = prev.nodeValue;
-	            //向上搜索的过程中,如果碰到某个Comment是注释的结束部分,再继续搜索其上是否存在注释的开始部分
-	            if (isRevealedClosingConditionalComment(nodeValue)) {
-	                prev = getPreviousRevealedOpeningConditionalComment(prev);
-	                // 向上发现了注释的开始部分,则说明DTD前存在条件注释
-	                if (prev) {
-	                    result.hasConditionalCommentBeforeDTD = true;
-	                    return result;
-	                }
-	                continue;
-	            }
-	            // 判断某个Comment是否为一个条件注释
-	            var isConditionalComm = isConditionalComment(nodeValue);
-	            if (!isConditionalComm) {
-	                result.hasCommentBeforeDTD = true;
-	                continue;
-	            }
-	            // 存在非IE的条件注释:如<!--[if !IE]> some text <![endif]-->
-	            if (isNotIEHiddenConditionalComment(nodeValue)) {
-	                result.hasConditionalCommentBeforeDTD = true;
-	            }
-	        }
-	    }
-	    return result;
-	}
-	
-	/**
-	 * 开始进行文档类型的侦测
-	 */
-	function processDoctypeDetectionResult(){
-		//获取doctype
-	    var doctype = document.doctype;
-		
-	    var compatMode = document.compatMode.toLowerCase();
-	    documentMode.hasDocType = (doctype) ? true : false;
-	    
-		// 如果页面是以混杂模式渲染的,则compatMode为BackCompat
-	    documentMode.WebKit = (compatMode == 'backcompat') ? 'Q' : 'S';
-	    documentMode.IE = documentMode.WebKit;
-		
-	    // 如果文档压根儿就没有写doctype,则不需要继续侦测了
-	    if (!doctype) {
-	        return;
-	    }
-
-		//下面三个是doctype中最重要的组成部分
-	    var name = doctype ? doctype.name.toLowerCase() : '';
-	    var publicId = doctype ? doctype.publicId : '';
-	    var systemId = doctype ? doctype.systemId : '';
-
-	    // 非正常工作模式
-	    if (name != 'html') {
-	        documentMode.IE = undefined;
-	        documentMode.isUnusualDocType = true;
-	    } else {
-			//在白名单中进一步检测publicId和systemId
-	        if (publicId in baidu.FlConst.PUBLIC_ID_WHITE_LIST) {
-	            if (!(systemId in baidu.FlConst.PUBLIC_ID_WHITE_LIST[publicId].systemIds)) {
-	                documentMode.IE = undefined;
-	                documentMode.isUnusualDocType = true;
-	            }
-	        } else {
-	            documentMode.IE = undefined;
-	            documentMode.isUnusualDocType = true;
-	        }
-	    }
-	    
-		// 对documentMode进行修正判断
-	    if ((publicId in baidu.FlConst.COMPAT_MODE_DIFF_PUBLIC_ID_MAP) &&
-	    (systemId in baidu.FlConst.COMPAT_MODE_DIFF_PUBLIC_ID_MAP[publicId].systemIds)) {
-	        documentMode.IE = baidu.FlConst.COMPAT_MODE_DIFF_PUBLIC_ID_MAP[publicId].systemIds[systemId].IE;
-	        documentMode.isUnusualDocType = false;
-	    }
-	    
-	    // 进一步修正
-	    fixDocTypeOfNASA(name, publicId, systemId);
-		
-	    // 判断文档头前面是否存在注释
-	    if (documentMode.IE != 'Q') {
-	        var result = checkForCommentBeforeDTD();
-	        if (result.hasConditionalCommentBeforeDTD) {
-	            documentMode.IE = undefined;
-	            documentMode.hasConditionalCommentBeforeDTD = true;
-	        }
-	        else 
-	            if (result.hasCommentBeforeDTD) {
-	                // IE6					DTD 前的任何非空白符都将使浏览器忽略 DTD,包括注释和 XML 声明。
-					//IE7 IE8				DTD 前的任何非空白符都将使浏览器忽略 DTD,包括注释,但不包括 XML 声明。
-					//Firefox				DTD 前的任何包含“<”的字符都将使浏览器忽略 DTD,但不包括 XML 声明。
-					//Chrome Safari Opera	DTD 前的任何非空白符都将使浏览器忽略 DTD,但不包括 XML 声明。
-	                documentMode.IE = 'Q';
-	                documentMode.hasCommentBeforeDTD = true;
-	            }
-	    }
-	}
-	
-	/**
-	 * 对外公开调用接口
-	 */
-	return {
-		getDocMode : function(){
-			//检测documentMode
-			processDoctypeDetectionResult();
-			return documentMode;
-		}
-	} 
-})();

+ 0 - 488
apps/code-standards/html/fcp-html.js

@@ -1,488 +0,0 @@
-
-/**
- * 注册命名空间
- */
-baidu.namespace.register("baidu.html");
-
-/**
- * html相关处理
- * @author zhaoxianlie 
- */
-baidu.html = (function(){
-	/**
-	 * 页面源代码
-	 */
-	var _pageSource = '';					
-	
-	/**
-	 * 结果集
-	 */
-	var _summaryInformation = null;
-	
-	/**
-	 * 初始化侦测结果
-	 */
-	var _initSummaryInformation = function(){
-	    _summaryInformation = {
-	        HTMLBase: {	
-	            HTMLDeprecatedAttribute: {},	//过期的属性
-	            HTMLDeprecatedTag: {}			//过期的标签
-	        },
-	        documentMode: {
-	            hasDocType: false,				//是否设置了doctype
-	            compatMode: {
-	                IE: 'Q',					//IE中的compatMode
-	                WebKit: 'Q'					//Webkit中的compatMode
-	            },
-	            publicId: '',					//doctype中的publicId
-	            hasComment: false,				//doctype前是否有注释
-	            hasConditionalComment: false,	//doctype前是否有条件注释
-	            isUnusualDocType: false			//是否为怪异的doctype
-	        },
-	        DOM: {
-	            IECondComm: [],					//所有的IE条件注释
-	            FFNotSptComm: [],				//Firefox中不支持的注释:不能出现‘--’
-				allComm:[],						//所有的注释
-				count : 0,						//所有节点的数量
-				invalidInput:{			
-					count:0,					//不合法的input数量
-					input:[]					//不合法的input集合
-				},
-				maxDepth : {
-					xpath : '',					//xpath
-					depth : 1					//深度
-				}
-	        },
-	        title: [],							//HTML的title检测
-	        LINK: {
-	            notInHead: []					//不在Head标签内部的Link标签
-	        },
-			ID: {
-				ids: {},						//重复的ID
-				count : 0						//出现重复ID的个数
-			},
-			tagInclude : [],					//标签的包含关系
-			unClosedTags : [],					//未闭合的标签
-			htmlMinified : true	,				//HTML是否压缩过
-			imgTag : []							//Img标签的检测,src为空否
-	    };
-	};
-	
-	/**
-	 * 检测某个标签是否为过时的标签
-	 * @param {Object} tagName
-	 */
-	var _isHTMLDeprecatedTag = function(tagName) {
-	  return baidu.FlConst.HTML_DEPRECATED_TAGS[tagName.toLowerCase()];
-	};
-	
-	/**
-	 * 判断某个属性是否已过时
-	 * @param {Object} tagName 待检测的标签
-	 * @param {Object} attrName 待检测的属性
-	 */
-	var _isHTMLDeprecatedAttribute = function(tagName, attrName){
-		tagName = tagName.toLowerCase();
-		attrName = attrName.toLowerCase();
-	    return (baidu.FlConst.HTML_DEPRECATED_ATTRIBUTES[attrName] && baidu.FlConst.HTML_DEPRECATED_ATTRIBUTES[attrName][tagName]);
-	};
-	
-	
-	/**
-	 * 将检测到的过时标签记录到结果集中
-	 * @param {Object} element
-	 */
-	var _detectDeprecatedTag = function(element){
-	    var tagName = element.tagName.toLowerCase();
-	    
-        if (_isHTMLDeprecatedTag(tagName)) {
-		    var HTMLDeprecatedTag = _summaryInformation.HTMLBase.HTMLDeprecatedTag;
-		    if (!HTMLDeprecatedTag[tagName]) {
-		        HTMLDeprecatedTag[tagName] = 0;
-		    }
-		    HTMLDeprecatedTag[tagName]++;
-        }
-	};
-	
-	
-	/**
-	 * 将检测到的过时属性记录到结果集中
-	 * @param {Object} element
-	 */
-	var _detectDeprecatedAttribute = function(element){
-        var tagName = element.tagName.toLowerCase();
-        var attributes = element.attributes;
-	    var HTMLDeprecatedAttribute = _summaryInformation.HTMLBase.HTMLDeprecatedAttribute;
-        for (var j = 0, c = attributes.length; j < c; ++j) {
-            var attrName = attributes[j].name;
-            if (_isHTMLDeprecatedAttribute(tagName, attrName)) {
-			    if (!HTMLDeprecatedAttribute[attrName]) {
-			        HTMLDeprecatedAttribute[attrName] = {};
-			    }
-				if(!HTMLDeprecatedAttribute[attrName][tagName]) {
-					HTMLDeprecatedAttribute[attrName][tagName] = 0;
-				}
-				HTMLDeprecatedAttribute[attrName][tagName]++;
-            }
-        }
-        
-	   
-	};
-	
-	/**
-	 * 获取页面上的符合过滤条件的所有节点,可以是TEXT、COMMENT、HTMLELEMENT等
-	 * @param {Object} rootNode 以该节点作为根节点开始进行搜索
-	 * @param {Integer} nodeFilter	过滤器,从NodeFilter中获得
-	 */
-	var _getNodes = function(rootNode, nodeFilter){
-	    var nodeIterator = document.createNodeIterator(rootNode, nodeFilter, null, false);
-	    var nodes = [];
-	    var node = nodeIterator.nextNode();
-	    while (node) {
-	        nodes.push(node);
-	        node = nodeIterator.nextNode();
-	    }
-	    return nodes;
-	};
-	
-	/**
-	 * 侦测IE条件注释
-	 */
-	var _detectIECondComm = function(){
-	    var nodes = _getNodes(document.documentElement, NodeFilter.SHOW_COMMENT);
-		//仅IE支持的注释
-	    var ieCondCommRegExp = /\[\s*if\s*[^\]][\s\w]*\]/i;
-		//FF的注释中不能出现'--'
-		var ffNotSupportComReg = /--/g;
-	    for (var i = 0, c = nodes.length; i < c; ++i) {
-	        var currentNode = nodes[i];
-	        if (ieCondCommRegExp.test(currentNode.nodeValue)) {
-	            _summaryInformation.DOM.IECondComm.push(currentNode.nodeValue);
-	        }
-			if(ffNotSupportComReg.test(currentNode.nodeValue)) {
-	            _summaryInformation.DOM.FFNotSptComm.push(currentNode.nodeValue);
-			}
-	        _summaryInformation.DOM.allComm.push(currentNode.nodeValue);
-	    }
-	};
-	
-	/**
-	 * 侦测documentMode
-	 */
-	var _detectCompatMode = function() {
-		_summaryInformation.documentMode = baidu.doctype.getDocMode();
-	};
-	
-	/**
-	 * 检测重复的ID
-	 */
-	var _detectDuplicatedID = function(ids){
-		var ID = _summaryInformation.ID;
-		for(var id in ids) {
-			if(ids[id] > 1) {
-				ID.ids[id] = ids[id];
-				ID['count']++;
-			}
-		}
-	};
-	
-	/**
-	 * 检测页面DOM节点的最大深度
-	 */
-	var _detectDomMaxDepth = function(dom){
-		//如果不是html节点,则直接退出
-		if(dom.nodeType !== 1 || !dom.tagName) return;
-		
-		//扩展屏蔽
-		if(dom.id === 'fe-helper-tab-box' || dom.id === 'fe-helper-pb-mask') return;
-		
-		//最大深度记录
-		var maxDepth = _summaryInformation.DOM.maxDepth;
-		var depth = 0;
-		var curTag , xpath = [];
-
-		//深度遍历
-		do {
-			//扩展屏蔽
-			if(dom.id === 'fe-helper-tab-box' || dom.id === 'fe-helper-pb-mask') return;
-            //忽略SVG节点
-            if(dom.tagName.toLowerCase() == 'svg') continue;
-
-            try{
-                if(dom.id) {	//如果该节点有id,则拼接id
-                    curTag = dom.tagName.toLowerCase() + '<span style="color:red;">#' + dom.id + '</span>';
-                } else if(dom.className) {	//没有id,但有class,则拼接class
-                    curTag = dom.tagName.toLowerCase() + '<span style="color:green;">.' + dom.className.split(/\s+/).join('.') + '</span>';
-                } else {		//没有id也没有class,就只要标签名
-                    curTag = dom.tagName.toLowerCase();
-                }
-            }catch(e){
-                continue;
-            }
-			
-			depth++;
-			xpath.unshift(curTag);
-		} while((dom = dom.parentNode) && dom.nodeType === 1);
-		
-		//判断当前这个dom节点是否为最大深度
-		if(depth > maxDepth.depth) {
-			maxDepth.depth = depth;
-			maxDepth.xpath = xpath.join('<span style="color:gray;">&gt;</span>');
-		}
-	};
-	
-	/**
-	 * 扫描整个页面的所有元素,侦测并记录结果
-	 */
-	var _scanAllElements = function(){
-		//所有节点
-	    var elementList = _getNodes(document.documentElement, NodeFilter.SHOW_ELEMENT);
-	    //所有节点个数
-	    _summaryInformation.DOM.count = elementList.length;
-		
-		//定义一个对象,用来标记节点的ID,当某一个节点的ID值大于1时,表示ID重复
-		var objDomId = {};
-	    
-		//页面扫描
-	    for (var i = 0, len = elementList.length; i < len; ++i) {
-	        var element = elementList[i];
-			//侦测过时的标签
-	        _detectDeprecatedTag(element);
-	        
-			//侦测过时的属性
-	        _detectDeprecatedAttribute(element);
-			
-			//最大深度检测
-			_detectDomMaxDepth(element);
-			
-			//ID记录
-			if(!!element.id) {
-				if(!objDomId[element.id]) objDomId[element.id] = 0;
-				objDomId[element.id]++;
-			}
-	    }
-		
-		//侦测重复的ID
-		_detectDuplicatedID(objDomId);
-	};
-	
-	/**
-	 * 检测页面上的link标签
-	 */
-	var _detectLink = function(){
-	    //获取页面上所有的link标签
-	    var allLink = document.querySelectorAll('link');
-	    //获取head标签内的link标签
-	    var inHeadLink = document.querySelectorAll('head link');
-		
-		//不在Head标签内的Link
-		var notInHeadLink = [];
-		jQuery.each(allLink,function(i,link){
-			var isNotInHead = true;
-			jQuery.each(inHeadLink,function(j,temp){
-				if(link.href == temp.href) {
-					isNotInHead = false;
-				}
-			});
-			isNotInHead ? notInHeadLink.push(link) : false;
-		});
-	    //记录未标记在head标签中的link
-	    _summaryInformation.LINK.notInHead = notInHeadLink;
-	};
-	
-	/**
-	 * 侦测页面上的title标签
-	 */
-	var _detectTitle = function(){
-		var allTitle = document.querySelectorAll('title');
-		var inHeadTitle = document.querySelectorAll('head title');
-		var flag = false;
-		
-		var titles = [];
-		jQuery.each(allTitle,function(i,t){
-			flag = false;
-			jQuery.each(inHeadTitle,function(j,k){
-				if(t == k) {
-					flag = true;
-					return false;
-				}
-			});
-			titles.push({
-				dom : t,
-				isInHead : flag
-			});
-		});
-		 _summaryInformation.title = titles;
-	};
-	
-	/**
-	 * 检测页面上是否存在src未空的img标签
-	 */
-	var _detectImgTags = function(){
-		//这里只检测src属性为空的img标签,如果img标签没有设置src属性,如<img />,则跳过检测
-		var allImgTags = document.querySelectorAll('img[src]');
-		
-		var imgTags = [];
-		var reg = /.*src=\"(.*)\".*/;
-		var arr = [];
-		jQuery.each(allImgTags,function(i,k){
-			arr = reg.exec(k.outerHTML);
-			if(!arr || arr[1].trim() == '') {
-				imgTags.push(k);
-			}
-		});
-		_summaryInformation.imgTag = imgTags;
-	};
-	
-	/**
-	 * 对input[type=text],input[type=password]进行监测
-	 * 不能以size属性来确定其尺寸
-	 */
-	var _detectInputBox = function(){
-		var inputBoxs = document.querySelectorAll('input[type=text],input[type=password]');
-		var invalidInput = _summaryInformation.DOM.invalidInput;
-		jQuery.each(inputBoxs,function(i,input){
-			if(input.getAttribute('size')) {
-				invalidInput.count++;
-				invalidInput.input.push(input);
-			}
-		});
-	};
-
-    /**
-     * 获取某个节点的outerhtml,超过40个字符,则以...代替
-     * @param {} elm
-     */
-    var getOuterHtmlEllipsis = function(elm) {
-        var reg = /(<[^>]+>)/g;
-        var arr = reg.exec(elm.outerHTML);
-        var rst = arr ? arr[1] : elm.outerHTML;
-        rst = rst.length > 40 ? rst.substr(0,40) + '...' : rst;
-        return rst.replace(/</g,'&lt;').replace(/>/g,'&gt;');
-    };
-	
-	/**
-	 * 检测标签的包含情况:是否有inline-tag包含了block-tag
-	 */
-	var _detectTagIncludeCase = function(){
-		var tagInclude = _summaryInformation.tagInclude;
-		var tempArr = null;
-		var inlineElm = null;
-		//遍历inline-tag
-		jQuery.each(baidu.FlConst.INLINE_HTML_ELEMENT,function(i,inlineTag){
-			//遍历block-tag
-			jQuery.each(baidu.FlConst.BLOCK_HTML_ELEMENT,function(j,blockTag){
-				tempArr = document.querySelectorAll(inlineTag + '>' + blockTag); 
-				if(tempArr.length > 0) {
-					inlineElm = getOuterHtmlEllipsis(tempArr[0].parentNode);
-					jQuery.each(tempArr,function(k,item){
-						tagInclude.push({
-							inline : inlineElm,						//包含了block-tag的inline-tag
-							block : getOuterHtmlEllipsis(item)		//被包含的block-tag
-						});
-					});
-				}
-			});
-		});
-	};
-	
-	
-	/**
-	 * 检测页面上是否有没有闭合的标签
-	 * Chrome会自动补全未闭合的标签,所以通过innerHTML获取到的HTML内容已经是闭合的了
-	 */
-	var _detectTagUnClosed = function(){
-		var html = _pageSource;
-		
-		//开始进行html代码词法分析
-		var htmlInstance = new baidu.htmlAnalytic();
-		var rst = htmlInstance.getUnclosedTags(html);
-		for(var i = 0;i < rst.length;i++){
-			_summaryInformation.unClosedTags.push(rst[i].outerHTML.replace(/</g,'&lt;').replace(/>/g,'&gt;'));
-		}
-	};
-	
-	/**
-	 * 检测HTML代码是否压缩过
-	 */
-	var _detectHtmlMinify = function(){
-		var lines = _pageSource.split(/\n/);
-		var average_length_perline = _pageSource.length / lines.length;
-		if (average_length_perline < 150) {
-			_summaryInformation.htmlMinified = false;
-		}
-	};
-	
-	/**
-	 * 获取本页面的源代码
-	 */
-	var _getPageSource = function(callback){
-		chrome.runtime.sendMessage({
-			type : MSG_TYPE.GET_HTML,
-			link : location.href.split('#')[0]
-		},function(respData){
-			//保存源代码
-			_pageSource = respData.content;
-			
-			//html就绪	
-			chrome.runtime.sendMessage({
-				type : MSG_TYPE.HTML_READY
-			});
-
-			callback && callback();
-		});
-	};
-	
-	/**
-	 * 初始化
-	 */
-	var _init = function(callback){
-		
-		//获取本页源代码
-		_getPageSource(callback);
-	};
-	
-	
-	/**
-	 * 执行html侦测
-	 * @param {Function} callback 侦测完毕后的回调方法,形如:function(data){}
-	 * @config {Object} data 就是_summaryInformation
-	 */
-	var _detect = function (callback){
-		//初始化结果集
-		_initSummaryInformation();
-		//扫描整个页面
-	    _scanAllElements();
-		//侦测title标签
-		_detectTitle();
-		//侦测link标签
-	    _detectLink();
-		//检测页面上的img标签是否src=''
-		_detectImgTags();
-		//侦测compatmode
-	    _detectCompatMode();
-		//侦测IE条件注释
-	    _detectIECondComm();
-		//问题Input,使用了size来确定其尺寸,不合法
-		_detectInputBox();
-		//检测是否有inline-tag包含了block-tag
-		_detectTagIncludeCase();
-		//检测未闭合的标签
-		_detectTagUnClosed();
-		//检测HTML代码是否压缩过
-		_detectHtmlMinify();
-		
-		//执行回调
-		if(callback && typeof callback == "function") {
-			callback.call(null,_summaryInformation);
-		}
-	};
-	
-	
-	return {
-		init : _init,
-		detect : _detect
-	};
-	
-})();
-

+ 0 - 348
apps/code-standards/index.css

@@ -1,348 +0,0 @@
-.fe-helper-hide {
-	display:none;
-}
-#fe-helper-box {
-    position: fixed;
-    left: 1px;
-    bottom: 0;
-	right:8px;
-    z-index: 2147483646;
-	font-size:12px;
-}
-
-#fe-helper-main-ifr {
-    width: 100%;
-    height: 100%;
-    position: absolute;
-    left: 0;
-    top: 0;
-	bottom:0;
-    z-index: 2147483646;
-}
-
-#fe-helper-main-tab {
-    width: 100%;
-    position: absolute;
-	top:0;
-    left: 0;
-    bottom: 0;
-    z-index: 2147483647;
-    background: #fcfcfc;
-	text-align:left;
-}
-
-#fe-helper-main-tab .ui-tabs-nav {
-	-webkit-user-select : none;
-}
-
-#fe-helper-main-tab li .ui-icon-close {
-    float: left;
-    margin: 0.4em 0.2em 0 0;
-    cursor: pointer;
-}
-
-#fe-helper-main-tab.ui-tabs .ui-tabs-panel {
-	padding-right:0;
-}
-
-
-#fe-helper-main-tab .rst-title .rst-count{
-	color:#f00;
-}
-#fe-helper-main-tab .rst-content{
-	color:#666;
-	height:460px;
-	overflow-y:auto;
-}
-#fe-helper-main-tab .rst-content .x-num{
-	color:#f00;
-	font-weight:bold;
-}
-#fe-helper-main-tab .rst-content .x-tag{
-	color:#00f;
-	font-weight:bold;
-    word-wrap: break-word;
-}
-#fe-helper-main-tab .rst-content .x-attr{
-	color:#b00;
-	font-weight:bold;
-}
-#fe-helper-main-tab .rst-content .x-file{
-	color:#48b;
-}
-
-#fe-helper-box .ui-corner-top>a{
-	font-weight:bold;
-}
-#fe-helper-box .ui-resizable-s{
-    position: absolute;
-    top:0px;
-    z-index: 2147483647;
-}
-#fe-helper-box #fe-helper-minusthick,
-#fe-helper-box #fe-helper-plusthick,
-#fe-helper-box #fe-helper-closethick{
-    float:right;
-	cursor:pointer;
-	margin:5px 5px 0 ;
-}
-#fe-helper-box #fe-helper-minusthick:hover,
-#fe-helper-box #fe-helper-plusthick:hover,
-#fe-helper-box #fe-helper-closethick:hover{
-    background:#c00;
-}
-
-#fe-helper-box table {
-	font-size:11px;
-	border:2px solid #366EB9;
-	border-collapse:collapse;
-	width:100%;
-	table-layout:fixed;
-}
-#fe-helper-box table th,
-#fe-helper-box table td {
-	vertical-align:middle;
-	padding:3px 10px;
-	border:1px solid #eee;
-}
-#fe-helper-box table .td-linenum {
-	background:#ffc;
-	color:#900;
-	font-weight:normal;
-	font-style:italic;
-	text-align:right;
-	-webkit-user-select:none;
-	width:10%;
-	cursor:default;
-}
-#fe-helper-box table .td-content {
-	text-align:left;
-	padding-left:10px;
-	width:90%;
-}
-#fe-helper-box table .td-content-title {
-	text-align:right;
-	padding-right:10px;
-	width:6%;
-}
-#fe-helper-box table .td-content-content {
-	text-align:left;
-	padding-left:10px;
-	width:84%;
-}
-#fe-helper-box table .td-content-content.-c-x-issue {
-	color:#555;
-}
-
-#fe-helper-box table .td-cookie-linenum {
-	background:#ffc;
-	color:#900;
-	font-weight:normal;
-	font-style:italic;
-	text-align:right;
-	-webkit-user-select:none;
-	width:5%;
-}
-#fe-helper-box table .td-cookie-name {
-	width:20%;
-	word-wrap:break-word;
-}
-#fe-helper-box table .td-cookie-value {
-	word-wrap:break-word;
-	width:30%;
-}
-#fe-helper-box table .td-cookie-domain {
-	width:15%;
-}
-#fe-helper-box table .td-cookie-expires {
-	width:20%;
-}
-#fe-helper-box table .td-cookie-op {
-	width:10%;
-	text-align:center;
-}
-
-#fe-helper-box table thead td{
-	background:#366EB9;
-	font-weight:bold;
-	height:20px;
-	text-align:center;
-	color:#ff0;
-	cursor:default;
-}
-#fe-helper-box table thead .td-content-title {
-	border-right:none;
-}
-#fe-helper-box table thead .td-content-content {
-	border-left:none;
-	text-align:center;
-}
-#fe-helper-box table tbody tr.tr-content-even {
-	background:#F3FAFF;
-}
-#fe-helper-box table tbody tr:hover {
-	background:#ffa;
-}
-#fe-helper-box table tbody .-x-issue{
-	color:#b00;
-}
-#fe-helper-box table tbody .-x-suggestion{
-	color:#00b;
-}
-
-#fe-helper-box .x-selector {
-	color:#00f;
-}
-#fe-helper-box .x-css-text {
-	color:#aaa;
-	padding-left:20px;
-}
-#fe-helper-box .table-css-bg {
-	margin-left:40px;
-	margin-top:10px;
-}
-#fe-helper-box .table-css-bg div {
-	height:20px;
-}
-#fe-helper-box .table-css-bg div:hover {
-	background:#fff;
-}
-#fe-helper-box .x-expand {
-	margin-left:20px;
-	cursor:pointer;
-	color:#48b;
-	font-weight:bold;
-}
-#fe-img-tootip {
-	position:absolute;
-    z-index: 2147483647;
-	top:-10000px;
-	left:-10000px;
-}
-#f-h-p-m {
-	position:fixed;
-	top:0;
-	left:0;
-	bottom:0;
-	right:0;
-    z-index: 2147483646;
-	background:#000;
-	opacity:0.4;
-}
-#fe-helper-progress-bar-img {
-	position:fixed;
-    z-index: 2147483647;
-	width:800px;
-	height:20px;
-	line-height:20px;
-	text-align:center;
-	font-weight:bold;
-	color:#f00;
-	border:1px solid transparent;
-	-webkit-border-radius:2px 2px;
-}
-#fe-helper-progress-bar {
-	width:0px;
-	height:0px;
-}
-#fe-helper-acc-HTML-issue-sug,
-#fe-helper-acc-CSS-issue-sug,
-#fe-helper-acc-Javascript-issue-sug{
-	width:100%;
-	overflow:auto;
-}
-#fe-helper-box .-f-h-get-more- {
-	position:absolute;
-	bottom:10px;
-	right:20px;
-	color:#00f;
-}
-#fe-helper-grid {
-	position:absolute;
-	top:0;
-	left:0;
-	z-index:2147483647;
-	opacity:0.2;
-	overflow:hidden;
-	-webkit-user-select:none;
-}
-#fe-helper-btn-close-grid {
-	position:fixed;
-	bottom:10px;
-	right:10px;
-	font-size:12px;
-	font-weight:bold;
-	color:#00f;
-	z-index:2147483647;
-	cursor:pointer;
-}
-#fe-helper-btn-close-grid:hover {
-	color:#f00;
-}
-#fe-helper-g-pos {
-	background:none;
-	position:absolute;
-	top:0;
-	left:0;
-	z-index:2147483646;
-	border:1px solid #0b0;
-	border-width:0 1px 1px 0;
-}
-#fe-helper-gp-info {
-	position:absolute;
-	z-index:2147483646;
-	background:#ffc;
-	border:1px solid #666;
-	font-size:12px;
-	text-align:left;
-	padding:2px 10px;
-	display:none;
-	color:#000;
-}
-
-#fe-helper-ruler-top {
-	position:fixed;
-	top:0;
-	left:0;
-	right:0;
-	height:20px;
-	background:#fff;
-	border-bottom:1px solid #000;
-	z-index:2147483647;
-	overflow:hidden;
-}
-#fe-helper-ruler-left {
-	position:fixed;
-	top:0px;
-	left:0;
-	bottom:0;
-	width:20px;
-	background:#fff;
-	border-right:1px solid #000;
-	z-index:2147483647;
-	overflow:hidden;
-}
-#fe-helper-ruler-top .h-line{
-	position:absolute;
-	width:1px;
-	background:#000;
-}
-#fe-helper-ruler-top .h-text{
-	position:absolute;
-	top:0;
-	font-size:8px;
-	color:#000;
-}
-#fe-helper-ruler-left .v-line{
-	position:absolute;
-	height:1px;
-	background:#000;
-}
-#fe-helper-ruler-left .v-text{
-	position:absolute;
-	left:0;
-	font-size:8px;
-	color:#000;
-	-webkit-transform:rotate(90deg);
-	margin-top:4px;
-}

+ 0 - 49
apps/code-standards/index.js

@@ -1,49 +0,0 @@
-/**
- * 编码规范检测
- * @author 赵先烈
- */
-module.exports = (function () {
-
-    let _loadStaticFiles = function () {
-        Tarp.require('../static/js/utils');
-        window.MSG_TYPE = Tarp.require('../static/js/msg_type');
-        Tarp.require('../code-standards/fcp-fl');
-        Tarp.require('../code-standards/css/fcp-css-analytic');
-        Tarp.require('../code-standards/css/fcp-css');
-        Tarp.require('../code-standards/html/fcp-html-analytic');
-        Tarp.require('../code-standards/html/fcp-html-doctype');
-        Tarp.require('../code-standards/html/fcp-html');
-        Tarp.require('../code-standards/js/fcp-js');
-        Tarp.require('../code-standards/fcp-tabs');
-        Tarp.require('../code-standards/fcp-main');
-        Tarp.require('../static/vendor/jquery/jquery.extend.js');
-        Tarp.require('../static/vendor/jquery/jquery-ui.min.js');
-
-        if (!jQuery('#_fehelper_jq_ui_css_')[0]) {
-            let jqUiCss = chrome.extension.getURL('static/vendor/jquery/jquery-ui.min.css');
-            jQuery('<link id="_fehelper_jq_ui_css_" href="' + jqUiCss + '" rel="stylesheet" type="text/css" />').appendTo('head');
-
-            let fcpCss = chrome.extension.getURL('code-standards/index.css');
-            jQuery('<link id="_fehelper_fcp_css_" href="' + fcpCss + '" rel="stylesheet" type="text/css" />').appendTo('head');
-        }
-    };
-
-    let _helperInit = function () {
-        _loadStaticFiles();
-        baidu.fcphelper.initStaticFile();
-    };
-
-    let _helperDetect = function () {
-        baidu.fcphelper.initHtml(function () {
-            baidu.fcphelper.detect();
-        });
-    };
-
-    return {
-        init: _helperInit,
-        detect: _helperDetect
-    };
-
-})();
-
-

+ 0 - 324
apps/code-standards/js/fcp-js-analytic.js

@@ -1,324 +0,0 @@
-/**
- * 注册命名空间:baidu.jsAnalytic
- */
-baidu.namespace.register("baidu.jsAnalytic");
-
-/**
- * Javascript代码词法分析
- * @author lichengyin (FCP:PHP代码)
- * @cover zhaoxianlie (FCPHelper:将PHP代码重写为Javascript代码)
- */
-baidu.jsAnalytic = function(){
-	
-	this.parsePos = 0;
-	
-	this.content = '';
-	
-	this.contentLength = 0;
-	
-	this._output = [];
-	
-	this._whitespace = /[\n\r\t\s]/g;
-	
-	this._wordchar = /[a-zA-Z0-9_\$]/g;
-	
-	this._digits = /[0-9]/g;
-	
-	this._punct = /\+|-|\*|\/|%|&|\+\+|\-\-|=|\+=|\-=|\*=|\/=|%=|==|===|!=|!==|>|<|>=|<=|>>|<<|>>>|>>>=|>>=|<<=|&&|&=|\||\|\||!|!!|,|:|\?|\^|\^=|_|\|=|::/g;
-	
-	/**
-	 * 主方法
-	 * @param {Object} $content
-	 */
-	this.run = function($content){
-		
-		this.content = $content.trim().replace(/\r\n/g, "\n");
-		this.contentLength = this.content.length;
-		this.tokenAnalytic();
-		return this._output;
-	};
-	
-	/**
-	 * 此法分析器
-	 */
-	this.tokenAnalytic = function(){
-		while (true){
-			$token = this.getNextToken();
-			if ($token){
-				if ($token[1] === baidu.FL.FL_EOF) break;
-				this._output.push($token);
-			}
-		}
-	};
-	
-	/**
-	 * 检测$char是否在$array中
-	 * @param {Object} $char
-	 * @param {Object} $array
-	 */
-	this._is_match = function($char,$reg){
-		return $reg.test($char);
-	};
-	
-	/**
-	 * 单个此法分析
-	 */
-	this.getNextToken = function(){
-		if (this.parsePos >= this.contentLength){
-			return ['', baidu.FL.FL_EOF];
-		}
-		var $char = this.content[this.parsePos];
-		var $result,$tokenCount,$lastText,$lastType;
-		this.parsePos++;
-		
-		while (this._is_match($char, this._whitespace)){
-			if (this.parsePos >= this.contentLength){
-				return ['', baidu.FL.FL_EOF];
-			}
-			if ($char === "\x0d") return '';
-			if ($char === "\x0a"){
-				return [$char, baidu.FL.FL_NEW_LINE];
-			}
-			$char = this.content[this.parsePos];
-			this.parsePos++;
-		}
-		
-		//处理正常的字符
-		if (this._is_match($char, this._wordchar)){
-			$result = this._getWordToken($char);
-			if ($result) return $result;
-		}
-		switch (true){
-			case $char === '(' || $char === '[' : return [$char, baidu.FL.JS_START_EXPR];
-			case $char === ')' || $char === ']' : return [$char, baidu.FL.JS_END_EXPR];
-			case $char === '{' : return [$char, baidu.FL.JS_START_BLOCK];
-			case $char === '}' : return [$char, baidu.FL.JS_END_BLOCK];
-			case $char === ';' : return [$char, baidu.FL.JS_SEMICOLON];
-		}
-		//注释或者正则
-		if ($char === '/'){
-			//注释
-			$result = this._getCommentToken($char);
-			if ($result) return $result;
-			
-			//正则
-			$tokenCount = this._output.length;
-			if ($tokenCount){
-				var _tem = this._output[$tokenCount - 1];
-				$lastText = _tem[0];
-				$lastType = _tem[1];
-			}else {
-				$lastType = baidu.FL.JS_START_EXPR;
-			}
-			if (($lastType === baidu.FL.JS_WORD && ($lastText === 'return' || $lastText === 'to'))
-				|| ($lastType === baidu.FL.JS_START_EXPR
-					|| $lastType === baidu.FL.JS_START_BLOCK
-					|| $lastType === baidu.FL.JS_END_BLOCK
-					|| $lastType === baidu.FL.JS_OPERATOR
-					|| $lastType === baidu.FL.JS_EQUALS 
-					|| $lastType === baidu.FL.JS_SEMICOLON
-					|| $lastType === baidu.FL.FL_EOF
-					)){
-						
-				$result = this._getRegexpToken($char);
-				if ($result) return $result;
-			}
-		}
-		//引号
-		if ($char === '"' || $char === "'"){
-			$result = this._getQuoteToken($char);
-			if ($result) return $result;	
-		}
-		//sharp variables
-		if ($char === '#'){
-			$result = this._getSharpVariblesToken($char);
-			if ($result) return $result;
-		}
-		//操作符
-		if (this._is_match($char, this._punct)){
-			$result = this._getPunctToken($char);
-			if ($result) return $result;
-		}
-		
-		return [$char, baidu.FL.FL_NORMAL];
-	};
-	
-	/**
-	 * 正常的字符
-	 * @param {Object} $char
-	 */
-	this._getWordToken = function($char){
-		var $sign,$t;
-		
-		while (this._is_match(this.content[this.parsePos], this._wordchar) 
-			&& this.parsePos < this.contentLength){
-
-			$char += this.content[this.parsePos];
-			this.parsePos++;
-		}
-		//处理带E的数字,如:20010E+10,0.10E-10
-		if ((this.content[this.parsePos] === '+' || this.content[this.parsePos] === '-')
-			&& /^[0-9]+[Ee]$/.test()
-			&& this.parsePos < this.contentLength){
-				
-			$sign = this.content[this.parsePos];
-			this.parsePos++;
-			$t = this.getNextToken();
-			$char += $sign . $t[0];
-			return [$char, baidu.FL.JS_WORD];
-		}
-		//for in operator
-		if ($char === 'in'){
-			return [$char , baidu.FL.JS_OPERATOR];
-		}
-		return [$char, baidu.FL.JS_WORD];
-	};
-	
-	/**
-	 * 注释
-	 * @param {Object} $char
-	 */
-	this._getCommentToken = function($char){
-		var $comment = '';
-		var $lineComment = true;
-		var $c = this.content[this.parsePos];
-		var $cc,$lineComment;
-		//单行或者多行注释
-		if($c === '*'){
-			this.parsePos++;
-			while (!(this.content[this.parsePos] === '*' 
-					&& this.content[this.parsePos + 1] 
-					&& this.content[this.parsePos + 1] === '/') 
-					&& this.parsePos < this.contentLength){
-						
-				$cc = this.content[this.parsePos];
-				$comment += $cc;
-				//\x0d为\r, \x0a为\n
-				if ($cc === "\x0d" || $cc === "\x0a"){
-					$lineComment = false;
-				}
-				this.parsePos++;
-			}
-			
-			this.parsePos += 2;
-			//ie下的条件编译
-			if ($comment.indexOf('@cc_on') === 0){
-				return ['/*' + $comment + '*/', baidu.FL.JS_IE_CC];
-			}
-			if ($lineComment){
-				return ['/*' + $comment + '*/', baidu.FL.JS_INLINE_COMMENT];
-			}else{
-				return ['/*' + $comment + '*/', baidu.FL.JS_BLOCK_COMMENT];
-			}
-		}
-		//单行注释
-		if ($c === '/'){
-			$comment = $char;
-			//\x0d为\r, \x0a为\n
-			while (this.content[this.parsePos] !== "\x0d" 
-					&& this.content[this.parsePos] !== "\x0a"
-					&& this.parsePos < this.contentLength){
-				
-				$comment += this.content[this.parsePos];
-				this.parsePos++;
-			}
-			this.parsePos++;
-			return [$comment, baidu.FL.JS_COMMENT];
-		}
-	};
-	
-	/**
-	 * 引号
-	 * @param {Object} $char
-	 */
-	this._getQuoteToken = function($char){
-		var $sep = $char;
-		var $escape = false;
-		var $resultString = $char;
-		while (this.content[this.parsePos] !== $sep || $escape){
-			$resultString += this.content[this.parsePos];
-			$escape = !$escape ? (this.content[this.parsePos] === "\\") : false;
-			this.parsePos++;
-			if (this.parsePos >= this.contentLength){
-				return [$resultString, baidu.FL.JS_STRING];
-			}
-		}
-		this.parsePos++;
-		$resultString += $sep;
-		return [$resultString, baidu.FL.JS_STRING];
-	};
-	
-	/**
-	 * 正则
-	 * @param {Object} $char
-	 */
-	this._getRegexpToken = function($char){
-		var $sep = $char;
-		var $escape = false;
-		var $resultString = $char;
-		var $inCharClass = false;
-		while ($escape || $inCharClass || this.content[this.parsePos] !== $sep){
-			$resultString += this.content[this.parsePos];
-			if (!$escape){
-				$escape = (this.content[this.parsePos] === "\\");
-				if (this.content[this.parsePos] === '['){
-					$inCharClass = true;
-				}else if(this.content[this.parsePos] === ']'){
-					$inCharClass = false;
-				}
-			}else {
-				$escape = false;
-			}
-			this.parsePos++;
-			if (this.parsePos >= this.contentLength){
-				return [$resultString, baidu.FL.JS_REGEXP];
-			}
-		}
-		this.parsePos++;
-		$resultString += $sep;
-		while (this._is_match(this.content[this.parsePos], this._wordchar) 
-			&& this.parsePos < this.contentLength ) {
-				
-			$resultString += this.content[this.parsePos];
-			this.parsePos++;
-		}
-		return [$resultString, baidu.FL.JS_REGEXP];
-	};
-	
-	/**
-	 * sharp varibles
-	 * @param {Object} $char
-	 */
-	this._getSharpVariblesToken = function($char){
-		var $sharp = $char;
-		var $c,$next;
-		if (this._is_match(this.content[this.parsePos], this._digits)){
-			do{
-				$c = this.content[this.parsePos];
-				$sharp += $c;
-				this.parsePos++;
-			}while ($c !== '#' && $c !== '=' && this.parsePos < this.contentLength);
-			$next = this.content.substr( this.parsePos, 2);
-			if ($next === '[]' || $next === '{}'){
-				$sharp += $next;
-				this.parsePos += 2;
-			}
-			return [$sharp, baidu.FL.JS_WORD];
-		}
-	};
-	
-	/**
-	 * 操作符
-	 * @param {Object} $char
-	 */
-	this._getPunctToken = function($char){
-		while (this._is_match($char + this.content[this.parsePos], this._punct) 
-			&& this.parsePos < this.contentLength){
-				
-			$char += this.content[this.parsePos];
-			this.parsePos++;
-		}
-		return [$char, $char === '=' ? baidu.FL.JS_EQUALS : baidu.FL.JS_OPERATOR];
-	};
-	
-}

+ 0 - 435
apps/code-standards/js/fcp-js.js

@@ -1,435 +0,0 @@
-/**
- * 注册命名空间:baidu.js
- */
-baidu.namespace.register("baidu.js");
-
-/**
- * js相关处理
- * @author zhaoxianlie
- */
-baidu.js = (function(){
-	
-	var _readyQueen = null;
-	var _asyncInterface = 0;
-	var _scriptBlockCount  = 0;
-	
-	/**
-	 * js源代码
-	 * @item {fileName:'',fileContent:''}
-	 */
-	var _rawJsSource = [];
-	
-	/**
-	 * 页面cookies
-	 */
-	_cookies =  [];
-	
-	/**
-	 * 结果集
-	 */
-	var _summaryInformation = null;
-	
-	/**
-	 * 初始化js文件的读取队列
-	 * @param {Object} isFinished 是否读取完成
-	 */
-	var _initReadyQueen = function(isFinished){
-		_readyQueen = {
-			curIndex : 0,	//当前正处于读取的index
-			queen : [],	//js文件队列,格式为:{src:"",block:""},其中src和block不可能同时有值
-			callback : new Function(),	//回调方法
-			finished : isFinished	//是否读取完成
-		};
-	};
-		
-	/**
-	 * 增加一项读取项
-	 * @param {Object} readyItem 格式为:{src:Object,block:Object}
-	 */
-	var _addReadyItem = function(readyItem){
-		_readyQueen.queen.push(readyItem);
-	};
-	
-	/**
-	 * 获取当前正在解析的script块
-	 */
-	var _getCurrentReadyItem = function(){
-		return _readyQueen.queen[_readyQueen.curIndex];
-	};
-	
-	/**
-	 * 判断当前读取的是否为最后一个script块
-	 */
-	var _isLastReadyItem = function(){
-		return (_readyQueen.curIndex == _readyQueen.queen.length);
-	};
-	
-	/**
-	 * 读取队列移动到下一个元素
-	 */
-	var _moveToNextReadyItem = function(){
-		_readyQueen.curIndex += 1;
-	};
-	
-	/**
-	 * 判断当前队列是否读取完毕
-	 */
-	var _isDealFinished = function(){
-		return _readyQueen.finished;
-	};
-	
-	/**
-	 * 根据文件路径,提取文件名
-	 * @param {Object} path 文件url
-	 */
-	var _getFileName = function(path){
-		var reg = /(.*\/)([^\/]+\.js)/;
-		var p = reg.exec((path || '').replace(/\?.*/,''));
-		return p ? p[2] : path ? ('异步接口' + ++_asyncInterface) : ('script块' + ++_scriptBlockCount);
-	};
-	
-	/**
-	 * 初始化侦测结果
-	 */
-	var _initSummaryInformation = function(){
-	    _summaryInformation = {
-			cookies:  [],						//有效的cookie,[{key:'',value:''}]
-			scriptTag : {
-				scriptBlock:0,					//页面上的<script type="text/javascript"></script>数量
-				scriptSrc:0						//页面上的<script type="text/javascript" src=""></script>数量
-			},
-			domain : false,						//document是否设置了domain,设置了则domain值为document.domain
-			jsMinified : {						//js文件是否被压缩
-				files : [],
-				count : 0
-			},
-			tangram : [],						//页面上引用的tangram
-			duplicatedFiles : []				//重复的文件
-	    };
-	};
-	
-	/**
-	 * 保存JS代码
-	 * @param {Object} _filePath 文件完整路径
-	 * @param {Object} _fileContent 内容
-	 */
-	var _saveJsSource = function(_filePath,_fileContent){
-		
-		//过滤CSS注释
-		_fileContent = _fileContent.replace(/\/\*[\S\s]*?\*\//g,'');
-		
-		//提取文件名
-		var _fileName = _getFileName(_filePath);
-		
-		_rawJsSource.push({
-			href : _filePath ? _filePath : '#',
-			fileName : _fileName,
-			fileContent : _fileContent
-		});
-	};
-	
-	/**
-	 * 将scripts归类进行检测
-	 * @param {Array} scripts Script对象数组
-	 */
-	var _getJsData = function(scripts){
-		//从页面上获取<script src> 或者 <script> 内容
-		var jsdata = (function(){
-			var ss = {src:[],block:[]};
-			jQuery.each(scripts,function(i,script){
-				//通过 <script src> 标签引用的js
-				if(!!script.src) { ss.src.push(script); }
-				//通过 <script> 标签定义的js
-				else if(!!script.innerHTML) { ss.block.push(script); }
-			});
-			return ss;
-		})();
-
-		return jsdata;
-	};
-	
-	/**
-	 * 提取JS文件内容
-	 * @param {String} src 需要读取的js文件
-	 * @see 具体可参见network.js文件中定义的Function: _readFileContent
-	 */
-	var _getJsSourceByServer = function(link){
-		
-		//向background发送一个消息,要求其加载并处理js文件内容
-		chrome.runtime.sendMessage({
-			type : MSG_TYPE.GET_JS,
-			link : link.src
-		},function(respData){
-			//保存源代码
-			_saveJsSource(respData.path,respData.content)
-			//继续读取(是从就绪队列中进行读取)
-			_readRawJs();
-		});
-	};
-	
-	
-	/**
-	 * 读取页面上已经存在的<script>标签内的js
-	 * @param {script} script
-	 * @return {Undefined} 无返回值 
-	 */
-	var _readScriptTagContent = function(script){
-		//保存源代码
-		_saveJsSource('',script.innerHTML)
-		
-		//继续读取(是从就绪队列中进行读取)
-		_readRawJs();
-	};
-	
-	
-	/**
-	 * 从就绪队列中,逐个加载js
-	 */
-	var _readRawJs = function(){
-		
-		//取得当前需要读取的数据
-		var curData = _getCurrentReadyItem();
-
-		//是否读取完成
-		if(_isDealFinished() || !curData) {
-			chrome.runtime.sendMessage({
-				type : MSG_TYPE.JS_READY
-			});
-			return;
-		}
-	
-		//_readyQueen.curIndex是会被累加的
-		_moveToNextReadyItem();
-		
-		//清空队列
-		if(_isLastReadyItem()) {
-			_initReadyQueen(true);
-		}
-		
-		//如果是<script>标签
-		if(!!curData.block) {
-			_readScriptTagContent(curData.block);
-		}
-		//如果是<script src>标签
-		else if(!!curData.src){
-			_getJsSourceByServer(curData.src);
-		}
-	};
-	
-	/**
-	 * 初始化JS数据
-	 */
-	var _initJsData = function(){
-		scripts = _getJsData(document.scripts);
-		
-		//处理<script>定义的样式
-		if(scripts.block && scripts.block.length >0) {
-			jQuery.each(scripts.block,function(i,script){
-				//加入读取队列
-				_addReadyItem({src:null,block:script});
-			});
-		}
-		
-		//处理<script src>引入的js文件
-		if(scripts.src && scripts.src.length >0) {
-			jQuery.each(scripts.src,function(i,src){
-				//加入读取队列
-				_addReadyItem({src:src,block:null});
-			});
-		}
-	
-		//开始读取
-		_readRawJs();
-	};
-	
-		
-	/**
-	 * 对以存储的js代码进行解析
-	 */
-	var _dealJsFile = function(){
-		var isStop = false;
-		jQuery.each(_rawJsSource,function(i,fileObj){
-			_dealJs(fileObj);
-		});
-		
-		_summaryInformation.scriptTag = {
-			scriptSrc : jQuery('script[src]').length,
-			scriptBlock : jQuery('script:not(script[src])').length
-		};
-	};
-	
-	/**
-	 * 处理某个js文件内的js
-	 * @param {Object} _fileObj
-	 * @config {String} fileName
-	 * @config {String} fileContent
-	 */
-	var _dealJs = function(_fileObj){
-		var fileName = _fileObj.fileName;
-		var fileContent = _fileObj.fileContent;
-		
-		//检测js是否压缩
-		_detectJsMinify(_fileObj);
-		
-	};
-	
-	
-	/**
-	 * 获取浏览器记录的Cookie
-	 */
-	var _getCookies = function(){
-		chrome.runtime.sendMessage({
-			type : MSG_TYPE.GET_COOKIE,
-			url : location.href
-		},function(respData){
-			_cookies = respData.cookie;
-		});
-	};
-	
-	/**
-	 * 侦测页面的cookie
-	 */
-	var _detectCookies = function(){
-		//cookie已经在_getCookies中准备好了
-		_summaryInformation.cookies = _cookies;
-	};
-	
-	/**
-	 * 检测某个js文件是否被压缩
-	 * @param {Object} jsObj js文件对象
-	 * @config {String} href 文件路径
-	 * @config {String} fileName 文件名
-	 * @config {String} fileContent 文件内容
-	 */
-	var _detectJsMinify = function(jsObj){
-		var lines = jsObj.fileContent.split(/\n/);
-		var average_length_perline = jsObj.fileContent.length / lines.length;
-		if (average_length_perline < 150 && lines.length > 1) {
-			_summaryInformation.jsMinified.count++;
-			_summaryInformation.jsMinified.files.push({
-				href : jsObj.href,
-				fileName : jsObj.fileName
-			});
-		}
-	};
-	
-	/**
-	 * 检测tangram
-	 */
-	var _detectTangram = function(){
-		var allScripts = document.querySelectorAll('script[src]');
-		var tangram = [];
-		
-		jQuery.each(allScripts,function(i,item){
-			if(!item.src) return true;
-			var _fileName = _getFileName(item.src);
-			if(_fileName.indexOf('tangram') > -1) {
-				tangram.push(_fileName);
-			}
-		});
-		
-		_summaryInformation.tangram = tangram;
-	};
-	
-	/**
-	 * 检测是否引入的重复的文件
-	 */
-	var _detectDuplicatedFile = function(){
-		scripts = _getJsData(document.scripts);
-		
-		var files = {};
-		var duplicatedFiles = [];
-		var dealedFiles = {};
-		
-		//处理<script>引入的js文件
-		if(scripts.src && scripts.src.length >0) {
-			
-			jQuery.each(scripts.src,function(i,link){
-				files[link.src] = parseInt(files[link.src] || 0,10) + 1;
-			});
-			jQuery.each(files,function(href,count){
-				//文件src重复
-				if(count > 1) {
-					duplicatedFiles.push({
-						href : href,
-						count : count
-					});
-				} else {	//href不重复的情况下,检测文件内容是否相同
-					var _fileContent = '';
-					var _dupFiles = [];
-					jQuery.each(_rawJsSource,function(i,file){
-						if(file.href == href) {
-							_fileContent = file.fileContent.replace(/\s+/g,'');
-							return false;
-						}
-					});
-					jQuery.each(_rawJsSource,function(i,file){
-						if(_fileContent == file.fileContent.replace(/\s+/g,'') && !dealedFiles[file.href] && _dupFiles.join(',').indexOf(file.href) == -1) {
-							_dupFiles.push(file.href);
-						}
-					});
-					if(_dupFiles.length > 1) {
-						duplicatedFiles.push({
-							href : href,
-							dupFiles : _dupFiles
-						});
-						dealedFiles[href] = true;
-					}
-				}
-			});
-		}
-		
-		_summaryInformation.duplicatedFiles = duplicatedFiles;
-	};
-	
-	
-	/**
-	 * 检测Js
-	 */
-	var _detectJS = function(){
-		//处理js文件以及script块
-		_dealJsFile();
-	};
-	
-	/**
-	 * 初始化
-	 */
-	var _init = function(){
-		//初始化就绪队列
-		_initReadyQueen(false);
-		//初始化JS数据
-		_initJsData();
-		//初始化cookie
-		_getCookies();
-	};
-	
-	/**
-	 * js相关的侦测
-	 * @param {Function} callback 侦测完毕后的回调方法,形如:function(data){}
-	 * @config {Object} data 就是_summaryInformation
-	 */
-	var _detect = function(callback){
-		
-		//初始化结果集
-		_initSummaryInformation();
-		//cookie侦测
-		_detectCookies();
-		//侦测页面上script标签
-		_detectJS();
-		//tangram检测
-		_detectTangram();
-		//重复文件检测
-		_detectDuplicatedFile();
-		
-		//执行回调
-		callback.call(null,_summaryInformation);
-	};
-	
-	
-	return {
-		init : _init,
-		detect : _detect,
-		getCookies : _getCookies
-	};
-	
-})();

BIN
apps/code-standards/pbar-ani.gif


+ 561 - 0
apps/color-picker/content-script.js

@@ -0,0 +1,561 @@
+/**
+ * FeHelper Page Color Picker Tools
+ */
+
+window.colorpickerContentScript = function () {
+
+
+    let FeHelper = window.FeHelper || {};
+
+    FeHelper.elemTool = {
+        elm: function (nodeType, attributes, addchilds, appnedTo) {
+            var ne = document.createElement(nodeType), i, l;
+            if (attributes) {
+                if (attributes.event || attributes.events) {
+                    var lev = attributes.event || attributes.events;
+                    if (typeof(lev[0]) == 'string') ne.addEventListener(lev[0], lev[1], lev[2]);
+                    else if (lev.length)
+                        for (i = 0, l = lev.length; i < l; i++)
+                            ne.addEventListener(lev[i][0], lev[i][1], lev[i][2]);
+                }
+            }
+            for (i in attributes) {
+                if (i.substring(0, 5) == 'event') {
+                    //handled earlier
+                } else if (i == 'checked' || i == 'selected') {
+                    if (attributes[i]) ne.setAttribute(i, i);
+                } else ne.setAttribute(i, attributes[i]);
+            }
+            if (addchilds) {
+                for (i = 0, l = addchilds.length; i < l; i++) {
+                    if (addchilds[i]) ne.appendChild(addchilds[i]);//you probably forgot a comma when calling the function
+                }
+            }
+            if (appnedTo) {
+                this.insertNode(ne, appnedTo);
+            }
+
+            return ne;
+        },
+        /*elemTool.txt creates text nodes, does not support HTML entiteis */
+        txt: function (textContent) {
+            return document.createTextNode(textContent);
+        },
+        /*elemTool.ent creates text nodes that may or may not contain HTML entities.  From a
+        single entity to many entities interspersed with text are all supported by this */
+        ent: function (textContent) {
+            return document.createTextNode(this.unescapeHtml(textContent));
+        },
+        /*elemTool.paragraphs creates an array of nodes that may or may not contain HTML entities.*/
+        paragraphs: function (textContent) {
+            var textPieces = textContent.split("\n");
+            var elmArray = [];
+            for (var i = 0, l = textPieces.length; i < l; i++) {
+                elmArray.push(elemTool.elm('p', {}, [elemTool.ent(textPieces[i])]));
+            }
+            return elmArray;
+        },
+        insertNode: function (newNode, parentElem, optionalInsertBefore) {
+            if (!parentElem) parentElem = document.body;
+            if (optionalInsertBefore && optionalInsertBefore.parentNode == parentElem) {
+                parentElem.insertBefore(newNode, optionalInsertBefore);
+            } else {
+                parentElem.appendChild(newNode);
+            }
+        },
+        insertNodes: function (newNodes, parentElem, optionalInsertBefore) {
+            if (typeof(newNodes) != 'array')
+                this.insertNode(newNodes, parentElem, optionalInsertBefore);
+            else {
+                for (var i = 0, l = newNodes.length; i < l; i++) {
+                    this.insertNode(newNodes[i], parentElem, optionalInsertBefore, true);
+                }
+            }
+        },
+        empty: function (node) {
+            while (node.lastChild) node.removeChild(node.lastChild);
+        },
+        unescapeHtml: function (str) { //trick used to make HTMLentiites work inside textNodes
+            if (str.length < 1) return str;
+            var temp = document.createElement("div");
+            str = str.replace(/<\/?\w(?:[^"'>]|"[^"]*"|'[^']*')*>/gmi, '');
+            temp.innerHTML = str;
+            var result = temp.childNodes[0].nodeValue;
+            this.empty(temp);
+            return result;
+        }
+    };
+
+    /**
+     * 页面取色器
+     */
+    FeHelper.ColorPicker = (function () {
+
+        if (!(document.documentElement instanceof HTMLElement)) {
+            return;
+        }
+
+        var elmid1 = 'fehelper-colorpicker-box', elmid2 = 'fehelper-colorpicker-result';
+
+        function _ge(n) {
+            return document.getElementById(n);
+        }
+
+        var n = false, c = false, hex = 'F00BAF', lasthex = '', rgb = null;
+        var hsv = null;
+        var ex = 0, ey = 0, isEnabled = false, isLocked = false, hexIsLowerCase = false, borderValue = '1px solid #666',
+            blankgif = '';
+        var isUpdating = false, lastTimeout = 0, lx = 0, ly = 0;
+        var cvs = document.createElement('canvas');
+        var ctx = cvs.getContext('2d'), x_cvs_scale = 1, y_cvs_scale = 1;
+
+        function RGBtoHex(R, G, B) {
+            return applyHexCase(toHex(R) + toHex(G) + toHex(B))
+        }
+
+        function applyHexCase(hex) {
+            return hexIsLowerCase ? hex.toLowerCase() : hex;
+        }
+
+        function toHex(N) {//http://www.javascripter.net/faq/rgbtohex.htm
+            if (N == null) return "00";
+            N = parseInt(N);
+            if (N == 0 || isNaN(N)) return "00";
+            N = Math.max(0, N);
+            N = Math.min(N, 255);
+            N = Math.round(N);
+            return "0123456789ABCDEF".charAt((N - N % 16) / 16) + "0123456789ABCDEF".charAt(N % 16);
+        }
+
+        function rgb2hsl(r, g, b) {//http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
+            r /= 255, g /= 255, b /= 255;
+            var max = Math.max(r, g, b), min = Math.min(r, g, b);
+            var h, s, l = (max + min) / 2;
+            if (max == min) {
+                h = s = 0; // achromatic
+            } else {
+                var d = max - min;
+                s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+                switch (max) {
+                    case r:
+                        h = (g - b) / d + (g < b ? 6 : 0);
+                        break;
+                    case g:
+                        h = (b - r) / d + 2;
+                        break;
+                    case b:
+                        h = (r - g) / d + 4;
+                        break;
+                }
+                h /= 6;
+            }
+            return {
+                h: Math.round(h * 360),
+                s: Math.round(s * 100),
+                v: Math.round(l * 100)
+            };
+        }
+
+        function emptyNode(node) {
+            while (node.lastChild) node.removeChild(node.lastChild);
+        }
+
+        function snapshotLoaded() {
+            c.style.height = 'auto';
+            c.style.width = (innerWidth) + 'px';
+            x_cvs_scale = c.naturalWidth / innerWidth;
+            y_cvs_scale = c.naturalHeight / innerHeight;
+            cvs.width = c.naturalWidth;
+            cvs.height = c.naturalHeight;
+            ctx.drawImage(c, 0, 0);
+
+            setTimeout(function () {
+                isMakingNew = false;
+                c.style.visibility = "visible";
+                n.style.visibility = "visible";
+                document.body.style.cursor = 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAhCAYAAABX5MJvAAAA8ElEQVRYCe1WAQ6DIAwsm//e9rP9jOU2z0CpyWhlcQkkpoCld15rVcQ5cs5ZRLZrXbuiXVynDj40SVDQkBKfshChZdBe6yaRUkolmF6X934xxxsSGm4lQqjq8CRBQa6cBCwK9Bk4LzMdVO8USizW129k47HwFtX43i0YjiOIkIDGRGV/2/EezKGydxHBZY2btWntbT8mIMRhOe7s7RFo3IvYFSZqonEekQqAIO5KpMJETVRfw+rugIWFd4pXdJI4MtttZXdGn+mgYP+vBBoPnoaWT9Zr3UpoYL3uIeImARA2W9oe4NI3RKIMFJlPElTvBRTxXFcOwvQSAAAAAElFTkSuQmCC) 16 16,crosshair';
+                updateColorPreview();
+            }, 255);
+        }
+
+
+        function setPixelPreview(pix, hxe, lhex) {
+            if (isLocked) return;
+            var wid = 75, padr = 32;
+            wid = 150;
+            hex = hxe ? hxe : hex;
+            if (!_ge('fehelper-colorpicker-cpimprev') || (rgb && !_ge('cprgbvl'))) {
+                emptyNode(n);
+                FeHelper.elemTool.elm('div', {}, [
+                    FeHelper.elemTool.elm('img', {
+                        id: 'fehelper-colorpicker-cpimprev',
+                        height: wid,
+                        width: wid,
+                        src: pix,
+                        style: 'margin:0px;padding:0px;margin:0px;'
+                    }),
+                    FeHelper.elemTool.elm('br'),
+                    FeHelper.elemTool.elm('input', {
+                        type: 'text',
+                        size: 7,
+                        style: 'width:60px;height:20px;line-height:20px;font-size:10pt;border:' + borderValue,
+                        id: 'fehelper-colorpicker-cphexvl',
+                        value: '#' + hex,
+                        event: ['mouseover', selectTargElm]
+                    })
+                ], n)
+                keepOnScreen();
+            } else {
+                _ge('fehelper-colorpicker-cpimprev').src = pix;
+                _ge('fehelper-colorpicker-cpimprev').width = wid;
+                _ge('fehelper-colorpicker-cpimprev').height = wid;
+                _ge('fehelper-colorpicker-cphexvl').value = hex;
+                n.style.backgroundColor = '#' + hex;
+            }
+        }
+
+        function setCurColor(r) {
+            if (!n) return;
+            hex = r.hex ? r.hex : hex;
+            n.style.backgroundColor = '#' + hex;
+            if (isLocked) setDisplay();
+        }
+
+        function selectTargElm(ev) {
+            ev.target.select();
+        }
+
+        function setDisplay() {//FeHelper.elemTool.elm
+            emptyNode(n);
+            FeHelper.elemTool.elm('div', {}, [
+                FeHelper.elemTool.elm('input', {
+                    type: 'text',
+                    size: 7,
+                    style: 'width:80px;height:20px;line-height:20px;font-size:10pt;border:' + borderValue,
+                    id: 'fehelper-colorpicker-cphexvl',
+                    value: '#' + hex,
+                    event: ['mouseover', selectTargElm]
+                }),
+                FeHelper.elemTool.elm('img', {
+                    style: 'width:20px;height:20px;position:absolute;top:-10px;right:-10px;cursor:pointer;',
+                    src: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAACwklEQVQ4Ea1US0tbQRg9kwmp8YXBi7hKyMZAcaFuFN26daFEUg1BEF24FQr9D/0BIqhbDe0/0I0LceNCQSIVIcZdLkrER2LUm6+caeZiEmkp7cDlMnfOOfM9zneB/7zUn/REJAEgXsfllVI/fscJvncoIhrAZwArrutGb25uoJRCb28vROQKwBqAr0opr5nfEqGIpGq12s7h4SH29vZQKpUMR2uN9vZ29PX1YWJiAsPDw7zkk1Iq+1a0QVBEkuVy+dvW1hYuLi58HKMLBoPo6uoyb+6j0ShmZmYQDodnlVLfLdgXFJGQ53nVjY0NnJ+fo1arMT2Do0BbWxs6OjoQCASMqOd5iMViSKVS0Fp/UEo9ExywygBWj4+PUSwWTWpDQ0MYHx/3jylE4cHBQSwuLiIUCqFQKODk5ISYVQt8K7jCujE1RtPf349kMomRkRGLRSKRwNTUlMng+dkEBHLYPAsyXRaRj9fX19H7+3sTBQ+Pjo5Miul0Gg8PD+aS6elp831/f9/ycXd3B3KpoZTKWdvEb29vfRBT4yKR0S4vL5uLTk9Psbu7619qCeQ6jkOv5t6mbM/Nm6J8WFfWj4sXsFG2WQ2E+sYK5nt6elrOHcfB/Py8sdDl5SUWFhZokxbROjdPASPI3B3Hueru7vZFCZqbm2N9sL29jc3NTVMv1pQdtpHSm+RSwxesq6yNjY35grQMm5TNZvHy8oKnpyesr6/Tc6bz3L++vmJ0dJQcjqJZLcamAP1F+7B2lUoFj4+PRpBRdXZ2mm9kDwwMYGlp6X1j0+la61lag2PF26vVqpkYToVdtBD38XgcmUyGYhy9X6YE4EdoCfw5eJ63wwk4ODiA67ool8tGhJhIJILJyUmwPFrrlp+D1Wl4c65F5IuIFFzXlbOzM8nlclIsFoXf6mehBlJ90xJhM+hvf7DN/H/e/wRZ4k9klRmUggAAAABJRU5ErkJggg==',
+                    alt: 'Close',
+                    title: '[esc]键可直接关闭',
+                    id: 'fehelper-colorpicker-exitbtn',
+                    event: ['click', dissableColorPickerFromHere, true]
+                })
+            ], n);
+            if (_ge('fehelper-colorpicker-cphexvl')) _ge('fehelper-colorpicker-cphexvl').select();
+            keepOnScreen();
+        }
+
+        function picked() {
+            if (isLocked) {
+                lasthex = hex;
+                isLocked = false;
+                emptyNode(n);
+            } else {
+                isLocked = true;
+                setDisplay();
+            }
+        }
+
+        function dissableColorPickerFromHere() {
+            setTimeout(disableColorPicker, 500)
+        }
+
+        function disableColorPicker() {
+            isEnabled = false, isLocked = false;
+            document.removeEventListener('mousemove', mmf);
+            removeEventListener('scroll', ssf);
+            removeEventListener('resize', ssf);
+            removeEventListener('keyup', wk);
+            removeExistingNodes();
+            clearTimeout(lastNewTimeout);
+        }
+
+        function removeExistingNodes() {
+            if (document.body) {
+                c = _ge(elmid1), n = _ge(elmid2);
+                if (c) document.body.removeChild(c);
+                if (n) document.body.removeChild(n);
+                c = false, n = false;
+                document.body.style.cursor = '';
+            }
+        }
+
+        function wk(ev) {
+            if (!isEnabled) return;
+            if (ev.keyCode == 27) {
+                dissableColorPickerFromHere();
+            } else if (ev.keyCode == 82 || ev.keyCode == 74) {//r or j refresh
+                ssf();
+            } else if (ev.keyCode == 13) {
+                picked();
+            }
+        }
+
+        function mmf(ev) {
+            if (!isEnabled) return;
+            if (!isLocked) {
+                lx = (ev.pageX - pageXOffset), ly = (ev.pageY - pageYOffset);
+                ex = Math.round(lx * x_cvs_scale),
+                    ey = Math.round(ly * y_cvs_scale);
+                updateColorPreview();
+            }
+        }
+
+        function ssf(ev) {
+            if (!isEnabled) return;
+            n.style.visibility = "hidden";
+            c.style.visibility = "hidden";//redundent?
+            clearTimeout(lastNewTimeout);
+            lastNewTimeout = setTimeout(function () {
+                newImage()//some delay required OR it won't update
+            }, 250);
+        }
+
+        function initialInit() {
+            removeExistingNodes();
+            c = FeHelper.elemTool.elm('img', {
+                id: elmid1,
+                src: blankgif,
+                style: 'position:fixed;max-width:none!important;max-height:none!important;top:0px;left:0px;margin:0px;padding:0px;overflow:hidden;z-index:2147483646;',
+                events: [['click', picked, true], ['load', snapshotLoaded]]
+            }, [], document.body);
+            n = FeHelper.elemTool.elm('div', {
+                id: elmid2,
+                style: 'position:fixed;min-width:30px;max-width:300px;box-shadow:2px 2px 2px #666;border:' + borderValue + ';border-radius:5px;z-index:2147483646;cursor:default;padding:10px;text-align:center;'
+            }, [], document.body);
+            document.addEventListener('mousemove', mmf);
+            addEventListener('keyup', wk);
+            addEventListener('scroll', ssf);
+            addEventListener('resize', ssf);
+            initializeCanvas();
+            remainingInit();
+        }
+
+        function enableColorPicker() {
+            disableColorPicker();
+            if (!n) {
+                initialInit();
+                return false;
+            }
+            return remainingInit();
+        }
+
+        function remainingInit() {
+            if (!isEnabled) {
+                n.style.visibility = "hidden";
+                c.style.visibility = "hidden";
+                if (isLocked) picked();//unlocks for next pick
+                document.body.style.cursor = 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAhCAYAAABX5MJvAAAA8ElEQVRYCe1WAQ6DIAwsm//e9rP9jOU2z0CpyWhlcQkkpoCld15rVcQ5cs5ZRLZrXbuiXVynDj40SVDQkBKfshChZdBe6yaRUkolmF6X934xxxsSGm4lQqjq8CRBQa6cBCwK9Bk4LzMdVO8USizW129k47HwFtX43i0YjiOIkIDGRGV/2/EezKGydxHBZY2btWntbT8mIMRhOe7s7RFo3IvYFSZqonEekQqAIO5KpMJETVRfw+rugIWFd4pXdJI4MtttZXdGn+mgYP+vBBoPnoaWT9Zr3UpoYL3uIeImARA2W9oe4NI3RKIMFJlPElTvBRTxXFcOwvQSAAAAAElFTkSuQmCC) 16 16,crosshair';
+                isEnabled = true;
+                setTimeout(newImage, 1);
+                return false;
+            }
+            return true;
+        }
+
+        function keepOnScreen() {
+
+            if (!n) return;
+            n.style.top = (ly + 8) + "px";
+            n.style.left = (lx + 8) + "px";
+            if (n.clientWidth + n.offsetLeft + 24 > innerWidth) {
+                n.style.left = (lx - 8 - n.clientWidth) + "px";
+            }
+            if (n.clientHeight + n.offsetTop + 24 > innerHeight) {
+                n.style.top = (ly - 8 - n.clientHeight) + "px";
+            }
+        }
+
+        function updateColorPreview(ev) {
+            if (!isEnabled) return;
+            keepOnScreen();
+            var data = ctx.getImageData(ex, ey, 1, 1).data;
+            hsv = rgb2hsl(data[0], data[1], data[2]);
+            rgb = {r: data[0], g: data[1], b: data[2]};
+            setCurColor({hex: RGBtoHex(data[0], data[1], data[2])});
+            handleRendering();
+        }
+
+        var isMakingNew = false, lastNewTimeout = 0;
+
+        function newImage() {
+            if (!isEnabled) return;
+            if (isMakingNew) {
+                clearTimeout(lastNewTimeout);
+                lastNewTimeout = setTimeout(function () {
+                    newImage()
+                }, 255);
+                return;
+            }
+            document.body.style.cursor = 'wait';
+            isMakingNew = true;
+            n.style.visibility = "hidden";
+            c.style.visibility = "hidden";
+            c.src = blankgif;
+            var x = innerWidth, y = innerHeight;
+            c.style.width = x + 'px';
+            c.style.height = y + 'px';
+
+            setTimeout(function () {
+                try {
+                    chrome.runtime.sendMessage({
+                        type: 'fh-dynamic-any-thing',
+                        params: {
+                            url: location.href
+                        },
+                        func: ((params, callback) => {
+                            try {
+                                chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
+                                    chrome.tabs.captureVisibleTab(null, {format: 'png'}, function (dataUrl) {
+                                        chrome.tabs.executeScript(tabs[0].id, {
+                                            code: `(` + ((data) => {
+                                                window.colorpickerNoPage(data);
+                                            }).toString() + `)(${JSON.stringify({
+                                                setPickerImage: true,
+                                                pickerImage: dataUrl
+                                            })})`
+                                        });
+                                    });
+                                });
+                            } catch (e) {
+                            }
+                            callback && callback(null);
+                            return true;
+                        }).toString()
+                    }, function () {
+                    });
+                } catch (e) {
+                    console.log('有错误发生,可提交此反馈到官网!', e);
+                }
+            }, 255);
+        }
+
+        var lastPreviewURI;
+
+        var icvs = 0, totalWidth = 150;//750
+
+        function handleRendering(quick) {
+            var x = ex, y = ey;
+            if (isMakingNew) {
+                isUpdating = false;
+                return;
+            }
+
+            var startPoint = Math.floor(totalWidth * 0.5);
+            var ox = Math.round(x), oy = Math.round(y);
+
+            if (quick) {
+                var ictx = getMain2dContext();
+                ictx.scale(2, 2);
+                ictx.drawImage(cvs, -ox + (startPoint * 0.5), -oy + (startPoint * 0.5));
+                ictx.scale(0.5, 0.5);
+
+                ictx.fillStyle = "rgba(0,0,0,0.3)";//croshair
+
+                ictx.fillRect(startPoint, 0, 1, totalWidth);
+                ictx.fillRect(0, startPoint, totalWidth, 1);
+
+            } else {
+                var ictx = getMain2dContext();
+                ictx.drawImage(cvs, -ox + (startPoint), -oy + (startPoint));
+                var smi, spi, mp = 15 - 0;
+                //xx,yy
+                for (var i = 0; i < startPoint; i += 2) {
+                    smi = startPoint - i;
+                    spi = startPoint + i;
+                    //drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) //CANVAS
+                    ictx.drawImage(icvs, spi, 0, smi, totalWidth,//total width really??
+                        spi + 1, 0, smi, totalWidth);
+
+                    ictx.drawImage(icvs, 0, 0, smi + 1, totalWidth,
+                        -1, 0, smi + 1, totalWidth);
+
+                    ictx.drawImage(icvs, 0, spi, totalWidth, smi,
+                        0, spi + 1, totalWidth, smi);
+
+                    ictx.drawImage(icvs, 0, 0, totalWidth, smi + 1,
+                        0, -1, totalWidth, smi + 1);
+
+                    if (i == 0) {
+                        var dat = ictx.getImageData(startPoint, startPoint, 1, 1).data;//notarget
+                        var d = dat[0] + dat[1] + dat[2];
+                        if (d > 192) ictx.fillStyle = "rgba(30,30,30,0.8)";
+                        else ictx.fillStyle = "rgba(225,225,225,0.8)";
+                    } else ictx.fillStyle = "rgba(255,255,255,0.4)";
+
+                    for (var c = 0; c < mp; c++) {
+                        if (++i >= startPoint) break;
+                        smi = startPoint - i;
+                        spi = startPoint + i;
+                        ictx.drawImage(icvs, spi, 0, smi, totalWidth,
+                            spi + 1, 0, smi, totalWidth);
+
+                        ictx.drawImage(icvs, 0, 0, smi + 1, totalWidth,
+                            -1, 0, smi + 1, totalWidth);
+
+                        ictx.drawImage(icvs, 0, spi, totalWidth, smi,
+                            0, spi + 1, totalWidth, smi);
+
+                        ictx.drawImage(icvs, 0, 0, totalWidth, smi + 1,
+                            0, -1, totalWidth, smi + 1);
+                    }
+                    mp--;
+                    if (mp < 1) mp = 1;
+                    ictx.fillRect(spi + 1, 0, 1, totalWidth);
+                    ictx.fillRect(smi - 1, 0, 1, totalWidth);
+                    ictx.fillRect(0, spi + 1, totalWidth, 1);
+                    ictx.fillRect(0, smi - 1, totalWidth, 1);
+                }
+            }
+
+            lastPreviewURI = icvs.toDataURL();//the last one, large size, is cached for revisiting the menu
+
+            var browseIconWidth = (devicePixelRatio > 1 ? 38 : 19);
+            var browseIconHalfWidth = Math.floor(browseIconWidth * 0.5);
+
+            var tmpCvs = document.createElement('canvas');
+            tmpCvs.width = browseIconWidth, tmpCvs.height = browseIconWidth;
+            var tctx = tmpCvs.getContext("2d");
+            tctx.drawImage(icvs, startPoint - browseIconHalfWidth, startPoint - browseIconHalfWidth, browseIconWidth, browseIconWidth, 0, 0, browseIconWidth, browseIconWidth);
+            var pathData = {};
+            pathData[browseIconWidth] = tmpCvs.toDataURL();
+
+            setPixelPreview(lastPreviewURI, hex, lasthex);
+
+            isUpdating = false;
+        }
+
+        function getMain2dContext() {
+            var context = icvs.getContext("2d");
+            if (context) return context;
+            else {
+                initializeCanvas();
+                return icvs.getContext("2d");
+            }
+        }
+
+        function initializeCanvas() {
+            icvs = document.createElement('canvas');//icon canvas
+            icvs.width = totalWidth;
+            icvs.height = totalWidth;
+        }
+
+        return function (request) {
+            if (request.setPickerImage) {
+                c.src = request.pickerImage;
+            } else {
+                enableColorPicker();
+                picked();
+            }
+        };
+    })();
+
+    // 给background page直接调用的
+    window.colorpickerNoPage = function (request) {
+        FeHelper.ColorPicker(request)
+    };
+};

+ 13 - 0
apps/color-picker/index.html

@@ -0,0 +1,13 @@
+<!doctype html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport"
+          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+    <meta http-equiv="X-UA-Compatible" content="ie=edge">
+    <title>页面取色工具</title>
+</head>
+<body>
+<script type="text/javascript" src="content-script.js"></script>
+</body>
+</html>

+ 0 - 570
apps/color-picker/index.js

@@ -1,570 +0,0 @@
-/**
- * FeHelper Page Color Picker Tools
- */
-
-let FeHelper = window.FeHelper || {};
-
-FeHelper.elemTool = {
-    elm: function (nodeType, attributes, addchilds, appnedTo) {
-        var ne = document.createElement(nodeType), i, l;
-        if (attributes) {
-            if (attributes.event || attributes.events) {
-                var lev = attributes.event || attributes.events;
-                if (typeof(lev[0]) == 'string') ne.addEventListener(lev[0], lev[1], lev[2]);
-                else if (lev.length)
-                    for (i = 0, l = lev.length; i < l; i++)
-                        ne.addEventListener(lev[i][0], lev[i][1], lev[i][2]);
-            }
-        }
-        for (i in attributes) {
-            if (i.substring(0, 5) == 'event') {
-                //handled earlier
-            } else if (i == 'checked' || i == 'selected') {
-                if (attributes[i]) ne.setAttribute(i, i);
-            } else ne.setAttribute(i, attributes[i]);
-        }
-        if (addchilds) {
-            for (i = 0, l = addchilds.length; i < l; i++) {
-                if (addchilds[i]) ne.appendChild(addchilds[i]);//you probably forgot a comma when calling the function
-            }
-        }
-        if (appnedTo) {
-            this.insertNode(ne, appnedTo);
-        }
-
-        return ne;
-    },
-    /*elemTool.txt creates text nodes, does not support HTML entiteis */
-    txt: function (textContent) {
-        return document.createTextNode(textContent);
-    },
-    /*elemTool.ent creates text nodes that may or may not contain HTML entities.  From a
-    single entity to many entities interspersed with text are all supported by this */
-    ent: function (textContent) {
-        return document.createTextNode(this.unescapeHtml(textContent));
-    },
-    /*elemTool.paragraphs creates an array of nodes that may or may not contain HTML entities.*/
-    paragraphs: function (textContent) {
-        var textPieces = textContent.split("\n");
-        var elmArray = [];
-        for (var i = 0, l = textPieces.length; i < l; i++) {
-            elmArray.push(elemTool.elm('p', {}, [elemTool.ent(textPieces[i])]));
-        }
-        return elmArray;
-    },
-    insertNode: function (newNode, parentElem, optionalInsertBefore) {
-        if (!parentElem) parentElem = document.body;
-        if (optionalInsertBefore && optionalInsertBefore.parentNode == parentElem) {
-            parentElem.insertBefore(newNode, optionalInsertBefore);
-        } else {
-            parentElem.appendChild(newNode);
-        }
-    },
-    insertNodes: function (newNodes, parentElem, optionalInsertBefore) {
-        if (typeof(newNodes) != 'array')
-            this.insertNode(newNodes, parentElem, optionalInsertBefore);
-        else {
-            for (var i = 0, l = newNodes.length; i < l; i++) {
-                this.insertNode(newNodes[i], parentElem, optionalInsertBefore, true);
-            }
-        }
-    },
-    empty: function (node) {
-        while (node.lastChild) node.removeChild(node.lastChild);
-    },
-    unescapeHtml: function (str) { //trick used to make HTMLentiites work inside textNodes
-        if (str.length < 1) return str;
-        var temp = document.createElement("div");
-        str = str.replace(/<\/?\w(?:[^"'>]|"[^"]*"|'[^']*')*>/gmi, '');
-        temp.innerHTML = str;
-        var result = temp.childNodes[0].nodeValue;
-        this.empty(temp);
-        return result;
-    }
-};
-
-/**
- * 页面取色器
- */
-FeHelper.ColorPicker = (function () {
-
-    if (!(document.documentElement instanceof HTMLElement)) {
-        return;
-    }
-
-    var elmid1 = 'fehelper-colorpicker-box', elmid2 = 'fehelper-colorpicker-result';
-
-    function _ge(n) {
-        return document.getElementById(n);
-    }
-
-    var n = false, c = false, hex = 'F00BAF', lasthex = '', rgb = null;
-    var hsv = null;
-    var ex = 0, ey = 0, isEnabled = false, isLocked = false, hexIsLowerCase = false, borderValue = '1px solid #666',
-        blankgif = '';
-    var isUpdating = false, lastTimeout = 0, lx = 0, ly = 0;
-    var cvs = document.createElement('canvas');
-    var ctx = cvs.getContext('2d'), x_cvs_scale = 1, y_cvs_scale = 1;
-
-    function RGBtoHex(R, G, B) {
-        return applyHexCase(toHex(R) + toHex(G) + toHex(B))
-    }
-
-    function applyHexCase(hex) {
-        return hexIsLowerCase ? hex.toLowerCase() : hex;
-    }
-
-    function toHex(N) {//http://www.javascripter.net/faq/rgbtohex.htm
-        if (N == null) return "00";
-        N = parseInt(N);
-        if (N == 0 || isNaN(N)) return "00";
-        N = Math.max(0, N);
-        N = Math.min(N, 255);
-        N = Math.round(N);
-        return "0123456789ABCDEF".charAt((N - N % 16) / 16) + "0123456789ABCDEF".charAt(N % 16);
-    }
-
-    function rgb2hsl(r, g, b) {//http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
-        r /= 255, g /= 255, b /= 255;
-        var max = Math.max(r, g, b), min = Math.min(r, g, b);
-        var h, s, l = (max + min) / 2;
-        if (max == min) {
-            h = s = 0; // achromatic
-        } else {
-            var d = max - min;
-            s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
-            switch (max) {
-                case r:
-                    h = (g - b) / d + (g < b ? 6 : 0);
-                    break;
-                case g:
-                    h = (b - r) / d + 2;
-                    break;
-                case b:
-                    h = (r - g) / d + 4;
-                    break;
-            }
-            h /= 6;
-        }
-        return {
-            h: Math.round(h * 360),
-            s: Math.round(s * 100),
-            v: Math.round(l * 100)
-        };
-    }
-
-    function emptyNode(node) {
-        while (node.lastChild) node.removeChild(node.lastChild);
-    }
-
-    function snapshotLoaded() {
-        c.style.height = 'auto';
-        c.style.width = (innerWidth) + 'px';
-        x_cvs_scale = c.naturalWidth / innerWidth;
-        y_cvs_scale = c.naturalHeight / innerHeight;
-        cvs.width = c.naturalWidth;
-        cvs.height = c.naturalHeight;
-        ctx.drawImage(c, 0, 0);
-
-        setTimeout(function () {
-            isMakingNew = false;
-            c.style.visibility = "visible";
-            n.style.visibility = "visible";
-            document.body.style.cursor = 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAhCAYAAABX5MJvAAAA8ElEQVRYCe1WAQ6DIAwsm//e9rP9jOU2z0CpyWhlcQkkpoCld15rVcQ5cs5ZRLZrXbuiXVynDj40SVDQkBKfshChZdBe6yaRUkolmF6X934xxxsSGm4lQqjq8CRBQa6cBCwK9Bk4LzMdVO8USizW129k47HwFtX43i0YjiOIkIDGRGV/2/EezKGydxHBZY2btWntbT8mIMRhOe7s7RFo3IvYFSZqonEekQqAIO5KpMJETVRfw+rugIWFd4pXdJI4MtttZXdGn+mgYP+vBBoPnoaWT9Zr3UpoYL3uIeImARA2W9oe4NI3RKIMFJlPElTvBRTxXFcOwvQSAAAAAElFTkSuQmCC) 16 16,crosshair';
-            updateColorPreview();
-        }, 255);
-    }
-
-
-    function setPixelPreview(pix, hxe, lhex) {
-        if (isLocked) return;
-        var wid = 75, padr = 32;
-        wid = 150;
-        hex = hxe ? hxe : hex;
-        if (!_ge('fehelper-colorpicker-cpimprev') || (rgb && !_ge('cprgbvl'))) {
-            emptyNode(n);
-            FeHelper.elemTool.elm('div', {}, [
-                FeHelper.elemTool.elm('img', {
-                    id: 'fehelper-colorpicker-cpimprev',
-                    height: wid,
-                    width: wid,
-                    src: pix,
-                    style: 'margin:0px;padding:0px;margin:0px;'
-                }),
-                FeHelper.elemTool.elm('br'),
-                FeHelper.elemTool.elm('input', {
-                    type: 'text',
-                    size: 7,
-                    style: 'width:60px;height:20px;line-height:20px;font-size:10pt;border:' + borderValue,
-                    id: 'fehelper-colorpicker-cphexvl',
-                    value: '#' + hex,
-                    event: ['mouseover', selectTargElm]
-                })
-            ], n)
-            keepOnScreen();
-        } else {
-            _ge('fehelper-colorpicker-cpimprev').src = pix;
-            _ge('fehelper-colorpicker-cpimprev').width = wid;
-            _ge('fehelper-colorpicker-cpimprev').height = wid;
-            _ge('fehelper-colorpicker-cphexvl').value = hex;
-            n.style.backgroundColor = '#' + hex;
-        }
-    }
-
-    function setCurColor(r) {
-        if (!n) return;
-        hex = r.hex ? r.hex : hex;
-        n.style.backgroundColor = '#' + hex;
-        if (isLocked) setDisplay();
-    }
-
-    function selectTargElm(ev) {
-        ev.target.select();
-    }
-
-    function setDisplay() {//FeHelper.elemTool.elm
-        emptyNode(n);
-        FeHelper.elemTool.elm('div', {}, [
-            FeHelper.elemTool.elm('input', {
-                type: 'text',
-                size: 7,
-                style: 'width:80px;height:20px;line-height:20px;font-size:10pt;border:' + borderValue,
-                id: 'fehelper-colorpicker-cphexvl',
-                value: '#' + hex,
-                event: ['mouseover', selectTargElm]
-            }),
-            FeHelper.elemTool.elm('img', {
-                style: 'width:20px;height:20px;position:absolute;top:-10px;right:-10px;cursor:pointer;',
-                src: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAACwklEQVQ4Ea1US0tbQRg9kwmp8YXBi7hKyMZAcaFuFN26daFEUg1BEF24FQr9D/0BIqhbDe0/0I0LceNCQSIVIcZdLkrER2LUm6+caeZiEmkp7cDlMnfOOfM9zneB/7zUn/REJAEgXsfllVI/fscJvncoIhrAZwArrutGb25uoJRCb28vROQKwBqAr0opr5nfEqGIpGq12s7h4SH29vZQKpUMR2uN9vZ29PX1YWJiAsPDw7zkk1Iq+1a0QVBEkuVy+dvW1hYuLi58HKMLBoPo6uoyb+6j0ShmZmYQDodnlVLfLdgXFJGQ53nVjY0NnJ+fo1arMT2Do0BbWxs6OjoQCASMqOd5iMViSKVS0Fp/UEo9ExywygBWj4+PUSwWTWpDQ0MYHx/3jylE4cHBQSwuLiIUCqFQKODk5ISYVQt8K7jCujE1RtPf349kMomRkRGLRSKRwNTUlMng+dkEBHLYPAsyXRaRj9fX19H7+3sTBQ+Pjo5Miul0Gg8PD+aS6elp831/f9/ycXd3B3KpoZTKWdvEb29vfRBT4yKR0S4vL5uLTk9Psbu7619qCeQ6jkOv5t6mbM/Nm6J8WFfWj4sXsFG2WQ2E+sYK5nt6elrOHcfB/Py8sdDl5SUWFhZokxbROjdPASPI3B3Hueru7vZFCZqbm2N9sL29jc3NTVMv1pQdtpHSm+RSwxesq6yNjY35grQMm5TNZvHy8oKnpyesr6/Tc6bz3L++vmJ0dJQcjqJZLcamAP1F+7B2lUoFj4+PRpBRdXZ2mm9kDwwMYGlp6X1j0+la61lag2PF26vVqpkYToVdtBD38XgcmUyGYhy9X6YE4EdoCfw5eJ63wwk4ODiA67ool8tGhJhIJILJyUmwPFrrlp+D1Wl4c65F5IuIFFzXlbOzM8nlclIsFoXf6mehBlJ90xJhM+hvf7DN/H/e/wRZ4k9klRmUggAAAABJRU5ErkJggg==',
-                alt: 'Close',
-                title: '[esc]键可直接关闭',
-                id: 'fehelper-colorpicker-exitbtn',
-                event: ['click', dissableColorPickerFromHere, true]
-            })
-        ], n);
-        if (_ge('fehelper-colorpicker-cphexvl')) _ge('fehelper-colorpicker-cphexvl').select();
-        keepOnScreen();
-    }
-
-    function picked() {
-        if (isLocked) {
-            lasthex = hex;
-            isLocked = false;
-            emptyNode(n);
-        } else {
-            isLocked = true;
-            setDisplay();
-        }
-    }
-
-    function dissableColorPickerFromHere() {
-        var disableTimeout = setTimeout(disableColorPicker, 500)
-        chrome.runtime.sendMessage({disableColorPicker: true}, function (r) {
-            clearTimeout(disableTimeout);
-        });
-    }
-
-    function disableColorPicker() {
-        isEnabled = false, isLocked = false;
-        document.removeEventListener('mousemove', mmf);
-        removeEventListener('scroll', ssf);
-        removeEventListener('resize', ssf);
-        removeEventListener('keyup', wk);
-        removeExistingNodes();
-        clearTimeout(lastNewTimeout);
-    }
-
-    function removeExistingNodes() {
-        if (document.body) {
-            c = _ge(elmid1), n = _ge(elmid2);
-            if (c) document.body.removeChild(c);
-            if (n) document.body.removeChild(n);
-            c = false, n = false;
-            document.body.style.cursor = '';
-        }
-    }
-
-    function wk(ev) {
-        if (!isEnabled) return;
-        if (ev.keyCode == 27) {
-            dissableColorPickerFromHere();
-        } else if (ev.keyCode == 82 || ev.keyCode == 74) {//r or j refresh
-            ssf();
-        } else if (ev.keyCode == 13) {
-            picked();
-        }
-    }
-
-    function mmf(ev) {
-        if (!isEnabled) return;
-        if (!isLocked) {
-            lx = (ev.pageX - pageXOffset), ly = (ev.pageY - pageYOffset);
-            ex = Math.round(lx * x_cvs_scale),
-                ey = Math.round(ly * y_cvs_scale);
-            updateColorPreview();
-        }
-    }
-
-    function ssf(ev) {
-        if (!isEnabled) return;
-        n.style.visibility = "hidden";
-        c.style.visibility = "hidden";//redundent?
-        clearTimeout(lastNewTimeout);
-        lastNewTimeout = setTimeout(function () {
-            newImage()//some delay required OR it won't update
-        }, 250);
-    }
-
-    function initialInit() {
-        removeExistingNodes();
-        c = FeHelper.elemTool.elm('img', {
-            id: elmid1,
-            src: blankgif,
-            style: 'position:fixed;max-width:none!important;max-height:none!important;top:0px;left:0px;margin:0px;padding:0px;overflow:hidden;z-index:2147483646;',
-            events: [['click', picked, true], ['load', snapshotLoaded]]
-        }, [], document.body);
-        n = FeHelper.elemTool.elm('div', {
-            id: elmid2,
-            style: 'position:fixed;min-width:30px;max-width:300px;box-shadow:2px 2px 2px #666;border:' + borderValue + ';border-radius:5px;z-index:2147483646;cursor:default;padding:10px;text-align:center;'
-        }, [], document.body);
-        document.addEventListener('mousemove', mmf);
-        addEventListener('keyup', wk);
-        addEventListener('scroll', ssf);
-        addEventListener('resize', ssf);
-        initializeCanvas();
-        remainingInit();
-    }
-
-    function enableColorPicker() {
-        if (!n) {
-            initialInit();
-            return false;
-        }
-        return remainingInit();
-    }
-
-    function remainingInit() {
-        if (!isEnabled) {
-            n.style.visibility = "hidden";
-            c.style.visibility = "hidden";
-            if (isLocked) picked();//unlocks for next pick
-            document.body.style.cursor = 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAhCAYAAABX5MJvAAAA8ElEQVRYCe1WAQ6DIAwsm//e9rP9jOU2z0CpyWhlcQkkpoCld15rVcQ5cs5ZRLZrXbuiXVynDj40SVDQkBKfshChZdBe6yaRUkolmF6X934xxxsSGm4lQqjq8CRBQa6cBCwK9Bk4LzMdVO8USizW129k47HwFtX43i0YjiOIkIDGRGV/2/EezKGydxHBZY2btWntbT8mIMRhOe7s7RFo3IvYFSZqonEekQqAIO5KpMJETVRfw+rugIWFd4pXdJI4MtttZXdGn+mgYP+vBBoPnoaWT9Zr3UpoYL3uIeImARA2W9oe4NI3RKIMFJlPElTvBRTxXFcOwvQSAAAAAElFTkSuQmCC) 16 16,crosshair';
-            isEnabled = true;
-            setTimeout(newImage, 1);
-            return false;
-        }
-        return true;
-    }
-
-    function keepOnScreen() {
-
-        if (!n) return;
-        n.style.top = (ly + 8) + "px";
-        n.style.left = (lx + 8) + "px";
-        if (n.clientWidth + n.offsetLeft + 24 > innerWidth) {
-            n.style.left = (lx - 8 - n.clientWidth) + "px";
-        }
-        if (n.clientHeight + n.offsetTop + 24 > innerHeight) {
-            n.style.top = (ly - 8 - n.clientHeight) + "px";
-        }
-    }
-
-    function updateColorPreview(ev) {
-        if (!isEnabled) return;
-        keepOnScreen();
-        var data = ctx.getImageData(ex, ey, 1, 1).data;
-        hsv = rgb2hsl(data[0], data[1], data[2]);
-        rgb = {r: data[0], g: data[1], b: data[2]};
-        setCurColor({hex: RGBtoHex(data[0], data[1], data[2])});
-        handleRendering();
-    }
-
-    var isMakingNew = false, lastNewTimeout = 0;
-
-    function newImage() {
-        if (!isEnabled) return;
-        if (isMakingNew) {
-            clearTimeout(lastNewTimeout);
-            lastNewTimeout = setTimeout(function () {
-                newImage()
-            }, 255);
-            return;
-        }
-        document.body.style.cursor = 'wait';
-        isMakingNew = true;
-        n.style.visibility = "hidden";
-        c.style.visibility = "hidden";
-        c.src = blankgif;
-        var x = innerWidth, y = innerHeight;
-        c.style.width = x + 'px';
-        c.style.height = y + 'px';
-
-        setTimeout(function () {
-            try {
-                var MSG_TYPE = Tarp.require('../static/js/msg_type');
-                chrome.runtime.sendMessage({type: MSG_TYPE.COLOR_PICKER}, function (response) {
-                });
-            } catch (e) {
-                console.log('有错误发生,可提交此反馈到官网!', e);
-            }
-        }, 255);
-    }
-
-    var lastPreviewURI;
-
-    var icvs = 0, totalWidth = 150;//750
-
-    function handleRendering(quick) {
-        var x = ex, y = ey;
-        if (isMakingNew) {
-            isUpdating = false;
-            return;
-        }
-
-        var startPoint = Math.floor(totalWidth * 0.5);
-        var ox = Math.round(x), oy = Math.round(y);
-
-        if (quick) {
-            var ictx = getMain2dContext();
-            ictx.scale(2, 2);
-            ictx.drawImage(cvs, -ox + (startPoint * 0.5), -oy + (startPoint * 0.5));
-            ictx.scale(0.5, 0.5);
-
-            ictx.fillStyle = "rgba(0,0,0,0.3)";//croshair
-
-            ictx.fillRect(startPoint, 0, 1, totalWidth);
-            ictx.fillRect(0, startPoint, totalWidth, 1);
-
-        } else {
-            var ictx = getMain2dContext();
-            ictx.drawImage(cvs, -ox + (startPoint), -oy + (startPoint));
-            var smi, spi, mp = 15 - 0;
-            //xx,yy
-            for (var i = 0; i < startPoint; i += 2) {
-                smi = startPoint - i;
-                spi = startPoint + i;
-                //drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) //CANVAS
-                ictx.drawImage(icvs, spi, 0, smi, totalWidth,//total width really??
-                    spi + 1, 0, smi, totalWidth);
-
-                ictx.drawImage(icvs, 0, 0, smi + 1, totalWidth,
-                    -1, 0, smi + 1, totalWidth);
-
-                ictx.drawImage(icvs, 0, spi, totalWidth, smi,
-                    0, spi + 1, totalWidth, smi);
-
-                ictx.drawImage(icvs, 0, 0, totalWidth, smi + 1,
-                    0, -1, totalWidth, smi + 1);
-
-                if (i == 0) {
-                    var dat = ictx.getImageData(startPoint, startPoint, 1, 1).data;//notarget
-                    var d = dat[0] + dat[1] + dat[2];
-                    if (d > 192) ictx.fillStyle = "rgba(30,30,30,0.8)";
-                    else ictx.fillStyle = "rgba(225,225,225,0.8)";
-                } else ictx.fillStyle = "rgba(255,255,255,0.4)";
-
-                for (var c = 0; c < mp; c++) {
-                    if (++i >= startPoint) break;
-                    smi = startPoint - i;
-                    spi = startPoint + i;
-                    ictx.drawImage(icvs, spi, 0, smi, totalWidth,
-                        spi + 1, 0, smi, totalWidth);
-
-                    ictx.drawImage(icvs, 0, 0, smi + 1, totalWidth,
-                        -1, 0, smi + 1, totalWidth);
-
-                    ictx.drawImage(icvs, 0, spi, totalWidth, smi,
-                        0, spi + 1, totalWidth, smi);
-
-                    ictx.drawImage(icvs, 0, 0, totalWidth, smi + 1,
-                        0, -1, totalWidth, smi + 1);
-                }
-                mp--;
-                if (mp < 1) mp = 1;
-                ictx.fillRect(spi + 1, 0, 1, totalWidth);
-                ictx.fillRect(smi - 1, 0, 1, totalWidth);
-                ictx.fillRect(0, spi + 1, totalWidth, 1);
-                ictx.fillRect(0, smi - 1, totalWidth, 1);
-            }
-        }
-
-        lastPreviewURI = icvs.toDataURL();//the last one, large size, is cached for revisiting the menu
-
-        var browseIconWidth = (devicePixelRatio > 1 ? 38 : 19);
-        var browseIconHalfWidth = Math.floor(browseIconWidth * 0.5);
-        //chrome.browserAction.setIcon({imageData:ictx.getImageData(startPoint-browseIconHalfWidth, startPoint-browseIconHalfWidth, browseIconWidth, browseIconWidth)});
-
-        var tmpCvs = document.createElement('canvas');
-        tmpCvs.width = browseIconWidth, tmpCvs.height = browseIconWidth;
-        var tctx = tmpCvs.getContext("2d");
-        tctx.drawImage(icvs, startPoint - browseIconHalfWidth, startPoint - browseIconHalfWidth, browseIconWidth, browseIconWidth, 0, 0, browseIconWidth, browseIconWidth);
-        var pathData = {};
-        pathData[browseIconWidth] = tmpCvs.toDataURL();
-        chrome.runtime.sendMessage({browserIconMsg: true, path: (pathData)}, function () {
-        });
-
-        setPixelPreview(lastPreviewURI, hex, lasthex);
-
-        sendDataToPopup();
-        isUpdating = false;
-    }
-
-    function sendDataToPopup() {
-        chrome.runtime.sendMessage({
-            setPreview: true,
-            previewURI: lastPreviewURI,
-            hex: hex,
-            lhex: lasthex,
-            elemTool: rgb.r,
-            cg: rgb.g,
-            cb: rgb.b
-        }, function (response) {
-        });
-    }
-
-    function getMain2dContext() {
-        var context = icvs.getContext("2d");
-        if (context) return context;
-        else {
-            initializeCanvas();
-            return icvs.getContext("2d");
-        }
-    }
-
-    function initializeCanvas() {
-        icvs = document.createElement('canvas');//icon canvas
-        icvs.width = totalWidth;
-        icvs.height = totalWidth;
-    }
-
-    function reqLis(request, sender, sendResponse) {
-        var resp = {result: true};
-        if (request.enableColorPicker) {
-            disableColorPicker();
-            resp.wasAlreadyEnabled = enableColorPicker()
-            if (request.workerHasChanged) lsnaptabid = -1;
-            if (resp.wasAlreadyEnabled) {
-                resp.hex = hex;
-                resp.lhex = lasthex;
-                resp.previewURI = lastPreviewURI;
-                resp.FeHelper.elemTool = rgb.r;
-                resp.cg = rgb.g;
-                resp.cb = rgb.b;
-            }
-        } else if (request.doPick) {
-            picked()
-        } else if (request.setPickerImage) {
-            c.src = request.pickerImage;
-        }
-        resp.isPicking = !isLocked;
-        sendResponse(resp);
-    }
-
-    return {
-        handler: reqLis
-    };
-
-
-})();
-
-module.exports = FeHelper.ColorPicker;

+ 0 - 7
apps/content-script/fehelper-only.js

@@ -1,7 +0,0 @@
-/**
- * FE-Helper ContentScripts
- * @author [email protected]
- */
-window.onload = function(){
-    document.getElementById('btnInstallExtension').style.display = 'none';
-};

+ 0 - 96
apps/content-script/index.js

@@ -1,96 +0,0 @@
-/**
- * FeHelper Content Scripts Manager
- * @author zhaoxianlie
- */
-
-
-// Tarp.require.config初始化
-(() => {
-    try{
-        let absPath = (() => {
-            let stack;
-            try {
-                a.b();
-            }
-            catch (e) {
-                stack = e.fileName || e.sourceURL || e.stack || e.stacktrace;
-            }
-            if (stack) {
-                return /((?:http|https|file|chrome-extension|moz-extension):\/\/.*?\/[^:]+)(?::\d+:\d+)?/.exec(stack)[1];
-            }
-        })();
-        Tarp.require.config = {
-            paths: [absPath],
-            uri: absPath
-        };
-    }catch (ex){
-        console.log('FeHelper:',ex)
-    }
-
-})();
-
-// JSON页面自动格式化,事件注册
-chrome.runtime.sendMessage({
-    type: MSG_TYPE.JSON_PAGE_FORMAT_REQUEST
-});
-// js、css页面自动格式化,事件注册
-chrome.runtime.sendMessage({
-    type: MSG_TYPE.JS_CSS_PAGE_BEAUTIFY_REQUEST
-});
-
-// 在tab创建或者更新时候,监听事件,看看是否有参数传递过来
-chrome.runtime.onMessage.addListener(function (request, sender, callback) {
-
-    switch (request.type) {
-        // JSON页面自动格式化
-        case MSG_TYPE.JSON_PAGE_FORMAT:
-            Tarp.require('../json-format/automatic').format(request.options);
-            break;
-
-        // js、css页面自动检测,提示格式化
-        case MSG_TYPE.JS_CSS_PAGE_BEAUTIFY:
-            Tarp.require('../code-beautify/automatic', true).then(beautifier => beautifier.detect(request.content));
-            break;
-
-        // 二维码解码
-        case MSG_TYPE.QR_DECODE:
-            Tarp.require('../qr-code/decode', true).then(qrcode => qrcode.show(request.result));
-            break;
-
-        // 全屏截图
-        case MSG_TYPE.PAGE_CAPTURE_SCROLL:
-            Tarp.require('../page-capture/inject', true).then(page => page.scroll(callback));
-            break;
-
-        // 页面性能检测
-        case MSG_TYPE.GET_PAGE_WPO_INFO:
-            Tarp.require('../wpo/inject', true).then(wpo => {
-                (function check() {
-                    (document.readyState === "complete") ? wpo.getWpoInfo() : setTimeout(check, 500);
-                })()
-            });
-            break;
-
-        // 页面取色工具 color picker
-        case MSG_TYPE.SHOW_COLOR_PICKER:
-            Tarp.require('../color-picker/index', true).then(picker => picker.handler(request, sender, callback));
-            break;
-
-        // 编码规范检测
-        case MSG_TYPE.CODE_STANDARDS:
-            Tarp.require('../code-standards/index', true).then(cs => {
-                if (request.event === MSG_TYPE.FCP_HELPER_INIT) {
-                    cs.init();
-                } else if (MSG_TYPE.FCP_HELPER_DETECT) {
-                    cs.detect();
-                }
-            });
-            break;
-
-        // 屏幕栅格标尺
-        case MSG_TYPE.GRID_RULER:
-            Tarp.require('../ruler/index',true).then(ruler => ruler.detect(callback));
-            break;
-    }
-
-});

+ 3 - 2
apps/toolkit/crontab/index.css → apps/crontab/index.css

@@ -1,3 +1,5 @@
+@import url("../static/css/bootstrap.min.css");
+
 *,
 *::before,
 *::after {
@@ -82,7 +84,6 @@
 .mod-crontab div.warning {
     color: saddlebrown;
     font-size: 75%;
-    height: 0;
 }
 .mod-crontab .text-editor input {
     font-family: "Courier New", Courier, monospace;
@@ -232,4 +233,4 @@
 .mod-crontab .monitor .info {
     font-size: 75%;
     margin-top: 2em;
-}
+}

+ 1 - 0
apps/crontab/index.html

@@ -0,0 +1 @@
+<!DOCTYPE HTML>
<html lang="zh-CN">
<head>
    <title>Crontab生成器</title>
    <meta charset="UTF-8">
    <link rel="shortcut icon" href="../static/img/favicon.ico">
    <link rel="stylesheet" href="index.css" />
    <script type="text/javascript" src="../static/vendor/vue/vue.js"></script>
</head>
<body>

<div class="wrapper" id="pageContainer">
    <div class="panel panel-default" style="margin-bottom: 0px;">
        <div class="panel-heading">
            <h3 class="panel-title">
                <a href="http://www.baidufe.com/fehelper/feedback.html" target="_blank" class="x-a-high">
                    <img src="../static/img/fe-16.png" alt="fehelper"/> FeHelper</a>:Crontab生成器</h3>
        </div>
    </div>
    <div class="panel-body mod-crontab">
        <div id="contabContentBox">loading...</div>
    </div>
</div>
<script src="../static/vendor/jquery/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="index.js"></script>

<script src="../static/js/navbar.js"></script>
</body>
</html>

+ 0 - 0
apps/toolkit/crontab/index.js → apps/crontab/index.js


+ 69 - 0
apps/devtools/file-tpl.js

@@ -0,0 +1,69 @@
+window.FileTpl = {
+    // 配置文件
+    'fh-config.js': `{
+    "#toolName#" : {
+        "name": "#toolFullName#",
+        "tips": "我是 #toolName# 的描述信息!你可以在这里修改!",
+        "contentScript": #contentScript#,
+        "noPage": #noPage#, 
+        "updateUrl":"#updateUrl#",
+        "menuConfig": [
+            {
+                "icon": "#toolIcon#",
+                "text": "我是 #toolName#",
+                "onClick": function (info, tab) {
+                    alert("你好,我是#toolName#!");
+                    chrome.DynamicToolRunner({
+                        query: "tool=#toolName#"
+                    });
+                }
+            }
+        ]        
+    }                    
+}`,
+
+    // 主入口文件
+    'index.html': `<!DOCTYPE html>
+<html>
+<head>
+	<title>#toolName#</title>
+	<link rel="stylesheet" type="text/css" href="index.css">
+</head>
+<body>
+	#toolName#
+	<script type="text/javascript" src="index.js"></script>
+</body>
+</html>`,
+
+    // 内容脚本
+    'content-script.js': `/**
+ * 注意这里的方法名称,不要改!不要改!不要改!
+ */
+window.#toolNameLower#ContentScript = function () {
+    console.log('你好,我是来自FeHelper的工具:#toolName#!');
+};`,
+
+    // noPage为true时需要追加的内容脚本
+    'noPage.js': `/**
+ * 如果在 fh-config.js 中指定了 noPage参数为true,则这里必须定义noPage的接口方法,如:
+ * 注意这里的方法名称,不要改!不要改!不要改!
+ */
+window.#toolNameLower#NoPage = function (tabInfo) {
+    alert('你好,我是来自FeHelper的工具:#toolName#!你可以打开控制台看Demo的输出!');
+    console.log('你好,我是来自FeHelper的工具:#toolName#', tabInfo);
+};`,
+
+    // index.js & index.css
+    "index.js": `/* code here... */\n`,
+    "index.css": `/* code here... */\n`,
+
+    // 系统图标
+    'given-icons': `❤❥웃유☮☏☢☠✔☑♚▲♪✈✞÷↑↓◆◇⊙■□△▽¿─♥❣♂♀☿Ⓐ✉☣☤✘☒♛▼♫⌘☪≈←→◈◎☉★☆⊿※¡━♡ღツ☼☁❅✎©®™Σ✪✯☭➳卐√↖↗●◐Θ℃℉°✿ϟ☃☂✄¢€£∞✫★½✡×↙↘○◑⊕☽☾✚〓↔↕☽☾の①②③④⑤⑥⑦⑧⑨⑩ⅠⅡ
+                    ⅢⅣⅤⅥⅦⅧⅨⅩ♨♛❖☪✙┉☹☺☻ﭢ™℠℗©®♥❤❥❣❦❧♡۵웃유ღ♂♀☿☼☀☁☂☄☾☽❄☃☈⊙☉℃℉❅✺ϟ☇♤♧♡♢♠♣♥♦☜☞☚☛☟✽✾✿❁❃❋❀⚘☑✓✔√☐☒✗✘ㄨ✕✖✖⋆✢✣✤✥❋✦✧✩✰✪✫✬✭✮✯❂✡★✱✲✳✴✵✶✷✸✹✺✻✼❄❅❆❇❈❉
+                    ❊†☨✞✝☥☦☓☩☯☧☬☸✡♁✙♆☩☨☦✞✛✜✝✙✠✚†‡◉○◌◍◎●◐◑◒◓◔◕◖◗❂☢⊗⊙◘◙◍⅟½⅓⅕⅙⅛⅔⅖⅚⅜¾⅗⅝⅞⅘⊰⊱⋛⋚∫∬∭∮∯∰∱∲∳%℅‰‱㊣㊎㊍㊌㊋㊏㊐㊊㊚㊛㊤㊥㊦㊧㊨㊒㊞㊑㊒㊓㊔㊕㊖㊗㊘㊜㊝㊟
+                    ㊠㊡㊢㊩㊪㊫㊬㊭㊮㊯㊰㊙㉿囍♔♕♖♗♘♙♚♛♜♝♞♟ℂℍℕℙℚℝℤℬℰℯℱℊℋℎℐℒℓℳℴ℘ℛℭ℮ℌℑℜℨ♪♫♩♬♭♮♯°øⒶ☮☪✡☭✯卐✐✎✏✑✒✉✁✂✃✄✆✉☎☏➟➡➢➣➤➥➦➧➨➚➘➙➛➜➝➞➸➲➳⏎➴➵➶➷➸➹➺➻➼➽
+                    ←↑→↓↔↕↖↗↘↙↚↛↜↝↞↟↠↡↢↣↤↥↦↧↨➫➬➩➪➭➮➯➱↩↪↫↬↭↮↶↷↸↹↺↻↼⇄⇅⇆⇇⇈⇉⇊⇋⇌⇍⇎⇏⇐⇑⇒⇓⇔⇕⇖⇗⇘⇙⇚⇛⇜⇝⇞⇟⇦⇧⇨⇩⇪♤♧♡♢♠♣♥♦☀☁☂❄☃♨웃유❖☽☾☪✿♂♀✪✯☭➳卍卐√×■◆●○◐◑✙☺☻❀⚘♔♕♖♗♘♙♚♛♜
+                    ♝♞♟♧♡♂♀♠♣♥❤☜☞☎☏⊙◎☺☻☼▧▨♨◐◑↔↕▪▒◊◦▣▤▥▦▩◘◈◇♬♪♩♭♪の★☆♦◊◘◙◦☼♠♣▣▤▥▦▩◘◙◈♫♬♪£Ю〓§♤♥▶¤✲❈✿✲❈➹☀☂☁【】┱┲❣✚✪✣✤✥✦❉❥❦❧❃❂❁❀✄☪☣☢☠☭ღ▶▷◀◁☀☁☂☃☄★☆☇☈⊙☊☋☌☍➀
+                    ➁➂➃➄➅➆➇➈➉➊➋➌➍➎➏➐➑➒➓㊀㊁㊂㊃㊄㊅㊆㊇㊈㊉ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⒜
+                    ⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵`
+};

+ 279 - 0
apps/devtools/index.css

@@ -0,0 +1,279 @@
+@import url("../static/css/bootstrap.min.css");
+@import url("../static/vendor/codemirror/codemirror.css");
+
+ul {
+    margin: 0;
+    padding: 0;
+}
+
+ul.box-tools li {
+    list-style: none;
+    float: left;
+    font-size: 14px;
+    margin: 10px 20px 10px 0;
+    border: 1px solid #ccc;
+    padding: 20px 15px 10px;
+    border-radius: 10px;
+    background: #fefefe;
+    width: 400px;
+    box-shadow: 2px 2px #f2f2f2;
+    height: 154px;
+    user-select: none;
+}
+
+ul.box-tools li i.x-icon {
+    float: left;
+    font-size: 30pt;
+    font-style: normal;
+    display: block;
+    width: 60px;
+    height: 60px;
+    text-align: center;
+    color: red;
+    border: 1px solid #ddd;
+    background: #fff;
+    border-radius: 4px;
+    box-shadow: 2px 2px #eee;
+}
+
+ul.box-tools li .x-infos {
+    margin-left: 80px;
+    height: 90px;
+    overflow: hidden;
+}
+
+ul.box-tools li .x-tips {
+    margin-top: 10px;
+    color: #888;
+    font-size: 12px;
+}
+
+ul.box-tools li .x-btns {
+    border-top: 1px solid #eee;
+    padding-top: 10px;
+}
+
+.given-icons {
+    width: 70%;
+    margin: 0 auto;
+    box-sizing: border-box;
+    font-size: 18px;
+    word-break: break-all;
+    background: #fff;
+    border: 1px solid #aaa;
+    border-radius: 10px;
+    padding: 10px 10px 10px 20px;
+    position: fixed;
+    top: 120px;
+    left: 15%;
+    box-shadow: 4px 4px #eee;
+    z-index: 100;
+}
+
+.given-icons h3 {
+    color: #48b;
+}
+
+.given-icons .the-icons i {
+    font-style: normal;
+    padding: 5px;
+    cursor: default;
+}
+
+.given-icons .the-icons i:hover {
+    color: #f00;
+    background: #ff0;
+}
+
+.given-icons .x-close {
+    position: absolute;
+    top: 10px;
+    right: 10px;
+    color: #f00;
+    cursor: pointer;
+    font-size: 14px;
+}
+
+.given-icons .x-close:hover {
+    color: #b00;
+}
+
+ul.box-tools li.x-addtool {
+    width: 154px;
+    border-radius: 78px;
+    text-align: center;
+    font-size: 120pt;
+    line-height: 120px;
+    color: #eee;
+    text-shadow: 5px 4px #fff;
+    background: #fcfcfc;
+    border-color: #eee;
+}
+
+ul.box-tools li.x-addtool:hover {
+    color: #ccc;
+    cursor: pointer;
+    border-color: #ccc;
+    background: #f5f5f5;
+}
+
+.x-mask {
+    position: absolute;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    right: 0;
+    background: #000;
+    opacity: 0.4;
+}
+
+.fh-editor .x-editor-container {
+    width: 90%;
+    margin: 0 auto;
+    position: absolute;
+    left: 5%;
+    top: 40px;
+    height: calc(100% - 60px);
+    border: 1px solid #aaa;
+    border-radius: 4px;
+    box-shadow: 3px 3px #aaa;
+}
+
+.fh-editor .x-editor-container .x-topbar {
+    background: #f5f5f5;
+    height: 30px;
+    border-radius: 4px 4px 0 0;
+    line-height: 30px;
+    padding: 0 10px 0 10px;
+}
+
+.fh-editor .x-editor-container .x-leftside {
+    background: #fff;
+    width: 250px;
+    height: calc(100% - 30px);
+    float: left;
+    border-radius: 0 0 0 4px;
+    border-top: 1px solid #ddd;
+    overflow-x: hidden;
+    overflow-y: auto;
+}
+
+.fh-editor .x-editor-container .x-rightside {
+    position: absolute;
+    left: 250px;
+    right: 0;
+    top: 30px;
+    bottom: 0;
+}
+
+.fh-editor .x-editor-container .CodeMirror {
+    width: 100%;
+    height: 100%;
+    border-radius: 0 0 4px 0;
+}
+
+.fh-editor .x-editor-container .x-topbar .x-icon {
+    color: #f00;
+    font-style: normal;
+}
+
+.fh-editor .x-editor-container .x-topbar .x-close {
+    color: #f00;
+    font-size: 14px;
+    cursor: pointer;
+}
+
+.fh-editor .x-editor-container .x-topbar .x-close:hover {
+    text-decoration: underline;
+}
+
+.fh-editor .x-editor-container .x-topbar .x-save {
+    color: #48b;
+}
+
+.fh-editor .x-editor-container .x-topbar .x-save:hover {
+    color: #68b;
+}
+
+ul.box-files li {
+    list-style: none;
+    user-select: none;
+    padding: 0 2px 0 10px;
+    border-bottom: 1px solid #f2f2f2;
+    height: 26px;
+    line-height: 26px;
+    font-size: 14px;
+    cursor: default;
+    position: relative;
+}
+ul.box-files li.x-selected:after {
+    content: '\2714';
+    position: absolute;
+    right: 10px;
+}
+ul.box-files li:hover {
+    background: #eee;
+}
+ul.box-files li .x-delete {
+    position: absolute;
+    right: 30px;
+    top:0;
+    font-size: 12px;
+    color: #48b;
+    text-decoration: underline;
+    display: none;
+    cursor: pointer;
+}
+ul.box-files li .x-delete:hover {
+    color: #f00;
+}
+ul.box-files li:hover .x-delete {
+    display: inline-block;
+}
+ul.box-files li .x-folder {
+    color: #ddd;
+    font-style: italic;
+}
+ul.box-files li .x-file {
+    word-break: keep-all;
+    white-space: nowrap;
+}
+
+ul.box-files li.x-tools {
+    border-bottom: none;
+    padding: 10px 2px 0 10px
+}
+ul.box-files li.x-tools:hover {
+    background: transparent;
+}
+
+.x-new-tool-form form {
+    width: 600px;
+    position: absolute;
+    top: 130px;
+    left: calc(50% - 300px);
+    background: #fff;
+    padding: 10px;
+    border: 1px solid #aaa;
+    border-radius: 6px;
+}
+
+#fehelper_alertmsg {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    z-index: 1000000;
+    background: #000;
+    display: inline-block;
+    color: #fff;
+    text-align: center;
+    padding: 10px 10px;
+    margin: 0 auto;
+    font-size: 14px;
+    border-bottom: 1px solid #aaa;
+}
+
+[v-cloak] {
+    display: none;
+}

+ 162 - 0
apps/devtools/index.html

@@ -0,0 +1,162 @@
+<!DOCTYPE HTML>
+<html lang="zh-CN">
+    <head>
+        <title>FH开发者工具</title>
+        <meta charset="UTF-8">
+        <link rel="shortcut icon" href="../static/img/favicon.ico">
+        <link rel="stylesheet" href="index.css" />
+        <script type="text/javascript" src="../static/vendor/vue/vue.js"></script>
+    </head>
+    <body>
+        <div class="wrapper wp-json" id="pageContainer">
+            <div class="panel panel-default" style="margin-bottom: 0px;">
+                <div class="panel-heading">
+                    <h3 class="panel-title">
+                        <a href="http://www.baidufe.com/fehelper/feedback.html" target="_blank" class="x-a-high">
+                            <img src="../static/img/fe-16.png" alt="fehelper"/> FeHelper</a>:开发者工具
+                    </h3>
+                </div>
+            </div>
+
+            <div class="panel-body">
+                <button class="btn btn-primary" @click="addNewTool">开始我的第一个FH工具</button>
+                <button class="btn btn-default ui-ml-10" @click="loadTool(false)">载入本地工具包(*.zip)</button>
+                <button class="btn btn-default ui-ml-10" @click="addNewTool('url')">从远程服务载入工具</button>
+                <button class="btn btn-default ui-ml-10" @click="downloadTool()">下载Hello-world示例</button>
+                <button class="btn btn-default ui-ml-10" @click="givenIcons()">获取现成的图标</button>
+                <button class="btn btn-default ui-ml-10" @click="fhDeveloperDoc">FH工具开发文档</button>
+
+                <hr>
+                <ul class="box-tools">
+                    <li v-for="tool in Object.keys(myTools)">
+                        <i class="x-icon">{{myTools[tool].menuConfig[0].icon}}</i>
+                        <div class="x-infos">
+                            <b>{{myTools[tool].name}}</b>
+                            <div class="x-tips">{{myTools[tool].tips}}</div>
+                        </div>
+
+                        <div class="x-btns">
+                            <button class="btn btn-xs btn-success" @click="upgrade(tool)">{{tool===demo.name ? '在线' : 'zip包'}}更新</button>
+                            <button class="btn btn-xs btn-success ui-ml-10" @click="upgrade(tool,true)" v-if="myTools[tool].updateUrl">URL更新</button>
+                            <button class="btn btn-xs btn-primary ui-ml-10" @click="downloadTool(tool)">下载zip包</button>
+                            <button class="btn btn-xs btn-primary ui-ml-10" @click="toggleEditor(true,tool)">编辑</button>
+                            <button class="btn btn-xs btn-danger ui-ml-10" :disabled="tool==='hello-world'" @click="delToolConfigs(tool)">删除</button>
+                            <button class="btn btn-xs ui-fl-r" :class="myTools[tool]._enable ? 'btn-success' : 'btn-warning'" @click="toggleToolEnableStatus(tool)">{{myTools[tool]._enable ? '启用中' : '已停用'}}</button>
+                        </div>
+                    </li>
+
+                    <li class="x-addtool" title="创建工具" @click="addNewTool('local')">+</li>
+                </ul>
+            </div>
+
+            <div class="fh-editor" v-cloak v-show="showEditorFlag">
+                <div class="x-mask"></div>
+                <div class="x-editor-container">
+                    <div class="x-topbar">
+                        <i class="x-icon">{{model.icon}}</i>
+                        <b class="x-title">{{model.name + ' : ' + model.editingFile}}</b>
+                        <span class="x-close ui-fl-r" @click="toggleEditor(false)">关闭</span>
+                    </div>
+                    <div class="x-leftside">
+                        <ul class="box-files">
+                            <li v-for="file in model.files" @click="editFile(model.tool,file)" :class="model.editingFile === file ? 'x-selected' : ''">
+                                <span class="x-file">{{file.split('/').pop()}} <i class="x-folder" v-if="file.split('/').length>1">({{file.substr(0,file.lastIndexOf('/')).split('/').splice(-2,2).join('/')}})</i></span>
+                                <span class="x-delete" @click="deleteFile(model.tool,file,$event)" v-if="!['index.html','fh-config.js'].includes(file)">删除</span>
+                            </li>
+
+                            <li class="x-tools">
+                                <button class="btn btn-xs btn-primary" @click="createFile(model.tool)">创建文件</button>
+                                <button class="btn btn-xs btn-success ui-ml-10" @click="importFile(model.tool)">导入文件</button>
+                            </li>
+                        </ul>
+                    </div>
+                    <div class="x-rightside">
+                        <textarea class="form-control" id="txtEditor" ref="txtEditor" placeholder="FH开发者工具-编辑器"></textarea>
+                    </div>
+                </div>
+            </div>
+
+            <div class="given-icons" v-cloak v-if="showGivenIcons">
+                <h3>FeHelper为您提供的字符图标</h3>
+                <span class="ui-fl-r x-close" @click="givenIcons(true)">关闭</span>
+                <hr>
+                <div class="the-icons">
+                    <i class="x-icon" v-for="icon in givenIconList" @click="selectIcon(icon)">{{icon}}</i>
+                </div>
+            </div>
+
+            <div class="x-new-tool-form" v-cloak v-if="showNewToolForm">
+                <div class="x-mask"></div>
+                <form @submit.prevent="newToolAction($event)" action="#" class="form-horizontal">
+                    <div class="form-group">
+                        <label for="toolId" class="col-sm-2 control-label">工具ID</label>
+                        <div class="col-sm-10">
+                            <input type="text" class="form-control" id="toolId" ref="toolId" placeholder="由数字、字母、减号组成,如:hello-world" maxlength="30" required="required" pattern="[a-z\-0-9]+">
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label for="toolName" class="col-sm-2 control-label">工具名称</label>
+                        <div class="col-sm-10">
+                            <input type="text" class="form-control" id="toolName" ref="toolName" placeholder="中英文均可,但不要超过6个中文字符的宽度" maxlength="20" required="required" pattern="[^\s]+">
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label for="toolIcon" class="col-sm-2 control-label">工具Icon</label>
+                        <div class="col-sm-10">
+                            <input type="text" class="form-control" id="toolIcon" ref="toolIcon" placeholder="工具字符图标" maxlength="1" required="required" pattern="[^\s]+" style="width:300px;display: inline-block">
+                            <span class="btn btn-sm btn-primary" @click="givenIcons()">获取现成图标</span>
+                        </div>
+                    </div>
+                    <div class="form-group" v-if="updateUrlMode">
+                        <label for="updateUrl" class="col-sm-2 control-label">Web地址</label>
+                        <div class="col-sm-10">
+                            <input type="text" class="form-control" id="updateUrl" ref="updateUrl" placeholder="该独立工具所在的Web服务URL" required="required" pattern="http(s)?:\/\/[\w\.\-]+\S+">
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <div class="col-sm-offset-2 col-sm-10">
+                            <div class="checkbox">
+                                <label>
+                                    <input type="checkbox" id="hasContentScript" ref="hasContentScript"> 需要进行页面脚本注入
+                                </label>
+                            </div>
+                            <div class="checkbox">
+                                <label>
+                                    <input type="checkbox" id="noPage" ref="noPage"> 此工具不需要独立界面
+                                </label>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <div class="col-sm-offset-2 col-sm-10">
+                            <button type="submit" class="btn btn-success">确认创建</button>
+                            <button class="btn btn-default ui-ml-20" @click="showNewToolForm=false">取消</button>
+                        </div>
+                    </div>
+                </form>
+            </div>
+
+        </div>
+        <script src="../static/vendor/jszip/jszip.js"></script>
+        <script src="../static/vendor/jszip/zip.js"></script>
+        <script src="../static/vendor/jszip/zip-ext.js"></script>
+        <script src="../static/vendor/jszip/deflate.js"></script>
+        <script src="../static/vendor/jszip/inflate.js"></script>
+        <script src="../static/vendor/jszip/mime-types.js"></script>
+        <script src="../static/vendor/jszip/zip-fs.js"></script>
+        <script src="../static/vendor/jszip/z-worker.js"></script>
+
+        <script src="../static/vendor/codemirror/codemirror.js"></script>
+        <script src="../static/vendor/codemirror/javascript.js"></script>
+        <script src="../static/vendor/codemirror/htmlmixed.js"></script>
+        <script src="../static/vendor/codemirror/xml.js"></script>
+        <script src="../static/vendor/codemirror/css.js"></script>
+        <script src="../static/vendor/codemirror/active-line.js"></script>
+        <script src="../static/vendor/codemirror/matchbrackets.js"></script>
+        <script src="../static/vendor/codemirror/placeholder.js"></script>
+        <script src="../static/vendor/codemirror/formatting.js"></script>
+
+        <script src="./file-tpl.js"></script>
+        <script src="./index.js"></script>
+    </body>
+</html>

+ 809 - 0
apps/devtools/index.js

@@ -0,0 +1,809 @@
+const DEV_TOOLS_MY_TOOLS = 'DEV-TOOLS:MY-TOOLS';
+const TOOL_NAME_TPL = 'DYNAMIC_TOOL:#TOOL-NAME#';
+const TOOL_CONTENT_SCRIPT_TPL = 'DYNAMIC_TOOL:CS:#TOOL-NAME#';
+const TOOL_CONTENT_SCRIPT_CSS_TPL = 'DYNAMIC_TOOL:CS:CSS:#TOOL-NAME#';
+let editor = null;
+
+new Vue({
+    el: '#pageContainer',
+    data: {
+        showGivenIcons: false,
+        myTools: {},
+        showEditorFlag: false,
+        showNewToolForm: false,
+        updateUrlMode: false,
+        givenIconList: [],
+        model: {},
+        demo: {
+            name: 'hello-world',
+            files: ['fh-config.js', 'index.html', 'index.js', 'index.css', 'content-script.js']
+        }
+    },
+    mounted: function () {
+        // 本地没安装过demo,就强制再更新一遍
+        this.getContentFromLocal('hello-world', 'index.html').then(content => {
+            !content && this.loadDemo();
+        });
+        this.getToolConfigs();
+    },
+
+    methods: {
+
+        toggleEditor(show, toolName) {
+            this.showEditorFlag = show;
+            if (show && toolName) {
+
+                let toolObj = this.myTools[toolName] || {};
+                this.model = {
+                    tool: toolName,
+                    icon: toolObj.menuConfig[0].icon,
+                    name: toolObj.name
+                };
+
+                this.getToolFilesFromLocal(toolName).then(files => {
+                    this.model.files = files;
+
+                    if (!editor) {
+                        editor = CodeMirror.fromTextArea(this.$refs.txtEditor, {
+                            mode: 'javascript',
+                            lineNumbers: true,
+                            matchBrackets: true,
+                            styleActiveLine: true,
+                            lineWrapping: true
+                        });
+                        editor.on('change', (editor, changes) => {
+                            let result = this.saveContentToLocal(this.model.tool, this.model.editingFile, editor.getValue());
+                            if (this.model.editingFile === 'fh-config.js' && result) {
+                                result.contentScript && !this.model.files.includes('content-script.js') && this.model.files.push('content-script.js');
+                                result.contentScriptCss && !this.model.files.includes('content-script.css') && this.model.files.push('content-script.css');
+                                this.$forceUpdate();
+                            }
+                        });
+                        editor.on('keydown', (editor, event) => {
+                            if (event.metaKey || event.ctrlKey) {
+                                if (event.code === 'KeyS') {
+                                    this.toast('当前代码是自动保存的,无需Ctrl+S手动保存!');
+                                    event.preventDefault();
+                                    event.stopPropagation();
+                                    return false;
+                                }
+                            }
+                        });
+                    }
+                    this.$nextTick(() => this.editFile(toolName, files[0]));
+                });
+            }
+        },
+        editFile(toolName, fileName) {
+
+            let editorMode = {
+                css: 'text/css',
+                js: {name: 'javascript', json: true},
+                html: 'htmlmixed'
+            };
+            let mode = editorMode[/\.(js|css|html)$/.exec(fileName)[1]];
+            editor.setOption('mode', mode);
+            this.model.editingFile = fileName;
+
+            this.getContentFromLocal(toolName, fileName).then(content => {
+                editor.setValue(content);
+                editor.focus();
+                this.$forceUpdate();
+            });
+        },
+        importFile(toolName) {
+            let fileInput = document.createElement('input');
+            fileInput.type = 'file';
+            fileInput.multiple = 'multiple';
+            fileInput.accept = 'text/javascript,text/css,text/html';
+            fileInput.style.cssText = 'position:absolute;top:-100px;left:-100px';
+            fileInput.addEventListener('change', (evt) => {
+                Array.prototype.slice.call(fileInput.files).forEach(file => {
+                    let reader = new FileReader();
+                    reader.onload = (evt) => {
+                        if (this.model.files.includes(file.name)) {
+                            if (!confirm(`文件 ${file.name} 已经存在,是否需要覆盖?`)) {
+                                return false;
+                            }
+                        } else {
+                            this.model.files.push(file.name);
+                        }
+                        this.saveContentToLocal(toolName, file.name, evt.target.result);
+                        this.editFile(toolName, file.name);
+                    };
+                    reader.readAsText(file);
+                })
+            }, false);
+
+            document.body.appendChild(fileInput);
+            fileInput.click();
+            window.setTimeout(() => fileInput.remove(), 3000);
+        },
+        createFile(toolName) {
+            let fileName = prompt('请输入你要创建的文件名!注意,只能是 *.html 、*.js 、*.css 类型的文件!').trim();
+            let result = /^[\w\-_\.]+\.(html|js|css)$/.exec(fileName);
+            if (!result) {
+                return alert('文件格式不正确!创建文件失败!');
+            }
+            if (this.model.files.includes(fileName)) {
+                return alert(`文件 ${fileName} 已经存在!`);
+            }
+            this.model.files.push(fileName);
+            this.saveContentToLocal(toolName, fileName, '');
+            this.editFile(toolName, fileName);
+        },
+        deleteFile(toolName, fileName, event) {
+            event.preventDefault();
+            event.stopPropagation();
+
+            if (['fh-config.js', 'index.html'].includes(fileName)) {
+                return alert(`文件 ${fileName} 不允许被删除!`);
+            }
+
+            if (confirm(`确定要删除文件 ${fileName} 吗?此操作不可撤销,请三思!`)) {
+                this.model.files.splice(this.model.files.indexOf(fileName), 1);
+                this.$forceUpdate();
+
+                let key = '';
+                switch (fileName) {
+                    case 'index.html':
+                        key = TOOL_NAME_TPL.replace('#TOOL-NAME#', toolName);
+                        break;
+                    case 'content-script.js':
+                        key = TOOL_CONTENT_SCRIPT_TPL.replace('#TOOL-NAME#', toolName);
+                        break;
+                    case 'content-script.css':
+                        key = TOOL_CONTENT_SCRIPT_CSS_TPL.replace('#TOOL-NAME#', toolName);
+                        break;
+                    default:
+                        key = fileName.startsWith(`../${toolName}/`) ? fileName : `../${toolName}/${fileName}`;
+                }
+
+                if (window.chrome && chrome.storage && chrome.storage.local) {
+                    chrome.storage.local.remove(key);
+                } else {
+                    localStorage.removeItem(key)
+                }
+            }
+        },
+
+        addNewTool(localOrUrl) {
+            this.showNewToolForm = true;
+            this.updateUrlMode = localOrUrl === 'url';
+
+            if (this.updateUrlMode) {
+                this.toast('请务必载入自己的Web工具服务!PS:请尽量别把它当成网站原内容的爬取工具,因为别人的网站你爬取过来也不一定完全能运行!');
+            }
+        },
+        newToolAction(event) {
+            let toolId = this.$refs.toolId.value;
+            let toolName = this.$refs.toolName.value;
+            let toolIcon = this.$refs.toolIcon.value;
+            let contentScript = this.$refs.hasContentScript.checked;
+            let noPage = this.$refs.noPage.checked;
+            let updateUrl = '';
+            if (this.updateUrlMode) {
+                updateUrl = this.$refs.updateUrl.value;
+                if (updateUrl.indexOf('baidufe.com') > -1 || updateUrl.indexOf('fehelper.com') > -1) {
+                    return this.toast('如果你是要安装FeHelper官网的工具,请到插件配置页直接安装!');
+                }
+            }
+
+            if (this.myTools[toolId]) {
+                this.toast(`ID为 ${toolId} 的工具在本地已存在,请重新命名!`);
+                event.preventDefault();
+                return false;
+            }
+
+            // 关闭Form表单
+            this.showNewToolForm = false;
+
+            // 创建文件列表
+            let files = ['fh-config.js'];
+            (contentScript || noPage) && files.push('content-script.js');
+
+            // 本地创建的模式,需要用模板来初始化
+            if (!this.updateUrlMode) {
+                files.push('index.html');
+                if (!noPage) {
+                    files.push('index.css');
+                    files.push('index.js');
+                }
+            }
+
+            // 初始化的文件内容,需要进行存储
+            files.forEach(file => {
+                let content = FileTpl[file].replace(/#toolName#/gm, toolId)
+                    .replace(/#toolFullName#/gm, toolName)
+                    .replace(/#toolIcon#/gm, toolIcon)
+                    .replace(/#updateUrl#/gm, updateUrl)
+                    .replace(/#contentScript#/gm, !!contentScript || !!noPage)
+                    .replace(/#noPage#/gm, !!noPage)
+                    .replace(/#toolNameLower#/gm, toolId.replace(/[\-_]/g, ''));
+                if (noPage && file === 'content-script.js') {
+                    content += '\n\n' + FileTpl['noPage.js'].replace(/#toolName#/gm, toolId)
+                        .replace(/#toolNameLower#/gm, toolId.replace(/[\-_]/g, ''));
+                }
+
+                this.saveContentToLocal(toolId, file, content);
+            });
+
+            if (this.updateUrlMode) {
+                // 远程下载并安装工具
+                this.loadRemoteTool(toolId, updateUrl, this.toast).then(progress => {
+                    this.toast(progress);
+                    this.toggleEditor(true, toolId);
+                    this.toast('工具创建成功!现在可以进行实时编辑了!');
+                });
+            } else {
+                this.toggleEditor(true, toolId);
+                this.toast('工具创建成功!现在可以进行实时编辑了!');
+            }
+
+            event.preventDefault();
+            return false;
+        },
+
+        loadRemoteTool(toolName, updateUrl, fnProgress) {
+            return new Promise((resolve, reject) => {
+                fnProgress && fnProgress('开始下载...');
+                fetch(updateUrl).then(resp => resp.text()).then(html => {
+                    let result = this.htmlTplEncode(toolName, html, updateUrl);
+                    html = result.html;
+                    let files = result.jsCss;
+
+                    // 获取所有网络文件的总个数,以便于计算进度
+                    let total = eval(Object.values(files).map(a => a.length).join('+')) + 1;
+                    let loaded = 1;
+                    fnProgress && fnProgress(Math.floor(100 * loaded / total) + '%');
+
+                    (async () => {
+
+                        let toolObj = this.myTools[toolName];
+
+                        for (let t in files) {
+                            for (let f = 0; f < files[t].length; f++) {
+                                let fs = files[t][f];
+
+                                // script-block内识别出来的代码,直接保存
+                                if (t === 'js' && fs[0].indexOf('fh-script-block.js') > -1) {
+                                    this.saveContentToLocal(toolName, fs[0], fs[2]);
+                                    continue;
+                                }
+
+                                await fetch(fs[2]).then(resp => resp.text()).then(txt => {
+                                    this.saveContentToLocal(toolName, fs[0], txt);
+
+                                    // 保存content-script / background-script
+                                    if (toolObj.contentScript && fs[0].indexOf(toolName + '/content-script.js') !== -1) {
+                                        this.saveContentToLocal(toolName, 'content-script.js', txt);
+
+                                        // 存储content-script.css文件内容
+                                        if (toolObj.contentScriptCss) {
+                                            fetch(fs[2].replace('content-script.js', 'content-script.css')).then(resp => resp.text()).then(css => {
+                                                this.saveContentToLocal(toolName, 'content-script.css', css);
+                                            });
+                                        }
+                                    }
+
+                                    fnProgress && fnProgress(Math.floor(100 * ++loaded / total) + '%');
+                                });
+                            }
+                        }
+
+                        // 全部下载完成!
+                        resolve && resolve('100%');
+                    })();
+
+                    this.saveContentToLocal(toolName, 'index.html', html);
+                }).catch(e => {
+                    this.delToolConfigs(toolName);
+                    fnProgress && fnProgress(`糟糕,下载出错,工具远程安装失败!${e.toString()}`);
+                });
+            });
+        },
+
+
+        loadDemo() {
+            let demoName = this.demo.name;
+            let files = this.demo.files;
+            let site = '../';
+            if (window.chrome && chrome.runtime && chrome.runtime.getManifest) {
+                site = chrome.runtime.getManifest().homepage_url;
+            }
+            let arrPromise = files.map(file => fetch(`${site}/${demoName}/${file}`).then(resp => resp.text()));
+            Promise.all(arrPromise).then(contents => {
+                // fh-config.js
+                let json = new Function(`return ${contents[0]}`)();
+                this.addToolConfigs(json);
+
+                // index.html
+                let result = this.htmlTplEncode(demoName, contents[1]);
+                this.saveContentToLocal(demoName, files[1], result.html, true);
+
+                // 其他文件
+                for (let i = 2; i < contents.length; i++) {
+                    this.saveContentToLocal(demoName, files[i], contents[i]);
+                }
+
+                this.toast('工具更新成功!');
+            });
+        },
+
+        downloadTool(tool) {
+            let toolName = tool || this.demo.name;
+
+            this.getToolFilesFromLocal(toolName).then(files => {
+                let arrPromise = files.map(file => this.getContentFromLocal(toolName, file));
+
+                Promise.all(arrPromise).then(contents => {
+                    let zipper = new JSZip();
+                    let zipPkg = zipper.folder(toolName);
+                    files.forEach((file, index) => zipPkg.file(file, contents[index]));
+
+                    zipper.generateAsync({type: "blob"})
+                        .then(function (content) {
+                            let elA = document.createElement('a');
+                            elA.style.cssText = 'position:absolute;top:-1000px;left:-10000px;';
+                            elA.setAttribute('download', `${toolName}.zip`);
+                            elA.href = URL.createObjectURL(new Blob([content], {type: 'application/octet-stream'}));
+                            document.body.appendChild(elA);
+                            elA.click();
+                        });
+                });
+            });
+        },
+
+        upgrade(tool, urlMode) {
+            if (tool === 'hello-world') {
+                this.loadDemo();
+            } else if (urlMode) {
+                // 远程下载并安装工具
+                this.loadRemoteTool(tool, this.myTools[tool].updateUrl, this.toast).then(progress => {
+                    this.toast(progress);
+                    this.toast('工具更新完成!');
+                });
+            } else {
+                this.loadTool(true, tool);
+            }
+        },
+
+        loadTool(upgradeMode, upgradeToolName) {
+            let Model = (function () {
+                zip.useWebWorkers = false;
+
+                return {
+                    getEntries: function (file, onend) {
+                        zip.createReader(new zip.BlobReader(file), function (zipReader) {
+                            zipReader.getEntries(onend);
+                        }, function (e) {
+                            console.log(e);
+                        });
+                    },
+
+                    getEntryFile: function (entry, onend, onprogress) {
+                        entry.getData(new zip.TextWriter(), function (text) {
+                            onend(text);
+                        }, onprogress);
+                    }
+                };
+            })();
+
+            let fileInput = document.createElement('input');
+            fileInput.type = 'file';
+            fileInput.accept = 'application/zip';
+            fileInput.style.cssText = 'position:absolute;top:-100px;left:-100px';
+            fileInput.addEventListener('change', (evt) => {
+                let toolName = fileInput.files[0].name.replace('.zip', '');
+                if (upgradeMode && upgradeToolName !== toolName) {
+                    return this.toast(`请确保上传${upgradeToolName}.zip进行更新!`);
+                }
+                Model.getEntries(fileInput.files[0], (entries) => {
+                    entries = entries.filter(entry => !entry.directory && /\.(html|js|css)$/.test(entry.filename));
+                    let reg = /(fh-config\.js|index\.html|content-script\.(js|css))$/;
+                    let entPart1 = entries.filter(en => reg.test(en.filename));
+                    let entPart2 = entries.filter(en => !reg.test(en.filename));
+
+                    entPart1.forEach((entry) => {
+                        Model.getEntryFile(entry, (fileContent) => {
+                            let fileName = entry.filename.split('/').pop();
+                            try {
+                                if (fileName === `fh-config.js`) {
+                                    let json = new Function(`return ${fileContent}`)();
+                                    this.addToolConfigs(json);
+                                } else if (fileName === 'index.html') {
+                                    let result = this.htmlTplEncode(toolName, fileContent);
+                                    this.saveContentToLocal(toolName, fileName, result.html, true);
+
+                                    // 所有被引用的静态文件都在这里进行遍历
+                                    entPart2.forEach(jcEntry => {
+                                        Model.getEntryFile(jcEntry, jcContent => {
+                                            Object.keys(result.jsCss).forEach(tp => {
+                                                result.jsCss[tp].some(file => {
+                                                    if (file[0].indexOf(jcEntry.filename) > -1) {
+                                                        this.saveContentToLocal(toolName, file[0].replace(`../${toolName}/`, ''), jcContent);
+                                                        return true;
+                                                    }
+                                                });
+                                            });
+                                        });
+                                    });
+                                } else if (['content-script.js', 'content-script.css'].includes(fileName)) {
+                                    this.saveContentToLocal(toolName, fileName, fileContent);
+                                }
+                            } catch (err) {
+                                this.toast(`${fileName} 文件发生错误:${err.message}`);
+                            }
+                        });
+                    });
+                    this.toast('工具更新成功!');
+                });
+            }, false);
+
+            document.body.appendChild(fileInput);
+            fileInput.click();
+            window.setTimeout(() => fileInput.remove(), 3000);
+        },
+
+        getToolConfigs() {
+            this.myTools = JSON.parse(localStorage.getItem(DEV_TOOLS_MY_TOOLS) || '{}', (key, val) => {
+                return String(val).indexOf('function') > -1 ? new Function(`return ${val}`)() : val;
+            });
+        },
+
+        setToolConfigs() {
+            localStorage.setItem(DEV_TOOLS_MY_TOOLS, JSON.stringify(this.myTools, (key, val) => {
+                return (typeof val === 'function') ? val.toString() : val;
+            }));
+        },
+
+        addToolConfigs(configs) {
+            this.getToolConfigs();
+            Object.keys(configs).forEach(key => {
+                let config = configs[key];
+                this.myTools[key] = {
+                    _devTool: true,
+                    _enable: this.myTools[key] && this.myTools[key]._enable,
+                    name: config.name,
+                    tips: config.tips,
+                    noPage: !!config.noPage,
+                    contentScript: !!config.contentScript,
+                    contentScriptCss: !!config.contentScriptCss,
+                    menuConfig: ([].concat(config.menuConfig || [])).map(menu => {
+                        return {
+                            icon: menu.icon,
+                            text: menu.text,
+                            onClick: menu.onClick
+                        }
+                    }),
+                    updateUrl: config.updateUrl || null
+                }
+            });
+            this.setToolConfigs();
+        },
+
+        delToolConfigs(tools) {
+            // 先删除文件
+            [].concat(tools).forEach(tool => {
+                this.getToolFilesFromLocal(tool).then(files => {
+                    files.forEach(file => {
+                        file = file.startsWith(`../${tool}`) ? file : `../${tool}/${file}`;
+                        if (chrome.storage && chrome.storage.local) {
+                            chrome.storage.local.remove(file);
+                        } else {
+                            localStorage.removeItem(file);
+                        }
+                    });
+                });
+
+                // 删模板等
+                let removeItems = [
+                    TOOL_NAME_TPL.replace('#TOOL-NAME#', tool),
+                    TOOL_CONTENT_SCRIPT_TPL.replace('#TOOL-NAME#', tool),
+                    TOOL_CONTENT_SCRIPT_CSS_TPL.replace('#TOOL-NAME#', tool)
+                ];
+
+                if (chrome.storage && chrome.storage.local) {
+                    chrome.storage.local.remove(removeItems);
+                } else {
+                    removeItems.forEach(item => localStorage.removeItem(item));
+                }
+            });
+
+            // 再删配置
+            [].concat(tools).forEach(tool => {
+                delete this.myTools[tool];
+            });
+
+            this.setToolConfigs();
+            this.$forceUpdate();
+
+            this.toast('工具删除成功!');
+        },
+
+        toggleToolEnableStatus(tool) {
+            this.myTools[tool]._enable = !this.myTools[tool]._enable;
+            this.setToolConfigs();
+            this.$forceUpdate();
+        },
+
+        getToolFilesFromLocal(toolName) {
+            return new Promise(resolve => {
+                let files = ['fh-config.js', 'index.html'];
+                let toolObj = this.myTools[toolName];
+                toolObj.contentScript && files.push('content-script.js');
+                toolObj.contentScriptCss && files.push('content-script.css');
+                if (window.chrome && chrome.storage && chrome.storage.local) {
+                    chrome.storage.local.get(null, allDatas => {
+                        let fs = Object.keys(allDatas).filter(key => String(key).startsWith(`../${toolName}/`));
+                        files = files.concat(fs);
+                        resolve(files.map(f => f.replace(`../${toolName}/`, '')));
+                    });
+                } else {
+                    let fs = [];
+                    for (let key in localStorage) {
+                        String(key).startsWith(`../${toolName}/`) && fs.push(key);
+                    }
+                    files = files.concat(fs);
+                    resolve(files.map(f => f.replace(`../${toolName}/`, '')));
+                }
+            });
+        },
+
+        saveContentToLocal(toolName, fileName, content, htmlDone) {
+
+            if (fileName === 'fh-config.js') {
+                try {
+                    let json = new Function(`return ${content}`)();
+                    this.addToolConfigs(json);
+                    this.$forceUpdate();
+                    return json[toolName];
+                } catch (e) {
+                    return null;
+                }
+            }
+
+            let key = '';
+            switch (fileName) {
+                case 'index.html':
+                    key = TOOL_NAME_TPL.replace('#TOOL-NAME#', toolName);
+                    if (!htmlDone) {
+                        let result = this.htmlTplEncode(toolName, content);
+                        content = result.html;
+                    }
+                    break;
+                case 'content-script.js':
+                    key = TOOL_CONTENT_SCRIPT_TPL.replace('#TOOL-NAME#', toolName);
+                    break;
+                case 'content-script.css':
+                    key = TOOL_CONTENT_SCRIPT_CSS_TPL.replace('#TOOL-NAME#', toolName);
+                    break;
+                default:
+                    key = fileName.startsWith(`../${toolName}/`) ? fileName : `../${toolName}/${fileName}`;
+            }
+
+            if (chrome.storage && chrome.storage.local) {
+                let obj = {};
+                obj[key] = content;
+                chrome.storage.local.set(obj);
+            } else {
+                localStorage.setItem(key, content);
+            }
+        },
+
+        getContentFromLocal(toolName, fileName) {
+
+            return new Promise((resolve, reject) => {
+                if (fileName === 'fh-config.js') {
+                    let counter = 0;
+                    let config = {};
+                    config[toolName] = this.myTools[toolName];
+                    ['_devTool', '_enable'].forEach(k => delete config[toolName][k]);
+
+                    let jsonText = JSON.stringify(config, (key, val) => {
+                        if (key === 'onClick' && typeof val === 'function') {
+                            return '#menuClickFunc#';
+                        }
+                        return val;
+                    }, 4).replace(/#menuClickFunc#/g, () => {
+                        return '/*__func_start__*/' + config[toolName].menuConfig[counter++].onClick.toString() + '/*__func_end__*/';
+                    }).replace(/"\/\*__func_start__\*\/function/g, 'function').replace(/\}\/\*__func_end__\*\/"/g, '}');
+
+                    resolve(jsonText);
+                } else {
+                    let key = '';
+                    switch (fileName) {
+                        case 'index.html':
+                            key = TOOL_NAME_TPL.replace('#TOOL-NAME#', toolName);
+                            break;
+                        case 'content-script.js':
+                            key = TOOL_CONTENT_SCRIPT_TPL.replace('#TOOL-NAME#', toolName);
+                            break;
+                        case 'content-script.css':
+                            key = TOOL_CONTENT_SCRIPT_CSS_TPL.replace('#TOOL-NAME#', toolName);
+                            break;
+                        default:
+                            key = fileName.startsWith(`../${toolName}/`) ? fileName : `../${toolName}/${fileName}`;
+                    }
+
+                    // 获取到的数据需要做二次加工
+                    let _update = (content) => {
+                        content = content || '';
+                        if (fileName === 'index.html') {
+                            content = this.htmlTplDecode(toolName, content);
+                        }
+                        // 如果noPage为true,但content-script.js中还没有window.xxxNoPage定义的话,就自动加一个
+                        else if (fileName === 'content-script.js' && this.myTools[toolName].noPage) {
+                            if (content.indexOf(`window.${toolName.replace(/[\-_]/g, '')}NoPage`) === -1) {
+                                content += '\n\n' + FileTpl['noPage.js'].replace(/#toolName#/gm, toolName)
+                                    .replace(/#toolNameLower#/gm, toolName.replace(/[\-_]/g, ''));
+                                this.saveContentToLocal(toolName, fileName, content);
+                            }
+                        }
+
+                        return content
+                    };
+                    if (chrome.storage && chrome.storage.local) {
+                        chrome.storage.local.get(key, respObj => resolve(_update(respObj[key])));
+                    } else {
+                        resolve(_update(localStorage.getItem(key)));
+                    }
+                }
+            });
+        },
+
+        htmlTplEncode(toolName, html, updateUrl) {
+            let jsReg = /<script[^>]*src=['"]([^'"]+)['"][^>]*>\s*?<\/[^>]*script>/igm;
+            let csReg = /<link\s+[^>]*[^>]*href=['"]([^'"]+)['"][^>]*[^>]*>/igm;
+            let scriptBlockReg = /<script(?:[^>]+|(?!src=))*>([^<]+|<(?!\/script>))+<\/script>/gim;
+            let files = {};
+
+            [csReg, jsReg].forEach(reg => {
+                html = html.replace(reg, (tag, src) => {
+
+                    let tagName = /<script/.test(tag) ? 'js' : 'css';
+                    if (tagName === 'css' && !/stylesheet/i.test(tag)) {
+                        return tag;
+                    }
+
+                    // 这里必须保留带有md5戳的原地址,要不然会有cdn缓存,更新会失败
+                    let originSrc = src;
+
+                    // 这个src是携带了Query的,用于直接存储到html中
+                    let withQuerySrc = src;
+
+                    // src 去query处理,用于Storage存储,避免过多冗余key出现
+                    if (src.indexOf('?') !== -1) {
+                        let x = src.split('?');
+                        x.pop();
+                        src = x.join('');
+                    }
+
+                    if (!/^\./.test(src)) {
+                        src = `../${toolName}/${src}`;
+                        withQuerySrc = `../${toolName}/${withQuerySrc}`;
+                    }
+
+                    // 存储静态文件的内容
+                    let filePath = originSrc;
+                    if (!/^(http(s)?:)?\/\//.test(originSrc) && updateUrl) {
+                        filePath = new URL(originSrc, updateUrl).href;
+                    }
+
+                    files[tagName] = files[tagName] || [];
+                    files[tagName].push([src, withQuerySrc, filePath]);
+
+                    return '';
+                });
+            });
+
+            // 识别所有无src属性的script标签
+            let blockCodes = [];
+            html = html.replace(scriptBlockReg, (tag, codes) => {
+                codes = codes.trim();
+                codes.length && blockCodes.push(codes);
+                return '';
+            });
+            if (blockCodes.length) {
+                let blockName = `../${toolName}/fh-script-block.js`;
+                files.js = files.js || [];
+                files.js.push([blockName, blockName, blockCodes.join(';\n\n')]);
+            }
+
+            // 如果是updateURL模式,需要替换所有相对链接为绝对链接,包括:img、a
+            if (updateUrl) {
+                let relativePathRegexp = /^(http:\/\/|https:\/\/|\/\/)[^\s'"]+/igm;
+                let imgReg = /<img[^>]*src=['"]([^'"]+)['"][^>]*>(\s*?<\/[^>]*img>)?/igm;
+                let aReg = /<a[^>]*href=['"]([^'"]+)['"][^>]*>(\s*?<\/[^>]*a>)?/igm;
+
+                html = html.replace(imgReg, (tag, link) => {
+                    if (!relativePathRegexp.test(link)) {
+                        tag = tag.replace(/src=['"]([^'"]+)['"]/igm, () => ` src="${new URL(link, updateUrl).href}"`);
+                    }
+                    return tag;
+                }).replace(aReg, (tag, link) => {
+                    if (!relativePathRegexp.test(link)) {
+                        tag = tag.replace(/href=['"]([^'"]+)['"]/igm, () => ` href="${new URL(link, updateUrl).href}"`);
+                    }
+                    return tag;
+                });
+            }
+
+            html = html.replace('static/img/favicon.ico', 'static/img/fe-16.png')  // 替换favicon
+                .replace(/<\/body>/, () => { // 将静态文件添加到页面最底部
+                    return Object.keys(files).map(t => {
+                        return `<dynamic data-type="${t}" data-source="${files[t].map(f => f[1]).join(',')}"></dynamic>`;
+                    }).join('') + '</body>';
+                });
+            return {
+                html: html,
+                jsCss: files
+            };
+        },
+
+        htmlTplDecode(toolName, html) {
+            let reg = /<dynamic\s+data\-type="(js|css)"\s+data\-source=['"]([^'"]+)['"][^>]*>\s*?<\/[^>]*dynamic>/igm;
+            return html.replace(reg, (frag, tag, list) => {
+                list = list.split(',');
+                if (tag === 'js') {
+                    return list.map(src => `<script src="${src.replace(`../${toolName}/`, '')}"></script>`).join('');
+                } else {
+                    return list.map(href => `<link rel="stylesheet" type="text/css" href="${href.replace(`../${toolName}/`, '')}" />`).join('');
+                }
+            });
+        },
+
+        givenIcons(forceClose) {
+            if (!this.givenIconList.length) {
+                this.givenIconList = FileTpl['given-icons'].replace(/\s/gm, '').split('');
+            }
+
+            if (forceClose) {
+                this.showGivenIcons = false;
+            } else {
+                this.showGivenIcons = !this.showGivenIcons;
+            }
+            this.$forceUpdate();
+        },
+        selectIcon(icon) {
+            if (this.showNewToolForm) {
+                this.$refs.toolIcon.value = icon;
+                this.givenIcons(true);
+            } else {
+                this.copyToClipboard(icon);
+                this.toast(`图标 ${icon} 复制成功,随处粘贴可用!`);
+            }
+        },
+
+        toast(content) {
+            window.clearTimeout(window.feHelperAlertMsgTid);
+            let elAlertMsg = document.querySelector("#fehelper_alertmsg");
+            if (!elAlertMsg) {
+                let elWrapper = document.createElement('div');
+                elWrapper.innerHTML = '<div id="fehelper_alertmsg">' + content + '</div>';
+                elAlertMsg = elWrapper.childNodes[0];
+                document.body.appendChild(elAlertMsg);
+            } else {
+                elAlertMsg.innerHTML = content;
+                elAlertMsg.style.display = 'block';
+            }
+
+            window.feHelperAlertMsgTid = window.setTimeout(function () {
+                elAlertMsg.style.display = 'none';
+            }, 3000);
+        },
+        copyToClipboard(text) {
+            let input = document.createElement('textarea');
+            input.style.position = 'fixed';
+            input.style.opacity = 0;
+            input.value = text;
+            document.body.appendChild(input);
+            input.select();
+            document.execCommand('Copy');
+            document.body.removeChild(input);
+        },
+        fhDeveloperDoc() {
+            window.open(`https://github.com/zxlie/FeHelper/blob/master/README_NEW.md#%E5%85%ADopen-api`);
+        }
+    }
+});

+ 791 - 0
apps/dynamic/awesome.js

@@ -0,0 +1,791 @@
+/**
+ * 工具更新
+ * @type {{download}}
+ */
+let Awesome = (() => {
+
+    let manifest = chrome.runtime.getManifest();
+
+    const SERVER_SITE = manifest.homepage_url;
+    const URL_TOOL_TPL = `${SERVER_SITE}/#TOOL-NAME#/index.html`;
+    const TOOL_NAME_TPL = 'DYNAMIC_TOOL:#TOOL-NAME#';
+    const TOOL_CONTENT_SCRIPT_TPL = 'DYNAMIC_TOOL:CS:#TOOL-NAME#';
+    const TOOL_CONTENT_SCRIPT_CSS_TPL = 'DYNAMIC_TOOL:CS:CSS:#TOOL-NAME#';
+    const TOOL_MENU_TPL = 'DYNAMIC_MENU:#TOOL-NAME#';
+
+    /**
+     * 管理本地存储
+     */
+    let StorageMgr = (() => {
+
+        let get = keyArr => {
+            return new Promise((resolve, reject) => {
+                chrome.storage.local.get(keyArr, result => {
+                    resolve(typeof keyArr === 'string' ? result[keyArr] : result);
+                });
+            });
+        };
+
+
+        let getSync = async (keyArr) => {
+            return await (new Promise((resolve, reject) => {
+                chrome.storage.local.get(keyArr, result => {
+                    resolve(typeof keyArr === 'string' ? result[keyArr] : result);
+                });
+            }));
+        };
+
+        let set = (items, values) => {
+            return new Promise((resolve, reject) => {
+                if (typeof items === 'string') {
+                    let tmp = {};
+                    tmp[items] = values;
+                    items = tmp;
+                }
+                chrome.storage.local.set(items, () => {
+                    resolve();
+                });
+            });
+        };
+
+        let remove = keyArr => {
+            return new Promise((resolve, reject) => {
+                keyArr = [].concat(keyArr);
+                chrome.storage.local.remove(keyArr, () => {
+                    resolve();
+                });
+            });
+        };
+
+        return {get, set, remove,getSync};
+    })();
+
+    /**
+     * 检测工具是否已被成功安装
+     * @param toolName 工具名称
+     * @param detectMenu 是否进一步检测Menu的设置情况
+     * @param detectContent 是否检测内容实际存在
+     * @returns {Promise}
+     */
+    let detectInstall = (toolName, detectMenu, detectContent) => {
+
+        let menuKey = TOOL_MENU_TPL.replace('#TOOL-NAME#', toolName);
+        let toolKey = TOOL_NAME_TPL.replace('#TOOL-NAME#', toolName);
+
+        if (toolName === 'json-format' && !detectContent) {
+            if (detectMenu) {
+                return StorageMgr.get(menuKey).then(value => String(value) !== '0');
+            } else {
+                return Promise.resolve(true);
+            }
+        }
+
+        return Promise.all([StorageMgr.get(toolKey), StorageMgr.get(menuKey)]).then(values => {
+            if (detectMenu) {
+                return values[0] && String(values[1]) === '1';
+            }
+            return detectContent ? values[0] : !!values[0];
+        });
+    };
+
+    let log = (txt) => {
+        // console.log(String(new Date(new Date() * 1 - (new Date().getTimezoneOffset()) * 60 * 1000).toJSON()).replace(/T/i, ' ').replace(/Z/i, '') + '>', txt);
+    };
+
+    /**
+     * 从服务器下载得到的HTML需要进行加工,得到干净并且适用于插件的HTML,同时剥离出静态文件列表
+     * @param toolName
+     * @param html
+     * @returns {{html: *, jsCss: {}}}
+     * @private
+     */
+    let _tplHandler = (toolName, html) => {
+        let jsReg = /<script[^>]*src=['"]([^'"]+)['"][^>]*>\s*?<\/[^>]*script>/igm;
+        let csReg = /<link\s+[^>]*stylesheet[^>]*href=['"]([^'"]+)['"][^>]*>/igm;
+        let files = {};
+
+        [csReg, jsReg].forEach(reg => {
+            html = html.replace(reg, (tag, src) => {
+
+                // 这里必须保留带有md5戳的原地址,要不然会有cdn缓存,更新会失败
+                let originSrc = src;
+
+                // 这个src是携带了Query的,用于直接存储到html中
+                let withQuerySrc = src;
+
+                // src 去query处理,用于Storage存储,避免过多冗余key出现
+                if (src.indexOf('?') !== -1) {
+                    let x = src.split('?');
+                    x.pop();
+                    src = x.join('');
+                }
+
+                if (!/^\./.test(src)) {
+                    src = `../${toolName}/${src}`;
+                    withQuerySrc = `../${toolName}/${withQuerySrc}`;
+                }
+
+                if (src !== '../static/js/navbar.js') {
+                    // 存储静态文件的内容
+                    let filePath = `${SERVER_SITE}/${toolName}/${originSrc}`;
+                    let tagName = /<script/.test(tag) ? 'js' : 'css';
+
+                    files[tagName] = files[tagName] || [];
+                    files[tagName].push([src, withQuerySrc, filePath]);
+                }
+
+                return '';
+
+            });
+        });
+
+        html = html.replace('static/img/favicon.ico', 'static/img/fe-16.png')  // 替换favicon
+            .replace(/<body/gm, '<body browser-extension') // 给body添加属性
+            .replace(/<div\s+class="mod-pageheader">.*<\/div>\s*<div\sclass="panel\spanel-default"/gm, '<div class="panel panel-default"') // 去掉mod-pageheader
+            .replace(/<div\sid="pageFooter"\s+[^>]+>\s*<script\s.*<\/script>\s*<\/div>/gm, '') // 去除底部footer
+            .replace(/<\/body>/, () => { // 将静态文件添加到页面最底部
+                return Object.keys(files).map(t => {
+                    return `<dynamic data-type="${t}" data-source="${files[t].map(f => f[1]).join(',')}"></dynamic>`;
+                }).join('') + '</body>';
+            });
+        return {
+            html: html,
+            jsCss: files
+        };
+    };
+
+    /**
+     * 安装/更新工具,支持显示安装进度
+     * @param toolName
+     * @param fnProgress
+     * @returns {Promise<any>}
+     */
+    let install = (toolName, fnProgress) => {
+
+        log(toolName + '工具开始安装/更新...');
+
+        return new Promise((resolve, reject) => {
+            fetch(URL_TOOL_TPL.replace('#TOOL-NAME#', toolName)).then(resp => {
+                if (resp.ok) {
+                    fnProgress && fnProgress('1%');
+                    return resp.text();
+                } else {
+                    reject && reject({
+                        status: resp.status,
+                        statusText: resp.statusText
+                    });
+                }
+            }).then(html => {
+
+                // 请求回来的数据需要做一些加工
+                let result = _tplHandler(toolName, html);
+                html = result.html;
+                let files = result.jsCss;
+
+                // 获取所有网络文件的总个数,以便于计算进度
+                let total = window.evalCore.getEvalInstance(window)(Object.values(files).map(a => a.length).join('+')) + 1;
+                let loaded = 1;
+                fnProgress && fnProgress(Math.floor(100 * loaded / total) + '%');
+
+                (async () => {
+
+                    let allTools = getAllTools(true);
+
+                    for (let t in files) {
+                        for (let f = 0; f < files[t].length; f++) {
+                            let fs = files[t][f];
+                            await fetch(fs[2]).then(resp => resp.text()).then(txt => {
+                                StorageMgr.set(fs[0], txt);
+
+                                // 保存content-script / background-script
+                                if (allTools[toolName].contentScript && fs[0].indexOf(toolName + '/content-script.js') !== -1) {
+                                    StorageMgr.set(TOOL_CONTENT_SCRIPT_TPL.replace('#TOOL-NAME#', toolName), txt);
+
+                                    // 存储content-script.css文件内容
+                                    if (allTools[toolName].contentScriptCss) {
+                                        fetch(fs[2].replace('content-script.js', 'content-script.css')).then(resp => resp.text()).then(css => {
+                                            StorageMgr.set(TOOL_CONTENT_SCRIPT_CSS_TPL.replace('#TOOL-NAME#', toolName), css);
+                                        });
+                                    }
+                                }
+
+                                log(`${toolName}工具静态文件${fs[0]}安装/更新成功!`);
+                                fnProgress && fnProgress(Math.floor(100 * ++loaded / total) + '%');
+                            });
+                        }
+                    }
+
+                    log(toolName + '工具安装/更新成功!');
+
+                    // 全部下载完成!
+                    resolve && resolve('100%');
+                })();
+
+                // 存储html文件
+                StorageMgr.set(TOOL_NAME_TPL.replace('#TOOL-NAME#', toolName), html || '&nbsp;');
+                log(toolName + '工具html模板安装/更新成功!');
+            }).catch(error => reject && reject(error));
+        });
+    };
+
+    let offLoad = (toolName) => {
+        let items = [];
+        items.push(TOOL_NAME_TPL.replace('#TOOL-NAME#', toolName));
+        items.push(TOOL_CONTENT_SCRIPT_TPL.replace('#TOOL-NAME#', toolName));
+        items.push(TOOL_CONTENT_SCRIPT_CSS_TPL.replace('#TOOL-NAME#', toolName));
+
+        // 删除所有静态文件
+        chrome.storage.local.get(null, allDatas => {
+            StorageMgr.remove(Object.keys(allDatas).filter(key => String(key).startsWith(`../${toolName}/`)));
+        });
+
+        log(toolName + ' 卸载成功!');
+
+        return StorageMgr.remove(items);
+    };
+
+    /**
+     * 有些工具其实已经卸载过了,但是本地还有冗余的静态文件,都需要统一清理一遍
+     */
+    let gcLocalFiles = () => getAllTools().then(tools => Object.keys(tools).forEach(tool => {
+        if (!tools[tool]._devTool && !tools[tool].installed) {
+            offLoad(tool);
+        }
+    }));
+
+    /**
+     * 检查看本地已安装过哪些工具
+     * @returns {Promise}
+     */
+    let getInstalledTools = () => getAllTools().then(tools => {
+        let istTolls = {};
+        Object.keys(tools).filter(tool => {
+            if (tools[tool].installed) {
+                istTolls[tool] = tools[tool];
+            }
+        });
+        return istTolls;
+    });
+
+    /**
+     * 获取工具的content-script
+     * @param toolName
+     * @param cssMode
+     */
+    let getContentScript = (toolName, cssMode) => {
+        return StorageMgr.get(cssMode ? TOOL_CONTENT_SCRIPT_CSS_TPL.replace('#TOOL-NAME#', toolName)
+            : TOOL_CONTENT_SCRIPT_TPL.replace('#TOOL-NAME#', toolName));
+    };
+
+    /**
+     * 获取工具的html模板
+     * @param toolName
+     * @returns {*}
+     */
+    let getToolTpl = (toolName) => StorageMgr.get(TOOL_NAME_TPL.replace('#TOOL-NAME#', toolName));
+
+    /**
+     * 从服务器检查,看本地已安装的工具,有哪些又已经升级过了
+     * @param tool
+     */
+    let checkUpgrade = (tool) => {
+        let getOnline = (toolName) => fetch(URL_TOOL_TPL.replace('#TOOL-NAME#', toolName)).then(resp => resp.text());
+        let getOffline = (toolName) => StorageMgr.get(TOOL_NAME_TPL.replace('#TOOL-NAME#', toolName));
+        return Promise.all([getOnline(tool), getOffline(tool)]).then(values => {
+            let onlineData = _tplHandler(tool, values[0]);
+            let local = values[1];
+            return local !== onlineData.html;
+        });
+    };
+
+    /**
+     * 管理右键菜单
+     * @param toolName
+     * @param action 具体动作install/offload/get
+     * @returns {Promise<any>}
+     */
+    let menuMgr = (toolName, action) => {
+        let menuKey = TOOL_MENU_TPL.replace('#TOOL-NAME#', toolName);
+        switch (action) {
+            case 'get':
+                return StorageMgr.get(menuKey);
+            case 'offload':
+                // 必须用setItem模式,而不是removeItem,要处理 0/1/null三种结果
+                log(toolName + ' 卸载成功!');
+                return StorageMgr.set(menuKey, 0);
+            case 'install':
+                log(toolName + ' 安装成功!');
+                return StorageMgr.set(menuKey, 1);
+        }
+    };
+
+    /**
+     * 远程获取的代码管理器
+     * @type {{get, set}}
+     */
+    let CodeCacheMgr = (() => {
+        const TOOLS_FROM_REMOTE = 'TOOLS_FROM_REMOTE';
+
+        let get = () => {
+            return StorageMgr.getSync(TOOLS_FROM_REMOTE);
+        };
+
+        let set = (remoteCodes) => {
+            let obj = {};
+            obj[TOOLS_FROM_REMOTE]=remoteCodes;
+            chrome.storage.local.set(obj);
+        };
+
+        return {get, set};
+    })();
+
+    /**
+     * 工具排序管理器
+     * @type {{get, set}}
+     */
+    let SortToolMgr = (() => {
+        const TOOLS_CUSTOM_SORT = 'TOOLS_CUSTOM_SORT';
+
+        let get = async () => {
+            let cache = await StorageMgr.getSync(TOOLS_CUSTOM_SORT);
+
+            return [].concat(JSON.parse(cache || '[]')).filter(t => !!t);
+        };
+
+        let set = (newSortArray) => {
+            let obj = {};
+            obj[TOOLS_CUSTOM_SORT] = JSON.stringify([].concat(newSortArray || []).filter(t => !!t));
+            chrome.storage.local.set(obj);
+        };
+
+        return {get, set};
+    })();
+
+    let getAllTools = (declareOnly) => {
+        let toolMap = {
+            'json-format': {
+                name: 'JSON美化工具',
+                tips: '页面自动检测并格式化、手动格式化、乱码解码、排序、BigInt、编辑、下载、皮肤定制等',
+                contentScript: true,
+                contentScriptCss: true,
+                offloadForbid: true,
+                menuConfig: [{
+                    icon: '⒥',
+                    text: 'JSON格式化',
+                    contexts: ['page', 'selection', 'editable']
+                }]
+            },
+            'json-diff': {
+                name: 'JSON比对工具',
+                tips: '支持两个JSON内容的自动键值比较,并高亮显示差异点,同时也能判断JSON是否合法',
+                menuConfig: [{
+                    icon: '☷',
+                    text: 'JSON比对器'
+                }]
+            },
+            'qr-code': {
+                name: '二维码/解码',
+                tips: '支持自定义颜色和icon的二维码生成器,并且支持多种模式的二维码解码,包括截图后粘贴解码',
+                contentScript: true,
+                menuConfig: [{
+                    icon: '▣',
+                    text: '二维码生成器',
+                    contexts: ['page', 'selection', 'editable', 'link', 'image']
+                }, {
+                    icon: '◈',
+                    text: '二维码解码器',
+                    contexts: ['image']
+                }]
+            },
+            'image-base64': {
+                name: '图片转Base64',
+                tips: '支持多种模式的图片转Base64格式,比如链接粘贴/截图粘贴等,也支持Base64数据逆转图片',
+                menuConfig: [{
+                    icon: '▤',
+                    text: '图片与base64',
+                    contexts: ['image']
+                }]
+            },
+            'sticky-notes': {
+                name: '我的便签笔记',
+                tips: '方便快捷的浏览器便签笔记工具,支持创建目录对笔记进行分类管理,笔记支持一键导出/导入',
+                menuConfig: [{
+                    icon: '✐',
+                    text: '我的便签笔记'
+                }]
+            },
+            'en-decode': {
+                name: '信息编码转换',
+                tips: '支持多格式的信息编解码,如Unicode、UTF-8、UTF-16、URL、Base64、MD5、Hex、Gzip等',
+                menuConfig: [{
+                    icon: '♨',
+                    text: '字符串编解码',
+                    contexts: ['page', 'selection', 'editable']
+                }]
+            },
+            'code-beautify': {
+                name: '代码美化工具',
+                tips: '支持多语言的代码美化,包括 Javascript、CSS、HTML、XML、SQL,且会陆续支持更多格式',
+                menuConfig: [{
+                    icon: '✡',
+                    text: '代码美化工具',
+                    contexts: ['page', 'selection', 'editable']
+                }]
+            },
+            'timestamp': {
+                name: '时间(戳)转换',
+                tips: '本地化时间与时间戳之间的相互转换,支持秒/毫秒、支持世界时区切换、各时区时钟展示等',
+                menuConfig: [{
+                    icon: '♖',
+                    text: '时间(戳)转换'
+                }]
+            },
+            'password': {
+                name: '随机密码生成',
+                tips: '将各种字符进行随机组合生成密码,可以由数字、大小写字母、特殊符号组成,支持指定长度',
+                menuConfig: [{
+                    icon: '♆',
+                    text: '随机密码生成'
+                }]
+            },
+            'html2markdown': {
+                name: 'Markdown转换',
+                tips: 'Markdown编写/预览工具,支持HTML片段直接转Markdown,支持将内容以PDF格式进行下载',
+                menuConfig: [{
+                    icon: 'ⓜ',
+                    text: 'markown工具'
+                }]
+            },
+            'postman': {
+                name: '简易版Postman',
+                tips: '开发过程中的接口调试工具,支持GET/POST/HEAD请求方式,且支持JSON内容自动格式化',
+                menuConfig: [{
+                    icon: '☯',
+                    text: '简易Postman'
+                }]
+            },
+            'regexp': {
+                name: 'JS正则表达式',
+                tips: '正则校验工具,默认提供一些工作中常用的正则表达式,支持内容实时匹配并高亮显示结果',
+                menuConfig: [{
+                    icon: '✙',
+                    text: 'JS正则表达式'
+                }]
+            },
+            'trans-radix': {
+                name: '进制转换工具',
+                tips: '支持2进制到36进制数据之间的任意转换,比如:10进制转2进制,8进制转16进制,等等',
+                menuConfig: [{
+                    icon: '❖',
+                    text: '进制转换工具'
+                }]
+            },
+            'trans-color': {
+                name: '颜色转换工具',
+                tips: '支持HEX颜色到RGB格式的互转,比如HEX颜色「#43ad7f」转RGB后为「rgb(67, 173, 127)」',
+                menuConfig: [{
+                    icon: '▶',
+                    text: '颜色转换工具'
+                }]
+            },
+            'crontab': {
+                name: 'Crontab工具',
+                tips: '一个简易的Crontab生成工具,支持随机生成Demo,编辑过程中,分时日月周会高亮提示',
+                menuConfig: [{
+                    icon: '½',
+                    text: 'Crontab生成'
+                }]
+            },
+            'loan-rate': {
+                name: '贷(还)款利率',
+                tips: '贷款或还款利率的计算器,按月呈现还款计划;并支持按还款额反推贷款实际利率',
+                menuConfig: [{
+                    icon: '$',
+                    text: '贷(还)款利率'
+                }]
+            },
+            'devtools': {
+                name: 'FH开发者工具',
+                tips: '以开发平台的思想,FeHelper支持用户进行本地开发,将自己的插件功能集成进FH工具市场',
+                menuConfig: [{
+                    icon: '㉿',
+                    text: 'FH开发者工具'
+                }]
+            }
+        };
+        Object.keys(toolMap).forEach(tool => {
+            toolMap[tool].installed = toolMap[tool].offloadForbid;
+            toolMap[tool].menu = toolMap[tool].offloadForbid;
+            toolMap[tool].upgrade = false;
+
+            // context-menu
+            switch (tool) {
+                case 'json-format':
+                    toolMap[tool].menuConfig[0].onClick = function (info, tab) {
+                        chrome.scripting.executeScript({
+                            target: {tabId:tab.id,allFrames:false},
+                            func: () => info.selectionText
+                        }, txt => chrome.DynamicToolRunner({
+                            tool: 'json-format',
+                            withContent: txt[0]
+                        }));
+
+                        // chrome.tabs.executeScript(tab.id, {
+                        //     code: '(' + (function (pInfo) {
+                        //         return pInfo.selectionText;
+                        //     }).toString() + ')(' + JSON.stringify(info) + ')',
+                        //     allFrames: false
+                        // }, function (txt) {
+                        //     chrome.DynamicToolRunner({
+                        //         tool: 'json-format',
+                        //         withContent: txt[0]
+                        //     });
+                        // });
+                    };
+                    break;
+
+                case 'code-beautify':
+                case 'en-decode':
+                    toolMap[tool].menuConfig[0].onClick = function (info, tab) {
+                        chrome.tabs.executeScript(tab.id, {
+                            code: '(' + (function (pInfo) {
+                                let linkUrl = pInfo.linkUrl;
+                                let pageUrl = pInfo.pageUrl;
+                                let imgUrl = pInfo.srcUrl;
+                                let selection = pInfo.selectionText;
+
+                                return linkUrl || imgUrl || selection || pageUrl;
+                            }).toString() + ')(' + JSON.stringify(info) + ')',
+                            allFrames: false
+                        }, function (txt) {
+                            chrome.DynamicToolRunner({
+                                withContent: txt[0],
+                                query: `tool=${tool}`
+                            });
+                        });
+                    };
+                    break;
+
+
+                case 'qr-code':
+                    toolMap[tool].menuConfig[0].onClick = function (info, tab) {
+                        chrome.tabs.executeScript(tab.id, {
+                            code: '(' + (function (pInfo) {
+                                let linkUrl = pInfo.linkUrl;
+                                let pageUrl = pInfo.pageUrl;
+                                let imgUrl = pInfo.srcUrl;
+                                let selection = pInfo.selectionText;
+
+                                return linkUrl || imgUrl || selection || pageUrl;
+                            }).toString() + ')(' + JSON.stringify(info) + ')',
+                            allFrames: false
+                        }, function (txt) {
+                            chrome.DynamicToolRunner({
+                                withContent: txt[0],
+                                query: `tool=qr-code`
+                            });
+                        });
+                    };
+                    toolMap[tool].menuConfig[1].onClick = function (info, tab) {
+
+                        // V2020.2.618之前的版本都用这个方法
+                        let funForLowerVer = function () {
+                            chrome.tabs.executeScript(tab.id, {
+                                code: '(' + (function (pInfo) {
+                                    function loadImage(src) {
+                                        return new Promise(resolve => {
+                                            let image = new Image();
+                                            image.setAttribute('crossOrigin', 'Anonymous');
+                                            image.src = src;
+                                            image.onload = function () {
+                                                let width = this.naturalWidth;
+                                                let height = this.naturalHeight;
+                                                let canvas = document.createElement('canvas');
+                                                canvas.style.cssText = 'position:absolute;top:-10000px;left:-10000px';
+                                                document.body.appendChild(canvas);
+                                                canvas.setAttribute('id', 'qr-canvas');
+                                                canvas.height = height + 100;
+                                                canvas.width = width + 100;
+                                                let context = canvas.getContext('2d');
+                                                context.fillStyle = 'rgb(255,255,255)';
+                                                context.fillRect(0, 0, canvas.width, canvas.height);
+                                                context.drawImage(image, 0, 0, width, height, 50, 50, width, height);
+                                                resolve(canvas.toDataURL());
+                                            };
+                                            image.onerror = function () {
+                                                resolve(src);
+                                            };
+                                        });
+                                    }
+
+                                    let tempDataUrl = '__TEMP_DATA_URL_FOR_QRDECODE_';
+                                    loadImage(pInfo.srcUrl).then(dataUrl => {
+                                        window[tempDataUrl] = dataUrl;
+                                    });
+
+                                    return tempDataUrl;
+
+                                }).toString() + ')(' + JSON.stringify(info) + ')',
+                                allFrames: false
+                            }, function (resp) {
+                                let tempDataUrl = resp[0];
+                                let intervalId = -1;
+                                let repeatTime = 0;
+                                let loop = function () {
+                                    repeatTime++;
+                                    intervalId = setInterval(function () {
+                                        chrome.tabs.executeScript(tab.id, {
+                                            code: '(' + (function (tempDataUrl) {
+                                                return window[tempDataUrl];
+                                            }).toString() + ')(' + JSON.stringify(tempDataUrl) + ')',
+                                            allFrames: false
+                                        }, function (arr) {
+                                            if (arr[0] === null && repeatTime <= 10) {
+                                                loop();
+                                            } else {
+                                                clearInterval(intervalId);
+                                                chrome.DynamicToolRunner({
+                                                    withContent: arr[0] || info.srcUrl,
+                                                    query: `tool=qr-code&mode=decode`
+                                                });
+                                            }
+                                        });
+                                    }, 200);
+                                };
+                                loop();
+                            });
+                        };
+
+                        chrome.tabs.executeScript(tab.id, {
+                            code: '(' + (function (pInfo) {
+                                try {
+                                    if (typeof window.qrcodeContentScript === 'function') {
+                                        let qrcode = window.qrcodeContentScript();
+                                        if (typeof qrcode.decode === 'function') {
+                                            // 直接解码
+                                            qrcode.decode(pInfo.srcUrl);
+                                            return 1;
+                                        }
+                                    }
+                                } catch (e) {
+                                    return 0;
+                                }
+                                return 0;
+                            }).toString() + ')(' + JSON.stringify(info) + ')',
+                            allFrames: false
+                        }, function (resp) {
+                            (resp[0] === 0) && funForLowerVer();
+                        });
+
+                    };
+                    break;
+
+                default:
+                    toolMap[tool].menuConfig[0].onClick = function (info, tab) {
+                        chrome.DynamicToolRunner({
+                            withContent: tool === 'image-base64' ? info.srcUrl : '',
+                            query: `tool=${tool}`
+                        });
+                    };
+                    break;
+            }
+        });
+
+        // Merge本地存储的工具(远程获取而来的)
+        try {
+            let jsText = CodeCacheMgr.get();
+            jsText && window.evalCore.getEvalInstance(window)(jsText);
+            Object.keys(RemoteAwesome.newTools).forEach(tool => {
+                if (!toolMap[tool]) {
+                    toolMap[tool] = RemoteAwesome.newTools[tool];
+                } else {
+                    toolMap[tool].name = RemoteAwesome.newTools[tool].name || toolMap[tool].name;
+                    toolMap[tool].tips = RemoteAwesome.newTools[tool].tips || toolMap[tool].tips;
+                    toolMap[tool].menuConfig = RemoteAwesome.newTools[tool].menuConfig || toolMap[tool].menuConfig;
+                    if (RemoteAwesome.newTools[tool].contentScript !== undefined) {
+                        toolMap[tool].contentScript = RemoteAwesome.newTools[tool].contentScript;
+                    }
+                    if (RemoteAwesome.newTools[tool].contentScriptCss !== undefined) {
+                        toolMap[tool].contentScriptCss = RemoteAwesome.newTools[tool].contentScriptCss;
+                    }
+                    if (RemoteAwesome.newTools[tool].offloadForbid !== undefined) {
+                        toolMap[tool].offloadForbid = RemoteAwesome.newTools[tool].offloadForbid;
+                    }
+                }
+            });
+
+            // 下面是需要下架掉的旧工具
+            RemoteAwesome.removedTools.forEach(tool => {
+                delete toolMap[tool];
+            });
+        } catch (e) {
+        }
+
+        // 获取本地开发的插件,也拼接进来
+        // TODO ..
+        // try {
+        //     const DEV_TOOLS_MY_TOOLS = 'DEV-TOOLS:MY-TOOLS';
+        //     let _tools = await StorageMgr.getSync(DEV_TOOLS_MY_TOOLS);
+        //     let localDevTools = JSON.parse(_tools || '{}', (key, val) => {
+        //         return String(val).indexOf('function') > -1 ? new Function(`return ${val}`)() : val;
+        //     });
+        //     Object.keys(localDevTools).forEach(tool => {
+        //         toolMap[tool] = localDevTools[tool];
+        //     });
+        // } catch (e) {
+        // }
+
+        if (declareOnly) {
+            return toolMap;
+        } else {
+            let tools = Object.keys(toolMap);
+            let promises = [];
+            tools.forEach(tool => {
+                promises = promises.concat([detectInstall(tool), detectInstall(tool, true)])
+            });
+            return Promise.all(promises).then(values => {
+                values.forEach((v, i) => {
+                    let tool = tools[Math.floor(i / 2)];
+                    let key = i % 2 === 0 ? 'installed' : 'menu';
+                    toolMap[tool][key] = v;
+                    // 本地工具,还需要看是否处于开启状态
+                    if (toolMap[tool].hasOwnProperty('_devTool')) {
+                        toolMap[tool][key] = toolMap[tool][key] && toolMap[tool]._enable;
+                    }
+                });
+                let sortArr = SortToolMgr.get();
+                if (sortArr && sortArr.length) {
+                    let map = {};
+                    sortArr.forEach(tool => {
+                        map[tool] = toolMap[tool];
+                    });
+                    Object.keys(toolMap).forEach(tool => {
+                        if (!map[tool]) {
+                            map[tool] = toolMap[tool];
+                        }
+                    });
+                    return map;
+                }
+                return toolMap;
+            });
+        }
+    };
+
+    return {
+        StorageMgr,
+        detectInstall,
+        install,
+        offLoad,
+        getInstalledTools,
+        menuMgr,
+        checkUpgrade,
+        getContentScript,
+        getToolTpl,
+        gcLocalFiles,
+        getAllTools,
+        SortToolMgr,
+        CodeCacheMgr
+    }
+})();
+
+export default Awesome;

+ 14 - 0
apps/dynamic/index.html

@@ -0,0 +1,14 @@
+<!doctype html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <title>FeHelper</title>
+    <script type="text/javascript" src="../static/vendor/evalCore.min.js"></script>
+    <script type="text/javascript" src="../static/vendor/vue/vue.js"></script>
+    <script src="index.js" type="text/javascript"></script>
+    <script src="../static/js/dark-mode.js"></script>
+</head>
+<body>
+    <img src="../static/img/loading.gif" alt="loading" style="margin: 0 auto;width: 200px;position: absolute;left: calc(50% - 100px);top: calc(50% - 100px);">
+</body>
+</html>

+ 82 - 0
apps/dynamic/index.js

@@ -0,0 +1,82 @@
+/**
+ * FeHelper动态工具管理器,主要解决新版本向下兼容,为用户按需找回老版本的功能
+ * @author zhaoxianlie
+ */
+
+let DynamicTool = (() => {
+
+    // 工具渲染
+    let render = (toolName, Awesome) => {
+
+        Awesome.getToolTpl(toolName).then(html => {
+            if (!html) {
+                if (confirm('检测到当前指定的工具还未安装,这就去设置页面安装工具!')) {
+                    location.replace('../options/index.html');
+                } else {
+                    window.close();
+                }
+                return;
+            }
+            // 生成界面
+            document.write(html);
+
+            // 页面滤镜:关掉
+            DarkModeMgr.turnLightAuto();
+
+            // 更新静态文件
+            let list = document.querySelectorAll('dynamic[data-source]');
+            if (!list.length) return;
+            let allJs = [];
+            let allCss = [];
+            for (let i = 0; i < list.length; i++) {
+                let elm = list[i];
+                let fileType = elm.getAttribute('data-type');
+                let sources = elm.getAttribute('data-source') || '';
+                let files = sources.split(',').map(source => {
+                    // 去query处理,获得干净的local key
+                    if (source.indexOf('?') !== -1) {
+                        let x = source.split('?');
+                        x.pop();
+                        source = x.join('');
+                    }
+                    return source;
+                });
+
+                if (fileType === 'js') {
+                    allJs = allJs.concat(files);
+                } else {
+                    allCss = allCss.concat(files);
+                }
+            }
+
+            Promise.all([Awesome.StorageMgr.get(allCss), Awesome.StorageMgr.get(allJs)]).then(values => {
+                document.body.style.display = 'block';
+                allCss = allCss.map(f => values[0][f]).join(' ');
+                if (allCss.length) {
+                    let node = document.createElement('style');
+                    node.textContent = allCss;
+                    document.head.appendChild(node);
+                }
+                allJs = allJs.map(f => values[1][f]).join(';');
+                allJs.length && window.evalCore.getEvalInstance(window)(allJs);
+            });
+        });
+    };
+
+    // 页面初始化
+    let init = () => {
+        // 从Query中寻找需要动态渲染的工具名称
+        let toolName = new URL(location.href).searchParams.get('tool');
+        if (toolName) {
+            import('./awesome.js').then(dynamicModule => {
+                render(toolName, dynamicModule.default);
+            });
+        } else {
+            location.replace('../options/index.html');
+        }
+    };
+
+    return {init}
+})();
+
+DynamicTool.init();

+ 81 - 1
apps/en-decode/endecode-lib.js

@@ -390,6 +390,84 @@ module.exports = (() => {
         return outArr.join("");
     };
 
+
+    /**
+     * URL 参数解析
+     * @param url
+     * @returns {{url: *, params: Array}}
+     * @private
+     */
+    let _urlParamsDecode = function (url) {
+        let res = {};
+        try {
+            let params = [];
+            let urlObj = new URL(url);
+            for (let item of urlObj.searchParams) {
+                params.push(item);
+            }
+            res = {
+                url: urlObj.href,
+                params: params,
+                protocol: urlObj.protocol,
+                pathname: urlObj.pathname,
+                hostname: urlObj.hostname
+            }
+        } catch (e) {
+            res.error = '这不是一个合法的URL!无法完成解析!'
+        }
+        return res;
+    };
+
+    // sha1加密
+    let _sha1Encode = function (str) {
+        function encodeUTF8(s) {
+            let i, r = [], c, x;
+            for (i = 0; i < s.length; i++)
+                if ((c = s.charCodeAt(i)) < 0x80) r.push(c);
+                else if (c < 0x800) r.push(0xC0 + (c >> 6 & 0x1F), 0x80 + (c & 0x3F));
+                else {
+                    if ((x = c ^ 0xD800) >> 10 == 0)
+                        c = (x << 10) + (s.charCodeAt(++i) ^ 0xDC00) + 0x10000,
+                            r.push(0xF0 + (c >> 18 & 0x7), 0x80 + (c >> 12 & 0x3F));
+                    else r.push(0xE0 + (c >> 12 & 0xF));
+                    r.push(0x80 + (c >> 6 & 0x3F), 0x80 + (c & 0x3F));
+                }
+            return r;
+        }
+
+        var data = new Uint8Array(encodeUTF8(str))
+        var i, j, t;
+        var l = ((data.length + 8) >>> 6 << 4) + 16, s = new Uint8Array(l << 2);
+        s.set(new Uint8Array(data.buffer)), s = new Uint32Array(s.buffer);
+        for (t = new DataView(s.buffer), i = 0; i < l; i++)s[i] = t.getUint32(i << 2);
+        s[data.length >> 2] |= 0x80 << (24 - (data.length & 3) * 8);
+        s[l - 1] = data.length << 3;
+        var w = [], f = [
+                function () { return m[1] & m[2] | ~m[1] & m[3]; },
+                function () { return m[1] ^ m[2] ^ m[3]; },
+                function () { return m[1] & m[2] | m[1] & m[3] | m[2] & m[3]; },
+                function () { return m[1] ^ m[2] ^ m[3]; }
+            ], rol = function (n, c) { return n << c | n >>> (32 - c); },
+            k = [1518500249, 1859775393, -1894007588, -899497514],
+            m = [1732584193, -271733879, null, null, -1009589776];
+        m[2] = ~m[0], m[3] = ~m[1];
+        for (i = 0; i < s.length; i += 16) {
+            var o = m.slice(0);
+            for (j = 0; j < 80; j++)
+                w[j] = j < 16 ? s[i + j] : rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1),
+                    t = rol(m[0], 5) + f[j / 20 | 0]() + m[4] + w[j] + k[j / 20 | 0] | 0,
+                    m[1] = rol(m[1], 30), m.pop(), m.unshift(t);
+            for (j = 0; j < 5; j++)m[j] = m[j] + o[j] | 0;
+        };
+        t = new DataView(new Uint32Array(m).buffer);
+        for (var i = 0; i < 5; i++)m[i] = t.getUint32(i << 2);
+
+        var hex = Array.prototype.map.call(new Uint8Array(new Uint32Array(m).buffer), function (e) {
+            return (e < 16 ? "0" : "") + e.toString(16);
+        }).join("");
+        return hex;
+    };
+
     return {
         uniEncode: _uniEncode,
         uniDecode: _uniDecode,
@@ -404,7 +482,9 @@ module.exports = (() => {
         gzipDecode: gzipDecode,
         hexEncode: hexTools.hexEncode,
         hexDecode: hexTools.hexDecode,
-        html2js: _html2js
+        html2js: _html2js,
+        urlParamsDecode: _urlParamsDecode,
+        sha1Encode: _sha1Encode
     };
 })();
 

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 30 - 0
apps/en-decode/he.js


+ 41 - 0
apps/en-decode/index.css

@@ -11,4 +11,45 @@
 }
 .x-ps {
     color:#bbb;
+}
+.x-url-infos ul {
+    padding:0;
+    margin:0;
+}
+.x-url-infos ul li {
+    list-style: none;
+    font-size: 14px;
+    line-height: 24px;
+    padding:0;
+    margin:0;
+}
+.x-url-infos table {
+    width:700px;
+}
+#rst h5 {
+    padding-bottom: 10px;
+    border-bottom: 1px solid #eee;
+}
+.x-btns {
+    float: right;
+    top: -6px;
+    position: relative;
+}
+.x-opts {
+    font-size: 14px;
+}
+.x-opts .radio,
+.x-opts .checkbox{
+    margin-top:0;
+    margin-bottom: 0;
+}
+td.td-label {
+    width:60px;
+}
+.x-opts td {
+    padding:5px 0;
+    vertical-align: middle;
+}
+.x-opts tr:first-child {
+    border-bottom: 1px solid #eee;
 }

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
apps/en-decode/index.html


+ 42 - 10
apps/en-decode/index.js

@@ -6,27 +6,30 @@ new Vue({
     data: {
         selectedType: 'uniEncode',
         sourceContent: '',
-        resultContent: ''
+        resultContent: '',
+        urlResult: null
     },
 
     mounted: function () {
-        let MSG_TYPE = Tarp.require('../static/js/msg_type');
 
         // 在tab创建或者更新时候,监听事件,看看是否有参数传递过来
-        chrome.runtime.onMessage.addListener((request, sender, callback) => {
-            if (request.type === MSG_TYPE.TAB_CREATED_OR_UPDATED && request.event === MSG_TYPE.EN_DECODE) {
-                if (request.content) {
+        if (location.protocol === 'chrome-extension:') {
+            chrome.runtime.onMessage.addListener((request, sender, callback) => {
+                if (request.type === 'TAB_CREATED_OR_UPDATED' && request.content && request.event === location.pathname.split('/')[1]) {
                     this.sourceContent = request.content;
                     this.convert();
                 }
-            }
-        });
+                callback && callback();
+                return true;
+            });
+        }
 
         this.$refs.srcText.focus();
     },
     methods: {
         convert: function () {
             this.$nextTick(() => {
+                this.urlResult = null;
 
                 let tools = Tarp.require('./endecode-lib');
 
@@ -63,20 +66,49 @@ new Vue({
                 } else if (this.selectedType === 'hexDecode') {
 
                     this.resultContent = tools.hexDecode(this.sourceContent);
-                }else if (this.selectedType === 'gzipEncode') {
+                } else if (this.selectedType === 'gzipEncode') {
 
                     this.resultContent = tools.gzipEncode(this.sourceContent);
                 } else if (this.selectedType === 'gzipDecode') {
 
                     this.resultContent = tools.gzipDecode(this.sourceContent);
-                }  else if (this.selectedType === 'html2js') {
+                } else if (this.selectedType === 'html2js') {
 
                     this.resultContent = tools.html2js(this.sourceContent);
+                } else if (this.selectedType === 'sha1Encode') {
+
+                    this.resultContent = tools.sha1Encode(this.sourceContent);
+                } else if (this.selectedType === 'htmlEntityEncode') {
+
+                    this.resultContent = he.encode(this.sourceContent, {
+                        'useNamedReferences': true,
+                        'allowUnsafeSymbols': true
+                    });
+                } else if (this.selectedType === 'htmlEntityFullEncode') {
+
+                    this.resultContent = he.encode(this.sourceContent, {
+                        'encodeEverything': true,
+                        'useNamedReferences': true,
+                        'allowUnsafeSymbols': true
+                    });
+                } else if (this.selectedType === 'htmlEntityDecode') {
+
+                    this.resultContent = he.decode(this.sourceContent, {
+                        'isAttributeValue': false
+                    });
+                } else if (this.selectedType === 'urlParamsDecode') {
+                    let res = tools.urlParamsDecode(this.sourceContent);
+                    if (res.error) {
+                        this.resultContent = res.error;
+                    } else {
+                        this.urlResult = res;
+                    }
                 }
+                this.$forceUpdate();
             });
         },
 
-        clear: function() {
+        clear: function () {
             this.sourceContent = '';
             this.resultContent = '';
         },

+ 286 - 0
apps/excel2json/CSVParser.js

@@ -0,0 +1,286 @@
+//
+//  CSVParser.js
+//  Mr-Data-Converter
+//
+//  Input CSV or Tab-delimited data and this will parse it into a Data Grid Javascript object
+//
+//  CSV Parsing Function from Ben Nadel, http://www.bennadel.com/blog/1504-Ask-Ben-Parsing-CSV-Strings-With-Javascript-Exec-Regular-Expression-Command.htm
+
+
+var isDecimal_re     = /^\s*(\+|-)?((\d+([,\.]\d+)?)|([,\.]\d+))\s*$/;
+
+var CSVParser = {
+
+  //---------------------------------------
+  // UTILS
+  //---------------------------------------
+
+  isNumber: function(string) {
+    if( (string == null) || isNaN( new Number(string) ) ) {
+      return false;
+    }
+    return true;
+  },
+
+
+  //---------------------------------------
+  // PARSE
+  //---------------------------------------
+  //var parseOutput = CSVParser.parse(this.inputText, this.headersProvided, this.delimiter, this.downcaseHeaders, this.upcaseHeaders);
+
+  parse: function (input, headersIncluded, delimiterType, downcaseHeaders, upcaseHeaders, decimalSign) {
+
+    var dataArray = [];
+
+    var errors = [];
+
+    //test for delimiter
+    //count the number of commas
+    var RE = new RegExp("[^,]", "gi");
+    var numCommas = input.replace(RE, "").length;
+
+    //count the number of tabs
+    RE = new RegExp("[^\t]", "gi");
+    var numTabs = input.replace(RE, "").length;
+
+    var rowDelimiter = "\n";
+    //set delimiter
+    var columnDelimiter = ",";
+    if (numTabs > numCommas) {
+      columnDelimiter = "\t"
+    };
+
+    if (delimiterType === "comma") {
+      columnDelimiter = ","
+    } else if (delimiterType === "tab") {
+      columnDelimiter = "\t"
+    }
+
+
+    // kill extra empty lines
+    RE = new RegExp("^" + rowDelimiter + "+", "gi");
+    input = input.replace(RE, "");
+    RE = new RegExp(rowDelimiter + "+$", "gi");
+    input = input.replace(RE, "");
+
+    // var arr = input.split(rowDelimiter);
+    //
+    // for (var i=0; i < arr.length; i++) {
+    //   dataArray.push(arr[i].split(columnDelimiter));
+    // };
+
+
+    // dataArray = jQuery.csv(columnDelimiter)(input);
+    dataArray = this.CSVToArray(input, columnDelimiter);
+
+    //escape out any tabs or returns or new lines
+    for (var i = dataArray.length - 1; i >= 0; i--){
+      for (var j = dataArray[i].length - 1; j >= 0; j--){
+        dataArray[i][j] = dataArray[i][j].replace("\t", "\\t");
+        dataArray[i][j] = dataArray[i][j].replace("\n", "\\n");
+        dataArray[i][j] = dataArray[i][j].replace("\r", "\\r");
+      };
+    };
+
+
+    var headerNames = [];
+    var headerTypes = [];
+    var numColumns = dataArray[0].length;
+    var numRows = dataArray.length;
+    if (headersIncluded) {
+
+      //remove header row
+      headerNames = dataArray.splice(0,1)[0];
+      numRows = dataArray.length;
+
+    } else { //if no headerNames provided
+
+      //create generic property names
+      for (var i=0; i < numColumns; i++) {
+        headerNames.push("val"+String(i));
+        headerTypes.push("");
+      };
+
+    }
+
+
+    if (upcaseHeaders) {
+      for (var i = headerNames.length - 1; i >= 0; i--){
+        headerNames[i] = headerNames[i].toUpperCase();
+      };
+    };
+    if (downcaseHeaders) {
+      for (var i = headerNames.length - 1; i >= 0; i--){
+        headerNames[i] = headerNames[i].toLowerCase();
+      };
+    };
+
+    //test all the rows for proper number of columns.
+    for (var i=0; i < dataArray.length; i++) {
+      var numValues = dataArray[i].length;
+      if (numValues != numColumns) {this.log("Error parsing row "+String(i)+". Wrong number of columns.")};
+    };
+
+    //test columns for number data type
+    var numRowsToTest = dataArray.length;
+    var threshold = 0.9;
+    for (var i=0; i < headerNames.length; i++) {
+      var numFloats = 0;
+      var numInts = 0;
+      for (var r=0; r < numRowsToTest; r++) {
+        if (dataArray[r]) {
+          //replace comma with dot if comma is decimal separator
+          if(decimalSign='comma' && isDecimal_re.test(dataArray[r][i])){
+            dataArray[r][i] = dataArray[r][i].replace(",", ".");
+          }
+          if (CSVParser.isNumber(dataArray[r][i])) {
+            numInts++
+            if (String(dataArray[r][i]).indexOf(".") > 0) {
+              numFloats++
+            }
+          };
+        };
+
+      };
+
+      if ((numInts / numRowsToTest) > threshold){
+        if (numFloats > 0) {
+          headerTypes[i] = "float"
+        } else {
+          headerTypes[i] = "int"
+        }
+      } else {
+        headerTypes[i] = "string"
+      }
+    }
+
+
+
+
+
+    return {'dataGrid':dataArray, 'headerNames':headerNames, 'headerTypes':headerTypes, 'errors':this.getLog()}
+
+  },
+
+
+  //---------------------------------------
+  // ERROR LOGGING
+  //---------------------------------------
+  errorLog:[],
+
+  resetLog: function() {
+    this.errorLog = [];
+  },
+
+  log: function(l) {
+    this.errorLog.push(l);
+  },
+
+  getLog: function() {
+    var out = "";
+    if (this.errorLog.length > 0) {
+      for (var i=0; i < this.errorLog.length; i++) {
+        out += ("!!"+this.errorLog[i] + "!!\n");
+      };
+      out += "\n"
+    };
+
+    return out;
+  },
+
+
+
+  //---------------------------------------
+  // UTIL
+  //---------------------------------------
+
+    // This Function from Ben Nadel, http://www.bennadel.com/blog/1504-Ask-Ben-Parsing-CSV-Strings-With-Javascript-Exec-Regular-Expression-Command.htm
+    // This will parse a delimited string into an array of
+    // arrays. The default delimiter is the comma, but this
+    // can be overriden in the second argument.
+    CSVToArray: function( strData, strDelimiter ){
+      // Check to see if the delimiter is defined. If not,
+      // then default to comma.
+      strDelimiter = (strDelimiter || ",");
+
+      // Create a regular expression to parse the CSV values.
+      var objPattern = new RegExp(
+        (
+          // Delimiters.
+          "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +
+
+          // Quoted fields.
+          "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +
+
+          // Standard fields.
+          "([^\"\\" + strDelimiter + "\\r\\n]*))"
+        ),
+        "gi"
+        );
+
+
+      // Create an array to hold our data. Give the array
+      // a default empty first row.
+      var arrData = [[]];
+
+      // Create an array to hold our individual pattern
+      // matching groups.
+      var arrMatches = null;
+
+
+      // Keep looping over the regular expression matches
+      // until we can no longer find a match.
+      while (arrMatches = objPattern.exec( strData )){
+
+        // Get the delimiter that was found.
+        var strMatchedDelimiter = arrMatches[ 1 ];
+
+        // Check to see if the given delimiter has a length
+        // (is not the start of string) and if it matches
+        // field delimiter. If id does not, then we know
+        // that this delimiter is a row delimiter.
+        if (
+          strMatchedDelimiter.length &&
+          (strMatchedDelimiter != strDelimiter)
+          ){
+
+          // Since we have reached a new row of data,
+          // add an empty row to our data array.
+          arrData.push( [] );
+
+        }
+
+
+        // Now that we have our delimiter out of the way,
+        // let's check to see which kind of value we
+        // captured (quoted or unquoted).
+
+        if (arrMatches[ 2 ]){
+
+          // We found a quoted value. When we capture
+          // this value, unescape any double quotes.
+          var strMatchedValue = arrMatches[ 2 ].replace(
+            new RegExp( "\"\"", "g" ),
+            "\""
+            );
+
+        } else {
+
+          // We found a non-quoted value.
+          var strMatchedValue = arrMatches[ 3 ];
+
+        }
+
+
+        // Now that we have our value string, let's add
+        // it to the data array.
+        arrData[ arrData.length - 1 ].push( strMatchedValue );
+      }
+
+      // Return the parsed data.
+      return( arrData );
+    }
+
+
+
+}

+ 538 - 0
apps/excel2json/DataGridRenderer.js

@@ -0,0 +1,538 @@
+// 
+//  DataGridRenderer.js
+//  Part of Mr-Data-Converter
+//  
+//  Created by Shan Carter on 2010-10-18.
+// 
+
+
+var DataGridRenderer = {
+  
+  //---------------------------------------
+  // Actionscript
+  //---------------------------------------
+  
+  as: function (dataGrid, headerNames, headerTypes, indent, newLine) {
+    //inits...
+    var commentLine = "//";
+    var commentLineEnd = "";
+    var outputText = "[";
+    var numRows = dataGrid.length;
+    var numColumns = headerNames.length;
+    
+    //begin render loops
+    for (var i=0; i < numRows; i++) {
+      var row = dataGrid[i];
+      outputText += "{";
+      for (var j=0; j < numColumns; j++) {
+        if ((headerTypes[j] == "int")||(headerTypes[j] == "float")) {
+          var rowOutput = row[j] || "null";
+        } else {
+          var rowOutput = '"'+( row[j] || "" )+'"';
+        };      
+        outputText += (headerNames[j] + ":" + rowOutput)
+        if (j < (numColumns-1)) {outputText+=","};
+      };
+      outputText += "}";
+      if (i < (numRows-1)) {outputText += ","+newLine};
+    };
+    outputText += "];";
+    
+    
+    return outputText;
+  },
+  
+  
+  //---------------------------------------
+  // ASP / VBScript
+  //---------------------------------------
+  
+  asp: function (dataGrid, headerNames, headerTypes, indent, newLine) {
+    //inits...
+    var commentLine = "'";
+    var commentLineEnd = "";
+    var outputText = "";
+    var numRows = dataGrid.length;
+    var numColumns = headerNames.length;
+    
+    //begin render loop
+    for (var i=0; i < numRows; i++) {
+      var row = dataGrid[i];
+      for (var j=0; j < numColumns; j++) {
+        if ((headerTypes[j] == "int")||(headerTypes[j] == "float")) {
+          var rowOutput = row[j] || "null";
+        } else {
+          var rowOutput = '"'+( row[j] || "" )+'"';
+        };
+      outputText += 'myArray('+j+','+i+') = '+rowOutput+newLine;        
+      };
+    };
+    outputText = 'Dim myArray('+(j-1)+','+(i-1)+')'+newLine+outputText;
+    
+    return outputText;
+  },
+  
+  
+  //---------------------------------------
+  // HTML Table
+  //---------------------------------------
+  
+  html: function (dataGrid, headerNames, headerTypes, indent, newLine) {
+    //inits...
+    var commentLine = "<!--";
+    var commentLineEnd = "-->";
+    var outputText = "";
+    var numRows = dataGrid.length;
+    var numColumns = headerNames.length;
+    
+    //begin render loop
+    outputText += "<table>"+newLine;
+    outputText += indent+"<thead>"+newLine;
+    outputText += indent+indent+"<tr>"+newLine;
+    
+    for (var j=0; j < numColumns; j++) {
+      outputText += indent+indent+indent+'<th class="'+headerNames[j]+'-cell">';          
+      outputText += headerNames[j];
+      outputText += '</th>'+newLine;
+    };
+    outputText += indent+indent+"</tr>"+newLine;
+    outputText += indent+"</thead>"+newLine;
+    outputText += indent+"<tbody>"+newLine;
+    for (var i=0; i < numRows; i++) {
+      var row = dataGrid[i];
+      var rowClassName = ""
+      if (i === numRows-1) {
+        rowClassName = ' class="lastRow"';
+      } else if (i === 0){
+        rowClassName = ' class="firstRow"';
+      }
+      outputText += indent+indent+"<tr"+rowClassName+">"+newLine;
+      for (var j=0; j < numColumns; j++) {
+        outputText += indent+indent+indent+'<td class="'+headerNames[j]+'-cell">';          
+        outputText += row[j]
+        outputText += '</td>'+newLine
+      };
+      outputText += indent+indent+"</tr>"+newLine;
+    };
+    outputText += indent+"</tbody>"+newLine;
+    outputText += "</table>";
+    
+    return outputText;
+  },
+  
+  
+  //---------------------------------------
+  // JSON properties
+  //---------------------------------------
+  
+  json: function (dataGrid, headerNames, headerTypes, indent, newLine) {
+    //inits...
+    var commentLine = "//";
+    var commentLineEnd = "";
+    var outputText = "[";
+    var numRows = dataGrid.length;
+    var numColumns = headerNames.length;
+    
+    //begin render loop
+    for (var i=0; i < numRows; i++) {
+      var row = dataGrid[i];
+      outputText += "{";
+      for (var j=0; j < numColumns; j++) {
+        if ((headerTypes[j] == "int")||(headerTypes[j] == "float")) {
+          var rowOutput = row[j] || "null";
+        } else {
+          var rowOutput = '"' + ( row[j] || "" ) + '"';
+        };
+  
+      outputText += ('"'+headerNames[j] +'"' + ":" + rowOutput );
+  
+        if (j < (numColumns-1)) {outputText+=","};
+      };
+      outputText += "}";
+      if (i < (numRows-1)) {outputText += ","+newLine};
+    };
+    outputText += "]";
+    
+    return outputText;
+  },
+  
+  //---------------------------------------
+  // JSON Array of Columns
+  //---------------------------------------
+  jsonArrayCols: function (dataGrid, headerNames, headerTypes, indent, newLine) {
+    //inits...
+    var commentLine = "//";
+    var commentLineEnd = "";
+    var outputText = "";
+    var numRows = dataGrid.length;
+    var numColumns = headerNames.length;
+    
+    //begin render loop
+    outputText += "{"+newLine;
+    for (var i=0; i < numColumns; i++) {
+      outputText += indent+'"'+headerNames[i]+'":[';
+      for (var j=0; j < numRows; j++) {
+        if ((headerTypes[i] == "int")||(headerTypes[i] == "float")) {
+          outputText += dataGrid[j][i] || 0;
+        } else {
+          outputText += '"'+(dataGrid[j][i] || "")+'"' ;
+        }
+        if (j < (numRows-1)) {outputText+=","};
+      };
+      outputText += "]";
+      if (i < (numColumns-1)) {outputText += ","+newLine};
+    };
+    outputText += newLine+"}";
+    
+    
+    return outputText;
+  },
+  
+  
+  //---------------------------------------
+  // JSON Array of Rows
+  //---------------------------------------
+  jsonArrayRows: function (dataGrid, headerNames, headerTypes, indent, newLine) {
+    //inits...
+    var commentLine = "//";
+    var commentLineEnd = "";
+    var outputText = "";
+    var numRows = dataGrid.length;
+    var numColumns = headerNames.length;
+    
+    //begin render loop
+    outputText += "["+newLine;
+    for (var i=0; i < numRows; i++) {
+      outputText += indent+"[";
+      for (var j=0; j < numColumns; j++) {
+        if ((headerTypes[j] == "int")||(headerTypes[j] == "float")) {
+          outputText += dataGrid[i][j] || 0;
+        } else {
+          outputText += '"'+(dataGrid[i][j] || "")+'"' ;
+        }
+        if (j < (numColumns-1)) {outputText+=","};
+      };
+      outputText += "]";
+      if (i < (numRows-1)) {outputText += ","+newLine};
+    };
+    outputText += newLine+"]";
+    
+    
+    return outputText;
+  },
+  
+  
+
+  //---------------------------------------
+  // JSON Dictionary
+  //---------------------------------------
+  jsonDict: function(dataGrid, headerNames, headerTypes, indent, newLine) {
+    //inits...
+    var commentLine = "//";
+    var commentLineEnd = "";
+    var outputText = "";
+    var numRows = dataGrid.length;
+    var numColumns = headerNames.length;
+
+    //begin render loop
+    outputText += "{" + newLine;
+    for (var i = 0; i < numRows; i++) {
+      outputText += indent + '"' + dataGrid[i][0] + '": ';
+      if (numColumns == 2) {
+        outputText += _fmtVal(i, 1, dataGrid);
+      } else {
+        outputText += '{ ';
+        for (var j = 1; j < numColumns; j++) {
+          if (j > 1) outputText += ', ';
+          outputText += '"' + headerNames[j] + '"' + ":" + _fmtVal(i, j, dataGrid);
+        }
+        outputText += '}';
+      }
+      if (i < (numRows - 1)) {
+        outputText += "," + newLine;
+      }
+    }
+    outputText += newLine + "}";
+
+    function _fmtVal(i, j) {
+      if ((headerTypes[j] == "int")||(headerTypes[j] == "float")) {
+        return dataGrid[i][j] || 0;
+      } else {
+        return '"'+(dataGrid[i][j] || "")+'"' ;
+      }
+    }
+
+    return outputText;
+  },
+
+
+  //---------------------------------------
+  // MYSQL
+  //---------------------------------------
+  mysql: function (dataGrid, headerNames, headerTypes, indent, newLine) {
+    //inits...
+    var commentLine = "/*";
+    var commentLineEnd = "*/";
+    var outputText = "";
+    var numRows = dataGrid.length;
+    var numColumns = headerNames.length;
+    var tableName = "MrDataConverter"
+    
+    //begin render loop
+    outputText += 'CREATE TABLE '+tableName+' (' + newLine;
+    outputText += indent+"id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,"+newLine;
+    for (var j=0; j < numColumns; j++) {
+      var dataType = "VARCHAR(255)";
+      if ((headerTypes[j] == "int")||(headerTypes[j] == "float")) {
+        dataType = headerTypes[j].toUpperCase();
+      };
+      outputText += indent+""+headerNames[j]+" "+dataType;
+      if (j < numColumns - 1) {outputText += ","};
+      outputText += newLine;
+    };
+    outputText += ');' + newLine;
+    outputText += "INSERT INTO "+tableName+" "+newLine+indent+"(";
+    for (var j=0; j < numColumns; j++) {
+      outputText += headerNames[j];
+      if (j < numColumns - 1) {outputText += ","};
+    };
+    outputText += ") "+newLine+"VALUES "+newLine;
+    for (var i=0; i < numRows; i++) {
+      outputText += indent+"(";
+      for (var j=0; j < numColumns; j++) {
+        if ((headerTypes[j] == "int")||(headerTypes[j] == "float"))  {
+          outputText += dataGrid[i][j] || "null";
+        } else {
+          outputText += "'"+( dataGrid[i][j] || "" )+"'";
+        };
+        
+        if (j < numColumns - 1) {outputText += ","};
+      };
+      outputText += ")";
+      if (i < numRows - 1) {outputText += ","+newLine;};
+    };
+    outputText += ";";
+    
+    return outputText;
+  },
+  
+  
+  //---------------------------------------
+  // PHP
+  //---------------------------------------
+  php: function (dataGrid, headerNames, headerTypes, indent, newLine) {
+    //inits...
+    var commentLine = "//";
+    var commentLineEnd = "";
+    var outputText = "";
+    var numRows = dataGrid.length;
+    var numColumns = headerNames.length;
+    var tableName = "MrDataConverter"
+    
+    //begin render loop
+    outputText += "array(" + newLine;
+    for (var i=0; i < numRows; i++) {
+      var row = dataGrid[i];
+      outputText += indent + "array(";
+      for (var j=0; j < numColumns; j++) {
+        if ((headerTypes[j] == "int")||(headerTypes[j] == "float"))  {
+          var rowOutput = row[j] || "null";
+        } else {
+          var rowOutput = '"'+(row[j] || "")+'"';
+        };          
+        outputText += ('"'+headerNames[j]+'"' + "=>" + rowOutput)
+        if (j < (numColumns-1)) {outputText+=","};
+      };
+      outputText += ")";
+      if (i < (numRows-1)) {outputText += ","+newLine};
+    };
+    outputText += newLine + ");";
+    
+    return outputText;
+  },
+  
+  //---------------------------------------
+  // Python dict
+  //---------------------------------------
+  
+  python: function (dataGrid, headerNames, headerTypes, indent, newLine) {
+    //inits...
+    var commentLine = "//";
+    var commentLineEnd = "";
+    var outputText = "[";
+    var numRows = dataGrid.length;
+    var numColumns = headerNames.length;
+    
+    //begin render loop
+    for (var i=0; i < numRows; i++) {
+      var row = dataGrid[i];
+      outputText += "{";
+      for (var j=0; j < numColumns; j++) {
+        if ((headerTypes[j] == "int")||(headerTypes[j] == "float")) {
+          var rowOutput = row[j] || "None";
+        } else {
+          var rowOutput = '"'+(row[j] || "")+'"';
+        };
+  
+      outputText += ('"'+headerNames[j] +'"' + ":" + rowOutput );
+  
+        if (j < (numColumns-1)) {outputText+=","};
+      };
+      outputText += "}";
+      if (i < (numRows-1)) {outputText += ","+newLine};
+    };
+    outputText += "];";
+    
+    return outputText;
+  },
+  
+  
+  //---------------------------------------
+  // Ruby
+  //---------------------------------------
+  ruby: function (dataGrid, headerNames, headerTypes, indent, newLine) {
+    //inits...
+    var commentLine = "#";
+    var commentLineEnd = "";
+    var outputText = "";
+    var numRows = dataGrid.length;
+    var numColumns = headerNames.length;
+    var tableName = "MrDataConverter"
+    
+    //begin render loop
+    outputText += "[";
+    for (var i=0; i < numRows; i++) {
+      var row = dataGrid[i];
+      outputText += "{";
+      for (var j=0; j < numColumns; j++) {
+        if ((headerTypes[j] == "int")||(headerTypes[j] == "float")) {
+          var rowOutput = row[j] || "nil"
+        } else {
+          var rowOutput = '"'+(row[j] || "")+'"';
+        };         
+        outputText += ('"'+headerNames[j]+'"' + "=>" + rowOutput)
+        if (j < (numColumns-1)) {outputText+=","};
+      };
+      outputText += "}";
+      if (i < (numRows-1)) {outputText += ","+newLine};
+    };
+    outputText += "];";
+    
+    return outputText;
+  },
+  
+  
+  //---------------------------------------
+  // XML Nodes
+  //---------------------------------------
+  xml: function (dataGrid, headerNames, headerTypes, indent, newLine) {
+    //inits...
+    var commentLine = "<!--";
+    var commentLineEnd = "-->";
+    var outputText = "";
+    var numRows = dataGrid.length;
+    var numColumns = headerNames.length;
+    
+    //begin render loop
+    outputText = '<?xml version="1.0" encoding="UTF-8"?>' + newLine;
+    outputText += "<rows>"+newLine;
+    for (var i=0; i < numRows; i++) {
+      var row = dataGrid[i];
+      outputText += indent+"<row>"+newLine;
+      for (var j=0; j < numColumns; j++) {
+        outputText += indent+indent+'<'+headerNames[j]+'>';          
+        outputText += row[j] || ""
+        outputText += '</'+headerNames[j]+'>'+newLine
+      };
+      outputText += indent+"</row>"+newLine;
+    };
+    outputText += "</rows>";
+    
+    return outputText;
+    
+  },
+  
+  
+  
+  //---------------------------------------
+  // XML properties
+  //---------------------------------------
+  xmlProperties: function (dataGrid, headerNames, headerTypes, indent, newLine) {
+    //inits...
+    var commentLine = "<!--";
+    var commentLineEnd = "-->";
+    var outputText = "";
+    var numRows = dataGrid.length;
+    var numColumns = headerNames.length;
+  
+    //begin render loop
+    outputText = '<?xml version="1.0" encoding="UTF-8"?>' + newLine;
+    outputText += "<rows>"+newLine;
+    for (var i=0; i < numRows; i++) {
+      var row = dataGrid[i];
+      outputText += indent+"<row ";
+      for (var j=0; j < numColumns; j++) {
+        outputText += headerNames[j]+'=';          
+        outputText += '"' + row[j] + '" ';
+      };
+      outputText += "></row>"+newLine;
+    };
+    outputText += "</rows>";
+    
+    return outputText;
+    
+  },
+  
+  //---------------------------------------
+  // XML Illustrator
+  //---------------------------------------
+  xmlIllustrator: function (dataGrid, headerNames, headerTypes, indent, newLine) {
+    //inits...
+    var commentLine = "<!--";
+    var commentLineEnd = "-->";
+    var outputText = "";
+    var numRows = dataGrid.length;
+    var numColumns = headerNames.length;
+    
+    //begin render loop
+    outputText = '<?xml version="1.0" encoding="utf-8"?>' + newLine;
+    outputText += '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20001102//EN"    "http://www.w3.org/TR/2000/CR-SVG-20001102/DTD/svg-20001102.dtd" [' + newLine;
+    outputText += indent+'<!ENTITY ns_graphs "http://ns.adobe.com/Graphs/1.0/">' + newLine;
+    outputText += indent+'<!ENTITY ns_vars "http://ns.adobe.com/Variables/1.0/">' + newLine;
+    outputText += indent+'<!ENTITY ns_imrep "http://ns.adobe.com/ImageReplacement/1.0/">' + newLine;
+    outputText += indent+'<!ENTITY ns_custom "http://ns.adobe.com/GenericCustomNamespace/1.0/">' + newLine;
+    outputText += indent+'<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">' + newLine;
+    outputText += indent+'<!ENTITY ns_extend "http://ns.adobe.com/Extensibility/1.0/">' + newLine;
+    outputText += ']>' + newLine;
+    outputText += '<svg>' + newLine;
+    outputText += '<variableSets  xmlns="&ns_vars;">' + newLine;
+    outputText += indent+'<variableSet  varSetName="binding1" locked="none">' + newLine;
+    outputText += indent+indent+'<variables>' + newLine;
+    for (var i=0; i < numColumns; i++) {
+      outputText += indent+indent+indent+'<variable varName="'+headerNames[i]+'" trait="textcontent" category="&ns_flows;"></variable>' + newLine;
+    };
+    outputText += indent+indent+'</variables>' + newLine;
+    outputText += indent+indent+'<v:sampleDataSets  xmlns:v="http://ns.adobe.com/Variables/1.0/" xmlns="http://ns.adobe.com/GenericCustomNamespace/1.0/">' + newLine;
+    
+    for (var i=0; i < numRows; i++) {
+      var row = dataGrid[i];
+      outputText += indent+indent+indent+'<v:sampleDataSet dataSetName="' + row[0] + '">'+newLine;
+      for (var j=0; j < numColumns; j++) {
+        outputText += indent+indent+indent+indent+'<'+headerNames[j]+'>'+newLine;          
+        outputText += indent+indent+indent+indent+indent+'<p>' + row[j] + '</p>' +newLine;
+        outputText += indent+indent+indent+indent+'</'+headerNames[j]+'>'+newLine
+      };
+      outputText += indent+indent+indent+'</v:sampleDataSet>'+newLine;
+    };
+    
+    outputText += indent+indent+'</v:sampleDataSets>' + newLine;
+    outputText += indent+'</variableSet>' + newLine;
+    outputText += '</variableSets>' + newLine;
+    outputText += '</svg>' + newLine;
+    
+    
+    return outputText;
+    
+  },
+  
+}

+ 190 - 0
apps/excel2json/converter.js

@@ -0,0 +1,190 @@
+//
+//  converter.js
+//  Mr-Data-Converter
+//
+//  Created by Shan Carter on 2010-09-01.
+//
+
+
+
+function DataConverter(nodeId) {
+
+  //---------------------------------------
+  // PUBLIC PROPERTIES
+  //---------------------------------------
+
+  this.nodeId                 = nodeId;
+  this.node                   = $("#"+nodeId);
+
+  this.outputDataTypes        = [
+                                {"text":"Actionscript",           "id":"as",               "notes":""},
+                                {"text":"ASP/VBScript",           "id":"asp",              "notes":""},
+                                {"text":"HTML",                   "id":"html",             "notes":""},
+                                {"text":"JSON - Properties",      "id":"json",             "notes":""},
+                                {"text":"JSON - Column Arrays",   "id":"jsonArrayCols",    "notes":""},
+                                {"text":"JSON - Row Arrays",      "id":"jsonArrayRows",    "notes":""},
+                                {"text":"JSON - Dictionary",      "id":"jsonDict",         "notes":""},
+                                {"text":"MySQL",                  "id":"mysql",            "notes":""},
+                                {"text":"PHP",                    "id":"php",              "notes":""},
+                                {"text":"Python - Dict",          "id":"python",           "notes":""},
+                                {"text":"Ruby",                   "id":"ruby",             "notes":""},
+                                {"text":"XML - Properties",       "id":"xmlProperties",    "notes":""},
+                                {"text":"XML - Nodes",            "id":"xml",              "notes":""},
+                                {"text":"XML - Illustrator",      "id":"xmlIllustrator",   "notes":""}];
+  this.outputDataType         = "json";
+
+  this.columnDelimiter        = "\t";
+  this.rowDelimiter           = "\n";
+
+  this.inputTextArea          = {};
+  this.outputTextArea         = {};
+
+  this.inputHeader            = {};
+  this.outputHeader           = {};
+  this.dataSelect             = {};
+
+  this.inputText              = "";
+  this.outputText             = "";
+
+  this.newLine                = "\n";
+  this.indent                 = "  ";
+
+  this.root                   = "rows";
+  this.child                  = "row";
+
+  this.commentLine            = "//";
+  this.commentLineEnd         = "";
+  this.tableName              = "MrDataConverter"
+
+  this.useUnderscores         = true;
+  this.headersProvided        = true;
+  this.downcaseHeaders        = true;
+  this.upcaseHeaders          = false;
+  this.includeWhiteSpace      = true;
+  this.useTabsForIndent       = false;
+
+}
+
+//---------------------------------------
+// PUBLIC METHODS
+//---------------------------------------
+
+DataConverter.prototype.create = function(w,h) {
+  var self = this;
+
+  //build HTML for converter
+  this.inputHeader = $('<div class="groupHeader" id="inputHeader"><p class="groupHeadline">数据源输入<span class="subhead">(可以直接从Excel/CVS中拷贝内容到这里! <a href="#" id="insertSample">简单示例!</a>)</span></p></div>');
+  this.inputTextArea = $('<textarea class="textInputs" id="dataInput" placeholder="输入CVS或者以tab为间隔的数据"></textarea>');
+  var outputHeaderText = '<div class="groupHeader" id="outputHeader"><p class="groupHeadline">结果转换为<select name="Data Types" id="dataSelector" class="form-control">';
+    for (var i=0; i < this.outputDataTypes.length; i++) {
+
+      outputHeaderText += '<option value="'+this.outputDataTypes[i]["id"]+'" '
+              + (this.outputDataTypes[i]["id"] == this.outputDataType ? 'selected="selected"' : '')
+              + '>'
+              + this.outputDataTypes[i]["text"]+'</option>';
+    };
+    outputHeaderText += '</select><span class="subhead" id="outputNotes"></span></p></div>';
+  this.outputHeader = $(outputHeaderText);
+  this.outputTextArea = $('<textarea class="textInputs" id="dataOutput" placeholder="转换后的结果会显示在这里"></textarea>');
+
+  this.node.append(this.inputHeader);
+  this.node.append(this.inputTextArea);
+  this.node.append(this.outputHeader);
+  this.node.append(this.outputTextArea);
+
+  this.dataSelect = this.outputHeader.find("#dataSelector");
+
+
+  //add event listeners
+
+  // $("#convertButton").bind('click',function(evt){
+  //   evt.preventDefault();
+  //   self.convert();
+  // });
+
+  this.outputTextArea.click(function(evt){this.select();});
+
+
+  $("#insertSample").bind('click',function(evt){
+    evt.preventDefault();
+    self.insertSampleData();
+    self.convert();
+    _gaq.push(['_trackEvent', 'SampleData','InsertGeneric']);
+  });
+
+  $("#dataInput").keyup(function() {self.convert()});
+  $("#dataInput").change(function() {
+    self.convert();
+    _gaq.push(['_trackEvent', 'DataType',self.outputDataType]);
+  });
+
+  $("#dataSelector").bind('change',function(evt){
+       self.outputDataType = $(this).val();
+       self.convert();
+     });
+
+  this.resize(w,h);
+}
+
+DataConverter.prototype.resize = function(w,h) {
+
+  var paneWidth = w;
+  var paneHeight = (h-90)/2-20;
+
+  this.node.css({width:paneWidth});
+  this.inputTextArea.css({width:paneWidth-20,height:paneHeight});
+  this.outputTextArea.css({width: paneWidth-20, height:paneHeight});
+
+}
+
+DataConverter.prototype.convert = function() {
+
+  this.inputText = this.inputTextArea.val();
+  this.outputText = "";
+
+
+  //make sure there is input data before converting...
+  if (this.inputText.length > 0) {
+
+    if (this.includeWhiteSpace) {
+      this.newLine = "\n";
+    } else {
+      this.indent = "";
+      this.newLine = "";
+    }
+
+    CSVParser.resetLog();
+    var parseOutput = CSVParser.parse(this.inputText, this.headersProvided, this.delimiter, this.downcaseHeaders, this.upcaseHeaders);
+
+    var dataGrid = parseOutput.dataGrid;
+    var headerNames = parseOutput.headerNames;
+    var headerTypes = parseOutput.headerTypes;
+    var errors = parseOutput.errors;
+
+    this.outputText = DataGridRenderer[this.outputDataType](dataGrid, headerNames, headerTypes, this.indent, this.newLine);
+
+
+    //验证成功,将会对其中的节点进行替换
+    //否者,直接对数据进行输出
+    if(this.root
+        && this.child
+        && (this.outputDataType === "xmlProperties"
+            || this.outputDataType === "xml")){
+
+      //替换其中的根节点与字节点
+      this.outputText = this.outputText.replace(/rows/g,this.root)
+      this.outputText = this.outputText.replace(/row/g,this.child);
+    }
+    this.outputTextArea.val(errors + this.outputText);
+
+
+
+  }; //end test for existence of input text
+}
+
+
+DataConverter.prototype.insertSampleData = function() {
+  this.inputTextArea.val("NAME\tVALUE\tCOLOR\tDATE\nAlan\t12\tblue\tSep. 25, 2009\nShan\t13\t\"green\tblue\"\tSep. 27, 2009\nJohn\t45\torange\tSep. 29, 2009\nMinna\t27\tteal\tSep. 30, 2009");
+}
+
+

+ 143 - 0
apps/excel2json/index.css

@@ -0,0 +1,143 @@
+@import url("../static/css/bootstrap.min.css");
+
+#pageContainer > .panel-body {
+    margin: 0 auto;
+}
+
+body {
+    overflow: hidden;
+}
+
+#base {
+    padding: 15px;
+    position: relative;
+}
+
+/*header*/
+
+#header {
+    width: 300px;
+    overflow: auto;
+}
+
+#header a {
+    color: #99FFFF;
+}
+
+#description p {
+    margin-bottom: 18px;
+}
+
+#header h3 {
+    font-size: 16px;
+    margin: 15px 0 10px 0;
+    padding: 15px 0 0 0;
+    border-top: solid 1px #CCC;
+    text-transform: uppercase;
+}
+
+h1 {
+    color: #DEDEDE;
+    font-family: palatino, Georgia;
+    font-size: 40px;
+    line-height: 40px;
+    font-weight: bold;
+    margin-bottom: 8px;
+    text-shadow: 1px 1px 3px #000;
+}
+
+p {
+    font-size: 15px;
+    line-height: 22px;
+}
+
+/*settings*/
+#settings {
+    font-size:12px;
+    line-height: 30px;
+}
+
+#settings h5 {
+    line-height: 24px;
+}
+
+#settings p {
+    line-height: 24px;
+}
+
+#settings .settingsGroup p {
+    padding-left: 20px;
+}
+
+/*converter*/
+
+#converter {
+    position: absolute;
+    top: 15px;
+    left: 330px;
+    border:1px solid #ccc;
+}
+
+p.dataHeaders {
+    height: 30px;
+    padding: 15px 15px 10px;
+}
+
+.textInputs {
+    border: none;
+    color: #664D63;
+    font-family: monospace;
+    font-size: 12px;
+    height: 300px;
+    line-height: 18px;
+    padding: 10px;
+    text-shadow: #DED4DD 0px 1px 0px;
+    outline: none;
+    resize: none;
+}
+
+.groupHeader {
+    width: 100%;
+    color: #000;
+    height: 45px;
+    background-color: #f1f1f1;
+}
+
+p.groupHeadline {
+    padding: 10px;
+}
+
+.groupHeader span.subhead {
+    opacity: 0.7;
+    font-size: 12px;
+}
+
+.groupHeader a {
+    color: #FF66FF;
+}
+
+#outputHeader {
+    border-top:1px solid #ccc;
+}
+
+#dataInput, #dataOutput {
+    width: 100% !important;
+}
+
+#dataSelector {
+    width: 200px;
+    line-height: 22px;
+    font-size: 12px;
+    position: relative;
+    top: -2px;
+    left: 10px;
+}
+
+#dataSelector option {
+
+}
+.form-control {
+    display: inline-block;
+    width:200px;
+    height:28px;
+}

+ 78 - 0
apps/excel2json/index.html

@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html lang="zh-CN">
+<head>
+    <title>Excel/CVS转JSON</title>
+    <meta charset="UTF-8">
+    <link rel="shortcut icon" href="../static/img/favicon.ico">
+    <link rel="stylesheet" href="index.css" />
+</head>
+<body>
+
+<div class="wrapper" id="pageContainer">
+    <div class="panel panel-default" style="margin-bottom: 0px;">
+        <div class="panel-heading">
+            <h3 class="panel-title">
+                <a href="http://www.baidufe.com/fehelper/feedback.html" target="_blank" class="x-a-high">
+                    <img src="../static/img/fe-16.png" alt="fehelper"/> FeHelper</a>:Excel/CVS转JSON
+            </h3>
+        </div>
+    </div>
+    <div class="panel-body mod-endecode">
+        <div id='base'>
+            <div id='header'>
+
+                <div id='settings'>
+                    <h3>输入配置</h3>
+                    <form id='settingsForm'>
+                        <div><label><input class="settingsElement" type="checkbox" name="" value="" id="headersProvidedCB" checked/>首行为标题</label>
+                        (<span class="settingsGroup">表头转换:
+                            <label><input class="settingsElement" type="radio" name="headerModifications" value="downcase" id='headersDowncase'/>小写&nbsp;&nbsp;</label>
+                            <label><input class="settingsElement" type="radio" name="headerModifications" id='headersUpcase' value="upcase"/> 大写&nbsp;&nbsp;</label>
+                            <label><input class="settingsElement" type="radio" name="headerModifications" id='headersNoTransform' value="none" checked/> 无</label>
+                        </span>)</div>
+
+                        <div>字段分隔符:
+                            <label><input class="settingsElement" type="radio" name="delimiter" id='delimiterAuto' value="auto" checked/>自动&nbsp;&nbsp;</label>
+                            <label><input class="settingsElement" type="radio" name="delimiter" id='delimiterComma' value="comma"/>逗号&nbsp;&nbsp;</label>
+                            <label><input class="settingsElement" type="radio" name="delimiter" id='delimiterTab' value="tab"/>Tab键</label>
+                        </div>
+                        <div>数字分隔符:
+                            <label><input class="settingsElement" type="radio" name="decimal" id='decimalDot' value="dot" checked/>点&nbsp;&nbsp;</label>
+                            <label><input class="settingsElement" type="radio" name="decimal" id='decimalComma' value="comma"/>逗号</label>
+                        </div>
+                    </form>
+
+                    <h3 style="margin-top:40px">输出配置</h3>
+                    <form id="outSettingsForm">
+                        <div>
+                            <label><input class="settingsElement" type="checkbox" name="some_name" value="" id="includeWhiteSpaceCB" checked/>格式调整</label>
+                            (缩进:
+                            <label><input class="settingsElement" type="radio" name="indentType" value="tabs" id='includeWhiteSpaceTabs'/>tab键&nbsp;&nbsp;</label>
+                            <label><input class="settingsElement" type="radio" name="indentType" value="spaces" id='includeWhiteSpaceSpaces' checked/>空格</label>)
+                        </div>
+
+                        <div class="settingsGroup">
+                            <div>
+                            </div>
+                            <div><label>XML节点定义:</label>(仅针对XML结果有效)
+                                <div><label>根节点:<input class="settingsElement form-control" type="text" name="indentType" value="root" id='root'/></label></div>
+                                <div><label>子节点:<input class="settingsElement form-control" type="text" name="indentType" value="row" id='child'/></label></div>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+            </div>
+
+            <div id='converter' class=''></div>
+        </div>
+    </div>
+</div>
+
+<script src="../static/vendor/jquery/jquery-3.3.1.min.js"></script>
+<script type="text/javascript" src="CSVParser.js"></script>
+<script type="text/javascript" src="DataGridRenderer.js"></script>
+<script type="text/javascript" src="converter.js"></script>
+<script type="text/javascript" src="index.js"></script>
+
+</body>
+</html>

+ 104 - 0
apps/excel2json/index.js

@@ -0,0 +1,104 @@
+/* code here... */
+
+var _gaq = _gaq || [];
+
+var widthOffset = 375;
+var heightOffset = 90
+
+
+/**
+ * @type {DataConverter}
+ * 对数据内容进行转换
+ */
+var d = new DataConverter('converter');
+
+var sidebar = $('#header');
+
+var win = $(window);
+var base = $('#pageContainer');
+var w = base.width() - widthOffset;
+var h = win.height() - heightOffset;
+
+//重载页面,解决无法显示textarea问题
+d.create(w, h);
+d.resize(w, h);
+sidebar.height(h);
+
+$(".settingsElement").change(updateSettings);
+
+
+/**
+ * win发生窗口变化的时候,验证窗口的高宽
+ * 修正sidebar的高宽
+ */
+$(window).bind('resize', function () {
+
+    w = base.width() - widthOffset;
+    h = win.height() - heightOffset;
+    d.resize(w, h);
+    sidebar.height(h);
+
+});
+
+
+/**
+ * 监听dom树,修改设置内容
+ * 定界符
+ * 第一行标题
+ * 输出格式内容
+ * @param evt
+ */
+function updateSettings(evt) {
+
+    if (evt) {
+        _gaq.push(['_trackEvent', 'Settings', evt.currentTarget.id]);
+    }
+
+    d.includeWhiteSpace = $('#includeWhiteSpaceCB').prop('checked');
+
+    if (d.includeWhiteSpace) {
+        $("input[name=indentType]").removeAttr("disabled");
+        var indentType = $('input[name=indentType]:checked').val();
+        if (indentType === "tabs") {
+            d.indent = "\t";
+        } else if (indentType === "spaces") {
+            d.indent = "  "
+        }
+    } else {
+        $("input[name=indentType]").attr("disabled", "disabled");
+    }
+
+    d.headersProvided = $('#headersProvidedCB').prop('checked');
+
+    if (d.headersProvided) {
+        $("input[name=headerModifications]").removeAttr("disabled");
+
+        var hm = $('input[name=headerModifications]:checked').val();
+        if (hm === "downcase") {
+            d.downcaseHeaders = true;
+            d.upcaseHeaders = false;
+        } else if (hm === "upcase") {
+            d.downcaseHeaders = false;
+            d.upcaseHeaders = true;
+        } else if (hm === "none") {
+            d.downcaseHeaders = false;
+            d.upcaseHeaders = false;
+        }
+    } else {
+        $("input[name=headerModifications]").attr("disabled", "disabled");
+    }
+
+    d.delimiter = $('input[name=delimiter]:checked').val();
+    d.decimal = $('input[name=decimal]:checked').val();
+
+    d.useUnderscores = true;
+
+
+    d.root = $('#root').val();
+    d.child = $('#child').val();
+
+    d.convert();
+};
+
+updateSettings();
+  

+ 0 - 0
apps/ruler/index.css → apps/grid-ruler/content-script.css


+ 28 - 23
apps/ruler/index.js → apps/grid-ruler/content-script.js

@@ -2,15 +2,7 @@
  * 栅格相关处理
  * @author zhaoxianlie
  */
-let GridRuler = (function () {
-
-    // 加载CSS
-    let _loadCss = function () {
-        if (!jQuery('#_fehelper_grid_css_')[0]) {
-            let cssUrl = chrome.extension.getURL('ruler/index.css');
-            jQuery('<link id="_fehelper_grid_css_" href="' + cssUrl + '" rel="stylesheet" type="text/css" />').appendTo('head');
-        }
-    };
+window.gridrulerContentScript = function () {
 
     // 创建栅格
     let _createGrid = function () {
@@ -159,10 +151,34 @@ let GridRuler = (function () {
     /**
      * 执行栅格系统检测
      */
-    let _detect = function (callback) {
+    window.gridrulerNoPage = function (tabInfo) {
 
         // 加载CSS
-        _loadCss();
+        if (window.gridrulerContentScriptCssInject) {
+            window.gridrulerContentScriptCssInject();
+        } else {
+            // 注入css and html fragment
+            chrome.runtime.sendMessage({
+                type: 'fh-dynamic-any-thing',
+                func: ((params, callback) => {
+
+                    let injectFn = (cssText) => {
+                        chrome.tabs.insertCSS({
+                            code: cssText
+                        });
+                    };
+
+                    let cssText = Awesome.getContentScript('grid-ruler', true);
+                    if (typeof cssText === 'string') {
+                        injectFn(cssText);
+                    } else if (cssText instanceof Promise) {
+                        cssText.then(css => injectFn(css));
+                    }
+                    callback && callback();
+                    return true;
+                }).toString()
+            });
+        }
 
         //创建栅格
         _createGrid();
@@ -172,17 +188,6 @@ let GridRuler = (function () {
 
         // 事件绑定
         _bindEvent();
-
-
-        //执行回调
-        if (callback && typeof callback === "function") {
-            callback.call(null);
-        }
-    };
-
-    return {
-        detect: _detect
     };
-})();
 
-module.exports = GridRuler;
+};

+ 11 - 0
apps/grid-ruler/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>FeHelper-网页栅格系统</title>
+    <link rel="stylesheet" href="content-script.css">
+</head>
+<body>
+<script src="content-script.js"></script>
+</body>
+</html>

+ 41 - 0
apps/hello-world/content-script.js

@@ -0,0 +1,41 @@
+/**
+ * 注意这里的方法名称,其实是:window[`${toolName.replace(/[-_]/g,'')}ContentScript`];
+ * @author 阿烈叔
+ */
+window.helloworldContentScript = function () {
+    console.log('你好,我是来自FeHelper的工具Demo:hello world!');
+};
+
+/**
+ * 如果在 fh-config.js 中指定了 noPage参数为true,则这里必须定义noPage的接口方法,如:
+ * 注意这里的方法名称,其实是:window[`${toolName.replace(/[-_]/g,'')}NoPage`];
+ * @author 阿烈叔
+ */
+window.helloworldNoPage = function (tabInfo) {
+    alert('你好,我是来自FeHelper的工具Demo:hello world!你可以打开控制台看Demo的输出!');
+    console.log('你好,我是来自FeHelper的工具Demo:', tabInfo);
+
+    // background 示例:获取当前浏览器的所有tab
+    chrome.runtime.sendMessage({
+        type: 'fh-dynamic-any-thing',
+        params: {
+            tabId: window.__FH_TAB_ID__ // 这是FH的内置变量,表示当前Tab的id
+        },
+        func: ((params, callback) => {
+            // TODO: 这里可以调用 chrome.* 的API,随便用。。。
+
+            // Case1:获取当前窗口的全部tab
+            chrome.tabs.query({currentWindow: true}, tabs => {
+                let jsonInfo = JSON.stringify(tabs);
+
+                // 注入到页面,注意看这里如何读取外面传进来的参数
+                chrome.tabs.executeScript(params.tabId, {
+                    code: 'console.log(' + jsonInfo + ');'
+                });
+            });
+
+            callback && callback();
+            return true;
+        }).toString()
+    });
+};

+ 23 - 0
apps/hello-world/fh-config.js

@@ -0,0 +1,23 @@
+(function () {
+    return {
+        "hello-world": {
+            "name": "Hello world!",      // 工具的名称
+            "tips": "这是一个FH自定义工具的入门示例!一切都从Hello world开始,大家可体验,或下载后学习!", // 工具描述
+            "noPage": true,             // true表示此工具无独立页面
+            "contentScript": true,      // 是否有内容注入的脚本
+            "contentScriptCss": false,  // 是否注入Css
+            "minVersion": "2020.02.0718", // 工具从FeHelper的哪个版本开始支持
+            "menuConfig": [{            // 可以配置多个右键菜单
+                "icon": "웃",            // 右键菜单的icon,也是工具的默认icon
+                "text": "Hello world",  // 右键菜单中的工具名称
+                "onClick": function (info, tab) {
+                    alert('你好,我是Hello world;这是一个无独立页面的功能,我的内容已经输出到控制台了哦!');
+                    chrome.DynamicToolRunner({
+                        query: "tool=hello-world",
+                        noPage: true
+                    });
+                }
+            }]
+        }
+    };
+})();

+ 4 - 0
apps/hello-world/index.css

@@ -0,0 +1,4 @@
+body {
+	text-align: center;
+	font-size: 16px;
+}

+ 14 - 0
apps/hello-world/index.html

@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>demo</title>
+	<link rel="stylesheet" type="text/css" href="index.css">
+</head>
+<body>
+	hello world!
+	<br>
+	修改fh-config.js中的noPage参数为false时,点击工具菜单,就会在新窗口打开这个工具了!去试试吧!
+	<script type="text/javascript" src="index.js"></script>
+	<script type="text/javascript" src="content-script.js"></script>
+</body>
+</html>

+ 1 - 0
apps/hello-world/index.js

@@ -0,0 +1 @@
+alert('hello world');

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 201 - 0
apps/html2markdown/demo-tpl.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 77 - 0
apps/html2markdown/editor.css


+ 146 - 0
apps/html2markdown/index.css

@@ -1,4 +1,9 @@
 @import url("../static/css/bootstrap.min.css");
+@import url("editor.css");
+
+#pageContainer>.panel-default {
+    margin:0;
+}
 
 #srcText {
     height: 150px;
@@ -38,4 +43,145 @@
 .x-switch {
     color:#f00;
     border-bottom: 1px solid #f00;
+}
+
+
+.tbl-editor {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    width: 100%;
+    background: #fff;
+}
+
+#title {
+    width: 320px;
+    padding: 0 5px;
+    height: 24px;
+}
+
+.mod-toolbar {
+    position: absolute;
+    left: 0px;
+    right: 0px;
+    top: 46px;
+    background: #f9f9f9;
+    border: 1px solid #ddd;
+}
+
+.mod-editor, .markdown-body{
+    position: absolute;
+    top: 87px;
+    bottom: 0;
+}
+
+.mod-editor {
+    overflow: auto;
+    font-size: 14px;
+    left: 0;
+    right: 50%;
+}
+
+.CodeMirror-scroll {
+    height: 100%;
+    min-height: 100%;
+}
+
+.markdown-body {
+    overflow: auto;
+    padding: 10px;
+    padding-left: 20px;
+    left: 50%;
+    right: 0;
+    box-shadow: inset 1px -5px 10px #ddd
+}
+
+.blog-aside {
+    position: fixed;
+    top: 50px;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    z-index: 100;
+    padding: 20px;
+    background: rgba(0, 0, 0, .5);
+    border: 1px solid #ddd;
+    font-size: 14px;
+}
+
+.as-container {
+    background: #f1f1f1;
+    width: 800px;
+    margin: 50px auto 0;
+    padding: 20px;
+    border-radius: 6px;
+    position: relative;
+}
+
+fieldset {
+    padding: 0px 10px 5px;
+    border: 1px solid #ccc;
+    line-height: 28px;
+}
+
+.as-item {
+    margin: 0 0 5px;
+    position: relative;
+}
+
+
+.mod-pageheader, #editor, .mod-footer {
+    display: none;
+}
+.mod-markdown.preview-closed .markdown-body {
+    display:none;
+}
+.mod-markdown.preview-closed .mod-editor {
+    right:0;
+}
+.mod-markdown.mode-h2m .editor-toolbar .x-mask {
+    position: absolute;
+    top:0;
+    left:0;
+    right:0;
+    bottom:0;
+    background: #eee;
+    opacity: 0.3;
+    width: 356px;
+}
+.mod-markdown.mode-h2m textarea {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    width: 100%;
+    resize: none;
+    border: none;
+    background: transparent;
+    outline: none;
+    font-size: 14px;
+    padding: 15px;
+}
+
+.mod-markdown.mode-h2m .icon-bold,
+.mod-markdown.mode-h2m .icon-italic,
+.mod-markdown.mode-h2m .icon-quote,
+.mod-markdown.mode-h2m .icon-unordered-list,
+.mod-markdown.mode-h2m .icon-ordered-list,
+.mod-markdown.mode-h2m .icon-link,
+.mod-markdown.mode-h2m .icon-image,
+.mod-markdown.mode-h2m .icon-play,
+.mod-markdown.mode-h2m .icon-music,
+.mod-markdown.mode-h2m .icon-contract,
+.mod-markdown.mode-h2m .icon-fullscreen,
+.mod-markdown.mode-h2m .icon-question,
+.mod-markdown.mode-h2m .icon-info,
+.mod-markdown.mode-h2m .icon-undo,
+.mod-markdown.mode-h2m .icon-redo,
+.mod-markdown.mode-h2m .icon-code,
+.mod-markdown.mode-h2m .icon-preview {
+    opacity: 0.2;
 }

+ 45 - 45
apps/html2markdown/index.html

@@ -1,8 +1,9 @@
 <!DOCTYPE HTML>
 <html lang="zh-CN">
 <head>
-    <title>HTML转Markdown工具</title>
+    <title>Markdown编辑器(支持HTML转Markdown)</title>
     <meta charset="UTF-8">
+    <link rel="shortcut icon" href="../static/img/favicon.ico">
     <link rel="stylesheet" href="index.css" />
     <script type="text/javascript" src="../static/vendor/vue/vue.js"></script>
     <script src="../static/vendor/require/require.js"></script>
@@ -15,61 +16,60 @@
             <h3 class="panel-title">
                 <a href="http://www.baidufe.com/fehelper/feedback.html" target="_blank" class="x-a-high">
                     <img src="../static/img/fe-16.png" alt="fehelper"/> FeHelper</a>:{{toolName[codeType]}}
-                <span class="x-xdemo" ref="demoLink1" @click="setDemo">示例:{{codeType}}片段</span>
+                <span class="x-xdemo" ref="demoLink1" @click="setDemo">{{codeType}}是什么?</span>
                 <span class="x-xdemo" ref="importLink" @click="importContent">导入{{codeType}}文件</span>
 
-                <span class="x-switch ui-fl-r" ref="btnSwitch" @click="trans">切换为{{toolName[nextCodeType]}}&gt;&gt;</span>
+                <span class="x-switch ui-fl-r" ref="btnSwitch" @click="trans">{{toolName[nextCodeType]}}&gt;&gt;</span>
             </h3>
         </div>
     </div>
-    <div class="panel-body mod-endecode">
+    <div class="panel-body mod-markdown" ref="modMarkdownBox">
 
-        <div class="row">
-            <textarea class="form-control mod-textarea ui-mb-10" id="srcText" ref="srcText" v-model="sourceContent" @input="convert"
-                      placeholder="粘贴或输入需要进行转换的内容"></textarea>
-        </div>
+        <div class="mod-toolbar">
+            <div class="editor-toolbar">
+                <a class="icon-bold" @click="insert('b')" title="加粗"></a>
+                <a class="icon-italic" @click="insert('i')" title="斜体"></a>
+                <i class="separator">|</i>
+                <a class="icon-quote" @click="insert('quote')" title="备注"></a>
+                <a class="icon-code" @click="insert('code')" title="代码"></a>
+                <i class="separator">|</i>
+                <a class="icon-unordered-list" @click="insert('unordered-list')" title="无序列表"></a>
+                <a class="icon-ordered-list" @click="insert('ordered-list')" title="有序列表"></a>
+                <i class="separator">|</i>
+                <a class="icon-link" @click="insert('link')" title="链接"></a>
+                <a class="icon-image" @click="insert('image')" title="图片"></a>
+                <i class="separator">|</i>
+                <a class="icon-preview" @click="togglePreview" title="实时预览切换"></a>
+                <a class="icon-save" @click="saveMarkdown('md')" title="下载Markdown内容"></a>
+                <a class="icon-pdf" @click="saveMarkdown('html')" title="下载HTML内容"></a>
 
-        <div id="rst" class="row ui-mt-10" v-show="resultContent.length > 0">
-            <a href="#" class="x-xdemo" style="margin-left: 0" @click="preview" v-html="previewText"></a>
-            <a href="#" class="x-xdemo" @click="exportContent" title="在打印设置中,务必选择「PDF」">导出为PDF</a>
-            <hr>
-            <textarea class="mod-textarea" id="rstCode" ref="rstCode" v-model="resultContent" @click="getResult" v-show="!showPreview"></textarea>
-            <div class="x-preview" v-show="showPreview" v-html="previewHTML"></div>
+                <div class="x-mask"></div>
+            </div>
+        </div>
+        <div class="mod-editor">
+            <textarea id="editor" ref="elEditor"></textarea>
         </div>
+        <div class="markdown-body" ref="boxPreview"></div>
     </div>
 
-    <div class="html-demo hide" ref="htmlDemo">
-        <h2 class="fe-function-title">FE助手</h2>
-        <ul class="fe-function-list">
-            <li class="-x-endecode" >
-                <span>字符串编解码</span></li>
-            <li class="-x-jsonformat">
-                <span><code>Json</code>串格式化</span></li>
-            <li class="-x-codebeautify">
-                <span>代码美化工具</span></li>
-            <li class="-x-codecompress">
-                <span>代码压缩工具</span></li>
-            <li class="-x-qrcode">
-                <span>二维码生成器</span></li>
-            <li class="-x-colorpicker">
-                <span>页面取色工具</span></li>
-            <li class="-x-regexp">
-                <span>Js正则表达式</span></li>
-            <li class="-x-timestamp">
-                <span>时间(戳)转换</span></li>
-            <li class="-x-base64">
-                <span>图片Base64</span></li>
-            <li class="-x-fcp">
-                <span>编码规范检测</span></li>
-            <li class="-x-loadtime">
-                <span>页面性能检测</span></li>
-            <li class="-x-markdown">
-                <span>Html转<code>Markdown</code></span></li>
-            <li class="-x-ajax-debugger">
-                <span>Ajax调试:<strong>关</strong></span></li>
-        </ul>
-    </div>
 </div>
+
+<script type="text/javascript" src="../static/vendor/codemirror/codemirror.js"></script>
+<script type="text/javascript" src="../static/vendor/codemirror/overlay.js"></script>
+<script type="text/javascript" src="../static/vendor/codemirror/markdown.js"></script>
+<script type="text/javascript" src="../static/vendor/codemirror/gfm.js"></script>
+<script type="text/javascript" src="../static/vendor/codemirror/xml.js"></script>
+<script type="text/javascript" src="../static/vendor/codemirror/javascript.js"></script>
+<script type="text/javascript" src="../static/vendor/codemirror/css.js"></script>
+<script type="text/javascript" src="../static/vendor/codemirror/htmlmixed.js"></script>
+<script type="text/javascript" src="../static/vendor/codemirror/clike.js"></script>
+<script type="text/javascript" src="../static/vendor/codemirror/meta.js"></script>
+<script type="text/javascript" src="../static/vendor/h2m/h2m.js"></script>
+<script type="text/javascript" src="../static/vendor/highlight/highlight.js"></script>
+<script type="text/javascript" src="libs/marked.js"></script>
+<script type="text/javascript" src="libs/rawdeflate.js"></script>
+<script type="text/javascript" src="libs/rawinflate.js"></script>
+<script type="text/javascript" src="demo-tpl.js"></script>
 <script type="text/javascript" src="index.js"></script>
 </body>
 </html>

+ 193 - 237
apps/html2markdown/index.js

@@ -1,56 +1,200 @@
 /**
  * FeHelper HTML转Markdown
  */
+
+
+let editor = null;
+let hashtoTimeoutId;
+let previewElm;
+let previewTextArea;
+
 new Vue({
     el: '#pageContainer',
     data: {
-        sourceContent: '',
-        resultContent: '',
-        previewHTML: '',
         showPreview: false,
         previewText: '效果预览',
-        codeType: 'HTML',
-        nextCodeType: 'Markdown',
+        codeType: 'Markdown',
+        nextCodeType: 'HTML',
         toolName: {
             HTML: 'HTML转Markdown',
-            Markdown: 'Markdown转HTML'
+            Markdown: 'Markdown编辑器'
         }
     },
 
     mounted: function () {
-        this.$refs.srcText.focus();
+        this.init();
     },
     methods: {
-        convert: function () {
-            let h2m = Tarp.require('../static/vendor/h2m/h2m');
-            let markdown = Tarp.require('../static/vendor/h2m/markdown');
+
+        trans: function () {
+            editor.setValue('');
+
+            this.codeType = {HTML: 'Markdown', Markdown: 'HTML'}[this.codeType];
+            this.nextCodeType = {HTML: 'Markdown', Markdown: 'HTML'}[this.nextCodeType];
+
+            let classList = this.$refs.modMarkdownBox.classList;
             if (this.codeType === 'HTML') {
-                this.resultContent = h2m(this.sourceContent, {
-                    converter: 'CommonMark' // CommonMark | MarkdownExtra
-                });
-                this.previewHTML = markdown.toHTML(this.resultContent);
+                classList.add('mode-h2m');
+                previewElm.innerHTML = `<textarea disabled></textarea>`;
+                previewTextArea = previewElm.querySelector('textarea');
             } else {
-                this.resultContent = this.previewHTML = markdown.toHTML(this.sourceContent);
+                classList.remove('mode-h2m');
+                previewElm.innerHTML = '';
             }
         },
 
-        preview: function (event) {
-            event && event.preventDefault();
-            this.showPreview = !this.showPreview;
-            this.previewText = this.showPreview ? '查看源码' : '效果预览';
+        /**
+         * 初始化ctrl+s的保存
+         */
+        init() {
+
+            previewElm = this.$refs.boxPreview;
+
+            // ===========================editor初始化
+            editor = CodeMirror.fromTextArea(this.$refs.elEditor, {
+                mode: "gfm",
+                lineNumbers: true,
+                matchBrackets: true,
+                lineWrapping: true,
+                theme: 'default'
+            });
+            editor.on('change', this.updateHashAndPreview);
+
+            // ===========================支持save-as
+            window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
+            navigator.saveBlob = navigator.saveBlob || navigator.msSaveBlob || navigator.mozSaveBlob || navigator.webkitSaveBlob;
+            window.saveAs = window.saveAs || window.webkitSaveAs || window.mozSaveAs || window.msSaveAs;
+
+            document.addEventListener('keydown', (e) => {
+                if (e.keyCode === 83 && (e.ctrlKey || e.metaKey)) {
+                    e.preventDefault();
+                    this.saveMarkdown('md');
+                    return false;
+                }
+            });
+
+            // ===========================支持页面拖拽识别
+            document.addEventListener('drop', function (e) {
+                e.preventDefault();
+                e.stopPropagation();
+
+                let theFile = e.dataTransfer.files[0];
+                let theReader = new FileReader();
+                theReader.onload = function (e) {
+                    editor.setValue(e.target.result);
+                };
+
+                theReader.readAsText(theFile);
+            }, false);
+
+            this.initWithHash();
         },
 
-        trans: function () {
-            this.codeType = {HTML: 'Markdown', Markdown: 'HTML'}[this.codeType];
-            this.nextCodeType = {HTML: 'Markdown', Markdown: 'HTML'}[this.nextCodeType];
-            this.preview();
-            this.clear();
+        /**
+         * 根据Hash进行更新编辑器
+         */
+        initWithHash() {
+            if (this.codeType === 'HTML') return;
+
+            if (window.location.hash) {
+                let h = window.location.hash.replace(/^#/, '');
+                if (h.slice(0, 5) === 'view:') {
+                    let val = decodeURIComponent(escape(RawDeflate.inflate(atob(h.slice(5)))));
+                    previewElm.innerHTML = marked(val);
+                    previewElm.querySelectorAll('pre code').forEach((block) => {
+                        hljs.highlightBlock(block);
+                    });
+                    document.body.className = 'view';
+                } else {
+                    editor.setValue(decodeURIComponent(escape(RawDeflate.inflate(atob(h)))));
+                    this.updateHashAndPreview(editor);
+                    editor.focus();
+                }
+            } else {
+                this.updateHashAndPreview(editor);
+                editor.focus();
+            }
         },
 
-        clear: function () {
-            this.sourceContent = '';
-            this.resultContent = '';
-            this.resultContent = false;
+        /**
+         * 更新预览区域
+         */
+        updateHashAndPreview() {
+            try {
+                if (this.codeType === 'HTML') {
+                    previewTextArea.value = h2m(editor.getValue(), {
+                        converter: 'CommonMark' // CommonMark | MarkdownExtra
+                    });
+                } else {
+                    previewElm.innerHTML = marked(editor.getValue());
+                    previewElm.querySelectorAll('pre code').forEach((block) => {
+                        hljs.highlightBlock(block);
+                    });
+                    clearTimeout(hashtoTimeoutId);
+                    hashtoTimeoutId = setTimeout(function () {
+                        window.location.hash = btoa(RawDeflate.deflate(unescape(encodeURIComponent(editor.getValue()))))
+                    }, 1000);
+                }
+            } catch (e) {
+                console.log(e);
+            }
+        },
+
+        saveMarkdown(type) {
+
+            let date = new Date();
+            let name = "FH-" + date.getFullYear() + (date.getMonth() + 1) + date.getDate()
+                + date.getHours() + date.getMinutes() + date.getSeconds() + `.${type}`;
+
+            let code = editor.getValue();
+            if (this.codeType === 'HTML') {
+                if (type !== 'html') {
+                    code = previewTextArea.value;
+                }
+            } else {
+                if (type === 'html') {
+                    code = DemoTpl.exportHtml.replace('#title#', name).replace('#style#', DemoTpl.exportCss).replace('#html#', this.getParsedHtml());
+                }
+            }
+
+            let blob = new Blob([code], {type: type === 'md' ? 'text/plain' : 'text/html'});
+
+            if (window.saveAs) {
+                window.saveAs(blob, name);
+            } else if (navigator.saveBlob) {
+                navigator.saveBlob(blob, name);
+            } else {
+                let url = URL.createObjectURL(blob);
+                let link = document.createElement("a");
+                link.setAttribute("href", url);
+                link.setAttribute("download", name);
+                let event = document.createEvent('MouseEvents');
+                event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
+                link.dispatchEvent(event);
+            }
+        },
+
+        /**
+         * 获取编译后的
+         * @returns {*}
+         */
+        getParsedHtml() {
+            return previewElm.innerHTML;
+        },
+
+        insert(type) {
+            let textConfig = {
+                b: '** text-here **',
+                i: '* text-here *',
+                quote: '\n> text-here ',
+                code: '\n```javascript\n\n\n```\n',
+                'unordered-list': '\n\n- text-here\n- text-here\n- text-here\n',
+                'ordered-list': '\n\n1. text-here\n2. text-here\n3. text-here\n',
+                link: '\n[text-here](your-link-url)',
+                image: '\n![text-here](your-image-src)'
+            };
+            editor.replaceSelection(textConfig[type] || '');
+            editor.focus();
         },
 
         getResult: function () {
@@ -58,26 +202,7 @@ new Vue({
         },
 
         setDemo: function () {
-            if (this.codeType === 'HTML') {
-                this.sourceContent = this.$refs.htmlDemo.innerHTML;
-            } else {
-                this.sourceContent = '## FE助手\n' +
-                    '\n' +
-                    '- 字符串编解码\n' +
-                    '- `Json`串格式化\n' +
-                    '- 代码美化工具\n' +
-                    '- 代码压缩工具\n' +
-                    '- 二维码生成器\n' +
-                    '- 页面取色工具\n' +
-                    '- Js正则表达式\n' +
-                    '- 时间(戳)转换\n' +
-                    '- 图片Base64\n' +
-                    '- 编码规范检测\n' +
-                    '- 页面性能检测\n' +
-                    '- Html转`Markdown`\n' +
-                    '- Ajax调试:**关**';
-            }
-            this.convert();
+            editor.setValue(DemoTpl[this.codeType.toLowerCase()]);
         },
 
         // 导入内容
@@ -94,8 +219,7 @@ new Vue({
                     let reader = new FileReader();
                     reader.readAsText(fileInput.files[0], 'utf-8');
                     reader.onload = (evt) => {
-                        that.sourceContent = evt.target.result;
-                        that.convert();
+                        editor.setValue(evt.target.result);
                         document.body.removeChild(fileInput);
                     };
                 };
@@ -104,197 +228,29 @@ new Vue({
             fileInput.click();
         },
 
-        // 通过调用系统打印的形式,打印为pdf
-        exportContent: function () {
-            let css = `@page {
-   size: 5.5in 8.5in;  
-   margin: 70pt 60pt 70pt;
-}
-
-@page:first {
-   size: 5.5in 8.5in;  
-   margin: 0;
-}
-
-img {
-  max-width: 100%;
-}
-
-div.frontcover { 
-  page: cover; 
-  content: url("images/cover.png");
-  width: 100%;
-  height: 100%; 
-}
-
-
-
-/* styles for the right hand spread
-Bottom left we display the title of the book, bottom right the page using a CSS counter, top right the content of the current chapter */
-@page:right{ 
-   @bottom-left {
-       margin: 10pt 0 30pt 0;
-       border-top: .25pt solid #666;
-	   content: "Our Cats";
-       font-size: 9pt;
-       color: #333;
-   }
-   @bottom-right { 
-       margin: 10pt 0 30pt 0;
-       border-top: .25pt solid #666;
-       content: counter(page);
-       font-size: 9pt;
-   }
-   @top-right {
-   	   content:  string(doctitle);
-   	   margin: 30pt 0 10pt 0;
-   	   font-size: 9pt;
-   	   color: #333;
-   }
-}
-
-/* styles for the left hand spread 
-Bottom right book title, bottom left current page */
-@page:left {
-
-   @bottom-right {
-       margin: 10pt 0 30pt 0;
-       border-top: .25pt solid #666;
-	     content: "Our Cats";
-       font-size: 9pt;
-       color: #333;
-   }
-   @bottom-left { 
-       margin: 10pt 0 30pt 0;
-       border-top: .25pt solid #666;
-       content: counter(page);
-       font-size: 9pt;
-   }
-}
-
-/* first page */
-@page:first {
-  @bottom-right {
-    content: normal;
-    margin: 0;
-  }
-
-  @bottom-left {
-    content: normal;
-    margin: 0;
-  }
-}
-
-/* reset chapter and figure counters on the body */
-body {
-	counter-reset: chapternum figurenum;
-	font-family: "Trebuchet MS", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Tahoma, sans-serif;
-	line-height: 1.5;
-	font-size: 11pt;
-}
-
-/* get the title of the current chapter - this will be the content of the h1 
-reset figure counter as figures start from 1 in each chapter */
-h1 {
-    string-set: doctitle content();
-    page-break-before: always;
-    counter-reset: figurenum;
-    counter-reset: footnote;
-    line-height: 1.3;
-}
-
-/* increment chapter counter */
-h1.chapter:before {
-    counter-increment: chapternum;
-    content: counter(chapternum) ". ";
-}
-
-/* increment and display figure counter */
-figcaption:before {
-	counter-increment: figurenum;
-	content: counter(chapternum) "-" counter(figurenum) ". ";
-}
-
-/* footnotes */
-.fn {
-  float: footnote;
-}
-
-.fn {
-  counter-increment: footnote;
-}
-
-.fn::footnote-call {
-  content: counter(footnote);
-  font-size: 9pt;
-  vertical-align: super;
-  line-height: none;
-}
-
-.fn::footnote-marker {
-  font-weight: bold;
-}
-
-@page {
-  @footnotes {
-    border-top: 0.6pt solid black;
-    padding-top: 8pt;
-  }
-}
-
-h1,h2,h3,h4,h5 {
-	font-weight: bold;
-	page-break-after: avoid;
-	page-break-inside:avoid;
-}
-
-h1+p, h2+p, h3+p {
-	page-break-before: avoid;
-}
-
-table, figure {
-	page-break-inside: avoid;
-}
-
-ul.toc {
-	list-style: none;
-	margin: 0;
-	padding: 0;
-}
-
-/* create page numbers using target-counter in the TOC */
-ul.toc a::after {
-  content: leader('.') target-counter(attr(href), page);
-}
-
-ul.toc li {
-	line-height: 2;
-}
-
-ul.toc li a {
-	text-decoration: none;
-}
-
-a {
-	color: #000;
-}
+        togglePreview() {
+            let classList = this.$refs.modMarkdownBox.classList;
+            let closeClass = 'preview-closed';
+            if (classList.contains(closeClass)) {
+                classList.remove(closeClass);
+            } else {
+                classList.add(closeClass);
+            }
+        },
 
-/* add page number to cross references */
-a.xref:after {
-  content: " (page " target-counter(attr(href, url), page) ")";
-}
-`;
+        // 通过调用系统打印的形式,打印为pdf
+        exportContent: function (previewMode) {
             let newContent = "<html><head><meta charset='utf-8'/><title></title>" +
-                "<style>" + css + "</style>" +
-                "</head><body>" +
-                this.previewHTML + "</body></html>";
+                "<style>" + DemoTpl.printCss + "</style>" +
+                "</head><body class='markdown-body'>" + this.getParsedHtml() + "</body></html>";
             let newWin = window.open();
             newWin.focus();
             newWin.document.write(newContent);
-            newWin.print();
-            newWin.document.close();
-            newWin.close();
+            if (!previewMode) {
+                newWin.print();
+                newWin.document.close();
+                newWin.close();
+            }
         }
     }
-})
-;
+});

+ 1072 - 0
apps/html2markdown/libs/marked.js

@@ -0,0 +1,1072 @@
+/**
+ * marked - a markdown parser
+ * Copyright (c) 2011-2013, Christopher Jeffrey. (MIT Licensed)
+ * https://github.com/chjj/marked
+ */
+
+;(function() {
+
+/**
+ * Block-Level Grammar
+ */
+
+var block = {
+  newline: /^\n+/,
+  code: /^( {4}[^\n]+\n*)+/,
+  fences: noop,
+  hr: /^( *[-*_]){3,} *(?:\n+|$)/,
+  heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
+  nptable: noop,
+  lheading: /^([^\n]+)\n *(=|-){3,} *\n*/,
+  blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/,
+  list: /^( *)(bull) [\s\S]+?(?:hr|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
+  html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/,
+  def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
+  table: noop,
+  paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
+  text: /^[^\n]+/
+};
+
+block.bullet = /(?:[*+-]|\d+\.)/;
+block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
+block.item = replace(block.item, 'gm')
+  (/bull/g, block.bullet)
+  ();
+
+block.list = replace(block.list)
+  (/bull/g, block.bullet)
+  ('hr', /\n+(?=(?: *[-*_]){3,} *(?:\n+|$))/)
+  ();
+
+block._tag = '(?!(?:'
+  + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code'
+  + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo'
+  + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|@)\\b';
+
+block.html = replace(block.html)
+  ('comment', /<!--[\s\S]*?-->/)
+  ('closed', /<(tag)[\s\S]+?<\/\1>/)
+  ('closing', /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)
+  (/tag/g, block._tag)
+  ();
+
+block.paragraph = replace(block.paragraph)
+  ('hr', block.hr)
+  ('heading', block.heading)
+  ('lheading', block.lheading)
+  ('blockquote', block.blockquote)
+  ('tag', '<' + block._tag)
+  ('def', block.def)
+  ();
+
+/**
+ * Normal Block Grammar
+ */
+
+block.normal = merge({}, block);
+
+/**
+ * GFM Block Grammar
+ */
+
+block.gfm = merge({}, block.normal, {
+  fences: /^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/,
+  paragraph: /^/
+});
+
+block.gfm.paragraph = replace(block.paragraph)
+  ('(?!', '(?!' + block.gfm.fences.source.replace('\\1', '\\2') + '|')
+  ();
+
+/**
+ * GFM + Tables Block Grammar
+ */
+
+block.tables = merge({}, block.gfm, {
+  nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
+  table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/
+});
+
+/**
+ * Block Lexer
+ */
+
+function Lexer(options) {
+  this.tokens = [];
+  this.tokens.links = {};
+  this.options = options || marked.defaults;
+  this.rules = block.normal;
+
+  if (this.options.gfm) {
+    if (this.options.tables) {
+      this.rules = block.tables;
+    } else {
+      this.rules = block.gfm;
+    }
+  }
+}
+
+/**
+ * Expose Block Rules
+ */
+
+Lexer.rules = block;
+
+/**
+ * Static Lex Method
+ */
+
+Lexer.lex = function(src, options) {
+  var lexer = new Lexer(options);
+  return lexer.lex(src);
+};
+
+/**
+ * Preprocessing
+ */
+
+Lexer.prototype.lex = function(src) {
+  src = src
+    .replace(/\r\n|\r/g, '\n')
+    .replace(/\t/g, '    ')
+    .replace(/\u00a0/g, ' ')
+    .replace(/\u2424/g, '\n');
+
+  return this.token(src, true);
+};
+
+/**
+ * Lexing
+ */
+
+Lexer.prototype.token = function(src, top) {
+  var src = src.replace(/^ +$/gm, '')
+    , next
+    , loose
+    , cap
+    , bull
+    , b
+    , item
+    , space
+    , i
+    , l;
+
+  while (src) {
+    // newline
+    if (cap = this.rules.newline.exec(src)) {
+      src = src.substring(cap[0].length);
+      if (cap[0].length > 1) {
+        this.tokens.push({
+          type: 'space'
+        });
+      }
+    }
+
+    // code
+    if (cap = this.rules.code.exec(src)) {
+      src = src.substring(cap[0].length);
+      cap = cap[0].replace(/^ {4}/gm, '');
+      this.tokens.push({
+        type: 'code',
+        text: !this.options.pedantic
+          ? cap.replace(/\n+$/, '')
+          : cap
+      });
+      continue;
+    }
+
+    // fences (gfm)
+    if (cap = this.rules.fences.exec(src)) {
+      src = src.substring(cap[0].length);
+      this.tokens.push({
+        type: 'code',
+        lang: cap[2],
+        text: cap[3]
+      });
+      continue;
+    }
+
+    // heading
+    if (cap = this.rules.heading.exec(src)) {
+      src = src.substring(cap[0].length);
+      this.tokens.push({
+        type: 'heading',
+        depth: cap[1].length,
+        text: cap[2]
+      });
+      continue;
+    }
+
+    // table no leading pipe (gfm)
+    if (top && (cap = this.rules.nptable.exec(src))) {
+      src = src.substring(cap[0].length);
+
+      item = {
+        type: 'table',
+        header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
+        align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
+        cells: cap[3].replace(/\n$/, '').split('\n')
+      };
+
+      for (i = 0; i < item.align.length; i++) {
+        if (/^ *-+: *$/.test(item.align[i])) {
+          item.align[i] = 'right';
+        } else if (/^ *:-+: *$/.test(item.align[i])) {
+          item.align[i] = 'center';
+        } else if (/^ *:-+ *$/.test(item.align[i])) {
+          item.align[i] = 'left';
+        } else {
+          item.align[i] = null;
+        }
+      }
+
+      for (i = 0; i < item.cells.length; i++) {
+        item.cells[i] = item.cells[i].split(/ *\| */);
+      }
+
+      this.tokens.push(item);
+
+      continue;
+    }
+
+    // lheading
+    if (cap = this.rules.lheading.exec(src)) {
+      src = src.substring(cap[0].length);
+      this.tokens.push({
+        type: 'heading',
+        depth: cap[2] === '=' ? 1 : 2,
+        text: cap[1]
+      });
+      continue;
+    }
+
+    // hr
+    if (cap = this.rules.hr.exec(src)) {
+      src = src.substring(cap[0].length);
+      this.tokens.push({
+        type: 'hr'
+      });
+      continue;
+    }
+
+    // blockquote
+    if (cap = this.rules.blockquote.exec(src)) {
+      src = src.substring(cap[0].length);
+
+      this.tokens.push({
+        type: 'blockquote_start'
+      });
+
+      cap = cap[0].replace(/^ *> ?/gm, '');
+
+      // Pass `top` to keep the current
+      // "toplevel" state. This is exactly
+      // how markdown.pl works.
+      this.token(cap, top);
+
+      this.tokens.push({
+        type: 'blockquote_end'
+      });
+
+      continue;
+    }
+
+    // list
+    if (cap = this.rules.list.exec(src)) {
+      src = src.substring(cap[0].length);
+      bull = cap[2];
+
+      this.tokens.push({
+        type: 'list_start',
+        ordered: bull.length > 1
+      });
+
+      // Get each top-level item.
+      cap = cap[0].match(this.rules.item);
+
+      next = false;
+      l = cap.length;
+      i = 0;
+
+      for (; i < l; i++) {
+        item = cap[i];
+
+        // Remove the list item's bullet
+        // so it is seen as the next token.
+        space = item.length;
+        item = item.replace(/^ *([*+-]|\d+\.) +/, '');
+
+        // Outdent whatever the
+        // list item contains. Hacky.
+        if (~item.indexOf('\n ')) {
+          space -= item.length;
+          item = !this.options.pedantic
+            ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
+            : item.replace(/^ {1,4}/gm, '');
+        }
+
+        // Determine whether the next list item belongs here.
+        // Backpedal if it does not belong in this list.
+        if (this.options.smartLists && i !== l - 1) {
+          b = block.bullet.exec(cap[i+1])[0];
+          if (bull !== b && !(bull.length > 1 && b.length > 1)) {
+            src = cap.slice(i + 1).join('\n') + src;
+            i = l - 1;
+          }
+        }
+
+        // Determine whether item is loose or not.
+        // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
+        // for discount behavior.
+        loose = next || /\n\n(?!\s*$)/.test(item);
+        if (i !== l - 1) {
+          next = item[item.length-1] === '\n';
+          if (!loose) loose = next;
+        }
+
+        this.tokens.push({
+          type: loose
+            ? 'loose_item_start'
+            : 'list_item_start'
+        });
+
+        // Recurse.
+        this.token(item, false);
+
+        this.tokens.push({
+          type: 'list_item_end'
+        });
+      }
+
+      this.tokens.push({
+        type: 'list_end'
+      });
+
+      continue;
+    }
+
+    // html
+    if (cap = this.rules.html.exec(src)) {
+      src = src.substring(cap[0].length);
+      this.tokens.push({
+        type: this.options.sanitize
+          ? 'paragraph'
+          : 'html',
+        pre: cap[1] === 'pre',
+        text: cap[0]
+      });
+      continue;
+    }
+
+    // def
+    if (top && (cap = this.rules.def.exec(src))) {
+      src = src.substring(cap[0].length);
+      this.tokens.links[cap[1].toLowerCase()] = {
+        href: cap[2],
+        title: cap[3]
+      };
+      continue;
+    }
+
+    // table (gfm)
+    if (top && (cap = this.rules.table.exec(src))) {
+      src = src.substring(cap[0].length);
+
+      item = {
+        type: 'table',
+        header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
+        align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
+        cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n')
+      };
+
+      for (i = 0; i < item.align.length; i++) {
+        if (/^ *-+: *$/.test(item.align[i])) {
+          item.align[i] = 'right';
+        } else if (/^ *:-+: *$/.test(item.align[i])) {
+          item.align[i] = 'center';
+        } else if (/^ *:-+ *$/.test(item.align[i])) {
+          item.align[i] = 'left';
+        } else {
+          item.align[i] = null;
+        }
+      }
+
+      for (i = 0; i < item.cells.length; i++) {
+        item.cells[i] = item.cells[i]
+          .replace(/^ *\| *| *\| *$/g, '')
+          .split(/ *\| */);
+      }
+
+      this.tokens.push(item);
+
+      continue;
+    }
+
+    // top-level paragraph
+    if (top && (cap = this.rules.paragraph.exec(src))) {
+      src = src.substring(cap[0].length);
+      this.tokens.push({
+        type: 'paragraph',
+        text: cap[1][cap[1].length-1] === '\n'
+          ? cap[1].slice(0, -1)
+          : cap[1]
+      });
+      continue;
+    }
+
+    // text
+    if (cap = this.rules.text.exec(src)) {
+      // Top-level should never reach here.
+      src = src.substring(cap[0].length);
+      this.tokens.push({
+        type: 'text',
+        text: cap[0]
+      });
+      continue;
+    }
+
+    if (src) {
+      throw new
+        Error('Infinite loop on byte: ' + src.charCodeAt(0));
+    }
+  }
+
+  return this.tokens;
+};
+
+/**
+ * Inline-Level Grammar
+ */
+
+var inline = {
+  escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
+  autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
+  url: noop,
+  tag: /^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
+  link: /^!?\[(inside)\]\(href\)/,
+  reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
+  nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
+  strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,
+  em: /^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
+  code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,
+  br: /^ {2,}\n(?!\s*$)/,
+  del: noop,
+  text: /^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/
+};
+
+inline._inside = /(?:\[[^\]]*\]|[^\]]|\](?=[^\[]*\]))*/;
+inline._href = /\s*<?([^\s]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/;
+
+inline.link = replace(inline.link)
+  ('inside', inline._inside)
+  ('href', inline._href)
+  ();
+
+inline.reflink = replace(inline.reflink)
+  ('inside', inline._inside)
+  ();
+
+/**
+ * Normal Inline Grammar
+ */
+
+inline.normal = merge({}, inline);
+
+/**
+ * Pedantic Inline Grammar
+ */
+
+inline.pedantic = merge({}, inline.normal, {
+  strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
+  em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/
+});
+
+/**
+ * GFM Inline Grammar
+ */
+
+inline.gfm = merge({}, inline.normal, {
+  escape: replace(inline.escape)('])', '~|])')(),
+  url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,
+  del: /^~~(?=\S)([\s\S]*?\S)~~/,
+  text: replace(inline.text)
+    (']|', '~]|')
+    ('|', '|https?://|')
+    ()
+});
+
+/**
+ * GFM + Line Breaks Inline Grammar
+ */
+
+inline.breaks = merge({}, inline.gfm, {
+  br: replace(inline.br)('{2,}', '*')(),
+  text: replace(inline.gfm.text)('{2,}', '*')()
+});
+
+/**
+ * Inline Lexer & Compiler
+ */
+
+function InlineLexer(links, options) {
+  this.options = options || marked.defaults;
+  this.links = links;
+  this.rules = inline.normal;
+
+  if (!this.links) {
+    throw new
+      Error('Tokens array requires a `links` property.');
+  }
+
+  if (this.options.gfm) {
+    if (this.options.breaks) {
+      this.rules = inline.breaks;
+    } else {
+      this.rules = inline.gfm;
+    }
+  } else if (this.options.pedantic) {
+    this.rules = inline.pedantic;
+  }
+}
+
+/**
+ * Expose Inline Rules
+ */
+
+InlineLexer.rules = inline;
+
+/**
+ * Static Lexing/Compiling Method
+ */
+
+InlineLexer.output = function(src, links, options) {
+  var inline = new InlineLexer(links, options);
+  return inline.output(src);
+};
+
+/**
+ * Lexing/Compiling
+ */
+
+InlineLexer.prototype.output = function(src) {
+  var out = ''
+    , link
+    , text
+    , href
+    , cap;
+
+  while (src) {
+    // escape
+    if (cap = this.rules.escape.exec(src)) {
+      src = src.substring(cap[0].length);
+      out += cap[1];
+      continue;
+    }
+
+    // autolink
+    if (cap = this.rules.autolink.exec(src)) {
+      src = src.substring(cap[0].length);
+      if (cap[2] === '@') {
+        text = cap[1][6] === ':'
+          ? this.mangle(cap[1].substring(7))
+          : this.mangle(cap[1]);
+        href = this.mangle('mailto:') + text;
+      } else {
+        text = escape(cap[1]);
+        href = text;
+      }
+      out += '<a href="'
+        + href
+        + '">'
+        + text
+        + '</a>';
+      continue;
+    }
+
+    // url (gfm)
+    if (cap = this.rules.url.exec(src)) {
+      src = src.substring(cap[0].length);
+      text = escape(cap[1]);
+      href = text;
+      out += '<a href="'
+        + href
+        + '">'
+        + text
+        + '</a>';
+      continue;
+    }
+
+    // tag
+    if (cap = this.rules.tag.exec(src)) {
+      src = src.substring(cap[0].length);
+      out += this.options.sanitize
+        ? escape(cap[0])
+        : cap[0];
+      continue;
+    }
+
+    // link
+    if (cap = this.rules.link.exec(src)) {
+      src = src.substring(cap[0].length);
+      out += this.outputLink(cap, {
+        href: cap[2],
+        title: cap[3]
+      });
+      continue;
+    }
+
+    // reflink, nolink
+    if ((cap = this.rules.reflink.exec(src))
+        || (cap = this.rules.nolink.exec(src))) {
+      src = src.substring(cap[0].length);
+      link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
+      link = this.links[link.toLowerCase()];
+      if (!link || !link.href) {
+        out += cap[0][0];
+        src = cap[0].substring(1) + src;
+        continue;
+      }
+      out += this.outputLink(cap, link);
+      continue;
+    }
+
+    // strong
+    if (cap = this.rules.strong.exec(src)) {
+      src = src.substring(cap[0].length);
+      out += '<strong>'
+        + this.output(cap[2] || cap[1])
+        + '</strong>';
+      continue;
+    }
+
+    // em
+    if (cap = this.rules.em.exec(src)) {
+      src = src.substring(cap[0].length);
+      out += '<em>'
+        + this.output(cap[2] || cap[1])
+        + '</em>';
+      continue;
+    }
+
+    // code
+    if (cap = this.rules.code.exec(src)) {
+      src = src.substring(cap[0].length);
+      out += '<code>'
+        + escape(cap[2], true)
+        + '</code>';
+      continue;
+    }
+
+    // br
+    if (cap = this.rules.br.exec(src)) {
+      src = src.substring(cap[0].length);
+      out += '<br>';
+      continue;
+    }
+
+    // del (gfm)
+    if (cap = this.rules.del.exec(src)) {
+      src = src.substring(cap[0].length);
+      out += '<del>'
+        + this.output(cap[1])
+        + '</del>';
+      continue;
+    }
+
+    // text
+    if (cap = this.rules.text.exec(src)) {
+      src = src.substring(cap[0].length);
+      out += escape(cap[0]);
+      continue;
+    }
+
+    if (src) {
+      throw new
+        Error('Infinite loop on byte: ' + src.charCodeAt(0));
+    }
+  }
+
+  return out;
+};
+
+/**
+ * Compile Link
+ */
+
+InlineLexer.prototype.outputLink = function(cap, link) {
+  if (cap[0][0] !== '!') {
+    return '<a href="'
+      + escape(link.href)
+      + '"'
+      + (link.title
+      ? ' title="'
+      + escape(link.title)
+      + '"'
+      : '')
+      + '>'
+      + this.output(cap[1])
+      + '</a>';
+  } else {
+    return '<img src="'
+      + escape(link.href)
+      + '" alt="'
+      + escape(cap[1])
+      + '"'
+      + (link.title
+      ? ' title="'
+      + escape(link.title)
+      + '"'
+      : '')
+      + '>';
+  }
+};
+
+/**
+ * Mangle Links
+ */
+
+InlineLexer.prototype.mangle = function(text) {
+  var out = ''
+    , l = text.length
+    , i = 0
+    , ch;
+
+  for (; i < l; i++) {
+    ch = text.charCodeAt(i);
+    if (Math.random() > 0.5) {
+      ch = 'x' + ch.toString(16);
+    }
+    out += '&#' + ch + ';';
+  }
+
+  return out;
+};
+
+/**
+ * Parsing & Compiling
+ */
+
+function Parser(options) {
+  this.tokens = [];
+  this.token = null;
+  this.options = options || marked.defaults;
+}
+
+/**
+ * Static Parse Method
+ */
+
+Parser.parse = function(src, options) {
+  var parser = new Parser(options);
+  return parser.parse(src);
+};
+
+/**
+ * Parse Loop
+ */
+
+Parser.prototype.parse = function(src) {
+  this.inline = new InlineLexer(src.links, this.options);
+  this.tokens = src.reverse();
+
+  var out = '';
+  while (this.next()) {
+    out += this.tok();
+  }
+
+  return out;
+};
+
+/**
+ * Next Token
+ */
+
+Parser.prototype.next = function() {
+  return this.token = this.tokens.pop();
+};
+
+/**
+ * Preview Next Token
+ */
+
+Parser.prototype.peek = function() {
+  return this.tokens[this.tokens.length-1] || 0;
+};
+
+/**
+ * Parse Text Tokens
+ */
+
+Parser.prototype.parseText = function() {
+  var body = this.token.text;
+
+  while (this.peek().type === 'text') {
+    body += '\n' + this.next().text;
+  }
+
+  return this.inline.output(body);
+};
+
+/**
+ * Parse Current Token
+ */
+
+Parser.prototype.tok = function() {
+  switch (this.token.type) {
+    case 'space': {
+      return '';
+    }
+    case 'hr': {
+      return '<hr>\n';
+    }
+    case 'heading': {
+      return '<h'
+        + this.token.depth
+        + '>'
+        + this.inline.output(this.token.text)
+        + '</h'
+        + this.token.depth
+        + '>\n';
+    }
+    case 'code': {
+      if (this.options.highlight) {
+        var code = this.options.highlight(this.token.text, this.token.lang);
+        if (code != null && code !== this.token.text) {
+          this.token.escaped = true;
+          this.token.text = code;
+        }
+      }
+
+      if (!this.token.escaped) {
+        this.token.text = escape(this.token.text, true);
+      }
+
+      return '<pre><code'
+        + (this.token.lang
+        ? ' class="'
+        + this.options.langPrefix
+        + this.token.lang
+        + '"'
+        : '')
+        + '>'
+        + this.token.text
+        + '</code></pre>\n';
+    }
+    case 'table': {
+      var body = ''
+        , heading
+        , i
+        , row
+        , cell
+        , j;
+
+      // header
+      body += '<thead>\n<tr>\n';
+      for (i = 0; i < this.token.header.length; i++) {
+        heading = this.inline.output(this.token.header[i]);
+        body += this.token.align[i]
+          ? '<th align="' + this.token.align[i] + '">' + heading + '</th>\n'
+          : '<th>' + heading + '</th>\n';
+      }
+      body += '</tr>\n</thead>\n';
+
+      // body
+      body += '<tbody>\n'
+      for (i = 0; i < this.token.cells.length; i++) {
+        row = this.token.cells[i];
+        body += '<tr>\n';
+        for (j = 0; j < row.length; j++) {
+          cell = this.inline.output(row[j]);
+          body += this.token.align[j]
+            ? '<td align="' + this.token.align[j] + '">' + cell + '</td>\n'
+            : '<td>' + cell + '</td>\n';
+        }
+        body += '</tr>\n';
+      }
+      body += '</tbody>\n';
+
+      return '<table>\n'
+        + body
+        + '</table>\n';
+    }
+    case 'blockquote_start': {
+      var body = '';
+
+      while (this.next().type !== 'blockquote_end') {
+        body += this.tok();
+      }
+
+      return '<blockquote>\n'
+        + body
+        + '</blockquote>\n';
+    }
+    case 'list_start': {
+      var type = this.token.ordered ? 'ol' : 'ul'
+        , body = '';
+
+      while (this.next().type !== 'list_end') {
+        body += this.tok();
+      }
+
+      return '<'
+        + type
+        + '>\n'
+        + body
+        + '</'
+        + type
+        + '>\n';
+    }
+    case 'list_item_start': {
+      var body = '';
+
+      while (this.next().type !== 'list_item_end') {
+        body += this.token.type === 'text'
+          ? this.parseText()
+          : this.tok();
+      }
+
+      return '<li>'
+        + body
+        + '</li>\n';
+    }
+    case 'loose_item_start': {
+      var body = '';
+
+      while (this.next().type !== 'list_item_end') {
+        body += this.tok();
+      }
+
+      return '<li>'
+        + body
+        + '</li>\n';
+    }
+    case 'html': {
+      return !this.token.pre && !this.options.pedantic
+        ? this.inline.output(this.token.text)
+        : this.token.text;
+    }
+    case 'paragraph': {
+      return '<p>'
+        + this.inline.output(this.token.text)
+        + '</p>\n';
+    }
+    case 'text': {
+      return '<p>'
+        + this.parseText()
+        + '</p>\n';
+    }
+  }
+};
+
+/**
+ * Helpers
+ */
+
+function escape(html, encode) {
+  return html
+    .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
+    .replace(/</g, '&lt;')
+    .replace(/>/g, '&gt;')
+    .replace(/"/g, '&quot;')
+    .replace(/'/g, '&#39;');
+}
+
+function replace(regex, opt) {
+  regex = regex.source;
+  opt = opt || '';
+  return function self(name, val) {
+    if (!name) return new RegExp(regex, opt);
+    val = val.source || val;
+    val = val.replace(/(^|[^\[])\^/g, '$1');
+    regex = regex.replace(name, val);
+    return self;
+  };
+}
+
+function noop() {}
+noop.exec = noop;
+
+function merge(obj) {
+  var i = 1
+    , target
+    , key;
+
+  for (; i < arguments.length; i++) {
+    target = arguments[i];
+    for (key in target) {
+      if (Object.prototype.hasOwnProperty.call(target, key)) {
+        obj[key] = target[key];
+      }
+    }
+  }
+
+  return obj;
+}
+
+/**
+ * Marked
+ */
+
+function marked(src, opt) {
+  try {
+    if (opt) opt = merge({}, marked.defaults, opt);
+    return Parser.parse(Lexer.lex(src, opt), opt);
+  } catch (e) {
+    e.message += '\nPlease report this to https://github.com/chjj/marked.';
+    if ((opt || marked.defaults).silent) {
+      return '<p>An error occured:</p><pre>'
+        + escape(e.message + '', true)
+        + '</pre>';
+    }
+    throw e;
+  }
+}
+
+/**
+ * Options
+ */
+
+marked.options =
+marked.setOptions = function(opt) {
+  merge(marked.defaults, opt);
+  return marked;
+};
+
+marked.defaults = {
+  gfm: true,
+  tables: true,
+  breaks: false,
+  pedantic: false,
+  sanitize: false,
+  smartLists: false,
+  silent: false,
+  highlight: null,
+  langPrefix: 'lang-'
+};
+
+/**
+ * Expose
+ */
+
+marked.Parser = Parser;
+marked.parser = Parser.parse;
+
+marked.Lexer = Lexer;
+marked.lexer = Lexer.lex;
+
+marked.InlineLexer = InlineLexer;
+marked.inlineLexer = InlineLexer.output;
+
+marked.parse = marked;
+
+if (typeof exports === 'object') {
+  module.exports = marked;
+} else if (typeof define === 'function' && define.amd) {
+  define(function() { return marked; });
+} else {
+  this.marked = marked;
+}
+
+}).call(function() {
+  return this || (typeof window !== 'undefined' ? window : global);
+}());

+ 1671 - 0
apps/html2markdown/libs/rawdeflate.js

@@ -0,0 +1,1671 @@
+/*
+ * $Id: rawdeflate.js,v 0.3 2009/03/01 19:05:05 dankogai Exp dankogai $
+ *
+ * Original:
+ *   http://www.onicos.com/staff/iz/amuse/javascript/expert/deflate.txt
+ */
+
+(function(){
+
+/* Copyright (C) 1999 Masanao Izumo <[email protected]>
+ * Version: 1.0.1
+ * LastModified: Dec 25 1999
+ */
+
+/* Interface:
+ * data = zip_deflate(src);
+ */
+
+/* constant parameters */
+var zip_WSIZE = 32768;		// Sliding Window size
+var zip_STORED_BLOCK = 0;
+var zip_STATIC_TREES = 1;
+var zip_DYN_TREES    = 2;
+
+/* for deflate */
+var zip_DEFAULT_LEVEL = 6;
+var zip_FULL_SEARCH = true;
+var zip_INBUFSIZ = 32768;	// Input buffer size
+var zip_INBUF_EXTRA = 64;	// Extra buffer
+var zip_OUTBUFSIZ = 1024 * 8;
+var zip_window_size = 2 * zip_WSIZE;
+var zip_MIN_MATCH = 3;
+var zip_MAX_MATCH = 258;
+var zip_BITS = 16;
+// for SMALL_MEM
+var zip_LIT_BUFSIZE = 0x2000;
+var zip_HASH_BITS = 13;
+// for MEDIUM_MEM
+// var zip_LIT_BUFSIZE = 0x4000;
+// var zip_HASH_BITS = 14;
+// for BIG_MEM
+// var zip_LIT_BUFSIZE = 0x8000;
+// var zip_HASH_BITS = 15;
+if(zip_LIT_BUFSIZE > zip_INBUFSIZ)
+    alert("error: zip_INBUFSIZ is too small");
+if((zip_WSIZE<<1) > (1<<zip_BITS))
+    alert("error: zip_WSIZE is too large");
+if(zip_HASH_BITS > zip_BITS-1)
+    alert("error: zip_HASH_BITS is too large");
+if(zip_HASH_BITS < 8 || zip_MAX_MATCH != 258)
+    alert("error: Code too clever");
+var zip_DIST_BUFSIZE = zip_LIT_BUFSIZE;
+var zip_HASH_SIZE = 1 << zip_HASH_BITS;
+var zip_HASH_MASK = zip_HASH_SIZE - 1;
+var zip_WMASK = zip_WSIZE - 1;
+var zip_NIL = 0; // Tail of hash chains
+var zip_TOO_FAR = 4096;
+var zip_MIN_LOOKAHEAD = zip_MAX_MATCH + zip_MIN_MATCH + 1;
+var zip_MAX_DIST = zip_WSIZE - zip_MIN_LOOKAHEAD;
+var zip_SMALLEST = 1;
+var zip_MAX_BITS = 15;
+var zip_MAX_BL_BITS = 7;
+var zip_LENGTH_CODES = 29;
+var zip_LITERALS =256;
+var zip_END_BLOCK = 256;
+var zip_L_CODES = zip_LITERALS + 1 + zip_LENGTH_CODES;
+var zip_D_CODES = 30;
+var zip_BL_CODES = 19;
+var zip_REP_3_6 = 16;
+var zip_REPZ_3_10 = 17;
+var zip_REPZ_11_138 = 18;
+var zip_HEAP_SIZE = 2 * zip_L_CODES + 1;
+var zip_H_SHIFT = parseInt((zip_HASH_BITS + zip_MIN_MATCH - 1) /
+			   zip_MIN_MATCH);
+
+/* variables */
+var zip_free_queue;
+var zip_qhead, zip_qtail;
+var zip_initflag;
+var zip_outbuf = null;
+var zip_outcnt, zip_outoff;
+var zip_complete;
+var zip_window;
+var zip_d_buf;
+var zip_l_buf;
+var zip_prev;
+var zip_bi_buf;
+var zip_bi_valid;
+var zip_block_start;
+var zip_ins_h;
+var zip_hash_head;
+var zip_prev_match;
+var zip_match_available;
+var zip_match_length;
+var zip_prev_length;
+var zip_strstart;
+var zip_match_start;
+var zip_eofile;
+var zip_lookahead;
+var zip_max_chain_length;
+var zip_max_lazy_match;
+var zip_compr_level;
+var zip_good_match;
+var zip_nice_match;
+var zip_dyn_ltree;
+var zip_dyn_dtree;
+var zip_static_ltree;
+var zip_static_dtree;
+var zip_bl_tree;
+var zip_l_desc;
+var zip_d_desc;
+var zip_bl_desc;
+var zip_bl_count;
+var zip_heap;
+var zip_heap_len;
+var zip_heap_max;
+var zip_depth;
+var zip_length_code;
+var zip_dist_code;
+var zip_base_length;
+var zip_base_dist;
+var zip_flag_buf;
+var zip_last_lit;
+var zip_last_dist;
+var zip_last_flags;
+var zip_flags;
+var zip_flag_bit;
+var zip_opt_len;
+var zip_static_len;
+var zip_deflate_data;
+var zip_deflate_pos;
+
+/* objects (deflate) */
+
+var zip_DeflateCT = function() {
+    this.fc = 0; // frequency count or bit string
+    this.dl = 0; // father node in Huffman tree or length of bit string
+}
+
+var zip_DeflateTreeDesc = function() {
+    this.dyn_tree = null;	// the dynamic tree
+    this.static_tree = null;	// corresponding static tree or NULL
+    this.extra_bits = null;	// extra bits for each code or NULL
+    this.extra_base = 0;	// base index for extra_bits
+    this.elems = 0;		// max number of elements in the tree
+    this.max_length = 0;	// max bit length for the codes
+    this.max_code = 0;		// largest code with non zero frequency
+}
+
+/* Values for max_lazy_match, good_match and max_chain_length, depending on
+ * the desired pack level (0..9). The values given below have been tuned to
+ * exclude worst case performance for pathological files. Better values may be
+ * found for specific files.
+ */
+var zip_DeflateConfiguration = function(a, b, c, d) {
+    this.good_length = a; // reduce lazy search above this match length
+    this.max_lazy = b;    // do not perform lazy search above this match length
+    this.nice_length = c; // quit search above this match length
+    this.max_chain = d;
+}
+
+var zip_DeflateBuffer = function() {
+    this.next = null;
+    this.len = 0;
+    this.ptr = new Array(zip_OUTBUFSIZ);
+    this.off = 0;
+}
+
+/* constant tables */
+var zip_extra_lbits = new Array(
+    0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0);
+var zip_extra_dbits = new Array(
+    0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13);
+var zip_extra_blbits = new Array(
+    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7);
+var zip_bl_order = new Array(
+    16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15);
+var zip_configuration_table = new Array(
+	new zip_DeflateConfiguration(0,    0,   0,    0),
+	new zip_DeflateConfiguration(4,    4,   8,    4),
+	new zip_DeflateConfiguration(4,    5,  16,    8),
+	new zip_DeflateConfiguration(4,    6,  32,   32),
+	new zip_DeflateConfiguration(4,    4,  16,   16),
+	new zip_DeflateConfiguration(8,   16,  32,   32),
+	new zip_DeflateConfiguration(8,   16, 128,  128),
+	new zip_DeflateConfiguration(8,   32, 128,  256),
+	new zip_DeflateConfiguration(32, 128, 258, 1024),
+	new zip_DeflateConfiguration(32, 258, 258, 4096));
+
+
+/* routines (deflate) */
+
+var zip_deflate_start = function(level) {
+    var i;
+
+    if(!level)
+	level = zip_DEFAULT_LEVEL;
+    else if(level < 1)
+	level = 1;
+    else if(level > 9)
+	level = 9;
+
+    zip_compr_level = level;
+    zip_initflag = false;
+    zip_eofile = false;
+    if(zip_outbuf != null)
+	return;
+
+    zip_free_queue = zip_qhead = zip_qtail = null;
+    zip_outbuf = new Array(zip_OUTBUFSIZ);
+    zip_window = new Array(zip_window_size);
+    zip_d_buf = new Array(zip_DIST_BUFSIZE);
+    zip_l_buf = new Array(zip_INBUFSIZ + zip_INBUF_EXTRA);
+    zip_prev = new Array(1 << zip_BITS);
+    zip_dyn_ltree = new Array(zip_HEAP_SIZE);
+    for(i = 0; i < zip_HEAP_SIZE; i++)
+	zip_dyn_ltree[i] = new zip_DeflateCT();
+    zip_dyn_dtree = new Array(2*zip_D_CODES+1);
+    for(i = 0; i < 2*zip_D_CODES+1; i++)
+	zip_dyn_dtree[i] = new zip_DeflateCT();
+    zip_static_ltree = new Array(zip_L_CODES+2);
+    for(i = 0; i < zip_L_CODES+2; i++)
+	zip_static_ltree[i] = new zip_DeflateCT();
+    zip_static_dtree = new Array(zip_D_CODES);
+    for(i = 0; i < zip_D_CODES; i++)
+	zip_static_dtree[i] = new zip_DeflateCT();
+    zip_bl_tree = new Array(2*zip_BL_CODES+1);
+    for(i = 0; i < 2*zip_BL_CODES+1; i++)
+	zip_bl_tree[i] = new zip_DeflateCT();
+    zip_l_desc = new zip_DeflateTreeDesc();
+    zip_d_desc = new zip_DeflateTreeDesc();
+    zip_bl_desc = new zip_DeflateTreeDesc();
+    zip_bl_count = new Array(zip_MAX_BITS+1);
+    zip_heap = new Array(2*zip_L_CODES+1);
+    zip_depth = new Array(2*zip_L_CODES+1);
+    zip_length_code = new Array(zip_MAX_MATCH-zip_MIN_MATCH+1);
+    zip_dist_code = new Array(512);
+    zip_base_length = new Array(zip_LENGTH_CODES);
+    zip_base_dist = new Array(zip_D_CODES);
+    zip_flag_buf = new Array(parseInt(zip_LIT_BUFSIZE / 8));
+}
+
+var zip_deflate_end = function() {
+    zip_free_queue = zip_qhead = zip_qtail = null;
+    zip_outbuf = null;
+    zip_window = null;
+    zip_d_buf = null;
+    zip_l_buf = null;
+    zip_prev = null;
+    zip_dyn_ltree = null;
+    zip_dyn_dtree = null;
+    zip_static_ltree = null;
+    zip_static_dtree = null;
+    zip_bl_tree = null;
+    zip_l_desc = null;
+    zip_d_desc = null;
+    zip_bl_desc = null;
+    zip_bl_count = null;
+    zip_heap = null;
+    zip_depth = null;
+    zip_length_code = null;
+    zip_dist_code = null;
+    zip_base_length = null;
+    zip_base_dist = null;
+    zip_flag_buf = null;
+}
+
+var zip_reuse_queue = function(p) {
+    p.next = zip_free_queue;
+    zip_free_queue = p;
+}
+
+var zip_new_queue = function() {
+    var p;
+
+    if(zip_free_queue != null)
+    {
+	p = zip_free_queue;
+	zip_free_queue = zip_free_queue.next;
+    }
+    else
+	p = new zip_DeflateBuffer();
+    p.next = null;
+    p.len = p.off = 0;
+
+    return p;
+}
+
+var zip_head1 = function(i) {
+    return zip_prev[zip_WSIZE + i];
+}
+
+var zip_head2 = function(i, val) {
+    return zip_prev[zip_WSIZE + i] = val;
+}
+
+/* put_byte is used for the compressed output, put_ubyte for the
+ * uncompressed output. However unlzw() uses window for its
+ * suffix table instead of its output buffer, so it does not use put_ubyte
+ * (to be cleaned up).
+ */
+var zip_put_byte = function(c) {
+    zip_outbuf[zip_outoff + zip_outcnt++] = c;
+    if(zip_outoff + zip_outcnt == zip_OUTBUFSIZ)
+	zip_qoutbuf();
+}
+
+/* Output a 16 bit value, lsb first */
+var zip_put_short = function(w) {
+    w &= 0xffff;
+    if(zip_outoff + zip_outcnt < zip_OUTBUFSIZ - 2) {
+	zip_outbuf[zip_outoff + zip_outcnt++] = (w & 0xff);
+	zip_outbuf[zip_outoff + zip_outcnt++] = (w >>> 8);
+    } else {
+	zip_put_byte(w & 0xff);
+	zip_put_byte(w >>> 8);
+    }
+}
+
+/* ==========================================================================
+ * Insert string s in the dictionary and set match_head to the previous head
+ * of the hash chain (the most recent string with same hash key). Return
+ * the previous length of the hash chain.
+ * IN  assertion: all calls to to INSERT_STRING are made with consecutive
+ *    input characters and the first MIN_MATCH bytes of s are valid
+ *    (except for the last MIN_MATCH-1 bytes of the input file).
+ */
+var zip_INSERT_STRING = function() {
+    zip_ins_h = ((zip_ins_h << zip_H_SHIFT)
+		 ^ (zip_window[zip_strstart + zip_MIN_MATCH - 1] & 0xff))
+	& zip_HASH_MASK;
+    zip_hash_head = zip_head1(zip_ins_h);
+    zip_prev[zip_strstart & zip_WMASK] = zip_hash_head;
+    zip_head2(zip_ins_h, zip_strstart);
+}
+
+/* Send a code of the given tree. c and tree must not have side effects */
+var zip_SEND_CODE = function(c, tree) {
+    zip_send_bits(tree[c].fc, tree[c].dl);
+}
+
+/* Mapping from a distance to a distance code. dist is the distance - 1 and
+ * must not have side effects. dist_code[256] and dist_code[257] are never
+ * used.
+ */
+var zip_D_CODE = function(dist) {
+    return (dist < 256 ? zip_dist_code[dist]
+	    : zip_dist_code[256 + (dist>>7)]) & 0xff;
+}
+
+/* ==========================================================================
+ * Compares to subtrees, using the tree depth as tie breaker when
+ * the subtrees have equal frequency. This minimizes the worst case length.
+ */
+var zip_SMALLER = function(tree, n, m) {
+    return tree[n].fc < tree[m].fc ||
+      (tree[n].fc == tree[m].fc && zip_depth[n] <= zip_depth[m]);
+}
+
+/* ==========================================================================
+ * read string data
+ */
+var zip_read_buff = function(buff, offset, n) {
+    var i;
+    for(i = 0; i < n && zip_deflate_pos < zip_deflate_data.length; i++)
+	buff[offset + i] =
+	    zip_deflate_data.charCodeAt(zip_deflate_pos++) & 0xff;
+    return i;
+}
+
+/* ==========================================================================
+ * Initialize the "longest match" routines for a new file
+ */
+var zip_lm_init = function() {
+    var j;
+
+    /* Initialize the hash table. */
+    for(j = 0; j < zip_HASH_SIZE; j++)
+//	zip_head2(j, zip_NIL);
+	zip_prev[zip_WSIZE + j] = 0;
+    /* prev will be initialized on the fly */
+
+    /* Set the default configuration parameters:
+     */
+    zip_max_lazy_match = zip_configuration_table[zip_compr_level].max_lazy;
+    zip_good_match     = zip_configuration_table[zip_compr_level].good_length;
+    if(!zip_FULL_SEARCH)
+	zip_nice_match = zip_configuration_table[zip_compr_level].nice_length;
+    zip_max_chain_length = zip_configuration_table[zip_compr_level].max_chain;
+
+    zip_strstart = 0;
+    zip_block_start = 0;
+
+    zip_lookahead = zip_read_buff(zip_window, 0, 2 * zip_WSIZE);
+    if(zip_lookahead <= 0) {
+	zip_eofile = true;
+	zip_lookahead = 0;
+	return;
+    }
+    zip_eofile = false;
+    /* Make sure that we always have enough lookahead. This is important
+     * if input comes from a device such as a tty.
+     */
+    while(zip_lookahead < zip_MIN_LOOKAHEAD && !zip_eofile)
+	zip_fill_window();
+
+    /* If lookahead < MIN_MATCH, ins_h is garbage, but this is
+     * not important since only literal bytes will be emitted.
+     */
+    zip_ins_h = 0;
+    for(j = 0; j < zip_MIN_MATCH - 1; j++) {
+//      UPDATE_HASH(ins_h, window[j]);
+	zip_ins_h = ((zip_ins_h << zip_H_SHIFT) ^ (zip_window[j] & 0xff)) & zip_HASH_MASK;
+    }
+}
+
+/* ==========================================================================
+ * Set match_start to the longest match starting at the given string and
+ * return its length. Matches shorter or equal to prev_length are discarded,
+ * in which case the result is equal to prev_length and match_start is
+ * garbage.
+ * IN assertions: cur_match is the head of the hash chain for the current
+ *   string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1
+ */
+var zip_longest_match = function(cur_match) {
+    var chain_length = zip_max_chain_length; // max hash chain length
+    var scanp = zip_strstart; // current string
+    var matchp;		// matched string
+    var len;		// length of current match
+    var best_len = zip_prev_length;	// best match length so far
+
+    /* Stop when cur_match becomes <= limit. To simplify the code,
+     * we prevent matches with the string of window index 0.
+     */
+    var limit = (zip_strstart > zip_MAX_DIST ? zip_strstart - zip_MAX_DIST : zip_NIL);
+
+    var strendp = zip_strstart + zip_MAX_MATCH;
+    var scan_end1 = zip_window[scanp + best_len - 1];
+    var scan_end  = zip_window[scanp + best_len];
+
+    /* Do not waste too much time if we already have a good match: */
+    if(zip_prev_length >= zip_good_match)
+	chain_length >>= 2;
+
+//  Assert(encoder->strstart <= window_size-MIN_LOOKAHEAD, "insufficient lookahead");
+
+    do {
+//    Assert(cur_match < encoder->strstart, "no future");
+	matchp = cur_match;
+
+	/* Skip to next match if the match length cannot increase
+	    * or if the match length is less than 2:
+	*/
+	if(zip_window[matchp + best_len]	!= scan_end  ||
+	   zip_window[matchp + best_len - 1]	!= scan_end1 ||
+	   zip_window[matchp]			!= zip_window[scanp] ||
+	   zip_window[++matchp]			!= zip_window[scanp + 1]) {
+	    continue;
+	}
+
+	/* The check at best_len-1 can be removed because it will be made
+         * again later. (This heuristic is not always a win.)
+         * It is not necessary to compare scan[2] and match[2] since they
+         * are always equal when the other bytes match, given that
+         * the hash keys are equal and that HASH_BITS >= 8.
+         */
+	scanp += 2;
+	matchp++;
+
+	/* We check for insufficient lookahead only every 8th comparison;
+         * the 256th check will be made at strstart+258.
+         */
+	do {
+	} while(zip_window[++scanp] == zip_window[++matchp] &&
+		zip_window[++scanp] == zip_window[++matchp] &&
+		zip_window[++scanp] == zip_window[++matchp] &&
+		zip_window[++scanp] == zip_window[++matchp] &&
+		zip_window[++scanp] == zip_window[++matchp] &&
+		zip_window[++scanp] == zip_window[++matchp] &&
+		zip_window[++scanp] == zip_window[++matchp] &&
+		zip_window[++scanp] == zip_window[++matchp] &&
+		scanp < strendp);
+
+      len = zip_MAX_MATCH - (strendp - scanp);
+      scanp = strendp - zip_MAX_MATCH;
+
+      if(len > best_len) {
+	  zip_match_start = cur_match;
+	  best_len = len;
+	  if(zip_FULL_SEARCH) {
+	      if(len >= zip_MAX_MATCH) break;
+	  } else {
+	      if(len >= zip_nice_match) break;
+	  }
+
+	  scan_end1  = zip_window[scanp + best_len-1];
+	  scan_end   = zip_window[scanp + best_len];
+      }
+    } while((cur_match = zip_prev[cur_match & zip_WMASK]) > limit
+	    && --chain_length != 0);
+
+    return best_len;
+}
+
+/* ==========================================================================
+ * Fill the window when the lookahead becomes insufficient.
+ * Updates strstart and lookahead, and sets eofile if end of input file.
+ * IN assertion: lookahead < MIN_LOOKAHEAD && strstart + lookahead > 0
+ * OUT assertions: at least one byte has been read, or eofile is set;
+ *    file reads are performed for at least two bytes (required for the
+ *    translate_eol option).
+ */
+var zip_fill_window = function() {
+    var n, m;
+
+    // Amount of free space at the end of the window.
+    var more = zip_window_size - zip_lookahead - zip_strstart;
+
+    /* If the window is almost full and there is insufficient lookahead,
+     * move the upper half to the lower one to make room in the upper half.
+     */
+    if(more == -1) {
+	/* Very unlikely, but possible on 16 bit machine if strstart == 0
+         * and lookahead == 1 (input done one byte at time)
+         */
+	more--;
+    } else if(zip_strstart >= zip_WSIZE + zip_MAX_DIST) {
+	/* By the IN assertion, the window is not empty so we can't confuse
+         * more == 0 with more == 64K on a 16 bit machine.
+         */
+//	Assert(window_size == (ulg)2*WSIZE, "no sliding with BIG_MEM");
+
+//	System.arraycopy(window, WSIZE, window, 0, WSIZE);
+	for(n = 0; n < zip_WSIZE; n++)
+	    zip_window[n] = zip_window[n + zip_WSIZE];
+      
+	zip_match_start -= zip_WSIZE;
+	zip_strstart    -= zip_WSIZE; /* we now have strstart >= MAX_DIST: */
+	zip_block_start -= zip_WSIZE;
+
+	for(n = 0; n < zip_HASH_SIZE; n++) {
+	    m = zip_head1(n);
+	    zip_head2(n, m >= zip_WSIZE ? m - zip_WSIZE : zip_NIL);
+	}
+	for(n = 0; n < zip_WSIZE; n++) {
+	    /* If n is not on any hash chain, prev[n] is garbage but
+	     * its value will never be used.
+	     */
+	    m = zip_prev[n];
+	    zip_prev[n] = (m >= zip_WSIZE ? m - zip_WSIZE : zip_NIL);
+	}
+	more += zip_WSIZE;
+    }
+    // At this point, more >= 2
+    if(!zip_eofile) {
+	n = zip_read_buff(zip_window, zip_strstart + zip_lookahead, more);
+	if(n <= 0)
+	    zip_eofile = true;
+	else
+	    zip_lookahead += n;
+    }
+}
+
+/* ==========================================================================
+ * Processes a new input file and return its compressed length. This
+ * function does not perform lazy evaluationof matches and inserts
+ * new strings in the dictionary only for unmatched strings or for short
+ * matches. It is used only for the fast compression options.
+ */
+var zip_deflate_fast = function() {
+    while(zip_lookahead != 0 && zip_qhead == null) {
+	var flush; // set if current block must be flushed
+
+	/* Insert the string window[strstart .. strstart+2] in the
+	 * dictionary, and set hash_head to the head of the hash chain:
+	 */
+	zip_INSERT_STRING();
+
+	/* Find the longest match, discarding those <= prev_length.
+	 * At this point we have always match_length < MIN_MATCH
+	 */
+	if(zip_hash_head != zip_NIL &&
+	   zip_strstart - zip_hash_head <= zip_MAX_DIST) {
+	    /* To simplify the code, we prevent matches with the string
+	     * of window index 0 (in particular we have to avoid a match
+	     * of the string with itself at the start of the input file).
+	     */
+	    zip_match_length = zip_longest_match(zip_hash_head);
+	    /* longest_match() sets match_start */
+	    if(zip_match_length > zip_lookahead)
+		zip_match_length = zip_lookahead;
+	}
+	if(zip_match_length >= zip_MIN_MATCH) {
+//	    check_match(strstart, match_start, match_length);
+
+	    flush = zip_ct_tally(zip_strstart - zip_match_start,
+				 zip_match_length - zip_MIN_MATCH);
+	    zip_lookahead -= zip_match_length;
+
+	    /* Insert new strings in the hash table only if the match length
+	     * is not too large. This saves time but degrades compression.
+	     */
+	    if(zip_match_length <= zip_max_lazy_match) {
+		zip_match_length--; // string at strstart already in hash table
+		do {
+		    zip_strstart++;
+		    zip_INSERT_STRING();
+		    /* strstart never exceeds WSIZE-MAX_MATCH, so there are
+		     * always MIN_MATCH bytes ahead. If lookahead < MIN_MATCH
+		     * these bytes are garbage, but it does not matter since
+		     * the next lookahead bytes will be emitted as literals.
+		     */
+		} while(--zip_match_length != 0);
+		zip_strstart++;
+	    } else {
+		zip_strstart += zip_match_length;
+		zip_match_length = 0;
+		zip_ins_h = zip_window[zip_strstart] & 0xff;
+//		UPDATE_HASH(ins_h, window[strstart + 1]);
+		zip_ins_h = ((zip_ins_h<<zip_H_SHIFT) ^ (zip_window[zip_strstart + 1] & 0xff)) & zip_HASH_MASK;
+
+//#if MIN_MATCH != 3
+//		Call UPDATE_HASH() MIN_MATCH-3 more times
+//#endif
+
+	    }
+	} else {
+	    /* No match, output a literal byte */
+	    flush = zip_ct_tally(0, zip_window[zip_strstart] & 0xff);
+	    zip_lookahead--;
+	    zip_strstart++;
+	}
+	if(flush) {
+	    zip_flush_block(0);
+	    zip_block_start = zip_strstart;
+	}
+
+	/* Make sure that we always have enough lookahead, except
+	 * at the end of the input file. We need MAX_MATCH bytes
+	 * for the next match, plus MIN_MATCH bytes to insert the
+	 * string following the next match.
+	 */
+	while(zip_lookahead < zip_MIN_LOOKAHEAD && !zip_eofile)
+	    zip_fill_window();
+    }
+}
+
+var zip_deflate_better = function() {
+    /* Process the input block. */
+    while(zip_lookahead != 0 && zip_qhead == null) {
+	/* Insert the string window[strstart .. strstart+2] in the
+	 * dictionary, and set hash_head to the head of the hash chain:
+	 */
+	zip_INSERT_STRING();
+
+	/* Find the longest match, discarding those <= prev_length.
+	 */
+	zip_prev_length = zip_match_length;
+	zip_prev_match = zip_match_start;
+	zip_match_length = zip_MIN_MATCH - 1;
+
+	if(zip_hash_head != zip_NIL &&
+	   zip_prev_length < zip_max_lazy_match &&
+	   zip_strstart - zip_hash_head <= zip_MAX_DIST) {
+	    /* To simplify the code, we prevent matches with the string
+	     * of window index 0 (in particular we have to avoid a match
+	     * of the string with itself at the start of the input file).
+	     */
+	    zip_match_length = zip_longest_match(zip_hash_head);
+	    /* longest_match() sets match_start */
+	    if(zip_match_length > zip_lookahead)
+		zip_match_length = zip_lookahead;
+
+	    /* Ignore a length 3 match if it is too distant: */
+	    if(zip_match_length == zip_MIN_MATCH &&
+	       zip_strstart - zip_match_start > zip_TOO_FAR) {
+		/* If prev_match is also MIN_MATCH, match_start is garbage
+		 * but we will ignore the current match anyway.
+		 */
+		zip_match_length--;
+	    }
+	}
+	/* If there was a match at the previous step and the current
+	 * match is not better, output the previous match:
+	 */
+	if(zip_prev_length >= zip_MIN_MATCH &&
+	   zip_match_length <= zip_prev_length) {
+	    var flush; // set if current block must be flushed
+
+//	    check_match(strstart - 1, prev_match, prev_length);
+	    flush = zip_ct_tally(zip_strstart - 1 - zip_prev_match,
+				 zip_prev_length - zip_MIN_MATCH);
+
+	    /* Insert in hash table all strings up to the end of the match.
+	     * strstart-1 and strstart are already inserted.
+	     */
+	    zip_lookahead -= zip_prev_length - 1;
+	    zip_prev_length -= 2;
+	    do {
+		zip_strstart++;
+		zip_INSERT_STRING();
+		/* strstart never exceeds WSIZE-MAX_MATCH, so there are
+		 * always MIN_MATCH bytes ahead. If lookahead < MIN_MATCH
+		 * these bytes are garbage, but it does not matter since the
+		 * next lookahead bytes will always be emitted as literals.
+		 */
+	    } while(--zip_prev_length != 0);
+	    zip_match_available = 0;
+	    zip_match_length = zip_MIN_MATCH - 1;
+	    zip_strstart++;
+	    if(flush) {
+		zip_flush_block(0);
+		zip_block_start = zip_strstart;
+	    }
+	} else if(zip_match_available != 0) {
+	    /* If there was no match at the previous position, output a
+	     * single literal. If there was a match but the current match
+	     * is longer, truncate the previous match to a single literal.
+	     */
+	    if(zip_ct_tally(0, zip_window[zip_strstart - 1] & 0xff)) {
+		zip_flush_block(0);
+		zip_block_start = zip_strstart;
+	    }
+	    zip_strstart++;
+	    zip_lookahead--;
+	} else {
+	    /* There is no previous match to compare with, wait for
+	     * the next step to decide.
+	     */
+	    zip_match_available = 1;
+	    zip_strstart++;
+	    zip_lookahead--;
+	}
+
+	/* Make sure that we always have enough lookahead, except
+	 * at the end of the input file. We need MAX_MATCH bytes
+	 * for the next match, plus MIN_MATCH bytes to insert the
+	 * string following the next match.
+	 */
+	while(zip_lookahead < zip_MIN_LOOKAHEAD && !zip_eofile)
+	    zip_fill_window();
+    }
+}
+
+var zip_init_deflate = function() {
+    if(zip_eofile)
+	return;
+    zip_bi_buf = 0;
+    zip_bi_valid = 0;
+    zip_ct_init();
+    zip_lm_init();
+
+    zip_qhead = null;
+    zip_outcnt = 0;
+    zip_outoff = 0;
+
+    if(zip_compr_level <= 3)
+    {
+	zip_prev_length = zip_MIN_MATCH - 1;
+	zip_match_length = 0;
+    }
+    else
+    {
+	zip_match_length = zip_MIN_MATCH - 1;
+	zip_match_available = 0;
+    }
+
+    zip_complete = false;
+}
+
+/* ==========================================================================
+ * Same as above, but achieves better compression. We use a lazy
+ * evaluation for matches: a match is finally adopted only if there is
+ * no better match at the next window position.
+ */
+var zip_deflate_internal = function(buff, off, buff_size) {
+    var n;
+
+    if(!zip_initflag)
+    {
+	zip_init_deflate();
+	zip_initflag = true;
+	if(zip_lookahead == 0) { // empty
+	    zip_complete = true;
+	    return 0;
+	}
+    }
+
+    if((n = zip_qcopy(buff, off, buff_size)) == buff_size)
+	return buff_size;
+
+    if(zip_complete)
+	return n;
+
+    if(zip_compr_level <= 3) // optimized for speed
+	zip_deflate_fast();
+    else
+	zip_deflate_better();
+    if(zip_lookahead == 0) {
+	if(zip_match_available != 0)
+	    zip_ct_tally(0, zip_window[zip_strstart - 1] & 0xff);
+	zip_flush_block(1);
+	zip_complete = true;
+    }
+    return n + zip_qcopy(buff, n + off, buff_size - n);
+}
+
+var zip_qcopy = function(buff, off, buff_size) {
+    var n, i, j;
+
+    n = 0;
+    while(zip_qhead != null && n < buff_size)
+    {
+	i = buff_size - n;
+	if(i > zip_qhead.len)
+	    i = zip_qhead.len;
+//      System.arraycopy(qhead.ptr, qhead.off, buff, off + n, i);
+	for(j = 0; j < i; j++)
+	    buff[off + n + j] = zip_qhead.ptr[zip_qhead.off + j];
+	
+	zip_qhead.off += i;
+	zip_qhead.len -= i;
+	n += i;
+	if(zip_qhead.len == 0) {
+	    var p;
+	    p = zip_qhead;
+	    zip_qhead = zip_qhead.next;
+	    zip_reuse_queue(p);
+	}
+    }
+
+    if(n == buff_size)
+	return n;
+
+    if(zip_outoff < zip_outcnt) {
+	i = buff_size - n;
+	if(i > zip_outcnt - zip_outoff)
+	    i = zip_outcnt - zip_outoff;
+	// System.arraycopy(outbuf, outoff, buff, off + n, i);
+	for(j = 0; j < i; j++)
+	    buff[off + n + j] = zip_outbuf[zip_outoff + j];
+	zip_outoff += i;
+	n += i;
+	if(zip_outcnt == zip_outoff)
+	    zip_outcnt = zip_outoff = 0;
+    }
+    return n;
+}
+
+/* ==========================================================================
+ * Allocate the match buffer, initialize the various tables and save the
+ * location of the internal file attribute (ascii/binary) and method
+ * (DEFLATE/STORE).
+ */
+var zip_ct_init = function() {
+    var n;	// iterates over tree elements
+    var bits;	// bit counter
+    var length;	// length value
+    var code;	// code value
+    var dist;	// distance index
+
+    if(zip_static_dtree[0].dl != 0) return; // ct_init already called
+
+    zip_l_desc.dyn_tree		= zip_dyn_ltree;
+    zip_l_desc.static_tree	= zip_static_ltree;
+    zip_l_desc.extra_bits	= zip_extra_lbits;
+    zip_l_desc.extra_base	= zip_LITERALS + 1;
+    zip_l_desc.elems		= zip_L_CODES;
+    zip_l_desc.max_length	= zip_MAX_BITS;
+    zip_l_desc.max_code		= 0;
+
+    zip_d_desc.dyn_tree		= zip_dyn_dtree;
+    zip_d_desc.static_tree	= zip_static_dtree;
+    zip_d_desc.extra_bits	= zip_extra_dbits;
+    zip_d_desc.extra_base	= 0;
+    zip_d_desc.elems		= zip_D_CODES;
+    zip_d_desc.max_length	= zip_MAX_BITS;
+    zip_d_desc.max_code		= 0;
+
+    zip_bl_desc.dyn_tree	= zip_bl_tree;
+    zip_bl_desc.static_tree	= null;
+    zip_bl_desc.extra_bits	= zip_extra_blbits;
+    zip_bl_desc.extra_base	= 0;
+    zip_bl_desc.elems		= zip_BL_CODES;
+    zip_bl_desc.max_length	= zip_MAX_BL_BITS;
+    zip_bl_desc.max_code	= 0;
+
+    // Initialize the mapping length (0..255) -> length code (0..28)
+    length = 0;
+    for(code = 0; code < zip_LENGTH_CODES-1; code++) {
+	zip_base_length[code] = length;
+	for(n = 0; n < (1<<zip_extra_lbits[code]); n++)
+	    zip_length_code[length++] = code;
+    }
+    // Assert (length == 256, "ct_init: length != 256");
+
+    /* Note that the length 255 (match length 258) can be represented
+     * in two different ways: code 284 + 5 bits or code 285, so we
+     * overwrite length_code[255] to use the best encoding:
+     */
+    zip_length_code[length-1] = code;
+
+    /* Initialize the mapping dist (0..32K) -> dist code (0..29) */
+    dist = 0;
+    for(code = 0 ; code < 16; code++) {
+	zip_base_dist[code] = dist;
+	for(n = 0; n < (1<<zip_extra_dbits[code]); n++) {
+	    zip_dist_code[dist++] = code;
+	}
+    }
+    // Assert (dist == 256, "ct_init: dist != 256");
+    dist >>= 7; // from now on, all distances are divided by 128
+    for( ; code < zip_D_CODES; code++) {
+	zip_base_dist[code] = dist << 7;
+	for(n = 0; n < (1<<(zip_extra_dbits[code]-7)); n++)
+	    zip_dist_code[256 + dist++] = code;
+    }
+    // Assert (dist == 256, "ct_init: 256+dist != 512");
+
+    // Construct the codes of the static literal tree
+    for(bits = 0; bits <= zip_MAX_BITS; bits++)
+	zip_bl_count[bits] = 0;
+    n = 0;
+    while(n <= 143) { zip_static_ltree[n++].dl = 8; zip_bl_count[8]++; }
+    while(n <= 255) { zip_static_ltree[n++].dl = 9; zip_bl_count[9]++; }
+    while(n <= 279) { zip_static_ltree[n++].dl = 7; zip_bl_count[7]++; }
+    while(n <= 287) { zip_static_ltree[n++].dl = 8; zip_bl_count[8]++; }
+    /* Codes 286 and 287 do not exist, but we must include them in the
+     * tree construction to get a canonical Huffman tree (longest code
+     * all ones)
+     */
+    zip_gen_codes(zip_static_ltree, zip_L_CODES + 1);
+
+    /* The static distance tree is trivial: */
+    for(n = 0; n < zip_D_CODES; n++) {
+	zip_static_dtree[n].dl = 5;
+	zip_static_dtree[n].fc = zip_bi_reverse(n, 5);
+    }
+
+    // Initialize the first block of the first file:
+    zip_init_block();
+}
+
+/* ==========================================================================
+ * Initialize a new block.
+ */
+var zip_init_block = function() {
+    var n; // iterates over tree elements
+
+    // Initialize the trees.
+    for(n = 0; n < zip_L_CODES;  n++) zip_dyn_ltree[n].fc = 0;
+    for(n = 0; n < zip_D_CODES;  n++) zip_dyn_dtree[n].fc = 0;
+    for(n = 0; n < zip_BL_CODES; n++) zip_bl_tree[n].fc = 0;
+
+    zip_dyn_ltree[zip_END_BLOCK].fc = 1;
+    zip_opt_len = zip_static_len = 0;
+    zip_last_lit = zip_last_dist = zip_last_flags = 0;
+    zip_flags = 0;
+    zip_flag_bit = 1;
+}
+
+/* ==========================================================================
+ * Restore the heap property by moving down the tree starting at node k,
+ * exchanging a node with the smallest of its two sons if necessary, stopping
+ * when the heap property is re-established (each father smaller than its
+ * two sons).
+ */
+var zip_pqdownheap = function(
+    tree,	// the tree to restore
+    k) {	// node to move down
+    var v = zip_heap[k];
+    var j = k << 1;	// left son of k
+
+    while(j <= zip_heap_len) {
+	// Set j to the smallest of the two sons:
+	if(j < zip_heap_len &&
+	   zip_SMALLER(tree, zip_heap[j + 1], zip_heap[j]))
+	    j++;
+
+	// Exit if v is smaller than both sons
+	if(zip_SMALLER(tree, v, zip_heap[j]))
+	    break;
+
+	// Exchange v with the smallest son
+	zip_heap[k] = zip_heap[j];
+	k = j;
+
+	// And continue down the tree, setting j to the left son of k
+	j <<= 1;
+    }
+    zip_heap[k] = v;
+}
+
+/* ==========================================================================
+ * Compute the optimal bit lengths for a tree and update the total bit length
+ * for the current block.
+ * IN assertion: the fields freq and dad are set, heap[heap_max] and
+ *    above are the tree nodes sorted by increasing frequency.
+ * OUT assertions: the field len is set to the optimal bit length, the
+ *     array bl_count contains the frequencies for each bit length.
+ *     The length opt_len is updated; static_len is also updated if stree is
+ *     not null.
+ */
+var zip_gen_bitlen = function(desc) { // the tree descriptor
+    var tree		= desc.dyn_tree;
+    var extra		= desc.extra_bits;
+    var base		= desc.extra_base;
+    var max_code	= desc.max_code;
+    var max_length	= desc.max_length;
+    var stree		= desc.static_tree;
+    var h;		// heap index
+    var n, m;		// iterate over the tree elements
+    var bits;		// bit length
+    var xbits;		// extra bits
+    var f;		// frequency
+    var overflow = 0;	// number of elements with bit length too large
+
+    for(bits = 0; bits <= zip_MAX_BITS; bits++)
+	zip_bl_count[bits] = 0;
+
+    /* In a first pass, compute the optimal bit lengths (which may
+     * overflow in the case of the bit length tree).
+     */
+    tree[zip_heap[zip_heap_max]].dl = 0; // root of the heap
+
+    for(h = zip_heap_max + 1; h < zip_HEAP_SIZE; h++) {
+	n = zip_heap[h];
+	bits = tree[tree[n].dl].dl + 1;
+	if(bits > max_length) {
+	    bits = max_length;
+	    overflow++;
+	}
+	tree[n].dl = bits;
+	// We overwrite tree[n].dl which is no longer needed
+
+	if(n > max_code)
+	    continue; // not a leaf node
+
+	zip_bl_count[bits]++;
+	xbits = 0;
+	if(n >= base)
+	    xbits = extra[n - base];
+	f = tree[n].fc;
+	zip_opt_len += f * (bits + xbits);
+	if(stree != null)
+	    zip_static_len += f * (stree[n].dl + xbits);
+    }
+    if(overflow == 0)
+	return;
+
+    // This happens for example on obj2 and pic of the Calgary corpus
+
+    // Find the first bit length which could increase:
+    do {
+	bits = max_length - 1;
+	while(zip_bl_count[bits] == 0)
+	    bits--;
+	zip_bl_count[bits]--;		// move one leaf down the tree
+	zip_bl_count[bits + 1] += 2;	// move one overflow item as its brother
+	zip_bl_count[max_length]--;
+	/* The brother of the overflow item also moves one step up,
+	 * but this does not affect bl_count[max_length]
+	 */
+	overflow -= 2;
+    } while(overflow > 0);
+
+    /* Now recompute all bit lengths, scanning in increasing frequency.
+     * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all
+     * lengths instead of fixing only the wrong ones. This idea is taken
+     * from 'ar' written by Haruhiko Okumura.)
+     */
+    for(bits = max_length; bits != 0; bits--) {
+	n = zip_bl_count[bits];
+	while(n != 0) {
+	    m = zip_heap[--h];
+	    if(m > max_code)
+		continue;
+	    if(tree[m].dl != bits) {
+		zip_opt_len += (bits - tree[m].dl) * tree[m].fc;
+		tree[m].fc = bits;
+	    }
+	    n--;
+	}
+    }
+}
+
+  /* ==========================================================================
+   * Generate the codes for a given tree and bit counts (which need not be
+   * optimal).
+   * IN assertion: the array bl_count contains the bit length statistics for
+   * the given tree and the field len is set for all tree elements.
+   * OUT assertion: the field code is set for all tree elements of non
+   *     zero code length.
+   */
+var zip_gen_codes = function(tree,	// the tree to decorate
+		   max_code) {	// largest code with non zero frequency
+    var next_code = new Array(zip_MAX_BITS+1); // next code value for each bit length
+    var code = 0;		// running code value
+    var bits;			// bit index
+    var n;			// code index
+
+    /* The distribution counts are first used to generate the code values
+     * without bit reversal.
+     */
+    for(bits = 1; bits <= zip_MAX_BITS; bits++) {
+	code = ((code + zip_bl_count[bits-1]) << 1);
+	next_code[bits] = code;
+    }
+
+    /* Check that the bit counts in bl_count are consistent. The last code
+     * must be all ones.
+     */
+//    Assert (code + encoder->bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1,
+//	    "inconsistent bit counts");
+//    Tracev((stderr,"\ngen_codes: max_code %d ", max_code));
+
+    for(n = 0; n <= max_code; n++) {
+	var len = tree[n].dl;
+	if(len == 0)
+	    continue;
+	// Now reverse the bits
+	tree[n].fc = zip_bi_reverse(next_code[len]++, len);
+
+//      Tracec(tree != static_ltree, (stderr,"\nn %3d %c l %2d c %4x (%x) ",
+//	  n, (isgraph(n) ? n : ' '), len, tree[n].fc, next_code[len]-1));
+    }
+}
+
+/* ==========================================================================
+ * Construct one Huffman tree and assigns the code bit strings and lengths.
+ * Update the total bit length for the current block.
+ * IN assertion: the field freq is set for all tree elements.
+ * OUT assertions: the fields len and code are set to the optimal bit length
+ *     and corresponding code. The length opt_len is updated; static_len is
+ *     also updated if stree is not null. The field max_code is set.
+ */
+var zip_build_tree = function(desc) { // the tree descriptor
+    var tree	= desc.dyn_tree;
+    var stree	= desc.static_tree;
+    var elems	= desc.elems;
+    var n, m;		// iterate over heap elements
+    var max_code = -1;	// largest code with non zero frequency
+    var node = elems;	// next internal node of the tree
+
+    /* Construct the initial heap, with least frequent element in
+     * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1].
+     * heap[0] is not used.
+     */
+    zip_heap_len = 0;
+    zip_heap_max = zip_HEAP_SIZE;
+
+    for(n = 0; n < elems; n++) {
+	if(tree[n].fc != 0) {
+	    zip_heap[++zip_heap_len] = max_code = n;
+	    zip_depth[n] = 0;
+	} else
+	    tree[n].dl = 0;
+    }
+
+    /* The pkzip format requires that at least one distance code exists,
+     * and that at least one bit should be sent even if there is only one
+     * possible code. So to avoid special checks later on we force at least
+     * two codes of non zero frequency.
+     */
+    while(zip_heap_len < 2) {
+	var xnew = zip_heap[++zip_heap_len] = (max_code < 2 ? ++max_code : 0);
+	tree[xnew].fc = 1;
+	zip_depth[xnew] = 0;
+	zip_opt_len--;
+	if(stree != null)
+	    zip_static_len -= stree[xnew].dl;
+	// new is 0 or 1 so it does not have extra bits
+    }
+    desc.max_code = max_code;
+
+    /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,
+     * establish sub-heaps of increasing lengths:
+     */
+    for(n = zip_heap_len >> 1; n >= 1; n--)
+	zip_pqdownheap(tree, n);
+
+    /* Construct the Huffman tree by repeatedly combining the least two
+     * frequent nodes.
+     */
+    do {
+	n = zip_heap[zip_SMALLEST];
+	zip_heap[zip_SMALLEST] = zip_heap[zip_heap_len--];
+	zip_pqdownheap(tree, zip_SMALLEST);
+
+	m = zip_heap[zip_SMALLEST];  // m = node of next least frequency
+
+	// keep the nodes sorted by frequency
+	zip_heap[--zip_heap_max] = n;
+	zip_heap[--zip_heap_max] = m;
+
+	// Create a new node father of n and m
+	tree[node].fc = tree[n].fc + tree[m].fc;
+//	depth[node] = (char)(MAX(depth[n], depth[m]) + 1);
+	if(zip_depth[n] > zip_depth[m] + 1)
+	    zip_depth[node] = zip_depth[n];
+	else
+	    zip_depth[node] = zip_depth[m] + 1;
+	tree[n].dl = tree[m].dl = node;
+
+	// and insert the new node in the heap
+	zip_heap[zip_SMALLEST] = node++;
+	zip_pqdownheap(tree, zip_SMALLEST);
+
+    } while(zip_heap_len >= 2);
+
+    zip_heap[--zip_heap_max] = zip_heap[zip_SMALLEST];
+
+    /* At this point, the fields freq and dad are set. We can now
+     * generate the bit lengths.
+     */
+    zip_gen_bitlen(desc);
+
+    // The field len is now set, we can generate the bit codes
+    zip_gen_codes(tree, max_code);
+}
+
+/* ==========================================================================
+ * Scan a literal or distance tree to determine the frequencies of the codes
+ * in the bit length tree. Updates opt_len to take into account the repeat
+ * counts. (The contribution of the bit length codes will be added later
+ * during the construction of bl_tree.)
+ */
+var zip_scan_tree = function(tree,// the tree to be scanned
+		       max_code) {  // and its largest code of non zero frequency
+    var n;			// iterates over all tree elements
+    var prevlen = -1;		// last emitted length
+    var curlen;			// length of current code
+    var nextlen = tree[0].dl;	// length of next code
+    var count = 0;		// repeat count of the current code
+    var max_count = 7;		// max repeat count
+    var min_count = 4;		// min repeat count
+
+    if(nextlen == 0) {
+	max_count = 138;
+	min_count = 3;
+    }
+    tree[max_code + 1].dl = 0xffff; // guard
+
+    for(n = 0; n <= max_code; n++) {
+	curlen = nextlen;
+	nextlen = tree[n + 1].dl;
+	if(++count < max_count && curlen == nextlen)
+	    continue;
+	else if(count < min_count)
+	    zip_bl_tree[curlen].fc += count;
+	else if(curlen != 0) {
+	    if(curlen != prevlen)
+		zip_bl_tree[curlen].fc++;
+	    zip_bl_tree[zip_REP_3_6].fc++;
+	} else if(count <= 10)
+	    zip_bl_tree[zip_REPZ_3_10].fc++;
+	else
+	    zip_bl_tree[zip_REPZ_11_138].fc++;
+	count = 0; prevlen = curlen;
+	if(nextlen == 0) {
+	    max_count = 138;
+	    min_count = 3;
+	} else if(curlen == nextlen) {
+	    max_count = 6;
+	    min_count = 3;
+	} else {
+	    max_count = 7;
+	    min_count = 4;
+	}
+    }
+}
+
+  /* ==========================================================================
+   * Send a literal or distance tree in compressed form, using the codes in
+   * bl_tree.
+   */
+var zip_send_tree = function(tree, // the tree to be scanned
+		   max_code) { // and its largest code of non zero frequency
+    var n;			// iterates over all tree elements
+    var prevlen = -1;		// last emitted length
+    var curlen;			// length of current code
+    var nextlen = tree[0].dl;	// length of next code
+    var count = 0;		// repeat count of the current code
+    var max_count = 7;		// max repeat count
+    var min_count = 4;		// min repeat count
+
+    /* tree[max_code+1].dl = -1; */  /* guard already set */
+    if(nextlen == 0) {
+      max_count = 138;
+      min_count = 3;
+    }
+
+    for(n = 0; n <= max_code; n++) {
+	curlen = nextlen;
+	nextlen = tree[n+1].dl;
+	if(++count < max_count && curlen == nextlen) {
+	    continue;
+	} else if(count < min_count) {
+	    do { zip_SEND_CODE(curlen, zip_bl_tree); } while(--count != 0);
+	} else if(curlen != 0) {
+	    if(curlen != prevlen) {
+		zip_SEND_CODE(curlen, zip_bl_tree);
+		count--;
+	    }
+	    // Assert(count >= 3 && count <= 6, " 3_6?");
+	    zip_SEND_CODE(zip_REP_3_6, zip_bl_tree);
+	    zip_send_bits(count - 3, 2);
+	} else if(count <= 10) {
+	    zip_SEND_CODE(zip_REPZ_3_10, zip_bl_tree);
+	    zip_send_bits(count-3, 3);
+	} else {
+	    zip_SEND_CODE(zip_REPZ_11_138, zip_bl_tree);
+	    zip_send_bits(count-11, 7);
+	}
+	count = 0;
+	prevlen = curlen;
+	if(nextlen == 0) {
+	    max_count = 138;
+	    min_count = 3;
+	} else if(curlen == nextlen) {
+	    max_count = 6;
+	    min_count = 3;
+	} else {
+	    max_count = 7;
+	    min_count = 4;
+	}
+    }
+}
+
+/* ==========================================================================
+ * Construct the Huffman tree for the bit lengths and return the index in
+ * bl_order of the last bit length code to send.
+ */
+var zip_build_bl_tree = function() {
+    var max_blindex;  // index of last bit length code of non zero freq
+
+    // Determine the bit length frequencies for literal and distance trees
+    zip_scan_tree(zip_dyn_ltree, zip_l_desc.max_code);
+    zip_scan_tree(zip_dyn_dtree, zip_d_desc.max_code);
+
+    // Build the bit length tree:
+    zip_build_tree(zip_bl_desc);
+    /* opt_len now includes the length of the tree representations, except
+     * the lengths of the bit lengths codes and the 5+5+4 bits for the counts.
+     */
+
+    /* Determine the number of bit length codes to send. The pkzip format
+     * requires that at least 4 bit length codes be sent. (appnote.txt says
+     * 3 but the actual value used is 4.)
+     */
+    for(max_blindex = zip_BL_CODES-1; max_blindex >= 3; max_blindex--) {
+	if(zip_bl_tree[zip_bl_order[max_blindex]].dl != 0) break;
+    }
+    /* Update opt_len to include the bit length tree and counts */
+    zip_opt_len += 3*(max_blindex+1) + 5+5+4;
+//    Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld",
+//	    encoder->opt_len, encoder->static_len));
+
+    return max_blindex;
+}
+
+/* ==========================================================================
+ * Send the header for a block using dynamic Huffman trees: the counts, the
+ * lengths of the bit length codes, the literal tree and the distance tree.
+ * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.
+ */
+var zip_send_all_trees = function(lcodes, dcodes, blcodes) { // number of codes for each tree
+    var rank; // index in bl_order
+
+//    Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes");
+//    Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES,
+//	    "too many codes");
+//    Tracev((stderr, "\nbl counts: "));
+    zip_send_bits(lcodes-257, 5); // not +255 as stated in appnote.txt
+    zip_send_bits(dcodes-1,   5);
+    zip_send_bits(blcodes-4,  4); // not -3 as stated in appnote.txt
+    for(rank = 0; rank < blcodes; rank++) {
+//      Tracev((stderr, "\nbl code %2d ", bl_order[rank]));
+	zip_send_bits(zip_bl_tree[zip_bl_order[rank]].dl, 3);
+    }
+
+    // send the literal tree
+    zip_send_tree(zip_dyn_ltree,lcodes-1);
+
+    // send the distance tree
+    zip_send_tree(zip_dyn_dtree,dcodes-1);
+}
+
+/* ==========================================================================
+ * Determine the best encoding for the current block: dynamic trees, static
+ * trees or store, and output the encoded block to the zip file.
+ */
+var zip_flush_block = function(eof) { // true if this is the last block for a file
+    var opt_lenb, static_lenb; // opt_len and static_len in bytes
+    var max_blindex;	// index of last bit length code of non zero freq
+    var stored_len;	// length of input block
+
+    stored_len = zip_strstart - zip_block_start;
+    zip_flag_buf[zip_last_flags] = zip_flags; // Save the flags for the last 8 items
+
+    // Construct the literal and distance trees
+    zip_build_tree(zip_l_desc);
+//    Tracev((stderr, "\nlit data: dyn %ld, stat %ld",
+//	    encoder->opt_len, encoder->static_len));
+
+    zip_build_tree(zip_d_desc);
+//    Tracev((stderr, "\ndist data: dyn %ld, stat %ld",
+//	    encoder->opt_len, encoder->static_len));
+    /* At this point, opt_len and static_len are the total bit lengths of
+     * the compressed block data, excluding the tree representations.
+     */
+
+    /* Build the bit length tree for the above two trees, and get the index
+     * in bl_order of the last bit length code to send.
+     */
+    max_blindex = zip_build_bl_tree();
+
+    // Determine the best encoding. Compute first the block length in bytes
+    opt_lenb	= (zip_opt_len   +3+7)>>3;
+    static_lenb = (zip_static_len+3+7)>>3;
+
+//    Trace((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u dist %u ",
+//	   opt_lenb, encoder->opt_len,
+//	   static_lenb, encoder->static_len, stored_len,
+//	   encoder->last_lit, encoder->last_dist));
+
+    if(static_lenb <= opt_lenb)
+	opt_lenb = static_lenb;
+    if(stored_len + 4 <= opt_lenb // 4: two words for the lengths
+       && zip_block_start >= 0) {
+	var i;
+
+	/* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE.
+	 * Otherwise we can't have processed more than WSIZE input bytes since
+	 * the last block flush, because compression would have been
+	 * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to
+	 * transform a block into a stored block.
+	 */
+	zip_send_bits((zip_STORED_BLOCK<<1)+eof, 3);  /* send block type */
+	zip_bi_windup();		 /* align on byte boundary */
+	zip_put_short(stored_len);
+	zip_put_short(~stored_len);
+
+      // copy block
+/*
+      p = &window[block_start];
+      for(i = 0; i < stored_len; i++)
+	put_byte(p[i]);
+*/
+	for(i = 0; i < stored_len; i++)
+	    zip_put_byte(zip_window[zip_block_start + i]);
+
+    } else if(static_lenb == opt_lenb) {
+	zip_send_bits((zip_STATIC_TREES<<1)+eof, 3);
+	zip_compress_block(zip_static_ltree, zip_static_dtree);
+    } else {
+	zip_send_bits((zip_DYN_TREES<<1)+eof, 3);
+	zip_send_all_trees(zip_l_desc.max_code+1,
+			   zip_d_desc.max_code+1,
+			   max_blindex+1);
+	zip_compress_block(zip_dyn_ltree, zip_dyn_dtree);
+    }
+
+    zip_init_block();
+
+    if(eof != 0)
+	zip_bi_windup();
+}
+
+/* ==========================================================================
+ * Save the match info and tally the frequency counts. Return true if
+ * the current block must be flushed.
+ */
+var zip_ct_tally = function(
+	dist, // distance of matched string
+	lc) { // match length-MIN_MATCH or unmatched char (if dist==0)
+    zip_l_buf[zip_last_lit++] = lc;
+    if(dist == 0) {
+	// lc is the unmatched char
+	zip_dyn_ltree[lc].fc++;
+    } else {
+	// Here, lc is the match length - MIN_MATCH
+	dist--;		    // dist = match distance - 1
+//      Assert((ush)dist < (ush)MAX_DIST &&
+//	     (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) &&
+//	     (ush)D_CODE(dist) < (ush)D_CODES,  "ct_tally: bad match");
+
+	zip_dyn_ltree[zip_length_code[lc]+zip_LITERALS+1].fc++;
+	zip_dyn_dtree[zip_D_CODE(dist)].fc++;
+
+	zip_d_buf[zip_last_dist++] = dist;
+	zip_flags |= zip_flag_bit;
+    }
+    zip_flag_bit <<= 1;
+
+    // Output the flags if they fill a byte
+    if((zip_last_lit & 7) == 0) {
+	zip_flag_buf[zip_last_flags++] = zip_flags;
+	zip_flags = 0;
+	zip_flag_bit = 1;
+    }
+    // Try to guess if it is profitable to stop the current block here
+    if(zip_compr_level > 2 && (zip_last_lit & 0xfff) == 0) {
+	// Compute an upper bound for the compressed length
+	var out_length = zip_last_lit * 8;
+	var in_length = zip_strstart - zip_block_start;
+	var dcode;
+
+	for(dcode = 0; dcode < zip_D_CODES; dcode++) {
+	    out_length += zip_dyn_dtree[dcode].fc * (5 + zip_extra_dbits[dcode]);
+	}
+	out_length >>= 3;
+//      Trace((stderr,"\nlast_lit %u, last_dist %u, in %ld, out ~%ld(%ld%%) ",
+//	     encoder->last_lit, encoder->last_dist, in_length, out_length,
+//	     100L - out_length*100L/in_length));
+	if(zip_last_dist < parseInt(zip_last_lit/2) &&
+	   out_length < parseInt(in_length/2))
+	    return true;
+    }
+    return (zip_last_lit == zip_LIT_BUFSIZE-1 ||
+	    zip_last_dist == zip_DIST_BUFSIZE);
+    /* We avoid equality with LIT_BUFSIZE because of wraparound at 64K
+     * on 16 bit machines and because stored blocks are restricted to
+     * 64K-1 bytes.
+     */
+}
+
+  /* ==========================================================================
+   * Send the block data compressed using the given Huffman trees
+   */
+var zip_compress_block = function(
+	ltree,	// literal tree
+	dtree) {	// distance tree
+    var dist;		// distance of matched string
+    var lc;		// match length or unmatched char (if dist == 0)
+    var lx = 0;		// running index in l_buf
+    var dx = 0;		// running index in d_buf
+    var fx = 0;		// running index in flag_buf
+    var flag = 0;	// current flags
+    var code;		// the code to send
+    var extra;		// number of extra bits to send
+
+    if(zip_last_lit != 0) do {
+	if((lx & 7) == 0)
+	    flag = zip_flag_buf[fx++];
+	lc = zip_l_buf[lx++] & 0xff;
+	if((flag & 1) == 0) {
+	    zip_SEND_CODE(lc, ltree); /* send a literal byte */
+//	Tracecv(isgraph(lc), (stderr," '%c' ", lc));
+	} else {
+	    // Here, lc is the match length - MIN_MATCH
+	    code = zip_length_code[lc];
+	    zip_SEND_CODE(code+zip_LITERALS+1, ltree); // send the length code
+	    extra = zip_extra_lbits[code];
+	    if(extra != 0) {
+		lc -= zip_base_length[code];
+		zip_send_bits(lc, extra); // send the extra length bits
+	    }
+	    dist = zip_d_buf[dx++];
+	    // Here, dist is the match distance - 1
+	    code = zip_D_CODE(dist);
+//	Assert (code < D_CODES, "bad d_code");
+
+	    zip_SEND_CODE(code, dtree);	  // send the distance code
+	    extra = zip_extra_dbits[code];
+	    if(extra != 0) {
+		dist -= zip_base_dist[code];
+		zip_send_bits(dist, extra);   // send the extra distance bits
+	    }
+	} // literal or match pair ?
+	flag >>= 1;
+    } while(lx < zip_last_lit);
+
+    zip_SEND_CODE(zip_END_BLOCK, ltree);
+}
+
+/* ==========================================================================
+ * Send a value on a given number of bits.
+ * IN assertion: length <= 16 and value fits in length bits.
+ */
+var zip_Buf_size = 16; // bit size of bi_buf
+var zip_send_bits = function(
+	value,	// value to send
+	length) {	// number of bits
+    /* If not enough room in bi_buf, use (valid) bits from bi_buf and
+     * (16 - bi_valid) bits from value, leaving (width - (16-bi_valid))
+     * unused bits in value.
+     */
+    if(zip_bi_valid > zip_Buf_size - length) {
+	zip_bi_buf |= (value << zip_bi_valid);
+	zip_put_short(zip_bi_buf);
+	zip_bi_buf = (value >> (zip_Buf_size - zip_bi_valid));
+	zip_bi_valid += length - zip_Buf_size;
+    } else {
+	zip_bi_buf |= value << zip_bi_valid;
+	zip_bi_valid += length;
+    }
+}
+
+/* ==========================================================================
+ * Reverse the first len bits of a code, using straightforward code (a faster
+ * method would use a table)
+ * IN assertion: 1 <= len <= 15
+ */
+var zip_bi_reverse = function(
+	code,	// the value to invert
+	len) {	// its bit length
+    var res = 0;
+    do {
+	res |= code & 1;
+	code >>= 1;
+	res <<= 1;
+    } while(--len > 0);
+    return res >> 1;
+}
+
+/* ==========================================================================
+ * Write out any remaining bits in an incomplete byte.
+ */
+var zip_bi_windup = function() {
+    if(zip_bi_valid > 8) {
+	zip_put_short(zip_bi_buf);
+    } else if(zip_bi_valid > 0) {
+	zip_put_byte(zip_bi_buf);
+    }
+    zip_bi_buf = 0;
+    zip_bi_valid = 0;
+}
+
+var zip_qoutbuf = function() {
+    if(zip_outcnt != 0) {
+	var q, i;
+	q = zip_new_queue();
+	if(zip_qhead == null)
+	    zip_qhead = zip_qtail = q;
+	else
+	    zip_qtail = zip_qtail.next = q;
+	q.len = zip_outcnt - zip_outoff;
+//      System.arraycopy(zip_outbuf, zip_outoff, q.ptr, 0, q.len);
+	for(i = 0; i < q.len; i++)
+	    q.ptr[i] = zip_outbuf[zip_outoff + i];
+	zip_outcnt = zip_outoff = 0;
+    }
+}
+
+var zip_deflate = function(str, level) {
+    var i, j;
+
+    zip_deflate_data = str;
+    zip_deflate_pos = 0;
+    if(typeof level == "undefined")
+	level = zip_DEFAULT_LEVEL;
+    zip_deflate_start(level);
+
+    var buff = new Array(1024);
+    var aout = [];
+    while((i = zip_deflate_internal(buff, 0, buff.length)) > 0) {
+	var cbuf = new Array(i);
+	for(j = 0; j < i; j++){
+	    cbuf[j] = String.fromCharCode(buff[j]);
+	}
+	aout[aout.length] = cbuf.join("");
+    }
+    zip_deflate_data = null; // G.C.
+    return aout.join("");
+}
+
+if (! window.RawDeflate) RawDeflate = {};
+RawDeflate.deflate = zip_deflate;
+
+})();

+ 753 - 0
apps/html2markdown/libs/rawinflate.js

@@ -0,0 +1,753 @@
+/*
+ * $Id: rawinflate.js,v 0.2 2009/03/01 18:32:24 dankogai Exp $
+ *
+ * original:
+ * http://www.onicos.com/staff/iz/amuse/javascript/expert/inflate.txt
+ */
+
+(function(){
+
+/* Copyright (C) 1999 Masanao Izumo <[email protected]>
+ * Version: 1.0.0.1
+ * LastModified: Dec 25 1999
+ */
+
+/* Interface:
+ * data = zip_inflate(src);
+ */
+
+/* constant parameters */
+var zip_WSIZE = 32768;		// Sliding Window size
+var zip_STORED_BLOCK = 0;
+var zip_STATIC_TREES = 1;
+var zip_DYN_TREES    = 2;
+
+/* for inflate */
+var zip_lbits = 9; 		// bits in base literal/length lookup table
+var zip_dbits = 6; 		// bits in base distance lookup table
+var zip_INBUFSIZ = 32768;	// Input buffer size
+var zip_INBUF_EXTRA = 64;	// Extra buffer
+
+/* variables (inflate) */
+var zip_slide;
+var zip_wp;			// current position in slide
+var zip_fixed_tl = null;	// inflate static
+var zip_fixed_td;		// inflate static
+var zip_fixed_bl, fixed_bd;	// inflate static
+var zip_bit_buf;		// bit buffer
+var zip_bit_len;		// bits in bit buffer
+var zip_method;
+var zip_eof;
+var zip_copy_leng;
+var zip_copy_dist;
+var zip_tl, zip_td;	// literal/length and distance decoder tables
+var zip_bl, zip_bd;	// number of bits decoded by tl and td
+
+var zip_inflate_data;
+var zip_inflate_pos;
+
+
+/* constant tables (inflate) */
+var zip_MASK_BITS = new Array(
+    0x0000,
+    0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff,
+    0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff);
+// Tables for deflate from PKZIP's appnote.txt.
+var zip_cplens = new Array( // Copy lengths for literal codes 257..285
+    3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
+    35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0);
+/* note: see note #13 above about the 258 in this list. */
+var zip_cplext = new Array( // Extra bits for literal codes 257..285
+    0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
+    3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 99, 99); // 99==invalid
+var zip_cpdist = new Array( // Copy offsets for distance codes 0..29
+    1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
+    257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
+    8193, 12289, 16385, 24577);
+var zip_cpdext = new Array( // Extra bits for distance codes
+    0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
+    7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
+    12, 12, 13, 13);
+var zip_border = new Array(  // Order of the bit length code lengths
+    16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15);
+/* objects (inflate) */
+
+var zip_HuftList = function() {
+    this.next = null;
+    this.list = null;
+}
+
+var zip_HuftNode = function() {
+    this.e = 0; // number of extra bits or operation
+    this.b = 0; // number of bits in this code or subcode
+
+    // union
+    this.n = 0; // literal, length base, or distance base
+    this.t = null; // (zip_HuftNode) pointer to next level of table
+}
+
+var zip_HuftBuild = function(b,	// code lengths in bits (all assumed <= BMAX)
+		       n,	// number of codes (assumed <= N_MAX)
+		       s,	// number of simple-valued codes (0..s-1)
+		       d,	// list of base values for non-simple codes
+		       e,	// list of extra bits for non-simple codes
+		       mm	// maximum lookup bits
+		   ) {
+    this.BMAX = 16;   // maximum bit length of any code
+    this.N_MAX = 288; // maximum number of codes in any set
+    this.status = 0;	// 0: success, 1: incomplete table, 2: bad input
+    this.root = null;	// (zip_HuftList) starting table
+    this.m = 0;		// maximum lookup bits, returns actual
+
+/* Given a list of code lengths and a maximum table size, make a set of
+   tables to decode that set of codes.	Return zero on success, one if
+   the given code set is incomplete (the tables are still built in this
+   case), two if the input is invalid (all zero length codes or an
+   oversubscribed set of lengths), and three if not enough memory.
+   The code with value 256 is special, and the tables are constructed
+   so that no bits beyond that code are fetched when that code is
+   decoded. */
+    {
+	var a;			// counter for codes of length k
+	var c = new Array(this.BMAX+1);	// bit length count table
+	var el;			// length of EOB code (value 256)
+	var f;			// i repeats in table every f entries
+	var g;			// maximum code length
+	var h;			// table level
+	var i;			// counter, current code
+	var j;			// counter
+	var k;			// number of bits in current code
+	var lx = new Array(this.BMAX+1);	// stack of bits per table
+	var p;			// pointer into c[], b[], or v[]
+	var pidx;		// index of p
+	var q;			// (zip_HuftNode) points to current table
+	var r = new zip_HuftNode(); // table entry for structure assignment
+	var u = new Array(this.BMAX); // zip_HuftNode[BMAX][]  table stack
+	var v = new Array(this.N_MAX); // values in order of bit length
+	var w;
+	var x = new Array(this.BMAX+1);// bit offsets, then code stack
+	var xp;			// pointer into x or c
+	var y;			// number of dummy codes added
+	var z;			// number of entries in current table
+	var o;
+	var tail;		// (zip_HuftList)
+
+	tail = this.root = null;
+	for(i = 0; i < c.length; i++)
+	    c[i] = 0;
+	for(i = 0; i < lx.length; i++)
+	    lx[i] = 0;
+	for(i = 0; i < u.length; i++)
+	    u[i] = null;
+	for(i = 0; i < v.length; i++)
+	    v[i] = 0;
+	for(i = 0; i < x.length; i++)
+	    x[i] = 0;
+
+	// Generate counts for each bit length
+	el = n > 256 ? b[256] : this.BMAX; // set length of EOB code, if any
+	p = b; pidx = 0;
+	i = n;
+	do {
+	    c[p[pidx]]++;	// assume all entries <= BMAX
+	    pidx++;
+	} while(--i > 0);
+	if(c[0] == n) {	// null input--all zero length codes
+	    this.root = null;
+	    this.m = 0;
+	    this.status = 0;
+	    return;
+	}
+
+	// Find minimum and maximum length, bound *m by those
+	for(j = 1; j <= this.BMAX; j++)
+	    if(c[j] != 0)
+		break;
+	k = j;			// minimum code length
+	if(mm < j)
+	    mm = j;
+	for(i = this.BMAX; i != 0; i--)
+	    if(c[i] != 0)
+		break;
+	g = i;			// maximum code length
+	if(mm > i)
+	    mm = i;
+
+	// Adjust last length count to fill out codes, if needed
+	for(y = 1 << j; j < i; j++, y <<= 1)
+	    if((y -= c[j]) < 0) {
+		this.status = 2;	// bad input: more codes than bits
+		this.m = mm;
+		return;
+	    }
+	if((y -= c[i]) < 0) {
+	    this.status = 2;
+	    this.m = mm;
+	    return;
+	}
+	c[i] += y;
+
+	// Generate starting offsets into the value table for each length
+	x[1] = j = 0;
+	p = c;
+	pidx = 1;
+	xp = 2;
+	while(--i > 0)		// note that i == g from above
+	    x[xp++] = (j += p[pidx++]);
+
+	// Make a table of values in order of bit lengths
+	p = b; pidx = 0;
+	i = 0;
+	do {
+	    if((j = p[pidx++]) != 0)
+		v[x[j]++] = i;
+	} while(++i < n);
+	n = x[g];			// set n to length of v
+
+	// Generate the Huffman codes and for each, make the table entries
+	x[0] = i = 0;		// first Huffman code is zero
+	p = v; pidx = 0;		// grab values in bit order
+	h = -1;			// no tables yet--level -1
+	w = lx[0] = 0;		// no bits decoded yet
+	q = null;			// ditto
+	z = 0;			// ditto
+
+	// go through the bit lengths (k already is bits in shortest code)
+	for(; k <= g; k++) {
+	    a = c[k];
+	    while(a-- > 0) {
+		// here i is the Huffman code of length k bits for value p[pidx]
+		// make tables up to required level
+		while(k > w + lx[1 + h]) {
+		    w += lx[1 + h]; // add bits already decoded
+		    h++;
+
+		    // compute minimum size table less than or equal to *m bits
+		    z = (z = g - w) > mm ? mm : z; // upper limit
+		    if((f = 1 << (j = k - w)) > a + 1) { // try a k-w bit table
+			// too few codes for k-w bit table
+			f -= a + 1;	// deduct codes from patterns left
+			xp = k;
+			while(++j < z) { // try smaller tables up to z bits
+			    if((f <<= 1) <= c[++xp])
+				break;	// enough codes to use up j bits
+			    f -= c[xp];	// else deduct codes from patterns
+			}
+		    }
+		    if(w + j > el && w < el)
+			j = el - w;	// make EOB code end at table
+		    z = 1 << j;	// table entries for j-bit table
+		    lx[1 + h] = j; // set table size in stack
+
+		    // allocate and link in new table
+		    q = new Array(z);
+		    for(o = 0; o < z; o++) {
+			q[o] = new zip_HuftNode();
+		    }
+
+		    if(tail == null)
+			tail = this.root = new zip_HuftList();
+		    else
+			tail = tail.next = new zip_HuftList();
+		    tail.next = null;
+		    tail.list = q;
+		    u[h] = q;	// table starts after link
+
+		    /* connect to last table, if there is one */
+		    if(h > 0) {
+			x[h] = i;		// save pattern for backing up
+			r.b = lx[h];	// bits to dump before this table
+			r.e = 16 + j;	// bits in this table
+			r.t = q;		// pointer to this table
+			j = (i & ((1 << w) - 1)) >> (w - lx[h]);
+			u[h-1][j].e = r.e;
+			u[h-1][j].b = r.b;
+			u[h-1][j].n = r.n;
+			u[h-1][j].t = r.t;
+		    }
+		}
+
+		// set up table entry in r
+		r.b = k - w;
+		if(pidx >= n)
+		    r.e = 99;		// out of values--invalid code
+		else if(p[pidx] < s) {
+		    r.e = (p[pidx] < 256 ? 16 : 15); // 256 is end-of-block code
+		    r.n = p[pidx++];	// simple code is just the value
+		} else {
+		    r.e = e[p[pidx] - s];	// non-simple--look up in lists
+		    r.n = d[p[pidx++] - s];
+		}
+
+		// fill code-like entries with r //
+		f = 1 << (k - w);
+		for(j = i >> w; j < z; j += f) {
+		    q[j].e = r.e;
+		    q[j].b = r.b;
+		    q[j].n = r.n;
+		    q[j].t = r.t;
+		}
+
+		// backwards increment the k-bit code i
+		for(j = 1 << (k - 1); (i & j) != 0; j >>= 1)
+		    i ^= j;
+		i ^= j;
+
+		// backup over finished tables
+		while((i & ((1 << w) - 1)) != x[h]) {
+		    w -= lx[h];		// don't need to update q
+		    h--;
+		}
+	    }
+	}
+
+	/* return actual size of base table */
+	this.m = lx[1];
+
+	/* Return true (1) if we were given an incomplete table */
+	this.status = ((y != 0 && g != 1) ? 1 : 0);
+    } /* end of constructor */
+}
+
+
+/* routines (inflate) */
+
+var zip_GET_BYTE = function() {
+    if(zip_inflate_data.length == zip_inflate_pos)
+	return -1;
+    return zip_inflate_data.charCodeAt(zip_inflate_pos++) & 0xff;
+}
+
+var zip_NEEDBITS = function(n) {
+    while(zip_bit_len < n) {
+	zip_bit_buf |= zip_GET_BYTE() << zip_bit_len;
+	zip_bit_len += 8;
+    }
+}
+
+var zip_GETBITS = function(n) {
+    return zip_bit_buf & zip_MASK_BITS[n];
+}
+
+var zip_DUMPBITS = function(n) {
+    zip_bit_buf >>= n;
+    zip_bit_len -= n;
+}
+
+var zip_inflate_codes = function(buff, off, size) {
+    /* inflate (decompress) the codes in a deflated (compressed) block.
+       Return an error code or zero if it all goes ok. */
+    var e;		// table entry flag/number of extra bits
+    var t;		// (zip_HuftNode) pointer to table entry
+    var n;
+
+    if(size == 0)
+      return 0;
+
+    // inflate the coded data
+    n = 0;
+    for(;;) {			// do until end of block
+	zip_NEEDBITS(zip_bl);
+	t = zip_tl.list[zip_GETBITS(zip_bl)];
+	e = t.e;
+	while(e > 16) {
+	    if(e == 99)
+		return -1;
+	    zip_DUMPBITS(t.b);
+	    e -= 16;
+	    zip_NEEDBITS(e);
+	    t = t.t[zip_GETBITS(e)];
+	    e = t.e;
+	}
+	zip_DUMPBITS(t.b);
+
+	if(e == 16) {		// then it's a literal
+	    zip_wp &= zip_WSIZE - 1;
+	    buff[off + n++] = zip_slide[zip_wp++] = t.n;
+	    if(n == size)
+		return size;
+	    continue;
+	}
+
+	// exit if end of block
+	if(e == 15)
+	    break;
+
+	// it's an EOB or a length
+
+	// get length of block to copy
+	zip_NEEDBITS(e);
+	zip_copy_leng = t.n + zip_GETBITS(e);
+	zip_DUMPBITS(e);
+
+	// decode distance of block to copy
+	zip_NEEDBITS(zip_bd);
+	t = zip_td.list[zip_GETBITS(zip_bd)];
+	e = t.e;
+
+	while(e > 16) {
+	    if(e == 99)
+		return -1;
+	    zip_DUMPBITS(t.b);
+	    e -= 16;
+	    zip_NEEDBITS(e);
+	    t = t.t[zip_GETBITS(e)];
+	    e = t.e;
+	}
+	zip_DUMPBITS(t.b);
+	zip_NEEDBITS(e);
+	zip_copy_dist = zip_wp - t.n - zip_GETBITS(e);
+	zip_DUMPBITS(e);
+
+	// do the copy
+	while(zip_copy_leng > 0 && n < size) {
+	    zip_copy_leng--;
+	    zip_copy_dist &= zip_WSIZE - 1;
+	    zip_wp &= zip_WSIZE - 1;
+	    buff[off + n++] = zip_slide[zip_wp++]
+		= zip_slide[zip_copy_dist++];
+	}
+
+	if(n == size)
+	    return size;
+    }
+
+    zip_method = -1; // done
+    return n;
+}
+
+var zip_inflate_stored = function(buff, off, size) {
+    /* "decompress" an inflated type 0 (stored) block. */
+    var n;
+
+    // go to byte boundary
+    n = zip_bit_len & 7;
+    zip_DUMPBITS(n);
+
+    // get the length and its complement
+    zip_NEEDBITS(16);
+    n = zip_GETBITS(16);
+    zip_DUMPBITS(16);
+    zip_NEEDBITS(16);
+    if(n != ((~zip_bit_buf) & 0xffff))
+	return -1;			// error in compressed data
+    zip_DUMPBITS(16);
+
+    // read and output the compressed data
+    zip_copy_leng = n;
+
+    n = 0;
+    while(zip_copy_leng > 0 && n < size) {
+	zip_copy_leng--;
+	zip_wp &= zip_WSIZE - 1;
+	zip_NEEDBITS(8);
+	buff[off + n++] = zip_slide[zip_wp++] =
+	    zip_GETBITS(8);
+	zip_DUMPBITS(8);
+    }
+
+    if(zip_copy_leng == 0)
+      zip_method = -1; // done
+    return n;
+}
+
+var zip_inflate_fixed = function(buff, off, size) {
+    /* decompress an inflated type 1 (fixed Huffman codes) block.  We should
+       either replace this with a custom decoder, or at least precompute the
+       Huffman tables. */
+
+    // if first time, set up tables for fixed blocks
+    if(zip_fixed_tl == null) {
+	var i;			// temporary variable
+	var l = new Array(288);	// length list for huft_build
+	var h;	// zip_HuftBuild
+
+	// literal table
+	for(i = 0; i < 144; i++)
+	    l[i] = 8;
+	for(; i < 256; i++)
+	    l[i] = 9;
+	for(; i < 280; i++)
+	    l[i] = 7;
+	for(; i < 288; i++)	// make a complete, but wrong code set
+	    l[i] = 8;
+	zip_fixed_bl = 7;
+
+	h = new zip_HuftBuild(l, 288, 257, zip_cplens, zip_cplext,
+			      zip_fixed_bl);
+	if(h.status != 0) {
+	    alert("HufBuild error: "+h.status);
+	    return -1;
+	}
+	zip_fixed_tl = h.root;
+	zip_fixed_bl = h.m;
+
+	// distance table
+	for(i = 0; i < 30; i++)	// make an incomplete code set
+	    l[i] = 5;
+	zip_fixed_bd = 5;
+
+	h = new zip_HuftBuild(l, 30, 0, zip_cpdist, zip_cpdext, zip_fixed_bd);
+	if(h.status > 1) {
+	    zip_fixed_tl = null;
+	    alert("HufBuild error: "+h.status);
+	    return -1;
+	}
+	zip_fixed_td = h.root;
+	zip_fixed_bd = h.m;
+    }
+
+    zip_tl = zip_fixed_tl;
+    zip_td = zip_fixed_td;
+    zip_bl = zip_fixed_bl;
+    zip_bd = zip_fixed_bd;
+    return zip_inflate_codes(buff, off, size);
+}
+
+var zip_inflate_dynamic = function(buff, off, size) {
+    // decompress an inflated type 2 (dynamic Huffman codes) block.
+    var i;		// temporary variables
+    var j;
+    var l;		// last length
+    var n;		// number of lengths to get
+    var t;		// (zip_HuftNode) literal/length code table
+    var nb;		// number of bit length codes
+    var nl;		// number of literal/length codes
+    var nd;		// number of distance codes
+    var ll = new Array(286+30); // literal/length and distance code lengths
+    var h;		// (zip_HuftBuild)
+
+    for(i = 0; i < ll.length; i++)
+	ll[i] = 0;
+
+    // read in table lengths
+    zip_NEEDBITS(5);
+    nl = 257 + zip_GETBITS(5);	// number of literal/length codes
+    zip_DUMPBITS(5);
+    zip_NEEDBITS(5);
+    nd = 1 + zip_GETBITS(5);	// number of distance codes
+    zip_DUMPBITS(5);
+    zip_NEEDBITS(4);
+    nb = 4 + zip_GETBITS(4);	// number of bit length codes
+    zip_DUMPBITS(4);
+    if(nl > 286 || nd > 30)
+      return -1;		// bad lengths
+
+    // read in bit-length-code lengths
+    for(j = 0; j < nb; j++)
+    {
+	zip_NEEDBITS(3);
+	ll[zip_border[j]] = zip_GETBITS(3);
+	zip_DUMPBITS(3);
+    }
+    for(; j < 19; j++)
+	ll[zip_border[j]] = 0;
+
+    // build decoding table for trees--single level, 7 bit lookup
+    zip_bl = 7;
+    h = new zip_HuftBuild(ll, 19, 19, null, null, zip_bl);
+    if(h.status != 0)
+	return -1;	// incomplete code set
+
+    zip_tl = h.root;
+    zip_bl = h.m;
+
+    // read in literal and distance code lengths
+    n = nl + nd;
+    i = l = 0;
+    while(i < n) {
+	zip_NEEDBITS(zip_bl);
+	t = zip_tl.list[zip_GETBITS(zip_bl)];
+	j = t.b;
+	zip_DUMPBITS(j);
+	j = t.n;
+	if(j < 16)		// length of code in bits (0..15)
+	    ll[i++] = l = j;	// save last length in l
+	else if(j == 16) {	// repeat last length 3 to 6 times
+	    zip_NEEDBITS(2);
+	    j = 3 + zip_GETBITS(2);
+	    zip_DUMPBITS(2);
+	    if(i + j > n)
+		return -1;
+	    while(j-- > 0)
+		ll[i++] = l;
+	} else if(j == 17) {	// 3 to 10 zero length codes
+	    zip_NEEDBITS(3);
+	    j = 3 + zip_GETBITS(3);
+	    zip_DUMPBITS(3);
+	    if(i + j > n)
+		return -1;
+	    while(j-- > 0)
+		ll[i++] = 0;
+	    l = 0;
+	} else {		// j == 18: 11 to 138 zero length codes
+	    zip_NEEDBITS(7);
+	    j = 11 + zip_GETBITS(7);
+	    zip_DUMPBITS(7);
+	    if(i + j > n)
+		return -1;
+	    while(j-- > 0)
+		ll[i++] = 0;
+	    l = 0;
+	}
+    }
+
+    // build the decoding tables for literal/length and distance codes
+    zip_bl = zip_lbits;
+    h = new zip_HuftBuild(ll, nl, 257, zip_cplens, zip_cplext, zip_bl);
+    if(zip_bl == 0)	// no literals or lengths
+	h.status = 1;
+    if(h.status != 0) {
+	if(h.status == 1)
+	    ;// **incomplete literal tree**
+	return -1;		// incomplete code set
+    }
+    zip_tl = h.root;
+    zip_bl = h.m;
+
+    for(i = 0; i < nd; i++)
+	ll[i] = ll[i + nl];
+    zip_bd = zip_dbits;
+    h = new zip_HuftBuild(ll, nd, 0, zip_cpdist, zip_cpdext, zip_bd);
+    zip_td = h.root;
+    zip_bd = h.m;
+
+    if(zip_bd == 0 && nl > 257) {   // lengths but no distances
+	// **incomplete distance tree**
+	return -1;
+    }
+
+    if(h.status == 1) {
+	;// **incomplete distance tree**
+    }
+    if(h.status != 0)
+	return -1;
+
+    // decompress until an end-of-block code
+    return zip_inflate_codes(buff, off, size);
+}
+
+var zip_inflate_start = function() {
+    var i;
+
+    if(zip_slide == null)
+	zip_slide = new Array(2 * zip_WSIZE);
+    zip_wp = 0;
+    zip_bit_buf = 0;
+    zip_bit_len = 0;
+    zip_method = -1;
+    zip_eof = false;
+    zip_copy_leng = zip_copy_dist = 0;
+    zip_tl = null;
+}
+
+var zip_inflate_internal = function(buff, off, size) {
+    // decompress an inflated entry
+    var n, i;
+
+    n = 0;
+    while(n < size) {
+	if(zip_eof && zip_method == -1)
+	    return n;
+
+	if(zip_copy_leng > 0) {
+	    if(zip_method != zip_STORED_BLOCK) {
+		// STATIC_TREES or DYN_TREES
+		while(zip_copy_leng > 0 && n < size) {
+		    zip_copy_leng--;
+		    zip_copy_dist &= zip_WSIZE - 1;
+		    zip_wp &= zip_WSIZE - 1;
+		    buff[off + n++] = zip_slide[zip_wp++] =
+			zip_slide[zip_copy_dist++];
+		}
+	    } else {
+		while(zip_copy_leng > 0 && n < size) {
+		    zip_copy_leng--;
+		    zip_wp &= zip_WSIZE - 1;
+		    zip_NEEDBITS(8);
+		    buff[off + n++] = zip_slide[zip_wp++] = zip_GETBITS(8);
+		    zip_DUMPBITS(8);
+		}
+		if(zip_copy_leng == 0)
+		    zip_method = -1; // done
+	    }
+	    if(n == size)
+		return n;
+	}
+
+	if(zip_method == -1) {
+	    if(zip_eof)
+		break;
+
+	    // read in last block bit
+	    zip_NEEDBITS(1);
+	    if(zip_GETBITS(1) != 0)
+		zip_eof = true;
+	    zip_DUMPBITS(1);
+
+	    // read in block type
+	    zip_NEEDBITS(2);
+	    zip_method = zip_GETBITS(2);
+	    zip_DUMPBITS(2);
+	    zip_tl = null;
+	    zip_copy_leng = 0;
+	}
+
+	switch(zip_method) {
+	  case 0: // zip_STORED_BLOCK
+	    i = zip_inflate_stored(buff, off + n, size - n);
+	    break;
+
+	  case 1: // zip_STATIC_TREES
+	    if(zip_tl != null)
+		i = zip_inflate_codes(buff, off + n, size - n);
+	    else
+		i = zip_inflate_fixed(buff, off + n, size - n);
+	    break;
+
+	  case 2: // zip_DYN_TREES
+	    if(zip_tl != null)
+		i = zip_inflate_codes(buff, off + n, size - n);
+	    else
+		i = zip_inflate_dynamic(buff, off + n, size - n);
+	    break;
+
+	  default: // error
+	    i = -1;
+	    break;
+	}
+
+	if(i == -1) {
+	    if(zip_eof)
+		return 0;
+	    return -1;
+	}
+	n += i;
+    }
+    return n;
+}
+
+var zip_inflate = function(str) {
+    var i, j;
+
+    zip_inflate_start();
+    zip_inflate_data = str;
+    zip_inflate_pos = 0;
+
+    var buff = new Array(1024);
+    var aout = [];
+    while((i = zip_inflate_internal(buff, 0, buff.length)) > 0) {
+	var cbuf = new Array(i);
+	for(j = 0; j < i; j++){
+	    cbuf[j] = String.fromCharCode(buff[j]);
+	}
+	aout[aout.length] = cbuf.join("");
+    }
+    zip_inflate_data = null; // G.C.
+    return aout.join("");
+}
+
+if (! window.RawDeflate) RawDeflate = {};
+RawDeflate.inflate = zip_inflate;
+
+})();

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 3
apps/image-base64/index.css


+ 84 - 84
apps/image-base64/index.html

@@ -1,84 +1,84 @@
-<!DOCTYPE HTML>
-<html lang="zh-CN">
-    <head>
-        <title>图片Base64工具(DataURI数据)</title>
-        <meta charset="UTF-8">
-        <link rel="stylesheet" href="index.css" />
-        <script type="text/javascript" src="../static/vendor/vue/vue.js"></script>
-        <script src="../static/vendor/require/require.js"></script>
-    </head>
-    <body>
-        <div class="wrapper" id="pageContainer">
-            <div class="panel panel-default" style="margin-bottom: 0px;">
-                <div class="panel-heading">
-                    <h3 class="panel-title">
-                        <a href="http://www.baidufe.com/fehelper/feedback.html" target="_blank" class="x-a-high">
-                            <img src="../static/img/fe-16.png" alt="fehelper"/> FeHelper
-                        </a>:{{ toolName[curType] }}
-
-                        <span class="x-switch ui-fl-r" ref="btnSwitch" @click="trans">切换为{{toolName[nextType]}}&gt;&gt;</span>
-                    </h3>
-                </div>
-            </div>
-            <div class="panel-body mod-imagebase64" ref="imageBase64" v-show="curType=='image'">
-                <div class="row">
-                  <table>
-                      <tr>
-                          <td>
-                            <div class="x-panel" ref="panelBox">
-                              <img id="preview" alt="" :src="previewSrc" v-show="!!previewSrc.length">
-                              <div class="x-tips">
-                                  <a id="upload" href="#" ref="uploadBox" @click="upload($event)">选择图片</a><br>
-                                  或者选择一张图片拖拽图片到这里来
-                              </div>
-                            </div>
-
-                              <div class="tips">
-                                  1、支持<i>屏幕截图</i>后直接在此处粘贴进行转化<br/>2、支持<i>复制文件、复制图片</i>在线地址在此处直接粘贴进行转化
-                              </div>
-                          </td>
-                          <td>
-                              <textarea id="base64Result" title="点击自动选择" placeholder="内容会自动生成..." readonly ref="resultBox" @click="select()" v-model="resultContent" class="form-control"></textarea>
-                              <div class="x-result-info">
-                                  <div class="x-item">
-                                      <span class="x-title">原始图片大小:</span><span id="sizeOri">{{sizeOri}}</span>
-                                  </div>
-                                  <div class="x-item">
-                                      <span class="x-title">DataUri&nbsp;&nbsp;大小:</span><span id="sizeBase">{{sizeBase}}</span>
-                                  </div>
-                              </div>
-                          </td>
-                      </tr>
-                  </table>
-                  <form action="#">
-                      <input type="file" id="file" accept=".jpg,.jpeg,.gif,.png,.bmp" ref="fileBox" @change="convert()">
-                  </form>
-                  <img id="img" alt="">
-                </div>
-            </div>
-
-            <div class="panel-body mod-base64image" ref="base64Image" v-show="curType=='base64'">
-                <div class="row">
-                    <table>
-                        <tr>
-                            <td>
-                                <textarea id="base64Input" class="form-control" title="点击自动选择" placeholder="在这里粘贴DataURI数据..." v-model="txtBase64Input"></textarea>
-                            </td>
-                            <td>
-                                <div class="x-panel">
-                                    <img id="base64Image" style="width:100%;height:100%;" alt="" :src="txtBase64Output" v-show="!!txtBase64Input.length" @error="loadError">
-                                </div>
-                            </td>
-                        </tr>
-                    </table>
-                </div>
-            </div>
-        
-
-        <div v-show="!!error.length" class="x-error">{{ error }}</div>
-        </div>
-
-
-        <script type="text/javascript" src="index.js"></script>
-    </body>
-</html>
+<!DOCTYPE HTML>
+<html lang="zh-CN">
+    <head>
+        <title>图片Base64工具(DataURI数据)</title>
+        <meta charset="UTF-8">
+        <link rel="shortcut icon" href="../static/img/favicon.ico">
+        <link rel="stylesheet" href="index.css" />
+        <script type="text/javascript" src="../static/vendor/vue/vue.js"></script>
+    </head>
+    <body>
+        <div class="wrapper" id="pageContainer">
+            <div class="panel panel-default" style="margin-bottom: 0px;">
+                <div class="panel-heading">
+                    <h3 class="panel-title">
+                        <a href="http://www.baidufe.com/fehelper/feedback.html" target="_blank" class="x-a-high">
+                            <img src="../static/img/fe-16.png" alt="fehelper"/> FeHelper
+                        </a>:{{ toolName[curType] }}
+
+                        <span class="x-switch ui-fl-r" ref="btnSwitch" @click="trans">切换为{{toolName[nextType]}}&gt;&gt;</span>
+                    </h3>
+                </div>
+            </div>
+            <div class="panel-body mod-imagebase64" ref="imageBase64" v-show="curType=='image'">
+                <div class="row">
+                  <table>
+                      <tr>
+                          <td>
+                            <div class="x-panel" ref="panelBox">
+                              <img id="preview" alt="" :src="previewSrc" v-show="!!previewSrc.length">
+                              <div class="x-tips">
+                                  <a id="upload" href="#" ref="uploadBox" @click="upload($event)">选择图片</a><br>
+                                  或者选择一张图片拖拽图片到这里来
+                              </div>
+                            </div>
+
+                              <div class="tips">
+                                  1、支持<i>屏幕截图</i>后直接在此处粘贴进行转化<br/>2、支持<i>复制文件、复制图片</i>在线地址在此处直接粘贴进行转化
+                              </div>
+                          </td>
+                          <td>
+                              <textarea id="base64Result" title="点击自动选择" placeholder="内容会自动生成..." readonly ref="resultBox" @click="select()" v-model="resultContent" class="form-control"></textarea>
+                              <div class="x-result-info">
+                                  <div class="x-item">
+                                      <span class="x-title">原始图片大小:</span><span id="sizeOri">{{sizeOri}}</span>
+                                  </div>
+                                  <div class="x-item">
+                                      <span class="x-title">DataUri&nbsp;&nbsp;大小:</span><span id="sizeBase">{{sizeBase}}</span>
+                                  </div>
+                              </div>
+                          </td>
+                      </tr>
+                  </table>
+                  <form action="#">
+                      <input type="file" id="file" accept=".jpg,.jpeg,.gif,.png,.bmp" ref="fileBox" @change="convert()">
+                  </form>
+                  <img id="img" alt="">
+                </div>
+            </div>
+
+            <div class="panel-body mod-base64image" ref="base64Image" v-show="curType=='base64'">
+                <div class="row">
+                    <table>
+                        <tr>
+                            <td>
+                                <textarea id="base64Input" class="form-control" title="点击自动选择" placeholder="在这里粘贴DataURI数据..." v-model="txtBase64Input"></textarea>
+                            </td>
+                            <td>
+                                <div class="x-panel">
+                                    <img id="base64Image" alt="" :src="txtBase64Output" v-show="!!txtBase64Input.length" @error="loadError">
+                                </div>
+                            </td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+
+            <div v-show="!!error.length" v-cloak class="x-error" v-html="error"></div>
+        </div>
+
+        <script type="text/javascript" src="index.js"></script>
+
+        <script src="../static/vendor/jquery/jquery-3.3.1.min.js"></script>
+    </body>
+</html>

+ 13 - 15
apps/image-base64/index.js

@@ -20,7 +20,7 @@ new Vue({
         txtBase64Input:{
             immediate: true,
             handler(newVal, oldVal) {
-                this.error = ''   
+                this.error = ''
                 this.txtBase64Output = ''
                 if(newVal.length === 0) return
                 if(newVal.indexOf("data:") === -1) {
@@ -34,25 +34,23 @@ new Vue({
 
     mounted: function () {
 
-        let MSG_TYPE = Tarp.require('../static/js/msg_type');
-
         // 在tab创建或者更新时候,监听事件,看看是否有参数传递过来
-        chrome.runtime.onMessage.addListener((request, sender, callback) => {
-            if (request.type === MSG_TYPE.TAB_CREATED_OR_UPDATED && request.event === MSG_TYPE.IMAGE_BASE64) {
-                if (request.content) {
-                    if(this.curType !== 'image') {
+        if (location.protocol === 'chrome-extension:') {
+            chrome.runtime.onMessage.addListener((request, sender, callback) => {
+                if (request.type === 'TAB_CREATED_OR_UPDATED' && request.content && request.event === location.pathname.split('/')[1]) {
+                    if (this.curType !== 'image') {
                         this.trans();
                     }
                     this.convertOnline(request.content, flag => {
                         if (!flag) {
-                            chrome.extension.getBackgroundPage().BgPageInstance.notify({
-                                message: '抱歉,' + request.content + ' 对应的图片未转码成功!'
-                            });
+                            alert('抱歉,' + request.content + ' 对应的图片未转码成功!');
                         }
                     });
+                    callback && callback();
+                    return true;
                 }
-            }
-        });
+            });
+        }
 
         //监听paste事件
         document.addEventListener('paste', (event) => {
@@ -206,9 +204,9 @@ new Vue({
         },
 
         loadError: function (e) {
-            if(this.curType === 'base64' && this.txtBase64Input.trim().length) {
-                this.error ='无法识别的Base64编码,请确认是正确的图片Data URI?';
+            if (this.curType === 'base64' && this.txtBase64Input.trim().length) {
+                this.error = ('无法识别的Base64编码,请确认是正确的图片Data URI?');
             }
         }
     }
-});
+});

+ 137 - 0
apps/index/fh-config.js

@@ -0,0 +1,137 @@
+window.FhConfig = {
+    toolMap: {
+        'json-format': {
+            name: 'JSON美化工具',
+            tips: '页面自动检测并格式化、手动格式化、乱码解码、排序、BigInt、编辑、下载、皮肤定制等',
+            icon: '⒥'
+        },
+        'json-diff': {
+            name: 'JSON比对工具',
+            tips: '支持两个JSON内容的自动键值比较,并高亮显示差异点,同时也能判断JSON是否合法',
+            icon: '☷'
+        },
+        'qr-code': {
+            name: '二维码/解码',
+            tips: '支持自定义颜色和icon的二维码生成器,并且支持多种模式的二维码解码,包括截图后粘贴解码',
+            icon: '▣',
+        },
+        'image-base64': {
+            name: '图片转Base64',
+            tips: '支持多种模式的图片转Base64格式,比如链接粘贴/截图粘贴等,也支持Base64数据逆转图片',
+            icon: '▤',
+        },
+        'sticky-notes': {
+            name: '我的便签笔记',
+            tips: '方便快捷的浏览器便签笔记工具,支持创建目录对笔记进行分类管理,笔记支持一键导出/导入',
+            icon: '✐',
+        },
+        'en-decode': {
+            name: '信息编码转换',
+            tips: '支持多格式的信息编解码,如Unicode、UTF-8、UTF-16、URL、Base64、MD5、Hex、Gzip等',
+            icon: '♨',
+        },
+        'code-beautify': {
+            name: '代码美化工具',
+            tips: '支持多语言的代码美化,包括 Javascript、CSS、HTML、XML、SQL,且会陆续支持更多格式',
+            icon: '✡',
+        },
+        'code-compress': {
+            name: '代码压缩工具',
+            tips: 'Web开发用,提供简单的代码压缩功能,支持HTML、Javascript、CSS代码压缩',
+            icon: '♯'
+        },
+        'timestamp': {
+            name: '时间(戳)转换',
+            tips: '本地化时间与时间戳之间的相互转换,支持秒/毫秒、支持世界时区切换、各时区时钟展示等',
+            icon: '♖',
+        },
+        'password': {
+            name: '随机密码生成',
+            tips: '将各种字符进行随机组合生成密码,可以由数字、大小写字母、特殊符号组成,支持指定长度',
+            icon: '♆',
+        },
+        'html2markdown': {
+            name: 'Markdown工具',
+            tips: 'Markdown编辑器,支持在线编写、预览、下载等,并支持HTML内容到Markdown格式的转换',
+            icon: 'ⓜ',
+        },
+        'postman': {
+            name: '简易版Postman',
+            tips: '开发过程中的接口调试工具,支持GET/POST/HEAD请求方式,且支持JSON内容自动格式化',
+            icon: '☯',
+        },
+        'regexp': {
+            name: 'JS正则表达式',
+            tips: '正则校验工具,默认提供一些工作中常用的正则表达式,支持内容实时匹配并高亮显示结果',
+            icon: '✙',
+        },
+        'trans-radix': {
+            name: '进制转换工具',
+            tips: '支持2进制到36进制数据之间的任意转换,比如:10进制转2进制,8进制转16进制,等等',
+            icon: '❖',
+        },
+        'trans-color': {
+            name: '颜色转换工具',
+            tips: '支持HEX颜色到RGB格式的互转,比如HEX颜色「#43ad7f」转RGB后为「rgb(67, 173, 127)」',
+            icon: '▶',
+        },
+        'crontab': {
+            name: 'Crontab工具',
+            tips: '一个简易的Crontab生成工具,支持随机生成Demo,编辑过程中,分时日月周会高亮提示',
+            icon: '½',
+        },
+        'loan-rate': {
+            name: '贷(还)款利率',
+            tips: '贷款或还款利率的计算器,按月呈现还款计划;并支持按还款额反推贷款实际利率',
+            icon: '$',
+        },
+        'devtools': {
+            name: 'FH开发者工具',
+            tips: '以开发平台的思想,FeHelper支持用户进行本地开发,将自己的插件功能集成进FH工具市场',
+            icon: '㉿',
+            extensionOnly: true
+        },
+        'page-monkey': {
+            name: '网页油猴工具',
+            tips: '自行配置页面匹配规则、编写Hack脚本,实现网页Hack,如页面自动刷新、自动抢票等',
+            icon: '♀',
+            extensionOnly: true
+        },
+        'screenshot': {
+            name: '网页截屏工具',
+            tips: '可对任意网页进行截屏,支持可视区域截屏、全网页滚动截屏,最终结果可预览后再保存',
+            icon: '✂',
+            extensionOnly: true
+        },
+        'color-picker': {
+            name: '页面取色工具',
+            tips: '可直接在网页上针对任意元素进行色值采集,将光标移动到需要取色的位置,单击确定即可',
+            icon: '✑',
+            extensionOnly: true
+        },
+        'naotu': {
+            name: '便捷思维导图',
+            tips: '轻量便捷,随想随用,支持自动保存、本地数据存储、批量数据导入导出、图片格式下载等',
+            icon: 'Ψ',
+        },
+        'grid-ruler': {
+            name: '网页栅格标尺',
+            tips: 'Web开发用,横竖两把尺子,以10px为单位,用以检测&校准当前网页的栅格对齐率',
+            icon: 'Ⅲ',
+            extensionOnly: true
+        },
+        'page-timing': {
+            name: '网页性能检测',
+            tips: '检测网页加载性能,包括握手、响应、渲染等各阶段耗时,同时提供Response Headers以便分析',
+            icon: 'Σ',
+            extensionOnly: true
+        },
+        'excel2json': {
+            name: 'Excel转JSON',
+            tips: '将Excel或CVS中的数据,直接转换成为结构化数据,如JSON、XML、MySQL、PHP等(By @hpng)',
+            icon: 'Ⓗ'
+        },
+    },
+
+    screenshots: ['fh-market.png', 'fh-popup.png', 'fh-menu.png']
+};

+ 395 - 0
apps/index/index.css

@@ -0,0 +1,395 @@
+@import url("../static/css/bootstrap.min.css");
+
+.wrapper {
+    padding-bottom: 100px;
+}
+
+#pageContainer > .panel-body {
+    margin: 0 !important;
+}
+
+.panel-body {
+    padding: 0 !important;
+}
+
+.group::before,
+.group::after,
+.p-container::before,
+.p-container::after {
+    display: table;
+    content: "";
+    clear: both;
+}
+
+.new-banner {
+    background: #F6F8FB !important;
+    display: block;
+    padding: 0;
+    margin: 0;
+    border: 0;
+    outline: 0;
+    font-weight: inherit;
+    font-style: inherit;
+    font-family: inherit;
+    font-size: 100%;
+    vertical-align: baseline;
+}
+
+.new-banner .group {
+    padding-top: 80px;
+    padding-bottom: 20px;
+    width: 1180px;
+    margin: 0 auto;
+    height: 100%;
+    box-sizing: border-box;
+    position: relative;
+    background-repeat: no-repeat;
+}
+
+.new-banner .group h1 {
+    color: #000;
+    font-size: 34px;
+    line-height: 54px;
+    font-family: PingFangSC-Medium;
+    margin: 0;
+    font-weight: 400;
+    padding: 0;
+}
+
+.new-banner .desc {
+    width: 560px;
+    color: #272A49;
+    opacity: .8;
+    filter: alpha(opacity=80);
+    line-height: 26px;
+    font-family: PingFangSC-Regular;
+    margin-top: 24px;
+    font-size: 14px;
+    max-width: 680px;
+}
+
+.new-banner .img-box {
+    position: absolute;
+    right: 0;
+    top: 90px;
+    display: block;
+}
+
+.n-middle {
+    margin: 0;
+    background: #F6F8FB;
+    position: relative;
+}
+
+.n-middle .top {
+    background: #F6F8FB;
+    visibility: visible;
+    position: absolute;
+    width: 100%;
+    height: 50%;
+}
+
+.n-middle .bottom {
+    background: #FFF;
+    top: 50%;
+    visibility: visible;
+    position: absolute;
+    width: 100%;
+    height: 50%;
+}
+
+.n-middle .group {
+    background: #FFF;
+    -webkit-box-shadow: 0 10px 20px 0 rgba(0, 0, 0, 0.05);
+    -moz-box-shadow: 0 10px 20px 0 rgba(0, 0, 0, 0.05);
+    box-shadow: 0 10px 20px 0 rgba(0, 0, 0, 0.05);
+    -webkit-border-radius: 8px;
+    -moz-border-radius: 8px;
+    border-radius: 8px;
+    padding: 25px 0 32px 0;
+    position: relative;
+    z-index: 9;
+    width: 1180px;
+    margin: 0 auto;
+}
+
+.n-middle .promotion {
+    width: 100%;
+    padding: 0 10px 0 34px;
+    box-sizing: border-box;
+    float: left;
+    font-size: 14px;
+    color: #666666;
+    line-height: 26px;
+}
+
+.p-container {
+    width: 1180px;
+    margin-right: auto;
+    margin-left: auto;
+}
+
+.p-container h2 {
+    font-size: 30px;
+    line-height: 48px;
+    color: #333;
+    margin: 80px 0 40px 0;
+    text-align: center;
+}
+
+.p-container .group {
+    width: 1128px;
+    margin: 0 auto;
+}
+
+.p-container ul.section-container {
+    background: #FFFFFF;
+    margin:0;
+    padding:0;
+}
+.p-container ul.section-container li {
+    list-style: none;
+}
+
+.p-container .item {
+    float: left;
+    box-sizing: border-box;
+    height: 262px;
+    margin-top: 47px;
+    margin-left: -1px;
+    background: #FFFFFF;
+    border: 1px solid #EBEBEB;
+    text-align: center;
+    transform: translateY(0);
+    transition: .4s transform cubic-bezier(.645, .045, .355, 1)
+}
+
+.p-container .item .active-icon {
+    display: block
+}
+
+.p-container .item .icon {
+    width: 32px;
+    height: 32px;
+    margin: 90px auto 20px;
+    -webkit-border-radius: 16px;
+    -moz-border-radius: 16px;
+    border-radius: 16px
+}
+
+.p-container .item .title {
+    font-size: 16px;
+    color: #333333;
+    line-height: 30px
+}
+
+.p-container .item .desc, .p-container .item .btn-wrap {
+    display: none
+}
+
+.p-container .item {
+    width: 181px;
+}
+
+.p-container .current {
+    height: 358px;
+    width: 280px;
+    margin-top: 20px;
+    transform: translateY(-20px)
+}
+
+.p-container .current .default-icon {
+    display: none
+}
+
+.p-container .current .active-icon {
+    display: block
+}
+
+.p-container .current .icon {
+    width: 80px;
+    height: 80px;
+    margin: 40px auto 20px;
+    -webkit-border-radius: 40px;
+    -moz-border-radius: 40px;
+    border-radius: 40px;
+    background: #8cc;
+    font-size: 44pt;
+    color: #fff;
+    line-height: 84px;
+}
+
+.p-container .current .title {
+    font-size: 18px;
+    color: #333333;
+    line-height: 32px
+}
+
+.p-container .current .desc {
+    display: block;
+    margin: 10px 20px 0;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    font-size: 14px;
+    color: #666666;
+    letter-spacing: 0;
+    line-height: 26px
+}
+
+.p-container .current .btn-wrap {
+    display: block;
+    margin-top: 20px
+}
+
+.p-container .current .btn-wrap a {
+    font-size: 14px;
+    color: #4A90E2;
+    letter-spacing: 0;
+    text-align: center;
+    line-height: 26px;
+    text-decoration: none
+}
+.p-container .current.x-charset .btn-wrap a {
+    font-size: 12px;
+    color: #fff;
+    text-align: center;
+    line-height: inherit;
+    text-decoration: none
+}
+.p-container pre {
+    white-space: pre-wrap;
+    word-wrap: break-word;
+    background-color: transparent;
+    border: 0;
+    font-family: PingFangSC-Regular, 'Microsoft Yahei'
+}
+
+.p-container .item:hover .active-icon{
+    background: #2468F2;
+}
+.p-container .current.x-more {
+    background: #fdfdfd;
+    color:#aaa;
+}
+.p-container .current.x-more .title,
+.p-container .current.x-more .desc{
+    color:#aaa;
+}
+
+.img-box img {
+    border-radius: 50px;
+    opacity: 0.5;
+}
+
+.new-banner .go-install {
+    margin: 20px 0;
+}
+.new-banner .go-install .x-tips {
+    color:#aaa;
+    margin-right: 10px;
+}
+.promotion .x-github {
+    float: right;
+    margin-right: 20px;
+}
+
+.x-screenshots .x-img-box {
+    text-align: center;
+    margin: 20px;
+    border: 1px solid #ddd;
+    border-radius: 10px;
+    box-shadow: 2px 2px 10px #aaa, 5px 5px 30px #ddd;
+}
+
+[v-cloak] {
+    display: none;
+}
+
+
+/*crx动态安装*/
+.btn-fh-install {
+    background-color: #4d7bd6;
+    background-image: -moz-linear-gradient(top, #5689db 0, #4d7bd6 100%);
+    background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #5689db), color-stop(100%, #4d7bd6));
+    background-image: -webkit-linear-gradient(top, #5689db 0, #4d7bd6 100%);
+    background-image: -o-linear-gradient(top, #5689db 0, #4d7bd6 100%);
+    background-image: -ms-linear-gradient(top, #5689db 0, #4d7bd6 100%);
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#5689db, endColorstr=#4d7bd6, GradientType=0);
+    background-image: linear-gradient(top, #5689db 0, #4d7bd6 100%);
+    -webkit-border-radius: 2px;
+    -moz-border-radius: 2px;
+    border-radius: 2px;
+    cursor: pointer;
+    color: #fff;
+    font-size: 13px;
+    font-weight: bold;
+    text-align: center;
+    text-shadow: 0 1px 1px rgba(0,0,0,0.1);
+    padding: 10px 15px;
+    -webkit-box-shadow: 0 2px 0 0 rgba(255,255,255,0.06) inset,0 2px 3px 0 rgba(0,0,0,0.2);
+    -moz-box-shadow: 0 2px 0 0 rgba(255,255,255,0.06) inset,0 2px 3px 0 rgba(0,0,0,0.2);
+    box-shadow: 0 2px 0 0 rgba(255,255,255,0.06) inset,0 2px 3px 0 rgba(0,0,0,0.2);
+    outline: 0px;
+    display: inline-block;
+}
+
+.btn-fh-install:hover {
+    background: #2d53af;
+    border: none;
+    text-decoration: none;
+    color:#fff;
+}
+
+.x-install-box {
+    line-height: 24px;
+}
+.x-install-box .x-msg ul {
+    padding: 10px 0 0 30px;
+}
+.x-install-box .x-msg ul li {
+    list-style: decimal;
+}
+.x-install-box .x-hold-on {
+    padding: 0 0 10px 0;
+}
+
+#fehelper_mask {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: #000;
+    opacity: 0.5;
+    z-index: 1000
+}
+
+#fehelper_tips {
+    position: fixed;
+    top: 200px;
+    left: 40%;
+    color: #f00;
+    font-size: 20px;
+    font-weight: bold;
+    background: #f9f9f9;
+    z-index: 100000;
+    padding: 20px;
+    border: 1px solid #aaa;
+    border-radius: 10px;
+}
+
+#fehelper_tips .x-msg {
+    font-size: 14px;
+    color: #000
+}
+
+#fehelper_tips .x-msg .xx {
+    margin: 10px 0;
+    color: #f00
+}
+.install-close-btn {
+    position: absolute;
+    top:5px;
+    right:5px;
+    cursor: pointer;
+}

+ 148 - 0
apps/index/index.html

@@ -0,0 +1,148 @@
+<!DOCTYPE HTML>
+<html lang="zh-CN">
+<head>
+    <title>FeHelper - Awesome</title>
+    <meta charset="UTF-8">
+    <link rel="shortcut icon" href="../static/img/favicon.ico">
+    <link rel="stylesheet" href="index.css"/>
+    <script type="text/javascript" src="../static/vendor/vue/vue.js"></script>
+</head>
+<body>
+<div class="wrapper" id="pageContainer">
+
+    <div class="panel-body mod-code">
+        <section class="new-banner">
+            <div class="group bg"><h1>Web开发者助手 FeHelper</h1>
+                <div class="desc">
+                    本插件支持<b style="color:red">Chrome、Firefox、MS-Edge</b>浏览器,内部工具集持续增加,目前包括
+                    JSON自动/手动格式化、JSON内容比对、代码美化与压缩、信息编解码转换、二维码生成与解码、图片Base64编解码转换、Markdown、
+                    网页油猴、网页取色器、脑图(Xmind)等贴心工具,甚至在目前新版本的FeHelper中,还集成了<b style="color:red;">FH开发者工具</b>,
+                    如果你也想自己搞一个工具集成到FeHelper中,那这一定能满足到你。另外,本站也提供部分工具的在线版本,欢迎使用,欢迎反馈!
+                </div>
+
+                <div class="x-crx">
+                    <div class="go-install">
+                        <span class="x-tips">点击按钮快速安装</span>
+
+                        <a href="#" class="btn-fh-install" @click="install($event)"><span>Chrome版</span></a>
+                        <a href="#" class="btn-fh-install" @click="install($event)"><span>Firefox版</span></a>
+                        <a href="#" class="btn-fh-install" @click="install($event)"><span>Microsoft Edge版</span></a>
+                    </div>
+                    <a href="https://chrome.google.com/webstore/detail/pkgccpejnmalmdinmhkkfafefagiiiad">
+                        <img alt="FeHelper-version" src="https://img.shields.io/chrome-web-store/v/pkgccpejnmalmdinmhkkfafefagiiiad.svg?logo=Google%20Chrome&amp;logoColor=red&amp;color=blue"></a>
+                    <a href="https://chrome.google.com/webstore/detail/pkgccpejnmalmdinmhkkfafefagiiiad">
+                        <img alt="FeHelper-rating" src="https://img.shields.io/chrome-web-store/stars/pkgccpejnmalmdinmhkkfafefagiiiad.svg?logo=Google%20Chrome&amp;logoColor=red&amp;color=blue"></a>
+                    <a href="https://chrome.google.com/webstore/detail/pkgccpejnmalmdinmhkkfafefagiiiad">
+                        <img alt="FeHelper-users" src="https://img.shields.io/chrome-web-store/users/pkgccpejnmalmdinmhkkfafefagiiiad.svg?logo=Google%20Chrome&amp;logoColor=red&amp;color=blue"></a>
+                </div>
+                <div class="img-box">
+                    <img src="../static/img/fh-allinone.jpg">
+                </div>
+            </div>
+        </section>
+
+        <section class="n-middle">
+            <div class="top"></div>
+            <div class="bottom"></div>
+            <div class="group column-length-1">
+                <div class="promotion">
+                    <span>FeHelper</span>
+                    <span class="content">已在Github开源,也欢迎大家提issue,或者直接提交PR加入进来!</span>
+                    <a class="text-link" target="_blank" href="https://github.com/zxlie/FeHelper">现在就去Github看看&gt;&gt;</a>
+
+                    <span class="x-github">
+                        <a href="https://github.com/zxlie/FeHelper" target="_blank">
+                            <img src="https://img.shields.io/github/stars/zxlie/FeHelper?style=social" alt="star"></a>
+                        <a href="https://github.com/zxlie/FeHelper" target="_blank">
+                            <img src="https://img.shields.io/github/forks/zxlie/FeHelper?style=social" alt="fork"></a>
+                    </span>
+
+                </div>
+            </div>
+        </section>
+
+        <div class="p-container">
+            <h2>FH应用市场</h2>
+            <div class="group">
+                <ul class="section-container">
+
+
+                    <li class="item current x-charset">
+                        <div class="icon active-icon">卍</div>
+                        <div class="title">Charset(独立插件)</div>
+                        <pre class="desc">网页字符编码集修改工具,主要解决网页访问是乱码的问题,支持上百种字符集的选择</pre>
+                        <div class="btn-wrap"><span class="text-link">
+                            <a class="btn btn-sm btn-primary" target="_blank" href="https://chrome.google.com/webstore/detail/%E7%BD%91%E9%A1%B5%E7%BC%96%E7%A0%81%E4%BF%AE%E6%94%B9%EF%BC%88charset%EF%BC%89/mnnlnpbaaojjmihapdoffoicnnaokpmj">Chrome版</a>
+                            <a class="btn btn-sm btn-success" target="_blank" href="https://microsoftedge.microsoft.com/addons/detail/nobgpmgfcojjalaabiecojoeigheopjb">MS-Edge版</a>
+                            <a class="btn btn-sm btn-warning" target="_blank" href="https://github.com/zxlie/FH-Charset">Github</a>
+                        </span></div>
+                    </li>
+
+                    <li class="item current" v-for="tool in Object.keys(allTools)">
+                        <div class="icon active-icon">{{allTools[tool].icon}}</div>
+                        <div class="title">{{allTools[tool].name}}</div>
+                        <pre class="desc">{{allTools[tool].tips}}</pre>
+                        <div class="btn-wrap"><a class="text-link" :href="`../${tool}/index.html`" @click="preClick(tool,$event)">马上使用&gt;&gt;</a></div>
+                    </li>
+
+
+                    <li class="item current x-more">
+                        <div class="icon active-icon">?</div>
+                        <div class="title">更多FH新工具</div>
+                        <pre class="desc">只要你已经安装了FeHelper插件,新上架的工具会自动出现在你插件配置页的「FH应用市场」内</pre>
+                        <div class="btn-wrap"><span class="text-link">敬请期待...</span></div>
+                    </li>
+
+                </ul>
+            </div>
+        </div>
+
+        <div class="p-container x-screenshots">
+            <h2>FH插件界面预览</h2>
+
+            <div class="x-img-box" v-for="ss in screenshots">
+                <img :src="`../static/img/${ss}`" alt="ss">
+            </div>
+        </div>
+
+
+        <div class="x-install-box" v-cloak v-if="installIng">
+            <div id="fehelper_mask"></div>
+            <div id="fehelper_tips">
+                <img src="../static/img/close-icon.png" alt="close" class="install-close-btn" @click="installIng=false">
+                <div class="x-hold-on">插件安装中,请等待...</div>
+                <div class="x-msg">
+                    - 如果长时间无响应,估计是被墙了,可以选择下面的方式下载后再手动安装:<br>
+                    <ul>
+                        <li><a target="_blank" href="https://github.com/zxlie/FeHelper/tree/master/apps/static/screenshot/crx">去Github下载最新版*.crx文件直接安装</a></li>
+                        <li><a href="https://chrome-extension-downloader.com/?extension=pkgccpejnmalmdinmhkkfafefagiiiad">去代理网站下载FeHelper最新版*.crx文件再安装</a></li>
+                        <li><a href="https://chrome.google.com/webstore/detail/pkgccpejnmalmdinmhkkfafefagiiiad">老老实实的去Google Chrome Webstore安装</a></li>
+                    </ul>
+                </div>
+                <div class="x-msg">
+                    <div>- crx文件离线安装步骤:</div>
+                    <ul>
+                        <li>下载FeHelper.crx文件</li>
+                        <li>浏览器打开:<a href="chrome://extensions/" target="_blank">chrome://extensions/</a></li>
+                        <li>拖拽*.crx文件到浏览器窗口,完成安装</li>
+                    </ul>
+                </div>
+            </div>
+        </div>
+
+    </div>
+</div>
+
+<div class="mod-footer">
+    <div class="clearfix"></div>
+    <div class="footer-box">
+        Copyright &copy; 阿烈叔 All Rights Reserved.&nbsp;
+        <a href="http://beian.miit.gov.cn/" target="_blank" class="x-beian">京ICP备14006329号</a>
+    </div>
+</div>
+
+<script type="text/javascript" src="fh-config.js"></script>
+<script type="text/javascript" src="index.js"></script>
+<script src="../static/js/navbar.js"></script>
+</body>
+</html>

+ 85 - 0
apps/index/index.js

@@ -0,0 +1,85 @@
+/**
+ * FeHelper 首页
+ */
+new Vue({
+    el: '#pageContainer',
+    data: {
+        allTools: window.FhConfig.toolMap,
+        screenshots: window.FhConfig.screenshots,
+        installIng: false
+    },
+    mounted() {
+        // 读取url上的参数,针对 ?action=install-chrome 这样的动作进行解析
+        let action = new URL(location.href).searchParams.get('action');
+        if (action && ['install-chrome', 'install-firefox', 'install-msedge'].includes(action)) {
+            this.install();
+        }
+    },
+    methods: {
+        install(event) {
+
+            if (/Edg/.test(navigator.userAgent)) {
+                window.open("https://microsoftedge.microsoft.com/addons/detail/feolnkbgcbjmamimpfcnklggdcbgakhe?hl=zh-CN");
+            } else if (/Firefox/.test(navigator.userAgent)) {
+                fetch('/fe/web-files/firefox.updates.json').then(resp => resp.text()).then(json => {
+                    try {
+                        json = new Function(`return ${json}`)();
+                        let xpi = json.addons['[email protected]'].updates[0];
+                        let version = xpi.version;
+                        let link = xpi.update_link;
+
+                        if (window.InstallTrigger) {
+                            InstallTrigger.install({
+                                "FeHelper": {
+                                    URL: link,
+                                    IconURL: '../static/img/fe-48.png',
+                                    toString: function () {
+                                        return this.URL;
+                                    }
+                                }
+                            });
+                        } else {
+                            location.href = link;
+                        }
+                    } catch (e) {
+                        console.log(e)
+                    }
+                });
+            } else {
+                // 通过这个API可以直接判断当前浏览器是否已经安装了这个chrome extension
+                if (chrome.app.isInstalled) {
+                    alert("你已经安装过这个chrome扩展了");
+                } else {
+                    this.installIng = true;
+
+                    // Fehelper在chrome浏览器的官方安装页面
+                    let chromeFeHelper = 'https://chrome.google.com/webstore/detail/pkgccpejnmalmdinmhkkfafefagiiiad';
+
+                    chrome.webstore && chrome.webstore.install && chrome.webstore.install(chromeFeHelper,
+                        () => {
+                            alert("恭喜你,Chrome Extension安装成功");
+                            this.installIng = false;
+                        },
+                        (err) => {
+                            alert("抱歉,Chrome Extension安装失败");
+                            this.installIng = false;
+                        });
+                }
+            }
+
+            if (event) {
+                event.preventDefault();
+                event.stopPropagation();
+            }
+        },
+
+        preClick(tool, event) {
+            if (this.allTools[tool].extensionOnly) {
+                alert(`你好,${this.allTools[tool].name} 工具只能在浏览器插件中使用,如果你还没安装FeHelper插件,就快去安装吧!`);
+                event.preventDefault();
+                event.stopPropagation();
+            }
+
+        }
+    }
+});

+ 90 - 0
apps/index/json-demo.json

@@ -0,0 +1,90 @@
+{
+    "BigIntSupported": 995815895020119788889,
+    "date": "20180322",
+    "message": "Success !",
+    "status": 200,
+    "city": "北京",
+    "count": 632,
+    "data": {
+        "shidu": "34%",
+        "pm25": 73,
+        "pm10": 91,
+        "quality": "良",
+        "wendu": "5",
+        "ganmao": "极少数敏感人群应减少户外活动",
+        "yesterday": {
+            "date": "21日星期三",
+            "sunrise": "06:19",
+            "high": "高温 11.0℃",
+            "low": "低温 1.0℃",
+            "sunset": "18:26",
+            "aqi": 85,
+            "fx": "南风",
+            "fl": "<3级",
+            "type": "多云",
+            "notice": "阴晴之间,谨防紫外线侵扰"
+        },
+        "forecast": [
+            {
+                "date": "22日星期四",
+                "sunrise": "06:17",
+                "high": "高温 17.0℃",
+                "low": "低温 1.0℃",
+                "sunset": "18:27",
+                "aqi": 98,
+                "fx": "西南风",
+                "fl": "<3级",
+                "type": "晴",
+                "notice": "愿你拥有比阳光明媚的心情"
+            },
+            {
+                "date": "23日星期五",
+                "sunrise": "06:16",
+                "high": "高温 18.0℃",
+                "low": "低温 5.0℃",
+                "sunset": "18:28",
+                "aqi": 118,
+                "fx": "无持续风向",
+                "fl": "<3级",
+                "type": "多云",
+                "notice": "阴晴之间,谨防紫外线侵扰"
+            },
+            {
+                "date": "24日星期六",
+                "sunrise": "06:14",
+                "high": "高温 21.0℃",
+                "low": "低温 7.0℃",
+                "sunset": "18:29",
+                "aqi": 52,
+                "fx": "西南风",
+                "fl": "<3级",
+                "type": "晴",
+                "notice": "愿你拥有比阳光明媚的心情"
+            },
+            {
+                "date": "25日星期日",
+                "sunrise": "06:13",
+                "high": "高温 22.0℃",
+                "low": "低温 7.0℃",
+                "sunset": "18:30",
+                "aqi": 71,
+                "fx": "西南风",
+                "fl": "<3级",
+                "type": "晴",
+                "notice": "愿你拥有比阳光明媚的心情"
+            },
+            {
+                "date": "26日星期一",
+                "sunrise": "06:11",
+                "high": "高温 21.0℃",
+                "low": "低温 8.0℃",
+                "sunset": "18:31",
+                "aqi": 97,
+                "fx": "西南风",
+                "fl": "<3级",
+                "type": "多云",
+                "notice": "阴晴之间,谨防紫外线侵扰"
+            }
+        ]
+    }
+}

+ 14 - 14
apps/json-diff/index.css

@@ -1,19 +1,18 @@
 @import url("../static/vendor/codemirror/codemirror.css");
 @import url("../static/css/bootstrap.min.css");
 
-body {
-    background-color: #fff;
-}
-
 .wp-json {
     width:auto;
 }
 .wp-json .mod-json {
     position: absolute;
-    top: 60px;
+    top: 190px;
     bottom: 0;
-    right:20px;
-    left:20px;
+    right:0;
+    left:0;
+}
+body[browser-extension] .wp-json .mod-json {
+    top: 60px;
 }
 .wp-json .mod-json .panel-txt {
     position: absolute;
@@ -24,9 +23,6 @@ body {
 .wp-json .panel-body {
     padding:15px 0;
 }
-.wp-json .CodeMirror {
-    height: auto;
-}
 .wp-json .CodeMirror-scroll {
     min-height: 550px;
 }
@@ -44,10 +40,14 @@ body {
     font-size: 10px;
 }
 .x-error {
-    float: right;
-    color: #0c0;
-    font-size: 14px;
+    color: #1f0606;
+    font-size: 12px;
+    margin-left: 30px;
+}
+.x-error .x-hlt {
+    color:#f00;
+    font-weight: bold;
 }
-.x-error.x-hlt {
+.x-error .x-hlt1 {
     color:#f00;
 }

+ 5 - 1
apps/json-diff/index.html

@@ -3,6 +3,7 @@
     <head>
         <title>JSON比对工具</title>
         <meta charset="UTF-8">
+        <link rel="shortcut icon" href="../static/img/favicon.ico">
         <link rel="stylesheet" href="index.css" />
         <script type="text/javascript" src="../static/vendor/vue/vue.js"></script>
     </head>
@@ -11,9 +12,10 @@
             <div class="panel panel-default" style="margin-bottom: 0px;">
                 <div class="panel-heading">
                     <h3 class="panel-title">
-                        <span class="x-error" v-bind:class="{'x-hlt' : errorHighlight}" v-html="errorMessage"></span>
                         <a href="http://www.baidufe.com/fehelper/feedback.html" target="_blank" class="x-a-high">
                             <img src="../static/img/fe-16.png" alt="fehelper"/> FeHelper</a>:JSON比对工具
+
+                        <span class="x-error" v-bind:class="{'x-hlt' : errorHighlight}" v-html="errorMessage"></span>
                     </h3>
                 </div>
             </div>
@@ -40,5 +42,7 @@
         <script src="../static/vendor/json-diff/json-source-map.js"></script>
         <script src="../static/vendor/json-diff/json-diff.js"></script>
         <script src="./index.js"></script>
+
+        <script src="../static/js/navbar.js"></script>
     </body>
 </html>

+ 14 - 5
apps/json-diff/index.js

@@ -7,27 +7,36 @@ new Vue({
     mounted: function () {
         // 错误处理器
         let errorHandler = (which, ok) => {
+            let message = '';
             if (ok) {
-                this.errorMessage = '两侧JSON比对完成!';
+                message = '两侧JSON比对完成!';
                 this.errorHighlight = false;
             } else {
-                this.errorMessage = {'left': '左', 'right': '右', 'left-right': '两'}[which] + '侧JSON不合法!';
+                let side = {'left': '左', 'right': '右', 'left-right': '两'}[which];
+                if(!jsonBox.left.getValue().trim().length) {
+                    message = '请在<span class="x-hlt1">左侧</span>填入待比对的JSON内容!'
+                }else if(!jsonBox.right.getValue().trim().length) {
+                    message = '请在<span class="x-hlt1">右侧</span>填入待比对的JSON内容!'
+                }else{
+                    message = '<span class="x-hlt1">' + side + '侧</span>JSON不合法!';
+                }
                 this.errorHighlight = true;
             }
+            this.errorMessage = '<span class="x-hlt">Tips:</span>' + message;
         };
 
         // diff处理器
         let diffHandler = (diffs) => {
             if (!this.errorHighlight) {
                 if (diffs.length) {
-                    this.errorMessage += '共有 ' + diffs.length + ' 处不一致!';
+                    this.errorMessage += '共有 <span class="x-hlt">' + diffs.length + '</span> 处不一致!';
                 } else {
-                    this.errorMessage += '左右两侧JSON内容一致!';
+                    this.errorMessage += 'JSON内容一致!';
                 }
             }
         };
 
         // 代码比对
-        JsonDiff.init(this.$refs.srcLeft, this.$refs.srcRight, errorHandler, diffHandler);
+        let jsonBox = JsonDiff.init(this.$refs.srcLeft, this.$refs.srcRight, errorHandler, diffHandler);
     }
 });

+ 0 - 275
apps/json-format/automatic.js

@@ -1,275 +0,0 @@
-/**
- * Json Page Automatic Format Via FeHelper
- * @author zhaoxianlie
- */
-
-
-// json with bigint supported
-Tarp.require('../static/vendor/json-bigint/index');
-
-module.exports = (() => {
-
-    "use strict";
-
-    const JSON_SORT_TYPE_KEY = 'json_sort_type_key';
-
-    // 用于记录最原始的json串
-    let originalJsonStr = '';
-    let curSortType = 0;
-    // JSONP形式下的callback name
-    let funcName = null;
-    let jsonObj = null;
-    let fnTry = null;
-    let fnCatch = null;
-
-    let _htmlFragment = [
-        '<style type="text/css">.mod-contentscript #formattingMsg{position:absolute;top:0;font-size:14px;color:#333;margin:5px;}#formattingMsg .x-loading{width:12px;height:12px;border:1px solid #f00;border-radius:50%;box-shadow:0 0 10px 2px;color:#c00;border-right-color:transparent;border-top-color:transparent;animation:spin-right 1s linear infinite normal;animation-delay:0s;margin:0 5px 0 0;display:inline-block}#formattingMsg .x-loading:before{display:block;width:8px;height:8px;margin:1px;border:2px solid #f00;content:" ";border-radius:50%;border-left-color:transparent;border-bottom-color:transparent}@keyframes spin-right{from{transform:rotate(0deg);opacity:.2}50%{transform:rotate(180deg);opacity:1.0}to{transform:rotate(360deg);opacity:.2}}</style>',
-        '<div class="mod-json mod-contentscript"><div class="rst-item">',
-        '<div class="jf-sort" style="display:none"><span class="x-stitle">JSON排序:</span><label for="sort_null">默认</label><input type="radio" name="jsonsort" id="sort_null" value="0" checked="checked"><label for="sort_asc">升序</label><input type="radio" name="jsonsort" id="sort_asc" value="1"><label for="sort_desc">降序</label><input type="radio" name="jsonsort" id="sort_desc" value="-1"></div>',
-        '<div id="formattingMsg"><span class="x-loading"></span>格式化中...</div>',
-        '<div id="jfCallbackName_start" class="callback-name"></div>',
-        '<div id="jfContent"></div>',
-        '<pre id="jfContent_pre"></pre>',
-        '<div id="jfCallbackName_end" class="callback-name"></div>',
-        '</div></div>'
-    ].join('');
-
-    let _loadCss = function () {
-        let cssUrl = chrome.extension.getURL('json-format/without-ui.css');
-        $('<link id="_fehelper_fcp_css_" href="' + cssUrl + '" rel="stylesheet" type="text/css" />').appendTo('head');
-    };
-
-    /**
-     * 从页面提取JSON文本
-     * @returns {string}
-     * @private
-     */
-    let _getJsonText = function () {
-
-        let pre = $('body>pre:eq(0)')[0] || {textContent: ""};
-        let source = pre.textContent.trim();
-
-        if (!source) {
-            source = (document.body.textContent || '').trim()
-        }
-        if (!source) {
-            return false;
-        }
-
-        // 如果body的内容还包含HTML标签,肯定不是合法的json了
-        // 如果是合法的json,也只可能有一个text节点
-        let nodes = document.body.childNodes;
-        let newSource = '';
-        for (let i = 0, len = nodes.length; i < len; i++) {
-
-            if (nodes[i].nodeType === Node.TEXT_NODE) {
-                newSource += nodes[i].textContent;
-            } else if (nodes[i].nodeType === Node.ELEMENT_NODE) {
-                let tagName = nodes[i].tagName.toLowerCase();
-                let html = (nodes[i].textContent || '').trim();
-                // 如果是pre标签,则看内容是不是和source一样,一样则continue
-                if (tagName === 'pre' && html === source) {
-                } else if ((nodes[i].offsetWidth === 0 || nodes[i].offsetHeight === 0 || !html) && ['script', 'link'].indexOf(tagName) === -1) {
-                    // 如果用户安装迅雷或者其他的插件,也回破坏页面结构,需要兼容一下
-                } else {
-                    return false;
-                }
-            } else {
-                return false;
-            }
-        }
-
-        return (newSource || '').trim() || source;
-    };
-
-    /**
-     * 此方法用于将Unicode码解码为正常字符串
-     * @param {Object} text
-     */
-    let _uniDecode = function (text) {
-        try {
-            text = decodeURIComponent(text);
-        } catch (e) {
-        }
-        text = text.replace(/(\\)?\\u/gi, "%u").replace('%u0025', '%25');
-
-        text = unescape(text.toString().replace(/%2B/g, "+"));
-        let matches = text.match(/(%u00([0-9A-F]{2}))/gi);
-        if (matches) {
-            for (let matchid = 0; matchid < matches.length; matchid++) {
-                let code = matches[matchid].substring(1, 3);
-                let x = Number("0x" + code);
-                if (x >= 128) {
-                    text = text.replace(matches[matchid], code);
-                }
-            }
-        }
-        text = unescape(text.toString().replace(/%2B/g, "+"));
-
-        return text;
-    };
-
-    /**
-     * 获取一个JSON的所有Key数量
-     * @param json
-     * @returns {number}
-     * @private
-     */
-    let _getAllKeysCount = function (json) {
-        let count = 0;
-
-        if (typeof json === 'object') {
-            let keys = Object.keys(json);
-            count += keys.length;
-
-            keys.forEach(key => {
-                if (json[key] && typeof json[key] === 'object') {
-                    count += _getAllKeysCount(json[key]);
-                }
-            });
-        }
-
-        return count;
-    };
-
-    /**
-     * 执行format操作
-     * @private
-     */
-    let _format = function (options) {
-
-        let source = _getJsonText();
-        if (!source) {
-            return;
-        }
-
-        if (options && options['AUTO_TEXT_DECODE']) {
-            source = _uniDecode(source);
-        }
-
-        // 下面校验给定字符串是否为一个合法的json
-        try {
-
-            // 再看看是不是jsonp的格式
-            let reg = /^([\w\.]+)\(\s*([\s\S]*)\s*\)$/gm;
-            let reTry = /^(try\s*\{\s*)?/g;
-            let reCatch = /([;\s]*\}\s*catch\s*\(\s*\S+\s*\)\s*\{([\s\S])*\})?[;\s]*$/g;
-
-            // 检测是否有try-catch包裹
-            let sourceReplaced = source.replace(reTry, function () {
-                fnTry = fnTry ? fnTry : arguments[1];
-                return '';
-            }).replace(reCatch, function () {
-                fnCatch = fnCatch ? fnCatch : arguments[1];
-                return '';
-            }).trim();
-
-            let matches = reg.exec(sourceReplaced);
-            if (matches != null && (fnTry && fnCatch || !fnTry && !fnCatch)) {
-                funcName = matches[1];
-                source = matches[2];
-            } else {
-                reg = /^([\{\[])/;
-                if (!reg.test(source)) {
-                    return;
-                }
-            }
-
-            // 这里可能会throw exception
-            jsonObj = JSON.parse(source);
-        } catch (ex) {
-
-            // new Function的方式,能自动给key补全双引号,但是不支持bigint,所以是下下策,放在try-catch里搞
-            try {
-                jsonObj = new Function("return " + source)();
-            } catch (exx) {
-                try {
-                    // 再给你一次机会,是不是下面这种情况:  "{\"ret\":\"0\", \"msg\":\"ok\"}"
-                    jsonObj = new Function("return '" + source + "'")();
-                    if (typeof jsonObj === 'string') {
-                        // 最后给你一次机会,是个字符串,老夫给你再转一次
-                        jsonObj = new Function("return " + jsonObj)();
-                    }
-                } catch (exxx) {
-                    return;
-                }
-            }
-
-        }
-
-        // 是json格式,可以进行JSON自动格式化
-        if (jsonObj != null && typeof jsonObj === "object") {
-            try {
-                // 要尽量保证格式化的东西一定是一个json,所以需要把内容进行JSON.stringify处理
-                source = JSON.stringify(jsonObj);
-            } catch (ex) {
-                // 通过JSON反解不出来的,一定有问题
-                return;
-            }
-
-            // JSON的所有key不能超过预设的值,比如 10000 个,要不然自动格式化会比较卡
-            if (options && options['MAX_JSON_KEYS_NUMBER']) {
-                let keysCount = _getAllKeysCount(jsonObj);
-                if (keysCount > options['MAX_JSON_KEYS_NUMBER']) {
-                    let msg = '当前JSON共 <b style="color:red">' + keysCount + '</b> 个Key,大于预设值' + options['MAX_JSON_KEYS_NUMBER'] + ',已取消自动格式化;可到FeHelper设置页调整此配置!';
-                    return toast(msg);
-                }
-            }
-
-            _loadCss();
-            $('body').html(_htmlFragment);
-
-            originalJsonStr = source;
-
-            // 获取上次记录的排序方式
-            curSortType = parseInt(localStorage.getItem(JSON_SORT_TYPE_KEY) || 0);
-            _didFormat(curSortType);
-
-            // 初始化
-            $('[name=jsonsort][value=' + curSortType + ']').attr('checked', 1);
-            $('.jf-sort').slideDown(1000);
-
-            _bindSortEvent();
-        }
-    };
-
-    let _didFormat = function (sortType) {
-        sortType = sortType || 0;
-        let source = originalJsonStr;
-
-        if (sortType !== 0) {
-            let jsonObj = Tarp.require('../json-format/jsonabc').sortObj(JSON.parse(originalJsonStr), parseInt(sortType), true);
-            source = JSON.stringify(jsonObj);
-        }
-
-        // 格式化
-        Tarp.require('../json-format/format-lib').format(source);
-
-        // 如果是JSONP格式的,需要把方法名也显示出来
-        if (funcName != null) {
-            if (fnTry && fnCatch) {
-                $('#jfCallbackName_start').html('<pre style="padding:0">' + fnTry + '</pre>' + funcName + '(');
-                $('#jfCallbackName_end').html(')<br><pre style="padding:0">' + fnCatch + '</pre>');
-            } else {
-                $('#jfCallbackName_start').html(funcName + '(');
-                $('#jfCallbackName_end').html(')');
-            }
-        }
-
-        localStorage.setItem(JSON_SORT_TYPE_KEY, sortType);
-    };
-
-    let _bindSortEvent = function () {
-        $('[name=jsonsort]').click(function (e) {
-            let sortType = parseInt(this.value);
-            if (sortType !== curSortType) {
-                _didFormat(sortType);
-                curSortType = sortType;
-            }
-        });
-    };
-
-    return {
-        format: _format
-    };
-})();

+ 479 - 0
apps/json-format/content-script.css

@@ -0,0 +1,479 @@
+#jfContent {
+    -webkit-user-select: text;
+    margin: 0;
+}
+
+.xjf-btn {
+    cursor: pointer;
+    -webkit-border-radius: 2px;
+    -webkit-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);
+    -webkit-user-select: none;
+    background: -webkit-linear-gradient(#fafafa, #f4f4f4 40%, #e5e5e5);
+    outline: none;
+    border: 1px solid #aaa;
+    color: #444;
+    font-size: 12px;
+    margin-bottom: 0px;
+    min-width: 4em;
+    position: relative;
+    z-index: 10;
+    display: inline-block;
+    padding: 2px 10px;
+    text-shadow: 1px 1px rgba(255, 255, 255, 0.3)
+}
+
+.xjf-btn-mid,.xjf-btn-right {
+    margin-left: 0;
+    border-top-left-radius: 0;
+    border-bottom-left-radius: 0;
+}
+
+.xjf-btn-mid,.xjf-btn-left {
+    margin-right: 0;
+    border-top-right-radius: 0;
+    border-bottom-right-radius: 0;
+    border-right: none
+}
+
+.xjf-btn:hover {
+    -webkit-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.2);
+    background: #ebebeb -webkit-linear-gradient(#fefefe, #f8f8f8 40%, #e9e9e9);
+    border-color: #999;
+    color: #222
+}
+
+.xjf-btn:active {
+    -webkit-box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.2);
+    background: #ebebeb -webkit-linear-gradient(#f4f4f4, #efefef 40%, #dcdcdc);
+    color: #333
+}
+
+.xjf-btn.selected {
+    -webkit-box-shadow: inset 0px 1px 5px rgba(0, 0, 0, 0.2);
+    background: #ebebeb -webkit-linear-gradient(#e4e4e4, #dfdfdf 40%, #dcdcdc);
+    color: #333
+}
+
+#jsonpOpener, #jsonpCloser {
+    padding: 4px 0 0 8px;
+    color: black;
+    margin-bottom: -6px
+}
+
+#jsonpCloser {
+    margin-top: 0
+}
+
+#formattedJson {
+    padding-left: 21px;
+    padding-top: 6px
+}
+
+pre {
+    padding: 36px 5px 5px 5px
+}
+
+.kvov {
+    display: block;
+    padding-left: 20px;
+    margin-left: -20px;
+    position: relative;
+    padding-top: 2px;
+}
+
+#jfContent {
+    margin-bottom: 25px;
+}
+
+#jfContent .kvov .s a {
+    color: #00b;
+    text-decoration: underline;
+}
+
+#jfContent .kvov .s a:hover {
+    color: #b00;
+}
+
+.collapsed {
+    white-space: nowrap
+}
+
+.collapsed > .blockInner {
+    display: none
+}
+
+.collapsed > .ell:after {
+    content: "\2026";
+    font-weight: bold
+}
+
+.collapsed > .ell {
+    margin: 0 4px;
+    color: #888
+}
+
+.collapsed .kvov {
+    display: inline
+}
+
+.e {
+    width: 20px;
+    height: 18px;
+    display: block;
+    position: absolute;
+    left: -2px;
+    top: 4px;
+    z-index: 5;
+    opacity: 0.35;
+    -webkit-user-select: none;
+    cursor: pointer;
+}
+
+.e:after {
+    content: "\25bc";
+}
+
+.collapsed > .e {
+    -webkit-transform: rotate(-90deg);
+    top: -1px
+}
+
+.e:hover {
+    opacity: 0.35
+}
+
+.e:active {
+    opacity: 0.5
+}
+
+.collapsed .kvov .e {
+    display: none
+}
+
+.blockInner {
+    display: block;
+    padding-left: 24px;
+    border-left: 1px dotted #bbb;
+    margin-left: 2px
+}
+
+#formattedJson, #jsonpOpener, #jsonpCloser {
+    color: #333;
+    font: 13px/18px monospace
+}
+
+#formattedJson {
+    color: #444
+}
+
+.b {
+    font-weight: bold
+}
+
+.s {
+    color: #0B7500;
+    word-wrap: break-word
+}
+
+#jfContent a:link, #jfContent a:visited {
+    text-decoration: none;
+    color: inherit
+}
+
+#jfContent a:hover, #jfContent a:active {
+    text-decoration: underline;
+    color: #050
+}
+
+.bl, .nl, .n {
+    font-weight: bold;
+    color: #1A01CC
+}
+
+.k {
+    color: black
+}
+
+#formattingMsg {
+    position: absolute;
+    top: calc(40vh);
+    left: calc(45vw);
+    z-index: 100;
+    display: none;
+}
+
+#formattingMsg .x-loading {
+    width: 12px;
+    height: 12px;
+    border: 1px solid #f00;
+    border-radius: 50%;
+    box-shadow: 0 0 10px 2px;
+    color: #cc0000;
+    border-right-color: transparent;
+    border-top-color: transparent;
+    animation: spin-right 1s linear infinite normal;
+    animation-delay: 0s;
+    margin: 0 5px 0 0;
+    display: inline-block;
+}
+
+#formattingMsg .x-loading:before {
+    display: block;
+    width: 8px;
+    height: 8px;
+    margin: 1px;
+    border: 2px solid #f00;
+    content: " ";
+    border-radius: 50%;
+    border-left-color: transparent;
+    border-bottom-color: transparent;
+}
+
+@keyframes spin-right {
+    from {
+        transform: rotate(0deg);
+        opacity: 0.2;
+    }
+    50% {
+        transform: rotate(180deg);
+        opacity: 1.0;
+    }
+    to {
+        transform: rotate(360deg);
+        opacity: 0.2;
+    }
+}
+
+[hidden] {
+    display: none !important
+}
+
+#jfContentspan {
+    white-space: pre-wrap
+}
+
+@-webkit-keyframes spin {
+    from {
+        -webkit-transform: rotate(0deg)
+    }
+    to {
+        -webkit-transform: rotate(360deg)
+    }
+}
+
+#spinner {
+    -webkit-animation: spin 1s 0 infinite
+}
+
+* {
+    -webkit-font-smoothing: antialiased
+}
+
+#jfContent .x-json-tips {
+    color: red;
+}
+
+#jfContent_pre {
+    padding: 0;
+    margin: 0;
+    word-break: break-all;
+}
+
+html {
+    font-size: 14px;
+    color: #333;
+    direction: ltr;
+}
+
+html body {
+    direction: inherit;
+    margin: 0;
+}
+
+body {
+    padding: 0 8px;
+}
+
+/* 工具栏 */
+.x-toolbar {
+    background-color: #f5f5f5;
+    background: -webkit-linear-gradient(#fafafa, #f4f4f4 40%, #e5e5e5);
+    margin: 10px 0;
+    border: 1px solid #ddd;
+    border-bottom-color: #ccc;
+    border-radius: 4px;
+    -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
+    box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
+    padding: 5px 15px;
+    position: sticky;
+    top: 0;
+    z-index: 10000;
+    font-size: 12px;
+    user-select: none;
+}
+
+.x-toolbar .x-sort input {
+    margin-right: 10px;
+}
+
+.x-toolbar .x-sort input#sort_desc {
+    margin-right: 0;
+}
+
+.x-toolbar .x-sort {
+    display: inline;
+    margin: 0;
+}
+
+.x-toolbar .x-split {
+    margin: 0 15px;
+    color: #ccc;
+    display: inline;
+}
+
+.x-toolbar label {
+    font-weight: normal;
+    margin-bottom: 0;
+}
+
+.x-toolbar img {
+    vertical-align: middle;
+}
+
+.x-toolbar .x-a-title {
+    font-size: 14px;
+    color: blue;
+    text-decoration: none;
+    font-weight: bold;
+}
+
+.x-toolbar .x-b-title {
+    font-size: 14px;
+    font-weight: bold;
+}
+.x-toolbar.t-collapse {
+    position: absolute;
+    left: 100%;
+    margin-left: -24px;
+    width: 10000px;
+}
+.x-toolbar.t-collapse .fe-feedback {
+    float: left;
+    margin-left: -10px;
+    margin-right: 20px;
+}
+
+.mod-json .format-item button {
+    width: 80px;
+    height: 30px;
+    float: right;
+}
+
+.mod-contentscript {
+    width: auto;
+}
+
+#formatTips {
+    color: #888;
+    font-size: 14px;
+    display: block;
+    position: absolute;
+    top: 0px;
+    left: 0px;
+}
+
+#jsonSource {
+    height: 120px;
+}
+
+.mod-json .callback-name {
+    font-weight: bolder;
+    color: #a00;
+}
+
+#jfContent .x-hover {
+    outline: 1px solid #cdc;
+    background: #fff;
+}
+
+#jfContent .x-outline {
+    outline: 1px solid #8ac;
+    box-shadow: rgba(100, 100, 100, 0.4) -3px 3px 5px;
+    font-weight: bold;
+    background-color: #fffff8;
+}
+
+#errorMsg {
+    margin-top: 10px;
+    float: left;
+    color: #f00;
+}
+
+#boxOpt {
+    position: absolute;
+    z-index: 1024;
+}
+
+#boxOpt a {
+    cursor: pointer;
+    margin: 0 5px;
+    font-size: 12px;
+    color: #00f;
+}
+
+#boxOpt a:hover {
+    color: #f00;
+}
+.fe-feedback {
+    font-size: 12px;
+    padding-top: 3px;
+    color: #888;
+    float: right;
+    cursor: pointer;
+    font-weight:bold;
+}
+
+.fe-feedback a {
+    color: #888;
+    text-decoration: none;
+    text-align: left;
+    margin-right: 5px;
+}
+
+.fe-feedback a:hover {
+    color: #c00;
+}
+
+svg:not(:root) {
+    overflow: hidden;
+}
+
+.fe-feedback svg {
+    vertical-align: text-bottom;
+    display: inline-block;
+    fill: currentColor;
+}
+
+.fe-feedback img {
+    opacity: 0.6;
+    position: relative;
+    top: 1px;
+}
+
+.fe-feedback a:focus {
+    outline: none;
+}
+
+.fe-feedback a:hover img {
+    opacity: 1.0;
+}
+
+.fe-feedback .x-settings {
+    float: right;
+    cursor: pointer;
+    margin-left: 5px;
+}
+
+.fe-feedback .x-settings:hover {
+    color: #c00;
+}

+ 417 - 0
apps/json-format/content-script.js

@@ -0,0 +1,417 @@
+/**
+ * Json Page Automatic Format Via FeHelper
+ * @author zhaoxianlie
+ */
+
+// 留100ms时间给静态文件加载,当然,这个代码只是留给未开发过程中用的
+let pleaseLetJsLoaded = 0;
+let __importScript = (filename) => {
+    pleaseLetJsLoaded = 100;
+    let url = filename;
+    if (location.protocol === 'chrome-extension:' || chrome.runtime && chrome.runtime.getURL) {
+        url = chrome.runtime.getURL('json-format/' + filename);
+    }
+    fetch(url).then(resp => resp.text()).then(jsText => {
+        if(window.evalCore && window.evalCore.getEvalInstance){
+            return window.evalCore.getEvalInstance(window)(jsText);
+        }
+        let el = document.createElement('script');
+        el.textContent = jsText;
+        document.head.appendChild(el);
+    });
+};
+
+__importScript('json-bigint.js');
+__importScript('format-lib.js');
+__importScript('json-abc.js');
+__importScript('json-decode.js');
+
+window.JsonAutoFormat = (() => {
+
+    "use strict";
+
+    const JSON_SORT_TYPE_KEY = 'json_sort_type_key';
+    const JSON_AUTO_DECODE = 'json_auto_decode';
+    const JSON_TOOL_BAR_ALWAYS_SHOW = 'JSON_TOOL_BAR_ALWAYS_SHOW';
+
+    // 用于记录最原始的json串
+    let originalJsonStr = '';
+    let curSortType = 0;
+    // JSONP形式下的callback name
+    let funcName = null;
+    let jsonObj = null;
+    let fnTry = null;
+    let fnCatch = null;
+
+    let autoDecode = false;
+
+    let _getHtmlFragment = () => {
+        return [
+            '<div class="x-toolbar" style="display:none">' +
+            '    <a href="http://www.baidufe.com/fehelper/feedback.html" target="_blank" class="x-a-title">' +
+            '        <img src="' + chrome.runtime.getURL('static/img/fe-16.png') + '" alt="fehelper"/> FeHelper</a>' +
+            '        <span class="x-b-title"></span>' +
+            '        <span class="x-split">|</span>\n' +
+            '        <input type="checkbox" id="json_endecode"><label for="json_endecode">自动解码</label>' +
+            '        <span class="x-sort">' +
+            '            <span class="x-split">|</span>' +
+            '            <span class="x-stitle">排序:</span>' +
+            '            <label for="sort_null">默认</label><input type="radio" name="jsonsort" id="sort_null" value="0" checked>' +
+            '            <label for="sort_asc">升序</label><input type="radio" name="jsonsort" id="sort_asc" value="1">' +
+            '            <label for="sort_desc">降序</label><input type="radio" name="jsonsort" id="sort_desc" value="-1">' +
+            '        </span>' +
+            '    <span class="x-split">|</span>\n' +
+            '    <button class="xjf-btn" id="jsonGetCorrectCnt">乱码修正</button>' +
+            '    <span id="optionBar"></span>' +
+            '    <span class="fe-feedback">' +
+            '        <a id="toggleBtn" title="展开或收起工具栏">隐藏&gt;&gt;</a>' +
+            '    </span>' +
+            '</div>',
+            '<div id="formattingMsg"><span class="x-loading"></span>格式化中...</div>',
+            '<div class="mod-json mod-contentscript"><div class="rst-item">',
+            '<div id="jfCallbackName_start" class="callback-name"></div>',
+            '<div id="jfContent"></div>',
+            '<pre id="jfContent_pre"></pre>',
+            '<div id="jfCallbackName_end" class="callback-name"></div>',
+            '</div></div>'
+        ].join('')
+    };
+
+    /**
+     * 从页面提取JSON文本
+     * @returns {string}
+     * @private
+     */
+    let _getJsonText = function () {
+
+        let pre = document.querySelectorAll('body>pre')[0] || {textContent: ""};
+        let source = pre.textContent.trim();
+
+        if (!source) {
+            source = (document.body.textContent || '').trim()
+        }
+        if (!source) {
+            return false;
+        }
+
+        // 1、如果body的内容还包含HTML标签,肯定不是合法的json了
+        // 2、如果是合法的json,也只可能有一个text节点
+        // 3、但是要兼容一下其他插件对页面的破坏情况
+        // 4、对于content-type是application/json的页面可以做宽松处理
+        let nodes = document.body.childNodes;
+        let jsonText = '';
+        let isJsonContentType = document.contentType === 'application/json';
+        for (let i = 0, len = nodes.length; i < len; i++) {
+            let elm = nodes[i];
+            if (elm.nodeType === Node.TEXT_NODE) {
+                jsonText += (elm.textContent || '').trim();
+            } else if (isJsonContentType) {
+                if ((elm.offsetHeight + elm.offsetWidth !== 0) && elm.textContent.length > jsonText.length) {
+                    jsonText = elm.textContent;
+                }
+            } else {
+                if (nodes[i].nodeType === Node.ELEMENT_NODE) {
+                    let tagName = elm.tagName.toLowerCase();
+                    let text = (elm.textContent || '').trim();
+                    // 如果是pre标签,则看内容是不是和source一样,一样则continue
+                    if (!((tagName === 'pre' && text === source)
+                            || ((elm.offsetWidth + elm.offsetHeight === 0 || !text)
+                                && !['script', 'link'].includes(tagName)))) {
+                        return false;
+                    }
+                } else {
+                    return false;
+                }
+            }
+        }
+
+        return (jsonText || '').trim() || source;
+    };
+
+    /**
+     * 获取一个JSON的所有Key数量
+     * @param json
+     * @returns {number}
+     * @private
+     */
+    let _getAllKeysCount = function (json) {
+        let count = 0;
+
+        if (typeof json === 'object') {
+            let keys = Object.keys(json);
+            count += keys.length;
+
+            keys.forEach(key => {
+                if (json[key] && typeof json[key] === 'object') {
+                    count += _getAllKeysCount(json[key]);
+                }
+            });
+        }
+
+        return count;
+    };
+
+    /**
+     * 执行format操作
+     * @private
+     */
+    let _format = function (options) {
+
+        let source = _getJsonText();
+        if (!source) {
+            return;
+        }
+
+        // 下面校验给定字符串是否为一个合法的json
+        try {
+
+            // 再看看是不是jsonp的格式
+            let reg = /^([\w\.]+)\(\s*([\s\S]*)\s*\)$/gm;
+            let reTry = /^(try\s*\{\s*)?/g;
+            let reCatch = /([;\s]*\}\s*catch\s*\(\s*\S+\s*\)\s*\{([\s\S])*\})?[;\s]*$/g;
+
+            // 检测是否有try-catch包裹
+            let sourceReplaced = source.replace(reTry, function () {
+                fnTry = fnTry ? fnTry : arguments[1];
+                return '';
+            }).replace(reCatch, function () {
+                fnCatch = fnCatch ? fnCatch : arguments[1];
+                return '';
+            }).trim();
+
+            let matches = reg.exec(sourceReplaced);
+            if (matches != null && (fnTry && fnCatch || !fnTry && !fnCatch)) {
+                funcName = matches[1];
+                source = matches[2];
+            } else {
+                reg = /^([\{\[])/;
+                if (!reg.test(source)) {
+                    return;
+                }
+            }
+
+            // 这里可能会throw exception
+            jsonObj = JSON.parse(source);
+        } catch (ex) {
+
+            // new Function的方式,能自动给key补全双引号,但是不支持bigint,所以是下下策,放在try-catch里搞
+            try {
+                jsonObj = new Function("return " + source)();
+            } catch (exx) {
+                try {
+                    // 再给你一次机会,是不是下面这种情况:  "{\"ret\":\"0\", \"msg\":\"ok\"}"
+                    jsonObj = new Function("return '" + source + "'")();
+                    if (typeof jsonObj === 'string') {
+                        try {
+                            // 确保bigint不会失真
+                            jsonObj = JSON.parse(jsonObj);
+                        } catch (ie) {
+                            // 最后给你一次机会,是个字符串,老夫给你再转一次
+                            jsonObj = new Function("return " + jsonObj)();
+                        }
+                    }
+                } catch (exxx) {
+                    return;
+                }
+            }
+
+        }
+
+        // 是json格式,可以进行JSON自动格式化
+        if (jsonObj != null && typeof jsonObj === "object") {
+            try {
+                // 要尽量保证格式化的东西一定是一个json,所以需要把内容进行JSON.stringify处理
+                source = JSON.stringify(jsonObj);
+            } catch (ex) {
+                // 通过JSON反解不出来的,一定有问题
+                return;
+            }
+
+            // JSON的所有key不能超过预设的值,比如 10000 个,要不然自动格式化会比较卡
+            if (options && options['MAX_JSON_KEYS_NUMBER']) {
+                let keysCount = _getAllKeysCount(jsonObj);
+                if (keysCount > options['MAX_JSON_KEYS_NUMBER']) {
+                    let msg = '当前JSON共 <b style="color:red">' + keysCount + '</b> 个Key,大于预设值' + options['MAX_JSON_KEYS_NUMBER'] + ',已取消自动格式化;可到FeHelper设置页调整此配置!';
+                    return toast(msg);
+                }
+            }
+
+            if (window.jsonformatContentScriptCssInject) {
+                window.jsonformatContentScriptCssInject();
+            } else {
+                // 注入css and html fragment
+                chrome.runtime.sendMessage({
+                    type: 'fh-dynamic-any-thing'
+                },(params) => {
+                    let injectFn = (cssText) => {
+                        chrome.tabs.insertCSS({
+                            code: cssText
+                        });
+                    };
+
+                    let cssText = Awesome.getContentScript('json-format', true);
+                    if (typeof cssText === 'string' && cssText.length) {
+                        injectFn(cssText);
+                    } else if (cssText instanceof Promise) {
+                        cssText.then(css => {
+                            if (css) {
+                                injectFn(css)
+                            } else {
+                                fetch('../json-format/content-script.css').then(resp => resp.text()).then(css => injectFn(css));
+                            }
+                        });
+                    } else if (!cssText) {
+                        fetch('../json-format/content-script.css').then(resp => resp.text()).then(css => injectFn(css));
+                    }
+                    return true;
+                });
+            }
+
+            let preLength = $('body>pre').hide().length;
+            $('body').prepend(_getHtmlFragment());
+            if (!preLength) {
+                Array.prototype.slice.call(document.body.childNodes).forEach(node => {
+                    (node.nodeType === Node.TEXT_NODE) && node.remove();
+                });
+            }
+
+            originalJsonStr = source;
+
+            // 获取上次记录的排序方式
+            curSortType = parseInt(localStorage.getItem(JSON_SORT_TYPE_KEY) || 0);
+            _didFormat(curSortType);
+
+            // 排序选项初始化
+            $('[name=jsonsort][value=' + curSortType + ']').attr('checked', 1);
+
+            // 自动解码选项初始化
+            autoDecode = localStorage.getItem(JSON_AUTO_DECODE);
+            if (autoDecode === null) {
+                autoDecode = (options && options['AUTO_TEXT_DECODE']);
+            } else {
+                autoDecode = autoDecode === 'true';
+            }
+            $('#json_endecode').prop('checked', autoDecode);
+
+            _bindEvent();
+        }
+    };
+
+    let _didFormat = function (sortType) {
+        sortType = sortType || 0;
+        let source = originalJsonStr;
+
+        if (sortType !== 0) {
+            let jsonObj = JsonABC.sortObj(JSON.parse(originalJsonStr), parseInt(sortType), true);
+            source = JSON.stringify(jsonObj);
+        }
+
+        if (autoDecode) {
+            (async () => {
+                let txt = await JsonEnDecode.urlDecodeByFetch(source);
+                source = JsonEnDecode.uniDecode(txt);
+
+                // 格式化
+                Formatter.format(source);
+                $('.x-toolbar').fadeIn(500);
+            })();
+        } else {
+            // 格式化
+            Formatter.format(source);
+            $('.x-toolbar').fadeIn(500);
+        }
+
+
+        // 如果是JSONP格式的,需要把方法名也显示出来
+        if (funcName != null) {
+            if (fnTry && fnCatch) {
+                $('#jfCallbackName_start').html('<pre style="padding:0">' + fnTry + '</pre>' + funcName + '(');
+                $('#jfCallbackName_end').html(')<br><pre style="padding:0">' + fnCatch + '</pre>');
+            } else {
+                $('#jfCallbackName_start').html(funcName + '(');
+                $('#jfCallbackName_end').html(')');
+            }
+        }
+    };
+
+    let _getCorrectContent = function () {
+        fetch(location.href).then(res => res.text()).then(text => {
+            originalJsonStr = text;
+            _didFormat(curSortType);
+        });
+    };
+
+    let _bindEvent = function () {
+        $('[name=jsonsort]').click(function (e) {
+            let sortType = parseInt(this.value);
+            if (sortType !== curSortType) {
+                _didFormat(sortType);
+                curSortType = sortType;
+            }
+            localStorage.setItem(JSON_SORT_TYPE_KEY, sortType);
+        });
+
+        $('#json_endecode').click(function (e) {
+            autoDecode = this.checked;
+            localStorage.setItem(JSON_AUTO_DECODE, autoDecode);
+            _didFormat(curSortType);
+        });
+
+        let tgBtn = $('.fe-feedback #toggleBtn').click(function (e) {
+            e.preventDefault();
+            e.stopPropagation();
+
+            chrome.runtime.sendMessage({
+                type: 'fh-dynamic-any-thing'
+            }, (params) => {
+                let show = String(localStorage.getItem(JSON_TOOL_BAR_ALWAYS_SHOW)) !== 'false';
+                localStorage.setItem(JSON_TOOL_BAR_ALWAYS_SHOW, !show);
+
+                let toolBarClassList = document.querySelector('div.x-toolbar').classList;
+                if (!show) {
+                    toolBarClassList.remove('t-collapse');
+                    tgBtn.html('隐藏&gt;&gt;');
+                } else {
+                    toolBarClassList.add('t-collapse');
+                    tgBtn.html('&lt;&lt;');
+                }
+            });
+        });
+
+        chrome.runtime.sendMessage({
+            type: 'fh-dynamic-any-thing'
+        }, params => {
+            let show = String(localStorage.getItem(JSON_TOOL_BAR_ALWAYS_SHOW)) !== 'false';
+            let toolBarClassList = document.querySelector('div.x-toolbar').classList;
+            if (show) {
+                toolBarClassList.remove('t-collapse');
+                tgBtn.html('隐藏&gt;&gt;');
+            } else {
+                toolBarClassList.add('t-collapse');
+                tgBtn.html('&lt;&lt;');
+            }
+        });
+
+        $('#jsonGetCorrectCnt').click(function (e) {
+            _getCorrectContent();
+        });
+
+        $('#capturePage').click(function (e) {
+            chrome.runtime.sendMessage({
+                type: 'capture-visible-page'
+            }, uri => {
+                window.open(uri);
+            });
+            e.preventDefault();
+            e.stopPropagation();
+        });
+    };
+
+    return {
+        format: (options) => {
+            setTimeout(() => {
+                _format(options);
+            }, pleaseLetJsLoaded);
+        }
+    };
+})();

+ 96 - 25
apps/json-format/format-lib.js

@@ -1,7 +1,6 @@
 /**
  * FeHelper Json Format Lib
  */
-
 let JsonFormatEntrance = (function () {
 
     "use strict";
@@ -38,15 +37,9 @@ let JsonFormatEntrance = (function () {
             formattingMsg = $('<div id="formattingMsg"><span class="x-loading"></span>格式化中...</div>').appendTo('body');
         }
 
-        jfOptEl = $('#boxOpt');
-        if (!jfOptEl.length) {
-            jfOptEl = $('<div id="boxOpt"><a class="opt-download" target="_blank">下载</a>|<a class="opt-copy">复制</a>|<a class="opt-del">删除</a></div>').appendTo('body');
-        }
-
         try {
             jfContent.html('').show();
             jfPre.html('').hide();
-            jfOptEl && jfOptEl.hide();
             jfPathEl && jfPathEl.hide();
             formattingMsg.hide();
         } catch (e) {
@@ -70,7 +63,6 @@ let JsonFormatEntrance = (function () {
                 formattingMsg.hide();
                 jfContent.html(msg[1]);
 
-                _loadJs();
                 _buildOptionBar();
                 // 事件绑定
                 _addEvents();
@@ -118,14 +110,6 @@ let JsonFormatEntrance = (function () {
 
     };
 
-    let _loadJs = function () {
-        if (typeof Tarp === 'object') {
-            Tarp.require('../static/js/utils.js');
-        } else {
-            toast('无法加载Tarp.require.js');
-        }
-    };
-
     /**
      * 直接下载,能解决中文乱码
      * @param content
@@ -137,7 +121,7 @@ let JsonFormatEntrance = (function () {
         let dt = (new Date()).format('yyyyMMddHHmmss');
         let blob = new Blob([content], {type: 'application/octet-stream'});
 
-        let button = $('<button id="btnDownload">下载JSON</button>').appendTo('#optionBar');
+        let button = $('<button class="xjf-btn xjf-btn-right">下载JSON</button>').appendTo('#optionBar');
 
         if (typeof chrome === 'undefined' || !chrome.permissions) {
             button.click(function (e) {
@@ -264,6 +248,12 @@ let JsonFormatEntrance = (function () {
             jfPathEl && jfPathEl.hide();
         };
 
+
+        jfOptEl = $('#boxOpt');
+        if (!jfOptEl.length) {
+            jfOptEl = $('<div id="boxOpt"><a class="opt-download" target="_blank">下载</a>|<a class="opt-copy">复制</a>|<a class="opt-del">删除</a></div>').appendTo(jfContent);
+        }
+
         jfOptEl.find('a.opt-download').unbind('click').bind('click', fnDownload);
         jfOptEl.find('a.opt-copy').unbind('click').bind('click', fnCopy);
         jfOptEl.find('a.opt-del').unbind('click').bind('click', fnDel);
@@ -311,13 +301,15 @@ let JsonFormatEntrance = (function () {
     let _buildOptionBar = function () {
 
         let optionBar = $('#optionBar');
-        if (optionBar) {
-            optionBar.remove();
+        if (optionBar.length) {
+            optionBar.html('');
+        }else{
+            optionBar = $('<span id="optionBar" />').appendTo(jfContent.parent());
         }
-        optionBar = $('<div id="optionBar" />').appendTo(jfContent.parent());
 
-        let buttonFormatted = $('<button id="buttonFormatted">元数据</button>').appendTo(optionBar);
-        let buttonCollapseAll = $('<button id="buttonCollapseAll">折叠所有</button>').appendTo(optionBar);
+        $('<span class="x-split">|</span>').appendTo(optionBar);
+        let buttonFormatted = $('<button class="xjf-btn xjf-btn-left">元数据</button>').appendTo(optionBar);
+        let buttonCollapseAll = $('<button class="xjf-btn xjf-btn-mid">折叠所有</button>').appendTo(optionBar);
         let plainOn = false;
 
         buttonFormatted.bind('click', function (e) {
@@ -386,7 +378,8 @@ let JsonFormatEntrance = (function () {
                 color: '#ff0',
                 fontSize: '12px',
                 fontWeight: 'bold',
-                padding: '2px 10px 2px 2px'
+                padding: '2px 10px 2px 2px',
+                zIndex: 10
             }).appendTo('body');
         }
         jfPathEl.html('当前路径:' + path).show();
@@ -892,6 +885,84 @@ let JsonFormatDealer = (function () {
     };
 })();
 
-module.exports = {
+window.Formatter = {
     format: JsonFormatEntrance.format
-};
+};
+
+/**
+ * 日期格式化
+ * @param {Object} pattern
+ */
+Date.prototype.format = function (pattern) {
+    let pad = function (source, length) {
+        let pre = "",
+            negative = (source < 0),
+            string = String(Math.abs(source));
+
+        if (string.length < length) {
+            pre = (new Array(length - string.length + 1)).join('0');
+        }
+
+        return (negative ? "-" : "") + pre + string;
+    };
+
+    if ('string' !== typeof pattern) {
+        return this.toString();
+    }
+
+    let replacer = function (patternPart, result) {
+        pattern = pattern.replace(patternPart, result);
+    };
+
+    let year = this.getFullYear(),
+        month = this.getMonth() + 1,
+        date2 = this.getDate(),
+        hours = this.getHours(),
+        minutes = this.getMinutes(),
+        seconds = this.getSeconds(),
+        milliSec = this.getMilliseconds();
+
+    replacer(/yyyy/g, pad(year, 4));
+    replacer(/yy/g, pad(parseInt(year.toString().slice(2), 10), 2));
+    replacer(/MM/g, pad(month, 2));
+    replacer(/M/g, month);
+    replacer(/dd/g, pad(date2, 2));
+    replacer(/d/g, date2);
+
+    replacer(/HH/g, pad(hours, 2));
+    replacer(/H/g, hours);
+    replacer(/hh/g, pad(hours % 12, 2));
+    replacer(/h/g, hours % 12);
+    replacer(/mm/g, pad(minutes, 2));
+    replacer(/m/g, minutes);
+    replacer(/ss/g, pad(seconds, 2));
+    replacer(/s/g, seconds);
+    replacer(/SSS/g, pad(milliSec, 3));
+    replacer(/S/g, milliSec);
+
+    return pattern;
+};
+
+/**
+ * 自动消失的Alert弹窗
+ * @param content
+ */
+window.toast = function (content) {
+    window.clearTimeout(window.feHelperAlertMsgTid);
+    let elAlertMsg = document.querySelector("#fehelper_alertmsg");
+    if (!elAlertMsg) {
+        let elWrapper = document.createElement('div');
+        elWrapper.innerHTML = '<div id="fehelper_alertmsg" style="position:fixed;top:5px;right:5px;z-index:1000000">' +
+            '<p style="background:#000;display:inline-block;color:#fff;text-align:center;' +
+            'padding:10px 10px;margin:0 auto;font-size:14px;border-radius:4px;">' + content + '</p></div>';
+        elAlertMsg = elWrapper.childNodes[0];
+        document.body.appendChild(elAlertMsg);
+    } else {
+        elAlertMsg.querySelector('p').innerHTML = content;
+        elAlertMsg.style.display = 'block';
+    }
+
+    window.feHelperAlertMsgTid = window.setTimeout(function () {
+        elAlertMsg.style.display = 'none';
+    }, 3000);
+};

+ 70 - 98
apps/json-format/index.css

@@ -1,63 +1,37 @@
 @import url("../static/vendor/codemirror/codemirror.css");
 @import url("../static/css/bootstrap.min.css");
-@import url("./without-ui.css");
+@import url("content-script.css");
 
 body {
     background-color: #fff;
 }
-.wrapper {padding-right:0}
-.wp-json {
-    width:auto;
-}
-.wp-json .mod-json {
-    position: absolute;
-    top: 60px;
-    bottom: 0;
-    right:0;
-    left:20px;
-}
-.wp-json .mod-json .panel-txt {
-    position: absolute;
-    width: 500px;
-    top: 15px;
-    bottom: 0;
+html,body {
+    padding: 0;
+    margin: 0;
+    height:100%;
 }
 
-.wp-json .mod-json .rst-item {
-    margin-left: 500px;
-    position: relative;
-    padding-top: 0;
-    padding-bottom:20px;
+.wp-json {
+    width:auto;
 }
 
-#optionBar {
-    position: fixed;
-    top: 30px;
-    right: 30px;
+.wp-json .panel-body {
+    padding: 0;
 }
 #jfContent_pre {
     display: none;
     padding: 10px;
 }
-#jsonSource,.CodeMirror {
-    height: calc(100% - 70px);
-    /*font-size: 10px;*/
-}
+
 #errorMsg {
     color: #f00;
     margin-left: 10px;
     float: right;
 }
-#jfContent {
-    margin-right: 10px;
-}
-.x-placeholder {
-    padding-top: 50px;
-    text-align: center;
-}
 .x-placeholder img{
     width: 400px;
-    opacity: 0.15;
+    opacity: 0.3;
+    margin: 5px 0 0 -8px;
 }
 .x-xdemo,a.x-xdemo {
     margin-left: 30px;
@@ -70,34 +44,12 @@ body {
     text-decoration: underline;
 }
 
-
 #errorTips {
-    position: fixed;
-    left: 20px;
-    bottom: 4px;
-    z-index: 23;
-    width: 500px;
     border-radius: 4px;
     box-shadow: 6px 5px 7px rgba(229, 163, 163, 0.4);
     background-color: #ffecf1;
     border: 1px solid #dbb2b2;
-    overflow: auto;
-    max-height: 98%;
-    min-height: 100px;
-    display: none;
-}
-#errorBtn {
-    padding:5px 0 0 5px;
-    font-size:16px;
-    cursor:pointer;
-    color:#e96969;
-    left: 490px;
-    background: #ffecf1;
-    position: fixed;
-    z-index: 100;
-}
-#errorBtn:hover {
-    opacity:0.6;
+    margin-top:10px;
 }
 #errorCode .errorEm {
     background-color:#ff921b;
@@ -109,7 +61,6 @@ body {
 #errorCode {
     padding:0 10px 10px;
     word-break: break-all;
-    margin-top: 30px;
 }
 #errorCode ol {
     padding:0 0 0 3em;
@@ -121,9 +72,7 @@ body {
 #tipsBox {
     color:#b4465c;
     padding: 6px 0 4px 2em;
-    position: fixed;
     background-color: #ffecf1;
-    width: 500px;
 }
 #errorCode ol li {
     padding:4px 7px 0;
@@ -134,60 +83,83 @@ body {
 #errorCode ol li div {
     color:#000;
 }
-input[type=checkbox] {
-    margin-left: 20px;
-}
 
 .x-error {
     color:red;
 }
 
 /*layout-up-down:上下布局模式*/
-.wp-json .mod-json.layout-up-down {
-    padding: 0;
-    position: static;
-}
-.wp-json .mod-json.layout-up-down .panel-txt {
+.wp-json.layout-up-down .mod-json .panel-txt {
     position: static;
     width: 100%;
-    height: 300px;
-    margin: 2px 0;
+    height: 250px;
+    margin: 0;
 }
-.wp-json .mod-json.layout-up-down .rst-item {
-    margin-left: 0;
-    margin-right: 0;
+.wp-json.layout-up-down  .CodeMirror {
+    height: 250px;
 }
-.wp-json .mod-json.layout-up-down #optionBar {
-    position: absolute;
-    top: -40px;
-    right:0;
+.wp-json.layout-up-down .mod-json .rst-item {
+    margin: 0;
 }
-.wp-json .mod-json.layout-up-down .x-placeholder {
+.wp-json.layout-up-down .mod-json .x-placeholder {
     padding-top: 0;
     text-align: left;
 }
-.x-sort {
-    display: block;
-    margin-left: 150px;
+
+/* layout-left-right: 左右布局 */
+.wp-json.layout-left-right .mod-json .panel-txt {
+    width: 500px;
+    height: calc(100% - 10px);
+    float: left;
 }
-.x-sort .x-split {
-    display: none;
+.wp-json.layout-left-right .mod-json .rst-item {
+    margin: 0 20px 0 30px;
+    padding-bottom:20px;
+    float: left;
+    width: calc(100% - 520px);
 }
-.layout-up-down .x-sort {
-    display: inline;
-    margin: 0;
+.wp-json.layout-left-right .mod-json .x-placeholder {
+    padding-top: 50px;
+    text-align: center;
 }
-.layout-up-down .x-sort .x-split {
-    margin: 0 20px;
-    color: #ccc;
-    display: inline;
+.wp-json.layout-left-right .panel-body {
+    height:calc(100% - 95px);
+    padding-left: 15px;
 }
-.x-sort .x-stitle {
+.wp-json.layout-left-right  #jsonSource,
+.wp-json.layout-left-right  .CodeMirror {
+    height: calc(100% - 10px);
+}
+.wp-json.layout-left-right #formattedJson {
+    padding-top: 2px;
+}
+.wp-json.layout-left-right #errorTips {
+    margin-top: 0;
+}
+
+.x-toolbar {
+    padding-top:5px;
+    padding-bottom: 5px;
+}
+.x-toolbar.x-inpage {
+    margin:10px 20px;
+}
+.panel-title>a.x-other-tools {
+    margin:10px 0 0;
+    font-size: 12px;
+    cursor: pointer;
+    text-decoration: underline;
+    -webkit-user-select: none;
+    user-select: none;
     color: #f00;
-    font-weight: bold;
+    border-bottom: 1px solid #f00;
+    float: right;
+}
+.panel-title>a.x-other-tools:hover {
+    color:#00f;
 }
-.x-sort input {
-    margin-right: 10px;
+.panel-heading {
+    padding:5px 15px;
 }
 
 #layoutBar {margin-left:30px}

+ 40 - 29
apps/json-format/index.html

@@ -4,8 +4,8 @@
         <title>JSON格式化查看工具</title>
         <meta charset="UTF-8">
         <link rel="stylesheet" href="index.css" />
+		<script type="text/javascript" src="../static/vendor/evalCore.min.js"></script>
         <script type="text/javascript" src="../static/vendor/vue/vue.js"></script>
-        <script src="../static/vendor/require/require.js"></script>
     </head>
     <body>
         <div class="wrapper wp-json" id="pageContainer">
@@ -13,52 +13,60 @@
                 <div class="panel-heading">
                     <h3 class="panel-title">
                         <a href="http://www.baidufe.com/fehelper/feedback.html" target="_blank" class="x-a-high">
-                            <img src="../static/img/fe-16.png" alt="fehelper"/> FeHelper</a>:JSON格式化查看
+                            <img src="../static/img/fe-16.png" alt="fehelper"/> FeHelper</a>:JSON格式化
                         <span class="x-xdemo" ref="demoLink1" @click="setDemo">示例1:JSON片段</span>
-                        <a class="x-xdemo" href="http://t.weather.sojson.com/api/weather/city/101030100" target="_blank">示例2:在线JSON-1</a>
+                        <a class="x-xdemo" href="http://t.weather.sojson.com/api/weather/city/101030100" target="_blank">示例2:在线JSON</a>
 
                         <span id="layoutBar">
                             <button id="btnLeftRight" ref="btnLeftRight" class="selected" @click="changeLayout('left-right')">左右布局</button><button id="btnUpDown" ref="btnUpDown" @click="changeLayout('up-down')">上下布局</button>
                         </span>
+
+                        <a class="x-other-tools" @click="openOptionsPage()">去开启FeHelper的更多功能&gt;&gt;</a>
                     </h3>
                 </div>
             </div>
 
-            <div class="panel-body mod-json" ref="panelBody">
+            <div class="x-toolbar x-inpage">
+
+                <button id="btnFormat" class="btn btn-primary btn-xs ui-mr-10" @click="format">格式化</button>
+                <button id="btnCompress" class="btn btn-success btn-xs" @click="compress">压缩</button>
+                <span class="x-split">|</span>
+                <input type="checkbox" v-model="jsonLintSwitch" id="jsonLint" @click="lintOn"><label for="jsonLint">JSONLint</label>
+                <span class="x-split">|</span>
+                <input type="checkbox" v-model="autoDecode" id="endecode" @click="autoDecodeFn"><label for="endecode">自动解码</label>
+                <span class="x-split">|</span>
+                <input type="checkbox" v-model="overrideJson" id="jsonOvrd" @click="setCache"><label for="jsonOvrd">节点编辑</label>
+                <span class="x-split">|</span>
+                <span class="x-sort">
+                    <span class="x-stitle">排序:</span>
+                    <label for="sort_null">默认</label>
+                    <input type="radio" name="jsonsort" id="sort_null" value="0" checked @click="format">
+                    <label for="sort_asc">升序</label>
+                    <input type="radio" name="jsonsort" id="sort_asc" value="1" @click="format">
+                    <label for="sort_desc">降序</label>
+                    <input type="radio" name="jsonsort" id="sort_desc" value="-1" @click="format">
+                </span>
+                <span class="x-split">|</span>
+                <span class="x-endecode">
+                    <button class="xjf-btn xjf-btn-left" @click="uniEncode">Uni编码</button><button class="xjf-btn xjf-btn-mid" @click="uniDecode">Uni解码</button><button class="xjf-btn xjf-btn-right" @click="urlDecode">URL解码</button>
+                </span>
+
+                <span id="optionBar"></span>
+            </div>
+
+            <div class="panel-body mod-json">
                 <div class="row panel-txt">
                     <textarea class="form-control mod-textarea" id="jsonSource" placeholder="在这里粘贴您需要进行格式化的JSON代码" ref="jsonBox"></textarea>
-                    <div class="ui-mt-10">
-                        <button id="btnFormat" class="btn btn-primary" @click="format">格式化</button>
-                        <button id="btnCompress" class="btn btn-success" @click="compress">压缩</button>
-                        <input type="checkbox" v-model="jsonLintSwitch" id="jsonLint" @click="lintOn"><label for="jsonLint">开启JSONLint</label>
-                        <input type="checkbox" v-model="overrideJson" id="jsonOvrd" @click="setCache"><label for="jsonOvrd">选中JSON并编辑</label>
-                        <span class="x-sort">
-                            <span class="x-split">|</span>
-                            <span class="x-stitle">JSON排序:</span>
-                            <label for="sort_null">默认</label>
-                            <input type="radio" name="jsonsort" id="sort_null" value="0" checked @click="format">
-                            <label for="sort_asc">升序</label>
-                            <input type="radio" name="jsonsort" id="sort_asc" value="1" @click="format">
-                            <label for="sort_desc">降序</label>
-                            <input type="radio" name="jsonsort" id="sort_desc" value="-1" @click="format">
-                        </span>
-                    </div>
-                    <div id="errorTips" v-bind:style="{display:showTips?'block':'none'}">
-                        <div id="errorBtn" @click="closeTips"><span id="closeError">☓</span></div>
-                        <div id="tipsBox" v-html="errorPos"></div>
-                        <div id="errorCode" v-html="errorJsonCode"></div>
-                    </div>
                 </div>
 
                 <div class="row rst-item" id="modJsonResult">
                     <div id="formattingMsg"><span class="x-loading"></span>格式化中...</div>
                     <div id="jfCallbackName_start" class="callback-name" v-html="jfCallbackName_start"></div>
-                    <div id="jfContent" v-html="resultContent"></div>
+                    <div id="jfContent" v-html="placeHolder"></div>
                     <pre id="jfContent_pre"></pre>
                     <div id="jfCallbackName_end" class="callback-name" v-html="jfCallbackName_end"></div>
                 </div>
             </div>
-
         </div>
         <script src="../static/vendor/jquery/jquery-3.3.1.min.js"></script>
         <script src="../static/vendor/codemirror/codemirror.js"></script>
@@ -66,6 +74,9 @@
         <script src="../static/vendor/codemirror/active-line.js"></script>
         <script src="../static/vendor/codemirror/matchbrackets.js"></script>
         <script src="../static/vendor/codemirror/placeholder.js"></script>
-        <script src="index.js"></script>
+        <script src="json-lint.js"></script>
+        <script src="content-script.js"></script>
+        <script src="../static/js/dark-mode.js"></script>
+        <script src="index.js" type="module"></script>
     </body>
-</html>
+</html>

+ 93 - 47
apps/json-format/index.js

@@ -1,32 +1,38 @@
 /**
  * FeHelper Json Format Tools
  */
+
+// 一些全局变量
 let editor = {};
 let LOCAL_KEY_OF_LAYOUT = 'local-layout-key';
 let JSON_LINT = 'jsonformat:json-lint-switch';
 let EDIT_ON_CLICK = 'jsonformat:edit-on-click';
-
-// json with bigint supported
-Tarp.require('../static/vendor/json-bigint/index');
+let AUTO_DECODE = 'jsonformat:auto-decode';
 
 new Vue({
     el: '#pageContainer',
     data: {
-        defaultResultTpl: '<div class="x-placeholder"><img src="./json-demo.jpg" alt="json-placeholder"></div>',
-        resultContent: '',
+        defaultResultTpl: '<div class="x-placeholder"><img src="../json-format/json-demo.jpg" alt="json-placeholder"></div>',
+        placeHolder: '',
         jsonFormattedSource: '',
         errorMsg: '',
         errorJsonCode: '',
         errorPos: '',
         jfCallbackName_start: '',
         jfCallbackName_end: '',
-        showTips: false,
         jsonLintSwitch: true,
+        autoDecode: false,
         fireChange: true,
         overrideJson: false
     },
     mounted: function () {
-        this.resultContent = this.defaultResultTpl;
+        // 自动开关灯控制
+        DarkModeMgr.turnLightAuto();
+
+        this.placeHolder = this.defaultResultTpl;
+
+        this.autoDecode = localStorage.getItem(AUTO_DECODE);
+        this.autoDecode = this.autoDecode === 'true';
 
         this.jsonLintSwitch = (localStorage.getItem(JSON_LINT) !== 'false');
         this.overrideJson = (localStorage.getItem(EDIT_ON_CLICK) === 'true');
@@ -55,23 +61,23 @@ new Vue({
         });
 
         // 在tab创建或者更新时候,监听事件,看看是否有参数传递过来
-        chrome.runtime.onMessage.addListener((request, sender, callback) => {
-            let MSG_TYPE = Tarp.require('../static/js/msg_type');
-            if (request.type === MSG_TYPE.TAB_CREATED_OR_UPDATED && request.event === MSG_TYPE.JSON_FORMAT) {
-                if (request.content) {
+        if (location.protocol === 'chrome-extension:') {
+            chrome.runtime.onMessage.addListener((request, sender, callback) => {
+                if (request.type === 'TAB_CREATED_OR_UPDATED' && request.content && request.event === location.pathname.split('/')[1]) {
                     editor.setValue(request.content || this.defaultResultTpl);
                     this.format();
                 }
-            }
-        });
-
-
+                callback && callback();
+                return true;
+            });
+        }
     },
     methods: {
         format: function () {
-            this.showTips = false;
             this.errorMsg = '';
-            this.resultContent = this.defaultResultTpl;
+            this.placeHolder = this.defaultResultTpl;
+            this.jfCallbackName_start = '';
+            this.jfCallbackName_end = '';
 
             let source = editor.getValue().replace(/\n/gm, ' ');
             if (!source) {
@@ -103,8 +109,13 @@ new Vue({
                         // 再给你一次机会,是不是下面这种情况:  "{\"ret\":\"0\", \"msg\":\"ok\"}"
                         jsonObj = new Function("return '" + source + "'")();
                         if (typeof jsonObj === 'string') {
-                            // 最后给你一次机会,是个字符串,老夫给你再转一次
-                            jsonObj = new Function("return " + jsonObj)();
+                            try {
+                                // 确保bigint不会失真
+                                jsonObj = JSON.parse(jsonObj);
+                            } catch (ie) {
+                                // 最后给你一次机会,是个字符串,老夫给你再转一次
+                                jsonObj = new Function("return " + jsonObj)();
+                            }
                         }
                     } catch (exxx) {
                         this.errorMsg = exxx.message;
@@ -117,7 +128,7 @@ new Vue({
                 try {
                     let sortType = document.querySelectorAll('[name=jsonsort]:checked')[0].value;
                     if (sortType !== '0') {
-                        jsonObj = Tarp.require('../json-format/jsonabc').sortObj(jsonObj, parseInt(sortType), true);
+                        jsonObj = JsonABC.sortObj(jsonObj, parseInt(sortType), true);
                     }
                     source = JSON.stringify(jsonObj);
                 } catch (ex) {
@@ -126,8 +137,18 @@ new Vue({
                 }
 
                 if (!this.errorMsg.length) {
-                    // 格式化
-                    Tarp.require('./format-lib').format(source);
+
+                    if (this.autoDecode) {
+                        (async () => {
+                            let txt = await JsonEnDecode.urlDecodeByFetch(source);
+                            source = JsonEnDecode.uniDecode(txt);
+                            Formatter.format(source);
+                        })();
+                    } else {
+                        Formatter.format(source);
+                    }
+
+                    this.placeHolder = '';
                     this.jsonFormattedSource = source;
 
                     // 如果是JSONP格式的,需要把方法名也显示出来
@@ -138,6 +159,10 @@ new Vue({
                         this.jfCallbackName_start = '';
                         this.jfCallbackName_end = '';
                     }
+
+                    this.$nextTick(() => {
+                        this.updateWrapperHeight();
+                    })
                 }
             }
 
@@ -145,11 +170,11 @@ new Vue({
                 if (this.jsonLintSwitch) {
                     return this.lintOn();
                 } else {
-                    this.resultContent = '<span class="x-error">' + this.errorMsg + '</span>';
+                    this.placeHolder = '<span class="x-error">' + this.errorMsg + '</span>';
                     return false;
                 }
             }
-
+            
             return true;
         },
 
@@ -160,23 +185,50 @@ new Vue({
             }
         },
 
+        autoDecodeFn: function () {
+            this.$nextTick(() => {
+                localStorage.setItem(AUTO_DECODE, this.autoDecode);
+                this.format();
+            });
+        },
+
+        uniEncode: function () {
+            editor.setValue(JsonEnDecode.uniEncode(editor.getValue()));
+        },
+
+        uniDecode: function () {
+            editor.setValue(JsonEnDecode.uniDecode(editor.getValue()));
+        },
+
+        urlDecode: function () {
+            JsonEnDecode.urlDecodeByFetch(editor.getValue()).then(text => editor.setValue(text));
+        },
+
+        updateWrapperHeight: function () {
+            let curLayout = localStorage.getItem(LOCAL_KEY_OF_LAYOUT);
+            let elPc = document.querySelector('#pageContainer');
+            if (curLayout === 'up-down') {
+                elPc.style.height = 'auto';
+            } else {
+                elPc.style.height = Math.max(elPc.scrollHeight, document.body.scrollHeight) + 'px';
+            }
+        },
+
         changeLayout: function (type) {
+            let elPc = document.querySelector('#pageContainer');
             if (type === 'up-down') {
-                if (this.$refs.btnUpDown.classList.contains('selected')) {
-                    return;
-                }
-                this.$refs.panelBody.classList.add('layout-up-down');
+                elPc.classList.remove('layout-left-right');
+                elPc.classList.add('layout-up-down');
                 this.$refs.btnLeftRight.classList.remove('selected');
                 this.$refs.btnUpDown.classList.add('selected');
             } else {
-                if (this.$refs.btnLeftRight.classList.contains('selected')) {
-                    return;
-                }
-                this.$refs.panelBody.classList.remove('layout-up-down');
+                elPc.classList.remove('layout-up-down');
+                elPc.classList.add('layout-left-right');
                 this.$refs.btnLeftRight.classList.add('selected');
                 this.$refs.btnUpDown.classList.remove('selected');
             }
             localStorage.setItem(LOCAL_KEY_OF_LAYOUT, type);
+            this.updateWrapperHeight();
         },
 
         setCache: function () {
@@ -196,26 +248,16 @@ new Vue({
                 if (!this.jsonLintSwitch) {
                     return;
                 }
-                let lintResult = Tarp.require('./jsonlint')(editor.getValue());
+                let lintResult = JsonLint.lintDetect(editor.getValue());
                 if (!isNaN(lintResult.line)) {
-                    this.errorPos = '错误位置:' + (lintResult.line + 1) + '行,' + (lintResult.col + 1) + '列;缺少字符或字符不正确';
-                    this.errorJsonCode = lintResult.dom;
-                    this.showTips = true;
-                    this.$nextTick(() => {
-                        let el = document.querySelector('#errorCode .errorEm');
-                        el && el.scrollIntoView();
-                        let scrollEl = document.querySelector('#errorTips');
-                        scrollEl.scrollBy(0, el.offsetTop - scrollEl.scrollTop - 50);
-                    });
+                    this.placeHolder = '<div id="errorTips">' +
+                        '<div id="tipsBox">错误位置:' + (lintResult.line + 1) + '行,' + (lintResult.col + 1) + '列;缺少字符或字符不正确</div>' +
+                        '<div id="errorCode">' + lintResult.dom + '</div></div>';
                 }
             });
             return false;
         },
 
-        closeTips: function () {
-            this.showTips = false;
-        },
-
         disableEditorChange: function (jsonTxt) {
             this.fireChange = false;
             this.$nextTick(() => {
@@ -226,9 +268,13 @@ new Vue({
             })
         },
 
+        openOptionsPage: function(){
+            chrome.runtime.openOptionsPage();
+        },
+
         setDemo: function () {
             let demo = '{"BigIntSupported":995815895020119788889,"date":"20180322","message":"Success !","status":200,"city":"北京","count":632,"data":{"shidu":"34%","pm25":73,"pm10":91,"quality":"良","wendu":"5","ganmao":"极少数敏感人群应减少户外活动","yesterday":{"date":"21日星期三","sunrise":"06:19","high":"高温 11.0℃","low":"低温 1.0℃","sunset":"18:26","aqi":85,"fx":"南风","fl":"<3级","type":"多云","notice":"阴晴之间,谨防紫外线侵扰"},"forecast":[{"date":"22日星期四","sunrise":"06:17","high":"高温 17.0℃","low":"低温 1.0℃","sunset":"18:27","aqi":98,"fx":"西南风","fl":"<3级","type":"晴","notice":"愿你拥有比阳光明媚的心情"},{"date":"23日星期五","sunrise":"06:16","high":"高温 18.0℃","low":"低温 5.0℃","sunset":"18:28","aqi":118,"fx":"无持续风向","fl":"<3级","type":"多云","notice":"阴晴之间,谨防紫外线侵扰"},{"date":"24日星期六","sunrise":"06:14","high":"高温 21.0℃","low":"低温 7.0℃","sunset":"18:29","aqi":52,"fx":"西南风","fl":"<3级","type":"晴","notice":"愿你拥有比阳光明媚的心情"},{"date":"25日星期日","sunrise":"06:13","high":"高温 22.0℃","low":"低温 7.0℃","sunset":"18:30","aqi":71,"fx":"西南风","fl":"<3级","type":"晴","notice":"愿你拥有比阳光明媚的心情"},{"date":"26日星期一","sunrise":"06:11","high":"高温 21.0℃","low":"低温 8.0℃","sunset":"18:31","aqi":97,"fx":"西南风","fl":"<3级","type":"多云","notice":"阴晴之间,谨防紫外线侵扰"}]}}';
             editor.setValue(demo);
         }
     }
-});
+});

+ 2 - 2
apps/json-format/jsonabc.js → apps/json-format/json-abc.js

@@ -1,8 +1,8 @@
 /**
  * JSON排序处理
- * @type {{sort, sortObj, cleanJSON}}
+ * @author zhaoxianlie
  */
-module.exports = (function () {
+window.JsonABC = (function () {
 
     // Is a value an array?
     function isArray(val) {

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
apps/json-format/json-bigint.js


+ 103 - 0
apps/json-format/json-decode.js

@@ -0,0 +1,103 @@
+/**
+ * 此方法用于将Json内容的Unicode编解码
+ * @param {Object} text
+ */
+window.JsonEnDecode = {
+    uniEncode: function (str) {
+        return escape(str)
+            .replace(/%u/gi, '\\u')
+            .replace(/%7b/gi, '{')
+            .replace(/%7d/gi, '}')
+            .replace(/%3a/gi, ':')
+            .replace(/%2c/gi, ',')
+            .replace(/%27/gi, '\'')
+            .replace(/%22/gi, '"')
+            .replace(/%5b/gi, '[')
+            .replace(/%5d/gi, ']')
+            .replace(/%3D/gi, '=')
+            .replace(/%08/gi, '\b')
+            .replace(/%0D/gi, '\r')
+            .replace(/%0C/gi, '\f')
+            .replace(/%09/gi, '\t')
+            .replace(/%20/gi, ' ')
+            .replace(/%0A/gi, '\n')
+            .replace(/%3E/gi, '>')
+            .replace(/%3C/gi, '<')
+            .replace(/%3F/gi, '?');
+    },
+    uniDecode: function (text) {
+        text = text.replace(/(\\)?\\u/gi, "%u").replace('%u0025', '%25');
+        text = unescape(text.toString().replace(/%2B/g, "+"));
+
+        let matches = text.match(/(%u00([0-9A-F]{2}))/gi);
+        if (matches) {
+            for (let matchid = 0; matchid < matches.length; matchid++) {
+                let code = matches[matchid].substring(1, 3);
+                let x = Number("0x" + code);
+                if (x >= 128) {
+                    text = text.replace(matches[matchid], code);
+                }
+            }
+        }
+        text = unescape(text.toString().replace(/%2B/g, "+"));
+
+        return text;
+    },
+
+    urlDecode: function (str) {
+        try {
+            return decodeURIComponent(str);
+        } catch (e) {
+            return str;
+        }
+    },
+
+    // 此种模式,随便用
+    urlDecodeByFetch: function (str) {
+        return new Promise((resolve, reject) => {
+            try {
+                fetch(`data:text/javascript;charset=utf8,${str.replace(/"/g, '%22').replace(/#/g, '%23')}`)
+                    .then(res => res.text(), error => {
+                        reject && reject(error);
+                    })
+                    .then(text => {
+                        resolve && resolve(text);
+                    });
+            } catch (e) {
+                resolve && resolve(str);
+            }
+        });
+    },
+
+    // 此种形式需要在manifest中增加csp策略,暂且不用
+    urlDecodeByIframe: function (str, charset) {
+        charset = charset || 'utf8';
+        return new Promise((resolve, reject) => {
+            let iframe = document.querySelector('#_urlDecode_iframe_');
+            if (iframe) {
+                iframe.remove();
+            }
+            iframe = document.createElement('iframe');
+            iframe.setAttribute('id', '_urlDecode_iframe_');
+            iframe.style.display = 'none';
+            iframe.width = "0";
+            iframe.height = "0";
+            iframe.scrolling = "no";
+            iframe.allowtransparency = "true";
+            iframe.frameborder = "0";
+            iframe.src = 'about:blank';
+            document.body.appendChild(iframe);
+            window._urlDecodeCallback = window._urlDecodeCallback || function (e) {
+                resolve && resolve(e.data);
+                iframe.remove();
+            };
+            window.removeEventListener('message', window._urlDecodeCallback);
+            window.addEventListener('message', window._urlDecodeCallback, false);
+            try {
+                iframe.contentWindow.document.write('<html><scrip' + `t charset="${charset}" src="data:text/javascript;charset=${charset},parent.postMessage(\`${str.replace(/"/g, '%22').replace(/#/g, '%23')}\`)"></scrip` + 't></html>');
+            } catch (e) {
+                reject && reject(e);
+            }
+        });
+    }
+};

+ 6 - 2
apps/json-format/jsonlint.js → apps/json-format/json-lint.js

@@ -1,5 +1,5 @@
 /* Jison generated parser */
-var jsonlint = (function () {
+let jsonlint = (function () {
     var parser = {
         trace: function trace() {
         },
@@ -616,7 +616,7 @@ var jsonlint = (function () {
  * @param sJson
  * @returns {{}}
  */
-module.exports = function (sJson) {
+let lintDetect = function (sJson) {
     let result = {};
 
     let insertErrorFlag = function (s) {
@@ -655,4 +655,8 @@ module.exports = function (sJson) {
     result["dom"] = '<div class="line-code">' + sJson + '</div>';
 
     return result;
+};
+
+window.JsonLint = {
+    lintDetect: lintDetect
 };

+ 0 - 186
apps/json-format/without-ui.css

@@ -1,186 +0,0 @@
-#jfContent{-webkit-user-select:text;margin:0;}
-#optionBar{-webkit-user-select:none;display:block;position:absolute;top:0px;right:0px}
-#buttonFormatted,#buttonCollapseAll,#btnDownload{
-    cursor: pointer;
-    -webkit-border-radius:2px;-webkit-box-shadow:0px 1px 3px rgba(0,0,0,0.1);
-    -webkit-user-select:none;background:-webkit-linear-gradient(#fafafa, #f4f4f4 40%, #e5e5e5);outline: none;
-    border:1px solid #aaa;color:#444;font-size:12px;margin-bottom:0px;min-width:4em;padding:0px 10px;
-    position:relative;z-index:10;display:inline-block;height:30px;
-    text-shadow:1px 1px rgba(255,255,255,0.3)}
-#buttonCollapseAll,#btnDownload{margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0;}
-#buttonFormatted,#buttonCollapseAll{margin-right:0;border-top-right-radius:0;border-bottom-right-radius:0;border-right:none}
-#buttonFormatted:hover,#btnDownload:hover,#buttonCollapseAll:hover{-webkit-box-shadow:0px 1px 3px rgba(0,0,0,0.2);
-    background:#ebebeb -webkit-linear-gradient(#fefefe, #f8f8f8 40%, #e9e9e9);border-color:#999;color:#222}
-#buttonFormatted:active{-webkit-box-shadow:inset 0px 1px 3px rgba(0,0,0,0.2);
-    background:#ebebeb -webkit-linear-gradient(#f4f4f4, #efefef 40%, #dcdcdc);color:#333}
-#buttonFormatted.selected,#buttonCollapseAll.selected{-webkit-box-shadow:inset 0px 1px 5px rgba(0,0,0,0.2);
-    background:#ebebeb -webkit-linear-gradient(#e4e4e4, #dfdfdf 40%, #dcdcdc);color:#333}
-#jsonpOpener,#jsonpCloser{padding:4px 0 0 8px;color:black;margin-bottom:-6px}
-#jsonpCloser{margin-top:0}
-#formattedJson{padding-left:28px;padding-top:6px}pre{padding:36px 5px 5px 5px}
-.kvov{display:block;padding-left:20px;margin-left:-20px;position:relative;padding-top: 2px;}
-
-#jfContent {
-    margin-bottom: 25px;
-}
-#jfContent .kvov .s a {
-    color:#00b;text-decoration: underline;
-}
-#jfContent .kvov .s a:hover {
-    color:#b00;
-}
-.collapsed{white-space:nowrap}.collapsed>.blockInner{display:none}
-.collapsed>.ell:after{content:"\2026";font-weight:bold}
-.collapsed>.ell{margin:0 4px;color:#888}
-.collapsed .kvov{display:inline}
-.e{width:20px;height:18px;display:block;position:absolute;left:-2px;top:4px;z-index:5;
-    opacity:0.35;-webkit-user-select: none;
-    cursor: pointer;}
-.e:after {
-    content: "\25bc";
-}
-.collapsed>.e{-webkit-transform:rotate(-90deg);top:-1px}
-.e:hover{opacity:0.35}.e:active{opacity:0.5}.collapsed .kvov .e{display:none}
-.blockInner{display:block;padding-left:24px;border-left:1px dotted #bbb;margin-left:2px}
-#formattedJson,#jsonpOpener,#jsonpCloser{color:#333;font:13px/18px monospace}
-#formattedJson{color:#444}.b{font-weight:bold}.s{color:#0B7500;word-wrap:break-word}
-#jfContent a:link,#jfContent a:visited{text-decoration:none;color:inherit}
-#jfContent a:hover,#jfContent a:active{text-decoration:underline;color:#050}
-.bl,.nl,.n{font-weight:bold;color:#1A01CC}.k{color:black}
-#formattingMsg {
-    display: none;
-}
-.mod-contentscript #formattingMsg {
-    position: absolute;
-    top: 0;
-}
-#formattingMsg .x-loading{
-    width: 12px;
-    height: 12px;
-    border: 1px solid #f00;
-    border-radius: 50%;
-    box-shadow: 0 0 10px 2px;
-    color: #cc0000;
-    border-right-color: transparent;
-    border-top-color: transparent;
-    animation: spin-right 1s linear infinite normal;
-    animation-delay: 0s;
-    margin: 0 5px 0 0;
-    display: inline-block;
-}
-#formattingMsg .x-loading:before {
-    display: block;
-    width: 8px;
-    height: 8px;
-    margin: 1px;
-    border: 2px solid #f00;
-    content: " ";
-    border-radius: 50%;
-    border-left-color: transparent;
-    border-bottom-color: transparent;
-}
-@keyframes spin-right {
-    from {
-        transform: rotate(0deg);
-        opacity: 0.2;
-    }
-    50% {
-        transform: rotate(180deg);
-        opacity: 1.0;
-    }
-    to {
-        transform: rotate(360deg);
-        opacity: 0.2;
-    }
-}
-
-[hidden]{display:none !important}
-#jfContentspan{white-space:pre-wrap}
-@-webkit-keyframes spin{from{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(360deg)}}
-#spinner{-webkit-animation:spin 1s 0 infinite}*{-webkit-font-smoothing:antialiased}
-#jfContent .x-json-tips {
-    color:red;
-}
-#jfContent_pre {padding:0;margin:0;
-    word-break:break-word;
-}
-
-html {
-    font-size: 14px;
-    color:#333;
-    direction: ltr;
-    padding-right: 10px;
-}
-html body {
-    direction: inherit;
-}
-.mod-json .format-item button{
-    width:80px;
-    height:30px;
-    float: right;
-}
-.mod-json .rst-item {
-    position: relative;
-    padding-top:30px;
-}
-.mod-contentscript {
-    width: auto;
-}
-#formatTips {
-    color: #888;
-    font-size: 14px;
-    display: block;
-    position: absolute;
-    top: 0px;
-    left: 0px;
-}
-#jsonSource {
-    height: 120px;
-}
-.mod-json .callback-name {
-    font-weight: bolder;
-    color: #a00;
-}
-
-#jfContent .x-hover {
-    outline:1px solid #cdc;
-    background: #fff;
-}
-#jfContent .x-outline {
-    outline:1px solid #8ac;
-    box-shadow: rgba(100, 100, 100, 0.4) -3px 3px 5px;
-    font-weight: bold;
-    background-color: #fffff8;
-}
-#errorMsg {
-    margin-top: 10px;
-    float: left;
-    color: #f00;
-}
-#boxOpt {
-    position: absolute;
-    z-index: 1024;
-}
-#boxOpt a {
-    cursor: pointer;
-    margin: 0 5px;
-    font-size: 12px;
-    color: #00f;
-}
-#boxOpt a:hover {
-    color:#f00;
-}
-
-.jf-sort {
-    position: absolute;
-    top: 6px;
-    right: 250px;
-    font-size: 12px;
-    color: #615b5b;
-}
-.jf-sort .x-stitle {
-    font-weight: bold;
-}
-.jf-sort input {
-    margin-right: 10px;
-}

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels