Skip to content
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
24 changes: 20 additions & 4 deletions docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { recommendedBeforeDefaultRemarkPlugins, recommendedRehypePlugins, recomm
import { remarkPdfPluginConfig } from '@tdev/remark-pdf';
import { excalidrawPluginConfig } from '@tdev/excalidoc';
import type { EditThisPageOption, ShowEditThisPage, TdevConfig } from '@tdev/siteConfig/siteConfig';
import type { ParseFrontMatterResult } from '@docusaurus/types/src/markdown';

const siteConfig = getSiteConfig();

Expand All @@ -48,6 +49,21 @@ const PROJECT_NAME = siteConfig.gitHub?.projectName ?? 'teaching-dev';
const GH_OAUTH_CLIENT_ID = process.env.GH_OAUTH_CLIENT_ID;
const DEFAULT_TEST_USER = process.env.DEFAULT_TEST_USER?.trim();

/**
* exposes the `page_id` frontmatter as `pid` in `sidebar_custom_props`
* this way the sidebar can access the page_id without additional plugins
* and we can use it to access the page model for the current page in the sidebar
*/
const exposePidToSidebar = (fm: ParseFrontMatterResult) => {
if (!('sidebar_custom_props' in fm.frontMatter)) {
fm.frontMatter.sidebar_custom_props = {};
}
if (!('pid' in (fm.frontMatter as any).sidebar_custom_props) && ('page_id' in fm.frontMatter)) {
(fm.frontMatter.sidebar_custom_props as any).pid = fm.frontMatter.page_id;
}
return fm;
};

const config: Config = applyTransformers({
title: TITLE,
tagline: siteConfig.tagline ?? 'Eine Plattform zur Gestaltung interaktiver Lernerlebnisse',
Expand Down Expand Up @@ -138,21 +154,21 @@ const config: Config = applyTransformers({
parseFrontMatter: async (params) => {
const result = await params.defaultParseFrontMatter(params);
if (process.env.NODE_ENV === 'production') {
return result;
return exposePidToSidebar(result);
}
/**
* don't add frontmatter to partials
*/
const fileName = path.basename(params.filePath);
if (fileName.startsWith('_')) {
// it is a partial, don't add frontmatter
return result;
return exposePidToSidebar(result);
}
/**
* don't edit blogs frontmatter
*/
if (params.filePath.startsWith(`${BUILD_LOCATION}/blog/`)) {
return result;
return exposePidToSidebar(result);
}
if (process.env.NODE_ENV !== 'production') {
let needsRewrite = false;
Expand Down Expand Up @@ -182,7 +198,7 @@ const config: Config = applyTransformers({
)
}
}
return result;
return exposePidToSidebar(result);
},
mermaid: true,
hooks: {
Expand Down
1 change: 1 addition & 0 deletions packages/tdev/page-progress-state/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.assets
1 change: 1 addition & 0 deletions packages/tdev/page-progress-state/assets/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.json
15 changes: 15 additions & 0 deletions packages/tdev/page-progress-state/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "@tdev/page-progress-state",
"version": "1.0.0",
"main": "remark-plugin/index.ts",
"types": "remark-plugin/index.ts",
"dependencies": {},
"devDependencies": {
"vitest": "*",
"@docusaurus/module-type-aliases": "*",
"@docusaurus/core": "*"
},
"peerDependencies": {
"@tdev/core": "1.0.0"
}
}
95 changes: 95 additions & 0 deletions packages/tdev/page-progress-state/remark-plugin/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type { Plugin, Transformer } from 'unified';
import type { Node, Root } from 'mdast';
import path from 'path';
import { promises as fs, accessSync, mkdirSync, writeFileSync, readFileSync } from 'fs';

export interface PluginOptions {
extractors: { test: (node: Node) => boolean; getDocumentRootIds: (node: Node) => string[] }[];
}

const projectRoot = process.cwd();

/**
*
* sidebar:
* {
* "1f6db0ee-aa48-44c7-af43-4b66843f665e": [ ... document root ids ]
* }
*
* structure:
* {
* "path" : {
* "to": {
* "doc":
* },
* }
* }
*/

const ensureFile = async (indexPath: string) => {
const assetsDir = path.dirname(indexPath);
try {
accessSync(assetsDir);
} catch {
mkdirSync(assetsDir, { recursive: true });
}
try {
accessSync(indexPath);
} catch {
writeFileSync(indexPath, JSON.stringify({}, null, 2), {
encoding: 'utf-8'
});
}
};

/**
* A remark plugin that adds a `<MdxPage /> elements at the top of the current page.
* This is useful to initialize a page model on page load and to trigger side-effects on page display,
* as to load models attached to the `page_id`'s root document.
*/
const remarkPlugin: Plugin<PluginOptions[], Root> = function plugin(
options = { extractors: [] }
): Transformer<Root> {
const index = new Map<string, string[]>();
const structurePath = path.resolve(__dirname, '../assets/', 'structure.json');
const indexPath = path.resolve(__dirname, '../assets/', 'index.json');
ensureFile(indexPath);
ensureFile(structurePath);
try {
const content = readFileSync(indexPath, { encoding: 'utf-8' });
const parsed = JSON.parse(content) as { [key: string]: string[] };
for (const [key, values] of Object.entries(parsed)) {
index.set(key, values);
}
} catch {
console.log('Error parsing existing index file, starting fresh.');
}

return async (root, file) => {
const { visit, EXIT } = await import('unist-util-visit');
const { page_id } = (file.data?.frontMatter || {}) as { page_id?: string };
if (!page_id) {
return;
}
const filePath = path
.relative(projectRoot, file.path)
.replace(/\/(index|README)\.mdx?$/i, '')
.replace(/\.mdx?$/i, '');
console.log('file', filePath);
const pageIndex = new Set<string>([]);
visit(root, (node, idx, parent) => {
const extractor = options.extractors.find((ext) => ext.test(node));
if (!extractor) {
return;
}
const docRootIds = extractor.getDocumentRootIds(node);
docRootIds.forEach((id) => pageIndex.add(id));
});
index.set(page_id, [...pageIndex]);
await fs.writeFile(indexPath, JSON.stringify(Object.fromEntries(index), null, 2), {
encoding: 'utf-8'
});
};
};

export default remarkPlugin;
3 changes: 3 additions & 0 deletions packages/tdev/page-progress-state/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "../../../tsconfig.json"
}
4 changes: 2 additions & 2 deletions src/components/Admin/EditUser/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ const EditUser = observer((props: Props) => {
authClient.admin
.setUserPassword({ userId: user.id, newPassword: password })
.then((res) => {
if (res.data) {
if (res?.data) {
setPwState('success');
} else {
setPwState('error');
Expand Down Expand Up @@ -284,7 +284,7 @@ const EditUser = observer((props: Props) => {
setSpinState('deleting');
authClient.admin.removeUser({ userId: user.id }).then(
action((res) => {
if (res.data?.success) {
if (res?.data?.success) {
userStore.removeFromStore(user.id);
props.close();
}
Expand Down
6 changes: 3 additions & 3 deletions src/plugins/remark-page/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import type { Plugin, Transformer } from 'unified';
import type { MdxJsxFlowElement } from 'mdast-util-mdx';
import type { Root } from 'mdast';
import type { Node, Root } from 'mdast';
import { toJsxAttribute } from '../helpers';

/**
* A remark plugin that adds a `<MdxPage /> elements at the top of the current page.
* This is useful to initialize a page model on page load and to trigger side-effects on page display,
* as to load models attached to the `page_id`'s root document.
*/
const plugin: Plugin<unknown[], Root> = function plugin(): Transformer<Root> {
const plugin: Plugin<any, Root> = function plugin(): Transformer<Root> {
return async (root, file) => {
const { visit, EXIT } = await import('unist-util-visit');
const { page_id } = (file.data?.frontMatter || {}) as { page_id?: string };
if (!page_id) {
return;
}
visit(root, (node, index, parent) => {
visit(root, (node, idx, parent) => {
/** add the MdxPage exactly once at the top of the document and exit */
if (root === node && !parent) {
const loaderNode: MdxJsxFlowElement = {
Expand Down
40 changes: 39 additions & 1 deletion src/siteConfig/markdownPluginConfigs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { Node } from 'mdast';
import path from 'path';
import type { LeafDirective } from 'mdast-util-directive';
import type { MdxJsxFlowElement, MdxJsxTextElement } from 'mdast-util-mdx-jsx';
import strongPlugin, { transformer as captionVisitor } from '../plugins/remark-strong/plugin';
import deflistPlugin from '../plugins/remark-deflist/plugin';
import mdiPlugin from '../plugins/remark-mdi/plugin';
Expand All @@ -13,6 +15,7 @@ import linkAnnotationPlugin from '../plugins/remark-link-annotation/plugin';
import mediaPlugin from '../plugins/remark-media/plugin';
import detailsPlugin from '../plugins/remark-details/plugin';
import pagePlugin from '../plugins/remark-page/plugin';
import pageProgressStatePlugin from '@tdev/page-progress-state/remark-plugin';
import graphvizPlugin from '@tdev/remark-graphviz/remark-plugin';
import pdfPlugin from '@tdev/remark-pdf/remark-plugin';
import codeAsAttributePlugin from '../plugins/remark-code-as-attribute/plugin';
Expand Down Expand Up @@ -129,7 +132,41 @@ export const enumerateAnswersPluginConfig = [

export const pdfPluginConfig = pdfPlugin;

export const pagePluginConfig = pagePlugin;
const cwd = process.cwd();
const indexPath = path.resolve(cwd, './src/.page-index');
const ComponentsWithId = new Set(['TaskState', 'ProgressState']);
const AnswerTypes = new Set(['state', 'progress']);
export const pagePluginConfig = [pagePlugin, {}];

export const pageProgressStatePluginConfig = [
pageProgressStatePlugin,
{
extractors: [
{
test: (_node: Node) => {
if (_node.type !== 'mdxJsxFlowElement') {
return false;
}
const node = _node as MdxJsxFlowElement;
const name = node.name as string;
return (
ComponentsWithId.has(name) ||
node.attributes.some(
(a) =>
(a as { name?: string }).name === 'type' && AnswerTypes.has(a.value as string)
)
);
},
getDocumentRootIds: (node: Node) => {
const jsxNode = node as MdxJsxFlowElement;
const idAttr = jsxNode.attributes.find((attr) => (attr as any).name === 'id');
return idAttr ? [idAttr.value] : [];
}
}
]
}
];

export const graphvizPluginConfig = graphvizPlugin;

export const commentPluginConfig = [
Expand Down Expand Up @@ -169,6 +206,7 @@ export const recommendedRemarkPlugins = [
enumerateAnswersPluginConfig,
pdfPluginConfig,
pagePluginConfig,
pageProgressStatePluginConfig,
commentPluginConfig,
linkAnnotationPluginConfig,
codeAsAttributePluginConfig
Expand Down
32 changes: 32 additions & 0 deletions src/stores/PageStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { RootStore } from '@tdev-stores/rootStore';
import Page from '@tdev-models/Page';
import { computedFn } from 'mobx-utils';
import { allDocuments as apiAllDocuments } from '@tdev-api/document';
import type { useDocsSidebar } from '@docusaurus/plugin-content-docs/client';
import { PropSidebarItem } from '@docusaurus/plugin-content-docs';
type PageIndex = { [key: string]: string[] };

export class PageStore extends iStore {
readonly root: RootStore;
Expand All @@ -13,11 +16,40 @@ export class PageStore extends iStore {
@observable accessor currentPageId: string | undefined = undefined;
@observable accessor runningTurtleScriptId: string | undefined = undefined;

@observable.ref accessor pageIndex: PageIndex = {};
sidebars = observable.map<string, ReturnType<typeof useDocsSidebar>>([], { deep: false });

constructor(store: RootStore) {
super();
this.root = store;
}

@action
configureSidebar(id: string, sidebar: ReturnType<typeof useDocsSidebar>) {
if (this.sidebars.has(id) || !sidebar) {
return;
}
this.sidebars.set(id, sidebar);
sidebar.items.forEach((item) => {
if (item.type !== 'category') {
return;
}
item.items;
});
}

@action
load() {
return import('@tdev/page-progress-state/assets/index.json').then((mod) => {
this.updatePageIndex(mod.default as PageIndex);
});
}

@action
updatePageIndex(newIndex: PageIndex) {
this.pageIndex = newIndex;
}

find = computedFn(
function (this: PageStore, id?: string): Page | undefined {
if (!id) {
Expand Down
1 change: 1 addition & 0 deletions src/stores/rootStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export class RootStore {
* load stores
*/
this.userStore.load();
this.pageStore.load();
this.studentGroupStore.load();
this.cmsStore.initialize();
if (user.hasElevatedAccess) {
Expand Down
7 changes: 7 additions & 0 deletions src/theme/DocItem/Content/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,17 @@ import { observer } from 'mobx-react-lite';
import { useStore } from '@tdev-hooks/useStore';
import { useLocation } from '@docusaurus/router';
type Props = WrapperProps<typeof ContentType>;
import { useDocsSidebar } from '@docusaurus/plugin-content-docs/client';

const ContentWrapper = observer((props: Props): React.ReactNode => {
const pageStore = useStore('pageStore');
const location = useLocation();
const sidebar = useDocsSidebar();
React.useEffect(() => {
if (sidebar?.name) {
pageStore.configureSidebar(sidebar.name, sidebar);
}
}, [sidebar, pageStore]);

React.useEffect(() => {
if (pageStore.current) {
Expand Down
Loading
Loading