Skip to content

Commit

Permalink
feat(web app): add ability to navigate to particular sections
Browse files Browse the repository at this point in the history
- implement complete content taxonomy
- implement first draft of table-of-contents logic
- improve code organization
  • Loading branch information
inkwell-studio committed Jul 24, 2023
1 parent 712e45f commit 0e92881
Show file tree
Hide file tree
Showing 98 changed files with 25,958 additions and 9,403 deletions.
30 changes: 28 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,39 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"request": "launch",
"name": "Web App",
"type": "node",
"program": "${workspaceFolder}/dev.ts",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "deno",
"runtimeArgs": [
"run",
"--inspect-brk",
"-A"
],
"attachSimplePort": 9229
},
{
"request": "launch",
"name": "Tests",
"type": "node",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "deno",
"runtimeArgs": [
"test",
"--inspect-brk"
],
"attachSimplePort": 9229
},
{
"request": "launch",
"name": "Mock Data",
"type": "node",
"program": "${workspaceFolder}/content/utils/mock-data/build.ts",
"program": "${workspaceFolder}/catechism/mock-data/build.ts",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "/usr/local/bin/deno",
"runtimeExecutable": "deno",
"runtimeArgs": [
"run",
"--inspect-brk",
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ It is available at [https://www.catholiccatechism.app/](https://www.catholiccate

### Tasks

- `deno fmt` (code formatting)
- `deno test`
- `deno task pre-commit` (linting, testing, formatting, and artifact creation)
- `deno task build-mock-data`
- `deno task web-app-start`

### Committing changes
### Committing to `master`

The `pre-commit` task should be successfully executed before committing to ensure that the code is linted, correct, and
formatted, and that the artifacts are kept in-sync with the source.

Commit messages should follow the following pattern:

Expand Down
79 changes: 48 additions & 31 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,56 @@
# Tasks

- add GitHooks for linting and formatting on commits [to just `master`, if possible]
- render all content
- citation markers
- opening content
- what else?
- implement content loading
- cross-reference navigation
- chapter > chapter navigation
- routing
- to in-page anchor tags

- styling
- [ ] try converting JSON artifacts to YAML
- [ ] upgrade Fresh
- [ ] move import-map specs into `deno.json`
- [ ] use Twind v1: https://fresh.deno.dev/docs/examples/using-twind-v1
- [ ] document link regarding Radix colors and their other tools

- [ ] rebuild the table-of-contents generation algorithm:
- [ ] the algorithm:
- [ ] for the four parts:
- [ ] all populated `openingContent` arrays are an entry (referenced by the parent)
- [ ] all `mainContent` arrays are split by `InBrief` content; entries are created for each sub-array (referenced
by the first element in the sub-array)
- [ ] write tests to verify that all content is accessible via the generated table-of-contents
- [ ] determine if the semantic-path-to-path-id map needs refactoring

- [ ] investigate "TODO" note above `utils::hasChildContent()`
- [ ] investigate `utils::getOpeningAndMainContent()`: should the return type be `ContentBase` instead of `T`?

- [ ] render all content
- [ ] re-do the rendering structures (reorganize components, etc.)
- [ ] opening content
- [ ] citation markers
- [ ] is there anything else?
- [ ] implement content loading
- [ ] cross-reference navigation
- [ ] chapter > chapter navigation
- [ ] routing
- [ ] to in-page anchor tags

- [ ] styling

## Unprioritized

- implement table-of-contents navigation
- implement hierarchical navigation
- implement historical navigation
- implement search
- implement index
- implement glossary
- implement "copy" buttons (click a button to copy the entire text of a paragraph, quote, etc.)
- light/dark/high-contrast mode toggle
- dark mode: try to avoid the "window blending" (cannot tell where the browser window starts and another application
window begins)
- consider using the following colors:
- #E86D82 (red-pink)
- [ ] implement hierarchical navigation
- [ ] implement historical navigation
- [ ] implement search
- [ ] implement index
- [ ] implement glossary
- [ ] implement "copy" buttons (click a button to copy the entire text of a paragraph, quote, etc.)
- [ ] light/dark/high-contrast mode toggle
- [ ] dark mode: try to avoid the "window blending" problem (cannot tell where the browser window starts and another
application window begins)
- [ ] consider using the following colors:
- [ ] #E86D82 (red-pink)

# Possible features

- the ability to ask a question in natural language (via text or mic), e.g. "What happens in the sacrament of
Confirmation?"
- note-taking and highlighting
- permanent and temporary storage (easily toggleable)
- narration
- recordered audio (better than a screen reader)
- text is highlighted to follow along (toggleable)
- [ ] the ability to ask a question in natural language (via text or mic), e.g. "What happens in the sacrament of
Confirmation?"
- [ ] note-taking and highlighting
- [ ] permanent and temporary storage (easily toggleable)
- [ ] narration
- [ ] recordered audio (better than a screen reader)
- [ ] text is highlighted to follow along (toggleable)
7 changes: 5 additions & 2 deletions catechism-of-the-catholic-church.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@
{
"path": "."
}
]
}
],
"settings": {
"deno.enable": true
}
}
23 changes: 23 additions & 0 deletions catechism/artifact-builders/build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { build as buildPathMap } from './path-map.ts';
import { build as buildTableOfContents } from './table-of-contents.ts';

