重新糊了个UI

经验创意 · 69 次浏览
车站里的守望者 创建于 5天3小时前

原本的UI确实有那么一点点的丑,拿claude3.7重新糊了个UI,作者大大可看看是否有什么问题
(因为是AI一把梭哈,所以UI可能还是有点小问题的)

<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>数据中转站</title>
	<style>
		@import url('https://fonts.googleapis.com/css2?family=SF+Pro+Display:wght@300;400;500;600&display=swap');

		:root {
			--background-light: #ffffff;
			--text-light: #1d1d1f;
			--secondary-light: #6e6e73;
			--border-light: rgba(0, 0, 0, 0.1);
			--card-hover-light: rgba(0, 0, 0, 0.03);
			--card-select-light: rgba(0, 0, 0, 0.05);
			--tab-inactive-light: rgba(0, 0, 0, 0.05);
			--button-hover-light: rgba(0, 0, 0, 0.06);
			--accent-light: #0071e3;
			--danger-light: #ff3b30;
			--shadow-light: 0 1px 5px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06);

			/* Dark mode colors */
			--background-dark: #1d1d1f;
			--text-dark: #f5f5f7;
			--secondary-dark: #a1a1a6;
			--border-dark: rgba(255, 255, 255, 0.15);
			--card-hover-dark: rgba(255, 255, 255, 0.05);
			--card-select-dark: rgba(255, 255, 255, 0.08);
			--tab-inactive-dark: rgba(255, 255, 255, 0.08);
			--button-hover-dark: rgba(255, 255, 255, 0.1);
			--accent-dark: #2997ff;
			--danger-dark: #ff453a;
			--shadow-dark: 0 1px 5px rgba(0, 0, 0, 0.2);

			/* Default theme */
			--background: var(--background-light);
			--text: var(--text-light);
			--secondary: var(--secondary-light);
			--border: var(--border-light);
			--card-hover: var(--card-hover-light);
			--card-select: var(--card-select-light);
			--tab-inactive: var(--tab-inactive-light);
			--button-hover: var(--button-hover-light);
			--accent: var(--accent-light);
			--danger: var(--danger-light);
			--shadow: var(--shadow-light);

			/* Animation timing */
			--transition-fast: 0.15s;
			--transition-medium: 0.25s;
			--transition-slow: 0.4s;

			/* Spacing */
			--spacing-xs: 4px;
			--spacing-sm: 8px;
			--spacing-md: 16px;
			--spacing-lg: 24px;
			--spacing-xl: 32px;

			/* Border radius */
			--radius-sm: 6px;
			--radius-md: 8px;
			--radius-lg: 12px;
		}

		::-webkit-scrollbar {
			width: 8px;
			height: 8px;
		}

		::-webkit-scrollbar-thumb {
			background: rgba(0, 0, 0, 0.15);
			border-radius: 8px;
		}

		.dark::-webkit-scrollbar-thumb {
			background: rgba(255, 255, 255, 0.2);
		}

		::-webkit-scrollbar-thumb:hover {
			background: rgba(0, 0, 0, 0.25);
		}

		.dark::-webkit-scrollbar-thumb:hover {
			background: rgba(255, 255, 255, 0.3);
		}

		::-webkit-scrollbar-track {
			background: transparent;
		}

		html,
		body {
			margin: 0;
			height: 100%;
			width: 100%;
			font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif;
			color: var(--text);
			background-color: var(--background);
			font-size: 14px;
			line-height: 1.5;
			-webkit-font-smoothing: antialiased;
			-moz-osx-font-smoothing: grayscale;
			transition: background-color var(--transition-medium), color var(--transition-medium);
		}

		body {
			display: flex;
			flex-direction: column;
		}

		#tab-bar {
			display: flex;
			justify-content: space-between;
			align-items: center;
			padding: var(--spacing-xs) var(--spacing-sm);
			backdrop-filter: blur(10px);
			-webkit-backdrop-filter: blur(10px);
			background-color: rgba(255, 255, 255, 0.8);
			border-bottom: 1px solid var(--border);
            position: sticky;
            top: 0;
            z-index: 100;
		}

		.dark #tab-bar {
			background-color: rgba(29, 29, 31, 0.85);
		}

		#tab-bar,
		#tool-menu {
			border-bottom: 1px solid var(--border);
			transition: border-color var(--transition-medium);
		}

		#tool-menu {
			display: none;
			padding: var(--spacing-xs) var(--spacing-md);
			background-color: rgba(255, 255, 255, 0.8);
			backdrop-filter: blur(10px);
			-webkit-backdrop-filter: blur(10px);
		}

		.dark #tool-menu {
			background-color: rgba(29, 29, 31, 0.85);
		}

		body.manage #tool-menu {
			display: flex;
			gap: var(--spacing-sm);
			align-items: center;
		}

		#tab-head-container {
			display: flex;
			gap: var(--spacing-xs);
			align-items: center;
			overflow-x: auto;
			scrollbar-width: none;
			-ms-overflow-style: none;
		}

		#tab-head-container::-webkit-scrollbar {
			display: none;
		}

		#tab-plus,
		.tab-head {
			border-radius: var(--radius-md);
			padding: var(--spacing-xs) var(--spacing-sm);
			transition: background-color var(--transition-fast), transform var(--transition-fast), box-shadow var(--transition-medium);
			border: none;
			display: flex;
			align-items: center;
			cursor: pointer;
			height: 28px;
			user-select: none;
		}

		#tab-plus {
			background-color: var(--tab-inactive);
			color: var(--secondary);
			font-weight: 500;
			font-size: 16px;
			justify-content: center;
			min-width: 28px;
		}

		.tab-head:not(.active) {
			background-color: var(--tab-inactive);
			color: var(--secondary);
		}

		.tab-head.active {
			background-color: var(--accent);
			color: white;
			font-weight: 500;
			box-shadow: 0 3px 10px rgba(0, 113, 227, 0.3);
		}

		.dark .tab-head.active {
			box-shadow: 0 3px 10px rgba(41, 151, 255, 0.3);
		}

		#tab-plus:hover,
		.tab-head:not(.active):hover {
			background-color: var(--button-hover);
			transform: translateY(-1px);
		}

		.tab-head.active:hover {
			transform: translateY(-1px);
		}

		.tab-head.active:active, 
		#tab-plus:active,
		.tab-head:not(.active):active {
			transform: translateY(0);
		}

		body.manage {
			.card {
				padding-right: var(--spacing-md);
				position: relative;

				.card-extra {
					display: none;
				}
			}

			.card-close-button {
				position: absolute;
				right: var(--spacing-xs);
				top: 50%;
				transform: translateY(-50%);
				opacity: 0.7;
				transition: opacity var(--transition-fast);
				width: 22px;
				height: 22px;
				display: flex;
				align-items: center;
				justify-content: center;
				border-radius: 50%;
				background-color: var(--card-hover);
				
				&:hover {
					opacity: 1;
					background-color: var(--danger);
					color: white;
				}
			}

			.tab-head-close-button {
				margin-left: var(--spacing-xs);
				opacity: 0.7;
				transition: opacity var(--transition-fast), transform var(--transition-fast);
				width: 18px;
				height: 18px;
				display: flex;
				align-items: center;
				justify-content: center;
				border-radius: 50%;
				
				&:hover {
					opacity: 1;
					transform: scale(1.1);
					background-color: rgba(255, 255, 255, 0.3);
				}
			}

			.tab-head-close-button::after,
			.card-close-button::after {
				content: "×";
				font-size: 16px;
				line-height: 1;
				cursor: pointer;
			}
		}

		.button-control,
		.text-control {
			border-radius: var(--radius-md);
			padding: var(--spacing-xs) var(--spacing-sm);
			background-color: transparent;
			transition: background-color var(--transition-fast), transform var(--transition-fast);
			user-select: none;
			height: 28px;
			display: flex;
			align-items: center;
			cursor: pointer;
		}

		.text-control {
			padding-right: var(--spacing-md);
		}

		.tab-head span {
			outline: none;
			margin-right: var(--spacing-xs);
		}

		#settings-menu {
			border-left: 1px solid var(--border);
			padding: var(--spacing-md);
			min-width: 200px;
			background-color: var(--background);
			transition: border-color var(--transition-medium);
		}

		.emoji {
			font-family: 'Apple Color Emoji', 'Segoe UI Emoji', sans-serif;
			margin-right: 4px;
		}

		.text-control:hover,
		.button-control:hover {
			background-color: var(--button-hover);
			transform: translateY(-1px);
		}

		.text-control:active,
		.button-control:active {
			transform: translateY(0);
		}

		#trash-button.dragover {
			background-color: var(--danger);
			color: white;
			transform: scale(1.1);
		}

		.tab-content {
			overflow-y: auto;
			flex: 1;
			padding: var(--spacing-md);
			display: flex;
			flex-direction: column;
			gap: var(--spacing-sm);
			transition: all var(--transition-medium);
		}

		.tab-content:not(.active) {
			display: none;
		}

		.card.selected {
			background-color: var(--card-select);
			border-left: 3px solid var(--accent);
		}

		.card.selected:hover {
			background-color: var(--card-hover);
		}

		.card.selected[draggable=true] {
			cursor: grab;
		}

		.card {
			position: relative;
			border-radius: var(--radius-md);
			background-color: var(--background);
			box-shadow: var(--shadow);
			max-height: max(50%, 100px);
			box-sizing: border-box;
			overflow: hidden;
			display: flex;
			transition: transform var(--transition-fast), 
						box-shadow var(--transition-medium), 
						background-color var(--transition-fast),
						border-left var(--transition-fast);
			border-left: 3px solid transparent;
		}

		.card:hover {
			background-color: var(--card-hover);
			transform: translateY(-2px);
			box-shadow: 0 3px 10px rgba(0, 0, 0, 0.08);
		}

		.dark .card:hover {
			box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
		}
		
        .card-checkbox {
            margin: var(--spacing-md);
            appearance: none;
            width: 18px;
            height: 18px;
            border-radius: 4px;
            border: 1.5px solid var(--secondary);
            background-color: var(--background);
            position: relative;
            cursor: pointer;
            transition: all var(--transition-fast);
            
            &:checked {
                background-color: var(--accent);
                border-color: var(--accent);
                
                &::after {
                    content: "";
                    position: absolute;
                    top: 3px;
                    left: 6px;
                    width: 4px;
                    height: 8px;
                    border: solid white;
                    border-width: 0 2px 2px 0;
                    transform: rotate(45deg);
                }
            }
            
            &:hover {
                border-color: var(--accent);
            }
        }

		.card-wrapper {
			width: 100%;
			box-sizing: border-box;
			overflow-y: auto;
			padding: var(--spacing-md);
			flex: 1;

			.card-content {
				outline: none;
				width: 100%;
				padding-block: var(--spacing-xs);
				transition: all var(--transition-medium);

				* {
					pointer-events: none;
				}
			}

			.card-extra:empty {
				display: none;
			}
		}

		.card.text {
			.card-content {
				font-family: 'SF Mono', SFMono-Regular, ui-monospace, Menlo, Monaco, monospace;
				white-space: pre-wrap;
				word-break: break-all;
				font-size: 13px;
			}
		}

		.card.html {
			.card-content * {
				margin: 0 !important
			}
		}

		body:not(.manage) {
			.card.file .card-content:hover {
				color: var(--accent);
			}
		}

		.card.file {
			.card-content {
				font-family: 'SF Mono', SFMono-Regular, ui-monospace, Menlo, Monaco, monospace;
				white-space: nowrap;
				overflow-x: auto;
				font-size: 13px;
				display: flex;
				align-items: center;

				&.file::before,
				&.non-exist::before {
					content: "📄";
					margin-right: var(--spacing-sm);
					font-size: 16px;
				}

				&.directory::before {
					content: "📁";
					margin-right: var(--spacing-sm);
					font-size: 16px;
				}

				&.directory.open::before {
					content: "📂";
					margin-right: var(--spacing-sm);
					font-size: 16px;
				}

				&.non-exist {
					text-decoration: line-through;
					opacity: 0.7;
				}

				&::-webkit-scrollbar {
					height: 4px;
				}
			}

			.card-extra {
				font-family: 'SF Mono', SFMono-Regular, ui-monospace, Menlo, Monaco, monospace;
				white-space: nowrap;
				overflow-y: auto;
				margin-top: var(--spacing-sm);
				padding: var(--spacing-sm);
				border-radius: var(--radius-sm);
				border-top: 1px solid var(--border);
				max-height: 250px;
				background-color: rgba(0, 0, 0, 0.02);
				transition: border-color var(--transition-medium);

				.dark & {
					background-color: rgba(255, 255, 255, 0.05);
				}

				img {
					max-width: 100%;
					max-height: 100%;
					border-radius: var(--radius-sm);
					object-fit: contain;
				}

				.card:last-of-type {
					border-bottom: none;
				}
			}
		}

		#card-container {
			display: flex;
			flex: 1;
			overflow-y: auto;
		}

		.config-option {
			display: flex;
			align-items: center;
			padding: var(--spacing-xs) 0;
			cursor: pointer;
			transition: color var(--transition-fast);
			
			&:hover {
				color: var(--accent);
			}
		}

		select.config-option[size] {
			border-radius: var(--radius-sm);
			border: 1px solid var(--border);
			background-color: var(--background);
			color: var(--text);
			outline: none;
			padding: var(--spacing-xs);
			margin: var(--spacing-xs) 0 var(--spacing-md) 0;
			cursor: pointer;
			transition: all var(--transition-medium);
			
			&:focus {
				border-color: var(--accent);
			}
			
			option {
				padding: var(--spacing-xs);
			}
		}

		label {
			display: block;
			margin: var(--spacing-sm) 0 var(--spacing-xs) 0;
			font-weight: 500;
		}
		


     input[type="checkbox"] {
         appearance: none;
         width: 18px;
         height: 18px;
         border-radius: 4px;
         border: 1.5px solid var(--secondary);
         margin-right: var(--spacing-sm);
         position: relative;
         cursor: pointer;
         transition: all var(--transition-fast);
         background-color: var(--background);
         
         &:checked {
             background-color: var(--accent);
             border-color: var(--accent);
             
             &::after {
                 content: "";
                 position: absolute;
                 top: 3px;
                 left: 6px;
                 width: 4px;
                 height: 8px;
                 border: solid white;
                 border-width: 0 2px 2px 0;
                 transform: rotate(45deg);
             }
         }
         
         &:hover {
             border-color: var(--accent);
         }
         
         &:focus {
             outline: none;
             box-shadow: 0 0 0 2px rgba(0, 113, 227, 0.3);
         }
     }
     
     .dark input[type="checkbox"] {
         background-color: var(--background-dark);
     }

		/* Animations */
		@keyframes fadeIn {
			from { opacity: 0; }
			to { opacity: 1; }
		}

		@keyframes slideIn {
			from { transform: translateY(10px); opacity: 0; }
			to { transform: translateY(0); opacity: 1; }
		}

		.card {
			animation: slideIn var(--transition-medium) ease-out;
		}

		.tab-head {
			animation: fadeIn var(--transition-fast) ease-out;
		}
	</style>
	<style>
		.dark {
			--background: var(--background-dark);
			--text: var(--text-dark);
			--secondary: var(--secondary-dark);
			--border: var(--border-dark);
			--card-hover: var(--card-hover-dark);
			--card-select: var(--card-select-dark);
			--tab-inactive: var(--tab-inactive-dark);
			--button-hover: var(--button-hover-dark);
			--accent: var(--accent-dark);
			--danger: var(--danger-dark);
			--shadow: var(--shadow-dark);
		}
	</style>
