Code.gs
.....................................................................................................................................................................
// <<< แก้ตรงนี้: ใส่ Folder ID ของโฟลเดอร์ Google Drive ปลายทาง
const FOLDER_ID = '1OLZib_Pp961usiQR41z-uvPrFy0UZaIo2'; // <<< แก้ตรงนี้
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Attach')
.addItem('Upload file to cell', 'openUploadSidebar')
.addToUi();
}
function openUploadSidebar() {
const html = HtmlService.createHtmlOutputFromFile('Sidebar')
.setTitle('Upload & Link to Cell');
SpreadsheetApp.getUi().showSidebar(html);
}
function uploadAndInsert(payload) {
if (!FOLDER_ID || FOLDER_ID === 'PUT_YOUR_FOLDER_ID_HERE') {
throw new Error('กรุณาใส่ FOLDER_ID ใน Code.gs ก่อนใช้งาน'); // <<< แจ้งเตือนถ้าไม่ได้แก้
}
const folder = DriveApp.getFolderById(FOLDER_ID); // <<< ใช้ FOLDER_ID ที่แก้ไป
const base64 = payload.dataUrl.split(',')[1];
const bytes = Utilities.base64Decode(base64);
const blob = Utilities.newBlob(
bytes,
payload.mimeType || 'application/octet-stream',
payload.name || 'upload.bin'
);
const file = folder.createFile(blob);
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
const url = file.getUrl();
const sheet = SpreadsheetApp.getActiveSheet();
const targetRow = payload.row + (payload.offset || 0);
const targetCol = payload.col;
let value;
if (payload.asHyperlink) {
const text = (payload.displayText && payload.displayText.trim()) || file.getName();
const safeText = text.replace(/"/g, '""');
const safeUrl = url.replace(/"/g, '""');
value = `=HYPERLINK("${safeUrl}","${safeText}")`;
} else {
value = url;
}
sheet.getRange(targetRow, targetCol).setValue(value);
return { id: file.getId(), name: file.getName(), url, row: targetRow, col: targetCol };
}
function getActiveCellRC() {
const range = SpreadsheetApp.getActiveSheet().getActiveCell();
return { row: range.getRow(), col: range.getColumn() };
}
Sidebar.html
.................................................................................................................................................................
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<style>
body { font-family: system-ui, Arial, sans-serif; padding: 12px; }
.box { border: 1px solid #ddd; border-radius: 8px; padding: 12px; }
.row { margin-bottom: 10px; }
button { padding: 8px 12px; border: 0; border-radius: 6px; cursor: pointer; }
.primary { background: #1a73e8; color: #fff; }
.muted { font-size: 12px; color: #666; }
.progress { margin-top: 6px; font-size: 12px; }
input[type="text"] { width: 100%; padding: 6px 8px; box-sizing: border-box; }
</style>
</head>
<body>
<h2>Upload & Link to Cell</h2>
<div class="box">
<div class="row">
<label><b>Choose file(s)</b></label><br>
<input id="file" type="file" multiple />
<div class="muted">Tip: เลือกหลายไฟล์ได้ ระบบจะวางลิงก์เรียงลงตามแถว เริ่มที่เซลล์ที่เลือก</div>
</div>
<div class="row">
<label>
<input id="asHyperlink" type="checkbox" checked />
ใส่เป็นลิงก์แบบ HYPERLINK (แสดงข้อความแทน URL)
</label>
</div>
<div class="row">
<label>Display text (ถ้าเว้นไว้ จะใช้ชื่อไฟล์)</label>
<input id="displayText" type="text" placeholder="เช่น ใบงานนักเรียน, รูปหน้าปก ฯลฯ" />
</div>
<div class="row">
<button class="primary" id="btnUpload">Upload & Insert</button>
</div>
<div id="status" class="progress"></div>
</div>
<script>
const $ = (id) => document.getElementById(id);
document.getElementById('btnUpload').addEventListener('click', async () => {
const input = $('file');
const files = input.files;
if (!files || files.length === 0) {
return setStatus('กรุณาเลือกไฟล์ก่อน', true);
}
setDisabled(true);
setStatus('กำลังเตรียมอัปโหลด...');
// ขอพิกัดเซลล์ที่เลือกจากฝั่งเซิร์ฟเวอร์ไม่ได้โดยตรง
// ใช้วิธีให้ฝั่งเซิร์ฟเวอร์อ่าน ActiveCell เอง: เราจะส่ง row/col จากฝั่งเซิร์ฟเวอร์ไม่จำเป็น
// แต่เพื่อความชัดเจน ขอพิกัดจากฝั่งเซิร์ฟเวอร์ด้วยฟังก์ชันเล็ก ๆ ก็ได้
google.script.run.withSuccessHandler(({row, col}) => {
uploadSequential(files, row, col);
}).withFailureHandler(err => {
setStatus('เกิดข้อผิดพลาด: ' + err.message, true);
setDisabled(false);
}).getActiveCellRC();
});
function uploadSequential(fileList, startRow, startCol) {
const asHyperlink = $('asHyperlink').checked;
const displayText = $('displayText').value || '';
const files = Array.from(fileList);
let idx = 0;
const next = () => {
if (idx >= files.length) {
setStatus('อัปโหลดเสร็จทั้งหมดแล้ว ✅');
setDisabled(false);
return;
}
const f = files[idx];
setStatus(`อัปโหลด: ${f.name} (${idx+1}/${files.length}) ...`);
const reader = new FileReader();
reader.onload = function(e) {
const dataUrl = e.target.result; // base64 dataURL
const payload = {
dataUrl,
name: f.name,
mimeType: f.type || 'application/octet-stream',
asHyperlink,
displayText,
row: startRow,
col: startCol,
offset: idx // วางลงแถวถัดไปเรื่อย ๆ
};
google.script.run
.withSuccessHandler(res => {
setStatus(`เสร็จ: ${res.name} → R${res.row}C${res.col}`);
idx++;
next();
})
.withFailureHandler(err => {
setStatus('เกิดข้อผิดพลาด: ' + err.message, true);
setDisabled(false);
})
.uploadAndInsert(payload);
};
reader.onerror = function() {
setStatus('อ่านไฟล์ไม่สำเร็จ: ' + f.name, true);
setDisabled(false);
};
reader.readAsDataURL(f);
};
next();
}
function setDisabled(disabled) {
$('btnUpload').disabled = disabled;
$('file').disabled = disabled;
$('asHyperlink').disabled = disabled;
$('displayText').disabled = disabled;
}
function setStatus(msg, isError) {
const el = $('status');
el.textContent = msg;
el.style.color = isError ? '#c00' : '#333';
}
</script>
</body>
</html>
EmoticonEmoticon