diff --git a/.gitea/workflows/demo.yml b/.gitea/workflows/demo.yml index 5c06128..2747990 100644 --- a/.gitea/workflows/demo.yml +++ b/.gitea/workflows/demo.yml @@ -7,13 +7,4 @@ jobs: runs-on: linux steps: - run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event." - - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!" - - run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}." - - name: Check out repository code - uses: actions/checkout@v4 - - run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner." - - run: echo "🖥️ The workflow is now ready to test your code on the runner." - - name: List files in the repository - run: | - ls ${{ gitea.workspace }} - - run: echo "🍏 This job's status is ${{ job.status }}." + - run: Test "🎉 job completed! 🚀" diff --git a/package.json b/package.json index 0951d98..82568ef 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "superjson": "^2.2.1", "tailwind-merge": "^3.0.2", "tailwindcss-animate": "^1.0.7", + "tiptap-extension-resize-image": "^1.2.1", "use-debounce": "^10.0.4", "winston": "^3.17.0", "zod": "^3.24.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09bad78..f6077e4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -149,6 +149,9 @@ importers: tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@20.17.23)(typescript@5.8.2))) + tiptap-extension-resize-image: + specifier: ^1.2.1 + version: 1.2.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/extension-image@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5)))(@tiptap/pm@2.11.5) use-debounce: specifier: ^10.0.4 version: 10.0.4(react@18.3.1) @@ -4095,6 +4098,13 @@ packages: tiptap-extension-global-drag-handle@0.1.18: resolution: {integrity: sha512-jwFuy1K8DP3a4bFy76Hpc63w1Sil0B7uZ3mvhQomVvUFCU787Lg2FowNhn7NFzeyok761qY2VG+PZ/FDthWUdg==} + tiptap-extension-resize-image@1.2.1: + resolution: {integrity: sha512-SLMAujDa+0LN/6Iv2HtU4Uk0BL6LMh4b/r85frpdnjFDW2i6pIOfTVG8jzJQ8T1EgYHNn2YG1U2HoVAGuwLc3Q==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/extension-image': ^2.0.0 + '@tiptap/pm': ^2.0.0 + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -8492,6 +8502,12 @@ snapshots: tiptap-extension-global-drag-handle@0.1.18: {} + tiptap-extension-resize-image@1.2.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/extension-image@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5)))(@tiptap/pm@2.11.5): + dependencies: + '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5) + '@tiptap/extension-image': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5)) + '@tiptap/pm': 2.11.5 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 diff --git a/public/uploads/upload-1742077219929-person-3.jpg.png b/public/uploads/upload-1742077219929-person-3.jpg.png new file mode 100644 index 0000000..4129a0f Binary files /dev/null and b/public/uploads/upload-1742077219929-person-3.jpg.png differ diff --git a/public/uploads/upload-1742077526129-pexels-italo-melo-881954-2379005.jpg.png b/public/uploads/upload-1742077526129-pexels-italo-melo-881954-2379005.jpg.png new file mode 100644 index 0000000..029a58d Binary files /dev/null and b/public/uploads/upload-1742077526129-pexels-italo-melo-881954-2379005.jpg.png differ diff --git a/src/app/(PAGES)/artikel/[slug]/page.tsx b/src/app/(PAGES)/artikel/[slug]/page.tsx index ec240cb..1761a6f 100644 --- a/src/app/(PAGES)/artikel/[slug]/page.tsx +++ b/src/app/(PAGES)/artikel/[slug]/page.tsx @@ -1,5 +1,6 @@ -import RenderArticle from "@/components/article/render-article"; import BreadNavigator from "@/components/bread-navigator"; +import RenderContent from "@/components/editor/render-content"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { appRoutes } from "@/config"; import { hasPermission, Role } from "@/lib/validation/permissions"; @@ -21,8 +22,9 @@ async function Page({ params }: { params: Promise<{ slug: string }> }) { return (
-
+
}) { />
{isEditor && ( -
+
+ + {article.published ? "Veröffentlicht" : "Draft"} + +

{article.title}

- - {article?.content?.length ? ( - - ) : null} + {article.content && }
); } diff --git a/src/components/article/article-card.tsx b/src/components/article/article-card.tsx index 785194c..5be70af 100644 --- a/src/components/article/article-card.tsx +++ b/src/components/article/article-card.tsx @@ -13,14 +13,17 @@ import { } from "@/components/ui/card"; import Avatar from "../avatar"; import { Icons } from "../icons"; +import { Badge } from "../ui/badge"; function ArticleCard({ title, slug, author, + published, createdAt, -}: Pick) { +}: Pick) { const authorName = author?.name ?? `${appConfig.name} Team`; + return ( @@ -37,11 +40,21 @@ function ArticleCard({ )} {authorName}
-

- {createdAt.toLocaleDateString("de-DE", { - dateStyle: "long", - })} -

+
+ {typeof published === "boolean" && ( + + {published ? "Veröffentlicht" : "Draft"} + + )} +

+ {createdAt.toLocaleDateString("de-DE", { + dateStyle: "long", + })} +

+
diff --git a/src/components/article/article-filter-bar.tsx b/src/components/article/article-filter-bar.tsx index cc3bea4..0b255fe 100644 --- a/src/components/article/article-filter-bar.tsx +++ b/src/components/article/article-filter-bar.tsx @@ -85,6 +85,7 @@ function ArticleFilterBar({ { onFilterChange({ category: category?.length ? category : undefined, diff --git a/src/components/article/article-form.tsx b/src/components/article/article-form.tsx index 79c4bd7..ced26fc 100644 --- a/src/components/article/article-form.tsx +++ b/src/components/article/article-form.tsx @@ -26,6 +26,7 @@ import { CheckCircle, XCircle } from "lucide-react"; import PublishArticleAlertDialog from "./publish-article-alert-dialog"; import { Label } from "@/components/ui/label"; import dynamic from "next/dynamic"; +import { appRoutes } from "@/config"; const Editor = dynamic(() => import("../editor"), { ssr: false }); export default ({ server_article }: { server_article: Article }) => { @@ -78,7 +79,7 @@ export default ({ server_article }: { server_article: Article }) => { { field.onChange(e); @@ -117,7 +118,7 @@ export default ({ server_article }: { server_article: Article }) => { // loading && "border-t-blue-600", )} > -
+
{
- ? Hilfe + Ansehen
{ { field.onChange(categoryId); diff --git a/src/components/article/grid/infinite-article-grid.tsx b/src/components/article/grid/infinite-article-grid.tsx index 7487142..cfd1276 100644 --- a/src/components/article/grid/infinite-article-grid.tsx +++ b/src/components/article/grid/infinite-article-grid.tsx @@ -7,6 +7,7 @@ import ArticleCard from "../article-card"; import { useInfiniteItemsObserver } from "@/lib/hooks/infinite-items-observer-hook"; import { Skeleton } from "@/components/ui/skeleton"; import ArticleFilterBar, { ArticleFilter } from "../article-filter-bar"; +import { Article } from "@/server/db/schema"; function InfiniteArticlesGrid() { const [filter, setFilter] = React.useState( @@ -19,7 +20,7 @@ function InfiniteArticlesGrid() { }, { getNextPageParam: (lastPage) => lastPage.nextCursor, - staleTime: 60 * 4 * 1000, // 4 minutes stale time + // staleTime: 60 * 4 * 1000, // 4 minutes stale time refetchOnMount: false, // Prevents unnecessary refetching refetchOnWindowFocus: false, // Avoids refetch when switching tabs }, @@ -46,7 +47,7 @@ function InfiniteArticlesGrid() { {data?.pages?.length ? allItems.map((article, idx) => (
  • - +
  • )) : null} diff --git a/src/components/article/render-article.tsx b/src/components/article/render-article.tsx deleted file mode 100644 index df57631..0000000 --- a/src/components/article/render-article.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { JSONContent } from "novel"; - -function RenderArticle({ content }: { content: JSONContent }) { - return "render article: in work"; //; -} - -export default RenderArticle; diff --git a/src/components/category/category-select.tsx b/src/components/category/category-select.tsx index d8e138b..ffd03d7 100644 --- a/src/components/category/category-select.tsx +++ b/src/components/category/category-select.tsx @@ -16,7 +16,8 @@ function CategorySelect(props: Partial) { empty: "Keine Kategorien gefunden", }} data={ - categories?.map(({ name, id }) => ({ label: name!, value: id! })) ?? [] + categories?.map(({ name, slug }) => ({ label: name, value: slug })) ?? + [] } /> ); diff --git a/src/components/combobox.tsx b/src/components/combobox.tsx index d995fbd..c149d6f 100644 --- a/src/components/combobox.tsx +++ b/src/components/combobox.tsx @@ -50,6 +50,7 @@ export function Combobox({ const [open, setOpen] = React.useState(false); const [value, setValue] = React.useState(initialValue ?? ""); const selectedItem = data.find((item) => item.value === initialValue)!; + return ( diff --git a/src/components/editor/extentions/index.tsx b/src/components/editor/extentions/index.tsx index 77f04cf..fbbc69c 100644 --- a/src/components/editor/extentions/index.tsx +++ b/src/components/editor/extentions/index.tsx @@ -18,15 +18,15 @@ import { Youtube, } from "novel"; import LinkPreview from "./link-preview"; -import { Heading } from "@tiptap/extension-heading"; import { cx } from "class-variance-authority"; import { slashCommand } from "./slash-commands"; +import ImageResize from "tiptap-extension-resize-image"; const placeholder = Placeholder; const tiptapLink = TiptapLink.configure({ HTMLAttributes: { class: cx( - "text-muted-foreground underline underline-offset-[3px] hover:text-primary transition-colors cursor-pointer", + "underline text-foreground underline-offset-[3px] hover:text-primary transition-colors cursor-pointer", ), }, }); @@ -46,6 +46,12 @@ const tiptapImage = TiptapImage.extend({ }, }); +const resizeImage = ImageResize.configure({ + HTMLAttributes: { + class: cx("rounded-lg border border-muted"), + }, +}); + const updatedImage = UpdatedImage.configure({ HTMLAttributes: { class: cx("rounded-lg border border-muted"), @@ -108,6 +114,7 @@ export const defaultExtensions = [ tiptapLink, tiptapImage, updatedImage, + resizeImage, taskList, taskItem, horizontalRule, diff --git a/src/components/editor/extentions/link-preview/link-preview-component.tsx b/src/components/editor/extentions/link-preview/link-preview-component.tsx index 5a639fe..445d8ea 100644 --- a/src/components/editor/extentions/link-preview/link-preview-component.tsx +++ b/src/components/editor/extentions/link-preview/link-preview-component.tsx @@ -28,7 +28,7 @@ const InputLink = ({ onSubmit }: { onSubmit: (link: string) => void }) => { inputRef.current?.focus(); }, []); return ( -
    + void }) => { placeholder="Enter a link" value={link} onChange={(e) => setLink(e.target.value)} - className="flex-1 focus-visible:ring-transparent border-0" + className="flex-1 border-0 focus-visible:ring-transparent" />