├── .gitattributes ├── .gitignore ├── .vscode ├── settings.json └── extensions.json ├── src ├── sec-introduction.html └── index.html ├── .yo-rc.json ├── gulpfile.js ├── package.json ├── LICENSE ├── docs ├── index.html ├── ecmarkup.css └── ecmarkup.js └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.html": "ecmarkup" 4 | } 5 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "rbuckton.grammarkdown-language", 4 | "rbuckton.ecmarkup-vscode" 5 | ] 6 | } -------------------------------------------------------------------------------- /src/sec-introduction.html: -------------------------------------------------------------------------------- 1 |

Forthcoming

2 |

See the proposal repository for background material and discussion.

-------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-ecmascript-proposal": { 3 | "promptValues": { 4 | "stage": 0, 5 | "authorName": "Ron Buckton", 6 | "authorEmail": "ron.buckton@microsoft.com" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
 8 |     title: Proposal to add functional operators (`{+}`, `{-}`) to ECMAScript
 9 |     stage: 0
10 |     grammar: strict
11 |     contributors: Ron Buckton, Ecma International
12 | 
13 | 14 | 15 |

Introduction

16 | 17 |
18 | 19 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const del = require("del"); 2 | const gulp = require("gulp"); 3 | const emu = require("gulp-emu"); 4 | const gls = require("gulp-live-server"); 5 | const spawn = require("child_process").spawn; 6 | 7 | gulp.task("clean", () => del("docs/**/*")); 8 | 9 | gulp.task("build", () => gulp 10 | .src(["src/index.html"]) 11 | .pipe(emu({ js: "ecmarkup.js", css: "ecmarkup.css", assets: "none" })) 12 | .pipe(gulp.dest("docs"))); 13 | 14 | gulp.task("watch", () => gulp 15 | .watch(["src/**/*"], ["build"])); 16 | 17 | gulp.task("start", ["watch"], () => { 18 | const server = gls.static("docs", 8080); 19 | const promise = server.start(); 20 | gulp.watch(["docs/**/*"], file => server.notify(file)); 21 | return promise; 22 | }); 23 | 24 | gulp.task("default", ["build"]); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proposal-functional-operators", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "Proposal to add functional operators to ECMAScript", 6 | "homepage": "https://github.com/rbuckton/proposal-functional-operators#readme", 7 | "author": { 8 | "name": "Ron Buckton", 9 | "email": "ron.buckton@microsoft.com" 10 | }, 11 | "keywords": [ 12 | "javascript", 13 | "ecmascript" 14 | ], 15 | "scripts": { 16 | "compile": "gulp build", 17 | "start": "gulp start" 18 | }, 19 | "license": "SEE LICENSE IN https://tc39.github.io/ecma262/#sec-copyright-and-software-license", 20 | "devDependencies": { 21 | "del": "^2.2.2", 22 | "ecmarkup": "^3.11.2", 23 | "gulp": "^3.9.1", 24 | "gulp-emu": "^1.1.0", 25 | "gulp-live-server": "0.0.30" 26 | }, 27 | "repository": "git@github.com:rbuckton/proposal-functional-operators.git" 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Ron Buckton, Ecma International 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Proposal to add functional operators (`{+}`, `{-}`) to ECMAScript

Stage 0 Draft / July 20, 2017

Proposal to add functional operators ({+}, {-}) to ECMAScript

8 | 9 | 10 |

Introduction

11 |

Forthcoming

12 |

See the proposal repository for background material and discussion.

13 |
14 | 15 | 16 |

ACopyright & Software License

17 | 18 |

Copyright Notice

19 |

© 2017 Ron Buckton, Ecma International

20 | 21 |

Software License

22 |

All Software contained in this document ("Software") is protected by copyright and is being made available under the "BSD License", included below. This Software may be subject to third party rights (rights from parties other than Ecma International), including patent rights, and no licenses under such third party rights are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT http://www.ecma-international.org/memento/codeofconduct.htm FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS.

23 | 24 |

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

25 | 26 |
    27 |
  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. 28 |
  3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  4. 29 |
  5. Neither the name of the authors nor Ecma International may be used to endorse or promote products derived from this software without specific prior written permission.
  6. 30 |
31 | 32 |

THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ECMA INTERNATIONAL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

