Skip to content

Commit

Permalink
feat: Add readability score (#187)
Browse files Browse the repository at this point in the history
  • Loading branch information
evadecker authored Nov 15, 2024
1 parent f0996aa commit 8cfffcf
Show file tree
Hide file tree
Showing 17 changed files with 173 additions and 38 deletions.
5 changes: 5 additions & 0 deletions .changeset/twenty-oranges-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"namesake": minor
---

Display readability score alongside quest content editor
4 changes: 0 additions & 4 deletions convex/_generated/api.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/* prettier-ignore-start */

/* eslint-disable */
/**
* Generated `api` utility.
Expand Down Expand Up @@ -60,5 +58,3 @@ export declare const internal: FilterApi<
typeof fullApi,
FunctionReference<any, "internal">
>;

/* prettier-ignore-end */
4 changes: 0 additions & 4 deletions convex/_generated/api.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/* prettier-ignore-start */

/* eslint-disable */
/**
* Generated `api` utility.
Expand All @@ -22,5 +20,3 @@ import { anyApi } from "convex/server";
*/
export const api = anyApi;
export const internal = anyApi;

/* prettier-ignore-end */
4 changes: 0 additions & 4 deletions convex/_generated/dataModel.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/* prettier-ignore-start */

/* eslint-disable */
/**
* Generated data model types.
Expand Down Expand Up @@ -60,5 +58,3 @@ export type Id<TableName extends TableNames | SystemTableNames> =
* `mutationGeneric` to make them type-safe.
*/
export type DataModel = DataModelFromSchemaDefinition<typeof schema>;

/* prettier-ignore-end */
4 changes: 0 additions & 4 deletions convex/_generated/server.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/* prettier-ignore-start */

/* eslint-disable */
/**
* Generated utilities for implementing server-side Convex query and mutation functions.
Expand Down Expand Up @@ -142,5 +140,3 @@ export type DatabaseReader = GenericDatabaseReader<DataModel>;
* for the guarantees Convex provides your functions.
*/
export type DatabaseWriter = GenericDatabaseWriter<DataModel>;

/* prettier-ignore-end */
4 changes: 0 additions & 4 deletions convex/_generated/server.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/* prettier-ignore-start */

/* eslint-disable */
/**
* Generated utilities for implementing server-side Convex query and mutation functions.
Expand Down Expand Up @@ -89,5 +87,3 @@ export const internalAction = internalActionGeneric;
* @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`.
*/
export const httpAction = httpActionGeneric;

/* prettier-ignore-end */
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"tailwind-variants": "^0.2.1",
"tailwindcss": "^3.4.14",
"tailwindcss-animate": "^1.0.7",
"text-readability": "^1.1.0",
"use-debounce": "^10.0.4"
},
"devDependencies": {
Expand Down
37 changes: 37 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 20 additions & 8 deletions src/components/Badge/Badge.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { RemixiconComponentType } from "@remixicon/react";
import { tv } from "tailwind-variants";

export interface BadgeProps {
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
size?: "sm" | "lg";
variant?: "info" | "success" | "danger" | "warning";
Expand All @@ -16,10 +16,10 @@ const badge = tv({
lg: "text-sm rounded-md",
},
variant: {
info: "bg-blue-3 dark:bg-bluedark-3 text-blue-dim",
success: "bg-green-3 dark:bg-greendark-3 text-green-dim",
danger: "bg-red-3 dark:bg-reddark-3 text-red-dim",
warning: "bg-amber-3 dark:bg-amberdark-3 text-amber-dim",
info: "bg-blue-3 dark:bg-bluedark-3 text-blue-normal",
success: "bg-green-3 dark:bg-greendark-3 text-green-normal",
danger: "bg-red-3 dark:bg-reddark-3 text-red-normal",
warning: "bg-amber-3 dark:bg-amberdark-3 text-amber-normal",
},
},
defaultVariants: {
Expand All @@ -28,14 +28,26 @@ const badge = tv({
},
});

const icon = tv({
base: "w-4 h-4",
variants: {
variant: {
info: "text-blue-dim",
success: "text-green-dim",
danger: "text-red-dim",
warning: "text-amber-dim",
},
},
});

export function Badge({ icon: Icon, ...props }: BadgeProps) {
return (
<span
<div
{...props}
className={badge({ variant: props.variant, size: props.size })}
>
{Icon && <Icon size={16} />}
{Icon && <Icon className={icon({ variant: props.variant })} />}
{props.children}
</span>
</div>
);
}
20 changes: 20 additions & 0 deletions src/components/ReadingScore/ReadingScore.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Meta, StoryObj } from "@storybook/react";

import { ReadingScore } from "./ReadingScore";

const meta = {
component: ReadingScore,
parameters: {
layout: "centered",
},
} satisfies Meta<typeof ReadingScore>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
text: "Playing games has always been thought to be important to the development of well-balanced and creative children; however, what part, if any, they should play in the lives of adults has never been researched that deeply. I believe that playing games is every bit as important for adults as for children. Not only is taking time out to play games with our children and other adults valuable to building interpersonal relationships but is also a wonderful way to release built up tension.",
},
};
57 changes: 57 additions & 0 deletions src/components/ReadingScore/ReadingScore.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {
RiEmotionHappyFill,
RiEmotionNormalFill,
RiEmotionUnhappyFill,
} from "@remixicon/react";
import rs from "text-readability";
import { Badge } from "../Badge";

