├── .npmrc ├── .gitattributes ├── .github └── workflows │ └── build.yml ├── package.json ├── .gitignore ├── LICENSE ├── spec.html ├── README.md └── docs ├── ecmarkup.css ├── ecmarkup.js └── index.html /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | docs/index.html -diff merge=ours 2 | docs/spec.js -diff merge=ours 3 | docs/spec.css -diff merge=ours 4 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Deploy spec 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-node@v1 12 | with: 13 | node-version: '12.x' 14 | - run: npm install 15 | - run: npm run build 16 | - name: commit changes 17 | uses: elstudio/actions-js-build/commit@v3 18 | with: 19 | commitMessage: "fixup: [spec] `npm run build`" 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "proposal-item-method", 4 | "description": "Proposal for a .item() method on all the built-in indexables", 5 | "scripts": { 6 | "start": "npm run build -- --watch", 7 | "build": "ecmarkup spec.html docs/index.html" 8 | }, 9 | "homepage": "https://github.com/tc39/template-for-proposals#readme", 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/tc39/template-for-proposals.git" 13 | }, 14 | "license": "MIT", 15 | "devDependencies": { 16 | "ecmarkup": "^3.25.3" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.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 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # Only apps should have lockfiles 40 | yarn.lock 41 | package-lock.json 42 | npm-shrinkwrap.json 43 | pnpm-lock.yaml 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tab Atkins Jr. 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 | -------------------------------------------------------------------------------- /spec.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Relative Indexing Method 6 |
  7 |   title: Relative Indexing Method
  8 |   status: proposal
  9 |   stage: 4
 10 |   location: https://github.com/tabatkins/proposal-relative-indexing-method
 11 |   copyright: false
 12 |   contributors: Tab Atkins, Shu-yu Guo
 13 | 
14 | 15 |

Relative Indexing Method

16 |

We provide a new method, `at()`, that is on the prototype of the built-in indexable objects: Array, String, and TypedArrays objects. The method supports relative indexing from the end when passed a negative index.

17 |
18 | 19 | 20 |

Additions to Properties of the Array Prototype Object

21 | 22 | 23 |

Array.prototype.at ( _index_ )

24 | 25 | 1. Let _O_ be ? ToObject(*this* value). 26 | 1. Let _len_ be ? LengthOfArrayLike(_O_). 27 | 1. Let _relativeIndex_ be ? ToInteger(_index_). 28 | 1. If _relativeIndex_ ≥ 0, then 29 | 1. Let _k_ be _relativeIndex_. 30 | 1. Else, 31 | 1. Let _k_ be _len_ + _relativeIndex_. 32 | 1. If _k_ < 0 or _k_ ≥ _len_, then return *undefined*. 33 | 1. Return ? Get(_O_, ! ToString(_k_)). 34 | 35 |
36 |
37 | 38 | 39 |

Modifications to Properties of the Array Prototype Object

40 | 41 | 42 |

Array.prototype [ @@unscopables ]

43 |

The initial value of the @@unscopables data property is an object created by the following steps:

44 | 45 | 1. Let _unscopableList_ be ! OrdinaryObjectCreate(*null*). 46 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"at"*, *true*). 47 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"copyWithin"*, *true*). 48 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"entries"*, *true*). 49 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"fill"*, *true*). 50 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"find"*, *true*). 51 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"findIndex"*, *true*). 52 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"flat"*, *true*). 53 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"flatMap"*, *true*). 54 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"includes"*, *true*). 55 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"keys"*, *true*). 56 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"values"*, *true*). 57 | 1. Return _unscopableList_. 58 | 59 |
60 |
61 | 62 | 63 |

Additions to Properties of the String Prototype Object

64 | 65 | 66 |

String.prototype.at ( _index_ )

67 | 68 | 1. Let _O_ be ? RequireObjectCoercible(*this* value). 69 | 1. Let _S_ be ? ToString(_O_). 70 | 1. Let _len_ be the length of _S_. 71 | 1. Let _relativeIndex_ be ? ToInteger(_index_). 72 | 1. If _relativeIndex_ ≥ 0, then 73 | 1. Let _k_ be _relativeIndex_. 74 | 1. Else, 75 | 1. Let _k_ be _len_ + _relativeIndex_. 76 | 1. If _k_ < 0 or _k_ ≥ _len_, then return *undefined*. 77 | 1. Return the String value consisting of only the code unit at position _k_ in _S_. 78 | 79 |
80 |
81 | 82 | 83 |

Additions to Properties of the %TypedArray.prototype% Object

84 | 85 | 86 |

%TypedArray%.prototype.at ( _index_ )

