@fullstackdatasolutions/articles 0.6.0 → 0.7.1
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 +71 -12
- package/dist/index.cjs +68 -51
- 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 +66 -50
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +70 -18
- 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 +69 -17
- package/dist/server.js.map +1 -1
- package/package.json +34 -20
- package/src/ArticleBackLink.tsx +32 -0
- package/src/ArticleContent.tsx +4 -7
- package/src/ArticleSocialShare.tsx +2 -7
- package/src/__tests__/ArticleBackLink.test.tsx +89 -0
- package/src/__tests__/ArticleContent.test.tsx +72 -28
- package/src/__tests__/ArticleSocialShare.test.tsx +7 -12
- 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 +310 -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 +1 -2
package/dist/index.js
CHANGED
|
@@ -966,19 +966,10 @@ function ArticleSocialShare({ title, url, excerpt, shareMessage }) {
|
|
|
966
966
|
/* @__PURE__ */ jsx12(MessageCircle, { className: "h-4 w-4" }),
|
|
967
967
|
"Telegram"
|
|
968
968
|
] }),
|
|
969
|
-
/* @__PURE__ */ jsxs12(
|
|
970
|
-
"
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
onClick: () => {
|
|
974
|
-
globalThis.location.href = shareLinks.email;
|
|
975
|
-
},
|
|
976
|
-
children: [
|
|
977
|
-
/* @__PURE__ */ jsx12(Mail, { className: "h-4 w-4" }),
|
|
978
|
-
"Email"
|
|
979
|
-
]
|
|
980
|
-
}
|
|
981
|
-
),
|
|
969
|
+
/* @__PURE__ */ jsxs12("a", { className: btnClass, href: shareLinks.email, children: [
|
|
970
|
+
/* @__PURE__ */ jsx12(Mail, { className: "h-4 w-4" }),
|
|
971
|
+
"Email"
|
|
972
|
+
] }),
|
|
982
973
|
/* @__PURE__ */ jsxs12("button", { className: btnClass, onClick: copyToClipboard, children: [
|
|
983
974
|
copied ? /* @__PURE__ */ jsx12(Check, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx12(Copy, { className: "h-4 w-4" }),
|
|
984
975
|
copied ? "Copied!" : "Copy Link"
|
|
@@ -1029,18 +1020,42 @@ function ArticleNavigation({ previous, next, basePath }) {
|
|
|
1029
1020
|
] }) });
|
|
1030
1021
|
}
|
|
1031
1022
|
|
|
1032
|
-
// src/
|
|
1023
|
+
// src/ArticleBackLink.tsx
|
|
1024
|
+
import Link8 from "next/link";
|
|
1025
|
+
import { ArrowLeft } from "lucide-react";
|
|
1033
1026
|
import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1027
|
+
function ArticleBackLink({
|
|
1028
|
+
config,
|
|
1029
|
+
href = "/articles",
|
|
1030
|
+
label = "Back to Articles",
|
|
1031
|
+
className
|
|
1032
|
+
}) {
|
|
1033
|
+
if (config.showBackToArticles === false) return null;
|
|
1034
|
+
return /* @__PURE__ */ jsxs14(
|
|
1035
|
+
Link8,
|
|
1036
|
+
{
|
|
1037
|
+
href,
|
|
1038
|
+
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",
|
|
1039
|
+
children: [
|
|
1040
|
+
/* @__PURE__ */ jsx14(ArrowLeft, { className: "h-4 w-4" }),
|
|
1041
|
+
label
|
|
1042
|
+
]
|
|
1043
|
+
}
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// src/ArticleTOC.tsx
|
|
1048
|
+
import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1034
1049
|
function ArticleTOC({ toc, className }) {
|
|
1035
1050
|
if (!toc.length) return null;
|
|
1036
|
-
return /* @__PURE__ */
|
|
1051
|
+
return /* @__PURE__ */ jsxs15(
|
|
1037
1052
|
"nav",
|
|
1038
1053
|
{
|
|
1039
1054
|
"aria-label": "Table of contents",
|
|
1040
1055
|
className: `mb-8 rounded-lg border border-border bg-muted/40 px-6 py-4 ${className != null ? className : ""}`,
|
|
1041
1056
|
children: [
|
|
1042
|
-
/* @__PURE__ */
|
|
1043
|
-
/* @__PURE__ */
|
|
1057
|
+
/* @__PURE__ */ jsx15("p", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "On this page" }),
|
|
1058
|
+
/* @__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
1059
|
"a",
|
|
1045
1060
|
{
|
|
1046
1061
|
href: `#${item.id}`,
|
|
@@ -1056,7 +1071,7 @@ function ArticleTOC({ toc, className }) {
|
|
|
1056
1071
|
// src/ScrollToTop.tsx
|
|
1057
1072
|
import { useEffect as useEffect4, useState as useState5 } from "react";
|
|
1058
1073
|
import { ArrowUp } from "lucide-react";
|
|
1059
|
-
import { jsx as
|
|
1074
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
1060
1075
|
function ScrollToTop() {
|
|
1061
1076
|
const [visible, setVisible] = useState5(false);
|
|
1062
1077
|
useEffect4(() => {
|
|
@@ -1065,13 +1080,13 @@ function ScrollToTop() {
|
|
|
1065
1080
|
return () => globalThis.removeEventListener("scroll", onScroll);
|
|
1066
1081
|
}, []);
|
|
1067
1082
|
if (!visible) return null;
|
|
1068
|
-
return /* @__PURE__ */
|
|
1083
|
+
return /* @__PURE__ */ jsx16(
|
|
1069
1084
|
"button",
|
|
1070
1085
|
{
|
|
1071
1086
|
"aria-label": "Scroll to top",
|
|
1072
1087
|
onClick: () => globalThis.scrollTo({ top: 0, behavior: "smooth" }),
|
|
1073
1088
|
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__ */
|
|
1089
|
+
children: /* @__PURE__ */ jsx16(ArrowUp, { className: "h-5 w-5" })
|
|
1075
1090
|
}
|
|
1076
1091
|
);
|
|
1077
1092
|
}
|
|
@@ -1082,7 +1097,7 @@ import { useCallback as useCallback2, useEffect as useEffect5, useState as useSt
|
|
|
1082
1097
|
|
|
1083
1098
|
// src/CommentForm.tsx
|
|
1084
1099
|
import { useState as useState6 } from "react";
|
|
1085
|
-
import { jsx as
|
|
1100
|
+
import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1086
1101
|
var MAX_LENGTH = 2e3;
|
|
1087
1102
|
var WARN_THRESHOLD = 1800;
|
|
1088
1103
|
function submitLabel(submitting, parentId) {
|
|
@@ -1121,8 +1136,8 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
|
1121
1136
|
}
|
|
1122
1137
|
});
|
|
1123
1138
|
}
|
|
1124
|
-
return /* @__PURE__ */
|
|
1125
|
-
/* @__PURE__ */
|
|
1139
|
+
return /* @__PURE__ */ jsxs16("form", { onSubmit: handleSubmit, className: "space-y-2", children: [
|
|
1140
|
+
/* @__PURE__ */ jsx17(
|
|
1126
1141
|
"textarea",
|
|
1127
1142
|
{
|
|
1128
1143
|
value: body,
|
|
@@ -1135,13 +1150,13 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
|
1135
1150
|
"aria-label": parentId ? "Reply text" : "Comment text"
|
|
1136
1151
|
}
|
|
1137
1152
|
),
|
|
1138
|
-
remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */
|
|
1153
|
+
remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */ jsxs16("p", { className: `text-xs ${remaining < 0 ? "text-destructive" : "text-muted-foreground"}`, children: [
|
|
1139
1154
|
remaining,
|
|
1140
1155
|
" characters remaining"
|
|
1141
1156
|
] }),
|
|
1142
|
-
error && /* @__PURE__ */
|
|
1143
|
-
/* @__PURE__ */
|
|
1144
|
-
/* @__PURE__ */
|
|
1157
|
+
error && /* @__PURE__ */ jsx17("p", { className: "text-xs text-destructive", children: error }),
|
|
1158
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex gap-2", children: [
|
|
1159
|
+
/* @__PURE__ */ jsx17(
|
|
1145
1160
|
"button",
|
|
1146
1161
|
{
|
|
1147
1162
|
type: "submit",
|
|
@@ -1150,7 +1165,7 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
|
1150
1165
|
children: submitLabel(submitting, parentId)
|
|
1151
1166
|
}
|
|
1152
1167
|
),
|
|
1153
|
-
onCancel && /* @__PURE__ */
|
|
1168
|
+
onCancel && /* @__PURE__ */ jsx17(
|
|
1154
1169
|
"button",
|
|
1155
1170
|
{
|
|
1156
1171
|
type: "button",
|
|
@@ -1165,7 +1180,7 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
|
1165
1180
|
|
|
1166
1181
|
// src/CommentItem.tsx
|
|
1167
1182
|
import { useState as useState7 } from "react";
|
|
1168
|
-
import { Fragment as Fragment2, jsx as
|
|
1183
|
+
import { Fragment as Fragment2, jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1169
1184
|
function relativeTime(iso) {
|
|
1170
1185
|
const diff = Date.now() - new Date(iso).getTime();
|
|
1171
1186
|
const minutes = Math.floor(diff / 6e4);
|
|
@@ -1203,15 +1218,15 @@ function CommentItem({
|
|
|
1203
1218
|
}
|
|
1204
1219
|
});
|
|
1205
1220
|
}
|
|
1206
|
-
return /* @__PURE__ */
|
|
1207
|
-
/* @__PURE__ */
|
|
1208
|
-
/* @__PURE__ */
|
|
1209
|
-
/* @__PURE__ */
|
|
1210
|
-
/* @__PURE__ */
|
|
1221
|
+
return /* @__PURE__ */ jsxs17("div", { className: `${isReply ? "ml-8 border-l-2 border-border pl-4" : ""}`, children: [
|
|
1222
|
+
/* @__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: [
|
|
1223
|
+
/* @__PURE__ */ jsxs17("div", { className: "flex items-center justify-between gap-2", children: [
|
|
1224
|
+
/* @__PURE__ */ jsx18("span", { className: "text-sm font-medium text-foreground", children: comment.authorName }),
|
|
1225
|
+
/* @__PURE__ */ jsx18("span", { className: "text-xs text-muted-foreground", children: relativeTime(comment.createdAt) })
|
|
1211
1226
|
] }),
|
|
1212
|
-
/* @__PURE__ */
|
|
1213
|
-
/* @__PURE__ */
|
|
1214
|
-
canReply && /* @__PURE__ */
|
|
1227
|
+
/* @__PURE__ */ jsx18("p", { className: "text-sm text-foreground whitespace-pre-wrap break-words", children: comment.body }),
|
|
1228
|
+
/* @__PURE__ */ jsxs17("div", { className: "flex gap-3 pt-1", children: [
|
|
1229
|
+
canReply && /* @__PURE__ */ jsx18(
|
|
1215
1230
|
"button",
|
|
1216
1231
|
{
|
|
1217
1232
|
onClick: () => setShowReplyForm((v) => !v),
|
|
@@ -1219,7 +1234,7 @@ function CommentItem({
|
|
|
1219
1234
|
children: showReplyForm ? "Cancel" : "Reply"
|
|
1220
1235
|
}
|
|
1221
1236
|
),
|
|
1222
|
-
canDelete && /* @__PURE__ */
|
|
1237
|
+
canDelete && /* @__PURE__ */ jsx18(
|
|
1223
1238
|
"button",
|
|
1224
1239
|
{
|
|
1225
1240
|
onClick: handleDelete,
|
|
@@ -1230,7 +1245,7 @@ function CommentItem({
|
|
|
1230
1245
|
)
|
|
1231
1246
|
] })
|
|
1232
1247
|
] }) }),
|
|
1233
|
-
showReplyForm && /* @__PURE__ */
|
|
1248
|
+
showReplyForm && /* @__PURE__ */ jsx18("div", { className: "mt-2 ml-2", children: /* @__PURE__ */ jsx18(
|
|
1234
1249
|
CommentForm,
|
|
1235
1250
|
{
|
|
1236
1251
|
articleSlug,
|
|
@@ -1242,7 +1257,7 @@ function CommentItem({
|
|
|
1242
1257
|
onCancel: () => setShowReplyForm(false)
|
|
1243
1258
|
}
|
|
1244
1259
|
) }),
|
|
1245
|
-
replies.length > 0 && /* @__PURE__ */
|
|
1260
|
+
replies.length > 0 && /* @__PURE__ */ jsx18("div", { className: "mt-2 space-y-2", children: replies.map((reply) => /* @__PURE__ */ jsx18(
|
|
1246
1261
|
CommentItem,
|
|
1247
1262
|
{
|
|
1248
1263
|
comment: __spreadProps(__spreadValues({}, reply), { replies: [] }),
|
|
@@ -1257,7 +1272,7 @@ function CommentItem({
|
|
|
1257
1272
|
}
|
|
1258
1273
|
|
|
1259
1274
|
// src/CommentThread.tsx
|
|
1260
|
-
import { jsx as
|
|
1275
|
+
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
1261
1276
|
function CommentThread({
|
|
1262
1277
|
comments,
|
|
1263
1278
|
articleSlug,
|
|
@@ -1265,9 +1280,9 @@ function CommentThread({
|
|
|
1265
1280
|
onRefresh
|
|
1266
1281
|
}) {
|
|
1267
1282
|
if (comments.length === 0) {
|
|
1268
|
-
return /* @__PURE__ */
|
|
1283
|
+
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
1284
|
}
|
|
1270
|
-
return /* @__PURE__ */
|
|
1285
|
+
return /* @__PURE__ */ jsx19("div", { className: "space-y-4", children: comments.map((comment) => /* @__PURE__ */ jsx19(
|
|
1271
1286
|
CommentItem,
|
|
1272
1287
|
{
|
|
1273
1288
|
comment,
|
|
@@ -1280,7 +1295,7 @@ function CommentThread({
|
|
|
1280
1295
|
}
|
|
1281
1296
|
|
|
1282
1297
|
// src/CommentsSection.tsx
|
|
1283
|
-
import { jsx as
|
|
1298
|
+
import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1284
1299
|
function CommentsSection({ articleSlug, config }) {
|
|
1285
1300
|
var _a, _b, _c, _d, _e, _f;
|
|
1286
1301
|
const { data: session, status } = useSession();
|
|
@@ -1312,10 +1327,10 @@ function CommentsSection({ articleSlug, config }) {
|
|
|
1312
1327
|
if (!enabled) return null;
|
|
1313
1328
|
const count = comments.length;
|
|
1314
1329
|
const label = count === 1 ? "1 comment" : `${count} comments`;
|
|
1315
|
-
return /* @__PURE__ */
|
|
1316
|
-
/* @__PURE__ */
|
|
1317
|
-
status === "authenticated" ? /* @__PURE__ */
|
|
1318
|
-
/* @__PURE__ */
|
|
1330
|
+
return /* @__PURE__ */ jsxs18("section", { "aria-label": "Comments", className: "mt-12 pt-8 border-t border-border space-y-6", children: [
|
|
1331
|
+
/* @__PURE__ */ jsx20("h2", { className: "text-xl font-semibold text-foreground", children: loading ? "Comments" : label }),
|
|
1332
|
+
status === "authenticated" ? /* @__PURE__ */ jsx20(CommentForm, { articleSlug, onSuccess: fetchComments }) : /* @__PURE__ */ jsxs18("p", { className: "text-sm text-muted-foreground", children: [
|
|
1333
|
+
/* @__PURE__ */ jsx20(
|
|
1319
1334
|
"button",
|
|
1320
1335
|
{
|
|
1321
1336
|
onClick: () => signIn(),
|
|
@@ -1326,8 +1341,8 @@ function CommentsSection({ articleSlug, config }) {
|
|
|
1326
1341
|
" ",
|
|
1327
1342
|
"to join the discussion."
|
|
1328
1343
|
] }),
|
|
1329
|
-
fetchError && /* @__PURE__ */
|
|
1330
|
-
loading ? /* @__PURE__ */
|
|
1344
|
+
fetchError && /* @__PURE__ */ jsx20("p", { className: "text-sm text-destructive", children: fetchError }),
|
|
1345
|
+
loading ? /* @__PURE__ */ jsx20("p", { className: "text-sm text-muted-foreground", children: "Loading comments\u2026" }) : /* @__PURE__ */ jsx20(
|
|
1331
1346
|
CommentThread,
|
|
1332
1347
|
{
|
|
1333
1348
|
comments,
|
|
@@ -1339,6 +1354,7 @@ function CommentsSection({ articleSlug, config }) {
|
|
|
1339
1354
|
] });
|
|
1340
1355
|
}
|
|
1341
1356
|
export {
|
|
1357
|
+
ArticleBackLink,
|
|
1342
1358
|
ArticleCard,
|
|
1343
1359
|
ArticleCategoryGrid,
|
|
1344
1360
|
ArticleDetailHero,
|