</head>

<body>
	<nav id="tab-bar">
		<div id="tab-head-container">
			<div id="tab-plus">+</div>
		</div>
		<div style="display: flex; gap: var(--spacing-xs);">
			<div class="button-control emoji" id="trash-button" title="点击删除选中条目&#13;或拖动到这里删除">🗑</div>
			<div class="button-control emoji" id="settings-trigger"
				onclick="document.querySelector('#settings-menu').hidden^=1" title="设置">⚙
			</div>
			<div class="button-control emoji" id="edit-toggle" title="编辑内容" onclick="toggleManage()">📝</div>
			<div class="button-control emoji" id="tool-refreshfile" title="刷新文件状态">🔄</div>
<div class="button-control emoji" id="tool-readclipboard" title="添加剪贴板条目">➕</div>         </div> </nav> <div id="tool-menu" hidden> <div class="text-control emoji" id="tool-clearformat">🧹 清除格式</div> <div class="text-control emoji" id="tool-cardjoin">🔗 合并</div> </div> <div style="display:flex;flex:1;overflow-y:auto;"> <div id="card-container"></div> <div id="settings-menu" style="overflow-y:auto;" hidden> <label class="config-option"><input type="checkbox" id="config-richin" checked>拖入保留格式</label> <label class="config-option"><input type="checkbox" id="config-richout" checked>拖出保留格式</label> <label class="config-option"><input type="checkbox" id="config-copynotify">复制后提示消息</label> <label class="config-option"><input type="checkbox" id="config-droponjoin">合并后删除原卡片</label> <label class="config-option"><input type="checkbox" id="config-autopreview" checked>拖入文件自动预览</label> <label>单击效果</label> <select class="config-option" id="config-clickevent" size="2"> <option value="none" selected>无</option> <option value="copy">复制</option> </select> <label>双击效果——文本</label> <select class="config-option" id="config-text-dblclick" size="3"> <option value="none">无</option> <option value="copy">复制</option> <option value="edit" selected>编辑</option> </select> <label>双击效果——文件</label> <select class="config-option" id="config-file-dblclick" size="5"> <option value="none">无</option> <option value="copy">复制</option> <option value="open">打开</option> <option value="preview" selected>预览</option> <option value="locate">定位</option> </select> <label class="config-option"><input type="checkbox" id="config-previewfallback" checked>预览失败时打开文件</label> <label>拖放效果</label> <select class="config-option" id="config-dropeffect" size="4"> <option value="all" selected>自动</option> <option value="copy">复制</option> <option value="move">移动</option> <option value="link">链接</option> </select> <label>主题颜色</label> <select class="config-option" id="config-colorscheme" size="3" onchange="updateTheme()"> <option value="light">亮</option> <option value="dark">暗</option> <option value="system" selected>系统</option> </select> </div> </div> <script> function createElement(tagName, property = {}) { let ele = document.createElement(tagName); for (let attribute in property) { if (attribute == "style") { for (let key in property.style) { ele.style[key] = property.style[key] } } else { if (attribute) ele[attribute] = property[attribute] } } return ele } function getDateString(date) { return `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}_${date.getHours()}${date.getMinutes()}${date.getSeconds()}` } function getRandomId(prefix) { let id = prefix + "-" + Math.random().toString(36).slice(2); if (document.getElementById(id)) return getRandomId(prefix); return id; } function getFullPathAsync(file) { let timestamp = +new Date(); let { resolve, reject, promise } = Promise.withResolvers(); let listener = function (e) { if (e.data.op == "customMessage" && e.data.action == "responseFullPath" && e.data.timestamp == timestamp) { chrome.webview.removeEventListener("message", listener); resolve(e.data.paths[0]); } } chrome.webview.addEventListener("message", listener); try { chrome.webview.postMessageWithAdditionalObjects({ op: "customMessage", action: "requestFullPath", timestamp }, [file]); } catch (e) { reject(e); } return promise; } function getFileSystemHandlerAsync(path) { let timestamp = +new Date(); let { resolve, reject, promise } = Promise.withResolvers(); let listener = function (e) { if (e.data.op == "customMessage" && e.data.action == "responseFile" && e.data.timestamp == timestamp) { chrome.webview.removeEventListener("message", listener); resolve(e.additionalObjects[0]); } } chrome.webview.addEventListener("message", listener); chrome.webview.postMessage({ op: "customMessage", action: "requestFile", timestamp, paths: [path] }); return promise; } async function saveTempfileAsync(file) { let { promise, resolve, reject } = Promise.withResolvers(); const reader = new FileReader(); reader.onload = (event) => resolve(event.target.result); reader.onerror = (error) => reject(error); reader.readAsDataURL(file) let data = await promise; let { path } = await $quickerSp('保存临时文件', { data: data.split(",").pop(), ext: file.type.split("/").pop() }); return path; } async function getFileInfoAsync(path) { let res = await $quickerSp('获取文件信息', { path }); res.type = !res.exists ? "non-exist" : res.isDirectory ? "directory" : "file"; return res; } function notify(message, type = "Info") { $quickerSp('提示消息', { type, message }); } // Extend Array with fromAsync utility Array.fromAsync = async function(items, mapFn) { const results = []; for (const item of items) { results.push(await mapFn(item)); } return results; }; </script> <script> function createTab(name) { let id = "tab-" + Math.random().toString(36).slice(2); if (document.getElementById(id)) return createTab(name); let thContainer = document.querySelector('#tab-head-container'); if (!name) name = "标签" + thContainer.childElementCount; let tbPlus = document.querySelector('#tab-plus'); let tabHead = createElement('div', { id, className: 'tab-head', draggable: true, ondragstart: function (e) { e.dataTransfer.setData("text/plain", this.innerText); e.dataTransfer.setData("datatransfer/tab", id); e.dataTransfer.effectAllowed = "all"; e.dataTransfer.setDragImage(new Image(), 0, 0); }, }); tbPlus.before(tabHead); tabHead.append(createElement('span', { className: 'tab-head-content', innerText: name, onblur: function (e) { if (this.innerText == "") this.innerText = "标签"; }, onkeydown: function (e) { if (e.key == "Enter") { e.preventDefault(); this.blur(); } } })); tabHead.append(createElement('span', { className: 'tab-head-close-button', onclick: (e) => { e.stopPropagation(); tabHead.dispose(); } })); let tabContent = createElement('div', { className: 'tab-content' }); document.querySelector('#card-container').append(tabContent); tabHead.contentDiv = tabContent; tabContent.headDiv = tabHead; tabHead.ondragover = function (e) { e.preventDefault(); e.dataTransfer.dropEffect = "move"; }; tabHead.ondrop = function (e) { e.preventDefault(); let data; data = e.dataTransfer.getData("datatransfer/tab"); if (data) { let targetTab = e.target.closest('.tab-head'); let draggingElement = document.getElementById(data); let appendFunc = e.offsetX < targetTab.clientWidth / 2 ? "before" : "after"; targetTab[appendFunc](draggingElement); draggingElement.activate(); return } }; tabHead.dispose = tabContent.dispose = function () { if (tabHead.classList.contains("active")) { if (tabHead.previousElementSibling) tabHead.previousElementSibling.activate(); else if (tabHead.nextElementSibling != tbPlus) tabHead.nextElementSibling.activate(); } // Add slide out animation before removal tabHead.style.transition = "all 0.3s"; tabContent.style.transition = "all 0.3s"; tabHead.style.transform = "translateY(-20px)"; tabHead.style.opacity = "0"; tabContent.style.transform = "translateY(20px)"; tabContent.style.opacity = "0"; setTimeout(() => { tabHead.remove(); tabContent.remove(); if (!document.querySelector('.tab-head.active')) createTab().activate(); }, 300); }; tabContent.activate = tabHead.activate = tabHead.onclick = function () { document.querySelectorAll('.tab-head.active').forEach(e => { if (e != tabHead) e.classList.remove('active') }); document.querySelectorAll('.tab-content').forEach(e => { if (e != tabContent) { e.classList.remove('active'); e.querySelectorAll('.card.selected').forEach(cd => cd.classList.remove("selected")); } }); tabHead.classList.add('active'); tabContent.classList.add('active'); }; tabHead.ondragenter = function (e) { let activeHead = document.querySelector('.tab-head.active') let activeContent = document.querySelector('.tab-content.active') if (activeHead != tabHead) { activeHead.classList.remove('active'); tabHead.classList.add('active'); } if (activeContent != tabContent) { activeContent.classList.remove('active'); tabContent.classList.add('active'); } } tabContent.ondrop = async function (e) { e.preventDefault(); e.dataTransfer.dropEffect = "copy"; targetCard = e.target.closest('.card'); if (e.dataTransfer.types.includes("datatransfer/card") && e.dataTransfer.effectAllowed != "copy") { let elements = e.dataTransfer.getData("datatransfer/card").split("\n").map(x => document.getElementById(x)); if (targetCard) { let appendFunc = e.offsetY < targetCard.clientHeight / 2 ? "before" : "after"; targetCard[appendFunc](...elements); } else { tabContent.append(...elements); } } else { if (targetCard) { let appendFunc = e.offsetY < targetCard.clientHeight / 2 ? "before" : "after"; targetCard[appendFunc](...await createCardFromDataTransfer(e.dataTransfer)); } else { tabContent.append(...await createCardFromDataTransfer(e.dataTransfer)); } } }; tabContent.ondragover = function (e) { e.preventDefault(); if (e.dataTransfer.types.includes("datatransfer/card")) { e.dataTransfer.dropEffect = "all"; } else { e.dataTransfer.dropEffect = "copy"; } }; tabContent.onclick = function (e) { if (e.target == e.currentTarget) document.querySelectorAll('.card.selected').forEach(ele => ele.selected = false); }; return tabHead; } </script> <script> let editing = false; function toggleManage() { editing = !editing; document.body.classList.toggle('manage', editing); for (let card of document.querySelectorAll('.card')) { card.editable = editing; } for (let ele of document.querySelectorAll('.tab-head-content')) { if (!editing) { ele.contentEditable = false; } else { ele.contentEditable = "plaintext-only"; } } } function getSelectedCardData() { let selectedCards = Array.from(document.querySelector('.tab-content.active').querySelectorAll('.card.selected')); let data = { "datatransfer/card": selectedCards.map(ele => ele.id).join("\n"), }; if (selectedCards.some(x => x.type == "file")) { data["Files"] = selectedCards.filter(ele => ele.type == "file").map(ele => ele.contentDiv.innerText); } data["text/plain"] = selectedCards.map(ele => ele.contentDiv.innerText).join("\n"); if (config.richout) { data["text/html"] = selectedCards.map(ele => ele.contentDiv.innerHTML).join("<br>"); } return data; } function getSelectedCardJoin() { let selectedCards = Array.from(document.querySelector('.tab-content.active').querySelectorAll('.card.selected')); if (selectedCards.some(x => x.type == "html")) { return createHTMLCard(selectedCards.map(x => x.contentDiv.innerHTML).join("<br>")); } else { return createTextCard(selectedCards.map(x => x.contentDiv.innerText).join("\n")); } } function selectCard(card, dropOthers = false) { if (dropOthers) document.querySelectorAll('.card.selected').forEach(ele => ele.selected = false); card.selected = true; } function onCardDragStart(e) { selectCard(e.currentTarget, document.querySelectorAll('.card.selected').length <= 1 && !e.currentTarget.selected && !e.ctrlKey); e.preventDefault(); let effect = e.ctrlKey ? "copy" : config.dropeffect; chrome.webview.postMessage({ op: "customMessage", action: "doDragDrop", effect, data: getSelectedCardData() }); } async function onCardClick(e) { if (editing) return; e.stopPropagation(); selectCard(e.currentTarget, !e.ctrlKey); if (config.clickevent == "copy") { clipboardWrite("copy"); } } async function onCardContextMenu(e) { if (editing) return; e.preventDefault(); e.stopPropagation(); let activeCard = e.currentTarget; selectCard(activeCard, document.querySelectorAll('.card.selected').length <= 1 && !activeCard.selected && !e.ctrlKey); let selectedCards = Array.from(document.querySelector('.tab-content.active').querySelectorAll('.card.selected')); let types = cardTypes.filter(x => selectedCards.some(ele => ele.type == x)); let menuData = [ "[fa:Regular_Copy]复制|复制", "[fa:Regular_Trash]删除|删除", ]; let paths = selectedCards.filter(x => x.type == "file").map(x => x.contentDiv.innerText); if (selectedCards.length == 1) { if (activeCard.type == "html") { menuData.push("[fa:Solid_Edit]编辑HTML源码|编辑HTML源码"); } if (activeCard.type == "file") { let contentDiv = activeCard.contentDiv let { exists, isDirectory } = await getFileInfoAsync(contentDiv.innerText); if (exists) { menuData.push("[fa:Solid_PaperPlane]用默认打开方式打开|打开文件"); menuData.push("[fa:Regular_FolderOpen]在资源管理器中定位|在资源管理器中定位文件"); } if (isDirectory) { if (contentDiv.classList.contains("open")) menuData.push("[fa:Regular_FolderTree]收起|收起文件夹"); else menuData.push("[fa:Regular_FolderTree]列出文件|展开文件夹"); } } } menuData.push("[fa:Regular_ArrowCircleRight]转换为"); if (types.some(x => x != "text")) { menuData.push(" [fa:Regular_Text]纯文本|转换为纯文本"); } if (types.some(x => x != "html")) { menuData.push(" [fa:Regular_Globe]HTML|转换为HTML"); } if (types.some(x => x != "file")) { menuData.push(" [fa:Regular_File]文件|转换为文件"); } if (selectedCards.length > 1) { menuData.push("[fa:Regular_Archive]合并|合并"); } menuData.push("[fa:Regular_Text]将文本另存为|保存文本"); if (types.includes("file")) { menuData.push("[fa:Regular_FileAlt]文件操作"); menuData.push(" [fa:Brands_Windows]资源管理器菜单|资源管理器菜单"); menuData.push(" [fa:Regular_Copy]将所选文件(夹)复制到|文件复制到"); menuData.push(" [fa:Regular_FolderOpen]将所选文件(夹)移动到|文件移动到"); } let { action } = await $quickerSp('右键菜单', { menuData }); if (action == "复制") { clipboardWrite("copy"); } else if (action == "转换为纯文本") { selectedCards.forEach(ele => ele?.assign?.("type", "text")); } else if (action == "转换为文件") { selectedCards.forEach(ele => ele?.assign?.("type", "file")); } else if (action == "转换为HTML") { selectedCards.forEach(ele => ele?.assign?.("type", "html")); } else if (action == "删除") { // Add animation before removal for (let card of selectedCards) { card.style.transition = "all 0.3s"; card.style.transform = "translateX(50px)"; card.style.opacity = "0"; } // Remove after animation completes setTimeout(() => { selectedCards.forEach(ele => ele?.remove()); }, 300); } else if (action == "编辑") { selectedCards.forEach(ele => ele?.editContent()); } else if (action == "编辑HTML源码") { selectedCards.forEach(ele => ele?.editSource()); } else if (action == "展开文件夹") { selectedCards.forEach(ele => ele?.assign?.("open", true)); } else if (action == "收起文件夹") { selectedCards.forEach(ele => ele?.assign?.("open", false)); } else if (action == "打开文件") { $quickerSp('运行或打开', { path: paths }); } else if (action == "在资源管理器中定位文件") { $quickerSp('在资源管理器中定位文件', { path: paths }); } else if (action == "资源管理器菜单") { $quickerSp('资源管理器菜单', { paths }); } else if (action == "文件复制到") { $quickerSp('文件和目录操作', { paths, action: "copy" }); } else if (action == "文件移动到") { $quickerSp('文件和目录操作', { paths, action: "move" }); } else if (action == "保存文本") { $quickerSp('将文本另存为', { text: selectedCards.map(x => x.contentDiv.innerText).join("\n") }); } else if (action == "合并") { const newCard = await getSelectedCardJoin(); activeCard.before(newCard); // Add slide-in animation for new card newCard.style.transform = "translateY(20px)"; newCard.style.opacity = "0"; setTimeout(() => { newCard.style.transform = "translateY(0)"; newCard.style.opacity = "1"; }, 10); if (config.droponjoin) { // Add slide-out animation before removal for (let card of selectedCards) { card.style.transition = "all 0.3s"; card.style.transform = "translateY(20px)"; card.style.opacity = "0"; } // Remove after animation completes setTimeout(() => { selectedCards.forEach(ele => ele.remove()); }, 300); } } } async function clipboardWrite(effect = "all") { await chrome.webview.postMessage({ op: "customMessage", action: "doCopy", effect, data: getSelectedCardData() }); if (config.copynotify) { notify("已复制到剪贴板", "Success"); } } async function createDirectoryDiv(path) { let div = createElement('div', { className: 'items', innerText: path }); path = path.replace(/\\+$/g, ""); let handle = await getFileSystemHandlerAsync(path) div.replaceChildren(...await Array.fromAsync(handle.values(), h => createFileCard(`${path}\\${h.name}`))) return div; } const cardTypes = ["html", "file", "text", "image"]; function createCardBase() { let card = createElement('div', { id: getRandomId("card"), className: 'card', draggable: true, ondragstart: onCardDragStart, onclick: onCardClick, oncontextmenu: onCardContextMenu }); let checkbox = createElement('input', { type: 'checkbox', className: 'card-checkbox', onclick: (e) => e.stopPropagation(), onchange: async function (e) { card.classList.toggle('selected', this.checked) if (config.clickevent == "copy") { clipboardWrite("copy"); } } }); card.append(checkbox); let cardWrapper = createElement('div', { className: 'card-wrapper' }); let cardContent = createElement('div', { className: 'card-content' }); let cardExtra = createElement('div', { className: 'card-extra' }); card.append(cardWrapper); cardWrapper.append(cardContent); cardWrapper.append(cardExtra); card.append(createElement('div', { className: 'card-close-button', onclick: function (e) { e.stopPropagation(); // Add slide-out animation before removal card.style.transition = "all 0.3s"; card.style.transform = "translateY(20px)"; card.style.opacity = "0"; // Remove after animation completes setTimeout(() => { card.remove(); }, 300); } })); Object.defineProperty(card, "selected", { get: function () { return checkbox.checked; }, set: function (value) { let v = !!value; checkbox.checked = v; card.classList.toggle("selected", v); // Add subtle animation when selected if (v) { card.animate([ { transform: 'scale(1)' }, { transform: 'scale(1.02)' }, { transform: 'scale(1)' } ], { duration: 300, easing: 'ease-in-out' }); } } }); Object.defineProperty(card, "editable", { get: function () { return card.draggable }, set: function (value) { let type = card.type; if (value) { card.draggable = false; cardContent.contentEditable = type == "html" ? true : 'plaintext-only'; // Add edit mode styling cardContent.style.boxShadow = "inset 0 0 0 2px var(--accent)"; cardContent.style.padding = "var(--spacing-sm)"; cardContent.style.borderRadius = "var(--radius-sm)"; // Focus on the content setTimeout(() => { cardContent.focus(); // Place cursor at end const range = document.createRange(); range.selectNodeContents(cardContent); range.collapse(false); const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range); }, 10); } else { card.draggable = true; cardContent.contentEditable = false; // Remove edit mode styling cardContent.style.boxShadow = ""; cardContent.style.padding = ""; } }, configurable: true }); Object.defineProperty(card, "type", { get: function () { return cardTypes.find(x => [...card.classList].includes(x)); }, set: function (new_type) { let old_type = card.type; card.classList.remove(...cardTypes); cardExtra.replaceChildren(); if (new_type == "html") { card.classList.add("html"); } if (new_type == "file") { card.classList.add("file"); cardContent.textContent = cardContent.innerText; } if (new_type == "text") { card.classList.add("text"); if (old_type == "html") { cardContent.textContent = cardContent.innerText.replace(/\r/g, "").replace(/\n\n+/g, "\n\n"); } else { cardContent.textContent = cardContent.innerText; } } // Add animation when changing type card.animate([ { opacity: 0.7 }, { opacity: 1 } ], { duration: 300, easing: 'ease-in-out' }); card.updateInfo(); } }); Object.defineProperty(card, "open", { get: function () { return cardContent.classList.contains("open"); }, set: async function (value) { if (editing) return; const wasOpen = cardContent.classList.contains("open"); cardContent.classList.toggle("open", value); if (!value) { // Add animation for closing if (wasOpen) { cardExtra.style.transition = "all 0.3s"; cardExtra.style.maxHeight = "0"; cardExtra.style.opacity = "0"; // Wait for animation then clear setTimeout(() => { cardExtra.replaceChildren(); cardExtra.style = ""; }, 300); } return; } if (card.type != "file") return; let path = cardContent.innerText; let { exists, isDirectory, mime } = await getFileInfoAsync(path); if (!exists) return; // Start with zero height for animation if (!wasOpen) { cardExtra.style.maxHeight = "0"; cardExtra.style.opacity = "0"; cardExtra.style.transition = "all 0.3s"; } if (isDirectory) { let div = await createDirectoryDiv(path); cardExtra.replaceChildren(div); } else { let file = await getFileSystemHandlerAsync(path); file = await file.getFile(); if (file == null) return; if (file.type.startsWith("image/")) { let img = createElement('img', { src: URL.createObjectURL(file) }); img.ondblclick = function (e) { window.open(this.src, "_blank", `popup=yes,width=${window.innerWidth},height=${window.innerHeight},top=${window.screenY},left=${Math.max(window.screenX - window.innerWidth - 20, 0)}`); } cardExtra.replaceChildren(img); } else { throw new Error("Unsupported file type"); } } // Animate opening if (!wasOpen) { setTimeout(() => { cardExtra.style.maxHeight = "250px"; cardExtra.style.opacity = "1"; }, 10); } }, configurable: true }); card.toggleExtra = async function (state) { if (editing) return; const wasOpen = cardContent.classList.contains("open"); state = cardContent.classList.toggle("open", state); if (!state) { // Add animation for closing if (wasOpen) { cardExtra.style.transition = "all 0.3s"; cardExtra.style.maxHeight = "0"; cardExtra.style.opacity = "0"; // Wait for animation then clear setTimeout(() => { cardExtra.replaceChildren(); cardExtra.style = ""; }, 300); } return; } if (card.type != "file") return; let path = cardContent.innerText; let { exists, isDirectory, mime } = await getFileInfoAsync(path); if (!exists) return; // Start with zero height for animation if (!wasOpen) { cardExtra.style.maxHeight = "0"; cardExtra.style.opacity = "0"; cardExtra.style.transition = "all 0.3s"; } if (isDirectory) { let div = await createDirectoryDiv(path); cardExtra.replaceChildren(div); } else { let file = await getFileSystemHandlerAsync(path); file = await file.getFile(); if (file == null) return; if (file.type.startsWith("image/")) { let img = createElement('img', { src: URL.createObjectURL(file) }); img.ondblclick = function (e) { window.open(this.src, "_blank", `popup=yes,width=${window.innerWidth},height=${window.innerHeight},top=${window.screenY},left=${Math.max(window.screenX - window.innerWidth - 20, 0)}`); } cardExtra.replaceChildren(img); } else { cardContent.classList.toggle("open", false); throw new Error("Unsupported file type"); } } // Animate opening if (!wasOpen) { setTimeout(() => { cardExtra.style.maxHeight = "250px"; cardExtra.style.opacity = "1"; }, 10); } }; cardContent.ondblclick = async function () { if (editing) return; let type = card.type; if (type == "file") { let path = cardContent.innerText if (config.filedblclick == "copy") { clipboardWrite("copy"); } else if (config.filedblclick == "locate") { $quickerSp('在资源管理器中定位文件', { path }); } else if (config.filedblclick == "preview") { card.toggleExtra().catch(e => { if (config.previewfallback) $quickerSp('运行或打开', { path }); }) } else if (config.filedblclick == "open") { $quickerSp('运行或打开', { path }); } } else { if (config.textdblclick == "copy") { clipboardWrite("copy"); } else if (config.textdblclick == "edit") { if (type == "html") card.editContent(); if (type == "text") card.editText(); } } } cardContent.onblur = function (e) { let type = card.type; if (type == "file") { card.updateInfo(); } else { if (cardContent.innerText.trim() == "") { // Add slide-out animation before removal card.style.transition = "all 0.3s"; card.style.transform = "translateY(20px)"; card.style.opacity = "0"; // Remove after animation completes setTimeout(() => { card.remove(); }, 300); } } // Remove edit mode styling cardContent.style.boxShadow = ""; cardContent.style.padding = ""; } card.updateInfo = async function () { if (card.type == "file") { let { type } = await getFileInfoAsync(cardContent.innerText); cardContent.classList.remove("file", "directory", "non-exist"); cardContent.classList.add(type); if (type == "directory" && cardContent.classList.contains("open")) { let div = await createDirectoryDiv(cardContent.innerText) cardExtra.replaceChildren(div) } else { cardContent.classList.remove("open"); cardExtra.replaceChildren(); } } else { cardContent.classList.remove("file", "directory", "non-exist", "open"); } } card.editText = function () { let win = window.open("about:blank", "_blank", `popup=yes,width=${window.innerWidth},height=${window.innerHeight},top=${window.screenY},left=${Math.max(window.screenX - window.innerWidth - 20, 0)}`); win.document.head.innerHTML = ` <style> body { margin: 0; padding: 20px; font-family: 'SF Mono', SFMono-Regular, ui-monospace, Menlo, Monaco, monospace; font-size: 14px; line-height: 1.5; white-space: pre-wrap; word-break: break-all; height: 100vh; box-sizing: border-box; } body:focus { outline: none; } /* For dark mode */ @media (prefers-color-scheme: dark) { body.system { background-color: #1d1d1f; color: #f5f5f7; } } </style> `; if (document.documentElement.classList.contains("dark")) { win.document.body.style.background = "#1d1d1f"; win.document.body.style.color = "#f5f5f7"; } else if (config.colorscheme === "system") { win.document.body.classList.add("system"); } win.document.body.textContent = cardContent.innerText; win.document.body.contentEditable = "plaintext-only"; win.document.body.addEventListener("keydown", function(e) { if (e.key === "Escape") { win.close(); } }); win.onunload = function (e) { cardContent.innerHTML = win.document.body.innerText; } } card.editContent = function () { let win = window.open("about:blank", "_blank", `popup=yes,width=${window.innerWidth},height=${window.innerHeight},top=${window.screenY},left=${Math.max(window.screenX - window.innerWidth - 20, 0)}`); win.document.head.innerHTML = ` <style> body { margin: 0; padding: 20px; font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif; font-size: 14px; line-height: 1.5; height: 100vh; box-sizing: border-box; } body:focus { outline: none; } /* For dark mode */ @media (prefers-color-scheme: dark) { body.system { background-color: #1d1d1f; color: #f5f5f7; } } </style> `; if (document.documentElement.classList.contains("dark")) { win.document.body.style.background = "#1d1d1f"; win.document.body.style.color = "#f5f5f7"; } else if (config.colorscheme === "system") { win.document.body.classList.add("system"); } win.document.body.innerHTML = cardContent.innerHTML; win.document.body.contentEditable = true; win.document.body.addEventListener("keydown", function(e) { if (e.key === "Escape") { win.close(); } }); win.onunload = function (e) { cardContent.innerHTML = win.document.body.innerHTML; } } card.editSource = function () { let win = window.open("about:blank", "_blank", `popup=yes,width=${window.innerWidth},height=${window.innerHeight},top=${window.screenY},left=${Math.max(window.screenX - window.innerWidth - 20, 0)}`); win.document.head.innerHTML = ` <style> body { margin: 0; padding: ID; font-family: 'SF Mono', SFMono-Regular, ui-monospace, Menlo, Monaco, monospace; font-size: 14px; line-height: 1.5; white-space: pre-wrap; word-break: break-all; height: 100vh; box-sizing: border-box; } body:focus { outline: none; } /* For dark mode */ @media (prefers-color-scheme: dark) { body.system { background-color: #1d1d1f; color: #f5f5f7; } } </style> `; if (document.documentElement.classList.contains("dark")) { win.document.body.style.background = "#1d1d1f"; win.document.body.style.color = "#f5f5f7"; } else if (config.colorscheme === "system") { win.document.body.classList.add("system"); } win.document.body.textContent = cardContent.innerHTML; win.document.body.contentEditable = "plaintext-only"; win.document.body.addEventListener("keydown", function(e) { if (e.key === "Escape") { win.close(); } }); win.onunload = function (e) { cardContent.innerHTML = win.document.body.innerText; } } card.assign = function (property, value) { card[property] = value; } card.contentDiv = cardContent; return { card, cardContent }; } async function createFileCard(path, { open } = {}) { if (path instanceof File) path = await getFullPathAsync(path); if (!path) return; let { card, cardContent } = createCardBase(); card.classList.add("file"); let { type } = await getFileInfoAsync(path); cardContent.classList.add(type); cardContent.innerText = path; if (open) card.toggleExtra(true).catch(e => { }); // Add entry animation card.style.transform = "translateY(20px)"; card.style.opacity = "0"; setTimeout(() => { card.style.transform = "translateY(0)"; card.style.opacity = "1"; }, 10); return card; } function createHTMLCard(html) { let { card, cardContent } = createCardBase(); card.classList.add("html"); cardContent.innerHTML = html; // Add entry animation card.style.transform = "translateY(20px)"; card.style.opacity = "0"; setTimeout(() => { card.style.transform = "translateY(0)"; card.style.opacity = "1"; }, 10); return card; } function createTextCard(text) { if (!text) text = "新卡片"; let { card, cardContent } = createCardBase(); card.classList.add("text"); cardContent.textContent = text; // Add entry animation card.style.transform = "translateY(20px)"; card.style.opacity = "0"; setTimeout(() => { card.style.transform = "translateY(0)"; card.style.opacity = "1"; }, 10); return card; } async function createCard(type, data, args) { if (type == "html") return createHTMLCard(data); if (type == "file") return createFileCard(data, args); return createTextCard(data); } async function createCardFromDataTransfer(dataTransfer) { let data; if (dataTransfer.types.includes("Files")) { return Array.fromAsync( dataTransfer.files, async (file) => getFullPathAsync(file) .catch(e => saveTempfileAsync(file)) .then(path => createFileCard(path, { open: config.autopreview })) ); } if (config.richin) { data = dataTransfer.getData("text/html") .replace(/^<html>\s*/m, "").replace(/\s*<\/html>$/m, "") .replace(/^<body>\s*/m, "").replace(/\s*<\/body>$/m, "") .replace(new RegExp("<\!--StartFragment-->", "g"), "").replace(new RegExp("<\!--EndFragment-->", "g"), ""); if (data != "") return [createHTMLCard(data)]; } data = dataTransfer.getData("text/plain"); return [createTextCard(data)]; } document.querySelector('#tool-clearformat').onclick = (e) => { document.querySelectorAll('.card.selected').forEach(ele => ele.type = "text"); }; document.querySelector('#tool-cardjoin').onclick = async function (e) { let activeCard = document.querySelector('.tab-content.active').querySelector('.card.selected'); if (!activeCard) return; let card = await getSelectedCardJoin(); activeCard.before(card); card.editable = editing; // Add slide-in animation for new card card.style.transform = "translateY(20px)"; card.style.opacity = "0"; setTimeout(() => { card.style.transform = "translateY(0)"; card.style.opacity = "1"; }, 10); if (config.droponjoin) { let selectedCards = document.querySelector('.tab-content.active').querySelectorAll('.card.selected'); // Add slide-out animation before removal for (let c of selectedCards) { c.style.transition = "all 0.3s"; c.style.transform = "translateY(20px)"; c.style.opacity = "0"; } // Remove after animation completes setTimeout(() => { selectedCards.forEach(ele => ele.remove()); }, 300); } }; document.querySelector('#tool-readclipboard').onclick = () => { $quickerSp('添加剪贴板条目').catch(e => notify('剪贴板中没有内容', "Error")); }; document.querySelector("#tool-refreshfile").onclick = async function (e) { // Add refresh animation const refreshButton = document.querySelector("#tool-refreshfile"); refreshButton.style.transition = "transform 0.5s"; refreshButton.style.transform = "rotate(360deg)"; setTimeout(() => { refreshButton.style.transition = "none"; refreshButton.style.transform = "rotate(0deg)"; }, 500); let cardContents = document.querySelector('.tab-content.active').querySelectorAll('.card.selected.file>.card-wrapper>.card-content'); if (cardContents.length == 0) cardContents = document.querySelectorAll('.card.file .card-content') for (let content of cardContents) { let card = content.closest('.card'); let { type } = await getFileInfoAsync(content.innerText); content.classList.remove("file", "directory", "non-exist"); content.classList.add(type); } }; let tbPlus = document.querySelector('#tab-plus'); let trashButton = document.querySelector("#trash-button"); trashButton.onclick = function (e) { let selectedCards = document.querySelector(".tab-content.active").querySelectorAll('.card.selected'); if (selectedCards.length != 0) { // Add slide-out animation before removal for (let card of selectedCards) { card.style.transition = "all 0.3s"; card.style.transform = "translateY(20px)"; card.style.opacity = "0"; } // Remove after animation completes setTimeout(() => { selectedCards.forEach(ele => ele.remove()); }, 300); } }; trashButton.ondrop = function (e) { e.preventDefault(); trashButton.classList.remove("dragover"); let data; data = e.dataTransfer.getData("datatransfer/card"); if (data) { for (let id of data.split("\n")) { let card = document.getElementById(id); // Add slide-out animation before removal card.style.transition = "all 0.3s"; card.style.transform = "translateX(50px)"; card.style.opacity = "0"; // Remove after animation completes setTimeout(() => { card.remove(); }, 300); } return } data = e.dataTransfer.getData("datatransfer/tab"); if (data) { for (let id of data.split("\n")) { document.getElementById(id).dispose(); } return } }; trashButton.ondragover = function (e) { e.preventDefault(); trashButton.classList.add("dragover"); e.dataTransfer.dropEffect = "move"; }; trashButton.ondragleave = function (e) { trashButton.classList.remove("dragover"); }; tbPlus.onclick = e => { createTab().activate(); }; tbPlus.ondragover = function (e) { e.preventDefault(); e.dataTransfer.dropEffect = "All"; }; tbPlus.ondrop = async function (e) { e.preventDefault(); let tab = createTab(); tab.contentDiv.append(...await createCardFromDataTransfer(e.dataTransfer)); tab.activate(); } document.oncontextmenu = function (e) { e.preventDefault(); }; </script> <script> const config = { get richin() { return document.querySelector('#config-richin').checked }, set richin(value) { document.querySelector('#config-richin').checked = value }, get richout() { return document.querySelector('#config-richout').checked }, set richout(value) { document.querySelector('#config-richout').checked = value }, get copynotify() { return document.querySelector('#config-copynotify').checked }, set copynotify(value) { document.querySelector('#config-copynotify').checked = value }, get clickevent() { return document.querySelector('#config-clickevent').value }, set clickevent(value) { document.querySelector('#config-clickevent').value = value }, get textdblclick() { return document.querySelector('#config-text-dblclick').value }, set textdblclick(value) { document.querySelector('#config-text-dblclick').value = value }, get filedblclick() { return document.querySelector('#config-file-dblclick').value }, set filedblclick(value) { document.querySelector('#config-file-dblclick').value = value }, get dropeffect() { return document.querySelector('#config-dropeffect').value }, set dropeffect(value) { document.querySelector('#config-dropeffect').value = value }, get droponjoin() { return document.querySelector('#config-droponjoin').checked }, set droponjoin(value) { document.querySelector('#config-droponjoin').checked = value }, get previewfallback() { return document.querySelector('#config-previewfallback').checked }, set previewfallback(value) { document.querySelector('#config-previewfallback').checked = value }, get autopreview() { return document.querySelector('#config-autopreview').checked }, set autopreview(value) { document.querySelector('#config-autopreview').checked = value }, get colorscheme() { return document.querySelector('#config-colorscheme').value }, set colorscheme(value) { document.querySelector('#config-colorscheme').value = value }, } function saveData() { let metadata = { ...config }; let tabHeaders = Array.from(document.querySelectorAll('.tab-head')) metadata.activeTabN = tabHeaders.indexOf(document.querySelector('.tab-head.active')); let data = tabHeaders.map(tc => { let name = tc.innerText let cards = Array.from(tc.contentDiv.querySelectorAll('&>.card')).map(card => { let type = card.type; let content; if (type == "html") { content = card.contentDiv.innerHTML; } else { content = card.contentDiv.innerText; } let args = {}; if (type == "file") { args.open = card.contentDiv.classList.contains("open"); } return { type, content, args }; }) return { name, cards }; }); return $quickerSp("保存数据", { data: JSON.stringify({ metadata, data }) }); } window.onkeydown = (e) => { if (e.ctrlKey && e.key == "s") { e.preventDefault(); saveData().then(() => { notify("已保存", "Success"); // Add save animation const flash = document.createElement('div'); flash.style.position = "fixed"; flash.style.top = "0"; flash.style.left = "0"; flash.style.right = "0"; flash.style.bottom = "0"; flash.style.backgroundColor = "rgba(255, 255, 255, 0.2)"; flash.style.pointerEvents = "none"; flash.style.zIndex = "9999"; flash.style.animation = "flash 0.5s ease-out forwards"; document.body.appendChild(flash); // Add keyframes for flash animation const style = document.createElement('style'); style.textContent = ` @keyframes flash { 0% { opacity: 1; } 100% { opacity: 0; } } `; document.head.appendChild(style); setTimeout(() => { flash.remove(); style.remove(); }, 500); }); } if (e.ctrlKey && e.key == "e") { e.preventDefault(); chrome.webview.postMessage({ op: "customMessage", action: "moveWindowToCursor" }); } if (!editing && e.ctrlKey && e.key == "a") { e.preventDefault(); document.querySelector('.tab-content.active').querySelectorAll('.card').forEach(e => e.classList.add("selected")); } if (!editing && e.key == "Delete") { const selectedCards = document.querySelector('.tab-content.active').querySelectorAll('.card.selected'); // Add slide-out animation before removal for (let card of selectedCards) { card.style.transition = "all 0.3s"; card.style.transform = "translateX(50px)"; card.style.opacity = "0"; } // Remove after animation completes setTimeout(() => { selectedCards.forEach(e => e.remove()); }, 300); } if (!editing && e.ctrlKey && e.key == "x") { e.preventDefault(); clipboardWrite("move"); const selectedCards = document.querySelector('.tab-content.active').querySelectorAll('.card.selected'); // Add slide-out animation before removal for (let card of selectedCards) { card.style.transition = "all 0.3s"; card.style.transform = "translateX(50px)"; card.style.opacity = "0"; } // Remove after animation completes setTimeout(() => { selectedCards.forEach(e => e.remove()); }, 300); } if (!editing && e.ctrlKey && e.key == "c") { e.preventDefault(); clipboardWrite('copy'); // Add copy animation const selectedCards = document.querySelector('.tab-content.active').querySelectorAll('.card.selected'); for (let card of selectedCards) { card.animate([ { boxShadow: '0 0 0 2px var(--accent)' }, { boxShadow: '0 0 0 0 var(--accent)' } ], { duration: 300, easing: 'ease-out' }); } } if (e.key == "Escape") { document.querySelectorAll('.card.selected').forEach(e => e.classList.remove("selected")); } if (!editing && e.key == "F2") { document.querySelector('.tab-content.active').querySelector('.card.selected .card-content')?.ondblclick?.(); } }; document.onpaste = async function (e) { if (!editing) { const cards = await createCardFromDataTransfer(e.clipboardData); document.querySelector('.tab-content.active').append(...cards); } }; let unloadPromise; let loadPromise; async function updateTheme() { let isDark = config.colorscheme == "dark" if (config.colorscheme == "system") { let { colorscheme } = await $quickerSp('获取主题颜色', {}); isDark = /dark/.test(colorscheme) || colorscheme == null && window.matchMedia("(prefers-color-scheme: dark)").matches } document.documentElement.classList.toggle("dark", isDark) } window.chrome.webview.addEventListener('message', async (event) => { if (event.data.op != "customMessage") return if (event.data.action == "closingWindow") { if (unloadPromise) await unloadPromise; window.close(); return; } if (event.data.action == "loadData") { if (loadPromise) await loadPromise; let { resolve, reject, promise } = Promise.withResolvers(); loadPromise = promise; let { data = [], metadata = {} } = event.data || {}; document.querySelectorAll('.tab-head').forEach(e => e.remove()); document.querySelectorAll('.tab-content').forEach(e => e.remove()); document.body.classList.remove('manage'); Object.assign(config, metadata); updateTheme(); for (let tc of data) { let tab = createTab(tc.name); for (let c of tc.cards) { let card = await createCard(c.type, c.content, c.args); tab.contentDiv.append(card) } } if (data.length == 0) createTab().activate(); else (document.querySelectorAll('.tab-head')[metadata.activeTabN] ?? document.querySelector('.tab-head')).activate(); window.onbeforeunload = (e) => { unloadPromise = saveData(); } resolve(); } if (event.data.action == "createCard") { let tab = document.querySelector('.tab-head.active'); let { type, content } = event.data; let card; if (type == "file") { for (let file of content.replace(/\r/g, "").split("\n")) { card = await createFileCard(file); } } else if (type == "html") { card = await createHTMLCard(content); if (!config.richin) card.type = "text"; } else { card = await createTextCard(content); } tab.contentDiv.append(card); } }); window.onload = () => { $quickerSp('加载数据', {}) } </script> </body> </html>




车站里的守望者 最后更新于 2025/5/14

回复内容
FaniX 4天15小时前
#1

做的时候只考虑了功能,UI从简,所以默认样式确实不太好看。感谢你分享的UI样式。

刚刚更新了一版,支持用户自定义样式表,可以把<style></style>标签里的内容贴到 右键设置-自定义样式 一栏里面,实现自定义的UI效果

例如(点击展开)
@import url('https://fonts.googleapis.com/css2?family=SF+Pro+Display:wght@300;400;500;600&display=swap');

:root {
	--background-light: #ffffff;
	--text-light: #1d1d1f;
	--secondary-light: #6e6e73;
	--border-light: rgba(0, 0, 0, 0.1);
	--card-hover-light: rgba(0, 0, 0, 0.03);
	--card-select-light: rgba(0, 0, 0, 0.05);
	--tab-inactive-light: rgba(0, 0, 0, 0.05);
	--button-hover-light: rgba(0, 0, 0, 0.06);
	--accent-light: #0071e3;
	--danger-light: #ff3b30;
	--shadow-light: 0 1px 5px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06);

	/* Dark mode colors */
	--background-dark: #1d1d1f;
	--text-dark: #f5f5f7;
	--secondary-dark: #a1a1a6;
	--border-dark: rgba(255, 255, 255, 0.15);
	--card-hover-dark: rgba(255, 255, 255, 0.05);
	--card-select-dark: rgba(255, 255, 255, 0.08);
	--tab-inactive-dark: rgba(255, 255, 255, 0.08);
	--button-hover-dark: rgba(255, 255, 255, 0.1);
	--accent-dark: #2997ff;
	--danger-dark: #ff453a;
	--shadow-dark: 0 1px 5px rgba(0, 0, 0, 0.2);

	/* Default theme */
	--background: var(--background-light);
	--text: var(--text-light);
	--secondary: var(--secondary-light);
	--border: var(--border-light);
	--card-hover: var(--card-hover-light);
	--card-select: var(--card-select-light);
	--tab-inactive: var(--tab-inactive-light);
	--button-hover: var(--button-hover-light);
	--accent: var(--accent-light);
	--danger: var(--danger-light);
	--shadow: var(--shadow-light);

	/* Animation timing */
	--transition-fast: 0.15s;
	--transition-medium: 0.25s;
	--transition-slow: 0.4s;

	/* Spacing */
	--spacing-xs: 4px;
	--spacing-sm: 8px;
	--spacing-md: 16px;
	--spacing-lg: 24px;
	--spacing-xl: 32px;

	/* Border radius */
	--radius-sm: 6px;
	--radius-md: 8px;
	--radius-lg: 12px;
}

::-webkit-scrollbar {
	width: 8px;
	height: 8px;
}

::-webkit-scrollbar-thumb {
	background: rgba(0, 0, 0, 0.15);
	border-radius: 8px;
}

.dark::-webkit-scrollbar-thumb {
	background: rgba(255, 255, 255, 0.2);
}

::-webkit-scrollbar-thumb:hover {
	background: rgba(0, 0, 0, 0.25);
}

.dark::-webkit-scrollbar-thumb:hover {
	background: rgba(255, 255, 255, 0.3);
}

::-webkit-scrollbar-track {
	background: transparent;
}

html,
body {
	margin: 0;
	height: 100%;
	width: 100%;
	font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif;
	color: var(--text);
	background-color: var(--background);
	font-size: 14px;
	line-height: 1.5;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	transition: background-color var(--transition-medium), color var(--transition-medium);
}

body {
	display: flex;
	flex-direction: column;
}

#tab-bar {
	display: flex;
	justify-content: space-between;
	align-items: center;
	padding: var(--spacing-xs) var(--spacing-sm);
	backdrop-filter: blur(10px);
	-webkit-backdrop-filter: blur(10px);
	background-color: rgba(255, 255, 255, 0.8);
	border-bottom: 1px solid var(--border);
	position: sticky;
	top: 0;
	z-index: 100;
}

.dark #tab-bar {
	background-color: rgba(29, 29, 31, 0.85);
}

#tab-bar,
#tool-menu {
	border-bottom: 1px solid var(--border);
	transition: border-color var(--transition-medium);
}

#tool-menu {
	display: none;
	padding: var(--spacing-xs) var(--spacing-md);
	background-color: rgba(255, 255, 255, 0.8);
	backdrop-filter: blur(10px);
	-webkit-backdrop-filter: blur(10px);
}

.dark #tool-menu {
	background-color: rgba(29, 29, 31, 0.85);
}

body.manage #tool-menu {
	display: flex;
	gap: var(--spacing-sm);
	align-items: center;
}

#tab-head-container {
	display: flex;
	gap: var(--spacing-xs);
	align-items: center;
	overflow-x: auto;
}

#app {
	display: flex;
	flex: 1;
	overflow-y: auto;
}

#tab-plus,
.tab-head {
	white-space: nowrap;
	border-radius: var(--radius-md);
	padding: var(--spacing-xs) var(--spacing-sm);
	transition: background-color var(--transition-fast), transform var(--transition-fast), box-shadow var(--transition-medium);
	border: none;
	display: flex;
	align-items: center;
	cursor: pointer;
	height: 28px;
	user-select: none;
}

#tab-plus {
	background-color: var(--tab-inactive);
	color: var(--secondary);
	font-weight: 500;
	font-size: 16px;
	justify-content: center;
	min-width: 28px;
}

.tab-head:not(.active) {
	background-color: var(--tab-inactive);
	color: var(--secondary);
}

.tab-head.active {
	background-color: var(--accent);
	color: white;
	font-weight: 500;
	box-shadow: 0 3px 10px rgba(0, 113, 227, 0.3);
}

.dark .tab-head.active {
	box-shadow: 0 3px 10px rgba(41, 151, 255, 0.3);
}

#tab-plus:hover,
.tab-head:not(.active):hover {
	background-color: var(--button-hover);
	transform: translateY(-1px);
}

.tab-head.active:hover {
	transform: translateY(-1px);
}

.tab-head.active:active,
#tab-plus:active,
.tab-head:not(.active):active {
	transform: translateY(0);
}

body.manage {
	.card {
		padding-right: var(--spacing-md);
		position: relative;

		.card-extra {
			display: none;
		}
	}

	.card-close-button {
		position: absolute;
		right: var(--spacing-xs);
		top: 50%;
		transform: translateY(-50%);
		opacity: 0.7;
		transition: opacity var(--transition-fast);
		width: 22px;
		height: 22px;
		display: flex;
		align-items: center;
		justify-content: center;
		border-radius: 50%;
		background-color: var(--card-hover);

		&:hover {
			opacity: 1;
			background-color: var(--danger);
			color: white;
		}
	}

	.tab-head-close-button {
		margin-left: var(--spacing-xs);
		opacity: 0.7;
		transition: opacity var(--transition-fast), transform var(--transition-fast);
		width: 18px;
		height: 18px;
		display: flex;
		align-items: center;
		justify-content: center;
		border-radius: 50%;

		&:hover {
			opacity: 1;
			transform: scale(1.1);
			background-color: rgba(255, 255, 255, 0.3);
		}
	}

	.tab-head-close-button::after,
	.card-close-button::after {
		content: "×";
		font-size: 16px;
		line-height: 1;
		cursor: pointer;
	}
}

.button-control,
.text-control {
	border-radius: var(--radius-md);
	padding: var(--spacing-xs) var(--spacing-sm);
	background-color: transparent;
	transition: background-color var(--transition-fast), transform var(--transition-fast);
	user-select: none;
	height: 28px;
	display: flex;
	align-items: center;
	cursor: pointer;
}

.text-control {
	padding-right: var(--spacing-md);
}

.tab-head span {
	outline: none;
	margin-right: var(--spacing-xs);
}

#settings-menu {
	border-left: 1px solid var(--border);
	padding: var(--spacing-md);
	min-width: 200px;
	background-color: var(--background);
	transition: border-color var(--transition-medium);
}

.emoji {
	font-family: 'Apple Color Emoji', 'Segoe UI Emoji', sans-serif;
}

.text-control:hover,
.button-control:hover {
	background-color: var(--button-hover);
	transform: translateY(-1px);
}

.text-control:active,
.button-control:active {
	transform: translateY(0);
}

#trash-button.dragover {
	background-color: var(--danger);
	color: white;
	transform: scale(1.1);
}

.tab-content {
	overflow-y: auto;
	flex: 1;
	padding: var(--spacing-md);
	display: flex;
	flex-direction: column;
	gap: var(--spacing-sm);
	transition: all var(--transition-medium);
}

.tab-content:not(.active) {
	display: none;
}

.card.selected {
	background-color: var(--card-select);
	border-left: 3px solid var(--accent);
}

.card.selected:hover {
	background-color: var(--card-hover);
}

.card.selected[draggable=true] {
	cursor: grab;
}

.card {
	position: relative;
	border-radius: var(--radius-md);
	background-color: var(--background);
	box-shadow: var(--shadow);
	max-height: max(50%, 100px);
	box-sizing: border-box;
	overflow: hidden;
	display: flex;
	transition: transform var(--transition-fast),
		box-shadow var(--transition-medium),
		background-color var(--transition-fast),
		border-left var(--transition-fast);
	border-left: 3px solid transparent;
}

.card:hover {
	background-color: var(--card-hover);
	transform: translateY(-2px);
	box-shadow: 0 3px 10px rgba(0, 0, 0, 0.08);
}

.dark .card:hover {
	box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
}

.card-checkbox {
	margin: var(--spacing-md);
	appearance: none;
	width: 18px;
	height: 18px;
	border-radius: 4px;
	border: 1.5px solid var(--secondary);
	background-color: var(--background);
	position: relative;
	cursor: pointer;
	transition: all var(--transition-fast);

	&:checked {
		background-color: var(--accent);
		border-color: var(--accent);

		&::after {
			content: "";
			position: absolute;
			top: 3px;
			left: 6px;
			width: 4px;
			height: 8px;
			border: solid white;
			border-width: 0 2px 2px 0;
			transform: rotate(45deg);
		}
	}

	&:hover {
		border-color: var(--accent);
	}
}

.card-wrapper {
	width: 100%;
	box-sizing: border-box;
	overflow-y: auto;
	padding: var(--spacing-md);
	flex: 1;

	.card-content {
		outline: none;
		width: 100%;
		padding-block: var(--spacing-xs);
		transition: all var(--transition-medium);

		* {
			pointer-events: none;
		}
	}

	.card-extra:empty {
		display: none;
	}
}

.card.text {
	.card-content {
		font-family: 'SF Mono', SFMono-Regular, ui-monospace, Menlo, Monaco, monospace;
		white-space: pre-wrap;
		word-break: break-all;
		font-size: 13px;
	}
}

.card.html {
	.card-content * {
		margin: 0 !important
	}
}

body:not(.manage) {
	.card.file .card-content:hover {
		color: var(--accent);
	}
}

.card.file {
	.card-content {
		font-family: 'SF Mono', SFMono-Regular, ui-monospace, Menlo, Monaco, monospace;
		white-space: nowrap;
		overflow-x: auto;
		font-size: 13px;
		display: flex;
		align-items: center;

		&.file::before,
		&.non-exist::before {
			content: "📄";
			margin-right: var(--spacing-sm);
			font-size: 16px;
		}

		&.directory::before {
			content: "📁";
			margin-right: var(--spacing-sm);
			font-size: 16px;
		}

		&.directory.open::before {
			content: "📂";
			margin-right: var(--spacing-sm);
			font-size: 16px;
		}

		&.non-exist {
			text-decoration: line-through;
			opacity: 0.7;
		}

		&::-webkit-scrollbar {
			height: 4px;
		}
	}

	.card-extra {
		font-family: 'SF Mono', SFMono-Regular, ui-monospace, Menlo, Monaco, monospace;
		white-space: nowrap;
		overflow-y: auto;
		margin-top: var(--spacing-sm);
		padding: var(--spacing-sm);
		border-radius: var(--radius-sm);
		border-top: 1px solid var(--border);
		max-height: 250px;
		background-color: rgba(0, 0, 0, 0.02);
		transition: border-color var(--transition-medium);

		.dark & {
			background-color: rgba(255, 255, 255, 0.05);
		}

		img {
			max-width: 100%;
			max-height: 100%;
			border-radius: var(--radius-sm);
			object-fit: contain;
		}

		.card:last-of-type {
			border-bottom: none;
		}
	}
}

#card-container {
	display: flex;
	flex: 1;
	overflow-y: auto;
}

.config-option {
	display: flex;
	align-items: center;
	padding: var(--spacing-xs) 0;
	cursor: pointer;
	transition: color var(--transition-fast);

	&:hover {
		color: var(--accent);
	}
}

select.config-option[size] {
	border-radius: var(--radius-sm);
	border: 1px solid var(--border);
	background-color: var(--background);
	color: var(--text);
	outline: none;
	padding: var(--spacing-xs);
	margin: var(--spacing-xs) 0 var(--spacing-md) 0;
	cursor: pointer;
	transition: all var(--transition-medium);

	&:focus {
		border-color: var(--accent);
	}

	option {
		padding: var(--spacing-xs);
	}
}

label {
	display: block;
	margin: var(--spacing-sm) 0 var(--spacing-xs) 0;
	font-weight: 500;
}



input[type="checkbox"] {
	appearance: none;
	width: 18px;
	height: 18px;
	border-radius: 4px;
	border: 1.5px solid var(--secondary);
	margin-right: var(--spacing-sm);
	position: relative;
	cursor: pointer;
	transition: all var(--transition-fast);
	background-color: var(--background);

	&:checked {
		background-color: var(--accent);
		border-color: var(--accent);

		&::after {
			content: "";
			position: absolute;
			top: 3px;
			left: 6px;
			width: 4px;
			height: 8px;
			border: solid white;
			border-width: 0 2px 2px 0;
			transform: rotate(45deg);
		}
	}

	&:hover {
		border-color: var(--accent);
	}

	&:focus {
		outline: none;
		box-shadow: 0 0 0 2px rgba(0, 113, 227, 0.3);
	}
}

.dark input[type="checkbox"] {
	background-color: var(--background-dark);
}

/* Animations */
@keyframes fadeIn {
	from {
		opacity: 0;
	}

	to {
		opacity: 1;
	}
}

@keyframes slideIn {
	from {
		transform: translateY(10px);
		opacity: 0;
	}

	to {
		transform: translateY(0);
		opacity: 1;
	}
}

.card {
	animation: slideIn var(--transition-medium) ease-out;
}

.tab-head {
	animation: fadeIn var(--transition-fast) ease-out;
}

.dark {
	--background: var(--background-dark);
	--text: var(--text-dark);
	--secondary: var(--secondary-dark);
	--border: var(--border-dark);
	--card-hover: var(--card-hover-dark);
	--card-select: var(--card-select-dark);
	--tab-inactive: var(--tab-inactive-dark);
	--button-hover: var(--button-hover-dark);
	--accent: var(--accent-dark);
	--danger: var(--danger-dark);
	--shadow: var(--shadow-dark);
}
FaniX 最后更新于 4天15小时前
回复主贴