@fullstackdatasolutions/articles 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -6
- package/dist/index.cjs +64 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +62 -37
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +87 -24
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +3 -1
- package/dist/server.d.ts +3 -1
- package/dist/server.js +86 -23
- package/dist/server.js.map +1 -1
- package/package.json +11 -11
- package/src/ArticleBackLink.tsx +32 -0
- package/src/ArticleContent.tsx +4 -7
- package/src/__tests__/ArticleBackLink.test.tsx +89 -0
- package/src/__tests__/ArticleContent.test.tsx +72 -28
- package/src/__tests__/articlesConfig.test.ts +57 -0
- package/src/__tests__/markdown.test.ts +278 -0
- package/src/__tests__/renderMdx.test.tsx +280 -0
- package/src/__tests__/server-articles.test.ts +36 -1
- package/src/articlesConfig.ts +2 -0
- package/src/index.ts +1 -0
- package/src/markdown.ts +8 -6
- package/src/renderMdx.tsx +47 -0
- package/src/server-articles.ts +20 -11
package/dist/index.js
CHANGED
|
@@ -1029,18 +1029,42 @@ function ArticleNavigation({ previous, next, basePath }) {
|
|
|
1029
1029
|
] }) });
|
|
1030
1030
|
}
|
|
1031
1031
|
|
|
1032
|
-
// src/
|
|
1032
|
+
// src/ArticleBackLink.tsx
|
|
1033
|
+
import Link8 from "next/link";
|
|
1034
|
+
import { ArrowLeft } from "lucide-react";
|
|
1033
1035
|
import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1036
|
+
function ArticleBackLink({
|
|
1037
|
+
config,
|
|
1038
|
+
href = "/articles",
|
|
1039
|
+
label = "Back to Articles",
|
|
1040
|
+
className
|
|
1041
|
+
}) {
|
|
1042
|
+
if (config.showBackToArticles === false) return null;
|
|
1043
|
+
return /* @__PURE__ */ jsxs14(
|
|
1044
|
+
Link8,
|
|
1045
|
+
{
|
|
1046
|
+
href,
|
|
1047
|
+
className: className != null ? className : "inline-flex items-center gap-2 mb-6 -ml-3 px-3 py-2 rounded-md text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors",
|
|
1048
|
+
children: [
|
|
1049
|
+
/* @__PURE__ */ jsx14(ArrowLeft, { className: "h-4 w-4" }),
|
|
1050
|
+
label
|
|
1051
|
+
]
|
|
1052
|
+
}
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// src/ArticleTOC.tsx
|
|
1057
|
+
import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1034
1058
|
function ArticleTOC({ toc, className }) {
|
|
1035
1059
|
if (!toc.length) return null;
|
|
1036
|
-
return /* @__PURE__ */
|
|
1060
|
+
return /* @__PURE__ */ jsxs15(
|
|
1037
1061
|
"nav",
|
|
1038
1062
|
{
|
|
1039
1063
|
"aria-label": "Table of contents",
|
|
1040
1064
|
className: `mb-8 rounded-lg border border-border bg-muted/40 px-6 py-4 ${className != null ? className : ""}`,
|
|
1041
1065
|
children: [
|
|
1042
|
-
/* @__PURE__ */
|
|
1043
|
-
/* @__PURE__ */
|
|
1066
|
+
/* @__PURE__ */ jsx15("p", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "On this page" }),
|
|
1067
|
+
/* @__PURE__ */ jsx15("ul", { className: "space-y-1 text-sm", children: toc.map((item) => /* @__PURE__ */ jsx15("li", { style: { paddingLeft: `${Math.max(0, item.depth - 2) * 1}rem` }, children: /* @__PURE__ */ jsx15(
|
|
1044
1068
|
"a",
|
|
1045
1069
|
{
|
|
1046
1070
|
href: `#${item.id}`,
|
|
@@ -1056,7 +1080,7 @@ function ArticleTOC({ toc, className }) {
|
|
|
1056
1080
|
// src/ScrollToTop.tsx
|
|
1057
1081
|
import { useEffect as useEffect4, useState as useState5 } from "react";
|
|
1058
1082
|
import { ArrowUp } from "lucide-react";
|
|
1059
|
-
import { jsx as
|
|
1083
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
1060
1084
|
function ScrollToTop() {
|
|
1061
1085
|
const [visible, setVisible] = useState5(false);
|
|
1062
1086
|
useEffect4(() => {
|
|
@@ -1065,13 +1089,13 @@ function ScrollToTop() {
|
|
|
1065
1089
|
return () => globalThis.removeEventListener("scroll", onScroll);
|
|
1066
1090
|
}, []);
|
|
1067
1091
|
if (!visible) return null;
|
|
1068
|
-
return /* @__PURE__ */
|
|
1092
|
+
return /* @__PURE__ */ jsx16(
|
|
1069
1093
|
"button",
|
|
1070
1094
|
{
|
|
1071
1095
|
"aria-label": "Scroll to top",
|
|
1072
1096
|
onClick: () => globalThis.scrollTo({ top: 0, behavior: "smooth" }),
|
|
1073
1097
|
className: "fixed bottom-8 right-8 z-50 rounded-full bg-primary p-2 shadow-md text-primary-foreground hover:bg-primary/90 transition-colors",
|
|
1074
|
-
children: /* @__PURE__ */
|
|
1098
|
+
children: /* @__PURE__ */ jsx16(ArrowUp, { className: "h-5 w-5" })
|
|
1075
1099
|
}
|
|
1076
1100
|
);
|
|
1077
1101
|
}
|
|
@@ -1082,7 +1106,7 @@ import { useCallback as useCallback2, useEffect as useEffect5, useState as useSt
|
|
|
1082
1106
|
|
|
1083
1107
|
// src/CommentForm.tsx
|
|
1084
1108
|
import { useState as useState6 } from "react";
|
|
1085
|
-
import { jsx as
|
|
1109
|
+
import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1086
1110
|
var MAX_LENGTH = 2e3;
|
|
1087
1111
|
var WARN_THRESHOLD = 1800;
|
|
1088
1112
|
function submitLabel(submitting, parentId) {
|
|
@@ -1121,8 +1145,8 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
|
1121
1145
|
}
|
|
1122
1146
|
});
|
|
1123
1147
|
}
|
|
1124
|
-
return /* @__PURE__ */
|
|
1125
|
-
/* @__PURE__ */
|
|
1148
|
+
return /* @__PURE__ */ jsxs16("form", { onSubmit: handleSubmit, className: "space-y-2", children: [
|
|
1149
|
+
/* @__PURE__ */ jsx17(
|
|
1126
1150
|
"textarea",
|
|
1127
1151
|
{
|
|
1128
1152
|
value: body,
|
|
@@ -1135,13 +1159,13 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
|
1135
1159
|
"aria-label": parentId ? "Reply text" : "Comment text"
|
|
1136
1160
|
}
|
|
1137
1161
|
),
|
|
1138
|
-
remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */
|
|
1162
|
+
remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */ jsxs16("p", { className: `text-xs ${remaining < 0 ? "text-destructive" : "text-muted-foreground"}`, children: [
|
|
1139
1163
|
remaining,
|
|
1140
1164
|
" characters remaining"
|
|
1141
1165
|
] }),
|
|
1142
|
-
error && /* @__PURE__ */
|
|
1143
|
-
/* @__PURE__ */
|
|
1144
|
-
/* @__PURE__ */
|
|
1166
|
+
error && /* @__PURE__ */ jsx17("p", { className: "text-xs text-destructive", children: error }),
|
|
1167
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex gap-2", children: [
|
|
1168
|
+
/* @__PURE__ */ jsx17(
|
|
1145
1169
|
"button",
|
|
1146
1170
|
{
|
|
1147
1171
|
type: "submit",
|
|
@@ -1150,7 +1174,7 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
|
1150
1174
|
children: submitLabel(submitting, parentId)
|
|
1151
1175
|
}
|
|
1152
1176
|
),
|
|
1153
|
-
onCancel && /* @__PURE__ */
|
|
1177
|
+
onCancel && /* @__PURE__ */ jsx17(
|
|
1154
1178
|
"button",
|
|
1155
1179
|
{
|
|
1156
1180
|
type: "button",
|
|
@@ -1165,7 +1189,7 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
|
1165
1189
|
|
|
1166
1190
|
// src/CommentItem.tsx
|
|
1167
1191
|
import { useState as useState7 } from "react";
|
|
1168
|
-
import { Fragment as Fragment2, jsx as
|
|
1192
|
+
import { Fragment as Fragment2, jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1169
1193
|
function relativeTime(iso) {
|
|
1170
1194
|
const diff = Date.now() - new Date(iso).getTime();
|
|
1171
1195
|
const minutes = Math.floor(diff / 6e4);
|
|
@@ -1203,15 +1227,15 @@ function CommentItem({
|
|
|
1203
1227
|
}
|
|
1204
1228
|
});
|
|
1205
1229
|
}
|
|
1206
|
-
return /* @__PURE__ */
|
|
1207
|
-
/* @__PURE__ */
|
|
1208
|
-
/* @__PURE__ */
|
|
1209
|
-
/* @__PURE__ */
|
|
1210
|
-
/* @__PURE__ */
|
|
1230
|
+
return /* @__PURE__ */ jsxs17("div", { className: `${isReply ? "ml-8 border-l-2 border-border pl-4" : ""}`, children: [
|
|
1231
|
+
/* @__PURE__ */ jsx18("div", { className: "rounded-lg bg-muted/40 p-3 space-y-1", children: comment.isDeleted ? /* @__PURE__ */ jsx18("p", { className: "text-sm italic text-muted-foreground", children: "Comment removed" }) : /* @__PURE__ */ jsxs17(Fragment2, { children: [
|
|
1232
|
+
/* @__PURE__ */ jsxs17("div", { className: "flex items-center justify-between gap-2", children: [
|
|
1233
|
+
/* @__PURE__ */ jsx18("span", { className: "text-sm font-medium text-foreground", children: comment.authorName }),
|
|
1234
|
+
/* @__PURE__ */ jsx18("span", { className: "text-xs text-muted-foreground", children: relativeTime(comment.createdAt) })
|
|
1211
1235
|
] }),
|
|
1212
|
-
/* @__PURE__ */
|
|
1213
|
-
/* @__PURE__ */
|
|
1214
|
-
canReply && /* @__PURE__ */
|
|
1236
|
+
/* @__PURE__ */ jsx18("p", { className: "text-sm text-foreground whitespace-pre-wrap break-words", children: comment.body }),
|
|
1237
|
+
/* @__PURE__ */ jsxs17("div", { className: "flex gap-3 pt-1", children: [
|
|
1238
|
+
canReply && /* @__PURE__ */ jsx18(
|
|
1215
1239
|
"button",
|
|
1216
1240
|
{
|
|
1217
1241
|
onClick: () => setShowReplyForm((v) => !v),
|
|
@@ -1219,7 +1243,7 @@ function CommentItem({
|
|
|
1219
1243
|
children: showReplyForm ? "Cancel" : "Reply"
|
|
1220
1244
|
}
|
|
1221
1245
|
),
|
|
1222
|
-
canDelete && /* @__PURE__ */
|
|
1246
|
+
canDelete && /* @__PURE__ */ jsx18(
|
|
1223
1247
|
"button",
|
|
1224
1248
|
{
|
|
1225
1249
|
onClick: handleDelete,
|
|
@@ -1230,7 +1254,7 @@ function CommentItem({
|
|
|
1230
1254
|
)
|
|
1231
1255
|
] })
|
|
1232
1256
|
] }) }),
|
|
1233
|
-
showReplyForm && /* @__PURE__ */
|
|
1257
|
+
showReplyForm && /* @__PURE__ */ jsx18("div", { className: "mt-2 ml-2", children: /* @__PURE__ */ jsx18(
|
|
1234
1258
|
CommentForm,
|
|
1235
1259
|
{
|
|
1236
1260
|
articleSlug,
|
|
@@ -1242,7 +1266,7 @@ function CommentItem({
|
|
|
1242
1266
|
onCancel: () => setShowReplyForm(false)
|
|
1243
1267
|
}
|
|
1244
1268
|
) }),
|
|
1245
|
-
replies.length > 0 && /* @__PURE__ */
|
|
1269
|
+
replies.length > 0 && /* @__PURE__ */ jsx18("div", { className: "mt-2 space-y-2", children: replies.map((reply) => /* @__PURE__ */ jsx18(
|
|
1246
1270
|
CommentItem,
|
|
1247
1271
|
{
|
|
1248
1272
|
comment: __spreadProps(__spreadValues({}, reply), { replies: [] }),
|
|
@@ -1257,7 +1281,7 @@ function CommentItem({
|
|
|
1257
1281
|
}
|
|
1258
1282
|
|
|
1259
1283
|
// src/CommentThread.tsx
|
|
1260
|
-
import { jsx as
|
|
1284
|
+
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
1261
1285
|
function CommentThread({
|
|
1262
1286
|
comments,
|
|
1263
1287
|
articleSlug,
|
|
@@ -1265,9 +1289,9 @@ function CommentThread({
|
|
|
1265
1289
|
onRefresh
|
|
1266
1290
|
}) {
|
|
1267
1291
|
if (comments.length === 0) {
|
|
1268
|
-
return /* @__PURE__ */
|
|
1292
|
+
return /* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground text-center py-6", children: "No comments yet. Be the first to share your thoughts." });
|
|
1269
1293
|
}
|
|
1270
|
-
return /* @__PURE__ */
|
|
1294
|
+
return /* @__PURE__ */ jsx19("div", { className: "space-y-4", children: comments.map((comment) => /* @__PURE__ */ jsx19(
|
|
1271
1295
|
CommentItem,
|
|
1272
1296
|
{
|
|
1273
1297
|
comment,
|
|
@@ -1280,7 +1304,7 @@ function CommentThread({
|
|
|
1280
1304
|
}
|
|
1281
1305
|
|
|
1282
1306
|
// src/CommentsSection.tsx
|
|
1283
|
-
import { jsx as
|
|
1307
|
+
import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1284
1308
|
function CommentsSection({ articleSlug, config }) {
|
|
1285
1309
|
var _a, _b, _c, _d, _e, _f;
|
|
1286
1310
|
const { data: session, status } = useSession();
|
|
@@ -1312,10 +1336,10 @@ function CommentsSection({ articleSlug, config }) {
|
|
|
1312
1336
|
if (!enabled) return null;
|
|
1313
1337
|
const count = comments.length;
|
|
1314
1338
|
const label = count === 1 ? "1 comment" : `${count} comments`;
|
|
1315
|
-
return /* @__PURE__ */
|
|
1316
|
-
/* @__PURE__ */
|
|
1317
|
-
status === "authenticated" ? /* @__PURE__ */
|
|
1318
|
-
/* @__PURE__ */
|
|
1339
|
+
return /* @__PURE__ */ jsxs18("section", { "aria-label": "Comments", className: "mt-12 pt-8 border-t border-border space-y-6", children: [
|
|
1340
|
+
/* @__PURE__ */ jsx20("h2", { className: "text-xl font-semibold text-foreground", children: loading ? "Comments" : label }),
|
|
1341
|
+
status === "authenticated" ? /* @__PURE__ */ jsx20(CommentForm, { articleSlug, onSuccess: fetchComments }) : /* @__PURE__ */ jsxs18("p", { className: "text-sm text-muted-foreground", children: [
|
|
1342
|
+
/* @__PURE__ */ jsx20(
|
|
1319
1343
|
"button",
|
|
1320
1344
|
{
|
|
1321
1345
|
onClick: () => signIn(),
|
|
@@ -1326,8 +1350,8 @@ function CommentsSection({ articleSlug, config }) {
|
|
|
1326
1350
|
" ",
|
|
1327
1351
|
"to join the discussion."
|
|
1328
1352
|
] }),
|
|
1329
|
-
fetchError && /* @__PURE__ */
|
|
1330
|
-
loading ? /* @__PURE__ */
|
|
1353
|
+
fetchError && /* @__PURE__ */ jsx20("p", { className: "text-sm text-destructive", children: fetchError }),
|
|
1354
|
+
loading ? /* @__PURE__ */ jsx20("p", { className: "text-sm text-muted-foreground", children: "Loading comments\u2026" }) : /* @__PURE__ */ jsx20(
|
|
1331
1355
|
CommentThread,
|
|
1332
1356
|
{
|
|
1333
1357
|
comments,
|
|
@@ -1339,6 +1363,7 @@ function CommentsSection({ articleSlug, config }) {
|
|
|
1339
1363
|
] });
|
|
1340
1364
|
}
|
|
1341
1365
|
export {
|
|
1366
|
+
ArticleBackLink,
|
|
1342
1367
|
ArticleCard,
|
|
1343
1368
|
ArticleCategoryGrid,
|
|
1344
1369
|
ArticleDetailHero,
|