87 | 88 | 1. Let _O_ be the *this* value. 89 | 1. Perform ? ValidateTypedArray(_O_). 90 | 1. Let _len_ be _O_.[[ArrayLength]]. 91 | 1. Let _relativeIndex_ be ? ToInteger(_index_). 92 | 1. If _relativeIndex_ ≥ 0, then 93 | 1. Let _k_ be _relativeIndex_. 94 | 1. Else, 95 | 1. Let _k_ be _len_ + _relativeIndex_. 96 | 1. If _k_ < 0 or _k_ ≥ _len_, then return *undefined*. 97 | 1. Return ? Get(_O_, ! ToString(_k_)). 98 | 99 |
100 |
101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Proposal for an `.at()` method on all the built-in indexables 2 | 3 | A TC39 proposal to add an .at() method to all the basic indexable classes (Array, String, TypedArray) 4 | 5 | **Stage: 4** 6 | 7 | **Champions: Tab Atkins, Shu-yu Guo** 8 | 9 | **Proposed Spec Text:** 10 | 11 | ToC 12 | ---- 13 | 14 | 1. [Rationale](#rationale) 15 | 1. [Existing Methods](#existing-methods) 16 | 1. [Proposed Edits](#proposed-edits) 17 | 1. [Polyfill](#polyfill) 18 | 1. [Web Incompatibility History](#web-incompatibility-history) 19 | 1. [DOM Justifications](#dom-justifications) 20 | 1. [Convertable Interfaces](#convertable-interfaces) 21 | 1. [Possible Issues](#possible-issues) 22 | 1. [Possible DOM Compat Issues](#possible-dom-compat-issues) 23 | 24 | Rationale 25 | --------- 26 | 27 | For many years, programmers have asked for the ability to do "negative indexing" of JS Arrays, like you can do with Python. That is, asking for the ability to write `arr[-1]` instead of `arr[arr.length-1]`, where negative numbers count backwards from the last element. 28 | 29 | Unfortunately, JS's language design makes this impossible. The `[]` syntax is not specific to Arrays and Strings; it applies to all objects. Referring to a value by index, like `arr[1]`, actually just refers to the property of the object with the key "1", which is something that any object can have. So `arr[-1]` already "works" in today's code, but it returns the value of the "-1" property of the object, rather than returning an index counting back from the end. 30 | 31 | There have been many attempts to work around this; the most recent is a restricted proposal to make it easier to access just the last element of an array () via a `.last` property. 32 | 33 | This proposal instead adopts a more common approach, and suggests adding a `.at()` method to Array, String, and TypedArray, which takes an integer value and returns the item at that index, with the negative-number semantics as described above. 34 | 35 | This not only solves the long-standing request in an easy way, but also happens to solve a separate issue for [various DOM APIs, described below](#dom-justifications). 36 | 37 | ### Existing Methods 38 | 39 | Currently, to access a value from the end of an indexable object, the common practice is to write `arr[arr.length - N]`, where N is the Nth item from the end (starting at 1). This requires naming the indexable twice, additionally adds 7 more characters for the `.length`, and is hostile to anonymous values; you can't use this technique to grab the last item of the return value of a function unless you first store it in a temp variable. 40 | 41 | Another method that avoids some of those drawbacks, but has some performance drawbacks of its own, is `arr.slice(-N)[0]`. This avoids repeating the name, and thus is friendly to anonymous values as well. However, the spelling is a little weird, particularly the trailing `[0]` (since `.slice()` returns an Array). Also, a temporary array is created with all the contents of the source from the desired item to the end, only to be immediately thrown away after retrieving the first item. 42 | 43 | Note, however, the fact that `.slice()` (and related methods like `.splice()`) already have the notion of negative indexes, and resolve them exactly as desired. 44 | 45 | Possible Issues 46 | --------------- 47 | 48 | `.at()` might also be web incompatible for reasons yet unknown. 49 | 50 | Proposed Edits 51 | -------------- 52 | 53 | 54 | 55 | Polyfill 56 | -------- 57 | 58 | (Rough polyfill; correctly implements the behavior for well-behaved objects, but not guaranteed to match spec behavior precisely for edge cases, like calling the method on `undefined`.) 59 | 60 | ```js 61 | function at(n) { 62 | // ToInteger() abstract op 63 | n = Math.trunc(n) || 0; 64 | // Allow negative indexing from the end 65 | if (n < 0) n += this.length; 66 | // OOB access is guaranteed to return undefined 67 | if (n < 0 || n >= this.length) return undefined; 68 | // Otherwise, this is just normal property access 69 | return this[n]; 70 | } 71 | 72 | const TypedArray = Reflect.getPrototypeOf(Int8Array); 73 | for (const C of [Array, String, TypedArray]) { 74 | Object.defineProperty(C.prototype, "at", 75 | { value: at, 76 | writable: true, 77 | enumerable: false, 78 | configurable: true }); 79 | } 80 | ``` 81 | 82 | ## Implementations 83 | 84 | * Spec-compliant polyfills using the old name of `.item()`: [Array.prototype.item](https://www.npmjs.com/package/array.prototype.item), [String.prototype.item](https://www.npmjs.com/package/string.prototype.item) 85 | 86 | ## Web Incompatibility History 87 | 88 | The original iteration of this proposal proposed the name of the method to be `.item()`. Unfortunately, this was found out to be not web compatible. Libraries, notably YUI2 and YUI3, were duck-typing objects to be DOM collections based on the presence of a `.item` property. Please see [#28](https://github.com/tc39/proposal-relative-indexing-method/issues/28), [#31](https://github.com/tc39/proposal-relative-indexing-method/issues/31), and [#32](https://github.com/tc39/proposal-relative-indexing-method/issues/32) for more details. 89 | 90 | Captured below is the original motivation for choosing the `.item()` name and the original concerns. 91 | 92 | ### DOM Justifications 93 | 94 | A recent addition to the WebIDL spec is `ObservableArray<>` (thanks @domenic!), a proxy over an Array that allows web APIs to expose something that to page authors looks exactly like an Array, but still allows the browser to intercept get/set/delete/etc of indexed properties, enforcing type checks and other requirements exactly like they do today with named properties. 95 | 96 | We plan to start using this for most APIs that want to expose a list of something, but we'd also like to, when possible, upgrade *older* APIs to use this as well; the fact that many older APIs use bespoke interfaces that badly and incompletely copy the Array interface is a consistent source of frustration for web authors. 97 | 98 | (For example, `document.querySelectorAll()` returns, not an Array, but a NodeList, which supports indexed properties and `.length`, and so can be treated as an Array in basic ways, but has only a tiny selection of the Array prototype methods. Popular methods like `.map()` are missing, requiring authors to write code like `[...document.querySelectorAll("a")].map(foo)`.) 99 | 100 | This upgrade can *almost* be done in-place, just swapping the various bespoke interfaces with ObservableArray, avoiding breaking anything that doesn't explicitly test the value's type. There is *one* exception: all of them have a `.item()` method, which returns the value at the passed index. 101 | 102 | (This is a remnant of the very old (1990s-era) belief that Java was a reasonable language to use on the web, and so APIs were designed in a "lowest common denominator" style for use in both JS and Java. Java didn't have the ability to use indexed properties at the time unless you were actually a Java array, so the .item() method was a compromise that worked identically in both languages.) 103 | 104 | It's highly likely there is code that relies on using `.item()` on these interfaces, and we don't want to risk breakage there. 105 | 106 | We *could* address this by subclassing ObservableArray and adding `.item()` in the subclass. However, that would mean the values aren't of type Array; various type-checking methods in the community looking for an Array would fail. 107 | 108 | Or we could just add `.item()` to ObservableArray itself, as it's a proxy wrapper around Array. This would be confusing and weird however, making it appear that Array had such a method even tho it's not on the prototype. 109 | 110 | The ideal solution for us, instead, is to add `.item()` to the Array prototype itself, 111 | and for completeness/consistency, to the other indexable types that support the same general suite of index-related properties like `.slice()`. 112 | 113 | As such, the name `.item()` is a requirement of this proposal; changing it to something else would still help authors, but would fail to satisfy the DOM needs. 114 | 115 | #### Convertable Interfaces 116 | 117 | Assuming this proposal is adopted, the following legacy interfaces should be upgradable into ObservableArray: 118 | 119 | * [NodeList](https://dom.spec.whatwg.org/#nodelist) 120 | * Possibly [DOMTokenList](https://dom.spec.whatwg.org/#domtokenlist) as a subclass 121 | * [CSSRuleList](https://drafts.csswg.org/cssom/#cssrulelist) 122 | * [StyleSheetList](https://drafts.csswg.org/cssom/#stylesheetlist) 123 | * Possibly [CSSStyleDeclaration](https://drafts.csswg.org/cssom/#cssstyledeclaration) and [MediaList](https://drafts.csswg.org/cssom/#medialist), as subclasses 124 | * [FileList](https://w3c.github.io/FileAPI/#dfn-filelist) 125 | 126 | (maybe others, list is ongoing) 127 | 128 | ### Possible Issues 129 | 130 | The obvious looming issue with this, as with any addition to the built-ins, is the possibility that the name `.item()` is already added to these classes' prototypes by a framework with an incompatible definition, and added using one of the fragile patterns that avoids clobbering built-in names, so that code depending on the framework's definition will then break when it's instead given the new built-in definition. 131 | 132 | I'm prepared to eat my words, but I suspect that any library adding a `.item()` method to Array or the other indexables is going to be giving it compatible or identical semantics to what's outlined here; I can't imagine what else such a method name could possibly correspond to. 133 | 134 | There's good evidence that we're probably safe here, tho: none of MooTools, Prototype, or Ext add `.item()` to Array; those are generally the most dangerous libraries for this kind of addition (see: [smooshgate](https://developers.google.com/web/updates/2018/03/smooshgate)), so if we're safe there it's much more likely we'll be safe in general. 135 | 136 | ### Possible DOM Compat Issues 137 | 138 | The .item() method defined by all of the interfaces [listed above](#convertable-interfaces) has a common structure: 139 | 140 | ```webidl 141 | SomeType? item(unsigned long index); 142 | ``` 143 | 144 | If you follow the definition chain in WebIDL, you end up with a conversion algorithm for `unsigned long` into internal numbers that *mostly* matches what JS does for indexes in `.slice()`, with a few differences around the edges: 145 | 146 | * WebIDL treats Infinity and -Infinity as 0, while JS leaves them as is. 147 | * WebIDL modulos the value by 2^32 (using JS's "modulo" math op, so negatives become positive), while JS does not. 148 | * If the index is out of range, WebIDL returns `null`, while JS returns `undefined`. 149 | 150 | The first means that code that is accidentally passing infinities into `.item()` and relying on it returning the item at index 0 will break, as it will now get `undefined`. I find this very unlikely to be problematic. 151 | 152 | The second means that code passing extremely large numbers that are *just a little bit* larger than 2^32 (so the modulo brings them back into a reasonable index) and relying on that to return something from the list will break, as it will now get `undefined`. I also find this very unlikely to be problematic. 153 | 154 | The second also means that code relying on small negative numbers being modulo'd into the vicinity of 4 billion, and thus returning `null`, will break, as it will now return items from near the end of the list. I find this slightly likely, and believe we will need to do some instrumentation/testing to ensure it happens below our threshold for breakage. (There is a small chance that such code is *expecting* to recieve a value from the end of the list and is currently broken, and will be fixed by this change.) 155 | 156 | The third means that code which is testing for the presence of an item by *explicitly* comparing the return value with `null` will break, as it will now receive `undefined` and think a value was returned. I also find this slightly likely. In my experience, however, most such code is written either as `== null` or simply uses the truthiness of the return value (since a valid index will always return an object, which is truthy); both of these kinds of tests will continue to work after the change. 157 | 158 | --- 159 | 160 | We could potentially preview any of these changes before attempting to accept this proposal fully, so we know whether it's realistic to do such an upgrade, and thus whether the `.item()` name it a hard requirement or can be freely bikeshedded. 161 | 162 | In particular, testing negative indexes would be fairly simple, just requiring a change to `signed long` and an extra line in the algorithms of the methods. 163 | 164 | Testing returning `undefined` is also plausible; tho still slightly awkward to *express* in WebIDL (requiring the return type to be written as `any`), it's a tiny change to the algorithms of the methods. 165 | -------------------------------------------------------------------------------- /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 | 82 | /* depth 1 */ 83 | emu-alg ol, 84 | /* depth 4 */ 85 | emu-alg ol ol ol ol, 86 | emu-alg ol.nested-thrice, 87 | emu-alg ol.nested-twice ol, 88 | emu-alg ol.nested-once ol ol { 89 | list-style-type: decimal; 90 | } 91 | 92 | /* depth 2 */ 93 | emu-alg ol ol, 94 | emu-alg ol.nested-once, 95 | /* depth 5 */ 96 | emu-alg ol ol ol ol ol, 97 | emu-alg ol.nested-four-times, 98 | emu-alg ol.nested-thrice ol, 99 | emu-alg ol.nested-twice ol ol, 100 | emu-alg ol.nested-once ol ol ol { 101 | list-style-type: lower-alpha; 102 | } 103 | 104 | /* depth 3 */ 105 | emu-alg ol ol ol, 106 | emu-alg ol.nested-twice, 107 | emu-alg ol.nested-once ol, 108 | /* depth 6 */ 109 | emu-alg ol ol ol ol ol ol, 110 | emu-alg ol.nested-lots, 111 | emu-alg ol.nested-four-times ol, 112 | emu-alg ol.nested-thrice ol ol, 113 | emu-alg ol.nested-twice ol ol ol, 114 | emu-alg ol.nested-once ol ol ol ol, 115 | /* depth 7+ */ 116 | emu-alg ol.nested-lots ol { 117 | list-style-type: lower-roman; 118 | } 119 | 120 | emu-eqn { 121 | display: block; 122 | margin-left: 4em; 123 | } 124 | 125 | emu-eqn.inline { 126 | display: inline; 127 | margin: 0; 128 | } 129 | 130 | emu-eqn div:first-child { 131 | margin-left: -2em; 132 | } 133 | 134 | emu-note { 135 | margin: 1em 0; 136 | color: #666; 137 | border-left: 5px solid #ccc; 138 | display: flex; 139 | flex-direction: row; 140 | } 141 | 142 | emu-note > span.note { 143 | flex-basis: 100px; 144 | min-width: 100px; 145 | flex-grow: 0; 146 | flex-shrink: 1; 147 | text-transform: uppercase; 148 | padding-left: 5px; 149 | } 150 | 151 | emu-note[type=editor] { 152 | border-left-color: #faa; 153 | } 154 | 155 | emu-note > div.note-contents { 156 | flex-grow: 1; 157 | flex-shrink: 1; 158 | } 159 | 160 | emu-note > div.note-contents > p:first-of-type { 161 | margin-top: 0; 162 | } 163 | 164 | emu-note > div.note-contents > p:last-of-type { 165 | margin-bottom: 0; 166 | } 167 | 168 | emu-table td code { 169 | white-space: normal; 170 | } 171 | 172 | emu-figure { 173 | display: block; 174 | } 175 | 176 | emu-example { 177 | display: block; 178 | margin: 1em 3em; 179 | } 180 | 181 | emu-example figure figcaption { 182 | margin-top: 0.5em; 183 | text-align: left; 184 | } 185 | 186 | emu-figure figure, 187 | emu-example figure, 188 | emu-table figure { 189 | display: flex; 190 | flex-direction: column; 191 | align-items: center; 192 | } 193 | 194 | emu-production { 195 | display: block; 196 | } 197 | 198 | emu-grammar[type="example"] emu-production, 199 | emu-grammar[type="definition"] emu-production { 200 | margin-top: 1em; 201 | margin-bottom: 1em; 202 | margin-left: 5ex; 203 | } 204 | 205 | emu-grammar.inline, emu-production.inline, 206 | emu-grammar.inline emu-production emu-rhs, emu-production.inline emu-rhs, 207 | emu-grammar[collapsed] emu-production emu-rhs, emu-production[collapsed] emu-rhs { 208 | display: inline; 209 | padding-left: 1ex; 210 | margin-left: 0; 211 | } 212 | 213 | emu-grammar[collapsed] emu-production, emu-production[collapsed] { 214 | margin: 0; 215 | } 216 | 217 | emu-constraints { 218 | font-size: .75em; 219 | margin-right: 1ex; 220 | } 221 | 222 | emu-gann { 223 | margin-right: 1ex; 224 | } 225 | 226 | emu-gann emu-t:last-child, 227 | emu-gann emu-gprose:last-child, 228 | emu-gann emu-nt:last-child { 229 | margin-right: 0; 230 | } 231 | 232 | emu-geq { 233 | margin-left: 1ex; 234 | font-weight: bold; 235 | } 236 | 237 | emu-oneof { 238 | font-weight: bold; 239 | margin-left: 1ex; 240 | } 241 | 242 | emu-nt { 243 | display: inline-block; 244 | font-style: italic; 245 | white-space: nowrap; 246 | text-indent: 0; 247 | } 248 | 249 | emu-nt a, emu-nt a:visited { 250 | color: #333; 251 | } 252 | 253 | emu-rhs emu-nt { 254 | margin-right: 1ex; 255 | } 256 | 257 | emu-t { 258 | display: inline-block; 259 | font-family: monospace; 260 | font-weight: bold; 261 | white-space: nowrap; 262 | text-indent: 0; 263 | } 264 | 265 | emu-production emu-t { 266 | margin-right: 1ex; 267 | } 268 | 269 | emu-rhs { 270 | display: block; 271 | padding-left: 75px; 272 | text-indent: -25px; 273 | } 274 | 275 | emu-mods { 276 | font-size: .85em; 277 | vertical-align: sub; 278 | font-style: normal; 279 | font-weight: normal; 280 | } 281 | 282 | emu-params, emu-opt { 283 | margin-right: 1ex; 284 | font-family: monospace; 285 | } 286 | 287 | emu-params, emu-constraints { 288 | color: #2aa198; 289 | } 290 | 291 | emu-opt { 292 | color: #b58900; 293 | } 294 | 295 | emu-gprose { 296 | font-size: 0.9em; 297 | font-family: Helvetica, Arial, sans-serif; 298 | } 299 | 300 | emu-production emu-gprose { 301 | margin-right: 1ex; 302 | } 303 | 304 | h1.shortname { 305 | color: #f60; 306 | font-size: 1.5em; 307 | margin: 0; 308 | } 309 | 310 | h1.version { 311 | color: #f60; 312 | font-size: 1.5em; 313 | margin: 0; 314 | } 315 | 316 | h1.title { 317 | margin-top: 0; 318 | color: #f60; 319 | } 320 | 321 | h1.first { 322 | margin-top: 0; 323 | } 324 | 325 | h1, h2, h3, h4, h5, h6 { 326 | position: relative; 327 | } 328 | 329 | h1 .secnum { 330 | text-decoration: none; 331 | margin-right: 5px; 332 | } 333 | 334 | h1 span.title { 335 | order: 2; 336 | } 337 | 338 | 339 | h1 { font-size: 2.67em; margin-top: 2em; margin-bottom: 0; line-height: 1em;} 340 | h2 { font-size: 2em; } 341 | h3 { font-size: 1.56em; } 342 | h4 { font-size: 1.25em; } 343 | h5 { font-size: 1.11em; } 344 | h6 { font-size: 1em; } 345 | 346 | h1:hover span.utils { 347 | display: block; 348 | } 349 | 350 | span.utils { 351 | font-size: 18px; 352 | line-height: 18px; 353 | display: none; 354 | position: absolute; 355 | top: 100%; 356 | left: 0; 357 | right: 0; 358 | font-weight: normal; 359 | } 360 | 361 | span.utils:before { 362 | content: "⤷"; 363 | display: inline-block; 364 | padding: 0 5px; 365 | } 366 | 367 | span.utils > * { 368 | display: inline-block; 369 | margin-right: 20px; 370 | } 371 | 372 | h1 span.utils span.anchor a, 373 | h2 span.utils span.anchor a, 374 | h3 span.utils span.anchor a, 375 | h4 span.utils span.anchor a, 376 | h5 span.utils span.anchor a, 377 | h6 span.utils span.anchor a { 378 | text-decoration: none; 379 | font-variant: small-caps; 380 | } 381 | 382 | h1 span.utils span.anchor a:hover, 383 | h2 span.utils span.anchor a:hover, 384 | h3 span.utils span.anchor a:hover, 385 | h4 span.utils span.anchor a:hover, 386 | h5 span.utils span.anchor a:hover, 387 | h6 span.utils span.anchor a:hover { 388 | color: #333; 389 | } 390 | 391 | emu-intro h1, emu-clause h1, emu-annex h1 { font-size: 2em; } 392 | emu-intro h2, emu-clause h2, emu-annex h2 { font-size: 1.56em; } 393 | emu-intro h3, emu-clause h3, emu-annex h3 { font-size: 1.25em; } 394 | emu-intro h4, emu-clause h4, emu-annex h4 { font-size: 1.11em; } 395 | emu-intro h5, emu-clause h5, emu-annex h5 { font-size: 1em; } 396 | emu-intro h6, emu-clause h6, emu-annex h6 { font-size: 0.9em; } 397 | emu-intro emu-intro h1, emu-clause emu-clause h1, emu-annex emu-annex h1 { font-size: 1.56em; } 398 | emu-intro emu-intro h2, emu-clause emu-clause h2, emu-annex emu-annex h2 { font-size: 1.25em; } 399 | emu-intro emu-intro h3, emu-clause emu-clause h3, emu-annex emu-annex h3 { font-size: 1.11em; } 400 | emu-intro emu-intro h4, emu-clause emu-clause h4, emu-annex emu-annex h4 { font-size: 1em; } 401 | emu-intro emu-intro h5, emu-clause emu-clause h5, emu-annex emu-annex h5 { font-size: 0.9em; } 402 | 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; } 403 | 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; } 404 | emu-intro emu-intro emu-intro h3, emu-clause emu-clause emu-clause h3, emu-annex emu-annex emu-annex h3 { font-size: 1em; } 405 | 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; } 406 | 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; } 407 | 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; } 408 | 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; } 409 | 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; } 410 | 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; } 411 | 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 } 412 | 413 | emu-clause, emu-intro, emu-annex { 414 | display: block; 415 | } 416 | 417 | /* Figures and tables */ 418 | figure { display: block; margin: 1em 0 3em 0; } 419 | figure object { display: block; margin: 0 auto; } 420 | figure table.real-table { margin: 0 auto; } 421 | figure figcaption { 422 | display: block; 423 | color: #555555; 424 | font-weight: bold; 425 | text-align: center; 426 | } 427 | 428 | emu-table table { 429 | margin: 0 auto; 430 | } 431 | 432 | emu-table table, table.real-table { 433 | border-collapse: collapse; 434 | } 435 | 436 | emu-table td, emu-table th, table.real-table td, table.real-table th { 437 | border: 1px solid black; 438 | padding: 0.4em; 439 | vertical-align: baseline; 440 | } 441 | emu-table th, emu-table thead td, table.real-table th { 442 | background-color: #eeeeee; 443 | } 444 | 445 | /* Note: the left content edges of table.lightweight-table >tbody >tr >td 446 | and div.display line up. */ 447 | table.lightweight-table { 448 | border-collapse: collapse; 449 | margin: 0 0 0 1.5em; 450 | } 451 | table.lightweight-table td, table.lightweight-table th { 452 | border: none; 453 | padding: 0 0.5em; 454 | vertical-align: baseline; 455 | } 456 | 457 | /* diff styles */ 458 | ins { 459 | background-color: #e0f8e0; 460 | text-decoration: none; 461 | border-bottom: 1px solid #396; 462 | } 463 | 464 | del { 465 | background-color: #fee; 466 | } 467 | 468 | ins.block, del.block, 469 | emu-production > ins, emu-production > del, 470 | emu-grammar > ins, emu-grammar > del { 471 | display: block; 472 | } 473 | emu-rhs > ins, emu-rhs > del { 474 | display: inline; 475 | } 476 | 477 | tr.ins > td > ins { 478 | border-bottom: none; 479 | } 480 | 481 | tr.ins > td { 482 | background-color: #e0f8e0; 483 | } 484 | 485 | tr.del > td { 486 | background-color: #fee; 487 | } 488 | 489 | /* Menu Styles */ 490 | #menu-toggle { 491 | font-size: 2em; 492 | 493 | position: fixed; 494 | top: 0; 495 | left: 0; 496 | width: 1.5em; 497 | height: 1.5em; 498 | z-index: 3; 499 | visibility: hidden; 500 | color: #1567a2; 501 | background-color: #fff; 502 | 503 | line-height: 1.5em; 504 | text-align: center; 505 | -webkit-touch-callout: none; 506 | -webkit-user-select: none; 507 | -khtml-user-select: none; 508 | -moz-user-select: none; 509 | -ms-user-select: none; 510 | user-select: none;; 511 | 512 | cursor: pointer; 513 | } 514 | 515 | #menu { 516 | display: flex; 517 | flex-direction: column; 518 | width: 33%; height: 100vh; 519 | max-width: 500px; 520 | box-sizing: border-box; 521 | background-color: #ddd; 522 | overflow: hidden; 523 | transition: opacity 0.1s linear; 524 | padding: 0 5px; 525 | position: fixed; 526 | left: 0; top: 0; 527 | border-right: 2px solid #bbb; 528 | 529 | z-index: 2; 530 | } 531 | 532 | #menu-spacer { 533 | flex-basis: 33%; 534 | max-width: 500px; 535 | flex-grow: 0; 536 | flex-shrink: 0; 537 | } 538 | 539 | #menu a { 540 | color: #1567a2; 541 | } 542 | 543 | #menu.active { 544 | display: flex; 545 | opacity: 1; 546 | z-index: 2; 547 | } 548 | 549 | #menu-pins { 550 | flex-grow: 1; 551 | display: none; 552 | } 553 | 554 | #menu-pins.active { 555 | display: block; 556 | } 557 | 558 | #menu-pins-list { 559 | margin: 0; 560 | padding: 0; 561 | counter-reset: pins-counter; 562 | } 563 | 564 | #menu-pins-list > li:before { 565 | content: counter(pins-counter); 566 | counter-increment: pins-counter; 567 | display: inline-block; 568 | width: 25px; 569 | text-align: center; 570 | border: 1px solid #bbb; 571 | padding: 2px; 572 | margin: 4px; 573 | box-sizing: border-box; 574 | line-height: 1em; 575 | background-color: #ccc; 576 | border-radius: 4px; 577 | } 578 | #menu-toc > ol { 579 | padding: 0; 580 | flex-grow: 1; 581 | } 582 | 583 | #menu-toc > ol li { 584 | padding: 0; 585 | } 586 | 587 | #menu-toc > ol , #menu-toc > ol ol { 588 | list-style-type: none; 589 | margin: 0; 590 | padding: 0; 591 | } 592 | 593 | #menu-toc > ol ol { 594 | padding-left: 0.75em; 595 | } 596 | 597 | #menu-toc li { 598 | text-overflow: ellipsis; 599 | overflow: hidden; 600 | white-space: nowrap; 601 | } 602 | 603 | #menu-toc .item-toggle { 604 | display: inline-block; 605 | transform: rotate(-45deg) translate(-5px, -5px); 606 | transition: transform 0.1s ease; 607 | text-align: center; 608 | width: 20px; 609 | 610 | color: #aab; 611 | 612 | -webkit-touch-callout: none; 613 | -webkit-user-select: none; 614 | -khtml-user-select: none; 615 | -moz-user-select: none; 616 | -ms-user-select: none; 617 | user-select: none;; 618 | 619 | cursor: pointer; 620 | } 621 | 622 | #menu-toc .item-toggle-none { 623 | display: inline-block; 624 | width: 20px; 625 | } 626 | 627 | #menu-toc li.active > .item-toggle { 628 | transform: rotate(45deg) translate(-5px, -5px); 629 | } 630 | 631 | #menu-toc li > ol { 632 | display: none; 633 | } 634 | 635 | #menu-toc li.active > ol { 636 | display: block; 637 | } 638 | 639 | #menu-toc li.revealed > a { 640 | background-color: #bbb; 641 | font-weight: bold; 642 | /* 643 | background-color: #222; 644 | color: #c6d8e4; 645 | */ 646 | } 647 | 648 | #menu-toc li.revealed-leaf> a { 649 | color: #206ca7; 650 | /* 651 | background-color: #222; 652 | color: #c6d8e4; 653 | */ 654 | } 655 | 656 | #menu-toc li.revealed > .item-toggle { 657 | transform: rotate(45deg) translate(-5px, -5px); 658 | } 659 | 660 | #menu-toc li.revealed > ol { 661 | display: block; 662 | } 663 | 664 | #menu-toc li > a { 665 | padding: 2px 5px; 666 | } 667 | 668 | #menu > * { 669 | margin-bottom: 5px; 670 | } 671 | 672 | .menu-pane-header { 673 | padding: 0 5px; 674 | text-transform: uppercase; 675 | background-color: #aaa; 676 | color: #335; 677 | font-weight: bold; 678 | letter-spacing: 2px; 679 | flex-grow: 0; 680 | flex-shrink: 0; 681 | font-size: 0.8em; 682 | } 683 | 684 | #menu-toc { 685 | display: flex; 686 | flex-direction: column; 687 | width: 100%; 688 | overflow: hidden; 689 | flex-grow: 1; 690 | } 691 | 692 | #menu-toc ol.toc { 693 | overflow-x: hidden; 694 | overflow-y: auto; 695 | } 696 | 697 | #menu-search { 698 | position: relative; 699 | flex-grow: 0; 700 | flex-shrink: 0; 701 | width: 100%; 702 | 703 | display: flex; 704 | flex-direction: column; 705 | 706 | max-height: 300px; 707 | } 708 | 709 | #menu-trace-list { 710 | display: none; 711 | } 712 | 713 | #menu-search-box { 714 | box-sizing: border-box; 715 | display: block; 716 | width: 100%; 717 | margin: 5px 0 0 0; 718 | font-size: 1em; 719 | padding: 2px; 720 | background-color: #bbb; 721 | border: 1px solid #999; 722 | } 723 | 724 | #menu-search-results { 725 | overflow-x: hidden; 726 | overflow-y: auto; 727 | } 728 | 729 | li.menu-search-result-clause:before { 730 | content: 'clause'; 731 | width: 40px; 732 | display: inline-block; 733 | text-align: right; 734 | padding-right: 1ex; 735 | color: #666; 736 | font-size: 75%; 737 | } 738 | li.menu-search-result-op:before { 739 | content: 'op'; 740 | width: 40px; 741 | display: inline-block; 742 | text-align: right; 743 | padding-right: 1ex; 744 | color: #666; 745 | font-size: 75%; 746 | } 747 | 748 | li.menu-search-result-prod:before { 749 | content: 'prod'; 750 | width: 40px; 751 | display: inline-block; 752 | text-align: right; 753 | padding-right: 1ex; 754 | color: #666; 755 | font-size: 75% 756 | } 757 | 758 | 759 | li.menu-search-result-term:before { 760 | content: 'term'; 761 | width: 40px; 762 | display: inline-block; 763 | text-align: right; 764 | padding-right: 1ex; 765 | color: #666; 766 | font-size: 75% 767 | } 768 | 769 | #menu-search-results ul { 770 | padding: 0 5px; 771 | margin: 0; 772 | } 773 | 774 | #menu-search-results li { 775 | white-space: nowrap; 776 | text-overflow: ellipsis; 777 | } 778 | 779 | 780 | #menu-trace-list { 781 | counter-reset: item; 782 | margin: 0 0 0 20px; 783 | padding: 0; 784 | } 785 | #menu-trace-list li { 786 | display: block; 787 | white-space: nowrap; 788 | } 789 | 790 | #menu-trace-list li .secnum:after { 791 | content: " "; 792 | } 793 | #menu-trace-list li:before { 794 | content: counter(item) " "; 795 | background-color: #222; 796 | counter-increment: item; 797 | color: #999; 798 | width: 20px; 799 | height: 20px; 800 | line-height: 20px; 801 | display: inline-block; 802 | text-align: center; 803 | margin: 2px 4px 2px 0; 804 | } 805 | 806 | @media (max-width: 1000px) { 807 | body { 808 | margin: 0; 809 | display: block; 810 | } 811 | 812 | #menu { 813 | display: none; 814 | padding-top: 3em; 815 | width: 450px; 816 | } 817 | 818 | #menu.active { 819 | position: fixed; 820 | height: 100%; 821 | left: 0; 822 | top: 0; 823 | right: 300px; 824 | } 825 | 826 | #menu-toggle { 827 | visibility: visible; 828 | } 829 | 830 | #spec-container { 831 | padding: 0 5px; 832 | } 833 | 834 | #references-pane-spacer { 835 | display: none; 836 | } 837 | } 838 | 839 | @media only screen and (max-width: 800px) { 840 | #menu { 841 | width: 100%; 842 | } 843 | 844 | h1 .secnum:empty { 845 | margin: 0; padding: 0; 846 | } 847 | } 848 | 849 | 850 | /* Toolbox */ 851 | .toolbox { 852 | position: absolute; 853 | background: #ddd; 854 | border: 1px solid #aaa; 855 | display: none; 856 | color: #eee; 857 | padding: 5px; 858 | border-radius: 3px; 859 | } 860 | 861 | .toolbox.active { 862 | display: inline-block; 863 | } 864 | 865 | .toolbox a { 866 | text-decoration: none; 867 | padding: 0 5px; 868 | } 869 | 870 | .toolbox a:hover { 871 | text-decoration: underline; 872 | } 873 | 874 | .toolbox:after, .toolbox:before { 875 | top: 100%; 876 | left: 15px; 877 | border: solid transparent; 878 | content: " "; 879 | height: 0; 880 | width: 0; 881 | position: absolute; 882 | pointer-events: none; 883 | } 884 | 885 | .toolbox:after { 886 | border-color: rgba(0, 0, 0, 0); 887 | border-top-color: #ddd; 888 | border-width: 10px; 889 | margin-left: -10px; 890 | } 891 | .toolbox:before { 892 | border-color: rgba(204, 204, 204, 0); 893 | border-top-color: #aaa; 894 | border-width: 12px; 895 | margin-left: -12px; 896 | } 897 | 898 | #references-pane-container { 899 | position: fixed; 900 | bottom: 0; 901 | left: 0; 902 | right: 0; 903 | height: 250px; 904 | display: none; 905 | background-color: #ddd; 906 | z-index: 1; 907 | } 908 | 909 | #references-pane-table-container { 910 | overflow-x: hidden; 911 | overflow-y: auto; 912 | } 913 | 914 | #references-pane-spacer { 915 | flex-basis: 33%; 916 | max-width: 500px; 917 | } 918 | 919 | #references-pane { 920 | flex-grow: 1; 921 | overflow: hidden; 922 | display: flex; 923 | flex-direction: column; 924 | } 925 | 926 | #references-pane-container.active { 927 | display: flex; 928 | } 929 | 930 | #references-pane-close:after { 931 | content: '✖'; 932 | float: right; 933 | cursor: pointer; 934 | } 935 | 936 | #references-pane table tr td:first-child { 937 | text-align: right; 938 | padding-right: 5px; 939 | } 940 | 941 | @media print { 942 | #menu-toggle { 943 | display: none; 944 | } 945 | } 946 | -------------------------------------------------------------------------------- /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 | // Perform an initial search if the box is not empty. 17 | if (this.$searchBox.value) { 18 | this.search(this.$searchBox.value); 19 | } 20 | } 21 | 22 | Search.prototype.loadBiblio = function () { 23 | var $biblio = document.getElementById('menu-search-biblio'); 24 | if (!$biblio) { 25 | this.biblio = []; 26 | } else { 27 | this.biblio = JSON.parse($biblio.textContent); 28 | this.biblio.clauses = this.biblio.filter(function (e) { return e.type === 'clause' }); 29 | this.biblio.byId = this.biblio.reduce(function (map, entry) { 30 | map[entry.id] = entry; 31 | return map; 32 | }, {}); 33 | } 34 | } 35 | 36 | Search.prototype.documentKeydown = function (e) { 37 | if (e.keyCode === 191) { 38 | e.preventDefault(); 39 | e.stopPropagation(); 40 | this.triggerSearch(); 41 | } 42 | } 43 | 44 | Search.prototype.searchBoxKeydown = function (e) { 45 | e.stopPropagation(); 46 | e.preventDefault(); 47 | if (e.keyCode === 191 && e.target.value.length === 0) { 48 | e.preventDefault(); 49 | } else if (e.keyCode === 13) { 50 | e.preventDefault(); 51 | this.selectResult(); 52 | } 53 | } 54 | 55 | Search.prototype.searchBoxKeyup = function (e) { 56 | if (e.keyCode === 13 || e.keyCode === 9) { 57 | return; 58 | } 59 | 60 | this.search(e.target.value); 61 | } 62 | 63 | 64 | Search.prototype.triggerSearch = function (e) { 65 | if (this.menu.isVisible()) { 66 | this._closeAfterSearch = false; 67 | } else { 68 | this._closeAfterSearch = true; 69 | this.menu.show(); 70 | } 71 | 72 | this.$searchBox.focus(); 73 | this.$searchBox.select(); 74 | } 75 | // bit 12 - Set if the result starts with searchString 76 | // bits 8-11: 8 - number of chunks multiplied by 2 if cases match, otherwise 1. 77 | // bits 1-7: 127 - length of the entry 78 | // General scheme: prefer case sensitive matches with fewer chunks, and otherwise 79 | // prefer shorter matches. 80 | function relevance(result, searchString) { 81 | var relevance = 0; 82 | 83 | relevance = Math.max(0, 8 - result.match.chunks) << 7; 84 | 85 | if (result.match.caseMatch) { 86 | relevance *= 2; 87 | } 88 | 89 | if (result.match.prefix) { 90 | relevance += 2048 91 | } 92 | 93 | relevance += Math.max(0, 255 - result.entry.key.length); 94 | 95 | return relevance; 96 | } 97 | 98 | Search.prototype.search = function (searchString) { 99 | if (searchString === '') { 100 | this.displayResults([]); 101 | this.hideSearch(); 102 | return; 103 | } else { 104 | this.showSearch(); 105 | } 106 | 107 | if (searchString.length === 1) { 108 | this.displayResults([]); 109 | return; 110 | } 111 | 112 | var results; 113 | 114 | if (/^[\d\.]*$/.test(searchString)) { 115 | results = this.biblio.clauses.filter(function (clause) { 116 | return clause.number.substring(0, searchString.length) === searchString; 117 | }).map(function (clause) { 118 | return { entry: clause }; 119 | }); 120 | } else { 121 | results = []; 122 | 123 | for (var i = 0; i < this.biblio.length; i++) { 124 | var entry = this.biblio[i]; 125 | if (!entry.key) { 126 | // biblio entries without a key aren't searchable 127 | continue; 128 | } 129 | 130 | var match = fuzzysearch(searchString, entry.key); 131 | if (match) { 132 | results.push({ entry: entry, match: match }); 133 | } 134 | } 135 | 136 | results.forEach(function (result) { 137 | result.relevance = relevance(result, searchString); 138 | }); 139 | 140 | results = results.sort(function (a, b) { return b.relevance - a.relevance }); 141 | 142 | } 143 | 144 | if (results.length > 50) { 145 | results = results.slice(0, 50); 146 | } 147 | 148 | this.displayResults(results); 149 | } 150 | Search.prototype.hideSearch = function () { 151 | this.$search.classList.remove('active'); 152 | } 153 | 154 | Search.prototype.showSearch = function () { 155 | this.$search.classList.add('active'); 156 | } 157 | 158 | Search.prototype.selectResult = function () { 159 | var $first = this.$searchResults.querySelector('li:first-child a'); 160 | 161 | if ($first) { 162 | document.location = $first.getAttribute('href'); 163 | } 164 | 165 | this.$searchBox.value = ''; 166 | this.$searchBox.blur(); 167 | this.displayResults([]); 168 | this.hideSearch(); 169 | 170 | if (this._closeAfterSearch) { 171 | this.menu.hide(); 172 | } 173 | } 174 | 175 | Search.prototype.displayResults = function (results) { 176 | if (results.length > 0) { 177 | this.$searchResults.classList.remove('no-results'); 178 | 179 | var html = '
    '; 180 | 181 | results.forEach(function (result) { 182 | var entry = result.entry; 183 | var id = entry.id; 184 | var cssClass = ''; 185 | var text = ''; 186 | 187 | if (entry.type === 'clause') { 188 | var number = entry.number ? entry.number + ' ' : ''; 189 | text = number + entry.key; 190 | cssClass = 'clause'; 191 | id = entry.id; 192 | } else if (entry.type === 'production') { 193 | text = entry.key; 194 | cssClass = 'prod'; 195 | id = entry.id; 196 | } else if (entry.type === 'op') { 197 | text = entry.key; 198 | cssClass = 'op'; 199 | id = entry.id || entry.refId; 200 | } else if (entry.type === 'term') { 201 | text = entry.key; 202 | cssClass = 'term'; 203 | id = entry.id || entry.refId; 204 | } 205 | 206 | if (text) { 207 | html += ''; 208 | } 209 | }); 210 | 211 | html += '
' 212 | 213 | this.$searchResults.innerHTML = html; 214 | } else { 215 | this.$searchResults.innerHTML = ''; 216 | this.$searchResults.classList.add('no-results'); 217 | } 218 | } 219 | 220 | 221 | function Menu() { 222 | this.$toggle = document.getElementById('menu-toggle'); 223 | this.$menu = document.getElementById('menu'); 224 | this.$toc = document.querySelector('menu-toc > ol'); 225 | this.$pins = document.querySelector('#menu-pins'); 226 | this.$pinList = document.getElementById('menu-pins-list'); 227 | this.$toc = document.querySelector('#menu-toc > ol'); 228 | this.$specContainer = document.getElementById('spec-container'); 229 | this.search = new Search(this); 230 | 231 | this._pinnedIds = {}; 232 | this.loadPinEntries(); 233 | 234 | // toggle menu 235 | this.$toggle.addEventListener('click', this.toggle.bind(this)); 236 | 237 | // keydown events for pinned clauses 238 | document.addEventListener('keydown', this.documentKeydown.bind(this)); 239 | 240 | // toc expansion 241 | var tocItems = this.$menu.querySelectorAll('#menu-toc li'); 242 | for (var i = 0; i < tocItems.length; i++) { 243 | var $item = tocItems[i]; 244 | $item.addEventListener('click', function($item, event) { 245 | $item.classList.toggle('active'); 246 | event.stopPropagation(); 247 | }.bind(null, $item)); 248 | } 249 | 250 | // close toc on toc item selection 251 | var tocLinks = this.$menu.querySelectorAll('#menu-toc li > a'); 252 | for (var i = 0; i < tocLinks.length; i++) { 253 | var $link = tocLinks[i]; 254 | $link.addEventListener('click', function(event) { 255 | this.toggle(); 256 | event.stopPropagation(); 257 | }.bind(this)); 258 | } 259 | 260 | // update active clause on scroll 261 | window.addEventListener('scroll', debounce(this.updateActiveClause.bind(this))); 262 | this.updateActiveClause(); 263 | 264 | // prevent menu scrolling from scrolling the body 265 | this.$toc.addEventListener('wheel', function (e) { 266 | var target = e.currentTarget; 267 | var offTop = e.deltaY < 0 && target.scrollTop === 0; 268 | if (offTop) { 269 | e.preventDefault(); 270 | } 271 | var offBottom = e.deltaY > 0 272 | && target.offsetHeight + target.scrollTop >= target.scrollHeight; 273 | 274 | if (offBottom) { 275 | e.preventDefault(); 276 | } 277 | }); 278 | } 279 | 280 | Menu.prototype.documentKeydown = function (e) { 281 | e.stopPropagation(); 282 | if (e.keyCode === 80) { 283 | this.togglePinEntry(); 284 | } else if (e.keyCode > 48 && e.keyCode < 58) { 285 | this.selectPin(e.keyCode - 49); 286 | } 287 | } 288 | 289 | Menu.prototype.updateActiveClause = function () { 290 | this.setActiveClause(findActiveClause(this.$specContainer)) 291 | } 292 | 293 | Menu.prototype.setActiveClause = function (clause) { 294 | this.$activeClause = clause; 295 | this.revealInToc(this.$activeClause); 296 | } 297 | 298 | Menu.prototype.revealInToc = function (path) { 299 | var current = this.$toc.querySelectorAll('li.revealed'); 300 | for (var i = 0; i < current.length; i++) { 301 | current[i].classList.remove('revealed'); 302 | current[i].classList.remove('revealed-leaf'); 303 | } 304 | 305 | var current = this.$toc; 306 | var index = 0; 307 | while (index < path.length) { 308 | var children = current.children; 309 | for (var i = 0; i < children.length; i++) { 310 | if ('#' + path[index].id === children[i].children[1].getAttribute('href') ) { 311 | children[i].classList.add('revealed'); 312 | if (index === path.length - 1) { 313 | children[i].classList.add('revealed-leaf'); 314 | var rect = children[i].getBoundingClientRect(); 315 | // this.$toc.getBoundingClientRect().top; 316 | var tocRect = this.$toc.getBoundingClientRect(); 317 | if (rect.top + 10 > tocRect.bottom) { 318 | this.$toc.scrollTop = this.$toc.scrollTop + (rect.top - tocRect.bottom) + (rect.bottom - rect.top); 319 | } else if (rect.top < tocRect.top) { 320 | this.$toc.scrollTop = this.$toc.scrollTop - (tocRect.top - rect.top); 321 | } 322 | } 323 | current = children[i].querySelector('ol'); 324 | index++; 325 | break; 326 | } 327 | } 328 | 329 | } 330 | } 331 | 332 | function findActiveClause(root, path) { 333 | var clauses = new ClauseWalker(root); 334 | var $clause; 335 | var path = path || []; 336 | 337 | while ($clause = clauses.nextNode()) { 338 | var rect = $clause.getBoundingClientRect(); 339 | var $header = $clause.querySelector("h1"); 340 | var marginTop = parseInt(getComputedStyle($header)["margin-top"]); 341 | 342 | if ((rect.top - marginTop) <= 0 && rect.bottom > 0) { 343 | return findActiveClause($clause, path.concat($clause)) || path; 344 | } 345 | } 346 | 347 | return path; 348 | } 349 | 350 | function ClauseWalker(root) { 351 | var previous; 352 | var treeWalker = document.createTreeWalker( 353 | root, 354 | NodeFilter.SHOW_ELEMENT, 355 | { 356 | acceptNode: function (node) { 357 | if (previous === node.parentNode) { 358 | return NodeFilter.FILTER_REJECT; 359 | } else { 360 | previous = node; 361 | } 362 | if (node.nodeName === 'EMU-CLAUSE' || node.nodeName === 'EMU-INTRO' || node.nodeName === 'EMU-ANNEX') { 363 | return NodeFilter.FILTER_ACCEPT; 364 | } else { 365 | return NodeFilter.FILTER_SKIP; 366 | } 367 | } 368 | }, 369 | false 370 | ); 371 | 372 | return treeWalker; 373 | } 374 | 375 | Menu.prototype.toggle = function () { 376 | this.$menu.classList.toggle('active'); 377 | } 378 | 379 | Menu.prototype.show = function () { 380 | this.$menu.classList.add('active'); 381 | } 382 | 383 | Menu.prototype.hide = function () { 384 | this.$menu.classList.remove('active'); 385 | } 386 | 387 | Menu.prototype.isVisible = function() { 388 | return this.$menu.classList.contains('active'); 389 | } 390 | 391 | Menu.prototype.showPins = function () { 392 | this.$pins.classList.add('active'); 393 | } 394 | 395 | Menu.prototype.hidePins = function () { 396 | this.$pins.classList.remove('active'); 397 | } 398 | 399 | Menu.prototype.addPinEntry = function (id) { 400 | var entry = this.search.biblio.byId[id]; 401 | if (!entry) { 402 | // id was deleted after pin (or something) so remove it 403 | delete this._pinnedIds[id]; 404 | this.persistPinEntries(); 405 | return; 406 | } 407 | 408 | if (entry.type === 'clause') { 409 | var prefix; 410 | if (entry.number) { 411 | prefix = entry.number + ' '; 412 | } else { 413 | prefix = ''; 414 | } 415 | this.$pinList.innerHTML += '
  • ' + prefix + entry.titleHTML + '
  • '; 416 | } else { 417 | this.$pinList.innerHTML += '
  • ' + entry.key + '
  • '; 418 | } 419 | 420 | if (Object.keys(this._pinnedIds).length === 0) { 421 | this.showPins(); 422 | } 423 | this._pinnedIds[id] = true; 424 | this.persistPinEntries(); 425 | } 426 | 427 | Menu.prototype.removePinEntry = function (id) { 428 | var item = this.$pinList.querySelector('a[href="#' + id + '"]').parentNode; 429 | this.$pinList.removeChild(item); 430 | delete this._pinnedIds[id]; 431 | if (Object.keys(this._pinnedIds).length === 0) { 432 | this.hidePins(); 433 | } 434 | 435 | this.persistPinEntries(); 436 | } 437 | 438 | Menu.prototype.persistPinEntries = function () { 439 | try { 440 | if (!window.localStorage) return; 441 | } catch (e) { 442 | return; 443 | } 444 | 445 | localStorage.pinEntries = JSON.stringify(Object.keys(this._pinnedIds)); 446 | } 447 | 448 | Menu.prototype.loadPinEntries = function () { 449 | try { 450 | if (!window.localStorage) return; 451 | } catch (e) { 452 | return; 453 | } 454 | 455 | var pinsString = window.localStorage.pinEntries; 456 | if (!pinsString) return; 457 | var pins = JSON.parse(pinsString); 458 | for(var i = 0; i < pins.length; i++) { 459 | this.addPinEntry(pins[i]); 460 | } 461 | } 462 | 463 | Menu.prototype.togglePinEntry = function (id) { 464 | if (!id) { 465 | id = this.$activeClause[this.$activeClause.length - 1].id; 466 | } 467 | 468 | if (this._pinnedIds[id]) { 469 | this.removePinEntry(id); 470 | } else { 471 | this.addPinEntry(id); 472 | } 473 | } 474 | 475 | Menu.prototype.selectPin = function (num) { 476 | document.location = this.$pinList.children[num].children[0].href; 477 | } 478 | 479 | var menu; 480 | function init() { 481 | menu = new Menu(); 482 | var $container = document.getElementById('spec-container'); 483 | $container.addEventListener('mouseover', debounce(function (e) { 484 | Toolbox.activateIfMouseOver(e); 485 | })); 486 | document.addEventListener('keydown', debounce(function (e) { 487 | if (e.code === "Escape" && Toolbox.active) { 488 | Toolbox.deactivate(); 489 | } 490 | })); 491 | } 492 | 493 | document.addEventListener('DOMContentLoaded', init); 494 | 495 | function debounce(fn, opts) { 496 | opts = opts || {}; 497 | var timeout; 498 | return function(e) { 499 | if (opts.stopPropagation) { 500 | e.stopPropagation(); 501 | } 502 | var args = arguments; 503 | if (timeout) { 504 | clearTimeout(timeout); 505 | } 506 | timeout = setTimeout(function() { 507 | timeout = null; 508 | fn.apply(this, args); 509 | }.bind(this), 150); 510 | } 511 | } 512 | 513 | var CLAUSE_NODES = ['EMU-CLAUSE', 'EMU-INTRO', 'EMU-ANNEX']; 514 | function findLocalReferences ($elem) { 515 | var name = $elem.innerHTML; 516 | var references = []; 517 | 518 | var parentClause = $elem.parentNode; 519 | while (parentClause && CLAUSE_NODES.indexOf(parentClause.nodeName) === -1) { 520 | parentClause = parentClause.parentNode; 521 | } 522 | 523 | if(!parentClause) return; 524 | 525 | var vars = parentClause.querySelectorAll('var'); 526 | 527 | for (var i = 0; i < vars.length; i++) { 528 | var $var = vars[i]; 529 | 530 | if ($var.innerHTML === name) { 531 | references.push($var); 532 | } 533 | } 534 | 535 | return references; 536 | } 537 | 538 | function toggleFindLocalReferences($elem) { 539 | var references = findLocalReferences($elem); 540 | if ($elem.classList.contains('referenced')) { 541 | references.forEach(function ($reference) { 542 | $reference.classList.remove('referenced'); 543 | }); 544 | } else { 545 | references.forEach(function ($reference) { 546 | $reference.classList.add('referenced'); 547 | }); 548 | } 549 | } 550 | 551 | function installFindLocalReferences () { 552 | document.addEventListener('click', function (e) { 553 | if (e.target.nodeName === 'VAR') { 554 | toggleFindLocalReferences(e.target); 555 | } 556 | }); 557 | } 558 | 559 | document.addEventListener('DOMContentLoaded', installFindLocalReferences); 560 | 561 | 562 | 563 | 564 | // The following license applies to the fuzzysearch function 565 | // The MIT License (MIT) 566 | // Copyright © 2015 Nicolas Bevacqua 567 | // Copyright © 2016 Brian Terlson 568 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 569 | // this software and associated documentation files (the "Software"), to deal in 570 | // the Software without restriction, including without limitation the rights to 571 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 572 | // the Software, and to permit persons to whom the Software is furnished to do so, 573 | // subject to the following conditions: 574 | 575 | // The above copyright notice and this permission notice shall be included in all 576 | // copies or substantial portions of the Software. 577 | 578 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 579 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 580 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 581 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 582 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 583 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 584 | function fuzzysearch (searchString, haystack, caseInsensitive) { 585 | var tlen = haystack.length; 586 | var qlen = searchString.length; 587 | var chunks = 1; 588 | var finding = false; 589 | 590 | if (qlen > tlen) { 591 | return false; 592 | } 593 | 594 | if (qlen === tlen) { 595 | if (searchString === haystack) { 596 | return { caseMatch: true, chunks: 1, prefix: true }; 597 | } else if (searchString.toLowerCase() === haystack.toLowerCase()) { 598 | return { caseMatch: false, chunks: 1, prefix: true }; 599 | } else { 600 | return false; 601 | } 602 | } 603 | 604 | outer: for (var i = 0, j = 0; i < qlen; i++) { 605 | var nch = searchString[i]; 606 | while (j < tlen) { 607 | var targetChar = haystack[j++]; 608 | if (targetChar === nch) { 609 | finding = true; 610 | continue outer; 611 | } 612 | if (finding) { 613 | chunks++; 614 | finding = false; 615 | } 616 | } 617 | 618 | if (caseInsensitive) { return false; } 619 | 620 | return fuzzysearch(searchString.toLowerCase(), haystack.toLowerCase(), true); 621 | } 622 | 623 | return { caseMatch: !caseInsensitive, chunks: chunks, prefix: j <= qlen }; 624 | } 625 | 626 | var Toolbox = { 627 | init: function () { 628 | this.$container = document.createElement('div'); 629 | this.$container.classList.add('toolbox'); 630 | this.$permalink = document.createElement('a'); 631 | this.$permalink.textContent = 'Permalink'; 632 | this.$pinLink = document.createElement('a'); 633 | this.$pinLink.textContent = 'Pin'; 634 | this.$pinLink.setAttribute('href', '#'); 635 | this.$pinLink.addEventListener('click', function (e) { 636 | e.preventDefault(); 637 | e.stopPropagation(); 638 | menu.togglePinEntry(this.entry.id); 639 | }.bind(this)); 640 | 641 | this.$refsLink = document.createElement('a'); 642 | this.$refsLink.setAttribute('href', '#'); 643 | this.$refsLink.addEventListener('click', function (e) { 644 | e.preventDefault(); 645 | e.stopPropagation(); 646 | referencePane.showReferencesFor(this.entry); 647 | }.bind(this)); 648 | this.$container.appendChild(this.$permalink); 649 | this.$container.appendChild(this.$pinLink); 650 | this.$container.appendChild(this.$refsLink); 651 | document.body.appendChild(this.$container); 652 | }, 653 | 654 | activate: function (el, entry, target) { 655 | if (el === this._activeEl) return; 656 | this.active = true; 657 | this.entry = entry; 658 | this.$container.classList.add('active'); 659 | this.top = el.offsetTop - this.$container.offsetHeight - 10; 660 | this.left = el.offsetLeft; 661 | this.$container.setAttribute('style', 'left: ' + this.left + 'px; top: ' + this.top + 'px'); 662 | this.updatePermalink(); 663 | this.updateReferences(); 664 | this._activeEl = el; 665 | if (this.top < document.body.scrollTop && el === target) { 666 | // don't scroll unless it's a small thing (< 200px) 667 | this.$container.scrollIntoView(); 668 | } 669 | }, 670 | 671 | updatePermalink: function () { 672 | this.$permalink.setAttribute('href', '#' + this.entry.id); 673 | }, 674 | 675 | updateReferences: function () { 676 | this.$refsLink.textContent = 'References (' + this.entry.referencingIds.length + ')'; 677 | }, 678 | 679 | activateIfMouseOver: function (e) { 680 | var ref = this.findReferenceUnder(e.target); 681 | if (ref && (!this.active || e.pageY > this._activeEl.offsetTop)) { 682 | var entry = menu.search.biblio.byId[ref.id]; 683 | this.activate(ref.element, entry, e.target); 684 | } else if (this.active && ((e.pageY < this.top) || e.pageY > (this._activeEl.offsetTop + this._activeEl.offsetHeight))) { 685 | this.deactivate(); 686 | } 687 | }, 688 | 689 | findReferenceUnder: function (el) { 690 | while (el) { 691 | var parent = el.parentNode; 692 | if (el.nodeName === 'H1' && parent.nodeName.match(/EMU-CLAUSE|EMU-ANNEX|EMU-INTRO/) && parent.id) { 693 | return { element: el, id: parent.id }; 694 | } else if (el.nodeName.match(/EMU-(?!CLAUSE|XREF|ANNEX|INTRO)|DFN/) && 695 | el.id && el.id[0] !== '_') { 696 | if (el.nodeName === 'EMU-FIGURE' || el.nodeName === 'EMU-TABLE' || el.nodeName === 'EMU-EXAMPLE') { 697 | // return the figcaption element 698 | return { element: el.children[0].children[0], id: el.id }; 699 | } else if (el.nodeName === 'EMU-PRODUCTION') { 700 | // return the LHS non-terminal element 701 | return { element: el.children[0], id: el.id }; 702 | } else { 703 | return { element: el, id: el.id }; 704 | } 705 | } 706 | el = parent; 707 | } 708 | }, 709 | 710 | deactivate: function () { 711 | this.$container.classList.remove('active'); 712 | this._activeEl = null; 713 | this.activeElBounds = null; 714 | this.active = false; 715 | } 716 | } 717 | 718 | var referencePane = { 719 | init: function() { 720 | this.$container = document.createElement('div'); 721 | this.$container.setAttribute('id', 'references-pane-container'); 722 | 723 | var $spacer = document.createElement('div'); 724 | $spacer.setAttribute('id', 'references-pane-spacer'); 725 | 726 | this.$pane = document.createElement('div'); 727 | this.$pane.setAttribute('id', 'references-pane'); 728 | 729 | this.$container.appendChild($spacer); 730 | this.$container.appendChild(this.$pane); 731 | 732 | this.$header = document.createElement('div'); 733 | this.$header.classList.add('menu-pane-header'); 734 | this.$header.textContent = 'References to '; 735 | this.$headerRefId = document.createElement('a'); 736 | this.$header.appendChild(this.$headerRefId); 737 | this.$closeButton = document.createElement('span'); 738 | this.$closeButton.setAttribute('id', 'references-pane-close'); 739 | this.$closeButton.addEventListener('click', function (e) { 740 | this.deactivate(); 741 | }.bind(this)); 742 | this.$header.appendChild(this.$closeButton); 743 | 744 | this.$pane.appendChild(this.$header); 745 | var tableContainer = document.createElement('div'); 746 | tableContainer.setAttribute('id', 'references-pane-table-container'); 747 | 748 | this.$table = document.createElement('table'); 749 | this.$table.setAttribute('id', 'references-pane-table'); 750 | 751 | this.$tableBody = this.$table.createTBody(); 752 | 753 | tableContainer.appendChild(this.$table); 754 | this.$pane.appendChild(tableContainer); 755 | 756 | menu.$specContainer.appendChild(this.$container); 757 | }, 758 | 759 | activate: function () { 760 | this.$container.classList.add('active'); 761 | }, 762 | 763 | deactivate: function () { 764 | this.$container.classList.remove('active'); 765 | }, 766 | 767 | showReferencesFor(entry) { 768 | this.activate(); 769 | var newBody = document.createElement('tbody'); 770 | var previousId; 771 | var previousCell; 772 | var dupCount = 0; 773 | this.$headerRefId.textContent = '#' + entry.id; 774 | this.$headerRefId.setAttribute('href', '#' + entry.id); 775 | entry.referencingIds.map(function (id) { 776 | var target = document.getElementById(id); 777 | var cid = findParentClauseId(target); 778 | var clause = menu.search.biblio.byId[cid]; 779 | return { id: id, clause: clause } 780 | }).sort(function (a, b) { 781 | return sortByClauseNumber(a.clause, b.clause); 782 | }).forEach(function (record, i) { 783 | if (previousId === record.clause.id) { 784 | previousCell.innerHTML += ' (' + (dupCount + 2) + ')'; 785 | dupCount++; 786 | } else { 787 | var row = newBody.insertRow(); 788 | var cell = row.insertCell(); 789 | cell.innerHTML = record.clause.number; 790 | cell = row.insertCell(); 791 | cell.innerHTML = '' + record.clause.titleHTML + ''; 792 | previousCell = cell; 793 | previousId = record.clause.id; 794 | dupCount = 0; 795 | } 796 | }, this); 797 | this.$table.removeChild(this.$tableBody); 798 | this.$tableBody = newBody; 799 | this.$table.appendChild(this.$tableBody); 800 | } 801 | } 802 | function findParentClauseId(node) { 803 | while (node && node.nodeName !== 'EMU-CLAUSE' && node.nodeName !== 'EMU-INTRO' && node.nodeName !== 'EMU-ANNEX') { 804 | node = node.parentNode; 805 | } 806 | if (!node) return null; 807 | return node.getAttribute('id'); 808 | } 809 | 810 | function sortByClauseNumber(c1, c2) { 811 | var c1c = c1.number.split('.'); 812 | var c2c = c2.number.split('.'); 813 | 814 | for (var i = 0; i < c1c.length; i++) { 815 | if (i >= c2c.length) { 816 | return 1; 817 | } 818 | 819 | var c1 = c1c[i]; 820 | var c2 = c2c[i]; 821 | var c1cn = Number(c1); 822 | var c2cn = Number(c2); 823 | 824 | if (Number.isNaN(c1cn) && Number.isNaN(c2cn)) { 825 | if (c1 > c2) { 826 | return 1; 827 | } else if (c1 < c2) { 828 | return -1; 829 | } 830 | } else if (!Number.isNaN(c1cn) && Number.isNaN(c2cn)) { 831 | return -1; 832 | } else if (Number.isNaN(c1cn) && !Number.isNaN(c2cn)) { 833 | return 1; 834 | } else if(c1cn > c2cn) { 835 | return 1; 836 | } else if (c1cn < c2cn) { 837 | return -1; 838 | } 839 | } 840 | 841 | if (c1c.length === c2c.length) { 842 | return 0; 843 | } 844 | return -1; 845 | } 846 | 847 | document.addEventListener('DOMContentLoaded', function () { 848 | Toolbox.init(); 849 | referencePane.init(); 850 | }) 851 | var CLAUSE_NODES = ['EMU-CLAUSE', 'EMU-INTRO', 'EMU-ANNEX']; 852 | function findLocalReferences ($elem) { 853 | var name = $elem.innerHTML; 854 | var references = []; 855 | 856 | var parentClause = $elem.parentNode; 857 | while (parentClause && CLAUSE_NODES.indexOf(parentClause.nodeName) === -1) { 858 | parentClause = parentClause.parentNode; 859 | } 860 | 861 | if(!parentClause) return; 862 | 863 | var vars = parentClause.querySelectorAll('var'); 864 | 865 | for (var i = 0; i < vars.length; i++) { 866 | var $var = vars[i]; 867 | 868 | if ($var.innerHTML === name) { 869 | references.push($var); 870 | } 871 | } 872 | 873 | return references; 874 | } 875 | 876 | function toggleFindLocalReferences($elem) { 877 | var references = findLocalReferences($elem); 878 | if ($elem.classList.contains('referenced')) { 879 | references.forEach(function ($reference) { 880 | $reference.classList.remove('referenced'); 881 | }); 882 | } else { 883 | references.forEach(function ($reference) { 884 | $reference.classList.add('referenced'); 885 | }); 886 | } 887 | } 888 | 889 | function installFindLocalReferences () { 890 | document.addEventListener('click', function (e) { 891 | if (e.target.nodeName === 'VAR') { 892 | toggleFindLocalReferences(e.target); 893 | } 894 | }); 895 | } 896 | 897 | document.addEventListener('DOMContentLoaded', installFindLocalReferences); 898 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Relative Indexing Method 6 |

    Stage 4 Draft / January 24, 2022

    1849 | 1850 |

    Relative Indexing Method

    1851 |

    We provide a new method, at(), that is on the prototype of the built-in indexable objects: Array, String, and TypedArrays objects. The method supports relative indexing from the end when passed a negative index.

    1852 |
    1853 | 1854 | 1855 |

    1 Additions to Properties of the Array Prototype Object

    1856 | 1857 | 1858 |

    1.1 Array.prototype.at ( index )

    1859 |
    1. Let O be ? ToObject(this value).
    2. Let len be ? LengthOfArrayLike(O).
    3. Let relativeIndex be ? ToInteger(index).
    4. If relativeIndex ≥ 0, then
      1. Let k be relativeIndex.
    5. Else,
      1. Let k be len + relativeIndex.
    6. If k < 0 or klen, then return undefined.
    7. Return ? Get(O, ! ToString(k)).
    1860 |
    1861 |
    1862 | 1863 | 1864 |

    2 Modifications to Properties of the Array Prototype Object

    1865 | 1866 | 1867 |

    2.1 Array.prototype [ @@unscopables ]

    1868 |

    The initial value of the @@unscopables data property is an object created by the following steps:

    1869 |
    1. Let unscopableList be ! OrdinaryObjectCreate(null).
    2. Perform ! CreateDataPropertyOrThrow(unscopableList, "at", true).
    3. Perform ! CreateDataPropertyOrThrow(unscopableList, "copyWithin", true).
    4. Perform ! CreateDataPropertyOrThrow(unscopableList, "entries", true).
    5. Perform ! CreateDataPropertyOrThrow(unscopableList, "fill", true).
    6. Perform ! CreateDataPropertyOrThrow(unscopableList, "find", true).
    7. Perform ! CreateDataPropertyOrThrow(unscopableList, "findIndex", true).
    8. Perform ! CreateDataPropertyOrThrow(unscopableList, "flat", true).
    9. Perform ! CreateDataPropertyOrThrow(unscopableList, "flatMap", true).
    10. Perform ! CreateDataPropertyOrThrow(unscopableList, "includes", true).
    11. Perform ! CreateDataPropertyOrThrow(unscopableList, "keys", true).
    12. Perform ! CreateDataPropertyOrThrow(unscopableList, "values", true).
    13. Return unscopableList.
    1870 |
    1871 |
    1872 | 1873 | 1874 |

    3 Additions to Properties of the String Prototype Object

    1875 | 1876 | 1877 |

    3.1 String.prototype.at ( index )

    1878 |
    1. Let O be ? RequireObjectCoercible(this value).
    2. Let S be ? ToString(O).
    3. Let len be the length of S.
    4. Let relativeIndex be ? ToInteger(index).
    5. If relativeIndex ≥ 0, then
      1. Let k be relativeIndex.
    6. Else,
      1. Let k be len + relativeIndex.
    7. If k < 0 or klen, then return undefined.
    8. Return the String value consisting of only the code unit at position k in S.
    1879 |
    1880 |
    1881 | 1882 | 1883 |

    4 Additions to Properties of the %TypedArray.prototype% Object

    1884 | 1885 | 1886 |

    4.1 %TypedArray%.prototype.at ( index )

    1887 |
    1. Let O be the this value.
    2. Perform ? ValidateTypedArray(O).
    3. Let len be O.[[ArrayLength]].
    4. Let relativeIndex be ? ToInteger(index).
    5. If relativeIndex ≥ 0, then
      1. Let k be relativeIndex.
    6. Else,
      1. Let k be len + relativeIndex.
    7. If k < 0 or klen, then return undefined.
    8. Return ? Get(O, ! ToString(k)).
    1888 |
    1889 |
    1890 |
    --------------------------------------------------------------------------------