Trình soạn thảo văn bản JavaScript

Shalabh Vyas là một Kỹ sư Front-End với kinh nghiệm làm việc trong toàn bộ vòng đời phát triển sản phẩm, khởi chạy các ứng dụng dựa trên web phong phú. … Thông tin thêm về Shalabh ↬

Bản tin email

Email (đập vỡ) của bạn

Mẹo hàng tuần về giao diện người dùng & UX.
Được hơn 200.000 người tin cậy.

  • Trình soạn thảo văn bản JavaScript
    Danh sách kiểm tra thiết kế giao diện thông minh

  • Trình soạn thảo văn bản JavaScript
    Xem trực tiếp vào tháng 12. thứ 8
  • Trình soạn thảo văn bản JavaScript
    Đập tanConf Freiburg. Tất cả về Web. Ngày 4–6 tháng 9 năm 2023

  • Trình soạn thảo văn bản JavaScript
    Bắt đầu miễn phí
  • Trình soạn thảo văn bản JavaScript
    Giao diện người dùng SmashingConf 2023

  • Trình soạn thảo văn bản JavaScript
    Các mẫu thiết kế giao diện Đào tạo UX

Trong bài viết này, chúng ta sẽ tìm hiểu cách xây dựng Trình soạn thảo WYSIWYG/Rich-Text hỗ trợ văn bản, hình ảnh, liên kết phong phú và một số tính năng sắc thái từ các ứng dụng xử lý văn bản. Chúng tôi sẽ sử dụng SlateJS để xây dựng trình bao của trình chỉnh sửa, sau đó thêm thanh công cụ và cấu hình tùy chỉnh. Mã cho ứng dụng có sẵn trên GitHub để tham khảo

Trong những năm gần đây, lĩnh vực Sáng tạo và Thể hiện Nội dung trên nền tảng Kỹ thuật số đã chứng kiến ​​một sự đột phá lớn. Sự thành công rộng rãi của các sản phẩm như Quip, Google Docs và Dropbox Paper đã cho thấy cách các công ty đang chạy đua để xây dựng trải nghiệm tốt nhất cho người tạo nội dung trong miền doanh nghiệp và cố gắng tìm ra những cách sáng tạo để phá vỡ khuôn mẫu truyền thống về cách chia sẻ và tiêu thụ nội dung. Tận dụng khả năng tiếp cận rộng rãi của các nền tảng truyền thông xã hội, có một làn sóng mới những người tạo nội dung độc lập sử dụng các nền tảng như Phương tiện để tạo nội dung và chia sẻ nội dung đó với khán giả của họ

Vì rất nhiều người từ các ngành nghề và nền tảng khác nhau cố gắng tạo nội dung trên các sản phẩm này nên điều quan trọng là các sản phẩm này phải cung cấp trải nghiệm sáng tạo nội dung hiệu quả và liền mạch, đồng thời có các nhóm nhà thiết kế và kỹ sư phát triển một số cấp độ chuyên môn về miền theo thời gian trong không gian này. Với bài viết này, chúng tôi cố gắng không chỉ đặt nền móng cho việc xây dựng một trình soạn thảo mà còn cung cấp cho người đọc cái nhìn thoáng qua về cách các chức năng nhỏ khi được kết hợp với nhau có thể tạo ra trải nghiệm người dùng tuyệt vời cho người tạo nội dung

Hiểu cấu trúc tài liệu

Trước khi chúng tôi đi sâu vào việc xây dựng trình chỉnh sửa, hãy xem cách tài liệu được cấu trúc cho Trình soạn thảo văn bản có định dạng và các loại cấu trúc dữ liệu khác nhau có liên quan là gì

Nút tài liệu

Các nút tài liệu được sử dụng để thể hiện nội dung của tài liệu. Các loại nút phổ biến mà tài liệu văn bản đa dạng thức có thể chứa là đoạn văn, tiêu đề, hình ảnh, video, khối mã và trích dẫn kéo. Một số trong số này có thể chứa các nút khác khi còn nhỏ bên trong chúng (e. g. Các nút đoạn chứa các nút văn bản bên trong chúng). Các nút cũng giữ bất kỳ thuộc tính cụ thể nào đối với đối tượng mà chúng đại diện cần thiết để hiển thị các nút đó bên trong trình chỉnh sửa. (e. g. Các nút hình ảnh chứa thuộc tính

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
4 hình ảnh, Khối mã có thể chứa thuộc tính
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
5, v.v.)

Phần lớn có hai loại nút đại diện cho cách chúng sẽ được hiển thị -

  • Các nút khối (tương tự như khái niệm HTML về các phần tử cấp khối) được hiển thị trên một dòng mới và chiếm chiều rộng có sẵn. Các nút khối có thể chứa các nút khối khác hoặc các nút nội tuyến bên trong chúng. Một quan sát ở đây là các nút cấp cao nhất của tài liệu sẽ luôn là các nút khối
  • Các nút nội tuyến (tương tự như khái niệm HTML về các phần tử nội tuyến) bắt đầu hiển thị trên cùng một dòng với nút trước đó. Có một số khác biệt về cách các phần tử nội tuyến được thể hiện trong các thư viện chỉnh sửa khác nhau. SlateJS cho phép các phần tử nội tuyến trở thành các nút. DraftJS, một thư viện Rich Text Editing phổ biến khác, cho phép bạn sử dụng khái niệm Thực thể để hiển thị các phần tử nội tuyến. Liên kết và Hình ảnh Nội tuyến là ví dụ về các nút Nội tuyến
  • Các nút trống — SlateJS cũng cho phép loại nút thứ ba này mà chúng ta sẽ sử dụng sau trong bài viết này để kết xuất phương tiện

Nếu bạn muốn tìm hiểu thêm về các danh mục này, tài liệu về Nút của SlateJS là một nơi tốt để bắt đầu

Thêm sau khi nhảy. Tiếp tục đọc bên dưới ↓

Các biểu mẫu web là trung tâm của mọi tương tác có ý nghĩa, vì vậy chúng đáng để xử lý chắc chắn. Làm quen với Mẫu thiết kế biểu mẫu của Adam Silver, một hướng dẫn thiết thực để thiết kế và xây dựng biểu mẫu cho web

Chuyển đến mục lục ↬

Trình soạn thảo văn bản JavaScript

Thuộc tính

Tương tự như khái niệm thuộc tính của HTML, các thuộc tính trong Tài liệu văn bản đa dạng thức được sử dụng để biểu thị các thuộc tính không phải nội dung của một nút hoặc nút con. Chẳng hạn, một nút văn bản có thể có các thuộc tính kiểu ký tự cho chúng ta biết liệu văn bản có được in đậm/nghiêng/gạch chân hay không, v.v. Mặc dù bài viết này biểu thị các tiêu đề dưới dạng các nút, nhưng một cách khác để biểu thị chúng có thể là các nút có các kiểu đoạn văn (

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
6 &
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
7) làm thuộc tính trên chúng

Hình ảnh dưới đây đưa ra một ví dụ về cách cấu trúc của tài liệu (dưới dạng JSON) được mô tả ở mức chi tiết hơn bằng cách sử dụng các nút và thuộc tính làm nổi bật một số thành phần trong cấu trúc ở bên trái

Trình soạn thảo văn bản JavaScript
Tài liệu mẫu và biểu diễn cấu trúc của nó. (Xem trước lớn)

Một số điều đáng gọi ở đây với cấu trúc là

  • Các nút văn bản được biểu diễn dưới dạng
    // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    8
  • Thuộc tính của các nút được lưu trữ trực tiếp trên nút (e. g.
    // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    9 cho liên kết và
    import { Editable, Slate, withReact } from "slate-react";
    
    import { createEditor } from "slate";
    import { useMemo } from "react";
    
    export default function Editor({ document, onChange }) {
      const editor = useMemo(() => withReact(createEditor()), []);
      return (
        <Slate editor={editor} value={document} onChange={onChange}>
          <Editable />
        </Slate>
      );
    }
    
    0 cho hình ảnh)
  • Biểu diễn các thuộc tính văn bản dành riêng cho SlateJS sẽ phá vỡ các nút văn bản thành các nút của chính chúng nếu kiểu ký tự thay đổi. Do đó, văn bản 'Duis aute irure dolor' là nút văn bản của riêng nó với
    import { Editable, Slate, withReact } from "slate-react";
    
    import { createEditor } from "slate";
    import { useMemo } from "react";
    
    export default function Editor({ document, onChange }) {
      const editor = useMemo(() => withReact(createEditor()), []);
      return (
        <Slate editor={editor} value={document} onChange={onChange}>
          <Editable />
        </Slate>
      );
    }
    
    1 được đặt trên đó. Tương tự với văn bản kiểu chữ nghiêng, gạch dưới và kiểu mã trong tài liệu này

