Now.js Framework Documentation
CodeEditor
CodeEditor
ภาพรวม
CodeEditor เปลี่ยน <textarea> ให้เป็นตัวเขียนโค้ด มีสีตามไวยากรณ์ เลขบรรทัด และปุ่มลัดที่นักเขียนโค้ดคุ้นเคย
อยู่ใน core bundle จึงใช้ได้ทุกโปรเจ็คโดยไม่ต้องโหลดไฟล์เพิ่ม
textarea ยังเป็นพื้นที่พิมพ์จริง การพิมพ์ เคอร์เซอร์ การเลือกข้อความ undo การพิมพ์ผ่าน input method (รวมภาษาไทย)
และการส่งฟอร์มเป็นของเบราว์เซอร์ทั้งหมด ส่วน <pre> ที่วาดอยู่ด้านหลังแสดงข้อความเดียวกันเป็นสี และไม่มีการอ่านค่าใดกลับจากชั้นนั้น
ค่าของ textarea จึงเป็นโค้ดตรงตามที่พิมพ์เสมอ
ใช้เมื่อ:
- ฟอร์มที่แก้โค้ดหรือข้อความมีโครงสร้าง: CSS, template HTML, ค่าตั้ง JSON, SQL, PHP, JavaScript
- หน้าที่แสดงโค้ดซึ่งผู้อ่านอาจแก้หรือคัดลอก
ใช้ได้สองแบบ:
| เมื่อไร | |
|---|---|
<textarea data-element="code"> (CodeElementFactory) |
ในฟอร์ม: validation, required, FormError และ data-attr ทำงานเหมือนช่องอื่น |
new CodeEditor(textarea, options) |
จากสคริปต์ นอกฟอร์ม (เช่น playground) |
<form data-form="theme">
<textarea name="css" data-element="code" data-language="css" data-max-height="60vh" required></textarea>
</form>const editor = new CodeEditor(document.querySelector('#source'), {language: 'javascript', height: 400});
editor.getValue();ตัวเลือก
ส่งผ่าน constructor หรือเป็น attribute data-* บน textarea (data-line-numbers, data-tab-size, ...)
ค่าที่ส่งผ่าน constructor ชนะ attribute
| ตัวเลือก | Attribute | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
language |
data-language |
'text' |
ดู ภาษา |
lineNumbers |
data-line-numbers |
true |
แสดงเลขบรรทัด |
tabSize |
data-tab-size |
2 |
จำนวนคอลัมน์ต่อระดับการย่อหน้า (1–8) |
indentWithTabs |
data-indent-with-tabs |
false |
ย่อหน้าด้วยอักขระ tab |
autoCloseBrackets |
data-auto-close-brackets |
true |
พิมพ์ ) ] } และเครื่องหมายคำพูดปิดให้พร้อมตัวเปิด |
autoIndent |
data-auto-indent |
true |
Enter แล้วคงระดับย่อหน้า และลึกขึ้นหนึ่งระดับหลังตัวเปิด |
minLines |
data-min-lines |
3 (5 ในฟอร์ม) |
จำนวนบรรทัดที่เห็นอย่างน้อย |
height |
data-height |
null |
ความสูงคงที่ (px หรือหน่วย CSS ใดก็ได้) เลื่อนภายใน |
maxHeight |
data-max-height |
null |
สูงตามเนื้อหาจนถึงค่านี้ |
theme |
data-theme |
null |
'light' หรือ 'dark' ถ้าไม่ระบุจะตาม data-theme ของหน้า |
onChange |
— | null |
function(value, editor) ทุกครั้งที่โค้ดเปลี่ยน |
ถ้าไม่ระบุ height หรือ maxHeight ตัวเขียนจะสูงตามเนื้อหา และหน้าเว็บเป็นตัวเลื่อน
ปุ่มลัด
| ปุ่ม | ผล |
|---|---|
| Tab / Shift+Tab | ย่อหน้า / ถอยย่อหน้า ถ้าเลือกหลายบรรทัดจะทำทุกบรรทัด |
| Enter | ขึ้นบรรทัดใหม่โดยคงย่อหน้า หลัง { [ ( (หรือแท็กเปิด HTML) ลึกขึ้นหนึ่งระดับ และตัวปิดย้ายไปบรรทัดของตัวเอง |
| `( [ { " ' `` | พิมพ์พร้อมตัวปิด ถ้าเลือกข้อความไว้จะครอบข้อความนั้น |
| ตัวปิดที่อยู่ติดเคอร์เซอร์ | ข้ามไปแทนการพิมพ์ซ้ำ |
| Backspace | ในคู่วงเล็บว่างลบทั้งคู่ ในช่องว่างหน้าบรรทัดลบทีละหนึ่งระดับย่อหน้า |
| Ctrl+/ (⌘/) | ใส่หรือเอา comment ออกจากบรรทัดที่เลือก |
| Ctrl+Z / Ctrl+Shift+Z | undo / redo ของเบราว์เซอร์ รวมการแก้ที่ตัวเขียนทำเอง |
| Esc แล้ว Tab / Shift+Tab | ออกจากตัวเขียนไปช่องถัดไป / ช่องก่อนหน้าด้วยคีย์บอร์ด (Tab อย่างเดียวคือย่อหน้า) |
| คลิกเลขบรรทัด | เลือกทั้งบรรทัด |
Esc ปล่อยให้ออกได้แค่ Tab ครั้งถัดไปทันที ถ้าระหว่างนั้นผู้ใช้กดปุ่มอื่น พิมพ์หรือวางข้อความ (รวมข้อความจาก input method)
คลิกในโค้ดหรือที่เลขบรรทัด ออกจากตัวเขียนแล้วกลับเข้ามา หรือสคริปต์ย้ายเคอร์เซอร์ Tab จะกลับไปย่อหน้าตามปกติ
ส่วนปุ่ม modifier ที่กดเปล่า ๆ (Shift ของ Shift+Tab) ไม่นับ
Ctrl+Enter ไม่ถูกใช้ หน้าเว็บจึงผูกเองได้ (playground ใช้รันโค้ด)
ตัวเขียนไม่ห้าม Esc ส่งต่อไปถึง document หน้าต่างที่ปิดด้วย Esc (Modal, dialog) จึงปิดตามปกติ
ถ้าไม่อยากให้หน้าต่างที่มีโค้ดค้างแก้ปิดเพราะ Esc ให้หน้าเว็บกันเองที่ชั้นที่อยู่ใต้ document
API
new CodeEditor(textarea, options?)
ผูกตัวเขียนเข้ากับ textarea (element หรือ selector) ถ้า textarea มีตัวเขียนอยู่แล้วจะได้ตัวเดิมกลับมา
textarea ถูกย้ายเข้าไปใน element ของตัวเขียน (editor.root) ถ้า textarea ยังไม่อยู่ในหน้า ผู้เรียกต้องใส่ editor.root เข้าหน้าเอง
เมธอดของ instance
| เมธอด | คำอธิบาย |
|---|---|
getValue() |
โค้ดปัจจุบัน (ค่าของ textarea) |
setValue(value, options?) |
แทนโค้ดทั้งหมด ค่าเริ่มต้นเหมือนกำหนด textarea.value: undo ไม่ได้ ไม่มี event input · {history: true} ทำให้เป็นการแก้ที่ undo ได้ (focus ตัวเขียนและส่ง input) |
getLanguage() / setLanguage(name) |
อ่านหรือเปลี่ยนภาษาแล้วระบายสีใหม่ |
insertText(text) |
แทนข้อความที่เลือกเหมือนพิมพ์เอง (undo ได้) |
indent(outdent?) |
ย่อหน้าหรือถอยย่อหน้าบรรทัดที่เลือก |
toggleComment() |
ใส่หรือเอา comment ออกจากบรรทัดที่เลือก |
focus() |
ย้าย focus เข้าตัวเขียน |
refresh() |
วัดและวาดใหม่ (หลังเปลี่ยนฟอนต์หรือขนาด) |
destroy() |
คืน textarea กลับสภาพเดิม |
การกำหนด textarea.value จากสคริปต์ใดก็ตามจะระบายสีใหม่เช่นกัน และติดตาม form.reset() ด้วย
Property
editor.textarea, editor.root (element .code-editor), editor.language, editor.options
Static
| สมาชิก | คำอธิบาย |
|---|---|
CodeEditor.get(textarea) |
ตัวเขียนที่ผูกกับ textarea หรือ null |
CodeEditor.highlight(code, language) |
HTML ที่ escape แล้ว มี token เป็น <span class="ce-{type}"> |
CodeEditor.tokenize(code, language) |
[{type, text}] นำ text มาต่อกันได้โค้ดเดิมทุกตัวอักษร |
CodeEditor.registerLanguage(name, definition, aliases?) |
เพิ่มภาษา |
CodeEditor.resolveLanguage(name) |
ชื่อภาษาที่ลงทะเบียนของชื่อหรือชื่อแฝง ('text' ถ้าไม่รู้จัก) |
CodeEditor.defaults |
ค่าเริ่มต้นของตัวเลือก |
เหตุการณ์
event input ของ textarea เกิดทุกครั้งที่โค้ดเปลี่ยนจากการพิมพ์หรือจากปุ่มลัดของตัวเขียน (ไม่เกิดกับ setValue() ที่ไม่ใส่ history)
ภาษา
| ชื่อ | ชื่อแฝง | Comment |
|---|---|---|
javascript |
js, mjs, cjs, jsx, ts, typescript |
// |
json |
— | — |
html |
htm, xml, svg, xhtml, vue |
<!-- --> (เนื้อหา <script> และ <style> ระบายสีเป็น JavaScript และ CSS ส่วน <?php ?> เป็น PHP) |
css |
scss, less |
/* */ |
php |
— | // |
sql |
mysql |
-- |
text |
plain, txt, plaintext |
— (ไม่มีสี ไม่ปิดวงเล็บให้) |
เพิ่มภาษา
CodeEditor.registerLanguage('ini', {
lineComment: ';',
rules: [
{type: null, match: /\s+/y, skip: true},
{type: 'comment', match: /[;#][^\n]*/y, skip: true},
{type: 'selector', match: /\[[^\]\n]*\]/y},
{type: 'property', match: /[\w.-]+(?=\s*=)/y},
{type: 'string', match: /"[^"\n]*"?/y}
]
});กฎถูกลองตามลำดับทุกตำแหน่ง กฎแรกที่ตรงชนะ RegExp ทุกตัวต้องเป็นแบบ sticky (y) ข้อความที่ไม่ตรงกฎใดจะไม่มีสี
กฎหนึ่งข้อใส่ when(previousToken, state), onMatch(text, state), type แบบฟังก์ชัน หรือ scan(text, pos, state)
ที่คืน {tokens, end} สำหรับโครงสร้างที่ RegExp เดียวทำไม่ได้ และภาษาหนึ่งให้ state() สำหรับสถานะระหว่างสแกนได้
(ภาษา CSS ใช้แยก selector ออกจาก declaration)
ชนิด token ที่ภาษาในตัวใช้ แต่ละชนิดมีตัวแปรสี --ce-{type}: keyword, string, comment, number, constant,
function, property, builtin, operator, punctuation, regex, variable, tag, attr-name, attr-value,
selector, entity, meta
การจัดรูปแบบ
สไตล์อยู่ใน Now/css/code-editor.css ปรับด้วยตัวแปรบน .code-editor
.code-editor {
--code-editor-font-size: 14px;
--code-editor-line-height: 22px; /* px: สองชั้นต้องตรงกัน */
--code-editor-bg: #fff;
--ce-keyword: #8b5cf6;
}textarea และชั้นที่ระบายสีต้องใช้ฟอนต์ ขนาด ความสูงบรรทัด ระยะขอบ และ tab size เดียวกัน ไม่งั้นเคอร์เซอร์จะเลื่อนออกจากตัวอักษร
เปลี่ยนค่าเหล่านี้ผ่านตัวแปร --code-editor-* เท่านั้น บนจอสัมผัสฟอนต์จะขยายเป็น 16px เพื่อไม่ให้มือถือซูมเข้าช่อง
ประสิทธิภาพ
- ไฟล์ไม่เกิน 300 บรรทัดวาดทั้งไฟล์ ไฟล์ที่ยาวกว่าวาดเฉพาะบรรทัดที่เห็น (และส่วนเผื่อ) การพิมพ์จึงยังเร็วในไฟล์หลายพันบรรทัด
(ประมาณ 25 ms ต่อการกดในไฟล์โค้ดแน่น 1,500 บรรทัด) - หลังการแก้ แยก token ใหม่เฉพาะส่วนที่เปลี่ยน
- เกิน 300,000 ตัวอักษรจะแสดงโค้ดโดยไม่มีสี
ข้อควรระวัง
⚠️ 1. ในฟอร์มให้ใช้ data-element="code"
new CodeEditor() แบบตรง ๆ จะนำ textarea ออกจาก ElementManager (data-enhance="false") validation ของช่องจึงไม่ทำงาน
ฟอร์มควรใช้ CodeElementFactory
⚠️ 2. setValue() undo ไม่ได้โดยค่าเริ่มต้น
การโหลดเนื้อหาจากสคริปต์ไม่ใช่สิ่งที่ผู้ใช้ทำ สำหรับการกระทำของผู้ใช้ เช่นโหลดตัวอย่างหรือล้างโค้ด ให้ส่ง {history: true}
เพื่อให้ Ctrl+Z เอาโค้ดเดิมกลับมาได้
⚠️ 3. บรรทัดไม่ตัดขึ้นบรรทัดใหม่
บรรทัดยาวเลื่อนไปด้านข้าง เพราะถ้าตัดบรรทัด เลขบรรทัดและตำแหน่งเคอร์เซอร์จะขึ้นกับความกว้าง
เอกสารที่เกี่ยวข้อง
- CodeElementFactory - ตัวเขียนโค้ดในฐานะช่องของฟอร์ม
- ElementManager - ชนิดของช่องในฟอร์ม
- TextareaElementFactory - textarea ธรรมดา