Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(daffio): render design component guide and API docs with tabs #3377

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<daffio-doc-article [breadcrumbs]="doc().breadcrumbs" [toc]="toc()">
<h1>{{doc().title}}</h1>
<p [innerHtml]="doc().longDescription | safe"></p>
<daff-tabs
[initiallySelected]="USAGE_TAB_ID"
[linkMode]="true"
[url]="doc().id"
(tabChange)="setTab($event)"
>
<daff-tab [id]="USAGE_TAB_ID">
<daff-tab-label>
Usage
</daff-tab-label>
<daff-tab-panel>
<daff-article
[innerHtml]="doc().contents | safe">
</daff-article>
</daff-tab-panel>
</daff-tab>

<daff-tab [id]="API_TAB_ID">
<daff-tab-label>
API
</daff-tab-label>
<daff-tab-panel>
@for (apiDoc of doc().api; track $index) {
<section [innerHtml]="apiDoc | safe"></section>
}
</daff-tab-panel>
</daff-tab>
</daff-tabs>
</daffio-doc-article>
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import {
provideHttpClient,
withInterceptorsFromDi,
} from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { Component } from '@angular/core';
import {
waitForAsync,
ComponentFixture,
TestBed,
} from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { RouterTestingModule } from '@angular/router/testing';

import { DaffTabsComponent } from '@daffodil/design/tabs';
import {
DaffDocKind,
DaffPackageGuideDoc,
} from '@daffodil/docs-utils';

import { DaffioDocsDesignComponentContentComponent } from './component-content.component';
import { DaffioDocArticleComponent } from '../../../components/doc-article/component';

@Component({
template: `<daffio-docs-design-component-content
[doc]="docValue"
></daffio-docs-design-component-content>`,
standalone: true,
imports: [
DaffioDocsDesignComponentContentComponent,
],
})
class WrapperComponent {
docValue: DaffPackageGuideDoc;
}

describe('DaffioDocsDesignComponentContentComponent', () => {
let wrapper: WrapperComponent;
let component: DaffioDocsDesignComponentContentComponent;
let fixture: ComponentFixture<WrapperComponent>;
let tabsComponent: DaffTabsComponent;
let articleComponent: DaffioDocArticleComponent;

beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule,
NoopAnimationsModule,
WrapperComponent,
],
providers: [provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()],
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(WrapperComponent);
wrapper = fixture.componentInstance;
wrapper.docValue = {
id: 'id',
title: 'title',
symbols: [],
apiToc: [{
content: 'apiToc',
lvl: 2,
slug: 'apiToc',
}],
tableOfContents: [{
content: 'toc',
lvl: 2,
slug: 'toc',
}],
api: ['<div id="api">api</div>'],
contents: '<div id="contents">contents</div>',
longDescription: '<div id="longDescription">longDescription</div>',
breadcrumbs: [],
kind: DaffDocKind.COMPONENT,
};
fixture.detectChanges();

component = fixture.debugElement.query(By.directive(DaffioDocsDesignComponentContentComponent)).componentInstance;
tabsComponent = fixture.debugElement.query(By.directive(DaffTabsComponent)).componentInstance;
articleComponent = fixture.debugElement.query(By.directive(DaffioDocArticleComponent)).componentInstance;
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should render the title in an h1', () => {
expect(fixture.debugElement.query(By.css('h1')).nativeElement.innerText).toEqual(wrapper.docValue.title);
});

it('should render the long description', () => {
expect(fixture.debugElement.query(By.css('#longDescription')).nativeElement.innerText).toEqual('longDescription');
});

describe('on the usage tab', () => {
beforeEach(() => {
tabsComponent.select(component.USAGE_TAB_ID);
fixture.detectChanges();
});

it('should render the content', () => {
expect(fixture.debugElement.query(By.css('#contents')).nativeElement.innerText).toEqual('contents');
});

it('should pass the toc to the article', () => {
expect(articleComponent.toc).toEqual(wrapper.docValue.tableOfContents);
});
});

