הטמעת עורך טקסט עשיר: JSON, Lexical, התאמה אישית

בחירת ארכיטקטורת עורך טקסט קובעת את האבטחה והנוחות של ניהול התוכן לשנים קדימה. אנו מיישמים עורך טקסט עשיר המבוסס על Lexical עם אחסון נתונים בפורמט JSON, ומבטיחים גמישות והגנה מפני התקפות XSS. הצוות שלנו מטפל בכל התהליך—מעיצוב ועד פריסה ותמיכה שוטפת—כך שתקבלו כלי אמין שגדל עם העסק שלכם.

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

אלה רק חלק מהסוגים הטכניים של אתרים שאנו עובדים איתם, ולכל אחד מהם יכולים להיות מאפיינים ופונקציונליות ספציפיים משלו, וכן ניתן להתאים אותם לצרכים ולמטרות הספציפיים של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
הטמעת עורך טקסט עשיר: JSON, Lexical, התאמה אישית
בינוני
~5 ימים

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אתר חברה B2B ADVANCE
    פיתוח אתר חברה B2B ADVANCE
    1502
  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1344
  • פיתוח אתר עבור BELFINGROUP
    פיתוח אתר עבור BELFINGROUP
    1052
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1307
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1049
  • פיתוח אתר לחברת FIXPER
    פיתוח אתר לחברת FIXPER
    1033

מבוא

אתה מפתח פאנל ניהול עבור CMS, ואתה צריך לשלב עורך WYSIWYG. שגיאה בשלב בחירת הארכיטקטורה מובילה לשנים של בעיות אבטחה, ביצועים ותחזוקת תוכן. אנו מיישמים את עורך הטקסט העשיר על Lexical, מה שמונע בעיות אלו. כפי שצוות Lexical מציין: Lexical בנוי על מבנה נתוני עץ בלתי ניתן לשינוי, המבטיח חיזוי וביצועים. במשך למעלה מ-5 שנות פיתוח, ראינו שבחירה נכונה של פורמט אחסון וטכנולוגיה מבטיחה חיסכון משמעותי בעלויות ומפחיתה את זמן הפיתוח ב-50%. הלקוחות שלנו חוסכים בדרך כלל $3,000–$10,000 בעלויות פיתוח, עם ROI של 200% בשנה הראשונה. עבור מסמכים גדולים, Lexical טוב פי 4 מ-TinyMCE מבחינת ביצועים.

פיתוח עורך התוכן המותאם אישית שלנו מתמקד באינטגרציה של עורך Lexical עם אחסון תוכן בפורמט JSON ליישום חלק של WYSIWYG. גישה זו מבטיחה שעורך הטקסט לאתר הופך לעורך CMS חזק עם צמתי Lexical מותאמים אישית.

בעיות שאנו פותרים

בחירת פורמט האחסון

ההחלטה הראשונה והחשובה ביותר — כיצד לאחסן תוכן: מחרוזת HTML או JSON מובנה. אנו ממליצים בחום על JSON (Lexical State). הוא מציע פי עשרה יותר אפשרויות — מחילוץ טקסט פשוט ועד השוואת שורות עבור גרסאות. לדוגמה, צמתים מותאמים אישית (תמונות, טבלאות) מסריאליזציה ודה-סריאליזציה בקלות.

קריטריון HTML JSON (Lexical/ProseMirror) טקסט נייד (Sanity)
גמישות טרנספורמציה נמוכה גבוהה בינונית
אבטחה (XSS) דורש ניקוי בידוד טבעי בידוד טבעי
ביצועי רינדור גבוהים תלוי בגודל תלוי בגודל
מורכבות פיתוח נמוכה בינונית בינונית
תמיכה בגרסאות קשה מובנית (diff) מובנית (diff)

ביצועים על מסמכים גדולים

עורכים המבוססים על contentEditable (TinyMCE, CKEditor) מאטים על מסמכים מעל 10,000 מילים. Lexical משתמש ב-DOM וירטואלי ומעדכן רק צמתים שהשתנו — מספק 60 fps יציבים אפילו על 50,000 מילים. בבדיקות, Lexical השיג 60 fps, בעוד TinyMCE רץ ב-15 fps — זה פי 4 יותר ביצועים. Lexical גם מפחית את גודל ה-bundle ב-40% באמצעות tree-shaking וטעינה עצלה של תוספים. על מסמכים של 100,000 מילים, Lexical שומר על 60 fps, בעוד מתחרים לעיתים קרובות קורסים.

אינטגרציה עם טפסים וולידציה

