Rumah > hujung hadapan web > tutorial js > Membina Papan Pemuka Blog Dinamik dengan Next.js

Membina Papan Pemuka Blog Dinamik dengan Next.js

Barbara Streisand
Lepaskan: 2024-12-08 17:04:10
asal
637 orang telah melayarinya

pengenalan

Hello, apa khabar? Ini ialah Vítor, kembali dengan projek baharu untuk membantu anda meningkatkan kemahiran pengaturcaraan anda. Sudah lama sejak kali terakhir saya menerbitkan tutorial. Sejak beberapa bulan lalu, saya mengambil sedikit masa untuk berehat dan fokus kepada aktiviti lain. Dalam tempoh ini, saya membangunkan projek web kecil: blog, yang menjadi tumpuan tutorial ini.

Dalam panduan ini, kami akan mencipta bahagian hadapan halaman blog yang mampu memaparkan Markdown. Aplikasi ini akan merangkumi laluan awam dan peribadi, pengesahan pengguna dan keupayaan untuk menulis teks Markdown, menambah foto, memaparkan artikel dan banyak lagi.

Jangan teragak-agak untuk menyesuaikan aplikasi anda mengikut kehendak anda—saya juga menggalakkannya.

Anda boleh mengakses repositori untuk aplikasi ini di sini:

Building a Dynamic Blog Dashboard with Next.js Gondrak08 / platform blog

Plataform blog yang dibuat dengan Next.js/typescript.

Plataforma para blog

  • Tutorial em teks

Bahan-bahan

  • next-auth - biblioteca de autenticação para Next.js
  • github.com/markdown-it/markdown-it - markdown biblioteca.
  • github.com/sindresorhus/github-markdown-css- Para dar style ao nosso editor markdown.
  • github.com/remarkjs/react-markdown - Biblioteca untuk renderizar markdown em nosso componente react.
  • github.com/remarkjs/remark-react/tree/4722bdf - Pemalam untuk pengubah Markdown em React.
  • codemirror.net - Komponen penyunting untuk web.
  • ikon reaksi - lib de icones untuk bertindak balas.

Como usar

npm i
npm run start
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Pelayan

você pode encontrar or servidor dessa applicação em server


Lihat di GitHub


Tutorial ini juga termasuk penulisan pelayan Node.js yang akan digunakan dalam panduan ini:

Saya harap anda menikmatinya.

Selamat pengekodan!

Perpustakaan

Berikut ialah ringkasan perpustakaan yang digunakan dalam projek ini:

  • next-auth - Pustaka pengesahan untuk Next.js
  • github.com/markdown-it/markdown-it - Pustaka penurunan harga.
  • github.com/sindresorhus/github-markdown-css - Untuk menggayakan editor Markdown kami.
  • github.com/remarkjs/react-markdown - Pustaka untuk memberikan Markdown dalam komponen React kami.
  • github.com/remarkjs/remark-react/tree/4722bdf - Pemalam untuk mengubah Markdown menjadi React.
  • codemirror.net - Editor komponen web.
  • react-icons - Pustaka ikon untuk React.

Mencipta Projek React

Kami akan menggunakan versi terkini rangka kerja Next.js, yang, pada masa menulis tutorial ini, ialah versi 13.4.

Jalankan arahan berikut untuk mencipta projek:

npm i
npm run start
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Semasa pemasangan, pilih tetapan templat. Dalam tutorial ini, saya akan menggunakan TypeScript sebagai bahasa pengaturcaraan dan rangka kerja Tailwind CSS untuk menggayakan aplikasi kami.

Konfigurasi

Sekarang mari pasang semua perpustakaan yang akan kami gunakan.

