├── .gitignore ├── .travis.yml ├── README.md ├── deploy.sh ├── ecmarkup.css ├── ecmarkup.js ├── github_deploy_key.enc ├── index.html ├── package.json └── spec ├── biblio.json ├── index.html └── relativetimeformat.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: off 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - "8" 7 | 8 | script: 9 | - bash ./deploy.sh 10 | 11 | env: 12 | global: 13 | - ENCRYPTION_LABEL: "33173ec242dd" 14 | - GH_USER_NAME: "littledan" 15 | - GH_USER_EMAIL: "littledan@igalia.com" 16 | - PRIVATE_KEY_FILE_NAME: "github_deploy_key.enc" 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `Intl.RelativeTimeFormat` API Specification [draft] 2 | 3 | ## Overview 4 | 5 | ### Motivation 6 | 7 | Due to common use, relative time–formatted values exist in a majority of websites and are available for the majority of frameworks (e.g., React, via [react-intl](https://github.com/yahoo/react-intl) and [react-globalize](https://github.com/globalizejs/react-globalize); Ember, via [ember-intl](https://github.com/ember-intl/ember-intl)). Popular localization libraries like [Moment.js](https://momentjs.com/), [Format.js](https://formatjs.io/), [Globalize](http://globalizejs.com/), and [others](https://github.com/rxaviers/javascript-globalization) have implemented a formatting process for relative time values as well. 8 | 9 | It is highly probable that the majority of current relative time formatting implementations require a large portion of CLDR raw or compiled data to format relative time values. Bringing this into the platform will improve performance of the web and developer productivity as they no longer have to bring extra weight to format relative time values. 10 | 11 | ### Usage examples 12 | 13 | The following example shows how to create a relative time formatter using the English language. 14 | 15 | > Units : "year", "quarter", "month", "week", "day", "hour", "minute" and "second". 16 | 17 | ```js 18 | // Create a relative time formatter in your locale 19 | // with default values explicitly passed in. 20 | const rtf = new Intl.RelativeTimeFormat("en", { 21 | localeMatcher: "best fit", // other values: "lookup" 22 | numeric: "always", // other values: "auto" 23 | style: "long", // other values: "short" or "narrow" 24 | }); 25 | 26 | 27 | // Format relative time using negative value (-1). 28 | rtf.format(-1, "day"); 29 | // > "1 day ago" 30 | 31 | // Format relative time using positive value (1). 32 | rtf.format(1, "day"); 33 | // > "in 1 day" 34 | 35 | ``` 36 | 37 | > Note: If `numeric:auto` option is passed, it will produce the string `yesterday` or `tomorrow` instead of `1 day ago` or `in 1 day`, this allows to not always have to use numeric values in the output. 38 | 39 | ```js 40 | // Create a relative time formatter in your locale 41 | // with numeric: "auto" option value passed in. 42 | const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); 43 | 44 | // Format relative time using negative value (-1). 45 | rtf.format(-1, "day"); 46 | // > "yesterday" 47 | 48 | // Format relative time using positive day unit (1). 49 | rtf.format(1, "day"); 50 | // > "tomorrow" 51 | ``` 52 | 53 | ### Implementation Status 54 | 55 | __Stage 4__ 56 | 57 | Implementation Progress 58 | 59 | - [V8 v7.1.179](https://bugs.chromium.org/p/v8/issues/detail?id=7869), shipped in Chrome 71 60 | - Shipped in Firefox 65 61 | - [Polyfills](#polyfills) are available 62 | - [Browser compatibility](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RelativeTimeFormat#Browser_compatibility) 63 | 64 | Backpointers 65 | 66 | - https://github.com/tc39/ecma402/issues/35 67 | 68 | #### Polyfills 69 | 70 | There're several polyfills available which are listed in the comparison table below. The functionality of all polyfills is the same in terms of the API: they only differ in their implementation details like the way the polyfill is imported or the way locales are loaded or whether the implementation passes the [Official ECMAScript Conformance Test](https://github.com/tc39/test262) for the complete coverage of all possible edge cases. 71 | 72 | Polyfill | [`intl-relative-time-format`](https://www.npmjs.com/package/intl-relative-time-format) | [`@formatjs/intl-relativetimeformat`](https://www.npmjs.com/package/@formatjs/intl-relativetimeformat) | [`relative-time-format`](https://www.npmjs.com/package/relative-time-format) 73 | --- | --- | --- | --- 74 | Requirements | [Requirements](https://www.npmjs.com/package/intl-relative-time-format#dependencies--browser-support): [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat), [`Intl.PluralRules`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/PluralRules), [`Intl.getCanonicalLocales`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_ObjectsGlobal_Objects/Intl/getCanonicalLocales), `Object.is`, `WeakMap` and others | [Requirements](https://www.npmjs.com/package/@formatjs/intl-relativetimeformat#requirements): [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat), [`Intl.PluralRules`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/PluralRules), [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) | No requirements 75 | Core bundle size (gzipped) | ![3.2 kB](https://img.shields.io/bundlephobia/minzip/intl-relative-time-format.svg?label=size) | ![2.7 kB](https://img.shields.io/bundlephobia/minzip/@formatjs/intl-relativetimeformat.svg?label=size) | ![3.0 kB](https://img.shields.io/bundlephobia/minzip/relative-time-format.svg?label=size) 76 | | Passes the [Official ECMAScript Conformance Test](https://github.com/tc39/test262) | ✔️ [Yes](https://github.com/wessberg/intl-relative-time-format#description) | ✔️ [Yes](https://www.npmjs.com/package/@formatjs/intl-relativetimeformat#tests) | [No](https://github.com/catamphetamine/relative-time-format#test262) 77 | 78 | #### Authors 79 | 80 | - Caridy Patiño (@caridy) 81 | - Eric Ferraiuolo (@ericf) 82 | - Zibi Braniecki (@zbraniecki) 83 | - Rafael Xavier (@rxaviers) 84 | - Daniel Ehrenberg (@littledan) 85 | 86 | #### Reviewers 87 | 88 | TBD 89 | 90 | ## Proposal 91 | 92 | `Intl.RelativeTimeFormat` is a low level API to facilitate libraries and frameworks to format relative time in a localized fashion by providing internationalized messages for date and time fields, using customary word or phrase when available. 93 | 94 | ### Spec 95 | 96 | You can view the [spec text](spec/relativetimeformat.html) or rendered as [HTML](https://rawgit.com/tc39/proposal-intl-relative-time/master/index.html). 97 | 98 | ### Technical Design 99 | 100 | This proposal is based on the ICU Relative Date Time Formatter and on the Unicode CLDR Calendar Fields Relative values: 101 | 102 | - http://icu-project.org/apiref/icu4j/com/ibm/icu/text/RelativeDateTimeFormatter.html 103 | - https://unicode.org/reports/tr35/tr35-dates.html#Calendar_Fields 104 | 105 | It is also based on the LDML spec, C.11 Language Plural Rules: 106 | 107 | - https://unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules 108 | 109 | #### Prior Art 110 | 111 | ##### Java 112 | 113 | - [ICU](http://icu-project.org/apiref/icu4j/com/ibm/icu/text/RelativeDateTimeFormatter.html): `com.ibm.icu.impl.RelativeDateFormat` 114 | - `org.ocpsoft.prettytime.PrettyTime` 115 | 116 | ##### Ruby 117 | 118 | ```ruby 119 | include ActionView::Helpers::DateHelper 120 | def index 121 |  @friendly_date = time_ago_in_words(Date.today - 1) 122 | end 123 | ``` 124 | 125 | #### Naming 126 | 127 | For consistency with `Intl.NumberFormat` and `Intl.DateTimeFormat`, we have chosen a similar form for this new feature. The creation of an `Intl.RelativeTimeFormat` instance is an expensive operation that requires resolution of locale data, and most likely, libraries will attempt to cache those instances, just like they do for `Intl.NumberFormat` and `Intl.DateTimeFormat`. 128 | 129 | We have also chosen `style` as the primary form of switching between different formatting forms for consistency with `Intl.NumberFormat` and `Intl.DateTimeFormat`. 130 | 131 | Since this new feature does format a provided value just like instances of `Intl.NumberFormat`, and `Intl.DateTimeFormat`, we have chosen the same form by providing a `format(value)` method of the instance, which returns a formatted string value. 132 | 133 | #### Take number instead of date object for input 134 | 135 | Relative time is used to display date distances, therefore the natural form of input should intuitively be a date object. Although, in this API we chose to take a number instead due to the following reasons: 136 | 137 | 1. Basically, taking a number as input for the format method instead of a date object significantly simplifies the scope of this proposal while it still fully addresses the main objective which is to provide i18n building blocks to address this problem realm. 138 | 2. Taking a date object means we should implement the comparison logic (relative time is about date distance between target and source dates). The source date is usually *now*, but not always. We would have to address modifying that. See [#4](https://github.com/tc39/proposal-intl-relative-time/issues/4). 139 | 3. Taking a date object also means we should allow for different calendar calculations, which implies `Date` should support it. See [#6](https://github.com/tc39/proposal-intl-relative-time/issues/6) and [#13](https://github.com/tc39/proposal-intl-relative-time/issues/13). 140 | 4. Taking a date object suggests we should be able to implement a *bestFit* algorithm, which has its own API challenges with respect to standardizing an approach that works for all cases. See [#7](https://github.com/tc39/proposal-intl-relative-time/issues/7), [#14](https://github.com/tc39/proposal-intl-relative-time/issues/14), and [#15](https://github.com/tc39/proposal-intl-relative-time/issues/15). We'd probably need to provide a flag for users to fill, with no default setting, to choose between options for calendar calculation. 141 | 142 | #### Take number as input rather than exposing the underlying database 143 | 144 | An idea has been floated, in the context of "the extensible web", of just exposing the engine's copy of the CLDR database rather than a higher-level interface would be better. In the case of this specification, there is already a JS object model ready to go--the locale database is represented internally in the spec as a JavaScript object. 145 | 146 | However, we opted not to go that route for a couple reasons: 147 | - As described above, the API is already fairly low-level, taking numbers rather than dates. 148 | - Although there are clearly use cases for different policies about rounding dates into units, we haven't come across a use case for seeing the underlying data. 149 | - This new API is analogous to previous APIs, which should be useful for people learning the system. 150 | - CLDR changes schema over time; if the data model improves, implementations can transparently upgrade users to better results with the same API. However, if we freeze things to the current logic, the old data model would need to be emulated. 151 | 152 | #### Difference between this and `UnitFormat` 153 | 154 | The fundamental difference between `RelativeTimeFormat` and `UnitFormat` is that `RelativeTimeFormat` displays a relative unit (e.g., `5 days ago` or `in 5 days`) while `UnitFormat` displays an absolute unit (e.g., `-5 meters` or `5 meters`). Note that `RelativeTimeFormat` uses different internationalized messages based on the value sign direction, while `UnitFormat` uses the same internationalized message for all values. 155 | 156 | ##### Countdowns, e.g., 15 days, 0 hours, 27 minutes, and 52 seconds 157 | 158 | A countdown for example is a mix of `UnitFormat` and `ListFormat`, and is not a `RelativeTimeFormat`. 159 | 160 | #### NumberFormat options (e.g., useGrouping, maximumFractionDigits) 161 | 162 | RelativeTimeFormat messages may include number parts (e.g., the `1,000` in `1,000 days ago`), which are formatted using the NumberFormat default options. 163 | 164 | In this design, we didn't find any use case that could justify allowing to change/override these NumberFormat default options. Therefore, RelativeTimeFormat doesn't include any NumberFormat option. 165 | 166 | ### API 167 | 168 | #### `Intl.RelativeTimeFormat([locales[, options]])` 169 | 170 | The `Intl.RelativeTimeFormat` object is a constructor for objects that enable language-sensitive relative time formatting. 171 | 172 | #### `locales` 173 | 174 | Optional. A string with a BCP 47 language tag, or an array of such strings. For the general form and interpretation of the `locales` argument, see the [`Intl` page](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#Locale_identification_and_negotiation). 175 | 176 | #### `options` 177 | 178 | Optional. An object with some or all of the following properties: 179 | 180 | ##### `localeMatcher` 181 | 182 | The locale matching algorithm to use. Possible values are `"lookup"` and `"best fit"`; the default is `"best fit"`. For information about this option, see the [`Intl` page](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#Locale_negotiation). 183 | 184 | ##### `numeric` 185 | The format of output message. Possible values are `"always"` (default, e.g., `1 day ago`), or `"auto"` (e.g., `yesterday`). `"auto"` allows to not always have to use numeric values in the output. 186 | 187 | ##### `style` 188 | 189 | The length of the internationalized message. Possible values are: `"long"` (default, e.g., `in 1 month`); `"short"` (e.g., `in 1 mo.`), or `"narrow"` (e.g., `in 1 mo.`). The narrow style could be similar to the short style for some locales. 190 | 191 | #### Example 192 | 193 | ```js 194 | // Create a relative time formatter in your locale. 195 | let rtf = new Intl.RelativeTimeFormat("en", { 196 | localeMatcher: "best fit", // other values: "lookup" 197 | numeric: "always", // other values: "auto" 198 | style: "long", // other values: "short" or "narrow" 199 | }); 200 | ``` 201 | 202 | ### `Intl.RelativeTimeFormat.prototype.format(value, unit)` 203 | 204 | The `Intl.RelativeTimeFormat.prototype.format` method formats a `value` and `unit` according to the locale and formatting options of this `Intl.RelativeTimeFormat` object. 205 | 206 | While this method automatically provides the correct plural forms, the grammatical form is otherwise as neutral as possible. It is the caller's responsibility to handle cut-off logic such as deciding between displaying "in 7 days" or "in 1 week". This API does not support relative dates involving compound units. e.g "in 5 days and 4 hours". 207 | 208 | #### `value` 209 | 210 | Numeric value to use in the internationalized relative time message. 211 | 212 | #### `unit` 213 | 214 | Unit to use in the relative time internationalized message. Possible values are: `"year"`, `"quarter"`, `"month"`, `"week"`, `"day"`, `"hour"`, `"minute"`, `"second"`. Plural forms are also permitted. 215 | 216 | #### Example 217 | 218 | ```js 219 | const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); 220 | 221 | // Format relative time using the day unit. 222 | rtf.format(-1, "day"); 223 | // > "yesterday" 224 | 225 | rtf.format(2.15, "day"); 226 | // > "in 2.15 days" 227 | 228 | rtf.format(100, "day"); 229 | // > "in 100 days" 230 | 231 | rtf.format(0, "day"); 232 | // > "today" 233 | 234 | rtf.format(-0, "day"); 235 | // > "today" 236 | ``` 237 | 238 | Additionally, by combining the class option `style` and `unit`, you can achieve any of the following results: 239 | 240 | ```text 241 | last year 242 | this year 243 | next year 244 | in 1 year 245 | in 2 years 246 | 1 year ago 247 | 2 years ago 248 | yr. 249 | last yr. 250 | this yr. 251 | next yr. 252 | in 1 yr. 253 | in 2 yr. 254 | 1 yr. ago 255 | 2 yr. ago 256 | last quarter 257 | this quarter 258 | next quarter 259 | in 1 quarter 260 | in 2 quarters 261 | 1 quarter ago 262 | 2 quarters ago 263 | last qtr. 264 | this qtr. 265 | next qtr. 266 | in 1 qtr. 267 | in 2 qtrs. 268 | 1 qtr. ago 269 | 2 qtrs. ago 270 | last month 271 | this month 272 | next month 273 | in 1 month 274 | in 2 months 275 | 1 month ago 276 | 2 months ago 277 | last mo. 278 | this mo. 279 | next mo. 280 | in 1 mo. 281 | in 2 mo. 282 | 1 mo. ago 283 | 2 mo. ago 284 | last week 285 | this week 286 | next week 287 | in 1 week 288 | in 2 weeks 289 | 1 week ago 290 | 2 weeks ago 291 | last wk. 292 | this wk. 293 | next wk. 294 | in 1 wk. 295 | in 2 wk. 296 | 1 wk. ago 297 | 2 wk. ago 298 | in 1 day 299 | in 2 days 300 | 1 day ago 301 | 2 days ago 302 | yesterday 303 | today 304 | tomorrow 305 | in 1 hour 306 | in 2 hours 307 | 1 hour ago 308 | 2 hours ago 309 | in 1 hr. 310 | in 2 hr. 311 | 1 hr. ago 312 | 2 hr. ago 313 | in 1 minute 314 | in 2 minutes 315 | 1 minute ago 316 | 2 minutes ago 317 | in 1 min. 318 | in 2 min. 319 | 1 min. ago 320 | 2 min. ago 321 | in 1 second 322 | in 2 seconds 323 | 1 second ago 324 | 2 seconds ago 325 | in 1 sec. 326 | in 1 sec. 327 | 1 sec. ago 328 | 2 sec. ago 329 | now 330 | ``` 331 | 332 | ### `Intl.RelativeTimeFormat.prototype.formatToParts(value, unit)` 333 | 334 | The `Intl.RelativeTimeFormat.prototype.formatToParts` method is a version of the `format` method which it returns an array of objects which represent "parts" of the object, separating the formatted number into its consituent parts and separating it from other surrounding text. These objects have two properties: `type` a NumberFormat formatToParts type, and `value`, which is the String which is the component of the output. If a "part" came from NumberFormat, it will have a `unit` property which indicates the unit being formatted; literals which are part of the larger frame will not have this property. 335 | 336 | #### Example 337 | 338 | ```js 339 | const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); 340 | 341 | // Format relative time using the day unit. 342 | rtf.formatToParts(-1, "day"); 343 | // > [{ type: "literal", value: "yesterday"}] 344 | 345 | rtf.formatToParts(100, "day"); 346 | // > [{ type: "literal", value: "in " }, { type: "integer", value: "100", unit: "day" }, { type: "literal", value: " days" }] 347 | ``` 348 | 349 | ## Development 350 | 351 | ### Render Spec 352 | 353 | ``` 354 | npm install 355 | npm run build 356 | open index.html 357 | ``` 358 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ev 4 | 5 | # Enable SSH authentication 6 | 7 | ENCRYPTED_KEY_VAR="encrypted_${ENCRYPTION_LABEL}_key" 8 | ENCRYPTED_IV_VAR="encrypted_${ENCRYPTION_LABEL}_iv" 9 | ENCRYPTED_KEY=${!ENCRYPTED_KEY_VAR} 10 | ENCRYPTED_IV=${!ENCRYPTED_IV_VAR} 11 | 12 | $(npm bin)/set-up-ssh --key "$ENCRYPTED_KEY" \ 13 | --iv "$ENCRYPTED_IV" \ 14 | --path-encrypted-key "$PRIVATE_KEY_FILE_NAME" 15 | 16 | # Update the content from the `gh-pages` branch 17 | 18 | $(npm bin)/update-branch --commands "mkdir out && ecmarkup spec/index.html out/index.html" \ 19 | --commit-message "Update gh-pages [skip ci]" \ 20 | --directory "out" \ 21 | --distribution-branch "gh-pages" \ 22 | --source-branch "master" 23 | 24 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 = '' 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 | -------------------------------------------------------------------------------- /github_deploy_key.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tc39/proposal-intl-relative-time/a66f6928d4ea20a9b88aae3752c32d7693d7d9cf/github_deploy_key.enc -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Intl.RelativeTimeFormat Spec Proposal

    July 14, 2017

    Intl.RelativeTimeFormat Spec Proposal

    8 | 9 |

    1RelativeTimeFormat Objects

    10 | 11 | 12 |

    1.1Abstract Operations for RelativeTimeFormat Objects

    13 | 14 | 15 | 16 |

    1.1.1InitializeRelativeTimeFormat (relativeTimeFormat, locales, options)

    17 | 18 |

    19 | The abstract operation InitializeRelativeTimeFormat accepts the arguments relativeTimeFormat (which must be an object), locales, and options. It initializes relativeTimeFormat as a RelativeTimeFormat object. It performas the following steps: 20 | 21 |

    22 | 23 |
    1. If relativeTimeFormat has an [[InitializedIntlObject]] internal slot with value true, throw a TypeError exception.
    2. Set relativeTimeFormat.[[InitializedIntlObject]] to true.
    3. Let requestedLocales be ? CanonicalizeLocaleList(locales).
    4. If options is undefined, then
      1. Let options be ObjectCreate(%ObjectPrototype%).
    5. Else
      1. Let options be ? ToObject(options).
    6. Let opt be a new Record.
    7. Let matcher be ? GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit").
    8. Set opt.[[LocaleMatcher]] to matcher.
    9. Let localeData be %RelativeTimeFormat%.[[LocaleData]].
    10. Let r be ResolveLocale(%RelativeTimeFormat%.[[AvailableLocales]], requestedLocales, opt, %RelativeTimeFormat%.[[RelevantExtensionKeys]], localeData).
    11. Set relativeTimeFormat.[[Locale]] to the value of r.[[Locale]].
    12. Let dataLocale be r.[[DataLocale]].
    13. Let s be ? GetOption(options, "style", "string", «"long", "short", "narrow"», "long").
    14. Set relativeTimeFormat.[[Style]] to s.
    15. Let dataLocaleData be Get(localeData, dataLocale).
    16. Let fields be Get(dataLocaleData, "fields").
    17. Assert: fields is an object (see 1.3.3).
    18. Set relativeTimeFormat.[[Fields]] to fields.
    19. Let nfLocale be CreateArrayFromListlocale »).
    20. Let nfOptions be ObjectCreate(%ObjectPrototype%).
    21. Perform ! CreateDataPropertyOrThrow(nfOptions, "useGrouping", false).
    22. Perform ! CreateDataPropertyOrThrow(nfOptions, "minimumIntegerDigits", 2).
    23. Let relativeTimeFormat.[[NumberFormat]] be ? Construct(%NumberFormat%, « nfLocale, nfOptions »).
    24. Let prLocale be CreateArrayFromListlocale »).
    25. Let prOptions be ObjectCreate(%ObjectPrototype%).
    26. Perform ! CreateDataPropertyOrThrow(prOptions, "style", "cardinal").
    27. Let relativeTimeFormat.[[PluralRules]] be ? Construct(%PluralRules%, « prLocale, prOptions »).
    28. Set relativeTimeFormat.[[InitializedRelativeTimeFormat]] to true.
    29. Return relativeTimeFormat. 24 |
    25 |
    26 | 27 | 28 |

    1.1.2PartitionRelativeTimePattern (relativeTimeFormat, value, unit)

    29 | 30 |

    31 | When the FormatRelativeTime abstract operation is called with arguments relativeTimeFormat, value, and unit it returns a String value representing value (interpreted as a time value as specified in ES2016, 20.3.1.1) according to the effective locale and the formatting options of relativeTimeFormat. 32 | 33 |

    34 | 35 |
    1. Assert: relativeTimeFormat.[[InitializedRelativeTimeFormat]] is true.
    2. Assert: Type(value) is a Number.
    3. Assert: Type(unit) is a String.
    4. If isFinite(value) is false, then throw a RangeError exception.
    5. If unit is not one of "second", "minute", "hour", "day", "week", "month", "quarter", "year", throw a RangeError exception.
    6. Let fields be relativeTimeFormat.[[Fields]].
    7. Let style be relativeTimeFormat.[[Style]].
    8. If style is equal to "short", then
      1. Let entry be unit + "-short".
    9. Else if style is equal to "narrow", then
      1. Let entry be unit + "-narrow".
    10. Else
      1. Let entry be unit.
    11. Let exists be ? HasProperty(fields, entry).
    12. If exists is false, then
      1. Let entry be unit.
    13. Let patterns be ? Get(fields, entry).
    14. Let exists be ? HasProperty(patterns, ToString(value)).
    15. If exists is true, then
      1. Let result be Get(patterns, ToString(value)).
      2. Return a List containing the Record { [[Type]]: "literal", [[Value]]: result }.
    16. If value is less than 0, then
      1. Let tl be "past".
    17. Else
      1. Let tl be "future".
    18. Let po be ? Get(patterns, tl).
    19. Let fv be ! FormatNumber(relativeTimeFormat.[[NumberFormat]], value).
    20. Let pr be ! ResolvePlural(relativeTimeFormat.[[PluralRules]], value).
    21. Let pattern be ? Get(po, pr).
    22. Return ? MakePartsList(pattern, unit, fv). 36 |
    37 |
    38 | 39 | 40 |

    1.1.3MakePartsList ( parts, unit, value )

    41 | 42 |

    43 | The MakePartsList abstract operation is called with arguments parts, a pattern String, unit, a String, and value a String. 44 | 45 | Note

    46 | Example: 47 | 48 |

              MakePartsList("AA{0}BB", "hour", "15")  --<
     49 | 
     50 |         Output (List of Records):
     51 |           [
     52 |             {type: 'literal', value: 'AA'},
     53 |             {type: 'hour', value: '15'},
     54 |             {type: 'literal', value: 'BB'},
     55 |           ]
     56 |         
    57 |

    58 |
    1. Let result be a new empty List.
    2. Let beginIndex be Call(%StringProto_indexOf%, pattern, "{", 0).
    3. Let length be ! Get(parts, "length").
    4. Assert: length is a non-negative integer.
    5. Repeat while beginIndex is an integer index into pattern:
      1. Set endIndex to Call(%StringProto_indexOf%, pattern, "}", beginIndex)
      2. Assert: endIndex is not -1, otherwise the pattern would be malformed.
      3. If beginIndex is greater than nextIndex, then:
        1. Let literal be a substring of pattern from position nextIndex, inclusive, to position beginIndex, exclusive.
        2. Add new part record { [[Type]]: "literal", [[Value]]: literal } as a new element of the list result.
      4. Let p be the substring of pattern from position beginIndex, exclusive, to position endIndex, exclusive.
      5. Assert: p is "0".
      6. Add new part Record { [[Type]]: unit, [[Value]]: value } as a new element on the List result.
    6. If nextIndex is less than length, then:
      1. Let literal be the substring of pattern from position nextIndex, exclusive, to position length, exclusive.
      2. Add new part record { [[Type]]: "literal", [[Value]]: literal } as a new element of the list result.
    7. Return result. 59 |
    60 |
    61 | 62 | 63 |

    1.1.4FormatRelativeTime (relativeTimeFormat, value, unit)

    64 | 65 |

    66 | The FormatRelativeTime abstract operation is called with arguments relativeTimeFormat (which must be an object initialized as a RelativeTimeFormat), value (which must be a Number value), and unit (which must be a String denoting the value unit) and performs the following steps: 67 | 68 |

    69 | 70 |
    1. Let parts be ? PartitionRelativeTimePattern(relativeTimeFormat, value, unit).
    2. Let result be an empty String.
    3. For each part in parts, do:
      1. Set result to a String value produced by concatenating result and part.[[Value]].
    4. Return result. 71 |
    72 |
    73 | 74 | 75 |

    1.1.5FormatRelativeTimeToParts (relativeTimeFormat, value, unit)

    76 | The FormatRelativeTimeToParts abstract operation is called with arguments relativeTimeFormat (which must be an object initialized as a RelativeTimeFormat), value (which must be a Number value), and unit (which must be a String denoting the value unit) and performs the following steps: 77 | 78 |

    79 |

    80 | 81 |
    1. Let parts be ? PartitionRelativeTimePattern(relativeTimeFormat, value, unit).
    2. Let result be ArrayCreate(0).
    3. Let n be 0.
    4. For each part in parts, do:
      1. Let O be ObjectCreate(%ObjectPrototype%).
      2. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]).
      3. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]).
      4. Perform ! CreateDataPropertyOrThrow(result, ! ToString(n), O).
      5. Increment n by 1.
    5. Return result. 82 |
    83 |
    84 |
    85 | 86 | 87 |

    1.2The Intl.RelativeTimeFormat Constructor

    88 | 89 |

    90 | The RelativeTimeFormat constructor is a standard built-in property of the Intl object. Behaviour common to all service constructor properties of the Intl object is specified in . 91 | 92 |

    93 | 94 | 95 |

    1.2.1Intl.RelativeTimeFormat ([ locales [ , options ]])

    96 | 97 |

    98 | When the Intl.RelativeTimeFormat function is called with optional arguments the following steps are taken: 99 | 100 |

    101 | 102 |
    1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget.
    2. Let relativeTimeFormat be ! OrdinaryCreateFromConstructor(newTarget, %RelativeTimeFormatPrototype%).
    3. Return ? InitializeRelativeTimeFormat(relativeTimeFormat, locales, options). 103 |
    104 |
    105 |
    106 | 107 | 108 |

    1.3Properties of the Intl.RelativeTimeFormat Constructor

    109 | 110 |

    111 | The Intl.RelativeTimeFormat constructor has the following properties: 112 | 113 |

    114 | 115 | 116 |

    1.3.1Intl.RelativeTimeFormat.prototype

    117 | 118 |

    119 | The value of Intl.RelativeTimeFormat.prototype is %RelativeTimeFormatPrototype%. 120 | 121 |

    122 |

    123 | This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }. 124 | 125 |

    126 |
    127 | 128 | 129 |

    1.3.2Intl.RelativeTimeFormat.supportedLocalesOf (locales [, options ])

    130 | 131 |

    132 | When the supportedLocalesOf method of %RelativeTimeFormat% is called, the following steps are taken: 133 | 134 |

    135 | 136 |
    1. Let availableLocales be %RelativeTimeFormat%.[[AvailableLocales]].
    2. Let requestedLocales be CanonicalizeLocaleList(locales).
    3. Return SupportedLocales(availableLocales, requestedLocales, options). 137 |
    138 | 139 |

    140 | The value of the length property of the supportedLocalesOf method is 1. 141 | 142 |

    143 |
    144 | 145 | 146 |

    1.3.3Internal slots

    147 | 148 |

    149 | The value of the [[AvailableLocales]] internal slot is implementation defined within the constraints described in . 150 | 151 |

    152 | 153 |

    154 | The value of the [[RelevantExtensionKeys]] internal slot is []. 155 | 156 |

    157 | 158 | Note 1
    159 | Unicode Technical Standard 35 describes no locale extension keys that are relevant to relative time formatting. 160 | 161 |
    162 | 163 |

    164 | The value of the [[LocaleData]] internal slot is implementation defined within the constraints described in and the following additional constraints: 165 | 166 |

    167 | 168 |
      169 |
    • [[LocaleData]][Locale] has ordinary data properties "second", "minute", "hour", "day", "week", "month", "quarter" and "year". Additional property keys may exist with the previous names concatenated with the strings "-narrow" or "-short". The values corresponding to these property keys are objects which contain these two categories of properties:
    • 170 |
      • 171 |
      • "future" and "past" properties, which are objects with a property for each of the [[PluralCategories]] in the Locale. The value corresponding to those properties is a pattern which may contain "{0}" to be replaced by a formatted number.
      • 172 |
      • Optionally, additional properties whose key is the result of ToString of a Number, and whose values are literal Strings which are not treated as templates.
      • 173 |
      174 |
    175 | 176 | Note 2
    177 | It is recommended that implementations use the locale data provided by the Common Locale Data Repository (available at http://cldr.unicode.org/). 178 | 179 |
    180 |
    181 |
    182 | 183 | 184 |

    1.4Properties of the Intl.RelativeTimeFormat Prototype Object

    185 | 186 |

    187 | The Intl.RelativeTimeFormat prototype object is itself an ordinary object. %RelativeTimeFormatPrototype% is not an Intl.RelativeTimeFormat instance and does not have an [[InitializedRelatimveTimeFormat]] internal slot or any of the other internal slots of Intl.RelativeTimeFormat instance objects. 188 | 189 |

    190 |

    191 | In the following descriptions of functions that are properties or [[Get]] attributes of properties of %RelativeTimeFormatPrototype%, the phrase "this RelativeTimeFormat object" refers to the object that is the this value for the invocation of the function; a TypeError exception is thrown if the this value is not an object or an object that does not have an [[InitializedRelativeTimeFormat]] internal slot with value true. 192 | 193 |

    194 | 195 | 196 |

    1.4.1Intl.RelativeTimeFormat.prototype.constructor

    197 | 198 |

    199 | The initial value of Intl.RelativeTimeFormat.prototype.constructor is %RelativeTimeFormat%. 200 | 201 |

    202 |
    203 | 204 | 205 |

    1.4.2Intl.RelativeTimeFormat.prototype[ @@toStringTag ]

    206 | 207 |

    208 | The initial value of the @@toStringTag property is the string value "Object". 209 | 210 |

    211 |

    212 | This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }. 213 | 214 |

    215 |
    216 | 217 | 218 |

    1.4.3Intl.RelativeTimeFormat.prototype.format( value, unit )

    219 | 220 |

    221 | When the Intl.RelativeTimeFormat.prototype.format is called with an optional argument value, the following steps are taken: 222 | 223 |

    224 | 225 |
    1. Let relativeTimeFormat be this value.
    2. If Type(relativeTimeFormat) is not Object or relativeTimeFormat does not have an [[InitializedRelativeTimeFormat]] internal slot whose value is true, throw a TypeError exception.
    3. Let value be ? ToNumber(value).
    4. Let unit be ? ToString(unit).
    5. Return ? FormatRelativeTime(relativeTimeFormat, value, unit). 226 |
    227 |
    228 | 229 | 230 |

    1.4.4Intl.RelativeTimeFormat.prototype.formatToParts( value, unit )

    231 | 232 |

    233 | When the Intl.RelativeTimeFormat.prototype.formatToParts is called with an optional argument value, the following steps are taken: 234 | 235 |

    236 | 237 |
    1. Let relativeTimeFormat be this value.
    2. If Type(relativeTimeFormat) is not Object or relativeTimeFormat does not have an [[InitializedRelativeTimeFormat]] internal slot whose value is true, throw a TypeError exception.
    3. Let value be ? ToNumber(value).
    4. Let unit be ? ToString(unit).
    5. Return ? FormatRelativeTimeToParts(relativeTimeFormat, value, unit). 238 |
    239 |
    240 | 241 | 242 |

    1.4.5Intl.RelativeTimeFormat.prototype.resolvedOptions ()

    243 | 244 |

    245 | This function provides access to the locale and formatting options computed during initialization of the object. 246 | 247 |

    248 |

    249 | The function returns a new object whose properties and attributes are set as if constructed by an object literal assigning to each of the following properties the value of the corresponding internal slot of this RelativeTimeFormat object (see 1.5): locale and style. 250 | 251 |

    252 |
    253 |
    254 | 255 | 256 |

    1.5Properties of Intl.RelativeTimeFormat Instances

    257 | 258 |

    259 | Intl.RelativeTimeFormat instances inherit properties from %RelativeTimeFormatPrototype%. 260 | 261 |

    262 | 263 |

    264 | Intl.RelativeTimeFormat instances and other objects that have been successfully initialized as a RelativeTimeFormat have [[InitializedIntlObject]] and [[InitializedRelativeTimeFormat]] internal slots whose values are true. 265 | 266 |

    267 | 268 |

    269 | Objects that have been successfully initialized as a RelativeTimeFormat object also have several internal slots that are computed by the constructor: 270 | 271 |

    272 | 273 |
      274 |
    • [[Locale]] is a String value with the language tag of the locale whose localization is used for formatting.
    • 275 |
    • [[Style]] is one of the String values "long", "short", or "narrow", identifying the relative time format style used.
    • 276 |
    277 |
    278 |
    279 |
    280 |
    -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intl-relative-time-spec", 3 | "version": "1.0.0-pre-draft", 4 | "description": "Intl.RelativeTime API Specification [Draft]", 5 | "scripts": { 6 | "build": "ecmarkup spec/index.html index.html --css ecmarkup.css --js ecmarkup.js", 7 | "watch": "ecmarkup --watch spec/index.html index.html --css ecmarkup.css --js ecmarkup.js" 8 | }, 9 | "private": true, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/tc39/intl-relative-time-spec.git" 13 | }, 14 | "devDependencies": { 15 | "ecmarkup": "latest", 16 | "@alrra/travis-scripts": "^3.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /spec/biblio.json: -------------------------------------------------------------------------------- 1 | { 2 | "https://tc39.github.io/ecma262/": [ 3 | { 4 | "type": "op", 5 | "aoid": "string-concatenation", 6 | "id": "sec-ecmascript-language-types-string-type" 7 | } 8 | ], 9 | "https://tc39.github.io/ecma402/": [ 10 | { 11 | "type": "op", 12 | "aoid": "GetOption", 13 | "id": "sec-getoption" 14 | }, 15 | { 16 | "type": "op", 17 | "aoid": "ResolveLocale", 18 | "id": "sec-resolvelocale" 19 | }, 20 | { 21 | "type": "op", 22 | "aoid": "CanonicalizeLocaleList", 23 | "id": "sec-canonicalizelocalelist" 24 | }, 25 | { 26 | "type": "op", 27 | "aoid": "SupportedLocales", 28 | "id": "sec-supportedlocales" 29 | }, 30 | { 31 | "type": "op", 32 | "aoid": "PartitionNumberPattern", 33 | "id": "sec-partitionnumberpattern" 34 | }, 35 | { 36 | "type": "op", 37 | "aoid": "ResolvePlural", 38 | "id": "sec-resolveplural" 39 | }, 40 | { 41 | "type": "term", 42 | "term": "%StringProto_indexOf%", 43 | "id": "sec-402-well-known-intrinsic-objects" 44 | }, 45 | { 46 | "type": "clause", 47 | "number": "9.1", 48 | "id": "sec-internal-slots" 49 | }, 50 | { 51 | "type": "op", 52 | "aoid": "PartitionPattern", 53 | "id": "sec-partitionpattern" 54 | } 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /spec/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
     8 |   title: Intl.RelativeTimeFormat Spec Proposal
     9 |   stage: 3
    10 |   contributors: Caridy Patiño, Eric Ferraiuolo, Zibi Braniecki, Rafael Xavier, Daniel Ehrenberg
    11 |   status: draft
    12 |   copyright: false
    13 |   location: https://rawgit.com/tc39/proposal-intl-relative-time/master/index.html
    14 | 
    15 | 16 | 17 | -------------------------------------------------------------------------------- /spec/relativetimeformat.html: -------------------------------------------------------------------------------- 1 | 2 |

    RelativeTimeFormat Objects

    3 | 4 | 5 |

    Abstract Operations for RelativeTimeFormat Objects

    6 | 7 | 8 | 9 |

    InitializeRelativeTimeFormat ( _relativeTimeFormat_, _locales_, _options_ )

    10 | 11 |

    12 | The abstract operation InitializeRelativeTimeFormat accepts the arguments _relativeTimeFormat_ (which must be an object), _locales_, and _options_. It initializes _relativeTimeFormat_ as a RelativeTimeFormat object. 13 |

    14 |

    The following steps are taken:

    15 | 16 | 17 | 1. Let _requestedLocales_ be ? CanonicalizeLocaleList(_locales_). 18 | 1. If _options_ is *undefined*, then 19 | 1. Let _options_ be ObjectCreate(*null*). 20 | 1. Else, 21 | 1. Let _options_ be ? ToObject(_options_). 22 | 1. Let _opt_ be a new Record. 23 | 1. Let _matcher_ be ? GetOption(_options_, `"localeMatcher"`, `"string"`, «`"lookup"`, `"best fit"`», `"best fit"`). 24 | 1. Set _opt_.[[LocaleMatcher]] to _matcher_. 25 | 1. Let _numberingSystem_ be ? GetOption(_options_, `"numberingSystem"`, `"string"`, *undefined*, *undefined*). 26 | 1. If _numberingSystem_ is not *undefined*, then 27 | 1. If _numberingSystem_ does not match the `(3*8alphanum) *("-" (3*8alphanum))` sequence, throw a *RangeError* exception. 28 | 1. Set _opt_.[[nu]] to _numberingSystem_. 29 | 1. Let _localeData_ be %RelativeTimeFormat%.[[LocaleData]]. 30 | 1. Let _r_ be ResolveLocale(%RelativeTimeFormat%.[[AvailableLocales]], _requestedLocales_, _opt_, %RelativeTimeFormat%.[[RelevantExtensionKeys]], _localeData_). 31 | 1. Let _locale_ be _r_.[[Locale]]. 32 | 1. Set _relativeTimeFormat_.[[Locale]] to _locale_. 33 | 1. Set _relativeTimeFormat_.[[NumberingSystem]] to _r_.[[nu]]. 34 | 1. Let _dataLocale_ be _r_.[[DataLocale]]. 35 | 1. Let _s_ be ? GetOption(_options_, `"style"`, `"string"`, «`"long"`, `"short"`, `"narrow"`», `"long"`). 36 | 1. Set _relativeTimeFormat_.[[Style]] to _s_. 37 | 1. Let _numeric_ be ? GetOption(_options_, `"numeric"`, `"string"`, «`"always"`, `"auto"`», `"always"`). 38 | 1. Set _relativeTimeFormat_.[[Numeric]] to _numeric_. 39 | 1. Let _fields_ be ! Get(_localeData_, _dataLocale_). 40 | 1. Assert: _fields_ is an object (see ). 41 | 1. Set _relativeTimeFormat_.[[Fields]] to _fields_. 42 | 1. Let _relativeTimeFormat_.[[NumberFormat]] be ! Construct(%NumberFormat%, « _locale_ »). 43 | 1. Let _relativeTimeFormat_.[[PluralRules]] be ! Construct(%PluralRules%, « _locale_ »). 44 | 1. Return _relativeTimeFormat_. 45 | 46 |
    47 | 48 | 49 |

    SingularRelativeTimeUnit ( _unit_ )

    50 | 51 | 1. Assert: Type(_unit_) is String. 52 | 1. If _unit_ is `"seconds"`, return `"second"`. 53 | 1. If _unit_ is `"minutes"`, return `"minute"`. 54 | 1. If _unit_ is `"hours"`, return `"hour"`. 55 | 1. If _unit_ is `"days"`, return `"day"`. 56 | 1. If _unit_ is `"weeks"`, return `"week"`. 57 | 1. If _unit_ is `"months"`, return `"month"`. 58 | 1. If _unit_ is `"quarters"`, return `"quarter"`. 59 | 1. If _unit_ is `"years"`, return `"year"`. 60 | 1. If _unit_ is not one of `"second"`, `"minute"`, `"hour"`, `"day"`, `"week"`, `"month"`, `"quarter"`, or `"year"`, throw a *RangeError* exception. 61 | 1. Return _unit_. 62 | 63 |
    64 | 65 | 66 |

    PartitionRelativeTimePattern ( _relativeTimeFormat_, _value_, _unit_ )

    67 | 68 |

    69 | When the FormatRelativeTime abstract operation is called with arguments _relativeTimeFormat_, _value_, and _unit_ it returns a String value representing _value_ (which must be a Number value) according to the effective locale and the formatting options of _relativeTimeFormat_. 70 |

    71 | 72 | 73 | 1. Assert: _relativeTimeFormat_ has an [[InitializedRelativeTimeFormat]] internal slot. 74 | 1. Assert: Type(_value_) is Number. 75 | 1. Assert: Type(_unit_) is String. 76 | 1. If _value_ is *NaN*, *+∞*, or *-∞*, throw a *RangeError* exception. 77 | 1. Let _unit_ be ? SingularRelativeTimeUnit(_unit_). 78 | 1. Let _fields_ be _relativeTimeFormat_.[[Fields]]. 79 | 1. Let _style_ be _relativeTimeFormat_.[[Style]]. 80 | 1. If _style_ is equal to `"short"`, then 81 | 1. Let _entry_ be the string-concatenation of _unit_ and `"-short"`. 82 | 1. Else if _style_ is equal to `"narrow"`, then 83 | 1. Let _entry_ be the string-concatenation of _unit_ and `"-narrow"`. 84 | 1. Else, 85 | 1. Let _entry_ be _unit_. 86 | 1. Let _exists_ be ! HasProperty(_fields_, _entry_). 87 | 1. If _exists_ is *false*, then 88 | 1. Let _entry_ be _unit_. 89 | 1. Let _patterns_ be ! Get(_fields_, _entry_). 90 | 1. Let _numeric_ be _relativeTimeFormat_.[[Numeric]]. 91 | 1. If _numeric_ is equal to `"auto"`, then 92 | 1. Let _exists_ be ! HasProperty(_patterns_, ! ToString(_value_)). 93 | 1. If _exists_ is *true*, then 94 | 1. Let _result_ be ! Get(_patterns_, ! ToString(_value_)). 95 | 1. Return a List containing the Record { [[Type]]: `"literal"`, [[Value]]: _result_ }. 96 | 1. If _value_ is *-0* or if _value_ is less than 0, then 97 | 1. Let _tl_ be `"past"`. 98 | 1. Else, 99 | 1. Let _tl_ be `"future"`. 100 | 1. Let _po_ be ! Get(_patterns_, _tl_). 101 | 1. Let _fv_ be ! PartitionNumberPattern(_relativeTimeFormat_.[[NumberFormat]], _value_). 102 | 1. Let _pr_ be ! ResolvePlural(_relativeTimeFormat_.[[PluralRules]], _value_). 103 | 1. Let _pattern_ be ! Get(_po_, _pr_). 104 | 1. Return ! MakePartsList(_pattern_, _unit_, _fv_). 105 | 106 |
    107 | 108 | 109 |

    MakePartsList ( _pattern_, _unit_, _parts_ )

    110 | 111 |

    112 | The MakePartsList abstract operation is called with arguments _pattern_, a pattern String, _unit_, a String, and _parts_, a List of Records representing a formatted Number. 113 | 114 | Example: 115 |

    116 |           MakePartsList("AA{0}BB", "hour", « { [[Type]]: "integer", [[Value]]: "15" } » )
    117 | 
    118 |         Output (List of Records):
    119 |           «
    120 |             { [[Type]]: "literal", [[Value]]: "AA"},
    121 |             { [[Type]]: "integer", [[Value]]: "15", [[Unit]]: "hour"},
    122 |             { [[Type]]: "literal", [[Value]]: "BB"}
    123 |           »
    124 |         
    125 |

    126 | 127 | 1. Let _patternParts_ be PartitionPattern(_pattern_). 128 | 1. Let _result_ be a new empty List. 129 | 1. For each element _patternPart_ of _patternParts_, in List order, do 130 | 1. If _patternPart_.[[Type]] is `"literal"`, then 131 | 1. Append Record { [[Type]]: `"literal"`, [[Value]]: _patternPart_.[[Value]] } to _result_. 132 | 1. Else, 133 | 1. Assert: _patternPart_.[[Type]] is `"0"`. 134 | 1. For each _part_ in _parts_, do 135 | 1. Append Record { [[Type]]: _part_.[[Type]], [[Value]]: _part_.[[Value]], [[Unit]]: _unit_ } to _result_. 136 | 1. Return _result_. 137 | 138 |
    139 | 140 | 141 |

    FormatRelativeTime ( _relativeTimeFormat_, _value_, _unit_ )

    142 | 143 |

    144 | The FormatRelativeTime abstract operation is called with arguments _relativeTimeFormat_ (which must be an object initialized as a RelativeTimeFormat), _value_ (which must be a Number value), and _unit_ (which must be a String denoting the value unit) and performs the following steps: 145 |

    146 | 147 | 148 | 1. Let _parts_ be ? PartitionRelativeTimePattern(_relativeTimeFormat_, _value_, _unit_). 149 | 1. Let _result_ be an empty String. 150 | 1. For each _part_ in _parts_, do 151 | 1. Set _result_ to the string-concatenation of _result_ and _part_.[[Value]]. 152 | 1. Return _result_. 153 | 154 |
    155 | 156 | 157 |

    FormatRelativeTimeToParts ( _relativeTimeFormat_, _value_, _unit_ )

    158 | 159 |

    160 | The FormatRelativeTimeToParts abstract operation is called with arguments _relativeTimeFormat_ (which must be an object initialized as a RelativeTimeFormat), _value_ (which must be a Number value), and _unit_ (which must be a String denoting the value unit) and performs the following steps: 161 |

    162 | 163 | 164 | 1. Let _parts_ be ? PartitionRelativeTimePattern(_relativeTimeFormat_, _value_, _unit_). 165 | 1. Let _result_ be ArrayCreate(0). 166 | 1. Let _n_ be 0. 167 | 1. For each _part_ in _parts_, do 168 | 1. Let _O_ be ObjectCreate(%ObjectPrototype%). 169 | 1. Perform ! CreateDataPropertyOrThrow(_O_, `"type"`, _part_.[[Type]]). 170 | 1. Perform ! CreateDataPropertyOrThrow(_O_, `"value"`, _part_.[[Value]]). 171 | 1. If _part_ has a [[Unit]] field, 172 | 1. Perform ! CreateDataPropertyOrThrow(_O_, `"unit"`, _part_.[[Unit]]). 173 | 1. Perform ! CreateDataPropertyOrThrow(_result_, ! ToString(_n_), _O_). 174 | 1. Increment _n_ by 1. 175 | 1. Return _result_. 176 | 177 |
    178 |
    179 | 180 | 181 |

    The Intl.RelativeTimeFormat Constructor

    182 | 183 |

    184 | The RelativeTimeFormat constructor is the %RelativeTimeFormat% intrinsic object and a standard built-in property of the Intl object. Behaviour common to all service constructor properties of the Intl object is specified in . 185 |

    186 | 187 | 188 |

    Intl.RelativeTimeFormat ([ _locales_ [ , _options_ ]])

    189 | 190 |

    191 | When the *Intl.RelativeTimeFormat* function is called with optional arguments the following steps are taken: 192 |

    193 | 194 | 195 | 1. If NewTarget is *undefined*, throw a *TypeError* exception. 196 | 1. Let _relativeTimeFormat_ be ? OrdinaryCreateFromConstructor(NewTarget, `"%RelativeTimeFormatPrototype%"`, « [[InitializedRelativeTimeFormat]], [[Locale]], [[Style]], [[Numeric]], [[Fields]], [[NumberFormat]], [[NumberingSystem]], [[PluralRules]] »). 197 | 1. Return ? InitializeRelativeTimeFormat(_relativeTimeFormat_, _locales_, _options_). 198 | 199 |
    200 |
    201 | 202 | 203 |

    Properties of the Intl.RelativeTimeFormat Constructor

    204 | 205 |

    206 | The Intl.RelativeTimeFormat constructor has the following properties: 207 |

    208 | 209 | 210 |

    Intl.RelativeTimeFormat.prototype

    211 | 212 |

    213 | The value of `Intl.RelativeTimeFormat.prototype` is %RelativeTimeFormatPrototype%. 214 |

    215 |

    216 | This property has the attributes { [[Writable]]: *false*, [[Enumerable]]: *false*, [[Configurable]]: *false* }. 217 |

    218 |
    219 | 220 | 221 |

    Intl.RelativeTimeFormat.supportedLocalesOf ( _locales_ [, _options_ ])

    222 | 223 |

    224 | When the `supportedLocalesOf` method of %RelativeTimeFormat% is called, the following steps are taken: 225 |

    226 | 227 | 228 | 1. Let _availableLocales_ be %RelativeTimeFormat%.[[AvailableLocales]]. 229 | 1. Let _requestedLocales_ be ? CanonicalizeLocaleList(_locales_). 230 | 1. Return ? SupportedLocales(_availableLocales_, _requestedLocales_, _options_). 231 | 232 |
    233 | 234 | 235 |

    Internal slots

    236 | 237 |

    238 | The value of the [[AvailableLocales]] internal slot is implementation defined within the constraints described in . 239 |

    240 | 241 |

    242 | The value of the [[RelevantExtensionKeys]] internal slot is « `"nu"` ». 243 |

    244 | 245 | 246 | Unicode Technical Standard 35 implicitly describes one locale extension key that is relevant to relative time formatting, "nu" for numbering system. 247 | 248 | 249 |

    250 | The value of the [[LocaleData]] internal slot is implementation defined within the constraints described in and the following additional constraints: 251 |

    252 | 253 |
      254 |
    • [[LocaleData]][Locale] has ordinary data properties `"second"`, `"minute"`, `"hour"`, `"day"`, `"week"`, `"month"`, `"quarter"`, and `"year"`. Additional property keys may exist with the previous names concatenated with the strings `"-narrow"` or `"-short"`. The values corresponding to these property keys are objects which contain these two categories of properties: 255 |
        256 |
      • `"future"` and `"past"` properties, which are objects with a property for each of the [[PluralCategories]] in the Locale. The value corresponding to those properties is a pattern which may contain `"{0}"` to be replaced by a formatted number.
      • 257 |
      • Optionally, additional properties whose key is the result of ToString of a Number, and whose values are literal Strings which are not treated as templates.
      • 258 |
      259 |
    • 260 |
    • 261 | The list that is the value of the `"nu"` field of any locale field of [[LocaleData]] must not include the values `"native"`, `"traditio"`, or `"finance"`. 262 |
    • 263 |
    264 | 265 | 266 | It is recommended that implementations use the locale data provided by the Common Locale Data Repository (available at http://cldr.unicode.org/). 267 | 268 |
    269 |
    270 | 271 | 272 |

    Properties of the Intl.RelativeTimeFormat Prototype Object

    273 | 274 |

    275 | The Intl.RelativeTimeFormat prototype object is itself an ordinary object. %RelativeTimeFormatPrototype% is not an Intl.RelativeTimeFormat instance and does not have an [[InitializedRelativeTimeFormat]] internal slot or any of the other internal slots of Intl.RelativeTimeFormat instance objects. 276 |

    277 | 278 | 279 |

    Intl.RelativeTimeFormat.prototype.constructor

    280 | 281 |

    282 | The initial value of `Intl.RelativeTimeFormat.prototype.constructor` is %RelativeTimeFormat%. 283 |

    284 |
    285 | 286 | 287 |

    Intl.RelativeTimeFormat.prototype[ @@toStringTag ]

    288 | 289 |

    290 | The initial value of the @@toStringTag property is the string value `"Intl.RelativeTimeFormat"`. 291 |

    292 |

    293 | This property has the attributes { [[Writable]]: *false*, [[Enumerable]]: *false*, [[Configurable]]: *true* }. 294 |

    295 |
    296 | 297 | 298 |

    Intl.RelativeTimeFormat.prototype.format( _value_, _unit_ )

    299 | 300 |

    301 | When the `format` method is called with arguments _value_ and _unit_, the following steps are taken: 302 |

    303 | 304 | 305 | 1. Let _relativeTimeFormat_ be the *this* value. 306 | 1. If Type(_relativeTimeFormat_) is not Object, throw a *TypeError* exception. 307 | 1. If _relativeTimeFormat_ does not have an [[InitializedRelativeTimeFormat]] internal slot, throw a *TypeError* exception. 308 | 1. Let _value_ be ? ToNumber(_value_). 309 | 1. Let _unit_ be ? ToString(_unit_). 310 | 1. Return ? FormatRelativeTime(_relativeTimeFormat_, _value_, _unit_). 311 | 312 |
    313 | 314 | 315 |

    Intl.RelativeTimeFormat.prototype.formatToParts( _value_, _unit_ )

    316 | 317 |

    318 | When the `formatToParts` method is called with arguments _value_ and _unit_, the following steps are taken: 319 |

    320 | 321 | 322 | 1. Let _relativeTimeFormat_ be the *this* value. 323 | 1. If Type(_relativeTimeFormat_) is not Object, throw a *TypeError* exception. 324 | 1. If _relativeTimeFormat_ does not have an [[InitializedRelativeTimeFormat]] internal slot, throw a *TypeError* exception. 325 | 1. Let _value_ be ? ToNumber(_value_). 326 | 1. Let _unit_ be ? ToString(_unit_). 327 | 1. Return ? FormatRelativeTimeToParts(_relativeTimeFormat_, _value_, _unit_). 328 | 329 |
    330 | 331 | 332 |

    Intl.RelativeTimeFormat.prototype.resolvedOptions ()

    333 | 334 |

    335 | This function provides access to the locale and options computed during initialization of the object. 336 |

    337 | 338 | 339 | 1. Let _relativeTimeFormat_ be the *this* value. 340 | 1. If Type(_relativeTimeFormat_) is not Object, throw a *TypeError* exception. 341 | 1. If _relativeTimeFormat_ does not have an [[InitializedRelativeTimeFormat]] internal slot, throw a *TypeError* exception. 342 | 1. Let _options_ be ! ObjectCreate(%ObjectPrototype%). 343 | 1. For each row of , except the header row, in table order, do 344 | 1. Let _p_ be the Property value of the current row. 345 | 1. Let _v_ be the value of _relativeTimeFormat_'s internal slot whose name is the Internal Slot value of the current row. 346 | 1. Assert: _v_ is not *undefined*. 347 | 1. Perform ! CreateDataPropertyOrThrow(_options_, _p_, _v_). 348 | 1. Return _options_. 349 | 350 | 351 | 352 | Resolved Options of RelativeTimeFormat Instances 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 |
    Internal SlotProperty
    [[Locale]]`"locale"`
    [[Style]]`"style"`
    [[Numeric]]`"numeric"`
    [[NumberingSystem]]`"numberingSystem"`
    377 |
    378 |
    379 |
    380 | 381 | 382 |

    Properties of Intl.RelativeTimeFormat Instances

    383 | 384 |

    385 | Intl.RelativeTimeFormat instances are ordinary objects that inherit properties from %RelativeTimeFormatPrototype%. 386 |

    387 | 388 |

    389 | Intl.RelativeTimeFormat instances have an [[InitializedRelativeTimeFormat]] internal slot. 390 |

    391 | 392 |

    393 | Intl.RelativeTimeFormat instances also have several internal slots that are computed by the constructor: 394 |

    395 | 396 | 402 |
    403 |
    404 | --------------------------------------------------------------------------------