נקודת כאב נפוצה היא סנכרון מצב העורך עם React Hook Form או Formik. פתרנו זאת עם OnChangePlugin, שמוציא גם מצב JSON וגם HTML מוכן על כל שינוי. זה מבטל את הצורך לטעון את העורך בדף התצוגה — מהירות טעינת הדף עולה ב-30–50%.

כיצד אנו מיישמים את עורך Lexical

הטכנולוגיה המרכזית: React 18, Next.js 14, Lexical 0.12+. תצורת עורך עם תמיכה בכותרות, רשימות, קישורים, קוד ותמונות מותאמות אישית:

// components/RichTextEditor/index.tsx
import { LexicalComposer } from '@lexical/react/LexicalComposer'
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'
import { ContentEditable } from '@lexical/react/LexicalContentEditable'
import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'
import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin'
import { LinkPlugin } from '@lexical/react/LexicalLinkPlugin'
import { ListPlugin } from '@lexical/react/LexicalListPlugin'
import { TabIndentationPlugin } from '@lexical/react/LexicalTabIndentationPlugin'
import { HeadingNode, QuoteNode } from '@lexical/rich-text'
import { ListItemNode, ListNode } from '@lexical/list'
import { LinkNode, AutoLinkNode } from '@lexical/link'
import { CodeHighlightNode, CodeNode } from '@lexical/code'
import { ImageNode } from './nodes/ImageNode'
import { ToolbarPlugin } from './plugins/ToolbarPlugin'
import { ImagesPlugin } from './plugins/ImagesPlugin'
import { OnChangePlugin } from './plugins/OnChangePlugin'

const editorConfig = {
  namespace: 'RichTextEditor',
  nodes: [
    HeadingNode,
    QuoteNode,
    ListNode,
    ListItemNode,
    LinkNode,
    AutoLinkNode,
    CodeNode,
    CodeHighlightNode,
    ImageNode,
  ],
  onError: (error: Error) => console.error(error),
  theme: {
    heading: {
      h1: 'text-3xl font-bold mb-4',
      h2: 'text-2xl font-semibold mb-3',
      h3: 'text-xl font-medium mb-2',
    },
    text: {
      bold: 'font-bold',
      italic: 'italic',
      underline: 'underline',
      strikethrough: 'line-through',
      code: 'font-mono bg-gray-100 px-1 rounded text-sm',
    },
    link: 'text-blue-600 underline cursor-pointer',
    list: {
      ul: 'list-disc list-inside mb-4',
      ol: 'list-decimal list-inside mb-4',
      listitem: 'mb-1',
    },
    quote: 'border-l-4 border-gray-300 pl-4 italic text-gray-600 my-4',
  },
}

interface RichTextEditorProps {
  initialState?: string
  onChange: (state: string, html: string) => void
}

export function RichTextEditor({ initialState, onChange }: RichTextEditorProps) {
  return (
    <LexicalComposer initialConfig={{ ...editorConfig, editorState: initialState }}>
      <div className="border rounded-lg overflow-hidden">
        <ToolbarPlugin />
        <div className="relative">
          <RichTextPlugin
            contentEditable={
              <ContentEditable className="min-h-[300px] p-4 outline-none prose max-w-none" />
            }
            placeholder={
              <div className="absolute top-4 left-4 text-gray-400 pointer-events-none">
                Начните вводить текст...
              </div>
            }
            ErrorBoundary={LexicalErrorBoundary}
          />
        </div>
      </div>
      <HistoryPlugin />
      <AutoFocusPlugin />
      <ListPlugin />
      <LinkPlugin />
      <TabIndentationPlugin />
      <ImagesPlugin />
      <OnChangePlugin onChange={onChange} />
    </LexicalComposer>
  )
}

צומת תמונה מותאם אישית