describe('on the API tab', () => {
beforeEach(() => {
tabsComponent.select(component.API_TAB_ID);
fixture.detectChanges();
});

it('should render the api', () => {
expect(fixture.debugElement.query(By.css('#api')).nativeElement.innerText).toEqual('api');
});

it('should pass the API toc to the article', () => {
expect(articleComponent.toc).toEqual(wrapper.docValue.apiToc);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {
ChangeDetectionStrategy,
Component,
computed,
effect,
input,
signal,
viewChild,
} from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { ActivatedRoute } from '@angular/router';

import { DAFF_ARTICLE_COMPONENTS } from '@daffodil/design/article';
import {
DAFF_TABS_COMPONENTS,
DaffTabsComponent,
} from '@daffodil/design/tabs';
import {
DaffDocKind,
DaffPackageGuideDoc,
} from '@daffodil/docs-utils';

import { DaffioSafeHtmlPipe } from '../../../../core/html-sanitizer/safe.pipe';
import { DaffioDocArticleModule } from '../../../components/doc-article/module';
import { DaffioDocsDynamicContent } from '../../../dynamic-content/dynamic-content.type';


@Component({
selector: 'daffio-docs-design-component-content',
templateUrl: './component-content.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
DAFF_TABS_COMPONENTS,
DAFF_ARTICLE_COMPONENTS,
DaffioDocArticleModule,
DaffioSafeHtmlPipe,
],
})
export class DaffioDocsDesignComponentContentComponent implements DaffioDocsDynamicContent<DaffPackageGuideDoc> {
static readonly kind = DaffDocKind.COMPONENT;

private readonly _tab = signal<string>('');

readonly USAGE_TAB_ID = 'usage-tab';
readonly API_TAB_ID = 'api-tab';

tabs = viewChild.required(DaffTabsComponent);
doc = input<DaffPackageGuideDoc>();
toc = computed(() => {
switch (this._tab()) {
case this.API_TAB_ID:
return this.doc().apiToc;

default:
case this.USAGE_TAB_ID:
return this.doc().tableOfContents;
}
});

setTab(tab: string) {
this._tab.set(tab);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { DaffioDocsDesignComponentContentComponent } from './component-content.component';
import { provideDaffioDocsDynamicContentComponents } from '../../../dynamic-content/dynamic-content-components.token';

export const provideDaffioDocsDesignComponentContentComponent = () => provideDaffioDocsDynamicContentComponents(DaffioDocsDesignComponentContentComponent);
4 changes: 4 additions & 0 deletions apps/daffio/src/app/docs/design/design.module.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { NgModule } from '@angular/core';

import { provideDaffioDocsDesignComponentContentComponent } from './components/component-content/component-content.provider';
import { DaffioDocsDesignRoutingModule } from './design-routing.module';
import { DaffioDocsDesignIndexService } from './services/index.service';
import { provideDaffioDocsPackagesContentComponent } from '../packages/components/packages-content/packages-content.provider';

@NgModule({
imports: [
DaffioDocsDesignRoutingModule,
],
providers: [
DaffioDocsDesignIndexService,
provideDaffioDocsDesignComponentContentComponent(),
provideDaffioDocsPackagesContentComponent(),
],
})
export class DaffioDocsDesignModule {}
4 changes: 4 additions & 0 deletions libs/docs-utils/src/doc/package-guide.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ export interface DaffPackageGuideDoc extends DaffDoc {
* A table of contents for the API section.
*/
apiToc: DaffDocTableOfContents;
/**
* A description of the package. This is renderable HTML.
*/
longDescription: string;
}
6 changes: 5 additions & 1 deletion tools/dgeni/src/processors/add-api-symbols-to-package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from '@daffodil/docs-utils';

import { CollectLinkableSymbolsProcessor } from './collect-linkable-symbols';
import { MARKDOWN_CODE_PROCESSOR_NAME } from './markdown';
import { API_TEMPLATES_PATH } from '../transforms/config';
import { FilterableProcessor } from '../utils/filterable-processor.type';

Expand All @@ -16,7 +17,7 @@ export const ADD_API_SYMBOLS_TO_PACKAGES_PROCESSOR_NAME = 'addApiSymbolsToPackag
export class AddApiSymbolsToPackagesProcessor implements FilterableProcessor {
readonly name = ADD_API_SYMBOLS_TO_PACKAGES_PROCESSOR_NAME;
readonly $runAfter = ['paths-absolutified'];
readonly $runBefore = ['rendering-docs'];
readonly $runBefore = ['rendering-docs', MARKDOWN_CODE_PROCESSOR_NAME];

docTypes = [];
lookup = (doc: Document) => doc.id;
Expand Down Expand Up @@ -51,6 +52,9 @@ export class AddApiSymbolsToPackagesProcessor implements FilterableProcessor {
slug: entry.slug === 'examples' ? `${symbol.slug}-examples` : entry.slug,
})),
]);
const match = doc.content.match(/# .*\n+(.*)\n*/);
doc.longDescription = match[1];
doc.content = doc.content.replace(match[0], '');
}
return doc;
});
Expand Down
15 changes: 3 additions & 12 deletions tools/dgeni/src/processors/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ hljs.registerLanguage('gql', graphql);
export const MARKDOWN_CODE_PROCESSOR_NAME = 'markdown';