Vị trí và lựa chọn

Khi xây dựng trình soạn thảo văn bản đa dạng thức, điều quan trọng là phải hiểu cách phần chi tiết nhất của tài liệu (ví dụ một ký tự) có thể được biểu diễn bằng một số loại tọa độ. Điều này giúp chúng tôi điều hướng cấu trúc tài liệu trong thời gian chạy để hiểu chúng tôi đang ở đâu trong hệ thống phân cấp tài liệu. Quan trọng nhất, các đối tượng vị trí cung cấp cho chúng tôi một cách để thể hiện lựa chọn của người dùng, được sử dụng khá rộng rãi để điều chỉnh trải nghiệm người dùng của trình chỉnh sửa trong thời gian thực. Chúng tôi sẽ sử dụng lựa chọn để xây dựng thanh công cụ của chúng tôi sau trong bài viết này. Ví dụ về những điều này có thể là

  • Con trỏ của người dùng hiện đang ở trong một liên kết, có lẽ chúng ta nên hiển thị cho họ một menu để chỉnh sửa/xóa liên kết?
  • Người dùng đã chọn một hình ảnh chưa?
  • Nếu người dùng chọn một số văn bản nhất định và nhấn nút XÓA, chúng tôi sẽ xác định văn bản mà người dùng đã chọn là gì và xóa văn bản đó khỏi tài liệu

Tài liệu của SlateJS về Vị trí giải thích rộng rãi các cấu trúc dữ liệu này nhưng chúng tôi sẽ nhanh chóng lướt qua chúng ở đây khi chúng tôi sử dụng các thuật ngữ này ở các trường hợp khác nhau trong bài viết và hiển thị một ví dụ trong sơ đồ sau

  • Đường dẫn
    Được biểu thị bằng một dãy số, đường dẫn là cách để đến một nút trong tài liệu. Chẳng hạn, đường dẫn
    import { Editable, Slate, withReact } from "slate-react";
    
    import { createEditor } from "slate";
    import { useMemo } from "react";
    
    export default function Editor({ document, onChange }) {
      const editor = useMemo(() => withReact(createEditor()), []);
      return (
        <Slate editor={editor} value={document} onChange={onChange}>
          <Editable />
        </Slate>
      );
    }
    
    2 đại diện cho nút con thứ 3 của nút thứ 2 trong tài liệu.
  • Điểm
    Vị trí chi tiết hơn của nội dung được biểu thị bằng đường dẫn + độ lệch. Chẳng hạn, một điểm của
    import { Editable, Slate, withReact } from "slate-react";
    
    import { createEditor } from "slate";
    import { useMemo } from "react";
    
    export default function Editor({ document, onChange }) {
      const editor = useMemo(() => withReact(createEditor()), []);
      return (
        <Slate editor={editor} value={document} onChange={onChange}>
          <Editable />
        </Slate>
      );
    }
    
    3 đại diện cho ký tự thứ 14 của nút con thứ 3 bên trong nút thứ 2 của tài liệu.
  • Phạm vi
    Một cặp điểm (được gọi là
    import { Editable, Slate, withReact } from "slate-react";
    
    import { createEditor } from "slate";
    import { useMemo } from "react";
    
    export default function Editor({ document, onChange }) {
      const editor = useMemo(() => withReact(createEditor()), []);
      return (
        <Slate editor={editor} value={document} onChange={onChange}>
          <Editable />
        </Slate>
      );
    }
    
    4 và
    import { Editable, Slate, withReact } from "slate-react";
    
    import { createEditor } from "slate";
    import { useMemo } from "react";
    
    export default function Editor({ document, onChange }) {
      const editor = useMemo(() => withReact(createEditor()), []);
      return (
        <Slate editor={editor} value={document} onChange={onChange}>
          <Editable />
        </Slate>
      );
    }
    
    5) đại diện cho một phạm vi văn bản bên trong tài liệu. Khái niệm này xuất phát từ API Lựa chọn của Web trong đó
    import { Editable, Slate, withReact } from "slate-react";
    
    import { createEditor } from "slate";
    import { useMemo } from "react";
    
    export default function Editor({ document, onChange }) {
      const editor = useMemo(() => withReact(createEditor()), []);
      return (
        <Slate editor={editor} value={document} onChange={onChange}>
          <Editable />
        </Slate>
      );
    }
    
    4 là nơi bắt đầu lựa chọn của người dùng và
    import { Editable, Slate, withReact } from "slate-react";
    
    import { createEditor } from "slate";
    import { useMemo } from "react";
    
    export default function Editor({ document, onChange }) {
      const editor = useMemo(() => withReact(createEditor()), []);
      return (
        <Slate editor={editor} value={document} onChange={onChange}>
          <Editable />
        </Slate>
      );
    }
    
    5 là nơi kết thúc. Một phạm vi/lựa chọn được thu gọn biểu thị nơi các điểm neo và tiêu điểm giống nhau (chẳng hạn như nghĩ về một con trỏ nhấp nháy trong một kiểu nhập văn bản).

Ví dụ: giả sử lựa chọn của người dùng trong ví dụ tài liệu trên của chúng tôi là

import { Editable, Slate, withReact } from "slate-react";

import { createEditor } from "slate";
import { useMemo } from "react";

export default function Editor({ document, onChange }) {
  const editor = useMemo(() => withReact(createEditor()), []);
  return (
    <Slate editor={editor} value={document} onChange={onChange}>
      <Editable />
    </Slate>
  );
}
8

Trình soạn thảo văn bản JavaScript
Người dùng chọn từ
import { Editable, Slate, withReact } from "slate-react";

import { createEditor } from "slate";
import { useMemo } from "react";

export default function Editor({ document, onChange }) {
  const editor = useMemo(() => withReact(createEditor()), []);
  return (
    <Slate editor={editor} value={document} onChange={onChange}>
      <Editable />
    </Slate>
  );
}
8. (Xem trước lớn)

Lựa chọn của người dùng có thể được biểu diễn dưới dạng

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
6

Thiết lập Trình chỉnh sửa

Trong phần này, chúng ta sẽ thiết lập ứng dụng và có một trình soạn thảo văn bản đa dạng thức cơ bản với SlateJS. Ứng dụng soạn sẵn sẽ là

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
70 với các phụ thuộc SlateJS được thêm vào nó. Chúng tôi đang xây dựng giao diện người dùng của ứng dụng bằng cách sử dụng các thành phần từ
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
71. Bắt đầu nào

Tạo một thư mục có tên là

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
72 và chạy lệnh bên dưới từ bên trong thư mục để thiết lập ứng dụng phản ứng. Sau đó, chúng tôi chạy lệnh
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
73 sẽ khởi động máy chủ web cục bộ (cổng mặc định là 3000) và hiển thị cho bạn màn hình chào mừng React

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
1

Sau đó, chúng tôi chuyển sang thêm các phụ thuộc SlateJS vào ứng dụng

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
2

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
74 là gói cốt lõi của SlateJS và
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
75 bao gồm tập hợp các thành phần React mà chúng tôi sẽ sử dụng để hiển thị trình chỉnh sửa Slate. SlateJS hiển thị thêm một số gói được sắp xếp theo chức năng mà người ta có thể xem xét thêm vào trình chỉnh sửa của họ

Trước tiên, chúng tôi tạo một thư mục

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
76 chứa bất kỳ mô-đun tiện ích nào chúng tôi tạo trong ứng dụng này. Chúng tôi bắt đầu với việc tạo một
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
77 trả về cấu trúc tài liệu cơ bản có chứa một đoạn văn bản. Mô-đun này trông giống như dưới đây

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
7