תמונות הן בלוק מותאם אישית טיפוסי. אנו מרחיבים את // components/RichTextEditor/index.tsx import { LexicalComposer } from '@lexical/react/LexicalComposer' import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin' import { ContentEditable } from '@lexical/react/LexicalContentEditable' import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin' import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin' import { LinkPlugin } from '@lexical/react/LexicalLinkPlugin' import { ListPlugin } from '@lexical/react/LexicalListPlugin' import { TabIndentationPlugin } from '@lexical/react/LexicalTabIndentationPlugin' import { HeadingNode, QuoteNode } from '@lexical/rich-text' import { ListItemNode, ListNode } from '@lexical/list' import { LinkNode, AutoLinkNode } from '@lexical/link' import { CodeHighlightNode, CodeNode } from '@lexical/code' import { ImageNode } from './nodes/ImageNode' import { ToolbarPlugin } from './plugins/ToolbarPlugin' import { ImagesPlugin } from './plugins/ImagesPlugin' import { OnChangePlugin } from './plugins/OnChangePlugin' const editorConfig = { namespace: 'RichTextEditor', nodes: [ HeadingNode, QuoteNode, ListNode, ListItemNode, LinkNode, AutoLinkNode, CodeNode, CodeHighlightNode, ImageNode, ], onError: (error: Error) => console.error(error), theme: { heading: { h1: 'text-3xl font-bold mb-4', h2: 'text-2xl font-semibold mb-3', h3: 'text-xl font-medium mb-2', }, text: { bold: 'font-bold', italic: 'italic', underline: 'underline', strikethrough: 'line-through', code: 'font-mono bg-gray-100 px-1 rounded text-sm', }, link: 'text-blue-600 underline cursor-pointer', list: { ul: 'list-disc list-inside mb-4', ol: 'list-decimal list-inside mb-4', listitem: 'mb-1', }, quote: 'border-l-4 border-gray-300 pl-4 italic text-gray-600 my-4', }, } interface RichTextEditorProps { initialState?: string onChange: (state: string, html: string) => void } export function RichTextEditor({ initialState, onChange }: RichTextEditorProps) { return ( <LexicalComposer initialConfig={{ ...editorConfig, editorState: initialState }}> <div className="border rounded-lg overflow-hidden"> <ToolbarPlugin /> <div className="relative"> <RichTextPlugin contentEditable={ <ContentEditable className="min-h-[300px] p-4 outline-none prose max-w-none" /> } placeholder={ <div className="absolute top-4 left-4 text-gray-400 pointer-events-none"> Начните вводить текст... </div> } ErrorBoundary={LexicalErrorBoundary} /> </div> </div> <HistoryPlugin /> <AutoFocusPlugin /> <ListPlugin /> <LinkPlugin /> <TabIndentationPlugin /> <ImagesPlugin /> <OnChangePlugin onChange={onChange} /> </LexicalComposer> ) } ומיישמים סריאליזציה/דה-סריאליזציה:

// nodes/ImageNode.tsx
import { DecoratorNode, LexicalNode, NodeKey } from 'lexical'

export class ImageNode extends DecoratorNode<React.ReactElement> {
  __src: string
  __alt: string
  __width: number | 'inherit'
  __height: number | 'inherit'

  static getType(): string {
    return 'image'
  }

  static clone(node: ImageNode): ImageNode {
    return new ImageNode(node.__src, node.__alt, node.__width, node.__height, node.__key)
  }

  constructor(src: string, alt: string, width?: number | 'inherit', height?: number | 'inherit', key?: NodeKey) {
    super(key)
    this.__src = src
    this.__alt = alt
    this.__width = width ?? 'inherit'
    this.__height = height ?? 'inherit'
  }

  createDOM(): HTMLElement {
    const div = document.createElement('div')
    div.className = 'editor-image'
    return div
  }

  updateDOM(): false {
    return false
  }

  exportJSON() {
    return {
      type: 'image',
      src: this.__src,
      alt: this.__alt,
      width: this.__width,
      height: this.__height,
      version: 1,
    }
  }

  static importJSON(data: any): ImageNode {
    return new ImageNode(data.src, data.alt, data.width, data.height)
  }

  decorate(): React.ReactElement {
    return (
      <ImageComponent
        src={this.__src}
        alt={this.__alt}
        width={this.__width}
        height={this.__height}
        nodeKey={this.getKey()}
      />
    )
  }
}

ארכיטקטורה זו מאפשרת הוספת כל בלוק מותאם אישית: טבלאות, וידאו מוטמע, ציטוטים, גלריות. אנו תומכים ביותר מ-20 סוגי צמתים מותאמים אישית.

טיפ לאופטימיזציהעבור מספר גדול של צמתים מותאמים אישית, השתמש בטעינה עצלה עבור דקורטורים.

למה JSON הוא הבחירה הטובה ביותר לפאנלי ניהול

מצב ה-JSON נשמר בעמודת DB אחת. בעריכה, אנו שומרים אותו כפי שהוא; עבור רינדור בצד הלקוח, אנו ממירים ל-HTML באמצעות DecoratorNode. זה מבטל את הצורך לטעון את העורך בתצוגה — מהירות טעינת הדף עולה ב-30–50%. השוואת פורמטים לפי פרמטרים מרכזיים:

פרמטר JSON HTML
גרסאות מובנית (diff) ידנית
בטיחות XSS בידוד טבעי דורש ניקוי
גמישות התאמה אישית גבוהה נמוכה

שירות יישום עורך הטקסט העשיר שלנו כולל אינטגרציה עם React Hook Form, Formik ו-Redux Form.