Penurunan harga
npx create-next-app myblog
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
React Remark
npm i  markdown-it @types/markdown-it markdown-it-style github-markdown-css react-markdown
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Codemirror
remark remark-gfm remark-react
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
ikon
npm @codemirror/commands @codemirror/highlight @codemirror/lang-javascript @codemirror/lang-markdown @codemirror/language @codemirror/language-data @codemirror/state @codemirror/theme-one-dark @codemirror/view
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Kemudian bersihkan struktur awal pemasangan anda dengan mengalih keluar semua yang tidak akan kami gunakan.

Seni bina

Ini adalah struktur akhir aplikasi kami.

npm i react-icons @types/react-icons
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Langkah Pertama

Mengkonfigurasi seterusnya.config

Dalam akar projek, dalam fail next.config.js, mari kita konfigurasikan alamat domain dari mana kita akan mengakses imej untuk artikel kita. Untuk tutorial ini, atau jika anda menggunakan pelayan tempatan, kami akan menggunakan localhost.

Pastikan anda memasukkan konfigurasi ini untuk memastikan pemuatan imej yang betul dalam aplikasi anda.

src-
  |- app/
  |    |-(pages)/
  |    |      |- (private)/
  |    |      |       |- (home)
  |    |      |       |- editArticle/[id]
  |    |      |       |
  |    |      |       |- newArticle
  |    |      | - (public)/
  |    |              | - article/[id]
  |    |              | - login
  |    |
  |   api/
  |    |- auth/[...nextAuth]/route.ts
  |    |- global.css
  |    |- layout.tsx
  |
  | - components/
  | - context/
  | - interfaces/
  | - lib/
  | - services/
middleware.ts
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Mengkonfigurasi Middleware

Dalam folder akar aplikasi src/, cipta middleware.ts untuk mengesahkan akses kepada laluan peribadi.

const nextConfig = {
   images: {
    domains: ["localhost"],
  },
};
Salin selepas log masuk
Salin selepas log masuk

Untuk mengetahui lebih lanjut tentang perisian tengah dan semua yang anda boleh lakukan dengannya, semak dokumentasi.

Mengkonfigurasi Laluan Pengesahan

Di dalam folder /app, cipta fail bernama route.ts dalam api/auth/[...nextauth]. Ia akan mengandungi konfigurasi untuk laluan kami, menyambung ke API pengesahan kami menggunakan CredentialsProvider.

Penyedia Kredensial membolehkan anda mengendalikan log masuk dengan bukti kelayakan sewenang-wenangnya, seperti nama pengguna dan kata laluan, domain, pengesahan dua faktor, peranti perkakasan, dll.

Pertama, dalam akar projek anda, buat fail .env.local dan tambahkan token yang akan digunakan sebagai rahsia kami.

npm i
npm run start
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Seterusnya, mari tulis sistem pengesahan kami, di mana NEXTAUTH_SECRET ini akan ditambahkan pada rahsia kami dalam fail src/app/auth/[...nextauth]/routes.ts.

npx create-next-app myblog
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Pembekal Pengesahan

Mari kita cipta penyedia pengesahan, konteks, yang akan berkongsi data pengguna kami merentasi halaman laluan peribadi kami. Kami akan menggunakannya kemudian untuk membungkus salah satu fail susun atur.tsx kami.

Buat fail dalam src/context/auth-provider.tsx dengan kandungan berikut:

npm i  markdown-it @types/markdown-it markdown-it-style github-markdown-css react-markdown
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Gaya Global

Secara keseluruhan, dalam aplikasi kami, kami akan menggunakan CSS Tailwind untuk mencipta gaya kami. Walau bagaimanapun, di sesetengah tempat, kami akan berkongsi kelas CSS tersuai antara halaman dan komponen.

remark remark-gfm remark-react
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Susun atur

Sekarang mari kita tulis reka letak, baik peribadi mahupun awam.

app/layout.tsx

npm @codemirror/commands @codemirror/highlight @codemirror/lang-javascript @codemirror/lang-markdown @codemirror/language @codemirror/language-data @codemirror/state @codemirror/theme-one-dark @codemirror/view
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