Bây giờ chúng tôi thêm một thư mục có tên là

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
78 sẽ chứa tất cả các thành phần React của chúng tôi và thực hiện các thao tác sau

  • Thêm thành phần React đầu tiên của chúng tôi
    // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    79 vào nó. Hiện tại nó chỉ trả về một
    // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    00
  • Cập nhật thành phần
    // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    01 để giữ tài liệu ở trạng thái được khởi tạo thành
    // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    02 của chúng tôi ở trên
  • Kết xuất Trình chỉnh sửa bên trong ứng dụng và chuyển trạng thái tài liệu và trình xử lý
    // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    03 xuống Trình chỉnh sửa để trạng thái tài liệu của chúng tôi được cập nhật khi người dùng cập nhật nó
  • Chúng tôi cũng sử dụng các thành phần Nav của React bootstrap để thêm thanh điều hướng vào ứng dụng

Thành phần

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
01 bây giờ trông như bên dưới

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
5

Bên trong thành phần Trình chỉnh sửa, sau đó chúng tôi khởi tạo trình chỉnh sửa SlateJS và giữ nó bên trong một

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
05 để đối tượng không thay đổi giữa các lần kết xuất lại

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
06 cung cấp cho chúng tôi phiên bản SlateJS
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
07 mà chúng tôi sử dụng rộng rãi thông qua ứng dụng để truy cập các lựa chọn, chạy chuyển đổi dữ liệu, v.v. withReact là một plugin SlateJS bổ sung các hành vi React và DOM vào đối tượng trình chỉnh sửa. Plugin SlateJS là các hàm Javascript nhận đối tượng
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
07 và đính kèm một số cấu hình cho nó. Điều này cho phép các nhà phát triển web thêm cấu hình vào phiên bản trình soạn thảo SlateJS của họ theo cách có thể kết hợp được

Bây giờ, chúng tôi nhập và kết xuất các thành phần

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
09 và
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
20 từ SlateJS với tài liệu chống đỡ mà chúng tôi nhận được từ Ứng dụng. js.
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
21 hiển thị một loạt ngữ cảnh React mà chúng tôi sử dụng để truy cập trong mã ứng dụng.
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
22 là thành phần hiển thị phân cấp tài liệu để chỉnh sửa. Nhìn chung, mô-đun
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
47 ở giai đoạn này trông như bên dưới

import { Editable, Slate, withReact } from "slate-react";

import { createEditor } from "slate";
import { useMemo } from "react";

export default function Editor({ document, onChange }) {
  const editor = useMemo(() => withReact(createEditor()), []);
  return (
    <Slate editor={editor} value={document} onChange={onChange}>
      <Editable />
    </Slate>
  );
}

Tại thời điểm này, chúng tôi đã thêm các thành phần React cần thiết và trình chỉnh sửa được điền bằng một tài liệu mẫu. Trình chỉnh sửa của chúng tôi hiện đã được thiết lập cho phép chúng tôi nhập và thay đổi nội dung trong thời gian thực — như trong bản ghi màn hình bên dưới

Thiết lập Trình chỉnh sửa Cơ bản đang hoạt động

Bây giờ, hãy chuyển sang phần tiếp theo, nơi chúng tôi định cấu hình trình chỉnh sửa để hiển thị kiểu ký tự và nút đoạn văn

KẾT XUẤT VĂN BẢN TÙY CHỈNH VÀ THANH CÔNG CỤ

Nút kiểu đoạn văn

Hiện tại, trình chỉnh sửa của chúng tôi sử dụng kết xuất mặc định của SlateJS cho bất kỳ loại nút mới nào mà chúng tôi có thể thêm vào tài liệu. Trong phần này, chúng tôi muốn có thể hiển thị các nút tiêu đề. Để có thể làm được điều đó, chúng tôi cung cấp một hàm

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
24 cho các thành phần của Slate. Hàm này được gọi bởi Slate trong thời gian chạy khi nó đang cố duyệt qua cây tài liệu và hiển thị từng nút. Hàm renderElement có ba tham số —

  • // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    25
    SlateJS cụ thể phải được áp dụng cho phần tử DOM cấp cao nhất được trả về từ hàm này.
  • // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    26
    Bản thân đối tượng nút khi nó tồn tại trong cấu trúc tài liệu
  • // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    27
    Các phần tử con của nút này như được định nghĩa trong cấu trúc tài liệu.

Chúng tôi thêm triển khai

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
24 của mình vào một hook có tên là
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
29, nơi chúng tôi sẽ thêm nhiều cấu hình trình chỉnh sửa hơn khi chúng tôi thực hiện. Sau đó, chúng tôi sử dụng hook trên phiên bản trình soạn thảo bên trong
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
79

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
7

Vì chức năng này cho phép chúng tôi truy cập vào

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
26 (chính là nút đó), nên chúng tôi có thể tùy chỉnh
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
24 để triển khai kết xuất tùy chỉnh hơn, thực hiện được nhiều việc hơn là chỉ kiểm tra
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
103. Chẳng hạn, bạn có thể có một nút hình ảnh có thuộc tính
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
104 mà chúng tôi có thể sử dụng để trả về một cấu trúc DOM khác giúp chúng tôi hiển thị hình ảnh nội tuyến so với hình ảnh khối

Bây giờ chúng tôi cập nhật thành phần Trình chỉnh sửa để sử dụng hook này như bên dưới

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
0

Với hiển thị tùy chỉnh tại chỗ, chúng tôi cập nhật ExampleDocument để bao gồm các loại nút mới của chúng tôi và xác minh rằng chúng hiển thị chính xác bên trong trình chỉnh sửa

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
2
Trình soạn thảo văn bản JavaScript
Nút Tiêu đề và Đoạn văn trong Trình chỉnh sửa. (Xem trước lớn)

Kiểu nhân vật

Tương tự như

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
24, SlateJS đưa ra một chức năng hỗ trợ gọi là renderLeaf có thể được sử dụng để tùy chỉnh hiển thị các nút văn bản (
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
106 đề cập đến các nút văn bản là nút lá/nút cấp thấp nhất của cây tài liệu). Theo ví dụ của
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
24, chúng tôi viết triển khai cho
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
108

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
10

Một nhận xét quan trọng về cách thực hiện trên là nó cho phép chúng ta tôn trọng ngữ nghĩa HTML đối với các kiểu ký tự. Vì renderLeaf cho phép chúng tôi truy cập vào chính nút văn bản

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
109, nên chúng tôi có thể tùy chỉnh chức năng để triển khai kết xuất tùy chỉnh hơn. Chẳng hạn, bạn có thể có cách cho phép người dùng chọn một
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
110 cho văn bản và kiểm tra thuộc tính leaf ở đây để đính kèm các kiểu tương ứng

Bây giờ, chúng tôi cập nhật thành phần Trình chỉnh sửa để sử dụng phần trên,

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
02 để có một vài nút văn bản trong đoạn với sự kết hợp của các kiểu này và xác minh rằng chúng được hiển thị như mong đợi trong Trình chỉnh sửa với các thẻ ngữ nghĩa mà chúng tôi đã sử dụng

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
11
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
12
Trình soạn thảo văn bản JavaScript
Kiểu ký tự trong giao diện người dùng và cách chúng được hiển thị trong cây DOM. (Xem trước lớn)

Thêm Thanh công cụ

Hãy bắt đầu bằng cách thêm một thành phần mới

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
112 mà chúng tôi thêm một vài nút cho kiểu ký tự và danh sách thả xuống cho kiểu đoạn văn và chúng tôi kết nối chúng sau trong phần

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
13

Chúng tôi trừu tượng hóa các nút thành thành phần

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
113 là một trình bao bọc xung quanh thành phần Nút React Bootstrap. Sau đó, chúng tôi hiển thị thanh công cụ phía trên thành phần
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
114 bên trong thành phần
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
115 và xác minh rằng thanh công cụ hiển thị trong ứng dụng

Trình soạn thảo văn bản JavaScript
Thanh công cụ với các nút (Xem trước lớn)

Dưới đây là ba chức năng chính mà chúng tôi cần thanh công cụ để hỗ trợ

  1. Khi con trỏ của người dùng ở một vị trí nhất định trong tài liệu và họ nhấp vào một trong các nút kiểu ký tự, chúng ta cần chuyển đổi kiểu cho văn bản mà họ có thể nhập tiếp theo
  2. Khi người dùng chọn một phạm vi văn bản và nhấp vào một trong các nút kiểu ký tự, chúng ta cần chuyển đổi kiểu cho phần cụ thể đó
  3. Khi người dùng chọn một phạm vi văn bản, chúng tôi muốn cập nhật trình đơn thả xuống kiểu đoạn để phản ánh kiểu đoạn của lựa chọn. Nếu họ chọn một giá trị khác với lựa chọn, chúng tôi muốn cập nhật kiểu đoạn văn của toàn bộ lựa chọn thành những gì họ đã chọn

