37 | *.hbs
38 |
--------------------------------------------------------------------------------
/app/metrics-adapters/local-adapter.js:
--------------------------------------------------------------------------------
1 | import BaseAdapter from 'ember-metrics/metrics-adapters/base';
2 |
3 | export default class LocalAdapter extends BaseAdapter {
4 | toStringExtension() {
5 | return 'local';
6 | }
7 |
8 | install() {}
9 |
10 | identify(options = {}) {
11 | console.log('Metrics:', 'identify', options);
12 | }
13 |
14 | trackEvent(options = {}) {
15 | console.log('Metrics:', 'trackEvent', options);
16 | }
17 |
18 | trackPage(options = {}) {
19 | console.log('Metrics:', 'trackPage', options);
20 | }
21 |
22 | alias(options = {}) {
23 | console.log('Metrics:', 'alias', options);
24 | }
25 |
26 | uninstall() {}
27 | }
28 |
--------------------------------------------------------------------------------
/app/initializers/add-string-includes-polyfill.js:
--------------------------------------------------------------------------------
1 | export function initialize() {
2 | // Source: https://developer.mozilla.org/cs/docs/Web/JavaScript/Reference/Global_Objects/String/includes#Polyfill
3 | if (!String.prototype.includes) {
4 | String.prototype.includes = function (search, start) {
5 | 'use strict';
6 | if (typeof start !== 'number') {
7 | start = 0;
8 | }
9 | if (start + search.length > this.length) {
10 | return false;
11 | } else {
12 | return this.indexOf(search, start) !== -1;
13 | }
14 | };
15 | }
16 | }
17 |
18 | export default {
19 | name: 'add-string-includes-polyfill',
20 | initialize,
21 | };
22 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 | indent_style = space
13 | indent_size = 2
14 |
15 | [*.js]
16 | indent_style = space
17 | indent_size = 2
18 |
19 | [*.hbs]
20 | insert_final_newline = false
21 | indent_style = space
22 | indent_size = 2
23 |
24 | [*.css]
25 | indent_style = space
26 | indent_size = 2
27 |
28 | [*.html]
29 | indent_style = space
30 | indent_size = 2
31 |
32 | [*.{diff,md}]
33 | trim_trailing_whitespace = false
34 |
--------------------------------------------------------------------------------
/app/utils/get-full-version.js:
--------------------------------------------------------------------------------
1 | import getCompactVersion from 'ember-api-docs/utils/get-compact-version';
2 | import getLastVersion from 'ember-api-docs/utils/get-last-version';
3 |
4 | export default function getFullVersion(
5 | urlVersion,
6 | project,
7 | projectObj,
8 | metaStore,
9 | ) {
10 | let projectVersion;
11 | if (urlVersion === 'release') {
12 | let versions = projectObj.hasMany('projectVersions').ids();
13 | projectVersion = metaStore.getFullVersion(
14 | project,
15 | getCompactVersion(getLastVersion(versions)),
16 | );
17 | } else {
18 | projectVersion = metaStore.getFullVersion(project, urlVersion);
19 | }
20 |
21 | return projectVersion;
22 | }
23 |
--------------------------------------------------------------------------------
/app/app.js:
--------------------------------------------------------------------------------
1 | import Application from '@ember/application';
2 | import Resolver from 'ember-resolver';
3 | import loadInitializers from 'ember-load-initializers';
4 | import config from 'ember-api-docs/config/environment';
5 | import { importSync, isDevelopingApp, macroCondition } from '@embroider/macros';
6 | import 'ember-power-select/styles';
7 | import './assets/styles.css';
8 |
9 | if (macroCondition(isDevelopingApp())) {
10 | importSync('./deprecation-workflow');
11 | }
12 |
13 | export default class App extends Application {
14 | modulePrefix = config.modulePrefix;
15 | podModulePrefix = config.podModulePrefix;
16 | Resolver = Resolver;
17 | }
18 |
19 | loadInitializers(App, config.modulePrefix);
20 |
--------------------------------------------------------------------------------
/app/components/table-of-projects.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
5 | Projects
6 |
7 | Ember
8 | EmberData
9 | Ember CLI
10 |
11 |
--------------------------------------------------------------------------------
/tests/acceptance/app-layout-test.js:
--------------------------------------------------------------------------------
1 | import { findAll, visit } from '@ember/test-helpers';
2 | import { module, test } from 'qunit';
3 | import { setupApplicationTest } from 'ember-qunit';
4 | import { selectSearch } from 'ember-power-select/test-support';
5 |
6 | module('Acceptance | Application Layout', function (hooks) {
7 | setupApplicationTest(hooks);
8 |
9 | test('lists the project versions in a select box', async function (assert) {
10 | await visit('/ember/1.0/classes/Ember.Component');
11 |
12 | await selectSearch('.select-container', '2');
13 |
14 | assert.dom('.ember-power-select-options').exists({ count: 1 });
15 | assert.ok(findAll('.ember-power-select-options')[0].children.length > 1);
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/.github/workflows/gh-pages.yml:
--------------------------------------------------------------------------------
1 | name: github pages
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - main
8 |
9 | jobs:
10 | deploy:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 |
15 | - uses: pnpm/action-setup@v4
16 | - uses: actions/setup-node@v4
17 | with:
18 | cache: "pnpm"
19 | node-version: 20
20 | - run: pnpm i --frozen-lockfile
21 | - run: npx lint-to-the-future output -o lttfOutput --rootUrl ember-api-docs --previous-results https://ember-learn.github.io/ember-api-docs/data.json
22 | - name: Deploy
23 | uses: peaceiris/actions-gh-pages@v3
24 | with:
25 | github_token: ${{ secrets.GITHUB_TOKEN }}
26 | publish_dir: ./lttfOutput
27 |
--------------------------------------------------------------------------------
/tests/acceptance/method-inheritance-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from 'qunit';
2 | import { setupApplicationTest } from 'ember-qunit';
3 | import { visit, click } from '@ember/test-helpers';
4 |
5 | module('Acceptance | method inheritance', function (hooks) {
6 | setupApplicationTest(hooks);
7 |
8 | test('no duplicate methods displayed', async function (assert) {
9 | await visit('/ember-data/2.13/classes/DS.JSONAPIAdapter');
10 | assert.dom("[data-test-item='createRecord']").exists({ count: 1 });
11 | });
12 |
13 | test('most local inherited method is shown', async function (assert) {
14 | await visit('/ember-data/2.13/classes/DS.JSONAPIAdapter');
15 | await click(`${'[data-test-item="createRecord"]'} a`);
16 | assert.dom("[data-test-file='addon/adapters/rest.js']").exists();
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/app/instance-initializers/ember-meta-store.js:
--------------------------------------------------------------------------------
1 | import { isPresent } from '@ember/utils';
2 |
3 | export function initialize(appInstance) {
4 | const metaStore = appInstance.lookup('service:meta-store');
5 | const fastBootService = appInstance.lookup('service:fastboot');
6 |
7 | const shoebox = fastBootService.get('shoebox');
8 |
9 | if (typeof FastBoot !== 'undefined') {
10 | shoebox.put(
11 | 'meta-store',
12 | metaStore.getProperties('availableProjectVersions', 'projectRevMap'),
13 | );
14 | } else if (isPresent(shoebox.retrieve('meta-store'))) {
15 | const { availableProjectVersions, projectRevMap } =
16 | shoebox.retrieve('meta-store');
17 | metaStore.initializeStore(availableProjectVersions, projectRevMap);
18 | }
19 | }
20 |
21 | export default {
22 | name: 'ember-meta-store',
23 | initialize,
24 | };
25 |
--------------------------------------------------------------------------------
/config/fastboot.js:
--------------------------------------------------------------------------------
1 | module.exports = function () {
2 | return {
3 | buildSandboxGlobals(defaultGlobals) {
4 | return Object.assign({}, defaultGlobals, {
5 | atob: atob,
6 | AbortController,
7 | fetch: fetch,
8 | ReadableStream:
9 | typeof ReadableStream !== 'undefined'
10 | ? ReadableStream
11 | : require('node:stream/web').ReadableStream,
12 | WritableStream:
13 | typeof WritableStream !== 'undefined'
14 | ? WritableStream
15 | : require('node:stream/web').WritableStream,
16 | TransformStream:
17 | typeof TransformStream !== 'undefined'
18 | ? TransformStream
19 | : require('node:stream/web').TransformStream,
20 | Headers: typeof Headers !== 'undefined' ? Headers : undefined,
21 | });
22 | },
23 | };
24 | };
25 |
--------------------------------------------------------------------------------
/tests/acceptance/open-graph-tags-test.js:
--------------------------------------------------------------------------------
1 | import { visit } from '@ember/test-helpers';
2 | import { module, skip } from 'qunit';
3 | import { setupApplicationTest } from 'ember-qunit';
4 |
5 | module('Acceptance | open graph tags', function (hooks) {
6 | setupApplicationTest(hooks);
7 |
8 | hooks.beforeEach(function () {
9 | return visit('/ember/1.0/classes/Container');
10 | });
11 |
12 | function findOpenGraphContent(propertyName) {
13 | const el = document.querySelector(
14 | `head meta[property="og:${propertyName}"]`,
15 | );
16 | return el.content;
17 | }
18 |
19 | skip('assigns title property', function (assert) {
20 | // TODO find a better way to update the og:title using ember-page-title
21 |
22 | const title = findOpenGraphContent('title');
23 | assert.equal(title, 'Container | 1.0.0 | Ember API Documentation');
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/tests/unit/utils/get-last-version-test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable qunit/no-assert-equal */
2 | import { A } from '@ember/array';
3 | import getLastVersion from 'ember-api-docs/utils/get-last-version';
4 | import { module, test } from 'qunit';
5 |
6 | module('Unit | Utility | get-latest-version', function () {
7 | test('should pick the latest version of ember', function (assert) {
8 | let versions = A(['ember-1.13.0', 'ember-2.7', 'ember-2.1.10']);
9 | let latestVersion = getLastVersion(versions);
10 | assert.equal(latestVersion, '2.7');
11 | });
12 |
13 | test('should pick the latest version of ember-data', function (assert) {
14 | let versions = A([
15 | 'ember-data-1.13.0',
16 | 'ember-data-2.7',
17 | 'ember-data-2.1.10',
18 | ]);
19 | let latestVersion = getLastVersion(versions);
20 | assert.equal(latestVersion, '2.7');
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/tests/integration/components/loading-spinner-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from 'qunit';
2 | import { setupRenderingTest } from 'ember-qunit';
3 | import { render } from '@ember/test-helpers';
4 | import hbs from 'htmlbars-inline-precompile';
5 |
6 | module('Integration | Component | loading spinner', function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test('it renders', async function (assert) {
10 | // Set any properties with this.set('myProperty', 'value');
11 | // Handle any actions with this.on('myAction', function(val) { ... });
12 |
13 | await render(hbs` `);
14 |
15 | assert.dom('*').hasText('');
16 |
17 | // Template block usage:
18 | await render(hbs`
19 |
20 | template block text
21 |
22 | `);
23 |
24 | assert.dom('*').hasText('template block text');
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/app/deprecation-workflow.js:
--------------------------------------------------------------------------------
1 | import setupDeprecationWorkflow from 'ember-cli-deprecation-workflow';
2 |
3 | setupDeprecationWorkflow({
4 | throwOnUnhandled: true,
5 | workflow: [
6 | { handler: 'throw', matchId: 'ember.component.reopen' },
7 | { handler: 'throw', matchId: 'implicit-injections' },
8 | { handler: 'throw', matchId: 'this-property-fallback' },
9 | { handler: 'throw', matchId: 'ember-component.is-visible' },
10 | {
11 | handler: 'throw',
12 | matchId: 'deprecated-run-loop-and-computed-dot-access',
13 | },
14 | {
15 | handler: 'silence',
16 | matchId: 'ember-data:deprecate-non-strict-relationships',
17 | },
18 | { handler: 'silence', matchId: 'ember-data:deprecate-store-find' },
19 | { handler: 'silence', matchId: 'remove-owner-inject' },
20 | { handler: 'silence', matchId: 'ember-polyfills.deprecate-assign' },
21 | ],
22 | });
23 |
--------------------------------------------------------------------------------
/tests/acceptance/redirects-test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable qunit/no-assert-equal */
2 | import { currentURL, visit } from '@ember/test-helpers';
3 | import { module, test } from 'qunit';
4 | import { setupApplicationTest } from 'ember-qunit';
5 |
6 | module('Acceptance | redirects', function (hooks) {
7 | setupApplicationTest(hooks);
8 |
9 | test('visiting /', async function (assert) {
10 | await visit('/');
11 | assert.equal(
12 | currentURL(),
13 | `/ember/release`,
14 | 'routes to the latest version of the project',
15 | );
16 | assert.dom('h1').hasText('Ember API Documentation');
17 | });
18 |
19 | test('visiting pre-2.16 version', async function (assert) {
20 | await visit('/ember/1.0');
21 |
22 | assert.equal(
23 | currentURL(),
24 | '/ember/1.0/modules/ember',
25 | 'routes to the first module of the project-version',
26 | );
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/app/helpers/version-lt.js:
--------------------------------------------------------------------------------
1 | import { helper } from '@ember/component/helper';
2 |
3 | /** Converts a compact version string like '2.16' into an int array [2, 16] */
4 | function toIntArray(versionStr) {
5 | let versionStrArray = versionStr.split('.');
6 | return versionStrArray.map((item) => parseInt(item));
7 | }
8 |
9 | function major(versionArray) {
10 | return versionArray[0];
11 | }
12 |
13 | function minor(versionArray) {
14 | return versionArray[1];
15 | }
16 |
17 | export function versionLt(params /*, hash*/) {
18 | let currentVersionArray = toIntArray(params[0]);
19 | let compareToVersionArray = toIntArray(params[1]);
20 | return (
21 | major(currentVersionArray) < major(compareToVersionArray) ||
22 | (major(currentVersionArray) === major(compareToVersionArray) &&
23 | minor(currentVersionArray) < minor(compareToVersionArray))
24 | );
25 | }
26 |
27 | export default helper(versionLt);
28 |
--------------------------------------------------------------------------------
/tests/acceptance/percy-test.js:
--------------------------------------------------------------------------------
1 | import { visit } from '@ember/test-helpers';
2 | import percySnapshot from '@percy/ember';
3 | import { setupApplicationTest } from 'ember-qunit';
4 | import { module, test } from 'qunit';
5 |
6 | let snapshots = [
7 | ['/', 'Landing Page'],
8 | ['/ember-data/', 'Ember Data Landing Page'],
9 | ['/ember/release/modules/@ember%2Fcomponent', 'Package Page'],
10 | ['/ember/release/classes/Component', 'Class Index'],
11 | ['/ember/release/functions/@ember%2Fcomponent/capabilities', 'Function Page'],
12 | ['/ember/release/namespaces/Instrumentation', 'Namespace Page'],
13 | ];
14 |
15 | module('Acceptance | percy', function (hooks) {
16 | setupApplicationTest(hooks);
17 |
18 | test('Percy snapshots', async function (assert) {
19 | for (let [page, title] of snapshots) {
20 | await visit(page);
21 | await percySnapshot(title);
22 | }
23 |
24 | assert.ok(true);
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/app/components/scroll-to-top-button.gjs:
--------------------------------------------------------------------------------
1 | import { modifier } from 'ember-modifier';
2 | import { on } from '@ember/modifier';
3 |
4 | const showOnScroll = modifier(function showOnScroll(element) {
5 | function toggleVisibility() {
6 | if (window.scrollY > window.innerHeight) {
7 | element.classList.add('is-visible');
8 | } else {
9 | element.classList.remove('is-visible');
10 | }
11 | }
12 |
13 | toggleVisibility();
14 | window.addEventListener('scroll', toggleVisibility);
15 |
16 | return () => {
17 | window.removeEventListener('scroll', toggleVisibility);
18 | };
19 | });
20 |
21 | function scrollToTopOfPageOnClick() {
22 | window.scrollTo({ top: 0, behavior: 'smooth' });
23 | }
24 |
25 |
26 |
33 |
34 |
--------------------------------------------------------------------------------
/public/assets/images/copy.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/models/project-version.js:
--------------------------------------------------------------------------------
1 | import Model, { attr, hasMany, belongsTo } from '@ember-data/model';
2 | import { computed } from '@ember/object';
3 | import getCompactVersion from '../utils/get-compact-version';
4 |
5 | export default Model.extend({
6 | version: attr(),
7 | classes: hasMany('class', { async: true }),
8 | modules: hasMany('module', { async: true }),
9 | namespaces: hasMany('namespace', { async: true }),
10 | 'public-classes': hasMany('class', { async: true }),
11 | 'private-classes': hasMany('class', { async: true }),
12 | 'public-modules': hasMany('module', { async: true }),
13 | 'private-modules': hasMany('module', { async: true }),
14 | 'public-namespaces': hasMany('namespace', { async: true }),
15 | 'private-namespaces': hasMany('namespace', { async: true }),
16 | project: belongsTo('project'),
17 | compactVersion: computed('version', function () {
18 | return getCompactVersion(this.version);
19 | }),
20 | });
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | /concat-stats-for
4 |
5 | # Terraform
6 | terraform.tfstate
7 | terraform.tfstate.backup
8 | .env
9 | .envrc
10 |
11 | # compiled output
12 | /dist/
13 | /tmp/
14 |
15 | # dependencies
16 | /bower_components/
17 | /node_modules/
18 |
19 | # misc
20 | /.projectile
21 | /.env*
22 | /.pnp*
23 | /.sass-cache
24 | /.eslintcache
25 | /connect.lock
26 | /coverage/
27 | /libpeerconnection.log
28 | /npm-debug.log*
29 | /testem.log
30 |
31 | public/json-docs/
32 | public/rev-index/
33 |
34 |
35 | browserstack-local.pid
36 | local.log
37 | .vscode/
38 |
39 | # ember-api-docs-data checkout
40 | /ember-api-docs-data/
41 | /ember-api-docs-data
42 |
43 | # ember-try
44 | /.node_modules.ember-try/
45 | /bower.json.ember-try
46 | /npm-shrinkwrap.json.ember-try
47 | /package.json.ember-try
48 | /package-lock.json.ember-try
49 | /yarn.lock.ember-try
50 |
51 | # broccoli-debug
52 | /DEBUG/
53 |
--------------------------------------------------------------------------------
/public/assets/images/header.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/acceptance/function-test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable qunit/no-assert-equal */
2 | import { module, test } from 'qunit';
3 | import { setupApplicationTest } from 'ember-qunit';
4 | import { visit } from '@ember/test-helpers';
5 |
6 | module('Acceptance | Function', function (hooks) {
7 | setupApplicationTest(hooks);
8 |
9 | test('shows function when loaded from url', async function (assert) {
10 | await visit('ember/2.18/functions/@ember%2Fapplication/getOwner');
11 |
12 | assert.dom('.method').exists({ count: 1 }, 'Single function per page');
13 | assert.dom('.method .method-name').hasText('getOwner');
14 | });
15 |
16 | test('shows function when loaded from url in a more modern version', async function (assert) {
17 | await visit('ember/3.28/functions/@ember%2Fapplication/getOwner');
18 |
19 | assert.dom('.method').exists({ count: 1 }, 'Single function per page');
20 | assert
21 | .dom('.method .method-name')
22 | .hasText('getOwner', 'Correct function is shown');
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/public/assets/images/link.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/tests/integration/helpers/better-get-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from 'qunit';
2 | import { setupRenderingTest } from 'ember-qunit';
3 | import { render } from '@ember/test-helpers';
4 | import hbs from 'htmlbars-inline-precompile';
5 |
6 | module('helper:better-get', function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test('should get dot separated', async function (assert) {
10 | let obj = {
11 | 'Ember.Object': 'hello',
12 | };
13 | this.set('dataStructure', obj);
14 | this.set('key', 'Ember.Object');
15 |
16 | await render(hbs`{{better-get this.dataStructure this.key}}`);
17 |
18 | assert.dom(this.element).hasText('hello');
19 | });
20 |
21 | test('should get rfc 176 module', async function (assert) {
22 | let obj = {
23 | '@ember/object': 'hello',
24 | };
25 | this.set('dataStructure', obj);
26 | this.set('key', '@ember/object');
27 |
28 | await render(hbs`{{better-get this.dataStructure this.key}}`);
29 |
30 | assert.dom(this.element).hasText('hello');
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/public/assets/images/discord-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {{content-for "head"}}
11 |
12 |
13 |
18 |
22 |
23 | {{content-for "head-footer"}}
24 |
25 |
26 | {{content-for "body"}}
27 |
28 |
29 |
30 |
31 | {{content-for "body-footer"}}
32 |
33 |
34 |
--------------------------------------------------------------------------------
/config/deploy.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node: true */
2 |
3 | module.exports = function (deployTarget) {
4 | var ENV = {
5 | build: {},
6 | gzip: {
7 | ignorePattern: '{fastboot/*.js,*.json}',
8 | keep: true,
9 | },
10 | // include other plugin configuration that applies to all deploy targets here
11 | };
12 |
13 | if (deployTarget === 'development') {
14 | ENV.build.environment = 'development';
15 | // configure other plugins for development deploy target here
16 | }
17 |
18 | if (deployTarget === 'staging') {
19 | ENV.build.environment = 'production';
20 | // configure other plugins for staging deploy target here
21 | }
22 |
23 | if (deployTarget === 'production') {
24 | ENV.build.environment = 'production';
25 | // configure other plugins for production deploy target here
26 | }
27 |
28 | // Note: if you need to build some configuration asynchronously, you can return
29 | // a promise that resolves with the ENV object instead of returning the
30 | // ENV object synchronously.
31 | return ENV;
32 | };
33 |
--------------------------------------------------------------------------------
/app/routes/project-version/namespaces/namespace.js:
--------------------------------------------------------------------------------
1 | import ClassRoute from '../classes/class';
2 | import getFullVersion from 'ember-api-docs/utils/get-full-version';
3 | import { inject as service } from '@ember/service';
4 |
5 | export default class NamespaceRoute extends ClassRoute {
6 | @service store;
7 |
8 | templateName = 'project-version/classes/class';
9 |
10 | async model(params) {
11 | const { project, project_version: compactVersion } =
12 | this.paramsFor('project-version');
13 |
14 | let projectRecord = await this.store.findRecord(
15 | 'project',
16 | project.toLowerCase(),
17 | );
18 | let projectVersion = getFullVersion(
19 | compactVersion,
20 | project,
21 | projectRecord,
22 | this.metaStore,
23 | );
24 | const klass = params['namespace'];
25 | return this.find(
26 | 'namespace',
27 | `${project}-${projectVersion}-${klass}`.toLowerCase(),
28 | );
29 | }
30 |
31 | serialize(model) {
32 | return {
33 | namespace: model.get('name'),
34 | };
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/integration/components/import-example-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from 'qunit';
2 | import { setupRenderingTest } from 'ember-qunit';
3 | import { render } from '@ember/test-helpers';
4 | import hbs from 'htmlbars-inline-precompile';
5 |
6 | module('Integration | Component | import example', function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test('it renders a class import example', async function (assert) {
10 | await render(
11 | hbs`{{!-- template-lint-disable no-potential-path-strings --}}
12 | `,
13 | );
14 | assert.dom('*').hasText("import Application from '@ember/application';");
15 | });
16 |
17 | test('it renders a function import example', async function (assert) {
18 | await render(
19 | hbs`{{!-- template-lint-disable no-potential-path-strings --}}
20 | `,
21 | );
22 | assert.dom('*').hasText("import { uniqBy } from '@ember/object/computed';");
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/app/templates/error.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{#if (eq this.model.status 404)}}
3 | Ack! 404 friend, you're in the wrong place
4 | {{#if this.model.attemptedVersion}}
5 |
6 | {{this.model.message}}
7 |
8 |
9 | Modules, classes, and functions sometimes move around or are renamed across versions.
10 | Try the v{{this.model.attemptedVersion}} API docs index.
11 |
12 | {{else}}
13 |
14 | This page wasn't found. Please try the API docs page .
15 | If you expected something else to be here, please file a ticket .
16 |
17 | {{/if}}
18 | {{else}}
19 |
20 | Whoops! Something went wrong.
21 |
22 | {{/if}}
23 |
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2016 Ember Learning Team and contributors
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9 |
--------------------------------------------------------------------------------
/app/routes/index.js:
--------------------------------------------------------------------------------
1 | import Route from '@ember/routing/route';
2 | import { inject as service } from '@ember/service';
3 |
4 | export default class IndexRoute extends Route {
5 | /** @type {import('@ember/routing/router-service').default} */
6 | @service
7 | router;
8 |
9 | @service store;
10 |
11 | async redirect() {
12 | // redirect to first available project ember => ember-data => ember-cli
13 | let foundProject = 'ember-cli';
14 | try {
15 | await this.store.findRecord('project', 'ember', {
16 | includes: 'project-version',
17 | });
18 | foundProject = 'ember';
19 | } catch {
20 | try {
21 | await this.store.findRecord('project', 'ember-data', {
22 | includes: 'project-version',
23 | });
24 | foundProject = 'ember-data';
25 | } catch (e) {
26 | foundProject = 'ember-cli';
27 | }
28 | }
29 |
30 | if (foundProject === 'ember-cli') {
31 | return this.router.transitionTo('ember-cli');
32 | }
33 | return this.router.transitionTo('project-version', foundProject, 'release');
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/routes/project.js:
--------------------------------------------------------------------------------
1 | import Route from '@ember/routing/route';
2 | import { inject as service } from '@ember/service';
3 |
4 | export default class ProjectRoute extends Route {
5 | /** @type {import('@ember/routing/router-service').default} */
6 | @service
7 | router;
8 |
9 | @service store;
10 |
11 | model({ project: projectName }) {
12 | let projectNameToLookUp = 'ember';
13 |
14 | // Accounts for old references to ember-data API docs
15 | if (projectName.indexOf('data') !== -1) {
16 | projectNameToLookUp = 'ember-data';
17 | }
18 |
19 | if (projectName.indexOf('cli') !== -1) {
20 | return this.router.transitionTo('ember-cli');
21 | }
22 |
23 | return this.store.findRecord('project', projectNameToLookUp, {
24 | includes: 'project-version',
25 | });
26 | }
27 |
28 | // Using redirect instead of afterModel so transition succeeds and returns 307 in fastboot
29 | redirect(project /*, transition */) {
30 | return this.router.transitionTo(
31 | 'project-version',
32 | project.get('id'),
33 | 'release',
34 | );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/routes/module.js:
--------------------------------------------------------------------------------
1 | import { inject as service } from '@ember/service';
2 | import Route from '@ember/routing/route';
3 |
4 | export default class ModuleRoute extends Route {
5 | /** @type {import('@ember/routing/router-service').default} */
6 | @service
7 | router;
8 |
9 | @service
10 | legacyModuleMappings;
11 |
12 | model(params) {
13 | return {
14 | moduleName: params.module.substr(0, params.module.lastIndexOf('.')),
15 | mappings: this.legacyModuleMappings.buildMappings(
16 | this.legacyModuleMappings.legacyMappings,
17 | ),
18 | };
19 | }
20 |
21 | redirect(model) {
22 | let mappingInfo = this.legacyModuleMappings.getNewModuleFromOld(
23 | model.moduleName,
24 | model.mappings,
25 | );
26 | if (!mappingInfo.error && model.moduleName !== mappingInfo.module) {
27 | return this.router.transitionTo(
28 | `project-version.modules.module`,
29 | 'ember',
30 | 'release',
31 | mappingInfo.module,
32 | );
33 | }
34 | return this.router.transitionTo('project-version', 'ember', 'release');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/services/algolia.js:
--------------------------------------------------------------------------------
1 | import Service from '@ember/service';
2 | import algoliasearch from 'algoliasearch';
3 | import config from 'ember-api-docs/config/environment';
4 |
5 | export default class AlgoliaService extends Service {
6 | async search(query, params) {
7 | if (query) {
8 | if (Array.isArray(query) && !params) {
9 | // if multiple indices
10 | return this._client.search(query);
11 | } else if (!params) {
12 | // if no params
13 | return this.accessIndex(query.indexName).search(query.query);
14 | } else {
15 | // if params and callback
16 | return this.accessIndex(query.indexName).search(query.query, params);
17 | }
18 | }
19 | }
20 |
21 | accessIndex(IndexName) {
22 | if (!this._indices[IndexName]) {
23 | this._indices[IndexName] = this._client.initIndex(IndexName);
24 | }
25 | return this._indices[IndexName];
26 | }
27 |
28 | constructor() {
29 | super(...arguments);
30 | this._client = algoliasearch(
31 | config.algolia.algoliaId,
32 | config.algolia.algoliaKey,
33 | );
34 | this._indices = {};
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/acceptance/link-from-ember-data-to-ember-test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable qunit/no-assert-equal */
2 | import { module, test } from 'qunit';
3 | import { setupApplicationTest } from 'ember-qunit';
4 | import { visit, click, currentURL, settled } from '@ember/test-helpers';
5 | import { timeout } from 'ember-concurrency';
6 |
7 | async function waitForSettled() {
8 | await settled();
9 | await timeout(10);
10 | await settled();
11 | }
12 |
13 | module('Acceptance | link from ember data to ember test', function (hooks) {
14 | setupApplicationTest(hooks);
15 |
16 | test('extends link test', async function (assert) {
17 | await visit('/ember-data/3.14/classes/Errors');
18 | await waitForSettled();
19 | await click('[data-test-extends-link]');
20 | await waitForSettled();
21 | assert.equal(currentURL(), '/ember/3.14/classes/ArrayProxy');
22 | });
23 |
24 | test('uses link test', async function (assert) {
25 | await visit('/ember-data/3.14/classes/Errors');
26 | await waitForSettled();
27 | await click('[data-test-uses-link]');
28 | await waitForSettled();
29 | assert.equal(currentURL(), '/ember/3.14/classes/Evented');
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/run-tests.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable n/no-process-exit */
2 | const spawn = require('spawndamnit');
3 | const ember = `./node_modules/.bin/ember`;
4 |
5 | const userName = process.env['BROWSERSTACK_USERNAME'];
6 | const accessKey = process.env['BROWSERSTACK_ACCESS_KEY'];
7 |
8 | const canConnectToBrowserStack =
9 | userName &&
10 | userName.trim().length !== 0 &&
11 | accessKey &&
12 | accessKey.trim().length !== 0;
13 |
14 | const hookupLoggers = (proc) => {
15 | proc.on('stdout', (data) => console.log(data.toString()));
16 | proc.on('stderr', (data) => console.error(data.toString()));
17 | };
18 |
19 | const execEmberProcess = async (cmd) => {
20 | let proc = spawn(ember, [cmd]);
21 | hookupLoggers(proc);
22 | try {
23 | let { code } = await proc;
24 | if (code !== 0) {
25 | throw Error();
26 | }
27 | } catch (e) {
28 | return process.exit(1); // eslint-disable-line no-process-exit
29 | }
30 | };
31 |
32 | (async () => {
33 | if (canConnectToBrowserStack) {
34 | await execEmberProcess('browserstack:connect');
35 | }
36 |
37 | await execEmberProcess('exam');
38 |
39 | if (canConnectToBrowserStack) {
40 | await execEmberProcess('browserstack:disconnect');
41 | }
42 | })();
43 |
--------------------------------------------------------------------------------
/tests/integration/helpers/is-latest-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from 'qunit';
2 | import { setupRenderingTest } from 'ember-qunit';
3 | import { render } from '@ember/test-helpers';
4 | import hbs from 'htmlbars-inline-precompile';
5 | import { A } from '@ember/array';
6 |
7 | const versions = A(['ember-1.13.0', 'ember-3.5.0', 'ember-2.1.10']);
8 |
9 | module('helper:is-latest', function (hooks) {
10 | setupRenderingTest(hooks);
11 |
12 | test('should resolve true if latest release', async function (assert) {
13 | this.set('version', '3.5.0');
14 | this.set('allVersions', versions);
15 |
16 | await render(hbs`
17 | {{#if (is-latest version=this.version allVersions=this.allVersions)}}
18 | Hello World
19 | {{/if}}
20 | `);
21 |
22 | assert.dom(this.element).hasText('Hello World');
23 | });
24 |
25 | test('should resolve false if not latest', async function (assert) {
26 | this.set('version', '3.1.0');
27 | this.set('allVersions', versions);
28 | await render(hbs`
29 | {{#if (is-latest version=this.version allVersions=this.allVersions)}}
30 | Hello World
31 | {{/if}}
32 | `);
33 | assert.notEqual(this.element.textContent.trim(), 'Hello World');
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/app/routes/data-class.js:
--------------------------------------------------------------------------------
1 | import { inject as service } from '@ember/service';
2 | import Route from '@ember/routing/route';
3 |
4 | export default class DataClassRoute extends Route {
5 | /** @type {import('@ember/routing/router-service').default} */
6 | @service
7 | router;
8 |
9 | @service
10 | legacyModuleMappings;
11 |
12 | model(params) {
13 | return {
14 | mappings: this.legacyModuleMappings.buildMappings(
15 | this.legacyModuleMappings.legacyMappings,
16 | ),
17 | className: params['class'].substr(0, params['class'].lastIndexOf('.')),
18 | };
19 | }
20 |
21 | redirect(model) {
22 | let mappingInfo = this.legacyModuleMappings.getNewClassFromOld(
23 | model.className,
24 | model.mappings,
25 | );
26 | let { newName } = mappingInfo;
27 | if (newName.substr(0, 3) === 'DS.') {
28 | newName = newName.substr(3);
29 | }
30 | if (!mappingInfo.error) {
31 | return this.router.transitionTo(
32 | `project-version.classes.class`,
33 | 'ember-data',
34 | 'release',
35 | newName,
36 | );
37 | } else {
38 | return this.router.transitionTo('project-version', 'ember', 'release');
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/public/assets/images/search-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/routes/project-version/modules/module.js:
--------------------------------------------------------------------------------
1 | import ClassRoute from '../classes/class';
2 | import getFullVersion from 'ember-api-docs/utils/get-full-version';
3 | import { inject as service } from '@ember/service';
4 |
5 | export default class ModuleRoute extends ClassRoute {
6 | @service store;
7 |
8 | async model(params) {
9 | const { project, project_version: compactVersion } =
10 | this.paramsFor('project-version');
11 |
12 | let projectObj = await this.store.findRecord('project', project);
13 | let projectVersion = getFullVersion(
14 | compactVersion,
15 | project,
16 | projectObj,
17 | this.metaStore,
18 | );
19 | let klass = params['module'];
20 |
21 | // These modules should not have `ember-` tacked onto the front of them
22 | // when forming the ids and URLs.
23 | let isNotEmber = klass.match(/@warp-drive|@glimmer|rsvp|jquery/);
24 |
25 | if (!~klass.indexOf(project) && !isNotEmber) {
26 | klass = `${project}-${klass}`;
27 | }
28 |
29 | return this.find(
30 | 'module',
31 | `${project}-${projectVersion}-${klass}`.toLowerCase(),
32 | );
33 | }
34 |
35 | serialize(model) {
36 | return {
37 | module: model.get('name'),
38 | };
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/routes/not-found.js:
--------------------------------------------------------------------------------
1 | import Route from '@ember/routing/route';
2 | import { inject as service } from '@ember/service';
3 |
4 | export default class NotFoundRoute extends Route {
5 | @service fastboot;
6 |
7 | beforeModel() {
8 | if (!this.fastboot.isFastBoot) {
9 | return;
10 | }
11 |
12 | this.fastboot.response.statusCode = 404;
13 | }
14 |
15 | redirect() {
16 | if (typeof window === 'undefined' || !window.location) {
17 | return;
18 | }
19 | const url = new URL(window.location.href);
20 | const anchor = url.searchParams.get('anchor');
21 | const redirected = url.searchParams.get('redirected');
22 | if (anchor && !redirected) {
23 | url.searchParams.set('redirected', '1');
24 | // If we get here with an anchor query param, it means something in the
25 | // api docs app is continuing to link to it (sometimes in the markdown)
26 | // Force full reload to get netlify redirects
27 | window.location.replace(url.toString());
28 | // A more elegant solution would be to restore the various routes that
29 | // handled ?anchor previously and redirect from there but that would
30 | // necessitate adding support to the Ember Router to redirect to #anchors
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/components/api-index.js:
--------------------------------------------------------------------------------
1 | import Component from '@glimmer/component';
2 |
3 | /**
4 | * @typedef ItemData
5 | * @property {Array<{ name: string }>} methods
6 | * @property {Array<{ name: string }>} properties
7 | * @property {Array<{ name: string }>} events
8 | */
9 |
10 | /**
11 | * @typedef Args
12 | * @property {ItemData} itemData
13 | */
14 |
15 | /**
16 | * @typedef Blocks
17 | * @property {[{ sections: ApiIndex['sections'] }]} default
18 | */
19 |
20 | /**
21 | * @extends Component<{ Args: Args, Blocks: Blocks }>
22 | */
23 | export default class ApiIndex extends Component {
24 | get sections() {
25 | return [
26 | {
27 | title: 'Methods',
28 | tab: 'methods',
29 | items: this.args.itemData.methods,
30 | class: 'spec-method-list',
31 | routeSuffix: '.methods.method',
32 | },
33 | {
34 | title: 'Properties',
35 | tab: 'properties',
36 | items: this.args.itemData.properties,
37 | class: 'spec-property-list',
38 | routeSuffix: '.properties.property',
39 | },
40 | {
41 | title: 'Events',
42 | tab: 'events',
43 | items: this.args.itemData.events,
44 | class: 'spec-event-list',
45 | routeSuffix: '.events.event',
46 | },
47 | ];
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/controllers/project-version/index.js:
--------------------------------------------------------------------------------
1 | import Controller from '@ember/controller';
2 | import { htmlSafe } from '@ember/template';
3 |
4 | export default Controller.extend({
5 | oldPackageImportSyntax: htmlSafe(
6 | "
1\n2\n3\n4 import Ember from 'ember' ; \nexport default Ember.Component.extend({\n});
",
7 | ),
8 |
9 | newPackageImportSyntax: htmlSafe(
10 | "
1\n2\n3\n4 import Component from '@ember/component' ; \nexport default Component.extend({\n});
",
11 | ),
12 | });
13 |
--------------------------------------------------------------------------------
/app/serializers/application.js:
--------------------------------------------------------------------------------
1 | import { inject as service } from '@ember/service';
2 | import JSONAPISerializer from '@ember-data/serializer/json-api';
3 |
4 | export default class Application extends JSONAPISerializer {
5 | @service
6 | metaStore;
7 |
8 | extractId(_, hash) {
9 | return hash.id.toLowerCase();
10 | }
11 |
12 | extractRelationship(relationship) {
13 | // for some reason we only need to update the id to lowercase when it's an object relationship and not an array 🤷
14 | if (relationship?.data?.id) {
15 | relationship.data.id = relationship.data.id.toLowerCase();
16 | }
17 | return super.extractRelationship(relationship);
18 | }
19 |
20 | normalizeFindRecordResponse(store, primaryModelClass, payload, id) {
21 | let normalizedDocument = super.normalizeFindRecordResponse(...arguments);
22 |
23 | // We do this because ember data doesn't handle meta data in accordance to json-api spec yet
24 | if (primaryModelClass.modelName === 'project') {
25 | this.metaStore.availableProjectVersions[id] =
26 | normalizedDocument.meta.availableVersions;
27 | } else if (primaryModelClass.modelName === 'project-version') {
28 | this.metaStore.addToProjectRevMap(id, normalizedDocument.meta);
29 | }
30 |
31 | return normalizedDocument;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/routes/data-module.js:
--------------------------------------------------------------------------------
1 | import { inject as service } from '@ember/service';
2 | import Route from '@ember/routing/route';
3 |
4 | export default class DataModuleRoute extends Route {
5 | /** @type {import('@ember/routing/router-service').default} */
6 | @service
7 | router;
8 |
9 | @service
10 | legacyModuleMappings;
11 |
12 | model(params) {
13 | return {
14 | moduleName: params.module.substr(0, params.module.lastIndexOf('.')),
15 | mappings: this.legacyModuleMappings.buildMappings(
16 | this.legacyModuleMappings.legacyMappings,
17 | ),
18 | };
19 | }
20 |
21 | redirect(model) {
22 | let { moduleName, mappings } = model;
23 | if (moduleName.indexOf('ember-data') === 0) {
24 | moduleName = '@' + moduleName;
25 | }
26 | let mappingInfo = this.legacyModuleMappings.getNewModuleFromOld(
27 | moduleName,
28 | mappings,
29 | );
30 | if (mappingInfo.module === '@ember-data') {
31 | return this.router.transitionTo(
32 | `project-version`,
33 | 'ember-data',
34 | 'release',
35 | );
36 | } else {
37 | return this.router.transitionTo(
38 | `project-version.modules.module`,
39 | 'ember-data',
40 | 'release',
41 | mappingInfo.module,
42 | );
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/integration/helpers/version-lt-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from 'qunit';
2 | import { setupRenderingTest } from 'ember-qunit';
3 | import { render } from '@ember/test-helpers';
4 | import hbs from 'htmlbars-inline-precompile';
5 |
6 | module('helper:version-lt', function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test('should calculate major version diffs', async function (assert) {
10 | await render(hbs`{{version-lt '2.10' '3.0' 'ember'}}`);
11 | assert.dom(this.element).hasText('true');
12 | });
13 |
14 | test('should calculate minor version diffs', async function (assert) {
15 | await render(hbs`{{version-lt '2.15' '2.16' 'ember'}}`);
16 | assert.dom(this.element).hasText('true');
17 | });
18 |
19 | test('should fail if major greater', async function (assert) {
20 | await render(hbs`{{version-lt '3.0' '2.16' 'ember'}}`);
21 | assert.dom(this.element).hasText('false');
22 | });
23 |
24 | test('should fail if minor greater', async function (assert) {
25 | await render(hbs`{{version-lt '2.17' '2.16' 'ember'}}`);
26 | assert.dom(this.element).hasText('false');
27 | });
28 |
29 | test('should fail if equal', async function (assert) {
30 | await render(hbs`{{version-lt '2.16' '2.16' 'ember'}}`);
31 | assert.dom(this.element).hasText('false');
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | EmberApiDocs Tests
6 |
7 |
8 |
9 | {{content-for "head"}} {{content-for "test-head"}}
10 |
11 |
12 |
13 |
14 |
15 | {{content-for "head-footer"}} {{content-for "test-head-footer"}}
16 |
17 |
18 | {{content-for "body"}} {{content-for "test-body"}}
19 |
20 |
21 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | {{content-for "body-footer"}} {{content-for "test-body-footer"}}
34 |
35 |
36 |
--------------------------------------------------------------------------------
/public/assets/images/twitter-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 |
13 |
15 |
16 |
18 | image/svg+xml
19 |
21 |
22 |
23 |
24 |
25 |
27 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/templates/project-version.hbs:
--------------------------------------------------------------------------------
1 | {{page-title @model.version}}
2 |
3 |
40 |
--------------------------------------------------------------------------------
/public/assets/images/google-plus-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 |
13 |
15 |
16 |
18 | image/svg+xml
19 |
21 |
22 |
23 |
24 |
25 |
27 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/tests/helpers/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | setupApplicationTest as upstreamSetupApplicationTest,
3 | setupRenderingTest as upstreamSetupRenderingTest,
4 | setupTest as upstreamSetupTest,
5 | } from 'ember-qunit';
6 |
7 | // This file exists to provide wrappers around ember-qunit's / ember-mocha's
8 | // test setup functions. This way, you can easily extend the setup that is
9 | // needed per test type.
10 |
11 | function setupApplicationTest(hooks, options) {
12 | upstreamSetupApplicationTest(hooks, options);
13 |
14 | // Additional setup for application tests can be done here.
15 | //
16 | // For example, if you need an authenticated session for each
17 | // application test, you could do:
18 | //
19 | // hooks.beforeEach(async function () {
20 | // await authenticateSession(); // ember-simple-auth
21 | // });
22 | //
23 | // This is also a good place to call test setup functions coming
24 | // from other addons:
25 | //
26 | // setupIntl(hooks); // ember-intl
27 | // setupMirage(hooks); // ember-cli-mirage
28 | }
29 |
30 | function setupRenderingTest(hooks, options) {
31 | upstreamSetupRenderingTest(hooks, options);
32 |
33 | // Additional setup for rendering tests can be done here.
34 | }
35 |
36 | function setupTest(hooks, options) {
37 | upstreamSetupTest(hooks, options);
38 |
39 | // Additional setup for unit tests can be done here.
40 | }
41 |
42 | export { setupApplicationTest, setupRenderingTest, setupTest };
43 |
--------------------------------------------------------------------------------
/app/routes/class.js:
--------------------------------------------------------------------------------
1 | import { inject as service } from '@ember/service';
2 | import Route from '@ember/routing/route';
3 |
4 | export default class ClassRoute extends Route {
5 | /** @type {import('@ember/routing/router-service').default} */
6 | @service
7 | router;
8 |
9 | @service
10 | legacyModuleMappings;
11 |
12 | model(params) {
13 | let ret = {
14 | mappings: this.legacyModuleMappings.buildMappings(
15 | this.legacyModuleMappings.legacyMappings,
16 | ),
17 | className: params['class'].substr(0, params['class'].lastIndexOf('.')),
18 | };
19 | return ret;
20 | }
21 |
22 | redirect(model) {
23 | let mappedInfo = this.legacyModuleMappings.getNewClassFromOld(
24 | model.className,
25 | model.mappings,
26 | );
27 | if (!mappedInfo.error && model.className !== mappedInfo.newName) {
28 | let { itemType, newName, newModule } = mappedInfo;
29 | if (itemType === 'class') {
30 | return this.router.transitionTo(
31 | `project-version.classes.class`,
32 | 'ember',
33 | 'release',
34 | newName,
35 | );
36 | } else {
37 | return this.router.transitionTo(
38 | `project-version.functions.function`,
39 | 'ember',
40 | 'release',
41 | newModule,
42 | newName,
43 | );
44 | }
45 | }
46 | return this.router.transitionTo('project-version', 'ember', 'release');
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/config/manifest.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | 'use strict';
3 |
4 | module.exports = function (environment, appConfig) {
5 | // See https://github.com/san650/ember-web-app#documentation for a list of
6 | // supported properties
7 | return {
8 | name: 'Ember API Docs',
9 | short_name: 'Ember API',
10 | description: 'Ember & Ember Data API Documentation',
11 | start_url: appConfig.routerRootURL,
12 | display: 'standalone',
13 | background_color: '#FDFDFD',
14 | theme_color: '#f67862',
15 | scope: appConfig.routerRootURL,
16 | lang: 'en-US',
17 | icons: [
18 | {
19 | src: '/assets/images/launch-icon-4x.png',
20 | sizes: '192x192',
21 | type: 'image/png',
22 | },
23 | {
24 | src: '/assets/images/launch-icon-3x.png',
25 | sizes: '144x122',
26 | type: 'image/png',
27 | },
28 | {
29 | src: '/assets/images/launch-icon-2x.png',
30 | sizes: '96x96',
31 | type: 'image/png',
32 | },
33 | {
34 | src: '/assets/images/launch-icon-1-5x.png',
35 | sizes: '72x72',
36 | type: 'image/png',
37 | },
38 | {
39 | src: '/assets/images/launch-icon-1x.png',
40 | sizes: '48x48',
41 | type: 'image/png',
42 | },
43 | {
44 | src: '/assets/images/launch-icon-0-75x.png',
45 | sizes: '36x36',
46 | type: 'image/png',
47 | },
48 | ],
49 | apple: {
50 | statusBarStyle: 'black-translucent',
51 | },
52 | };
53 | };
54 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - main
8 | pull_request:
9 |
10 | env:
11 | NODE_VERSION: 20
12 | PERCY_PARALLEL_NONCE: ${{ github.run_id }}-${{ github.run_number }}
13 | PERCY_PARALLEL_TOTAL: 1
14 |
15 | concurrency:
16 | group: ci-${{ github.head_ref || github.ref }}
17 | cancel-in-progress: true
18 |
19 | jobs:
20 | lint:
21 | name: "Lint"
22 | runs-on: ubuntu-latest
23 | timeout-minutes: 10
24 |
25 | steps:
26 | - uses: actions/checkout@v4
27 | - uses: pnpm/action-setup@v4
28 | - uses: actions/setup-node@v4
29 | with:
30 | cache: pnpm
31 | node-version: ${{ env.NODE_VERSION }}
32 | - name: Install Dependencies
33 | run: pnpm install
34 | - name: Lint
35 | run: pnpm run lint
36 |
37 | test:
38 | name: "Test"
39 | runs-on: ubuntu-latest
40 | timeout-minutes: 10
41 |
42 | steps:
43 | - uses: actions/checkout@v4
44 | - uses: pnpm/action-setup@v4
45 | - uses: actions/setup-node@v4
46 | with:
47 | cache: pnpm
48 | node-version: ${{ env.NODE_VERSION }}
49 | - run: pnpm install
50 | - run: pnpm run clone
51 | - name: Run Tests
52 | env:
53 | PERCY_PARALLEL_NONCE: ${{ env.PERCY_PARALLEL_NONCE }}
54 | PERCY_PARALLEL_TOTAL: ${{ env.PERCY_PARALLEL_TOTAL }}
55 | PERCY_TOKEN: 5ad6687f6b1ad3dec2b964f94d3d59ff3880baccf1492c0663e85c1ce79c1a52
56 | run: pnpm percy exec -- pnpm run test:ember
57 |
--------------------------------------------------------------------------------
/tests/acceptance/head-test.js:
--------------------------------------------------------------------------------
1 | import { visit } from '@ember/test-helpers';
2 | import ENV from 'ember-api-docs/config/environment';
3 | import { module, test } from 'qunit';
4 |
5 | import { setupApplicationTest } from 'ember-qunit';
6 |
7 | module('Acceptance | head', function (hooks) {
8 | setupApplicationTest(hooks);
9 |
10 | hooks.beforeEach(function () {
11 | this.head = document.querySelector('head');
12 | });
13 |
14 | test('no link rel=canonical for release url', async function (assert) {
15 | await visit('/ember/release/classes/Application');
16 | assert.dom('link[rel=canonical]', this.head).doesNotExist();
17 | });
18 |
19 | test('shows link rel=canonical for version url', async function (assert) {
20 | await visit('/ember/2.16/classes/Application');
21 | assert.dom('link[rel=canonical]', this.head).hasAttribute('href');
22 | });
23 |
24 | test('no link rel=canonical when root url visited', async function (assert) {
25 | await visit('/');
26 | assert.dom('link[rel=canonical]', this.head).doesNotExist();
27 | });
28 |
29 | test('dns prefetch should be populated', async function (assert) {
30 | await visit('/ember/release/classes/Application');
31 | assert
32 | .dom('link[rel=dns-prefetch]', this.head)
33 | .hasAttribute('href', ENV.API_HOST);
34 | });
35 |
36 | test('dns prefetch should be populated when root url visited', async function (assert) {
37 | await visit('/');
38 | assert
39 | .dom('link[rel=dns-prefetch]', this.head)
40 | .hasAttribute('href', ENV.API_HOST);
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/ember-cli-build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const EmberApp = require('ember-cli/lib/broccoli/ember-app');
4 | const envIsProduction = process.env.EMBER_ENV === 'production';
5 | const premberUrls = require('./prember-urls');
6 |
7 | module.exports = function (defaults) {
8 | const app = new EmberApp(defaults, {
9 | prember: {
10 | urls: premberUrls(),
11 | },
12 | fingerprint: {
13 | extensions: ['js', 'css', 'jpg', 'png', 'gif', 'map', 'webmanifest'],
14 | generateAssetMap: true,
15 | },
16 | autoprefixer: {
17 | enabled: true,
18 | cascade: true,
19 | sourcemap: !envIsProduction,
20 | overrideBrowsersList: ['default'],
21 | },
22 | 'ember-composable-helpers': {
23 | only: ['join', 'map-by'],
24 | },
25 | 'asset-cache': {
26 | version: '4', //Might have to change this with the app build,
27 | },
28 | svgJar: {
29 | sourceDirs: ['public/assets/images'],
30 | },
31 | 'ember-cli-babel': {
32 | includePolyfill: true,
33 | },
34 | babel: {
35 | plugins: [
36 | // ... any other plugins
37 | require.resolve('ember-concurrency/async-arrow-task-transform'),
38 |
39 | // NOTE: put any code coverage plugins last, after the transform.
40 | ],
41 | },
42 | });
43 |
44 | const { Webpack } = require('@embroider/webpack');
45 | const appTree = require('@embroider/compat').compatBuild(app, Webpack, {
46 | staticAddonTrees: true,
47 | staticAddonTestSupportTrees: true,
48 | staticHelpers: true,
49 | staticModifiers: true,
50 | staticComponents: true,
51 | });
52 |
53 | return require('prember').prerender(app, appTree);
54 | };
55 |
--------------------------------------------------------------------------------
/app/components/ember-data-landing-page.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | Ember Data API Documentation
4 |
5 |
6 |
7 | Ember Data is a library for robustly managing data in applications built with Ember.js.
8 |
9 |
10 | Commonly searched-for documentation
11 |
12 |
13 |
14 |
15 | Model
16 |
17 | - an object that represents the underlying data that your application presents to the user.
18 |
19 |
20 |
21 | Store
22 |
23 | - a service that contains all of the data for records loaded from the server.
24 |
25 |
26 |
27 | Adapter
28 |
29 | - determines how data is persisted to a backend data store.
30 |
31 |
32 |
33 | Serializer
34 |
35 | - format the data sent to and received from the backend store.
36 |
37 |
38 |
39 | Useful links
40 |
41 |
42 |
43 |
48 |
49 |
50 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/app/helpers/github-link.js:
--------------------------------------------------------------------------------
1 | import { helper } from '@ember/component/helper';
2 | import githubMap, { mainDir } from '../utils/github-map';
3 |
4 | export function githubLink([project, version, file, line], { isEdit = false }) {
5 | const isEmberProject = project === 'ember';
6 | const majorVersion = parseInt(version?.split('.')[0].replace('v', ''), 10);
7 |
8 | // Check if the project is 'ember' and adjust the tag only if the major version is >= 6 to match the Git tags
9 | const adjustedVersion =
10 | isEmberProject && majorVersion >= 6 ? `${version}-ember-source` : version;
11 |
12 | if (isEdit) {
13 | return `https://github.com/${githubMap[project]}/edit/release${mainDir(
14 | project,
15 | adjustedVersion,
16 | )}${file}#L${line}`;
17 | }
18 |
19 | // This 'packages' replacement can be removed if the following PR goes into a patch release of
20 | // Ember Data 4.12: https://github.com/emberjs/data/pull/8598/files
21 | //
22 | // If the file has packages already in the path, make sure we don't
23 | // add duplicate packages via the mainDir function.
24 | // Fixes an issue with ember data URLS having an incorrect
25 | // 'packages/packages' in the GitHub source URL
26 | // For example, without this fixedFile line, a `file` with value
27 | // '../packages/store/addon/-private/record-arrays/identifier-array.ts'
28 | // would become
29 | // 'https://github.com/emberjs/data/tree/v4.10.0/packages/packages/store/addon/-private/record-arrays/identifier-array.ts#L118'
30 | const fixedFile = file?.replace('../packages/', '../');
31 |
32 | return `https://github.com/${
33 | githubMap[project]
34 | }/tree/v${adjustedVersion}${mainDir(
35 | project,
36 | adjustedVersion,
37 | )}${fixedFile}#L${line}`;
38 | }
39 |
40 | export default helper(githubLink);
41 |
--------------------------------------------------------------------------------
/app/routes/application.js:
--------------------------------------------------------------------------------
1 | import { inject as service } from '@ember/service';
2 | import Route from '@ember/routing/route';
3 | import { set } from '@ember/object';
4 | import ENV from 'ember-api-docs/config/environment';
5 | import { isDestroying, isDestroyed } from '@ember/destroyable';
6 |
7 | export default class ApplicationRoute extends Route {
8 | @service
9 | headData;
10 |
11 | @service
12 | legacyModuleMappings;
13 |
14 | @service
15 | router;
16 |
17 | @service
18 | fastboot;
19 |
20 | @service
21 | metrics;
22 |
23 | @service
24 | routerScroll;
25 |
26 | constructor() {
27 | super(...arguments);
28 | if (!this.fastboot.isFastBoot) {
29 | this.router.on('routeDidChange', this.trackPage);
30 |
31 | /* Hax from https://github.com/DockYard/ember-router-scroll/issues/263
32 | to handle router scroll behavior when the page was initially served
33 | with fastboot
34 | */
35 | this.routerScroll.set('preserveScrollPosition', true);
36 |
37 | setTimeout(() => {
38 | if (!isDestroying(this) && !isDestroyed(this)) {
39 | this.routerScroll.set('preserveScrollPosition', false);
40 | }
41 | }, 1000);
42 | }
43 | }
44 |
45 | trackPage = () => {
46 | // this is constant for this app and is only used to identify page views in the GA dashboard
47 | const hostname = ENV.APP.domain.replace(/(http|https)?:?\/\//g, '');
48 |
49 | const page = this.router.currentURL;
50 | const title = this.router.currentRouteName ?? 'unknown';
51 | this.metrics.trackPage({ page, title, hostname });
52 | };
53 |
54 | async afterModel() {
55 | set(this, 'headData.cdnDomain', ENV.API_HOST);
56 | await this.legacyModuleMappings.initMappings();
57 | return super.afterModel(...arguments);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/public/assets/images/ribbon-js.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/components/table-of-contents.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | Show Private / Deprecated packages
4 |
5 |
6 |
7 | Packages
8 |
9 | {{#each @moduleIDs as |moduleID|}}
10 |
11 | {{#if (not-eq moduleID '@ember/object/computed')}}
12 |
13 | {{moduleID}}
14 |
15 | {{/if}}
16 |
17 | {{/each}}
18 |
19 |
20 |
21 | {{#if @isShowingNamespaces}}
22 |
23 | Namespaces
24 |
25 | {{#each @namespaceIDs as |namespaceID|}}
26 |
27 | {{namespaceID}}
28 |
29 | {{/each}}
30 |
31 |
32 | {{/if}}
33 |
34 |
35 | Classes
36 |
37 | {{#each @classesIDs as |classID|}}
38 |
39 | {{classID}}
40 |
41 | {{/each}}
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/public/assets/images/github-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 |
13 |
15 |
16 |
18 | image/svg+xml
19 |
21 |
22 |
23 |
24 |
25 |
27 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/services/search.js:
--------------------------------------------------------------------------------
1 | import Service, { inject as service } from '@ember/service';
2 | import { restartableTask } from 'ember-concurrency';
3 | import { tracked } from '@glimmer/tracking';
4 |
5 | export default class SearchService extends Service {
6 | @service('algolia') _algoliaService;
7 | @service('project') projectService;
8 |
9 | /** @type {?string} */
10 | #lastQueriedProjectVersion = null;
11 |
12 | @tracked results = [];
13 |
14 | get projectVersion() {
15 | return this.projectService.version;
16 | }
17 |
18 | search = restartableTask(async (query) => {
19 | const projectVersion = this.projectVersion;
20 |
21 | const params = {
22 | hitsPerPage: 15,
23 | restrictSearchableAttributes: [
24 | 'hierarchy.lvl0',
25 | 'hierarchy.lvl1',
26 | 'hierarchy.lvl2',
27 | ],
28 | tagFilters: [`version:${projectVersion}`],
29 | facetFilters: ['access:-private'],
30 | };
31 |
32 | const searchObj = {
33 | indexName: 'methods',
34 | query,
35 | };
36 |
37 | this.#lastQueriedProjectVersion = projectVersion;
38 |
39 | this.results = await this.doSearch(searchObj, params);
40 | return this.results;
41 | });
42 |
43 | doSearch(searchObj, params) {
44 | return this._algoliaService
45 | .search(searchObj, params)
46 | .then((results) => results.hits);
47 | }
48 |
49 | /**
50 | * Whenever the version changes in service:project, the results in this
51 | * service become stale. Presenting them any further could allow the user to
52 | * undo their version change by clicking a stale link.
53 | * @returns {boolean}
54 | */
55 | hasStaleResults() {
56 | return (
57 | this.#lastQueriedProjectVersion !== null &&
58 | this.projectVersion !== this.#lastQueriedProjectVersion
59 | );
60 | }
61 |
62 | clearResults() {
63 | this.results = [];
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/tests/acceptance/sidebar-nav-test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable qunit/no-assert-equal */
2 | import { module, test } from 'qunit';
3 | import { setupApplicationTest } from 'ember-qunit';
4 | import { visit, click, currentURL, find, settled } from '@ember/test-helpers';
5 | import { timeout } from 'ember-concurrency';
6 |
7 | module('Acceptance | sidebar navigation', function (hooks) {
8 | setupApplicationTest(hooks);
9 |
10 | test('can navigate to namespace from sidebar', async function (assert) {
11 | await visit('/ember/1.0');
12 | await click(find(`[data-test-namespace="Ember.String"] a`));
13 |
14 | await timeout(10);
15 | await settled();
16 |
17 | assert.equal(
18 | currentURL(),
19 | '/ember/1.0/namespaces/Ember.String',
20 | 'navigated to namespace',
21 | );
22 | });
23 |
24 | test('can navigate to module from sidebar', async function (assert) {
25 | await visit('/ember/1.0');
26 | await click(find(`[data-test-module="ember-application"] a`));
27 |
28 | await timeout(10);
29 | await settled();
30 |
31 | assert.equal(
32 | currentURL(),
33 | '/ember/1.0/modules/ember-application',
34 | 'navigated to module',
35 | );
36 | });
37 |
38 | test('can navigate to class from sidebar', async function (assert) {
39 | await visit('/ember/1.0');
40 | await click(find(`[data-test-class="Ember.Component"] a`));
41 |
42 | await timeout(10);
43 | await settled();
44 |
45 | assert.equal(
46 | currentURL(),
47 | '/ember/1.0/classes/Ember.Component',
48 | 'navigated to class',
49 | );
50 | });
51 |
52 | test('can navigate to home landing page', async function (assert) {
53 | await visit('/ember-data/2.12');
54 | await click('[data-test-home] a');
55 |
56 | await timeout(10);
57 | await settled();
58 |
59 | assert.equal(currentURL(), '/ember/release', 'navigated to landing page');
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/tests/unit/helpers/github-link-test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable qunit/no-assert-equal */
2 | import { githubLink } from 'ember-api-docs/helpers/github-link';
3 | import { module, test } from 'qunit';
4 |
5 | module('Unit | Helper | github link', function () {
6 | test('should render a github link for ember from file info', function (assert) {
7 | let result = githubLink(
8 | ['ember', '2.10.0', 'ember-glimmer/lib/component.js', '35'],
9 | {},
10 | );
11 | assert.equal(
12 | result,
13 | 'https://github.com/emberjs/ember.js/tree/v2.10.0/ember-glimmer/lib/component.js#L35',
14 | );
15 | });
16 |
17 | test('should append "ember-source" to the version for git tags v6 and above', function (assert) {
18 | let result = githubLink(
19 | ['ember', '6.0.0', 'ember-glimmer/lib/component.js', '35'],
20 | {},
21 | );
22 | assert.equal(
23 | result,
24 | 'https://github.com/emberjs/ember.js/tree/v6.0.0-ember-source/ember-glimmer/lib/component.js#L35',
25 | );
26 | });
27 |
28 | test('should render a github link for ember-data from file info', function (assert) {
29 | let result = githubLink(
30 | ['ember-data', '2.10.0', 'addon/-private/adapters/errors.js', '10'],
31 | {},
32 | );
33 | assert.equal(
34 | result,
35 | 'https://github.com/emberjs/data/tree/v2.10.0/addon/-private/adapters/errors.js#L10',
36 | );
37 | });
38 |
39 | test('should render a github link for ember-data from file info for v3.11+', function (assert) {
40 | let result = githubLink(
41 | [
42 | 'ember-data',
43 | '3.12.2',
44 | '../adapter/addon/-private/adapters/errors.ts',
45 | '8',
46 | ],
47 | {},
48 | );
49 | assert.equal(
50 | result,
51 | 'https://github.com/emberjs/data/tree/v3.12.2/packages/-ember-data/../adapter/addon/-private/adapters/errors.ts#L8',
52 | );
53 | });
54 | });
55 |
--------------------------------------------------------------------------------
/app/router.js:
--------------------------------------------------------------------------------
1 | import EmberRouter from '@ember/routing/router';
2 | import config from 'ember-api-docs/config/environment';
3 | import { withHashSupport } from 'ember-api-docs/utils/url-hash-polyfill';
4 |
5 | // The following adds support for URL hash routing for those URLs not rendered with fastboot
6 | @withHashSupport
7 | class AppRouter extends EmberRouter {
8 | location = config.locationType;
9 | rootURL = config.routerRootURL;
10 | }
11 |
12 | AppRouter.map(function () {
13 | this.route('ember-cli');
14 | this.route('project', { path: '/:project' });
15 |
16 | this.route(
17 | 'project-version',
18 | { path: '/:project/:project_version' },
19 | function () {
20 | this.route('classes', function () {
21 | this.route('class', { path: '/:class' });
22 | });
23 |
24 | this.route('functions', function () {
25 | this.route('function', { path: '/:module/:fn' });
26 | });
27 |
28 | // Namespace routes
29 | this.route('namespaces', function () {
30 | this.route('namespace', { path: '/:namespace' });
31 | });
32 |
33 | // Module routes
34 | this.route('modules', function () {
35 | this.route('module', { path: '/:module' });
36 | });
37 | },
38 | );
39 | this.route('class', { path: '/classes/:class' });
40 | this.route('module', { path: '/modules/:module' });
41 | this.route('data-class', { path: '/data/classes/:class' });
42 | this.route('data-module', { path: '/data/modules/:module' });
43 | this.route('not-found', { path: '/*' });
44 | });
45 |
46 | /*
47 | 404
48 | ember-cli
49 | project
50 |
51 | OTHER STATES
52 | private, deprecated, inherited, protected
53 | inherited is not reflected in URL state but it's checked by default
54 |
55 | MAYBE REDIRECTS
56 |
57 | /data/modules/:module
58 | /data/classes/:class
59 | /modules/:module
60 | /classes/:class
61 |
62 | See _redirects for netlify redirects
63 | */
64 |
65 | export default AppRouter;
66 |
--------------------------------------------------------------------------------
/tests/acceptance/search-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from 'qunit';
2 | import { visit, fillIn, focus } from '@ember/test-helpers';
3 | import { selectChoose } from 'ember-power-select/test-support';
4 | import { setupApplicationTest } from 'ember-qunit';
5 | import Selectors from '../helpers/search-selectors';
6 | import searchResultsV4_1 from 'ember-api-docs/tests/fixtures/searchresult-v-4-1';
7 | import searchResultsV5_1 from 'ember-api-docs/tests/fixtures/searchresult-v-5-1';
8 |
9 | module('Acceptance | search', function (hooks) {
10 | setupApplicationTest(hooks);
11 |
12 | test('search for an EmberArray function and navigate properly', async function (assert) {
13 | await visit('/');
14 |
15 | const algoliaService = this.owner.lookup('service:algolia');
16 |
17 | algoliaService.search = async () => {
18 | return searchResultsV4_1;
19 | };
20 |
21 | await fillIn(Selectors.input, 'forEach');
22 |
23 | assert
24 | .dom(Selectors.searchResult)
25 | .hasAttribute('href', '/ember/4.1/classes/EmberArray#forEach');
26 | });
27 |
28 | test('discards stale search results when version changes', async function (assert) {
29 | await visit('/');
30 |
31 | const algoliaService = this.owner.lookup('service:algolia');
32 |
33 | algoliaService.search = async () => {
34 | return searchResultsV4_1;
35 | };
36 |
37 | await selectChoose('.ember-power-select-trigger', '4.1');
38 |
39 | await fillIn(Selectors.input, 'forEach');
40 |
41 | // the url contains /ember/4.1/
42 | assert.dom(Selectors.searchResult).hasAttribute('href', /\/ember\/4\.1\//);
43 |
44 | algoliaService.search = async () => {
45 | return searchResultsV5_1;
46 | };
47 |
48 | await selectChoose('.ember-power-select-trigger', '5.1');
49 |
50 | await focus(Selectors.input);
51 |
52 | // the url contains /ember/5.1/
53 | assert.dom(Selectors.searchResult).hasAttribute('href', /\/ember\/5\.1\//);
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/tests/integration/components/class-field-description-test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable qunit/no-assert-equal */
2 | import EmberObject from '@ember/object';
3 | import { module, test } from 'qunit';
4 | import { setupRenderingTest } from 'ember-qunit';
5 | import { render, findAll, find } from '@ember/test-helpers';
6 | import hbs from 'htmlbars-inline-precompile';
7 |
8 | module('Integration | Component | class field description', function (hooks) {
9 | setupRenderingTest(hooks);
10 |
11 | test('it renders', async function (assert) {
12 | this.set('type', 'method');
13 | this.set(
14 | 'field',
15 | EmberObject.create({
16 | access: 'public',
17 | deprecated: true,
18 | name: 'concat',
19 | description: 'concatenates',
20 | params: [{ name: 'param1' }, { name: 'param2' }, { name: 'param3' }],
21 | }),
22 | );
23 |
24 | await render(
25 | hbs` `,
26 | );
27 |
28 | assert.dom('.method-name').hasText('concat');
29 | assert.dom(findAll('.access')[0]).hasText('public');
30 | assert.dom(findAll('.access')[1]).hasText('deprecated');
31 | assert.dom(findAll('.args')[0]).hasText('param1, param2, param3');
32 | });
33 |
34 | test('parameter props are displayed', async function (assert) {
35 | this.set('type', 'method');
36 | this.set(
37 | 'field',
38 | EmberObject.create({
39 | access: 'public',
40 | deprecated: true,
41 | name: 'concat',
42 | description: 'concatenates',
43 | params: [
44 | { name: 'param1' },
45 | { name: 'param2' },
46 | { name: 'options', props: [{ name: 'prop1' }, { name: 'prop2' }] },
47 | ],
48 | }),
49 | );
50 |
51 | await render(
52 | hbs` `,
53 | );
54 |
55 | assert.dom(find('.prop:nth-child(1) dt')).hasText('prop1');
56 | assert.dom(find('.prop:nth-child(2) dt')).hasText('prop2');
57 | });
58 | });
59 |
--------------------------------------------------------------------------------
/public/assets/images/fastly-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/assets/images/ribbon-hbs.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/controllers/project-version/modules/module.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable ember/no-computed-properties-in-native-classes, ember/classic-decorator-no-classic-methods */
2 | import { computed } from '@ember/object';
3 | import { inject as service } from '@ember/service';
4 | import { alias } from '@ember/object/computed';
5 | import ClassController from '../classes/class';
6 | import uniq from 'lodash.uniq';
7 | import union from 'lodash.union';
8 |
9 | export default class ModuleController extends ClassController {
10 | @service
11 | filterData;
12 |
13 | @alias('filterData.sideNav.showPrivate')
14 | showPrivateClasses;
15 |
16 | @computed('model.submodules')
17 | get submodules() {
18 | return Object.keys(this.get('model.submodules'));
19 | }
20 |
21 | @computed('model.namespaces')
22 | get namespaces() {
23 | return Object.keys(this.get('model.namespaces'));
24 | }
25 |
26 | @computed('model.{privateclasses,publicclasses}', 'showPrivateClasses')
27 | get classes() {
28 | if (this.showPrivateClasses) {
29 | return this.get('model.publicclasses').concat(
30 | this.get('model.privateclasses'),
31 | );
32 | }
33 | return this.get('model.publicclasses');
34 | }
35 |
36 | @computed('classes', 'namespaces')
37 | get classesAndNamespaces() {
38 | return uniq(union(this.namespaces, this.classes).sort(), true);
39 | }
40 |
41 | @computed('model.{allstaticfunctions,staticfunctions}', 'showPrivateClasses')
42 | get functionHeadings() {
43 | if (this.get('model.allstaticfunctions') && this.showPrivateClasses) {
44 | return Object.keys(this.get('model.allstaticfunctions')).sort();
45 | } else if (this.get('model.staticfunctions')) {
46 | return Object.keys(this.get('model.staticfunctions')).sort();
47 | }
48 | return {};
49 | }
50 |
51 | @computed('model.{allstaticfunctions,staticfunctions}', 'showPrivateClasses')
52 | get functions() {
53 | if (this.showPrivateClasses && this.get('model.allstaticfunctions')) {
54 | return this.get('model.allstaticfunctions');
55 | }
56 | return this.get('model.staticfunctions');
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/services/meta-store.js:
--------------------------------------------------------------------------------
1 | import Service from '@ember/service';
2 | import { isPresent } from '@ember/utils';
3 | import { set } from '@ember/object';
4 | import { A } from '@ember/array';
5 | import getCompactVersion from 'ember-api-docs/utils/get-compact-version';
6 | import getLastVersion from 'ember-api-docs/utils/get-last-version';
7 | import { tracked } from '@glimmer/tracking';
8 |
9 | export default class MetaStoreService extends Service {
10 | @tracked availableProjectVersions = {
11 | ember: A(),
12 | 'ember-data': A(),
13 | };
14 | @tracked projectRevMap = {};
15 |
16 | addToProjectRevMap(projectVersionKey, projectRevDoc) {
17 | let projectRevMap = this.projectRevMap;
18 | if (!isPresent(projectRevMap[projectVersionKey])) {
19 | projectRevMap[projectVersionKey] = projectRevDoc;
20 | set(this, 'projectRevMap', projectRevMap);
21 | }
22 | }
23 |
24 | getRevId(project, version, type, id) {
25 | let encodedId = id;
26 | let revType = this.projectRevMap[`${project}-${version}`][type];
27 |
28 | let matchingKey = Object.keys(revType).find(
29 | (key) => key.toLowerCase() === encodedId.toLowerCase(),
30 | );
31 |
32 | return this.projectRevMap[`${project}-${version}`][type][matchingKey];
33 | }
34 |
35 | getEncodedModulesFromProjectRev(id) {
36 | return Object.keys(this.projectRevMap[id].module).sort();
37 | }
38 |
39 | initializeStore(availableProjectVersions, projectRevMap) {
40 | this.availableProjectVersions = {
41 | ember: A(availableProjectVersions['ember']),
42 | 'ember-data': A(availableProjectVersions['ember-data']),
43 | };
44 | this.projectRevMap = projectRevMap;
45 | }
46 |
47 | getFullVersion(projectName, compactProjVersion) {
48 | const availProjVersions = this.availableProjectVersions[projectName];
49 | let filtered = availProjVersions.filter(
50 | (v) => getCompactVersion(v) === getCompactVersion(compactProjVersion),
51 | );
52 | if (filtered.length === 0) {
53 | return;
54 | }
55 | // since there can be multiple full versions that match the compact version, use the most recent one.
56 | return getLastVersion(filtered);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/acceptance/module-test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable qunit/no-assert-equal */
2 | import { module, test } from 'qunit';
3 | import { setupApplicationTest } from 'ember-qunit';
4 | import { visit, click, findAll } from '@ember/test-helpers';
5 |
6 | module('Acceptance | Module', function (hooks) {
7 | setupApplicationTest(hooks);
8 |
9 | test('lists all public/private classes and namespaces on the module page', async function (assert) {
10 | await visit('ember/1.0/modules/ember-handlebars');
11 |
12 | const store = this.owner.lookup('service:store');
13 | const container = store.peekRecord(
14 | 'module',
15 | 'ember-1.0.0-ember-handlebars',
16 | );
17 |
18 | let numberNameSpaces = Object.keys(container.get('namespaces')).length;
19 | let numberPublicClasses = Object.keys(
20 | container.get('publicclasses'),
21 | ).length;
22 | let numberPrivateClasses = Object.keys(
23 | container.get('privateclasses'),
24 | ).length;
25 |
26 | assert.equal(
27 | findAll('.spec-property-list li').length,
28 | numberPublicClasses + numberNameSpaces,
29 | );
30 |
31 | await click('[data-test-private-deprecated-toggle]');
32 | assert.equal(
33 | findAll('.spec-property-list li').length,
34 | numberPublicClasses + numberNameSpaces + numberPrivateClasses,
35 | );
36 | });
37 |
38 | test('lists all submodules on the module page', async function (assert) {
39 | await visit('ember/1.0/modules/ember');
40 |
41 | const store = this.owner.lookup('service:store');
42 | const container = store.peekRecord('module', 'ember-1.0.0-ember');
43 |
44 | let numberSubModules = Object.keys(container.get('submodules')).length;
45 |
46 | assert.equal(findAll('.spec-method-list li').length, numberSubModules);
47 | });
48 |
49 | test('display submodule parent', async function (assert) {
50 | await visit('ember/1.0/modules/ember-application');
51 |
52 | const store = this.owner.lookup('service:store');
53 | const container = store.peekRecord(
54 | 'module',
55 | 'ember-1.0.0-ember-application',
56 | );
57 | assert.dom(`.attribute-value`).hasText(container.get('parent'));
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/tests/acceptance/switch-project-test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable qunit/no-assert-equal */
2 | import { module, test } from 'qunit';
3 | import { setupApplicationTest } from 'ember-qunit';
4 | import {
5 | visit,
6 | click,
7 | findAll,
8 | find,
9 | currentURL,
10 | settled,
11 | } from '@ember/test-helpers';
12 | import { selectSearch } from 'ember-power-select/test-support';
13 | import { timeout } from 'ember-concurrency';
14 |
15 | async function waitForSettled() {
16 | await settled();
17 | await timeout(10);
18 | await settled();
19 | }
20 |
21 | async function ensureVersionsExist(assert) {
22 | await waitForSettled();
23 | await selectSearch('.select-container', '2');
24 | await settled();
25 |
26 | assert.dom('.ember-power-select-options').exists({ count: 1 });
27 | assert.ok(findAll('.ember-power-select-options')[0].children.length > 1);
28 | }
29 |
30 | module('Acceptance | Switch Project', function (hooks) {
31 | setupApplicationTest(hooks);
32 |
33 | test('Can switch projects back and forth', async function (assert) {
34 | assert.expect(9);
35 | await visit('/');
36 |
37 | await click('.spec-ember-data');
38 | await ensureVersionsExist(assert);
39 | assert.dom(find('.spec-ember-data')).hasClass('active');
40 |
41 | await click('.spec-ember');
42 | await ensureVersionsExist(assert);
43 | assert.dom(find('.spec-ember')).hasClass('active');
44 |
45 | await click('.spec-ember-data');
46 | await ensureVersionsExist(assert);
47 | assert.dom(find('.spec-ember-data')).hasClass('active');
48 | });
49 |
50 | test('Can open class after switching projects back and forth', async function (assert) {
51 | assert.expect(10);
52 | await visit('/');
53 | await ensureVersionsExist(assert);
54 | assert.dom(find('.spec-ember')).hasClass('active');
55 |
56 | await click('.spec-ember-data');
57 | await ensureVersionsExist(assert);
58 | assert.dom(find('.spec-ember-data')).hasClass('active');
59 |
60 | await click('.spec-ember');
61 | await ensureVersionsExist(assert);
62 | assert.dom(find('.spec-ember')).hasClass('active');
63 |
64 | await click('[data-test-class=EmberObject] > a');
65 | await waitForSettled();
66 | assert.equal(currentURL(), '/ember/release/classes/EmberObject');
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/tests/unit/utils/create-excerpt-test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable qunit/no-ok-equality */
2 | import createExcerpt from 'ember-api-docs/utils/create-excerpt';
3 | import { module, test } from 'qunit';
4 |
5 | module('Unit | Utility | create excerpt', function () {
6 | const testString =
7 | 'HashLocation implements the location API using the browser's\nhash. At present, it relies on a hashchange event existing in the\nbrowser.
\nUsing HashLocation results in URLs with a # (hash sign) separating the\nserver side URL portion of the URL from the portion that is used by Ember.
\nExample:
\n\n
\n
\n \n \n app/router.js \n \n \n \n \n 1\n2\n3\n4\n5\n6\n7\n8\n9\n \n Router.map(function () {\n this .route('posts' , function () {\n this .route('new' );\n });\n});\n\nRouter.reopen({\n location: 'hash' \n}); \n \n \n
\n
\n \nThis will result in a posts.new url of /#/posts/new.
\n';
8 |
9 | test('it works', function (assert) {
10 | let result = createExcerpt(testString);
11 | assert.ok(result.includes('/\n') === false, 'does not include tabs');
12 | assert.ok(
13 | result.includes(' fn.name === functionName),
64 | };
65 | }
66 |
67 | afterModel(model) {
68 | let description = model.fn.description;
69 | if (description) {
70 | set(this, 'headData.description', createExcerpt(description));
71 | }
72 | }
73 |
74 | getFunctionObjFromList(classObj, functionName) {
75 | return classObj.get('methods').find((fn) => {
76 | return fn.name === functionName;
77 | });
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/DEPLOYING.md:
--------------------------------------------------------------------------------
1 | ## Deployment Playbook
2 |
3 | This playbook is not intended as a complete set of instructions, but rather pointers and reminders for maintainers, plus emergency maintenance.
4 |
5 | ### Deploying to staging/test environment
6 |
7 | Auto-generated deployments associated with Pull Requests typically do not work as expected
8 | due to unusual environmental configurations. To see an app in staging, they must be deployed
9 | from the [Heroku console](https://dashboard.heroku.com/teams/ember/overview) instead.
10 |
11 | 1. Visit [the ember team org](https://dashboard.heroku.com/teams/ember/overview) in the Heroku
12 | console.
13 | 2. Select the "Apps" tab and then ember-api-docs
14 | 3. From the "Staging" column, click the arrows next to the instance name, choose "Deploy a
15 | branch," and select the branch you wish to deploy to staging.
16 | 4. When deployment is complete, click the arrows next to the instance again and choose
17 | "Open app in browser"
18 |
19 | ### Deploying to production
20 |
21 | First, deploy the `master` branch to staging following the steps above, and check it over thoroughly. Get a second person to look it over too. There are many variables that could affect how an app displays in production compared to locally, particularly for this app.
22 |
23 | **Never click the "promote to production" button on Heroku.** We shouldn't promote from staging with this button because Heroku uses the same build from staging in the production container. This would mean that the variables set during ember builds get reused. Some of these variables have different values between staging and production, which leads to issues.
24 |
25 | To deploy properly to production, click on the arrows for the `ember-api-docs` instance under Production, choose "Deploy a branch" and choose the branch name.
26 |
27 | ### Rolling back to an earlier version
28 |
29 | Never "revert a merge" on GitHub. Never click the "promote to production" button on Heroku.
30 |
31 | From within the Heroku console, click on the instance name, i.e. ember-api-docs-staging,
32 | choose the "Activity" tab, and choose "Roll back to here."
33 |
34 | ### About the API docs stack
35 |
36 | This app is the final piece of the api docs stack. The stack begins with
37 | documentation blocks in the ember.js source code. When new versions of
38 | Ember are released, a worker intakes the documentation from S3 and turns it into
39 | jsonapi, [ember-jsonapi-docs](https://github.com/ember-learn/ember-jsonapi-docs).
40 | The resulting docs are fetched and consumed by this app.
41 |
--------------------------------------------------------------------------------
/public/assets/images/ember-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/integration/components/table-of-contents-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from 'qunit';
2 | import { setupRenderingTest } from 'ember-qunit';
3 | import { render, findAll } from '@ember/test-helpers';
4 | import hbs from 'htmlbars-inline-precompile';
5 |
6 | const CLASSES = ['Descriptor', 'Ember'];
7 | const MODULES = ['@ember/application', '@ember/array'];
8 |
9 | module('Integration | Component | table of contents', function (hooks) {
10 | setupRenderingTest(hooks);
11 |
12 | test('it renders classes', async function (assert) {
13 | // Set any properties with this.set('myProperty', 'value');
14 | this.set('emberVersion', '2.4.3');
15 | this.set('classesIDs', CLASSES);
16 |
17 | await render(hbs`
18 |
24 | `);
25 |
26 | const contentTitle = document.querySelector(
27 | '[data-test-toc-title="classes"]',
28 | );
29 | const contentReference = '.sub-table-of-contents';
30 |
31 | assert.dom(contentTitle).includesText('Classes');
32 | assert
33 | .dom(`${contentReference} li`)
34 | .exists({ count: 2 }, 'We have two items to display');
35 | assert.dom(findAll(`${contentReference} li`)[0]).hasText(CLASSES[0]);
36 | assert.dom(findAll(`${contentReference} li`)[1]).hasText(CLASSES[1]);
37 | });
38 |
39 | test('it renders packages', async function (assert) {
40 | // Set any properties with this.set('myProperty', 'value');
41 | this.set('emberVersion', '2.4.3');
42 | this.set('moduleIDs', MODULES);
43 |
44 | await render(hbs`
45 |
51 | `);
52 |
53 | const contentReference = '.sub-table-of-contents';
54 | const content = document.querySelector(contentReference);
55 | const contentTitle = document.querySelector(
56 | '[data-test-toc-title="packages"]',
57 | );
58 |
59 | assert.dom(contentTitle).includesText('Packages');
60 | assert
61 | .dom(`${contentReference} li`)
62 | .exists({ count: 2 }, 'We have two items to display');
63 | assert.dom(content).isVisible();
64 | assert.dom(findAll(`${contentReference} li`)[0]).hasText(MODULES[0]);
65 | assert.dom(findAll(`${contentReference} li`)[1]).hasText(MODULES[1]);
66 | });
67 | });
68 |
--------------------------------------------------------------------------------
/tests/acceptance/class-test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable qunit/no-assert-equal */
2 | import { module, test } from 'qunit';
3 | import { setupApplicationTest } from 'ember-qunit';
4 | import { visit, click, findAll, currentURL } from '@ember/test-helpers';
5 |
6 | module('Acceptance | Class', function (hooks) {
7 | setupApplicationTest(hooks);
8 |
9 | hooks.beforeEach(async function () {
10 | await visit('/ember/1.0/classes/Container');
11 | await click('[data-test-checkbox="inherited"]');
12 | await click('[data-test-checkbox="protected"]');
13 | await click('[data-test-checkbox="private"]');
14 | await click('[data-test-checkbox="deprecated"]');
15 | });
16 |
17 | test('lists all the methods on the class page', async function (assert) {
18 | const store = this.owner.lookup('service:store');
19 | const container = store.peekRecord('class', 'ember-1.0.0-container');
20 | assert.equal(
21 | findAll('.spec-method-list li').length,
22 | container.get('methods.length'),
23 | );
24 |
25 | await click('[data-test-checkbox="private"]'); // turn private back off
26 |
27 | assert.equal(
28 | findAll('.spec-method-list li').length,
29 | container.get('methods').filter((method) => method.access !== 'private')
30 | .length,
31 | );
32 | });
33 |
34 | test('lists all the properties on the class page', function (assert) {
35 | const store = this.owner.lookup('service:store');
36 | const container = store.peekRecord('class', 'ember-1.0.0-container');
37 | assert.equal(
38 | findAll('.spec-property-list li').length,
39 | container.get('properties.length'),
40 | );
41 | });
42 |
43 | test('lists all the events on the class page', function (assert) {
44 | const store = this.owner.lookup('service:store');
45 | const container = store.peekRecord('class', 'ember-1.0.0-container');
46 | assert.equal(
47 | findAll('.spec-event-list li').length,
48 | container.get('events.length'),
49 | );
50 | });
51 |
52 | test('has query params for access visibilities', function (assert) {
53 | let params = (currentURL().match(/show=([\w\d%]*)/)[1] || '').split('%2C');
54 | assert.notOk(
55 | params.includes('inherited'),
56 | 'show param does not include inherited because it is the default and we unselected it',
57 | );
58 | assert.ok(params.includes('protected'), 'show param includes protected');
59 | assert.ok(params.includes('private'), 'show param includes private');
60 | assert.ok(params.includes('deprecated'), 'show param includes deprecated');
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/app/controllers/project-version/classes/class.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable ember/no-computed-properties-in-native-classes, ember/classic-decorator-no-classic-methods */
2 | import { action, computed, set, get } from '@ember/object';
3 | import { inject as service } from '@ember/service';
4 | import Controller from '@ember/controller';
5 | import { A } from '@ember/array';
6 | import { capitalize } from '@ember/string';
7 | import { isEmpty } from '@ember/utils';
8 | import { parentName } from '../../../utils/parent-name';
9 |
10 | const filterTypes = ['inherited', 'protected', 'private', 'deprecated'];
11 | const DEFAULT_FILTER = 'inherited';
12 |
13 | export default class ClassController extends Controller {
14 | /** @type {import('@ember/routing/router-service').default} */
15 | @service
16 | router;
17 |
18 | @service
19 | filterData;
20 |
21 | queryParams = [{ visibilityFilter: 'show' }];
22 |
23 | @service
24 | legacyModuleMappings;
25 |
26 | @service
27 | metaStore;
28 |
29 | @computed(
30 | 'filterData.{showInherited,showProtected,showPrivate,showDeprecated}',
31 | )
32 | get visibilityFilter() {
33 | let appliedFilters = filterTypes
34 | .reduce((filters, filter) => {
35 | let filterValue = get(this, `filterData.show${capitalize(filter)}`)
36 | ? filter
37 | : null;
38 | filters.push(filterValue);
39 | return filters;
40 | }, A())
41 | .compact();
42 |
43 | if (isEmpty(appliedFilters)) {
44 | return DEFAULT_FILTER;
45 | } else {
46 | return appliedFilters.join(',');
47 | }
48 | }
49 |
50 | set visibilityFilter(value = '') {
51 | let filters = A(value.split(','));
52 | filterTypes.forEach((filter) => {
53 | let enabled = filters.indexOf(filter) > -1;
54 | set(this, `filterData.show${capitalize(filter)}`, enabled);
55 | });
56 | }
57 |
58 | @computed('legacyModuleMappings.mappings', 'model.{module,name}')
59 | get hasImportExample() {
60 | return this.legacyModuleMappings.hasClassMapping(
61 | this.get('model.name'),
62 | this.get('model.module'),
63 | );
64 | }
65 |
66 | @computed('legacyModulemappings.mappings', 'model.{module,name}')
67 | get module() {
68 | return this.legacyModuleMappings.getModule(
69 | this.get('model.name'),
70 | this.get('model.module'),
71 | );
72 | }
73 |
74 | @computed('metaStore.availableProjectVersions', 'model.project.id')
75 | get allVersions() {
76 | return this.get('metaStore.availableProjectVersions')[
77 | this.get('model.project.id')
78 | ];
79 | }
80 |
81 | get parentName() {
82 | return parentName(this.router.currentRouteName);
83 | }
84 |
85 | @action
86 | updateFilter(filter) {
87 | this.toggleProperty(`filterData.${filter}`);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/bin/ember-fastboot:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | "use strict";
3 |
4 | /* eslint-env node */
5 | const FastBootAppServer = require("fastboot-app-server");
6 | const ExpressHTTPServer = require("fastboot-app-server/src/express-http-server");
7 | const parseArgs = require("minimist");
8 | const express = require("express");
9 | const { URL } = require("url");
10 | const enforce = require("express-sslify");
11 |
12 | // Provide a title to the process in `ps`
13 | process.title = "ember-fastboot-server";
14 |
15 | let argOptions = {
16 | default: { port: 3000, host: "::" },
17 | };
18 |
19 | let options = parseArgs(process.argv.slice(2), argOptions);
20 | let distPath = options._[0];
21 |
22 | if (!distPath) {
23 | console.error(
24 | `You must call ember-fastboot with the path of a fastboot-dist directory:
25 | ember-fastboot fastboot-dist`,
26 | );
27 | process.exit(1);
28 | }
29 |
30 | const serverOptions = {
31 | port: options.port,
32 | distPath,
33 | gzip: false, // Let Fastly take care of compression, reducing load on the fastboot
34 | };
35 |
36 | const httpServer = new ExpressHTTPServer(serverOptions);
37 |
38 | const app = httpServer.app;
39 |
40 | app.use(
41 | enforce.HTTPS({
42 | trustProtoHeader: true,
43 | trustXForwardedHostHeader: true,
44 | }),
45 | );
46 |
47 | app.use(
48 | express.static(distPath, {
49 | setHeaders(res, path) {
50 | if (!path.endsWith("index.html")) {
51 | res.setHeader("Cache-Control", "public, max-age=365000000, immutable");
52 | }
53 | res.removeHeader("X-Powered-By");
54 | },
55 | }),
56 | );
57 |
58 | /** We rewrite the 307 location header into a relativeURL so that our special setup is handled */
59 | app.use(function (req, res, next) {
60 | const originalSendFn = res.send;
61 |
62 | res.send = function () {
63 | if (res.hasHeader("location")) {
64 | let originalLocation = res.getHeader("location");
65 |
66 | // FastBoot broke us once by removing the protocol so adding a check for safety
67 | if (originalLocation.startsWith("//")) {
68 | originalLocation = `http:${originalLocation}`;
69 | }
70 |
71 | let relativeURL = "/ember/2.14/namespaces/Ember";
72 |
73 | try {
74 | relativeURL = new URL(originalLocation).pathname;
75 | } catch (e) {
76 | console.log(`Original location value: ${originalLocation}`);
77 | console.log(e);
78 | }
79 |
80 | res.setHeader("location", relativeURL);
81 | arguments[0] = arguments[0].replace(
82 | new RegExp(originalLocation, "g"),
83 | relativeURL,
84 | );
85 | }
86 | originalSendFn.apply(res, arguments);
87 | };
88 |
89 | res.removeHeader("X-Powered-By");
90 | next();
91 | });
92 |
93 | let server = new FastBootAppServer(
94 | Object.assign({ httpServer }, serverOptions),
95 | );
96 |
97 | server.start();
98 |
--------------------------------------------------------------------------------
/app/services/legacy-module-mappings.js:
--------------------------------------------------------------------------------
1 | import Service from '@ember/service';
2 | import { tracked } from '@glimmer/tracking';
3 |
4 | import legacyMappings from 'ember-rfc176-data/mappings.json';
5 |
6 | const LOCALNAME_CONVERSIONS = {
7 | Object: 'EmberObject',
8 | Array: 'EmberArray',
9 | Error: 'EmberError',
10 | };
11 |
12 | export default class LegacyModuleMappingsService extends Service {
13 | @tracked mappings;
14 | legacyMappings = legacyMappings;
15 |
16 | async initMappings() {
17 | try {
18 | let newMappings = this.buildMappings(legacyMappings);
19 | this.mappings = newMappings;
20 | } catch (e) {
21 | this.mappings = [];
22 | }
23 | }
24 |
25 | buildMappings(mappings) {
26 | return mappings.map((item) => {
27 | let newItem = Object.assign({}, item);
28 | if (LOCALNAME_CONVERSIONS[newItem.localName]) {
29 | newItem.localName = LOCALNAME_CONVERSIONS[newItem.localName];
30 | }
31 | return newItem;
32 | });
33 | }
34 |
35 | getModule(name, documentedModule) {
36 | if (!this.mappings) {
37 | return '';
38 | }
39 | let matches = this.mappings.filter((element) => element.localName === name);
40 | return matches.length > 0 ? matches[0].module : documentedModule;
41 | }
42 |
43 | getNewClassFromOld(oldClassName, mappings) {
44 | let matches = mappings.filter((element) => element.global === oldClassName);
45 | if (matches.length > 0) {
46 | if (matches[0].localName) {
47 | return {
48 | itemType: 'class',
49 | newModule: matches[0].module,
50 | newName: matches[0].localName,
51 | };
52 | } else {
53 | return {
54 | itemType: 'function',
55 | newModule: matches[0].module,
56 | newName: matches[0].export,
57 | };
58 | }
59 | } else {
60 | return {
61 | itemType: 'class',
62 | newName: oldClassName,
63 | };
64 | }
65 | }
66 |
67 | getNewModuleFromOld(oldModuleName, mappings) {
68 | let matches = mappings.filter(
69 | (element) => element.module === oldModuleName,
70 | );
71 | if (matches.length > 0) {
72 | return {
73 | module: matches[0].replacement.module,
74 | };
75 | } else {
76 | return {
77 | module: oldModuleName,
78 | };
79 | }
80 | }
81 |
82 | hasFunctionMapping(name, module) {
83 | if (!this.mappings) {
84 | return false;
85 | }
86 | let filtered = this.mappings.filter(
87 | (element) => element.export === name && element.module === module,
88 | );
89 | return filtered.length > 0;
90 | }
91 |
92 | hasClassMapping(name) {
93 | if (!this.mappings) {
94 | return false;
95 | }
96 | return (
97 | this.mappings.filter((element) => element.localName === name).length > 0
98 | );
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/app/models/class.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable ember/no-computed-properties-in-native-classes, ember/classic-decorator-no-classic-methods */
2 | import { computed } from '@ember/object';
3 | import Model, { belongsTo, attr } from '@ember-data/model';
4 |
5 | const projectNameFromClassName = (key) => {
6 | return computed(key, 'project.id', function () {
7 | const value = this.get(key) || '';
8 | if (value.indexOf('Ember.') > -1) {
9 | return 'ember';
10 | }
11 |
12 | if (value.indexOf('DS.') > 1) {
13 | return 'ember-data';
14 | }
15 |
16 | return this.get('project.id');
17 | });
18 | };
19 |
20 | // ideally this computed property would not be needed and we'd have extendsVersion, extendsProject attrs from json-api-docs
21 | const guessVersionFor = (key) => {
22 | return computed(
23 | key,
24 | 'extendedClassProjectName',
25 | 'project.id',
26 | 'projectVersion.version',
27 | function () {
28 | if (this.extendedClassProjectName === this.get('project.id')) {
29 | return this.get('projectVersion.version');
30 | }
31 |
32 | // try linking to latest version at least
33 | return 'release';
34 | },
35 | );
36 | };
37 |
38 | export default class Class extends Model {
39 | @attr()
40 | name;
41 |
42 | @attr()
43 | methods;
44 |
45 | @attr()
46 | properties;
47 |
48 | @attr()
49 | access;
50 |
51 | @attr()
52 | events;
53 |
54 | @attr()
55 | description;
56 |
57 | @attr()
58 | ogDescription;
59 |
60 | @attr()
61 | extends;
62 |
63 | @attr()
64 | uses;
65 |
66 | @attr()
67 | since;
68 |
69 | @attr()
70 | file;
71 |
72 | @attr()
73 | line;
74 |
75 | @attr()
76 | module;
77 |
78 | @belongsTo('class', { async: true, inverse: null })
79 | parentClass;
80 |
81 | @belongsTo('project-version', { inverse: 'classes' })
82 | projectVersion;
83 |
84 | @computed('projectVersion.id')
85 | get project() {
86 | return this.projectVersion.get('project');
87 | }
88 |
89 | @projectNameFromClassName('extends')
90 | extendedClassProjectName;
91 |
92 | @guessVersionFor('extends')
93 | extendedClassVersion;
94 |
95 | @projectNameFromClassName('uses')
96 | usedClassProjectName;
97 |
98 | @guessVersionFor('uses')
99 | usedClassVersion;
100 |
101 | @computed('extends')
102 | get extendedClassShortName() {
103 | let extendedClassName = this['extends'];
104 | if (extendedClassName.substr(0, 6) === 'Ember.') {
105 | return extendedClassName.substr(6);
106 | }
107 | return extendedClassName;
108 | }
109 |
110 | @computed('project.id', 'uses')
111 | get usesObjects() {
112 | return this.uses.map((className) => ({
113 | name: className,
114 | shortName:
115 | className.substr(0, 6) === 'Ember.' ? className.substr(6) : className,
116 | projectId:
117 | className.substr(0, 6) === 'Ember.'
118 | ? 'ember'
119 | : className.substr(0, 3) === 'DS'
120 | ? 'ember-data'
121 | : this.get('project.id'),
122 | }));
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/testem.js:
--------------------------------------------------------------------------------
1 | const userName = process.env['BROWSERSTACK_USERNAME'];
2 | const accessKey = process.env['BROWSERSTACK_ACCESS_KEY'];
3 |
4 | const canConnectToBrowserStack =
5 | userName &&
6 | userName.trim().length !== 0 &&
7 | accessKey &&
8 | accessKey.trim().length !== 0;
9 |
10 | let allBrowsers = [
11 | 'Chrome',
12 | 'Firefox',
13 | 'BS_Safari_Current',
14 | 'BS_Safari_Last',
15 | 'BS_MS_Edge',
16 | 'BS_IE_11',
17 | ];
18 | let localBrowsers = ['Chrome'];
19 | let ciBrowsers = canConnectToBrowserStack ? allBrowsers : localBrowsers;
20 |
21 | module.exports = {
22 | test_page: 'tests/index.html?hidepassed&nolint¬rycatch',
23 | disable_watching: true,
24 | timeout: 1200,
25 | browser_start_timeout: 2000,
26 | parallel: 4,
27 | launch_in_ci: ciBrowsers,
28 | launch_in_dev: localBrowsers,
29 | browser_args: {
30 | Chrome: {
31 | dev: [
32 | '--no-sandbox',
33 | '--disable-gpu',
34 | '--auto-open-devtools-for-tabs',
35 | '--window-size=1440,900',
36 | ].filter(Boolean),
37 | ci: [
38 | '--no-sandbox',
39 | '--disable-gpu',
40 | '--headless',
41 | '--disable-dev-shm-usage',
42 | '--disable-software-rasterizer',
43 | '--mute-audio',
44 | '--remote-debugging-port=0',
45 | '--window-size=1440,900',
46 | ].filter(Boolean),
47 | },
48 | Firefox: ['-headless'],
49 | },
50 | launchers: {
51 | BS_Safari_Current: {
52 | exe: 'node_modules/.bin/browserstack-launch',
53 | args: [
54 | '--os',
55 | 'OS X',
56 | '--osv',
57 | 'High Sierra',
58 | '--b',
59 | 'Safari',
60 | '--bv',
61 | 'latest',
62 | '-t',
63 | '1200',
64 | '--u',
65 | '',
66 | ],
67 | protocol: 'browser',
68 | },
69 | BS_Safari_Last: {
70 | exe: 'node_modules/.bin/browserstack-launch',
71 | args: [
72 | '--os',
73 | 'OS X',
74 | '--osv',
75 | 'Sierra',
76 | '--b',
77 | 'safari',
78 | '--bv',
79 | '10.1',
80 | '-t',
81 | '1200',
82 | '--u',
83 | '',
84 | ],
85 | protocol: 'browser',
86 | },
87 | BS_MS_Edge: {
88 | exe: 'node_modules/.bin/browserstack-launch',
89 | args: [
90 | '--os',
91 | 'Windows',
92 | '--osv',
93 | '10',
94 | '--b',
95 | 'edge',
96 | '--bv',
97 | 'latest',
98 | '-t',
99 | '1200',
100 | '--u',
101 | '',
102 | ],
103 | protocol: 'browser',
104 | },
105 | BS_IE_11: {
106 | exe: 'node_modules/.bin/browserstack-launch',
107 | args: [
108 | '--os',
109 | 'Windows',
110 | '--osv',
111 | '7',
112 | '--b',
113 | 'ie',
114 | '--bv',
115 | '11.0',
116 | '-t',
117 | '1500',
118 | '--u',
119 | '',
120 | ],
121 | protocol: 'browser',
122 | },
123 | },
124 | };
125 |
--------------------------------------------------------------------------------
/app/templates/project-version/modules/module.hbs:
--------------------------------------------------------------------------------
1 | {{! template-lint-disable no-invalid-link-text }}
2 |
3 |
4 | {{#if (eq this.model.name 'ember-data-overview')}}
5 |
EmberData Overview
6 | {{else}}
7 | Package {{this.model.name}}
8 | {{#if this.model.access}}{{this.model.access}} {{/if}}
9 |
10 | {{/if}}
11 |
12 |
13 |
14 |
15 | {{#if this.model.parent}}
16 |
17 | Parent:
18 | {{this.model.parent}}
19 |
20 | {{/if}}
21 |
22 |
23 |
24 |
25 | {{#if this.submodules}}
26 |
27 |
28 |
29 | Submodules
30 |
31 |
32 | {{#each this.submodules as |module|}}
33 |
34 |
35 | {{module}}
36 |
37 |
38 | {{/each}}
39 |
40 |
41 | {{/if}}
42 |
43 | {{#if this.classesAndNamespaces}}
44 |
45 |
46 |
47 | Classes
48 |
49 |
50 | {{#each this.classesAndNamespaces as |klass|}}
51 | {{#if (not (and (eq this.model.name "ember-data") (eq klass "Ember")))}}
52 |
53 |
54 | {{klass}}
55 |
56 |
57 | {{/if}}
58 | {{/each}}
59 |
60 |
61 | {{/if}}
62 |
63 | {{#if (and this.functionHeadings (gt this.functionHeadings.length 0)) }}
64 |
65 |
66 |
67 | Functions
68 |
69 | {{#each this.functionHeadings as |funcHeading|}}
70 |
71 |
72 | {{funcHeading}}
73 |
74 |
75 | {{#each (better-get this.functions funcHeading) as |method|}}
76 |
77 |
78 | {{method.name}}
79 |
80 |
81 | {{/each}}
82 |
83 | {{/each}}
84 |
85 | {{/if}}
86 | {{outlet}}
87 |
88 |
89 |
--------------------------------------------------------------------------------
/app/components/class-field-description.hbs:
--------------------------------------------------------------------------------
1 | {{! template-lint-disable no-invalid-interactive }}
2 |
3 |
23 | {{#if @model.module}}
24 |
25 |
26 | Module:
27 | {{@model.module}}
28 |
29 |
30 | {{/if}}
31 |
32 | {{#if @field.inherited}}
33 | Inherited from
34 |
35 | {{@field.inheritedFrom}} {{@field.file}}:{{@field.line}}
36 |
37 | {{else}}
38 | Defined in
39 |
40 | {{@field.file}}:{{@field.line}}
41 |
42 | {{/if}}
43 |
44 | {{#if @field.since}}
45 |
46 | Available since v{{@field.since}}
47 |
48 | {{/if}}
49 | {{#if (and (eq @field.static 1) (eq @field.itemtype 'method') this.hasImportExample)}}
50 |
51 | {{/if}}
52 |
53 | {{#each @field.params as |param|}}
54 |
55 |
{{param.name}}
56 |
{{param.type}}
57 |
58 | {{#if param.props}}
59 |
60 | {{#each param.props as |prop|}}
61 |
62 |
{{prop.name}}
63 | {{prop.type}}
64 |
65 |
66 | {{/each}}
67 |
68 | {{/if}}
69 |
70 | {{/each}}
71 | {{#if @field.return}}
72 |
73 |
returns
74 | {{@field.return.type}}
75 |
76 |
77 | {{/if}}
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/app/adapters/application.js:
--------------------------------------------------------------------------------
1 | import { inject as service } from '@ember/service';
2 | import JSONAPIAdapter from '@ember-data/adapter/json-api';
3 | import { pluralize } from 'ember-inflector';
4 | import { isBlank } from '@ember/utils';
5 | import config from 'ember-api-docs/config/environment';
6 |
7 | export default class Application extends JSONAPIAdapter {
8 | currentProject = '';
9 | currentProjectVersion = '';
10 |
11 | @service
12 | metaStore;
13 |
14 | @service('project')
15 | projectService;
16 |
17 | ids = null;
18 |
19 | shouldReloadRecord(store, { modelName, id }) {
20 | if (modelName === 'project') {
21 | this.currentProject = id;
22 | } else if (modelName === 'project-version') {
23 | this.currentProjectVersion = id;
24 | }
25 | return; // return undefined so auto determinated
26 | }
27 |
28 | shouldBackgroundReloadAll() {
29 | return false;
30 | }
31 |
32 | shouldBackgroundReloadRecord() {
33 | return false;
34 | }
35 |
36 | constructor() {
37 | super(...arguments);
38 | this.ids = {};
39 | }
40 |
41 | async findRecord(store, { modelName }, id) {
42 | let url;
43 | let projectName = this.currentProject;
44 |
45 | if (['namespace', 'class', 'module'].indexOf(modelName) > -1) {
46 | let [version] = id.replace(`${projectName}-`, '').split('-');
47 | let revId = this.metaStore.getRevId(projectName, version, modelName, id);
48 |
49 | let modelNameToUse = modelName;
50 | // To account for namespaces that are also classes but not defined properly in yuidocs
51 | if (isBlank(revId) && modelNameToUse === 'class') {
52 | revId = this.metaStore.getRevId(projectName, version, 'namespace', id);
53 | modelNameToUse = 'namespace';
54 | }
55 |
56 | if (typeof revId !== 'undefined') {
57 | let encodedRevId = encodeURIComponent(revId);
58 | url = `json-docs/${projectName}/${version}/${pluralize(
59 | modelNameToUse,
60 | )}/${encodedRevId}`;
61 | } else {
62 | throw new Error('Documentation item not found');
63 | }
64 | } else if (modelName === 'missing') {
65 | let version = this.projectService.version;
66 | let revId = this.metaStore.getRevId(projectName, version, modelName, id);
67 | url = `json-docs/${projectName}/${version}/${pluralize(
68 | modelName,
69 | )}/${revId}`;
70 | } else if (modelName === 'project') {
71 | this.currentProject = id;
72 | url = `rev-index/${id}`;
73 | } else if (modelName === 'project-version') {
74 | this.currentProjectVersion = id;
75 | url = `rev-index/${id}`;
76 | } else {
77 | throw new Error('Unexpected model lookup');
78 | }
79 |
80 | const base = this.fastboot.isFastBoot
81 | ? config.APP.domain
82 | : window.location.origin;
83 |
84 | url = `${base}/${url}.json`;
85 | try {
86 | let response = await fetch(url);
87 | if (!response.ok) {
88 | throw new Error(
89 | `Network response was not ok: ${response.status} ${response.statusText}`,
90 | );
91 | }
92 | let json = await response.json();
93 | return json;
94 | } catch (error) {
95 | console.error(`Failed to fetch or parse JSON from ${url}:`, error);
96 | throw new Error(`Failed to load data for ${url}: ${error.message}`);
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/public/_redirects:
--------------------------------------------------------------------------------
1 | # Old-style ?anchor=... URLs to new-style #:... URLs for classes
2 | /:project/:version/classes/:class/methods/* anchor=:anchor /:project/:version/classes/:class#:anchor 301!
3 | /:project/:version/classes/:class/properties/* anchor=:anchor /:project/:version/classes/:class#:anchor 301!
4 | /:project/:version/classes/:class/events/* anchor=:anchor /:project/:version/classes/:class#:anchor 301!
5 |
6 | # Old-style classes/Application/methods/foo to new-style classes/Application#foo
7 | /:project/:version/classes/:class/methods/:method /:project/:version/classes/:class#:method 301!
8 | /:project/:version/classes/:class/properties/:property /:project/:version/classes/:class#:property 301!
9 | /:project/:version/classes/:class/events/:event /:project/:version/classes/:class#:event 301!
10 |
11 | # Index page redirects for classes
12 | /:project/:version/classes/:class/methods/ /:project/:version/classes/:class 301!
13 | /:project/:version/classes/:class/properties/ /:project/:version/classes/:class 301!
14 | /:project/:version/classes/:class/events/ /:project/:version/classes/:class 301!
15 |
16 | # Old-style ?anchor=... URLs to new-style #:... URLs for namespaces
17 | /:project/:version/namespaces/:namespace/methods/* anchor=:anchor /:project/:version/namespaces/:namespace#:anchor 301!
18 | /:project/:version/namespaces/:namespace/properties/* anchor=:anchor /:project/:version/namespaces/:namespace#:anchor 301!
19 | /:project/:version/namespaces/:namespace/events/* anchor=:anchor /:project/:version/namespaces/:namespace#:anchor 301!
20 |
21 | # Old-style namespaces/Ember.FEATURES/methods/foo to new-style namespaces/Ember.FEATURES#foo
22 | /:project/:version/namespaces/:namespace/methods/:method /:project/:version/namespaces/:namespace#:method 301!
23 | /:project/:version/namespaces/:namespace/properties/:property /:project/:version/namespaces/:namespace#:property 301!
24 | /:project/:version/namespaces/:namespace/events/:event /:project/:version/namespaces/:namespace#:event 301!
25 |
26 | # Index page redirects for namespaces
27 | /:project/:version/namespaces/:namespace/methods/ /:project/:version/namespaces/:namespace 301!
28 | /:project/:version/namespaces/:namespace/properties/ /:project/:version/namespaces/:namespace 301!
29 | /:project/:version/namespaces/:namespace/events/ /:project/:version/namespaces/:namespace 301!
30 |
31 | # Old-style ?anchor=... URLs to new-style #:... URLs for modules (but not sure these were ever used)
32 | /:project/:version/modules/:module/methods/* anchor=:anchor /:project/:version/modules/:module#:anchor 301!
33 | /:project/:version/modules/:module/properties/* anchor=:anchor /:project/:version/modules/:module#:anchor 301!
34 | /:project/:version/modules/:module/events/* anchor=:anchor /:project/:version/modules/:module#:anchor 301!
35 |
36 | # Old-style modules/Ember.String/methods/foo to new-style modules/Ember.String#foo
37 | /:project/:version/modules/:module/methods/:method /:project/:version/modules/:module#:method 301!
38 | /:project/:version/modules/:module/properties/:property /:project/:version/modules/:module#:property 301!
39 | /:project/:version/modules/:module/events/:event /:project/:version/modules/:module#:event 301!
40 |
41 | # Index page redirects for modules
42 | /:project/:version/modules/:module/methods/ /:project/:version/modules/:module 301!
43 | /:project/:version/modules/:module/properties/ /:project/:version/modules/:module 301!
44 | /:project/:version/modules/:module/events/ /:project/:version/modules/:module 301!
45 |
46 | /* /index.html 200
47 |
--------------------------------------------------------------------------------
/config/environment.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | 'use strict';
3 |
4 | module.exports = function (environment) {
5 | let ALGOLIA_APP_ID = process.env.ALGOLIA_APP_ID || 'Y1OMR4C7MF';
6 | let ALGOLIA_API_KEY =
7 | process.env.ALGOLIA_API_KEY || 'c35425b69b31be1bb4786f0a72146306';
8 |
9 | const ENV = {
10 | modulePrefix: 'ember-api-docs',
11 | environment,
12 | rootURL: '/',
13 | routerRootURL: '/',
14 | locationType: 'history',
15 | API_HOST: process.env.API_HOST || 'https://api-store.emberjs.com',
16 | EmberENV: {
17 | EXTEND_PROTOTYPES: false,
18 | FEATURES: {
19 | //'ember-glimmer': true
20 | // Here you can enable experimental features on an ember canary build
21 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true
22 | },
23 | },
24 |
25 | APP: {
26 | // Here you can pass flags/options to your application instance
27 | // when it is created
28 | scrollContainerSelector: 'body, html',
29 | domain: 'http://localhost:4200',
30 | },
31 |
32 | fastboot: {
33 | hostWhitelist: [
34 | /^[\w-]+\.herokuapp\.com$/,
35 | /^localhost:\d+$/,
36 | /^127\.0\.0\.1:\d+$/,
37 | /^[\w-]+\.fastly\.net$/,
38 | /^[\w-]+\.emberjs\.com$/,
39 | ],
40 | },
41 | algolia: {
42 | algoliaId: ALGOLIA_APP_ID,
43 | algoliaKey: ALGOLIA_API_KEY,
44 | },
45 | metricsAdapters: [
46 | {
47 | name: 'GoogleAnalytics',
48 | environments: ['production'],
49 | config: {
50 | id: 'UA-27675533-1',
51 | },
52 | },
53 | {
54 | name: 'LocalAdapter',
55 | environments: ['development'],
56 | },
57 | ],
58 | };
59 |
60 | ENV.contentSecurityPolicy = {
61 | 'default-src': "'self' *.emberjs.com",
62 | 'connect-src': "'self' *.algolia.net *.algolianet.com *.emberjs.com",
63 | 'script-src':
64 | "'self' unsafe-inline use.typekit.net 'sha256-LEXBvGgYbhXJLZxA/dKnIx07iQsbEcS9SDWq01pWVAk=' *.emberjs.com https://www.google-analytics.com",
65 | 'font-src': "'self' data://* https://fonts.gstatic.com *.emberjs.com",
66 | 'img-src':
67 | "'self' data://* *.emberjs.com https://www.google-analytics.com",
68 | 'style-src':
69 | "'self' 'unsafe-inline' https://fonts.googleapis.com *.emberjs.com",
70 | };
71 |
72 | if (environment === 'development') {
73 | // ENV.APP.LOG_RESOLVER = true;
74 | // ENV.APP.LOG_ACTIVE_GENERATION = true;
75 | // ENV.APP.LOG_TRANSITIONS = true;
76 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
77 | // ENV.APP.LOG_VIEW_LOOKUPS = true;
78 | ENV['ember-a11y-testing'] = {
79 | componentOptions: {
80 | turnAuditOff: process.env.test_a11y !== 'yes',
81 | },
82 | };
83 |
84 | ENV.contentSecurityPolicy['connect-src'] += ' localhost:5050';
85 | }
86 |
87 | if (environment === 'test') {
88 | // Testem prefers this...
89 | ENV.locationType = 'none';
90 | ENV.testing = true;
91 |
92 | // keep test console output quieter
93 | ENV.APP.LOG_ACTIVE_GENERATION = false;
94 | ENV.APP.LOG_VIEW_LOOKUPS = false;
95 |
96 | ENV.APP.rootElement = '#ember-testing';
97 | ENV.APP.scrollContainerSelector = '#ember-testing-container';
98 |
99 | ENV.APP.autoboot = false;
100 |
101 | ENV['ember-tether'] = {
102 | bodyElementId: 'ember-testing',
103 | };
104 | }
105 |
106 | if (environment === 'production') {
107 | ENV.APP.domain = process.env.APP_DOMAIN_URL
108 | ? process.env.APP_DOMAIN_URL
109 | : 'https://api.emberjs.com';
110 | }
111 |
112 | return ENV;
113 | };
114 |
--------------------------------------------------------------------------------
/app/components/ember-landing-page.hbs:
--------------------------------------------------------------------------------
1 |
2 | Ember API Documentation
3 |
4 |
5 | To get started, choose a project (Ember or Ember Data) and a version
6 | from the dropdown menu. Ember has core methods used in any app, while Ember Data has
7 | documentation of the built-in library for making requests to a back end.
8 | If you're looking for documentation of the command line tool used to generate files, build your
9 | app, and more, visit ember-cli . The latest
10 | testing API is available at
11 | ember-test-helpers .
12 |
13 | Commonly searched-for documentation
14 |
15 | {{! template-lint-disable no-potential-path-strings }}
16 | Components - Classic or Glimmer ; a view that is completely isolated
17 | Tracked - make your templates responsive to property updates
18 | Computed Properties - declare functions as properties
19 | {{! template-lint-disable no-potential-path-strings }}
20 | Computed Macros - shorter ways of expressing certain types of computed properties
21 |
22 | EmberArray - contains methods like
23 | forEach and
24 | mapBy
25 | that help you iterate over Ember Objects
26 |
27 |
28 | EmberObject - the main base class for all Ember objects, including the
29 | get and
30 | set methods
31 |
32 |
33 | Ember.Templates.helpers - built-in functions that can be used in templates, such as the
34 | each and
35 | on helpers
36 |
37 | Helpers - a way to define custom display functions that are used in templates
38 |
39 | Route - used to define individual routes, including the
40 | model hook for loading data
41 |
42 | Service - an Ember object that lives for the duration of the application, and can be made available in different parts of your application
43 |
44 | Useful links
45 |
46 |
47 |
50 |
51 |
52 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/app/routes/project-version/classes/class.js:
--------------------------------------------------------------------------------
1 | import { inject as service } from '@ember/service';
2 | import { resolve, all } from 'rsvp';
3 | import Route from '@ember/routing/route';
4 | import { set } from '@ember/object';
5 |
6 | import getFullVersion from 'ember-api-docs/utils/get-full-version';
7 | import createExcerpt from 'ember-api-docs/utils/create-excerpt';
8 |
9 | export default class ClassRoute extends Route {
10 | /** @type {import('@ember/routing/router-service').default} */
11 | @service
12 | router;
13 |
14 | @service
15 | headData;
16 |
17 | @service
18 | metaStore;
19 |
20 | @service store;
21 |
22 | async model(params) {
23 | const { project, project_version: compactVersion } =
24 | this.paramsFor('project-version');
25 | let projectObj = await this.store.findRecord('project', project);
26 | let projectVersion = getFullVersion(
27 | compactVersion,
28 | project,
29 | projectObj,
30 | this.metaStore,
31 | );
32 | const klass = params['class'];
33 | return this.find(
34 | 'class',
35 | `${project}-${projectVersion}-${klass}`.toLowerCase(),
36 | );
37 | }
38 |
39 | find(typeName, param) {
40 | return this.store.find(typeName, param).catch((e1) => {
41 | if (typeName != 'namespace') {
42 | console.warn(
43 | e1,
44 | 'fetching by class or module failed, retrying as namespace',
45 | );
46 | return this.store.find('namespace', param).catch((e2) => {
47 | console.error(e2);
48 | return resolve({
49 | isError: true,
50 | status: 404,
51 | });
52 | });
53 | }
54 | console.error(e1);
55 | return resolve({
56 | isError: true,
57 | status: 404,
58 | });
59 | });
60 | }
61 |
62 | redirect(model, transition) {
63 | if (model.isError) {
64 | // Transitioning to the same route, probably only changing version
65 | // Could explicitly check by comparing transition.to and transition.from
66 | if (transition.to.name === transition?.from?.name) {
67 | const projectVersionRouteInfo = transition.to.find(function (item) {
68 | return item.params?.project_version;
69 | });
70 | const attemptedVersion =
71 | projectVersionRouteInfo.params?.project_version;
72 | const attemptedProject = projectVersionRouteInfo.params?.project;
73 | let error = new Error(
74 | `We could not find ${transition.to.localName} ${transition.to.params[transition.to.paramNames[0]]} in v${attemptedVersion} of ${attemptedProject}.`,
75 | );
76 | error.status = 404;
77 | error.attemptedProject = attemptedProject;
78 | error.attemptedVersion = attemptedVersion;
79 | throw error;
80 | }
81 |
82 | let error = new Error(
83 | 'Error retrieving model in routes/project-version/classes/class',
84 | );
85 |
86 | error.status = 404;
87 |
88 | throw error;
89 | }
90 | }
91 |
92 | afterModel(klass) {
93 | if (!klass.isError) {
94 | let description = klass.get('ogDescription') || klass.get('description');
95 | if (description) {
96 | set(this, 'headData.description', createExcerpt(description));
97 | }
98 |
99 | const relationships = klass.constructor.relationshipNames;
100 | const promises = Object.keys(relationships).reduce(
101 | (memo, relationshipType) => {
102 | const relationshipPromises = relationships[relationshipType].map(
103 | (name) => klass.get(name),
104 | );
105 | return memo.concat(relationshipPromises);
106 | },
107 | [],
108 | );
109 | return all(promises);
110 | }
111 | }
112 |
113 | serialize(model) {
114 | return {
115 | class: model.name,
116 | };
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/app/controllers/application.js:
--------------------------------------------------------------------------------
1 | import Controller from '@ember/controller';
2 | import config from 'ember-api-docs/config/environment';
3 |
4 | const links = [
5 | {
6 | name: 'Docs',
7 | type: 'dropdown',
8 | items: [
9 | {
10 | href: 'https://guides.emberjs.com',
11 | name: 'Ember.js Guides',
12 | type: 'link',
13 | },
14 | {
15 | href: config.APP.domain,
16 | name: 'API Reference',
17 | type: 'link',
18 | },
19 | {
20 | href: 'https://cli.emberjs.com',
21 | name: 'CLI Guides',
22 | type: 'link',
23 | },
24 | {
25 | type: 'divider',
26 | },
27 | {
28 | href: 'https://emberjs.com/learn',
29 | name: 'Learn Ember',
30 | type: 'link',
31 | },
32 | ],
33 | },
34 | {
35 | name: 'Releases',
36 | type: 'dropdown',
37 | items: [
38 | {
39 | href: 'https://emberjs.com/builds',
40 | name: 'Channels',
41 | type: 'link',
42 | },
43 | {
44 | href: 'https://emberjs.com/builds/release',
45 | name: '-- Stable',
46 | type: 'link',
47 | },
48 | {
49 | href: 'https://emberjs.com/builds/beta',
50 | name: '-- Beta',
51 | type: 'link',
52 | },
53 | {
54 | href: 'https://emberjs.com/builds/canary',
55 | name: '-- Canary',
56 | type: 'link',
57 | },
58 | {
59 | type: 'divider',
60 | },
61 | {
62 | href: 'https://emberjs.com/deprecations',
63 | name: 'Deprecations',
64 | type: 'link',
65 | },
66 | {
67 | href: 'https://emberjs.com/statusboard',
68 | name: 'Status Board',
69 | type: 'link',
70 | },
71 | ],
72 | },
73 | {
74 | href: 'https://emberjs.com/blog',
75 | name: 'Blog',
76 | type: 'link',
77 | },
78 | {
79 | name: 'Community',
80 | type: 'dropdown',
81 | items: [
82 | {
83 | href: 'https://emberjs.com/community',
84 | name: 'The Ember Community',
85 | type: 'link',
86 | },
87 | {
88 | href: 'https://emberjs.com/guidelines',
89 | name: 'Guidelines',
90 | type: 'link',
91 | },
92 | {
93 | href: 'https://github.com/emberjs/',
94 | name: 'Contribute (Github)',
95 | type: 'link',
96 | },
97 | {
98 | type: 'divider',
99 | },
100 | {
101 | href: 'https://emberjs.com/community/meetups',
102 | name: 'Meetups',
103 | type: 'link',
104 | },
105 | {
106 | href: 'https://jobs.emberjs.com/',
107 | name: 'Job Board',
108 | type: 'link',
109 | },
110 | {
111 | type: 'divider',
112 | },
113 | {
114 | href: 'https://emberconf.com/',
115 | name: 'Ember Conf',
116 | type: 'link',
117 | },
118 | ],
119 | },
120 | {
121 | name: 'About',
122 | type: 'dropdown',
123 | items: [
124 | {
125 | href: 'https://emberjs.com/team',
126 | name: 'The Team',
127 | type: 'link',
128 | },
129 | {
130 | type: 'divider',
131 | },
132 | {
133 | href: 'https://emberjs.com/logos',
134 | name: 'Logos',
135 | type: 'link',
136 | },
137 | {
138 | href: 'https://emberjs.com/mascots',
139 | name: 'Mascots',
140 | type: 'link',
141 | },
142 | {
143 | type: 'divider',
144 | },
145 | {
146 | href: 'https://emberjs.com/ember-users',
147 | name: 'Who Uses Ember',
148 | type: 'link',
149 | },
150 | {
151 | href: 'https://emberjs.com/sponsors',
152 | name: 'Sponsors',
153 | type: 'link',
154 | },
155 | {
156 | type: 'divider',
157 | },
158 | {
159 | href: 'https://emberjs.com/legal',
160 | name: 'Legal',
161 | type: 'link',
162 | },
163 | {
164 | href: 'https://emberjs.com/security',
165 | name: 'Security',
166 | type: 'link',
167 | },
168 | ],
169 | },
170 | ];
171 |
172 | export default class ApplicationController extends Controller {
173 | links = links;
174 | }
175 |
--------------------------------------------------------------------------------
/tests/unit/controllers/project-version-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from 'qunit';
2 | /* eslint-disable ember/no-restricted-resolver-tests */
3 | import { setupTest } from 'ember-qunit';
4 | import { findEndingRoute } from 'ember-api-docs/controllers/project-version';
5 |
6 | const moduleIds = [
7 | 'ember-2.10.0-ember',
8 | 'ember-2.10.0-ember-application',
9 | 'ember-2.10.0-ember-debug',
10 | 'ember-2.10.0-ember-extension-support',
11 | 'ember-2.10.0-ember-glimmer',
12 | 'ember-2.10.0-ember-htmlbars',
13 | 'ember-2.10.0-ember-metal',
14 | 'ember-2.10.0-ember-routing',
15 | 'ember-2.10.0-ember-runtime',
16 | 'ember-2.10.0-ember-testing',
17 | 'ember-2.10.0-ember-views',
18 | ];
19 |
20 | const expectedModuleNames = [
21 | 'ember',
22 | 'ember-application',
23 | 'ember-debug',
24 | 'ember-extension-support',
25 | 'ember-glimmer',
26 | 'ember-htmlbars',
27 | 'ember-metal',
28 | 'ember-routing',
29 | 'ember-runtime',
30 | 'ember-testing',
31 | 'ember-views',
32 | ];
33 |
34 | module('Unit | Controller | project version', function (hooks) {
35 | setupTest(hooks);
36 |
37 | test('should render module names', function (assert) {
38 | let controller = this.owner
39 | .factoryFor('controller:project-version')
40 | .create({
41 | getRelations() {
42 | return moduleIds;
43 | },
44 | });
45 |
46 | let moduleNames = controller.getModuleRelationships('ember-2.10.1');
47 | assert.deepEqual(moduleNames, expectedModuleNames);
48 | });
49 |
50 | module('findEndingRoute', function () {
51 | test('Maintains anchors', function (assert) {
52 | let endingRoute = findEndingRoute({
53 | project: 'ember',
54 | targetVersion: '6.4.0',
55 | currentVersion: '6.5.0',
56 | currentUrlVersion: '6.5',
57 | currentURL: '/ember/6.5/classes/Component',
58 | currentAnchor: '#didInsertElement',
59 | });
60 |
61 | assert.strictEqual(
62 | endingRoute,
63 | '/ember/6.4/classes/Component#didInsertElement',
64 | );
65 |
66 | endingRoute = findEndingRoute({
67 | project: 'ember',
68 | targetVersion: '6.4.0',
69 | currentVersion: '6.5.0',
70 | currentUrlVersion: '6.5',
71 | currentURL: '/ember/6.5/modules/%40ember%2Fapplication',
72 | currentAnchor: '#classes',
73 | });
74 |
75 | assert.strictEqual(
76 | endingRoute,
77 | '/ember/6.4/modules/%40ember%2Fapplication#classes',
78 | );
79 | });
80 |
81 | test('For ember project, it goes to version root when crossing 2.16 boundary', function (assert) {
82 | let endingRoute = findEndingRoute({
83 | project: 'ember',
84 | targetVersion: '2.15.0',
85 | currentVersion: '2.16.0',
86 | currentUrlVersion: '2.16',
87 | currentURL: '/ember/2.16/classes/Component',
88 | currentAnchor: '#didInsertElement',
89 | });
90 |
91 | assert.strictEqual(endingRoute, '/ember/2.15');
92 |
93 | endingRoute = findEndingRoute({
94 | project: 'ember',
95 | targetVersion: '2.16.0',
96 | currentVersion: '2.15.0',
97 | currentUrlVersion: '2.15',
98 | currentURL: '/ember/2.15/classes/Component',
99 | currentAnchor: '#didInsertElement',
100 | });
101 |
102 | assert.strictEqual(endingRoute, '/ember/2.16');
103 | });
104 |
105 | test('For ember-data project, it goes to version root when crossing 4.0 boundary', function (assert) {
106 | let endingRoute = findEndingRoute({
107 | project: 'ember-data',
108 | targetVersion: '3.28.0',
109 | currentVersion: '4.0.0',
110 | currentUrlVersion: '4.0',
111 | currentURL: '/ember-data/4.0/classes/Adapter',
112 | currentAnchor: '',
113 | });
114 |
115 | assert.strictEqual(endingRoute, '/ember-data/3.28');
116 |
117 | endingRoute = findEndingRoute({
118 | project: 'ember-data',
119 | targetVersion: '4.0.0',
120 | currentVersion: '3.28.0',
121 | currentUrlVersion: '3.28',
122 | currentURL: '/ember-data/3.28/classes/DS.Adapter',
123 | currentAnchor: '',
124 | });
125 |
126 | assert.strictEqual(endingRoute, '/ember-data/4.0');
127 | });
128 | });
129 | });
130 |
--------------------------------------------------------------------------------
/app/components/api-index-filter.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable ember/no-computed-properties-in-native-classes */
2 | import { classNames } from '@ember-decorators/component';
3 | import { computed } from '@ember/object';
4 | import Component from '@ember/component';
5 | import sortBy from 'lodash.sortby';
6 |
7 | const filterDataComputedParams =
8 | 'filterData.{showInherited,showProtected,showPrivate,showDeprecated}';
9 |
10 | /**
11 | * @typedef Args
12 | * @property {object} model
13 | * @property {object} filterData
14 | */
15 |
16 | /**
17 | * @typedef Blocks
18 | * @property {[ApiIndexFilter['filteredData']]} default
19 | */
20 |
21 | /**
22 | * @extends Component<{ Args: Args, Blocks: Blocks }>
23 | */
24 | @classNames('api-index-filter')
25 | export default class ApiIndexFilter extends Component {
26 | @computed('model.methods.[]', filterDataComputedParams)
27 | get filteredMethods() {
28 | return this.filterItems('methods');
29 | }
30 |
31 | @computed('model.events.[]', filterDataComputedParams)
32 | get filteredEvents() {
33 | return this.filterItems('events');
34 | }
35 |
36 | @computed('model.properties.[]', filterDataComputedParams)
37 | get filteredProperties() {
38 | return this.filterItems('properties');
39 | }
40 |
41 | filterItems(itemType) {
42 | let items =
43 | this.model[itemType] === undefined ? [] : this.model[`${itemType}`];
44 | if (!this.filterData.showInherited) {
45 | items = items.filter((item) => item.inherited !== true);
46 | }
47 | if (!this.filterData.showProtected) {
48 | items = items.filter((item) => item.access !== 'protected');
49 | }
50 | if (!this.filterData.showPrivate) {
51 | items = items.filter((item) => item.access !== 'private');
52 | }
53 | if (!this.filterData.showDeprecated) {
54 | items = items.filter((item) => item.deprecated !== true);
55 | }
56 |
57 | let sortedItems = sortBy(items, (item) => item.name);
58 | return this.filterMultipleInheritance(sortedItems);
59 | }
60 |
61 | @computed('filteredMethods', 'filteredProperties', 'filteredEvents')
62 | get filteredData() {
63 | return {
64 | methods: this.filteredMethods,
65 | properties: this.filteredProperties,
66 | events: this.filteredEvents,
67 | };
68 | }
69 |
70 | /**
71 | * Returns an array where duplicate methods (by name) are removed.
72 | * The docs for the nearest inheritance are typically more helpful to users,
73 | * so in cases of duplicates, "more local" is preferred.
74 | * Without this, multiple entries for some methods will show up.
75 | * @method filterMultipleInheritance
76 | */
77 | filterMultipleInheritance(items) {
78 | let dedupedArray = [];
79 | for (let i = 0; i < items.length; i++) {
80 | let currentItem = items[i];
81 | if (i === items.length - 1) {
82 | // if it's the last item, keep it
83 | dedupedArray.push(currentItem);
84 | } else {
85 | let nextItem = items[i + 1];
86 | if (currentItem.name === nextItem.name) {
87 | // if the method would be listed twice, find the more local documentation
88 | let mostLocal = this.findMostLocal(currentItem, nextItem);
89 | dedupedArray.push(mostLocal);
90 | i += 1; // skip the next item with duplicate name
91 | } else {
92 | dedupedArray.push(currentItem);
93 | }
94 | }
95 | }
96 | return dedupedArray;
97 | }
98 |
99 | /**
100 | * Returns whichever item is most local.
101 | * What is "most local" is determined by looking at the file path for the
102 | * method, the file path for the class being viewed, and the parent if needed.
103 | * @method findMostLocal
104 | */
105 | findMostLocal(currentItem, nextItem) {
106 | let currentScope = this.model.file;
107 | let parentClassScope = this.model.get('parentClass').get('file');
108 | if (currentScope === currentItem.file) {
109 | // if the item belongs to the class, keep it
110 | return currentItem;
111 | } else if (parentClassScope === currentItem.file) {
112 | // or if the item belongs to the parent class, keep it
113 | return currentItem;
114 | } else {
115 | // otherwise, the next item must be "more local"
116 | return nextItem;
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/ember-learn/ember-api-docs/actions?query=workflow%3ACI)
2 | [](https://percy.io/Ember/ember-api-docs)
3 |
4 | # [Ember API Docs](https://api.emberjs.com/)
5 |
6 | This project contains only the [API docs](https://api.emberjs.com/) portion of [Emberjs.com](https://emberjs.com/). If you're looking for the rest of the site,
7 | see the [website](https://github.com/ember-learn/ember-website)
8 | and [guides](https://github.com/ember-learn/guides-source) repositories.
9 |
10 | There are many pieces that together create the Ember API docs site:
11 | - The app in this repository, which fetches and displays the API docs data. It is what you see at
12 | [https://api.emberjs.com/](https://api.emberjs.com/)
13 | - The YUIdoc code comments found in the
14 | [Ember.js codebase](https://github.com/emberjs/ember.js), where the informational content of the API documentation can be edited
15 | - The data generator that serializes code comments into JSONAPI and
16 | deploys the result,
17 | [ember-jsonapi-docs](https://github.com/ember-learn/ember-jsonapi-docs)
18 | - [ember-styleguide](https://github.com/ember-learn/ember-styleguide),
19 | a component and styling library shared across apps.
20 | - [algolia-index-update-scripts](https://github.com/ember-learn/algolia-index-update-scripts) for managing the search feature of the api docs site
21 |
22 | ## Contributing
23 |
24 | New contributors are welcome! This project is maintained by an all-volunteer team,
25 | and we are thankful for your help.
26 |
27 | The best way to get started is to find an issue labeled "good first issue" or "help wanted." If you have questions or want a buddy to pair with, drop by the #dev-ember-learning channel on the
28 | [Ember Community Discord](https://discordapp.com/invite/emberjs).
29 | Like most open-source projects, contributors are encouraged to open an issue
30 | to propose changes and iterate on ideas before investing time in coding.
31 | Some tips for working with git/GitHub can be found in
32 | [Making your first pull request](https://github.com/emberjs/guides/blob/master/CONTRIBUTING.md#making-your-first-pull-request) in the Guides repository.
33 |
34 | Please also see [CONTRIBUTING.md](CONTRIBUTING.md).
35 |
36 | ## Running in your local environment
37 |
38 | ember-api-docs expects a folder in its root that links to the `ember-api-docs-data` folder, so you can either use the `npm run clone` script to clone the `ember-api-docs-data` repo into `ember-api-docs`, OR you can create a symbolic link to `ember-api-docs-data` from `ember-api-docs`. You might want to sym-link `ember-api-docs-data` if you are generating new versions of the docs files with `ember-jsonapi-docs`, otherwise you can probably use the clone script.
39 |
40 | ### Quickstart to run locally
41 |
42 | Follow these instructions to run the app using publically available online data.
43 | You do not need to run [ember-jsonapi-docs](https://github.com/ember-learn/ember-jsonapi-docs)
44 | locally yourself.
45 |
46 | ```
47 | git clone https://github.com/ember-learn/ember-api-docs.git
48 | cd ember-api-docs
49 | pnpm install
50 | pnpm run clone
51 | pnpm run start
52 | ```
53 |
54 | View at http://localhost:4200
55 |
56 | ### Run locally with a sym-link
57 |
58 | Clone all of the following repositories into the same directory so they are "siblings" on the file system:
59 |
60 | - This repository, `ember-api-docs`
61 | - [ember-api-docs-data](https://github.com/ember-learn/ember-api-docs-data)
62 |
63 | ```sh
64 | git clone https://github.com/ember-learn/ember-api-docs-data
65 | git clone https://github.com/ember-learn/ember-api-docs
66 | cd ember-api-docs
67 | ln -s ../ember-api-docs-data
68 | pnpm install
69 | pnpm start
70 | ```
71 |
72 | Visit the app in your browser at [http://localhost:4200](http://localhost:4200)
73 |
74 | ## a11y testing
75 |
76 | To run a11y tests, run `test_a11y=yes ember serve`
77 |
78 | ## Linting
79 |
80 | * `pnpm run lint:hbs`
81 | * `pnpm run lint:js`
82 | * `pnpm run lint:js -- --fix`
83 |
84 | ## Staging and Deployment
85 |
86 | See the [DEPLOYING.md](https://github.com/ember-learn/ember-api-docs/blob/master/DEPLOYING.md) guide for instructions.
87 |
88 | ## Building
89 |
90 | Cross-browser testing provided by:
91 |
92 |
93 |
--------------------------------------------------------------------------------
/public/assets/images/dnsimple-logo-dark.svg:
--------------------------------------------------------------------------------
1 |
2 | image/svg+xml
38 |
--------------------------------------------------------------------------------