pages/layout.tsx

npm i react-icons @types/react-icons
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Panggilan API

Aplikasi kami akan membuat beberapa panggilan ke API kami, dan anda boleh menyesuaikan aplikasi ini untuk menggunakan mana-mana API luaran. Dalam contoh kami, kami menggunakan aplikasi tempatan kami. Jika anda belum melihat tutorial bahagian belakang dan penciptaan pelayan, semaknya.

Dalam src/services/, mari tulis fungsi berikut:

  1. authService.ts: fungsi yang bertanggungjawab untuk mengesahkan pengguna kami pada pelayan.
src-
  |- app/
  |    |-(pages)/
  |    |      |- (private)/
  |    |      |       |- (home)
  |    |      |       |- editArticle/[id]
  |    |      |       |
  |    |      |       |- newArticle
  |    |      | - (public)/
  |    |              | - article/[id]
  |    |              | - login
  |    |
  |   api/
  |    |- auth/[...nextAuth]/route.ts
  |    |- global.css
  |    |- layout.tsx
  |
  | - components/
  | - context/
  | - interfaces/
  | - lib/
  | - services/
middleware.ts
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

2.refreshAccessToken.tsx:

const nextConfig = {
   images: {
    domains: ["localhost"],
  },
};
Salin selepas log masuk
Salin selepas log masuk
  1. getArticles.tsx: fungsi yang bertanggungjawab untuk mengambil semua artikel yang disimpan dalam pangkalan data kami:
export { default } from "next-auth/middleware";
export const config = {
  matcher: ["/", "/newArticle/", "/article/", "/article/:path*"],
};
Salin selepas log masuk
  1. postArticle.tsx: fungsi yang bertanggungjawab untuk menyerahkan data artikel ke pelayan kami.
.env.local
NEXTAUTH_SECRET = SubsTituaPorToken
Salin selepas log masuk
  1. editArticle.tsx: fungsi yang bertanggungjawab untuk mengubah suai artikel tertentu dalam pangkalan data.
import NextAuth from "next-auth/next";
import type { AuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { authenticate } from "@/services/authService";
import refreshAccessToken from "@/services/refreshAccessToken";

export const authOptions: AuthOptions = {
  providers: [
    CredentialsProvider({
      name: "credentials",
      credentials: {
        email: {
          name: "email",
          label: "email",
          type: "email",
          placeholder: "Email",
        },
        password: {
          name: "password",
          label: "password",
          type: "password",
          placeholder: "Password",
        },
      },
      async authorize(credentials, req) {
        if (typeof credentials !== "undefined") {
          const res = await authenticate({
            email: credentials.email,
            password: credentials.password,
          });
          if (typeof res !== "undefined") {
            return { ...res };
          } else {
            return null;
          }
        } else {
          return null;
        }
      },
    }),
  ],

  session: { strategy: "jwt" },
  secret: process.env.NEXTAUTH_SECRET,
  callbacks: {
    async jwt({ token, user, account }: any) {
      if (user && account) {
        return {
          token: user?.token,
          accessTokenExpires: Date.now() + parseInt(user?.expiresIn, 10),
          refreshToken: user?.tokenRefresh,
        };
      }

      if (Date.now() < token.accessTokenExpires) {
        return token;
      } else {
        const refreshedToken = await refreshAccessToken(token.refreshToken);
        return {
          ...token,
          token: refreshedToken.token,
          refreshToken: refreshedToken.tokenRefresh,
          accessTokenExpires:
            Date.now() + parseInt(refreshedToken.expiresIn, 10),
        };
      }
    },
    async session({ session, token }) {
      session.user = token;
      return session;
    },
  },

  pages: {
    signIn: "/login",
    signOut: "/login",
  },
};

const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
Salin selepas log masuk
  1. deleteArticle.tsx: fungsi yang bertanggungjawab untuk mengalih keluar artikel tertentu daripada pangkalan data kami.
'use client';
import React from 'react';
import { SessionProvider } from "next-auth/react";
export default function Provider({
    children,
    session
}: {
    children: React.ReactNode,
    session: any
}): React.ReactNode {
    return (
        <SessionProvider session={session} >
            {children}
        </SessionProvider>
    )
};
Salin selepas log masuk

Komponen

Seterusnya, mari tulis setiap komponen yang digunakan sepanjang aplikasi.

Komponen/Navbar.tsx

Komponen ringkas dengan dua pautan navigasi.

/*global.css*/
.container {
  max-width: 1100px;
  width: 100%;
  margin: 0px auto;
}

.image-container {
  position: relative;
  width: 100%;
  height: 5em;
  padding-top: 56.25%; /* Aspect ratio 16:9 (dividindo a altura pela largura) */
}

.image-container img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

@keyframes spinner {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

.loading-spinner {
  width: 50px;
  height: 50px;
  border: 10px solid #f3f3f3;
  border-top: 10px solid #293d71;
  border-radius: 50%;
  animation: spinner 1.5s linear infinite;
}
Salin selepas log masuk

Komponen/Pemuatan.tsx

Komponen pemuatan mudah, digunakan sementara menunggu panggilan API selesai.

import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import Provider from "@/context/auth-provider";
import { getServerSession } from "next-auth";
import { authOptions } from "./api/auth/[...nextauth]/route";
const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Markdown Text Editor",
  description: "Created by <@vitorAlecrim>",
};

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const session = await getServerSession(authOptions);
  return (
    <Provider session={session}>
      <html lang="en">
        <body className={inter.className}>{children}</body>
      </html>
    </Provider>
  );
}
Salin selepas log masuk