Hãy xem cách các chức năng này hoạt động trên Trình chỉnh sửa trước khi chúng tôi bắt đầu triển khai chúng

Hành vi chuyển đổi Kiểu ký tự

Nghe Tuyển Chọn

Điều quan trọng nhất mà Thanh công cụ cần để có thể thực hiện các chức năng trên là trạng thái Lựa chọn của tài liệu. Khi viết bài viết này, SlateJS không đưa ra phương pháp

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
116 có thể cung cấp cho chúng tôi trạng thái lựa chọn mới nhất của tài liệu. Tuy nhiên, khi lựa chọn thay đổi trong trình chỉnh sửa, SlateJS sẽ gọi phương thức
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
03, ngay cả khi nội dung tài liệu không thay đổi. Chúng tôi sử dụng điều này như một cách để được thông báo về thay đổi lựa chọn và lưu trữ nó ở trạng thái của thành phần
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
115. Chúng tôi tóm tắt điều này thành một cái móc
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
119 nơi chúng tôi có thể cập nhật trạng thái lựa chọn một cách tối ưu hơn. Điều này rất quan trọng vì lựa chọn là thuộc tính thay đổi khá thường xuyên đối với phiên bản WYSIWYG Editor

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
14

Chúng tôi sử dụng hook này bên trong thành phần

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
115 như bên dưới và chuyển vùng chọn sang thành phần Thanh công cụ

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
15

Xem xét hiệu suất

Trong một ứng dụng mà chúng tôi có cơ sở mã Editor lớn hơn nhiều với nhiều chức năng hơn, điều quan trọng là lưu trữ và lắng nghe các thay đổi lựa chọn theo cách hiệu quả (như sử dụng một số thư viện quản lý trạng thái) vì các thành phần lắng nghe các thay đổi lựa chọn cũng có thể hiển thị . Một cách để làm điều này là có các bộ chọn được tối ưu hóa trên trạng thái Lựa chọn chứa thông tin lựa chọn cụ thể. Chẳng hạn, một trình chỉnh sửa có thể muốn hiển thị menu thay đổi kích thước hình ảnh khi một Hình ảnh được chọn. Trong trường hợp như vậy, có thể hữu ích khi có bộ chọn

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
121 được tính toán từ trạng thái lựa chọn của trình chỉnh sửa và menu Hình ảnh sẽ chỉ hiển thị lại khi giá trị của bộ chọn này thay đổi. Redux's Reselect là một trong những thư viện như vậy cho phép xây dựng bộ chọn

Chúng tôi không sử dụng

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
122 bên trong thanh công cụ cho đến sau này nhưng việc chuyển nó xuống dưới dạng chỗ dựa sẽ khiến thanh công cụ hiển thị lại mỗi khi lựa chọn thay đổi trên Trình chỉnh sửa. Chúng tôi làm điều này bởi vì chúng tôi không thể chỉ dựa vào thay đổi nội dung tài liệu để kích hoạt kết xuất lại trên hệ thống phân cấp (
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
123) vì người dùng có thể tiếp tục nhấp vào tài liệu do đó thay đổi lựa chọn nhưng không bao giờ thực sự thay đổi chính nội dung tài liệu

Chuyển đổi kiểu ký tự

Bây giờ chúng ta chuyển sang lấy các kiểu ký tự đang hoạt động là gì từ SlateJS và sử dụng các kiểu bên trong Trình chỉnh sửa. Hãy thêm một mô-đun JS mới

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
124 sẽ lưu trữ tất cả các chức năng tiện ích mà chúng tôi xây dựng trong tương lai để nhận/làm nội dung với SlateJS. Chức năng đầu tiên của chúng tôi trong mô-đun là
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
125 cung cấp
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
126 kiểu hoạt động trong trình chỉnh sửa. Chúng tôi cũng thêm chức năng chuyển đổi kiểu trên chức năng trình chỉnh sửa —
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
127

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
16

Cả hai hàm đều lấy đối tượng

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
07 là đối tượng Slate làm tham số cũng như rất nhiều hàm sử dụng mà chúng tôi sẽ thêm vào sau trong bài viết. Theo thuật ngữ của Slate, các kiểu định dạng được gọi là Dấu và chúng tôi sử dụng các phương thức trợ giúp trên giao diện Trình chỉnh sửa để lấy, thêm và xóa các dấu này. Chúng tôi nhập các hàm tiện ích này bên trong Thanh công cụ và kết nối chúng với các nút mà chúng tôi đã thêm trước đó

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
17

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
129 là một Slate hook cho phép chúng ta truy cập vào đối tượng Slate từ ngữ cảnh nơi nó được đính kèm bởi thành phần
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
130 ở cấp cao hơn trong hệ thống phân cấp kết xuất

Người ta có thể thắc mắc tại sao chúng tôi sử dụng

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
131 ở đây thay vì
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
132? . Vì vậy, nếu chúng tôi đính kèm trình xử lý
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
132 vào các nút trên thanh công cụ của mình, thì
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
122 sẽ trở thành
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
134 và người dùng sẽ mất vị trí con trỏ khi cố chuyển đổi một kiểu không phải là một trải nghiệm tuyệt vời. Thay vào đó, chúng tôi chuyển đổi kiểu bằng cách đính kèm một sự kiện
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
131 để ngăn lựa chọn bị đặt lại. Một cách khác để làm điều này là tự theo dõi lựa chọn để chúng tôi biết lựa chọn cuối cùng là gì và sử dụng lựa chọn đó để chuyển đổi các kiểu. Chúng tôi giới thiệu khái niệm về
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
139 ở phần sau của bài viết nhưng để giải quyết một vấn đề khác

SlateJS cho phép chúng ta cấu hình event handlers trên Editor. Chúng tôi sử dụng điều đó để kết nối các phím tắt để chuyển đổi các kiểu ký tự. Để làm điều đó, chúng tôi thêm một đối tượng

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
140 bên trong
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
29 nơi chúng tôi hiển thị trình xử lý sự kiện
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
142 được gắn vào thành phần
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
114. Chúng tôi sử dụng tiện ích
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
144 để xác định tổ hợp phím và chuyển đổi kiểu tương ứng

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
18

Kiểu ký tự được bật bằng phím tắt

Làm cho danh sách thả xuống kiểu đoạn văn hoạt động

Hãy chuyển sang làm cho trình đơn thả xuống Kiểu đoạn văn hoạt động. Tương tự như cách trình đơn thả xuống kiểu đoạn văn hoạt động trong các ứng dụng Xử lý văn bản phổ biến như MS Word hoặc Google Docs, chúng tôi muốn các kiểu khối cấp cao nhất trong lựa chọn của người dùng được phản ánh trong trình đơn thả xuống. Nếu có một kiểu nhất quán duy nhất trong toàn bộ lựa chọn, chúng tôi sẽ cập nhật giá trị thả xuống thành kiểu đó. Nếu có nhiều trong số đó, chúng tôi đặt giá trị thả xuống là 'Nhiều'. Hành vi này phải hoạt động cho cả hai — lựa chọn được thu gọn và mở rộng

Để thực hiện hành vi này, chúng tôi cần có khả năng tìm thấy các khối cấp cao nhất bao trùm lựa chọn của người dùng. Để làm như vậy, chúng tôi sử dụng Slate's

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
145 - Một hàm trợ giúp thường được sử dụng để tìm kiếm các nút trong cây được lọc theo các tùy chọn khác nhau

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
19

