# SMF 2.1.7 — คู่มือติดตั้ง YouTube แนวตั้ง 9:16
**คู่มือฉบับใช้งานจริง | 8 ตุลาคม 2026**
ต้นฉบับของคู่มือนี้เป็นโค้ดที่ดึงตรงจากแพ็กเกจ `SMF_YouTube_Vertical_2.1.7.zip` ซึ่งผู้ใช้ได้นำ 3 ไฟล์ไปติดตั้งแล้ว **ยืนยันว่าช่อง "วิดีโอแนวตั้ง 9:16" ปรากฏในหน้าต่างแทรก YouTube** อย่างถูกต้อง ณ วันดังกล่าว แต่ยังไม่ใช่การยืนยันการเล่นวิดีโอทุกลักษณะหรือทุกธีม
## จุดประสงค์
- แทรก YouTube แบบปกติผ่าน `[youtube]VIDEO_ID[/youtube]` ตามเดิม
- เพิ่มทางเลือกแนวตั้งผ่าน `[youtube=vertical]VIDEO_ID[/youtube]`
- ในหน้าเขียนกระทู้ มี Checkbox `วิดีโอแนวตั้ง 9:16` ให้เลือกเอง
- สำหรับแนวตั้งกำหนดพื้นที่แสดงอัตราส่วน **9:16** กว้างสูงสุด 360px และรองรับหน้าจอแคบ
## ไฟล์ที่แก้ (3 ไฟล์)
| ไฟล์ | ตำแหน่งภายในโฟลเดอร์ forum |
|---|---|
| `Subs.php` | `Sources/Subs.php` |
| `index.css` | `Themes/default/css/index.css` |
| `jquery.sceditor.smf.js` | `Themes/default/scripts/jquery.sceditor.smf.js` |
**ข้อสำคัญ:** คัดลอกทั้งโค้ดต้นฉบับของ SMF 2.1.7 มาแก้เฉพาะส่วน อย่านำทั้งไฟล์ที่แนบไปทับ SMF เวอร์ชันอื่นหรือเว็บที่มีส่วนขยาย/ธีมปรับแต่งโดยไม่เปรียบเทียบก่อน
## ขั้นตอนก่อนแก้
1. สำรองทั้ง 3 ไฟล์จากเว็บปลายทาง โดยระบุวันที่และชื่อเว็บ เช่น `backup_2026-10-08` และเก็บไฟล์ที่ใช้งานจริงไว้ต่างหาก
2. ตรวจสอบเวอร์ชัน SMF (ชุดนี้ใช้กับ **2.1.7**) และธีมที่ใช้งานจริง ถ้าไม่ใช่ `default` ต้องตรวจว่าโหลด JS/CSS จากตำแหน่งใด
3. ใช้การแทนที่ชุด YouTube เดิมใน JS ไม่เพิ่ม `set('youtube')` ซ้ำ
## 1) `Sources/Subs.php`
ค้นหา array ที่มี `'tag' => 'youtube'` และ `'type' => 'unparsed_content'` ซึ่งรองรับวิดีโอปกติ
**เพิ่ม array นี้ไว้ทันที *ก่อน* array YouTube ปกติ โดยไม่ลบของเดิม:**
```php
// YouTube Shorts / portrait videos, [youtube=vertical]VIDEO_ID[/youtube]
array(
'tag' => 'youtube',
'type' => 'unparsed_equals_content',
'test' => 'vertical\]',
'content' => '<div class="videocontainer" data-youtube-orientation="vertical"><div><iframe frameborder="0" src="https://www.youtube.com/embed/$1?origin=' . $hosturl . '&wmode=opaque" data-youtube-id="$1" allowfullscreen loading="lazy"></iframe></div></div>',
'disabled_content' => '<a href="https://www.youtube.com/watch?v=$1" target="_blank" rel="noopener">https://www.youtube.com/watch?v=$1</a>',
'block_level' => true,
),
```
เหตุผล: YouTube แนวตั้งต้องถูกแยกด้วย `'type' => 'unparsed_equals_content'` และ `test => 'vertical\]'` เพื่อจับ `[youtube=vertical]` ก่อนกรณี YouTube ทั่วไป
## 2) `Themes/default/css/index.css`
เพิ่มบล็อกนี้ **หนึ่งครั้งที่ท้ายไฟล์** (ถ้ามีบล็อกแนวตั้งเก่าอยู่แล้ว ให้แทนที่ ไม่เพิ่มซ้ำ):
```css
/* YouTube portrait video 9:16 */
.videocontainer[data-youtube-orientation="vertical"] {
display: inline-block;
vertical-align: top;
width: 360px;
max-width: 100%;
margin: 0 12px 12px 0;
}
.videocontainer[data-youtube-orientation="vertical"] > div {
padding-bottom: 177.7777778%;
}
```
`padding-bottom: 177.7777778%` คืออัตราส่วนสูงต่อกว้าง 16:9 สำหรับกรอบแนวตั้ง **กว้าง:สูง = 9:16**
## 3) `Themes/default/scripts/jquery.sceditor.smf.js`
ต้องแก้ **สองจุด** ในไฟล์เดียวกัน โดยใช้ **แทนที่** โค้ดเดิม ไม่ใช่แปะท้ายไฟล์เพิ่ม
### 3A — แทนที่ `sceditor.command.set('youtube', ...)` ทั้งชุด
ค้นหา `sceditor.command.set(` ที่มี `'youtube'` (ของ SMF เดิมมี `exec` แต่ยังไม่มีช่องติ๊ก) แล้วแทนที่ทั้งคำสั่งด้วย:
```javascript
sceditor.command.set(
'youtube', {
_dropDown: function (editor, caller, callback) {
var content = document.createElement('div');
var label = document.createElement('label');
label.textContent = editor._('Video URL:');
label.style.display = 'block';
label.style.marginBottom = '4px';
var input = document.createElement('input');
input.type = 'text';
input.placeholder = 'https://';
input.style.width = '100%';
input.style.boxSizing = 'border-box';
var verticalWrap = document.createElement('label');
verticalWrap.style.display = 'block';
verticalWrap.style.marginTop = '10px';
verticalWrap.style.marginBottom = '10px';
verticalWrap.style.cursor = 'pointer';
var vertical = document.createElement('input');
vertical.type = 'checkbox';
vertical.style.marginRight = '6px';
verticalWrap.appendChild(vertical);
verticalWrap.appendChild(document.createTextNode('วิดีโอแนวตั้ง 9:16'));
var button = document.createElement('button');
button.type = 'button';
button.className = 'button';
button.textContent = editor._('Insert');
content.appendChild(label);
content.appendChild(input);
content.appendChild(verticalWrap);
content.appendChild(button);
function insertYoutube(e) {
var val = input.value.trim();
var idMatch = val.match(/(?:v=|v\/|embed\/|youtu\.be\/|shorts\/)([a-zA-Z0-9_-]{11})/);
if (!idMatch && /^[a-zA-Z0-9_-]{11}$/.test(val))
idMatch = [val, val];
var timeMatch = val.match(/[&?](?:start|t)=((\d+[hms]?){1,3})/i);
var time = 0;
if (timeMatch) {
timeMatch[1].split(/[hms]/).forEach(function (part) {
if (part !== '')
time = (time * 60) + Number(part);
});
}
if (idMatch)
callback(idMatch[1], time, vertical.checked);
editor.closeDropDown(true);
if (e)
e.preventDefault();
}
button.addEventListener('click', insertYoutube);
input.addEventListener('keypress', function (e) {
if (e.key === 'Enter') insertYoutube(e);
});
editor.createDropDown(caller, 'insertlink', content);
setTimeout(function () { input.focus(); }, 0);
},
exec: function (caller) {
var editor = this;
editor.commands.youtube._dropDown(editor, caller, function (id, time, isVertical) {
var orientation = isVertical ? ' data-youtube-orientation="vertical"' : '';
editor.wysiwygEditorInsertHtml(
'<div class="videocontainer"' + orientation + '><div>' +
'<iframe frameborder="0" allowfullscreen src="https://www.youtube-nocookie.com/embed/' +
id + '?wmode=opaque&start=' + time + '" data-youtube-id="' + id +
'" loading="lazy"></iframe></div></div>'
);
});
},
txtExec: function (caller) {
var editor = this;
editor.commands.youtube._dropDown(editor, caller, function (id, time, isVertical) {
editor.insertText('[youtube' + (isVertical ? '=vertical' : '') + ']' + id + '[/youtube]');
});
}
}
);
```
### 3B — แทนที่ `sceditor.formats.bbcode.set('youtube', ...)` ทั้งชุด
ค้นหา `sceditor.formats.bbcode.set(` ที่มี `'youtube'` และ **แทนที่ชุดเดิมทั้งหมด** ด้วย:
```javascript
sceditor.formats.bbcode.set(
'youtube', {
allowsEmpty: true,
tags: {
div: { class: 'videocontainer' }
},
isInline: false,
skipLastLineBreak: true,
format: function (element, content) {
var $element = $(element);
var youtube_id = $element.find('iframe').data('youtube-id');
var orientation = $element.attr('data-youtube-orientation');
if (typeof youtube_id !== 'undefined')
return '[youtube' + (orientation === 'vertical' ? '=vertical' : '') + ']' + youtube_id + '[/youtube]';
return content;
},
html: function (token, attrs, content) {
if (!/^[a-zA-Z0-9_-]{11}$/.test(content))
return content;
var orientation = attrs.defaultattr === 'vertical' ? ' data-youtube-orientation="vertical"' : '';
return '<div class="videocontainer"' + orientation + '><div>' +
'<iframe frameborder="0" src="https://www.youtube-nocookie.com/embed/' + content +
'?wmode=opaque" data-youtube-id="' + content +
'" loading="lazy" allowfullscreen></iframe></div></div>';
}
}
);
```
**ก่อนบันทึก ตรวจอีกครั้ง:** ต้องมี `sceditor.command.set('youtube', ...)` เพียงชุดเดียว และ `sceditor.formats.bbcode.set('youtube', ...)` เพียงชุดเดียว (อย่าให้ BBCode YouTube ถูกประกาศซ้ำ)
## สิ่งที่ผิดในบันทึกเดิม และสิ่งที่แก้
1. **PHP:** บันทึกเดิมมี `;` หลัง `'?origin='` ทำให้ต่อ PHP String กับ `$hosturl` ไม่ถูกต้อง โค้ดที่ถูกคือ `'?origin=' . $hosturl . '&wmode=opaque'` (ไม่มี `;` กลาง expression)
2. **JavaScript:** URL `'.../embed/'; +` มี semicolon เกิน **2 ตำแหน่ง** (คำสั่ง Insert และการแปลง BBCode) ต้องเป็น `'.../embed/' +`
3. **JS BBCode:** ข้อความว่า "และเพิ่มตอนท้าย" ทำให้ประกาศ YouTube ซ้ำ ต้อง "**แทนที่ชุดเดิม**"
4. **CSS:** บันทึกเดิมกำหนด `.videocontainer[data-youtube-orientation="vertical"]` สองครั้ง การทำงานไม่จำเป็นต้องเสีย แต่ควรรวมเหลือกฎชุดเดียวอย่างในคู่มือนี้
## ทดสอบหลังอัปโหลด
1. อัปโหลดไฟล์ทั้ง 3 ไปยังตำแหน่งที่กำหนด (ชื่อไฟล์บนเซิร์ฟเวอร์ต้องตรงตามตาราง ไม่ใช่ชื่อ `(2)` ที่ดาวน์โหลด)
2. ไปหน้าเขียนกระทู้ กด `Ctrl + F5` เพื่อโหลด CSS และ JS ใหม่ แล้วกดปุ่ม YouTube
3. ต้องเห็น URL + **Checkbox "วิดีโอแนวตั้ง 9:16"** + ปุ่มแทรก
4. ทดสอบวิดีโอปกติ **ไม่ติ๊ก** → สร้าง `[youtube]VIDEO_ID[/youtube]`
5. ทดสอบวิดีโอแนวตั้ง **ติ๊ก** → สร้าง `[youtube=vertical]VIDEO_ID[/youtube]`
6. บันทึกกระทู้และเปิดดูบนคอมพิวเตอร์/มือถือ จากนั้นแก้ไขกระทู้อีกครั้งเพื่อตรวจว่า tag แนวตั้งยังอยู่
## ถ้า Checkbox ยังไม่ขึ้น
**อย่ารีบแก้สามไฟล์ใหม่** ให้ตรวจเส้นทาง JS ที่ Browser โหลดก่อน: F12 → Network → ค้นหา `jquery.sceditor.smf.js` → ดู `Request URL` ว่ามาจาก Theme/Path ที่อัปโหลดหรือไม่ หากใช้ไฟล์รวม/minified หรือ cache ต้องปรับตามไฟล์ที่เว็บใช้จริง
## การย้อนกลับ (Rollback)
คืน `Subs.php`, `index.css`, และ `jquery.sceditor.smf.js` จาก Backup ของเว็บเดียวกันและช่วงเวลาเดียวกัน แล้ว Hard Refresh อีกครั้ง
## ไฟล์สำเร็จรูป
ในแพ็กเกจ ZIP คู่มือ จะมีไฟล์ที่ใช้งานได้จริงอยู่ในโครงสร้าง `Sources/` และ `Themes/default/` พร้อมแพตช์เทียบกับ Baseline ที่ผู้ใช้ส่งมาเป็น `* (2)` โดยไม่รวมข้อมูลบัญชีหรือค่าติดตั้งเว็บอื่น











































