├── menus ├── app.js ├── edit.js ├── file.js ├── help.js ├── view.js └── window.js ├── .gitignore ├── js ├── autoindent.js ├── importer.js ├── history.js ├── exporter.js ├── tags.js ├── autofill.js ├── ranui.js ├── clipboard.js ├── utils.js ├── parseHTML.js ├── mouse.js ├── keydown.js ├── selection.js ├── editing.js ├── html.json └── jquery-3.1.1.min.js ├── package.json ├── LICENSE.md ├── index.html ├── README.md ├── filemanager.js ├── main.js └── css └── ranui.css /menus/app.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /menus/edit.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /menus/file.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /menus/help.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /menus/view.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /menus/window.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /js/autoindent.js: -------------------------------------------------------------------------------- 1 | //TODO 2 | // get reference to topmost node 3 | // get next rows until there's a ro that's less indented than ref 4 | // make a group from ref until found row 5 | // make groups until end of rows 6 | // for each group: 7 | // get indentation of topmost row 8 | // substract that much indentation from each row in group 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ranui", 3 | "productName": "Ranui", 4 | "author": "Ville Vanninen", 5 | "version": "0.0.0", 6 | "main": "main.js", 7 | "scripts": { 8 | "start": "electron ." 9 | }, 10 | "devDependencies": { 11 | "devtron": "^1.4.0", 12 | "electron": "^1.7.8", 13 | "electron-document-manager": "^0.1.0", 14 | "gumbo-parser": "^0.3.0", 15 | "jquery": "^3.2.1", 16 | "pug": "^2.0.0-rc.4" 17 | }, 18 | "dependencies": { 19 | "electron-window-manager": "^1.0.4" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /js/importer.js: -------------------------------------------------------------------------------- 1 | module.exports = {renderDoc, renderRows, renderProps} 2 | 3 | function renderDoc (doc) { 4 | return rows(doc.rows) 5 | } 6 | 7 | function renderRows (rows) { 8 | let out = '' 9 | for (var i = 0; i < rows.length; i++) { 10 | let row = rows[i] 11 | let props = importer.renderProps(row.props) 12 | out += `${props}` 13 | } 14 | return out 15 | } 16 | 17 | function renderProps (props) { 18 | let out = '' 19 | for (let i = 0; i < props.length; i++) { 20 | let prop = props[i] 21 | let type = prop.type 22 | let text = prop.text 23 | out += `<${type} text="${text}" class="new">${text}` 24 | } 25 | return out 26 | } 27 | 28 | -------------------------------------------------------------------------------- /js/history.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | function History(initial) { 4 | let index = 0 5 | let stack = [initial] 6 | let isModified = false 7 | 8 | function modified() { 9 | return isModified 10 | } 11 | 12 | function update() { 13 | let item = $('doc')[0].outerHTML 14 | stack[index] = item 15 | isModified = true 16 | } 17 | 18 | function add() { 19 | let item = $('doc')[0].outerHTML 20 | if (item !== stack[index]) { 21 | index = index + 1 22 | stack.splice(index) 23 | stack.push(item) 24 | isModified = true 25 | } 26 | } 27 | 28 | //TODO: sometimes you can undo the document into oblivion, something's wrong 29 | function undo () { 30 | if (scope === 'editing') { 31 | document.execCommand('undo', '', null) 32 | } else if (index > 0) { 33 | index = index - 1 34 | $('doc').replaceWith(stack[index]) 35 | } 36 | } 37 | 38 | function redo () { 39 | if (scope === 'editing') { 40 | document.execCommand('redo', '', null) 41 | } else if (index < stack.length - 1) { 42 | index = index + 1 43 | $('doc').replaceWith(stack[index]) 44 | } 45 | } 46 | 47 | return {add, update, undo, redo, modified} 48 | } 49 | 50 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | In plain English: 2 | 3 | Spread the idea. Make cool stuff. Give credit where credit is due. 4 | 5 | ---- 6 | 7 | Official licence: 8 | 9 | MIT License 10 | 11 | Copyright (c) 2017 Ville Vanninen 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | -------------------------------------------------------------------------------- /js/exporter.js: -------------------------------------------------------------------------------- 1 | //TODO: this should take in ranui dom or html and return plain html 2 | //This needs to also export attributes without a tag and plain text, because this will be used for copy paste too. 3 | 4 | const pug = require.main.require('pug') 5 | 6 | module.exports = {domToPug, pugToHTML} 7 | 8 | function pugToHTML (string) { 9 | return pug.render(string) 10 | } 11 | 12 | function domToPug (rows) { 13 | rows = rows || $('doc').children('selector') 14 | let out = '' 15 | 16 | rows.each(function(i, el) { 17 | let row = $(el) 18 | let rowType = row.attr('type') 19 | let tabs = parseInt(row.attr('tabs')) 20 | let spaces = ' '.repeat(tabs) 21 | 22 | out += spaces 23 | if (row.hasClass('com')) { 24 | out += '//' 25 | } 26 | 27 | if (rowType === 'tag') { 28 | row.children().each(function(i, el) { 29 | let token = $(el) 30 | let tokenType = token[0].tagName 31 | if (tokenType === 'TAG') { 32 | out += token.text() + '(' 33 | } else if (tokenType === 'PROP') { 34 | out += token.text() 35 | } else if (tokenType === 'VAL') { 36 | out += '=\'' + token.text() + '\' ' 37 | } 38 | }) 39 | out += ')' 40 | } else if (rowType === 'txt') { 41 | out += '| ' + row.children().first().html() 42 | } 43 | 44 | out += '\n' 45 | 46 | }) 47 | 48 | return out 49 | } 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Ranui 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | dividrules 20 | dividuniqueclasssomething 21 | plaintext 22 | plaintext 23 | divclasssomething 24 | divclasstext 25 | div 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Foolproof HTML editor prototype 2 | 3 | The grand idea is to build a native html editor (so not a general text editor that's customized for html, but purpose built for only html) that can handle any html, even with random template code in the middle. Then hopefully expand the editing model to support css, then json and others. 4 | 5 | I wrote a little article on the rationale here 6 | 7 | There's also this, more of a note to self type explainer on how the rows are handled. 8 | 9 | ## Contributing 10 | 11 | My prototype code is a mess, so we're trying to get some proper app arcitecture done. I set up a Gitter chat for planning and sharing stuff. The chat is the best way to contribute right now. https://gitter.im/flprf/Lobby 12 | 13 | The prototype is good enough for demos, but not really usable yet. It's Mac only for now, mainly because doing good multi platform keyboard support would take time off from making it actually work. Contributions welcome! 14 | 15 | ## Running 16 | 17 | 18 | 1. Clone the repo 19 | 2. Run `npm install` 20 | 3. Run `npm start` 21 | 22 | 23 | - Type lowercase to create elements 24 | - Type uppercase to create text 25 | - Press space to add attributes 26 | - Press enter to edit what you have selected. 27 | 28 | You can find most actions in js/keydown.js. Some actions come through the app shell from menu items, the ones you'd expect like undo/redo, saving (TODO), copy & paste etc. The interactions are modelled pretty closely after Sublime Text. I'm hoping to make the UI feel instantly familiar and productive to anyone who's ever written HTML in a text editor. 29 | 30 | Probably needless to say, but expect buggy behaviour. Most stuff seems to be working fine, but that's just me using it. 31 | 32 | Built with [Electron](http://electron.atom.io). 33 | -------------------------------------------------------------------------------- /js/tags.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | //TODO: order these so that similar tags are ordered by how common they are, so it's mostly alphabetical, but you'll get what you want when you type just a few letters 4 | 5 | 6 | const tags = [ 7 | 'a', 8 | 'article', 9 | 'aside', 10 | 'abbr', 11 | 'address', 12 | 'area', 13 | 'audio', 14 | 'b', 15 | 'base', 16 | 'bdi', 17 | 'bdo', 18 | 'blockquote', 19 | 'body', 20 | 'br', 21 | 'button', 22 | 'canvas', 23 | 'caption', 24 | 'cite', 25 | 'code', 26 | 'col', 27 | 'colgroup', 28 | 'command', 29 | 'div', 30 | 'datalist', 31 | 'dd', 32 | 'del', 33 | 'details', 34 | 'dfn', 35 | 'dl', 36 | 'dt', 37 | 'em', 38 | 'embed', 39 | 'fieldset', 40 | 'figcaption', 41 | 'figure', 42 | 'footer', 43 | 'form', 44 | 'h1', 45 | 'h2', 46 | 'h3', 47 | 'h4', 48 | 'h5', 49 | 'h6', 50 | 'head', 51 | 'header', 52 | 'hgroup', 53 | 'hr', 54 | 'html', 55 | 'i', 56 | 'iframe', 57 | 'img', 58 | 'input', 59 | 'ins', 60 | 'kbd', 61 | 'keygen', 62 | 'label', 63 | 'legend', 64 | 'li', 65 | 'link', 66 | 'map', 67 | 'mark', 68 | 'menu', 69 | 'meta', 70 | 'meter', 71 | 'nav', 72 | 'noscript', 73 | 'object', 74 | 'ol', 75 | 'optgroup', 76 | 'option', 77 | 'output', 78 | 'p', 79 | 'param', 80 | 'pre', 81 | 'progress', 82 | 'q', 83 | 'rp', 84 | 'rt', 85 | 'ruby', 86 | 's', 87 | 'samp', 88 | 'script', 89 | 'section', 90 | 'select', 91 | 'small', 92 | 'source', 93 | 'span', 94 | 'strong', 95 | 'style', 96 | 'sub', 97 | 'summary', 98 | 'sup', 99 | 'table', 100 | 'tbody', 101 | 'td', 102 | 'textarea', 103 | 'tfoot', 104 | 'th', 105 | 'thead', 106 | 'time', 107 | 'title', 108 | 'tr', 109 | 'track', 110 | 'u', 111 | 'ul', 112 | 'var', 113 | 'video', 114 | 'wbr', 115 | ] 116 | 117 | 118 | module.exports = tags 119 | -------------------------------------------------------------------------------- /js/autofill.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | //TODO: eventually this module should use a fuzzy search and show an autocomplete list. 4 | 5 | //Chrome has rudimentary datalist element support, but it sucks, so we need our own implementation 6 | 7 | module.exports = {prevent, fill, processNode} 8 | 9 | let isPrevented = false 10 | 11 | function prevent () { 12 | isPrevented = true 13 | } 14 | 15 | function fill (node) { 16 | if (isPrevented) { 17 | isPrevented = false 18 | } else { 19 | processNode(node) 20 | } 21 | } 22 | 23 | function processNode (node) { 24 | const textNode = node.childNodes[0] 25 | const text = node.innerText 26 | let autoFillValues = [] 27 | let autoFilledText 28 | 29 | //Different behaviour for different types of cases 30 | //autofill could be super smart based on context, like only adding li's inside ul & ol elements, but too aggressive smarts get irritating really quickly, so not doing it for now. 31 | if (node.tagName === 'TAG') { 32 | autoFillValues = tags 33 | } else { 34 | //TODO: add autofill for props based on tag and values based on prop 35 | return //cancel autofill if we're not in a node where there's something to autofill 36 | } 37 | 38 | //If we have an exact match, don't bother with autofill, it's all good 39 | if (!autoFillValues[text]) { 40 | autoFilledText = autoFillValues.find((item)=>{ 41 | return item.indexOf(text) === 0 42 | }) 43 | 44 | if (autoFilledText) { 45 | const selStart = text.length 46 | const selEnd = autoFilledText.length 47 | node.innerText = autoFilledText 48 | const range = document.createRange() 49 | range.setStart(textNode, selStart) 50 | range.setEnd(textNode, selEnd) 51 | const sel = window.getSelection() 52 | sel.removeAllRanges() 53 | sel.addRange(range) 54 | } 55 | } 56 | } 57 | 58 | function cycle() { 59 | //TODO: up/down arrows should advance autofill to the prev/next match, so you can type 'ar' and get 'area', then 'article' 60 | } 61 | 62 | -------------------------------------------------------------------------------- /js/ranui.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const {ipcRenderer} = require('electron') 4 | const devtron = require('devtron') //DEBUG, remove for compiled app 5 | devtron.install() 6 | 7 | const autofill = require.main.require('./js/autofill.js') 8 | const tags = require.main.require('./js/tags.js') //list of html tags 9 | const importer = require.main.require('./js/importer.js') 10 | importer.parseHTML = require.main.require('./js/parseHTML.js') 11 | const exporter = require.main.require('./js/exporter.js') 12 | const history = new History() 13 | var scope = '' 14 | 15 | //DEBUG 16 | // console.log(exporter.domToPug($('doc'))) 17 | // console.log(exporter.pugToHTML(exporter.domToPug($('doc')))) 18 | 19 | //ranui.js is in global scope, so anything required here will be available through all main scripts. Yeah, should be encapsulated and all that. 20 | 21 | 22 | //I could do most of the editing inputs via OS level menus and their accelerators. Menu items can be hidden, so maybe some menu items wouldn't need to be visible. Most of the could be in the menus too for discoverability and so you could overwrite the shortcuts easier without making custom app level configs. 23 | 24 | 25 | window.addEventListener('keydown', keydown) 26 | window.addEventListener('input', e=>{ 27 | if (scope === 'editing') { 28 | input(e.target) 29 | } 30 | }) 31 | window.addEventListener('blur', e=>{ 32 | if (scope === 'editing') { 33 | //Not sure if window blur should escape editing mode, but that's what happens in devtools too. It kinda feels more solid and predictable if you always have a 'solid' selection when returning to the app 34 | commitEdit() 35 | } 36 | }) 37 | 38 | //Copy & paste 39 | //TODO: copypaste events seem to work great. Implement functions for setting/getting data and data parsing via http://electron.atom.io/docs/api/clipboard 40 | window.addEventListener('beforecut', beforeCut) 41 | window.addEventListener('beforecopy', beforeCopy) 42 | window.addEventListener('cut', cut) 43 | window.addEventListener('copy', copy) 44 | window.addEventListener('paste', paste) 45 | 46 | 47 | //Mouse 48 | window.addEventListener('dblclick', e=>{history.update();startEdit(e)}) 49 | window.addEventListener('mousedown', e=>mouseDown(e)) 50 | window.addEventListener('mousemove', throttle(mouseMove, 16)) //Only running mousemove at max 60fps 51 | window.addEventListener('mouseup', e=>mouseUp(e)) 52 | 53 | 54 | //Undo Redo 55 | ipcRenderer.on('undo', history.undo) 56 | ipcRenderer.on('redo', history.redo) 57 | 58 | 59 | //Files 60 | ipcRenderer.on('new', e=>{}) 61 | ipcRenderer.on('open', e=>{}) 62 | ipcRenderer.on('save', e=>{}) 63 | ipcRenderer.on('saveAs', e=>{}) 64 | window.addEventListener('beforeunload', e=>{ 65 | if (history.modified()) { 66 | //confirm('Save changes?') //TODO: show proper [don't save / cancel / save] dialog 67 | } 68 | }) 69 | -------------------------------------------------------------------------------- /filemanager.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { 4 | ipcMain, 5 | dialog, 6 | BrowserWindow 7 | } = require('electron') 8 | 9 | const path = require('path') 10 | const url = require('url') 11 | 12 | 13 | //TODO: use macOS native tabs for documents, don't implement any custom tabs. Not enabled in Electron yet, but here's the issue: https://github.com/electron/electron/issues/6124 Tabs have been intentionally disabled, so it shouldn't be hard to add back tab support. 14 | 15 | 16 | const windowList = [] 17 | 18 | function newFile (menuItem, browserWindow, event) { 19 | let newWin = new BrowserWindow({ 20 | width: 1000, 21 | height: 700, 22 | title: 'Untitled', 23 | //titleBarStyle: 'hidden', 24 | webPreferences: { 25 | scrollBounce: true 26 | }, 27 | backgroundColor: '#272822' //Bg color of doc, so even if loading takes a while, the window won't flash white 28 | }) 29 | newWin.loadURL(url.format({ 30 | pathname: path.join(__dirname, 'index.html'), 31 | protocol: 'file:', 32 | slashes: true 33 | })) 34 | //TODO: win.setRepresentedFilename('filename.html') //this should happen on file open 35 | //TODO: win.setDocumentEdited(true) if not true, this should happen on any edit command in the render process 36 | newWin.webContents.openDevTools() //for debugging 37 | 38 | windowList.push(newWin) 39 | 40 | 41 | newWin.on('closed', ()=> { 42 | windowList.splice(windowList.indexOf(newWin), 1) 43 | newWin = null 44 | }) 45 | } 46 | 47 | //TODO: put window management and file open/save stuff in its own module 48 | function open (menuItem, browserWindow, event) { 49 | dialog.showOpenDialog({properties: ['openFile','multiSelections']}, paths=>{ 50 | for (let path in paths) { 51 | console.log(path) 52 | } 53 | }) 54 | //TODO: open file? spawn new window, give that new window the path for opening or maybe text of the file, parse text inside the new window. Do it this way so any problem in parsing will only ever affect the one window. 55 | //newWindow() needs to take path as a parameter? 56 | } 57 | function close (menuItem, browserWindow, event) { 58 | browserWindow.close() 59 | //TODO: if file contents dirty, call saveFile for browserwindow 60 | } 61 | 62 | function save (menuItem, browserWindow, event) { 63 | //TODO: get frontmost window, parse contents to html, show save dialog 64 | //maybe take in filename as a parameter or something, need to handle saveAs, saveAll too 65 | //maybe this should get the browserwindow as a parameter? so it would be easy to use this with saveAll 66 | 67 | } 68 | function saveAs (menuItem, browserWindow, event) { 69 | dialog.showSaveDialog() 70 | //etc... 71 | } 72 | function saveAll (menuItem, browserWindow, event) { 73 | //TODO: call saveFile for each browserwindow 74 | } 75 | 76 | 77 | 78 | module.exports = {newFile, open, close, save, saveAs, saveAll} 79 | -------------------------------------------------------------------------------- /js/clipboard.js: -------------------------------------------------------------------------------- 1 | function beforeCopy (e) { 2 | console.log('beforeCopy') 3 | } 4 | function copy (e) { 5 | e.preventDefault() 6 | 7 | if (scope === '') { 8 | let selRows = $('.hilite') 9 | if (selRows.length) { 10 | e.preventDefault() 11 | /*TODO: 12 | - get .sel 13 | - do the same as drag & drop does when starting to drag 14 | - gather elements 15 | - normalise tabs 16 | - use exporter.domToPug to render to pug 17 | - install pug via npm 18 | - use pug to parse exporters output to plain html 19 | */ 20 | let renderedPug = exporter.domToPug(selRows) 21 | let renderedHTML = exporter.pugToHTML(renderedPug) 22 | console.log(renderedPug) 23 | console.log(renderedHTML) 24 | 25 | //Needs to be text/plain so pasting works in text editors etc. We could use custom data for internal copy & paste, but I think the system should be robus enough that copy & paste works with only plain html code. 26 | console.log(renderedHTML) 27 | e.clipboardData.setData('text/plain', renderedHTML) 28 | return 29 | } 30 | 31 | let sel = $('.sel') 32 | if (sel.length) { 33 | console.log(sel) 34 | //TODO: combine props to a single row and parse them to html 35 | } 36 | } else if (scope === 'editing') { 37 | //Let's only copy plain text when editing a prop 38 | let text = window.getSelection().toString() 39 | e.clipboardData.setData('text/plain', text) 40 | } 41 | } 42 | 43 | function beforeCut (e) { 44 | console.log('beforeCut') 45 | } 46 | function cut (e) { 47 | if (scope === '') { 48 | e.preventDefault() 49 | copy(e) 50 | del() 51 | } else if (scope === 'editing') { 52 | //TODO: cut needs to copy plain text in editing mode, no frigging rich text 53 | } 54 | } 55 | 56 | function paste (e) { 57 | 58 | //TODO: regular paste needs to prevent contenteditable from pasting styles, or clean up html after paste 59 | 60 | if (scope === '') { 61 | 62 | history.update() 63 | 64 | e.preventDefault() 65 | 66 | //TODO: this needs the exact same smarts for tab handling as drag & drop 67 | 68 | const cur = $('.cur') 69 | const clip = event.clipboardData.getData('text/plain') 70 | const data = importer.parseHTML(clip) 71 | 72 | if (data.type === 'props') { 73 | //Paste in like 74 | let dom = importer.renderProps(data.props) 75 | cur.after(dom) 76 | } else if (data.type === 'rows') { 77 | //Paste in like
dsa
78 | let dom = importer.renderRows(data.rows) 79 | //TODO: needs tab smarts here too 80 | cur.parent().after(dom) 81 | } 82 | 83 | cur.removeClass('cur') 84 | let newSel = $('.new') 85 | if (data.type === 'props') { 86 | newSel.last().addClass('cur') 87 | } else if (data.type === 'rows') { 88 | newSel.last().parent().children().first().addClass('cur') 89 | } 90 | newSel.removeClass('new') 91 | select(newSel) 92 | 93 | history.add() 94 | 95 | } else if (scope === 'editing') { 96 | 97 | e.preventDefault() 98 | let text = e.clipboardData.getData("text/plain") 99 | document.execCommand("insertHTML", false, text) 100 | 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /js/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | //Ranui utils 4 | function getRowChildren(node) { 5 | //Finds rows that are more indented than the given row, until encounters a row with the same indentation or less. Does not select anything by itself, more of a utility function. 6 | let row = $(node) 7 | let tabs = parseInt(row.attr('tabs')) 8 | let children = $() 9 | row.nextAll().each((i, el)=>{ 10 | let childTabs = parseInt($(el).attr('tabs')) 11 | if (childTabs > tabs) { 12 | children = children.add(el) 13 | } else { 14 | return false 15 | } 16 | }) 17 | return children 18 | } 19 | 20 | 21 | 22 | 23 | 24 | //Misc js utility stuff that's not directly related to editing. 25 | 26 | jQuery.fn.selectText = function(){ 27 | const element = this[0] 28 | if (element) { 29 | const selection = window.getSelection() 30 | const range = document.createRange() 31 | range.selectNodeContents(element) 32 | selection.removeAllRanges() 33 | selection.addRange(range) 34 | } 35 | } 36 | jQuery.fn.selectEnd = function(){ 37 | const element = this[0] 38 | if (element) { 39 | const range = document.createRange() 40 | const selection = window.getSelection() 41 | range.selectNodeContents(element) 42 | range.collapse(false) 43 | selection.removeAllRanges() 44 | selection.addRange(range) 45 | } 46 | } 47 | 48 | 49 | 50 | //mod returns modifier keys in exclusive form, so you don't need to do e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey, just check if only shiftKey is pressed 51 | function modkeys (e, key) { 52 | let keys = { 53 | shift: e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey, 54 | alt: !e.shiftKey && e.altKey && !e.ctrlKey && !e.metaKey, 55 | ctrl: !e.shiftKey && !e.altKey && e.ctrlKey && !e.metaKey, 56 | cmd: !e.shiftKey && !e.altKey && !e.ctrlKey && e.metaKey, 57 | any: e.shiftKey || e.altKey || e.ctrlKey || e.metaKey, 58 | 'cmdShift': e.metaKey && e.shiftKey && !e.altKey && !e.ctrlKey, 59 | 'ctrlShift': e.ctrlKey && e.shiftKey && !e.altKey && !e.metaKey, 60 | 'ctrlCmd': e.metaKey && e.ctrlKey && !e.altKey && !e.shiftKey, 61 | } 62 | keys.none = !keys.any 63 | keys['shiftCmd'] = keys['cmdShift'] 64 | keys['shiftCtrl'] = keys['ctrlShift'] 65 | keys['ctrlCmd'] = keys['cmdCtrl'] 66 | 67 | return keys 68 | //if (keys[key]) {return true} 69 | //else {return false} 70 | } 71 | 72 | 73 | 74 | //create a throttled instance of a function 75 | //throttledFunction = throttle(someFunctionHere) 76 | //use it 77 | //addEventListener(throttledFunction) or e=>throttled(e, arg, arg, etc) 78 | function throttle (fn, time, scope) { 79 | time = time || 250 80 | var last 81 | var deferTimer 82 | 83 | //create a scope with throttle, then return the throttled function that has access to the throttle scope, so it can set last & timer vars 84 | return function () { 85 | let context = scope || this 86 | 87 | let now = +new Date 88 | let args = arguments 89 | 90 | if (last && now < last + time) { 91 | clearTimeout(deferTimer) 92 | deferTimer = setTimeout(function () { 93 | last = now 94 | fn.apply(context, args) 95 | }, time) 96 | } else { 97 | last = now 98 | fn.apply(context, args) 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { 4 | app, 5 | shell, 6 | Menu, 7 | BrowserWindow, 8 | MenuItem, 9 | ipcMain, 10 | } = require('electron') 11 | 12 | const fileManager = require('./filemanager.js') 13 | 14 | 15 | 16 | //TODO: track frontmost window and send menu messages there? 17 | 18 | function createMenu() { 19 | const menuTemplate = [ 20 | { 21 | label: app.name, 22 | submenu: [ 23 | { 24 | role: 'about', 25 | }, 26 | { 27 | type: 'separator' 28 | }, 29 | { 30 | label: 'Services', 31 | role: 'services', 32 | submenu: [] 33 | }, 34 | { 35 | type: 'separator' 36 | }, 37 | { 38 | role: 'hide' 39 | }, 40 | { 41 | role: 'hideothers' 42 | }, 43 | { 44 | role: 'unhide' 45 | }, 46 | { 47 | type: 'separator' 48 | }, 49 | { 50 | label: 'Quit', 51 | accelerator: 'Command+Q', 52 | click: app.quit 53 | }, 54 | ] 55 | }, 56 | { 57 | label: 'File', 58 | submenu: [ 59 | { 60 | label: 'New', 61 | accelerator: 'Command+N', 62 | click: fileManager.newFile 63 | }, 64 | { 65 | label: 'Open…', 66 | accelerator: 'Command+O', 67 | click: fileManager.open 68 | }, 69 | { 70 | label: 'Save', 71 | accelerator: 'Command+S', 72 | click: fileManager.save //TODO: fix saveAs etc. 73 | }, 74 | { 75 | label: 'Save As…', 76 | accelerator: 'Command+Shift+S', 77 | click: fileManager.saveAs 78 | }, 79 | { 80 | label: 'Save All', 81 | accelerator: 'Command+Option+S', 82 | click: fileManager.saveAll 83 | }, 84 | { 85 | type: 'separator' 86 | }, 87 | { 88 | label: 'Close File', 89 | accelerator: 'Command+W', 90 | click: fileManager.close 91 | }, 92 | ] 93 | }, 94 | { 95 | label: 'Edit', 96 | submenu: [ 97 | { 98 | label: 'Undo', 99 | accelerator: 'CmdOrCtrl+Z', 100 | //role: 'undo', 101 | click: (menuItem, browserWindow, event)=>{ 102 | browserWindow.webContents.send('undo') 103 | } 104 | }, 105 | { 106 | label: 'Redo', 107 | accelerator: 'CmdOrCtrl+Shift+Z', 108 | //role: 'redo', 109 | click: (menuItem, browserWindow, event)=>{ 110 | browserWindow.webContents.send('redo') 111 | } 112 | }, 113 | { 114 | type: 'separator' 115 | }, 116 | { 117 | role: 'cut' 118 | }, 119 | { 120 | role: 'copy' 121 | }, 122 | { 123 | role: 'paste' 124 | }, 125 | { 126 | role: 'delete' 127 | }, 128 | { 129 | role: 'selectall' 130 | }, 131 | { 132 | label: 'Speech', 133 | submenu: [ 134 | { 135 | role: 'startspeaking' 136 | }, 137 | { 138 | role: 'stopspeaking' 139 | } 140 | ] 141 | } 142 | ] 143 | }, 144 | { 145 | label: 'View', 146 | submenu: [ 147 | { 148 | role: 'reload' 149 | }, 150 | { 151 | role: 'toggledevtools' 152 | }, 153 | { 154 | type: 'separator' 155 | }, 156 | { 157 | role: 'resetzoom' 158 | }, 159 | { 160 | role: 'zoomin' 161 | }, 162 | { 163 | role: 'zoomout' 164 | }, 165 | { 166 | type: 'separator' 167 | }, 168 | { 169 | role: 'togglefullscreen' 170 | }, 171 | ] 172 | }, 173 | { 174 | label: 'Window', 175 | role: 'window', 176 | submenu: [ 177 | { 178 | role: 'minimize' 179 | }, 180 | { 181 | role: 'zoom' 182 | }, 183 | { 184 | type: 'separator' 185 | }, 186 | { 187 | role: 'front' 188 | } 189 | ] 190 | }, 191 | { 192 | label: 'Help', 193 | role: 'help', 194 | submenu: [ 195 | { 196 | label: 'Ask @sakamies on Twitter', 197 | click: ()=>{ 198 | shell.openExternal('https://twitter.com/sakamies') 199 | } 200 | }, 201 | ] 202 | }, 203 | ] 204 | const menu = Menu.buildFromTemplate(menuTemplate) 205 | Menu.setApplicationMenu(menu) 206 | } 207 | 208 | function createDockMenu() { 209 | const menuTemplate = [ 210 | { 211 | label: 'New File', 212 | click: fileManager.newFile 213 | }, 214 | ] 215 | const menu = Menu.buildFromTemplate(menuTemplate) 216 | app.dock.setMenu(menu) 217 | } 218 | 219 | app.on('ready', ()=>{ 220 | createMenu() 221 | createDockMenu() 222 | fileManager.newFile() 223 | }) 224 | app.on('window-all-closed', ()=>{ 225 | if (process.platform !== 'darwin') 226 | app.quit() 227 | }) 228 | app.on('activate', ()=> { 229 | if (BrowserWindow.getAllWindows().length === 0) { 230 | fileManager.newFile() 231 | } 232 | }) 233 | -------------------------------------------------------------------------------- /css/ranui.css: -------------------------------------------------------------------------------- 1 | /* 2 | $light: #f8f8f2; 3 | $lightgray: #828380; 4 | $gray1: #75715e; 5 | $gray2: #49483E; 6 | $gray3: #3e3d32; 7 | $dark: #272822; 8 | $red: #f92772; 9 | $purple: #be84ff; 10 | $green: #a6e22d; 11 | $yellow: #e6db74; 12 | $orange: #f6aa10; 13 | $blue: #66d9ef; 14 | 15 | $linkBlue: hsl(198, 49%, 46%); 16 | $hiGreen: hsl(70, 100%, 50%); 17 | */ 18 | 19 | /*TODO: these styles have been made with a non retina screen, check with retina too! */ 20 | 21 | * { 22 | box-sizing: border-box; 23 | margin: 0; 24 | padding: 0; 25 | font: inherit; 26 | line-height: inherit; 27 | color: inherit; 28 | //-webkit-font-smoothing: antialiased; 29 | -webkit-user-select: none; 30 | } 31 | 32 | :root { 33 | font-size: 62.5%; 34 | font-size: 10px; 35 | } 36 | 37 | body { 38 | font-family: monospace; 39 | font-family: "Courier"; 40 | font-family: "Menlo"; 41 | font-size: 1.5rem; 42 | line-height: 2.0rem; 43 | white-space: nowrap; 44 | color: #f8f8f2; 45 | background-color: #272822; 46 | -webkit-text-size-adjust: 100%; 47 | } 48 | 49 | ::selection { 50 | background-color: hsla(40, 3.5%, 62%, 1); 51 | } 52 | :focus { 53 | outline: none; 54 | } 55 | 56 | doc, row { 57 | display: block; 58 | } 59 | tag, prop, val, txt { 60 | display: inline; 61 | line-height: 2.0rem; 62 | height: 2.0rem; 63 | white-space: pre-wrap; 64 | padding: .1rem .5ch; 65 | } 66 | 67 | doc { 68 | position: relative; 69 | padding: 0 0; 70 | background-image: linear-gradient(to left, #49483E 1px, transparent 1px); 71 | background-position: left -.5ch top; 72 | background-repeat: repeat-x; 73 | background-size: 2ch 100%; 74 | } 75 | row { 76 | background-color: #272822; 77 | background-origin: content-box; 78 | background-clip: content-box; 79 | background-position: left top; 80 | background-repeat: no-repeat; 81 | background-size: 1px 100%; 82 | } 83 | 84 | tag { 85 | color: #f92772; 86 | } 87 | prop { 88 | color: #a6e22d; 89 | } 90 | val { 91 | color: #e6db74; 92 | } 93 | prop + val::before { 94 | content: ':'; 95 | font: inherit; 96 | position: absolute; 97 | margin-left: calc(-1ch); 98 | color: #a6e22d; 99 | color: white; 100 | } 101 | txt ~ * { 102 | border-left: 3px solid #f92772; 103 | } 104 | 105 | 106 | /* TODO: Lol crazy indentation with css, need to make this with js or somehow smarter */ 107 | [tabs] {margin-left: 22ch;} 108 | [tabs="0"] {margin-left: 0ch} 109 | [tabs="1"] {margin-left: 2ch} 110 | [tabs="2"] {margin-left: 4ch} 111 | [tabs="3"] {margin-left: 6ch} 112 | [tabs="4"] {margin-left: 8ch} 113 | [tabs="5"] {margin-left: 10ch} 114 | [tabs="6"] {margin-left: 12ch} 115 | [tabs="7"] {margin-left: 14ch} 116 | [tabs="8"] {margin-left: 16ch} 117 | [tabs="9"] {margin-left: 18ch} 118 | [tabs="10"] {margin-left: 20ch} 119 | 120 | /* TODO: could use requestIdleCallback for checking and marking errors, kinda like os level spell check does */ 121 | [tabs="0"] + :-webkit-any([tabs="2"],[tabs="3"],[tabs="4"],[tabs="5"],[tabs="6"],[tabs="7"],[tabs="8"],[tabs="9"],[tabs="10"]) > :first-child, 122 | [tabs="1"] + :-webkit-any([tabs="3"],[tabs="4"],[tabs="5"],[tabs="6"],[tabs="7"],[tabs="8"],[tabs="9"],[tabs="10"]) > :first-child, 123 | [tabs="2"] + :-webkit-any([tabs="4"],[tabs="5"],[tabs="6"],[tabs="7"],[tabs="8"],[tabs="9"],[tabs="10"]) > :first-child, 124 | [tabs="3"] + :-webkit-any([tabs="5"],[tabs="6"],[tabs="7"],[tabs="8"],[tabs="9"],[tabs="10"]) > :first-child, 125 | [tabs="4"] + :-webkit-any([tabs="6"],[tabs="7"],[tabs="8"],[tabs="9"],[tabs="10"]) > :first-child, 126 | [tabs="5"] + :-webkit-any([tabs="7"],[tabs="8"],[tabs="9"],[tabs="10"]) > :first-child, 127 | [tabs="6"] + :-webkit-any([tabs="8"],[tabs="9"],[tabs="10"]) > :first-child, 128 | [tabs="7"] + :-webkit-any([tabs="9"],[tabs="10"]) > :first-child, 129 | [tabs="8"] + :-webkit-any([tabs="10"]) > :first-child { 130 | border-left: 3px dotted red; 131 | } 132 | [type="txt"][tabs="0"] + :-webkit-any([tabs="2"],[tabs="2"],[tabs="3"],[tabs="4"],[tabs="5"],[tabs="6"],[tabs="7"],[tabs="8"],[tabs="9"],[tabs="10"]) > :first-child, 133 | [type="txt"][tabs="1"] + :-webkit-any([tabs="2"],[tabs="3"],[tabs="4"],[tabs="5"],[tabs="6"],[tabs="7"],[tabs="8"],[tabs="9"],[tabs="10"]) > :first-child, 134 | [type="txt"][tabs="2"] + :-webkit-any([tabs="3"],[tabs="4"],[tabs="5"],[tabs="6"],[tabs="7"],[tabs="8"],[tabs="9"],[tabs="10"]) > :first-child, 135 | [type="txt"][tabs="3"] + :-webkit-any([tabs="4"],[tabs="5"],[tabs="6"],[tabs="7"],[tabs="8"],[tabs="9"],[tabs="10"]) > :first-child, 136 | [type="txt"][tabs="4"] + :-webkit-any([tabs="5"],[tabs="6"],[tabs="7"],[tabs="8"],[tabs="9"],[tabs="10"]) > :first-child, 137 | [type="txt"][tabs="5"] + :-webkit-any([tabs="6"],[tabs="7"],[tabs="8"],[tabs="9"],[tabs="10"]) > :first-child, 138 | [type="txt"][tabs="6"] + :-webkit-any([tabs="7"],[tabs="8"],[tabs="9"],[tabs="10"]) > :first-child, 139 | [type="txt"][tabs="7"] + :-webkit-any([tabs="8"],[tabs="9"],[tabs="10"]) > :first-child, 140 | [type="txt"][tabs="8"] + :-webkit-any([tabs="9"],[tabs="10"]) > :first-child { 141 | outline: 1px dotted red; 142 | } 143 | 144 | .com { 145 | font-style: italic; 146 | } 147 | .com > *, 148 | .com > *::before { 149 | /*TODO: comments could mix the original color of the tag with this gray 50/50, this needs sass, so set it up*/ 150 | color: #75715e; 151 | } 152 | 153 | .sel { 154 | background-color: #49483E; 155 | //box-shadow: inset 0 -2px 0 0 hsla(200, 0%, 50%, .5); 156 | } 157 | .cur { 158 | position: relative; 159 | box-shadow: inset 0 -2px 0 0 hsla(200, 100%, 50%, 1); 160 | } 161 | .hilite { 162 | background-color: #49483E; 163 | //box-shadow: inset 0 -2px 0 0 hsla(200, 0%, 50%, .5); 164 | } 165 | .folded::after { 166 | content: '…'; 167 | } 168 | .hidden { 169 | display: none; 170 | } 171 | 172 | 173 | 174 | [contenteditable="true"], 175 | .clone { 176 | /*TODO Editable styling needs work, you need to be able to see what type of prop you're editing, can't be just plain black on white. But still needs to be obvious that now you're editing text. Editing cursor appearance would be ace, but sometimes chrome just doesn't even render the cursor in styled contenteditables. */ 177 | background-color: transparent; 178 | } 179 | 180 | .dragsource, 181 | .dragsource::before { 182 | color: transparent; 183 | } 184 | .dragsource > *, 185 | .dragsource > *::before { 186 | color: transparent; 187 | } 188 | 189 | .dragghost { 190 | position: absolute; 191 | z-index: 200; 192 | display: none; 193 | pointer-events: none; 194 | box-shadow: 0 2px 8px -2px rgba(0,0,0,.5); 195 | } 196 | 197 | /*TODO: drop point indicator should be a div that travels the page and gets rendered according to droptarget clientrect */ 198 | .dropbefore, 199 | .dropafter { 200 | position: relative; 201 | //background-color: hsla(200, 100%, 50%, .33); 202 | } 203 | .dropbefore::after, 204 | .dropafter::after { 205 | content: ''; 206 | position: absolute; 207 | display: block; 208 | z-index: 100; 209 | top: .5rem; 210 | left: -.5ch; 211 | width: 1ch; 212 | height: 1.0rem; 213 | border-radius: 1ch; 214 | background-color: hsla(200, 100%, 50%, 1); 215 | } 216 | .dropafter::after { 217 | left: auto; 218 | right: -.5ch; 219 | } 220 | row.dropbefore::after, 221 | row.dropafter::after { 222 | top: -2px; 223 | left: auto; 224 | right: auto; 225 | width: 100%; 226 | height: 4px; 227 | } 228 | row.dropafter::after { 229 | top: auto; 230 | bottom: -2px; 231 | } 232 | -------------------------------------------------------------------------------- /js/parseHTML.js: -------------------------------------------------------------------------------- 1 | module.exports = parseHTML 2 | 3 | 4 | function parseHTML (htmlString) { 5 | 6 | //Check if string is like and if it is, treat is as a bunch of attributes, although browsers would create an element or something crazy. 7 | if (htmlString.match(/^<\w*=".*">$/)) { 8 | let props = [] 9 | let attrs = htmlString.slice(1, -2).split('" ') //Remove < and > from start & end and split string at attribute boundaries. Also props always need a valuve inside quotes, even if the value is empty. Quotes should always be encoded insite attributes. This is not really very robust, but works for now. 10 | console.log('split attrs', attrs) 11 | for (let i = 0; i < attrs.length; i++) { 12 | let attr = attrs[i] 13 | let pv = attr.split('="') 14 | props.push({ 15 | type: 'prop', 16 | text: pv[0] 17 | }) 18 | if (pv[1]) { 19 | props.push({ 20 | type: 'val', 21 | text: pv[1] 22 | }) 23 | } 24 | } 25 | console.log(props) 26 | return { 27 | type: 'props', 28 | props: props 29 | } 30 | } 31 | 32 | else if (htmlString) { 33 | let rows = HTMLstring(htmlString) 34 | let doc = { 35 | type: 'rows', 36 | rows: rows 37 | } 38 | return doc 39 | } 40 | 41 | else { 42 | return [] 43 | } 44 | } 45 | 46 | //All these processing methods return an array of rows 47 | function HTMLstring (htmlString, depth, commented) { 48 | if (!commented) {commented = false} 49 | let rows = [] 50 | if (!depth) { 51 | depth = 0 52 | } 53 | if (htmlString.indexOf('text", so there's no whitespace after a tag. Since the text will be on its own row that no whitespace situation should probably be noted somehow, maybe with the >< whitespace eating crocodiles syntax. Also if there's some text and then a line break, should the line break + whitespace be cleaned up? 145 | //TODO: handle whitespace inside pre, code, textarea (etc?) elements somehow. Check ['pre', 'code'].indexOf($().parents().nodeName)) or something 146 | /*TODO: 147 | - split text nodes on any '\n', so each row is a row in the doc too 148 | - trim rows and somehow smartly add tabs, impossibble to do totally reliably, with all the mixes space & tab combos and such 149 | */ 150 | let textContent = node.textContent 151 | let texts = [] 152 | let rows = [] 153 | 154 | if (textContent.match(/^\s*\n\s*$/)) { //Skip text that's probably only whitespace for formatting the html. TODO: a line break will add a text node that will affect inline(-block) element rendering, so this needs to be pretty smart 155 | return false 156 | } else if (textContent.match(/^\s+$/)) { 157 | texts = [textContent] 158 | } else { 159 | texts = textContent.trim().split('\n') 160 | } 161 | //TODO: text.match(/^\s+$/) !?!? 162 | 163 | for (let i = 0; i < texts.length; i++) { 164 | let props = [] 165 | let row = {} 166 | props.push({ 167 | type: 'txt', 168 | text: texts[i], //TODO: trim away only as much spaces as depth needs from the front of the string 169 | }) 170 | row = { 171 | type: 'txt', 172 | commented: commented, 173 | tabs: depth, 174 | props: props 175 | } 176 | rows.push(row) 177 | } 178 | 179 | return rows 180 | } 181 | 182 | function commentNode (node, depth, commented) { 183 | //Contents of commentnode get parsed as html, so they're not just some blubber of text, but evetyrhint that gets parsed inside a comment node will be marked as commented out rows 184 | let rows = [] 185 | commented = true 186 | rows = rows.concat(HTMLstring(node.textContent, depth, commented)) 187 | return rows 188 | } 189 | 190 | function doctypeNode (node, depth, commented) { 191 | return [{ 192 | commented: commented, 193 | tabs: depth, 194 | props: [{ 195 | type: 'tag', 196 | text: '!doctype' 197 | }] 198 | }] 199 | } 200 | 201 | function documentNode (node, depth, commented) { 202 | let rows = [] 203 | if (node.childNodes.length != 0) { 204 | for (let childNum = 0; childNum < node.childNodes.length; childNum++) { 205 | rows = rows.concat(domNode(node.childNodes[childNum], depth, commented)) 206 | } 207 | } 208 | return rows 209 | } 210 | -------------------------------------------------------------------------------- /js/mouse.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | let dragTimer 4 | let mouseDownEvent = null 5 | let dragGhost = $('
') 6 | let dropTarget = null 7 | let dragMode = '' 8 | let mouseStart = '' 9 | 10 | function mouseDown(e) { 11 | //Allow mouse to function according to platform defaults when editing text, also this way there's no need to worry about editing mode for the rest of this function & other mouse events. 12 | if (e.target.isContentEditable === true) { 13 | return true 14 | } else if (scope === 'editing') { 15 | commitEdit() 16 | } 17 | 18 | //e.preventDefault() 19 | let target = $(e.target) 20 | mouseDownEvent = e 21 | scope = 'paintselection' 22 | 23 | if (!target.hasClass('sel') && !target.hasClass('hilite')) { 24 | selTarget(e) 25 | } 26 | 27 | dragTimer = setTimeout(_=>dragStart(e), 220) 28 | } 29 | 30 | function dragStart(e) { 31 | history.update() 32 | scope = 'dragging' 33 | 34 | //Format what's being dragged, so the drag ghost exactly reflects what will be dropped. 35 | //Dragging gathers all selected items and puts them in flat rows. 36 | let dragSourceRows = $('.hilite') 37 | //TODO: this could be made more efficient, seems wasteful 38 | dragSourceRows = dragSourceRows.add($('.hilite.folded').nextUntil('row:not(.hidden)')) //If you fold a row and drag it, its children will come with the drag 39 | let dragSourceProps = $('row:not(.hilite) .sel') 40 | let dragPayloadRows = dragSourceRows.clone() 41 | let dragPayloadProps = dragSourceProps.clone() 42 | dragSourceRows.addClass('dragsource') 43 | dragSourceProps.addClass('dragsource') 44 | 45 | //TODO: This allows nonsensical prop/row combinations, combining props with a txt row, but css will mark it as an error and the user needs to correct it. Should make this foolproof somehow. 46 | if (dragPayloadRows.length) { 47 | dragMode += ':rows' 48 | 49 | /*TODO: 50 | 1. Split continuous ranges of selected rows to groups. 51 | 2. Go through each group 52 | 1. Get the children of the topmost row, split to a group 53 | 2. If there's rows left, repeat 1. 54 | 3. Unindent each group enough so that the upmost row is at tab 0 55 | */ 56 | 57 | dragPayloadRows.children().first().after(dragPayloadProps) 58 | dragGhost.append(dragPayloadRows) 59 | } else if (dragPayloadProps.length) { 60 | dragMode += ':props' 61 | dragGhost.append(dragPayloadProps) 62 | } 63 | 64 | dragGhost.css({ 65 | 'display': 'inline-block', 66 | 'left': e.pageX + 'px', 67 | 'top': e.pageY + 'px', 68 | }) 69 | $('doc').after(dragGhost) 70 | } 71 | 72 | function mouseMove(e) { 73 | clearTimeout(dragTimer) 74 | if (scope === '') { 75 | //TODO: render that left side element+children selection highlight here. There should also be a generic 'render' function or something like that, that renders all the necessary effects, like a highlight for the selected element to the vertical lines that show indentation and stuff. 76 | } else if (scope === 'paintselection') { 77 | //Paint selection if mouse was moved before drag was initiated 78 | selTarget(e, ':add') 79 | } else if (scope === 'dragging') { 80 | //Make dragGhost follow mouse 81 | dragGhost.css({ 82 | 'left': e.pageX + 'px', 83 | 'top': e.pageY + 'px', 84 | }) 85 | 86 | 87 | if (dropTarget) { 88 | dropTarget.removeClass('dropbefore dropafter') 89 | dropTarget = null 90 | } 91 | let target = $(e.target) 92 | 93 | //What a crazy contraption, is there a simpler way to do this? 94 | if (dragMode.includes(':props')) { 95 | if (['PROP','VAL'].includes(e.target.tagName)) { 96 | dropTarget = target 97 | let hitbox = e.target.getBoundingClientRect() 98 | let centerX = hitbox.left + hitbox.width / 2 99 | let centerY = hitbox.top + hitbox.height / 2 100 | if (e.clientX < centerX) { 101 | dropTarget.addClass('dropbefore') 102 | } else { 103 | dropTarget.addClass('dropafter') 104 | } 105 | } else if (e.target.tagName === 'TAG') { 106 | dropTarget = target 107 | dropTarget.addClass('dropafter') 108 | } else if (e.target.tagName === 'ROW' && target.attr('type') !== 'txt') { 109 | //This if case needs to be here in case the props are dragged more left or right than any props, so the dragmode is props mut cursor is on row 110 | let children = target.children() 111 | 112 | let first = children.eq(0) 113 | let hitFirst = first[0].getBoundingClientRect() 114 | let hitLeft = hitFirst.left + hitFirst.width 115 | 116 | let last = children.last() 117 | let hitLast = last[0].getBoundingClientRect() 118 | let hitRight = hitLast.left + hitLast.width 119 | 120 | if (e.clientX < hitLeft) { 121 | dropTarget = first 122 | dropTarget.addClass('dropafter') 123 | } else if (e.clientX > hitRight) { 124 | dropTarget = last 125 | dropTarget.addClass('dropafter') 126 | } 127 | } else { 128 | } 129 | } 130 | else if (dragMode.includes(':rows')) { 131 | let hitbox = e.target.getBoundingClientRect() 132 | let centerY = hitbox.top + hitbox.height / 2 133 | let toTop = false 134 | let toBottom = false 135 | 136 | if (['TAG','PROP','VAL','TXT'].includes(e.target.tagName)) { 137 | dropTarget = target.parent() 138 | } else if (e.target.tagName === 'ROW') { 139 | dropTarget = target 140 | } 141 | 142 | if (dropTarget && e.clientY < centerY) { 143 | dropTarget.addClass('dropbefore') 144 | } else if (dropTarget && e.clientY >= centerY) { 145 | dropTarget.addClass('dropafter') 146 | } 147 | 148 | } 149 | } 150 | } 151 | 152 | function mouseUp(e) { 153 | //e.preventDefault() 154 | clearTimeout(dragTimer) 155 | 156 | //TODO: check for shift/alt/ctrl/cmd, there should be a key that lets you clone elements 157 | let dragSource = $('.dragsource') 158 | 159 | if (scope === 'dragging' && dropTarget) { 160 | let dragPayload = dragGhost.children() 161 | //Set tabs according to droptarget tabs. This means that dragging does not preserve hierarchy in any way. It probably should preserve hierarchies where there's only an element and its children selected 162 | //dragPayload.attr('tabs', dropTarget.attr('tabs')) 163 | if (dropTarget.hasClass('dropafter')) { 164 | dropTarget.after(dragPayload) 165 | } else if (dropTarget.hasClass('dropbefore')) { 166 | dropTarget.before(dragPayload) 167 | } 168 | dragSource.remove() 169 | 170 | //At this poin the operation happened, add entry to history. The rest of mouseup is just cleanup 171 | history.add() 172 | } else if (scope === 'paintselection' && mouseDownEvent.screenX === e.screenX && mouseDownEvent.screenY === e.screenY) { 173 | //If you just click on an item and don't do a lasso selection or drag, then select the item 174 | selTarget(e) 175 | } 176 | 177 | if (dropTarget) {dropTarget.removeClass('dropbefore dropafter')} 178 | dropTarget = null 179 | if (dragSource) {dragSource.removeClass('dragsource')} 180 | dragGhost.css('display', 'none').empty() 181 | 182 | dragMode = '' 183 | mouseDownEvent = null 184 | if (scope === 'dragging' || scope === 'paintselection') { 185 | scope = '' //only reset scopes we have set in mouse.js, leave any other scopes alone 186 | } 187 | } 188 | 189 | 190 | function cancelDrag(e) { 191 | e.preventDefault() 192 | if (scope === 'dragging' || scope === 'paintselection') { 193 | scope = '' //only reset scopes we have set in mouse.js, leave any other scopes alone 194 | } 195 | mouseUp(e) 196 | } 197 | -------------------------------------------------------------------------------- /js/keydown.js: -------------------------------------------------------------------------------- 1 | //TODO: This is so horrible to read, should make a nicer abstractions for input handling. But all input handling libs I've found have been lacking. 2 | 3 | //TODO: some of these probably belong in app menus and need to be listened to by ipcRenderer.on('whatever', e=>{}) 4 | 5 | function keydown(e) { 6 | console.log(e.key, e.code) 7 | //mod returns modifier keys in exclusive form, so you don't need to do e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey, just check if only shiftKey is pressed 8 | let mod = modkeys(e) 9 | 10 | //Selection 11 | if (scope === '') { 12 | if (mod.none && e.key === 'ArrowUp') { 13 | e.preventDefault() 14 | selRow('up') 15 | return 16 | } 17 | else if (mod.none && e.key === 'ArrowDown') { 18 | e.preventDefault() 19 | selRow('down') 20 | return 21 | } 22 | else if (mod.none && e.key === 'ArrowLeft') { 23 | e.preventDefault() 24 | selCol('left') 25 | return 26 | } 27 | else if (mod.none && e.key === 'ArrowRight') { 28 | e.preventDefault() 29 | selCol('right') 30 | return 31 | } 32 | else if (mod.shift && e.key === 'ArrowUp') { 33 | e.preventDefault() 34 | selRow('up:add') 35 | return 36 | } 37 | else if (mod.shift && e.key === 'ArrowDown') { 38 | e.preventDefault() 39 | selRow('down:add') 40 | return 41 | } 42 | else if (mod.shift && e.key === 'ArrowLeft') { 43 | e.preventDefault() 44 | selCol('left:add') 45 | return 46 | } 47 | else if (mod.shift && e.key === 'ArrowRight') { 48 | e.preventDefault() 49 | selCol('right:add') 50 | return 51 | } 52 | else if (mod.cmd && e.key === 'a') { 53 | e.preventDefault() 54 | selAll(e) 55 | return 56 | } 57 | else if (mod.cmd && e.key === 'd') { 58 | e.preventDefault() 59 | selSimilar(e) 60 | return 61 | } 62 | else if (mod.none && e.key === 'Escape') { 63 | e.preventDefault() 64 | selEscape() 65 | return 66 | } 67 | else if (mod.none && e.key === '-') { 68 | e.preventDefault() 69 | fold(':fold') 70 | return 71 | } 72 | else if (mod.none && e.key === '+') { 73 | e.preventDefault() 74 | fold(':unfold') 75 | return 76 | } 77 | } 78 | 79 | //Creating stuff 80 | if (scope === '') { 81 | if (mod.none && e.key.match(/^[a-z]$/)) { 82 | e.preventDefault() 83 | //TODO: should parse the letter here and send just that to createRow, not the whole event in these create functions 84 | createRow(e, ':tag') 85 | return 86 | } 87 | else if ((mod.none || mod.shift) && e.key.match(/^[A-Z]$/)) { 88 | e.preventDefault() 89 | createRow(e, ':txt') 90 | return 91 | } 92 | else if (mod.shift && e.key === 'Enter') { 93 | e.preventDefault() 94 | //TODO: shift+enter should add a line break when editing a txt 95 | createRow(e, ':txt') 96 | return 97 | } 98 | else if (mod.cmd && e.key === 'Enter') { 99 | e.preventDefault() 100 | //TODO: cmd+enter should work in any scope 101 | createRow(e, ':tag', 'div') 102 | return 103 | } 104 | else if (mod.none && e.key === ' ') { 105 | e.preventDefault() 106 | createProp(e) 107 | return 108 | } 109 | else if (e.key === ',') { 110 | e.preventDefault() 111 | createProp(e, ':prop') 112 | return 113 | } 114 | else if (e.key === ':' || e.key === '=') { 115 | e.preventDefault() 116 | createProp(e, ':val') 117 | return 118 | } 119 | else if (e.key === '#') { 120 | e.preventDefault() 121 | createProp(e, ':id') 122 | return 123 | } 124 | else if (e.key === '.') { 125 | e.preventDefault() 126 | createProp(e, ':class') 127 | return 128 | } 129 | } 130 | 131 | //Edit actions while in root scope 132 | if (scope === '') { 133 | if (mod.none && e.key === 'Enter') { 134 | history.update() 135 | e.preventDefault() 136 | startEdit() 137 | return 138 | } 139 | else if (mod.none && e.key === 'Backspace') { 140 | e.preventDefault() 141 | del(':backward') 142 | return 143 | } 144 | else if (mod.none && e.key === 'Delete') { 145 | e.preventDefault() 146 | del(':forward') 147 | return 148 | } 149 | else if (mod.cmdShift && e.code === 'KeyD') { //Use KeyD so there's no confusion because of shift modifying the letter that's output 150 | //TODO: implement duplicate 151 | e.preventDefault() 152 | duplicate() 153 | return 154 | } 155 | else if (mod.none && e.key === 'Tab') { 156 | e.preventDefault() 157 | tab(1) 158 | return 159 | } 160 | else if (mod.shift && e.key === 'Tab') { 161 | e.preventDefault() 162 | tab(-1) 163 | return 164 | } 165 | else if (e.metaKey && e.key === '/') { //Check for emetakey instead of mod function because / could come through modifiers on some key layouts, like Scandinavian ones for example. 166 | e.preventDefault() 167 | comment() 168 | return 169 | } 170 | else if (mod.ctrl && e.key === 'ArrowUp') { 171 | e.preventDefault() 172 | moveRow(':up') 173 | return 174 | } 175 | else if (mod.ctrl && e.key === 'ArrowDown') { 176 | e.preventDefault() 177 | moveRow(':down') 178 | return 179 | } 180 | else if (mod.ctrl && e.key === 'ArrowLeft') { 181 | e.preventDefault() 182 | moveCol(':left') 183 | return 184 | } 185 | else if (mod.ctrl && e.key === 'ArrowRight') { 186 | e.preventDefault() 187 | moveCol(':right') 188 | return 189 | } 190 | } 191 | 192 | //Edit actions while editing an item 193 | if (scope === 'editing') { 194 | let target = $('[contenteditable="true"]') 195 | 196 | if (mod.none && e.key === 'Enter' || e.key === 'Escape') { 197 | e.preventDefault() 198 | commitEdit() 199 | return 200 | } 201 | else if (mod.none && e.key === 'Backspace' || e.key === 'Delete') { 202 | autofill.prevent() 203 | return 204 | } 205 | else if (mod.none && e.key === 'Tab') { 206 | e.preventDefault() 207 | commitEdit() 208 | //Pressing tab to indent while editing felt way too fiddly, fought with muscle memory, so pressing tab is like autocompletion in the terminal or text editor, it just accepts whatever's in the input box. 209 | return 210 | } 211 | else if (mod.shift && e.key === 'Tab') { 212 | e.preventDefault() 213 | commitEdit() 214 | return 215 | } 216 | //Make new props when pressing keys that make sense. Like, you'd expect that if you type `div` + space, that stuff after that would be an attribute name, so we make add an attribute when you press space when editing a tag. This becomes troublesome when the visualised syntax clashes with html validity. HTML allows : * and stuff in attribute names. Pressing : inside an attribute name must allow you to keep typing, because svg is a common case where you use some namespacing. 217 | //TODO: That `:` is dependant on how the editor visualizes attrs, it should be `=` if the editor shows attr=val and `:` if its' attr:val and so on. So there should be some viz/style config that determines how the editor looks and how some shortcuts behave. Maybe : should be = instead, because that would make more sense for html. 218 | else if (target[0].tagName === 'TAG' && e.code === 'Space') { 219 | e.preventDefault() 220 | commitEdit() 221 | createProp(e) 222 | } else if (target[0].tagName === 'PROP' && (e.code === 'Space' || e.key === ':' || e.key === '=')) { 223 | e.preventDefault() 224 | commitEdit() 225 | createProp(e) 226 | } 227 | } 228 | 229 | //Drag & drop 230 | if (scope === 'dragging') { 231 | if (e.key === 'Escape') { 232 | e.preventDefault() 233 | cancelDrag(e) 234 | return 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /js/selection.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | let col = 0 4 | 5 | 6 | function select(to, opts) { 7 | 8 | opts = opts || '' 9 | //If selection is not additive, remove sel class from everything that's not cursor 10 | if (opts.includes(':add')) { 11 | } else { 12 | $('.sel:not(.cur)').removeClass('sel') 13 | } 14 | if (opts.includes(':children')) { 15 | to = to.add(getRowChildren(to.parent('row')).children(':first-child')) 16 | } 17 | to.addClass('sel') 18 | 19 | 20 | //Tag & txt props are proxies for their row, so when they are selected, highlight entire row 21 | $('.hilite').removeClass('hilite') 22 | $('tag.sel, txt.sel').parent().addClass('hilite') 23 | 24 | //When you select a prop, select its val too, so you can't make operations for prop alone, because if the value doesn't have a prop before it, it makes no sense as html. 25 | $('prop.sel').nextUntil(':not(val)').addClass('sel') 26 | 27 | //When you select a folded row, all its children need to be selected too, so let's get any folded stuff from the new selection and select them too. This is so tabbing, moving, dragging etc work like they're supposed to. Chould use `select(to, ':children:add')` here, but if the indentation is not pristine, that could produce the wrong result. 28 | $('.hilite.folded') 29 | .nextUntil(':not(.hidden)').addClass('hilite') 30 | .children('tag, txt').addClass('sel') 31 | 32 | //TODO: check that any functions that modify or select stuff use this function to set selection (instead of modifying `.sel` classes directly), so I can be sure that hidden rows are always selected if the folded row is selected 33 | //TODO: all edit operations need to be aware of hidden rows for folding to work, maybe I should disable folding for a while until I get a handle on how it should work 34 | //TODO: createRow either unfold a row just before adding a row, or only create siblings for folded rows. 35 | //TODO: I could handle all these cases if I had a generic function to get an element reference, so when doing move operations etc, I'd get the folded row and its children and move the whole bunch 36 | 37 | } 38 | 39 | 40 | function selTarget (e, opts) { 41 | //This is only invoved from mouse events for now, so this can assume e is a real mouse event 42 | 43 | opts = opts || '' 44 | if (e && e.preventDefault) {e.preventDefault()} 45 | 46 | let cursors = $('.cur') 47 | let target = $(e.target) 48 | let newCur = $() 49 | 50 | if (target.parent('row').length) { 51 | newCur = target 52 | } else if (e.target.tagName === 'ROW') { 53 | newCur = target.children().first() 54 | if (e.layerX < newCur.position().left) { 55 | //TODO: clicking near a vertical line that shows indentation depth should select the row where that vertical line originates from, and that rows children. Select children of row if you click on the left side of the tag. This should extend all the way down for the whole element, but row level is fine for now. 56 | console.log(e) 57 | opts += ':children' 58 | } 59 | } else { 60 | newCur = $('doc').children().last().children().last() 61 | } 62 | 63 | if (e.shiftKey) {opts += ':add'} //TODO: Shift should select a range and Cmd should toggle selection on individual rows? 64 | //TODO: move modkey checking and option adding from here to event listeners, so shift+click runs selTarget(e, ':add') 65 | if (!e.altKey) {cursors.removeClass('cur')} //You can drop multiple cursors by pressing alt TODO: move this to opts via mouse.js 66 | newCur.addClass('cur') 67 | select(newCur, opts) 68 | } 69 | 70 | 71 | //Select up/down, additive selects up/down by nearest col it finds 72 | function selRow (act) { 73 | let cursors = $('.cur') 74 | let cursor = cursors.first() 75 | let newCurs = $() 76 | 77 | //Track column based on the first cursor, because multiple cursors always collapse to the first cursor 78 | //TODO: if there's only one cursor, use col to track farthest right position like text editors do. I could do that for every cursor by making col into an array, but not sure that's the right thing to do. 79 | col = Math.max(col, cursor.parent().children().index(cursor)) 80 | 81 | let up = act.includes('up') 82 | let down = act.includes('down') 83 | let end = act.includes('end') //TODO: implement end 84 | 85 | //TODO: add simple cases for up & down if there's no selection. The app should take care that there's always some element selected, but might be good to have just in case 86 | 87 | 88 | cursors.each(function(index, el) { 89 | let cursor = $(el) 90 | let row = cursor.parent() 91 | let props = row.children() 92 | let cursorCol = props.index(cursor) 93 | let newRow 94 | let newCur 95 | if (up) {newRow = row.prevAll(':not(.hidden)').first()} //Skip children of folded rows 96 | if (down) {newRow = row.nextAll(':not(.hidden)').first()} 97 | let newProps = props 98 | if (newRow.length) {newProps = newRow.children()} 99 | if (act.includes('add')) { 100 | //Additive up & down selection selects only rows, so clear selection on row and select first prop 101 | row.find('.sel').removeClass('sel') 102 | row.find('tag, txt').addClass('sel') 103 | newCur = newProps.first() 104 | } else if (newProps.length - 1 >= cursorCol) { //Because col is zero based, ugh 105 | newCur = newProps.eq(cursorCol) 106 | } else if (newProps.length > 0) { 107 | newCur = newProps.first() 108 | } 109 | newCurs = newCurs.add(newCur) 110 | }) 111 | cursors.removeClass('cur') 112 | newCurs.addClass('cur') 113 | select(newCurs, act) //Pass options to select functions, actual additive selection happens there 114 | } 115 | 116 | 117 | function selCol (act) { 118 | let cursors = $('.cur') 119 | let cursor = $('cur').first() 120 | let newCurs = $() 121 | 122 | //Track column based on the first cursor, because multiple cursors always collapse to the first cursor 123 | col = cursor.parent().children().index(cursor) 124 | 125 | let left = act.includes('left') 126 | let right = act.includes('right') 127 | let end = act.includes('end') //TODO: implement end 128 | 129 | //TODO: add simple cases for left & right if there's no selection. The app actions should really always result in a selection, and the first tag should be selected on document open, but it might be good to have just in case 130 | 131 | cursors.each(function(index, el) { 132 | let cursor = $(el) 133 | let newCur 134 | if (left) {newCur = cursor.prev()} 135 | if (right) {newCur = cursor.next()} 136 | 137 | //These following options just didn't feel right, so going left & going right stays on the same row 138 | //if (!newCur.length) { 139 | //Going left could select the last item of the previous row, like in text editor 140 | //if (left) {newCur = cursor.parent().prev().children().first()} 141 | //Going left could select the closest parent of the row 142 | // if (left) { 143 | // let cursorRow = cursor.parent() 144 | // let cursorTabs = cursorRow.attr('tabs') 145 | // cursorRow.prevAll().each(function(i) { 146 | // let prevRow = $(this) 147 | // let prevTabs = parseInt(prevRow.attr('tabs')) 148 | // if (prevTabs < cursorTabs) { 149 | // newCur = prevRow.children().first() 150 | // return false 151 | // } 152 | // }) 153 | // } 154 | //Going right could select the first item of the next row, or select the first child of the row 155 | //if (right) {newCur = cursor.parent().next().children().first()} 156 | //} 157 | 158 | if (!newCur.length) { 159 | newCur = cursor 160 | } 161 | newCurs = newCurs.add(newCur) 162 | }) 163 | 164 | cursors.removeClass('cur') 165 | newCurs.addClass('cur') 166 | select(newCurs, act) 167 | } 168 | 169 | 170 | function selEscape () { 171 | let cursors = $('.cur') 172 | let cursor = cursors.first() 173 | let newCur 174 | 175 | if (cursors.length > 1) { 176 | //Collapse multiple cursors 177 | newCur = cursor 178 | } else if (['PROP', 'VAL'].includes(cursor[0].tagName)) { 179 | //If cursor is not on beginning of row (tag), move it there 180 | newCur = cursor.parent().children().first() 181 | } else { 182 | //Select row prev until indent is less than current 183 | let row = cursor.parent() 184 | let tabs = Math.max(0, parseInt(row.attr('tabs')) - 1) //Max with 0 so tabs can't go negative 185 | let prevs = row.prevAll(`[tabs="${tabs}"]`) 186 | if (prevs.length) { 187 | newCur = prevs.first().children().first() 188 | } else { 189 | newCur = cursor 190 | } 191 | } 192 | 193 | cursors.removeClass('cur') 194 | newCur.addClass('cur') 195 | select(newCur) 196 | } 197 | 198 | 199 | function selSimilar(e, opts) { 200 | if (e && e.preventDefault) {e.preventDefault()} 201 | 202 | opts = opts || '' 203 | let cursor = $('.cur').last() 204 | let text = cursor.attr('text') 205 | let similars = $(`[text="${text}"]`) 206 | let newCur 207 | if (opts.includes(':all')) { 208 | newCur = similars 209 | } else { 210 | //Find cursor among similars and get next 211 | let index = similars.index(cursor) + 1 212 | newCur = similars.eq(index) 213 | if (!newCur.length) { 214 | //If there's no next, start searching from the beginning of the document 215 | newCur = $(`[text="${text}"]:not(.cur)`).first() 216 | } 217 | } 218 | newCur.addClass('cur') 219 | select(newCur) 220 | } 221 | 222 | 223 | function selAll(e) { 224 | //Should this select the whole row first and only after that the whole doc? 225 | $('.cur').removeClass('cur') 226 | $('doc row:last :last-child').addClass('cur') 227 | select($('tag, prop, val, txt')) 228 | } 229 | 230 | -------------------------------------------------------------------------------- /js/editing.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | //All these functions work for any combination of selections that have rows or props. Editing is not limited to any kind of props/rows modes, but should be able handle anything. If we want to limit editing to rows at a time or props at a time, selections should be limited to props or rows at a time. 4 | 5 | 6 | let editmodeStartValue = null //The value of a tag/prop/val/txt needs to be shared so i can check if the value has changed between entering edit mode and committing the edit. 7 | 8 | 9 | function startEdit (e, opts) { 10 | if (e && e.preventDefault) {e.preventDefault()} 11 | 12 | //startEdit could be called from many contexts, so it doen't itself have history.update, history.update needs to be called before invoking startEdit if needed 13 | 14 | //TODO: startEdit should probably collapse selection to cursors 15 | 16 | scope = 'editing' 17 | opts = opts || '' 18 | 19 | let cur = $('.cur') 20 | let target = cur.first() 21 | let clones = cur.not(target) 22 | editmodeStartValue = target.text() 23 | select(cur) 24 | $('.hilite').removeClass('hilite') //Remove row hilite so editing a tag & txt looks more explicit 25 | clones.addClass('clone') 26 | target.attr('contenteditable', 'true').focus() 27 | 28 | if (opts.includes(':selectEnd')) { 29 | target.selectEnd() 30 | } 31 | else { 32 | target.selectText() 33 | } 34 | } 35 | 36 | function commitEdit () { 37 | 38 | let target = $('[contenteditable="true"]') 39 | let text = target.text() 40 | let clones = $('.clone') 41 | clones.removeClass('clone') 42 | target.attr('contenteditable', 'false') 43 | select($(target, clones)) //Re-select what was being edited to restore proper selection state, because row hilites have been removed during editing. 44 | 45 | scope = '' 46 | 47 | if (text === '') { 48 | del(':emptydelete:backward') 49 | } 50 | else if (editmodeStartValue !== text) { 51 | history.add() 52 | } 53 | 54 | editmodeStartValue = null 55 | } 56 | 57 | 58 | function input (node) { 59 | //input() takes in a node instead of event is that it can be called from anywhere, not just from input event. So you can trigger autofill programmatically when creating tags, props & vals. 60 | //This function could parse input and create stuff based on that, but that logic is probably better to put in the paste handler and keyboard shortcuts. 61 | 62 | //Could use discard.js here to throw away any invalid characters for tags, props & vals as the user is typing them, so you could never add anything illegal in the tag. Cleanup could also happen on commitEdit? 63 | 64 | let cur = $('.cur') 65 | let clones = $('.clone') 66 | let tagName = node.tagName 67 | let text = node.innerText 68 | let lastChar = text.slice(-1) 69 | 70 | //Try autofill 71 | autofill.fill(node) 72 | 73 | //If multiple items are being edited, match their text 74 | text = node.innerText 75 | clones.text(text) 76 | //Update text attributes to match text content for easier selection via dom queries 77 | cur.attr('text', text) 78 | } 79 | 80 | 81 | function createRow (e, opts, str) { 82 | if (e && e.preventDefault) {e.preventDefault()} 83 | 84 | history.update() 85 | 86 | let tag = '' 87 | let txt = '' 88 | opts = opts || '' 89 | str = str || '' 90 | 91 | if (opts.includes(':txt')) {txt = str || (e && e.key) || ' '} 92 | else if (opts.includes(':tag')) {tag = str || (e && e.key)} 93 | else {tag = 'div'} 94 | 95 | if (txt || tag) { 96 | let cursors = $('.cur') 97 | let sel = $('.sel') 98 | let selrows = cursors.parent() 99 | let template 100 | 101 | if (txt) {template = $(`${txt}`)} 102 | else if (tag) {template = $(`${tag}`)} 103 | 104 | if (selrows.length) { 105 | selrows.after(template) 106 | } else { //If there's no selection, add stuff at the end of doc 107 | $('doc').append(template) 108 | } 109 | 110 | let newRows = $('.new').removeClass('new') 111 | newRows.each(function(index, row) { 112 | let newRow = $(row) 113 | let prevRow = newRow.prev() 114 | let nextTabs = parseInt(newRow.next().attr('tabs')) || 0 115 | let prevTabs = parseInt(prevRow.attr('tabs')) || 0 116 | if (txt) { 117 | if (prevRow.attr('type') === 'tag') { 118 | newRow.attr('tabs', prevTabs + 1) 119 | } else if (prevRow.attr('type') === 'txt') { //Text can't be a child of text so make a new textrow a sibling of previous textrow 120 | newRow.attr('tabs', prevTabs) 121 | } else { 122 | newRow.attr('tabs', 0) 123 | } 124 | } else if (tag) { 125 | newRow.attr('tabs', Math.max(nextTabs, prevTabs)) 126 | } 127 | }) 128 | 129 | let newCurs = newRows.children() 130 | cursors.removeClass('cur') 131 | newCurs.addClass('cur') 132 | select(newCurs) 133 | 134 | //If the user starts creatig a tag or a text with a letter, then don't select the whole thing so the user can just continue typing 135 | if ((tag && tag.length === 1) || (txt && txt !== ' ')) { 136 | startEdit(e, ':selectEnd') 137 | } else { 138 | //Else select the whole thing, so the user can start typing and replace whatever intial vaue we guessed into the created item 139 | //If the tag value was prefilled to be something, don't autofill right away, because it messes up the selection 140 | startEdit(e) 141 | autofill.prevent() 142 | } 143 | 144 | input(document.querySelector('[contenteditable="true"]')) 145 | } 146 | } 147 | 148 | 149 | function createProp (e, type, str) { 150 | if (e && e.preventDefault) {e.preventDefault()} 151 | 152 | history.update() 153 | 154 | let sel = $('.sel') 155 | let cursors = $('.cur') 156 | let action = false 157 | 158 | if (cursors.length === 0) {return} //Can't add props to nonexistent selections 159 | 160 | //Treat each cursor individually 161 | cursors.each((i, el)=>{ 162 | if (el.tagName === 'TXT') { 163 | //Text rows can't have props 164 | return 165 | } 166 | 167 | type = type || '' 168 | let cur = $(el) 169 | action = true 170 | 171 | //Without options, try to automatically add the right thing 172 | if (type.includes(':val') || (type === '' && el.tagName === 'PROP')) { 173 | str = str || 'value' 174 | cur.after(`${str}`) 175 | } 176 | else if (type.includes(':prop') || (type === '' && ['TAG','VAL'].includes(el.tagName))) { 177 | str = str || 'attr' 178 | cur.after(`${str}`) 179 | } 180 | 181 | //id & class check if the element already has an id/class and act according to that. If there's an id, just edit the id val, if there's a class, add a class after that 182 | //TODO: add id & add class should act on hilited rows, not cursors, so you never get a double class added to a row if there's two or more cursors on a row 183 | if (type.includes(':id')) { 184 | let idProp = cur.parent().find('prop[text="id"] + val') 185 | //TODO: first check if there's an id+val combo and add new to val if there is 186 | //then check if there's a lone id prop without val, if there is, add a new val for id 187 | //else add new id+val combo after tag 188 | if (idProp.length) { 189 | idProp.addClass('new') 190 | } else { 191 | cur.after(`id${str}`) 192 | } 193 | } 194 | if (type.includes(':class')) { 195 | //TODO: first check if there's a class + val combo 196 | //then check if there's a lone class 197 | //else add class after tag or id+val 198 | let prop = `class` 199 | let val = `${str}` 200 | let classProp = cur.parent().find('prop[text="class"]') 201 | if (classProp.length) { 202 | classProp.nextUntil('prop').last().after(val) 203 | } else { 204 | cur.after(prop + val) 205 | } 206 | } 207 | }) 208 | 209 | if (action) { 210 | let newCurs = $('.new').removeClass('new') 211 | cursors.removeClass('cur') 212 | newCurs.addClass('cur') 213 | select(newCurs) 214 | startEdit(e) 215 | } 216 | } 217 | 218 | 219 | function del (opts) { 220 | 221 | opts = opts || ':backward' 222 | let sel = $('.sel') 223 | let cursors = $('.cur') 224 | 225 | if (opts.includes(':emptydelete') === false) { 226 | history.update() 227 | } 228 | 229 | 230 | let newCurs = $() 231 | sel.each(function(i, el) { 232 | let $el = $(el) 233 | if (el.tagName === 'TAG' || el.tagName === 'TXT') { 234 | if (opts.includes(':backward')) {newCurs = newCurs.add($el.parent().prev().children().first())} 235 | if (opts.includes(':forward')) {newCurs = newCurs.add($el.parent().next().children().first())} 236 | } else { 237 | let lastchild = $el.is(':last-child') 238 | if (opts.includes(':backward') || lastchild) {newCurs = newCurs.add($el.prev())} 239 | //Forward delete moves only on the selected row, selection does not jump to next row even if the :last prop is forward deleted, that's why there's the last-child check 240 | else if (opts.includes(':forward')) {newCurs = newCurs.add($el.next())} 241 | } 242 | if ($el.prev().length) { 243 | newCurs = newCurs.add($el) 244 | } 245 | }) 246 | 247 | //Find tags & txt in selection and delete their parents. Those are proxies for the whole row, so rows should get deleted with them. 248 | sel.filter('tag, txt').parent().remove() 249 | 250 | //If you delete a prop, its attr will get deleted too. Not strictly necessary because you can have lonely values like
in html, but at least chrome will parse that as an attribute that has a name of "something", so it's kinda nonsensical. 251 | //This is commented out because the way this should work is that you cannot have a prop selected without its value being also selected. Selecting a prop should always select its val too. The cursor will behave normally, but the selection will extend to the value(s) when the cursor hits the prop. 252 | //sel.filter('prop').next('val').remove() 253 | 254 | //Delete the rest of selected stuff 255 | sel.remove() 256 | 257 | //No need to remove cur class from anything since cursors will have been removed from dom 258 | //No need to filter out selected items from newCurs because they too will have been deleted, so selection will automatically collapse to the previous available props from the deleted ones 259 | newCurs.addClass('cur') 260 | select(newCurs) 261 | 262 | history.add() 263 | } 264 | 265 | 266 | function duplicate() { 267 | //TODO: implement duplicate 268 | } 269 | 270 | 271 | function tab (amount) { 272 | 273 | history.update() 274 | 275 | //Should tabbing happen only for tags? 276 | amount = amount || 1 277 | let rows = $('.sel').parent() 278 | rows.each(function(index, row) { 279 | row = $(row) 280 | let prevTabs = parseInt(row.prev().attr('tabs')) 281 | let rowTabs = parseInt(row.attr('tabs')) 282 | let newTabs = rowTabs + amount 283 | let tabs = Math.max(newTabs, 0) //So tabs don't go negative 284 | row.attr('tabs', tabs) 285 | }) 286 | 287 | history.add() 288 | } 289 | 290 | 291 | function comment () { 292 | 293 | history.update() 294 | 295 | let sel = $('.sel') 296 | sel.parent().toggleClass('com') 297 | 298 | history.add() 299 | } 300 | 301 | 302 | 303 | function fold (opts) { 304 | //TODO: folding behaviour needs quite a bit of work all over the app, it's buggy and inconsistent with regards to other editing functions 305 | 306 | opts = opts || '' 307 | let sel = $('.sel') 308 | let rows = sel.parent() 309 | let fold = opts.includes(':fold') 310 | let unfold = opts.includes(':unfold') 311 | 312 | rows.each(function(i, el) { 313 | let row = $(el) 314 | let children = getRowChildren(row) 315 | 316 | if (fold && children.length) { 317 | row.addClass('folded') 318 | children.addClass('hidden').removeClass('folded') 319 | } else if (unfold && row.hasClass('folded')) { 320 | row.removeClass('folded') 321 | children.removeClass('hidden') 322 | } 323 | }) 324 | 325 | //Anything that's a hidden row as a child of a folded row will have its childrens foldings reset 326 | $('.hidden').removeClass('hilite folded').children().removeClass('cur sel') 327 | 328 | } 329 | 330 | 331 | 332 | function moveRow (act) { 333 | 334 | history.update() 335 | 336 | let up = act.includes(':up') 337 | let down = act.includes(':down') 338 | let end = act.includes(':end') //TODO: implement end 339 | 340 | //Move props 341 | let sel = $('.sel') 342 | sel.each(function(i, el) { 343 | let source = $(el) 344 | let row = source.parent('row:not(.hilite)') 345 | let target 346 | 347 | if (up) { 348 | target = row.prevUntil('[type="tag"]') 349 | if (target.length) { 350 | target = target.last().prev() 351 | } else { 352 | target = row.prev('[type="tag"]') 353 | } 354 | } 355 | else if (down) { 356 | target = row.nextUntil('[type="tag"]') 357 | if (target.length) { 358 | target = target.last().next() 359 | } else { 360 | target = row.next('[type="tag"]') 361 | } 362 | } 363 | 364 | target.append(source) 365 | }) 366 | 367 | //Move rows 368 | let selrows = $('row.hilite') 369 | selrows.each(function(i, el) { 370 | let selrow = $(el) 371 | let source 372 | let target 373 | if (up) { 374 | source = selrow.prev(':not(.hilite)') 375 | target = source.nextUntil(':not(.hilite)').last() 376 | target.after(source) 377 | } 378 | else if (down) { 379 | source = selrow.next(':not(.hilite)') 380 | target = source.prevUntil(':not(.hilite)').last() 381 | target.before(source) 382 | } 383 | }) 384 | //TODO: moving needs automatic tab smarts, the same as in drag & drop & cut & paste 385 | 386 | history.add() 387 | 388 | } 389 | 390 | function moveCol (act) { 391 | 392 | history.update() 393 | 394 | let left = act.includes(':left') 395 | let right = act.includes(':right') 396 | let end = act.includes(':end') //TODO: implement end 397 | 398 | let sel = $('.sel') 399 | sel.each(function(i, el) { 400 | if (el.tagName == 'TXT') {return} 401 | let prop = $(el) 402 | let source 403 | let target 404 | if (left) { 405 | source = prop.prev(':not(tag):not(.sel)') 406 | target = source.nextUntil(':not(.sel)').last() 407 | target.after(source) 408 | } 409 | else if (right) { 410 | source = prop.next(':not(tag):not(.sel)') 411 | target = source.prevUntil(':not(.sel)').last() 412 | target.before(source) 413 | } 414 | }) 415 | 416 | 417 | history.add() 418 | } 419 | 420 | 421 | //Maybe do actions like this, so each action would conform to managing history automatically, kinda like with selections and select() function 422 | // function action(e, function) { 423 | // if (e && e.preventDefault) {e.preventDefault()} 424 | // history.update() 425 | // function(e) 426 | // history.add() 427 | // } 428 | -------------------------------------------------------------------------------- /js/html.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec": "https://html.spec.whatwg.org/", 3 | "version": "0.0.0", 4 | "attributes": { 5 | "global": { 6 | "title": "", 7 | "lang": "", 8 | "translate": ["", "yes", "no"], 9 | "dir": ["ltr", "rtl", "auto"], 10 | "style": "", 11 | "data-": "" 12 | } 13 | }, 14 | "elements": { 15 | "html": { 16 | "category": null, 17 | "parents": false, 18 | "children": "head, body", 19 | "attributes": "", 20 | }, 21 | "head": { 22 | "category": null, 23 | "parents": "html", 24 | "children": "category:metadata-content", 25 | "attributes": "", 26 | }, 27 | "title": { 28 | "category": "metadata-content", 29 | "parents": "head", 30 | "children": "textNode", 31 | "attributes": "", 32 | }, 33 | "base": { 34 | "category": "metadata-content", 35 | "parents": "head", 36 | "children": false, 37 | "attributes": "href, target", 38 | }, 39 | "link": { 40 | "category": "metadata-content", 41 | "parents": "head, head>noscript", 42 | "children": false, 43 | "attributes": "href, crossorigin, rel, as, media, nonce, integrity, hreflang, type, sizes, referrerpolicy", 44 | }, 45 | "meta": { 46 | "category": "metadata-content", 47 | "parents": "head, head>noscript", 48 | "children": false, 49 | "attributes": "name, http-equiv, content, charset", 50 | }, 51 | "style": { 52 | "category": "metadata-content", 53 | "parents": "head>noscript", 54 | "children": "textNode", 55 | "attributes": "media, nonce, type, title", 56 | }, 57 | "body": { 58 | "category": "sectioning-root", 59 | "parents": "html", 60 | "children": "flow-content", 61 | "attributes": "", 62 | }, 63 | "article": { 64 | "category": "", 65 | "parents": , 66 | "children": , 67 | "attributes": "", 68 | }, 69 | "section": { 70 | "category": "", 71 | "parents": , 72 | "children": , 73 | "attributes": "", 74 | }, 75 | "nav": { 76 | "category": "", 77 | "parents": , 78 | "children": , 79 | "attributes": "", 80 | }, 81 | "aside": { 82 | "category": "", 83 | "parents": , 84 | "children": , 85 | "attributes": "", 86 | }, 87 | "h1": { 88 | "category": "", 89 | "parents": , 90 | "children": , 91 | "attributes": "", 92 | }, 93 | "h2": { 94 | "category": "", 95 | "parents": , 96 | "children": , 97 | "attributes": "", 98 | }, 99 | "h3": { 100 | "category": "", 101 | "parents": , 102 | "children": , 103 | "attributes": "", 104 | }, 105 | "h4": { 106 | "category": "", 107 | "parents": , 108 | "children": , 109 | "attributes": "", 110 | }, 111 | "h5": { 112 | "category": "", 113 | "parents": , 114 | "children": , 115 | "attributes": "", 116 | }, 117 | "h6": { 118 | "category": "", 119 | "parents": , 120 | "children": , 121 | "attributes": "", 122 | }, 123 | "hgroup": { 124 | "category": "", 125 | "parents": , 126 | "children": , 127 | "attributes": "", 128 | }, 129 | "header": { 130 | "category": "", 131 | "parents": , 132 | "children": , 133 | "attributes": "", 134 | }, 135 | "footer": { 136 | "category": "", 137 | "parents": , 138 | "children": , 139 | "attributes": "", 140 | }, 141 | "address": { 142 | "category": "", 143 | "parents": , 144 | "children": , 145 | "attributes": "", 146 | }, 147 | "p": { 148 | "category": "", 149 | "parents": , 150 | "children": , 151 | "attributes": "", 152 | }, 153 | "hr": { 154 | "category": "", 155 | "parents": , 156 | "children": , 157 | "attributes": "", 158 | }, 159 | "pre": { 160 | "category": "", 161 | "parents": , 162 | "children": , 163 | "attributes": "", 164 | }, 165 | "blockquote": { 166 | "category": "", 167 | "parents": , 168 | "children": , 169 | "attributes": "", 170 | }, 171 | "ol": { 172 | "category": "", 173 | "parents": , 174 | "children": , 175 | "attributes": "", 176 | }, 177 | "ul": { 178 | "category": "", 179 | "parents": , 180 | "children": , 181 | "attributes": "", 182 | }, 183 | "li": { 184 | "category": "", 185 | "parents": , 186 | "children": , 187 | "attributes": "", 188 | }, 189 | "dl": { 190 | "category": "", 191 | "parents": , 192 | "children": , 193 | "attributes": "", 194 | }, 195 | "dt": { 196 | "category": "", 197 | "parents": , 198 | "children": , 199 | "attributes": "", 200 | }, 201 | "dd": { 202 | "category": "", 203 | "parents": , 204 | "children": , 205 | "attributes": "", 206 | }, 207 | "figure": { 208 | "category": "", 209 | "parents": , 210 | "children": , 211 | "attributes": "", 212 | }, 213 | "figcaption": { 214 | "category": "", 215 | "parents": , 216 | "children": , 217 | "attributes": "", 218 | }, 219 | "main": { 220 | "category": "", 221 | "parents": , 222 | "children": , 223 | "attributes": "", 224 | }, 225 | "div": { 226 | "category": "", 227 | "parents": , 228 | "children": , 229 | "attributes": "", 230 | }, 231 | "a": { 232 | "category": "", 233 | "parents": , 234 | "children": , 235 | "attributes": "", 236 | }, 237 | "em": { 238 | "category": "", 239 | "parents": , 240 | "children": , 241 | "attributes": "", 242 | }, 243 | "strong": { 244 | "category": "", 245 | "parents": , 246 | "children": , 247 | "attributes": "", 248 | }, 249 | "small": { 250 | "category": "", 251 | "parents": , 252 | "children": , 253 | "attributes": "", 254 | }, 255 | "s": { 256 | "category": "", 257 | "parents": , 258 | "children": , 259 | "attributes": "", 260 | }, 261 | "cite": { 262 | "category": "", 263 | "parents": , 264 | "children": , 265 | "attributes": "", 266 | }, 267 | "q": { 268 | "category": "", 269 | "parents": , 270 | "children": , 271 | "attributes": "", 272 | }, 273 | "dfn": { 274 | "category": "", 275 | "parents": , 276 | "children": , 277 | "attributes": "", 278 | }, 279 | "abbr": { 280 | "category": "", 281 | "parents": , 282 | "children": , 283 | "attributes": "", 284 | }, 285 | "ruby": { 286 | "category": "", 287 | "parents": , 288 | "children": , 289 | "attributes": "", 290 | }, 291 | "rt": { 292 | "category": "", 293 | "parents": , 294 | "children": , 295 | "attributes": "", 296 | }, 297 | "rp": { 298 | "category": "", 299 | "parents": , 300 | "children": , 301 | "attributes": "", 302 | }, 303 | "data": { 304 | "category": "", 305 | "parents": , 306 | "children": , 307 | "attributes": "", 308 | }, 309 | "time": { 310 | "category": "", 311 | "parents": , 312 | "children": , 313 | "attributes": "", 314 | }, 315 | "code": { 316 | "category": "", 317 | "parents": , 318 | "children": , 319 | "attributes": "", 320 | }, 321 | "var": { 322 | "category": "", 323 | "parents": , 324 | "children": , 325 | "attributes": "", 326 | }, 327 | "samp": { 328 | "category": "", 329 | "parents": , 330 | "children": , 331 | "attributes": "", 332 | }, 333 | "kbd": { 334 | "category": "", 335 | "parents": , 336 | "children": , 337 | "attributes": "", 338 | }, 339 | "sub": { 340 | "category": "", 341 | "parents": , 342 | "children": , 343 | "attributes": "", 344 | }, 345 | "sup": { 346 | "category": "", 347 | "parents": , 348 | "children": , 349 | "attributes": "", 350 | }, 351 | "i": { 352 | "category": "", 353 | "parents": , 354 | "children": , 355 | "attributes": "", 356 | }, 357 | "b": { 358 | "category": "", 359 | "parents": , 360 | "children": , 361 | "attributes": "", 362 | }, 363 | "u": { 364 | "category": "", 365 | "parents": , 366 | "children": , 367 | "attributes": "", 368 | }, 369 | "mark": { 370 | "category": "", 371 | "parents": , 372 | "children": , 373 | "attributes": "", 374 | }, 375 | "bdi": { 376 | "category": "", 377 | "parents": , 378 | "children": , 379 | "attributes": "", 380 | }, 381 | "bdo": { 382 | "category": "", 383 | "parents": , 384 | "children": , 385 | "attributes": "", 386 | }, 387 | "span": { 388 | "category": "", 389 | "parents": , 390 | "children": , 391 | "attributes": "", 392 | }, 393 | "br": { 394 | "category": "", 395 | "parents": , 396 | "children": , 397 | "attributes": "", 398 | }, 399 | "wbr": { 400 | "category": "", 401 | "parents": , 402 | "children": , 403 | "attributes": "", 404 | }, 405 | "ins": { 406 | "category": "", 407 | "parents": , 408 | "children": , 409 | "attributes": "", 410 | }, 411 | "del": { 412 | "category": "", 413 | "parents": , 414 | "children": , 415 | "attributes": "", 416 | }, 417 | "picture": { 418 | "category": "", 419 | "parents": , 420 | "children": , 421 | "attributes": "", 422 | }, 423 | "source": { 424 | "category": "", 425 | "parents": , 426 | "children": , 427 | "attributes": "", 428 | }, 429 | "img": { 430 | "category": "", 431 | "parents": , 432 | "children": , 433 | "attributes": "", 434 | }, 435 | "iframe": { 436 | "category": "", 437 | "parents": , 438 | "children": , 439 | "attributes": "", 440 | }, 441 | "embed": { 442 | "category": "", 443 | "parents": , 444 | "children": , 445 | "attributes": "", 446 | }, 447 | "object": { 448 | "category": "", 449 | "parents": , 450 | "children": , 451 | "attributes": "", 452 | }, 453 | "param": { 454 | "category": "", 455 | "parents": , 456 | "children": , 457 | "attributes": "", 458 | }, 459 | "video": { 460 | "category": "", 461 | "parents": , 462 | "children": , 463 | "attributes": "", 464 | }, 465 | "audio": { 466 | "category": "", 467 | "parents": , 468 | "children": , 469 | "attributes": "", 470 | }, 471 | "track": { 472 | "category": "", 473 | "parents": , 474 | "children": , 475 | "attributes": "", 476 | }, 477 | "map": { 478 | "category": "", 479 | "parents": , 480 | "children": , 481 | "attributes": "", 482 | }, 483 | "area": { 484 | "category": "", 485 | "parents": , 486 | "children": , 487 | "attributes": "", 488 | }, 489 | "table": { 490 | "category": "", 491 | "parents": , 492 | "children": , 493 | "attributes": "", 494 | }, 495 | "caption": { 496 | "category": "", 497 | "parents": , 498 | "children": , 499 | "attributes": "", 500 | }, 501 | "colgroup": { 502 | "category": "", 503 | "parents": , 504 | "children": , 505 | "attributes": "", 506 | }, 507 | "col": { 508 | "category": "", 509 | "parents": , 510 | "children": , 511 | "attributes": "", 512 | }, 513 | "tbody": { 514 | "category": "", 515 | "parents": , 516 | "children": , 517 | "attributes": "", 518 | }, 519 | "thead": { 520 | "category": "", 521 | "parents": , 522 | "children": , 523 | "attributes": "", 524 | }, 525 | "tfoot": { 526 | "category": "", 527 | "parents": , 528 | "children": , 529 | "attributes": "", 530 | }, 531 | "tr": { 532 | "category": "", 533 | "parents": , 534 | "children": , 535 | "attributes": "", 536 | }, 537 | "td": { 538 | "category": "", 539 | "parents": , 540 | "children": , 541 | "attributes": "", 542 | }, 543 | "th": { 544 | "category": "", 545 | "parents": , 546 | "children": , 547 | "attributes": "", 548 | }, 549 | "form": { 550 | "category": "", 551 | "parents": , 552 | "children": , 553 | "attributes": "", 554 | }, 555 | "label": { 556 | "category": "", 557 | "parents": , 558 | "children": , 559 | "attributes": "", 560 | }, 561 | "input": { 562 | "category": "", 563 | "parents": , 564 | "children": , 565 | "attributes": "", 566 | }, 567 | "button": { 568 | "category": "", 569 | "parents": , 570 | "children": , 571 | "attributes": "", 572 | }, 573 | "select": { 574 | "category": "", 575 | "parents": , 576 | "children": , 577 | "attributes": "", 578 | }, 579 | "datalist": { 580 | "category": "", 581 | "parents": , 582 | "children": , 583 | "attributes": "", 584 | }, 585 | "optgroup": { 586 | "category": "", 587 | "parents": , 588 | "children": , 589 | "attributes": "", 590 | }, 591 | "option": { 592 | "category": "", 593 | "parents": , 594 | "children": , 595 | "attributes": "", 596 | }, 597 | "textarea": { 598 | "category": "", 599 | "parents": , 600 | "children": , 601 | "attributes": "", 602 | }, 603 | "output": { 604 | "category": "", 605 | "parents": , 606 | "children": , 607 | "attributes": "", 608 | }, 609 | "progress": { 610 | "category": "", 611 | "parents": , 612 | "children": , 613 | "attributes": "", 614 | }, 615 | "meter": { 616 | "category": "", 617 | "parents": , 618 | "children": , 619 | "attributes": "", 620 | }, 621 | "fieldset": { 622 | "category": "", 623 | "parents": , 624 | "children": , 625 | "attributes": "", 626 | }, 627 | "legend": { 628 | "category": "", 629 | "parents": , 630 | "children": , 631 | "attributes": "", 632 | }, 633 | "details": { 634 | "category": "", 635 | "parents": , 636 | "children": , 637 | "attributes": "", 638 | }, 639 | "summary": { 640 | "category": "", 641 | "parents": , 642 | "children": , 643 | "attributes": "", 644 | }, 645 | "menu": { 646 | "category": "", 647 | "parents": , 648 | "children": , 649 | "attributes": "", 650 | }, 651 | "menuitem": { 652 | "category": "", 653 | "parents": , 654 | "children": , 655 | "attributes": "", 656 | }, 657 | "dialog": { 658 | "category": "", 659 | "parents": , 660 | "children": , 661 | "attributes": "", 662 | }, 663 | "script": { 664 | "category": "metadata-content", 665 | "parents": , 666 | "children": , 667 | "attributes": "", 668 | }, 669 | "noscript": { 670 | "category": "metadata-content", 671 | "parents": , 672 | "children": , 673 | "attributes": "", 674 | }, 675 | "template": { 676 | "category": "metadata-content", 677 | "parents": , 678 | "children": , 679 | "attributes": "", 680 | }, 681 | "slot": { 682 | "category": "", 683 | "parents": , 684 | "children": , 685 | "attributes": "", 686 | }, 687 | "canvas": "" 688 | } 689 | } 690 | -------------------------------------------------------------------------------- /js/jquery-3.1.1.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v3.1.1 | (c) jQuery Foundation | jquery.org/license */ 2 | !function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.1.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext,B=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,C=/^.[^:#\[\.,]*$/;function D(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):C.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(D(this,a||[],!1))},not:function(a){return this.pushStack(D(this,a||[],!0))},is:function(a){return!!D(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var E,F=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,G=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||E,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:F.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),B.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};G.prototype=r.fn,E=r(d);var H=/^(?:parents|prev(?:Until|All))/,I={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function J(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return J(a,"nextSibling")},prev:function(a){return J(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return a.contentDocument||r.merge([],a.childNodes)}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(I[a]||r.uniqueSort(e),H.test(a)&&e.reverse()),this.pushStack(e)}});var K=/[^\x20\t\r\n\f]+/g;function L(a){var b={};return r.each(a.match(K)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?L(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function M(a){return a}function N(a){throw a}function O(a,b,c){var d;try{a&&r.isFunction(d=a.promise)?d.call(a).done(b).fail(c):a&&r.isFunction(d=a.then)?d.call(a,b,c):b.call(void 0,a)}catch(a){c.call(void 0,a)}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==N&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:M,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:M)),c[2][3].add(g(0,a,r.isFunction(d)?d:N))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(O(a,g.done(h(c)).resolve,g.reject),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)O(e[c],h(c),g.reject);return g.promise()}});var P=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&P.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var Q=r.Deferred();r.fn.ready=function(a){return Q.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,holdReady:function(a){a?r.readyWait++:r.ready(!0)},ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||Q.resolveWith(d,[r]))}}),r.ready.then=Q.then;function R(){d.removeEventListener("DOMContentLoaded",R), 3 | a.removeEventListener("load",R),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",R),a.addEventListener("load",R));var S=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)S(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){W.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=V.get(a,b),c&&(!d||r.isArray(c)?d=V.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return V.get(a,c)||V.access(a,c,{empty:r.Callbacks("once memory").add(function(){V.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,ka=/^$|\/(?:java|ecma)script/i,la={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};la.optgroup=la.option,la.tbody=la.tfoot=la.colgroup=la.caption=la.thead,la.th=la.td;function ma(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&r.nodeName(a,b)?r.merge([a],c):c}function na(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=ma(l.appendChild(f),"script"),j&&na(g),c){k=0;while(f=g[k++])ka.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var qa=d.documentElement,ra=/^key/,sa=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ta=/^([^.]*)(?:\.(.+)|)/;function ua(){return!0}function va(){return!1}function wa(){try{return d.activeElement}catch(a){}}function xa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)xa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=va;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(qa,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(K)||[""],j=b.length;while(j--)h=ta.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.hasData(a)&&V.get(a);if(q&&(i=q.events)){b=(b||"").match(K)||[""],j=b.length;while(j--)if(h=ta.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&V.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(V.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i\x20\t\r\n\f]*)[^>]*)\/>/gi,za=/\s*$/g;function Da(a,b){return r.nodeName(a,"table")&&r.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a:a}function Ea(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Fa(a){var b=Ba.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ga(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(V.hasData(a)&&(f=V.access(a),g=V.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&Aa.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ia(f,b,c,d)});if(m&&(e=pa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(ma(e,"script"),Ea),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=ma(h),f=ma(a),d=0,e=f.length;d0&&na(g,!i&&ma(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(T(c)){if(b=c[V.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[V.expando]=void 0}c[W.expando]&&(c[W.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ja(this,a,!0)},remove:function(a){return Ja(this,a)},text:function(a){return S(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ia(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Da(this,a);b.appendChild(a)}})},prepend:function(){return Ia(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Da(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ia(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ia(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(ma(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return S(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!za.test(a)&&!la[(ja.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function Ya(a,b,c,d,e){return new Ya.prototype.init(a,b,c,d,e)}r.Tween=Ya,Ya.prototype={constructor:Ya,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=Ya.propHooks[this.prop];return a&&a.get?a.get(this):Ya.propHooks._default.get(this)},run:function(a){var b,c=Ya.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Ya.propHooks._default.set(this),this}},Ya.prototype.init.prototype=Ya.prototype,Ya.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},Ya.propHooks.scrollTop=Ya.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=Ya.prototype.init,r.fx.step={};var Za,$a,_a=/^(?:toggle|show|hide)$/,ab=/queueHooks$/;function bb(){$a&&(a.requestAnimationFrame(bb),r.fx.tick())}function cb(){return a.setTimeout(function(){Za=void 0}),Za=r.now()}function db(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ba[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function eb(a,b,c){for(var d,e=(hb.tweeners[b]||[]).concat(hb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?ib:void 0)), 4 | void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&r.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(K);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),ib={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=jb[b]||r.find.attr;jb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=jb[g],jb[g]=e,e=null!=c(a,b,d)?g:null,jb[g]=f),e}});var kb=/^(?:input|select|textarea|button)$/i,lb=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return S(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):kb.test(a.nodeName)||lb.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function mb(a){var b=a.match(K)||[];return b.join(" ")}function nb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,nb(this)))});if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=nb(c),d=1===c.nodeType&&" "+mb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=mb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,nb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=nb(c),d=1===c.nodeType&&" "+mb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=mb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,nb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(K)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=nb(this),b&&V.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":V.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+mb(nb(c))+" ").indexOf(b)>-1)return!0;return!1}});var ob=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":r.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(ob,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:mb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(r.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var pb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!pb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,pb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(V.get(h,"events")||{})[b.type]&&V.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&T(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!T(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=V.access(d,b);e||d.addEventListener(a,c,!0),V.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=V.access(d,b)-1;e?V.access(d,b,e):(d.removeEventListener(a,c,!0),V.remove(d,b))}}});var qb=a.location,rb=r.now(),sb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var tb=/\[\]$/,ub=/\r?\n/g,vb=/^(?:submit|button|image|reset|file)$/i,wb=/^(?:input|select|textarea|keygen)/i;function xb(a,b,c,d){var e;if(r.isArray(b))r.each(b,function(b,e){c||tb.test(a)?d(a,e):xb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)xb(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(r.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)xb(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&wb.test(this.nodeName)&&!vb.test(a)&&(this.checked||!ia.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:r.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(ub,"\r\n")}}):{name:b.name,value:c.replace(ub,"\r\n")}}).get()}});var yb=/%20/g,zb=/#.*$/,Ab=/([?&])_=[^&]*/,Bb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Cb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Db=/^(?:GET|HEAD)$/,Eb=/^\/\//,Fb={},Gb={},Hb="*/".concat("*"),Ib=d.createElement("a");Ib.href=qb.href;function Jb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(K)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Kb(a,b,c,d){var e={},f=a===Gb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Lb(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Mb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Nb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:qb.href,type:"GET",isLocal:Cb.test(qb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Hb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Lb(Lb(a,r.ajaxSettings),b):Lb(r.ajaxSettings,a)},ajaxPrefilter:Jb(Fb),ajaxTransport:Jb(Gb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Bb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||qb.href)+"").replace(Eb,qb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(K)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Ib.protocol+"//"+Ib.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Kb(Fb,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Db.test(o.type),f=o.url.replace(zb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(yb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(sb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Ab,"$1"),n=(sb.test(f)?"&":"?")+"_="+rb++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Hb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Kb(Gb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Mb(o,y,d)),v=Nb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Ob={0:200,1223:204},Pb=r.ajaxSettings.xhr();o.cors=!!Pb&&"withCredentials"in Pb,o.ajax=Pb=!!Pb,r.ajaxTransport(function(b){var c,d;if(o.cors||Pb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Ob[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r("