Hàm trợ giúp lấy một đối tượng Editor và một đối tượng

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
146, đây là một cách để lọc các nút trong cây khi nó đi qua nó. Hàm trả về một trình tạo của
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
147. Một
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
147 trong thuật ngữ Slate là một bộ của một nút và đường dẫn đến nó —
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
149. Các tùy chọn được tìm thấy ở đây có sẵn trên hầu hết các chức năng của trình trợ giúp Slate. Chúng ta hãy đi qua ý nghĩa của mỗi trong số đó

  • // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    150
    Đây có thể là Đường dẫn/Điểm/Phạm vi mà hàm trợ giúp sẽ sử dụng để thu hẹp phạm vi duyệt cây xuống. Điều này mặc định là
    // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    151 nếu không được cung cấp. Chúng tôi cũng sử dụng giá trị mặc định cho trường hợp sử dụng bên dưới vì chúng tôi quan tâm đến các nút trong lựa chọn của người dùng.
  • // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    152
    Đây là một hàm khớp mà người ta có thể cung cấp, được gọi trên mỗi nút và được bao gồm nếu nó khớp. Chúng tôi sử dụng tham số này trong quá trình triển khai bên dưới để chỉ lọc để chặn các phần tử.
  • // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    153
    Hãy để các chức năng của trình trợ giúp biết liệu chúng tôi có quan tâm đến tất cả các nút cấp cao nhất hay cấp thấp nhất
    // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    150 vị trí đã cho phù hợp với chức năng
    // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    152. Tham số này (được đặt thành
    // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    156) giúp chúng tôi thoát khỏi việc cố gắng tự mình đi qua cây để tìm các nút cấp cao nhất.
  • // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    157
    Gắn cờ để chọn giữa các nút phù hợp hoàn toàn hoặc một phần. (Vấn đề GitHub với đề xuất cho cờ này có một số ví dụ giải thích về nó)
  • // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    158
    Nếu tìm kiếm nút phải theo hướng ngược lại với điểm bắt đầu và điểm kết thúc của vị trí được chuyển vào.
  • ______2159
    Nếu tìm kiếm chỉ lọc để loại bỏ các phần tử.

SlateJS hiển thị rất nhiều hàm trợ giúp cho phép bạn truy vấn các nút theo nhiều cách khác nhau, duyệt qua cây, cập nhật các nút hoặc lựa chọn theo những cách phức tạp. Đáng để nghiên cứu một số giao diện này (được liệt kê ở cuối bài viết này) khi xây dựng các chức năng chỉnh sửa phức tạp trên Slate

Với nền tảng đó về chức năng của trình trợ giúp, bên dưới là triển khai của

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
160

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
20

Xem xét hiệu suất

Việc triển khai hiện tại của

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
145 tìm thấy tất cả các nút trong toàn bộ cây ở tất cả các cấp nằm trong phạm vi của tham số
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
150 và sau đó chạy các bộ lọc khớp trên đó (kiểm tra
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
163 và quá trình lọc sau — nguồn). Điều này không sao đối với các tài liệu nhỏ hơn. Tuy nhiên, đối với trường hợp sử dụng của chúng tôi, nếu người dùng đã chọn, giả sử 3 tiêu đề và 2 đoạn văn (mỗi đoạn chứa 10 nút văn bản chẳng hạn), nó sẽ chuyển qua ít nhất 25 nút (3 + 2 + 2*10) và cố gắng chạy các bộ lọc . Vì chúng tôi đã biết rằng chúng tôi chỉ quan tâm đến các nút cấp cao nhất, nên chúng tôi có thể tìm chỉ mục bắt đầu và kết thúc của các khối cấp cao nhất từ ​​vùng chọn và tự lặp lại. Logic như vậy sẽ chỉ lặp qua 3 mục nút (2 tiêu đề và 1 đoạn văn). Mã cho điều đó sẽ trông giống như bên dưới

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
21

Khi chúng tôi thêm nhiều chức năng hơn vào Trình soạn thảo WYSIWYG và cần duyệt qua cây tài liệu thường xuyên, điều quan trọng là phải suy nghĩ về những cách hiệu quả nhất để thực hiện điều đó cho trường hợp sử dụng hiện tại vì API có sẵn hoặc các phương thức trợ giúp có thể không phải lúc nào cũng tốt nhất

Khi chúng tôi đã triển khai

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
160, việc chuyển đổi kiểu khối tương đối đơn giản. Nếu kiểu hiện tại không phải là kiểu người dùng đã chọn trong danh sách thả xuống, chúng tôi sẽ chuyển kiểu đó sang kiểu đó. Nếu nó đã là những gì người dùng đã chọn, chúng tôi chuyển nó thành một đoạn văn. Bởi vì chúng tôi đang biểu diễn các kiểu đoạn văn dưới dạng các nút trong cấu trúc tài liệu của mình, nên việc chuyển đổi kiểu đoạn văn về cơ bản có nghĩa là thay đổi thuộc tính
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
165 trên nút. Chúng tôi sử dụng
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
166 do Slate cung cấp để cập nhật thuộc tính trên các nút

Việc triển khai của

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
167 của chúng tôi như sau

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
22

Cuối cùng, chúng tôi cập nhật trình đơn thả xuống Kiểu đoạn văn để sử dụng các chức năng tiện ích này

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
23

Chọn nhiều loại khối và thay đổi loại bằng danh sách thả xuống

LIÊN KẾT

Trong phần này, chúng tôi sẽ thêm hỗ trợ để hiển thị, thêm, xóa và thay đổi liên kết. Chúng tôi cũng sẽ thêm chức năng Trình phát hiện liên kết — khá giống với cách Google Tài liệu hoặc MS Word quét văn bản do người dùng nhập và kiểm tra xem có liên kết nào trong đó không. Nếu có, chúng sẽ được chuyển đổi thành các đối tượng liên kết để người dùng không phải sử dụng các nút trên thanh công cụ để tự làm điều đó

Kết xuất liên kết

Trong trình chỉnh sửa của chúng tôi, chúng tôi sẽ triển khai các liên kết dưới dạng các nút nội tuyến với SlateJS. Chúng tôi cập nhật cấu hình trình chỉnh sửa của mình để gắn cờ các liên kết dưới dạng các nút nội tuyến cho SlateJS và cũng cung cấp một thành phần để kết xuất để Slate biết cách kết xuất các nút liên kết

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
24
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
25

Sau đó, chúng tôi thêm nút liên kết vào

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
02 của mình và xác minh rằng nút đó hiển thị chính xác (bao gồm trường hợp kiểu ký tự bên trong liên kết) trong Trình chỉnh sửa

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
26
Trình soạn thảo văn bản JavaScript
Liên kết được hiển thị trong Trình chỉnh sửa (Xem trước lớn)

Thêm nút liên kết vào thanh công cụ

Hãy thêm Nút liên kết vào thanh công cụ cho phép người dùng thực hiện các thao tác sau

  • Chọn một số văn bản và nhấp vào nút sẽ chuyển đổi văn bản đó thành một liên kết
  • Có một con trỏ nhấp nháy (lựa chọn thu gọn) và nhấp vào nút sẽ chèn một liên kết mới vào đó
  • Nếu lựa chọn của người dùng nằm trong một liên kết, thì việc nhấp vào nút sẽ chuyển đổi liên kết - nghĩa là chuyển liên kết trở lại thành văn bản

Để xây dựng các chức năng này, chúng tôi cần một cách trên thanh công cụ để biết liệu lựa chọn của người dùng có nằm trong nút liên kết hay không. Chúng tôi thêm một hàm tiện ích đi qua các mức theo hướng đi lên từ lựa chọn của người dùng để tìm nút liên kết nếu có, sử dụng hàm trợ giúp

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
169 từ SlateJS

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
27

Bây giờ, hãy thêm một nút vào thanh công cụ đang ở trạng thái hoạt động nếu lựa chọn của người dùng nằm trong nút liên kết

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
28

Nút liên kết trong Thanh công cụ sẽ hoạt động nếu lựa chọn nằm trong một liên kết

Để chuyển đổi các liên kết trong trình chỉnh sửa, chúng tôi thêm một chức năng sử dụng

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
170. Trước tiên, hãy xem cách chuyển đổi hoạt động khi bạn chọn một số văn bản. Khi người dùng chọn một số văn bản và nhấp vào nút, chúng tôi chỉ muốn văn bản đã chọn trở thành một liên kết. Điều này vốn có nghĩa là chúng ta cần ngắt nút văn bản chứa văn bản đã chọn và trích xuất văn bản đã chọn vào một nút liên kết mới. Trạng thái trước và sau của những trạng thái này sẽ giống như bên dưới

Trình soạn thảo văn bản JavaScript
Cấu trúc nút Trước và Sau sau khi liên kết được chèn. (Xem trước lớn)

