├── .babelrc
├── .codeclimate.yml
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── docs
├── 00 Documentation
│ └── Documentation.md
└── index.md
├── examples
├── 00 Connecting Targets
│ ├── Multiple Targets
│ │ ├── index.js
│ │ ├── target-container.js
│ │ └── target.js
│ └── Single Target
│ │ ├── index.js
│ │ ├── target-container.js
│ │ └── target.js
├── 01 Styling
│ └── Themes
│ │ ├── body.js
│ │ ├── index.js
│ │ ├── target-container.js
│ │ ├── target.js
│ │ └── themes.js
├── 02 Dynamic Menus
│ ├── Dynamic Menus
│ │ ├── body.js
│ │ ├── index.js
│ │ ├── target-container.js
│ │ └── target.js
│ └── Stress Test
│ │ ├── body.js
│ │ ├── index.js
│ │ ├── target-container.js
│ │ └── target.js
└── README.md
├── package.json
├── react-menus.md
├── scripts
├── build.sh
├── buildSiteIndexPages.sh
├── buildStaticSite.sh
├── cssTransformLoader.js
├── markdownLoader.js
├── prism.js
├── publishStaticSite.sh
├── resolvers.js
└── startSiteDevServer.sh
├── site
├── Constants.js
├── IndexPage.js
├── LICENSE
├── base.less
├── client.js
├── components
│ ├── CodeBlock.js
│ ├── CodeBlock.less
│ ├── Cover.js
│ ├── Cover.less
│ ├── Header.js
│ ├── Header.less
│ ├── NavBar.js
│ ├── NavBar.less
│ ├── PageBody.js
│ ├── PageBody.less
│ ├── SideBar.js
│ ├── SideBar.less
│ └── StaticHTMLBlock.js
├── constants.less
├── pages
│ ├── APIPage.js
│ ├── ExamplePage.js
│ └── HomePage.js
├── renderPath.js
├── webpack-client.config.js
└── webpack-prerender.config.js
└── src
├── context_menu.js
└── index.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["env","react","stage-0"],
3 | "plugins": ["transform-class-properties","add-module-exports"]
4 | }
5 |
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | ---
2 | engines:
3 | duplication:
4 | enabled: true
5 | config:
6 | languages:
7 | - ruby
8 | - javascript
9 | - python
10 | - php
11 | eslint:
12 | enabled: true
13 | fixme:
14 | enabled: true
15 | ratings:
16 | paths:
17 | - "**.inc"
18 | - "**.js"
19 | - "**.jsx"
20 | - "**.module"
21 | - "**.php"
22 | - "**.py"
23 | - "**.rb"
24 | exclude_paths:
25 | - site/
26 | - scripts/
27 | - examples/
28 | - docs/
29 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | __site__
2 | __site_prerender__
3 | site
4 | scripts
5 | dist
6 | lib
7 | **/node_modules
8 | **/webpack*.config.js
9 | tests.webpack.js
10 | karma.conf.js
11 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | ecmaFeatures:
2 | modules: true
3 | jsx: true
4 |
5 | env:
6 | amd: true
7 | browser: true
8 | es6: true
9 | jquery: true
10 | node: true
11 |
12 | # http://eslint.org/docs/rules/
13 | rules:
14 | # Possible Errors
15 | comma-dangle: [2, never]
16 | no-cond-assign: 2
17 | no-console: 0
18 | no-constant-condition: 2
19 | no-control-regex: 2
20 | no-debugger: 2
21 | no-dupe-args: 2
22 | no-dupe-keys: 2
23 | no-duplicate-case: 2
24 | no-empty: 2
25 | no-empty-character-class: 2
26 | no-ex-assign: 2
27 | no-extra-boolean-cast: 2
28 | no-extra-parens: 0
29 | no-extra-semi: 2
30 | no-func-assign: 2
31 | no-inner-declarations: [2, functions]
32 | no-invalid-regexp: 2
33 | no-irregular-whitespace: 2
34 | no-negated-in-lhs: 2
35 | no-obj-calls: 2
36 | no-regex-spaces: 2
37 | no-sparse-arrays: 2
38 | no-unexpected-multiline: 2
39 | no-unreachable: 2
40 | use-isnan: 2
41 | valid-jsdoc: 0
42 | valid-typeof: 2
43 |
44 | # Best Practices
45 | accessor-pairs: 2
46 | block-scoped-var: 0
47 | complexity: [2, 6]
48 | consistent-return: 0
49 | curly: 0
50 | default-case: 0
51 | dot-location: 0
52 | dot-notation: 0
53 | eqeqeq: 2
54 | guard-for-in: 2
55 | no-alert: 2
56 | no-caller: 2
57 | no-case-declarations: 2
58 | no-div-regex: 2
59 | no-else-return: 0
60 | no-empty-label: 2
61 | no-empty-pattern: 2
62 | no-eq-null: 2
63 | no-eval: 2
64 | no-extend-native: 2
65 | no-extra-bind: 2
66 | no-fallthrough: 2
67 | no-floating-decimal: 0
68 | no-implicit-coercion: 0
69 | no-implied-eval: 2
70 | no-invalid-this: 0
71 | no-iterator: 2
72 | no-labels: 0
73 | no-lone-blocks: 2
74 | no-loop-func: 2
75 | no-magic-number: 0
76 | no-multi-spaces: 0
77 | no-multi-str: 0
78 | no-native-reassign: 2
79 | no-new-func: 2
80 | no-new-wrappers: 2
81 | no-new: 2
82 | no-octal-escape: 2
83 | no-octal: 2
84 | no-proto: 2
85 | no-redeclare: 2
86 | no-return-assign: 2
87 | no-script-url: 2
88 | no-self-compare: 2
89 | no-sequences: 0
90 | no-throw-literal: 0
91 | no-unused-expressions: 2
92 | no-useless-call: 2
93 | no-useless-concat: 2
94 | no-void: 2
95 | no-warning-comments: 0
96 | no-with: 2
97 | radix: 2
98 | vars-on-top: 0
99 | wrap-iife: 2
100 | yoda: 0
101 |
102 | # Strict
103 | strict: 0
104 |
105 | # Variables
106 | init-declarations: 0
107 | no-catch-shadow: 2
108 | no-delete-var: 2
109 | no-label-var: 2
110 | no-shadow-restricted-names: 2
111 | no-shadow: 0
112 | no-undef-init: 2
113 | no-undef: 0
114 | no-undefined: 0
115 | no-unused-vars: 0
116 | no-use-before-define: 0
117 |
118 | # Node.js and CommonJS
119 | callback-return: 2
120 | global-require: 2
121 | handle-callback-err: 2
122 | no-mixed-requires: 0
123 | no-new-require: 0
124 | no-path-concat: 2
125 | no-process-exit: 2
126 | no-restricted-modules: 0
127 | no-sync: 0
128 |
129 | # Stylistic Issues
130 | array-bracket-spacing: 0
131 | block-spacing: 0
132 | brace-style: 0
133 | camelcase: 0
134 | comma-spacing: 0
135 | comma-style: 0
136 | computed-property-spacing: 0
137 | consistent-this: 0
138 | eol-last: 0
139 | func-names: 0
140 | func-style: 0
141 | id-length: 0
142 | id-match: 0
143 | indent: 0
144 | jsx-quotes: 0
145 | key-spacing: 0
146 | linebreak-style: 0
147 | lines-around-comment: 0
148 | max-depth: 0
149 | max-len: 0
150 | max-nested-callbacks: 0
151 | max-params: 0
152 | max-statements: [2, 30]
153 | new-cap: 0
154 | new-parens: 0
155 | newline-after-var: 0
156 | no-array-constructor: 0
157 | no-bitwise: 0
158 | no-continue: 0
159 | no-inline-comments: 0
160 | no-lonely-if: 0
161 | no-mixed-spaces-and-tabs: 0
162 | no-multiple-empty-lines: 0
163 | no-negated-condition: 0
164 | no-nested-ternary: 0
165 | no-new-object: 0
166 | no-plusplus: 0
167 | no-restricted-syntax: 0
168 | no-spaced-func: 0
169 | no-ternary: 0
170 | no-trailing-spaces: 0
171 | no-underscore-dangle: 0
172 | no-unneeded-ternary: 0
173 | object-curly-spacing: 0
174 | one-var: 0
175 | operator-assignment: 0
176 | operator-linebreak: 0
177 | padded-blocks: 0
178 | quote-props: 0
179 | quotes: 0
180 | require-jsdoc: 0
181 | semi-spacing: 0
182 | semi: 0
183 | sort-vars: 0
184 | space-after-keywords: 0
185 | space-before-blocks: 0
186 | space-before-function-paren: 0
187 | space-before-keywords: 0
188 | space-in-parens: 0
189 | space-infix-ops: 0
190 | space-return-throw-case: 0
191 | space-unary-ops: 0
192 | spaced-comment: 0
193 | wrap-regex: 0
194 |
195 | # ECMAScript 6
196 | arrow-body-style: 0
197 | arrow-parens: 0
198 | arrow-spacing: 0
199 | constructor-super: 0
200 | generator-star-spacing: 0
201 | no-arrow-condition: 0
202 | no-class-assign: 0
203 | no-const-assign: 0
204 | no-dupe-class-members: 0
205 | no-this-before-super: 0
206 | no-var: 0
207 | object-shorthand: 0
208 | prefer-arrow-callback: 0
209 | prefer-const: 0
210 | prefer-reflect: 0
211 | prefer-spread: 0
212 | prefer-template: 0
213 | require-yield: 0
214 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
18 | .grunt
19 |
20 | # node-waf configuration
21 | .lock-wscript
22 |
23 | # Compiled binary addons (http://nodejs.org/api/addons.html)
24 | build/Release
25 |
26 | # Dependency directory
27 | node_modules
28 |
29 | # Optional npm cache directory
30 | .npm
31 |
32 | # Optional REPL history
33 | .node_repl_history
34 |
35 | lib
36 | __site*
37 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | scripts
3 | examples
4 | site
5 | __site__
6 | __site_prerender__
7 | webpack.config.js
8 | .babelrc
9 | .eslintrc
10 | bower.json
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Joshua Christman
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.npmjs.com/package/react-context-menus)
2 | [](https://david-dm.org/jchristman/react-context-menus)
3 | [](https://david-dm.org/jchristman/react-context-menus#info=devDependencies)
4 | [](https://david-dm.org/jchristman/react-context-menus#info=peerDependencies)
5 | [](https://codeclimate.com/github/jchristman/react-context-menus)
6 |
7 | # react-context-menus
8 | A library to make context menus very easy for React. See the demo and documentation at http://jchristman.github.io/react-context-menus/
9 |
--------------------------------------------------------------------------------
/docs/00 Documentation/Documentation.md:
--------------------------------------------------------------------------------
1 | Overview
2 | ========
3 |
4 | React Context Menus is a library that aims to simplify the process of getting right click menus into your application. After searching for a long time, I was unsatisfied with the libraries that existed. The aim with this library is to simplify the API while providing control over the functionality and display of the menu to the developer. It is built on top of [Zippy UI react-menus](https://github.com/zippyui/react-menus).
5 |
6 | Installation
7 | ============
8 |
9 | As easy as it gets:
10 |
11 | ```bash
12 | npm install --save react-context-menus
13 | ```
14 |
15 | Usage
16 | =====
17 |
18 | This library is meant to be used as a Container. It will provide a prop to your component called ```connectContextMenu```. You can call this function with any part of your component to make it right clickable. Like so:
19 |
20 | -------------------
21 | ```js
22 | var React = require('react');
23 | var ContextMenu = require('react-context-menus');
24 |
25 | var ComponentToReceiveContextMenu = React.createClass({
26 | render: function() {
27 | return this.props.connectContextMenu(
28 |
29 | );
30 | }
31 | });
32 |
33 | var menu_items = [
34 | /* ... */
35 | ];
36 |
37 | module.exports = ContextMenu(menu_items)(ComponentToReceiveContextMenu);
38 | ```
39 | -------------------
40 | ```js
41 | import React from 'react';
42 | import ContextMenu from 'react-context-menus';
43 |
44 | class ComponentToReceiveContextMenu extends React.Component {
45 | render() {
46 | return this.props.connectContextMenu(
47 |
48 | );
49 | }
50 | });
51 |
52 | const menu_items = [
53 | /* ... */
54 | ];
55 |
56 | export default ContextMenu(menu_items)(ComponentToReceiveContextMenu);
57 | ```
58 | -------------------
59 | ```js
60 | import React from 'react';
61 | import ContextMenu from 'react-context-menus';
62 |
63 | @ContextMenu(menu_items)
64 | export default class ComponentToReceiveContextMenu extends React.Component {
65 | render() {
66 | return this.props.connectContextMenu(
67 |
68 | );
69 | }
70 | }
71 | ```
72 | -------------------
73 |
74 | Arguments To ContextMenu
75 | ========================
76 |
77 | The ContextMenu container can receive two arguments - a menu_items and an options argument. The menu_items argument must be an array or a function that receives the props of the item being wrapped and returns an array. The options argument must be an object or a function that receives the props of the item being wrapped and returns an object.
78 |
79 | Menu items are defined below. See [react-menus](https://github.com/zippyui/react-menus) for more info.
80 |
81 | ```js
82 | // This can be either a static array or a function to build a dynamic menu based on the thing that is clicked.
83 | const menu_items = (props) => {
84 | return [
85 | {
86 | label: 'Label 1' // The actual text that gets displayed
87 | onClick: (event, props) => {} // A function that gets called on click of menu item. It receives the props of the wrapped component.
88 | },
89 | '-',
90 | {
91 | label: 'Label 2',
92 | disabled: true // Disable the item. Defaults to false.
93 | }
94 | ];
95 | }
96 | ```
97 |
98 | Options are defined as such:
99 |
100 | ```js
101 | // This can be either a static object or a function to build a dynamic object based on the thing that is clicked.
102 | const options = (props) => {
103 | return {
104 | theme: props.theme.theme, // The theme object is applied to items
105 | style: props.theme.style, // The style object is applied to the overall menu
106 | at: { x: 30, y: 30 }, // The at object positions the menu (if you want to do it manually)
107 | show: props.show // The show property will determine whether to show the menu (if you want to do it manually)
108 | container: { zIndex: 1 } // The container property will adjust the menu container (the fixed element that's attached to your component).
109 | // Current, the only option is to adjust its zIndex if needed.
110 | }
111 | }
112 | ```
113 |
114 | How It Works
115 | ============
116 |
117 | The library works by appending a child to the wrapped component that will be the context menu container. Then, any time the "connected" DOM items are right clicked, it will calculate the position of the menu and render it to the DOM at the context menu container. The menu is added and removed in an intelligent way, such that it does not exist when it is not showing.
118 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | React Context Menu
2 | ==================
3 |
4 | React Context Menus is a library that aims to simplify the process of getting right click menus into your application. After searching for a long time, I was unsatisfied with the libraries that existed. The aim with this library is to simplify the API while providing control over the functionality and display of the menu to the developer. It is built on top of [Zippy UI react-menus](https://github.com/zippyui/react-menus).
5 |
--------------------------------------------------------------------------------
/examples/00 Connecting Targets/Multiple Targets/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import Target from './target-container';
4 |
5 | export default class SingleTargetApp extends Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | render() {
11 | return (
12 |
13 |
14 | Browse the Source
15 |
16 |
17 | In this example, there are multiple context menu "spots" on the same component, each of which generates the same context menu.
18 |
19 |
20 |
21 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/00 Connecting Targets/Multiple Targets/target-container.js:
--------------------------------------------------------------------------------
1 | import ContextMenu from 'react-context-menus';
2 | import Target from './target.js';
3 |
4 | const menu_items = [
5 | {
6 | label: 'Item 1',
7 | onClick: () => alert('Clicked Item 1!')
8 | },
9 | {
10 | label: 'Item 2',
11 | onClick: () => alert('Clicked Item 2!')
12 | },
13 | '-',
14 | {
15 | label: 'Item 3',
16 | onClick: () => alert('Clicked Item 3 below the divider!')
17 | }
18 | ];
19 |
20 | const options = {
21 | theme: {
22 | style: {
23 | color: 'black'
24 | }
25 | }
26 | }
27 |
28 | export default ContextMenu(menu_items, options)(Target);
29 |
--------------------------------------------------------------------------------
/examples/00 Connecting Targets/Multiple Targets/target.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const style = {
4 | position: 'relative',
5 | width: 300,
6 | height: 300,
7 | backgroundColor: '#111',
8 | color: 'white',
9 | textAlign: 'center'
10 | }
11 |
12 | const textStyle = {
13 | pointerEvents: 'none',
14 | pointer: 'cursor'
15 | }
16 |
17 | const context_menu_styles = [
18 | {
19 | position: 'absolute',
20 | width: 50,
21 | heigh: 50,
22 | top: 40,
23 | left: 20,
24 | backgroundColor: 'red',
25 | },
26 | {
27 | position: 'absolute',
28 | width: 50,
29 | heigh: 50,
30 | top: 120,
31 | right: 40,
32 | backgroundColor: 'blue'
33 | },
34 | {
35 | position: 'absolute',
36 | width: 50,
37 | heigh: 50,
38 | bottom: 60,
39 | left: 140,
40 | backgroundColor: 'green'
41 | }
42 | ];
43 |
44 | const Target = (props) => {
45 | return (
46 |
47 | {
48 | context_menu_styles.map((box, idx) => {
49 | return props.connectContextMenu(
50 |
51 |
Right click here
52 |
53 | )
54 | })
55 | }
56 |
57 | );
58 | }
59 |
60 | export default Target;
61 |
--------------------------------------------------------------------------------
/examples/00 Connecting Targets/Single Target/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import Target from './target-container';
4 |
5 | export default class SingleTargetApp extends Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | render() {
11 | return (
12 |
13 |
14 | Browse the Source
15 |
16 |
17 | This is a simplest example that exists. Right click in the box below to get a context menu.
18 |
19 |
20 |
21 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/00 Connecting Targets/Single Target/target-container.js:
--------------------------------------------------------------------------------
1 | import ContextMenu from 'react-context-menus';
2 | import Target from './target.js';
3 |
4 | const menu_items = [
5 | {
6 | label: 'Item 1',
7 | onClick: (event, props, item) => alert('Clicked Item 1!')
8 | },
9 | {
10 | label: 'Item 2',
11 | onClick: () => alert('Clicked Item 2!')
12 | },
13 | '-',
14 | {
15 | label: 'Item 3',
16 | onClick: () => alert('Clicked Item 3 below the divider!')
17 | }
18 | ];
19 |
20 | const options = {
21 | theme: {
22 | style: {
23 | color: 'black'
24 | }
25 | }
26 | }
27 |
28 | export default ContextMenu(menu_items, options)(Target);
29 |
--------------------------------------------------------------------------------
/examples/00 Connecting Targets/Single Target/target.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const style = {
4 | position: 'relative',
5 | width: 300,
6 | height: 300,
7 | backgroundColor: '#111',
8 | color: 'white',
9 | textAlign: 'center'
10 | }
11 |
12 | const textStyle = {
13 | pointerEvents: 'none',
14 | pointer: 'cursor'
15 | }
16 |
17 | const Target = (props) => {
18 | return props.connectContextMenu(
19 |
20 |
21 | Right click here for a context menu
22 |
23 |
24 | );
25 | }
26 |
27 | export default Target;
28 |
--------------------------------------------------------------------------------
/examples/01 Styling/Themes/body.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import _ from 'underscore';
3 | import Target from './target-container.js';
4 |
5 | import * as _Themes from './themes.js';
6 | const Themes = _.omit(_Themes, '__esModule'); // Because it gets added by default
7 |
8 | class Body extends React.Component {
9 | constructor() {
10 | super();
11 | this.state = {
12 | selected: Object.keys(Themes)[0]
13 | }
14 | }
15 |
16 | handleChange(event) {
17 | this.setState({ selected: event.target.value });
18 | }
19 |
20 | render() {
21 | return (
22 |
23 |
26 | {
27 | _.map(Themes, (theme, key) => (
28 | {theme.name}
29 | ))
30 | }
31 |
32 |
33 |
34 |
35 | );
36 | }
37 | }
38 |
39 | export default Body;
40 |
--------------------------------------------------------------------------------
/examples/01 Styling/Themes/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import Body from './body.js';
4 |
5 | export default class SingleTargetApp extends Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | render() {
11 | return (
12 |
13 |
14 | Browse the Source
15 |
16 |
17 | This example shows how you can change the style of the context menus. The menu will automatically open when you change the selection to demonstrate the colors. You can still right click around though... See the react-menus homepage for full stying information.
18 |
19 |
20 |
21 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/01 Styling/Themes/target-container.js:
--------------------------------------------------------------------------------
1 | import ContextMenu from 'react-context-menus';
2 | import Target from './target.js';
3 |
4 | const menu_items = [
5 | {
6 | label: 'Item 1',
7 | onClick: (event, props, item) => alert('Clicked Item 1!')
8 | },
9 | {
10 | label: 'Item 2',
11 | onClick: () => alert('Clicked Item 2!')
12 | },
13 | '-',
14 | {
15 | label: 'Item 3',
16 | onClick: () => alert('Clicked Item 3 below the divider!')
17 | }
18 | ];
19 |
20 | const options = (props) => {
21 | return {
22 | theme: props.theme.theme,
23 | style: props.theme.style,
24 | container: {
25 | zIndex: 1
26 | }
27 | }
28 | }
29 |
30 | export default ContextMenu(menu_items, options)(Target);
31 |
--------------------------------------------------------------------------------
/examples/01 Styling/Themes/target.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const style = {
4 | position: 'relative',
5 | width: 300,
6 | height: 300,
7 | backgroundColor: '#111',
8 | color: 'white',
9 | textAlign: 'center'
10 | }
11 |
12 | const textStyle = {
13 | pointerEvents: 'none',
14 | pointer: 'cursor'
15 | }
16 |
17 | const Target = (props) => {
18 | return props.connectContextMenu(
19 |
20 |
21 | Right click here for a context menu
22 |
23 |
24 | );
25 | }
26 |
27 | export default Target;
28 |
--------------------------------------------------------------------------------
/examples/01 Styling/Themes/themes.js:
--------------------------------------------------------------------------------
1 | export const AURORA_RED = {
2 | name: 'Aurora Red',
3 | style: { border: '1px solid #e7ada9' },
4 | theme: {
5 | style: { background: '#b93a32', color: 'white' },
6 | overStyle: { background: '#f3d6d4', color: 'black' },
7 | activeStyle: { background: '#b93a32', color: 'white' }
8 | }
9 | }
10 |
11 | export const SERENITY = {
12 | name: 'Serenity',
13 | style: { border: '1px solid #6181bd' },
14 | theme: {
15 | style: { background: '#92a8d1', color: 'black' },
16 | overStyle: { background: '#e9eef6', color: 'black' },
17 | activeStyle: { background: '#92a8d1', color: 'black' }
18 | }
19 | }
20 |
21 | export const LUSH_MEADOW = {
22 | name: 'Lush Meadow',
23 | style: { border: '1px solid #e7fff9' },
24 | theme: {
25 | style: { background: '#006e51', color: 'white' },
26 | overStyle: { background: '#afffea', color: 'black' },
27 | activeStyle: { background: '#006e51', color: 'white' }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/examples/02 Dynamic Menus/Dynamic Menus/body.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Target from './target-container.js';
4 |
5 | const items = [
6 | 'Box 1',
7 | 'Box 2',
8 | 'Box 3',
9 | 'Box 4',
10 | 'Box 5',
11 | 'Box 6'
12 | ];
13 |
14 | const Body = (props) => (
15 |
16 | { items.map((item, idx) => (
{item}
)) }
17 |
18 | )
19 |
20 | export default Body;
21 |
--------------------------------------------------------------------------------
/examples/02 Dynamic Menus/Dynamic Menus/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import Body from './body';
4 |
5 | export default class SingleTargetApp extends Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | render() {
11 | return (
12 |
13 |
14 | Browse the Source
15 |
16 |
17 | This example shows how you can use a single function to generate unique menus based on the props of the item that was right clicked.
18 |
19 |
20 |
21 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/02 Dynamic Menus/Dynamic Menus/target-container.js:
--------------------------------------------------------------------------------
1 | import ContextMenu from 'react-context-menus';
2 | import Target from './target.js';
3 |
4 | const menu_items = (props) => {
5 | return [
6 | {
7 | label: 'Item 1, ' + props.text,
8 | onClick: (event, props, item) => alert('Clicked Item 1, ' + props.text + '!')
9 | },
10 | {
11 | label: 'Item 2, ' + props.text + '',
12 | onClick: () => alert('Clicked Item 2, ' + props.text + '!')
13 | },
14 | '-',
15 | {
16 | label: 'Item 3, ' + props.text + '',
17 | onClick: () => alert('Clicked Item 3, ' + props.text + ' below the divider!')
18 | }
19 | ];
20 | }
21 |
22 | const options = {
23 | theme: {
24 | style: {
25 | color: 'black'
26 | }
27 | }
28 | }
29 |
30 | export default ContextMenu(menu_items, options)(Target);
31 |
--------------------------------------------------------------------------------
/examples/02 Dynamic Menus/Dynamic Menus/target.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const style = {
4 | position: 'relative',
5 | width: 200,
6 | height: 40,
7 | backgroundColor: '#111',
8 | color: 'white',
9 | textAlign: 'center',
10 | marginBottom: 5
11 | }
12 |
13 | const textStyle = {
14 | pointerEvents: 'none',
15 | pointer: 'cursor'
16 | }
17 |
18 | const Target = (props) => {
19 | return props.connectContextMenu(
20 |
21 |
22 | { props.children }
23 |
24 |
25 | );
26 | }
27 |
28 | export default Target;
29 |
--------------------------------------------------------------------------------
/examples/02 Dynamic Menus/Stress Test/body.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Target from './target-container.js';
4 |
5 | const items = [
6 | 'Box 1',
7 | 'Box 2',
8 | 'Box 3',
9 | 'Box 4',
10 | 'Box 5',
11 | 'Box 6'
12 | ];
13 |
14 | const Body = (props) => (
15 |
16 | { items.map((item, idx) => (
{item}
)) }
17 |
18 | )
19 |
20 | export default Body;
21 |
--------------------------------------------------------------------------------
/examples/02 Dynamic Menus/Stress Test/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import Body from './body';
4 |
5 | export default class SingleTargetApp extends Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | render() {
11 | return (
12 |
20 | );
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/02 Dynamic Menus/Stress Test/target-container.js:
--------------------------------------------------------------------------------
1 | import ContextMenu from 'react-context-menus';
2 | import Target from './target.js';
3 |
4 | const menu_items = (props) => {
5 | return [
6 | {
7 | label: 'Item 1, ' + props.text,
8 | onClick: (event, props, item) => alert('Clicked Item 1, ' + props.text + '!')
9 | },
10 | {
11 | label: 'Item 2, ' + props.text + '',
12 | onClick: () => alert('Clicked Item 2, ' + props.text + '!')
13 | },
14 | '-',
15 | {
16 | label: 'Item 3, ' + props.text + '',
17 | onClick: () => alert('Clicked Item 3, ' + props.text + ' below the divider!')
18 | }
19 | ];
20 | }
21 |
22 | const options = {
23 | theme: {
24 | style: {
25 | color: 'black'
26 | }
27 | }
28 | }
29 |
30 | export default ContextMenu(menu_items, options)(Target);
31 |
--------------------------------------------------------------------------------
/examples/02 Dynamic Menus/Stress Test/target.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const style = {
4 | position: 'relative',
5 | width: 200,
6 | height: 40,
7 | backgroundColor: '#111',
8 | color: 'white',
9 | textAlign: 'center',
10 | marginBottom: 5
11 | }
12 |
13 | const textStyle = {
14 | pointerEvents: 'none',
15 | pointer: 'cursor'
16 | }
17 |
18 | const Target = (props) => {
19 | return props.connectContextMenu(
20 |
21 |
22 | { props.children }
23 |
24 |
25 | );
26 | }
27 |
28 | export default Target;
29 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jchristman/react-context-menus/8bf5fb7b0aeb740345d07e7128007384ee6bcf5c/examples/README.md
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-context-menus",
3 | "version": "1.0.10",
4 | "description": "Better context menus for React",
5 | "main": "lib/context_menu.js",
6 | "scripts": {
7 | "start": "./scripts/startSiteDevServer.sh",
8 | "build-site": "./scripts/buildStaticSite.sh",
9 | "publish-site": "./scripts/publishStaticSite.sh",
10 | "build": "./scripts/build.sh",
11 | "lint": "eslint .",
12 | "prepublish": "npm run build"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "https://github.com/jchristman/react-context-menus.git"
17 | },
18 | "keywords": [
19 | "javascript",
20 | "react",
21 | "reactjs",
22 | "contextmenu",
23 | "context-menu",
24 | "react-component",
25 | "rightclick",
26 | "right-click"
27 | ],
28 | "author": "Joshua Christman (http://github.com/jchristman)",
29 | "license": "MIT",
30 | "bugs": {
31 | "url": "https://github.com/jchristman/react-context-menus/issues"
32 | },
33 | "homepage": "https://github.com/jchristman/react-context-menus",
34 | "devDependencies": {
35 | "babel-cli": "^6.9.0",
36 | "babel-core": "6.x.x",
37 | "babel-loader": "^7.1.2",
38 | "babel-plugin-add-module-exports": "^0.2.1",
39 | "babel-plugin-transform-class-properties": "^6.9.0",
40 | "babel-preset-env": "^1.6.0",
41 | "babel-preset-react": "6.x.x",
42 | "babel-preset-stage-0": "^6.24.1",
43 | "css-loader": "^0.28.7",
44 | "extract-text-webpack-plugin": "^3.0.0",
45 | "html-loader": "^0.5.1",
46 | "less": "^2.7.1",
47 | "less-loader": "^4.0.5",
48 | "marked": "^0.3.6",
49 | "null-loader": "^0.1.1",
50 | "react-hot-loader": "^1.3.0",
51 | "style-loader": "^0.18.2",
52 | "webpack": "^3.6.0",
53 | "webpack-dev-server": "^2.4.1"
54 | },
55 | "dependencies": {
56 | "invariant": "2.x.x",
57 | "react": ">=0.14.x",
58 | "react-dom": ">=0.14.x",
59 | "react-menus2": "3.0.0",
60 | "underscore": "^1.8.3"
61 | },
62 | "peerDependencies": {
63 | "invariant": "2.x.x",
64 | "react": ">=0.14.x",
65 | "react-dom": ">=0.14.x",
66 | "react-menus2": "3.0.0",
67 | "underscore": "^1.8.3"
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/react-menus.md:
--------------------------------------------------------------------------------
1 | react-menus
2 | ===========
3 |
4 | > A carefully crafted menu for React
5 |
6 | ## Install
7 |
8 | ```sh
9 | $ npm install react-menus --save
10 | ```
11 |
12 | ## Description
13 |
14 | The `react-menus` component is a context-menu like widget for React. It features **smart positioning**, overflow **scrolling** on too many menu items and **smart submenu positioning**.
15 |
16 | ## Changelog
17 |
18 | See [Changelog](./CHANGELOG.md)
19 |
20 | ## Roadmap
21 |
22 | See [Roadmap](./ROADMAP.md)
23 |
24 | ## Usage
25 |
26 | ```jsx
27 | var items = [
28 | {
29 | label: 'hello',
30 | onClick: function(itemProps, index, event) {
31 | console.log('well, hello')
32 | }
33 | },
34 | '-', //show separator
35 | {
36 | label: 'hi'
37 | },
38 | {
39 | label: 'export',
40 | disabled: true
41 | }
42 | ]
43 |
44 | function onClick(event, props, index){
45 |
46 | }
47 |
48 |
49 | ```
50 |
51 | For rendering separators, just use a `'-'` in the items array.
52 |
53 | ## Properties
54 |
55 | * items: Object[]
56 | * onClick: Function(event, props, index) - Called on a click on a direct menu item. For clicks on menu items at any level of nesting, use `onChildClick`
57 | * onChildClick: Function(event, props) - Called when a menu item at any level of nesting was clicked
58 | * columns: String[] - defaults to ['label']
59 |
60 | For every item in the items property, a row will be rendered, with all the columns specified in `props.columns`. Every column displays the value in item[<column_name>].
61 |
62 | Every item can optionally have an **onClick** property, which is called when the item is clicked. (**onClick: Function(event, itemProps, index)**). Making an item disabled is done by specifying **disabled: true** on the item object.
63 |
64 | * expander: String/ReactElement - an expander tool to use when a menu item has other subitems. Defaults to the unicode arrow character **›**.
65 |
66 | ### Styling & advanced usage
67 |
68 | By default, the `react-menus` component comes with built-in structural styles as well as with styles for a default nice theme. The specified theme is applied to menu items. If you don't want to render menu items with the default theme, just specify `theme=''` (or any falsy value).
69 |
70 | ```jsx
71 | var items = [ {label: 'Save', onClick: function(){} }, { label: 'Export'}]
72 |
73 | ```
74 |
75 | Or you can specify your own theme for the button. The value for the `theme` property is just an object with different styles:
76 |
77 | ```jsx
78 | var theme = {
79 | style: { background: 'blue'},
80 | overStyle: { background: 'red', color: 'white'},
81 | activeStyle: { background: 'magenta'},
82 | expandedStyle: { background: 'magenta'},
83 | disabledStyle: {background: 'gray'}
84 | }
85 |
86 |
87 | ```
88 |
89 | Or you can specify a theme as string: 'default'. The `'default'` theme is the only one built in.
90 |
91 | ```jsx
92 |
93 | ```
94 | But you can add named themes:
95 | ```jsx
96 | var theme = require('react-menus').theme
97 |
98 | theme.goldenTheme = { overStyle: {background: 'yellow'}}
99 |
100 |
101 | ```
102 |
103 | For styling menu separators, set the desired style properties on `Menu.Separator.style`
104 |
105 | ```jsx
106 | var Menu = require('react-menus')
107 |
108 | var Separator = Menu.Separator
109 |
110 | Separator.style = {
111 | background: 'red' //the color of the separator
112 | }
113 |
114 | Separator.size = 10 //will be 10 px in height
115 | ```
116 |
117 | ### Style props
118 |
119 | Styling menu items overrides theme styles.
120 |
121 | * itemStyle - style to be applied to menu items. Overrides `theme.style`
122 | * itemOverStyle - style to be applied to menu items on mouse over. Overrides `theme.overStyle`
123 | * itemActiveStyle - style to be applied to menu items on mouse down on the item. Overrides `theme.activeStyle`
124 | * itemExpandedStyle - style to be applied to menu items when the item is expanded. Overrides `theme.expandedStyle`
125 | * itemDisabledStyle - style to be applied to disabled menu items. Overrides `theme.defaultStyle`
126 |
127 | * cellStyle - style to be applied to menu item cells (expect the expander cell).
128 |
129 | ### Scrolling
130 |
131 | Menu scrolling is enabled by default (`enableScroll: true`). When you have too many items, and the menu is bigger than it's parent container, the menu shows a scrolling user interface.
132 |
133 | Or you can specify a `maxHeight` property on the menu, and if that is exceeded, the menu is scrollable.
134 |
135 | ```jsx
136 |
137 | ```
138 |
139 | Of course you can turn off scrolling with `enableScroll: false`
140 |
141 | ### Smart submenus
142 |
143 | Showing and hiding submenus is implemented with a smart algorithm, as described [here](http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown). Also submenu positioning is made taking into account the available space. More documentation on this soon.
144 |
145 | ## License
146 |
147 | ```MIT```
148 |
--------------------------------------------------------------------------------
/scripts/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # build minified standalone version in dist
4 | rm -rf dist
5 | ./node_modules/.bin/webpack --output-filename=dist/ContextMenu.js
6 | ./node_modules/.bin/webpack --output-filename=dist/ContextMenu.min.js --optimize-minimize
7 |
8 | # build ES5 modules to lib
9 | rm -rf lib
10 | babel src --out-dir lib
11 |
--------------------------------------------------------------------------------
/scripts/buildSiteIndexPages.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | require('babel-core/register');
3 |
4 | // -*- mode: js -*-
5 | "use strict";
6 |
7 | var fs = require('fs');
8 | var path = require('path');
9 | var glob = require('glob');
10 | var Constants = require('../site/Constants');
11 | var renderPath = require('../__site_prerender__/renderPath');
12 | var flatten = require('lodash/flatten');
13 |
14 | var sitePath = path.join(__dirname, '../__site__');
15 | if (!fs.existsSync(sitePath)) {
16 | fs.mkdirSync(sitePath);
17 | }
18 |
19 | var files = {
20 | 'main.css': 'main.css',
21 | 'main.js': 'main.js'
22 | };
23 |
24 | if (process.env.NODE_ENV === 'production') {
25 | Object.keys(files).forEach(function(fileName) {
26 | var searchPath = path.join(
27 | __dirname,
28 | '../__site__/' + fileName.replace('.', '-*.')
29 | );
30 | var hashedFilename = glob.sync(searchPath)[0];
31 | if (!hashedFilename) {
32 | throw new Error(
33 | 'Hashed file of "' + fileName + '" ' +
34 | 'not found when searching with "' + searchPath + '"'
35 | );
36 | }
37 |
38 | files[fileName] = path.basename(hashedFilename);
39 | });
40 | }
41 |
42 | var locations = flatten([
43 | Constants.APIPages.map(function (group) {
44 | return group.pages;
45 | }),
46 | Constants.ExamplePages.map(function (group) {
47 | return group.pages;
48 | }),
49 | ]).reduce(function(paths, pages) {
50 | return paths.concat(
51 | Object.keys(pages).map(function(key) {
52 | return pages[key].location;
53 | })
54 | );
55 | }, []);
56 |
57 | locations.forEach(function(fileName) {
58 | var props = {
59 | location: fileName,
60 | devMode: process.env.NODE_ENV !== 'production',
61 | files: files
62 | };
63 |
64 | renderPath(fileName, props, function(content) {
65 | fs.writeFileSync(
66 | path.join(sitePath, fileName),
67 | content
68 | );
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/scripts/buildStaticSite.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | PATH=$(npm bin):$PATH
4 |
5 | rm -rf ./__site__
6 | rm -rf ./__site_prerender__
7 | NODE_ENV=production webpack --config "$PWD/site/webpack-client.config.js"
8 | NODE_ENV=production webpack --config "$PWD/site/webpack-prerender.config.js"
9 | NODE_ENV=production ./scripts/buildSiteIndexPages.sh
10 |
--------------------------------------------------------------------------------
/scripts/cssTransformLoader.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var postcss = require('postcss');
4 | var autoPrefixer = require('autoprefixer');
5 |
6 | module.exports = function(content) {
7 | if (this && this.cacheable) {
8 | // Webpack specific call
9 | this.cacheable();
10 | }
11 |
12 | content = postcss()
13 | .use(autoPrefixer())
14 | .process(content).css;
15 |
16 | return content;
17 | };
18 |
--------------------------------------------------------------------------------
/scripts/markdownLoader.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var marked = require('marked');
4 | var prism = require('./prism');
5 |
6 | // functions come before keywords
7 | prism.languages.insertBefore('javascript', 'keyword', {
8 | 'var': /\b(this)\b/g,
9 | 'block-keyword': /\b(if|else|while|for|function)\b/g,
10 | 'primitive': /\b(true|false|null|undefined)\b/g,
11 | 'function': prism.languages.function,
12 | });
13 |
14 | prism.languages.insertBefore('javascript', {
15 | 'qualifier': /\b[A-Z][a-z0-9_]+/g,
16 | });
17 |
18 | marked.setOptions({
19 | xhtml: true,
20 | highlight: function(code) {
21 | return prism.highlight(code, prism.languages.javascript);
22 | }
23 | });
24 |
25 | var renderer = new marked.Renderer();
26 |
27 | renderer.heading = function (text, level) {
28 | var escapedText = text.toLowerCase().replace(/[^\w]+/g, '-');
29 |
30 | // A hack to have proper anchor scrolling despite the navbar on top of them.
31 | // In CSS, they'll be positioned relatively.
32 | return '' + text + ' ';
33 | };
34 |
35 | renderer.code = function(code, lang, escaped) {
36 | if (this.options.highlight) {
37 | var out = this.options.highlight(code, lang);
38 | if (out != null && out !== code) {
39 | escaped = true;
40 | code = out;
41 | }
42 | }
43 | return '' +
44 | (escaped ? code : escapeCode(code, true)) +
45 | '
';
46 | };
47 |
48 | function escapeCode(code) {
49 | return code
50 | .replace(/&/g, '&')
51 | .replace(//g, '>')
53 | .replace(/"/g, '"')
54 | .replace(/'/g, ''');
55 | }
56 |
57 | module.exports = function(markdown) {
58 | if (this && this.cacheable) {
59 | // Webpack specific call
60 | this.cacheable();
61 | }
62 |
63 | return marked(markdown, { renderer: renderer });
64 | };
65 |
--------------------------------------------------------------------------------
/scripts/prism.js:
--------------------------------------------------------------------------------
1 | /* **********************************************
2 | Begin prism-core.js
3 | ********************************************** */
4 |
5 | self = (typeof window !== 'undefined')
6 | ? window // if in browser
7 | : (
8 | (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope)
9 | ? self // if in worker
10 | : {} // if in node js
11 | );
12 |
13 | /**
14 | * Prism: Lightweight, robust, elegant syntax highlighting
15 | * MIT license http://www.opensource.org/licenses/mit-license.php/
16 | * @author Lea Verou http://lea.verou.me
17 | */
18 |
19 | var Prism = (function(){
20 |
21 | // Private helper vars
22 | var lang = /\blang(?:uage)?-(?!\*)(\w+)\b/i;
23 |
24 | var _ = self.Prism = {
25 | util: {
26 | encode: function (tokens) {
27 | if (tokens instanceof Token) {
28 | return new Token(tokens.type, _.util.encode(tokens.content), tokens.alias);
29 | } else if (_.util.type(tokens) === 'Array') {
30 | return tokens.map(_.util.encode);
31 | } else {
32 | return tokens.replace(/&/g, '&').replace(/ text.length) {
271 | // Something went terribly wrong, ABORT, ABORT!
272 | break tokenloop;
273 | }
274 |
275 | if (str instanceof Token) {
276 | continue;
277 | }
278 |
279 | pattern.lastIndex = 0;
280 |
281 | var match = pattern.exec(str);
282 |
283 | if (match) {
284 | if(lookbehind) {
285 | lookbehindLength = match[1].length;
286 | }
287 |
288 | var from = match.index - 1 + lookbehindLength,
289 | match = match[0].slice(lookbehindLength),
290 | len = match.length,
291 | to = from + len,
292 | before = str.slice(0, from + 1),
293 | after = str.slice(to + 1);
294 |
295 | var args = [i, 1];
296 |
297 | if (before) {
298 | args.push(before);
299 | }
300 |
301 | var wrapped = new Token(token, inside? _.tokenize(match, inside) : match, alias);
302 |
303 | args.push(wrapped);
304 |
305 | if (after) {
306 | args.push(after);
307 | }
308 |
309 | Array.prototype.splice.apply(strarr, args);
310 | }
311 | }
312 | }
313 | }
314 |
315 | return strarr;
316 | },
317 |
318 | hooks: {
319 | all: {},
320 |
321 | add: function (name, callback) {
322 | var hooks = _.hooks.all;
323 |
324 | hooks[name] = hooks[name] || [];
325 |
326 | hooks[name].push(callback);
327 | },
328 |
329 | run: function (name, env) {
330 | var callbacks = _.hooks.all[name];
331 |
332 | if (!callbacks || !callbacks.length) {
333 | return;
334 | }
335 |
336 | for (var i=0, callback; callback = callbacks[i++];) {
337 | callback(env);
338 | }
339 | }
340 | }
341 | };
342 |
343 | var Token = _.Token = function(type, content, alias) {
344 | this.type = type;
345 | this.content = content;
346 | this.alias = alias;
347 | };
348 |
349 | Token.stringify = function(o, language, parent) {
350 | if (typeof o == 'string') {
351 | return o;
352 | }
353 |
354 | if (Object.prototype.toString.call(o) == '[object Array]') {
355 | return o.map(function(element) {
356 | return Token.stringify(element, language, o);
357 | }).join('');
358 | }
359 |
360 | var env = {
361 | type: o.type,
362 | content: Token.stringify(o.content, language, parent),
363 | tag: 'span',
364 | classes: ['token', o.type],
365 | attributes: {},
366 | language: language,
367 | parent: parent
368 | };
369 |
370 | if (env.type == 'comment') {
371 | env.attributes['spellcheck'] = 'true';
372 | }
373 |
374 | if (o.alias) {
375 | var aliases = _.util.type(o.alias) === 'Array' ? o.alias : [o.alias];
376 | Array.prototype.push.apply(env.classes, aliases);
377 | }
378 |
379 | _.hooks.run('wrap', env);
380 |
381 | var attributes = '';
382 |
383 | for (var name in env.attributes) {
384 | attributes += name + '="' + (env.attributes[name] || '') + '"';
385 | }
386 |
387 | return '<' + env.tag + ' class="' + env.classes.join(' ') + '" ' + attributes + '>' + env.content + '' + env.tag + '>';
388 |
389 | };
390 |
391 | if (!self.document) {
392 | if (!self.addEventListener) {
393 | // in Node.js
394 | return self.Prism;
395 | }
396 | // In worker
397 | self.addEventListener('message', function(evt) {
398 | var message = JSON.parse(evt.data),
399 | lang = message.language,
400 | code = message.code;
401 |
402 | self.postMessage(JSON.stringify(_.util.encode(_.tokenize(code, _.languages[lang]))));
403 | self.close();
404 | }, false);
405 |
406 | return self.Prism;
407 | }
408 |
409 | // Get current script and highlight
410 | var script = document.getElementsByTagName('script');
411 |
412 | script = script[script.length - 1];
413 |
414 | if (script) {
415 | _.filename = script.src;
416 |
417 | if (document.addEventListener && !script.hasAttribute('data-manual')) {
418 | document.addEventListener('DOMContentLoaded', _.highlightAll);
419 | }
420 | }
421 |
422 | return self.Prism;
423 |
424 | })();
425 |
426 | if (typeof module !== 'undefined' && module.exports) {
427 | module.exports = Prism;
428 | }
429 |
430 |
431 | /* **********************************************
432 | Begin prism-markup.js
433 | ********************************************** */
434 |
435 | Prism.languages.markup = {
436 | 'comment': //g,
437 | 'prolog': /<\?.+?\?>/,
438 | 'doctype': //,
439 | 'cdata': //i,
440 | 'tag': {
441 | pattern: /<\/?[\w:-]+\s*(?:\s+[\w:-]+(?:=(?:("|')(\\?[\w\W])*?\1|[^\s'">=]+))?\s*)*\/?>/gi,
442 | inside: {
443 | 'tag': {
444 | pattern: /^<\/?[\w:-]+/i,
445 | inside: {
446 | 'punctuation': /^<\/?/,
447 | 'namespace': /^[\w-]+?:/
448 | }
449 | },
450 | 'attr-value': {
451 | pattern: /=(?:('|")[\w\W]*?(\1)|[^\s>]+)/gi,
452 | inside: {
453 | 'punctuation': /=|>|"/g
454 | }
455 | },
456 | 'punctuation': /\/?>/g,
457 | 'attr-name': {
458 | pattern: /[\w:-]+/g,
459 | inside: {
460 | 'namespace': /^[\w-]+?:/
461 | }
462 | }
463 |
464 | }
465 | },
466 | 'entity': /\?[\da-z]{1,8};/gi
467 | };
468 |
469 | // Plugin to make entity title show the real entity, idea by Roman Komarov
470 | Prism.hooks.add('wrap', function(env) {
471 |
472 | if (env.type === 'entity') {
473 | env.attributes['title'] = env.content.replace(/&/, '&');
474 | }
475 | });
476 |
477 |
478 | /* **********************************************
479 | Begin prism-css.js
480 | ********************************************** */
481 |
482 | Prism.languages.css = {
483 | 'comment': /\/\*[\w\W]*?\*\//g,
484 | 'atrule': {
485 | pattern: /@[\w-]+?.*?(;|(?=\s*{))/gi,
486 | inside: {
487 | 'punctuation': /[;:]/g
488 | }
489 | },
490 | 'url': /url\((["']?).*?\1\)/gi,
491 | 'selector': /[^\{\}\s][^\{\};]*(?=\s*\{)/g,
492 | 'property': /(\b|\B)[\w-]+(?=\s*:)/ig,
493 | 'string': /("|')(\\?.)*?\1/g,
494 | 'important': /\B!important\b/gi,
495 | 'punctuation': /[\{\};:]/g,
496 | 'function': /[-a-z0-9]+(?=\()/ig
497 | };
498 |
499 | if (Prism.languages.markup) {
500 | Prism.languages.insertBefore('markup', 'tag', {
501 | 'style': {
502 | pattern: /