Komponen/Penomboran.tsx

Komponen penomboran yang digunakan pada halaman kami memaparkan semua artikel kami, dalam laluan peribadi kami. Anda boleh mendapatkan artikel yang lebih terperinci tentang cara menulis komponen ini di sini

npm i
npm run start
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Komponen/ArticleCard.tsx

Komponen kad untuk memaparkan artikel bertulis.

Komponen ini juga mengandungi pautan yang akan membawa kepada kedua-dua halaman paparan artikel dan halaman untuk mengedit artikel yang ditulis sebelum ini.

npx create-next-app myblog
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Komponen/ArticleList.tsx

Komponen yang bertanggungjawab untuk membuat panggilan API dan memaparkan respons.

Di sini, kami akan menggunakan dua panggilan API melalui fungsi yang kami tulis:

  1. getArticles.ts - mengembalikan semua artikel yang akan dipaparkan dalam komponen.
  2. removeArticle - mengalih keluar artikel tertentu daripada senarai kami dan daripada pelayan kami.

Kami akan menggunakan komponen Pagination.tsx, yang ditulis sebelum ini, untuk membahagikan bilangan artikel merentas halaman.

npm i  markdown-it @types/markdown-it markdown-it-style github-markdown-css react-markdown
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

muka surat

Seterusnya, kami akan melalui setiap halaman kami, dibahagikan dengan laluan masing-masing.

Halaman Awam

Log masuk

Ini adalah halaman utama aplikasi kami. Ia adalah halaman yang mudah, dan anda boleh mengubah suainya mengikut kesesuaian anda. Pada halaman ini, kami akan menggunakan fungsi log masuk yang disediakan oleh pustaka navigasi pengesahan seterusnya.

Dalam fail src/app/pages/public/login/page.tsx.

remark remark-gfm remark-react
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Halaman Artikel

Untuk mencipta halaman bacaan artikel, kami akan membangunkan halaman dinamik.

Setiap platform blog yang anda lawati berkemungkinan mempunyai halaman khusus untuk membaca artikel, boleh diakses melalui URL. Sebabnya ialah laluan halaman dinamik. Nasib baik, Next.js memudahkan perkara ini dengan kaedah AppRouter baharunya, menjadikan kehidupan kita lebih mudah.

