ecmarkup.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893
  1. "use strict";
  2. function Search(menu) {
  3. this.menu = menu;
  4. this.$search = document.getElementById('menu-search');
  5. this.$searchBox = document.getElementById('menu-search-box');
  6. this.$searchResults = document.getElementById('menu-search-results');
  7. this.loadBiblio();
  8. document.addEventListener('keydown', this.documentKeydown.bind(this));
  9. this.$searchBox.addEventListener('keydown', debounce(this.searchBoxKeydown.bind(this), { stopPropagation: true }));
  10. this.$searchBox.addEventListener('keyup', debounce(this.searchBoxKeyup.bind(this), { stopPropagation: true }));
  11. }
  12. Search.prototype.loadBiblio = function () {
  13. var $biblio = document.getElementById('menu-search-biblio');
  14. if (!$biblio) {
  15. this.biblio = [];
  16. } else {
  17. this.biblio = JSON.parse($biblio.textContent);
  18. this.biblio.clauses = this.biblio.filter(function (e) { return e.type === 'clause' });
  19. this.biblio.byId = this.biblio.reduce(function (map, entry) {
  20. map[entry.id] = entry;
  21. return map;
  22. }, {});
  23. }
  24. }
  25. Search.prototype.documentKeydown = function (e) {
  26. if (e.keyCode === 191) {
  27. e.preventDefault();
  28. e.stopPropagation();
  29. this.triggerSearch();
  30. }
  31. }
  32. Search.prototype.searchBoxKeydown = function (e) {
  33. e.stopPropagation();
  34. e.preventDefault();
  35. if (e.keyCode === 191 && e.target.value.length === 0) {
  36. e.preventDefault();
  37. } else if (e.keyCode === 13) {
  38. e.preventDefault();
  39. this.selectResult();
  40. }
  41. }
  42. Search.prototype.searchBoxKeyup = function (e) {
  43. if (e.keyCode === 13 || e.keyCode === 9) {
  44. return;
  45. }
  46. this.search(e.target.value);
  47. }
  48. Search.prototype.triggerSearch = function (e) {
  49. if (this.menu.isVisible()) {
  50. this._closeAfterSearch = false;
  51. } else {
  52. this._closeAfterSearch = true;
  53. this.menu.show();
  54. }
  55. this.$searchBox.focus();
  56. this.$searchBox.select();
  57. }
  58. // bit 12 - Set if the result starts with searchString
  59. // bits 8-11: 8 - number of chunks multiplied by 2 if cases match, otherwise 1.
  60. // bits 1-7: 127 - length of the entry
  61. // General scheme: prefer case sensitive matches with fewer chunks, and otherwise
  62. // prefer shorter matches.
  63. function relevance(result, searchString) {
  64. var relevance = 0;
  65. relevance = Math.max(0, 8 - result.match.chunks) << 7;
  66. if (result.match.caseMatch) {
  67. relevance *= 2;
  68. }
  69. if (result.match.prefix) {
  70. relevance += 2048
  71. }
  72. relevance += Math.max(0, 255 - result.entry.key.length);
  73. return relevance;
  74. }
  75. Search.prototype.search = function (searchString) {
  76. var s = Date.now();
  77. if (searchString === '') {
  78. this.displayResults([]);
  79. this.hideSearch();
  80. return;
  81. } else {
  82. this.showSearch();
  83. }
  84. if (searchString.length === 1) {
  85. this.displayResults([]);
  86. return;
  87. }
  88. var results;
  89. if (/^[\d\.]*$/.test(searchString)) {
  90. results = this.biblio.clauses.filter(function (clause) {
  91. return clause.number.substring(0, searchString.length) === searchString;
  92. }).map(function (clause) {
  93. return { entry: clause };
  94. });
  95. } else {
  96. results = [];
  97. for (var i = 0; i < this.biblio.length; i++) {
  98. var entry = this.biblio[i];
  99. if (!entry.key) {
  100. // biblio entries without a key aren't searchable
  101. continue;
  102. }
  103. var match = fuzzysearch(searchString, entry.key);
  104. if (match) {
  105. results.push({ entry: entry, match: match });
  106. }
  107. }
  108. results.forEach(function (result) {
  109. result.relevance = relevance(result, searchString);
  110. });
  111. results = results.sort(function (a, b) { return b.relevance - a.relevance });
  112. }
  113. if (results.length > 50) {
  114. results = results.slice(0, 50);
  115. }
  116. this.displayResults(results);
  117. }
  118. Search.prototype.hideSearch = function () {
  119. this.$search.classList.remove('active');
  120. }
  121. Search.prototype.showSearch = function () {
  122. this.$search.classList.add('active');
  123. }
  124. Search.prototype.selectResult = function () {
  125. var $first = this.$searchResults.querySelector('li:first-child a');
  126. if ($first) {
  127. document.location = $first.getAttribute('href');
  128. }
  129. this.$searchBox.value = '';
  130. this.$searchBox.blur();
  131. this.displayResults([]);
  132. this.hideSearch();
  133. if (this._closeAfterSearch) {
  134. this.menu.hide();
  135. }
  136. }
  137. Search.prototype.displayResults = function (results) {
  138. if (results.length > 0) {
  139. this.$searchResults.classList.remove('no-results');
  140. var html = '<ul>';
  141. results.forEach(function (result) {
  142. var entry = result.entry;
  143. var id = entry.id;
  144. var cssClass = '';
  145. var text = '';
  146. if (entry.type === 'clause') {
  147. var number = entry.number ? entry.number + ' ' : '';
  148. text = number + entry.key;
  149. cssClass = 'clause';
  150. id = entry.id;
  151. } else if (entry.type === 'production') {
  152. text = entry.key;
  153. cssClass = 'prod';
  154. id = entry.id;
  155. } else if (entry.type === 'op') {
  156. text = entry.key;
  157. cssClass = 'op';
  158. id = entry.id || entry.refId;
  159. } else if (entry.type === 'term') {
  160. text = entry.key;
  161. cssClass = 'term';
  162. id = entry.id || entry.refId;
  163. }
  164. if (text) {
  165. html += '<li class=menu-search-result-' + cssClass + '><a href="#' + id + '">' + text + '</a></li>'
  166. }
  167. });
  168. html += '</ul>'
  169. this.$searchResults.innerHTML = html;
  170. } else {
  171. this.$searchResults.innerHTML = '';
  172. this.$searchResults.classList.add('no-results');
  173. }
  174. }
  175. function Menu() {
  176. this.$toggle = document.getElementById('menu-toggle');
  177. this.$menu = document.getElementById('menu');
  178. this.$toc = document.querySelector('menu-toc > ol');
  179. this.$pins = document.querySelector('#menu-pins');
  180. this.$pinList = document.getElementById('menu-pins-list');
  181. this.$toc = document.querySelector('#menu-toc > ol');
  182. this.$specContainer = document.getElementById('spec-container');
  183. this.search = new Search(this);
  184. this._pinnedIds = {};
  185. this.loadPinEntries();
  186. // toggle menu
  187. this.$toggle.addEventListener('click', this.toggle.bind(this));
  188. // keydown events for pinned clauses
  189. document.addEventListener('keydown', this.documentKeydown.bind(this));
  190. // toc expansion
  191. var tocItems = this.$menu.querySelectorAll('#menu-toc li');
  192. for (var i = 0; i < tocItems.length; i++) {
  193. var $item = tocItems[i];
  194. $item.addEventListener('click', function($item, event) {
  195. $item.classList.toggle('active');
  196. event.stopPropagation();
  197. }.bind(null, $item));
  198. }
  199. // close toc on toc item selection
  200. var tocLinks = this.$menu.querySelectorAll('#menu-toc li > a');
  201. for (var i = 0; i < tocLinks.length; i++) {
  202. var $link = tocLinks[i];
  203. $link.addEventListener('click', function(event) {
  204. this.toggle();
  205. event.stopPropagation();
  206. }.bind(this));
  207. }
  208. // update active clause on scroll
  209. window.addEventListener('scroll', debounce(this.updateActiveClause.bind(this)));
  210. this.updateActiveClause();
  211. // prevent menu scrolling from scrolling the body
  212. this.$toc.addEventListener('wheel', function (e) {
  213. var target = e.currentTarget;
  214. var offTop = e.deltaY < 0 && target.scrollTop === 0;
  215. if (offTop) {
  216. e.preventDefault();
  217. }
  218. var offBottom = e.deltaY > 0
  219. && target.offsetHeight + target.scrollTop >= target.scrollHeight;
  220. if (offBottom) {
  221. e.preventDefault();
  222. }
  223. })
  224. }
  225. Menu.prototype.documentKeydown = function (e) {
  226. e.stopPropagation();
  227. if (e.keyCode === 80) {
  228. this.togglePinEntry();
  229. } else if (e.keyCode > 48 && e.keyCode < 58) {
  230. this.selectPin(e.keyCode - 49);
  231. }
  232. }
  233. Menu.prototype.updateActiveClause = function () {
  234. this.setActiveClause(findActiveClause(this.$specContainer))
  235. }
  236. Menu.prototype.setActiveClause = function (clause) {
  237. this.$activeClause = clause;
  238. this.revealInToc(this.$activeClause);
  239. }
  240. Menu.prototype.revealInToc = function (path) {
  241. var current = this.$toc.querySelectorAll('li.revealed');
  242. for (var i = 0; i < current.length; i++) {
  243. current[i].classList.remove('revealed');
  244. current[i].classList.remove('revealed-leaf');
  245. }
  246. var current = this.$toc;
  247. var index = 0;
  248. while (index < path.length) {
  249. var children = current.children;
  250. for (var i = 0; i < children.length; i++) {
  251. if ('#' + path[index].id === children[i].children[1].getAttribute('href') ) {
  252. children[i].classList.add('revealed');
  253. if (index === path.length - 1) {
  254. children[i].classList.add('revealed-leaf');
  255. var rect = children[i].getBoundingClientRect();
  256. this.$toc.getBoundingClientRect().top
  257. var tocRect = this.$toc.getBoundingClientRect();
  258. if (rect.top + 10 > tocRect.bottom) {
  259. this.$toc.scrollTop = this.$toc.scrollTop + (rect.top - tocRect.bottom) + (rect.bottom - rect.top);
  260. } else if (rect.top < tocRect.top) {
  261. this.$toc.scrollTop = this.$toc.scrollTop - (tocRect.top - rect.top);
  262. }
  263. }
  264. current = children[i].querySelector('ol');
  265. index++;
  266. break;
  267. }
  268. }
  269. }
  270. }
  271. function findActiveClause(root, path) {
  272. var clauses = new ClauseWalker(root);
  273. var $clause;
  274. var found = false;
  275. var path = path || [];
  276. while ($clause = clauses.nextNode()) {
  277. var rect = $clause.getBoundingClientRect();
  278. var $header = $clause.children[0];
  279. var marginTop = parseInt(getComputedStyle($header)["margin-top"]);
  280. if ((rect.top - marginTop) <= 0 && rect.bottom > 0) {
  281. found = true;
  282. return findActiveClause($clause, path.concat($clause)) || path;
  283. }
  284. }
  285. return path;
  286. }
  287. function ClauseWalker(root) {
  288. var previous;
  289. var treeWalker = document.createTreeWalker(
  290. root,
  291. NodeFilter.SHOW_ELEMENT,
  292. {
  293. acceptNode: function (node) {
  294. if (previous === node.parentNode) {
  295. return NodeFilter.FILTER_REJECT;
  296. } else {
  297. previous = node;
  298. }
  299. if (node.nodeName === 'EMU-CLAUSE' || node.nodeName === 'EMU-INTRO' || node.nodeName === 'EMU-ANNEX') {
  300. return NodeFilter.FILTER_ACCEPT;
  301. } else {
  302. return NodeFilter.FILTER_SKIP;
  303. }
  304. }
  305. },
  306. false
  307. );
  308. return treeWalker;
  309. }
  310. Menu.prototype.toggle = function () {
  311. this.$menu.classList.toggle('active');
  312. }
  313. Menu.prototype.show = function () {
  314. this.$menu.classList.add('active');
  315. }
  316. Menu.prototype.hide = function () {
  317. this.$menu.classList.remove('active');
  318. }
  319. Menu.prototype.isVisible = function() {
  320. return this.$menu.classList.contains('active');
  321. }
  322. Menu.prototype.showPins = function () {
  323. this.$pins.classList.add('active');
  324. }
  325. Menu.prototype.hidePins = function () {
  326. this.$pins.classList.remove('active');
  327. }
  328. Menu.prototype.addPinEntry = function (id) {
  329. var entry = this.search.biblio.byId[id];
  330. if (!entry) {
  331. // id was deleted after pin (or something) so remove it
  332. delete this._pinnedIds[id];
  333. this.persistPinEntries();
  334. return;
  335. }
  336. if (entry.type === 'clause') {
  337. var prefix;
  338. if (entry.number) {
  339. prefix = entry.number + ' ';
  340. } else {
  341. prefix = '';
  342. }
  343. this.$pinList.innerHTML += '<li><a href="#' + entry.id + '">' + prefix + entry.titleHTML + '</a></li>';
  344. } else {
  345. this.$pinList.innerHTML += '<li><a href="#' + entry.id + '">' + entry.key + '</a></li>';
  346. }
  347. if (Object.keys(this._pinnedIds).length === 0) {
  348. this.showPins();
  349. }
  350. this._pinnedIds[id] = true;
  351. this.persistPinEntries();
  352. }
  353. Menu.prototype.removePinEntry = function (id) {
  354. var item = this.$pinList.querySelector('a[href="#' + id + '"]').parentNode;
  355. this.$pinList.removeChild(item);
  356. delete this._pinnedIds[id];
  357. if (Object.keys(this._pinnedIds).length === 0) {
  358. this.hidePins();
  359. }
  360. this.persistPinEntries();
  361. }
  362. Menu.prototype.persistPinEntries = function () {
  363. try {
  364. if (!window.localStorage) return;
  365. } catch (e) {
  366. return;
  367. }
  368. localStorage.pinEntries = JSON.stringify(Object.keys(this._pinnedIds));
  369. }
  370. Menu.prototype.loadPinEntries = function () {
  371. try {
  372. if (!window.localStorage) return;
  373. } catch (e) {
  374. return;
  375. }
  376. var pinsString = window.localStorage.pinEntries;
  377. if (!pinsString) return;
  378. var pins = JSON.parse(pinsString);
  379. for(var i = 0; i < pins.length; i++) {
  380. this.addPinEntry(pins[i]);
  381. }
  382. }
  383. Menu.prototype.togglePinEntry = function (id) {
  384. if (!id) {
  385. id = this.$activeClause[this.$activeClause.length - 1].id;
  386. }
  387. if (this._pinnedIds[id]) {
  388. this.removePinEntry(id);
  389. } else {
  390. this.addPinEntry(id);
  391. }
  392. }
  393. Menu.prototype.selectPin = function (num) {
  394. document.location = this.$pinList.children[num].children[0].href;
  395. }
  396. var menu;
  397. function init() {
  398. menu = new Menu();
  399. var $container = document.getElementById('spec-container');
  400. $container.addEventListener('mouseover', debounce(function (e) {
  401. Toolbox.activateIfMouseOver(e);
  402. }));
  403. }
  404. document.addEventListener('DOMContentLoaded', init);
  405. function debounce(fn, opts) {
  406. opts = opts || {};
  407. var timeout;
  408. return function(e) {
  409. if (opts.stopPropagation) {
  410. e.stopPropagation();
  411. }
  412. var args = arguments;
  413. if (timeout) {
  414. clearTimeout(timeout);
  415. }
  416. timeout = setTimeout(function() {
  417. timeout = null;
  418. fn.apply(this, args);
  419. }.bind(this), 150);
  420. }
  421. }
  422. var CLAUSE_NODES = ['EMU-CLAUSE', 'EMU-INTRO', 'EMU-ANNEX'];
  423. function findLocalReferences ($elem) {
  424. var name = $elem.innerHTML;
  425. var references = [];
  426. var parentClause = $elem.parentNode;
  427. while (parentClause && CLAUSE_NODES.indexOf(parentClause.nodeName) === -1) {
  428. parentClause = parentClause.parentNode;
  429. }
  430. if(!parentClause) return;
  431. var vars = parentClause.querySelectorAll('var');
  432. for (var i = 0; i < vars.length; i++) {
  433. var $var = vars[i];
  434. if ($var.innerHTML === name) {
  435. references.push($var);
  436. }
  437. }
  438. return references;
  439. }
  440. function toggleFindLocalReferences($elem) {
  441. var references = findLocalReferences($elem);
  442. if ($elem.classList.contains('referenced')) {
  443. references.forEach(function ($reference) {
  444. $reference.classList.remove('referenced');
  445. });
  446. } else {
  447. references.forEach(function ($reference) {
  448. $reference.classList.add('referenced');
  449. });
  450. }
  451. }
  452. function installFindLocalReferences () {
  453. document.addEventListener('click', function (e) {
  454. if (e.target.nodeName === 'VAR') {
  455. toggleFindLocalReferences(e.target);
  456. }
  457. });
  458. }
  459. document.addEventListener('DOMContentLoaded', installFindLocalReferences);
  460. // The following license applies to the fuzzysearch function
  461. // The MIT License (MIT)
  462. // Copyright © 2015 Nicolas Bevacqua
  463. // Copyright © 2016 Brian Terlson
  464. // Permission is hereby granted, free of charge, to any person obtaining a copy of
  465. // this software and associated documentation files (the "Software"), to deal in
  466. // the Software without restriction, including without limitation the rights to
  467. // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  468. // the Software, and to permit persons to whom the Software is furnished to do so,
  469. // subject to the following conditions:
  470. // The above copyright notice and this permission notice shall be included in all
  471. // copies or substantial portions of the Software.
  472. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  473. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  474. // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  475. // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  476. // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  477. // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  478. function fuzzysearch (searchString, haystack, caseInsensitive) {
  479. var tlen = haystack.length;
  480. var qlen = searchString.length;
  481. var chunks = 1;
  482. var finding = false;
  483. var prefix = true;
  484. if (qlen > tlen) {
  485. return false;
  486. }
  487. if (qlen === tlen) {
  488. if (searchString === haystack) {
  489. return { caseMatch: true, chunks: 1, prefix: true };
  490. } else if (searchString.toLowerCase() === haystack.toLowerCase()) {
  491. return { caseMatch: false, chunks: 1, prefix: true };
  492. } else {
  493. return false;
  494. }
  495. }
  496. outer: for (var i = 0, j = 0; i < qlen; i++) {
  497. var nch = searchString[i];
  498. while (j < tlen) {
  499. var targetChar = haystack[j++];
  500. if (targetChar === nch) {
  501. finding = true;
  502. continue outer;
  503. }
  504. if (finding) {
  505. chunks++;
  506. finding = false;
  507. }
  508. }
  509. if (caseInsensitive) { return false }
  510. return fuzzysearch(searchString.toLowerCase(), haystack.toLowerCase(), true);
  511. }
  512. return { caseMatch: !caseInsensitive, chunks: chunks, prefix: j <= qlen };
  513. }
  514. var Toolbox = {
  515. init: function () {
  516. this.$container = document.createElement('div');
  517. this.$container.classList.add('toolbox');
  518. this.$permalink = document.createElement('a');
  519. this.$permalink.textContent = 'Permalink';
  520. this.$pinLink = document.createElement('a');
  521. this.$pinLink.textContent = 'Pin';
  522. this.$pinLink.setAttribute('href', '#');
  523. this.$pinLink.addEventListener('click', function (e) {
  524. e.preventDefault();
  525. e.stopPropagation();
  526. menu.togglePinEntry(this.entry.id);
  527. }.bind(this));
  528. this.$refsLink = document.createElement('a');
  529. this.$refsLink.setAttribute('href', '#');
  530. this.$refsLink.addEventListener('click', function (e) {
  531. e.preventDefault();
  532. e.stopPropagation();
  533. referencePane.showReferencesFor(this.entry);
  534. }.bind(this));
  535. this.$container.appendChild(this.$permalink);
  536. this.$container.appendChild(this.$pinLink);
  537. this.$container.appendChild(this.$refsLink);
  538. document.body.appendChild(this.$container);
  539. },
  540. activate: function (el, entry, target) {
  541. if (el === this._activeEl) return;
  542. this.active = true;
  543. this.entry = entry;
  544. this.$container.classList.add('active');
  545. this.top = el.offsetTop - this.$container.offsetHeight - 10;
  546. this.left = el.offsetLeft;
  547. this.$container.setAttribute('style', 'left: ' + this.left + 'px; top: ' + this.top + 'px');
  548. this.updatePermalink();
  549. this.updateReferences();
  550. this._activeEl = el;
  551. if (this.top < document.body.scrollTop && el === target) {
  552. // don't scroll unless it's a small thing (< 200px)
  553. this.$container.scrollIntoView();
  554. }
  555. },
  556. updatePermalink: function () {
  557. this.$permalink.setAttribute('href', '#' + this.entry.id);
  558. },
  559. updateReferences: function () {
  560. this.$refsLink.textContent = 'References (' + this.entry.referencingIds.length + ')';
  561. },
  562. activateIfMouseOver: function (e) {
  563. var ref = this.findReferenceUnder(e.target);
  564. if (ref && (!this.active || e.pageY > this._activeEl.offsetTop)) {
  565. var entry = menu.search.biblio.byId[ref.id];
  566. this.activate(ref.element, entry, e.target);
  567. } else if (this.active && ((e.pageY < this.top) || e.pageY > (this._activeEl.offsetTop + this._activeEl.offsetHeight))) {
  568. this.deactivate();
  569. }
  570. },
  571. findReferenceUnder: function (el) {
  572. while (el) {
  573. var parent = el.parentNode;
  574. if (el.nodeName === 'H1' && parent.nodeName.match(/EMU-CLAUSE|EMU-ANNEX|EMU-INTRO/) && parent.id) {
  575. return { element: el, id: parent.id };
  576. } else if (el.nodeName.match(/EMU-(?!CLAUSE|XREF|ANNEX|INTRO)|DFN/) &&
  577. el.id && el.id[0] !== '_') {
  578. if (el.nodeName === 'EMU-FIGURE' || el.nodeName === 'EMU-TABLE' || el.nodeName === 'EMU-EXAMPLE') {
  579. // return the figcaption element
  580. return { element: el.children[0].children[0], id: el.id };
  581. } else if (el.nodeName === 'EMU-PRODUCTION') {
  582. // return the LHS non-terminal element
  583. return { element: el.children[0], id: el.id };
  584. } else {
  585. return { element: el, id: el.id };
  586. }
  587. }
  588. el = parent;
  589. }
  590. },
  591. deactivate: function () {
  592. this.$container.classList.remove('active');
  593. this._activeEl = null;
  594. this.activeElBounds = null;
  595. this.active = false;
  596. }
  597. }
  598. var referencePane = {
  599. init: function() {
  600. this.$container = document.createElement('div');
  601. this.$container.setAttribute('id', 'references-pane-container');
  602. var $spacer = document.createElement('div');
  603. $spacer.setAttribute('id', 'references-pane-spacer');
  604. this.$pane = document.createElement('div');
  605. this.$pane.setAttribute('id', 'references-pane');
  606. this.$container.appendChild($spacer);
  607. this.$container.appendChild(this.$pane);
  608. this.$header = document.createElement('div');
  609. this.$header.classList.add('menu-pane-header');
  610. this.$header.textContent = 'References to ';
  611. this.$headerRefId = document.createElement('a');
  612. this.$header.appendChild(this.$headerRefId);
  613. this.$closeButton = document.createElement('span');
  614. this.$closeButton.setAttribute('id', 'references-pane-close');
  615. this.$closeButton.addEventListener('click', function (e) {
  616. this.deactivate();
  617. }.bind(this));
  618. this.$header.appendChild(this.$closeButton);
  619. this.$pane.appendChild(this.$header);
  620. var tableContainer = document.createElement('div');
  621. tableContainer.setAttribute('id', 'references-pane-table-container');
  622. this.$table = document.createElement('table');
  623. this.$table.setAttribute('id', 'references-pane-table');
  624. this.$tableBody = this.$table.createTBody();
  625. tableContainer.appendChild(this.$table);
  626. this.$pane.appendChild(tableContainer);
  627. menu.$specContainer.appendChild(this.$container);
  628. },
  629. activate: function () {
  630. this.$container.classList.add('active');
  631. },
  632. deactivate: function () {
  633. this.$container.classList.remove('active');
  634. },
  635. showReferencesFor(entry) {
  636. this.activate();
  637. var newBody = document.createElement('tbody');
  638. var previousId;
  639. var previousCell;
  640. var dupCount = 0;
  641. this.$headerRefId.textContent = '#' + entry.id;
  642. this.$headerRefId.setAttribute('href', '#' + entry.id);
  643. entry.referencingIds.map(function (id) {
  644. var target = document.getElementById(id);
  645. var cid = findParentClauseId(target);
  646. var clause = menu.search.biblio.byId[cid];
  647. var dupCount = 0;
  648. return { id: id, clause: clause }
  649. }).sort(function (a, b) {
  650. return sortByClauseNumber(a.clause, b.clause);
  651. }).forEach(function (record, i) {
  652. if (previousId === record.clause.id) {
  653. previousCell.innerHTML += ' (<a href="#' + record.id + '">' + (dupCount + 2) + '</a>)';
  654. dupCount++;
  655. } else {
  656. var row = newBody.insertRow();
  657. var cell = row.insertCell();
  658. cell.innerHTML = record.clause.number;
  659. cell = row.insertCell();
  660. cell.innerHTML = '<a href="#' + record.id + '">' + record.clause.titleHTML + '</a>';
  661. previousCell = cell;
  662. previousId = record.clause.id;
  663. dupCount = 0;
  664. }
  665. }, this);
  666. this.$table.removeChild(this.$tableBody);
  667. this.$tableBody = newBody;
  668. this.$table.appendChild(this.$tableBody);
  669. }
  670. }
  671. function findParentClauseId(node) {
  672. while (node && node.nodeName !== 'EMU-CLAUSE' && node.nodeName !== 'EMU-INTRO' && node.nodeName !== 'EMU-ANNEX') {
  673. node = node.parentNode;
  674. }
  675. if (!node) return null;
  676. return node.getAttribute('id');
  677. }
  678. function sortByClauseNumber(c1, c2) {
  679. var c1c = c1.number.split('.');
  680. var c2c = c2.number.split('.');
  681. for (var i = 0; i < c1c.length; i++) {
  682. if (i >= c2c.length) {
  683. return 1;
  684. }
  685. var c1 = c1c[i];
  686. var c2 = c2c[i];
  687. var c1cn = Number(c1);
  688. var c2cn = Number(c2);
  689. if (Number.isNaN(c1cn) && Number.isNaN(c2cn)) {
  690. if (c1 > c2) {
  691. return 1;
  692. } else if (c1 < c2) {
  693. return -1;
  694. }
  695. } else if (!Number.isNaN(c1cn) && Number.isNaN(c2cn)) {
  696. return -1;
  697. } else if (Number.isNaN(c1cn) && !Number.isNaN(c2cn)) {
  698. return 1;
  699. } else if(c1cn > c2cn) {
  700. return 1;
  701. } else if (c1cn < c2cn) {
  702. return -1;
  703. }
  704. }
  705. if (c1c.length === c2c.length) {
  706. return 0;
  707. }
  708. return -1;
  709. }
  710. document.addEventListener('DOMContentLoaded', function () {
  711. Toolbox.init();
  712. referencePane.init();
  713. })
  714. var CLAUSE_NODES = ['EMU-CLAUSE', 'EMU-INTRO', 'EMU-ANNEX'];
  715. function findLocalReferences ($elem) {
  716. var name = $elem.innerHTML;
  717. var references = [];
  718. var parentClause = $elem.parentNode;
  719. while (parentClause && CLAUSE_NODES.indexOf(parentClause.nodeName) === -1) {
  720. parentClause = parentClause.parentNode;
  721. }
  722. if(!parentClause) return;
  723. var vars = parentClause.querySelectorAll('var');
  724. for (var i = 0; i < vars.length; i++) {
  725. var $var = vars[i];
  726. if ($var.innerHTML === name) {
  727. references.push($var);
  728. }
  729. }
  730. return references;
  731. }
  732. function toggleFindLocalReferences($elem) {
  733. var references = findLocalReferences($elem);
  734. if ($elem.classList.contains('referenced')) {
  735. references.forEach(function ($reference) {
  736. $reference.classList.remove('referenced');
  737. });
  738. } else {
  739. references.forEach(function ($reference) {
  740. $reference.classList.add('referenced');
  741. });
  742. }
  743. }
  744. function installFindLocalReferences () {
  745. document.addEventListener('click', function (e) {
  746. if (e.target.nodeName === 'VAR') {
  747. toggleFindLocalReferences(e.target);
  748. }
  749. });
  750. }
  751. document.addEventListener('DOMContentLoaded', installFindLocalReferences);