buildArtifacts();

function buildArtifacts(): void {
console.log('\nBuilding artifacts ...');

console.log('\ttable-of-contents ...');
const tableOfContents = buildTableOfContents();
writeJson(tableOfContents, 'table-of-contents');

console.log('\tsemantic-path to path-id map ...');
const pathMap = buildPathMap(tableOfContents);
writeJson(pathMap, 'semantic-path-to-path-id');
}

function writeJson(object: Record<string, unknown>, filename: string): void {
Deno.writeTextFileSync(
`catechism/artifacts/${filename}.json`,
JSON.stringify(object, undefined, ' ') + '\n',
);
}
28 changes: 28 additions & 0 deletions catechism/artifact-builders/path-map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { PathMap, TableOfContentsEntry, TableOfContentsType } from '../source/types/types.ts';

export function build(tableOfContents: TableOfContentsType): PathMap {
const pathMaps = [
tableOfContents.prologue,
...tableOfContents.parts,
].flatMap((entry) => entryToPathMaps(entry));

return pathMaps.reduce((previous, current) => (
{
...previous,
...current,
}
));
}

function entryToPathMaps(entry: TableOfContentsEntry): Array<PathMap> {
return [
getPathMap(entry),
...entry.children.flatMap((childEntry) => entryToPathMaps(childEntry)),
];
}

function getPathMap(entry: TableOfContentsEntry): PathMap {
return {
[entry.semanticPath]: entry.pathID,
};
}
162 changes: 162 additions & 0 deletions catechism/artifact-builders/table-of-contents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { Catechism } from '../source/catechism.ts';
import { ArticleParagraph } from '../source/types/article-paragraph.ts';
import { Subarticle } from '../source/types/subarticle.ts';
import {
Article,
buildSemanticPath,
Chapter,
Content,
ContentBase,
ContentContainer,
Paragraph,
Part,
Section,
SemanticPathSource,
TableOfContentsEntry,
TableOfContentsType,
} from '../source/types/types.ts';
import { getParagraphs, hasMainContent, hasOpeningContent } from '../utils.ts';
import { ParagraphGroup } from '../source/types/paragraph-group.ts';

//#region builders
export function build(): TableOfContentsType {
return {
prologue: buildEntry(Catechism.prologue, [], true),
parts: Catechism.parts.map((part) => buildEntry(part, [])),
};
}

function buildEntry<T extends ContentBase | ContentBase & ContentContainer>(
content: T,
ancestors: Array<SemanticPathSource>,
ignoreChildren = false,
): TableOfContentsEntry {
const firstParagraphNumber = getFirstParagraphNumber(content);
if (typeof firstParagraphNumber !== 'number') {
throw Error(
`A paragraph could not be found for the given content: ${content.contentType}, pathID: ${content.pathID}`,
);
}

const semanticPathSource = getSemanticPathSource(content);

return {
contentType: content.contentType,
title: getTitle(content),
pathID: content.pathID,
semanticPath: buildSemanticPath(semanticPathSource, ancestors),
firstParagraphNumber,
children: ignoreChildren ? [] : buildChildEntries(content, [semanticPathSource, ...ancestors]),
};
}

