เปิดตัว Wordgard 0.1
(marijnhaverbeke.nl)- Wordgard 0.1 คือไลบรารีแก้ไข rich text สำหรับ JavaScript ที่สร้างขึ้นใหม่ โดยสะท้อนประสบการณ์ 9 ปีหลัง ProseMirror เสถียร และแนวทางการออกแบบของ CodeMirror 6
- แทนที่จะเปลี่ยน ProseMirror เดิมเป็น 2.0 หรือเสริมเพิ่มเข้าไปใน 1.x จึงออกแบบใหม่ด้วยชื่อแยกต่างหากและ API ใหม่ที่ไม่ต้องแบกรับภาระด้านความเข้ากันได้
- การออกแบบหลักใช้ โมเดลตามส่วนของการเปลี่ยนแปลง แทน steps และผสานชนิด node/mark ที่เป็นอิสระเข้ากับระบบขยายแบบ facet สไตล์ CodeMirror
- ลดการพึ่งพาพฤติกรรมการเลือกของเบราว์เซอร์ โดยจัดการการเลือกด้วย pointer และ keyboard เอง แต่ การเลือกด้วย touch ยังคงใช้การทำงานของเบราว์เซอร์ เพราะปัญหาเมนูบริบทแบบ native
- ติดตั้งได้จาก npm ด้วย
wordgardและมีเอกสารกับคู่มืออ้างอิงเผยแพร่แล้ว แต่จะยังคงอยู่ใน เวอร์ชัน 0.x ไปอีกระยะเพื่อรับ feedback และแก้บั๊ก
ลักษณะของ Wordgard และสถานะการเผยแพร่
- Wordgard เป็นโปรเจกต์ที่ทำซ้ำระบบแก้ไข rich text สไตล์ ProseMirror ขึ้นมาใหม่
- ได้รับอิทธิพลอย่างมากจากสิ่งที่เรียนรู้ตลอด 9 ปีหลัง ProseMirror เสถียร และจากการออกแบบใหม่ของ CodeMirror เวอร์ชัน 6
- เป็น ไลบรารี JavaScript ที่แสดงอินเทอร์เฟซของ editor ผ่าน DOM ของเบราว์เซอร์ และใช้ไลเซนส์ MIT
- โค้ดเผยแพร่บน เซิร์ฟเวอร์ Forgejo
- ติดตั้งได้จาก npm registry ด้วย
wordgardและดูวิธีใช้งานได้บนเว็บไซต์
เหตุผลที่สร้างระบบใหม่แทนการเปลี่ยน ProseMirror
- ProseMirror จะยังได้รับการดูแลต่อไป แต่บางส่วนของการออกแบบยังเป็นจุดที่หากมองจากปัจจุบันก็ควรทำต่างออกไป
- หากออก ProseMirror 2.0 ที่มีอินเทอร์เฟซไม่เข้ากัน อาจทำให้คลุมเครือว่าเมื่อคนพูดถึง “ProseMirror” หมายถึงอะไร
- หากนำไอเดียใหม่ไปต่อเติมใน ProseMirror 1.x แบบรักษาความเข้ากันได้ย้อนหลัง โครงสร้างอาจต้องประนีประนอม
- Wordgard นำไอเดียจำนวนมากจาก ProseMirror มาใช้ แต่ อินเทอร์เฟซสำหรับโปรแกรมมิง ถูกออกแบบใหม่ตั้งแต่ต้นโดยไม่คำนึงถึงความเข้ากันได้
การแทนค่าการเปลี่ยนแปลง: โมเดลตาม section แทน steps
- steps ของ ProseMirror แบ่งการเปลี่ยนแปลงเป็นการทำงานแบบอะตอมหลายรายการ โดยแต่ละ step จะถูกนำไปใช้กับเอกสารที่เกิดจาก step ก่อนหน้า
- วิธีนี้ใช้งานได้ แต่การปรับตำแหน่งระหว่างหลาย step และการติดตามช่วงที่เปลี่ยนแปลงซับซ้อน ทำให้จัดการได้ไม่ถนัด
- Wordgard ใช้โมเดลที่เรียบง่ายกว่า โดยอิงแนวทางจากการแทนค่าการเปลี่ยนแปลงของ CodeMirror และรูปแบบ “delta” ของ ShareJS
- เมื่อความยาวเอกสารเป็น 10 แล้วแทรก
Lที่ตำแหน่ง 4 จะแทนเป็น[keep 4] [replace 0 with "L"] [keep 6] - เมื่อลบอักขระสองตัวแรก จะแทนเป็น
[replace 2 with ""] [keep 8]
- เมื่อความยาวเอกสารเป็น 10 แล้วแทรก
- เพื่อรองรับ rich text จึงเพิ่ม section ของการเปลี่ยนแปลง ทำให้สามารถเพิ่มหรือลบ mark เช่น ตัวหนา สไตล์ลิงก์ หรือข้อความ alt ของรูปภาพได้ โดยคงโครงสร้างไว้
- หากทำให้คำตั้งแต่ 3 ถึง 6 เป็นตัวหนา จะแทนเป็น
[keep 3] [update 3 +bold] [keep 4]
- หากทำให้คำตั้งแต่ 3 ถึง 6 เป็นตัวหนา จะแทนเป็น
- Wordgard ใช้ token-counting index เช่นเดียวกับ ProseMirror เพื่อจัดการตำแหน่งในเอกสารเป็นลำดับแบนของ token เปิด/ปิด node และ leaf token
- transaction เดี่ยวจะมีการเปลี่ยนแปลงเพียงหนึ่งรายการเสมอ ทำให้การประกอบการเปลี่ยนแปลง การตรวจสอบ และการอนุมานง่ายขึ้น
- รองรับ operational transformation แบบจำกัด เพื่อรวมการเปลี่ยนแปลงหลายรายการที่อธิบายบนเอกสารตั้งต้นเดียวกันได้
- สามารถแสดง transaction ที่มีการเปลี่ยนแปลงหลายรายการได้สะดวกขึ้น
- นำไปใช้กับ collaborative editing และการทำ undo history ที่ย้อนกลับเฉพาะบางการเปลี่ยนแปลงได้
วิธีรักษาโครงสร้างเอกสารที่ถูกต้อง
- เอกสาร Wordgard ไม่ใช่แค่ลำดับ token ธรรมดา แต่ต้องเป็นโครงสร้างต้นไม้ที่สมดุล
- ตัวอย่างเช่น หากลบ token ปิด node ออก อาจทำให้สมดุลของ token เสียและเกิดการเปลี่ยนแปลงที่นำไปใช้ไม่ได้
- โค้ดที่สร้างชุดการเปลี่ยนแปลงต้องตรวจสอบและปรับแก้การเปลี่ยนแปลงเพื่อให้ผลลัพธ์เป็น โครงสร้างเอกสารที่ถูกต้อง
- ใน operational transformation การเปลี่ยนแปลงที่ถูก transform แล้วก็ต้องไม่ทำให้เอกสารไม่ถูกต้องเช่นกัน
- โมเดลการเปลี่ยนแปลงของ Wordgard คำนวณ fix-up change ที่ปรับแก้ผลการรวมระหว่างการ transform
- ใช้อินพุตอย่างระมัดระวังเพื่อให้ได้การปรับแก้เดียวกันสำหรับ A-over-B และ B-over-A
- หากไม่มีการปรับแก้ ทั้งสองลำดับอาจสร้างเอกสารเดียวกัน แต่อาจเป็นเอกสารที่ไม่ถูกต้อง
- เมื่อประกอบการปรับแก้เดียวกัน ทั้งสองลำดับจะลู่เข้าสู่เอกสารที่ถูกต้องเดียวกัน
- การเปลี่ยนแปลงส่วนใหญ่ไม่ต้องปรับแก้ แต่แม้จำเป็นก็ถูกออกแบบให้รักษาการลู่เข้าหากันไว้ได้
การประกอบ schema และการทำให้ mark ทั่วไปขึ้น
- schema เอกสารของ ProseMirror ระบุความสัมพันธ์ระหว่าง node โดยตรง จึงมักต้องตั้งค่าด้วยมือ
- ชนิด node และ mark ของ ProseMirror มีอยู่ได้เฉพาะภายใน schema หนึ่ง ๆ และไม่มีตัวตนของ node ที่แชร์ข้าม schema ได้
- ใน Wordgard ชนิด node และ mark เป็นออบเจ็กต์อิสระที่สามารถรวมอยู่ใน schema เอกสารหลายชุดได้
- ออบเจ็กต์เหล่านี้ทำงานเหมือน handle ที่รองรับการกำหนดชนิดและ auto-completion ทำให้นำองค์ประกอบที่ต้องการมาประกอบเป็น schema ได้ง่าย
- schema สามารถ override ความสัมพันธ์ขององค์ประกอบที่มีอยู่ได้
- นิยาม node หรือ mark จะกำหนดเนื้อหาเริ่มต้นหรือชนิดเป้าหมาย
- เมื่ออยากใช้องค์ประกอบเดียวกันต่างแบบ schema สามารถเปลี่ยนความสัมพันธ์นั้นได้
- สามารถให้ node พื้นฐานในตัวมาพร้อมความสามารถมากขึ้น ทำให้ผูกการขยายการสนับสนุนการแก้ไขหรือการรวมระบบอย่างปุ่มเมนูเข้ากับ node นั้นโดยตรงได้ง่าย
- ฟีเจอร์ที่เคยผูกกับคุณสมบัติของ node เฉพาะ เช่น การจัดแนวข้อความหรือข้อความ alt สามารถเพิ่มแบบโมดูลาร์มากขึ้นผ่าน การทำให้ mark ทั่วไปขึ้น
- ชนิด node เองไม่จำเป็นต้องรู้ว่า mark ใดมุ่งเป้ามาที่ตัวเอง
เหตุผลที่ผ่อนคลายข้อจำกัดของเนื้อหา
- การระบุเนื้อหาที่อนุญาตตาม regular expression ซึ่งเป็นฟีเจอร์เด่นของ ProseMirror ไม่รองรับใน Wordgard
- คำอธิบายเนื้อหา node ของ Wordgard จำกัดเพียงว่ารองรับชนิดลูกใดบ้าง แต่ไม่จำกัด ลำดับ ของลูกเหล่านั้น
- ข้อจำกัดแบบ regular expression ทำให้เขียนโค้ดจัดการเอกสารทั่วไปได้ยาก
- โค้ดที่ไม่ได้เขียนให้เข้ากับ schema เฉพาะแทบจะตั้งสมมติฐานไม่ได้ว่า transformation ใดถูกต้อง
- ทุกการทำงานต้องนำไปเทียบกับข้อจำกัดของเนื้อหา และกระบวนการนี้ละเอียดอ่อนและเป็นภาระ
- ข้อจำกัดที่ล็อกหน้าตาเอกสารอย่างเข้มงวดอาจขัดขวางขั้นตอนการแก้ไขระหว่างทางที่ผู้ใช้ต้องผ่านเพื่อไปสู่รูปแบบที่ตั้งใจ ทำให้ประสบการณ์ผู้ใช้แย่ลง
- Wordgard สนับสนุนแนวทางรูปแบบเอกสารที่ยืดหยุ่นกว่า
- เมื่อจำเป็นต้องมี invariant ที่เกินกฎของ schema จะมี abstraction ชื่อ correction ให้ใช้
- ปรับแก้รูปแบบเอกสารที่ไม่ต้องการอนุญาตด้วยโปรแกรม
- ทำการปรับแก้ที่ฉลาดกว่าและคำนึงถึงบริบทมากกว่าการบังคับ content expression ได้
- ใช้กับเงื่อนไขอย่างการรับประกันว่าตารางเป็นสี่เหลี่ยมผืนผ้า ซึ่งแม้ข้อจำกัดของ ProseMirror ก็อธิบายไม่ได้
ระบบขยาย: facet สไตล์ CodeMirror 6
- ระบบขยายของ ProseMirror เป็นแบบที่ plugin รับหลายหน้าที่ และลำดับใน array ส่งผลต่อ priority
- อาจเกิดสถานการณ์ที่ plugin หนึ่งต้องมี priority ต่ำใน hook หนึ่ง แต่ต้องมี priority สูงในอีก hook หนึ่ง
- ระบบที่อิง facets ของ CodeMirror ทำให้ extension ละเอียดขึ้น และให้ค่า extension แต่ละตัวตั้งหมวด priority ของตัวเองได้
- Facet คือจุดขยายที่มี type กำกับ และไม่ใช่แค่ตัวไลบรารีเองเท่านั้น แต่โค้ดใด ๆ ก็สามารถนิยามได้
- Wordgard นำระบบของ CodeMirror ในส่วนนี้มาแทบทั้งหมด รวมถึงกลไกการอัปเดตสถานะและการ reconfiguration
- configuration ไม่ใช่ array ของ plugin แต่เป็น ต้นไม้ของ extension
- นิยาม event handler
- ตั้งค่าคุณสมบัติของ editor
- เพิ่มสถานะ editor ใหม่
- การทำฟีเจอร์มักประกอบด้วยชุด extension ที่ทำงานร่วมกัน
- องค์ประกอบพื้นฐานถูกออกแบบให้ bundle ของ extension ส่วนใหญ่ประกอบกันได้ดีเพียงแค่ใส่เข้าไปในการตั้งค่า
การลดการพึ่งพาเบราว์เซอร์และการจัดการ selection
- ปัญหาหลายอย่างของ ProseMirror เกี่ยวข้องกับวิธีที่พึ่งพาพฤติกรรม selection แบบ native ของเบราว์เซอร์
- แนวทางเดิมคือปล่อยให้เบราว์เซอร์จัดการการเลื่อน cursor ในข้อความสองทิศทางหรือเนื้อหาที่มีสไตล์แปลก ๆ แล้วสะท้อนผลลัพธ์นั้นเข้ามาในโมเดล selection ของตัวเอง
- ในความเป็นจริง เบราว์เซอร์อาจไม่เลื่อน cursor ผ่านเนื้อหาบางส่วน ไม่วาด cursor วาดผิดตำแหน่ง หรือมีพฤติกรรมผิดปกติเมื่อ drag เลือกด้วยเมาส์
- Wordgard จัดการ selection ที่อิง pointer และ keyboard เองแทบทั้งหมด
- implement การจัดการข้อความสองทิศทาง
- สร้างโมเดลการจัดวางเนื้อหา
- วาด cursor เอง
- การเลือกด้วย touch เป็นข้อยกเว้นที่ยังใช้ implementation แบบ native
- หาก implement ใหม่ ดูเหมือนจะทำให้เมนูบริบทแบบ native เสีย
- บนโทรศัพท์และแท็บเล็ต การทดแทนเมนูบริบททำได้ยาก
- การเลือกด้วย touch มักมีพฤติกรรมแปลกน้อยกว่าการเลือกด้วย keyboard
การจัดการ input event และการเลิกเฝ้าดูการเปลี่ยนแปลง DOM
- ตลอด 9 ปีที่ผ่านมา การรองรับ edit event ของเบราว์เซอร์ โดยเฉพาะ
beforeinputมีความสม่ำเสมอมากขึ้น - ยังต้องทดสอบในสภาพแวดล้อมใช้งานจริง แต่ดูเหมือนว่า Wordgard จะทำงานได้โดยไม่ต้องใช้การเฝ้าดูการเปลี่ยนแปลง DOM และเทคนิค parse เนื้อหาที่เปลี่ยนไปซึ่ง ProseMirror เคยพึ่งพา
- Wordgard จัดการ event
beforeinputสำหรับทุกอย่าง ยกเว้นการป้อนข้อความแบบ composition - แนวทางนี้หลีกเลี่ยงกลุ่มปัญหาที่ต้องใช้ workaround สกปรกหลายอย่าง
ความเสถียร แผนเวอร์ชัน และไลเซนส์
- Wordgard อยู่ในสถานะที่คืบหน้ากว่าเล็กน้อยเมื่อเทียบกับสถานะของโปรเจกต์ก่อนหน้าตอนประกาศ
- อินเทอร์เฟซหลักรองรับฟีเจอร์ที่ต้องการเกือบทั้งหมดแล้ว และมีการเขียน extension หลายตัวเพื่อยืนยันว่าการออกแบบใช้งานได้จริง
- เอกสารยังค่อนข้างหยาบอยู่บ้าง แต่ คู่มืออ้างอิง เสร็จสมบูรณ์และใช้งานได้แล้ว
- ปัญหาหลายอย่างอาจยังไม่ปรากฏจนกว่าจะมีคนใช้กับงานจริง
- ยังมีฟีเจอร์ที่อยากเพิ่มในอนาคต และคาดหวังว่าหลังเผยแพร่แล้วคนอื่น ๆ จะเข้ามาดูด้วย
- อินเทอร์เฟซสาธารณะบางส่วนอาจต้องคิดใหม่ตาม insight ที่เพิ่มขึ้น
- เวอร์ชันแรกคือ 0.1 และจะอยู่ในเวอร์ชัน 0.x ไปอีกระยะเพื่อรวบรวม feedback แก้บั๊ก และเก็บรายละเอียดส่วนที่ยังหยาบ
- คาดว่าระยะเวลาอย่างน้อยประมาณ 1 ปี
- ไลเซนส์คือ MIT เช่นเดียวกับโปรเจกต์ก่อนหน้า
- เคยพิจารณาไลเซนส์ที่จำกัดกว่านี้ แต่เลือกไลเซนส์แบบ permissive เพราะสนใจให้มีการใช้งานอย่างแพร่หลายมากกว่า
นโยบายเกี่ยวกับโมเดล AI, การสร้างโค้ด และ pull request
- ไม่มีการใช้ language model ในการสร้างซอฟต์แวร์นี้
- เนื่องจากโค้ด JavaScript อยู่บนเว็บและเอกสารถูกเผยแพร่ จึงมองว่าไม่มีวิธีที่น่าเชื่อถือในการป้องกันไม่ให้โค้ดและไอเดียที่เผยแพร่แล้วเข้าไปอยู่ใน large language model
- Wordgard ทดลองไม่รับ pull request ซึ่งต่างจากแนวปฏิบัติมาตรฐานของโอเพนซอร์ส
- กระบวนการรีวิวการเปลี่ยนแปลงขนาดใหญ่และปรับให้ตรงกับความคาดหวังมักใช้แรงงานมากกว่าการ implement เอง
- เมื่อต้นทุนการสร้างโค้ดลดลงมาก โครงสร้างที่คนอื่นโยนโค้ดมา แล้ว maintainer ต้องรีวิว ดูแลต่อ หรืออธิบายเหตุผลในการปฏิเสธ ก็ยิ่งไม่น่าสนใจมากขึ้น
1 ความคิดเห็น
ความคิดเห็นบน Lobste.rs
ในฐานะผู้เขียน หากมีคำถามหรือฟีดแบ็ก ผม/ฉันจะเข้ามาดูเธรดนี้เป็นระยะ
น่าจะสร้าง HTML ด้วย Markdown renderer แล้วให้แก้ไขใน Wordgard ได้ แต่หลังจากนั้นจะดึง Markdown ออกจากเนื้อหาในตัวแก้ไขได้อย่างไร?
สุดท้ายจะย้ายไป Wordgard กันไหม? ถ้ามีคนจะใช้ ProseMirror ในโปรเจกต์ใหม่ ควรเลือก Wordgard เมื่อไหร่?
และยังมีงานอาร์ตสวย ๆ ที่ทำโดย ศิลปินมนุษย์ตัวจริง ด้วย
ดีเลย
ขอแสดงความยินดีอย่างยิ่งกับโปรเจกต์และการเปิดตัวของ Marijn ดูยอดเยี่ยม และผม/ฉันก็ชอบ งานอาร์ตของ Kamila Stankiewicz ด้วย
โฮมเพจหลักของโปรเจกต์คือ https://wordgard.net/
ส่วนนี้ โดยเฉพาะทั้งย่อหน้าสุดท้าย น่าสนใจมากจริง ๆ
ก็สงสัยเหมือนกันว่าวิธีแบบนี้อาจพบเห็นได้บ่อยขึ้นไปอีกสักระยะ จนกว่าเราจะมีความสัมพันธ์ที่ดีกว่านี้กับ การสร้างโค้ดด้วย AI หรือไม่ก็เลือกที่จะไม่ข้องเกี่ยวกับมันเลย
หมายเหตุ โค้ดใช้ไลเซนส์ MIT