Multi-Column Layout for printing (Print Only)

Convert single column layout to multi-column layout only when printing. Press Ctrl+S to open settings.

当前为 2024-11-05 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Multi-Column Layout for printing (Print Only)
  3. // @namespace http://tampermonkey.net/
  4. // @license MIT
  5. // @version 2.3
  6. // @description Convert single column layout to multi-column layout only when printing. Press Ctrl+S to open settings.
  7. // @author KQ yang
  8. // @match *://*
  9. // @match file:///*
  10. // @match http://127.0.0.1:*/*
  11. // @match http://localhost:*/*
  12. // @grant GM_addStyle
  13. // @run-at document-start
  14. // @noframes
  15. // ==/UserScript==
  16.  
  17. (function() {
  18. 'use strict';
  19.  
  20. console.log('Script initialized'); // 调试日志
  21.  
  22. // Default configuration
  23. const DEFAULT_CONFIG = {
  24. columns: 2,
  25. columnGap: '30px',
  26. fontSize: '16px',
  27. paragraphSpacing: '1em',
  28. enablePageBreak: true,
  29. lineHeight: '1.5',
  30. };
  31.  
  32. // Load config from localStorage or use defaults
  33. let CONFIG = loadConfig();
  34.  
  35. function loadConfig() {
  36. const savedConfig = localStorage.getItem('printLayoutConfig');
  37. console.log('Loaded config:', savedConfig); // 调试日志
  38. return savedConfig ? {...DEFAULT_CONFIG, ...JSON.parse(savedConfig)} : DEFAULT_CONFIG;
  39. }
  40.  
  41. function saveConfig(config) {
  42. localStorage.setItem('printLayoutConfig', JSON.stringify(config));
  43. CONFIG = config;
  44. console.log('Saved config:', config); // 调试日志
  45. updateStyles();
  46. }
  47.  
  48. let configModal = null; // 将configModal提升为全局变量
  49.  
  50. // Create and inject the configuration UI
  51. function createConfigUI() {
  52. console.log('Creating config UI'); // 调试日志
  53.  
  54. // 如果已经存在modal,先移除
  55. if (configModal) {
  56. configModal.remove();
  57. }
  58.  
  59. configModal = document.createElement('div');
  60. configModal.id = 'print-layout-config-modal';
  61. configModal.setAttribute('style', `
  62. all: initial;
  63. position: fixed !important;
  64. top: 50% !important;
  65. left: 50% !important;
  66. transform: translate(-50%, -50%) !important;
  67. background: white !important;
  68. padding: 30px !important;
  69. border-radius: 12px !important;
  70. box-shadow: 0 8px 24px rgba(0,0,0,0.15) !important;
  71. z-index: 2147483647 !important;
  72. display: none !important;
  73. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif !important;
  74. min-width: 320px !important;
  75. max-width: 90vw !important;
  76. animation: modalFadeIn 0.3s ease-out !important;
  77. color: black !important;
  78. `);
  79.  
  80. const styleElement = document.createElement('style');
  81. styleElement.textContent = `
  82. @keyframes modalFadeIn {
  83. from {
  84. opacity: 0;
  85. transform: translate(-50%, -48%);
  86. }
  87. to {
  88. opacity: 1;
  89. transform: translate(-50%, -50%);
  90. }
  91. }
  92. .settings-row {
  93. margin-bottom: 20px;
  94. display: flex;
  95. align-items: center;
  96. justify-content: space-between;
  97. }
  98. .settings-row label {
  99. color: #333;
  100. font-size: 14px;
  101. margin-right: 15px;
  102. }
  103. .settings-row input[type="number"],
  104. .settings-row input[type="text"] {
  105. width: 100px;
  106. padding: 8px;
  107. border: 1px solid #ddd;
  108. border-radius: 6px;
  109. font-size: 14px;
  110. transition: border-color 0.2s;
  111. }
  112. .settings-row input:focus {
  113. outline: none;
  114. border-color: #4A90E2;
  115. box-shadow: 0 0 0 2px rgba(74,144,226,0.2);
  116. }
  117. .settings-row input[type="checkbox"] {
  118. width: 18px;
  119. height: 18px;
  120. cursor: pointer;
  121. }
  122. .modal-title {
  123. color: #333;
  124. margin: 0 0 25px 0;
  125. font-size: 18px;
  126. font-weight: 600;
  127. border-bottom: 2px solid #eee;
  128. padding-bottom: 15px;
  129. }
  130. .modal-footer {
  131. margin-top: 25px;
  132. padding-top: 20px;
  133. border-top: 2px solid #eee;
  134. text-align: right;
  135. }
  136. .save-button {
  137. background: #4A90E2;
  138. color: white;
  139. border: none;
  140. padding: 10px 20px;
  141. border-radius: 6px;
  142. font-size: 14px;
  143. cursor: pointer;
  144. transition: background-color 0.2s;
  145. }
  146. .save-button:hover {
  147. background: #357ABD;
  148. }
  149. .close-button {
  150. position: absolute;
  151. top: 15px;
  152. right: 15px;
  153. background: none;
  154. border: none;
  155. font-size: 20px;
  156. cursor: pointer;
  157. color: #666;
  158. padding: 5px;
  159. line-height: 1;
  160. }
  161. .close-button:hover {
  162. color: #333;
  163. }
  164. #print-layout-config-modal {
  165. visibility: visible !important;
  166. display: block !important;
  167. }
  168. `;
  169. document.head.appendChild(styleElement);
  170.  
  171. configModal.innerHTML = ''; // 清空现有内容
  172. const modalContent = document.createElement('div');
  173. modalContent.style.cssText = `
  174. all: initial;
  175. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
  176. color: black !important;
  177. `;
  178. modalContent.innerHTML = `
  179. <h3 class="modal-title">Print Layout Settings</h3>
  180. <button class="close-button" title="Close">×</button>
  181. <div class="settings-row">
  182. <label>Columns (1-4):</label>
  183. <input type="number" id="columns" min="1" max="4" value="${CONFIG.columns}">
  184. </div>
  185. <div class="settings-row">
  186. <label>Column Gap:</label>
  187. <input type="text" id="columnGap" value="${CONFIG.columnGap}">
  188. </div>
  189. <div class="settings-row">
  190. <label>Font Size:</label>
  191. <input type="text" id="fontSize" value="${CONFIG.fontSize}">
  192. </div>
  193. <div class="settings-row">
  194. <label>Paragraph Spacing:</label>
  195. <input type="text" id="paragraphSpacing" value="${CONFIG.paragraphSpacing}">
  196. </div>
  197. <div class="settings-row">
  198. <label>Line Height:</label>
  199. <input type="text" id="lineHeight" value="${CONFIG.lineHeight}">
  200. </div>
  201. <div class="settings-row">
  202. <label>Enable Page Break:</label>
  203. <input type="checkbox" id="enablePageBreak" ${CONFIG.enablePageBreak ? 'checked' : ''}>
  204. </div>
  205. <div class="modal-footer">
  206. <button class="save-button">Save Changes</button>
  207. </div>
  208. `;
  209.  
  210. configModal.appendChild(modalContent);
  211. document.documentElement.appendChild(configModal);
  212. console.log('Config UI created and appended to body'); // 调试日志
  213.  
  214. // Save button handler
  215. const saveButton = configModal.querySelector('.save-button');
  216. if (saveButton) {
  217. saveButton.addEventListener('click', () => {
  218. console.log('Save button clicked'); // 调试日志
  219. const newConfig = {
  220. columns: parseInt(configModal.querySelector('#columns').value, 10),
  221. columnGap: configModal.querySelector('#columnGap').value,
  222. fontSize: configModal.querySelector('#fontSize').value,
  223. paragraphSpacing: configModal.querySelector('#paragraphSpacing').value,
  224. lineHeight: configModal.querySelector('#lineHeight').value,
  225. enablePageBreak: configModal.querySelector('#enablePageBreak').checked
  226. };
  227. saveConfig(newConfig);
  228. configModal.style.setProperty('display', 'none', 'important');
  229. });
  230. }
  231.  
  232. // Close button handler
  233. const closeButton = configModal.querySelector('.close-button');
  234. if (closeButton) {
  235. closeButton.addEventListener('click', () => {
  236. console.log('Close button clicked'); // 调试日志
  237. configModal.style.setProperty('display', 'none', 'important');
  238. });
  239. }
  240.  
  241. // Click outside to close
  242. configModal.addEventListener('click', (e) => {
  243. if (e.target === configModal) {
  244. configModal.style.setProperty('display', 'none', 'important');
  245. }
  246. });
  247.  
  248. return configModal;
  249. }
  250.  
  251. function showConfigModal() {
  252. console.log('Showing config modal'); // 调试日志
  253. if (!configModal) {
  254. configModal = createConfigUI();
  255. }
  256. configModal.style.setProperty('display', 'block', 'important');
  257. configModal.style.setProperty('visibility', 'visible', 'important');
  258. }
  259.  
  260. function hideConfigModal() {
  261. console.log('Hiding config modal'); // 调试日志
  262. if (configModal) {
  263. configModal.style.setProperty('display', 'none', 'important');
  264. }
  265. }
  266.  
  267. function toggleConfigModal() {
  268. console.log('Toggling config modal'); // 调试日志
  269. if (!configModal || configModal.style.display === 'none') {
  270. showConfigModal();
  271. } else {
  272. hideConfigModal();
  273. }
  274. }
  275.  
  276. // Create and update styles based on current config
  277. function updateStyles() {
  278. console.log('Updating styles'); // 调试日志
  279. const styleSheet = `
  280. @media print {
  281. html, body {
  282. margin: 0 !important;
  283. padding: 0 !important;
  284. min-height: 0 !important;
  285. height: auto !important;
  286. }
  287. .print-column-container {
  288. column-count: ${CONFIG.columns} !important;
  289. column-gap: ${CONFIG.columnGap} !important;
  290. column-rule: 1px solid #ddd !important;
  291. width: 100% !important;
  292. margin: 0 !important;
  293. padding: 0 !important;
  294. min-height: 0 !important;
  295. height: auto !important;
  296. overflow: visible !important;
  297. box-sizing: border-box !important;
  298. font-size: ${CONFIG.fontSize} !important;
  299. line-height: ${CONFIG.lineHeight} !important;
  300. ${CONFIG.enablePageBreak ? '' : 'page-break-inside: avoid !important;'}
  301. }
  302. .print-column-container > * {
  303. break-inside: avoid !important;
  304. margin-bottom: ${CONFIG.paragraphSpacing} !important;
  305. max-width: 100% !important;
  306. box-sizing: border-box !important;
  307. page-break-inside: avoid !important;
  308. }
  309. .print-column-container img {
  310. max-width: 100% !important;
  311. height: auto !important;
  312. page-break-inside: avoid !important;
  313. }
  314. }
  315. `;
  316.  
  317. const existingStyle = document.getElementById('print-layout-style');
  318. if (existingStyle) {
  319. existingStyle.remove();
  320. }
  321.  
  322. const style = document.createElement('style');
  323. style.id = 'print-layout-style';
  324. style.textContent = styleSheet;
  325. document.head.appendChild(style);
  326. }
  327.  
  328. // Apply columns to main content
  329. function applyPrintColumns() {
  330. console.log('Applying print columns'); // 调试日志
  331. const mainContent = document.querySelector('.target-content') || document.body;
  332. mainContent.classList.add('print-column-container');
  333.  
  334. const printStyle = document.createElement('style');
  335. printStyle.media = 'print';
  336. printStyle.textContent = `
  337. @page {
  338. margin: 1cm !important;
  339. padding: 0 !important;
  340. size: auto !important;
  341. }
  342. `;
  343. document.head.appendChild(printStyle);
  344. }
  345.  
  346. // Initialize modal globally
  347. configModal = createConfigUI();
  348.  
  349. // Handle Ctrl+S shortcut with improved event handling
  350. document.addEventListener('keydown', function(e) {
  351. if (e.ctrlKey && (e.key === 's' || e.key === 'S' || e.keyCode === 83)) {
  352. console.log('Ctrl+S detected'); // 调试日志
  353. e.stopPropagation();
  354. e.preventDefault();
  355. toggleConfigModal();
  356. return false;
  357. }
  358. }, true);
  359.  
  360. // Initial style application
  361. updateStyles();
  362.  
  363. // Handle DOMContentLoaded
  364. function onDOMContentLoaded() {
  365. console.log('DOM Content Loaded'); // 调试日志
  366. applyPrintColumns();
  367. }
  368.  
  369. if (document.readyState === 'loading') {
  370. document.addEventListener('DOMContentLoaded', onDOMContentLoaded);
  371. } else {
  372. onDOMContentLoaded();
  373. }
  374.  
  375. // Add a global function for testing
  376. window.togglePrintLayoutConfig = toggleConfigModal;
  377.  
  378. // Log usage instructions to console
  379. console.log(`
  380. Multi-Column Layout Userscript Usage:
  381. -----------------------------------
  382. 1. Press Ctrl+S (or Cmd+S on Mac) to open settings
  383. 2. Adjust your preferences:
  384. - Columns: Number of columns (1-4)
  385. - Column Gap: Space between columns
  386. - Font Size: Text size in print
  387. - Paragraph Spacing: Space between paragraphs
  388. - Line Height: Text line height
  389. - Enable Page Break: Allow/prevent content breaks across pages
  390. 3. Click Save Changes to apply settings
  391. 4. Settings are automatically saved between sessions
  392. 5. Press Ctrl+P to see the print preview with your settings
  393.  
  394. For testing, you can also use: window.togglePrintLayoutConfig()
  395.  
  396. Current configuration:`, CONFIG);
  397.  
  398. })();