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
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"dependencies": {
"@ai-sdk/react": "^2.0.115",
"@arcjet/next": "1.0.0-alpha.20",
"@changespage/react": "workspace:*",
"@changespage/react": "0.2.0",
"@changespage/supabase": "workspace:*",
"@changespage/ui": "workspace:*",
"@changespage/utils": "workspace:*",
Expand Down
14 changes: 10 additions & 4 deletions apps/web/pages/changelog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,17 @@ export default function Changelog({
<div className="space-y-12">
{posts.map((post, index) => (
<ChangelogPost key={post.id} post={post}>
{({ title, content, tags, formattedDate }) => (
{({ title, content, tags, publicationDate }) => (
<article className="md:border-l-2 md:border-gray-200 md:dark:border-gray-700 md:pl-6">
<time className="text-sm text-gray-500 dark:text-gray-400">
{formattedDate}
</time>
{publicationDate && (
<time className="text-sm text-gray-500 dark:text-gray-400">
{new Date(publicationDate).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})}
</time>
)}
<h2 className={`text-2xl font-semibold text-gray-900 dark:text-white mt-1 mb-3 underline ${UNDERLINE_COLORS[index % UNDERLINE_COLORS.length]}`}>
{title}
</h2>
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "changes-page",
"version": "1.1.0",
"private": true,
"scripts": {
"build": "pnpm --filter './packages/*' -r build",
"dev:page": "pnpm --filter './apps/page' -r dev",
Expand Down
81 changes: 81 additions & 0 deletions packages/core-sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# @changespage/core

Framework-agnostic JavaScript SDK for changes.page.

## Installation

```bash
npm install @changespage/core
```

## Usage

```ts
import { createChangesPageClient } from '@changespage/core';

const client = createChangesPageClient({
baseUrl: 'https://yourpage.changes.page'
});

const { posts, totalCount, hasMore } = await client.getPosts({ limit: 10 });

const latestPost = await client.getLatestPost();

const pinnedPost = await client.getPinnedPost();
```

## API

### `createChangesPageClient(config)`

| Option | Type | Description |
|--------|------|-------------|
| `baseUrl` | `string` | Your changes.page URL |

### `client.getPosts(options?)`

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `limit` | `number` | 10 | Posts per page (max 50) |
| `offset` | `number` | 0 | Pagination offset |

Returns `{ posts, totalCount, hasMore }`

### `client.getLatestPost()`

Returns the most recent post or `null`.

### `client.getPinnedPost()`

Returns the pinned post or `null` if none is pinned.

## Utilities

### `getTagLabel(tag)`

Returns a display label for a post tag.

```ts
import { getTagLabel } from '@changespage/core';

getTagLabel('new'); // "New"
getTagLabel('fix'); // "Fix"
```

## Types

```ts
type PostTag = 'fix' | 'new' | 'improvement' | 'announcement' | 'alert';

interface Post {
id: string;
title: string;
content: string;
tags: PostTag[];
publication_date: string | null;
updated_at: string;
created_at: string;
url: string;
plain_text_content: string;
}
```
34 changes: 34 additions & 0 deletions packages/core-sdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@changespage/core",
"version": "0.1.0",
"type": "module",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"files": [
"dist"
],
"scripts": {
"build": "rimraf dist && tsc",
"prepublishOnly": "npm run build"
},
"dependencies": {},
"devDependencies": {
"rimraf": "^6.1.0",
"typescript": "^5.3.3"
},
"author": "Arjun Komath <arjunkomath@gmail.com>",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/techulus/changes-page.git"
},
"publishConfig": {
"access": "public"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,30 @@ export function createChangesPageClient(config: ClientConfig): ChangesPageClient
return result.posts[0] ?? null;
}

async function getPinnedPost(): Promise<Post | null> {
const response = await fetch(`${baseUrl}/api/pinned`, {
headers: {
"X-API-Version": API_VERSION,
},
});

if (!response.ok) {
if (response.status === 404) {
return null;
}
const errorData = await response.json().catch(() => ({}));
const errorMessage =
(errorData as { error?: string }).error || response.statusText;
throw new Error(errorMessage);
}

const data: Post | null = await response.json();
return data;
}

return {
getPosts,
getLatestPost,
getPinnedPost,
};
}
10 changes: 10 additions & 0 deletions packages/core-sdk/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export { createChangesPageClient } from "./client";
export { getTagLabel } from "./utils";
export type {
ChangesPageClient,
ClientConfig,
GetPostsOptions,
GetPostsResult,
Post,
PostTag,
} from "./types";
34 changes: 34 additions & 0 deletions packages/core-sdk/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export type PostTag = "fix" | "new" | "improvement" | "announcement" | "alert";

export interface Post {
id: string;
title: string;
content: string;
tags: PostTag[];
publication_date: string | null;
updated_at: string;
created_at: string;
url: string;
plain_text_content: string;
}

export interface ClientConfig {
baseUrl: string;
}

export interface GetPostsOptions {
limit?: number;
offset?: number;
}

export interface GetPostsResult {
posts: Post[];
totalCount: number;
hasMore: boolean;
}

export interface ChangesPageClient {
getPosts: (options?: GetPostsOptions) => Promise<GetPostsResult>;
getLatestPost: () => Promise<Post | null>;
getPinnedPost: () => Promise<Post | null>;
}
13 changes: 13 additions & 0 deletions packages/core-sdk/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { PostTag } from "./types";

const tagLabels: Record<PostTag, string> = {
fix: "Fix",
new: "New",
improvement: "Improvement",
announcement: "Announcement",
alert: "Alert",
};

export function getTagLabel(tag: PostTag): string {
return tagLabels[tag] ?? tag;
}
21 changes: 21 additions & 0 deletions packages/core-sdk/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022", "DOM"],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"rootDir": "./src",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"verbatimModuleSyntax": true
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
6 changes: 3 additions & 3 deletions packages/react-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ export default async function ChangelogPage() {
<div>
{posts.map(post => (
<ChangelogPost key={post.id} post={post}>
{({ title, content, tags, formattedDate, url }) => (
{({ title, content, tags, publicationDate, url }) => (
<article>
<h2>{title}</h2>
<time>{formattedDate}</time>
{publicationDate && <time>{new Date(publicationDate).toLocaleDateString()}</time>}
<div>{tags.map(t => <span key={t}>{t}</span>)}</div>
<ReactMarkdown>{content}</ReactMarkdown>
</article>
Expand Down Expand Up @@ -65,7 +65,7 @@ Returns the most recent post or `null`.

Render prop component exposing:

- `id`, `title`, `content` (markdown), `plainText`, `tags`, `date`, `formattedDate`, `url`
- `id`, `title`, `content` (markdown), `plainText`, `tags`, `publicationDate`, `url`

## Hook

Expand Down
6 changes: 4 additions & 2 deletions packages/react-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@changespage/react",
"version": "0.1.0",
"version": "0.2.0",
"type": "module",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand All @@ -17,7 +17,9 @@
"build": "rimraf dist && tsc",
"prepublishOnly": "npm run build"
},
"dependencies": {},
"dependencies": {
"@changespage/core": "workspace:*"
},
"devDependencies": {
"@types/react": "^18.3.18",
"react": "^18.3.1",
Expand Down
6 changes: 2 additions & 4 deletions packages/react-sdk/src/components/ChangelogPost.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import type { ChangelogPostProps, ChangelogPostRenderProps } from "../types";
import { formatDate, parseDate } from "../utils";

export function ChangelogPost({ post, locale, children }: ChangelogPostProps) {
export function ChangelogPost({ post, children }: ChangelogPostProps) {
const renderProps: ChangelogPostRenderProps = {
id: post.id,
title: post.title,
content: post.content,
plainText: post.plain_text_content,
tags: post.tags,
date: parseDate(post.publication_date),
formattedDate: formatDate(post.publication_date, locale),
publicationDate: post.publication_date,
url: post.url,
};

Expand Down
2 changes: 1 addition & 1 deletion packages/react-sdk/src/hooks/usePosts.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useState, useCallback, useEffect, useRef } from "react";
import type { ChangesPageClient, Post } from "../types";
import type { ChangesPageClient, Post } from "@changespage/core";

export interface UsePostsInitialData {
posts: Post[];
Expand Down
13 changes: 6 additions & 7 deletions packages/react-sdk/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
export { createChangesPageClient } from "./client";
export { ChangelogPost } from "./components";
export { usePosts } from "./hooks";
export { formatDate, getTagLabel, parseDate } from "./utils";
export { createChangesPageClient, getTagLabel } from "@changespage/core";
export type {
ChangesPageClient,
ChangelogPostProps,
ChangelogPostRenderProps,
ClientConfig,
GetPostsOptions,
GetPostsResult,
Post,
PostTag,
} from "./types";
} from "@changespage/core";

export { ChangelogPost } from "./components";
export { usePosts } from "./hooks";
export type { ChangelogPostProps, ChangelogPostRenderProps } from "./types";
export type {
UsePostsInitialData,
UsePostsOptions,
Expand Down
Loading