Pertama: kita perlu mencipta laluan dalam struktur kita dengan menambahkan folder [id]. Ini akan menghasilkan struktur berikut: pages/(public)/articles/[id]/pages.tsx.

  • Id sepadan dengan slug laluan navigasi kami.
  • params ialah harta yang diluluskan melalui pepohon aplikasi kami yang mengandungi slug navigasi.
npm @codemirror/commands @codemirror/highlight @codemirror/lang-javascript @codemirror/lang-markdown @codemirror/language @codemirror/language-data @codemirror/state @codemirror/theme-one-dark @codemirror/view
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Kedua: gunakan pustaka MarkdownIt untuk membolehkan halaman memaparkan teks dalam format Markdown.

npm i react-icons @types/react-icons
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Dan akhirnya,

setelah halaman itu sedia, dengan mengakses, sebagai contoh, localhost:3000/articles/1 dalam penyemak imbas, anda akan dapat melihat artikel dengan ID yang disediakan.

Dalam kes kami, ID akan dihantar melalui navigasi apabila mengklik pada salah satu komponen ArticleCards.tsx, yang akan dipaparkan pada halaman utama laluan peribadi kami.

src-
  |- app/
  |    |-(pages)/
  |    |      |- (private)/
  |    |      |       |- (home)
  |    |      |       |- editArticle/[id]
  |    |      |       |
  |    |      |       |- newArticle
  |    |      | - (public)/
  |    |              | - article/[id]
  |    |              | - login
  |    |
  |   api/
  |    |- auth/[...nextAuth]/route.ts
  |    |- global.css
  |    |- layout.tsx
  |
  | - components/
  | - context/
  | - interfaces/
  | - lib/
  | - services/
middleware.ts
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Halaman Persendirian

Berikut ialah halaman peribadi kami, yang hanya boleh diakses setelah pengguna disahkan dalam aplikasi kami.

Rumah

Di dalam folder apl/halaman/ kami, apabila fail diisytiharkan di dalam (), ini bermakna laluan itu sepadan dengan /.

Dalam kes kami, folder (Laman Utama) merujuk kepada halaman utama laluan peribadi kami. Ia adalah halaman pertama yang pengguna lihat apabila mengesahkan ke dalam sistem. Halaman ini akan memaparkan senarai artikel daripada pangkalan data kami.

Data akan diproses oleh komponen ArticlesList.tsx kami. Jika anda belum menulis kod ini lagi, rujuk semula bahagian komponen.

Dalam apl/(halaman)/(peribadi)/(rumah)/halaman.tsx.

npm i
npm run start
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Artikel Baru

Ini adalah salah satu halaman paling penting dalam aplikasi kami, kerana ia membolehkan kami mendaftarkan artikel kami.

Halaman ini akan membolehkan pengguna untuk:

  1. Tulis artikel dalam format Markdown.
  2. Tetapkan imej pada artikel.
  3. Pratonton teks Markdown sebelum menyerahkannya ke pelayan.

Halaman menggunakan beberapa cangkuk:

  1. useCallback - digunakan untuk menghafal fungsi.
  2. useState - membolehkan anda menambah pembolehubah keadaan pada komponen kami.
  3. useSession - membolehkan kami menyemak sama ada pengguna disahkan dan mendapatkan token pengesahan.

Untuk ini, kami akan menggunakan dua komponen:

  1. TextEditor.tsx: editor teks yang kami tulis sebelum ini.
  2. Preview.tsx: komponen untuk memaparkan fail dalam format Markdown.

Semasa membina halaman ini, kami akan menggunakan API kami:

  1. POST: Menggunakan fungsi postArticle kami, kami akan menghantar artikel ke pelayan.