33 | 34 |
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 25 | 26 | # Proposal to add functional operators to ECMAScript 27 | 28 | This strawman seeks to define the possible syntax and semantics for functional operators 29 | for ECMAScript. 30 | 31 | ## Status 32 | 33 | **Stage:** 0 34 | **Champion:** _None identified_ 35 | 36 | _For more information see the [TC39 proposal process](https://tc39.github.io/process-document/)._ 37 | 38 | 39 | 40 | 41 | 42 | 43 | # Proposal 44 | 45 | A "functional operator" is an operator-like sigil that acts like a function at runtime. In 46 | addition, a "binary functional operator" allows either the first or second operand to be fixed. 47 | 48 | Most functional operators are written as `{<>}` where `<>` is one of the binary or 49 | unary operator punctuators, or one of the `instanceof`, `in`, or `typeof` keywords. In cases 50 | where an operator has an overloaded definition between both binary and unary forms, the unary 51 | versions of the operator have the form `{~<>}`. 52 | 53 | Each functional operator can be expressed as existing JavaScript given the following syntactic 54 | conversions: 55 | 56 | ```js 57 | // exponentiation operator function 58 | {**} // (a, b) => a ** b 59 | 60 | // multiplicative operator function 61 | {*} // (a, b) => a * b 62 | {/} // (a, b) => a / b 63 | {%} // (a, b) => a % b 64 | 65 | // additive operator function 66 | {+} // (a, b) => a + b 67 | {-} // (a, b) => a - b 68 | 69 | // shift operator function 70 | {<<} // (a, b) => a << b 71 | {>>} // (a, b) => a >> b 72 | {>>>} // (a, b) => a >>> b 73 | 74 | // relational operator function 75 | {<} // (a, b) => a < b 76 | {<=} // (a, b) => a <= b 77 | {>} // (a, b) => a > b 78 | {>=} // (a, b) => a >= b 79 | {instanceof} // (a, b) => a instanceof b 80 | {in} // (a, b) => a in b 81 | 82 | // equality operator function 83 | {==} // (a, b) => a == b 84 | {===} // (a, b) => a === b 85 | {!=} // (a, b) => a != b 86 | {!==} // (a, b) => a !== b 87 | 88 | // bitwise operator function 89 | {&} // (a, b) => a & b 90 | {|} // (a, b) => a | b 91 | {^} // (a, b) => a ^ b 92 | 93 | // logical operator function 94 | {&&} // (a, b) => a && b 95 | {||} // (a, b) => a || b 96 | 97 | // unary additive operator function 98 | {~+} // (a) => +a 99 | {~-} // (a) => -a 100 | 101 | // unary bitwise operator function 102 | {~} // (a) => ~a 103 | 104 | // unary logical operator function 105 | {!} // (a) => !a 106 | 107 | // other unary operator function 108 | {typeof} // (a) => typeof a 109 | ``` 110 | 111 | Each functional operator is a frozen function that exists at most once per realm. This can help to 112 | cut down on the number of function objects allocated within a given program. 113 | 114 | ## Fixed arguments 115 | 116 | In addition, binary functional operators may fix either the first or second operand: 117 | 118 | ```js 119 | // fixed arguments 120 | {+} 1 // (a) => a + 1 121 | 122 | 2 {*} // (a) => 2 * a 123 | ``` 124 | 125 | In these cases, the function returned is a unique frozen function object. 126 | 127 | # Examples 128 | 129 | ```js 130 | const sum = ar.reduce({+}); 131 | 132 | const result = numbers 133 | .map({*} 2) // scale 134 | .map({+} 5) // offset 135 | 136 | const strings = numbers 137 | .map({+} ""); 138 | 139 | const numbers = strings 140 | .map({~+}); 141 | 142 | const positive = numbers 143 | .filter({>} 0); 144 | 145 | const halves = numbers 146 | .map({/} 2); // no need for regexp lookahead/cover grammar 147 | ``` 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | # Prior Art 159 | 160 | * [F#](http://fsharp.org) symbolic operators. -------------------------------------------------------------------------------- /docs/ecmarkup.css: -------------------------------------------------------------------------------- 1 | body { 2 | display: flex; 3 | font-size: 18px; 4 | line-height: 1.5; 5 | font-family: Cambria, Palatino Linotype, Palatino, Liberation Serif, serif; 6 | padding: 0; 7 | margin: 0; 8 | color: #111; 9 | } 10 | 11 | #spec-container { 12 | padding: 0 20px; 13 | flex-grow: 1; 14 | flex-basis: 66%; 15 | box-sizing: border-box; 16 | overflow: hidden; 17 | } 18 | 19 | body.oldtoc { 20 | margin: 0 auto; 21 | } 22 | 23 | a { 24 | text-decoration: none; 25 | color: #206ca7; 26 | } 27 | 28 | a:visited { 29 | color: #206ca7; 30 | } 31 | 32 | a:hover { 33 | text-decoration: underline; 34 | color: #239dee; 35 | } 36 | 37 | 38 | code { 39 | font-weight: bold; 40 | font-family: Consolas, Monaco, monospace; 41 | white-space: pre; 42 | } 43 | 44 | pre code { 45 | font-weight: inherit; 46 | } 47 | 48 | pre code.hljs { 49 | background-color: #fff; 50 | margin: 0; 51 | padding: 0; 52 | } 53 | 54 | ol.toc { 55 | list-style: none; 56 | padding-left: 0; 57 | } 58 | 59 | ol.toc ol.toc { 60 | padding-left: 2ex; 61 | list-style: none; 62 | } 63 | 64 | var { 65 | color: #2aa198; 66 | transition: background-color 0.25s ease; 67 | cursor: pointer; 68 | } 69 | 70 | var.referenced { 71 | background-color: #ffff33; 72 | } 73 | 74 | emu-const { 75 | font-family: sans-serif; 76 | } 77 | 78 | emu-val { 79 | font-weight: bold; 80 | } 81 | emu-alg ol, emu-alg ol ol ol ol { 82 | list-style-type: decimal; 83 | } 84 | 85 | emu-alg ol ol, emu-alg ol ol ol ol ol { 86 | list-style-type: lower-alpha; 87 | } 88 | 89 | emu-alg ol ol ol, ol ol ol ol ol ol { 90 | list-style-type: lower-roman; 91 | } 92 | 93 | emu-eqn { 94 | display: block; 95 | margin-left: 4em; 96 | } 97 | 98 | emu-eqn.inline { 99 | display: inline; 100 | margin: 0; 101 | } 102 | 103 | emu-eqn div:first-child { 104 | margin-left: -2em; 105 | } 106 | 107 | emu-note { 108 | margin: 1em 0; 109 | color: #666; 110 | border-left: 5px solid #ccc; 111 | display: flex; 112 | flex-direction: row; 113 | } 114 | 115 | emu-note > span.note { 116 | flex-basis: 100px; 117 | min-width: 100px; 118 | flex-grow: 0; 119 | flex-shrink: 1; 120 | text-transform: uppercase; 121 | padding-left: 5px; 122 | } 123 | 124 | emu-note[type=editor] { 125 | border-left-color: #faa; 126 | } 127 | 128 | emu-note > div.note-contents { 129 | flex-grow: 1; 130 | flex-shrink: 1; 131 | } 132 | 133 | emu-note > div.note-contents > p:first-child { 134 | margin-top: 0; 135 | } 136 | 137 | emu-note > div.note-contents > p:last-child { 138 | margin-bottom: 0; 139 | } 140 | 141 | emu-figure { 142 | display: block; 143 | } 144 | 145 | emu-example { 146 | display: block; 147 | margin: 1em 3em; 148 | } 149 | 150 | emu-example figure figcaption { 151 | margin-top: 0.5em; 152 | text-align: left; 153 | } 154 | 155 | emu-figure figure, 156 | emu-example figure, 157 | emu-table figure { 158 | display: flex; 159 | flex-direction: column; 160 | align-items: center; 161 | } 162 | 163 | emu-production { 164 | display: block; 165 | margin-top: 1em; 166 | margin-bottom: 1em; 167 | margin-left: 5ex; 168 | } 169 | 170 | emu-grammar.inline, emu-production.inline, 171 | emu-grammar.inline emu-production emu-rhs, emu-production.inline emu-rhs, 172 | emu-grammar[collapsed] emu-production emu-rhs, emu-production[collapsed] emu-rhs { 173 | display: inline; 174 | padding-left: 1ex; 175 | margin-left: 0; 176 | } 177 | 178 | emu-grammar[collapsed] emu-production, emu-production[collapsed] { 179 | margin: 0; 180 | } 181 | 182 | emu-constraints { 183 | font-size: .75em; 184 | margin-right: 1ex; 185 | } 186 | 187 | emu-gann { 188 | margin-right: 1ex; 189 | } 190 | 191 | emu-gann emu-t:last-child, 192 | emu-gann emu-nt:last-child { 193 | margin-right: 0; 194 | } 195 | 196 | emu-geq { 197 | margin-left: 1ex; 198 | font-weight: bold; 199 | } 200 | 201 | emu-oneof { 202 | font-weight: bold; 203 | margin-left: 1ex; 204 | } 205 | 206 | emu-nt { 207 | display: inline-block; 208 | font-style: italic; 209 | white-space: nowrap; 210 | text-indent: 0; 211 | } 212 | 213 | emu-nt a, emu-nt a:visited { 214 | color: #333; 215 | } 216 | 217 | emu-rhs emu-nt { 218 | margin-right: 1ex; 219 | } 220 | 221 | emu-t { 222 | display: inline-block; 223 | font-family: monospace; 224 | font-weight: bold; 225 | white-space: nowrap; 226 | text-indent: 0; 227 | } 228 | 229 | emu-production emu-t { 230 | margin-right: 1ex; 231 | } 232 | 233 | emu-rhs { 234 | display: block; 235 | padding-left: 75px; 236 | text-indent: -25px; 237 | } 238 | 239 | emu-mods { 240 | font-size: .85em; 241 | vertical-align: sub; 242 | font-style: normal; 243 | font-weight: normal; 244 | } 245 | 246 | emu-production[collapsed] emu-mods { 247 | display: none; 248 | } 249 | 250 | emu-params, emu-opt { 251 | margin-right: 1ex; 252 | font-family: monospace; 253 | } 254 | 255 | emu-params, emu-constraints { 256 | color: #2aa198; 257 | } 258 | 259 | emu-opt { 260 | color: #b58900; 261 | } 262 | 263 | emu-gprose { 264 | font-size: 0.9em; 265 | font-family: Helvetica, Arial, sans-serif; 266 | } 267 | 268 | h1.shortname { 269 | color: #f60; 270 | font-size: 1.5em; 271 | margin: 0; 272 | } 273 | 274 | h1.version { 275 | color: #f60; 276 | font-size: 1.5em; 277 | margin: 0; 278 | } 279 | 280 | h1.title { 281 | margin-top: 0; 282 | color: #f60; 283 | } 284 | 285 | h1.first { 286 | margin-top: 0; 287 | } 288 | 289 | h1, h2, h3, h4, h5, h6 { 290 | position: relative; 291 | } 292 | 293 | h1 .secnum { 294 | text-decoration: none; 295 | margin-right: 10px; 296 | } 297 | 298 | h1 span.title { 299 | order: 2; 300 | } 301 | 302 | 303 | h1 { font-size: 2.67em; margin-top: 2em; margin-bottom: 0; line-height: 1em;} 304 | h2 { font-size: 2em; } 305 | h3 { font-size: 1.56em; } 306 | h4 { font-size: 1.25em; } 307 | h5 { font-size: 1.11em; } 308 | h6 { font-size: 1em; } 309 | 310 | h1:hover span.utils { 311 | display: block; 312 | } 313 | 314 | span.utils { 315 | font-size: 18px; 316 | line-height: 18px; 317 | display: none; 318 | position: absolute; 319 | top: 100%; 320 | left: 0; 321 | right: 0; 322 | font-weight: normal; 323 | } 324 | 325 | span.utils:before { 326 | content: "⤷"; 327 | display: inline-block; 328 | padding: 0 5px; 329 | } 330 | 331 | span.utils > * { 332 | display: inline-block; 333 | margin-right: 20px; 334 | } 335 | 336 | h1 span.utils span.anchor a, 337 | h2 span.utils span.anchor a, 338 | h3 span.utils span.anchor a, 339 | h4 span.utils span.anchor a, 340 | h5 span.utils span.anchor a, 341 | h6 span.utils span.anchor a { 342 | text-decoration: none; 343 | font-variant: small-caps; 344 | } 345 | 346 | h1 span.utils span.anchor a:hover, 347 | h2 span.utils span.anchor a:hover, 348 | h3 span.utils span.anchor a:hover, 349 | h4 span.utils span.anchor a:hover, 350 | h5 span.utils span.anchor a:hover, 351 | h6 span.utils span.anchor a:hover { 352 | color: #333; 353 | } 354 | 355 | emu-intro h1, emu-clause h1, emu-annex h1 { font-size: 2em; } 356 | emu-intro h2, emu-clause h2, emu-annex h2 { font-size: 1.56em; } 357 | emu-intro h3, emu-clause h3, emu-annex h3 { font-size: 1.25em; } 358 | emu-intro h4, emu-clause h4, emu-annex h4 { font-size: 1.11em; } 359 | emu-intro h5, emu-clause h5, emu-annex h5 { font-size: 1em; } 360 | emu-intro h6, emu-clause h6, emu-annex h6 { font-size: 0.9em; } 361 | emu-intro emu-intro h1, emu-clause emu-clause h1, emu-annex emu-annex h1 { font-size: 1.56em; } 362 | emu-intro emu-intro h2, emu-clause emu-clause h2, emu-annex emu-annex h2 { font-size: 1.25em; } 363 | emu-intro emu-intro h3, emu-clause emu-clause h3, emu-annex emu-annex h3 { font-size: 1.11em; } 364 | emu-intro emu-intro h4, emu-clause emu-clause h4, emu-annex emu-annex h4 { font-size: 1em; } 365 | emu-intro emu-intro h5, emu-clause emu-clause h5, emu-annex emu-annex h5 { font-size: 0.9em; } 366 | emu-intro emu-intro emu-intro h1, emu-clause emu-clause emu-clause h1, emu-annex emu-annex emu-annex h1 { font-size: 1.25em; } 367 | emu-intro emu-intro emu-intro h2, emu-clause emu-clause emu-clause h2, emu-annex emu-annex emu-annex h2 { font-size: 1.11em; } 368 | emu-intro emu-intro emu-intro h3, emu-clause emu-clause emu-clause h3, emu-annex emu-annex emu-annex h3 { font-size: 1em; } 369 | emu-intro emu-intro emu-intro h4, emu-clause emu-clause emu-clause h4, emu-annex emu-annex emu-annex h4 { font-size: 0.9em; } 370 | emu-intro emu-intro emu-intro emu-intro h1, emu-clause emu-clause emu-clause emu-clause h1, emu-annex emu-annex emu-annex emu-annex h1 { font-size: 1.11em; } 371 | emu-intro emu-intro emu-intro emu-intro h2, emu-clause emu-clause emu-clause emu-clause h2, emu-annex emu-annex emu-annex emu-annex h2 { font-size: 1em; } 372 | emu-intro emu-intro emu-intro emu-intro h3, emu-clause emu-clause emu-clause emu-clause h3, emu-annex emu-annex emu-annex emu-annex h3 { font-size: 0.9em; } 373 | emu-intro emu-intro emu-intro emu-intro emu-intro h1, emu-clause emu-clause emu-clause emu-clause emu-clause h1, emu-annex emu-annex emu-annex emu-annex emu-annex h1 { font-size: 1em; } 374 | emu-intro emu-intro emu-intro emu-intro emu-intro h2, emu-clause emu-clause emu-clause emu-clause emu-clause h2, emu-annex emu-annex emu-annex emu-annex emu-annex h2 { font-size: 0.9em; } 375 | emu-intro emu-intro emu-intro emu-intro emu-intro emu-intro h1, emu-clause emu-clause emu-clause emu-clause emu-clause emu-clause h1, emu-annex emu-annex emu-annex emu-annex emu-annex emu-annex h1 { font-size: 0.9em } 376 | 377 | emu-clause, emu-intro, emu-annex { 378 | display: block; 379 | } 380 | 381 | /* Figures and tables */ 382 | figure { display: block; margin: 1em 0 3em 0; } 383 | figure object { display: block; margin: 0 auto; } 384 | figure table.real-table { margin: 0 auto; } 385 | figure figcaption { 386 | display: block; 387 | color: #555555; 388 | font-weight: bold; 389 | text-align: center; 390 | } 391 | 392 | emu-table table { 393 | margin: 0 auto; 394 | } 395 | 396 | emu-table table, table.real-table { 397 | border-collapse: collapse; 398 | } 399 | 400 | emu-table td, emu-table th, table.real-table td, table.real-table th { 401 | border: 1px solid black; 402 | padding: 0.4em; 403 | vertical-align: baseline; 404 | } 405 | emu-table th, emu-table thead td, table.real-table th { 406 | background-color: #eeeeee; 407 | } 408 | 409 | /* Note: the left content edges of table.lightweight-table >tbody >tr >td 410 | and div.display line up. */ 411 | table.lightweight-table { 412 | border-collapse: collapse; 413 | margin: 0 0 0 1.5em; 414 | } 415 | table.lightweight-table td, table.lightweight-table th { 416 | border: none; 417 | padding: 0 0.5em; 418 | vertical-align: baseline; 419 | } 420 | 421 | /* diff styles */ 422 | ins { 423 | background-color: #e0f8e0; 424 | text-decoration: none; 425 | border-bottom: 1px solid #396; 426 | } 427 | 428 | del { 429 | background-color: #fee; 430 | } 431 | 432 | ins.block, del.block, 433 | emu-production > ins, emu-production > del, 434 | emu-grammar > ins, emu-grammar > del { 435 | display: block; 436 | } 437 | 438 | tr.ins > td > ins { 439 | border-bottom: none; 440 | } 441 | 442 | tr.ins > td { 443 | background-color: #e0f8e0; 444 | } 445 | 446 | tr.del > td { 447 | background-color: #fee; 448 | } 449 | 450 | /* Menu Styles */ 451 | #menu-toggle { 452 | font-size: 2em; 453 | 454 | position: fixed; 455 | top: 0; 456 | left: 0; 457 | width: 1.5em; 458 | height: 1.5em; 459 | z-index: 3; 460 | visibility: hidden; 461 | color: #1567a2; 462 | background-color: #fff; 463 | 464 | line-height: 1.5em; 465 | text-align: center; 466 | -webkit-touch-callout: none; 467 | -webkit-user-select: none; 468 | -khtml-user-select: none; 469 | -moz-user-select: none; 470 | -ms-user-select: none; 471 | user-select: none;; 472 | 473 | cursor: pointer; 474 | } 475 | 476 | #menu { 477 | display: flex; 478 | flex-direction: column; 479 | width: 33%; height: 100vh; 480 | max-width: 500px; 481 | box-sizing: border-box; 482 | background-color: #ddd; 483 | overflow: hidden; 484 | transition: opacity 0.1s linear; 485 | padding: 0 5px; 486 | position: fixed; 487 | left: 0; top: 0; 488 | border-right: 2px solid #bbb; 489 | 490 | z-index: 2; 491 | } 492 | 493 | #menu-spacer { 494 | flex-basis: 33%; 495 | max-width: 500px; 496 | flex-grow: 0; 497 | flex-shrink: 0; 498 | } 499 | 500 | #menu a { 501 | color: #1567a2; 502 | } 503 | 504 | #menu.active { 505 | display: flex; 506 | opacity: 1; 507 | z-index: 2; 508 | } 509 | 510 | #menu-pins { 511 | flex-grow: 1; 512 | display: none; 513 | } 514 | 515 | #menu-pins.active { 516 | display: block; 517 | } 518 | 519 | #menu-pins-list { 520 | margin: 0; 521 | padding: 0; 522 | counter-reset: pins-counter; 523 | } 524 | 525 | #menu-pins-list > li:before { 526 | content: counter(pins-counter); 527 | counter-increment: pins-counter; 528 | display: inline-block; 529 | width: 25px; 530 | text-align: center; 531 | border: 1px solid #bbb; 532 | padding: 2px; 533 | margin: 4px; 534 | box-sizing: border-box; 535 | line-height: 1em; 536 | background-color: #ccc; 537 | border-radius: 4px; 538 | } 539 | #menu-toc > ol { 540 | padding: 0; 541 | flex-grow: 1; 542 | } 543 | 544 | #menu-toc > ol li { 545 | padding: 0; 546 | } 547 | 548 | #menu-toc > ol , #menu-toc > ol ol { 549 | list-style-type: none; 550 | margin: 0; 551 | padding: 0; 552 | } 553 | 554 | #menu-toc > ol ol { 555 | padding-left: 0.75em; 556 | } 557 | 558 | #menu-toc li { 559 | text-overflow: ellipsis; 560 | overflow: hidden; 561 | white-space: nowrap; 562 | } 563 | 564 | #menu-toc .item-toggle { 565 | display: inline-block; 566 | transform: rotate(-45deg) translate(-5px, -5px); 567 | transition: transform 0.1s ease; 568 | text-align: center; 569 | width: 20px; 570 | 571 | color: #aab; 572 | 573 | -webkit-touch-callout: none; 574 | -webkit-user-select: none; 575 | -khtml-user-select: none; 576 | -moz-user-select: none; 577 | -ms-user-select: none; 578 | user-select: none;; 579 | 580 | cursor: pointer; 581 | } 582 | 583 | #menu-toc .item-toggle-none { 584 | display: inline-block; 585 | width: 20px; 586 | } 587 | 588 | #menu-toc li.active > .item-toggle { 589 | transform: rotate(45deg) translate(-5px, -5px); 590 | } 591 | 592 | #menu-toc li > ol { 593 | display: none; 594 | } 595 | 596 | #menu-toc li.active > ol { 597 | display: block; 598 | } 599 | 600 | #menu-toc li.revealed > a { 601 | background-color: #bbb; 602 | font-weight: bold; 603 | /* 604 | background-color: #222; 605 | color: #c6d8e4; 606 | */ 607 | } 608 | 609 | #menu-toc li.revealed-leaf> a { 610 | color: #206ca7; 611 | /* 612 | background-color: #222; 613 | color: #c6d8e4; 614 | */ 615 | } 616 | 617 | #menu-toc li.revealed > .item-toggle { 618 | transform: rotate(45deg) translate(-5px, -5px); 619 | } 620 | 621 | #menu-toc li.revealed > ol { 622 | display: block; 623 | } 624 | 625 | #menu-toc li > a { 626 | padding: 2px 5px; 627 | } 628 | 629 | #menu > * { 630 | margin-bottom: 5px; 631 | } 632 | 633 | .menu-pane-header { 634 | padding: 0 5px; 635 | text-transform: uppercase; 636 | background-color: #aaa; 637 | color: #335; 638 | font-weight: bold; 639 | letter-spacing: 2px; 640 | flex-grow: 0; 641 | flex-shrink: 0; 642 | font-size: 0.8em; 643 | } 644 | 645 | #menu-toc { 646 | display: flex; 647 | flex-direction: column; 648 | width: 100%; 649 | overflow: hidden; 650 | flex-grow: 1; 651 | } 652 | 653 | #menu-toc ol.toc { 654 | overflow-x: hidden; 655 | overflow-y: auto; 656 | } 657 | 658 | #menu-search { 659 | position: relative; 660 | flex-grow: 0; 661 | flex-shrink: 0; 662 | width: 100%; 663 | 664 | display: flex; 665 | flex-direction: column; 666 | 667 | max-height: 300px; 668 | } 669 | 670 | #menu-trace-list { 671 | display: none; 672 | } 673 | 674 | #menu-search-box { 675 | box-sizing: border-box; 676 | display: block; 677 | width: 100%; 678 | margin: 5px 0 0 0; 679 | font-size: 1em; 680 | padding: 2px; 681 | background-color: #bbb; 682 | border: 1px solid #999; 683 | } 684 | 685 | #menu-search-results { 686 | overflow-x: hidden; 687 | overflow-y: auto; 688 | } 689 | 690 | li.menu-search-result-clause:before { 691 | content: 'clause'; 692 | width: 40px; 693 | display: inline-block; 694 | text-align: right; 695 | padding-right: 1ex; 696 | color: #666; 697 | font-size: 75%; 698 | } 699 | li.menu-search-result-op:before { 700 | content: 'op'; 701 | width: 40px; 702 | display: inline-block; 703 | text-align: right; 704 | padding-right: 1ex; 705 | color: #666; 706 | font-size: 75%; 707 | } 708 | 709 | li.menu-search-result-prod:before { 710 | content: 'prod'; 711 | width: 40px; 712 | display: inline-block; 713 | text-align: right; 714 | padding-right: 1ex; 715 | color: #666; 716 | font-size: 75% 717 | } 718 | 719 | 720 | li.menu-search-result-term:before { 721 | content: 'term'; 722 | width: 40px; 723 | display: inline-block; 724 | text-align: right; 725 | padding-right: 1ex; 726 | color: #666; 727 | font-size: 75% 728 | } 729 | 730 | #menu-search-results ul { 731 | padding: 0 5px; 732 | margin: 0; 733 | } 734 | 735 | #menu-search-results li { 736 | white-space: nowrap; 737 | text-overflow: ellipsis; 738 | } 739 | 740 | 741 | #menu-trace-list { 742 | counter-reset: item; 743 | margin: 0 0 0 20px; 744 | padding: 0; 745 | } 746 | #menu-trace-list li { 747 | display: block; 748 | white-space: nowrap; 749 | } 750 | 751 | #menu-trace-list li .secnum:after { 752 | content: " "; 753 | } 754 | #menu-trace-list li:before { 755 | content: counter(item) " "; 756 | background-color: #222; 757 | counter-increment: item; 758 | color: #999; 759 | width: 20px; 760 | height: 20px; 761 | line-height: 20px; 762 | display: inline-block; 763 | text-align: center; 764 | margin: 2px 4px 2px 0; 765 | } 766 | 767 | @media (max-width: 1000px) { 768 | body { 769 | margin: 0; 770 | display: block; 771 | } 772 | 773 | #menu { 774 | display: none; 775 | padding-top: 3em; 776 | width: 450px; 777 | } 778 | 779 | #menu.active { 780 | position: fixed; 781 | height: 100%; 782 | left: 0; 783 | top: 0; 784 | right: 300px; 785 | } 786 | 787 | #menu-toggle { 788 | visibility: visible; 789 | } 790 | 791 | #spec-container { 792 | padding: 0 5px; 793 | } 794 | 795 | #references-pane-spacer { 796 | display: none; 797 | } 798 | } 799 | 800 | @media only screen and (max-width: 800px) { 801 | #menu { 802 | width: 100%; 803 | } 804 | 805 | h1 .secnum:empty { 806 | margin: 0; padding: 0; 807 | } 808 | } 809 | 810 | 811 | /* Toolbox */ 812 | .toolbox { 813 | position: absolute; 814 | background: #ddd; 815 | border: 1px solid #aaa; 816 | display: none; 817 | color: #eee; 818 | padding: 5px; 819 | border-radius: 3px; 820 | } 821 | 822 | .toolbox.active { 823 | display: inline-block; 824 | } 825 | 826 | .toolbox a { 827 | text-decoration: none; 828 | padding: 0 5px; 829 | } 830 | 831 | .toolbox a:hover { 832 | text-decoration: underline; 833 | } 834 | 835 | .toolbox:after, .toolbox:before { 836 | top: 100%; 837 | left: 15px; 838 | border: solid transparent; 839 | content: " "; 840 | height: 0; 841 | width: 0; 842 | position: absolute; 843 | pointer-events: none; 844 | } 845 | 846 | .toolbox:after { 847 | border-color: rgba(0, 0, 0, 0); 848 | border-top-color: #ddd; 849 | border-width: 10px; 850 | margin-left: -10px; 851 | } 852 | .toolbox:before { 853 | border-color: rgba(204, 204, 204, 0); 854 | border-top-color: #aaa; 855 | border-width: 12px; 856 | margin-left: -12px; 857 | } 858 | 859 | #references-pane-container { 860 | position: fixed; 861 | bottom: 0; 862 | left: 0; 863 | right: 0; 864 | height: 250px; 865 | display: none; 866 | background-color: #ddd; 867 | z-index: 1; 868 | } 869 | 870 | #references-pane-table-container { 871 | overflow-x: hidden; 872 | overflow-y: auto; 873 | } 874 | 875 | #references-pane-spacer { 876 | flex-basis: 33%; 877 | max-width: 500px; 878 | } 879 | 880 | #references-pane { 881 | flex-grow: 1; 882 | overflow: hidden; 883 | display: flex; 884 | flex-direction: column; 885 | } 886 | 887 | #references-pane-container.active { 888 | display: flex; 889 | } 890 | 891 | #references-pane-close:after { 892 | content: '✖'; 893 | float: right; 894 | cursor: pointer; 895 | } 896 | 897 | #references-pane table tr td:first-child { 898 | text-align: right; 899 | padding-right: 5px; 900 | } 901 | -------------------------------------------------------------------------------- /docs/ecmarkup.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function Search(menu) { 4 | this.menu = menu; 5 | this.$search = document.getElementById('menu-search'); 6 | this.$searchBox = document.getElementById('menu-search-box'); 7 | this.$searchResults = document.getElementById('menu-search-results'); 8 | 9 | this.loadBiblio(); 10 | 11 | document.addEventListener('keydown', this.documentKeydown.bind(this)); 12 | 13 | this.$searchBox.addEventListener('keydown', debounce(this.searchBoxKeydown.bind(this), { stopPropagation: true })); 14 | this.$searchBox.addEventListener('keyup', debounce(this.searchBoxKeyup.bind(this), { stopPropagation: true })); 15 | } 16 | 17 | Search.prototype.loadBiblio = function () { 18 | var $biblio = document.getElementById('menu-search-biblio'); 19 | if (!$biblio) { 20 | this.biblio = []; 21 | } else { 22 | this.biblio = JSON.parse($biblio.textContent); 23 | this.biblio.clauses = this.biblio.filter(function (e) { return e.type === 'clause' }); 24 | this.biblio.byId = this.biblio.reduce(function (map, entry) { 25 | map[entry.id] = entry; 26 | return map; 27 | }, {}); 28 | } 29 | } 30 | 31 | Search.prototype.documentKeydown = function (e) { 32 | if (e.keyCode === 191) { 33 | e.preventDefault(); 34 | e.stopPropagation(); 35 | this.triggerSearch(); 36 | } 37 | } 38 | 39 | Search.prototype.searchBoxKeydown = function (e) { 40 | e.stopPropagation(); 41 | e.preventDefault(); 42 | if (e.keyCode === 191 && e.target.value.length === 0) { 43 | e.preventDefault(); 44 | } else if (e.keyCode === 13) { 45 | e.preventDefault(); 46 | this.selectResult(); 47 | } 48 | } 49 | 50 | Search.prototype.searchBoxKeyup = function (e) { 51 | if (e.keyCode === 13 || e.keyCode === 9) { 52 | return; 53 | } 54 | 55 | this.search(e.target.value); 56 | } 57 | 58 | 59 | Search.prototype.triggerSearch = function (e) { 60 | if (this.menu.isVisible()) { 61 | this._closeAfterSearch = false; 62 | } else { 63 | this._closeAfterSearch = true; 64 | this.menu.show(); 65 | } 66 | 67 | this.$searchBox.focus(); 68 | this.$searchBox.select(); 69 | } 70 | // bit 12 - Set if the result starts with searchString 71 | // bits 8-11: 8 - number of chunks multiplied by 2 if cases match, otherwise 1. 72 | // bits 1-7: 127 - length of the entry 73 | // General scheme: prefer case sensitive matches with fewer chunks, and otherwise 74 | // prefer shorter matches. 75 | function relevance(result, searchString) { 76 | var relevance = 0; 77 | 78 | relevance = Math.max(0, 8 - result.match.chunks) << 7; 79 | 80 | if (result.match.caseMatch) { 81 | relevance *= 2; 82 | } 83 | 84 | if (result.match.prefix) { 85 | relevance += 2048 86 | } 87 | 88 | relevance += Math.max(0, 255 - result.entry.key.length); 89 | 90 | return relevance; 91 | } 92 | 93 | Search.prototype.search = function (searchString) { 94 | var s = Date.now(); 95 | 96 | if (searchString === '') { 97 | this.displayResults([]); 98 | this.hideSearch(); 99 | return; 100 | } else { 101 | this.showSearch(); 102 | } 103 | 104 | if (searchString.length === 1) { 105 | this.displayResults([]); 106 | return; 107 | } 108 | 109 | var results; 110 | 111 | if (/^[\d\.]*$/.test(searchString)) { 112 | results = this.biblio.clauses.filter(function (clause) { 113 | return clause.number.substring(0, searchString.length) === searchString; 114 | }).map(function (clause) { 115 | return { entry: clause }; 116 | }); 117 | } else { 118 | results = []; 119 | 120 | for (var i = 0; i < this.biblio.length; i++) { 121 | var entry = this.biblio[i]; 122 | 123 | var match = fuzzysearch(searchString, entry.key); 124 | if (match) { 125 | results.push({ entry: entry, match: match }); 126 | } 127 | } 128 | 129 | results.forEach(function (result) { 130 | result.relevance = relevance(result, searchString); 131 | }); 132 | 133 | results = results.sort(function (a, b) { return b.relevance - a.relevance }); 134 | 135 | } 136 | 137 | if (results.length > 50) { 138 | results = results.slice(0, 50); 139 | } 140 | 141 | this.displayResults(results); 142 | } 143 | Search.prototype.hideSearch = function () { 144 | this.$search.classList.remove('active'); 145 | } 146 | 147 | Search.prototype.showSearch = function () { 148 | this.$search.classList.add('active'); 149 | } 150 | 151 | Search.prototype.selectResult = function () { 152 | var $first = this.$searchResults.querySelector('li:first-child a'); 153 | 154 | if ($first) { 155 | document.location = $first.getAttribute('href'); 156 | } 157 | 158 | this.$searchBox.value = ''; 159 | this.$searchBox.blur(); 160 | this.displayResults([]); 161 | this.hideSearch(); 162 | 163 | if (this._closeAfterSearch) { 164 | this.menu.hide(); 165 | } 166 | } 167 | 168 | Search.prototype.displayResults = function (results) { 169 | if (results.length > 0) { 170 | this.$searchResults.classList.remove('no-results'); 171 | 172 | var html = '
    '; 173 | 174 | results.forEach(function (result) { 175 | var entry = result.entry; 176 | var id = entry.id; 177 | var cssClass = ''; 178 | var text = ''; 179 | 180 | if (entry.type === 'clause') { 181 | var number = entry.number ? entry.number + ' ' : ''; 182 | text = number + entry.key; 183 | cssClass = 'clause'; 184 | id = entry.id; 185 | } else if (entry.type === 'production') { 186 | text = entry.key; 187 | cssClass = 'prod'; 188 | id = entry.id; 189 | } else if (entry.type === 'op') { 190 | text = entry.key; 191 | cssClass = 'op'; 192 | id = entry.id || entry.refId; 193 | } else if (entry.type === 'term') { 194 | text = entry.key; 195 | cssClass = 'term'; 196 | id = entry.id || entry.refId; 197 | } 198 | 199 | if (text) { 200 | html += '' 201 | } 202 | }); 203 | 204 | html += '