/**
* A badge that displays a grade-level readability score of a given text.
*/
export interface ReadingScoreProps {
/**
* The text to calculate the readability score for.
*/
text: string;
/**
* The minimum length of the text to calculate the score.
* @default 280
*/
minLength?: number;
}

export const ReadingScore = ({ text, minLength = 280 }: ReadingScoreProps) => {
if (!text || text.length < minLength) return null;

const score = rs.daleChallReadabilityScore(text);

if (!score) return null;

const variant = score >= 8 ? "danger" : score >= 6 ? "warning" : "success";
const icon =
score >= 8
? RiEmotionUnhappyFill
: score >= 6
? RiEmotionNormalFill
: RiEmotionHappyFill;

const readingLevel = (score: number) => {
if (score >= 9) return "College";
if (score >= 8) return "11th–12th grade";
if (score >= 7) return "9th–10th grade";
if (score >= 6) return "7th–8th grade";
if (score >= 5) return "5th–6th grade";
if (score > 0) return "4th grade or lower";
return "Unknown";
};

return (
<Badge icon={icon} variant={variant}>
<span>
<span>{readingLevel(score)} </span>
<span className="opacity-60">reading level</span>
</span>
</Badge>
);
};
1 change: 1 addition & 0 deletions src/components/ReadingScore/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./ReadingScore";
20 changes: 17 additions & 3 deletions src/components/RichTextEditor/RichTextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,26 @@ import {
import "@mdxeditor/editor/style.css";
import { useRef } from "react";
import { twMerge } from "tailwind-merge";
import { useDebounce } from "use-debounce";
import { ReadingScore } from "../ReadingScore";

export interface RichTextEditorProps extends MDXEditorProps {}
export interface RichTextEditorProps extends MDXEditorProps {
showReadingScore?: boolean;
}

export function RichTextEditor({ className, ...props }: RichTextEditorProps) {
export function RichTextEditor({
className,
showReadingScore = false,
...props
}: RichTextEditorProps) {
const ref = useRef<MDXEditorMethods>(null);
const [debouncedMarkdown] = useDebounce(props.markdown, 500);

return (
<MDXEditor
className={twMerge(
className,
"dark-theme dark-editor border border-gray-dim text-gray-normal rounded-lg flex flex-col",
"dark-theme dark-editor border border-gray-dim text-gray-normal rounded-lg flex flex-col w-full",
)}
contentEditableClassName="prose dark:prose-invert"
suppressHtmlProcessing
Expand All @@ -42,6 +51,11 @@ export function RichTextEditor({ className, ...props }: RichTextEditorProps) {
<Separator />
<ListsToggle options={["bullet", "number"]} />
<CreateLink />
{showReadingScore && (
<div className="ml-auto">
<ReadingScore text={debouncedMarkdown} />
</div>
)}
</>
),
}),
Expand Down
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export * from "./Popover";
export * from "./ProgressBar";
export * from "./RadioGroup";
export * from "./RangeCalendar";
export * from "./ReadingScore";
export * from "./RichTextEditor";
export * from "./SearchField";
export * from "./Select";
Expand Down
8 changes: 3 additions & 5 deletions src/routeTree.gen.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/* prettier-ignore-start */

/* eslint-disable */

// @ts-nocheck

// noinspection JSUnusedGlobalSymbols

// This file is auto-generated by TanStack Router
// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.

// Import Routes

Expand Down Expand Up @@ -521,8 +521,6 @@ export const routeTree = rootRoute
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()

/* prettier-ignore-end */

/* ROUTE_MANIFEST_START
{
"routes": {
Expand Down
Loading

0 comments on commit 8cfffcf

Please sign in to comment.