// ==UserScript==
// @name FB Marketplace Item Sort
// @namespace http://tampermonkey.net/
// @icon https://www.facebook.com/favicon.ico
// @version 2025-09-24.2
// @description Marketplace 排序上架日期控制面板
// @author Henrik
// @match *://www.facebook.com/*
// @grant none
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
const PANEL_ID = 'marketplace-control-panel';
const COLLAPSE_KEY = PANEL_ID + '-collapsed';
let panel = null;
let container = null;
// ---------- 建立控制面板 ----------
function createPanel() {
if (panel) return;
panel = document.createElement('div');
panel.id = PANEL_ID;
panel.style.position = 'fixed';
panel.style.top = '80px';
panel.style.right = '20px'; // 固定在右側
panel.style.zIndex = '9999';
panel.style.width = '220px';
panel.style.background = 'rgba(255,255,255,0.97)';
panel.style.border = '1px solid #ddd';
panel.style.borderRadius = '8px';
panel.style.boxShadow = '0 4px 10px rgba(0,0,0,0.2)';
panel.style.fontFamily = 'system-ui, -apple-system, "Segoe UI", Roboto, Arial';
panel.style.fontSize = '14px';
panel.style.color = '#111';
// 標題列
const titleBar = document.createElement('div');
titleBar.style.display = 'flex';
titleBar.style.justifyContent = 'space-between';
titleBar.style.alignItems = 'center';
titleBar.style.padding = '4px 6px';
titleBar.style.background = '#f3f3f3';
titleBar.style.fontWeight = '600';
const title = document.createElement('div');
title.textContent = 'Marketplace 排序';
const toggleBtn = document.createElement('button');
toggleBtn.textContent = '-';
toggleBtn.style.border = 'none';
toggleBtn.style.cursor = 'pointer';
toggleBtn.style.fontSize = '16px';
toggleBtn.style.lineHeight = '1';
toggleBtn.style.padding = '0 6px';
titleBar.appendChild(title);
titleBar.appendChild(toggleBtn);
panel.appendChild(titleBar);
// 控制項容器
container = document.createElement('div');
container.style.padding = '8px';
const controls = [
{
label: '排序方式',
param: 'sortBy',
type: 'select',
options: [
{ text: '推薦', value: 'best_match' },
{ text: '從近到遠', value: 'distance_ascend' },
{ text: '由新到舊', value: 'creation_time_descend' },
{ text: '價格低至高', value: 'price_ascend' },
{ text: '價格高至低', value: 'price_descend' }
]
},
{
label: '上架日期(天)',
param: 'daysSinceListed',
type: 'select',
options: [
{ text: '不限', value: '' },
...Array.from({ length: 30 }, (_, i) => ({ text: `${i + 1} 天內`, value: `${i + 1}` }))
]
}
];
const inputs = {};
controls.forEach(ctrl => {
const label = document.createElement('label');
label.textContent = ctrl.label;
label.style.fontWeight = '500';
container.appendChild(label);
if (ctrl.type === 'select') {
const select = document.createElement('select');
select.style.width = '100%';
select.style.padding = '6px';
select.style.borderRadius = '6px';
select.style.border = '1px solid #ccc';
select.style.fontSize = '13px';
ctrl.options.forEach(opt => {
const o = document.createElement('option');
o.value = opt.value;
o.textContent = opt.text;
select.appendChild(o);
});
container.appendChild(select);
inputs[ctrl.param] = select;
}
});
panel.appendChild(container);
document.body.appendChild(panel);
// 初始化 URL 參數
try {
const url = new URL(window.location.href);
controls.forEach(ctrl => {
const val = url.searchParams.get(ctrl.param);
if (val !== null && inputs[ctrl.param]) inputs[ctrl.param].value = val;
});
} catch (e) {
console.warn('URL 解析失敗', e);
}
// select 改變即更新 URL 並刷新頁面
function updateURL() {
try {
const newUrl = new URL(window.location.href);
Object.entries(inputs).forEach(([param, el]) => {
const value = el.value;
if (value) newUrl.searchParams.set(param, value);
else newUrl.searchParams.delete(param);
});
window.location.href = newUrl.toString();
} catch (e) {
console.error('更新 URL 失敗', e);
}
}
Object.values(inputs).forEach(el => el.addEventListener('change', updateURL));
// ---------- 收合/展開 ----------
function setCollapsed(collapsed) {
if (collapsed) {
container.style.display = 'none';
panel.style.width = '160px';
toggleBtn.textContent = '+';
} else {
container.style.display = 'block';
panel.style.width = '220px';
toggleBtn.textContent = '-';
}
localStorage.setItem(COLLAPSE_KEY, collapsed ? '1' : '0');
}
toggleBtn.addEventListener('click', () => {
const isCollapsed = container.style.display !== 'none';
setCollapsed(isCollapsed);
});
const savedCollapsed = localStorage.getItem(COLLAPSE_KEY);
if (savedCollapsed === '1') setCollapsed(true);
}
function removePanel() {
const p = document.getElementById(PANEL_ID);
if (p) p.remove();
panel = null;
}
// ---------- 判斷是否顯示 ----------
function checkPanelVisibility() {
const isMarketplacePage = !!window.location.pathname.match(/\/marketplace/);
const isItemPage = window.location.pathname.includes('/item');
if (isItemPage || !isMarketplacePage) {
if (panel) removePanel();
} else {
if (!panel) createPanel();
}
}
// ---------- SPA 導航支援 ----------
(function () {
const wrapHistory = function (type) {
const orig = history[type];
return function () {
const rv = orig.apply(this, arguments);
setTimeout(checkPanelVisibility, 300);
return rv;
};
};
history.pushState = wrapHistory('pushState');
history.replaceState = wrapHistory('replaceState');
window.addEventListener('popstate', () => setTimeout(checkPanelVisibility, 300));
})();
// 初始檢查
checkPanelVisibility();
})();