Nếu chúng tôi phải tự làm điều này, chúng tôi sẽ phải tìm ra phạm vi lựa chọn và tạo ba nút mới (văn bản, liên kết, văn bản) thay thế nút văn bản gốc. SlateJS có một hàm trợ giúp có tên là

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
171 thực hiện chính xác điều này — gói các nút tại một vị trí vào một nút vùng chứa mới. Chúng tôi cũng có sẵn một trình trợ giúp để đảo ngược quy trình này —
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
172 mà chúng tôi sử dụng để xóa các liên kết khỏi văn bản đã chọn và hợp nhất văn bản đó trở lại vào các nút văn bản xung quanh nó. Cùng với đó,
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
170 có triển khai bên dưới để chèn liên kết mới vào lựa chọn mở rộng

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
29

Nếu lựa chọn bị thu gọn, chúng tôi sẽ chèn một nút mới vào đó với

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
174 để chèn nút vào vị trí đã cho trong tài liệu. Chúng tôi kết nối chức năng này với nút trên thanh công cụ và bây giờ sẽ có cách thêm/xóa liên kết khỏi tài liệu với sự trợ giúp của nút liên kết

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
70

Trình chỉnh sửa liên kết

Cho đến nay, trình chỉnh sửa của chúng tôi có một cách để thêm và xóa các liên kết nhưng chúng tôi không có cách nào để cập nhật các URL được liên kết với các liên kết này. Chúng ta có thể mở rộng trải nghiệm người dùng để cho phép người dùng chỉnh sửa dễ dàng bằng menu ngữ cảnh không? . Hãy bắt đầu với việc xây dựng một thành phần

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
175 trống và hiển thị nó bất cứ khi nào lựa chọn của người dùng nằm trong một liên kết

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
71
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
72

Vì chúng tôi đang hiển thị

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
175 bên ngoài trình chỉnh sửa, nên chúng tôi cần một cách để cho biết vị trí của liên kết
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
175 trong cây DOM để nó có thể tự hiển thị gần trình chỉnh sửa. Cách chúng tôi làm điều này là sử dụng API React của Slate để tìm nút DOM tương ứng với nút liên kết được chọn. Và sau đó, chúng tôi sử dụng
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
178 để tìm giới hạn của phần tử DOM của liên kết và giới hạn của thành phần trình chỉnh sửa và tính toán
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
179 và
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
180 cho trình chỉnh sửa liên kết. Các bản cập nhật mã cho
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
115 và
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
175 như sau —

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
73
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
74

SlateJS duy trì nội bộ bản đồ của các nút tới các phần tử DOM tương ứng của chúng. Chúng tôi truy cập bản đồ đó và tìm phần tử DOM của liên kết bằng cách sử dụng

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
183

Lựa chọn bên trong một liên kết hiển thị cửa sổ bật lên của trình chỉnh sửa liên kết

Như đã thấy trong video ở trên, khi một liên kết được chèn vào và không có URL, vì vùng chọn nằm bên trong liên kết, nó sẽ mở trình chỉnh sửa liên kết, do đó cung cấp cho người dùng cách nhập URL cho liên kết mới được chèn và

Bây giờ chúng tôi thêm một yếu tố đầu vào và một nút vào

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
175 để cho phép người dùng nhập URL và áp dụng nó vào nút liên kết. Chúng tôi sử dụng gói
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
185 để xác thực URL

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
75

Với các phần tử biểu mẫu được kết nối, hãy xem trình chỉnh sửa liên kết có hoạt động như mong đợi không

Trình chỉnh sửa mất lựa chọn khi nhấp vào bên trong trình chỉnh sửa liên kết

Như chúng ta thấy ở đây trong video, khi người dùng cố gắng nhấp vào đầu vào, trình chỉnh sửa liên kết sẽ biến mất. Điều này là do khi chúng tôi kết xuất trình chỉnh sửa liên kết bên ngoài thành phần

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
114, khi người dùng nhấp vào phần tử đầu vào, SlateJS cho rằng trình chỉnh sửa đã mất tiêu điểm và đặt lại
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
122 thành
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
134, loại bỏ
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
175 vì
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
190 không còn là
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
191 nữa. Có một Vấn đề GitHub mở nói về hành vi Slate này. Một cách để giải quyết vấn đề này là theo dõi lựa chọn trước đó của người dùng khi nó thay đổi và khi trình chỉnh sửa mất tiêu điểm, chúng tôi có thể xem lựa chọn trước đó và vẫn hiển thị menu trình chỉnh sửa liên kết nếu lựa chọn trước đó có liên kết trong đó. Hãy cập nhật hook
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
119 để ghi nhớ lựa chọn trước đó và đưa nó trở lại thành phần Editor

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
76

Sau đó, chúng tôi cập nhật logic trong thành phần

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
115 để hiển thị menu liên kết ngay cả khi lựa chọn trước đó có liên kết trong đó

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
77

Sau đó, chúng tôi cập nhật

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
175 để sử dụng
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
195 để tra cứu nút liên kết, hiển thị bên dưới và cập nhật URL của nút đó

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
78

Chỉnh sửa liên kết bằng thành phần LinkEditor

Phát hiện liên kết trong văn bản

Hầu hết các ứng dụng xử lý văn bản xác định và chuyển đổi các liên kết bên trong văn bản thành các đối tượng liên kết. Hãy xem nó sẽ hoạt động như thế nào trong trình chỉnh sửa trước khi chúng ta bắt đầu xây dựng nó

Các liên kết được phát hiện khi người dùng nhập chúng vào

Các bước logic để kích hoạt hành vi này sẽ là

  1. Khi tài liệu thay đổi khi người dùng nhập, hãy tìm ký tự cuối cùng do người dùng chèn vào. Nếu ký tự đó là một khoảng trắng, chúng tôi biết phải có một từ có thể xuất hiện trước nó
  2. Nếu ký tự cuối cùng là khoảng trắng, chúng tôi đánh dấu đó là ranh giới cuối của từ đứng trước nó. Sau đó, chúng tôi duyệt lại từng ký tự bên trong nút văn bản để tìm từ đó bắt đầu từ đâu. Trong quá trình truyền tải này, chúng ta phải cẩn thận để không đi qua cạnh của nút bắt đầu vào nút trước đó
  3. Khi chúng tôi đã tìm thấy ranh giới bắt đầu và kết thúc của từ trước đó, chúng tôi kiểm tra chuỗi của từ và xem đó có phải là URL không. Nếu đúng như vậy, chúng tôi chuyển đổi nó thành một nút liên kết

Logic của chúng ta nằm trong một hàm tiện ích

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
196 nằm trong
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
124 và được gọi bên trong thành phần
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
03 trong thành phần
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
115

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
79

Đây là

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
196 với logic cho Bước 1 đã được triển khai

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
50

Có hai hàm trợ giúp SlateJS giúp mọi thứ trở nên dễ dàng ở đây

  • // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    201 — Cung cấp cho chúng tôi điểm trước một vị trí nhất định. Nó lấy
    // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    202 làm tham số để chúng tôi có thể yêu cầu ký tự/từ/khối, v.v. trước khi
    // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    203 được chuyển vào
  • // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    204 — Lấy chuỗi bên trong một phạm vi

Ví dụ, sơ đồ bên dưới giải thích giá trị của các biến này là gì khi người dùng chèn một ký tự 'E' và con trỏ của họ đang ở sau ký tự đó

Trình soạn thảo văn bản JavaScript
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
205 và
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
206 sau Bước 1 với văn bản mẫu. (Xem trước lớn)

Nếu văn bản 'ABCDE' là nút văn bản đầu tiên của đoạn đầu tiên trong tài liệu, thì các giá trị điểm của chúng tôi sẽ là -

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
51

Nếu ký tự cuối cùng là một khoảng trắng, chúng tôi biết nó bắt đầu từ đâu —

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
207Hãy chuyển sang bước 2, nơi chúng tôi di chuyển ngược lại từng ký tự cho đến khi chúng tôi tìm thấy một khoảng trắng khác hoặc bắt đầu chính nút văn bản

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
52

Đây là một sơ đồ cho thấy những điểm khác nhau này trỏ đến đâu khi chúng ta tìm thấy từ cuối cùng được nhập là

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
208

Trình soạn thảo văn bản JavaScript
Trường hợp các điểm khác nhau sau bước 2 của phát hiện liên kết với một ví dụ. (Xem trước lớn)

