Skip to content
Merged
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
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
npm run typecheck
npm run lint:fix
git add .
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/actions/log-out.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const logOut = async (): Promise<void> => {
`getYourDocuments`,
`getYourAccount`,
`getYourMindmaps`,
`getUserResourceCompletions`,
);
} catch {}
};
Expand Down
17 changes: 17 additions & 0 deletions src/api-4markdown-contracts/atoms.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { SUID } from "development-kit/suid";
import { Brand } from "development-kit/utility-types";

type Id = string;
Expand All @@ -14,6 +15,17 @@ type Url = string;
type UserProfileId = Brand<Id, `UserProfileId`>;
type CommentId = Brand<Id, `CommentId`>;

type DocumentId = Brand<Id, `DocumentId`>;
type MindmapNodeId = Brand<SUID, `MindmapNodeId`>;
type MindmapId = Brand<Id, `MindmapId`>;

type ResourceId = DocumentId | MindmapNodeId | MindmapId;

const RESOURCE_TYPES = ["document", "mindmap", "mindmap-node"] as const;

type ResourceType = (typeof RESOURCE_TYPES)[number];

export { RESOURCE_TYPES };
export type {
Id,
Name,
Expand All @@ -28,4 +40,9 @@ export type {
Slug,
UserProfileId,
CommentId,
ResourceId,
DocumentId,
MindmapId,
MindmapNodeId,
ResourceType,
};
41 changes: 39 additions & 2 deletions src/api-4markdown-contracts/contracts/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import { Brand, type Prettify } from "development-kit/utility-types";
import type { Base64, Date, Id, Url, UserProfileId } from "../atoms";
import type {
Base64,
Date,
DocumentId,
Id,
MindmapId,
MindmapNodeId,
ResourceId,
ResourceType,
Url,
UserProfileId,
} from "../atoms";
import type {
DocumentDto,
PermanentDocumentDto,
Expand All @@ -14,6 +25,7 @@ import type {
RewriteAssistantPersona,
YourAccountDto,
CommentDto,
ResourceCompletionDto,
} from "../dtos";
// @TODO[PRIO=1]: [Add better error handling and throwing custom errors].

Expand Down Expand Up @@ -234,6 +246,29 @@ type AddUserProfileCommentContract = Contract<
}
>;

type GetUserResourceCompletionsContract = Contract<
`getUserResourceCompletions`,
Record<ResourceId, ResourceCompletionDto>
>;

type SetUserResourceCompletionContract = Contract<
"setUserResourceCompletion",
ResourceCompletionDto | null,
| {
type: Extract<ResourceType, "document">;
resourceId: DocumentId;
}
| {
type: Extract<ResourceType, "mindmap">;
resourceId: MindmapId;
}
| {
type: Extract<ResourceType, "mindmap-node">;
resourceId: MindmapNodeId;
parentId: MindmapId;
}
>;

type API4MarkdownContracts =
| CreateMindmapContract
| GetYourDocumentsContract
Expand Down Expand Up @@ -261,7 +296,9 @@ type API4MarkdownContracts =
| CreateContentWithAIContract
| GetYourAccountContract
| GetUserProfileContract
| AddUserProfileCommentContract;
| AddUserProfileCommentContract
| GetUserResourceCompletionsContract
| SetUserResourceCompletionContract;

type API4MarkdownContractKey = API4MarkdownContracts["key"];
type API4MarkdownDto<TKey extends API4MarkdownContractKey> = Extract<
Expand Down
1 change: 1 addition & 0 deletions src/api-4markdown-contracts/dtos/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from "./full-mindmap.dto";
export * from "./rewrite-assistant.dto";
export * from "./your-account.dto";
export * from "./comment.dto";
export * from "./resource-completion.dto";
27 changes: 27 additions & 0 deletions src/api-4markdown-contracts/dtos/resource-completion.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type {
Date,
DocumentId,
MindmapId,
MindmapNodeId,
ResourceType,
} from "../atoms";

type ResourceCompletionDto =
| {
cdate: Date;
type: Extract<ResourceType, "document">;
resourceId: DocumentId;
}
| {
cdate: Date;
type: Extract<ResourceType, "mindmap">;
resourceId: MindmapId;
}
| {
cdate: Date;
type: Extract<ResourceType, "mindmap-node">;
resourceId: MindmapNodeId;
parentId: MindmapId;
};

export type { ResourceCompletionDto };
29 changes: 25 additions & 4 deletions src/components/education-documents-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,31 @@ import { Link } from "gatsby";
import type { RichEducationDocumentModel } from "models/page-models";
import React from "react";
import { meta } from "../../meta";
import { BiCheckboxChecked } from "react-icons/bi";
import { useResourceCompletion } from "modules/resource-completions";
import { DocumentId } from "api-4markdown-contracts";

type EducationDocumentsListProps = {
documents: RichEducationDocumentModel[];
};

const now = new Date();

const ResourceCompletionMarkerContainer = ({ id }: { id: DocumentId }) => {
const completion = useResourceCompletion(id);

if (!completion) {
return null;
}

return (
<span className="flex items-center gap-0.5 text-sm font-medium uppercase w-fit rounded-md bg-green-700 text-white py-1 px-2 line-clamp-1">
<BiCheckboxChecked aria-hidden="true" className="shrink-0" size={20} />
<span>Completed</span>
</span>
);
};

const EducationDocumentsList = ({ documents }: EducationDocumentsListProps) => {
return (
<ol className="flex flex-col space-y-8">
Expand Down Expand Up @@ -56,10 +74,13 @@ const EducationDocumentsList = ({ documents }: EducationDocumentsListProps) => {
{document.name}
</Link>
</h2>
<p className="lg:max-w-[70%] mt-1 mb-3">{document.description}</p>
<p className="mb-5 text-sm uppercase w-fit rounded-md bg-slate-200 dark:bg-slate-800 py-1 px-3 line-clamp-1">
{document.tags.join(`, `)}
</p>
<p className="lg:max-w-[70%] mt-2 mb-3">{document.description}</p>
<div className="mb-5 flex flex-wrap gap-2">
<ResourceCompletionMarkerContainer id={document.id as DocumentId} />
<span className="flex text-sm font-medium uppercase w-fit rounded-md bg-slate-200 dark:bg-slate-800 py-1 px-3 line-clamp-1">
{document.tags.join(`, `)}
</span>
</div>
<div className="flex items-center space-x-2">
{RATING_ICONS.map(([Icon, category]) => (
<div className="flex items-center" key={category}>
Expand Down
57 changes: 30 additions & 27 deletions src/components/markdown-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,7 @@ const MarkdownWidget = ({
const asideNavigation = useSimpleFeature();
const [activeHeading, setActiveHeading] = React.useState<string | null>(null);

const headings = React.useMemo(
() => (asideNavigation.isOn ? extractHeadings(markdown) : []),
[markdown, asideNavigation.isOn],
);
const headings = React.useMemo(() => extractHeadings(markdown), [markdown]);

const chunks = React.useMemo((): string[] => {
if (chunksMode.isOff) return [];
Expand Down Expand Up @@ -216,13 +213,6 @@ const MarkdownWidget = ({
>
{chunksMode.isOn ? <BiListOl /> : <BiDetail />}
</Button>
<Button i={2} s={1} title="Copy markdown" onClick={copyMarkdown}>
{copyState.is === `copied` ? (
<BiCheck className="text-green-700" />
) : (
<BiCopyAlt />
)}
</Button>
</Modal2.Header>
<Modal2.Body
id={bodyId}
Expand Down Expand Up @@ -276,28 +266,41 @@ const MarkdownWidget = ({
)}
</Modal2.Body>
<Modal2.Footer className="justify-between">
<Button
i={2}
s={1}
title="Scroll to top preview top"
disabled={asideNavigation.isOn}
onClick={scrollToTop}
>
<BiArrowToTop />
</Button>
<div className="flex items-center gap-2">
<Button
title={
asideNavigation.isOn
? "Hide table of contents"
: "Show table of contents"
}
i={2}
s={1}
onClick={asideNavigation.toggle}
title="Scroll to top preview top"
disabled={asideNavigation.isOn}
onClick={scrollToTop}
>
{asideNavigation.isOn ? <BiChevronDown /> : <BiBookContent />}
<BiArrowToTop />
</Button>
<Button i={2} s={1} title="Copy markdown" onClick={copyMarkdown}>
{copyState.is === `copied` ? (
<BiCheck className="text-green-700" />
) : (
<BiCopyAlt />
)}
</Button>
</div>

<div className="flex items-center gap-2">
{finalHeadings.length > 1 && (
<Button
title={
asideNavigation.isOn
? "Hide table of contents"
: "Show table of contents"
}
i={2}
s={1}
onClick={asideNavigation.toggle}
>
{asideNavigation.isOn ? <BiChevronDown /> : <BiBookContent />}
</Button>
)}

{chunksMode.isOn && (
<>
<Button
Expand Down
73 changes: 72 additions & 1 deletion src/containers/document-layout.container.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import React from "react";
import { Badge } from "design-system/badge";
import { Avatar } from "design-system/avatar";
import { BiBook, BiCheck, BiCopyAlt, BiLogoMarkdown } from "react-icons/bi";
import {
BiBook,
BiCheck,
BiCheckboxChecked,
BiCheckboxMinus,
BiCopyAlt,
BiLogoMarkdown,
} from "react-icons/bi";
import { Button } from "design-system/button";
import { useCopy } from "development-kit/use-copy";
import { Status } from "design-system/status";
Expand All @@ -16,6 +23,12 @@ import { ScrollToTop } from "components/scroll-to-top";
import { Markdown } from "components/markdown";
import { useSimpleFeature } from "@greenonsoftware/react-kit";
import { TableOfContent } from "components/table-of-content";
import {
useResourceCompletion,
useResourceCompletionToggle,
useResourcesCompletionState,
} from "modules/resource-completions";
import { API4MarkdownPayload, DocumentId } from "api-4markdown-contracts";

const MarkdownWidget = React.lazy(() =>
import("components/markdown-widget").then(({ MarkdownWidget }) => ({
Expand All @@ -25,6 +38,60 @@ const MarkdownWidget = React.lazy(() =>

const CONTENT_ID = `document-layout-content`;

const ResourceCompletionTriggerContainer = () => {
const [{ document }] = useDocumentLayoutContext();
const [toggleConfig] = React.useState<
API4MarkdownPayload<"setUserResourceCompletion">
>(() => ({
type: "document",
resourceId: document.id as DocumentId,
}));
const [toggleState, completion, toggle] =
useResourceCompletionToggle(toggleConfig);
const resourcesCompletionState = useResourcesCompletionState();

// @TODO[PRIO=2]: [Handle error case with some toast or error message].
return (
<Button
s={2}
i={2}
disabled={
toggleState.is === `busy` || resourcesCompletionState.is === `busy`
}
auto
onClick={toggle}
>
{completion ? (
<>
Mark As Uncompleted <BiCheckboxMinus />
</>
) : (
<>
Mark As Completed <BiCheckboxChecked />
</>
)}
</Button>
);
};

const ResourceCompletionMarkerContainer = () => {
const [{ document }] = useDocumentLayoutContext();
const completion = useResourceCompletion(document.id as DocumentId);

if (!completion) {
return null;
}

return (
<p className="mb-4 flex gap-1 text-sm justify-center items-center border bg-zinc-200 dark:bg-gray-950 border-zinc-300 dark:border-zinc-800 p-2 rounded-md">
<BiCheckboxChecked className="shrink-0" size={24} />
<span>
You're browsing already <strong>completed resource</strong>.
</span>
</p>
);
};

const DocumentLayoutContainer = () => {
const [{ document }] = useDocumentLayoutContext();
const { code, author } = document;
Expand All @@ -40,6 +107,7 @@ const DocumentLayoutContainer = () => {
<>
<div className="px-4 py-10 relative lg:flex lg:justify-center">
<main className="max-w-prose mx-auto mb-8 lg:mr-8 lg:mb-0 lg:mx-0">
<ResourceCompletionMarkerContainer />
<section className="flex items-center gap-2.5 mb-6 justify-end sm:justify-start">
<Button
title="Open in documents creator"
Expand Down Expand Up @@ -83,6 +151,9 @@ const DocumentLayoutContainer = () => {
<section id={CONTENT_ID}>
<Markdown>{code}</Markdown>
</section>
<section className="mt-10">
<ResourceCompletionTriggerContainer />
</section>
{author?.bio && author?.displayName && (
<section className="mt-12">
<div className="flex max-w-xl space-x-5 ml-auto rounded-lg">
Expand Down
1 change: 1 addition & 0 deletions src/core/app-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ const useAppEvent = (callback: (event: AppEvent) => void): void => {
}, []);
};

export type { AppEvent };
export { emit, subscribe, useAppEvent };
Loading
Loading