-
-
- {{#if docsRoutes.previous}}
-
- Previous
-
- {{#link-to params=docsRoutes.previous.route
- class='text-grey-darkest text-2xl font-light no-underline
- border-b border-grey hover:border-grey-darkest transition'}}
- {{docsRoutes.previous.label}}
- {{/link-to}}
- {{/if}}
-
+
+
+
+
+
+ {{#if docsRoutes.previous}}
+
+ Previous
+
+ {{#link-to params=docsRoutes.previous.route
+ class='text-grey-darkest text-2xl font-light no-underline
+ border-b border-grey hover:border-grey-darkest transition'}}
+ {{docsRoutes.previous.label}}
+ {{/link-to}}
+ {{/if}}
+
-
- {{#if docsRoutes.next}}
-
- Next
-
- {{#link-to params=docsRoutes.next.route
- class='text-grey-darkest text-2xl font-light no-underline
- border-b border-grey hover:border-grey-darkest transition'}}
- {{docsRoutes.next.label}}
- {{/link-to}}
- {{/if}}
-
+
+ {{#if docsRoutes.next}}
+
+ Next
+
+ {{#link-to params=docsRoutes.next.route
+ class='text-grey-darkest text-2xl font-light no-underline
+ border-b border-grey hover:border-grey-darkest transition'}}
+ {{docsRoutes.next.label}}
+ {{/link-to}}
+ {{/if}}
-
+
diff --git a/addon/components/docs-viewer/x-page-index/component.js b/addon/components/docs-viewer/x-page-index/component.js
new file mode 100644
index 000000000..6a0f24a05
--- /dev/null
+++ b/addon/components/docs-viewer/x-page-index/component.js
@@ -0,0 +1,12 @@
+import Component from '@ember/component';
+import { inject as service } from '@ember/service';
+import layout from './template';
+
+export default Component.extend({
+
+ layout,
+
+ tagName: '',
+
+ pageIndex: service()
+});
diff --git a/addon/components/docs-viewer/x-page-index/template.hbs b/addon/components/docs-viewer/x-page-index/template.hbs
new file mode 100644
index 000000000..469f4e672
--- /dev/null
+++ b/addon/components/docs-viewer/x-page-index/template.hbs
@@ -0,0 +1,30 @@
+
diff --git a/addon/router.js b/addon/router.js
new file mode 100644
index 000000000..ded6377cf
--- /dev/null
+++ b/addon/router.js
@@ -0,0 +1,18 @@
+import EmberRouter from '@ember/routing/router';
+import RouterScroll from 'ember-router-scroll';
+
+export default EmberRouter.extend(RouterScroll);
+
+export function docsRoute(router, callback) {
+ router.route('docs', function() {
+ callback.apply(this);
+
+ apiRoute(this);
+ });
+}
+
+export function apiRoute(router) {
+ router.route('api', function() {
+ this.route('item', { path: '/*path' });
+ });
+}
diff --git a/addon/services/page-index.js b/addon/services/page-index.js
new file mode 100644
index 000000000..81104e722
--- /dev/null
+++ b/addon/services/page-index.js
@@ -0,0 +1,51 @@
+import Service from '@ember/service';
+import { scheduleOnce } from '@ember/runloop';
+
+let tagToSize = {
+ H2: 'sm',
+ H3: 'xs',
+};
+
+let tagToIndent = {
+ H2: '2',
+ H3: '4',
+};
+
+let tagToMarginTop = {
+ H2: '2',
+ H3: '0',
+};
+
+let tagToMarginBottom = {
+ H2: '1',
+ H3: '0',
+};
+
+export default Service.extend({
+ reindex(target) {
+ scheduleOnce('afterRender', this, '_reindex', target);
+ },
+
+ _reindex(target) {
+ let mainSection = document.querySelector('main');
+
+ if (!mainSection) {
+ return;
+ }
+
+ let headers = Array.from(
+ mainSection.querySelectorAll('h2, h3')
+ );
+
+ this.set('index', headers.map((header) => {
+ return {
+ id: header.id,
+ text: header.dataset.text || header.textContent,
+ size: tagToSize[header.tagName],
+ indent: tagToIndent[header.tagName],
+ marginTop: tagToMarginTop[header.tagName],
+ marginBottom: tagToMarginBottom[header.tagName],
+ };
+ }));
+ }
+});
diff --git a/addon/styles/addon.scss b/addon/styles/addon.scss
index c3f3709b3..786b8807b 100644
--- a/addon/styles/addon.scss
+++ b/addon/styles/addon.scss
@@ -38,3 +38,41 @@ svg {
.ember-modal-dialog {
z-index: 10;
}
+
+h1 > a:before,
+h2 > a:before,
+h3 > a:before {
+ content: '\B6';
+ cursor: pointer;
+ display: block;
+ float: left;
+ visibility: hidden;
+
+ color: #aaa;
+}
+
+h1 > a:hover:before,
+h2 > a:hover:before,
+h3 > a:hover:before {
+ visibility: visible;
+}
+
+h1 > a:before {
+ font-size: 0.7em;
+ width: 0.7em;
+ padding: 0.2em 0;
+ margin-left: -0.9em;
+}
+
+h2 > a:before {
+ font-size: 0.85em;
+ width: 0.8em;
+ padding: 0.1em 0em;
+ margin-left: -0.9em;
+}
+
+h3 > a:before {
+ font-size: 1em;
+ width: 0em;
+ margin-left: -0.9em;
+}
diff --git a/addon/tailwind/components/docs-md.css b/addon/tailwind/components/docs-md.css
index 09575576d..c106b0084 100644
--- a/addon/tailwind/components/docs-md.css
+++ b/addon/tailwind/components/docs-md.css
@@ -3,17 +3,26 @@
}
.docs-md__h1,
-.docs-h1 {
- @apply mb-6 text-grey-darkest text-4xl font-extrabold leading-tight;
+.docs-h1,
+.docs-md__h1 a,
+.docs-h1 a {
+ @apply mb-6 text-grey-darkest text-4xl font-extrabold leading-tight no-underline;
}
+
.docs-md__h2,
-.docs-h2 {
- @apply mt-8 mb-4 text-grey-darkest leading-tight;
+.docs-h2,
+.docs-md__h2 a,
+.docs-h2 a {
+ @apply pt-8 mb-4 text-grey-darkest leading-tight no-underline;
}
+
.docs-md__h3,
-.docs-h3 {
- @apply mb-2 text-grey-darkest leading-tight;
+.docs-h3,
+.docs-md__h3 a,
+.docs-h3 a {
+ @apply pt-4 mb-2 text-grey-darkest leading-tight no-underline;
}
+
.docs-md p {
@apply mb-4;
}
diff --git a/addon/tailwind/config/margin.js b/addon/tailwind/config/margin.js
index f51474e09..9492e6deb 100644
--- a/addon/tailwind/config/margin.js
+++ b/addon/tailwind/config/margin.js
@@ -25,4 +25,5 @@ export default {
'8': '2rem',
'12': '3rem',
'16': '4rem',
+ '20': '5rem',
};
diff --git a/lib/utils/compile-markdown.js b/lib/utils/compile-markdown.js
index 82ba6ee3a..b7e670256 100644
--- a/lib/utils/compile-markdown.js
+++ b/lib/utils/compile-markdown.js
@@ -102,7 +102,13 @@ class HBSRenderer extends marked.Renderer {
heading(text, level) {
let id = text.toLowerCase().replace(/<\/?.*?>/g, '').replace(/[^\w]+/g, '-');
- return `
${text}`;
+ return `
+
+
+ ${text}
+
+
+ `;
}
hr() {
diff --git a/public/.DS_Store b/public/.DS_Store
index d8d844af3..7596da1f7 100644
Binary files a/public/.DS_Store and b/public/.DS_Store differ
diff --git a/public/icons/link.svg b/public/icons/link.svg
new file mode 100644
index 000000000..11a696f84
--- /dev/null
+++ b/public/icons/link.svg
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/tests/acceptance/sandbox/api/components-test.js b/tests/acceptance/sandbox/api/components-test.js
index 189055766..a765e2654 100644
--- a/tests/acceptance/sandbox/api/components-test.js
+++ b/tests/acceptance/sandbox/api/components-test.js
@@ -19,4 +19,30 @@ module('Acceptance | API | components', function(hooks) {
assert.equal(currentURL(), `/sandbox/api/components/simple-list/item`, 'correct url');
});
+
+ test('component page index works', async function(assert) {
+ await visit('/sandbox');
+ await modulePage.navItems.findOne({ text: `{{esdoc-component}}` }).click();
+
+ assert.equal(currentURL(), `/sandbox/api/components/esdoc-component`, 'correct url');
+
+ let indexItems = modulePage.index.items.map(i => i.text);
+
+ assert.equal(indexItems.length, 7, 'correct number of items rendered');
+ assert.ok(indexItems.includes('Yields') && indexItems.includes('Arguments'), 'correct sections rendered');
+
+ await modulePage.toggles.findOne({ text: 'Internal' }).click();
+
+ indexItems = modulePage.index.items.map(i => i.text);
+
+ assert.equal(indexItems.length, 12, 'correct number of items rendered');
+ assert.ok(indexItems.includes('Fields') && indexItems.includes('Methods'), 'correct sections rendered');
+
+ await modulePage.toggles.findOne({ text: 'Private' }).click();
+
+ indexItems = modulePage.index.items.map(i => i.text);
+
+ assert.equal(indexItems.length, 13, 'correct number of items rendered');
+ assert.ok(indexItems.includes('_privateField'), 'private field rendered');
+ });
});
diff --git a/tests/acceptance/sandbox/api/guides-test.js b/tests/acceptance/sandbox/api/guides-test.js
new file mode 100644
index 000000000..82aab3a52
--- /dev/null
+++ b/tests/acceptance/sandbox/api/guides-test.js
@@ -0,0 +1,22 @@
+import { module, test } from 'qunit';
+import { setupApplicationTest } from 'ember-qunit';
+import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
+import { currentURL, visit } from '@ember/test-helpers';
+
+import modulePage from '../../../pages/api/module';
+
+module('Acceptance | sandbox | guides', function(hooks) {
+ setupApplicationTest(hooks);
+ setupMirage(hooks);
+
+ test('page index works', async function(assert) {
+ await visit('/sandbox');
+
+ assert.equal(currentURL(), `/sandbox`, 'correct url');
+
+ let indexItems = modulePage.index.items.map(i => i.text);
+
+ assert.equal(indexItems.length, 2, 'correct number of items rendered');
+ assert.ok(indexItems.includes('Subsection') && indexItems.includes('Sub-subsection'), 'correct sections rendered');
+ });
+});
diff --git a/tests/dummy/app/pods/sandbox/index/template.md b/tests/dummy/app/pods/sandbox/index/template.md
index f357d5fb5..76322fc10 100644
--- a/tests/dummy/app/pods/sandbox/index/template.md
+++ b/tests/dummy/app/pods/sandbox/index/template.md
@@ -3,3 +3,11 @@
Welcome to the Ember CLI Addon Docs Sandbox!
This area of the site demonstrates the many different ways you can document your addon code.
+
+## Subsection
+
+This is a subsection
+
+### Sub-subsection
+
+This is a sub-subsection
diff --git a/tests/dummy/app/router.js b/tests/dummy/app/router.js
index 6acae74de..951a9e3ee 100644
--- a/tests/dummy/app/router.js
+++ b/tests/dummy/app/router.js
@@ -1,15 +1,14 @@
-import EmberRouter from '@ember/routing/router';
+import AddonDocsRouter, { docsRoute, apiRoute } from 'ember-cli-addon-docs/router';
import config from './config/environment';
-import RouterScroll from 'ember-router-scroll';
-const Router = EmberRouter.extend(RouterScroll, {
+const Router = AddonDocsRouter.extend({
location: config.locationType,
- rootURL: config.rootURL
+ rootURL: config.rootURL,
});
Router.map(function() {
- this.route('docs', function() {
+ docsRoute(this, function() {
this.route('usage');
this.route('quickstart');
this.route('patterns');
@@ -23,16 +22,10 @@ Router.map(function() {
this.route('docs-viewer');
this.route('docs-demo');
});
-
- this.route('api', function() {
- this.route('item', { path: '/*path' });
- });
});
this.route('sandbox', function() {
- this.route('api', function() {
- this.route('item', { path: '/*path' });
- });
+ apiRoute(this);
});
this.route('not-found', { path: '/*path' });
diff --git a/tests/pages/api/module.js b/tests/pages/api/module.js
index 0e188c90a..6406f05f0 100644
--- a/tests/pages/api/module.js
+++ b/tests/pages/api/module.js
@@ -3,6 +3,10 @@ import PageObject, { collection, text } from 'ember-classy-page-object';
const ModulePage = PageObject.extend({
navItems: collection({ scope: '[data-test-id="nav-item"]' }),
+ toggles: collection({
+ scope: '[data-test-toggle]'
+ }),
+
sections: collection({
scope: '[data-test-api-section]',
@@ -19,7 +23,16 @@ const ModulePage = PageObject.extend({
scope: '[data-test-item-params] [data-test-item-param]'
})
})
- })
+ }),
+
+ // eslint-disable-next-line ember/avoid-leaking-state-in-ember-objects
+ index: {
+ scope: '[data-test-page-index]',
+
+ items: collection({
+ scope: '[data-test-index-item]'
+ })
+ }
});
export default ModulePage.create();
diff --git a/tests/pages/guide.js b/tests/pages/guide.js
new file mode 100644
index 000000000..d6c7def38
--- /dev/null
+++ b/tests/pages/guide.js
@@ -0,0 +1,16 @@
+import PageObject, { collection } from 'ember-classy-page-object';
+
+const GuidePage = PageObject.extend({
+ navItems: collection({ scope: '[data-test-id="nav-item"]' }),
+
+ // eslint-disable-next-line ember/avoid-leaking-state-in-ember-objects
+ index: {
+ scope: '[data-test-page-index]',
+
+ items: collection({
+ scope: '[data-test-index-item]'
+ })
+ }
+});
+
+export default GuidePage.create();