Lưu ý rằng

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
209 và
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
210 là các điểm trước và sau khoảng trắng ở đó. Tương tự,
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
206 và
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
205 là các điểm trước và sau dấu cách người dùng vừa chèn. Do đó,
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
213 cho chúng ta từ cuối cùng được chèn vào

Chúng tôi ghi giá trị của

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
214 vào bảng điều khiển và xác minh các giá trị khi chúng tôi nhập

Nhật ký bảng điều khiển xác minh từ cuối cùng do người dùng nhập sau logic ở Bước 2

Bây giờ chúng tôi đã suy ra từ cuối cùng mà người dùng đã nhập, chúng tôi xác minh rằng đó thực sự là một URL và chuyển đổi phạm vi đó thành một đối tượng liên kết. Chuyển đổi này tương tự như cách nút liên kết thanh công cụ chuyển đổi văn bản đã chọn của người dùng thành liên kết

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
53

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
196 được gọi bên trong
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
03 của Slate, vì vậy chúng tôi không muốn cập nhật cấu trúc tài liệu bên trong
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
03. Do đó, chúng tôi đưa bản cập nhật này vào hàng đợi tác vụ của mình bằng lệnh gọi
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
218

Hãy xem logic kết hợp với nhau trong hành động. Chúng tôi xác minh xem chúng tôi có chèn liên kết ở cuối, ở giữa hoặc đầu nút văn bản hay không

Liên kết được phát hiện khi người dùng đang gõ chúng

Cùng với đó, chúng tôi đã tổng hợp các chức năng cho các liên kết trên trình chỉnh sửa và chuyển sang Hình ảnh

Xử lý hình ảnh

Trong phần này, chúng tôi tập trung vào việc thêm hỗ trợ để kết xuất các nút hình ảnh, thêm hình ảnh mới và cập nhật chú thích hình ảnh. Hình ảnh, trong cấu trúc tài liệu của chúng tôi, sẽ được biểu diễn dưới dạng các nút Void. Các nút trống trong SlateJS (tương tự như các phần tử Void trong thông số HTML) sao cho nội dung của chúng không phải là văn bản có thể chỉnh sửa. Điều đó cho phép chúng tôi hiển thị hình ảnh dưới dạng khoảng trống. Do tính linh hoạt của Slate với kết xuất, chúng tôi vẫn có thể kết xuất các phần tử có thể chỉnh sửa của riêng mình bên trong các phần tử Void — điều mà chúng tôi sẽ thực hiện để chỉnh sửa chú thích hình ảnh. SlateJS có một ví dụ minh họa cách bạn có thể nhúng toàn bộ Trình soạn thảo văn bản đa dạng thức vào bên trong phần tử Void

Để hiển thị hình ảnh, chúng tôi định cấu hình trình chỉnh sửa để coi hình ảnh là phần tử Void và cung cấp triển khai kết xuất về cách hình ảnh sẽ được hiển thị. Chúng tôi thêm một hình ảnh vào ExampleDocument của mình và xác minh rằng hình ảnh đó hiển thị chính xác với chú thích

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
54

Hai điều cần nhớ khi cố gắng kết xuất các nút trống với SlateJS

  • Phần tử DOM gốc phải có
    // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    219 được đặt trên đó để SlateJS xử lý nội dung của nó sao cho. Không có điều này, khi bạn tương tác với phần tử void, SlateJS có thể cố gắng tính toán các lựa chọn, v.v. và kết quả là phá vỡ
  • Ngay cả khi các nút Void không có bất kỳ nút con nào (ví dụ như nút hình ảnh của chúng tôi), chúng tôi vẫn cần hiển thị
    // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    27 và cung cấp một nút văn bản trống khi còn nhỏ (xem
    // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    02 bên dưới) được coi là điểm lựa chọn của Void

Bây giờ, chúng tôi cập nhật

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
02 để thêm hình ảnh và xác minh rằng hình ảnh đó hiển thị cùng với chú thích trong trình chỉnh sửa

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
55
Trình soạn thảo văn bản JavaScript
Hình ảnh được hiển thị trong Trình chỉnh sửa. (Xem trước lớn)

Bây giờ hãy tập trung vào chỉnh sửa phụ đề. Cách chúng tôi muốn điều này trở thành trải nghiệm liền mạch cho người dùng là khi họ nhấp vào chú thích, chúng tôi sẽ hiển thị kiểu nhập văn bản để họ có thể chỉnh sửa chú thích. Nếu họ nhấp vào bên ngoài đầu vào hoặc nhấn phím RETURN, chúng tôi coi đó là xác nhận để áp dụng chú thích. Sau đó, chúng tôi cập nhật chú thích trên nút hình ảnh và chuyển chú thích trở lại chế độ đọc. Hãy xem nó hoạt động để chúng tôi có ý tưởng về những gì chúng tôi đang xây dựng

Chỉnh sửa chú thích hình ảnh đang hoạt động

Hãy cập nhật thành phần Hình ảnh của chúng ta để có trạng thái cho chế độ đọc-chỉnh sửa phụ đề. Chúng tôi cập nhật trạng thái phụ đề cục bộ khi người dùng cập nhật nó và khi họ nhấp ra (

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
223) hoặc nhấn RETURN (
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
142), chúng tôi áp dụng phụ đề cho nút và chuyển lại chế độ đọc

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
56

Như vậy là xong chức năng chỉnh sửa phụ đề. Bây giờ chúng tôi chuyển sang thêm một cách để người dùng tải hình ảnh lên trình chỉnh sửa. Hãy thêm một nút trên thanh công cụ cho phép người dùng chọn và tải lên một hình ảnh

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
57

Khi chúng tôi làm việc với việc tải lên hình ảnh, mã có thể phát triển thêm một chút, vì vậy chúng tôi chuyển phần xử lý tải lên hình ảnh sang một hook

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
225 đưa ra một lệnh gọi lại được đính kèm với phần tử đầu vào tệp. Chúng ta sẽ thảo luận ngay về lý do tại sao nó cần trạng thái
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
139

Trước khi chúng tôi triển khai

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
225, chúng tôi sẽ thiết lập máy chủ để có thể tải hình ảnh lên. Chúng tôi thiết lập một máy chủ Express và cài đặt hai gói khác —
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
228 và
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
229 để xử lý tệp tải lên cho chúng tôi

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
58

Sau đó, chúng tôi thêm tập lệnh

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
230 để định cấu hình máy chủ Express bằng cors và multer và hiển thị điểm cuối
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
231 mà chúng tôi sẽ tải hình ảnh lên

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
59

Bây giờ chúng tôi đã thiết lập máy chủ, chúng tôi có thể tập trung vào việc xử lý tải lên hình ảnh. Khi người dùng tải lên một hình ảnh, có thể mất vài giây trước khi hình ảnh được tải lên và chúng tôi có một URL cho hình ảnh đó. Tuy nhiên, chúng tôi làm những gì để cung cấp cho người dùng phản hồi ngay lập tức rằng quá trình tải lên hình ảnh đang diễn ra để họ biết rằng hình ảnh đang được chèn vào trình chỉnh sửa. Dưới đây là các bước chúng tôi thực hiện để làm cho hành vi này hoạt động -

  1. Sau khi người dùng chọn một hình ảnh, chúng tôi sẽ chèn một nút hình ảnh vào vị trí con trỏ của người dùng với cờ
    // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    232 được đặt trên đó để chúng tôi có thể hiển thị cho người dùng trạng thái tải
  2. Chúng tôi gửi yêu cầu đến máy chủ để tải hình ảnh lên
  3. Sau khi yêu cầu hoàn tất và chúng tôi có URL hình ảnh, chúng tôi sẽ đặt URL đó trên hình ảnh và xóa trạng thái tải

Hãy bắt đầu với bước đầu tiên, nơi chúng tôi chèn nút hình ảnh. Bây giờ, phần phức tạp ở đây là chúng ta gặp vấn đề tương tự với lựa chọn như với nút liên kết trên thanh công cụ. Ngay khi người dùng nhấp vào nút Hình ảnh trên thanh công cụ, trình chỉnh sửa sẽ mất tiêu điểm và lựa chọn trở thành

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
134. Nếu chúng tôi cố gắng chèn một hình ảnh, chúng tôi không biết con trỏ của người dùng ở đâu. Theo dõi
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
139 cung cấp cho chúng tôi vị trí đó và chúng tôi sử dụng vị trí đó để chèn nút

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
0