כיצד להתאים את העורך למשימות שלך

אנו משתמשים במערכת תוספים. לכל משימה מותאמת אישית יש תוסף משלה, המספק בידוד ותחזוקה קלה. לדוגמה, עבור תמונות כתבנו // nodes/ImageNode.tsx import { DecoratorNode, LexicalNode, NodeKey } from 'lexical' export class ImageNode extends DecoratorNode<React.ReactElement> { __src: string __alt: string __width: number | 'inherit' __height: number | 'inherit' static getType(): string { return 'image' } static clone(node: ImageNode): ImageNode { return new ImageNode(node.__src, node.__alt, node.__width, node.__height, node.__key) } constructor(src: string, alt: string, width?: number | 'inherit', height?: number | 'inherit', key?: NodeKey) { super(key) this.__src = src this.__alt = alt this.__width = width ?? 'inherit' this.__height = height ?? 'inherit' } createDOM(): HTMLElement { const div = document.createElement('div') div.className = 'editor-image' return div } updateDOM(): false { return false } exportJSON() { return { type: 'image', src: this.__src, alt: this.__alt, width: this.__width, height: this.__height, version: 1, } } static importJSON(data: any): ImageNode { return new ImageNode(data.src, data.alt, data.width, data.height) } decorate(): React.ReactElement { return ( <ImageComponent src={this.__src} alt={this.__alt} width={this.__width} height={this.__height} nodeKey={this.getKey()} /> ) } } שמוסיף כפתור העלאה ומכניס $generateHtmlFromNodes.

ה-OnChangePlugin עוקב אחר שינויים ומעביר JSON + HTML החוצה:

// plugins/OnChangePlugin.tsx
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { $generateHtmlFromNodes } from '@lexical/html'
import { useEffect } from 'react'

export function OnChangePlugin({ onChange }: { onChange: (state: string, html: string) => void }) {
  const [editor] = useLexicalComposerContext()

  useEffect(() => {
    return editor.registerUpdateListener(({ editorState }) => {
      editorState.read(() => {
        const stateJSON = JSON.stringify(editorState.toJSON())
        const html = $generateHtmlFromNodes(editor, null)
        onChange(stateJSON, html)
      })
    })
  }, [editor, onChange])

  return null
}

אינטגרציה עם React Hook Form — דרך Controller, העברת ImagesPlugin וטיפול ב-ImageNode.

תהליך העבודה

  1. ניתוח — אנו לומדים את התוכן הנוכחי שלך, המבנה, דרישות הבלוקים.
  2. עיצוב — בחירת פורמט אחסון, רשימת צמתים, עיצוב סרגל כלים.
  3. יישום — הגדרת סביבה, צמתים מותאמים אישית, תוספים, אינטגרציה עם טפסים ו-API.
  4. בדיקות — בדיקה על מסמכים גדולים (עד 100,000 מילים), תאימות דפדפנים, ביצועים.
  5. פריסה ותיעוד — פרסום, הדרכת עורכים, מסירת הוראות.

לוח זמנים

  • עורך בסיסי (Lexical + JSON + HTML, סרגל כלים, אינטגרציה): 3–4 ימים (החל מ-$1,500).
  • עם צמתים מותאמים אישית, גרסאות וסרגל כלים מורחב: 8–12 ימים (החל מ-$5,000).

מה כלול

  • קוד מקור של רכיבים (React/Next.js) עם הערות.
  • שמירה מוגדרת של JSON ב-DB ורינדור HTML בפרונטאנד.
  • תיעוד API עבור רכיבים והוספת צמתים חדשים.
  • הדרכה לעורכים (שעה).
  • אחריות תמיכה למשך שבועיים לאחר המסירה.
  • החל מ-$1,500 ליישום בסיסי, התאמה מלאה החל מ-$5,000.

טעויות נפוצות ביישום

  • ניקוי HTML — בעת רינדור מ-JSON, השתמש תמיד ב-DOMPurify אם התוכן נוצר על ידי משתמשים.
  • חבילה ראשונית גדולה מדי — פצל את העורך והרנדרר (code splitting).
  • התעלמות ממקלדת ניידת — ב-iOS Safari, contentEditable יכול להתנהג בצורה לא יציבה; בדוק על מכשירים אמיתיים.

לצוות שלנו יש ניסיון של למעלה מ-5 שנים ו-200+ פרויקטים ביישום עורכי תוכן לפרויקטים גדולים. קבל ייעוץ לפרויקט שלך — נעריך את המשימה תוך יום עסקים אחד. הזמן יישום עורך טקסט עשיר במפתח — נבצע בדיקה ונציע פתרון אופטימלי.