function buildChildEntries<T extends ContentBase | ContentBase & ContentContainer>(
content: T,
ancestors: Array<SemanticPathSource>,
): Array<TableOfContentsEntry> {
const childEntries: Array<TableOfContentsEntry> = [];

const hasPopulatedOpeningContent = hasOpeningContent(content) &&
(content as ContentContainer).openingContent.length > 0;

if (hasPopulatedOpeningContent) {
// TODO: Implement
/*/
const openingContentRoot = (content as ContentContainer).openingContent[0];
// TODO: Which is correct? (Should `openingContentRoot` or `content` be used?)
const openingContentEntry: TableOfContentsEntry = {
contentType: openingContentRoot.contentType,
title: 'Opening Content',
pathID: openingContentRoot.pathID,
semanticPath: TODO,
firstParagraphNumber: TODO,
children: []
};
const openingContentEntry: TableOfContentsEntry = {
contentType: content.contentType,
title: 'Opening Content',
pathID: content.pathID,
semanticPath: TODO,
firstParagraphNumber: TODO,
children: []
};
childEntries.push(openingContentEntry);
/*/
}

const mainContentEntries = hasMainContent(content)
? (content as ContentContainer).mainContent
.filter((content) => includeInTableOfContents(content))
.map((child) => buildEntry(child, ancestors))
: [];

return childEntries.concat(mainContentEntries);
}
//#endregion

//#region helpers
/**
* @returns `true` if the content should be included in the Table of Contents, and `false` otherwise
*/
function includeInTableOfContents<T extends ContentBase>(content: T): boolean {
return Content.PROLOGUE === content.contentType ||
Content.PART === content.contentType ||
Content.SECTION === content.contentType ||
Content.CHAPTER === content.contentType ||
Content.ARTICLE === content.contentType ||
Content.ARTICLE_PARAGRAPH === content.contentType ||
Content.SUB_ARTICLE === content.contentType ||
Content.IN_BRIEF === content.contentType;
}

function getTitle(content: ContentBase): string {
return `${Content[content.contentType]} ${content.pathID}`;
}

function getSemanticPathSource<T extends ContentBase>(
content: T,
): SemanticPathSource {
return {
content: content.contentType,
number: getNumber(content),
};
}

function getNumber<T extends ContentBase>(content: T): number | null {
if (Content.PART === content.contentType) {
return (content as unknown as Part).partNumber;
} else if (Content.SECTION === content.contentType) {
return (content as unknown as Section).sectionNumber;
} else if (Content.CHAPTER === content.contentType) {
return (content as unknown as Chapter).chapterNumber;
} else if (Content.ARTICLE === content.contentType) {
return (content as unknown as Article).articleNumber;
} else if (Content.ARTICLE_PARAGRAPH === content.contentType) {
return (content as unknown as ArticleParagraph).articleParagraphNumber;
} else if (Content.SUB_ARTICLE === content.contentType) {
return (content as unknown as Subarticle).subarticleNumber;
} else if (Content.PARAGRAPH_GROUP === content.contentType) {
return (content as unknown as ParagraphGroup).paragraphGroupNumber;
} else if (Content.PARAGRAPH === content.contentType) {
return (content as unknown as Paragraph).paragraphNumber;
} else {
return null;
}
}

/**
* @returns the `paragraphNumber` value of the first `Paragraph` that's a child of the given content, or `null` if no such `Paragraph` exists
*/
function getFirstParagraphNumber<T extends ContentBase | ContentBase & ContentContainer>(content: T): number | null {
const mainContentExists = hasMainContent(content);
if (mainContentExists) {
const paragraphs = getParagraphs([content as ContentBase & ContentContainer]);
return paragraphs[0]?.paragraphNumber ?? null;
} else {
return null;
}
}
//#endregion
1 change: 1 addition & 0 deletions catechism/artifacts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Excepting this file, all files in this directory are programmatically generated, and should not be manually modified.

0 comments on commit 0e92881

Please sign in to comment.