From 2a32b4080c15d02ecb4ccc3258bff0ad7f27b62c Mon Sep 17 00:00:00 2001 From: Brian Terlson Date: Thu, 20 Aug 2015 18:18:45 -0700 Subject: [PATCH] Add new floating toc --- bin/args.js | 1 + bin/ecmarkup.js | 2 +- css/elements.css | 153 ++++++++++++++++++++++++++++++++++++- js/menu.js | 36 +++++++++ lib/Menu.js | 25 ++++++ lib/Spec.js | 13 +++- lib/Toc.js | 40 +++++++--- package.json | 2 +- spec/index.html | 1 + test/build.js | 4 +- test/clauses.html | 1 + test/clauses.html.baseline | 3 +- test/test.html.baseline | 2 +- 13 files changed, 259 insertions(+), 24 deletions(-) create mode 100644 js/menu.js create mode 100644 lib/Menu.js diff --git a/bin/args.js b/bin/args.js index ebb81dad..ab3a1bd4 100644 --- a/bin/args.js +++ b/bin/args.js @@ -10,6 +10,7 @@ module.exports = require('nomnom') css: { metavar: 'FILE', help: 'Write Emu CSS dependencies to FILE' }, js: { metavar: 'FILE', help: 'Write Emu JS dependencies to FILE' }, toc: { flag: true, help: 'Don\'t include the table of contents' }, + oldToc: { full: 'old-toc', name: 'old-toc', flag: true, help: 'Use the old table of contents styling' }, verbose: { flag: true, default: false, help: 'Display document build progress' }, version: { abbr: 'v', diff --git a/bin/ecmarkup.js b/bin/ecmarkup.js index 6c90d720..b211c094 100644 --- a/bin/ecmarkup.js +++ b/bin/ecmarkup.js @@ -30,6 +30,6 @@ ecmarkup.build(args.infile, fetch, args).then(function (spec) { } if (args.js) { - fs.writeFileSync(args.js, fs.readFileSync(Path.join(__dirname, '../js/ecmarkup.js'))); + fs.writeFileSync(args.js, fs.readFileSync(Path.join(__dirname, '../js/menu.js'))); } }); diff --git a/css/elements.css b/css/elements.css index 67441cbb..ac1590e3 100644 --- a/css/elements.css +++ b/css/elements.css @@ -1,5 +1,4 @@ html { - padding: 0 7em; } body { @@ -8,8 +7,13 @@ body { font-family: Cambria, Palatino Linotype, Palatino, Liberation Serif, serif; padding: 0; color: #333; - max-width: 65em; - margin: 0 auto; + max-width: 844px; + margin: 0 0 0 31%; + overflow-x: hidden; +} + +body.oldtoc { + margin: 0 auto; } a { @@ -327,3 +331,146 @@ del { del.block { display: block; } + +/* Menu Styles */ +#menu-toggle { + font-size: 2em; + + position: fixed; + top: 0; + left: 0; + width: 1.5em; + height: 1.5em; + z-index: 3; + visibility: hidden; + + background-color: #111; + color: #B6C8E4; + + line-height: 1.5em; + text-align: center; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none;; + + cursor: pointer; +} + +#menu { + position: fixed; + left: 0; + top: 0; + height: 100%; + width: 24%; + z-index: 2; + overflow-x: hidden; + overflow-y: auto; + box-sizing: border-box; + + background-color: #111; + + transition: opacity 0.1s linear; +} + +#menu.active { + display: block; + opacity: 1; +} + +#menu-toc > ol { + padding: 0; +} + +#menu-toc > ol , #menu-toc > ol ol { + list-style-type: none; +} + +#menu-toc > ol ol { + padding-left: 0.75em; +} + +#menu-toc li { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +#menu-toc .item-toggle { + display: inline-block; + transform: rotate(-45deg) translate(-5px, -5px); + transition: transform 0.1s ease; + width: 1em; + + color: #555F6E; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none;; + + cursor: pointer; +} + +#menu-toc .item-toggle-none { + display: inline-block; + width: 1em; +} + +#menu-toc li.active > .item-toggle { + transform: rotate(0); +} + +#menu-toc li > ol { + display: none; +} + +#menu-toc li.active > ol { + display: block; +} + +#menu-toc li > a { + padding-left: 0.25em; + color: #B6C8E4; +} + +@media (max-width: 1366px) { + body { + margin: 0 0 0 150px; + } + + #menu { + display: none; + padding-top: 2em; + width: 323px; + } + + #menu-toggle { + visibility: visible; + } +} + +@media only screen and (max-width: 800px) { + body { + margin: 2em 10px 0 10px; + } + + #menu { + width: 100%; + } + + h1 .secnum { + display: inline; + position: inherit; + left: 0; + right: 0; + } + + h1 .secnum:empty { + margin: 0; padding: 0; + } +} diff --git a/js/menu.js b/js/menu.js new file mode 100644 index 00000000..796e056c --- /dev/null +++ b/js/menu.js @@ -0,0 +1,36 @@ +"use strict"; + +function Menu() { + this.$toggle = document.getElementById('menu-toggle'); + this.$menu = document.getElementById('menu'); + + this.$toggle.addEventListener('click', this.toggle.bind(this)); + + var tocItems = this.$menu.querySelectorAll('#menu-toc li'); + for (var i = 0; i < tocItems.length; i++) { + var $item = tocItems[i]; + $item.addEventListener('click', function($item, event) { + $item.classList.toggle('active'); + event.stopPropagation(); + }.bind(null, $item)); + } + + var tocLinks = this.$menu.querySelectorAll('#menu-toc li > a'); + for (var i = 0; i < tocLinks.length; i++) { + var $link = tocLinks[i]; + $link.addEventListener('click', function(event) { + this.toggle(); + event.stopPropagation(); + }.bind(this)); + } +} + +Menu.prototype.toggle = function () { + this.$menu.classList.toggle("active"); +} + +function init() { + var menu = new Menu(); +} + +document.addEventListener('DOMContentLoaded', init); diff --git a/lib/Menu.js b/lib/Menu.js new file mode 100644 index 00000000..1fdbbbcc --- /dev/null +++ b/lib/Menu.js @@ -0,0 +1,25 @@ +'use strict'; + +const Builder = require('./Builder'); +const Toc = require('./Toc'); + +module.exports = class Menu extends Builder { + build() { + const toc = Toc.build(this.spec, true); + const tocContainer = this.spec.doc.createElement('div'); + tocContainer.setAttribute('id', 'menu-toc'); + tocContainer.innerHTML = toc; + + const menuContainer = this.spec.doc.createElement('div'); + menuContainer.setAttribute('id', 'menu'); + menuContainer.appendChild(tocContainer); + + this.spec.doc.body.insertBefore(menuContainer, this.spec.doc.body.firstChild); + + const menuToggle = this.spec.doc.createElement('div'); + menuToggle.setAttribute('id', 'menu-toggle'); + menuToggle.textContent = '☰'; + + this.spec.doc.body.insertBefore(menuToggle, this.spec.doc.body.firstChild); + } +}; diff --git a/lib/Spec.js b/lib/Spec.js index 6c76e915..eaaa740c 100644 --- a/lib/Spec.js +++ b/lib/Spec.js @@ -13,6 +13,7 @@ const Algorithm = require('./Algorithm'); const Dfn = require('./Dfn'); const Note = require('./Note'); const Toc = require('./Toc'); +const Menu = require('./Menu'); const Production = require('./Production'); const ProdRef = require('./ProdRef'); const Grammar = require('./Grammar'); @@ -124,8 +125,15 @@ module.exports = class Spec { if (this.opts.toc) { p = p.then(function() { this._log('Building table of contents...'); - const tb = new Toc(this); - return tb.build(); + + let toc; + + if (this.opts.oldToc) { + toc = new Toc(this); + } else { + toc = new Menu(this); + } + return toc.build(); }.bind(this)); } @@ -311,4 +319,3 @@ function assign(target, source) { target[k] = source[k]; }); } - diff --git a/lib/Toc.js b/lib/Toc.js index 90029c22..f2f7a0bb 100644 --- a/lib/Toc.js +++ b/lib/Toc.js @@ -9,26 +9,42 @@ module.exports = class Toc extends Builder { return; } - const html = buildToc(this.spec); + const html = Toc.build(this.spec); const tocContainer = this.spec.doc.createElement('div'); tocContainer.innerHTML = '

Table of Contents

' + html; const intro = this.spec.doc.querySelector('emu-intro, emu-clause, emu-annex'); intro.parentNode.insertBefore(tocContainer, intro); + + const bodyClass = this.spec.doc.body.getAttribute('class') || ''; + this.spec.doc.body.setAttribute('class', bodyClass + ' oldtoc'); } -}; -function buildToc(spec, level) { - level = level || spec; + static build(level, expandy) { + let html = '
    '; + + level.subclauses.forEach(function(sub) { + html += '
  1. '; - let html = '
      '; + if (expandy) { + if (sub.subclauses.length > 0) { + html += ''; + } else { + html += ''; + } + } - level.subclauses.forEach(function (sub) { - html += '
    1. ' + sub.number + ' ' + emd.fragment(sub.title) + ''; - if (sub.subclauses.length > 0) html += buildToc(spec, sub); - html += '
    2. '; - }); + html += '' + sub.number + ' ' + emd.fragment(shorten(sub.title)) + ''; + if (sub.subclauses.length > 0) html += Toc.build(sub, expandy); + html += ''; + }); - html += '
    '; + html += '
'; + + return html; + } +}; - return html; +function shorten(title) { + return title.replace('Static Semantics:', 'SS:') + .replace('Runtime Semantics:', 'RS:'); } diff --git a/package.json b/package.json index 058049f1..35cb1649 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ecmarkup", - "version": "2.0.0-beta4", + "version": "2.0.0-beta5", "description": "Custom element definitions and core utilities for markup that specifies ECMAScript and related technologies.", "main": "lib/ecmarkup.js", "scripts": { diff --git a/spec/index.html b/spec/index.html index eda6e8ad..b2b026d9 100644 --- a/spec/index.html +++ b/spec/index.html @@ -1,6 +1,7 @@ Ecmarkup +
diff --git a/test/build.js b/test/build.js
index 01bf4376..7aa3b647 100644
--- a/test/build.js
+++ b/test/build.js
@@ -4,8 +4,8 @@ const Promise = require('bluebird');
 
 const build = require('../lib/ecmarkup').build;
 
-const doc = '

hi

'; -const out = '\n

Table of Contents

  1. 1 hi

1hi#

'; +const doc = '

hi

'; +const out = '\n

1hi#

'; function fetch(file) { if (file.match(/\.json$/)) { return '{}'; diff --git a/test/clauses.html b/test/clauses.html index 8f4db30e..c0fa37c5 100644 --- a/test/clauses.html +++ b/test/clauses.html @@ -1,3 +1,4 @@ +

Intro

diff --git a/test/clauses.html.baseline b/test/clauses.html.baseline index 77279be2..280a136e 100644 --- a/test/clauses.html.baseline +++ b/test/clauses.html.baseline @@ -1,5 +1,6 @@ -

Table of Contents

  1. Intro
    1. Sub Intro
  2. 1 Clause Foo(a, b)
    1. 1.1 Sub Clause
  3. A Annex
    1. A.1 Sub-annex
+ +

Intro#

Sub Intro#

diff --git a/test/test.html.baseline b/test/test.html.baseline index 812ca19e..223db364 100644 --- a/test/test.html.baseline +++ b/test/test.html.baseline @@ -1,7 +1,7 @@ - +

Intro#

Sub Intro#