export class MarkdownCodeProcessor implements FilterableProcessor {
private docDescription: string;
private marked = new Marked(
markedHighlight({
highlight: (code, lang, info) => {
Expand Down Expand Up @@ -57,13 +56,6 @@ export class MarkdownCodeProcessor implements FilterableProcessor {
const path = CollectLinkableSymbolsProcessor.symbols.get(text);
return path ? `<a href="${path}"><code>${text}</code></a>` : false;
},
paragraph: (text) => {
if (!this.docDescription) {
// get the first paragraph of the doc
this.docDescription = text;
}
return false;
},
},
},
);
Expand All @@ -82,16 +74,15 @@ export class MarkdownCodeProcessor implements FilterableProcessor {
const ret = docs.map((doc) => {
if (this.docTypes.includes(doc.docType)) {
doc[this.contentKey] = this.marked.parse(typeof doc.description === 'undefined' ? doc.content : doc.description);
if (doc.kind === DaffDocKind.PACKAGE || doc.kind === DaffDocKind.COMPONENT) {
doc.description = this.docDescription;
}
if (doc.examples) {
doc.examples = (<Array<DaffDocExample>>doc.examples).map((example) => ({
...example,
body: this.marked.parse(example.body),
}));
}
this.docDescription = null;
if (doc.longDescription) {
doc.longDescription = (<string>this.marked.parse(doc.longDescription)).replaceAll(/(^<p>)|(<\/p>(\n)*$)/gm, '');
}
doc.slug = slugify(doc.name || doc.title);
};
return doc;
Expand Down
2 changes: 1 addition & 1 deletion tools/dgeni/src/templates/api/base.template.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% block header %}
{$'<h3 id="' + doc.slug + '"><a href="' + doc.path + '">' if child else '<h1>'$}{$ doc.name $}{$'</a></h3>' if child else '</h1>'$}
{$'<h3 id="' + doc.slug + '">' if child else '<h1>'$}{$ doc.name $}{$'</h3>' if child else '</h1>'$}
{% endblock %}
<p>{$ doc.description $}</p>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const transformDesignGuideDoc = (doc: Document): DaffDesignGuideNavDoc =>
id: doc.id,
title: doc.title,
path: doc.path,
description: doc.description,
description: doc.longDescription,
};


Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export const designDocsPackage = new Package('design-docs', [design])
];
})
.config((convertToJson: ConvertToJsonProcessor) => {
convertToJson.extraFields.push('description', 'symbols');
convertToJson.extraFields.push('longDescription', 'symbols');
})
.config((absolutifyPaths: AbsolutifyPathsProcessor) => {
absolutifyPaths.docTypes = docTypes;
Expand Down
Loading