Khi chúng tôi chèn nút hình ảnh mới, chúng tôi cũng gán cho nó một mã định danh

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
235 bằng cách sử dụng gói uuid. Chúng ta sẽ thảo luận về việc triển khai Bước (3) tại sao chúng ta cần điều đó. Bây giờ chúng tôi cập nhật thành phần hình ảnh để sử dụng cờ
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
232 để hiển thị trạng thái tải

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
1

Như vậy là hoàn thành việc thực hiện bước 1. Hãy xác minh rằng chúng tôi có thể chọn một hình ảnh để tải lên, xem nút hình ảnh được chèn với chỉ báo tải ở nơi nó được chèn vào tài liệu

Tải lên hình ảnh tạo một nút hình ảnh với trạng thái tải

Sang Bước (2) chúng ta sẽ sử dụng thư viện axios để gửi request lên server

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
2

Chúng tôi xác minh rằng hình ảnh tải lên hoạt động và hình ảnh hiển thị trong thư mục

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
237 của ứng dụng. Bây giờ quá trình tải lên hình ảnh đã hoàn tất, chúng tôi chuyển sang Bước (3) nơi chúng tôi muốn đặt URL trên hình ảnh trong chức năng
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
238 của lời hứa axios. Chúng tôi có thể cập nhật hình ảnh với
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
166 nhưng chúng tôi gặp sự cố — chúng tôi không có đường dẫn đến nút hình ảnh mới được chèn. Hãy xem các tùy chọn của chúng tôi là gì để có được hình ảnh đó -

  • Chúng ta không thể sử dụng
    // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    151 vì vùng chọn phải nằm trên nút hình ảnh mới được chèn sao?
  • Làm thế nào về việc sử dụng
    // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    139 mà chúng tôi đã sử dụng để chèn nút hình ảnh ở vị trí đầu tiên?
  • SlateJS có một mô-đun Lịch sử theo dõi tất cả các thay đổi xảy ra với tài liệu. Chúng tôi có thể sử dụng mô-đun này để tìm kiếm lịch sử và tìm nút hình ảnh được chèn cuối cùng. Điều này cũng không hoàn toàn đáng tin cậy nếu hình ảnh tải lên mất nhiều thời gian hơn và người dùng đã chèn nhiều hình ảnh hơn vào các phần khác nhau của tài liệu trước khi quá trình tải lên đầu tiên hoàn tất
  • Hiện tại, API của
    // dependencies imported as below.
    import { withReact } from "slate-react";
    import { createEditor } from "slate";
    
    const editor = useMemo(() => withReact(createEditor()), []);
    
    244 không trả về bất kỳ thông tin nào về các nút được chèn. Nếu nó có thể trả về đường dẫn đến các nút được chèn, chúng tôi có thể sử dụng nó để tìm nút hình ảnh chính xác mà chúng tôi nên cập nhật

Vì không có phương pháp nào ở trên hoạt động, chúng tôi áp dụng một

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
235 cho nút hình ảnh được chèn (ở Bước (1)) và sử dụng lại cùng một
// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
235 để xác định vị trí của nó khi quá trình tải lên hình ảnh hoàn tất. Cùng với đó, mã của chúng tôi cho Bước (3) trông giống như bên dưới —

// dependencies imported as below.
import { withReact } from "slate-react";
import { createEditor } from "slate";

const editor = useMemo(() => withReact(createEditor()), []);
3

Với việc thực hiện hoàn tất cả ba bước, chúng tôi đã sẵn sàng để kiểm tra việc tải lên hình ảnh từ đầu đến cuối

Tải lên hình ảnh hoạt động từ đầu đến cuối

Cùng với đó, chúng tôi đã hoàn thành Hình ảnh cho trình chỉnh sửa của mình. Hiện tại, chúng tôi hiển thị trạng thái tải có cùng kích thước bất kể hình ảnh. Đây có thể là một trải nghiệm khó chịu cho người dùng nếu trạng thái tải được thay thế bằng một hình ảnh lớn hơn hoặc nhỏ hơn nhiều khi quá trình tải lên hoàn tất. Một cách tốt để theo dõi trải nghiệm tải lên là lấy kích thước hình ảnh trước khi tải lên và hiển thị trình giữ chỗ có kích thước đó để quá trình chuyển đổi diễn ra liền mạch. Móc mà chúng tôi thêm ở trên có thể được mở rộng để hỗ trợ các loại phương tiện khác như video hoặc tài liệu và cũng hiển thị các loại nút đó

Sự kết luận

Trong bài viết này, chúng tôi đã xây dựng Trình chỉnh sửa WYSIWYG có bộ chức năng cơ bản và một số trải nghiệm người dùng nhỏ như phát hiện liên kết, chỉnh sửa liên kết tại chỗ và chỉnh sửa chú thích hình ảnh giúp chúng tôi tìm hiểu sâu hơn về SlateJS và các khái niệm về Chỉnh sửa văn bản có định dạng trong . Nếu không gian vấn đề xung quanh Chỉnh sửa văn bản có định dạng hoặc Xử lý văn bản này khiến bạn quan tâm, thì một số vấn đề thú vị cần giải quyết có thể là

  • Sự hợp tác
  • Trải nghiệm chỉnh sửa văn bản phong phú hơn hỗ trợ sắp xếp văn bản, hình ảnh nội tuyến, sao chép-dán, thay đổi phông chữ và màu văn bản, v.v.
  • Nhập từ các định dạng phổ biến như tài liệu Word và Markdown

Nếu bạn muốn tìm hiểu thêm về SlateJS, đây là một số liên kết có thể hữu ích

  • Ví dụ về SlateJS
    Rất nhiều ví dụ vượt xa những điều cơ bản và xây dựng các chức năng thường thấy trong Trình chỉnh sửa như Tìm kiếm & Đánh dấu, Xem trước Markdown và Đề cập.
  • Tài liệu API
    Tham chiếu đến rất nhiều chức năng của trình trợ giúp được SlateJS đưa ra mà người ta có thể muốn sử dụng khi cố gắng thực hiện các truy vấn/chuyển đổi phức tạp trên các đối tượng SlateJS.

Cuối cùng, Kênh Slack của SlateJS là một cộng đồng rất tích cực gồm các nhà phát triển web đang xây dựng các ứng dụng Chỉnh sửa văn bản đa dạng thức bằng SlateJS và là nơi tuyệt vời để tìm hiểu thêm về thư viện và nhận trợ giúp nếu cần

Tôi có thể viết JavaScript trong trình soạn thảo văn bản không?

Bạn có thể viết JavaScript trên bất kỳ trình soạn thảo nào giống như Ruby rồi dán vào Bảng điều khiển JS .

Notepad ++ có tốt cho JavaScript không?

Nếu bạn là người mới bắt đầu và muốn tìm cách tiếp cận đơn giản để viết chương trình JavaScript, hãy sử dụng trình soạn thảo văn bản đơn giản như Notepad++. Nó là mã nguồn mở, miễn phí và sẽ hoạt động tốt để phát triển JavaScript . Notepad++ là trình soạn thảo đa năng hỗ trợ làm nổi bật cú pháp của mã JavaScript và HTML.

Tôi nên sử dụng trình soạn thảo nào cho JavaScript?

Visual Studio Code là trình chỉnh sửa mã hợp lý của Microsoft, hỗ trợ các hoạt động phát triển như gỡ lỗi, kiểm soát phiên bản và chạy tác vụ. Đây là một trong những trình soạn thảo mã phổ biến và đáng tin cậy nhất dành cho JavaScript.

Trình chỉnh sửa JavaScript miễn phí tốt nhất là gì?

Các IDE JavaScript tốt nhất có thể đáng để bạn chú ý .
Văn bản tuyệt vời. .
nhật thực. .
Bảng anh sang. .
tôm mã. .
Văn bản RJ. Một tùy chọn tiếp theo để phát triển web. .
NetBeans. Một công cụ giám sát mã mạnh mẽ. .
MãLite. Một IDE mã nguồn mở cho JS, C, C++ và PHP. .
Đám mây AWS9. IDE JavaScript dựa trên đám mây để phát triển web