' 205 | 206 | this.$searchResults.innerHTML = html; 207 | } else { 208 | this.$searchResults.innerHTML = ''; 209 | this.$searchResults.classList.add('no-results'); 210 | } 211 | } 212 | 213 | 214 | function Menu() { 215 | this.$toggle = document.getElementById('menu-toggle'); 216 | this.$menu = document.getElementById('menu'); 217 | this.$toc = document.querySelector('menu-toc > ol'); 218 | this.$pins = document.querySelector('#menu-pins'); 219 | this.$pinList = document.getElementById('menu-pins-list'); 220 | this.$toc = document.querySelector('#menu-toc > ol'); 221 | this.$specContainer = document.getElementById('spec-container'); 222 | this.search = new Search(this); 223 | 224 | this._pinnedIds = {}; 225 | this.loadPinEntries(); 226 | 227 | // toggle menu 228 | this.$toggle.addEventListener('click', this.toggle.bind(this)); 229 | 230 | // keydown events for pinned clauses 231 | document.addEventListener('keydown', this.documentKeydown.bind(this)); 232 | 233 | // toc expansion 234 | var tocItems = this.$menu.querySelectorAll('#menu-toc li'); 235 | for (var i = 0; i < tocItems.length; i++) { 236 | var $item = tocItems[i]; 237 | $item.addEventListener('click', function($item, event) { 238 | $item.classList.toggle('active'); 239 | event.stopPropagation(); 240 | }.bind(null, $item)); 241 | } 242 | 243 | // close toc on toc item selection 244 | var tocLinks = this.$menu.querySelectorAll('#menu-toc li > a'); 245 | for (var i = 0; i < tocLinks.length; i++) { 246 | var $link = tocLinks[i]; 247 | $link.addEventListener('click', function(event) { 248 | this.toggle(); 249 | event.stopPropagation(); 250 | }.bind(this)); 251 | } 252 | 253 | // update active clause on scroll 254 | window.addEventListener('scroll', debounce(this.updateActiveClause.bind(this))); 255 | this.updateActiveClause(); 256 | 257 | // prevent menu scrolling from scrolling the body 258 | this.$toc.addEventListener('wheel', function (e) { 259 | var target = e.currentTarget; 260 | var offTop = e.deltaY < 0 && target.scrollTop === 0; 261 | if (offTop) { 262 | e.preventDefault(); 263 | } 264 | var offBottom = e.deltaY > 0 265 | && target.offsetHeight + target.scrollTop >= target.scrollHeight; 266 | 267 | if (offBottom) { 268 | e.preventDefault(); 269 | } 270 | }) 271 | } 272 | 273 | Menu.prototype.documentKeydown = function (e) { 274 | e.stopPropagation(); 275 | if (e.keyCode === 80) { 276 | this.togglePinEntry(); 277 | } else if (e.keyCode > 48 && e.keyCode < 58) { 278 | this.selectPin(e.keyCode - 49); 279 | } 280 | } 281 | 282 | Menu.prototype.updateActiveClause = function () { 283 | this.setActiveClause(findActiveClause(this.$specContainer)) 284 | } 285 | 286 | Menu.prototype.setActiveClause = function (clause) { 287 | this.$activeClause = clause; 288 | this.revealInToc(this.$activeClause); 289 | } 290 | 291 | Menu.prototype.revealInToc = function (path) { 292 | var current = this.$toc.querySelectorAll('li.revealed'); 293 | for (var i = 0; i < current.length; i++) { 294 | current[i].classList.remove('revealed'); 295 | current[i].classList.remove('revealed-leaf'); 296 | } 297 | 298 | var current = this.$toc; 299 | var index = 0; 300 | while (index < path.length) { 301 | var children = current.children; 302 | for (var i = 0; i < children.length; i++) { 303 | if ('#' + path[index].id === children[i].children[1].getAttribute('href') ) { 304 | children[i].classList.add('revealed'); 305 | if (index === path.length - 1) { 306 | children[i].classList.add('revealed-leaf'); 307 | var rect = children[i].getBoundingClientRect(); 308 | this.$toc.getBoundingClientRect().top 309 | var tocRect = this.$toc.getBoundingClientRect(); 310 | if (rect.top + 10 > tocRect.bottom) { 311 | this.$toc.scrollTop = this.$toc.scrollTop + (rect.top - tocRect.bottom) + (rect.bottom - rect.top); 312 | } else if (rect.top < tocRect.top) { 313 | this.$toc.scrollTop = this.$toc.scrollTop - (tocRect.top - rect.top); 314 | } 315 | } 316 | current = children[i].querySelector('ol'); 317 | index++; 318 | break; 319 | } 320 | } 321 | 322 | } 323 | } 324 | 325 | function findActiveClause(root, path) { 326 | var clauses = new ClauseWalker(root); 327 | var $clause; 328 | var found = false; 329 | var path = path || []; 330 | 331 | while ($clause = clauses.nextNode()) { 332 | var rect = $clause.getBoundingClientRect(); 333 | var $header = $clause.children[0]; 334 | var marginTop = parseInt(getComputedStyle($header)["margin-top"]); 335 | 336 | if ((rect.top - marginTop) <= 0 && rect.bottom > 0) { 337 | found = true; 338 | return findActiveClause($clause, path.concat($clause)) || path; 339 | } 340 | } 341 | 342 | return path; 343 | } 344 | 345 | function ClauseWalker(root) { 346 | var previous; 347 | var treeWalker = document.createTreeWalker( 348 | root, 349 | NodeFilter.SHOW_ELEMENT, 350 | { 351 | acceptNode: function (node) { 352 | if (previous === node.parentNode) { 353 | return NodeFilter.FILTER_REJECT; 354 | } else { 355 | previous = node; 356 | } 357 | if (node.nodeName === 'EMU-CLAUSE' || node.nodeName === 'EMU-INTRO' || node.nodeName === 'EMU-ANNEX') { 358 | return NodeFilter.FILTER_ACCEPT; 359 | } else { 360 | return NodeFilter.FILTER_SKIP; 361 | } 362 | } 363 | }, 364 | false 365 | ); 366 | 367 | return treeWalker; 368 | } 369 | 370 | Menu.prototype.toggle = function () { 371 | this.$menu.classList.toggle('active'); 372 | } 373 | 374 | Menu.prototype.show = function () { 375 | this.$menu.classList.add('active'); 376 | } 377 | 378 | Menu.prototype.hide = function () { 379 | this.$menu.classList.remove('active'); 380 | } 381 | 382 | Menu.prototype.isVisible = function() { 383 | return this.$menu.classList.contains('active'); 384 | } 385 | 386 | Menu.prototype.showPins = function () { 387 | this.$pins.classList.add('active'); 388 | } 389 | 390 | Menu.prototype.hidePins = function () { 391 | this.$pins.classList.remove('active'); 392 | } 393 | 394 | Menu.prototype.addPinEntry = function (id) { 395 | var entry = this.search.biblio.byId[id]; 396 | if (!entry) { 397 | // id was deleted after pin (or something) so remove it 398 | delete this._pinnedIds[id]; 399 | this.persistPinEntries(); 400 | return; 401 | } 402 | 403 | if (entry.type === 'clause') { 404 | var prefix; 405 | if (entry.number) { 406 | prefix = entry.number + ' '; 407 | } else { 408 | prefix = ''; 409 | } 410 | this.$pinList.innerHTML += '
  • ' + prefix + entry.titleHTML + '
  • '; 411 | } else { 412 | this.$pinList.innerHTML += '
  • ' + entry.key + '
  • '; 413 | } 414 | 415 | if (Object.keys(this._pinnedIds).length === 0) { 416 | this.showPins(); 417 | } 418 | this._pinnedIds[id] = true; 419 | this.persistPinEntries(); 420 | } 421 | 422 | Menu.prototype.removePinEntry = function (id) { 423 | var item = this.$pinList.querySelector('a[href="#' + id + '"]').parentNode; 424 | this.$pinList.removeChild(item); 425 | delete this._pinnedIds[id]; 426 | if (Object.keys(this._pinnedIds).length === 0) { 427 | this.hidePins(); 428 | } 429 | 430 | this.persistPinEntries(); 431 | } 432 | 433 | Menu.prototype.persistPinEntries = function () { 434 | try { 435 | if (!window.localStorage) return; 436 | } catch (e) { 437 | return; 438 | } 439 | 440 | localStorage.pinEntries = JSON.stringify(Object.keys(this._pinnedIds)); 441 | } 442 | 443 | Menu.prototype.loadPinEntries = function () { 444 | try { 445 | if (!window.localStorage) return; 446 | } catch (e) { 447 | return; 448 | } 449 | 450 | var pinsString = window.localStorage.pinEntries; 451 | if (!pinsString) return; 452 | var pins = JSON.parse(pinsString); 453 | for(var i = 0; i < pins.length; i++) { 454 | this.addPinEntry(pins[i]); 455 | } 456 | } 457 | 458 | Menu.prototype.togglePinEntry = function (id) { 459 | if (!id) { 460 | id = this.$activeClause[this.$activeClause.length - 1].id; 461 | } 462 | 463 | if (this._pinnedIds[id]) { 464 | this.removePinEntry(id); 465 | } else { 466 | this.addPinEntry(id); 467 | } 468 | } 469 | 470 | Menu.prototype.selectPin = function (num) { 471 | document.location = this.$pinList.children[num].children[0].href; 472 | } 473 | 474 | var menu; 475 | function init() { 476 | menu = new Menu(); 477 | var $container = document.getElementById('spec-container'); 478 | $container.addEventListener('mouseover', debounce(function (e) { 479 | Toolbox.activateIfMouseOver(e); 480 | })); 481 | } 482 | 483 | document.addEventListener('DOMContentLoaded', init); 484 | 485 | function debounce(fn, opts) { 486 | opts = opts || {}; 487 | var timeout; 488 | return function(e) { 489 | if (opts.stopPropagation) { 490 | e.stopPropagation(); 491 | } 492 | var args = arguments; 493 | if (timeout) { 494 | clearTimeout(timeout); 495 | } 496 | timeout = setTimeout(function() { 497 | timeout = null; 498 | fn.apply(this, args); 499 | }.bind(this), 150); 500 | } 501 | } 502 | 503 | var CLAUSE_NODES = ['EMU-CLAUSE', 'EMU-INTRO', 'EMU-ANNEX']; 504 | function findLocalReferences ($elem) { 505 | var name = $elem.innerHTML; 506 | var references = []; 507 | 508 | var parentClause = $elem.parentNode; 509 | while (parentClause && CLAUSE_NODES.indexOf(parentClause.nodeName) === -1) { 510 | parentClause = parentClause.parentNode; 511 | } 512 | 513 | if(!parentClause) return; 514 | 515 | var vars = parentClause.querySelectorAll('var'); 516 | 517 | for (var i = 0; i < vars.length; i++) { 518 | var $var = vars[i]; 519 | 520 | if ($var.innerHTML === name) { 521 | references.push($var); 522 | } 523 | } 524 | 525 | return references; 526 | } 527 | 528 | function toggleFindLocalReferences($elem) { 529 | var references = findLocalReferences($elem); 530 | if ($elem.classList.contains('referenced')) { 531 | references.forEach(function ($reference) { 532 | $reference.classList.remove('referenced'); 533 | }); 534 | } else { 535 | references.forEach(function ($reference) { 536 | $reference.classList.add('referenced'); 537 | }); 538 | } 539 | } 540 | 541 | function installFindLocalReferences () { 542 | document.addEventListener('click', function (e) { 543 | if (e.target.nodeName === 'VAR') { 544 | toggleFindLocalReferences(e.target); 545 | } 546 | }); 547 | } 548 | 549 | document.addEventListener('DOMContentLoaded', installFindLocalReferences); 550 | 551 | 552 | 553 | 554 | // The following license applies to the fuzzysearch function 555 | // The MIT License (MIT) 556 | // Copyright © 2015 Nicolas Bevacqua 557 | // Copyright © 2016 Brian Terlson 558 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 559 | // this software and associated documentation files (the "Software"), to deal in 560 | // the Software without restriction, including without limitation the rights to 561 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 562 | // the Software, and to permit persons to whom the Software is furnished to do so, 563 | // subject to the following conditions: 564 | 565 | // The above copyright notice and this permission notice shall be included in all 566 | // copies or substantial portions of the Software. 567 | 568 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 569 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 570 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 571 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 572 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 573 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 574 | function fuzzysearch (searchString, haystack, caseInsensitive) { 575 | var tlen = haystack.length; 576 | var qlen = searchString.length; 577 | var chunks = 1; 578 | var finding = false; 579 | var prefix = true; 580 | 581 | if (qlen > tlen) { 582 | return false; 583 | } 584 | 585 | if (qlen === tlen) { 586 | if (searchString === haystack) { 587 | return { caseMatch: true, chunks: 1, prefix: true }; 588 | } else if (searchString.toLowerCase() === haystack.toLowerCase()) { 589 | return { caseMatch: false, chunks: 1, prefix: true }; 590 | } else { 591 | return false; 592 | } 593 | } 594 | 595 | outer: for (var i = 0, j = 0; i < qlen; i++) { 596 | var nch = searchString[i]; 597 | while (j < tlen) { 598 | var targetChar = haystack[j++]; 599 | if (targetChar === nch) { 600 | finding = true; 601 | continue outer; 602 | } 603 | if (finding) { 604 | chunks++; 605 | finding = false; 606 | } 607 | } 608 | 609 | if (caseInsensitive) { return false } 610 | 611 | return fuzzysearch(searchString.toLowerCase(), haystack.toLowerCase(), true); 612 | } 613 | 614 | return { caseMatch: !caseInsensitive, chunks: chunks, prefix: j <= qlen }; 615 | } 616 | 617 | var Toolbox = { 618 | init: function () { 619 | this.$container = document.createElement('div'); 620 | this.$container.classList.add('toolbox'); 621 | this.$permalink = document.createElement('a'); 622 | this.$permalink.textContent = 'Permalink'; 623 | this.$pinLink = document.createElement('a'); 624 | this.$pinLink.textContent = 'Pin'; 625 | this.$pinLink.setAttribute('href', '#'); 626 | this.$pinLink.addEventListener('click', function (e) { 627 | e.preventDefault(); 628 | e.stopPropagation(); 629 | menu.togglePinEntry(this.entry.id); 630 | }.bind(this)); 631 | 632 | this.$refsLink = document.createElement('a'); 633 | this.$refsLink.setAttribute('href', '#'); 634 | this.$refsLink.addEventListener('click', function (e) { 635 | e.preventDefault(); 636 | e.stopPropagation(); 637 | referencePane.showReferencesFor(this.entry); 638 | }.bind(this)); 639 | this.$container.appendChild(this.$permalink); 640 | this.$container.appendChild(this.$pinLink); 641 | this.$container.appendChild(this.$refsLink); 642 | document.body.appendChild(this.$container); 643 | }, 644 | 645 | activate: function (el, entry, target) { 646 | if (el === this._activeEl) return; 647 | this.active = true; 648 | this.entry = entry; 649 | this.$container.classList.add('active'); 650 | this.top = el.offsetTop - this.$container.offsetHeight - 10; 651 | this.left = el.offsetLeft; 652 | this.$container.setAttribute('style', 'left: ' + this.left + 'px; top: ' + this.top + 'px'); 653 | this.updatePermalink(); 654 | this.updateReferences(); 655 | this._activeEl = el; 656 | if (this.top < document.body.scrollTop && el === target) { 657 | // don't scroll unless it's a small thing (< 200px) 658 | this.$container.scrollIntoView(); 659 | } 660 | }, 661 | 662 | updatePermalink: function () { 663 | this.$permalink.setAttribute('href', '#' + this.entry.id); 664 | }, 665 | 666 | updateReferences: function () { 667 | this.$refsLink.textContent = 'References (' + this.entry.referencingIds.length + ')'; 668 | }, 669 | 670 | activateIfMouseOver: function (e) { 671 | var ref = this.findReferenceUnder(e.target); 672 | if (ref && (!this.active || e.pageY > this._activeEl.offsetTop)) { 673 | var entry = menu.search.biblio.byId[ref.id]; 674 | this.activate(ref.element, entry, e.target); 675 | } else if (this.active && ((e.pageY < this.top) || e.pageY > (this._activeEl.offsetTop + this._activeEl.offsetHeight))) { 676 | this.deactivate(); 677 | } 678 | }, 679 | 680 | findReferenceUnder: function (el) { 681 | while (el) { 682 | var parent = el.parentNode; 683 | if (el.nodeName === 'H1' && parent.nodeName.match(/EMU-CLAUSE|EMU-ANNEX|EMU-INTRO/) && parent.id) { 684 | return { element: el, id: parent.id }; 685 | } else if (el.nodeName.match(/EMU-(?!CLAUSE|XREF|ANNEX|INTRO)|DFN/) && 686 | el.id && el.id[0] !== '_') { 687 | if (el.nodeName === 'EMU-FIGURE' || el.nodeName === 'EMU-TABLE' || el.nodeName === 'EMU-EXAMPLE') { 688 | // return the figcaption element 689 | return { element: el.children[0].children[0], id: el.id }; 690 | } else if (el.nodeName === 'EMU-PRODUCTION') { 691 | // return the LHS non-terminal element 692 | return { element: el.children[0], id: el.id }; 693 | } else { 694 | return { element: el, id: el.id }; 695 | } 696 | } 697 | el = parent; 698 | } 699 | }, 700 | 701 | deactivate: function () { 702 | this.$container.classList.remove('active'); 703 | this._activeEl = null; 704 | this.activeElBounds = null; 705 | this.active = false; 706 | } 707 | } 708 | 709 | var referencePane = { 710 | init: function() { 711 | this.$container = document.createElement('div'); 712 | this.$container.setAttribute('id', 'references-pane-container'); 713 | 714 | var $spacer = document.createElement('div'); 715 | $spacer.setAttribute('id', 'references-pane-spacer'); 716 | 717 | this.$pane = document.createElement('div'); 718 | this.$pane.setAttribute('id', 'references-pane'); 719 | 720 | this.$container.appendChild($spacer); 721 | this.$container.appendChild(this.$pane); 722 | 723 | this.$header = document.createElement('div'); 724 | this.$header.classList.add('menu-pane-header'); 725 | this.$header.textContent = 'References to '; 726 | this.$headerRefId = document.createElement('a'); 727 | this.$header.appendChild(this.$headerRefId); 728 | this.$closeButton = document.createElement('span'); 729 | this.$closeButton.setAttribute('id', 'references-pane-close'); 730 | this.$closeButton.addEventListener('click', function (e) { 731 | this.deactivate(); 732 | }.bind(this)); 733 | this.$header.appendChild(this.$closeButton); 734 | 735 | this.$pane.appendChild(this.$header); 736 | var tableContainer = document.createElement('div'); 737 | tableContainer.setAttribute('id', 'references-pane-table-container'); 738 | 739 | this.$table = document.createElement('table'); 740 | this.$table.setAttribute('id', 'references-pane-table'); 741 | 742 | this.$tableBody = this.$table.createTBody(); 743 | 744 | tableContainer.appendChild(this.$table); 745 | this.$pane.appendChild(tableContainer); 746 | 747 | menu.$specContainer.appendChild(this.$container); 748 | }, 749 | 750 | activate: function () { 751 | this.$container.classList.add('active'); 752 | }, 753 | 754 | deactivate: function () { 755 | this.$container.classList.remove('active'); 756 | }, 757 | 758 | showReferencesFor(entry) { 759 | this.activate(); 760 | var newBody = document.createElement('tbody'); 761 | var previousId; 762 | var previousCell; 763 | var dupCount = 0; 764 | this.$headerRefId.textContent = '#' + entry.id; 765 | this.$headerRefId.setAttribute('href', '#' + entry.id); 766 | entry.referencingIds.map(function (id) { 767 | var target = document.getElementById(id); 768 | var cid = findParentClauseId(target); 769 | var clause = menu.search.biblio.byId[cid]; 770 | var dupCount = 0; 771 | return { id: id, clause: clause } 772 | }).sort(function (a, b) { 773 | return sortByClauseNumber(a.clause, b.clause); 774 | }).forEach(function (record, i) { 775 | if (previousId === record.clause.id) { 776 | previousCell.innerHTML += ' (' + (dupCount + 2) + ')'; 777 | dupCount++; 778 | } else { 779 | var row = newBody.insertRow(); 780 | var cell = row.insertCell(); 781 | cell.innerHTML = record.clause.number; 782 | cell = row.insertCell(); 783 | cell.innerHTML = '' + record.clause.titleHTML + ''; 784 | previousCell = cell; 785 | previousId = record.clause.id; 786 | dupCount = 0; 787 | } 788 | }, this); 789 | this.$table.removeChild(this.$tableBody); 790 | this.$tableBody = newBody; 791 | this.$table.appendChild(this.$tableBody); 792 | } 793 | } 794 | function findParentClauseId(node) { 795 | while (node && node.nodeName !== 'EMU-CLAUSE' && node.nodeName !== 'EMU-INTRO' && node.nodeName !== 'EMU-ANNEX') { 796 | node = node.parentNode; 797 | } 798 | if (!node) return null; 799 | return node.getAttribute('id'); 800 | } 801 | 802 | function sortByClauseNumber(c1, c2) { 803 | var c1c = c1.number.split('.'); 804 | var c2c = c2.number.split('.'); 805 | 806 | for (var i = 0; i < c1c.length; i++) { 807 | if (i >= c2c.length) { 808 | return 1; 809 | } 810 | 811 | var c1 = c1c[i]; 812 | var c2 = c2c[i]; 813 | var c1cn = Number(c1); 814 | var c2cn = Number(c2); 815 | 816 | if (Number.isNaN(c1cn) && Number.isNaN(c2cn)) { 817 | if (c1 > c2) { 818 | return 1; 819 | } else if (c1 < c2) { 820 | return -1; 821 | } 822 | } else if (!Number.isNaN(c1cn) && Number.isNaN(c2cn)) { 823 | return -1; 824 | } else if (Number.isNaN(c1cn) && !Number.isNaN(c2cn)) { 825 | return 1; 826 | } else if(c1cn > c2cn) { 827 | return 1; 828 | } else if (c1cn < c2cn) { 829 | return -1; 830 | } 831 | } 832 | 833 | if (c1c.length === c2c.length) { 834 | return 0; 835 | } 836 | return -1; 837 | } 838 | 839 | document.addEventListener('DOMContentLoaded', function () { 840 | Toolbox.init(); 841 | referencePane.init(); 842 | }) 843 | var CLAUSE_NODES = ['EMU-CLAUSE', 'EMU-INTRO', 'EMU-ANNEX']; 844 | function findLocalReferences ($elem) { 845 | var name = $elem.innerHTML; 846 | var references = []; 847 | 848 | var parentClause = $elem.parentNode; 849 | while (parentClause && CLAUSE_NODES.indexOf(parentClause.nodeName) === -1) { 850 | parentClause = parentClause.parentNode; 851 | } 852 | 853 | if(!parentClause) return; 854 | 855 | var vars = parentClause.querySelectorAll('var'); 856 | 857 | for (var i = 0; i < vars.length; i++) { 858 | var $var = vars[i]; 859 | 860 | if ($var.innerHTML === name) { 861 | references.push($var); 862 | } 863 | } 864 | 865 | return references; 866 | } 867 | 868 | function toggleFindLocalReferences($elem) { 869 | var references = findLocalReferences($elem); 870 | if ($elem.classList.contains('referenced')) { 871 | references.forEach(function ($reference) { 872 | $reference.classList.remove('referenced'); 873 | }); 874 | } else { 875 | references.forEach(function ($reference) { 876 | $reference.classList.add('referenced'); 877 | }); 878 | } 879 | } 880 | 881 | function installFindLocalReferences () { 882 | document.addEventListener('click', function (e) { 883 | if (e.target.nodeName === 'VAR') { 884 | toggleFindLocalReferences(e.target); 885 | } 886 | }); 887 | } 888 | 889 | document.addEventListener('DOMContentLoaded', installFindLocalReferences); 890 | --------------------------------------------------------------------------------