Kami juga akan menggunakan cangkuk useSession, yang disediakan oleh perpustakaan pengesahan seterusnya, untuk mendapatkan token pengesahan pengguna, yang akan digunakan untuk mendaftarkan artikel pada pelayan.

Ini akan melibatkan tiga panggilan API yang berbeza.
Dalam app/pages/(private)/newArticle/page.tsx.

"gunakan klien";
import React, { ChangeEvent, useCallback, useState } daripada "react";
import { useSession } daripada "next-auth/react";
import { ubah hala } daripada "next/navigation";
import postArtical daripada "@/services/postArticle";
import { AiOutlineFolderOpen } daripada "react-icons/ai";
import { RiImageEditLine } daripada "react-icons/ri";

import Imej daripada "seterusnya/imej";
import TextEditor daripada "@/components/textEditor";
import Pratonton daripada "@/komponen/PreviewText";
import { AiOutlineSend } daripada "react-icons/ai";
import { BsBodyText } daripada "react-icons/bs";

eksport fungsi lalai NewArticle(params:any) {
  const { data: session }: any = useSession({
    dikehendaki: benar,
    onUnauthenticated() {
      redirect("/log masuk");
    },
  });
  const [imageUrl, setImageUrl] = useState<objek>({});
  const [previewImage, setPreviewImage] = useState<rentetan>("");
  const [previewText, setPreviewText] = useState<boolean>(false);
  const [title, setTitle] = useState<rentetan>("");
  const [doc, setDoc] = useState<string>("# Escreva o seu texto... n");
  const handleDocChange = useCallback((newDoc: any) => {
    setDoc(newDoc);
  }, []);

  jika (!session?.user) mengembalikan null;

  const handleArticleSubmit = tak segerak (e:mana-mana) => {
        e.preventDefault();
    token const: rentetan = session.user.token;
    cuba {
      const res = menunggu postArtical({
        id: session.user.userId.toString(),
        token: token,
        imageUrl: imageUrl,
        tajuk: "tajuk,"
        doc: doc,
      });
      console.log('re--->', res);
      redirect('/success');
    } tangkap (ralat) {
      console.error('Ralat menghantar artikel:', ralat);
      // Kendalikan ralat jika perlu
      ralat lontaran;
    }
  };

  const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    jika (e.target.files && e.target.files.length > 0) {
      fail const = e.target.files[0];
      const url = URL.createObjectURL(file);
      setPreviewImage(url);
      setImageUrl(fail);
    }
  };

  const handleTextPreview = (e: mana-mana) => {
    e.preventDefault();
    setPreviewText(!previewText);
  };
  kembali (
    <section className="w-full h-penuh min-h-skrin relatif py-8">
      {previewTeks && (
        <div className="kanan mutlak-16 atas-5 p-5 sempadan-2 sempadan-slate-500 bg-slate-100 rounded-xl w-full max-w-[33em] z-30">
          <Pratonton
            doc={doc}
            tajuk={tajuk}
            previewImage={previewImage}
            onPreview={() => setPreviewText(!previewText)}
          />
        </div>
      )}

      <form className="relative mx-auto max-w-[700px] h-penuh min-h-[90%] w-penuh p-2 sempadan-2 sempadan-slate-200 bulat-md bg-slate-50 drop-shadow-xl flex flex-col gap-2 ">
        {" "}
        <div className="flex justify-antara item-center">
          <butang
            className="border-b-2 rounded-md border-slate-500 p-2 flex items-center gap-2 hover:border-slate-400 hover:text-slate-800"
            onClick={handleTextPreview}
          >
            <BsBodyText />
            Pratonton
          </button>{" "}
          <butang
            className="group border border-b-2 border-slate-500 rounded-md p-2 flex items-center gap-2 hover:border-slate-400 hover:text-slate-800 "
            onClick={handleArticleSubmit}
          >
            Enviar Texto
            <AiOutlineSend className="w-5 h-5 group-hover:text-red-500" />
          </butang>
        </div>
        <div className="header-wrapper flex flex-col gap-2 ">
          <div className="image-box">
            {previewImage.length === 0 && (
              <div className="select-image">
                <label
                  htmlUntuk="imej"
                  className="p-4 sempadan putus-putus sempadan-4 sempadan-slate-400 kursor-penunjuk flex flex-col item-pusat justify-center"
                >
                  <AiOutlineFolderOpen className="w-7 h-7" />
                  drang dan lepaskan imej
                </label>
                <masukan
                 >



<h4>
  
  
  Edit Artikel
</h4>

<p>Halaman yang serupa dengan <em>Artikel Baharu</em> (Artikel baharu), dengan beberapa perbezaan.</p>

<p>Pertama, kami mentakrifkan laluan dinamik di mana kami menerima id sebagai parameter navigasi. Ini hampir sama dengan apa yang dilakukan pada halaman membaca artikel. <br>
app/(pages)/(private)/editArticle/[id]/page.tsx<br>
</p><pre class="brush:php;toolbar:false">"gunakan klien";
import React, { useState, useEffect, useCallback, useRef, ChangeEvent } daripada "react";
import { useSession } daripada "next-auth/react";
import { ubah hala } daripada "next/navigation";
import Imej daripada 'next/image';

import { IArticle } daripada "@/interfaces/article.interface";
import { AiOutlineEdit } daripada "react-icons/ai";
import { BsBodyText } daripada "react-icons/bs";
import { AiOutlineFolderOpen } daripada "react-icons/ai";
import { RiImageEditLine } daripada "react-icons/ri";

import Pratonton daripada "@/komponen/PreviewText";
import TextEditor daripada "@/components/textEditor";
import Pemuatan daripada '@/komponen/Pemuatan';
import editArtical daripada "@/services/editArticle";

eksport fungsi lalai EditArticle({ params }: { params: any }) {
 const { data: session }: any = useSession({
    dikehendaki: benar,
    onUnauthenticated() {
      redirect("/log masuk");
    },
  });
  const id: nombor = params.id;
  const [artikel, setArticle] = useState<IArticle | null>(null);
  const [imageUrl, setImageUrl] = useState<objek>({});
  const [previewImage, setPreviewImage] = useState<rentetan>("");
  const [previewText, setPreviewText] = useState<boolean>(false)
  const [title, setTitle] = useState<rentetan>("");
  const [doc, setDoc] = useState<rentetan>('');
  const handleDocChange = useCallback((newDoc: any) => {
    setDoc(newDoc);
  }, []);
  const inputRef= useRef<HTMLInputElement>(null);

  const fetchArticle = tak segerak (id: nombor) => {
    cuba {
      respons const = tunggu ambil(
        `http://localhost:8080/articles/getById/${id}`,
      );
      const jsonData = menunggu respons.json();
      setArticle(jsonData);
    } tangkap (err) {
      console.log("sesuatu telah berlaku:", err);
    }
  };
  useEffect(() => {
    jika (artikel !== null || artikel !== tidak ditentukan) {
      fetchArticle(id);
    }
  }, [id]);

  useEffect(()=>{
    if(artikel != null && article.content){
        setDoc(article.content)
    }

    if(artikel !=null && article.image){
      setPreviewImage(`http://localhost:8080/` article.image)
    }
  },[artikel])

  const handleArticleSubmit = tak segerak (e:mana-mana) => {
     e.preventDefault();
    token const: rentetan = session.user.token;
    cuba{
      const res = menunggu editArtical({
      id: id,
      token: token,
      imageUrl:imageUrl,
      tajuk: tajuk,
      doc: doc,
      });
        console.log('re--->',res)
        kembalikan semula;
    } tangkap (ralat){
    console.log("Ralat:", ralat)
    }
  };
  const handleImageClick = ()=>{
      console.log('hiii')
    if(inputRef.current){
      inputRef.current.click();
    }
  }const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    jika (e.target.files && e.target.files.length > 0) {
      fail const = e.target.files[0];
      const url = URL.createObjectURL(file);
      setPreviewImage(url);
      setImageUrl(fail);
    }

  };
   const handleTextPreview = (e: mana-mana) => {
    e.preventDefault();
    setPreviewText(!previewText);
    console.log('hello dari pratonton!')
  };

  if(!article) return <Memuatkan/>
  jika(artikel?.kandungan)
  kembali (
    <section className='w-penuh h-penuh min-h-skrin relatif py-8'>
      {previewTeks && (
        <div className="kanan mutlak-16 atas-5 p-5 sempadan-2 sempadan-slate-500 bg-slate-100 rounded-xl w-full max-w-[33em] z-30">
          <Pratonton
            doc={doc}
            tajuk={tajuk}
            previewImage={previewImage}
            onPreview={() => setPreviewText(!previewText)}
          />
        </div>
      )}

      <div className='relative mx-auto max-w-[700px] h-min-h-penuh-[90%] w-penuh p-2 sempadan-2 sempadan-slate-200 bulat-md bg-titik putih- shadow-md flex flex-col gap-2'>
        <form className='relative mx-auto max-w-[700px] h-penuh min-h-[90%] w-penuh p-2 sempadan-2 sempadan-slate-200 bulat-md bg-slate-50 drop-shadow-md flex flex-col gap-2 '>
          {" "}
          <div className='flex justify-antara item-center'>
            <butang
              className='border-b-2 rounded-md border-slate-500 p-2 flex items-center gap-2 hover:border-slate-400 hover:text-slate-800'
              onClick={handleTextPreview}
            >
              <BsBodyText />
              Pratonton
            </button>{" "}
            <butang
              className='group border border-b-2 border-slate-500 rounded-md p-2 flex items-center gap-2 hover:border-slate-400 hover:text-slate-800 '
              onClick={handleArticleSubmit}
            >
                Edit artigo 
              <AiOutlineEdit className='w-5 h-5 group-hover:text-red-500' />
            </butang>
          </div>
          <div className='header-wrapper flex flex-col gap-2 '>
            <div className='image-box'>
              {previewImage.length === 0 && (
                <div className='select-image'>
                  <label
                    htmlFor='image'
                    className='p-4 sempadan putus-putus sempadan-4 sempadan-slate-400 kursor-penunjuk flex flex-col item-pusat justify-center'
                  >
                    <AiOutlineFolderOpen className='w-7 h-7' />
                    drang dan lepaskan imej
                  </label>
                  <masukan
                   >



<h2>
  
  
  Kesimpulan
</h2>

<p>Pertama, saya ingin mengucapkan terima kasih kerana meluangkan masa untuk membaca tutorial ini, dan saya juga ingin mengucapkan tahniah kepada anda kerana telah menyelesaikannya. Saya harap ia berfungsi dengan baik dan arahan langkah demi langkah mudah diikuti.</p>

<p>Kedua, saya ingin menyerlahkan beberapa perkara tentang perkara yang baru kami bina. Ini adalah asas sistem blog, dan masih banyak yang perlu ditambah, seperti halaman awam yang memaparkan semua artikel, halaman pendaftaran pengguna, atau halaman ralat 404 tersuai. Jika, semasa tutorial, anda tertanya-tanya tentang halaman ini dan terlepasnya, ketahui bahawa ini adalah disengajakan. Tutorial ini memberikan anda pengalaman yang mencukupi untuk membuat halaman baharu ini sendiri, menambah banyak lagi dan melaksanakan ciri baharu.</p>

<p>Terima kasih banyak-banyak, dan sehingga kali seterusnya. o/</p>


          

            
        
Salin selepas log masuk

Atas ialah kandungan terperinci Membina Papan Pemuka Blog Dinamik dengan Next.js. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

sumber:dev.to
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Artikel terbaru oleh pengarang
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan