소스 검색

add "max length unit" option (fix #862)

Gildas 4 년 전
부모
커밋
fb7f9b13f1

+ 10 - 2
_locales/de/messages.json

@@ -188,8 +188,16 @@
 		"description": "Options page label: 'template'"
 	},
 	"optionFilenameMaxLength": {
-		"message": "maximale Länge (bytes)",
-		"description": "Options page label: 'max length (bytes)'"
+		"message": "Maximale Länge",
+		"description": "Options page label: 'max length'"
+	},
+	"optionFilenameMaxLengthBytesUnit": {
+		"message": "Bytes",
+		"description": "Options page label: 'bytes'"
+	},
+	"optionFilenameMaxLengthCharsUnit": {
+		"message": "Zeichen",
+		"description": "Options page label: 'characters'"
 	},
 	"optionConfirmFilename": {
 		"message": "Dialogfenster \"Sichern als\" zur Bestätigung des Dateinamens öffnen",

+ 10 - 2
_locales/en/messages.json

@@ -188,8 +188,16 @@
 		"description": "Options page label: 'template'"
 	},
 	"optionFilenameMaxLength": {
-		"message": "max length (bytes)",
-		"description": "Options page label: 'max length (bytes)'"
+		"message": "max length",
+		"description": "Options page label: 'max length'"
+	},
+	"optionFilenameMaxLengthBytesUnit": {
+		"message": "bytes",
+		"description": "Options page label: 'bytes'"
+	},
+	"optionFilenameMaxLengthCharsUnit": {
+		"message": "characters",
+		"description": "Options page label: 'characters'"
 	},
 	"optionConfirmFilename": {
 		"message": "open the \"Save as\" dialog to confirm the file name",

+ 10 - 2
_locales/es/messages.json

@@ -188,8 +188,16 @@
 		"description": "Options page label: 'template'"
 	},
 	"optionFilenameMaxLength": {
-		"message": "longitud máxima (bytes)",
-		"description": "Options page label: 'max length (bytes)'"
+		"message": "longitud máxima",
+		"description": "Options page label: 'max length'"
+	},
+	"optionFilenameMaxLengthBytesUnit": {
+		"message": "bytes",
+		"description": "Options page label: 'bytes'"
+	},
+	"optionFilenameMaxLengthCharsUnit": {
+		"message": "caracteres",
+		"description": "Options page label: 'characters'"
 	},
 	"optionConfirmFilename": {
 		"message": "abrir el cuadro de diálogo \"Guardar como\" para confirmar el nombre del archivo",

+ 10 - 2
_locales/fr/messages.json

@@ -188,8 +188,16 @@
 		"description": "Options page label: 'template'"
 	},
 	"optionFilenameMaxLength": {
-		"message": "longueur maximale (octets)",
-		"description": "Options page label: 'max length (bytes)'"
+		"message": "longueur maximale",
+		"description": "Options page label: 'max length'"
+	},
+	"optionFilenameMaxLengthBytesUnit": {
+		"message": "octets",
+		"description": "Options page label: 'bytes'"
+	},
+	"optionFilenameMaxLengthCharsUnit": {
+		"message": "caractères",
+		"description": "Options page label: 'characters'"
 	},
 	"optionConfirmFilename": {
 		"message": "ouvrir la boite de dialogue \"Sauver sous\" pour confimer le nom de fichier",

+ 10 - 2
_locales/it/messages.json

@@ -188,8 +188,16 @@
 		"description": "Options page label: 'template'"
 	},
 	"optionFilenameMaxLength": {
-		"message": "lunghezza massima (byte)",
-		"description": "Options page label: 'max length (bytes)'"
+		"message": "lunghezza massima",
+		"description": "Options page label: 'max length'"
+	},
+	"optionFilenameMaxLengthBytesUnit": {
+		"message": "bytes",
+		"description": "Options page label: 'bytes'"
+	},
+	"optionFilenameMaxLengthCharsUnit": {
+		"message": "caratteri",
+		"description": "Options page label: 'characters'"
 	},
 	"optionConfirmFilename": {
 		"message": "Apri la finesta di dialogo \"Salva come\" per confermare il nome del file",

+ 10 - 2
_locales/ja/messages.json

@@ -188,8 +188,16 @@
 		"description": "Options page label: 'template'"
 	},
 	"optionFilenameMaxLength": {
-		"message": "最大長(バイト)",
-		"description": "Options page label: 'max length (bytes)'"
+		"message": "最大長",
+		"description": "Options page label: 'max length'"
+	},
+	"optionFilenameMaxLengthBytesUnit": {
+		"message": "バイト",
+		"description": "Options page label: 'bytes'"
+	},
+	"optionFilenameMaxLengthCharsUnit": {
+		"message": "characters",
+		"description": "Options page label: 'characters'"
 	},
 	"optionConfirmFilename": {
 		"message": "を開く(として保存する:ファイル名を確認するためのダイアログ)",

+ 10 - 2
_locales/pl/messages.json

@@ -188,8 +188,16 @@
 		"description": "Options page label: 'template'"
 	},
 	"optionFilenameMaxLength": {
-		"message": "maksymalna długość (bajty)",
-		"description": "Options page label: 'max length (bytes)'"
+		"message": "maksymalna długość",
+		"description": "Options page label: 'max length'"
+	},
+	"optionFilenameMaxLengthBytesUnit": {
+		"message": "bajty",
+		"description": "Options page label: 'bytes'"
+	},
+	"optionFilenameMaxLengthCharsUnit": {
+		"message": "characters",
+		"description": "Options page label: 'characters'"
 	},
 	"optionConfirmFilename": {
 		"message": "otwieraj okno \"Zapisz jako\", aby potwierdzić nazwę pliku",

+ 11 - 3
_locales/ru/messages.json

@@ -188,8 +188,16 @@
 		"description": "Options page label: 'template'"
 	},
 	"optionFilenameMaxLength": {
-		"message": "максимальная длина (байт)",
-		"description": "Options page label: 'max length (bytes)'"
+		"message": "максимальная длина",
+		"description": "Options page label: 'max length'"
+	},
+	"optionFilenameMaxLengthBytesUnit": {
+		"message": "байт",
+		"description": "Options page label: 'bytes'"
+	},
+	"optionFilenameMaxLengthCharsUnit": {
+		"message": "characters",
+		"description": "Options page label: 'characters'"
 	},
 	"optionConfirmFilename": {
 		"message": "открыть диалоговое окно \"Сохранить как\" для подтверждения имени файла",
@@ -779,4 +787,4 @@
 		"message": "Отмена",
 		"description": "Add URLs popup cancel button: 'Cancel'"
 	}
-}
+}

+ 10 - 2
_locales/uk/messages.json

@@ -188,8 +188,16 @@
 		"description": "Options page label: 'template'"
 	},
 	"optionFilenameMaxLength": {
-		"message": "максимальна довжина (байти)",
-		"description": "Options page label: 'max length (bytes)'"
+		"message": "максимальна довжина",
+		"description": "Options page label: 'max length'"
+	},
+	"optionFilenameMaxLengthBytesUnit": {
+		"message": "байти",
+		"description": "Options page label: 'bytes'"
+	},
+	"optionFilenameMaxLengthCharsUnit": {
+		"message": "characters",
+		"description": "Options page label: 'characters'"
 	},
 	"optionConfirmFilename": {
 		"message": "відкривати діалогове вікно \"Save as\" для підтвердження імені файлу",

+ 10 - 2
_locales/zh_CN/messages.json

@@ -188,8 +188,16 @@
 		"description": "Options page label: 'template'"
 	},
 	"optionFilenameMaxLength": {
-		"message": "最大长度(字节)",
-		"description": "Options page label: 'max length (bytes)'"
+		"message": "最大长度",
+		"description": "Options page label: 'max length'"
+	},
+	"optionFilenameMaxLengthBytesUnit": {
+		"message": "字节",
+		"description": "Options page label: 'bytes'"
+	},
+	"optionFilenameMaxLengthCharsUnit": {
+		"message": "characters",
+		"description": "Options page label: 'characters'"
 	},
 	"optionConfirmFilename": {
 		"message": "打开 “另存为” 对话框以确认文件名",

+ 10 - 2
_locales/zh_TW/messages.json

@@ -188,8 +188,16 @@
 		"description": "Options page label: 'template'"
 	},
 	"optionFilenameMaxLength": {
-		"message": "最大長度(字節)",
-		"description": "Options page label: 'max length (bytes)'"
+		"message": "最大長度",
+		"description": "Options page label: 'max length'"
+	},
+	"optionFilenameMaxLengthBytesUnit": {
+		"message": "字節",
+		"description": "Options page label: 'bytes'"
+	},
+	"optionFilenameMaxLengthCharsUnit": {
+		"message": "characters",
+		"description": "Options page label: 'characters'"
 	},
 	"optionConfirmFilename": {
 		"message": "打開 “另存為” 對話框以確認文件名",

+ 6 - 3
cli/args.js

@@ -63,6 +63,7 @@ const args = require("yargs")
 		"filename-conflict-action": "uniquify",
 		"filename-replacement-character": "_",
 		"filename-max-length": 192,
+		"filename-max-length-unit": "bytes",
 		"group-duplicate-images": true,
 		"http-header": [],
 		"include-infobar": false,
@@ -96,7 +97,7 @@ const args = require("yargs")
 		"crawl-max-depth": 1,
 		"crawl-external-links-max-depth": 1,
 		"crawl-replace-urls": false,
-		"crawl-rewrite-rule": []		
+		"crawl-rewrite-rule": []
 	})
 	.options("back-end", { description: "Back-end to use" })
 	.choices("back-end", ["jsdom", "puppeteer", "webdriver-chromium", "webdriver-gecko", "puppeteer-firefox", "playwright-firefox", "playwright-chromium"])
@@ -172,8 +173,10 @@ const args = require("yargs")
 	.string("filename-conflict-action")
 	.options("filename-replacement-character", { description: "The character used for replacing invalid characters in filenames" })
 	.string("filename-replacement-character")
-	.options("filename-max-length", { description: "Specify the maximum length in bytes of the filename " })
-	.string("filename-max-length")
+	.options("filename-max-length", { description: "Specify the maximum length of the filename" })
+	.number("filename-max-length")
+	.options("filename-max-length-unit", { description: "Specify the unit of the maximum length of the filename ('bytes' or 'chars')" })
+	.string("filename-max-length-unit")
 	.options("group-duplicate-images", { description: "Group duplicate images into CSS custom properties" })
 	.boolean("group-duplicate-images")
 	.options("http-header", { description: "Extra HTTP header (puppeteer, jsdom)" })

+ 1 - 0
cli/single-file-cli-api.js

@@ -45,6 +45,7 @@ const DEFAULT_OPTIONS = {
 	infobarTemplate: "",
 	includeInfobar: false,
 	filenameMaxLength: 192,
+	filenameMaxLengthUnit: "bytes",
 	filenameReplacedCharacters: ["~", "+", "\\\\", "?", "%", "*", ":", "|", "\"", "<", ">", "\x00-\x1f", "\x7F"],
 	filenameReplacementCharacter: "_",
 	maxResourceSizeEnabled: false,

+ 1 - 0
extension/core/bg/config.js

@@ -54,6 +54,7 @@ const DEFAULT_CONFIG = {
 	confirmFilename: false,
 	filenameConflictAction: "uniquify",
 	filenameMaxLength: 192,
+	filenameMaxLengthUnit: "bytes",
 	filenameReplacedCharacters: ["~", "+", "\\\\", "?", "%", "*", ":", "|", "\"", "<", ">", "\x00-\x1f", "\x7F"],
 	filenameReplacementCharacter: "_",
 	contextMenuEnabled: true,

+ 7 - 0
extension/ui/bg/ui-options.js

@@ -55,6 +55,8 @@ const loadDeferredImagesKeepZoomLevelLabel = document.getElementById("loadDeferr
 const addMenuEntryLabel = document.getElementById("addMenuEntryLabel");
 const filenameTemplateLabel = document.getElementById("filenameTemplateLabel");
 const filenameMaxLengthLabel = document.getElementById("filenameMaxLengthLabel");
+const filenameMaxLengthBytesUnitLabel = document.getElementById("filenameMaxLengthBytesUnitLabel");
+const filenameMaxLengthCharsUnitLabel = document.getElementById("filenameMaxLengthCharsUnitLabel");
 const shadowEnabledLabel = document.getElementById("shadowEnabledLabel");
 const setMaxResourceSizeLabel = document.getElementById("setMaxResourceSizeLabel");
 const maxResourceSizeLabel = document.getElementById("maxResourceSizeLabel");
@@ -156,6 +158,7 @@ const loadDeferredImagesKeepZoomLevelInput = document.getElementById("loadDeferr
 const contextMenuEnabledInput = document.getElementById("contextMenuEnabledInput");
 const filenameTemplateInput = document.getElementById("filenameTemplateInput");
 const filenameMaxLengthInput = document.getElementById("filenameMaxLengthInput");
+const filenameMaxLengthUnitInput = document.getElementById("filenameMaxLengthUnitInput");
 const shadowEnabledInput = document.getElementById("shadowEnabledInput");
 const maxResourceSizeInput = document.getElementById("maxResourceSizeInput");
 const maxResourceSizeEnabledInput = document.getElementById("maxResourceSizeEnabledInput");
@@ -504,6 +507,8 @@ loadDeferredImagesKeepZoomLevelLabel.textContent = browser.i18n.getMessage("opti
 addMenuEntryLabel.textContent = browser.i18n.getMessage("optionAddMenuEntry");
 filenameTemplateLabel.textContent = browser.i18n.getMessage("optionFilenameTemplate");
 filenameMaxLengthLabel.textContent = browser.i18n.getMessage("optionFilenameMaxLength");
+filenameMaxLengthBytesUnitLabel.textContent = browser.i18n.getMessage("optionFilenameMaxLengthBytesUnit");
+filenameMaxLengthCharsUnitLabel.textContent = browser.i18n.getMessage("optionFilenameMaxLengthCharsUnit");
 shadowEnabledLabel.textContent = browser.i18n.getMessage("optionDisplayShadow");
 setMaxResourceSizeLabel.textContent = browser.i18n.getMessage("optionSetMaxResourceSize");
 maxResourceSizeLabel.textContent = browser.i18n.getMessage("optionMaxResourceSize");
@@ -725,6 +730,7 @@ async function refresh(profileName) {
 	contextMenuEnabledInput.checked = profileOptions.contextMenuEnabled;
 	filenameTemplateInput.value = profileOptions.filenameTemplate;
 	filenameMaxLengthInput.value = profileOptions.filenameMaxLength;
+	filenameMaxLengthUnitInput.value = profileOptions.filenameMaxLengthUnit;
 	shadowEnabledInput.checked = profileOptions.shadowEnabled;
 	maxResourceSizeEnabledInput.checked = profileOptions.maxResourceSizeEnabled;
 	maxResourceSizeInput.value = profileOptions.maxResourceSize;
@@ -816,6 +822,7 @@ async function update() {
 			contextMenuEnabled: contextMenuEnabledInput.checked,
 			filenameTemplate: filenameTemplateInput.value,
 			filenameMaxLength: filenameMaxLengthInput.value,
+			filenameMaxLengthUnit: filenameMaxLengthUnitInput.value,
 			shadowEnabled: shadowEnabledInput.checked,
 			maxResourceSizeEnabled: maxResourceSizeEnabledInput.checked,
 			maxResourceSize: Math.max(maxResourceSizeInput.value, 0),

+ 9 - 6
extension/ui/pages/help.html

@@ -170,9 +170,9 @@
 								"2018/9/15/Introduction to SingleFile.html").</li>
 						</ul>
 					</li>
-					<li data-options-label="filenameMaxLengthLabel"> <span class="option">Option: max length
-							(bytes)</span>
-						<p>Specify the maximum length in bytes of the filename.</p>
+					<li data-options-label="filenameMaxLengthLabel"> <span class="option">Option: max length</span>
+						<p>Specify the maximum length of the filename (without the extension) and its unit (bytes or
+							characters).</p>
 					</li>
 					<li data-options-label="confirmFilenameLabel"> <span class="option">Option: open the "Save as"
 							dialog to confirm the file name</span>
@@ -671,9 +671,12 @@
 			<li><a id="template-variables">Template variables</a>
 				<p>The template variables are used to customize the infobar content or the file name of a saved page.
 					They help to insert dynamic values like the save date or the page title.</p>
-				<p> You can limit the length of a dynamic value by adding <code>[<em>maxByteSize</em>]</code> just after
-					the variable name. The <code>maxByteSize</code> value is the maximum length of the value in bytes
-					(e.g. `{page-title}[20]` to limit the title to 20 bytes).</p>
+				<p> You can limit the length of a dynamic value by adding <code>[<em>maxByteSize</em>]</code> or
+					<code>[<em>maxCharSize</em>ch]</code> just after the variable name. The <code>maxByteSize</code>
+					value is the maximum length of the value in bytes (e.g. `{page-title}[20]` to limit the title to 20
+					bytes). The <code>maxCharSize</code> value is the maximum length in characters (e.g.
+					`{page-title}[10ch]` to limit the title to 10 characters).
+				</p>
 				<ul>
 					<li><code>{page-title}</code>: the title of the page</li>
 					<li><code>{page-heading}</code>: the content of the H1 tag in the page</li>

+ 13 - 2
extension/ui/pages/options.css

@@ -67,8 +67,11 @@ input[type="number"] {
 }
 
 select {
+    min-height: 20px;
+}
+
+#profileNamesInput {
     min-height: 22px;
-    max-height: 22px;
 }
 
 input.medium-input {
@@ -79,6 +82,14 @@ input.large-input {
     min-width: 60px;
 }
 
+.option-input {
+    display: flex;
+}
+
+#filenameMaxLengthUnitInput {
+    margin-left: 2px;
+}
+
 h3 {
     display: flex;
     padding-left: 8px;
@@ -577,7 +588,7 @@ html.maximized {
     ::-webkit-scrollbar {
         background-color: #2A2A2E;
     }
-    
+
     ::-webkit-scrollbar-thumb {
         background-color: #555;
     }

+ 7 - 1
extension/ui/pages/options.html

@@ -64,7 +64,13 @@
 			</div>
 			<div class="option">
 				<label for="filenameMaxLengthInput" id="filenameMaxLengthLabel"></label>
-				<input type="number" id="filenameMaxLengthInput" min="1">
+				<div class="option-input">
+					<input type="number" id="filenameMaxLengthInput" min="1">
+					<select id="filenameMaxLengthUnitInput">
+						<option id="filenameMaxLengthBytesUnitLabel" value="bytes"></option>
+						<option id="filenameMaxLengthCharsUnitLabel" value="chars"></option>
+					</select>
+				</div>
 			</div>
 			<div class="option">
 				<label for="confirmFilenameInput" id="confirmFilenameLabel"></label>

+ 19 - 7
lib/single-file/single-file-core.js

@@ -532,10 +532,13 @@ class Processor {
 		if (!this.options.backgroundSave) {
 			filename = filename.replace(/\//g, replacementCharacter);
 		}
-		if (!this.options.saveToGDrive && !this.options.saveToGitHub && !this.options.saveWithCompanion && util.getContentSize(filename) > this.options.filenameMaxLength) {
+		if (!this.options.saveToGDrive && !this.options.saveToGitHub && !this.options.saveWithCompanion &&
+			((this.options.filenameMaxLengthUnit == "bytes" && util.getContentSize(filename) > this.options.filenameMaxLength) || (filename.length > this.options.filenameMaxLength))) {
 			const extensionMatch = filename.match(/(\.[^.]{3,4})$/);
 			const extension = extensionMatch && extensionMatch[0] && extensionMatch[0].length > 1 ? extensionMatch[0] : "";
-			filename = await util.truncateText(filename, this.options.filenameMaxLength - extension.length);
+			filename = this.options.filenameMaxLengthUnit == "bytes" ?
+				await util.truncateText(filename, this.options.filenameMaxLength - extension.length) :
+				filename.substring(0, this.options.filenameMaxLength - extension.length);
 			filename = filename + "…" + extension;
 		}
 		if (!filename) {
@@ -2157,15 +2160,22 @@ function getOnEventAttributeNames(doc) {
 }
 
 async function evalTemplateVariable(template, variableName, valueGetter, dontReplaceSlash, replacementCharacter) {
-	let maxLength;
+	let maxLength, maxCharLength;
 	if (template) {
 		const regExpVariable = "{\\s*" + variableName.replace(/\W|_/g, "[$&]") + "\\s*}";
-		let replaceRegExp = new RegExp(regExpVariable + "\\[\\d+\\]", "g");
+		let replaceRegExp = new RegExp(regExpVariable + "\\[\\d+(ch)?\\]", "g");
 		if (template.match(replaceRegExp)) {
 			const matchedLength = template.match(replaceRegExp)[0];
-			maxLength = Number(matchedLength.match(/\[(\d+)\]$/)[1]);
-			if (isNaN(maxLength) || maxLength <= 0) {
-				maxLength = null;
+			if (matchedLength.match(/\[(\d+)\]$/)) {
+				maxLength = Number(matchedLength.match(/\[(\d+)\]$/)[1]);
+				if (isNaN(maxLength) || maxLength <= 0) {
+					maxLength = null;
+				}
+			} else {
+				maxCharLength = Number(matchedLength.match(/\[(\d+)ch\]$/)[1]);
+				if (isNaN(maxCharLength) || maxCharLength <= 0) {
+					maxCharLength = null;
+				}
 			}
 		} else {
 			replaceRegExp = new RegExp(regExpVariable, "g");
@@ -2177,6 +2187,8 @@ async function evalTemplateVariable(template, variableName, valueGetter, dontRep
 			}
 			if (maxLength) {
 				value = await util.truncateText(value, maxLength);
+			} else if (maxCharLength) {
+				value = value.substring(0, maxCharLength);
 			}
 			return template.replace(replaceRegExp, value);
 		}