diff --git a/google-script.js b/google-script.js
index 0ea3242..1806176 100644
--- a/google-script.js
+++ b/google-script.js
@@ -24,41 +24,51 @@ function doPost(e) {
sheet = ss.getActiveSheet();
}
- var headers = [];
-
- // 2. Determine headers
- if (sheet.getLastRow() === 0) {
- // Use provided headers if available
- if (data.headers && Array.isArray(data.headers)) {
- headers = data.headers;
- } else {
- // Fallback: Use keys but exclude internal control keys
- headers = Object.keys(data).filter(function(key) {
- return !["sheetName", "headers"].includes(key);
- });
- }
- sheet.appendRow(headers);
+ var existingHeaders = [];
+ var lastCol = sheet.getLastColumn();
+ var lastRow = sheet.getLastRow();
+
+ // 2. Get incoming headers (from data.headers or keys)
+ var incomingHeaders = data.headers || Object.keys(data).filter(function(k) {
+ return !["sheetName", "headers"].includes(k);
+ });
+
+ if (lastRow === 0) {
+ // New sheet: Write all incoming headers as the first row
+ existingHeaders = incomingHeaders;
+ sheet.appendRow(existingHeaders);
} else {
- // Get existing headers from the first row
- var lastCol = sheet.getLastColumn();
- if (lastCol > 0) {
- headers = sheet.getRange(1, 1, 1, lastCol).getValues()[0];
+ // Existing sheet: Read current headers from row 1
+ existingHeaders = sheet.getRange(1, 1, 1, lastCol).getValues()[0];
+
+ // Find headers that are in 'incoming' but not in 'existing'
+ var missingHeaders = incomingHeaders.filter(function(h) {
+ return existingHeaders.indexOf(h) === -1;
+ });
+
+ if (missingHeaders.length > 0) {
+ // Append missing headers to the end of row 1
+ sheet.getRange(1, lastCol + 1, 1, missingHeaders.length).setValues([missingHeaders]);
+ // Update local existingHeaders list to include new ones
+ existingHeaders = existingHeaders.concat(missingHeaders);
}
}
- // 3. Map data to headers
- var row = headers.map(function(header) {
+ // 3. Map data to the (potentially updated) existingHeaders
+ var row = existingHeaders.map(function(header) {
var val = data[header];
if (val === undefined || val === null) return "N/A";
- // If value is an object or array, stringify it to avoid [Ljava.lang.Object]
if (typeof val === 'object') return JSON.stringify(val);
return val;
});
sheet.appendRow(row);
- return ContentService.createTextOutput(JSON.stringify({ status: "success", sheet: sheet.getName() }))
- .setMimeType(ContentService.MimeType.JSON);
+ return ContentService.createTextOutput(JSON.stringify({
+ status: "success",
+ sheet: sheet.getName(),
+ addedColumns: missingHeaders ? missingHeaders.length : 0
+ })).setMimeType(ContentService.MimeType.JSON);
} catch (error) {
return ContentService.createTextOutput(JSON.stringify({ status: "error", message: error.toString() }))
diff --git a/sidepanel.html b/sidepanel.html
index 60c9ba9..f251242 100644
--- a/sidepanel.html
+++ b/sidepanel.html
@@ -44,7 +44,10 @@
-
+
diff --git a/sidepanel.js b/sidepanel.js
index 03eb423..e953507 100644
--- a/sidepanel.js
+++ b/sidepanel.js
@@ -11,6 +11,7 @@ const newPresetName = document.getElementById('newPresetName');
const newPresetSheet = document.getElementById('newPresetSheet');
const newPresetFields = document.getElementById('newPresetFields');
const addPresetBtn = document.getElementById('addPreset');
+const cancelEditBtn = document.getElementById('cancelEdit');
const presetList = document.getElementById('presetList');
const presetSelect = document.getElementById('presetSelect');
const runPresetBtn = document.getElementById('runPreset');
@@ -26,6 +27,7 @@ const DEFAULT_PRESETS = [
];
let currentPresets = [];
+let editingPresetId = null;
toggleConfig.addEventListener('click', () => {
const isHidden = configContent.classList.toggle('hidden');
@@ -49,7 +51,7 @@ chrome.storage.local.get(['geminiApiKey', 'googleScriptUrl', 'geminiModel', 'use
renderPresets(currentPresets);
});
-// Save settings
+// Save global settings
saveConfigBtn.addEventListener('click', () => {
const apiKey = apiKeyInput.value.trim();
const scriptUrl = scriptUrlInput.value.trim();
@@ -72,25 +74,66 @@ addPresetBtn.addEventListener('click', async () => {
if (!name || !rawFields) return alert('请输入名称和字段');
- // 核心逻辑:支持中英文逗号,并清理多余空格
const normalizedFields = rawFields
- .split(/[,,]/) // 使用正则匹配中英文逗号
- .map(f => f.trim()) // 清理每一项前后的空格
- .filter(f => f !== "") // 过滤掉空项
- .join(', '); // 统一用英文逗号+空格连接
+ .split(/[,,]/)
+ .map(f => f.trim())
+ .filter(f => f !== "")
+ .join(', ');
const { userPresets = DEFAULT_PRESETS } = await chrome.storage.local.get('userPresets');
- const newPreset = { id: Date.now().toString(), name, sheetName, fields: normalizedFields };
- currentPresets = [...userPresets, newPreset];
+
+ if (editingPresetId) {
+ // Update existing
+ currentPresets = userPresets.map(p =>
+ p.id === editingPresetId ? { ...p, name, sheetName, fields: normalizedFields } : p
+ );
+ stopEditing();
+ } else {
+ // Add new
+ const newPreset = { id: Date.now().toString(), name, sheetName, fields: normalizedFields };
+ currentPresets = [...userPresets, newPreset];
+ }
await chrome.storage.local.set({ userPresets: currentPresets });
- newPresetName.value = '';
- newPresetSheet.value = '';
- newPresetFields.value = '';
+ clearPresetInputs();
renderPresets(currentPresets);
});
+cancelEditBtn.addEventListener('click', stopEditing);
+
+function startEditing(preset) {
+ editingPresetId = preset.id;
+ newPresetName.value = preset.name;
+ newPresetSheet.value = preset.sheetName || '';
+ newPresetFields.value = preset.fields;
+
+ addPresetBtn.textContent = '更新预设';
+ addPresetBtn.classList.replace('bg-indigo-50', 'bg-indigo-600');
+ addPresetBtn.classList.replace('text-indigo-700', 'text-white');
+ cancelEditBtn.classList.remove('hidden');
+
+ // Ensure config is expanded so user can see inputs
+ configContent.classList.remove('hidden');
+ configChevron.classList.add('rotate-180');
+}
+
+function stopEditing() {
+ editingPresetId = null;
+ clearPresetInputs();
+ addPresetBtn.textContent = '添加新预设';
+ addPresetBtn.classList.replace('bg-indigo-600', 'bg-indigo-50');
+ addPresetBtn.classList.replace('text-white', 'text-indigo-700');
+ cancelEditBtn.classList.add('hidden');
+}
+
+function clearPresetInputs() {
+ newPresetName.value = '';
+ newPresetSheet.value = '';
+ newPresetFields.value = '';
+}
+
async function deletePreset(id) {
+ if (editingPresetId === id) stopEditing();
const { userPresets = DEFAULT_PRESETS } = await chrome.storage.local.get('userPresets');
currentPresets = userPresets.filter(p => p.id !== id);
await chrome.storage.local.set({ userPresets: currentPresets });
@@ -98,33 +141,32 @@ async function deletePreset(id) {
}
function renderPresets(presets) {
- // Clear containers
presetList.innerHTML = '';
presetSelect.innerHTML = '';
presets.forEach(preset => {
- // 1. Render in Settings List
+ // 1. Settings List Item
const item = document.createElement('div');
item.className = 'flex items-center justify-between bg-white p-3 mb-2 rounded-lg border border-gray-200 shadow-sm transition-all hover:border-indigo-200';
item.innerHTML = `
-
+
${preset.name} ${preset.sheetName ? `#${preset.sheetName}` : ''}
${preset.fields}
-
+
`;
- item.querySelector('button').onclick = (e) => {
- e.stopPropagation();
- deletePreset(preset.id);
- };
+ item.querySelector('.edit-btn').onclick = () => startEditing(preset);
+ item.querySelector('.delete-btn').onclick = () => deletePreset(preset.id);
presetList.appendChild(item);
- // 2. Render as Dropdown Option
+ // 2. Dropdown Option
const opt = document.createElement('option');
opt.value = preset.id;
opt.textContent = preset.name;
@@ -132,7 +174,7 @@ function renderPresets(presets) {
});
}
-// Action Handlers
+// Extraction Handler
runPresetBtn.addEventListener('click', () => {
const selectedId = presetSelect.value;
const preset = currentPresets.find(p => p.id === selectedId);
@@ -149,7 +191,7 @@ async function handleExtraction(type, presetObj) {
const selectedModel = geminiModel || 'gemini-1.5-flash';
updateStatus('提取中...', 'bg-blue-500 text-white');
- resultsArea.textContent = `正在使用 ${selectedModel} 提取 ${presetObj.name || '自定义项'}...`;
+ resultsArea.textContent = `正在使用 ${selectedModel} 提取 ${presetObj.name}...`;
try {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });