├── .gitignore ├── less ├── Show.less ├── About.less ├── JsonFormat.less ├── index.less ├── TxtDiff.less └── sections.css ├── img ├── logo128x128.ico ├── logo128x128.icns └── logoAlien16x16.png ├── app ├── assets │ ├── img │ │ ├── logoAlien.png │ │ ├── logo128x128.icns │ │ ├── logo128x128.ico │ │ ├── logoAlien16x16.png │ │ ├── fontawesome-webfont-674f50d287a8c48dc19ba404d20fe713.eot │ │ ├── fontawesome-webfont-b06871f281fee6b241d60582ae9369b9.ttf │ │ ├── fontawesome-webfont-fee66e712a8a08eef5805a46892932ad.woff │ │ ├── fontawesome-webfont-af7ae505a9eed503f8b8e6982036873e.woff2 │ │ └── jsoneditor-icons-bfab7b16cb24ac5e2856e2b172f47fe8.svg │ └── css │ │ └── tools.min.css ├── package.json ├── js │ ├── ext │ │ └── Link.js │ ├── tray │ │ └── Tray.js │ └── menu │ │ └── Menu.js ├── main.js └── index.html ├── sections ├── JsonFormat.html ├── About.html ├── svg │ ├── IconHistory.svg │ ├── TxtDiffSvg.svg │ ├── LoadingSvg.svg │ ├── AboutSvg.svg │ ├── SwitchSvg.svg │ └── JsonFormatSvg.svg └── TxtDiff.html ├── README.md ├── js ├── ext │ └── Link.js ├── index.js ├── JsonFormat.js ├── TxtDiff.js └── Tools.js ├── package.json ├── index.html ├── LICENSE ├── webpack.config.js ├── 3rd └── jsdifflib │ ├── diffview.css │ ├── diffview.js │ └── difflib.js └── gulpfile.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .DS_Store? 4 | dist 5 | -------------------------------------------------------------------------------- /less/Show.less: -------------------------------------------------------------------------------- 1 | #show { 2 | #what { 3 | height: 100%; 4 | } 5 | } -------------------------------------------------------------------------------- /img/logo128x128.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizuyan/gram-tools/HEAD/img/logo128x128.ico -------------------------------------------------------------------------------- /img/logo128x128.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizuyan/gram-tools/HEAD/img/logo128x128.icns -------------------------------------------------------------------------------- /img/logoAlien16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizuyan/gram-tools/HEAD/img/logoAlien16x16.png -------------------------------------------------------------------------------- /app/assets/img/logoAlien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizuyan/gram-tools/HEAD/app/assets/img/logoAlien.png -------------------------------------------------------------------------------- /app/assets/img/logo128x128.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizuyan/gram-tools/HEAD/app/assets/img/logo128x128.icns -------------------------------------------------------------------------------- /app/assets/img/logo128x128.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizuyan/gram-tools/HEAD/app/assets/img/logo128x128.ico -------------------------------------------------------------------------------- /app/assets/img/logoAlien16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizuyan/gram-tools/HEAD/app/assets/img/logoAlien16x16.png -------------------------------------------------------------------------------- /sections/JsonFormat.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | -------------------------------------------------------------------------------- /app/assets/img/fontawesome-webfont-674f50d287a8c48dc19ba404d20fe713.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizuyan/gram-tools/HEAD/app/assets/img/fontawesome-webfont-674f50d287a8c48dc19ba404d20fe713.eot -------------------------------------------------------------------------------- /app/assets/img/fontawesome-webfont-b06871f281fee6b241d60582ae9369b9.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizuyan/gram-tools/HEAD/app/assets/img/fontawesome-webfont-b06871f281fee6b241d60582ae9369b9.ttf -------------------------------------------------------------------------------- /app/assets/img/fontawesome-webfont-fee66e712a8a08eef5805a46892932ad.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizuyan/gram-tools/HEAD/app/assets/img/fontawesome-webfont-fee66e712a8a08eef5805a46892932ad.woff -------------------------------------------------------------------------------- /app/assets/img/fontawesome-webfont-af7ae505a9eed503f8b8e6982036873e.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizuyan/gram-tools/HEAD/app/assets/img/fontawesome-webfont-af7ae505a9eed503f8b8e6982036873e.woff2 -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tools", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC" 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gram-tools 2 | 3 | > 依赖于electron,跨平台 4 | 5 | > 收集整理各种常用工具 6 | 7 | > GramTools是一个工具箱的集锦,志在提高工作效率。缩写刚好是GT,就是用了外星人GT作为图标,欢迎大家一起贡献 8 | 9 | ![图片1](https://ritoyantools.github.io/images/capture.png) 10 | 11 | ![图片2](https://ritoyantools.github.io/images/feature_1.png) 12 | 13 | 14 | -------------------------------------------------------------------------------- /less/About.less: -------------------------------------------------------------------------------- 1 | #about{ 2 | .container { 3 | .logo { 4 | text-align: center; 5 | margin: 20px 0; 6 | } 7 | .name { 8 | text-align: center; 9 | font-size: 20px; 10 | } 11 | .version { 12 | text-align: center; 13 | color: #C1C1C1; 14 | font-size: 18px; 15 | } 16 | .about-me { 17 | font-size: 20px; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /app/js/ext/Link.js: -------------------------------------------------------------------------------- 1 | const shell = require('electron').shell 2 | 3 | const links = document.querySelectorAll('a[href]') 4 | 5 | function InitLink() { 6 | Array.prototype.forEach.call(links, (link) => { 7 | const url = link.getAttribute('href') 8 | if (url.indexOf('http') === 0) { 9 | link.addEventListener('click', (e) => { 10 | e.preventDefault() 11 | shell.openExternal(url) 12 | }) 13 | } 14 | }) 15 | } 16 | 17 | InitLink(); -------------------------------------------------------------------------------- /js/ext/Link.js: -------------------------------------------------------------------------------- 1 | const {shell} = require("electron"); 2 | 3 | const links = document.querySelectorAll('a[href]') 4 | 5 | function InitLink() { 6 | Array.prototype.forEach.call(links, (link) => { 7 | const url = link.getAttribute('href') 8 | if (url.indexOf('http') === 0) { 9 | link.addEventListener('click', (e) => { 10 | e.preventDefault() 11 | shell.openExternal(url) 12 | }) 13 | } 14 | }) 15 | } 16 | 17 | $(function(){ 18 | InitLink(); 19 | }); -------------------------------------------------------------------------------- /sections/About.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 6 |
7 | Gram Tools 8 |
9 |
10 | V1.0.0 11 |
12 |
13 | 18 |
19 |
20 |
21 | -------------------------------------------------------------------------------- /sections/svg/IconHistory.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sections/svg/TxtDiffSvg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gram-tools", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "bootstrap": "^4.0.0", 14 | "css-loader": "^0.28.9", 15 | "diff": "^3.4.0", 16 | "electron": "^1.8.2", 17 | "file-loader": "^1.1.6", 18 | "gulp": "^3.9.1", 19 | "gulp-clean-css": "^3.9.2", 20 | "gulp-concat": "^2.6.1", 21 | "gulp-htmlmin": "^4.0.0", 22 | "gulp-less": "^3.4.0", 23 | "gulp-minify-css": "^1.2.4", 24 | "gulp-rename": "^1.2.2", 25 | "gulp-replace": "^0.6.1", 26 | "jquery": "^3.3.1", 27 | "jsoneditor": "^5.13.2", 28 | "layui-layer": "^1.0.9", 29 | "popper.js": "^1.12.9", 30 | "style-loader": "^0.20.1", 31 | "uglifyjs-webpack-plugin": "^1.2.2", 32 | "url-loader": "^0.6.2", 33 | "velocity-animate": "^1.5.1", 34 | "webpack": "^3.10.0" 35 | }, 36 | "dependencies": { 37 | "antd": "^3.2.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 工具箱 5 | 6 | 7 | 8 |
9 |
10 | {LoadingSvg} 11 |
12 |
13 |
14 |
15 |
16 |
文本工具
17 |
18 |
19 |
20 | {JsonFormatSvg} 21 | json转换 22 |
23 |
24 | {TxtDiffSvg} 25 | 文本DIFF 26 |
27 |
28 |
About
29 |
30 |
31 |
32 | {AboutSvg} 33 | 关于 34 |
35 |
36 |
37 | {JsonFormat} 38 | {TxtDiff} 39 | {About} 40 |
41 |
42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 一颗小白菜 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | webpack = require('webpack'), 3 | uglify = require('uglifyjs-webpack-plugin'); 4 | /** 5 | * 常用变量 6 | */ 7 | const projectPath = __dirname; 8 | const appPath = path.join(projectPath, "app"); 9 | const assetsPath = path.join(appPath, "assets"); 10 | 11 | module.exports = { 12 | entry: path.join(projectPath, "js", "index.js"), 13 | output: { 14 | path: assetsPath, 15 | filename: "js/tools.js" 16 | }, 17 | 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.css$/, 22 | use: ['style-loader', 'css-loader'] 23 | }, { 24 | test: /\.(eot|woff|woff2|ttf|svg|png|jpg)$/, 25 | use: 'url-loader?limit=8000&name=[name]-[hash].[ext]&outputPath=img/&publicPath=assets/' 26 | }, 27 | ] 28 | }, 29 | plugins: [ 30 | // 打包内容里面全局jquery 31 | new webpack.ProvidePlugin({ 32 | "$": "jquery", 33 | "jQuery": "jquery", 34 | "window.jQuery": 'jquery' 35 | }), 36 | // 压缩 37 | new uglify() 38 | ] 39 | }; -------------------------------------------------------------------------------- /app/js/tray/Tray.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var path = require('path'); 4 | const {Menu, Tray} = require('electron') 5 | 6 | function InitTray(app, win) { 7 | var trayIcon = null; 8 | 9 | if (process.platform === 'darwin') { 10 | trayIcon = new Tray(path.join(__dirname, "../..", 'assets/img/logoAlien16x16.png')); 11 | } 12 | else { 13 | trayIcon = new Tray(path.join(__dirname, "../..", 'assets/img/logoAlien16x16.png')); 14 | } 15 | trayIcon.setToolTip("GramTools") 16 | 17 | var trayMenuTemplate = [ 18 | { 19 | label: "Gram Tools", 20 | click: () => { 21 | app.emit('show') 22 | } 23 | }, 24 | { 25 | type: "separator" 26 | }, 27 | { 28 | label: "退出", 29 | accelerator: 'CommandOrControl+Q', 30 | click: () => { 31 | app.quit() 32 | } 33 | } 34 | ]; 35 | var trayMenu = Menu.buildFromTemplate(trayMenuTemplate); 36 | trayIcon.setContextMenu(trayMenu); 37 | 38 | trayIcon.on('click', () => { 39 | if (process.platform === 'win32') { 40 | app.emit('show') 41 | } 42 | }); 43 | }; 44 | 45 | exports.InitTray = InitTray; -------------------------------------------------------------------------------- /less/JsonFormat.less: -------------------------------------------------------------------------------- 1 | #json-format { 2 | #json-format-container { 3 | height: 100%; 4 | background-color: #FFF; 5 | .jsoneditor { 6 | &.jsoneditor-mode-tree { 7 | div.jsoneditor-readonly, div.jsoneditor-field, div.jsoneditor-value, div.jsoneditor td, div.jsoneditor th, div.jsoneditor textarea, .jsoneditor-schema-error { 8 | font-size: 15px !important; 9 | line-height: 1.2em !important; 10 | } 11 | } 12 | .ace_editor.ace-jsoneditor { 13 | font-size: 15px !important; 14 | } 15 | .jsoneditor-menu { 16 | button[type=button] { 17 | cursor: pointer; 18 | outline: none; 19 | } 20 | .jsoneditor-menu { 21 | button[type=button].jsoneditor-selected { 22 | background-color: #3883fa; 23 | } 24 | } 25 | .jsoneditor-history { 26 | background-image: url("../sections/IconHistory.svg"); 27 | } 28 | ul.jsoneditor-menu { 29 | li { 30 | button { 31 | &.jsoneditor-type-modes { 32 | div.jsoneditor-text { 33 | padding-top: 10px; 34 | padding-bottom: 10px; 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | .jsoneditor-navigation-bar { 42 | div.jsoneditor-contextmenu ul li button.jsoneditor-selected, div.jsoneditor-contextmenu ul li button.jsoneditor-selected:hover, div.jsoneditor-contextmenu ul li button.jsoneditor-selected:focus { 43 | background-color: #3883fa; 44 | } 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /sections/TxtDiff.html: -------------------------------------------------------------------------------- 1 |
2 | 8 |
9 |
10 | 11 |
12 |
13 | 16 | 19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 27 |
28 |
29 |
30 |
31 |
32 |
33 | 34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | 48 | -------------------------------------------------------------------------------- /sections/svg/LoadingSvg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/js/menu/Menu.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const {Menu, Tray} = require('electron'); 4 | const os = process.platform 5 | 6 | function InitMenu(app, win) { 7 | let template = [ 8 | { 9 | label: "编辑", 10 | submenu: [ 11 | { 12 | label: "撤销", 13 | role: "undo" 14 | }, 15 | { 16 | label: "重做", 17 | role: "redo" 18 | }, 19 | { 20 | type: "separator" 21 | }, 22 | { 23 | label: "剪切", 24 | role: "cut" 25 | }, 26 | { 27 | label: "复制", 28 | role: "copy" 29 | }, 30 | { 31 | label: "粘贴", 32 | role: "paste" 33 | }, 34 | { 35 | label: "删除", 36 | role: "delete" 37 | }, 38 | { 39 | label: "全选", 40 | role: "selectall" 41 | } 42 | ] 43 | } 44 | ]; 45 | 46 | if (os === 'darwin') { 47 | template.unshift({ 48 | label: "GramTools", 49 | submenu: [ 50 | { label: "关于 GramTools"}, 51 | { type: "separator" }, 52 | { 53 | label: "退出", 54 | accelerator: 'CommandOrControl+Q', 55 | click: () => { 56 | app.quit() 57 | } 58 | } 59 | ] 60 | }); 61 | } 62 | 63 | 64 | //注册菜单 65 | Menu.setApplicationMenu(Menu.buildFromTemplate(template)); 66 | } 67 | exports.InitMenu = InitMenu; -------------------------------------------------------------------------------- /sections/svg/AboutSvg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sections/svg/SwitchSvg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /js/index.js: -------------------------------------------------------------------------------- 1 | import "velocity-animate"; 2 | import "../node_modules/velocity-animate/velocity.ui.js"; 3 | //import "bootstrap"; 4 | import "bootstrap"; 5 | import "../node_modules/bootstrap/dist/css/bootstrap.css"; 6 | 7 | // import layer 8 | import "../node_modules/layui-layer/dist/layer.js"; 9 | 10 | import JsonFromat from "./JsonFormat.js"; 11 | import TxtDiff from "./TxtDiff.js"; 12 | 13 | 14 | var pageObj = { 15 | system: {}, // app系统级别的变量 16 | sectionIndex: {}, // 各个section的index 17 | nowSectionIndex: 1, // 当前的section索引 18 | sectionCache: {}, // 各个section内容的缓存 19 | }; 20 | 21 | pageObj.initLoading = function() { 22 | $("#loading").velocity("transition.whirlOut", { duration: 650 }); 23 | $("#loading").remove(); 24 | }; 25 | 26 | pageObj.initMenu = function() { 27 | let zIndexStart = 1000; 28 | $("#index>.right>.section:eq(0)").css("z-index", zIndexStart++); 29 | $("#index>.right>.section:gt(0)").css("top", "-100%"); 30 | $("#index>.left").on("click", ".item", function(e){ 31 | let me = $(this); 32 | let index = $("#index>.left>.item").index(this); 33 | let lastIndex = $("#index>.left>.item").index($("#index>.left>.item.active")); 34 | // 判断是否当前元素 35 | if (index == lastIndex) { 36 | return true; 37 | } 38 | let topGap = lastIndex > index ? "-100%" : "100%"; 39 | let lastTopGap = lastIndex > index ? "100%" : "-100%"; 40 | 41 | let section = $("#index>.right>.section:eq("+index+")"); 42 | let lastSection = $("#index>.right>.section:eq("+lastIndex+")"); 43 | me.siblings(".item").removeClass("active"); 44 | section.css({ 45 | "top": topGap, 46 | "z-index": zIndexStart++ 47 | }); 48 | setTimeout(function (argument) { 49 | lastSection.velocity( 50 | { 51 | top: lastTopGap 52 | }, 53 | { 54 | duration: 300, 55 | easing: "ease-in" 56 | } 57 | ); 58 | section.velocity( 59 | { 60 | top: 0 61 | }, 62 | { 63 | duration: 300, 64 | queue: false, 65 | easing: "ease-in" 66 | } 67 | ); 68 | me.addClass("active"); 69 | }, 100); 70 | }); 71 | }; 72 | 73 | pageObj.init = function(){ 74 | // 初始化各个模块 75 | $(function() { 76 | pageObj.initLoading(); 77 | pageObj.initMenu(); 78 | JsonFromat(); 79 | TxtDiff(); 80 | }); 81 | }; 82 | pageObj.init(); 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /less/index.less: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | html { 6 | height: 100%; 7 | body { 8 | height: 100%; 9 | } 10 | font-size: 10px; 11 | } 12 | #loading { 13 | position: fixed; 14 | width: 100%; 15 | height: 100%; 16 | top: 0; 17 | left: 0; 18 | z-index: 10000; 19 | background: black; 20 | opacity: .8; 21 | >.loading-svg { 22 | text-align: center; 23 | top: 50%; 24 | position: relative; 25 | margin: 0 auto; 26 | } 27 | } 28 | #index { 29 | width: 100%; 30 | height: 100%; 31 | box-sizing: border-box; 32 | -webkit-box-sizing: border-box; 33 | overflow: hidden; 34 | display: flex; 35 | >.left{ 36 | -webkit-touch-callout: none; 37 | -webkit-user-select: none; 38 | touch-callout: none; 39 | user-select: none; 40 | width: 20%; 41 | max-width: 200px; 42 | min-width: 150px; 43 | height: 100%; 44 | box-sizing: border-box; 45 | -webkit-box-sizing: border-box; 46 | background-color: rgb(34, 32, 39); 47 | font-size: 2rem; 48 | overflow-y: auto; 49 | >.item{ 50 | font-size: 1.6rem; 51 | box-sizing: border-box; 52 | -webkit-box-sizing: border-box; 53 | padding: 7px 20px 10px; 54 | color: rgb(255, 255, 255); 55 | >.icon { 56 | margin-right: 5px; 57 | } 58 | &.active { 59 | background-color: rgb(37, 135, 247); 60 | } 61 | cursor: pointer; 62 | } 63 | >.division{ 64 | display: flex; 65 | box-sizing: border-box; 66 | -webkit-box-sizing: border-box; 67 | color: rgb(143, 143, 143); 68 | font-size: 1.6rem; 69 | padding: 5px 10px; 70 | position: relative; 71 | >.txt { 72 | } 73 | >.line{ 74 | flex:1; 75 | position: relative; 76 | margin-left: 1rem; 77 | &:after { 78 | content: ""; 79 | display: block; 80 | width: 100%; 81 | background-color: rgb(77, 77, 77); 82 | height: 0.1rem; 83 | position: absolute; 84 | top: 1rem; 85 | } 86 | } 87 | } 88 | } 89 | >.right { 90 | flex:1; 91 | height: 100%; 92 | box-sizing: border-box; 93 | -webkit-box-sizing: border-box; 94 | position: relative; 95 | >.section { 96 | width: 100%; 97 | height: 100%; 98 | overflow-y: scroll; 99 | position: absolute; 100 | left: 0; 101 | right: 0; 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /3rd/jsdifflib/diffview.css: -------------------------------------------------------------------------------- 1 | /* 2 | This is part of jsdifflib v1.0. 3 | 4 | Copyright 2007 - 2011 Chas Emerick . All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, are 7 | permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this list of 10 | conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, this list 13 | of conditions and the following disclaimer in the documentation and/or other materials 14 | provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY Chas Emerick ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 18 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Chas Emerick OR 19 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 24 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | The views and conclusions contained in the software and documentation are those of the 27 | authors and should not be interpreted as representing official policies, either expressed 28 | or implied, of Chas Emerick. 29 | */ 30 | table.diff { 31 | border-collapse:collapse; 32 | border:1px solid darkgray; 33 | white-space:pre-wrap 34 | } 35 | table.diff tbody { 36 | font-family:Courier, monospace 37 | } 38 | table.diff tbody th { 39 | font-family:verdana,arial,'Bitstream Vera Sans',helvetica,sans-serif; 40 | background:#EED; 41 | font-size:11px; 42 | font-weight:normal; 43 | border:1px solid #BBC; 44 | color:#886; 45 | padding:.3em .5em .1em 2em; 46 | text-align:right; 47 | vertical-align:top 48 | } 49 | table.diff thead { 50 | border-bottom:1px solid #BBC; 51 | background:#EFEFEF; 52 | font-family:Verdana 53 | } 54 | table.diff thead th.texttitle { 55 | text-align:left 56 | } 57 | table.diff tbody td { 58 | padding:0px .4em; 59 | padding-top:.4em; 60 | vertical-align:top; 61 | } 62 | table.diff .empty { 63 | background-color:#DDD; 64 | } 65 | table.diff .replace { 66 | background-color:#FD8 67 | } 68 | table.diff .delete { 69 | background-color:#E99; 70 | } 71 | table.diff .skip { 72 | background-color:#EFEFEF; 73 | border:1px solid #AAA; 74 | border-right:1px solid #BBC; 75 | } 76 | table.diff .insert { 77 | background-color:#9E9 78 | } 79 | table.diff th.author { 80 | text-align:right; 81 | border-top:1px solid #BBC; 82 | background:#EFEFEF 83 | } -------------------------------------------------------------------------------- /js/JsonFormat.js: -------------------------------------------------------------------------------- 1 | import JSONEditor from "JSONEditor"; 2 | import "../node_modules/jsoneditor/dist/jsoneditor.css"; 3 | import utilMine from "../node_modules/jsoneditor/src/js/util"; 4 | JSONEditor.prototype.setMode = function (mode) { 5 | var container = this.container; 6 | var options = utilMine.extend({}, this.options); 7 | var oldMode = options.mode; 8 | var data; 9 | var name; 10 | 11 | options.mode = mode; 12 | var config = JSONEditor.modes[mode]; 13 | if (config) { 14 | try { 15 | var asText = (config.data == 'text'); 16 | name = this.getName(); 17 | data = this[asText ? 'getText' : 'get'](); // get text or json 18 | 19 | this.destroy(); 20 | utilMine.clear(this); 21 | utilMine.extend(this, config.mixin); 22 | this.create(container, options); 23 | 24 | this.setName(name); 25 | this[asText ? 'setText' : 'set'](data); // set text or json 26 | 27 | if (typeof config.load === 'function') { 28 | try { 29 | config.load.call(this); 30 | } 31 | catch (err) { 32 | console.error(err); 33 | } 34 | } 35 | 36 | if (typeof options.onModeChange === 'function') { 37 | try { 38 | options.onModeChange(mode, oldMode); 39 | } 40 | catch (err) { 41 | console.error(err); 42 | } 43 | } 44 | } 45 | catch (err) { 46 | this._onError(err); 47 | } 48 | } 49 | else { 50 | throw new Error('Unknown mode "' + options.mode + '"'); 51 | } 52 | }; 53 | 54 | export default () => { 55 | let container, options, json, 56 | nowFontSize = 13, 57 | containerId = "json-format-container"; 58 | 59 | function changeEditorFontSize(fontSize, duration) { 60 | duration = typeof(duration) != "undefined" ? duration : 0; 61 | $("#json-format #json-format-container .jsoneditor .ace_editor.ace-jsoneditor").velocity({ 62 | "font-size": fontSize 63 | }, { 64 | duration: duration, 65 | }); 66 | } 67 | 68 | function formatMenuInfo() { 69 | $("button[type=button].jsoneditor-repair").remove(); 70 | $("a.jsoneditor-poweredBy").remove(); 71 | 72 | $("button.jsoneditor-type-modes.jsoneditor-selected").attr("disabled", "disabled"); 73 | 74 | // 增加历史记录按钮 75 | //$(".jsoneditor-menu > .jsoneditor-modes").after(''); 76 | }; 77 | 78 | container = document.getElementById(containerId); 79 | 80 | options = { 81 | mode: 'code', 82 | modes: ['code', 'tree'], // allowed modes 83 | onError: function (err) { 84 | var error = err.toString(); 85 | layer.msg( 86 | "出现错误:" + error, { 87 | time: 2000, //2s后自动关闭 88 | }); 89 | }, 90 | onModeChange: function(newMode, oldMode) { 91 | formatMenuInfo(); 92 | } 93 | }; 94 | 95 | json = { 96 | "weatherinfo": { 97 | "city": "北京", 98 | "city_en": "beijing", 99 | "date_y": "2013年9月24日", 100 | "index": "较冷", 101 | "index_d": "建议着大衣、呢外套加毛衣、卫衣等服装。体弱者宜着厚外套、厚毛衣。因昼夜温差较大,注意增减衣服。", 102 | "index48": "较舒适", 103 | "index_co": "舒", 104 | "st1": "21", 105 | "index_ag": "极易发" 106 | } 107 | }; 108 | var editor = new JSONEditor(container, options, json); 109 | 110 | 111 | 112 | (function() { 113 | formatMenuInfo(); 114 | })(); 115 | }; -------------------------------------------------------------------------------- /less/TxtDiff.less: -------------------------------------------------------------------------------- 1 | #txt-diff { 2 | .sm-alert { 3 | padding: 1rem; 4 | padding-right: 6rem; 5 | font-size: 1.4rem; 6 | .sm-colse { 7 | padding: 1rem; 8 | font-size: 2.2rem; 9 | outline: none; 10 | } 11 | } 12 | .diff-condtion { 13 | margin: 1.6rem 1.6rem 0; 14 | font-size: 1.6rem; 15 | label { 16 | &.btn { 17 | font-size: 1.4rem; 18 | } 19 | } 20 | input[type=radio][name=diffLevel] { 21 | cursor: pointer; 22 | } 23 | .do-diff { 24 | button[type=button] { 25 | outline: none; 26 | &:focus { 27 | outline: none; 28 | box-shadow: none; 29 | } 30 | } 31 | display: inline-block; 32 | margin-left: 2rem; 33 | } 34 | .switch-icon { 35 | 36 | } 37 | } 38 | .txt-wrap { 39 | margin: 0 0; 40 | textarea { 41 | &.code-diff-left, &.code-diff-right { 42 | font-size: 1.4rem; 43 | } 44 | } 45 | } 46 | .txt-diff-result { 47 | margin: 0 1rem 1rem; 48 | border: 1px solid #F0F0F0; 49 | font-size: 2rem; 50 | .level-char { 51 | div { 52 | display: inline-block; 53 | } 54 | background-color: #FFF; 55 | padding-left: 2em; 56 | position: relative; 57 | &:hover { 58 | .char-header { 59 | .char-num { 60 | background-color: #DED7FC; 61 | border-color: #CABFFA; 62 | } 63 | } 64 | } 65 | .char-info { 66 | display: block; 67 | word-wrap: break-word; 68 | word-break: break-all; 69 | line-height: 3rem; 70 | font-size: 2rem; 71 | padding-left: 2em; 72 | height: 3rem; 73 | &:has(.changed-delete) { 74 | color: red; 75 | } 76 | &.changed { 77 | background-color: #fafbfc; 78 | } 79 | &.changed-delete { 80 | background-color: #ffeef0; 81 | } 82 | &.changed-add { 83 | background-color: #e6ffed; 84 | } 85 | .char-add { 86 | background-color: #acf2bd; 87 | } 88 | .char-delete { 89 | background-color: #fdb8c0; 90 | } 91 | } 92 | .char-header { 93 | margin-left: -2em; 94 | position: absolute; 95 | height: 100%; 96 | .char-num { 97 | display: inline-block; 98 | width: 4rem; 99 | height: 100%; 100 | background-color: #fafafa; 101 | border-right: 1px solid; 102 | border-color: #f0f0f0; 103 | text-align: right; 104 | padding: 0 .2em 0 0; 105 | vertical-align: top; 106 | color: rgba(0,0,0,0.3); 107 | font-size: .8em; 108 | } 109 | .char-flag { 110 | width: 2em; 111 | height: 100%; 112 | text-align: center; 113 | color: #9bb0a1 114 | } 115 | } 116 | } 117 | .level-line { 118 | background-color: #FFF; 119 | padding-left: 5em; 120 | position: relative; 121 | &:hover { 122 | .line-header { 123 | .line-num { 124 | background-color: #DED7FC; 125 | border-color: #CABFFA; 126 | } 127 | } 128 | } 129 | div { 130 | display: inline-block; 131 | } 132 | .line-info { 133 | word-wrap: break-word; 134 | word-break: break-all; 135 | line-height: 3rem; 136 | font-size: 2rem; 137 | padding-left: 1rem; 138 | } 139 | .line-header { 140 | margin-left: -5em; 141 | position: absolute; 142 | height: 100%; 143 | .line-num { 144 | display: inline-block; 145 | width: 4rem; 146 | height: 100%; 147 | background-color: #fafafa; 148 | border-right: 1px solid; 149 | border-color: #f0f0f0; 150 | text-align: right; 151 | padding: 0 .2em 0 0; 152 | vertical-align: top; 153 | color: rgba(0,0,0,0.3); 154 | font-size: .8em; 155 | } 156 | .line-flag { 157 | width: 1em; 158 | height: 100%; 159 | text-align: center; 160 | color: #9bb0a1 161 | } 162 | } 163 | &.line-add { 164 | background-color: #ecfdf0; 165 | .line-num { 166 | background-color: #ddfbe6; 167 | border-color: #c7f0d2; 168 | } 169 | } 170 | &.line-delete { 171 | background-color: #fbe9eb; 172 | .line-num { 173 | background-color: #f9d7dc; 174 | border-color: #fac5cd; 175 | } 176 | } 177 | } 178 | } 179 | } -------------------------------------------------------------------------------- /app/main.js: -------------------------------------------------------------------------------- 1 | const {app, BrowserWindow} = require('electron') 2 | const path = require('path') 3 | const url = require('url') 4 | const electron = require('electron'); 5 | 6 | const Tray = require("./js/tray/Tray"); // 托盘图标 7 | const Menu = require("./js/menu/Menu"); // 复制粘贴 8 | 9 | 10 | let win = null; 11 | let contents = null; 12 | let isTrayInit = false; 13 | let willQuitApp = false; 14 | 15 | // 判断是不是第二个实例,第二个实例执行到这里显示之前的窗口 16 | const isSecondInstance = app.makeSingleInstance((commandLine, workingDirectory) => { 17 | if (win) { 18 | if (win.isMinimized()) win.restore() 19 | win.focus() 20 | } 21 | }) 22 | if (isSecondInstance) { 23 | app.quit() 24 | } 25 | 26 | // 创建窗口 27 | function createWin () { 28 | // 创建主窗口 29 | win = new BrowserWindow({ 30 | width: 960, 31 | height: 600, 32 | minWidth: 768, 33 | minHeight: 480, 34 | fullscreenable: true, 35 | autoHideMenuBar: true 36 | }) 37 | 38 | contents = win.webContents; 39 | 40 | // 内容加在完毕之后,设置托盘图标 41 | contents.on('did-finish-load', () => { 42 | Menu.InitMenu(app, win); 43 | if (!isTrayInit) { 44 | Tray.InitTray(app, win); 45 | isTrayInit = true 46 | } 47 | }) 48 | 49 | // 加载app首页面 50 | win.loadURL(url.format({ 51 | pathname: path.join(__dirname, 'index.html'), 52 | protocol: 'file:', 53 | })) 54 | 55 | // 关闭的时候可能只是想最小化 56 | win.on('close', (e) => { 57 | console.log(); 58 | if (willQuitApp) { 59 | win = null; 60 | } else { 61 | if (process.platform == 'darwin') { 62 | if (win.isMaximized()) { 63 | win.setFullScreen(false); 64 | let times = 0; 65 | let timer = setInterval(function() { 66 | win.hide(); 67 | if (times++ > 6) { 68 | clearInterval(timer); 69 | } 70 | }, 200); 71 | } else { 72 | win.hide(); 73 | } 74 | } else { 75 | win.hide(); 76 | } 77 | e.preventDefault(); 78 | } 79 | }); 80 | 81 | // 窗口已经关闭的时候销毁 82 | win.on('closed', () => { 83 | win = null 84 | }) 85 | 86 | // Open the DevTools. 87 | //contents.openDevTools() 88 | } 89 | app.on('ready', function() { 90 | createWin(); 91 | }); 92 | 93 | /** 94 | * 添加一个展示时间,通过emit调用 95 | */ 96 | app.on('show', function () { 97 | if (win) { 98 | if (win.isMinimized()) { 99 | win.restore() 100 | } 101 | win.show() 102 | } else { 103 | createWin() 104 | } 105 | }) 106 | 107 | // mac下面点击关闭,最小化到docker中,再次点击恢复 108 | app.on('activate', function () { 109 | if (!win) { 110 | createWin() 111 | } else if (win.isMinimized()) { 112 | win.restore() 113 | } else { 114 | win.show() 115 | } 116 | }) 117 | 118 | // 真正的退出 119 | app.on('before-quit', () => willQuitApp = true) 120 | 121 | app.on('window-all-closed', function () { 122 | // On OS X it is common for applications and their menu bar 123 | // to stay active until the user quits explicitly with Cmd + Q 124 | if (process.platform !== 'darwin') { 125 | app.quit() 126 | } 127 | }) 128 | 129 | 130 | 131 | const ipc = require('electron').ipcMain 132 | const dialog = require('electron').dialog 133 | 134 | ipc.on('select-file-dialog', function (event) { 135 | dialog.showOpenDialog({ 136 | properties: ['openFile', 'multiSelections'] 137 | }, function (files) { 138 | if (files) event.sender.send('select-file-paths', files) 139 | }) 140 | }) 141 | 142 | ipc.on('select-single-file-dialog', function (event) { 143 | dialog.showOpenDialog({ 144 | properties: ['openFile'] 145 | }, function (files) { 146 | if (files) event.sender.send('select-file-path', files) 147 | }) 148 | }) 149 | 150 | ipc.on('save-file-dialog', function (event) { 151 | dialog.showSaveDialog({ 152 | "title": "保存合并文件位置", 153 | }, function (fullPath) { 154 | if (fullPath) event.sender.send('save-file-path', fullPath) 155 | }) 156 | }) 157 | 158 | ipc.on('get-app-path', function (event) { 159 | event.sender.send('got-app-path', app.getAppPath()) 160 | }) 161 | 162 | // In this file you can include the rest of your app's specific main process 163 | // code. You can also put them in separate files and require them here. 164 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | less = require('gulp-less'), 3 | cssMin = require('gulp-clean-css'), 4 | rename = require('gulp-rename'), 5 | concat = require('gulp-concat'), 6 | path = require('path'), 7 | replace = require("gulp-replace"), 8 | fs = require("fs"), 9 | htmlmin = require('gulp-htmlmin'), 10 | exec = require('child_process').exec; 11 | 12 | /** 13 | * 常用变量 14 | */ 15 | const projectPath = __dirname; 16 | const appPath = path.join(projectPath, "app"); 17 | const assetsPath = path.join(appPath, "assets"); 18 | 19 | gulp.task("makeCss", function() { 20 | gulp.src([ 21 | "less/**/*.less" 22 | ]) 23 | .pipe(less()) 24 | .pipe(concat("sections.css")) 25 | .pipe(gulp.dest(path.join(projectPath, "less"))); 26 | }); 27 | 28 | /** 29 | * 合并压缩所含有用到的css到一个文件 30 | */ 31 | gulp.task('handleCss', ["makeCss"], function () { 32 | gulp.src([ 33 | "./3rd/jsdifflib/diffview.css", 34 | path.join(projectPath, "less", "sections.css") 35 | ]) 36 | .pipe(concat("tools.css")) 37 | .pipe(cssMin()) 38 | .pipe(rename({suffix:'.min'})) 39 | .pipe(gulp.dest(path.join(assetsPath, "css"))); 40 | }); 41 | 42 | gulp.task("handleImg", function () { 43 | return gulp.src(["img/**/*.png", "img/**/*.icns", "img/**/*.ico"]) 44 | .pipe(gulp.dest(path.join(assetsPath, "img"))); 45 | }); 46 | 47 | 48 | /** 49 | * 处理html 50 | */ 51 | gulp.task("handleHtml", function() { 52 | let getHtmlSection = (sectionName) => { 53 | let html = fs.readFileSync( 54 | path.join(projectPath, "sections", sectionName + ".html"), 55 | "utf8" 56 | ); 57 | return html; 58 | }; 59 | let getSvgSection = (sectionName) => { 60 | let svg = fs.readFileSync( 61 | path.join(projectPath, "sections", "svg", sectionName + ".svg"), 62 | "utf8" 63 | ); 64 | return svg; 65 | }; 66 | gulp.src(["./index.html"]) 67 | .pipe(replace( 68 | "{JsonFormat}", getHtmlSection("JsonFormat") 69 | )) 70 | .pipe(replace( 71 | "{TxtDiff}", getHtmlSection("TxtDiff") 72 | )) 73 | .pipe(replace( 74 | "{About}", getHtmlSection("About") 75 | )) 76 | .pipe(replace( 77 | "{JsonFormatSvg}", getSvgSection("JsonFormatSvg") 78 | )) 79 | .pipe(replace( 80 | "{TxtDiffSvg}", getSvgSection("TxtDiffSvg") 81 | )) 82 | .pipe(replace( 83 | "{LoadingSvg}", getSvgSection("LoadingSvg") 84 | )) 85 | .pipe(replace( 86 | "{SwitchSvg}", getSvgSection("SwitchSvg") 87 | )) 88 | .pipe(replace( 89 | "{AboutSvg}", getSvgSection("AboutSvg") 90 | )) 91 | .pipe(htmlmin({collapseWhitespace: true})) 92 | .pipe(gulp.dest(appPath)); 93 | }); 94 | 95 | gulp.task('pack', () => { 96 | const ELECTRON_VERSION = "1.7.9"; 97 | const APP_VERSION = "1.0.0"; 98 | let pack = {} 99 | pack.macOS = `electron-packager ./app 'GramTools' --platform=darwin --arch=x64 --electron-version=${ELECTRON_VERSION} --overwrite --asar=true --prune --icon=app/assets/img/logo128x128.icns --out=dist --app-version=${APP_VERSION}` 100 | pack.win64 = `electron-packager ./app 'GramTools' --platform=win32 --arch=x64 --electron-version=${ELECTRON_VERSION} --overwrite --asar=true --prune --icon=app/assets/img/logo128x128.ico --out=dist --app-version=${APP_VERSION}` 101 | pack.win32 = `electron-packager ./app 'GramTools' --platform=win32 --arch=ia32 --electron-version=${ELECTRON_VERSION} --overwrite --asar=true --prune --icon=app/assets/img/logo128x128.ico --out=dist --app-version=${APP_VERSION}` 102 | pack.linux = `electron-packager ./app 'GramTools' --platform=linux --arch=x64 --electron-version=${ELECTRON_VERSION} --overwrite --asar=true --prune --icon=app/assets/img/logo128x128.ico --out=dist --app-version=${APP_VERSION}` 103 | 104 | let cmds = [] 105 | cmds = [pack.macOS, pack.win64, pack.win32, pack.linux] 106 | 107 | console.log(cmds.join('\n')) 108 | exec(cmds.join('\n'), (error, stdout, stderr) => { 109 | console.log('end pack.') 110 | if (error) { 111 | console.error(`exec error: ${error}`) 112 | } 113 | }) 114 | }) -------------------------------------------------------------------------------- /sections/svg/JsonFormatSvg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /js/TxtDiff.js: -------------------------------------------------------------------------------- 1 | var JsDiff = require("diff"); 2 | import Tools from "./Tools.js"; 3 | const tools = new Tools(); 4 | function handleLine(leftTxt, rightTxt, resultObj) { 5 | let diff = JsDiff.diffLines(leftTxt, rightTxt, { 6 | ignoreWhitespace: false 7 | }); 8 | let lineLeft = 0, lineRight = 0; 9 | let lines = []; 10 | let element = ""; 11 | let parts; 12 | console.log(diff); 13 | for (let i=0 ; i < diff.length; i++) { 14 | if (diff[i].value[diff[i].value.length - 1] == "\n") { 15 | diff[i].value = diff[i].value.substr(0, diff[i].value.length - 1); 16 | } 17 | } 18 | 19 | diff.forEach(function(part){ 20 | parts = part.value.split("\n"); 21 | if (part.added) { 22 | parts.forEach(function(item) { 23 | item = tools.htmlEntities(item); 24 | lineRight++; 25 | element = "
"+lineRight+"
+
"+item+"
"; 26 | lines.push(element); 27 | }); 28 | } else if (part.removed) { 29 | parts.forEach(function(item) { 30 | item = tools.htmlEntities(item); 31 | lineLeft++; 32 | element = "
"+lineLeft+"
-
"+item+"
"; 33 | lines.push(element); 34 | }); 35 | } else { 36 | parts.forEach(function(item) { 37 | item = tools.htmlEntities(item); 38 | lineLeft++; 39 | lineRight++; 40 | element = "
"+lineLeft+"
"+lineRight+"
"+item+"
"; 41 | lines.push(element); 42 | }); 43 | } 44 | }); 45 | let html = lines.join(""); 46 | $(resultObj).html(""); 47 | $(resultObj).append(html); 48 | } 49 | 50 | function handleChar(leftTxt, rightTxt, resultObj) { 51 | let diff = JsDiff.diffChars(leftTxt, rightTxt); 52 | console.log(diff); 53 | let lines = [], line = 0, partValue, charAtPos; 54 | let element = "", changed = 0, last; 55 | let isOnlyEnter = function(str) { 56 | let ret = true; 57 | for (let i=0; i" 85 | + "
" + line 86 | + "
" 87 | + "
{CHARFLAG}
" 88 | + "" 89 | + "
"; 90 | } 91 | 92 | if (charAtPos == "\n") { 93 | element += "
"; 94 | element = tools.formatString(element, { 95 | "CHANGED": (changed==1) ? "changed-add" : 96 | ((changed == 2) ? "changed-delete" : 97 | ((changed == 3) ? "changed" : "")), 98 | "CHARFLAG": (changed==1) ? "+" : 99 | ((changed == 2) ? "-" : 100 | ((changed == 3) ? "+-" : "")), 101 | }); 102 | changed = 0; 103 | lines.push(element); 104 | element = ""; 105 | continue; 106 | } 107 | 108 | charAtPos = tools.htmlEntities(charAtPos); 109 | if (part.added) { 110 | element += ""+charAtPos+""; 111 | } else if (part.removed) { 112 | element += ""+charAtPos+""; 113 | } else { 114 | element += ""+charAtPos+""; 115 | } 116 | } 117 | }); 118 | 119 | if (element) { 120 | element += ""; 121 | element = tools.formatString(element, { 122 | "CHANGED": (changed==1) ? "changed-add" : 123 | ((changed == 2) ? "changed-delete" : 124 | ((changed == 3) ? "changed" : "")), 125 | "CHARFLAG": (changed==1) ? "+" : 126 | ((changed == 2) ? "-" : 127 | ((changed == 3) ? "+-" : "")), 128 | }); 129 | changed = 0; 130 | lines.push(element); 131 | } 132 | let html = lines.join(""); 133 | $(resultObj).html(""); 134 | $(resultObj).append(html); 135 | }; 136 | 137 | export default () => { 138 | let leftTxt, rightTxt, diffLevel, txtDiffResult, handleFunc; 139 | handleFunc = function() { 140 | leftTxt = $("#code-diff-left").val(); 141 | rightTxt = $("#code-diff-right").val(); 142 | diffLevel = $("#txt-diff input[name=diffLevel]:checked").val(); 143 | txtDiffResult = $("#txt-diff .txt-diff-result"); 144 | 145 | switch (diffLevel) { 146 | case "char": 147 | handleChar(leftTxt, rightTxt, txtDiffResult); 148 | break; 149 | case "word": 150 | handleLine(leftTxt, rightTxt, txtDiffResult); 151 | break; 152 | case "line": 153 | handleLine(leftTxt, rightTxt, txtDiffResult); 154 | break; 155 | } 156 | }; 157 | // 初始化点击比较级别事件 158 | $("#txt-diff").on("click", ".diff-level-type", function() { 159 | let me = $(this); 160 | me.siblings(".diff-level-type") 161 | .removeClass("active") 162 | .find("input[name=diffLevel]") 163 | .removeAttr("checked"); 164 | me.addClass("active") 165 | .find("input[name=diffLevel]") 166 | .attr("checked", "checked"); 167 | handleFunc(); 168 | }); 169 | 170 | // 处理diff 171 | $("#txt-diff").on("change, keyup", "#code-diff-left, #code-diff-right", function() { 172 | handleFunc(); 173 | }); 174 | }; -------------------------------------------------------------------------------- /app/assets/css/tools.min.css: -------------------------------------------------------------------------------- 1 | table.diff{border-collapse:collapse;border:1px solid #a9a9a9;white-space:pre-wrap}table.diff tbody{font-family:Courier,monospace}table.diff tbody th{font-family:verdana,arial,'Bitstream Vera Sans',helvetica,sans-serif;background:#eed;font-size:11px;font-weight:400;border:1px solid #bbc;color:#886;padding:.3em .5em .1em 2em;text-align:right;vertical-align:top}table.diff thead{border-bottom:1px solid #bbc;background:#efefef;font-family:Verdana}table.diff thead th.texttitle{text-align:left}table.diff tbody td{padding:0 .4em;padding-top:.4em;vertical-align:top}table.diff .empty{background-color:#ddd}table.diff .replace{background-color:#fd8}table.diff .delete{background-color:#e99}table.diff .skip{background-color:#efefef;border:1px solid #aaa;border-right:1px solid #bbc}table.diff .insert{background-color:#9e9}table.diff th.author{text-align:right;border-top:1px solid #bbc;background:#efefef}#about .container .logo{text-align:center;margin:20px 0}#about .container .name{text-align:center;font-size:20px}#about .container .version{text-align:center;color:#c1c1c1;font-size:18px}#about .container .about-me{font-size:20px}#json-format #json-format-container{height:100%;background-color:#fff}#json-format #json-format-container .jsoneditor.jsoneditor-mode-tree .jsoneditor-schema-error,#json-format #json-format-container .jsoneditor.jsoneditor-mode-tree div.jsoneditor td,#json-format #json-format-container .jsoneditor.jsoneditor-mode-tree div.jsoneditor textarea,#json-format #json-format-container .jsoneditor.jsoneditor-mode-tree div.jsoneditor th,#json-format #json-format-container .jsoneditor.jsoneditor-mode-tree div.jsoneditor-field,#json-format #json-format-container .jsoneditor.jsoneditor-mode-tree div.jsoneditor-readonly,#json-format #json-format-container .jsoneditor.jsoneditor-mode-tree div.jsoneditor-value{font-size:15px!important;line-height:1.2em!important}#json-format #json-format-container .jsoneditor .ace_editor.ace-jsoneditor{font-size:15px!important}#json-format #json-format-container .jsoneditor .jsoneditor-menu button[type=button]{cursor:pointer;outline:0}#json-format #json-format-container .jsoneditor .jsoneditor-menu .jsoneditor-menu button[type=button].jsoneditor-selected{background-color:#3883fa}#json-format #json-format-container .jsoneditor .jsoneditor-menu .jsoneditor-history{background-image:url(../sections/IconHistory.svg)}#json-format #json-format-container .jsoneditor .jsoneditor-menu ul.jsoneditor-menu li button.jsoneditor-type-modes div.jsoneditor-text{padding-top:10px;padding-bottom:10px}#json-format #json-format-container .jsoneditor .jsoneditor-navigation-bar div.jsoneditor-contextmenu ul li button.jsoneditor-selected,#json-format #json-format-container .jsoneditor .jsoneditor-navigation-bar div.jsoneditor-contextmenu ul li button.jsoneditor-selected:focus,#json-format #json-format-container .jsoneditor .jsoneditor-navigation-bar div.jsoneditor-contextmenu ul li button.jsoneditor-selected:hover{background-color:#3883fa}#show #what{height:100%}#txt-diff .sm-alert{padding:1rem;padding-right:6rem;font-size:1.4rem}#txt-diff .sm-alert .sm-colse{padding:1rem;font-size:2.2rem;outline:0}#txt-diff .diff-condtion{margin:1.6rem 1.6rem 0;font-size:1.6rem}#txt-diff .diff-condtion label.btn{font-size:1.4rem}#txt-diff .diff-condtion input[type=radio][name=diffLevel]{cursor:pointer}#txt-diff .diff-condtion .do-diff{display:inline-block;margin-left:2rem}#txt-diff .diff-condtion .do-diff button[type=button]{outline:0}#txt-diff .diff-condtion .do-diff button[type=button]:focus{outline:0;box-shadow:none}#txt-diff .txt-wrap{margin:0 0}#txt-diff .txt-wrap textarea.code-diff-left,#txt-diff .txt-wrap textarea.code-diff-right{font-size:1.4rem}#txt-diff .txt-diff-result{margin:0 1rem 1rem;border:1px solid #f0f0f0;font-size:2rem}#txt-diff .txt-diff-result .level-char{background-color:#fff;padding-left:2em;position:relative}#txt-diff .txt-diff-result .level-char div{display:inline-block}#txt-diff .txt-diff-result .level-char:hover .char-header .char-num{background-color:#ded7fc;border-color:#cabffa}#txt-diff .txt-diff-result .level-char .char-info{display:block;word-wrap:break-word;word-break:break-all;line-height:3rem;font-size:2rem;padding-left:2em;height:3rem}#txt-diff .txt-diff-result .level-char .char-info:has(.changed-delete){color:red}#txt-diff .txt-diff-result .level-char .char-info.changed{background-color:#fafbfc}#txt-diff .txt-diff-result .level-char .char-info.changed-delete{background-color:#ffeef0}#txt-diff .txt-diff-result .level-char .char-info.changed-add{background-color:#e6ffed}#txt-diff .txt-diff-result .level-char .char-info .char-add{background-color:#acf2bd}#txt-diff .txt-diff-result .level-char .char-info .char-delete{background-color:#fdb8c0}#txt-diff .txt-diff-result .level-char .char-header{margin-left:-2em;position:absolute;height:100%}#txt-diff .txt-diff-result .level-char .char-header .char-num{display:inline-block;width:4rem;height:100%;background-color:#fafafa;border-right:1px solid;border-color:#f0f0f0;text-align:right;padding:0 .2em 0 0;vertical-align:top;color:rgba(0,0,0,.3);font-size:.8em}#txt-diff .txt-diff-result .level-char .char-header .char-flag{width:2em;height:100%;text-align:center;color:#9bb0a1}#txt-diff .txt-diff-result .level-line{background-color:#fff;padding-left:5em;position:relative}#txt-diff .txt-diff-result .level-line:hover .line-header .line-num{background-color:#ded7fc;border-color:#cabffa}#txt-diff .txt-diff-result .level-line div{display:inline-block}#txt-diff .txt-diff-result .level-line .line-info{word-wrap:break-word;word-break:break-all;line-height:3rem;font-size:2rem;padding-left:1rem}#txt-diff .txt-diff-result .level-line .line-header{margin-left:-5em;position:absolute;height:100%}#txt-diff .txt-diff-result .level-line .line-header .line-num{display:inline-block;width:4rem;height:100%;background-color:#fafafa;border-right:1px solid;border-color:#f0f0f0;text-align:right;padding:0 .2em 0 0;vertical-align:top;color:rgba(0,0,0,.3);font-size:.8em}#txt-diff .txt-diff-result .level-line .line-header .line-flag{width:1em;height:100%;text-align:center;color:#9bb0a1}#txt-diff .txt-diff-result .level-line.line-add{background-color:#ecfdf0}#txt-diff .txt-diff-result .level-line.line-add .line-num{background-color:#ddfbe6;border-color:#c7f0d2}#txt-diff .txt-diff-result .level-line.line-delete{background-color:#fbe9eb}#txt-diff .txt-diff-result .level-line.line-delete .line-num{background-color:#f9d7dc;border-color:#fac5cd}*{margin:0;padding:0}html{height:100%;font-size:10px}html body{height:100%}#loading{position:fixed;width:100%;height:100%;top:0;left:0;z-index:10000;background:#000;opacity:.8}#loading>.loading-svg{text-align:center;top:50%;position:relative;margin:0 auto}#index{width:100%;height:100%;box-sizing:border-box;-webkit-box-sizing:border-box;overflow:hidden;display:flex}#index>.left{-webkit-touch-callout:none;-webkit-user-select:none;touch-callout:none;user-select:none;width:20%;max-width:200px;min-width:150px;height:100%;box-sizing:border-box;-webkit-box-sizing:border-box;background-color:#222027;font-size:2rem;overflow-y:auto}#index>.left>.item{font-size:1.6rem;box-sizing:border-box;-webkit-box-sizing:border-box;padding:7px 20px 10px;color:#fff;cursor:pointer}#index>.left>.item>.icon{margin-right:5px}#index>.left>.item.active{background-color:#2587f7}#index>.left>.division{display:flex;box-sizing:border-box;-webkit-box-sizing:border-box;color:#8f8f8f;font-size:1.6rem;padding:5px 10px;position:relative}#index>.left>.division>.line{flex:1;position:relative;margin-left:1rem}#index>.left>.division>.line:after{content:"";display:block;width:100%;background-color:#4d4d4d;height:.1rem;position:absolute;top:1rem}#index>.right{flex:1;height:100%;box-sizing:border-box;-webkit-box-sizing:border-box;position:relative}#index>.right>.section{width:100%;height:100%;overflow-y:scroll;position:absolute;left:0;right:0} -------------------------------------------------------------------------------- /3rd/jsdifflib/diffview.js: -------------------------------------------------------------------------------- 1 | /* 2 | This is part of jsdifflib v1.0. 3 | 4 | Copyright 2007 - 2011 Chas Emerick . All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, are 7 | permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this list of 10 | conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, this list 13 | of conditions and the following disclaimer in the documentation and/or other materials 14 | provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY Chas Emerick ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 18 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Chas Emerick OR 19 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 24 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | The views and conclusions contained in the software and documentation are those of the 27 | authors and should not be interpreted as representing official policies, either expressed 28 | or implied, of Chas Emerick. 29 | */ 30 | var diffview = { 31 | /** 32 | * Builds and returns a visual diff view. The single parameter, `params', should contain 33 | * the following values: 34 | * 35 | * - baseTextLines: the array of strings that was used as the base text input to SequenceMatcher 36 | * - newTextLines: the array of strings that was used as the new text input to SequenceMatcher 37 | * - opcodes: the array of arrays returned by SequenceMatcher.get_opcodes() 38 | * - baseTextName: the title to be displayed above the base text listing in the diff view; defaults 39 | * to "Base Text" 40 | * - newTextName: the title to be displayed above the new text listing in the diff view; defaults 41 | * to "New Text" 42 | * - contextSize: the number of lines of context to show around differences; by default, all lines 43 | * are shown 44 | * - viewType: if 0, a side-by-side diff view is generated (default); if 1, an inline diff view is 45 | * generated 46 | */ 47 | buildView: function (params) { 48 | var baseTextLines = params.baseTextLines; 49 | var newTextLines = params.newTextLines; 50 | var opcodes = params.opcodes; 51 | var baseTextName = params.baseTextName ? params.baseTextName : "Base Text"; 52 | var newTextName = params.newTextName ? params.newTextName : "New Text"; 53 | var contextSize = params.contextSize; 54 | var inline = (params.viewType == 0 || params.viewType == 1) ? params.viewType : 0; 55 | 56 | if (baseTextLines == null) 57 | throw "Cannot build diff view; baseTextLines is not defined."; 58 | if (newTextLines == null) 59 | throw "Cannot build diff view; newTextLines is not defined."; 60 | if (!opcodes) 61 | throw "Canno build diff view; opcodes is not defined."; 62 | 63 | function celt (name, clazz) { 64 | var e = document.createElement(name); 65 | e.className = clazz; 66 | return e; 67 | } 68 | 69 | function telt (name, text) { 70 | var e = document.createElement(name); 71 | e.appendChild(document.createTextNode(text)); 72 | return e; 73 | } 74 | 75 | function ctelt (name, clazz, text) { 76 | var e = document.createElement(name); 77 | e.className = clazz; 78 | e.appendChild(document.createTextNode(text)); 79 | return e; 80 | } 81 | 82 | var tdata = document.createElement("thead"); 83 | var node = document.createElement("tr"); 84 | tdata.appendChild(node); 85 | if (inline) { 86 | node.appendChild(document.createElement("th")); 87 | node.appendChild(document.createElement("th")); 88 | node.appendChild(ctelt("th", "texttitle", baseTextName + " vs. " + newTextName)); 89 | } else { 90 | node.appendChild(document.createElement("th")); 91 | node.appendChild(ctelt("th", "texttitle", baseTextName)); 92 | node.appendChild(document.createElement("th")); 93 | node.appendChild(ctelt("th", "texttitle", newTextName)); 94 | } 95 | tdata = [tdata]; 96 | 97 | var rows = []; 98 | var node2; 99 | 100 | /** 101 | * Adds two cells to the given row; if the given row corresponds to a real 102 | * line number (based on the line index tidx and the endpoint of the 103 | * range in question tend), then the cells will contain the line number 104 | * and the line of text from textLines at position tidx (with the class of 105 | * the second cell set to the name of the change represented), and tidx + 1 will 106 | * be returned. Otherwise, tidx is returned, and two empty cells are added 107 | * to the given row. 108 | */ 109 | function addCells (row, tidx, tend, textLines, change) { 110 | if (tidx < tend) { 111 | row.appendChild(telt("th", (tidx + 1).toString())); 112 | row.appendChild(ctelt("td", change, textLines[tidx].replace(/\t/g, "\u00a0\u00a0\u00a0\u00a0"))); 113 | return tidx + 1; 114 | } else { 115 | row.appendChild(document.createElement("th")); 116 | row.appendChild(celt("td", "empty")); 117 | return tidx; 118 | } 119 | } 120 | 121 | function addCellsInline (row, tidx, tidx2, textLines, change) { 122 | row.appendChild(telt("th", tidx == null ? "" : (tidx + 1).toString())); 123 | row.appendChild(telt("th", tidx2 == null ? "" : (tidx2 + 1).toString())); 124 | row.appendChild(ctelt("td", change, textLines[tidx != null ? tidx : tidx2].replace(/\t/g, "\u00a0\u00a0\u00a0\u00a0"))); 125 | } 126 | 127 | for (var idx = 0; idx < opcodes.length; idx++) { 128 | code = opcodes[idx]; 129 | change = code[0]; 130 | var b = code[1]; 131 | var be = code[2]; 132 | var n = code[3]; 133 | var ne = code[4]; 134 | var rowcnt = Math.max(be - b, ne - n); 135 | var toprows = []; 136 | var botrows = []; 137 | for (var i = 0; i < rowcnt; i++) { 138 | // jump ahead if we've alredy provided leading context or if this is the first range 139 | if (contextSize && opcodes.length > 1 && ((idx > 0 && i == contextSize) || (idx == 0 && i == 0)) && change=="equal") { 140 | var jump = rowcnt - ((idx == 0 ? 1 : 2) * contextSize); 141 | if (jump > 1) { 142 | toprows.push(node = document.createElement("tr")); 143 | 144 | b += jump; 145 | n += jump; 146 | i += jump - 1; 147 | node.appendChild(telt("th", "...")); 148 | if (!inline) node.appendChild(ctelt("td", "skip", "")); 149 | node.appendChild(telt("th", "...")); 150 | node.appendChild(ctelt("td", "skip", "")); 151 | 152 | // skip last lines if they're all equal 153 | if (idx + 1 == opcodes.length) { 154 | break; 155 | } else { 156 | continue; 157 | } 158 | } 159 | } 160 | 161 | toprows.push(node = document.createElement("tr")); 162 | if (inline) { 163 | if (change == "insert") { 164 | addCellsInline(node, null, n++, newTextLines, change); 165 | } else if (change == "replace") { 166 | botrows.push(node2 = document.createElement("tr")); 167 | if (b < be) addCellsInline(node, b++, null, baseTextLines, "delete"); 168 | if (n < ne) addCellsInline(node2, null, n++, newTextLines, "insert"); 169 | } else if (change == "delete") { 170 | addCellsInline(node, b++, null, baseTextLines, change); 171 | } else { 172 | // equal 173 | addCellsInline(node, b++, n++, baseTextLines, change); 174 | } 175 | } else { 176 | b = addCells(node, b, be, baseTextLines, change); 177 | n = addCells(node, n, ne, newTextLines, change); 178 | } 179 | } 180 | 181 | for (var i = 0; i < toprows.length; i++) rows.push(toprows[i]); 182 | for (var i = 0; i < botrows.length; i++) rows.push(botrows[i]); 183 | } 184 | 185 | rows.push(node = ctelt("th", "author", "diff view generated by ")); 186 | node.setAttribute("colspan", inline ? 3 : 4); 187 | node.appendChild(node2 = telt("a", "jsdifflib")); 188 | node2.setAttribute("href", "http://github.com/cemerick/jsdifflib"); 189 | 190 | tdata.push(node = document.createElement("tbody")); 191 | for (var idx in rows) rows.hasOwnProperty(idx) && node.appendChild(rows[idx]); 192 | 193 | node = celt("table", "diff" + (inline ? " inlinediff" : "")); 194 | for (var idx in tdata) tdata.hasOwnProperty(idx) && node.appendChild(tdata[idx]); 195 | return node; 196 | } 197 | }; 198 | 199 | -------------------------------------------------------------------------------- /less/sections.css: -------------------------------------------------------------------------------- 1 | #about .container .logo { 2 | text-align: center; 3 | margin: 20px 0; 4 | } 5 | #about .container .name { 6 | text-align: center; 7 | font-size: 20px; 8 | } 9 | #about .container .version { 10 | text-align: center; 11 | color: #C1C1C1; 12 | font-size: 18px; 13 | } 14 | #about .container .about-me { 15 | font-size: 20px; 16 | } 17 | 18 | #json-format #json-format-container { 19 | height: 100%; 20 | background-color: #FFF; 21 | } 22 | #json-format #json-format-container .jsoneditor.jsoneditor-mode-tree div.jsoneditor-readonly, 23 | #json-format #json-format-container .jsoneditor.jsoneditor-mode-tree div.jsoneditor-field, 24 | #json-format #json-format-container .jsoneditor.jsoneditor-mode-tree div.jsoneditor-value, 25 | #json-format #json-format-container .jsoneditor.jsoneditor-mode-tree div.jsoneditor td, 26 | #json-format #json-format-container .jsoneditor.jsoneditor-mode-tree div.jsoneditor th, 27 | #json-format #json-format-container .jsoneditor.jsoneditor-mode-tree div.jsoneditor textarea, 28 | #json-format #json-format-container .jsoneditor.jsoneditor-mode-tree .jsoneditor-schema-error { 29 | font-size: 15px !important; 30 | line-height: 1.2em !important; 31 | } 32 | #json-format #json-format-container .jsoneditor .ace_editor.ace-jsoneditor { 33 | font-size: 15px !important; 34 | } 35 | #json-format #json-format-container .jsoneditor .jsoneditor-menu button[type=button] { 36 | cursor: pointer; 37 | outline: none; 38 | } 39 | #json-format #json-format-container .jsoneditor .jsoneditor-menu .jsoneditor-menu button[type=button].jsoneditor-selected { 40 | background-color: #3883fa; 41 | } 42 | #json-format #json-format-container .jsoneditor .jsoneditor-menu .jsoneditor-history { 43 | background-image: url("../sections/IconHistory.svg"); 44 | } 45 | #json-format #json-format-container .jsoneditor .jsoneditor-menu ul.jsoneditor-menu li button.jsoneditor-type-modes div.jsoneditor-text { 46 | padding-top: 10px; 47 | padding-bottom: 10px; 48 | } 49 | #json-format #json-format-container .jsoneditor .jsoneditor-navigation-bar div.jsoneditor-contextmenu ul li button.jsoneditor-selected, 50 | #json-format #json-format-container .jsoneditor .jsoneditor-navigation-bar div.jsoneditor-contextmenu ul li button.jsoneditor-selected:hover, 51 | #json-format #json-format-container .jsoneditor .jsoneditor-navigation-bar div.jsoneditor-contextmenu ul li button.jsoneditor-selected:focus { 52 | background-color: #3883fa; 53 | } 54 | 55 | #show #what { 56 | height: 100%; 57 | } 58 | 59 | #txt-diff .sm-alert { 60 | padding: 1rem; 61 | padding-right: 6rem; 62 | font-size: 1.4rem; 63 | } 64 | #txt-diff .sm-alert .sm-colse { 65 | padding: 1rem; 66 | font-size: 2.2rem; 67 | outline: none; 68 | } 69 | #txt-diff .diff-condtion { 70 | margin: 1.6rem 1.6rem 0; 71 | font-size: 1.6rem; 72 | } 73 | #txt-diff .diff-condtion label.btn { 74 | font-size: 1.4rem; 75 | } 76 | #txt-diff .diff-condtion input[type=radio][name=diffLevel] { 77 | cursor: pointer; 78 | } 79 | #txt-diff .diff-condtion .do-diff { 80 | display: inline-block; 81 | margin-left: 2rem; 82 | } 83 | #txt-diff .diff-condtion .do-diff button[type=button] { 84 | outline: none; 85 | } 86 | #txt-diff .diff-condtion .do-diff button[type=button]:focus { 87 | outline: none; 88 | box-shadow: none; 89 | } 90 | #txt-diff .txt-wrap { 91 | margin: 0 0; 92 | } 93 | #txt-diff .txt-wrap textarea.code-diff-left, 94 | #txt-diff .txt-wrap textarea.code-diff-right { 95 | font-size: 1.4rem; 96 | } 97 | #txt-diff .txt-diff-result { 98 | margin: 0 1rem 1rem; 99 | border: 1px solid #F0F0F0; 100 | font-size: 2rem; 101 | } 102 | #txt-diff .txt-diff-result .level-char { 103 | background-color: #FFF; 104 | padding-left: 2em; 105 | position: relative; 106 | } 107 | #txt-diff .txt-diff-result .level-char div { 108 | display: inline-block; 109 | } 110 | #txt-diff .txt-diff-result .level-char:hover .char-header .char-num { 111 | background-color: #DED7FC; 112 | border-color: #CABFFA; 113 | } 114 | #txt-diff .txt-diff-result .level-char .char-info { 115 | display: block; 116 | word-wrap: break-word; 117 | word-break: break-all; 118 | line-height: 3rem; 119 | font-size: 2rem; 120 | padding-left: 2em; 121 | height: 3rem; 122 | } 123 | #txt-diff .txt-diff-result .level-char .char-info:has(.changed-delete) { 124 | color: red; 125 | } 126 | #txt-diff .txt-diff-result .level-char .char-info.changed { 127 | background-color: #fafbfc; 128 | } 129 | #txt-diff .txt-diff-result .level-char .char-info.changed-delete { 130 | background-color: #ffeef0; 131 | } 132 | #txt-diff .txt-diff-result .level-char .char-info.changed-add { 133 | background-color: #e6ffed; 134 | } 135 | #txt-diff .txt-diff-result .level-char .char-info .char-add { 136 | background-color: #acf2bd; 137 | } 138 | #txt-diff .txt-diff-result .level-char .char-info .char-delete { 139 | background-color: #fdb8c0; 140 | } 141 | #txt-diff .txt-diff-result .level-char .char-header { 142 | margin-left: -2em; 143 | position: absolute; 144 | height: 100%; 145 | } 146 | #txt-diff .txt-diff-result .level-char .char-header .char-num { 147 | display: inline-block; 148 | width: 4rem; 149 | height: 100%; 150 | background-color: #fafafa; 151 | border-right: 1px solid; 152 | border-color: #f0f0f0; 153 | text-align: right; 154 | padding: 0 .2em 0 0; 155 | vertical-align: top; 156 | color: rgba(0, 0, 0, 0.3); 157 | font-size: .8em; 158 | } 159 | #txt-diff .txt-diff-result .level-char .char-header .char-flag { 160 | width: 2em; 161 | height: 100%; 162 | text-align: center; 163 | color: #9bb0a1; 164 | } 165 | #txt-diff .txt-diff-result .level-line { 166 | background-color: #FFF; 167 | padding-left: 5em; 168 | position: relative; 169 | } 170 | #txt-diff .txt-diff-result .level-line:hover .line-header .line-num { 171 | background-color: #DED7FC; 172 | border-color: #CABFFA; 173 | } 174 | #txt-diff .txt-diff-result .level-line div { 175 | display: inline-block; 176 | } 177 | #txt-diff .txt-diff-result .level-line .line-info { 178 | word-wrap: break-word; 179 | word-break: break-all; 180 | line-height: 3rem; 181 | font-size: 2rem; 182 | padding-left: 1rem; 183 | } 184 | #txt-diff .txt-diff-result .level-line .line-header { 185 | margin-left: -5em; 186 | position: absolute; 187 | height: 100%; 188 | } 189 | #txt-diff .txt-diff-result .level-line .line-header .line-num { 190 | display: inline-block; 191 | width: 4rem; 192 | height: 100%; 193 | background-color: #fafafa; 194 | border-right: 1px solid; 195 | border-color: #f0f0f0; 196 | text-align: right; 197 | padding: 0 .2em 0 0; 198 | vertical-align: top; 199 | color: rgba(0, 0, 0, 0.3); 200 | font-size: .8em; 201 | } 202 | #txt-diff .txt-diff-result .level-line .line-header .line-flag { 203 | width: 1em; 204 | height: 100%; 205 | text-align: center; 206 | color: #9bb0a1; 207 | } 208 | #txt-diff .txt-diff-result .level-line.line-add { 209 | background-color: #ecfdf0; 210 | } 211 | #txt-diff .txt-diff-result .level-line.line-add .line-num { 212 | background-color: #ddfbe6; 213 | border-color: #c7f0d2; 214 | } 215 | #txt-diff .txt-diff-result .level-line.line-delete { 216 | background-color: #fbe9eb; 217 | } 218 | #txt-diff .txt-diff-result .level-line.line-delete .line-num { 219 | background-color: #f9d7dc; 220 | border-color: #fac5cd; 221 | } 222 | 223 | * { 224 | margin: 0; 225 | padding: 0; 226 | } 227 | html { 228 | height: 100%; 229 | font-size: 10px; 230 | } 231 | html body { 232 | height: 100%; 233 | } 234 | #loading { 235 | position: fixed; 236 | width: 100%; 237 | height: 100%; 238 | top: 0; 239 | left: 0; 240 | z-index: 10000; 241 | background: black; 242 | opacity: .8; 243 | } 244 | #loading > .loading-svg { 245 | text-align: center; 246 | top: 50%; 247 | position: relative; 248 | margin: 0 auto; 249 | } 250 | #index { 251 | width: 100%; 252 | height: 100%; 253 | box-sizing: border-box; 254 | -webkit-box-sizing: border-box; 255 | overflow: hidden; 256 | display: flex; 257 | } 258 | #index > .left { 259 | -webkit-touch-callout: none; 260 | -webkit-user-select: none; 261 | touch-callout: none; 262 | user-select: none; 263 | width: 20%; 264 | max-width: 200px; 265 | min-width: 150px; 266 | height: 100%; 267 | box-sizing: border-box; 268 | -webkit-box-sizing: border-box; 269 | background-color: #222027; 270 | font-size: 2rem; 271 | overflow-y: auto; 272 | } 273 | #index > .left > .item { 274 | font-size: 1.6rem; 275 | box-sizing: border-box; 276 | -webkit-box-sizing: border-box; 277 | padding: 7px 20px 10px; 278 | color: #ffffff; 279 | cursor: pointer; 280 | } 281 | #index > .left > .item > .icon { 282 | margin-right: 5px; 283 | } 284 | #index > .left > .item.active { 285 | background-color: #2587f7; 286 | } 287 | #index > .left > .division { 288 | display: flex; 289 | box-sizing: border-box; 290 | -webkit-box-sizing: border-box; 291 | color: #8f8f8f; 292 | font-size: 1.6rem; 293 | padding: 5px 10px; 294 | position: relative; 295 | } 296 | #index > .left > .division > .line { 297 | flex: 1; 298 | position: relative; 299 | margin-left: 1rem; 300 | } 301 | #index > .left > .division > .line:after { 302 | content: ""; 303 | display: block; 304 | width: 100%; 305 | background-color: #4d4d4d; 306 | height: 0.1rem; 307 | position: absolute; 308 | top: 1rem; 309 | } 310 | #index > .right { 311 | flex: 1; 312 | height: 100%; 313 | box-sizing: border-box; 314 | -webkit-box-sizing: border-box; 315 | position: relative; 316 | } 317 | #index > .right > .section { 318 | width: 100%; 319 | height: 100%; 320 | overflow-y: scroll; 321 | position: absolute; 322 | left: 0; 323 | right: 0; 324 | } 325 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 工具箱
文本工具
json转换
文本DIFF
About
关于


Gram Tools
V1.0.0
-------------------------------------------------------------------------------- /js/Tools.js: -------------------------------------------------------------------------------- 1 | /** 2 | * mcn垂直业务前段基本组件 3 | * 4 | * 1. 遮罩层+弹窗提示: 5 | var maskId = tools.mask("", "opacity:0.3;"); 6 | tools.showInfo(res.msg, 2000, "z-index:1000;top:300px;", (function(){ 7 | return function() { 8 | $("#"+maskId).remove(); 9 | }; 10 | })()); 11 | */ 12 | "use strict"; 13 | function Tools(){ 14 | this.timers = {}; 15 | }; 16 | 17 | /** 18 | * 将字符串html中的<>& "替换为html实体 19 | * 20 | * @param {[type]} html [description] 21 | * @return {[type]} [description] 22 | */ 23 | Tools.prototype.htmlEntities = function(html) { 24 | if (!this.isString(html)) { 25 | throw "转换html实体,参数必须是字符串"; 26 | } 27 | // 替换的顺序很重要 28 | html = this.regA2B(html, [ 29 | [/\t/gm, " "], 30 | [/&/gm, "&"], 31 | [/ /gm, " "], 32 | [//gm, ">"], 34 | [/"/gm, """], 35 | [/'/gm, "'"] 36 | ]); 37 | 38 | return html; 39 | }; 40 | 41 | /** 42 | * 从浏览器链接参数中获取参数值 43 | * 44 | * @param {[type]} name [description] 45 | * @return {[type]} [description] 46 | */ 47 | Tools.prototype.getQueryString = function(name, url) { 48 | url = typeof(url) != "string" ? window.location.search : url; 49 | var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i"); 50 | var r = url.substr(1).match(reg); 51 | if (r != null) { 52 | return decodeURIComponent(r[2]); 53 | } else { 54 | return null; 55 | } 56 | }; 57 | 58 | /** 59 | * 节流器 60 | * 使用场景举例: 61 | * 滚动条滚动事件 62 | * 63 | * @param {function} func 执行的函数 64 | * @param {int} gap 定时器毫秒,可为空,默认0.3s 65 | * @param {string} 使用的定时器id,可为空,默认为函数.toString() 66 | * @param {object} context 函数执行的上下文,可为空 67 | * @return {} [description] 68 | */ 69 | Tools.prototype.throttler = function(func, gap, timerId, context) { 70 | gap = gap || 300; 71 | timerId = timerId || func.toString(); 72 | // 清除之前的定时器 73 | clearTimeout(this.timers[timerId]); 74 | this.timers[timerId] = setTimeout(function(){ 75 | func.call(context); 76 | }, gap); 77 | return this.timers[timerId]; 78 | }; 79 | 80 | /** 81 | * 字符串变量替换 82 | * var str = "你好{name},我今年{age}岁"; 83 | * var format = tools.formatString(str, { 84 | * name: "燕睿涛", 85 | * age: 26 86 | * }); 87 | * 88 | * format 为 你好燕睿涛,我今年26岁 89 | * 90 | * @param {[type]} str [description] 91 | * @param {[type]} formats [description] 92 | * @return {[type]} [description] 93 | */ 94 | Tools.prototype.formatString = function(str, formats) { 95 | var i, re; 96 | for (i in formats) { 97 | re = new RegExp("\\{" + i + "\\}", "gm"); 98 | str = str.replace(re, formats[i]); 99 | } 100 | return str; 101 | }; 102 | 103 | /** 104 | * 正则批量替换字符串 105 | * var str = " 你好 ;‘’“”()——, 106 | * 107 | * "; 108 | * var format = tools.regA2B(str, [ 109 | * [/^\s+|\s+$/gm, ""], 110 | * [/(\r\n)|(\t)/gm, ""], 111 | * [/(/gm, "("], 112 | * [/)/gm, ")"], 113 | * [/“|”/gm, "\""], 114 | * [/‘|’/gm, "'"], 115 | * [/,/gm, ","], 116 | * [/;/gm, ";"], 117 | * [/:/gm, ":"], 118 | * [/——/gm, "-"] 119 | * ]); 120 | * format 为 你好 ;''""()-, 121 | * 122 | * 123 | * @param {[type]} str [description] 124 | * @param {[type]} trans [description] 125 | * @return {[type]} [description] 126 | */ 127 | Tools.prototype.regA2B = function(str, trans) { 128 | var i, re, to; 129 | for (i in trans) { 130 | re = trans[i][0]; 131 | to = trans[i][1]; 132 | str = str.replace(re, to); 133 | } 134 | return str; 135 | }; 136 | 137 | /** 138 | * 转半角字符 139 | * var str = "你好,I'm Yan Ruitao. ()[]"; 140 | * var format = tools.toHalfWidth(str); 141 | * format 为 你好,I'm Yan Ruitao. ()[] 142 | * 143 | * @param {[type]} str [description] 144 | * @return {[type]} [description] 145 | */ 146 | Tools.prototype.toHalfWidth = function(str) { 147 | var result = "", 148 | cCode; 149 | var len = str.length; 150 | for(var i=0; i=0xFF01 && cCode<=0xFF5E)?(cCode - 65248) : cCode; 155 | //处理空格 156 | cCode = (cCode==0x03000)?0x0020:cCode; 157 | result += String.fromCharCode(cCode); 158 | } 159 | return result; 160 | }; 161 | 162 | Tools.prototype.formatNum = function(num, decimal, preciseDecimal) { 163 | if (typeof(num) == "undefined") { 164 | num = 0; 165 | } 166 | decimal = typeof(decimal) == "undefined" ? 1 : decimal; 167 | if (num <= 9999) { 168 | preciseDecimal = typeof(preciseDecimal) == "undefined" ? 0 : preciseDecimal; 169 | num = this.numberFormat(num, preciseDecimal); 170 | } else if (num <= 99999999) { 171 | num = num / 10000; 172 | num = this.numberFormat(num, decimal); 173 | num = num + "万"; 174 | } else { 175 | num = num / 100000000; 176 | num = this.numberFormat(num, decimal); 177 | num = num + "亿"; 178 | } 179 | return num; 180 | }; 181 | 182 | Tools.prototype.numberFormat = function(number, decimals, dec_point, thousands_sep,roundtag) { 183 | /* 184 | * 参数说明: 185 | * number:要格式化的数字 186 | * decimals:保留几位小数 187 | * dec_point:小数点符号 188 | * thousands_sep:千分位符号 189 | * roundtag:舍入参数,默认 "ceil" 向上取,"floor"向下取,"round" 四舍五入 190 | * */ 191 | number = (number + '').replace(/[^0-9+-Ee.]/g, ''); 192 | roundtag = roundtag || "round"; //"ceil","floor","round" 193 | var n = !isFinite(+number) ? 0 : +number, 194 | prec = !isFinite(+decimals) ? 0 : Math.abs(decimals), 195 | sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep, 196 | dec = (typeof dec_point === 'undefined') ? '.' : dec_point, 197 | s = '', 198 | toFixedFix = function (n, prec) { 199 | 200 | var k = Math.pow(10, prec); 201 | console.log(); 202 | 203 | return '' + parseFloat(Math[roundtag](parseFloat((n * k).toFixed(prec*2))).toFixed(prec*2)) / k; 204 | }; 205 | s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.'); 206 | var re = /(-?\d+)(\d{3})/; 207 | while (re.test(s[0])) { 208 | s[0] = s[0].replace(re, "$1" + sep + "$2"); 209 | } 210 | 211 | if ((s[1] || '').length < prec) { 212 | s[1] = s[1] || ''; 213 | s[1] += new Array(prec - s[1].length + 1).join('0'); 214 | } 215 | return s.join(dec); 216 | }; 217 | 218 | /** 219 | * 判断一个参数是不是对象 220 | * 221 | * @param {[type]} object [description] 222 | * @return {Boolean} [description] 223 | */ 224 | Tools.prototype.isObject = function(object) { 225 | var flag = false; 226 | flag = toString.apply(object) == "[object Object]"; 227 | return flag; 228 | }; 229 | 230 | /** 231 | * 格式化,可以加参数,修改参数 232 | * var url = "https://weibo.com/u/5824742984/home?wvr=5#where"; 233 | * url = formatUrlParams(url, { 234 | * age: 26, 235 | * name: "燕睿涛", 236 | * where: "homepage", 237 | * wvr: 123 238 | * }); 239 | * url的新值为 "https://weibo.com/u/5824742984/home?wvr=123&age=26&name=燕睿涛&where=homepage#where" 240 | * 241 | * @param {[type]} url [description] 242 | * @param {[type]} params [description] 243 | * @param {bool} isEncode 是否使用encodeURIComponent对参数进行编码 244 | * @return {[type]} [description] 245 | */ 246 | Tools.prototype.formatUrlParams = function(url, params, isEncode) { 247 | var splitByHash, pureUrl, hashInfo = "" 248 | , i, reg; 249 | if(typeof(isEncode) == "undefined") { 250 | isEncode = true; 251 | } 252 | // 参数校验 253 | if (typeof(url) != "string") { 254 | throw "要格式化的url必须是字符串"; 255 | } 256 | if (!this.isObject(params)) { 257 | throw "参数必须为对象,里面是键值对"; 258 | } 259 | splitByHash = url.split("#"); 260 | if (splitByHash.length > 2) { 261 | throw "要格式化的url中最多有一个#hash"; 262 | } 263 | pureUrl = splitByHash[0]; 264 | if (splitByHash.length == 2) { 265 | hashInfo = "#" + splitByHash[1]; 266 | } 267 | 268 | for (i in params) { 269 | reg = new RegExp("(^|)"+ i +"=([^&]*)(|$)"); 270 | if (pureUrl.match(reg) != null) { 271 | pureUrl = pureUrl.replace( 272 | reg, 273 | i+"="+ (isEncode ? encodeURIComponent(params[i]) : params[i]) 274 | ); 275 | } else { 276 | if (pureUrl.match(/\?/g) != null) { 277 | pureUrl = pureUrl + "&" + i + "=" + (isEncode ? encodeURIComponent(params[i]) : params[i]); 278 | } else { 279 | pureUrl = pureUrl + "?" + i + "=" + (isEncode ? encodeURIComponent(params[i]) : params[i]); 280 | } 281 | } 282 | } 283 | 284 | pureUrl += hashInfo; 285 | 286 | return pureUrl; 287 | }; 288 | 289 | /** 290 | * 产生随机数,包含最大最小数 291 | * @param int min 292 | * @param int max [description] 293 | * @return int [description] 294 | */ 295 | Tools.prototype.random = function(min, max) { 296 | var range = max - min; 297 | var rand = Math.random(); 298 | var num = min + Math.round(rand * range); 299 | return num; 300 | }; 301 | 302 | 303 | Tools.prototype.isString = function(text) { 304 | return (typeof(text)=='string') && text.constructor == String; 305 | }; 306 | 307 | Tools.prototype.isNumber = function(num) { 308 | return (typeof(num)=='number') && num.constructor == Number; 309 | } 310 | 311 | /** 312 | * 匹配字符串中的字符是否全是给定的选择类型 313 | * zh: 表示汉子, 314 | * en: 表示大小写字母 315 | * *: 其他的用户传入的会加入字符串匹配中 316 | * 317 | * ex: 318 | * checkChar("燕睿涛"); true 319 | * checkChar("我asdsAC"); true 320 | * checkChar("我们的_哈哈", "[-_!]"); true 321 | * 要匹配-要注意转义 322 | * checkChar("燕睿涛a sdASS ", ["zh", "en", "[_\- ]"]); true 323 | * 匹配\ 324 | * checkChar("燕睿涛a \\sdASS ", ["zh", "en", "[_\- ]", "[\\\\]"]); true 325 | * 326 | * @param {[type]} text [description] 327 | * @param {[type]} types [description] 328 | * @return {[type]} [description] 329 | */ 330 | Tools.prototype.checkChar = function(text, types, min, max) { 331 | var typeRegs, i, reg, regObj, ret, scope; 332 | if (!this.isString(text)) { 333 | throw "要校验的对象不是字符串"; 334 | } 335 | 336 | if ("undefined" == typeof(min)) { 337 | scope = "+"; 338 | } else { 339 | if ( 340 | !this.isNumber(min) || parseInt(min) < 0 341 | ) { 342 | throw "字符串长度最小值应该是大于等于0的整数"; 343 | } 344 | min = parseInt(min); 345 | scope = "{"+min+","; 346 | if ("undefined" == typeof(max)) { 347 | scope += "}"; 348 | } else { 349 | if( 350 | !this.isNumber(max) || parseInt(max) < 0 || 351 | parseInt(max) < min 352 | ) { 353 | throw "字符串长度最小值应该是大于等于0的整数,且应该大于等于最小长度"; 354 | } 355 | max = parseInt(max); 356 | scope += max + "}"; 357 | } 358 | } 359 | 360 | var typeRegs = { 361 | "zh": "[\u4e00-\u9fa5]", 362 | "en": "[a-zA-Z]" 363 | }, 364 | i, reg, regObj, ret; 365 | 366 | types = types ? types : ["zh", "en"]; 367 | reg = "^(" 368 | for (i=0; i" 415 | + ''; 416 | $('body').append(html); 417 | return id; 418 | }; 419 | 420 | export default Tools; -------------------------------------------------------------------------------- /3rd/jsdifflib/difflib.js: -------------------------------------------------------------------------------- 1 | /*** 2 | This is part of jsdifflib v1.0. 3 | 4 | Copyright (c) 2007, Snowtide Informatics Systems, Inc. 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | * Neither the name of the Snowtide Informatics Systems nor the names of its 16 | contributors may be used to endorse or promote products derived from this 17 | software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 22 | SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 24 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 25 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 27 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 28 | DAMAGE. 29 | ***/ 30 | /* Author: Chas Emerick */ 31 | var __whitespace = {" ":true, "\t":true, "\n":true, "\f":true, "\r":true}; 32 | 33 | var difflib = { 34 | defaultJunkFunction: function (c) { 35 | return __whitespace.hasOwnProperty(c); 36 | }, 37 | 38 | stripLinebreaks: function (str) { return str.replace(/^[\n\r]*|[\n\r]*$/g, ""); }, 39 | 40 | stringAsLines: function (str) { 41 | var lfpos = str.indexOf("\n"); 42 | var crpos = str.indexOf("\r"); 43 | var linebreak = ((lfpos > -1 && crpos > -1) || crpos < 0) ? "\n" : "\r"; 44 | 45 | var lines = str.split(linebreak); 46 | for (var i = 0; i < lines.length; i++) { 47 | lines[i] = difflib.stripLinebreaks(lines[i]); 48 | } 49 | 50 | return lines; 51 | }, 52 | 53 | // iteration-based reduce implementation 54 | __reduce: function (func, list, initial) { 55 | if (initial != null) { 56 | var value = initial; 57 | var idx = 0; 58 | } else if (list) { 59 | var value = list[0]; 60 | var idx = 1; 61 | } else { 62 | return null; 63 | } 64 | 65 | for (; idx < list.length; idx++) { 66 | value = func(value, list[idx]); 67 | } 68 | 69 | return value; 70 | }, 71 | 72 | // comparison function for sorting lists of numeric tuples 73 | __ntuplecomp: function (a, b) { 74 | var mlen = Math.max(a.length, b.length); 75 | for (var i = 0; i < mlen; i++) { 76 | if (a[i] < b[i]) return -1; 77 | if (a[i] > b[i]) return 1; 78 | } 79 | 80 | return a.length == b.length ? 0 : (a.length < b.length ? -1 : 1); 81 | }, 82 | 83 | __calculate_ratio: function (matches, length) { 84 | return length ? 2.0 * matches / length : 1.0; 85 | }, 86 | 87 | // returns a function that returns true if a key passed to the returned function 88 | // is in the dict (js object) provided to this function; replaces being able to 89 | // carry around dict.has_key in python... 90 | __isindict: function (dict) { 91 | return function (key) { return dict.hasOwnProperty(key); }; 92 | }, 93 | 94 | // replacement for python's dict.get function -- need easy default values 95 | __dictget: function (dict, key, defaultValue) { 96 | return dict.hasOwnProperty(key) ? dict[key] : defaultValue; 97 | }, 98 | 99 | SequenceMatcher: function (a, b, isjunk) { 100 | this.set_seqs = function (a, b) { 101 | this.set_seq1(a); 102 | this.set_seq2(b); 103 | } 104 | 105 | this.set_seq1 = function (a) { 106 | if (a == this.a) return; 107 | this.a = a; 108 | this.matching_blocks = this.opcodes = null; 109 | } 110 | 111 | this.set_seq2 = function (b) { 112 | if (b == this.b) return; 113 | this.b = b; 114 | this.matching_blocks = this.opcodes = this.fullbcount = null; 115 | this.__chain_b(); 116 | } 117 | 118 | this.__chain_b = function () { 119 | var b = this.b; 120 | var n = b.length; 121 | var b2j = this.b2j = {}; 122 | var populardict = {}; 123 | for (var i = 0; i < b.length; i++) { 124 | var elt = b[i]; 125 | if (b2j.hasOwnProperty(elt)) { 126 | var indices = b2j[elt]; 127 | if (n >= 200 && indices.length * 100 > n) { 128 | populardict[elt] = 1; 129 | delete b2j[elt]; 130 | } else { 131 | indices.push(i); 132 | } 133 | } else { 134 | b2j[elt] = [i]; 135 | } 136 | } 137 | 138 | for (var elt in populardict) { 139 | if (populardict.hasOwnProperty(elt)) { 140 | delete b2j[elt]; 141 | } 142 | } 143 | 144 | var isjunk = this.isjunk; 145 | var junkdict = {}; 146 | if (isjunk) { 147 | for (var elt in populardict) { 148 | if (populardict.hasOwnProperty(elt) && isjunk(elt)) { 149 | junkdict[elt] = 1; 150 | delete populardict[elt]; 151 | } 152 | } 153 | for (var elt in b2j) { 154 | if (b2j.hasOwnProperty(elt) && isjunk(elt)) { 155 | junkdict[elt] = 1; 156 | delete b2j[elt]; 157 | } 158 | } 159 | } 160 | 161 | this.isbjunk = difflib.__isindict(junkdict); 162 | this.isbpopular = difflib.__isindict(populardict); 163 | } 164 | 165 | this.find_longest_match = function (alo, ahi, blo, bhi) { 166 | var a = this.a; 167 | var b = this.b; 168 | var b2j = this.b2j; 169 | var isbjunk = this.isbjunk; 170 | var besti = alo; 171 | var bestj = blo; 172 | var bestsize = 0; 173 | var j = null; 174 | var k; 175 | 176 | var j2len = {}; 177 | var nothing = []; 178 | for (var i = alo; i < ahi; i++) { 179 | var newj2len = {}; 180 | var jdict = difflib.__dictget(b2j, a[i], nothing); 181 | for (var jkey in jdict) { 182 | if (jdict.hasOwnProperty(jkey)) { 183 | j = jdict[jkey]; 184 | if (j < blo) continue; 185 | if (j >= bhi) break; 186 | newj2len[j] = k = difflib.__dictget(j2len, j - 1, 0) + 1; 187 | if (k > bestsize) { 188 | besti = i - k + 1; 189 | bestj = j - k + 1; 190 | bestsize = k; 191 | } 192 | } 193 | } 194 | j2len = newj2len; 195 | } 196 | 197 | while (besti > alo && bestj > blo && !isbjunk(b[bestj - 1]) && a[besti - 1] == b[bestj - 1]) { 198 | besti--; 199 | bestj--; 200 | bestsize++; 201 | } 202 | 203 | while (besti + bestsize < ahi && bestj + bestsize < bhi && 204 | !isbjunk(b[bestj + bestsize]) && 205 | a[besti + bestsize] == b[bestj + bestsize]) { 206 | bestsize++; 207 | } 208 | 209 | while (besti > alo && bestj > blo && isbjunk(b[bestj - 1]) && a[besti - 1] == b[bestj - 1]) { 210 | besti--; 211 | bestj--; 212 | bestsize++; 213 | } 214 | 215 | while (besti + bestsize < ahi && bestj + bestsize < bhi && isbjunk(b[bestj + bestsize]) && 216 | a[besti + bestsize] == b[bestj + bestsize]) { 217 | bestsize++; 218 | } 219 | 220 | return [besti, bestj, bestsize]; 221 | } 222 | 223 | this.get_matching_blocks = function () { 224 | if (this.matching_blocks != null) return this.matching_blocks; 225 | var la = this.a.length; 226 | var lb = this.b.length; 227 | 228 | var queue = [[0, la, 0, lb]]; 229 | var matching_blocks = []; 230 | var alo, ahi, blo, bhi, qi, i, j, k, x; 231 | while (queue.length) { 232 | qi = queue.pop(); 233 | alo = qi[0]; 234 | ahi = qi[1]; 235 | blo = qi[2]; 236 | bhi = qi[3]; 237 | x = this.find_longest_match(alo, ahi, blo, bhi); 238 | i = x[0]; 239 | j = x[1]; 240 | k = x[2]; 241 | 242 | if (k) { 243 | matching_blocks.push(x); 244 | if (alo < i && blo < j) 245 | queue.push([alo, i, blo, j]); 246 | if (i+k < ahi && j+k < bhi) 247 | queue.push([i + k, ahi, j + k, bhi]); 248 | } 249 | } 250 | 251 | matching_blocks.sort(difflib.__ntuplecomp); 252 | 253 | var i1 = 0, j1 = 0, k1 = 0, block = 0; 254 | var i2, j2, k2; 255 | var non_adjacent = []; 256 | for (var idx in matching_blocks) { 257 | if (matching_blocks.hasOwnProperty(idx)) { 258 | block = matching_blocks[idx]; 259 | i2 = block[0]; 260 | j2 = block[1]; 261 | k2 = block[2]; 262 | if (i1 + k1 == i2 && j1 + k1 == j2) { 263 | k1 += k2; 264 | } else { 265 | if (k1) non_adjacent.push([i1, j1, k1]); 266 | i1 = i2; 267 | j1 = j2; 268 | k1 = k2; 269 | } 270 | } 271 | } 272 | 273 | if (k1) non_adjacent.push([i1, j1, k1]); 274 | 275 | non_adjacent.push([la, lb, 0]); 276 | this.matching_blocks = non_adjacent; 277 | return this.matching_blocks; 278 | } 279 | 280 | this.get_opcodes = function () { 281 | if (this.opcodes != null) return this.opcodes; 282 | var i = 0; 283 | var j = 0; 284 | var answer = []; 285 | this.opcodes = answer; 286 | var block, ai, bj, size, tag; 287 | var blocks = this.get_matching_blocks(); 288 | for (var idx in blocks) { 289 | if (blocks.hasOwnProperty(idx)) { 290 | block = blocks[idx]; 291 | ai = block[0]; 292 | bj = block[1]; 293 | size = block[2]; 294 | tag = ''; 295 | if (i < ai && j < bj) { 296 | tag = 'replace'; 297 | } else if (i < ai) { 298 | tag = 'delete'; 299 | } else if (j < bj) { 300 | tag = 'insert'; 301 | } 302 | if (tag) answer.push([tag, i, ai, j, bj]); 303 | i = ai + size; 304 | j = bj + size; 305 | 306 | if (size) answer.push(['equal', ai, i, bj, j]); 307 | } 308 | } 309 | 310 | return answer; 311 | } 312 | 313 | // this is a generator function in the python lib, which of course is not supported in javascript 314 | // the reimplementation builds up the grouped opcodes into a list in their entirety and returns that. 315 | this.get_grouped_opcodes = function (n) { 316 | if (!n) n = 3; 317 | var codes = this.get_opcodes(); 318 | if (!codes) codes = [["equal", 0, 1, 0, 1]]; 319 | var code, tag, i1, i2, j1, j2; 320 | if (codes[0][0] == 'equal') { 321 | code = codes[0]; 322 | tag = code[0]; 323 | i1 = code[1]; 324 | i2 = code[2]; 325 | j1 = code[3]; 326 | j2 = code[4]; 327 | codes[0] = [tag, Math.max(i1, i2 - n), i2, Math.max(j1, j2 - n), j2]; 328 | } 329 | if (codes[codes.length - 1][0] == 'equal') { 330 | code = codes[codes.length - 1]; 331 | tag = code[0]; 332 | i1 = code[1]; 333 | i2 = code[2]; 334 | j1 = code[3]; 335 | j2 = code[4]; 336 | codes[codes.length - 1] = [tag, i1, Math.min(i2, i1 + n), j1, Math.min(j2, j1 + n)]; 337 | } 338 | 339 | var nn = n + n; 340 | var group = []; 341 | var groups = []; 342 | for (var idx in codes) { 343 | if (codes.hasOwnProperty(idx)) { 344 | code = codes[idx]; 345 | tag = code[0]; 346 | i1 = code[1]; 347 | i2 = code[2]; 348 | j1 = code[3]; 349 | j2 = code[4]; 350 | if (tag == 'equal' && i2 - i1 > nn) { 351 | group.push([tag, i1, Math.min(i2, i1 + n), j1, Math.min(j2, j1 + n)]); 352 | groups.push(group); 353 | group = []; 354 | i1 = Math.max(i1, i2-n); 355 | j1 = Math.max(j1, j2-n); 356 | } 357 | 358 | group.push([tag, i1, i2, j1, j2]); 359 | } 360 | } 361 | 362 | if (group && !(group.length == 1 && group[0][0] == 'equal')) groups.push(group) 363 | 364 | return groups; 365 | } 366 | 367 | this.ratio = function () { 368 | matches = difflib.__reduce( 369 | function (sum, triple) { return sum + triple[triple.length - 1]; }, 370 | this.get_matching_blocks(), 0); 371 | return difflib.__calculate_ratio(matches, this.a.length + this.b.length); 372 | } 373 | 374 | this.quick_ratio = function () { 375 | var fullbcount, elt; 376 | if (this.fullbcount == null) { 377 | this.fullbcount = fullbcount = {}; 378 | for (var i = 0; i < this.b.length; i++) { 379 | elt = this.b[i]; 380 | fullbcount[elt] = difflib.__dictget(fullbcount, elt, 0) + 1; 381 | } 382 | } 383 | fullbcount = this.fullbcount; 384 | 385 | var avail = {}; 386 | var availhas = difflib.__isindict(avail); 387 | var matches = numb = 0; 388 | for (var i = 0; i < this.a.length; i++) { 389 | elt = this.a[i]; 390 | if (availhas(elt)) { 391 | numb = avail[elt]; 392 | } else { 393 | numb = difflib.__dictget(fullbcount, elt, 0); 394 | } 395 | avail[elt] = numb - 1; 396 | if (numb > 0) matches++; 397 | } 398 | 399 | return difflib.__calculate_ratio(matches, this.a.length + this.b.length); 400 | } 401 | 402 | this.real_quick_ratio = function () { 403 | var la = this.a.length; 404 | var lb = this.b.length; 405 | return _calculate_ratio(Math.min(la, lb), la + lb); 406 | } 407 | 408 | this.isjunk = isjunk ? isjunk : difflib.defaultJunkFunction; 409 | this.a = this.b = null; 410 | this.set_seqs(a, b); 411 | } 412 | }; 413 | 414 | -------------------------------------------------------------------------------- /app/assets/img/jsoneditor-icons-bfab7b16cb24ac5e2856e2b172f47fe8.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | JSON Editor Icons 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | JSON Editor Icons 27 | 28 | 29 | 30 | 32 | 56 | 60 | 61 | 62 | 64 | 71 | 78 | 85 | 92 | 99 | 102 | 109 | 116 | 117 | 121 | 128 | 135 | 136 | 143 | 150 | 157 | 159 | 166 | 173 | 180 | 181 | 184 | 191 | 198 | 205 | 206 | 213 | 219 | 225 | 232 | 237 | 242 | 249 | 255 | 260 | 267 | 273 | 279 | 280 | 287 | 294 | 301 | 308 | 315 | 319 | 326 | 333 | 334 | 338 | 345 | 352 | 353 | 360 | 367 | 374 | 377 | 384 | 391 | 398 | 399 | 402 | 409 | 416 | 423 | 424 | 431 | 437 | 443 | 450 | 455 | 460 | 467 | 473 | 478 | 485 | 491 | 497 | 504 | 511 | 518 | 525 | 532 | 539 | 546 | 552 | 560 | 566 | 572 | 579 | 587 | 595 | 602 | 609 | 616 | 623 | 630 | 637 | 644 | 651 | 657 | 665 | 671 | 677 | 684 | 692 | 700 | 707 | 714 | 721 | 728 | 735 | 742 | 749 | 756 | 761 | 766 | 771 | 777 | 783 | 788 | 793 | 798 | 803 | 808 | 824 | 841 | 858 | 875 | 881 | 887 | 893 | 899 | 900 | --------------------------------------------------------------------------------