Repository: hsen-dev/vscode-elastic Branch: master Commit: 0baefb194bc6 Files: 395 Total size: 1.2 MB Directory structure: gitextract_b_w37aut/ ├── .github/ │ └── workflows/ │ ├── publish.yaml │ └── runTests.yaml ├── .gitignore ├── .husky/ │ └── pre-commit ├── .prettierrc.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build/ │ └── restSpecIndex.js ├── grammar/ │ ├── es.configuration.json │ └── es.tmLanguage ├── media/ │ ├── jquery.contextMenu.css │ ├── jquery.contextMenu.js │ ├── json-formatter.js │ ├── result.tmpl │ └── results.css ├── package.json ├── src/ │ ├── ElasticCodeLensProvider.ts │ ├── ElasticCompletionItemProvider.ts │ ├── ElasticContentProvider.ts │ ├── ElasticDecoration.ts │ ├── ElasticMatch.ts │ ├── ElasticMatches.ts │ ├── axiosInstance.ts │ ├── extension.ts │ ├── helpers.ts │ ├── jsonPanel.ts │ └── rest-spec/ │ ├── index.ts │ ├── json.d.ts │ ├── v2_4_6/ │ │ ├── bulk.json │ │ ├── cat.aliases.json │ │ ├── cat.allocation.json │ │ ├── cat.count.json │ │ ├── cat.fielddata.json │ │ ├── cat.health.json │ │ ├── cat.help.json │ │ ├── cat.indices.json │ │ ├── cat.master.json │ │ ├── cat.nodeattrs.json │ │ ├── cat.nodes.json │ │ ├── cat.pending_tasks.json │ │ ├── cat.plugins.json │ │ ├── cat.recovery.json │ │ ├── cat.repositories.json │ │ ├── cat.segments.json │ │ ├── cat.shards.json │ │ ├── cat.snapshots.json │ │ ├── cat.thread_pool.json │ │ ├── clear_scroll.json │ │ ├── cluster.get_settings.json │ │ ├── cluster.health.json │ │ ├── cluster.pending_tasks.json │ │ ├── cluster.put_settings.json │ │ ├── cluster.reroute.json │ │ ├── cluster.state.json │ │ ├── cluster.stats.json │ │ ├── count.json │ │ ├── count_percolate.json │ │ ├── delete.json │ │ ├── delete_script.json │ │ ├── delete_template.json │ │ ├── exists.json │ │ ├── explain.json │ │ ├── field_stats.json │ │ ├── get.json │ │ ├── get_script.json │ │ ├── get_source.json │ │ ├── get_template.json │ │ ├── index.json │ │ ├── index.ts │ │ ├── indices.analyze.json │ │ ├── indices.clear_cache.json │ │ ├── indices.close.json │ │ ├── indices.create.json │ │ ├── indices.delete.json │ │ ├── indices.delete_alias.json │ │ ├── indices.delete_template.json │ │ ├── indices.delete_warmer.json │ │ ├── indices.exists.json │ │ ├── indices.exists_alias.json │ │ ├── indices.exists_template.json │ │ ├── indices.exists_type.json │ │ ├── indices.flush.json │ │ ├── indices.flush_synced.json │ │ ├── indices.forcemerge.json │ │ ├── indices.get.json │ │ ├── indices.get_alias.json │ │ ├── indices.get_aliases.json │ │ ├── indices.get_field_mapping.json │ │ ├── indices.get_mapping.json │ │ ├── indices.get_settings.json │ │ ├── indices.get_template.json │ │ ├── indices.get_upgrade.json │ │ ├── indices.get_warmer.json │ │ ├── indices.open.json │ │ ├── indices.optimize.json │ │ ├── indices.put_alias.json │ │ ├── indices.put_mapping.json │ │ ├── indices.put_settings.json │ │ ├── indices.put_template.json │ │ ├── indices.put_warmer.json │ │ ├── indices.recovery.json │ │ ├── indices.refresh.json │ │ ├── indices.segments.json │ │ ├── indices.shard_stores.json │ │ ├── indices.stats.json │ │ ├── indices.update_aliases.json │ │ ├── indices.upgrade.json │ │ ├── indices.validate_query.json │ │ ├── info.json │ │ ├── mget.json │ │ ├── mpercolate.json │ │ ├── msearch.json │ │ ├── mtermvectors.json │ │ ├── nodes.hot_threads.json │ │ ├── nodes.info.json │ │ ├── nodes.stats.json │ │ ├── percolate.json │ │ ├── ping.json │ │ ├── put_script.json │ │ ├── put_template.json │ │ ├── reindex.json │ │ ├── reindex_rethrottle.json │ │ ├── render_search_template.json │ │ ├── scroll.json │ │ ├── search.json │ │ ├── search_exists.json │ │ ├── search_shards.json │ │ ├── search_template.json │ │ ├── snapshot.create.json │ │ ├── snapshot.create_repository.json │ │ ├── snapshot.delete.json │ │ ├── snapshot.delete_repository.json │ │ ├── snapshot.get.json │ │ ├── snapshot.get_repository.json │ │ ├── snapshot.restore.json │ │ ├── snapshot.status.json │ │ ├── snapshot.verify_repository.json │ │ ├── suggest.json │ │ ├── tasks.cancel.json │ │ ├── tasks.list.json │ │ ├── termvectors.json │ │ ├── update.json │ │ └── update_by_query.json │ ├── v5_6_4/ │ │ ├── _common.json │ │ ├── bulk.json │ │ ├── cat.aliases.json │ │ ├── cat.allocation.json │ │ ├── cat.count.json │ │ ├── cat.fielddata.json │ │ ├── cat.health.json │ │ ├── cat.help.json │ │ ├── cat.indices.json │ │ ├── cat.master.json │ │ ├── cat.nodeattrs.json │ │ ├── cat.nodes.json │ │ ├── cat.pending_tasks.json │ │ ├── cat.plugins.json │ │ ├── cat.recovery.json │ │ ├── cat.repositories.json │ │ ├── cat.segments.json │ │ ├── cat.shards.json │ │ ├── cat.snapshots.json │ │ ├── cat.tasks.json │ │ ├── cat.templates.json │ │ ├── cat.thread_pool.json │ │ ├── clear_scroll.json │ │ ├── cluster.allocation_explain.json │ │ ├── cluster.get_settings.json │ │ ├── cluster.health.json │ │ ├── cluster.pending_tasks.json │ │ ├── cluster.put_settings.json │ │ ├── cluster.reroute.json │ │ ├── cluster.state.json │ │ ├── cluster.stats.json │ │ ├── count.json │ │ ├── count_percolate.json │ │ ├── create.json │ │ ├── delete.json │ │ ├── delete_by_query.json │ │ ├── delete_script.json │ │ ├── delete_template.json │ │ ├── exists.json │ │ ├── exists_source.json │ │ ├── explain.json │ │ ├── field_caps.json │ │ ├── field_stats.json │ │ ├── get.json │ │ ├── get_script.json │ │ ├── get_source.json │ │ ├── get_template.json │ │ ├── index.json │ │ ├── index.ts │ │ ├── indices.analyze.json │ │ ├── indices.clear_cache.json │ │ ├── indices.close.json │ │ ├── indices.create.json │ │ ├── indices.delete.json │ │ ├── indices.delete_alias.json │ │ ├── indices.delete_template.json │ │ ├── indices.exists.json │ │ ├── indices.exists_alias.json │ │ ├── indices.exists_template.json │ │ ├── indices.exists_type.json │ │ ├── indices.flush.json │ │ ├── indices.flush_synced.json │ │ ├── indices.forcemerge.json │ │ ├── indices.get.json │ │ ├── indices.get_alias.json │ │ ├── indices.get_field_mapping.json │ │ ├── indices.get_mapping.json │ │ ├── indices.get_settings.json │ │ ├── indices.get_template.json │ │ ├── indices.get_upgrade.json │ │ ├── indices.open.json │ │ ├── indices.put_alias.json │ │ ├── indices.put_mapping.json │ │ ├── indices.put_settings.json │ │ ├── indices.put_template.json │ │ ├── indices.recovery.json │ │ ├── indices.refresh.json │ │ ├── indices.rollover.json │ │ ├── indices.segments.json │ │ ├── indices.shard_stores.json │ │ ├── indices.shrink.json │ │ ├── indices.stats.json │ │ ├── indices.update_aliases.json │ │ ├── indices.upgrade.json │ │ ├── indices.validate_query.json │ │ ├── info.json │ │ ├── ingest.delete_pipeline.json │ │ ├── ingest.get_pipeline.json │ │ ├── ingest.processor.grok.json │ │ ├── ingest.put_pipeline.json │ │ ├── ingest.simulate.json │ │ ├── mget.json │ │ ├── mpercolate.json │ │ ├── msearch.json │ │ ├── msearch_template.json │ │ ├── mtermvectors.json │ │ ├── nodes.hot_threads.json │ │ ├── nodes.info.json │ │ ├── nodes.stats.json │ │ ├── percolate.json │ │ ├── ping.json │ │ ├── put_script.json │ │ ├── put_template.json │ │ ├── reindex.json │ │ ├── reindex_rethrottle.json │ │ ├── remote.info.json │ │ ├── render_search_template.json │ │ ├── scroll.json │ │ ├── search.json │ │ ├── search_shards.json │ │ ├── search_template.json │ │ ├── snapshot.create.json │ │ ├── snapshot.create_repository.json │ │ ├── snapshot.delete.json │ │ ├── snapshot.delete_repository.json │ │ ├── snapshot.get.json │ │ ├── snapshot.get_repository.json │ │ ├── snapshot.restore.json │ │ ├── snapshot.status.json │ │ ├── snapshot.verify_repository.json │ │ ├── suggest.json │ │ ├── tasks.cancel.json │ │ ├── tasks.get.json │ │ ├── tasks.list.json │ │ ├── termvectors.json │ │ ├── update.json │ │ └── update_by_query.json │ └── v6_0_0/ │ ├── _common.json │ ├── bulk.json │ ├── cat.aliases.json │ ├── cat.allocation.json │ ├── cat.count.json │ ├── cat.fielddata.json │ ├── cat.health.json │ ├── cat.help.json │ ├── cat.indices.json │ ├── cat.master.json │ ├── cat.nodeattrs.json │ ├── cat.nodes.json │ ├── cat.pending_tasks.json │ ├── cat.plugins.json │ ├── cat.recovery.json │ ├── cat.repositories.json │ ├── cat.segments.json │ ├── cat.shards.json │ ├── cat.snapshots.json │ ├── cat.tasks.json │ ├── cat.templates.json │ ├── cat.thread_pool.json │ ├── clear_scroll.json │ ├── cluster.allocation_explain.json │ ├── cluster.get_settings.json │ ├── cluster.health.json │ ├── cluster.pending_tasks.json │ ├── cluster.put_settings.json │ ├── cluster.reroute.json │ ├── cluster.state.json │ ├── cluster.stats.json │ ├── count.json │ ├── create.json │ ├── delete.json │ ├── delete_by_query.json │ ├── delete_script.json │ ├── exists.json │ ├── exists_source.json │ ├── explain.json │ ├── field_caps.json │ ├── get.json │ ├── get_script.json │ ├── get_source.json │ ├── index.json │ ├── index.ts │ ├── indices.analyze.json │ ├── indices.clear_cache.json │ ├── indices.close.json │ ├── indices.create.json │ ├── indices.delete.json │ ├── indices.delete_alias.json │ ├── indices.delete_template.json │ ├── indices.exists.json │ ├── indices.exists_alias.json │ ├── indices.exists_template.json │ ├── indices.exists_type.json │ ├── indices.flush.json │ ├── indices.flush_synced.json │ ├── indices.forcemerge.json │ ├── indices.get.json │ ├── indices.get_alias.json │ ├── indices.get_field_mapping.json │ ├── indices.get_mapping.json │ ├── indices.get_settings.json │ ├── indices.get_template.json │ ├── indices.get_upgrade.json │ ├── indices.open.json │ ├── indices.put_alias.json │ ├── indices.put_mapping.json │ ├── indices.put_settings.json │ ├── indices.put_template.json │ ├── indices.recovery.json │ ├── indices.refresh.json │ ├── indices.rollover.json │ ├── indices.segments.json │ ├── indices.shard_stores.json │ ├── indices.shrink.json │ ├── indices.stats.json │ ├── indices.update_aliases.json │ ├── indices.upgrade.json │ ├── indices.validate_query.json │ ├── info.json │ ├── ingest.delete_pipeline.json │ ├── ingest.get_pipeline.json │ ├── ingest.processor.grok.json │ ├── ingest.put_pipeline.json │ ├── ingest.simulate.json │ ├── mget.json │ ├── msearch.json │ ├── msearch_template.json │ ├── mtermvectors.json │ ├── nodes.hot_threads.json │ ├── nodes.info.json │ ├── nodes.stats.json │ ├── nodes.usage.json │ ├── ping.json │ ├── put_script.json │ ├── reindex.json │ ├── reindex_rethrottle.json │ ├── remote.info.json │ ├── render_search_template.json │ ├── scroll.json │ ├── search.json │ ├── search_shards.json │ ├── search_template.json │ ├── snapshot.create.json │ ├── snapshot.create_repository.json │ ├── snapshot.delete.json │ ├── snapshot.delete_repository.json │ ├── snapshot.get.json │ ├── snapshot.get_repository.json │ ├── snapshot.restore.json │ ├── snapshot.status.json │ ├── snapshot.verify_repository.json │ ├── tasks.cancel.json │ ├── tasks.get.json │ ├── tasks.list.json │ ├── termvectors.json │ ├── update.json │ └── update_by_query.json ├── tsconfig.json ├── tslint-imports.json └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/publish.yaml ================================================ name: Publish To Vscode on: push: branches: - master jobs: publish: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Install Node.js uses: actions/setup-node@v1 with: node-version: 16.x - run: npm i - name: Publish run: npm run deploy env: VSCE_PAT: ${{ secrets.VSCE_PAT }} REPO_URL: "${{ github.server_url }}/${{ github.repository }}" BRANCH: "${GITHUB_REF##*/}" ================================================ FILE: .github/workflows/runTests.yaml ================================================ name: Run Tests on: push: branches: - master pull_request: branches: [ master ] jobs: prepare: runs-on: ubuntu-latest name: Run Tests steps: - name: Checkout uses: actions/checkout@v2 - name: Install Node uses: actions/setup-node@v1 with: node-version: '16.x' - name: Install Dependencies run: npm i - name: Run Tests run: npm run test ================================================ FILE: .gitignore ================================================ out node_modules .vscode .DS_Store package-lock.json ================================================ FILE: .husky/pre-commit ================================================ #!/bin/sh . "$(dirname "$0")/_/husky.sh" npm version patch --commit-hooks false --git-tag-version false --force git add . ================================================ FILE: .prettierrc.json ================================================ { "bracketSpacing": true, "jsxSingleQuote": true, "tabWidth": 4, "arrowParens": "avoid", "endOfLine": "auto", "htmlWhitespaceSensitivity": "css", "jsxBracketSameLine": false, "proseWrap": "preserve", "semi": true, "singleQuote": true, "printWidth": 160, "useTabs": false, "trailingComma": "all" } ================================================ FILE: .vscodeignore ================================================ .vscode/** .vscode-test/** out/test/** test/** src/** **/*.map .gitignore tsconfig.json vsc-extension-quickstart.md ================================================ FILE: CHANGELOG.md ================================================ ## [0.13] - show simple documention of urls endpoints and its external links to [elastic.co](https://elastic.co) on hover. ## [0.12] - [@KenDobbins](https://github.com/KenDobbins): Allow ctrl+enter to run command - better request body selection (blank line allowed) - simple protocol (https) support ## [0.11] - [@jgowdyelastic](https://github.com/jgowdyelastic): Using tabSize setting for json indentation - minor bugfix ## [0.10] - adding requested feature: [keybindings](https://github.com/hsen-dev/vscode-elastic/issues/5) - fix some decorations issues ## [0.9] - [@tsouza](https://github.com/tsouza): add IntelliSense/autocomplete for es6 - bug fixes ## [0.8] - [@tsouza](https://github.com/tsouza): add simple IntelliSense/autocomplete for methods path - [@heatwave](https://github.com/heatwave): bug fixes ## [0.7] - Get Payload from File ([requested feature](https://github.com/hsen-dev/vscode-elastic/issues/4)) ## [0.6] - fixed issue [#1](https://github.com/hsen-dev/vscode-elastic/issues/1): elasticsearch logo style guides - adding [requested feature](https://github.com/hsen-dev/vscode-elastic/issues/3): `elastic.showResultAsDocument`: Show result in a new json file, or show in default view. - some bug fixes - better syntax highlight ## [0.5] - big changes: migrate execution of query to CodeLens. - add Json validator and `Auto indent` command. ## [0.4] - bug fixes - add context menu for `copy JSON path` and `copy JSON content` ## [0.3] - bug fixes - better output ui - add `_cat` toolbar - work with `Elasticsearch` file type without `.es` extention ## [0.2] - bug fixes - better output ui ## [0.1] - initial release ================================================ FILE: LICENSE ================================================ This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to ================================================ FILE: README.md ================================================ ## Elasticsearch for VSCode [![.github/workflows/runTests.yaml](/../../actions/workflows/runTests.yaml/badge.svg)](/../../actions/workflows/runTests.yaml) [![.github/workflows/publish.yaml](/../../actions/workflows/publish.yaml/badge.svg)](/../../actions/workflows/publish.yaml) Welcome to Elasticsearch for VSCode! An extension for developing elasticsearch queries. ![shot](shots/all.gif) ## Using - Open an existing file with a `.es` file extenion or open a new text file (`ctrl+n`) and change the language mode to `Elasticsearch (es)` by pressing `ctrl+k,m` and select `es`. Elasticsearch queries and funtionalities are enabled in the es language mode in Visual Studio Code editor. - For https endpoints, just add protocol type in url : `https://host` - For auth protected clusters, you can use `http://user:pass@host:9200` as the endpoint url to have it auth. ### Submit requests Simple way: ```text GET /my-index/_search { "size":7, "query": { "match" : { "message" : { "query" : "this is a test" } } } } ``` Get payload from file [[#4](https://github.com/hsen-dev/vscode-elastic/issues/4)]: ```text PUT /my-index !./opt/elasticsearch/mapping.json ``` ## Commands - **Elasticsearch: Set Host** to create connection profile and connect. ## Keymaps - **Alt + Enter** / **Ctrl + Enter** to execute selected query. ## Roadmap - Work with multi host - User Authentication - IntelliSense like kibana autocomplete ================================================ FILE: build/restSpecIndex.js ================================================ /*jslint esversion: 6*/ const del = require('del'); const fs = require('fs'); const jsesc = require('jsesc'); (function () { 'use strict'; const restSpecPath = '../src/rest-spec'; const SRC_ROOT = `${__dirname}/${restSpecPath}`; del.sync(`${SRC_ROOT}/**/*.ts`); fs.writeFileSync(`${SRC_ROOT}/json.d.ts`, 'declare module "*.json" { const value: any; export default value; }'); const requireDir = require('require-dir'); const restSpec = requireDir(restSpecPath, { recurse: true }); let rootIndex = ''; let versions = Object.keys(restSpec); versions.forEach(version => { rootIndex += `import * as ${version} from './${version}';\n`; let endpointNames = Object.keys(restSpec[version]); let versionIndex = endpointNames .map(endpointName => `const def_${endpointName.replace(/\./g, '_')} = JSON.parse('${jsesc(JSON.stringify(restSpec[version][endpointName]))}')`) .join(';\n'); versionIndex += `\n\nexport default {\n`; versionIndex += endpointNames .map(endpointName => ` '${endpointName}': def_${endpointName.replace(/\./g, '_')}` + (endpointName != '_common' ? `['${endpointName}']` : '')) .join(',\n'); versionIndex += '\n}'; fs.writeFileSync(`${SRC_ROOT}/${version}/index.ts`, versionIndex, 'utf-8'); }); rootIndex += `\nexport default {\n`; rootIndex += versions.map(version => ` '${version.substring(1).replace(/_/g, '.')}': ${version}`).join(',\n'); rootIndex += '\n}'; fs.writeFileSync(`${SRC_ROOT}/index.ts`, rootIndex, 'utf-8'); })(); ================================================ FILE: grammar/es.configuration.json ================================================ { "comments": { "lineComment": "//", "blockComment": [ "/*", "*/" ] }, "brackets": [ [ "{", "}" ], [ "[", "]" ] ], "autoClosingPairs": [ { "open": "{", "close": "}" }, { "open": "[", "close": "]" }, { "open": "(", "close": ")" }, { "open": "'", "close": "'", "notIn": [ "string", "comment" ] }, { "open": "\"", "close": "\"", "notIn": [ "string" ] }, { "open": "/**", "close": " */", "notIn": [ "string" ] } ] } ================================================ FILE: grammar/es.tmLanguage ================================================ fileTypes es json sublime-settings sublime-menu sublime-keymap sublime-mousemap sublime-theme sublime-build sublime-project sublime-completions foldingStartMarker (?x) # turn on extended mode ^ # a line beginning with \s* # some optional space [{\[] # the start of an object or array (?! # but not followed by .* # whatever [}\]] # and the close of an object or array ,? # an optional comma \s* # some optional space $ # at the end of the line ) | # ...or... [{\[] # the start of an object or array \s* # some optional space $ # at the end of the line foldingStopMarker (?x) # turn on extended mode ^ # a line beginning with \s* # some optional space [}\]] # and the close of an object or array keyEquivalent ^~J name JSON (Javascript Next) patterns include #value repository array begin \[ beginCaptures 0 name punctuation.definition.array.begin.json end \] endCaptures 0 name punctuation.definition.array.end.json name meta.structure.array.json patterns include #value match , name punctuation.separator.array.json match [^\s\]] name invalid.illegal.expected-array-separator.json comments patterns begin /\*\*(?!/) captures 0 name punctuation.definition.comment.json end \*/ name comment.block.documentation.json begin /\* captures 0 name punctuation.definition.comment.json end \*/ name comment.block.json captures 1 name punctuation.definition.comment.json match (//).*$\n? name comment.line.double-slash.js constant match \b(?:true|false|null)\b name constant.language.json number match (?x) # turn on extended mode -? # an optional minus (?: 0 # a zero | # ...or... [1-9] # a 1-9 character \d* # followed by zero or more digits ) (?: (?: \. # a period \d+ # followed by one or more digits )? (?: [eE] # an e character [+-]? # followed by an option +/- \d+ # followed by one or more digits )? # make exponent optional )? # make decimal portion optional name constant.numeric.json object begin \{ beginCaptures 0 name punctuation.definition.dictionary.begin.json end \} endCaptures 0 name punctuation.definition.dictionary.end.json name meta.structure.dictionary.json patterns comment the JSON object key include #objectkey include #comments begin : beginCaptures 0 name punctuation.separator.dictionary.key-value.json end (,)|(?=\}) endCaptures 1 name punctuation.separator.dictionary.pair.json name meta.structure.dictionary.value.json patterns comment the JSON object value include #value match [^\s,] name invalid.illegal.expected-dictionary-separator.json match [^\s\}] name invalid.illegal.expected-dictionary-separator.json string begin " beginCaptures 0 name punctuation.definition.string.begin.json end " endCaptures 0 name punctuation.definition.string.end.json name string.quoted.double.json patterns include #stringcontent objectkey begin " beginCaptures 0 name punctuation.support.type.property-name.begin.json end " endCaptures 0 name punctuation.support.type.property-name.end.json name string.json support.type.property-name.json patterns include #stringcontent stringcontent patterns match (?x) # turn on extended mode \\ # a literal backslash (?: # ...followed by... ["\\/bfnrt] # one of these characters | # ...or... u # a u [0-9a-fA-F]{4}) # and four hex digits name constant.character.escape.json match \\. name invalid.illegal.unrecognized-string-escape.json value patterns include #constant include #number include #string include #array include #object include #comments scopeName source.es uuid 8f97457b-516e-48ce-83c7-08ae12fb327a ================================================ FILE: media/jquery.contextMenu.css ================================================ @charset "UTF-8"; /*! * jQuery contextMenu - Plugin for simple contextMenu handling * * Version: v2.4.2 * * Authors: Björn Brala (SWIS.nl), Rodney Rehm, Addy Osmani (patches for FF) * Web: http://swisnl.github.io/jQuery-contextMenu/ * * Copyright (c) 2011-2017 SWIS BV and contributors * * Licensed under * MIT License http://www.opensource.org/licenses/mit-license * * Date: 2017-01-02T09:03:00.383Z */ @-webkit-keyframes cm-spin { 0% { -webkit-transform: translateY(-50%) rotate(0deg); transform: translateY(-50%) rotate(0deg); } 100% { -webkit-transform: translateY(-50%) rotate(359deg); transform: translateY(-50%) rotate(359deg); } } @-o-keyframes cm-spin { 0% { -webkit-transform: translateY(-50%) rotate(0deg); -o-transform: translateY(-50%) rotate(0deg); transform: translateY(-50%) rotate(0deg); } 100% { -webkit-transform: translateY(-50%) rotate(359deg); -o-transform: translateY(-50%) rotate(359deg); transform: translateY(-50%) rotate(359deg); } } @keyframes cm-spin { 0% { -webkit-transform: translateY(-50%) rotate(0deg); -o-transform: translateY(-50%) rotate(0deg); transform: translateY(-50%) rotate(0deg); } 100% { -webkit-transform: translateY(-50%) rotate(359deg); -o-transform: translateY(-50%) rotate(359deg); transform: translateY(-50%) rotate(359deg); } } @font-face { font-family: "context-menu-icons"; font-style: normal; font-weight: normal; src: url("font/context-menu-icons.eot?3lfph"); src: url("font/context-menu-icons.eot?3lfph#iefix") format("embedded-opentype"), url("font/context-menu-icons.woff2?3lfph") format("woff2"), url("font/context-menu-icons.woff?3lfph") format("woff"), url("font/context-menu-icons.ttf?3lfph") format("truetype"); } .context-menu-icon-add:before { content: "\EA01"; } .context-menu-icon-copy:before { content: "\EA02"; } .context-menu-icon-cut:before { content: "\EA03"; } .context-menu-icon-delete:before { content: "\EA04"; } .context-menu-icon-edit:before { content: "\EA05"; } .context-menu-icon-loading:before { content: "\EA06"; } .context-menu-icon-paste:before { content: "\EA07"; } .context-menu-icon-quit:before { content: "\EA08"; } .context-menu-icon::before { position: absolute; top: 50%; left: 0; width: 2em; font-family: "context-menu-icons"; font-size: 1em; font-style: normal; font-weight: normal; line-height: 1; color: #2980b9; text-align: center; -webkit-transform: translateY(-50%); -ms-transform: translateY(-50%); -o-transform: translateY(-50%); transform: translateY(-50%); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .context-menu-icon.context-menu-hover:before { color: #fff; } .context-menu-icon.context-menu-disabled::before { color: #bbb; } .context-menu-icon.context-menu-icon-loading:before { -webkit-animation: cm-spin 2s infinite; -o-animation: cm-spin 2s infinite; animation: cm-spin 2s infinite; } .context-menu-icon.context-menu-icon--fa { display: list-item; font-family: inherit; } .context-menu-icon.context-menu-icon--fa::before { position: absolute; top: 50%; left: 0; width: 2em; font-family: FontAwesome; font-size: 1em; font-style: normal; font-weight: normal; line-height: 1; color: #2980b9; text-align: center; -webkit-transform: translateY(-50%); -ms-transform: translateY(-50%); -o-transform: translateY(-50%); transform: translateY(-50%); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .context-menu-icon.context-menu-icon--fa.context-menu-hover:before { color: #fff; } .context-menu-icon.context-menu-icon--fa.context-menu-disabled::before { color: #bbb; } .context-menu-list { position: absolute; display: inline-block; min-width: 13em; max-width: 26em; padding: .25em 0; margin: .3em; font-family: inherit; font-size: inherit; list-style-type: none; background: #fff; border: 1px solid #bebebe; border-radius: .2em; -webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, .5); box-shadow: 0 2px 5px rgba(0, 0, 0, .5); } .context-menu-item { position: relative; padding: .2em 2em; color: #2f2f2f; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; background-color: #fff; } .context-menu-separator { padding: 0; margin: .35em 0; border-bottom: 1px solid #e6e6e6; } .context-menu-item > label > input, .context-menu-item > label > textarea { -webkit-user-select: text; -moz-user-select: text; -ms-user-select: text; user-select: text; } .context-menu-item.context-menu-hover { color: #fff; cursor: pointer; background-color: #2980b9; } .context-menu-item.context-menu-disabled { color: #bbb; cursor: default; background-color: #fff; } .context-menu-input.context-menu-hover { color: #2f2f2f; cursor: default; } .context-menu-submenu:after { position: absolute; top: 50%; right: .5em; z-index: 1; width: 0; height: 0; content: ''; border-color: transparent transparent transparent #2f2f2f; border-style: solid; border-width: .25em 0 .25em .25em; -webkit-transform: translateY(-50%); -ms-transform: translateY(-50%); -o-transform: translateY(-50%); transform: translateY(-50%); } /** * Inputs */ .context-menu-item.context-menu-input { padding: .3em .6em; } /* vertically align inside labels */ .context-menu-input > label > * { vertical-align: top; } /* position checkboxes and radios as icons */ .context-menu-input > label > input[type="checkbox"], .context-menu-input > label > input[type="radio"] { position: relative; top: .12em; margin-right: .4em; } .context-menu-input > label { margin: 0; } .context-menu-input > label, .context-menu-input > label > input[type="text"], .context-menu-input > label > textarea, .context-menu-input > label > select { display: block; width: 100%; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .context-menu-input > label > textarea { height: 7em; } .context-menu-item > .context-menu-list { top: .3em; /* re-positioned by js */ right: -.3em; display: none; } .context-menu-item.context-menu-visible > .context-menu-list { display: block; } .context-menu-accesskey { text-decoration: underline; } ================================================ FILE: media/jquery.contextMenu.js ================================================ /*! * jQuery contextMenu v2.4.2 - Plugin for simple contextMenu handling * * Version: v2.4.2 * * Authors: Björn Brala (SWIS.nl), Rodney Rehm, Addy Osmani (patches for FF) * Web: http://swisnl.github.io/jQuery-contextMenu/ * * Copyright (c) 2011-2017 SWIS BV and contributors * * Licensed under * MIT License http://www.opensource.org/licenses/mit-license * GPL v3 http://opensource.org/licenses/GPL-3.0 * * Date: 2017-01-02T09:03:00.678Z */ // jscs:disable /* jshint ignore:start */ (function (factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as anonymous module. define(['jquery'], factory); } else if (typeof exports === 'object') { // Node / CommonJS factory(require('jquery')); } else { // Browser globals. factory(jQuery); } })(function ($) { 'use strict'; // TODO: - // ARIA stuff: menuitem, menuitemcheckbox und menuitemradio // create structure if $.support[htmlCommand || htmlMenuitem] and !opt.disableNative // determine html5 compatibility $.support.htmlMenuitem = ('HTMLMenuItemElement' in window); $.support.htmlCommand = ('HTMLCommandElement' in window); $.support.eventSelectstart = ('onselectstart' in document.documentElement); /* // should the need arise, test for css user-select $.support.cssUserSelect = (function(){ var t = false, e = document.createElement('div'); $.each('Moz|Webkit|Khtml|O|ms|Icab|'.split('|'), function(i, prefix) { var propCC = prefix + (prefix ? 'U' : 'u') + 'serSelect', prop = (prefix ? ('-' + prefix.toLowerCase() + '-') : '') + 'user-select'; e.style.cssText = prop + ': text;'; if (e.style[propCC] == 'text') { t = true; return false; } return true; }); return t; })(); */ if (!$.ui || !$.widget) { // duck punch $.cleanData like jQueryUI does to get that remove event $.cleanData = (function (orig) { return function (elems) { var events, elem, i; for (i = 0; elems[i] != null; i++) { elem = elems[i]; try { // Only trigger remove when necessary to save time events = $._data(elem, 'events'); if (events && events.remove) { $(elem).triggerHandler('remove'); } // Http://bugs.jquery.com/ticket/8235 } catch (e) {} } orig(elems); }; })($.cleanData); } /* jshint ignore:end */ // jscs:enable var // currently active contextMenu trigger $currentTrigger = null, // is contextMenu initialized with at least one menu? initialized = false, // window handle $win = $(window), // number of registered menus counter = 0, // mapping selector to namespace namespaces = {}, // mapping namespace to options menus = {}, // custom command type handlers types = {}, // default values defaults = { // selector of contextMenu trigger selector: null, // where to append the menu to appendTo: null, // method to trigger context menu ["right", "left", "hover"] trigger: 'right', // hide menu when mouse leaves trigger / menu elements autoHide: false, // ms to wait before showing a hover-triggered context menu delay: 200, // flag denoting if a second trigger should simply move (true) or rebuild (false) an open menu // as long as the trigger happened on one of the trigger-element's child nodes reposition: true, //ability to select submenu selectableSubMenu: false, // Default classname configuration to be able avoid conflicts in frameworks classNames : { hover: 'context-menu-hover', // Item hover disabled: 'context-menu-disabled', // Item disabled visible: 'context-menu-visible', // Item visible notSelectable: 'context-menu-not-selectable', // Item not selectable icon: 'context-menu-icon', iconEdit: 'context-menu-icon-edit', iconCut: 'context-menu-icon-cut', iconCopy: 'context-menu-icon-copy', iconPaste: 'context-menu-icon-paste', iconDelete: 'context-menu-icon-delete', iconAdd: 'context-menu-icon-add', iconQuit: 'context-menu-icon-quit', iconLoadingClass: 'context-menu-icon-loading' }, // determine position to show menu at determinePosition: function ($menu) { // position to the lower middle of the trigger element if ($.ui && $.ui.position) { // .position() is provided as a jQuery UI utility // (...and it won't work on hidden elements) $menu.css('display', 'block').position({ my: 'center top', at: 'center bottom', of: this, offset: '0 5', collision: 'fit' }).css('display', 'none'); } else { // determine contextMenu position var offset = this.offset(); offset.top += this.outerHeight(); offset.left += this.outerWidth() / 2 - $menu.outerWidth() / 2; $menu.css(offset); } }, // position menu position: function (opt, x, y) { var offset; // determine contextMenu position if (!x && !y) { opt.determinePosition.call(this, opt.$menu); return; } else if (x === 'maintain' && y === 'maintain') { // x and y must not be changed (after re-show on command click) offset = opt.$menu.position(); } else { // x and y are given (by mouse event) offset = {top: y, left: x}; } // correct offset if viewport demands it var bottom = $win.scrollTop() + $win.height(), right = $win.scrollLeft() + $win.width(), height = opt.$menu.outerHeight(), width = opt.$menu.outerWidth(); if (offset.top + height > bottom) { offset.top -= height; } if (offset.top < 0) { offset.top = 0; } if (offset.left + width > right) { offset.left -= width; } if (offset.left < 0) { offset.left = 0; } opt.$menu.css(offset); }, // position the sub-menu positionSubmenu: function ($menu) { if ($menu === undefined) { // When user hovers over item (which has sub items) handle.focusItem will call this. // but the submenu does not exist yet if opt.items is a promise. just return, will // call positionSubmenu after promise is completed. return; } if ($.ui && $.ui.position) { // .position() is provided as a jQuery UI utility // (...and it won't work on hidden elements) $menu.css('display', 'block').position({ my: 'left top-5', at: 'right top', of: this, collision: 'flipfit fit' }).css('display', ''); } else { // determine contextMenu position var offset = { top: -9, left: this.outerWidth() - 5 }; $menu.css(offset); } }, // offset to add to zIndex zIndex: 1, // show hide animation settings animation: { duration: 50, show: 'slideDown', hide: 'slideUp' }, // events events: { show: $.noop, hide: $.noop }, // default callback callback: null, // list of contextMenu items items: {} }, // mouse position for hover activation hoveract = { timer: null, pageX: null, pageY: null }, // determine zIndex zindex = function ($t) { var zin = 0, $tt = $t; while (true) { zin = Math.max(zin, parseInt($tt.css('z-index'), 10) || 0); $tt = $tt.parent(); if (!$tt || !$tt.length || 'html body'.indexOf($tt.prop('nodeName').toLowerCase()) > -1) { break; } } return zin; }, // event handlers handle = { // abort anything abortevent: function (e) { e.preventDefault(); e.stopImmediatePropagation(); }, // contextmenu show dispatcher contextmenu: function (e) { var $this = $(this); // disable actual context-menu if we are using the right mouse button as the trigger if (e.data.trigger === 'right') { e.preventDefault(); e.stopImmediatePropagation(); } // abort native-triggered events unless we're triggering on right click if ((e.data.trigger !== 'right' && e.data.trigger !== 'demand') && e.originalEvent) { return; } // Let the current contextmenu decide if it should show or not based on its own trigger settings if (e.mouseButton !== undefined && e.data) { if (!(e.data.trigger === 'left' && e.mouseButton === 0) && !(e.data.trigger === 'right' && e.mouseButton === 2)) { // Mouse click is not valid. return; } } // abort event if menu is visible for this trigger if ($this.hasClass('context-menu-active')) { return; } if (!$this.hasClass('context-menu-disabled')) { // theoretically need to fire a show event at // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#context-menus // var evt = jQuery.Event("show", { data: data, pageX: e.pageX, pageY: e.pageY, relatedTarget: this }); // e.data.$menu.trigger(evt); $currentTrigger = $this; if (e.data.build) { var built = e.data.build($currentTrigger, e); // abort if build() returned false if (built === false) { return; } // dynamically build menu on invocation e.data = $.extend(true, {}, defaults, e.data, built || {}); // abort if there are no items to display if (!e.data.items || $.isEmptyObject(e.data.items)) { // Note: jQuery captures and ignores errors from event handlers if (window.console) { (console.error || console.log).call(console, 'No items specified to show in contextMenu'); } throw new Error('No Items specified'); } // backreference for custom command type creation e.data.$trigger = $currentTrigger; op.create(e.data); } var showMenu = false; for (var item in e.data.items) { if (e.data.items.hasOwnProperty(item)) { var visible; if ($.isFunction(e.data.items[item].visible)) { visible = e.data.items[item].visible.call($(e.currentTarget), item, e.data); } else if (typeof e.data.items[item] !== 'undefined' && e.data.items[item].visible) { visible = e.data.items[item].visible === true; } else { visible = true; } if (visible) { showMenu = true; } } } if (showMenu) { // show menu op.show.call($this, e.data, e.pageX, e.pageY); } } }, // contextMenu left-click trigger click: function (e) { e.preventDefault(); e.stopImmediatePropagation(); $(this).trigger($.Event('contextmenu', {data: e.data, pageX: e.pageX, pageY: e.pageY})); }, // contextMenu right-click trigger mousedown: function (e) { // register mouse down var $this = $(this); // hide any previous menus if ($currentTrigger && $currentTrigger.length && !$currentTrigger.is($this)) { $currentTrigger.data('contextMenu').$menu.trigger('contextmenu:hide'); } // activate on right click if (e.button === 2) { $currentTrigger = $this.data('contextMenuActive', true); } }, // contextMenu right-click trigger mouseup: function (e) { // show menu var $this = $(this); if ($this.data('contextMenuActive') && $currentTrigger && $currentTrigger.length && $currentTrigger.is($this) && !$this.hasClass('context-menu-disabled')) { e.preventDefault(); e.stopImmediatePropagation(); $currentTrigger = $this; $this.trigger($.Event('contextmenu', {data: e.data, pageX: e.pageX, pageY: e.pageY})); } $this.removeData('contextMenuActive'); }, // contextMenu hover trigger mouseenter: function (e) { var $this = $(this), $related = $(e.relatedTarget), $document = $(document); // abort if we're coming from a menu if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) { return; } // abort if a menu is shown if ($currentTrigger && $currentTrigger.length) { return; } hoveract.pageX = e.pageX; hoveract.pageY = e.pageY; hoveract.data = e.data; $document.on('mousemove.contextMenuShow', handle.mousemove); hoveract.timer = setTimeout(function () { hoveract.timer = null; $document.off('mousemove.contextMenuShow'); $currentTrigger = $this; $this.trigger($.Event('contextmenu', { data: hoveract.data, pageX: hoveract.pageX, pageY: hoveract.pageY })); }, e.data.delay); }, // contextMenu hover trigger mousemove: function (e) { hoveract.pageX = e.pageX; hoveract.pageY = e.pageY; }, // contextMenu hover trigger mouseleave: function (e) { // abort if we're leaving for a menu var $related = $(e.relatedTarget); if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) { return; } try { clearTimeout(hoveract.timer); } catch (e) { } hoveract.timer = null; }, // click on layer to hide contextMenu layerClick: function (e) { var $this = $(this), root = $this.data('contextMenuRoot'), button = e.button, x = e.pageX, y = e.pageY, target, offset; e.preventDefault(); e.stopImmediatePropagation(); setTimeout(function () { var $window; var triggerAction = ((root.trigger === 'left' && button === 0) || (root.trigger === 'right' && button === 2)); // find the element that would've been clicked, wasn't the layer in the way if (document.elementFromPoint && root.$layer) { root.$layer.hide(); target = document.elementFromPoint(x - $win.scrollLeft(), y - $win.scrollTop()); // also need to try and focus this element if we're in a contenteditable area, // as the layer will prevent the browser mouse action we want if (target.isContentEditable) { var range = document.createRange(), sel = window.getSelection(); range.selectNode(target); range.collapse(true); sel.removeAllRanges(); sel.addRange(range); } root.$layer.show(); } if (root.reposition && triggerAction) { if (document.elementFromPoint) { if (root.$trigger.is(target) || root.$trigger.has(target).length) { root.position.call(root.$trigger, root, x, y); return; } } else { offset = root.$trigger.offset(); $window = $(window); // while this looks kinda awful, it's the best way to avoid // unnecessarily calculating any positions offset.top += $window.scrollTop(); if (offset.top <= e.pageY) { offset.left += $window.scrollLeft(); if (offset.left <= e.pageX) { offset.bottom = offset.top + root.$trigger.outerHeight(); if (offset.bottom >= e.pageY) { offset.right = offset.left + root.$trigger.outerWidth(); if (offset.right >= e.pageX) { // reposition root.position.call(root.$trigger, root, x, y); return; } } } } } } if (target && triggerAction) { root.$trigger.one('contextmenu:hidden', function () { $(target).contextMenu({ x: x, y: y, button: button }); }); } if (root != null && root.$menu != null) { root.$menu.trigger('contextmenu:hide'); } }, 50); }, // key handled :hover keyStop: function (e, opt) { if (!opt.isInput) { e.preventDefault(); } e.stopPropagation(); }, key: function (e) { var opt = {}; // Only get the data from $currentTrigger if it exists if ($currentTrigger) { opt = $currentTrigger.data('contextMenu') || {}; } // If the trigger happen on a element that are above the contextmenu do this if (opt.zIndex === undefined) { opt.zIndex = 0; } var targetZIndex = 0; var getZIndexOfTriggerTarget = function (target) { if (target.style.zIndex !== '') { targetZIndex = target.style.zIndex; } else { if (target.offsetParent !== null && target.offsetParent !== undefined) { getZIndexOfTriggerTarget(target.offsetParent); } else if (target.parentElement !== null && target.parentElement !== undefined) { getZIndexOfTriggerTarget(target.parentElement); } } }; getZIndexOfTriggerTarget(e.target); // If targetZIndex is heigher then opt.zIndex dont progress any futher. // This is used to make sure that if you are using a dialog with a input / textarea / contenteditable div // and its above the contextmenu it wont steal keys events if (targetZIndex > opt.zIndex) { return; } switch (e.keyCode) { case 9: case 38: // up handle.keyStop(e, opt); // if keyCode is [38 (up)] or [9 (tab) with shift] if (opt.isInput) { if (e.keyCode === 9 && e.shiftKey) { e.preventDefault(); if(opt.$selected) { opt.$selected.find('input, textarea, select').blur(); } if (opt.$menu != null) opt.$menu.trigger('prevcommand'); return; } else if (e.keyCode === 38 && opt.$selected.find('input, textarea, select').prop('type') === 'checkbox') { // checkboxes don't capture this key e.preventDefault(); return; } } else if (e.keyCode !== 9 || e.shiftKey) { if (opt.$menu != null) opt.$menu.trigger('prevcommand'); return; } break; // omitting break; // case 9: // tab - reached through omitted break; case 40: // down handle.keyStop(e, opt); if (opt.isInput) { if (e.keyCode === 9) { e.preventDefault(); if(opt.$selected) { opt.$selected.find('input, textarea, select').blur(); } if (opt.$menu != null) opt.$menu.trigger('nextcommand'); return; } else if (e.keyCode === 40 && opt.$selected.find('input, textarea, select').prop('type') === 'checkbox') { // checkboxes don't capture this key e.preventDefault(); return; } } else { if (opt.$menu != null) opt.$menu.trigger('nextcommand'); return; } break; case 37: // left handle.keyStop(e, opt); if (opt.isInput || !opt.$selected || !opt.$selected.length) { break; } if (!opt.$selected.parent().hasClass('context-menu-root')) { var $parent = opt.$selected.parent().parent(); opt.$selected.trigger('contextmenu:blur'); opt.$selected = $parent; return; } break; case 39: // right handle.keyStop(e, opt); if (opt.isInput || !opt.$selected || !opt.$selected.length) { break; } var itemdata = opt.$selected.data('contextMenu') || {}; if (itemdata.$menu && opt.$selected.hasClass('context-menu-submenu')) { opt.$selected = null; itemdata.$selected = null; itemdata.$menu.trigger('nextcommand'); return; } break; case 35: // end case 36: // home if (opt.$selected && opt.$selected.find('input, textarea, select').length) { return; } else { (opt.$selected && opt.$selected.parent() || opt.$menu) .children(':not(.' + opt.classNames.disabled + ', .' + opt.classNames.notSelectable + ')')[e.keyCode === 36 ? 'first' : 'last']() .trigger('contextmenu:focus'); e.preventDefault(); return; } break; case 13: // enter handle.keyStop(e, opt); if (opt.isInput) { if (opt.$selected && !opt.$selected.is('textarea, select')) { e.preventDefault(); return; } break; } if (typeof opt.$selected !== 'undefined' && opt.$selected !== null) { opt.$selected.trigger('mouseup'); } return; case 32: // space case 33: // page up case 34: // page down // prevent browser from scrolling down while menu is visible handle.keyStop(e, opt); return; case 27: // esc handle.keyStop(e, opt); if (opt.$menu != null) opt.$menu.trigger('contextmenu:hide'); return; default: // 0-9, a-z var k = (String.fromCharCode(e.keyCode)).toUpperCase(); if (opt.accesskeys && opt.accesskeys[k]) { // according to the specs accesskeys must be invoked immediately opt.accesskeys[k].$node.trigger(opt.accesskeys[k].$menu ? 'contextmenu:focus' : 'mouseup'); return; } break; } // pass event to selected item, // stop propagation to avoid endless recursion e.stopPropagation(); if (typeof opt.$selected !== 'undefined' && opt.$selected !== null) { opt.$selected.trigger(e); } }, // select previous possible command in menu prevItem: function (e) { e.stopPropagation(); var opt = $(this).data('contextMenu') || {}; var root = $(this).data('contextMenuRoot') || {}; // obtain currently selected menu if (opt.$selected) { var $s = opt.$selected; opt = opt.$selected.parent().data('contextMenu') || {}; opt.$selected = $s; } var $children = opt.$menu.children(), $prev = !opt.$selected || !opt.$selected.prev().length ? $children.last() : opt.$selected.prev(), $round = $prev; // skip disabled or hidden elements while ($prev.hasClass(root.classNames.disabled) || $prev.hasClass(root.classNames.notSelectable) || $prev.is(':hidden')) { if ($prev.prev().length) { $prev = $prev.prev(); } else { $prev = $children.last(); } if ($prev.is($round)) { // break endless loop return; } } // leave current if (opt.$selected) { handle.itemMouseleave.call(opt.$selected.get(0), e); } // activate next handle.itemMouseenter.call($prev.get(0), e); // focus input var $input = $prev.find('input, textarea, select'); if ($input.length) { $input.focus(); } }, // select next possible command in menu nextItem: function (e) { e.stopPropagation(); var opt = $(this).data('contextMenu') || {}; var root = $(this).data('contextMenuRoot') || {}; // obtain currently selected menu if (opt.$selected) { var $s = opt.$selected; opt = opt.$selected.parent().data('contextMenu') || {}; opt.$selected = $s; } var $children = opt.$menu.children(), $next = !opt.$selected || !opt.$selected.next().length ? $children.first() : opt.$selected.next(), $round = $next; // skip disabled while ($next.hasClass(root.classNames.disabled) || $next.hasClass(root.classNames.notSelectable) || $next.is(':hidden')) { if ($next.next().length) { $next = $next.next(); } else { $next = $children.first(); } if ($next.is($round)) { // break endless loop return; } } // leave current if (opt.$selected) { handle.itemMouseleave.call(opt.$selected.get(0), e); } // activate next handle.itemMouseenter.call($next.get(0), e); // focus input var $input = $next.find('input, textarea, select'); if ($input.length) { $input.focus(); } }, // flag that we're inside an input so the key handler can act accordingly focusInput: function () { var $this = $(this).closest('.context-menu-item'), data = $this.data(), opt = data.contextMenu, root = data.contextMenuRoot; root.$selected = opt.$selected = $this; root.isInput = opt.isInput = true; }, // flag that we're inside an input so the key handler can act accordingly blurInput: function () { var $this = $(this).closest('.context-menu-item'), data = $this.data(), opt = data.contextMenu, root = data.contextMenuRoot; root.isInput = opt.isInput = false; }, // :hover on menu menuMouseenter: function () { var root = $(this).data().contextMenuRoot; root.hovering = true; }, // :hover on menu menuMouseleave: function (e) { var root = $(this).data().contextMenuRoot; if (root.$layer && root.$layer.is(e.relatedTarget)) { root.hovering = false; } }, // :hover done manually so key handling is possible itemMouseenter: function (e) { var $this = $(this), data = $this.data(), opt = data.contextMenu, root = data.contextMenuRoot; root.hovering = true; // abort if we're re-entering if (e && root.$layer && root.$layer.is(e.relatedTarget)) { e.preventDefault(); e.stopImmediatePropagation(); } // make sure only one item is selected (opt.$menu ? opt : root).$menu .children('.' + root.classNames.hover).trigger('contextmenu:blur') .children('.hover').trigger('contextmenu:blur'); if ($this.hasClass(root.classNames.disabled) || $this.hasClass(root.classNames.notSelectable)) { opt.$selected = null; return; } $this.trigger('contextmenu:focus'); }, // :hover done manually so key handling is possible itemMouseleave: function (e) { var $this = $(this), data = $this.data(), opt = data.contextMenu, root = data.contextMenuRoot; if (root !== opt && root.$layer && root.$layer.is(e.relatedTarget)) { if (typeof root.$selected !== 'undefined' && root.$selected !== null) { root.$selected.trigger('contextmenu:blur'); } e.preventDefault(); e.stopImmediatePropagation(); root.$selected = opt.$selected = opt.$node; return; } $this.trigger('contextmenu:blur'); }, // contextMenu item click itemClick: function (e) { var $this = $(this), data = $this.data(), opt = data.contextMenu, root = data.contextMenuRoot, key = data.contextMenuKey, callback; // abort if the key is unknown or disabled or is a menu if (!opt.items[key] || $this.is('.' + root.classNames.disabled + ', .context-menu-separator, .' + root.classNames.notSelectable) || ($this.is('.context-menu-submenu') && root.selectableSubMenu === false )) { return; } e.preventDefault(); e.stopImmediatePropagation(); if ($.isFunction(opt.callbacks[key]) && Object.prototype.hasOwnProperty.call(opt.callbacks, key)) { // item-specific callback callback = opt.callbacks[key]; } else if ($.isFunction(root.callback)) { // default callback callback = root.callback; } else { // no callback, no action return; } // hide menu if callback doesn't stop that if (callback.call(root.$trigger, key, root) !== false) { root.$menu.trigger('contextmenu:hide'); } else if (root.$menu.parent().length) { op.update.call(root.$trigger, root); } }, // ignore click events on input elements inputClick: function (e) { e.stopImmediatePropagation(); }, // hide hideMenu: function (e, data) { var root = $(this).data('contextMenuRoot'); op.hide.call(root.$trigger, root, data && data.force); }, // focus focusItem: function (e) { e.stopPropagation(); var $this = $(this), data = $this.data(), opt = data.contextMenu, root = data.contextMenuRoot; if ($this.hasClass(root.classNames.disabled) || $this.hasClass(root.classNames.notSelectable)) { return; } $this .addClass([root.classNames.hover, root.classNames.visible].join(' ')) // select other items and included items .parent().find('.context-menu-item').not($this) .removeClass(root.classNames.visible) .filter('.' + root.classNames.hover) .trigger('contextmenu:blur'); // remember selected opt.$selected = root.$selected = $this; // position sub-menu - do after show so dumb $.ui.position can keep up if (opt.$node) { root.positionSubmenu.call(opt.$node, opt.$menu); } }, // blur blurItem: function (e) { e.stopPropagation(); var $this = $(this), data = $this.data(), opt = data.contextMenu, root = data.contextMenuRoot; if (opt.autoHide) { // for tablets and touch screens this needs to remain $this.removeClass(root.classNames.visible); } $this.removeClass(root.classNames.hover); opt.$selected = null; } }, // operations op = { show: function (opt, x, y) { var $trigger = $(this), css = {}; // hide any open menus $('#context-menu-layer').trigger('mousedown'); // backreference for callbacks opt.$trigger = $trigger; // show event if (opt.events.show.call($trigger, opt) === false) { $currentTrigger = null; return; } // create or update context menu op.update.call($trigger, opt); // position menu opt.position.call($trigger, opt, x, y); // make sure we're in front if (opt.zIndex) { var additionalZValue = opt.zIndex; // If opt.zIndex is a function, call the function to get the right zIndex. if (typeof opt.zIndex === 'function') { additionalZValue = opt.zIndex.call($trigger, opt); } css.zIndex = zindex($trigger) + additionalZValue; } // add layer op.layer.call(opt.$menu, opt, css.zIndex); // adjust sub-menu zIndexes opt.$menu.find('ul').css('zIndex', css.zIndex + 1); // position and show context menu opt.$menu.css(css)[opt.animation.show](opt.animation.duration, function () { $trigger.trigger('contextmenu:visible'); }); // make options available and set state $trigger .data('contextMenu', opt) .addClass('context-menu-active'); // register key handler $(document).off('keydown.contextMenu').on('keydown.contextMenu', handle.key); // register autoHide handler if (opt.autoHide) { // mouse position handler $(document).on('mousemove.contextMenuAutoHide', function (e) { // need to capture the offset on mousemove, // since the page might've been scrolled since activation var pos = $trigger.offset(); pos.right = pos.left + $trigger.outerWidth(); pos.bottom = pos.top + $trigger.outerHeight(); if (opt.$layer && !opt.hovering && (!(e.pageX >= pos.left && e.pageX <= pos.right) || !(e.pageY >= pos.top && e.pageY <= pos.bottom))) { /* Additional hover check after short time, you might just miss the edge of the menu */ setTimeout(function () { if (!opt.hovering && opt.$menu != null) { opt.$menu.trigger('contextmenu:hide'); } }, 50); } }); } }, hide: function (opt, force) { var $trigger = $(this); if (!opt) { opt = $trigger.data('contextMenu') || {}; } // hide event if (!force && opt.events && opt.events.hide.call($trigger, opt) === false) { return; } // remove options and revert state $trigger .removeData('contextMenu') .removeClass('context-menu-active'); if (opt.$layer) { // keep layer for a bit so the contextmenu event can be aborted properly by opera setTimeout((function ($layer) { return function () { $layer.remove(); }; })(opt.$layer), 10); try { delete opt.$layer; } catch (e) { opt.$layer = null; } } // remove handle $currentTrigger = null; // remove selected opt.$menu.find('.' + opt.classNames.hover).trigger('contextmenu:blur'); opt.$selected = null; // collapse all submenus opt.$menu.find('.' + opt.classNames.visible).removeClass(opt.classNames.visible); // unregister key and mouse handlers // $(document).off('.contextMenuAutoHide keydown.contextMenu'); // http://bugs.jquery.com/ticket/10705 $(document).off('.contextMenuAutoHide').off('keydown.contextMenu'); // hide menu if(opt.$menu){ opt.$menu[opt.animation.hide](opt.animation.duration, function () { // tear down dynamically built menu after animation is completed. if (opt.build) { opt.$menu.remove(); $.each(opt, function (key) { switch (key) { case 'ns': case 'selector': case 'build': case 'trigger': return true; default: opt[key] = undefined; try { delete opt[key]; } catch (e) { } return true; } }); } setTimeout(function () { $trigger.trigger('contextmenu:hidden'); }, 10); }); } }, create: function (opt, root) { if (root === undefined) { root = opt; } // create contextMenu opt.$menu = $('
    ').addClass(opt.className || '').data({ 'contextMenu': opt, 'contextMenuRoot': root }); $.each(['callbacks', 'commands', 'inputs'], function (i, k) { opt[k] = {}; if (!root[k]) { root[k] = {}; } }); if(!root.accesskeys){ root.accesskeys = {}; } function createNameNode(item) { var $name = $(''); if (item._accesskey) { if (item._beforeAccesskey) { $name.append(document.createTextNode(item._beforeAccesskey)); } $('') .addClass('context-menu-accesskey') .text(item._accesskey) .appendTo($name); if (item._afterAccesskey) { $name.append(document.createTextNode(item._afterAccesskey)); } } else { if (item.isHtmlName) { // restrict use with access keys if (typeof item.accesskey !== 'undefined') { throw new Error('accesskeys are not compatible with HTML names and cannot be used together in the same item'); } $name.html(item.name); } else { $name.text(item.name); } } return $name; } // create contextMenu items $.each(opt.items, function (key, item) { var $t = $('
  • ').addClass(item.className || ''), $label = null, $input = null; // iOS needs to see a click-event bound to an element to actually // have the TouchEvents infrastructure trigger the click event $t.on('click', $.noop); // Make old school string seperator a real item so checks wont be // akward later. // And normalize 'cm_separator' into 'cm_seperator'. if (typeof item === 'string' || item.type === 'cm_separator') { item = { type : 'cm_seperator' }; } item.$node = $t.data({ 'contextMenu': opt, 'contextMenuRoot': root, 'contextMenuKey': key }); // register accesskey // NOTE: the accesskey attribute should be applicable to any element, but Safari5 and Chrome13 still can't do that if (typeof item.accesskey !== 'undefined') { var aks = splitAccesskey(item.accesskey); for (var i = 0, ak; ak = aks[i]; i++) { if (!root.accesskeys[ak]) { root.accesskeys[ak] = item; var matched = item.name.match(new RegExp('^(.*?)(' + ak + ')(.*)$', 'i')); if (matched) { item._beforeAccesskey = matched[1]; item._accesskey = matched[2]; item._afterAccesskey = matched[3]; } break; } } } if (item.type && types[item.type]) { // run custom type handler types[item.type].call($t, item, opt, root); // register commands $.each([opt, root], function (i, k) { k.commands[key] = item; // Overwrite only if undefined or the item is appended to the root. This so it // doesn't overwrite callbacks of root elements if the name is the same. if ($.isFunction(item.callback) && (k.callbacks[key] === undefined || opt.type === undefined)) { k.callbacks[key] = item.callback; } }); } else { // add label for input if (item.type === 'cm_seperator') { $t.addClass('context-menu-separator ' + root.classNames.notSelectable); } else if (item.type === 'html') { $t.addClass('context-menu-html ' + root.classNames.notSelectable); } else if (item.type === 'sub') { // We don't want to execute the next else-if if it is a sub. } else if (item.type) { $label = $('').appendTo($t); createNameNode(item).appendTo($label); $t.addClass('context-menu-input'); opt.hasTypes = true; $.each([opt, root], function (i, k) { k.commands[key] = item; k.inputs[key] = item; }); } else if (item.items) { item.type = 'sub'; } switch (item.type) { case 'cm_seperator': break; case 'text': $input = $('') .attr('name', 'context-menu-input-' + key) .val(item.value || '') .appendTo($label); break; case 'textarea': $input = $('') .attr('name', 'context-menu-input-' + key) .val(item.value || '') .appendTo($label); if (item.height) { $input.height(item.height); } break; case 'checkbox': $input = $('') .attr('name', 'context-menu-input-' + key) .val(item.value || '') .prop('checked', !!item.selected) .prependTo($label); break; case 'radio': $input = $('') .attr('name', 'context-menu-input-' + item.radio) .val(item.value || '') .prop('checked', !!item.selected) .prependTo($label); break; case 'select': $input = $('') .attr('name', 'context-menu-input-' + key) .appendTo($label); if (item.options) { $.each(item.options, function (value, text) { $('').val(value).text(text).appendTo($input); }); $input.val(item.selected); } break; case 'sub': createNameNode(item).appendTo($t); item.appendTo = item.$node; $t.data('contextMenu', item).addClass('context-menu-submenu'); item.callback = null; // If item contains items, and this is a promise, we should create it later // check if subitems is of type promise. If it is a promise we need to create // it later, after promise has been resolved. if ('function' === typeof item.items.then) { // probably a promise, process it, when completed it will create the sub menu's. op.processPromises(item, root, item.items); } else { // normal submenu. op.create(item, root); } break; case 'html': $(item.html).appendTo($t); break; default: $.each([opt, root], function (i, k) { k.commands[key] = item; // Overwrite only if undefined or the item is appended to the root. This so it // doesn't overwrite callbacks of root elements if the name is the same. if ($.isFunction(item.callback) && (k.callbacks[key] === undefined || opt.type === undefined)) { k.callbacks[key] = item.callback; } }); createNameNode(item).appendTo($t); break; } // disable key listener in if (item.type && item.type !== 'sub' && item.type !== 'html' && item.type !== 'cm_seperator') { $input .on('focus', handle.focusInput) .on('blur', handle.blurInput); if (item.events) { $input.on(item.events, opt); } } // add icons if (item.icon) { if ($.isFunction(item.icon)) { item._icon = item.icon.call(this, this, $t, key, item); } else { if ( typeof(item.icon) === 'string' && item.icon.substring(0,3) === 'fa-' ) { // to enable font awesome item._icon = root.classNames.icon + ' ' + root.classNames.icon + '--fa fa ' + item.icon; } else { item._icon = root.classNames.icon + ' ' + root.classNames.icon + '-' + item.icon; } } $t.addClass(item._icon); } } // cache contained elements item.$input = $input; item.$label = $label; // attach item to menu $t.appendTo(opt.$menu); // Disable text selection if (!opt.hasTypes && $.support.eventSelectstart) { // browsers support user-select: none, // IE has a special event for text-selection // browsers supporting neither will not be preventing text-selection $t.on('selectstart.disableTextSelect', handle.abortevent); } }); // attach contextMenu to (to bypass any possible overflow:hidden issues on parents of the trigger element) if (!opt.$node) { opt.$menu.css('display', 'none').addClass('context-menu-root'); } opt.$menu.appendTo(opt.appendTo || document.body); }, resize: function ($menu, nested) { var domMenu; // determine widths of submenus, as CSS won't grow them automatically // position:absolute within position:absolute; min-width:100; max-width:200; results in width: 100; // kinda sucks hard... // determine width of absolutely positioned element $menu.css({position: 'absolute', display: 'block'}); // don't apply yet, because that would break nested elements' widths $menu.data('width', (domMenu = $menu.get(0)).getBoundingClientRect ? Math.ceil(domMenu.getBoundingClientRect().width) : $menu.outerWidth() + 1); // outerWidth() returns rounded pixels // reset styles so they allow nested elements to grow/shrink naturally $menu.css({ position: 'static', minWidth: '0px', maxWidth: '100000px' }); // identify width of nested menus $menu.find('> li > ul').each(function () { op.resize($(this), true); }); // reset and apply changes in the end because nested // elements' widths wouldn't be calculatable otherwise if (!nested) { $menu.find('ul').addBack().css({ position: '', display: '', minWidth: '', maxWidth: '' }).outerWidth(function () { return $(this).data('width'); }); } }, update: function (opt, root) { var $trigger = this; if (root === undefined) { root = opt; op.resize(opt.$menu); } // re-check disabled for each item opt.$menu.children().each(function () { var $item = $(this), key = $item.data('contextMenuKey'), item = opt.items[key], disabled = ($.isFunction(item.disabled) && item.disabled.call($trigger, key, root)) || item.disabled === true, visible; if ($.isFunction(item.visible)) { visible = item.visible.call($trigger, key, root); } else if (typeof item.visible !== 'undefined') { visible = item.visible === true; } else { visible = true; } $item[visible ? 'show' : 'hide'](); // dis- / enable item $item[disabled ? 'addClass' : 'removeClass'](root.classNames.disabled); if ($.isFunction(item.icon)) { $item.removeClass(item._icon); item._icon = item.icon.call(this, $trigger, $item, key, item); $item.addClass(item._icon); } if (item.type) { // dis- / enable input elements $item.find('input, select, textarea').prop('disabled', disabled); // update input states switch (item.type) { case 'text': case 'textarea': item.$input.val(item.value || ''); break; case 'checkbox': case 'radio': item.$input.val(item.value || '').prop('checked', !!item.selected); break; case 'select': item.$input.val(item.selected || ''); break; } } if (item.$menu) { // update sub-menu op.update.call($trigger, item, root); } }); }, layer: function (opt, zIndex) { // add transparent layer for click area // filter and background for Internet Explorer, Issue #23 var $layer = opt.$layer = $('
    ') .css({height: $win.height(), width: $win.width(), display: 'block'}) .data('contextMenuRoot', opt) .insertBefore(this) .on('contextmenu', handle.abortevent) .on('mousedown', handle.layerClick); // IE6 doesn't know position:fixed; if (document.body.style.maxWidth === undefined) { // IE6 doesn't support maxWidth $layer.css({ 'position': 'absolute', 'height': $(document).height() }); } return $layer; }, processPromises: function (opt, root, promise) { // Start opt.$node.addClass(root.classNames.iconLoadingClass); function completedPromise(opt,root,items) { // Completed promise (dev called promise.resolve). We now have a list of items which can // be used to create the rest of the context menu. if (items === undefined) { // Null result, dev should have checked errorPromise(undefined);//own error object } finishPromiseProcess(opt,root, items); } function errorPromise(opt,root,errorItem) { // User called promise.reject() with an error item, if not, provide own error item. if (errorItem === undefined) { errorItem = { "error": { name: "No items and no error item", icon: "context-menu-icon context-menu-icon-quit" } }; if (window.console) { (console.error || console.log).call(console, 'When you reject a promise, provide an "items" object, equal to normal sub-menu items'); } } else if(typeof errorItem === 'string'){ errorItem = { "error": { name: errorItem } }; } finishPromiseProcess(opt,root,errorItem); } function finishPromiseProcess(opt,root,items) { if(root.$menu === undefined || !root.$menu.is(':visible')){ return; } opt.$node.removeClass(root.classNames.iconLoadingClass); opt.items = items; op.create(opt, root, true); // Create submenu op.update(opt, root); // Correctly update position if user is already hovered over menu item root.positionSubmenu.call(opt.$node, opt.$menu); // positionSubmenu, will only do anything if user already hovered over menu item that just got new subitems. } // Wait for promise completion. .then(success, error, notify) (we don't track notify). Bind the opt // and root to avoid scope problems promise.then(completedPromise.bind(this, opt, root), errorPromise.bind(this, opt, root)); } }; // split accesskey according to http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#assigned-access-key function splitAccesskey(val) { var t = val.split(/\s+/), keys = []; for (var i = 0, k; k = t[i]; i++) { k = k.charAt(0).toUpperCase(); // first character only // theoretically non-accessible characters should be ignored, but different systems, different keyboard layouts, ... screw it. // a map to look up already used access keys would be nice keys.push(k); } return keys; } // handle contextMenu triggers $.fn.contextMenu = function (operation) { var $t = this, $o = operation; if (this.length > 0) { // this is not a build on demand menu if (operation === undefined) { this.first().trigger('contextmenu'); } else if (operation.x !== undefined && operation.y !== undefined) { this.first().trigger($.Event('contextmenu', { pageX: operation.x, pageY: operation.y, mouseButton: operation.button })); } else if (operation === 'hide') { var $menu = this.first().data('contextMenu') ? this.first().data('contextMenu').$menu : null; if($menu){ $menu.trigger('contextmenu:hide'); } } else if (operation === 'destroy') { $.contextMenu('destroy', {context: this}); } else if ($.isPlainObject(operation)) { operation.context = this; $.contextMenu('create', operation); } else if (operation) { this.removeClass('context-menu-disabled'); } else if (!operation) { this.addClass('context-menu-disabled'); } } else { $.each(menus, function () { if (this.selector === $t.selector) { $o.data = this; $.extend($o.data, {trigger: 'demand'}); } }); handle.contextmenu.call($o.target, $o); } return this; }; // manage contextMenu instances $.contextMenu = function (operation, options) { if (typeof operation !== 'string') { options = operation; operation = 'create'; } if (typeof options === 'string') { options = {selector: options}; } else if (options === undefined) { options = {}; } // merge with default options var o = $.extend(true, {}, defaults, options || {}); var $document = $(document); var $context = $document; var _hasContext = false; if (!o.context || !o.context.length) { o.context = document; } else { // you never know what they throw at you... $context = $(o.context).first(); o.context = $context.get(0); _hasContext = !$(o.context).is(document); } switch (operation) { case 'create': // no selector no joy if (!o.selector) { throw new Error('No selector specified'); } // make sure internal classes are not bound to if (o.selector.match(/.context-menu-(list|item|input)($|\s)/)) { throw new Error('Cannot bind to selector "' + o.selector + '" as it contains a reserved className'); } if (!o.build && (!o.items || $.isEmptyObject(o.items))) { throw new Error('No Items specified'); } counter++; o.ns = '.contextMenu' + counter; if (!_hasContext) { namespaces[o.selector] = o.ns; } menus[o.ns] = o; // default to right click if (!o.trigger) { o.trigger = 'right'; } if (!initialized) { var itemClick = o.itemClickEvent === 'click' ? 'click.contextMenu' : 'mouseup.contextMenu'; var contextMenuItemObj = { // 'mouseup.contextMenu': handle.itemClick, // 'click.contextMenu': handle.itemClick, 'contextmenu:focus.contextMenu': handle.focusItem, 'contextmenu:blur.contextMenu': handle.blurItem, 'contextmenu.contextMenu': handle.abortevent, 'mouseenter.contextMenu': handle.itemMouseenter, 'mouseleave.contextMenu': handle.itemMouseleave }; contextMenuItemObj[itemClick] = handle.itemClick; // make sure item click is registered first $document .on({ 'contextmenu:hide.contextMenu': handle.hideMenu, 'prevcommand.contextMenu': handle.prevItem, 'nextcommand.contextMenu': handle.nextItem, 'contextmenu.contextMenu': handle.abortevent, 'mouseenter.contextMenu': handle.menuMouseenter, 'mouseleave.contextMenu': handle.menuMouseleave }, '.context-menu-list') .on('mouseup.contextMenu', '.context-menu-input', handle.inputClick) .on(contextMenuItemObj, '.context-menu-item'); initialized = true; } // engage native contextmenu event $context .on('contextmenu' + o.ns, o.selector, o, handle.contextmenu); if (_hasContext) { // add remove hook, just in case $context.on('remove' + o.ns, function () { $(this).contextMenu('destroy'); }); } switch (o.trigger) { case 'hover': $context .on('mouseenter' + o.ns, o.selector, o, handle.mouseenter) .on('mouseleave' + o.ns, o.selector, o, handle.mouseleave); break; case 'left': $context.on('click' + o.ns, o.selector, o, handle.click); break; /* default: // http://www.quirksmode.org/dom/events/contextmenu.html $document .on('mousedown' + o.ns, o.selector, o, handle.mousedown) .on('mouseup' + o.ns, o.selector, o, handle.mouseup); break; */ } // create menu if (!o.build) { op.create(o); } break; case 'destroy': var $visibleMenu; if (_hasContext) { // get proper options var context = o.context; $.each(menus, function (ns, o) { if (!o) { return true; } // Is this menu equest to the context called from if (!$(context).is(o.selector)) { return true; } $visibleMenu = $('.context-menu-list').filter(':visible'); if ($visibleMenu.length && $visibleMenu.data().contextMenuRoot.$trigger.is($(o.context).find(o.selector))) { $visibleMenu.trigger('contextmenu:hide', {force: true}); } try { if (menus[o.ns].$menu) { menus[o.ns].$menu.remove(); } delete menus[o.ns]; } catch (e) { menus[o.ns] = null; } $(o.context).off(o.ns); return true; }); } else if (!o.selector) { $document.off('.contextMenu .contextMenuAutoHide'); $.each(menus, function (ns, o) { $(o.context).off(o.ns); }); namespaces = {}; menus = {}; counter = 0; initialized = false; $('#context-menu-layer, .context-menu-list').remove(); } else if (namespaces[o.selector]) { $visibleMenu = $('.context-menu-list').filter(':visible'); if ($visibleMenu.length && $visibleMenu.data().contextMenuRoot.$trigger.is(o.selector)) { $visibleMenu.trigger('contextmenu:hide', {force: true}); } try { if (menus[namespaces[o.selector]].$menu) { menus[namespaces[o.selector]].$menu.remove(); } delete menus[namespaces[o.selector]]; } catch (e) { menus[namespaces[o.selector]] = null; } $document.off(namespaces[o.selector]); } break; case 'html5': // if or are not handled by the browser, // or options was a bool true, // initialize $.contextMenu for them if ((!$.support.htmlCommand && !$.support.htmlMenuitem) || (typeof options === 'boolean' && options)) { $('menu[type="context"]').each(function () { if (this.id) { $.contextMenu({ selector: '[contextmenu=' + this.id + ']', items: $.contextMenu.fromMenu(this) }); } }).css('display', 'none'); } break; default: throw new Error('Unknown operation "' + operation + '"'); } return this; }; // import values into commands $.contextMenu.setInputValues = function (opt, data) { if (data === undefined) { data = {}; } $.each(opt.inputs, function (key, item) { switch (item.type) { case 'text': case 'textarea': item.value = data[key] || ''; break; case 'checkbox': item.selected = data[key] ? true : false; break; case 'radio': item.selected = (data[item.radio] || '') === item.value; break; case 'select': item.selected = data[key] || ''; break; } }); }; // export values from commands $.contextMenu.getInputValues = function (opt, data) { if (data === undefined) { data = {}; } $.each(opt.inputs, function (key, item) { switch (item.type) { case 'text': case 'textarea': case 'select': data[key] = item.$input.val(); break; case 'checkbox': data[key] = item.$input.prop('checked'); break; case 'radio': if (item.$input.prop('checked')) { data[item.radio] = item.value; } break; } }); return data; }; // find