diff --git a/docs/assets/js/i18n.js b/docs/assets/js/i18n.js new file mode 100644 index 000000000..2d666e744 --- /dev/null +++ b/docs/assets/js/i18n.js @@ -0,0 +1,23 @@ +"use strict"; + +/** + * Tiny page i18n helper. + * Language comes from (set by Material / mkdocs-static-i18n). + * Tables: { key: { ru: "...", en: "...", ... } }, placeholders {0}, {1}, ... + */ +function pageI18n(table, fallback) { + const lang = (document.documentElement.lang || fallback || "ru").toLowerCase(); + const fb = fallback || "ru"; + + return { + lang: lang, + t: function (key) { + const entry = table[key] || {}; + let s = entry[lang] || entry[fb] || ""; + for (let i = 1; i < arguments.length; i++) { + s = s.split("{" + (i - 1) + "}").join(arguments[i]); + } + return s; + } + }; +} diff --git a/docs/assets/js/longCoding.js b/docs/assets/js/longCoding.js index f462c1810..36e76dbbb 100644 --- a/docs/assets/js/longCoding.js +++ b/docs/assets/js/longCoding.js @@ -4,6 +4,25 @@ let curSelectedByte = -1; let defaultByteSize = 30; let labelsFromFile = null; +const lcI18n = pageI18n({ + wrongSymbols: { + ru: "Кодировка содержит неверные символы. Поддерживаются только латинские буквы A-F и цифры 0-9.", + en: "Coding has wrong symbols. Latin characters A-F and digits 0-9 are allowed." + }, + oddLength: { + ru: "Неверное значение. Кодировка должна содержать чётное количество символов.", + en: "Incorrect value. Coding should have even number of symbols." + }, + bytesHeader: { + ru: "Байты", + en: "Bytes" + }, + badValueFormat: { + ru: "{0} - неверный формат введенного значения!", + en: "{0} - incorrect value!" + } +}); + // ========== Additional methods ========== function cleanHexValue(str) { @@ -139,18 +158,12 @@ function checkOrig() { } return true; } else { - alert( - "Кодировка содержит неверные символы. Поддерживаются только латинские буквы A-F и цифры 0-9. " + - "Coding has wrong symbols. Latin characters A-F and digits 0-9 are allowed." - ); + alert(lcI18n.t("wrongSymbols")); clearFields(); } } else { if (orig.length > 0) { - alert( - "Неверное значение. Кодировка должна содержать чётное количество символов. " + - "Incorrect value. Coding should have even number of symbols." - ); + alert(lcI18n.t("oddLength")); } clearFields(); } @@ -490,7 +503,7 @@ function createByteDescription(countOfBytes) { const lines = Math.ceil(countOfBytes / bytesPerLine); let html = ""; - html += ""; + html += ""; for (let line = 0; line < lines; line++) { const start = line * bytesPerLine; @@ -606,7 +619,7 @@ function wireByteEvents() { function setByte(ctrl, byteIndex) { const regexp = /^[0-9a-fA-F]*$/; if (!regexp.test(ctrl.value)) { - alert(ctrl.value + " - неверный формат введенного значения! Incorrect value!"); + alert(lcI18n.t("badValueFormat", ctrl.value)); ctrl.value = "00"; } else { ctrl.value = ctrl.value.toUpperCase(); diff --git a/docs/assets/js/tiresGenerator.js b/docs/assets/js/tiresGenerator.js index 588e20e50..c95685e0f 100644 --- a/docs/assets/js/tiresGenerator.js +++ b/docs/assets/js/tiresGenerator.js @@ -1,5 +1,68 @@ "use strict"; +const tgI18n = pageI18n({ + tireSetIndividual: { + ru: "Индивидуальная настройка давления в шинах", + en: "Tire set Individual" + }, + tireSetHeader: { + ru: "Настройка давления в шинах: #{0}", + en: "Tire set: #{0}" + }, + fieldName: { + ru: "Имя", + en: "Name" + }, + loadSituation: { + ru: "Тип загрузки машины", + en: "Load situation" + }, + frontWheels: { + ru: "Передние колеса", + en: "Front wheels" + }, + rearWheels: { + ru: "Задние колеса", + en: "Rear wheels" + }, + fullLoad: { + ru: "Полная загрузка", + en: "Full load" + }, + standardLoad: { + ru: "Стандартная загрузка", + en: "Standard load" + }, + comfortLoad: { + ru: "Комфортная загрузка", + en: "Comfort load" + }, + needMoreThanZero: { + ru: "Количество комплектов шин должно быть больше 0.", + en: "Number of tire sets should be greater than 0." + }, + nameInvalid: { + ru: "Имя конфигурации #{0} может содержать только латинские буквы, цифры, пробел, / и +.", + en: "Configuration name #{0} should have only latin letters, numbers, space, / and +." + }, + loadConfigPrompt: { + ru: "Восстановление конфигурации\n\nПожалуйста введите код, созданный ранее в данном приложении.", + en: "Load configuration\n\nPlease enter configuration code, created earlier with this tool" + }, + invalidConfigCode: { + ru: "Введён некорректный код конфигурации (не base64).", + en: "Invalid configuration code." + }, + versionMismatch: { + ru: "Введенный код конфигурации не может быть восстановлен\n\nСоздан версией: {0}\nТекущая версия: {1}", + en: "Configuration was created using an incompatible version of this tool and can not be restored\n\nCreated version: {0}\nCurrent version: {1}" + }, + saveConfigPrompt: { + ru: "Сохранить настройки\n\nПожалуйста, сохраните код, приведённый ниже. Он может быть использован для восстановления настроек в любое время.", + en: "Save configuration\n\nPlease backup the configuration code below, it can be used to restore the current configuration at a later point in time." + } +}); + function addToDataset(dataset, info) { dataset.value += info; } @@ -210,7 +273,7 @@ function doGenerate() { if (Number.isNaN(numTireSets)) numTireSets = 0; if (numTireSets < 1) { - window.alert("Количество комплектов шин должно быть больше 0.\nNumber of tire sets should be greater than 0."); + window.alert(tgI18n.t("needMoreThanZero")); return; } @@ -245,10 +308,7 @@ function doGenerate() { const nameValue = document.getElementById("t" + index + "name").value; if (!/^(?=.*[a-zA-Z0-9])[a-zA-Z0-9 /+]+$/.test(nameValue)) { - window.alert( - "Имя конфигурации #" + index + " может содержать только латинские буквы, цифры, пробел, / и +.\n" + - "Configuration name should have only latin letters, numbers, space, / and +." - ); + window.alert(tgI18n.t("nameInvalid", index)); return; } @@ -387,33 +447,33 @@ function createTireSets() { html += " "; if (index === 11) { - html += " "; + html += " "; } else { - html += " "; + html += " "; } html += " "; html += " "; - html += " "; + html += " "; html += " "; html += " "; html += " "; - html += " "; - html += " "; - html += " "; + html += " "; + html += " "; + html += " "; html += " "; html += " "; - html += " "; + html += " "; html += " "; html += " "; html += " "; html += " "; - html += " "; + html += " "; html += " "; html += " "; html += " "; html += " "; - html += " "; + html += " "; html += " "; html += " "; html += " "; @@ -444,9 +504,7 @@ const configFields = [ function loadConfig() { const result = window.prompt( - "Восстановление конфигурации. Load configuration\n\n" + - "Пожалуйста введите код, созданный ранее в данном приложении.\n" + - "Please enter configuration code, created earlier with this tool", + tgI18n.t("loadConfigPrompt"), "" ); @@ -456,7 +514,7 @@ function loadConfig() { try { code = window.atob(result); } catch (e) { - window.alert("Введён некорректный код конфигурации (не base64).\nInvalid configuration code."); + window.alert(tgI18n.t("invalidConfigCode")); return; } @@ -469,11 +527,7 @@ function loadConfig() { if (data[0] === "configVersion") { if (data[1] !== configVersion) { - window.alert( - "Введенный код конфигурации не может может быть восстановлен\n" + - "Configuration was created using an incompatible version of this tool and can not be restored\n\n" + - "Created version: " + data[1] + "\nCurrent version: " + configVersion - ); + window.alert(tgI18n.t("versionMismatch", data[1], configVersion)); return; } } else if (data[0].charAt(0) === "@") { @@ -512,13 +566,11 @@ function saveConfig() { const encoded = window.btoa(code); window.prompt( - "Сохранить настройки. Save configuration\n\n" + - "Пожалуйста, сохраните код, приведённый ниже. Он может быть использован для восстановления настроек в любое время.\n" + - "Please backup the configuration code below, it can be used to restore the current configuration at a later point in time.", + tgI18n.t("saveConfigPrompt"), encoded ); } -window.onload = function () { - createTireSets(); -}; +// Material navigation.instant re-runs this script but does not fire window.onload again. +// Script is at end of body, so the DOM is ready — call directly. +createTireSets(); diff --git a/docs/assets/js/xor.js b/docs/assets/js/xor.js index 6301ccfc0..4a3fa4202 100644 --- a/docs/assets/js/xor.js +++ b/docs/assets/js/xor.js @@ -1,6 +1,17 @@ let decodeString = "c9d2"; let codeLength = 4; +const xorI18n = pageI18n({ + lengthMismatch: { + ru: "Длина вводимого значения должна быть {0} символа.", + en: "Input HEX value must be exactly {0} characters long." + }, + hexOnly: { + ru: "Допустимы только HEX символы (0-9, A-F).", + en: "Only HEX characters (0-9, A-F) are allowed." + } +}); + function init() { const urlParams = new URLSearchParams(window.location.search); const codeParam = urlParams.get('code'); @@ -16,15 +27,12 @@ function calculateXor() { let result = ''; if (input.length !== codeLength) { - alert( - "Длина вводимого значения должна быть " + codeLength + " символа.\n" + - "Input HEX value must be exactly " + codeLength + " characters long." - ); + alert(xorI18n.t("lengthMismatch", codeLength)); return; } if (!/^[0-9a-fA-F]+$/.test(input)) { - alert("Допустимы только HEX символы (0-9, A-F).\nOnly HEX characters (0-9, A-F) are allowed."); + alert(xorI18n.t("hexOnly")); return; } diff --git a/docs/en/assets/css/fec.css b/docs/en/assets/css/fec.css deleted file mode 100644 index 1db65677e..000000000 --- a/docs/en/assets/css/fec.css +++ /dev/null @@ -1,620 +0,0 @@ -.p-5{padding:3rem!important} - -.row { - --bs-gutter-x: 1.5rem; - --bs-gutter-y: 0; - display: flex; - flex-wrap: wrap; - margin-top: calc(var(--bs-gutter-y) * -1); - margin-right: calc(var(--bs-gutter-x) * -.5); - margin-left: calc(var(--bs-gutter-x) * -.5); -} -.row > * { - flex-shrink: 0; - width: 100%; - max-width: 100%; - padding-right: calc(var(--bs-gutter-x) * .5); - padding-left: calc(var(--bs-gutter-x) * .5); - margin-top: var(--bs-gutter-y); -} - -.col { - flex: 1 0 0; -} - -.row-cols-auto > * { - flex: 0 0 auto; - width: auto; -} - -.row-cols-1 > * { - flex: 0 0 auto; - width: 100%; -} - -.row-cols-2 > * { - flex: 0 0 auto; - width: 50%; -} - -.row-cols-3 > * { - flex: 0 0 auto; - width: 33.3333333333%; -} - -.row-cols-4 > * { - flex: 0 0 auto; - width: 25%; -} - -.row-cols-5 > * { - flex: 0 0 auto; - width: 20%; -} - -.row-cols-6 > * { - flex: 0 0 auto; - width: 16.6666666667%; -} - - -.form-control { - display: block; - width: 100%; - height: 34px; - padding: 6px 12px; - font-size: 14px; - line-height: 1.42857143; - color: #555; - background: #fff; - border: 1px solid #ccc; - border-radius: 4px; - box-shadow: inset 0 1px 1px rgba(0,0,0,.075); - transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out; -} -.form-control:focus { - border-color: #66afe9; - outline: 0; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6); -} -.form-control::-moz-placeholder { - color: #999; - opacity: 1; -} - -.form-control:-ms-input-placeholder { - color: #999; -} -.form-control::-webkit-input-placeholder { - color: #999; -} -.form-control::-ms-expand { - background-color: transparent; - border: 0; -} -.form-control[disabled], -.form-control[readonly], -fieldset[disabled] .form-control { - background-color: #eeeeee; - opacity: 1; -} -.form-control[disabled], -fieldset[disabled] .form-control { - cursor: not-allowed; -} -textarea.form-control { - height: auto; -} -@media screen and (-webkit-min-device-pixel-ratio: 0) { - input[type="date"].form-control, - input[type="time"].form-control, - input[type="datetime-local"].form-control, - input[type="month"].form-control { - line-height: 34px; - } - input[type="date"].input-sm, - input[type="time"].input-sm, - input[type="datetime-local"].input-sm, - input[type="month"].input-sm, - .input-group-sm input[type="date"], - .input-group-sm input[type="time"], - .input-group-sm input[type="datetime-local"], - .input-group-sm input[type="month"] { - line-height: 30px; - } - input[type="date"].input-lg, - input[type="time"].input-lg, - input[type="datetime-local"].input-lg, - input[type="month"].input-lg, - .input-group-lg input[type="date"], - .input-group-lg input[type="time"], - .input-group-lg input[type="datetime-local"], - .input-group-lg input[type="month"] { - line-height: 46px; - } -} -.form-group { - margin-bottom: 15px; -} -.radio, -.checkbox { - position: relative; - display: block; -} -.radio.disabled label, -.checkbox.disabled label, -fieldset[disabled] .radio label, -fieldset[disabled] .checkbox label { - cursor: not-allowed; -} -.radio label, -.checkbox label { - min-height: 20px; - padding-left: 20px; - margin-bottom: 0; - font-weight: 400; - cursor: pointer; -} -.radio input[type="radio"], -.radio-inline input[type="radio"], -.checkbox input[type="checkbox"], -.checkbox-inline input[type="checkbox"] { - position: absolute; - margin-top: 4px \9; - margin-left: -20px; -} -.radio + .radio, -.checkbox + .checkbox { - margin-top: -5px; -} -.radio-inline, -.checkbox-inline { - position: relative; - display: inline-block; - padding-left: 20px; - margin-bottom: 0; - font-weight: 400; - vertical-align: middle; - cursor: pointer; -} -.radio-inline.disabled, -.checkbox-inline.disabled, -fieldset[disabled] .radio-inline, -fieldset[disabled] .checkbox-inline { - cursor: not-allowed; -} -.radio-inline + .radio-inline, -.checkbox-inline + .checkbox-inline { - margin-top: 0; - margin-left: 10px; -} -.form-control-static { - min-height: 34px; - padding-top: 7px; - padding-bottom: 7px; - margin-bottom: 0; -} -.form-control-static.input-lg, -.form-control-static.input-sm { - padding-right: 0; - padding-left: 0; -} -.input-sm { - height: 30px; - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -select.input-sm { - height: 30px; - line-height: 30px; -} -textarea.input-sm, -select[multiple].input-sm { - height: auto; -} -.form-group-sm .form-control { - height: 30px; - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -.form-group-sm select.form-control { - height: 30px; - line-height: 30px; -} -.form-group-sm textarea.form-control, -.form-group-sm select[multiple].form-control { - height: auto; -} -.form-group-sm .form-control-static { - height: 30px; - min-height: 32px; - padding: 6px 10px; - font-size: 12px; - line-height: 1.5; -} -.input-lg { - height: 46px; - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; - border-radius: 6px; -} -select.input-lg { - height: 46px; - line-height: 46px; -} -textarea.input-lg, -select[multiple].input-lg { - height: auto; -} -.form-group-lg .form-control { - height: 46px; - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; - border-radius: 6px; -} -.form-group-lg select.form-control { - height: 46px; - line-height: 46px; -} -.form-group-lg textarea.form-control, -.form-group-lg select[multiple].form-control { - height: auto; -} -.form-group-lg .form-control-static { - height: 46px; - min-height: 38px; - padding: 11px 16px; - font-size: 18px; - line-height: 1.3333333; -} -.has-feedback { - position: relative; -} -.has-feedback .form-control { - padding-right: 42.5px; -} -.form-control-feedback { - position: absolute; - top: 0; - right: 0; - z-index: 2; - display: block; - width: 34px; - height: 34px; - line-height: 34px; - text-align: center; - pointer-events: none; -} -.input-lg + .form-control-feedback, -.input-group-lg + .form-control-feedback, -.form-group-lg .form-control + .form-control-feedback { - width: 46px; - height: 46px; - line-height: 46px; -} -.input-sm + .form-control-feedback, -.input-group-sm + .form-control-feedback, -.form-group-sm .form-control + .form-control-feedback { - width: 30px; - height: 30px; - line-height: 30px; -} -.has-success .help-block, -.has-success .control-label, -.has-success .radio, -.has-success .checkbox, -.has-success .radio-inline, -.has-success .checkbox-inline, -.has-success.radio label, -.has-success.checkbox label, -.has-success.radio-inline label, -.has-success.checkbox-inline label { - color: #3c763d; -} -.has-success .form-control { - border-color: #3c763d; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -} -.has-success .form-control:focus { - border-color: #2b542c; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168; -} -.has-success .input-group-addon { - color: #3c763d; - background-color: #dff0d8; - border-color: #3c763d; -} -.has-success .form-control-feedback { - color: #3c763d; -} -.has-warning .help-block, -.has-warning .control-label, -.has-warning .radio, -.has-warning .checkbox, -.has-warning .radio-inline, -.has-warning .checkbox-inline, -.has-warning.radio label, -.has-warning.checkbox label, -.has-warning.radio-inline label, -.has-warning.checkbox-inline label { - color: #8a6d3b; -} -.has-warning .form-control { - border-color: #8a6d3b; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -} -.has-warning .form-control:focus { - border-color: #66512c; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b; -} -.has-warning .input-group-addon { - color: #8a6d3b; - background-color: #fcf8e3; - border-color: #8a6d3b; -} -.has-warning .form-control-feedback { - color: #8a6d3b; -} -.has-error .help-block, -.has-error .control-label, -.has-error .radio, -.has-error .checkbox, -.has-error .radio-inline, -.has-error .checkbox-inline, -.has-error.radio label, -.has-error.checkbox label, -.has-error.radio-inline label, -.has-error.checkbox-inline label { - color: #a94442; -} -.has-error .form-control { - border-color: #a94442; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -} -.has-error .form-control:focus { - border-color: #843534; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483; -} -.has-error .input-group-addon { - color: #a94442; - background-color: #f2dede; - border-color: #a94442; -} -.has-error .form-control-feedback { - color: #a94442; -} -.has-feedback label ~ .form-control-feedback { - top: 25px; -} -.has-feedback label.sr-only ~ .form-control-feedback { - top: 0; -} -.help-block { - display: block; - margin-top: 5px; - margin-bottom: 10px; - color: #737373; -} -@media (min-width: 768px) { - .form-inline .form-group { - display: inline-block; - margin-bottom: 0; - vertical-align: middle; - } - .form-inline .form-control { - display: inline-block; - width: auto; - vertical-align: middle; - } - .form-inline .form-control-static { - display: inline-block; - } - .form-inline .input-group { - display: inline-table; - vertical-align: middle; - } - .form-inline .input-group .input-group-addon, - .form-inline .input-group .input-group-btn, - .form-inline .input-group .form-control { - width: auto; - } - .form-inline .input-group > .form-control { - width: 100%; - } - .form-inline .control-label { - margin-bottom: 0; - vertical-align: middle; - } - .form-inline .radio, - .form-inline .checkbox { - display: inline-block; - margin-top: 0; - margin-bottom: 0; - vertical-align: middle; - } - .form-inline .radio label, - .form-inline .checkbox label { - padding-left: 0; - } - .form-inline .radio input[type="radio"], - .form-inline .checkbox input[type="checkbox"] { - position: relative; - margin-left: 0; - } - .form-inline .has-feedback .form-control-feedback { - top: 0; - } -} -.form-horizontal .radio, -.form-horizontal .checkbox, -.form-horizontal .radio-inline, -.form-horizontal .checkbox-inline { - padding-top: 7px; - margin-top: 0; - margin-bottom: 0; -} -.form-horizontal .radio, -.form-horizontal .checkbox { - min-height: 27px; -} -.form-horizontal .form-group { - margin-right: -15px; - margin-left: -15px; -} -@media (min-width: 768px) { - .form-horizontal .control-label { - padding-top: 7px; - margin-bottom: 0; - text-align: right; - } -} -.form-horizontal .has-feedback .form-control-feedback { - right: 15px; -} -.alert { - padding: 15px; - margin-bottom: 20px; - border: 1px solid transparent; - border-radius: 4px; -} -.alert h4 { - margin-top: 0; - color: inherit; -} -.alert .alert-link { - font-weight: bold; -} -.alert > p, -.alert > ul { - margin-bottom: 0; -} -.alert > p + p { - margin-top: 5px; -} -.alert-dismissable, -.alert-dismissible { - padding-right: 35px; -} -.alert-dismissable .close, -.alert-dismissible .close { - position: relative; - top: -2px; - right: -21px; - color: inherit; -} -.alert-success { - color: #3c763d; - background-color: #dff0d8; - border-color: #d6e9c6; -} -.alert-success hr { - border-top-color: #c9e2b3; -} -.alert-success .alert-link { - color: #2b542c; -} -.alert-info { - color: #31708f; - background-color: #d9edf7; - border-color: #bce8f1; -} -.alert-info hr { - border-top-color: #a6e1ec; -} -.alert-info .alert-link { - color: #245269; -} -.alert-warning { - color: #8a6d3b; - background-color: #fcf8e3; - border-color: #faebcc; -} -.alert-warning hr { - border-top-color: #f7e1b5; -} -.alert-warning .alert-link { - color: #66512c; -} -.alert-danger { - color: #a94442; - background-color: #f2dede; - border-color: #ebccd1; -} -.alert-danger hr { - border-top-color: #e4b9c0; -} -.alert-danger .alert-link { - color: #843534; -} -.row { - --bs-gutter-x: 1.5rem; - --bs-gutter-y: 0; - display: flex; - flex-wrap: wrap; - margin-top: calc(var(--bs-gutter-y) * -1); - margin-right: calc(var(--bs-gutter-x) * -.5); - margin-left: calc(var(--bs-gutter-x) * -.5); -} -.row > * { - flex-shrink: 0; - width: 100%; - max-width: 100%; - padding-right: calc(var(--bs-gutter-x) * .5); - padding-left: calc(var(--bs-gutter-x) * .5); - margin-top: var(--bs-gutter-y); -} - -.col { - flex: 1 0 0; -} - -.row-cols-auto > * { - flex: 0 0 auto; - width: auto; -} - -.row-cols-1 > * { - flex: 0 0 auto; - width: 100%; -} - -.row-cols-2 > * { - flex: 0 0 auto; - width: 50%; -} - -.row-cols-3 > * { - flex: 0 0 auto; - width: 33.3333333333%; -} - -.row-cols-4 > * { - flex: 0 0 auto; - width: 25%; -} - -.row-cols-5 > * { - flex: 0 0 auto; - width: 20%; -} - -.row-cols-6 > * { - flex: 0 0 auto; - width: 16.6666666667%; -} - -.font-weight-bold{font-weight:700!important} -.font-weight-bolder{font-weight:bolder!important} \ No newline at end of file diff --git a/docs/en/assets/custom.css b/docs/en/assets/custom.css deleted file mode 100644 index a23be60f1..000000000 --- a/docs/en/assets/custom.css +++ /dev/null @@ -1,301 +0,0 @@ -iframe[src*="ads-iframe"] { - display: none; -} - -iframe[id*="dsq-app"] { - display: none; -} - -iframe[id*="dsq-app"][src*="comments"] { - display: block; -} - -:root { - --md-admonition-icon--give-money: url('data:image/svg+xml;charset=utf-8,') -} - -.md-grid { - max-width: 1600px; -} - - -/* Preserve link color */ -.md-banner a, -.md-banner a:focus, -.md-banner a:hover { - color: currentColor; -} - -/* Don't wrap name of blog article */ -.md-banner strong { - white-space: nowrap; -} - -.md-banner { - color: #fafafa; - background-color: #4caf50; - cursor: pointer; -} - -.md-banner:hover, -.md-banner:focus { - color: #fafafa; - background-color: #087f23; -} - -/* Twitter icon */ -.md-banner .twitter { - margin-left: .2em; - color: #00acee; -} - -.md-typeset ul { - display: block; -} - -.md-header-nav__title { - padding-top: 1px; - padding-left: 0; -} - -.md-nav__item:hover { - background-color: rgba(63, 81, 181, 0.1); -} - -.md-nav__item--nested { - background: none !important; -} - -.md-tabs__item:hover { - background: #5564C3; - border-bottom: 4px solid #fff; - transform: scaleY(1); -} - -.md-tabs__item--active { - border-bottom: 4px solid #fff; - color: inherit; - opacity: 1; -} - -.md-header__button.md-logo img { - width: auto !important; - height: 43px !important; -} - -.pure-material-button-contained { - position: relative; - display: inline-block; - box-sizing: border-box; - border: none; - border-radius: 4px; - padding: 0 16px; - min-width: 64px; - height: 30px; - vertical-align: middle; - text-align: center; - text-overflow: ellipsis; - text-transform: uppercase; - color: rgb(var(--pure-material-onprimary-rgb, 255, 255, 255)) !important; - background-color: rgb(var(--pure-material-primary-rgb, 33, 150, 243)); - box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12); - font-family: var(--pure-material-font, "Roboto", "Segoe UI", BlinkMacSystemFont, system-ui, -apple-system), serif; - font-size: 14px; - font-weight: 500; - line-height: 30px; - overflow: hidden; - outline: none; - cursor: pointer; - transition: box-shadow 0.2s; -} - -.pure-material-button-contained::-moz-focus-inner { - border: none; -} - -/* Overlay */ -.pure-material-button-contained::before { - content: ""; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - background-color: rgb(var(--pure-material-onprimary-rgb, 255, 255, 255)); - opacity: 0; - transition: opacity 0.2s; -} - -/* Ripple */ -.pure-material-button-contained::after { - content: ""; - position: absolute; - left: 50%; - top: 50%; - border-radius: 50%; - padding: 50%; - width: 32px; /* Safari */ - height: 32px; /* Safari */ - background-color: rgb(var(--pure-material-onprimary-rgb, 255, 255, 255)); - opacity: 0; - transform: translate(-50%, -50%) scale(1); - transition: opacity 1s, transform 0.5s; -} - -/* Hover, Focus */ -.pure-material-button-contained:hover, -.pure-material-button-contained:focus { - box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12); -} - -.pure-material-button-contained:hover::before { - opacity: 0.08; -} - -.pure-material-button-contained:focus::before { - opacity: 0.24; -} - -.pure-material-button-contained:hover:focus::before { - opacity: 0.3; -} - -/* Active */ -.pure-material-button-contained:active { - box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12); -} - -.pure-material-button-contained:active::after { - opacity: 0.32; - transform: translate(-50%, -50%) scale(0); - transition: transform 0s; -} - -/* Disabled */ -.pure-material-button-contained:disabled { - color: rgba(var(--pure-material-onsurface-rgb, 0, 0, 0), 0.38); - background-color: rgba(var(--pure-material-onsurface-rgb, 0, 0, 0), 0.12); - box-shadow: none; - cursor: initial; -} - -.pure-material-button-contained:disabled::before { - opacity: 0; -} - -.pure-material-button-contained:disabled::after { - opacity: 0; -} - -.md-typeset .admonition { - margin-top: 0 !important; -} - -.md-typeset .admonition.give-money, -.md-typeset details.give-money { - border-color: #448aff; -} - -.md-typeset .give-money > .admonition-title, -.md-typeset .give-money > summary { - background-color: rgba(68,138,255,.1); -} - -.md-typeset .give-money > .admonition-title::before, -.md-typeset .give-money > summary::before { - background-color: rgba(68,138,255); - -webkit-mask-image: var(--md-admonition-icon--give-money); - mask-image: var(--md-admonition-icon--give-money); -} - -.mdx-container { - padding-top: 1rem; - background: url("data:image/svg+xml;utf8,") no-repeat bottom, linear-gradient(180deg, var(--md-primary-fg-color), #a63fd9 99%, var(--md-default-bg-color) 0) -} - -[data-md-color-scheme=slate] .mdx-container { - background: url("data:image/svg+xml;utf8,") no-repeat bottom, linear-gradient(180deg, var(--md-primary-fg-color), #a63fd9 99%, var(--md-default-bg-color) 0) -} - -.mdx-hero { - margin: 0 .8rem; - color: var(--md-primary-bg-color) -} - -.mdx-hero h1 { - margin-bottom: 1rem; - color: currentColor; - font-weight: 700 -} - -/* Styles for custom pages */ - -table { - *border-collapse: collapse; /* IE7 and lower */ - border-spacing: 0; - width: 100%; -} - -.bordered { - -moz-border-radius: 6px; - -webkit-border-radius: 6px; - border-radius: 6px; - -webkit-box-shadow: 0 1px 1px #ccc; - -moz-box-shadow: 0 1px 1px #ccc; - box-shadow: 0 1px 1px #ccc; -} - -.bordered td, .bordered th { - border-left: 1px solid #ccc; - border-top: 1px solid #ccc; - padding: 10px; - text-align: left; -} - -.bordered th { - background-color: #3F51B5; - color: #FFFFFF; - -webkit-box-shadow: 0 1px 0 rgba(255,255,255,.8) inset; - -moz-box-shadow:0 1px 0 rgba(255,255,255,.8) inset; - box-shadow: 0 1px 0 rgba(255,255,255,.8) inset; - border-top: none; - text-shadow: 0 1px 0 rgba(255,255,255,.5); -} - -.bordered tr:hover { - -o-transition: all 0.1s ease-in-out; - -webkit-transition: all 0.1s ease-in-out; - -moz-transition: all 0.1s ease-in-out; - -ms-transition: all 0.1s ease-in-out; - transition: all 0.1s ease-in-out; -} - -.bordered th:first-child { - -moz-border-radius: 6px 0 0 0; - -webkit-border-radius: 6px 0 0 0; - border-radius: 6px 0 0 0; -} - -.bordered th:last-child { - -moz-border-radius: 0 6px 0 0; - -webkit-border-radius: 0 6px 0 0; - border-radius: 0 6px 0 0; -} - -.bordered th:only-child{ - -moz-border-radius: 6px 6px 0 0; - -webkit-border-radius: 6px 6px 0 0; - border-radius: 6px 6px 0 0; -} - -.bordered tr:last-child td:first-child { - -moz-border-radius: 0 0 0 6px; - -webkit-border-radius: 0 0 0 6px; - border-radius: 0 0 0 6px; -} - -.bordered tr:last-child td:last-child { - -moz-border-radius: 0 0 6px 0; - -webkit-border-radius: 0 0 6px 0; - border-radius: 0 0 6px 0; -} \ No newline at end of file diff --git a/docs/en/assets/images/icon.png b/docs/en/assets/images/icon.png deleted file mode 100644 index 9376d1b61..000000000 Binary files a/docs/en/assets/images/icon.png and /dev/null differ diff --git a/docs/en/assets/images/illustration.png b/docs/en/assets/images/illustration.png deleted file mode 100644 index 69f739c05..000000000 Binary files a/docs/en/assets/images/illustration.png and /dev/null differ diff --git a/docs/en/assets/images/loading.gif b/docs/en/assets/images/loading.gif deleted file mode 100644 index 1448c29d5..000000000 Binary files a/docs/en/assets/images/loading.gif and /dev/null differ diff --git a/docs/en/assets/images/logo.png b/docs/en/assets/images/logo.png deleted file mode 100644 index eadb517c7..000000000 Binary files a/docs/en/assets/images/logo.png and /dev/null differ diff --git a/docs/en/assets/js/crc.min.js b/docs/en/assets/js/crc.min.js deleted file mode 100644 index c7180330e..000000000 --- a/docs/en/assets/js/crc.min.js +++ /dev/null @@ -1,147 +0,0 @@ -function CRC16_CCITT(charach){ - var crc = 0xFFFF; - var polynomial = 0x1021; - var i=0, k=0, byte_val, bit, c15; - - for (let i = 0; i < charach.length; i++) { - byte_val = charach[i]; - - for (k=0; k < 8; k++) { - bit = ((byte_val >> (7 - k) & 1) == 1); - c15 = ((crc >> 15 & 1) == 1); - crc <<= 1; - if (c15 ^ bit) { - crc ^=polynomial; - } - } - } - crc &= 0xffff; - - return crc.toString(16); -} - -/** - * [js-crc]{@link https://github.com/emn178/js-crc} - * - * @namespace crc - * @version 0.2.0 - * @author Chen, Yi-Cyuan [emn178@gmail.com] - * @copyright Chen, Yi-Cyuan 2015-2017 - * @license MIT - */ -/*jslint bitwise: true */ -(function () { - 'use strict'; - - var root = typeof window === 'object' ? window : {}; - var NODE_JS = !root.JS_CRC_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node; - if (NODE_JS) { - root = global; - } - var COMMON_JS = !root.JS_CRC_NO_COMMON_JS && typeof module === 'object' && module.exports; - var AMD = typeof define === 'function' && define.amd; - var ARRAY_BUFFER = !root.JS_CRC_NO_ARRAY_BUFFER && typeof ArrayBuffer !== 'undefined'; - var HEX_CHARS = '0123456789abcdef'.split(''); - - var Modules = [ - { - name: 'crc32', - polynom: 0xEDB88320, - initValue: -1, - bytes: 4 - }, - { - name: 'crc16', - polynom: 0xA001, - initValue: 0, - bytes: 2 - }, - { - name: 'crc16ccit', - polynom: 0x8408, - initValue: 0, - bytes: 2 - } - ]; - - var i, j, k, b; - for (i = 0; i < Modules.length; ++i) { - var m = Modules[i]; - m.method = (function (m) { - return function (message) { - return crc(message, m); - }; - })(m); - m.table = []; - for (j = 0; j < 256; ++j) { - b = j; - for (k = 0; k < 8; ++k) { - b = b & 1 ? m.polynom ^ (b >>> 1) : b >>> 1; - } - m.table[j] = b >>> 0; - } - } - - var crc = function (message, module) { - var notString = typeof message !== 'string'; - if (notString && ARRAY_BUFFER && message instanceof ArrayBuffer) { - message = new Uint8Array(message); - } - - var crc = module.initValue, code, i, length = message.length, table = module.table; - if (notString) { - for (i = 0; i < length; ++i) { - crc = table[(crc ^ message[i]) & 0xFF] ^ (crc >>> 8); - } - } else { - for (i = 0; i < length; ++i) { - code = message.charCodeAt(i); - if (code < 0x80) { - crc = table[(crc ^ code) & 0xFF] ^ (crc >>> 8); - } else if (code < 0x800) { - crc = table[(crc ^ (0xc0 | (code >> 6))) & 0xFF] ^ (crc >>> 8); - crc = table[(crc ^ (0x80 | (code & 0x3f))) & 0xFF] ^ (crc >>> 8); - } else if (code < 0xd800 || code >= 0xe000) { - crc = table[(crc ^ (0xe0 | (code >> 12))) & 0xFF] ^ (crc >>> 8); - crc = table[(crc ^ (0x80 | ((code >> 6) & 0x3f))) & 0xFF] ^ (crc >>> 8); - crc = table[(crc ^ (0x80 | (code & 0x3f))) & 0xFF] ^ (crc >>> 8); - } else { - code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++i) & 0x3ff)); - crc = table[(crc ^ (0xf0 | (code >> 18))) & 0xFF] ^ (crc >>> 8); - crc = table[(crc ^ (0x80 | ((code >> 12) & 0x3f))) & 0xFF] ^ (crc >>> 8); - crc = table[(crc ^ (0x80 | ((code >> 6) & 0x3f))) & 0xFF] ^ (crc >>> 8); - crc = table[(crc ^ (0x80 | (code & 0x3f))) & 0xFF] ^ (crc >>> 8); - } - } - } - crc ^= module.initValue; - - var hex = ''; - if (module.bytes > 2) { - hex += HEX_CHARS[(crc >> 28) & 0x0F] + HEX_CHARS[(crc >> 24) & 0x0F] + - HEX_CHARS[(crc >> 20) & 0x0F] + HEX_CHARS[(crc >> 16) & 0x0F]; - } - hex += HEX_CHARS[(crc >> 12) & 0x0F] + HEX_CHARS[(crc >> 8) & 0x0F] + - HEX_CHARS[(crc >> 4) & 0x0F] + HEX_CHARS[crc & 0x0F]; - return hex; - }; - - var exports = {}; - for (i = 0;i < Modules.length;++i) { - var m = Modules[i]; - exports[m.name] = m.method; - } - if (COMMON_JS) { - module.exports = exports; - } else { - for (i = 0;i < Modules.length;++i) { - var m = Modules[i]; - root[m.name] = m.method; - } - if (AMD) { - define(function() { - return exports; - }); - } - } -})(); \ No newline at end of file diff --git a/docs/en/assets/js/dataset.js b/docs/en/assets/js/dataset.js deleted file mode 100644 index b216c27b6..000000000 --- a/docs/en/assets/js/dataset.js +++ /dev/null @@ -1,563 +0,0 @@ -/** - * Convert big-endian hex to little-endian hex and vice-versa - */ -function setEndianConvert(value) { - let result = ''; - if (value.length % 2) { - value = '0' + String(value); - } - for (let n = 0; n < value.length; n += 4) { - const str = value.substring(n, n + 4); - result += str.replace(/(..)(..)?/g, "$2$1"); - } - return result; -} - -/** - * Check if hex is little-endian. Example: 5216 = true; 1652 = false - */ -function isLittleEndian(value) { - if (value.length % 2) { - value = '0' + String(value); - } - let array = hexToDecimalArray(value.substring(0, 4)); - if (array[0] === array[1]) { - return isLittleEndian(value.substring(4)); - } - return array[0] > array[1]; -} - -/** - * Convert hex string to decimal array. Example: FF -> 255 - */ -function hexToDecimalArray(hex) { - if (!hex.match(/^[0-9a-fA-F]+$/)) { - throw new Error('is not a hex string.'); - } - if (hex.length % 2 !== 0) { - hex = '0' + hex; - } - const bytes = []; - for (let n = 0; n < hex.length; n += 2) { - const code = parseInt(hex.substring(n, n + 2), 16); - bytes.push(code); - } - return bytes; -} - -/** - * Convert ascii representation to hex string. Example: Ex -> 4578 - */ -function asciiToHex(str) { - const arr1 = []; - let n = 0, l = str.length; - for (; n < l; n++) { - const hex = Number(str.charCodeAt(n)).toString(16); - arr1.push(hex); - } - return arr1.join(''); -} - -/** - * Convert hex string to ascii representation. Example: 4578 -> Ex - */ -function hexToAscii(str1) { - const hex = str1.toString(); - let str = ''; - for (let n = 0; n < hex.length; n += 2) { - str += String.fromCharCode(parseInt(hex.substring(n, n + 2), 16)); - } - return str; -} - -/** - * Convert hex value to readable array. Example: 0x45,0x78 -> 45,78 - */ -function parseHexValueToArray(str) { - const list = str.replace(/,/g, " ").replace(/[^A-Fa-f0-9 ]/g, "").split(' '); - const result = []; - - for (let i = 0; i < list.length; i++) { - if (list[i].length === 0) - continue; - result.push(list[i].substring(1)); - } - return result; -} - -/** - * Convert hex value to readable string. Example: 0x45,0x78 -> 4578 - */ -function parseHexValueToString(str) { - const list = str.replace(/,/g, " ").replace(/[^A-Fa-f0-9 ]/g, "").split(' '); - let result = ""; - - for (let i = 0; i < list.length; i++) { - if (list[i].length === 0) - continue; - result += list[i].substring(1); - } - return result; -} - -/** - * Convert readable string to hex value. Example: 4578 or 45 78 -> 0x45,0x78 - */ -function parseStringValueToHex(str) { - const matches = str.matchAll(/[^\s]{1,2}/g); - let result = ""; - for (const match of matches) { - result += "0x" + match + ","; - } - return result.substring(0, result.length - 1); -} - -/** - * Clean hex value. Example: 45 78 -> 4548 - */ -function cleanHexValue(str) { - const matches = str.matchAll(/[^\s]{1,2}/g); - let result = ""; - for (const match of matches) { - result += match; - } - return result; -} - -/** - * Convert hex value to readable string array delimited by 16 bytes. Example: 4578... or 45 78... -> 45 78 ... /n - */ -function parseValue(byteStream) { - let resultValue = ""; - let bytes; - - if (!Array.isArray(byteStream)) { - bytes = byteStream.match(/[^\s]{1,2}/g); - } else { - bytes = byteStream; - } - - for (let index = 0; index < bytes.length; index++) { - if (((index % 16) === 0) && (index > 0)) { - resultValue += "\n"; - } - - let value = bytes[index].toString(16).toUpperCase(); - if (value.length < 2) { - value = '0' + value; - } - resultValue += value + " "; - } - return resultValue; -} - -function addToDataset(dataset, info) { - dataset.value += info; -} - -function generateHeader(dataset) { - addToDataset(dataset, "\n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " 000000000\n"); - - const rows = document.getElementById('dataParams').rows; - if (rows.length > 3) { - let previousRow = ""; - for (let i = 1; i < rows.length - 1; i++) { - if (rows[i].id && previousRow !== rows[i].id) { - previousRow = rows[i].id; - addToDataset(dataset, " "); - addToDataset(dataset, doCreateParam(previousRow, false)); - addToDataset(dataset, "\n"); - } - } - } - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); -} - -function generateFooter(dataset) { - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, " \n"); - addToDataset(dataset, "\n"); -} - -function generateBody(dataset, data) { - addToDataset(dataset, " \n"); - addToDataset(dataset, " "); - - if (document.getElementById('uncompressedBytes').value !== '0' && - document.getElementById('compressedBytes').value !== '0') { - addToDataset(dataset, data); - } - - addToDataset(dataset, "\n"); - addToDataset(dataset, " \n"); -} - -function doGenerate() { - doOriginal(); - const dataset = document.getElementById('dataset'); - dataset.value = ""; - - const data = document.getElementById('data'); - - generateHeader(dataset); - generateBody(dataset, data); - generateFooter(dataset); - - const byteStream = new Uint8Array(dataset.value.length); - document.getElementById("generatedData").style.display = "table"; - return byteStream; -} - -function getRandomInt(max) { - return Math.floor(Math.random() * max); -} - -function doClearParams() { - while (document.getElementById('dataParams').rows.length > 3) { - const rows = document.getElementById('dataParams').rows; - for (let i = 0; i < rows.length; i++) { - if (rows[i].id) { - document.getElementById('dataParams').deleteRow(i); - } - } - } -} - -function doClearParsedParams() { - document.getElementById("parsedCompressedParams").style.display = "none"; - - while (document.getElementById('parsedCompressedParams').rows.length > 3) { - const rows = document.getElementById('parsedCompressedParams').rows; - for (let i = 0; i < rows.length; i++) { - if (rows[i].id) { - document.getElementById('parsedCompressedParams').deleteRow(i); - } - } - } -} - -function doClearFields() { - doClearParams(); - - document.getElementById('data').value = ""; - document.getElementById('dataset').value = ""; - - document.getElementById('isCompressed').checked = false; - document.getElementById('content').value = '0'; - document.getElementById('uncompressedBytes').value = '0'; - document.getElementById('compressedBytes').value = '0'; - - document.getElementById("compressedData").style.display = "none"; - document.getElementById("generatedData").style.display = "none"; - - doClearParsedParams(); -} - -function deleteRow(row, shouldBreak) { - const rows = document.getElementById('dataParams').rows; - for (let i = 0; i < rows.length; i++) { - if (rows[i].id === row) { - document.getElementById('dataParams').deleteRow(i); - if (shouldBreak) { - return; - } - deleteRow(row, true); - } - } -} - -function doCreateParsedParam(row, showDialog) { - let result = ''; - let raw = cleanHexValue(document.getElementById('parsedParam_' + row).value); - if (raw) { - doCRC32Calc(row); - result = asciiToHex(document.getElementById('parsedVersion_' + row).value) + raw + - document.getElementById("crc32_" + row).value; - if (showDialog) { - window.prompt("Parameter", result); - } - } - return result; -} - -function doCRC32Calc(row) { - let raw = cleanHexValue(document.getElementById('parsedParam_' + row).value); - if (document.getElementById('parsedVersion_' + row).value && raw) { - document.getElementById('crc32_' + row).value = crc32(hexToDecimalArray( - asciiToHex(document.getElementById('parsedVersion_' + row).value) + raw)).toUpperCase(); - } -} - -function doCreateParam(row, showDialog) { - let result = ''; - let raw = cleanHexValue(document.getElementById('param_' + row).value); - if (raw) { - doCRC16Calc(row); - result = raw + asciiToHex(document.getElementById('version_' + row).value.substring(0, 2)) + - document.getElementById("crc16_" + row).value; - if (document.getElementById('littleEndian_' + row).checked && !isLittleEndian(raw)) { - result = setEndianConvert(result); - } - if (showDialog) { - window.prompt("Parameter", parseStringValueToHex(result)); - } - } - return parseStringValueToHex(result); -} - -function doCRC16Calc(row) { - let raw = cleanHexValue(document.getElementById('param_' + row).value); - if (document.getElementById('version_' + row).value && raw) { - let hexValue = CRC16_CCITT(hexToDecimalArray( - raw + asciiToHex(document.getElementById('version_' + row).value.substring(0, 2)))).toUpperCase(); - if (isLittleEndian(raw)) { - hexValue = CRC16_CCITT(hexToDecimalArray( - raw + setEndianConvert(asciiToHex(document.getElementById('version_' + row).value.substring(0, 2))))).toUpperCase() - } - document.getElementById('crc16_' + row).value = hexValue; - } -} - -function addNewParam() { - const tableRef = document.getElementById('dataParams'); - let newRow = tableRef.insertRow(tableRef.rows.length - 1); - const randomName = getRandomInt(10000); - let html = ""; - html += ""; - html += ""; - html += ""; - html += ""; - html += ""; - html += ""; - html += ""; - html += ""; - newRow.innerHTML = html; - newRow.id = randomName; - - newRow = tableRef.insertRow(tableRef.rows.length - 1); - html = ""; - newRow.innerHTML = html; - newRow.id = randomName; - return randomName; -} - -function addNewParsedParam() { - const tableRef = document.getElementById('parsedCompressedParams'); - let newRow = tableRef.insertRow(tableRef.rows.length - 1); - const randomName = getRandomInt(10000); - let html = ""; - html += ""; - html += ""; - html += ""; - html += ""; - newRow.innerHTML = html; - newRow.id = randomName; - - newRow = tableRef.insertRow(tableRef.rows.length - 1); - html = ""; - newRow.innerHTML = html; - newRow.id = randomName; - - return randomName; -} - -function handleUpload() { - const files = document.getElementById('uploadBinaryButton').files; - const reader = new FileReader(); - const parser = new DOMParser(); - doClearFields(); - - reader.onload = function () { - const xmlDoc = parser.parseFromString(new TextDecoder().decode(reader.result), "text/xml"); - const errorNode = xmlDoc.querySelector('parsererror'); - - if (errorNode) { - const byteStream = new Uint8Array(reader.result); - document.getElementById("compressedData").style.display = "table"; - document.getElementById('data').value = parseValue(byteStream); - } else { - if (xmlDoc.querySelector('MESSAGE')) { - const parameterAbstract = xmlDoc.querySelectorAll("PARAMETER_DATA"); - parameterAbstract.forEach(a => { - const randomName = addNewParam(); - document.getElementById('diagAddr_' + randomName).defaultValue = a.getAttribute("DIAGNOSTIC_ADDRESS"); - document.getElementById('flashOffset_' + randomName).defaultValue = a.getAttribute("START_ADDRESS"); - document.getElementById('datasetName_' + randomName).defaultValue = a.getAttribute("ZDC_NAME"); - document.getElementById('datasetVersion_' + randomName).defaultValue = a.getAttribute("ZDC_VERSION"); - document.getElementById('login_' + randomName).defaultValue = a.getAttribute("LOGIN"); - document.getElementById('datasetType_' + randomName).defaultValue = a.getAttribute("DSD_TYPE"); - document.getElementById('fileName_' + randomName).defaultValue = a.getAttribute("FILENAME"); - if (a.textContent.length !== 0) { - const raw = parseHexValueToString(a.textContent.trim()); - if (isLittleEndian(raw)) { - document.getElementById('littleEndian_' + randomName).checked = true; - document.getElementById("version_" + randomName).value = hexToAscii(setEndianConvert(raw.substring(raw.length - 8, raw.length - 4))); - document.getElementById("crc16_" + randomName).value = setEndianConvert(raw.substring(raw.length - 4, raw.length)); - } else { - document.getElementById("version_" + randomName).value = hexToAscii(raw.substring(raw.length - 8, raw.length - 4)); - document.getElementById("crc16_" + randomName).value = raw.substring(raw.length - 4, raw.length); - } - document.getElementById("param_" + randomName).value = parseValue(raw.substring(0, raw.length - 8)); - } - }); - - if (xmlDoc.querySelector('COMPRESSED_DATA') && - xmlDoc.querySelector('COMPRESSED_DATA').getAttribute("BYTES_UNCOMPRESSED") !== '0' && - xmlDoc.querySelector('COMPRESSED_DATA').getAttribute("BYTES_COMPRESSED") !== '0') { - document.getElementById("compressedData").style.display = "table"; - document.getElementById('isCompressed').checked = true; - document.getElementById('content').value = xmlDoc.querySelector('COMPRESSED_DATA').getAttribute("CONTENT"); - document.getElementById('uncompressedBytes').value = xmlDoc.querySelector('COMPRESSED_DATA').getAttribute("BYTES_UNCOMPRESSED"); - document.getElementById('compressedBytes').value = xmlDoc.querySelector('COMPRESSED_DATA').getAttribute("BYTES_COMPRESSED"); - document.getElementById('data').value = xmlDoc.querySelector('COMPRESSED_DATA').textContent.trim(); - } - } else if (xmlDoc.querySelector('SW-CNT')) { - document.getElementById('diagAddr').defaultValue = ''; - document.getElementById('flashOffset').defaultValue = xmlDoc.querySelector('START-ADR').textContent.trim(); - document.getElementById('datasetName').defaultValue = xmlDoc.querySelector('DATEN-NAME').textContent.trim(); - document.getElementById('datasetVersion').defaultValue = xmlDoc.querySelector('CNT-VERSION-INHALT').textContent.trim(); - document.getElementById('login').defaultValue = xmlDoc.querySelector('LOGIN').textContent.trim(); - document.getElementById('datasetType').defaultValue = ''; - document.getElementById('fileName').defaultValue = xmlDoc.querySelector('CNT-DATEI').textContent.trim(); - document.getElementById('data').value = xmlDoc.querySelector('DATEN').textContent.trim(); - } else { - window.alert("Uploaded file is not dataset file"); - } - } - } - reader.readAsArrayBuffer(files[0]); -} - -function doDecode() { - if (document.getElementById('uncompressedBytes').value !== '0' && - document.getElementById('compressedBytes').value !== '0' && - document.getElementById('isCompressed').checked) { - document.getElementById('data').value = atob(document.getElementById('data').value); - document.getElementById('isCompressed').checked = false; - document.getElementById('uncompressedBytes').value = document.getElementById('data').value.length; - } -} - -function doOriginal() { - if (document.getElementById('uncompressedBytes').value !== '0' && - document.getElementById('compressedBytes').value !== '0' && - !document.getElementById('isCompressed').checked) { - document.getElementById('data').value = btoa(unescape(encodeURIComponent(document.getElementById('data').value))); - document.getElementById('isCompressed').checked = true; - document.getElementById('compressedBytes').value = document.getElementById('data').value.length; - } -} - -function doParseUncompressed() { - if (document.getElementById('uncompressedBytes').value !== '0' && - document.getElementById('compressedBytes').value !== '0') { - doDecode(); - const regexp = /[\s\S]*?<\/SW-CNT>/g; - const matches = document.getElementById('data').value.matchAll(regexp); - for (const match of matches) { - const xmlDoc = new DOMParser().parseFromString(match[0], "text/xml"); - const randomName = addNewParsedParam(); - document.getElementById("parsedCompressedParams").style.display = "table"; - document.getElementById('parsedFlashOffset_' + randomName).defaultValue = xmlDoc.querySelector('START-ADR').textContent.trim(); - document.getElementById('parsedDatasetName_' + randomName).defaultValue = xmlDoc.querySelector('DATEN-NAME').textContent.trim(); - document.getElementById('parsedDatasetVersion_' + randomName).defaultValue = xmlDoc.querySelector('CNT-VERSION-INHALT').textContent.trim(); - document.getElementById('parsedFileName_' + randomName).defaultValue = xmlDoc.querySelector('CNT-DATEI').textContent.trim(); - - const data = xmlDoc.querySelector('DATEN').textContent.trim(); - document.getElementById('parsedParam_' + randomName).value = parseValue(data.substring(8, data.length - 8)); - document.getElementById("parsedVersion_" + randomName).value = hexToAscii(data.substring(0, 8)); - document.getElementById("crc32_" + randomName).value = data.substring(data.length - 8, data.length); - } - } -} - -function doSimplify() { - const rows = document.getElementById('parsedCompressedParams').rows; - const rows_params = document.getElementById('dataParams').rows; - for (let i = 0; i < rows.length; i++) { - if (rows[i].id) { - let randomName; - if (rows_params.length > 3) { - for (let j = 2; j < rows_params.length - 2; j++) { - if (rows_params[j].innerHTML.includes(document.getElementById('parsedDatasetName_' + rows[i].id).value)) { - randomName = rows_params[j].id; - break; - } - } - } - if (!randomName) { - randomName = addNewParam(); - } - document.getElementById('flashOffset_' + randomName).defaultValue = document.getElementById('parsedFlashOffset_' + rows[i].id).value; - document.getElementById('datasetName_' + randomName).defaultValue = document.getElementById('parsedDatasetName_' + rows[i].id).value; - document.getElementById("version_" + randomName).value = document.getElementById("parsedVersion_" + rows[i].id).value.substring(0, 2); - document.getElementById("param_" + randomName).value = document.getElementById('parsedParam_' + rows[i].id).value; - if (isLittleEndian(cleanHexValue(document.getElementById('parsedParam_' + rows[i].id).value))) { - document.getElementById('littleEndian_' + randomName).checked = true; - } - doCRC16Calc(randomName); - } - } - doClearParsedParams(); -} \ No newline at end of file diff --git a/docs/en/assets/js/fecGenerator.js b/docs/en/assets/js/fecGenerator.js deleted file mode 100644 index 543685bd9..000000000 --- a/docs/en/assets/js/fecGenerator.js +++ /dev/null @@ -1,152 +0,0 @@ -const TRAILER_HEX = "0000000000000000003B0FC769BFFF15ECD445B8196D2203D6D56BD8F22748B37D68F863DAC57E23C90A5FEEF0C06394C8D48A2EAA4F0FB658557400E66441DDC7D5AC3610AA4D45056C0C6E17C7E4B60C40E52FFA891938AF186ED20AE83A99EB10F3088479E6CBD2770C1563B5AE235B440BEF16EBE696576E108F2F9F897D963DEFBAD3ABDF2FFE"; - -let codes = []; -let selectedCount = 0; - -function $(selector) { - return document.querySelector(selector); -} - -function $all(selector) { - return document.querySelectorAll(selector); -} - -function validateLength(actual, expected, alertId, label) { - const el = document.getElementById(alertId); - if (!el) return; - - el.classList.remove('alert-success', 'alert-danger'); - - if (actual === 0) { - el.classList.add('alert-danger'); - el.innerHTML = `${label} input cannot be empty!`; - el.style.display = ''; - } else if (actual !== expected) { - el.classList.add('alert-danger'); - el.innerHTML = `${label} should be ${expected} characters long! ${actual}/${expected}`; - el.style.display = ''; - } else { - el.classList.add('alert-success'); - el.innerHTML = `${label} status: OK`; - el.style.display = ''; - } -} - -function stringToHex(str) { - return Array.from(str) - .map(ch => ch.charCodeAt(0).toString(16)) - .join(""); -} - -function hasCode(list, code) { - return list.some(item => item.code === code); -} - -function renderCodes() { - const codesContainer = document.getElementById("codes"); - const vinInput = document.getElementById("vin"); - const vcrnInput = document.getElementById("vcrn"); - const jumbotron = document.querySelector(".jumbotron"); - - if (!codesContainer || !vinInput || !vcrnInput) return; - - codesContainer.innerHTML = ""; - - const vin = vinInput.value; - const vcrn = vcrnInput.value; - - const timestampHex = Math.floor(Date.now() / 1000) - .toString(16) - .toUpperCase(); - - codes.forEach(item => { - if (!item.visible) return; - - const hexVin = stringToHex(vin).toUpperCase(); - const finalCode = `1102${item.code}03${vcrn.toUpperCase()}${hexVin}00${timestampHex}${TRAILER_HEX}`; - - const wrapper = document.createElement("div"); - wrapper.className = "form-group"; - - const label = document.createElement("label"); - label.className = "font-weight-bold"; - label.textContent = item.name; - - const textarea = document.createElement("textarea"); - textarea.className = "form-control h-100"; - textarea.readOnly = true; - textarea.value = finalCode; - - // Выделять текст при клике/фокусе - textarea.addEventListener("click", function () { - this.select(); - }); - textarea.addEventListener("focus", function () { - this.select(); - }); - - wrapper.appendChild(label); - wrapper.appendChild(textarea); - codesContainer.appendChild(wrapper); - }); - - if (jumbotron) { - jumbotron.style.display = selectedCount > 0 ? "none" : ""; - } -} - -const vinEl = document.getElementById("vin"); -if (vinEl) { - vinEl.addEventListener("keyup", function () { - const value = this.value; - codes.forEach(item => (item.vin = value)); - - validateLength(value.length, 17, "avin", "VIN"); - renderCodes(); - }); -} - -const vcrnEl = document.getElementById("vcrn"); -if (vcrnEl) { - vcrnEl.addEventListener("keyup", function () { - const value = this.value; - codes.forEach(item => (item.vcrn = value)); - - validateLength(value.length, 10, "avcrn", "VCRN"); - renderCodes(); - }); -} - -$all(".checkbox input").forEach(input => { - input.addEventListener("change", function () { - const codeValue = this.value; - const name = this.name; - - if (this.checked) { - if (hasCode(codes, codeValue)) { - codes.forEach(item => { - if (item.code === codeValue) item.visible = true; - }); - } else { - const vinVal = vinEl ? vinEl.value : ""; - const vcrnVal = vcrnEl ? vcrnEl.value : ""; - - codes.push({ - code: codeValue, - vcrn: vcrnVal, - vin: vinVal, - visible: true, - name: name - }); - } - selectedCount++; - } else { - codes.forEach(item => { - if (item.code === codeValue) item.visible = false; - }); - selectedCount--; - } - - renderCodes(); - }); -}); diff --git a/docs/en/assets/js/longCoding.js b/docs/en/assets/js/longCoding.js deleted file mode 100644 index f462c1810..000000000 --- a/docs/en/assets/js/longCoding.js +++ /dev/null @@ -1,616 +0,0 @@ -"use strict"; - -let curSelectedByte = -1; -let defaultByteSize = 30; -let labelsFromFile = null; - -// ========== Additional methods ========== - -function cleanHexValue(str) { - if (!str) return ""; - return str.toUpperCase().replace(/[^0-9A-F]/g, ""); -} - -function trim(str) { - return (str || "").replace(/^\s*|\s*$/g, ""); -} - -function calculateByteIdFromNumber(num) { - return num < 10 ? "0" + num : String(num); -} - -function from10toradix(value, radix) { - const ConvArray = [ - "0","1","2","3","4","5","6","7","8","9", - "A","B","C","D","E","F" - ]; - - let intnum = parseInt(value, 10); - if (Number.isNaN(intnum)) { - return "NaN"; - } - - if (intnum === 0) { - return "0"; - } - - let retval = ""; - let i = 0; - - while (intnum > 0) { - i++; - const digit = intnum % radix; - retval = ConvArray[digit] + retval; - intnum = Math.floor(intnum / radix); - - if (i > 100) { - return "NaN"; // - } - } - - return retval || "0"; -} - -function hexToBin(value) { - const dezVal = parseInt(value, 16); - const bitValueBin = from10toradix(dezVal, 2); - return addLeadingZeros(bitValueBin); -} - -function addLeadingZeros(binVal) { - let retString = binVal || ""; - const anz = 8 - retString.length; - for (let i = 0; i < anz; i++) { - retString = "0" + retString; - } - return retString; -} - -function activateFields() { - const loading = document.getElementById("loading"); - const bitDescription = document.getElementById("bitDescription"); - if (loading) loading.style.display = "none"; - if (bitDescription) bitDescription.style.display = "block"; -} - -// ========== Initialisation ========== - -function init() { - const urlParams = new URLSearchParams(window.location.search); - - createByteDescription(defaultByteSize); - createBitDescription(); - wireBitEvents(); - wireByteEvents(); - - const codeParam = urlParams.get("code"); - const labelParam = urlParams.get("label"); - - if (labelParam != null) { - const langSel = document.getElementById("langBits"); - if (langSel) langSel.value = labelParam; - } - if (codeParam != null) { - const orig = document.getElementById("origCode"); - if (orig) { - orig.value = codeParam; - checkOrig(); - } - } -} - -// ========== Main functions ========== - -function checkOrig() { - const filter = /^[A-F0-9]+$/; - const origEl = document.getElementById("origCode"); - if (!origEl) return false; - - let orig = origEl.value; - orig = cleanHexValue(orig); - origEl.value = orig; - - const bitDescription = document.getElementById("bitDescription"); - if (bitDescription) bitDescription.style.display = "none"; - - if (orig.length % 2 === 0 && orig.length > 0) { - if (filter.test(orig)) { - const loading = document.getElementById("loading"); - if (loading) loading.style.display = "inline-block"; - - fillBytes(); - - const langBits = document.getElementById("langBits"); - if (langBits && langBits.value !== "none") { - fetch("../../labels/" + langBits.value + "_labels.txt") - .then(response => response.text()) - .then(text => { - labelsFromFile = text.split("\n"); - }) - .then(() => { - selByte(0); - }) - .catch(() => { - labelsFromFile = null; - selByte(0); - }); - } else { - selByte(curSelectedByte >= 0 ? curSelectedByte : 0); - } - return true; - } else { - alert( - "Кодировка содержит неверные символы. Поддерживаются только латинские буквы A-F и цифры 0-9. " + - "Coding has wrong symbols. Latin characters A-F and digits 0-9 are allowed." - ); - clearFields(); - } - } else { - if (orig.length > 0) { - alert( - "Неверное значение. Кодировка должна содержать чётное количество символов. " + - "Incorrect value. Coding should have even number of symbols." - ); - } - clearFields(); - } - return false; -} - -function fillBytes() { - const origEl = document.getElementById("origCode"); - const calcEl = document.getElementById("calcCode"); - if (!origEl || !calcEl) return; - - const orig = origEl.value; - const newNum = orig.toString().match(/.{1,2}/g) || []; - - if (newNum.length > defaultByteSize) { - defaultByteSize = newNum.length; - createByteDescription(defaultByteSize); - wireByteEvents(); - } - - for (let i = 0; i < newNum.length; i++) { - const el = document.getElementById("byte" + calculateByteIdFromNumber(i)); - if (el) el.value = newNum[i]; - } - - calcEl.value = orig; - curSelectedByte = 0; -} - -function fillDezAndBits(bin) { - for (let i = 0; i < 8; i++) { - const bit = document.getElementById("bit" + i); - if (bit) bit.value = bin.substr(7 - i, 1); - } -} - -function comparingOfBits(bin1, bin2) { - if (bin1 === bin2) return true; - - const bin1a = bin1.split("").map(Number); - const bin2a = bin2.split("").map(Number); - - let equal = false; - for (let i = 0; i < bin2a.length; i++) { - if (bin2a[i] === 1) { - if (bin2a[i] === bin1a[i]) { - equal = true; - } else { - return false; - } - } - } - return equal; -} - -function calculateBitValue(binOld, binNew) { - if (binNew === binOld) return binNew; - - const binNewAr = binNew.split("").map(Number); - const binOldAr = binOld.split("").map(Number); - - for (let i = 0; i < binNewAr.length; i++) { - if (binNewAr[i] !== binOldAr[i]) { - const idx = 7 - i; - const cb = document.getElementById("setModifiedBits" + idx); - if (cb) cb.checked = binNewAr[i] === 1; - chgBit(idx); - } - } -} - -function fillBitCheckBoxes(bin, reduced) { - for (let i = 0; i < 8; i++) { - const cb = document.getElementById("setModifiedBits" + (7 - i)); - if (cb) cb.checked = bin.substr(i, 1) === "1"; - } - - const bitVariants = document.getElementById("bitVariants"); - if (bitVariants) bitVariants.innerHTML = ""; - - for (let j = 0; j < 8; j++) { - const label = document.getElementById("setModifiedBitsLabel" + j); - if (label) label.innerHTML = " Bit " + j; - } - - if (reduced && reduced.length) { - reduced.forEach(label => { - if (!label) return; - - if (!isNaN(label.bit)) { - const lab = document.getElementById("setModifiedBitsLabel" + label.bit); - if (lab) lab.innerHTML = " " + label.description; - } else { - if (!bitVariants) return; - - const wrapper = document.createElement("div"); - const text = document.createElement("span"); - text.textContent = label.description + " (bits " + label.bit + "): "; - - const selectElement = document.createElement("select"); - selectElement.id = "select" + label.bit; - - const variants = (label.variants || "").split(";"); - variants.forEach(vStr => { - const cleaned = trim(vStr); - if (!cleaned) return; - - const parts = cleaned.split("-"); - const hex = trim(parts[0] || ""); - const desc = trim(parts[1] || cleaned); - - const currentOption = comparingOfBits(bin, hexToBin(hex)); - - const opt = document.createElement("option"); - opt.value = hex; - opt.textContent = desc; - if (currentOption) opt.selected = true; - selectElement.appendChild(opt); - }); - - let previous = selectElement.value; - - selectElement.addEventListener("focus", e => { - previous = e.target.value; - }); - - selectElement.addEventListener("change", e => { - const newVal = e.target.value; - if (previous !== newVal) { - calculateBitValue(hexToBin(previous), hexToBin(newVal)); - previous = newVal; - } - }); - - wrapper.appendChild(text); - wrapper.appendChild(selectElement); - wrapper.appendChild(document.createElement("br")); - wrapper.appendChild(document.createElement("br")); - bitVariants.appendChild(wrapper); - } - }); - } - - activateFields(); -} - -function selByte(noByte) { - const byteLabel = document.getElementById("Byte"); - if (byteLabel) byteLabel.innerHTML = "(Byte " + noByte + ")"; - - curSelectedByte = noByte; - - const calcCode = document.getElementById("calcCode"); - if (!calcCode) return; - - const startPos = noByte * 2; - const bitValueHex = calcCode.value.substr(startPos, 2); - - if (bitValueHex === "") { - clearBits(); - for (let i = 0; i < 8; i++) { - const bit = document.getElementById("bit" + i); - if (bit) bit.value = "0"; - } - toggleSelectedByteColor(noByte); - return; - } - - const fullBitString = hexToBin(bitValueHex); - - let reduced = null; - if (labelsFromFile != null) { - reduced = labelsFromFile.reduce((filtered, option) => { - const opt = option.split(","); - if (opt[0] == noByte) { - const labelValue = { - byte: opt[0], - bit: opt[1], - description: opt[2], - variants: opt[3] - }; - filtered.push(labelValue); - } - return filtered; - }, []); - } - - fillDezAndBits(fullBitString); - fillBitCheckBoxes(fullBitString, reduced); - toggleSelectedByteColor(noByte); -} - -function chgBit(no) { - const cb = document.getElementById("setModifiedBits" + no); - const bit = document.getElementById("bit" + no); - if (!cb || !bit) return; - - bit.value = cb.checked ? "1" : "0"; - setBits(); -} - -function setBits() { - let tmpBitStr = ""; - for (let i = 7; i >= 0; i--) { - const bit = document.getElementById("bit" + i); - tmpBitStr += bit ? bit.value : "0"; - } - - if (!tmpBitStr) return false; - - const tmpVal = parseInt(tmpBitStr, 2); - let hexValue = from10toradix(tmpVal, 16); - - if (hexValue.length === 1) hexValue = "0" + hexValue; - else if (hexValue.length < 1) hexValue = "00"; - - resetByteHex(hexValue); - selByte(curSelectedByte); - return true; -} - -function resetCalcCode() { - let tmpVal = ""; - for (let i = 0; i < defaultByteSize; i++) { - const el = document.getElementById("byte" + calculateByteIdFromNumber(i)); - if (el) tmpVal += el.value; - } - const calcCode = document.getElementById("calcCode"); - if (calcCode) calcCode.value = trim(tmpVal); -} - -function resetByteHex(hexVal) { - const el = document.getElementById("byte" + calculateByteIdFromNumber(curSelectedByte)); - if (el) el.value = hexVal; - resetCalcCode(); -} - -function clearBits() { - for (let i = 0; i < 8; i++) { - const bit = document.getElementById("bit" + i); - if (bit) bit.value = ""; - } - for (let j = 0; j < 8; j++) { - const cb = document.getElementById("setModifiedBits" + j); - if (cb) cb.checked = false; - const label = document.getElementById("setModifiedBitsLabel" + j); - if (label) label.innerHTML = " Bit " + j; - } - const bitVariants = document.getElementById("bitVariants"); - if (bitVariants) bitVariants.innerHTML = ""; -} - -function clearFields() { - curSelectedByte = -1; - const byteLabel = document.getElementById("Byte"); - if (byteLabel) byteLabel.innerHTML = ""; - - for (let i = 0; i < defaultByteSize; i++) { - const el = document.getElementById("byte" + calculateByteIdFromNumber(i)); - if (el) el.value = ""; - } - - clearBits(); - - const calcCode = document.getElementById("calcCode"); - if (calcCode) calcCode.value = ""; - - defaultByteSize = 30; - createByteDescription(defaultByteSize); - wireByteEvents(); -} - -function clearAll() { - labelsFromFile = null; - clearFields(); - - const orig = document.getElementById("origCode"); - if (orig) orig.value = ""; - - const bitDescription = document.getElementById("bitDescription"); - if (bitDescription) bitDescription.style.display = "none"; - - const langBits = document.getElementById("langBits"); - if (langBits) langBits.value = "none"; - - const loading = document.getElementById("loading"); - if (loading) loading.style.display = "none"; -} - -function clearLabels() { - labelsFromFile = null; - clearBits(); - if (curSelectedByte >= 0) { - selByte(curSelectedByte); - } -} - -function toggleSelectedByteColor(noByte) { - for (let i = 0; i < defaultByteSize; i++) { - const el = document.getElementById("byte" + calculateByteIdFromNumber(i)); - if (!el) continue; - el.className = i === noByte ? "byteSelected" : "byteNotSelected"; - } -} - -function selectAll(ctrl) { - if (ctrl && typeof ctrl.select === "function") { - ctrl.select(); - } -} - -function isBinary(event) { - const charCode = event.which ? event.which : event.keyCode; - - const controlKeys = [8, 9, 13, 35, 36, 37, 39, 46]; - if (controlKeys.includes(charCode)) { - if (charCode === 13) setBits(); - return true; - } - - if (charCode === 48 || charCode === 49) { - return true; - } - - event.preventDefault(); - return false; -} - -// ========== Generation of html tables ========== - -function createByteDescription(countOfBytes) { - const container = document.getElementById("bytesDescription"); - if (!container) return; - - const bytesPerLine = 30; - const colsFirstLine = Math.min(countOfBytes, bytesPerLine); - const lines = Math.ceil(countOfBytes / bytesPerLine); - - let html = "
Байты / Bytes
" + lcI18n.t("bytesHeader") + "
Индивидуальная настройка давления в шинах / Tire set Individual" + tgI18n.t("tireSetIndividual") + "Настройка давления в шинах / Tire set: #" + index + "" + tgI18n.t("tireSetHeader", index) + "
Имя" + tgI18n.t("fieldName") + "
Тип загрузки машины / Load situationПередние колеса / Front wheelsЗадние колеса / Rear wheels" + tgI18n.t("loadSituation") + "" + tgI18n.t("frontWheels") + "" + tgI18n.t("rearWheels") + "
Полная загрузка / Full load" + tgI18n.t("fullLoad") + "" + generatePD("t" + index + "pff") + "" + generatePD("t" + index + "prf") + "
Стандартная загрузка / Standard load" + tgI18n.t("standardLoad") + "" + generatePD("t" + index + "pfp") + "" + generatePD("t" + index + "prp") + "
Комфортная загрузка / Comfort load" + tgI18n.t("comfortLoad") + "" + generatePD("t" + index + "pfc") + "" + generatePD("t" + index + "prc") + "
" + (tableRef.rows.length - 2) / 2 + ""; - html += ""; - html += ""; - html += "Bytes in little-endian format:
"; - html += "Version of hex:
"; - html += "CRC16 sum:
"; - html += ""; - html += "
" + (tableRef.rows.length - 2) / 2 + ""; - html += ""; - html += ""; - html += "Version of hex:
"; - html += "CRC32 sum:
"; - html += ""; - html += "
"; - html += ""; - - for (let line = 0; line < lines; line++) { - const start = line * bytesPerLine; - const end = Math.min(start + bytesPerLine, countOfBytes); - - html += ""; - for (let i = start; i < end; i++) { - html += ""; - } - html += ""; - for (let j = start; j < end; j++) { - const byteName = calculateByteIdFromNumber(j); - html += ""; - } - html += ""; - } - - html += "
Байты / Bytes
" + calculateByteIdFromNumber(i) + "
"; - html += ""; - html += "
"; - container.innerHTML = html; -} - -function createBitDescription() { - const container = document.getElementById("bitDescription"); - if (!container) return; - - let html = ""; - html += ""; - html += ""; - html += ""; - html += ""; - html += ""; - html += ""; - html += ""; - html += "
"; - html += "Bits
"; - html += "
"; - html += ""; - html += ""; - - for (let i = 7; i >= 0; i--) { - html += ""; - } - - html += ""; - - for (let j = 7; j >= 0; j--) { - html += ""; - } - - html += "
" + i + "
"; - - for (let k = 0; k < 8; k++) { - html += ""; - html += "
"; - } - - html += "
"; - - container.innerHTML = html; -} - -// ========== Events handling ========== - -function wireBitEvents() { - for (let i = 0; i < 8; i++) { - const bit = document.getElementById("bit" + i); - if (!bit) continue; - - bit.onkeydown = e => isBinary(e); - bit.onclick = () => selectAll(bit); - bit.onblur = () => setBits(); - } - - for (let j = 0; j < 8; j++) { - const cb = document.getElementById("setModifiedBits" + j); - if (!cb) continue; - cb.onclick = () => chgBit(j); - } -} - -function wireByteEvents() { - const container = document.getElementById("bytesDescription"); - if (!container) return; - - container.addEventListener("click", e => { - const target = e.target; - if (!target || target.tagName !== "INPUT") return; - if (!target.id || !target.id.startsWith("byte")) return; - - const index = parseInt(target.getAttribute("data-index"), 10); - if (Number.isNaN(index)) return; - - selByte(index); - selectAll(target); - }); - - container.addEventListener("change", e => { - const target = e.target; - if (!target || target.tagName !== "INPUT") return; - if (!target.id || !target.id.startsWith("byte")) return; - - const index = parseInt(target.getAttribute("data-index"), 10); - if (Number.isNaN(index)) return; - - setByte(target, index); - }); -} - -function setByte(ctrl, byteIndex) { - const regexp = /^[0-9a-fA-F]*$/; - if (!regexp.test(ctrl.value)) { - alert(ctrl.value + " - неверный формат введенного значения! Incorrect value!"); - ctrl.value = "00"; - } else { - ctrl.value = ctrl.value.toUpperCase(); - } - resetCalcCode(); - selByte(byteIndex); -} \ No newline at end of file diff --git a/docs/en/assets/js/tiresGenerator.js b/docs/en/assets/js/tiresGenerator.js deleted file mode 100644 index 588e20e50..000000000 --- a/docs/en/assets/js/tiresGenerator.js +++ /dev/null @@ -1,524 +0,0 @@ -"use strict"; - -function addToDataset(dataset, info) { - dataset.value += info; -} - -function generateHeader(dataset, docType, ecuName, ecuOffset) { - if (docType === "vcp") { - addToDataset(dataset, "\n"); - addToDataset(dataset, "\n"); - addToDataset(dataset, "\n"); - addToDataset(dataset, "\n"); - addToDataset(dataset, "\n"); - addToDataset(dataset, "20103\n"); - addToDataset(dataset, "24\n"); - addToDataset(dataset, "\n"); - addToDataset(dataset, "\n"); - addToDataset(dataset, "\n"); - addToDataset(dataset, "\n"); - addToDataset(dataset, "" + ecuName + "\n"); - addToDataset(dataset, "DFN_HEX\n"); - addToDataset(dataset, "" + ecuOffset + "\n"); - addToDataset(dataset, "0x0800\n"); - addToDataset(dataset, ""); - } else if (docType === "odis") { - addToDataset(dataset, "\n"); - addToDataset(dataset, "\n"); - addToDataset(dataset, "\n"); - addToDataset(dataset, "\n"); - addToDataset( - dataset, - "" - ); - } else { - addToDataset(dataset, "\n\nERROR! Unknown docType (" + docType + ")\n\n"); - } -} - -function generateFooter(dataset, docType) { - if (docType === "vcp") { - addToDataset(dataset, "\n"); - addToDataset(dataset, "\n"); - addToDataset(dataset, "\n"); - addToDataset(dataset, "\n"); - } else if (docType === "odis") { - addToDataset(dataset, "\n"); - addToDataset(dataset, "\n"); - addToDataset(dataset, "\n"); - addToDataset(dataset, "\n"); - addToDataset(dataset, "\n"); - } else { - addToDataset(dataset, "\n\nERROR! Unknown docType\n\n"); - } -} - -function appendType(byteStream, ecuType) { - const type = parseInt(ecuType, 16); - byteStream.push(Number.isNaN(type) ? 0x00 : type); -} - -function appendSetName(byteStream, setName) { - let name = String(setName || ""); - let length = name.length; - - if (length > 60) { - name = name.substring(0, 60); - length = name.length; - } - - byteStream.push(length); - - for (let i = 0; i < length; i++) { - byteStream.push(name.charCodeAt(i)); - } - - for (let i = length; i < 61; i++) { - byteStream.push(0x00); - } -} - -function calcPressureValue(pressureStr) { - let trimmed = (pressureStr || "").trim(); - if (trimmed.length === 0) return 0xFF; - - trimmed = trimmed.replace(",", "."); - const val = parseFloat(trimmed); - - if (Number.isNaN(val)) return 0xFF; - - let scaled = Math.round(val * 10); - if (scaled < 0) scaled = 0; - if (scaled > 0xFF) scaled = 0xFF; - return scaled; -} - -function appendSetPressure(byteStream, ecuName, pressureValues) { - if (ecuName === "3AA907273H") { - byteStream.push(calcPressureValue(pressureValues.frontPartial)); - byteStream.push(calcPressureValue(pressureValues.frontFull)); - byteStream.push(calcPressureValue(pressureValues.frontComfort)); - byteStream.push(calcPressureValue(pressureValues.rearPartial)); - byteStream.push(calcPressureValue(pressureValues.rearFull)); - byteStream.push(calcPressureValue(pressureValues.rearComfort)); - } else { - byteStream.push(calcPressureValue(pressureValues.frontFull)); - byteStream.push(calcPressureValue(pressureValues.frontPartial)); - byteStream.push(calcPressureValue(pressureValues.frontComfort)); - byteStream.push(calcPressureValue(pressureValues.rearFull)); - byteStream.push(calcPressureValue(pressureValues.rearPartial)); - byteStream.push(calcPressureValue(pressureValues.rearComfort)); - } -} - -function appendTireSet(byteStream, ecuName, tireSet) { - for (let i = 0; i < 61; i++) { - byteStream.push(0x00); - } - - appendSetName(byteStream, tireSet.name); - appendSetPressure(byteStream, ecuName, tireSet); - - for (let i = 0; i < 6; i++) { - byteStream.push(0x00); - } -} - -function appendVersion(byteStream, ecuVersion) { - const v = String(ecuVersion || ""); - const version1 = v.charCodeAt(0) || 0x00; - const version2 = v.charCodeAt(1) || 0x00; - - byteStream.push(version1); - byteStream.push(version2); -} - -function appendCRC(byteStream) { - const lookupTable = []; - - for (let i = 0; i < 256; i++) { - let c = i; - - for (let k = 0; k < 8; k++) { - c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1); - } - - lookupTable[i] = c; - } - - let crc = 0xFFFFFFFF; - - for (let i = 0; i < byteStream.length; i++) { - crc = (crc >>> 8) ^ lookupTable[(crc ^ byteStream[i]) & 0xFF]; - } - - crc = crc ^ 0xFFFFFFFF; - - byteStream.push((crc >>> 24) & 0xFF); - byteStream.push((crc >>> 16) & 0xFF); - byteStream.push((crc >>> 8) & 0xFF); - byteStream.push(crc & 0xFF); -} - -function generateDataset(ecuName, ecuType, ecuVersion, tireSets) { - const byteStream = []; - - appendType(byteStream, ecuType); - byteStream.push(0x00); - - for (let i = 0; i < 11; i++) { - appendTireSet(byteStream, ecuName, tireSets[i]); - } - - for (let i = 0; i < 555; i++) { - byteStream.push(0xFF); - } - - appendVersion(byteStream, ecuVersion); - appendCRC(byteStream); - - return byteStream; -} - -function generateBody(dataset, ecuName, ecuType, ecuVersion, tireSets) { - const byteStream = generateDataset(ecuName, ecuType, ecuVersion, tireSets); - - for (let i = 0; i < byteStream.length; i++) { - let str = byteStream[i].toString(16).toUpperCase(); - - if (str.length < 2) { - str = "0" + str; - } - - if (i > 0) { - addToDataset(dataset, ",0x" + str); - } else { - addToDataset(dataset, "0x" + str); - } - } -} - -function doGenerate() { - const numTireSetsRaw = document.getElementById("numTireSets").value; - let numTireSets = parseInt(numTireSetsRaw, 10); - if (Number.isNaN(numTireSets)) numTireSets = 0; - - if (numTireSets < 1) { - window.alert("Количество комплектов шин должно быть больше 0.\nNumber of tire sets should be greater than 0."); - return; - } - - const dataset = document.getElementById("dataset"); - dataset.value = ""; - - const docType = document.getElementById("docType").value; - const ecuObj = document.getElementById("ecuModel"); - const ecuOptions = ecuObj.options[ecuObj.selectedIndex]; - const ecuName = ecuOptions.text; - const ecuOffset = ecuOptions.getAttribute("ecuOffset"); - const ecuVersion = ecuOptions.getAttribute("ecuVersion"); - const ecuType = ecuOptions.getAttribute("ecuType"); - - const tireSets = []; - - for (let index = 1; index <= 11; index++) { - const table = document.getElementById("t" + index); - const isHidden = table.hasAttribute("hidden"); - - if (isHidden) { - tireSets.push({ - name: "", - frontFull: "", - rearFull: "", - frontPartial: "", - rearPartial: "", - frontComfort: "", - rearComfort: "" - }); - } else { - const nameValue = document.getElementById("t" + index + "name").value; - - if (!/^(?=.*[a-zA-Z0-9])[a-zA-Z0-9 /+]+$/.test(nameValue)) { - window.alert( - "Имя конфигурации #" + index + " может содержать только латинские буквы, цифры, пробел, / и +.\n" + - "Configuration name should have only latin letters, numbers, space, / and +." - ); - return; - } - - const comfortHidden = document.getElementById("trcomfort" + index).hasAttribute("hidden"); - - const set = { - name: nameValue, - frontFull: document.getElementById("t" + index + "pff").value, - rearFull: document.getElementById("t" + index + "prf").value, - frontPartial: document.getElementById("t" + index + "pfp").value, - rearPartial: document.getElementById("t" + index + "prp").value, - frontComfort: comfortHidden ? "" : document.getElementById("t" + index + "pfc").value, - rearComfort: comfortHidden ? "" : document.getElementById("t" + index + "prc").value - }; - - tireSets.push(set); - } - } - - if (docType === "binary") { - const byteStream = generateDataset(ecuName, ecuType, ecuVersion, tireSets); - - for (let i = 0; i < byteStream.length; i++) { - if ((i % 16) === 0) { - if (i > 0) addToDataset(dataset, "\n"); - - let offset = i.toString(16).toUpperCase(); - while (offset.length < 4) offset = "0" + offset; - addToDataset(dataset, offset + " "); - } - - let value = byteStream[i].toString(16).toUpperCase(); - if (value.length < 2) value = "0" + value; - - addToDataset(dataset, value + " "); - } - - return new Uint8Array(byteStream); - } else { - generateHeader(dataset, docType, ecuName, ecuOffset); - generateBody(dataset, ecuName, ecuType, ecuVersion, tireSets); - generateFooter(dataset, docType); - - const text = dataset.value; - const byteStreamXml = new Uint8Array(text.length); - - for (let i = 0; i < text.length; i++) { - byteStreamXml[i] = text.charCodeAt(i); - } - - return byteStreamXml; - } -} - -function doGenerateDl() { - const content = doGenerate(); - if (!content) return; - - const blob = new Blob([content], {type: "application/octet-stream"}); - const element = window.document.createElement("a"); - - if ("download" in element) { - const docType = document.getElementById("docType").value; - const ecuObj = document.getElementById("ecuModel"); - const ecuOptions = ecuObj.options[ecuObj.selectedIndex]; - const ecuName = ecuOptions.text; - const ext = (docType === "binary") ? ".bin" : ".xml"; - - element.href = window.URL.createObjectURL(blob); - element.download = ecuName + ext; - document.body.appendChild(element); - element.click(); - document.body.removeChild(element); - } else { - const fileReader = new FileReader(); - fileReader.onload = function () { - location.href = this.result; - }; - fileReader.readAsDataURL(blob); - } -} - -function generatePD(id) { - let html = ""; - - html += ""; - - return html; -} - -function updateTireSets() { - const numTireSetsRaw = document.getElementById("numTireSets").value; - let numTireSets = parseInt(numTireSetsRaw, 10); - if (Number.isNaN(numTireSets)) numTireSets = 0; - - const enableIndividual = document.getElementById("enableIndividual").checked; - const enableComfort = document.getElementById("enableComfort").checked; - - for (let index = 1; index <= 11; index++) { - const table = document.getElementById("t" + index); - const br1 = document.getElementById("br1" + index); - const trcomfort = document.getElementById("trcomfort" + index); - - const shouldShowThisSet = - ((index === 11) && enableIndividual) || - (index <= numTireSets); - - if (shouldShowThisSet) { - table.removeAttribute("hidden"); - br1.removeAttribute("hidden"); - } else { - table.setAttribute("hidden", ""); - br1.setAttribute("hidden", ""); - } - - if (enableComfort) { - trcomfort.removeAttribute("hidden"); - } else { - trcomfort.setAttribute("hidden", ""); - } - } -} - -function createTireSets() { - let html = ""; - - for (let index = 1; index <= 11; index++) { - html += " "; - html += " "; - - if (index === 11) { - html += " "; - } else { - html += " "; - } - - html += " "; - html += " "; - html += " "; - html += " "; - html += " "; - html += " "; - html += " "; - html += " "; - html += " "; - html += " "; - html += " "; - html += " "; - html += " "; - html += " "; - html += " "; - html += " "; - html += " "; - html += " "; - html += " "; - html += " "; - html += " "; - html += " "; - html += " "; - html += " "; - html += " "; - html += "
Индивидуальная настройка давления в шинах / Tire set IndividualНастройка давления в шинах / Tire set: #" + index + "
Имя
Тип загрузки машины / Load situationПередние колеса / Front wheelsЗадние колеса / Rear wheels
Полная загрузка / Full load" + generatePD("t" + index + "pff") + "" + generatePD("t" + index + "prf") + "
Стандартная загрузка / Standard load" + generatePD("t" + index + "pfp") + "" + generatePD("t" + index + "prp") + "
Комфортная загрузка / Comfort load" + generatePD("t" + index + "pfc") + "" + generatePD("t" + index + "prc") + "
"; - html += "
"; - } - - document.getElementById("tireSetList").innerHTML = html; - updateTireSets(); -} - -let configVersion = "1.0"; - -const configFields = [ - "docType", "ecuModel", "numTireSets", "@enableIndividual", "@enableComfort", - "t1name", "t1pff", "t1prf", "t1pfp", "t1prp", "t1pfc", "t1prc", - "t2name", "t2pff", "t2prf", "t2pfp", "t2prp", "t2pfc", "t2prc", - "t3name", "t3pff", "t3prf", "t3pfp", "t3prp", "t3pfc", "t3prc", - "t4name", "t4pff", "t4prf", "t4pfp", "t4prp", "t4pfc", "t4prc", - "t5name", "t5pff", "t5prf", "t5pfp", "t5prp", "t5pfc", "t5prc", - "t6name", "t6pff", "t6prf", "t6pfp", "t6prp", "t6pfc", "t6prc", - "t7name", "t7pff", "t7prf", "t7pfp", "t7prp", "t7pfc", "t7prc", - "t8name", "t8pff", "t8prf", "t8pfp", "t8prp", "t8pfc", "t8prc", - "t9name", "t9pff", "t9prf", "t9pfp", "t9prp", "t9pfc", "t9prc", - "t10name", "t10pff", "t10prf", "t10pfp", "t10prp", "t10pfc", "t10prc", - "t11name", "t11pff", "t11prf", "t11pfp", "t11prp", "t11pfc", "t11prc" -]; - -function loadConfig() { - const result = window.prompt( - "Восстановление конфигурации. Load configuration\n\n" + - "Пожалуйста введите код, созданный ранее в данном приложении.\n" + - "Please enter configuration code, created earlier with this tool", - "" - ); - - if (result === null) return; - - let code; - try { - code = window.atob(result); - } catch (e) { - window.alert("Введён некорректный код конфигурации (не base64).\nInvalid configuration code."); - return; - } - - const keyValue = code.split("&"); - - for (let i = 0; i < keyValue.length; i++) { - const data = keyValue[i].split("="); - - if (data.length !== 2) continue; - - if (data[0] === "configVersion") { - if (data[1] !== configVersion) { - window.alert( - "Введенный код конфигурации не может может быть восстановлен\n" + - "Configuration was created using an incompatible version of this tool and can not be restored\n\n" + - "Created version: " + data[1] + "\nCurrent version: " + configVersion - ); - return; - } - } else if (data[0].charAt(0) === "@") { - const idCheck = data[0].substr(1); - const elCheck = document.getElementById(idCheck); - if (elCheck) { - elCheck.checked = (data[1] === "true"); - } - } else { - const el = document.getElementById(data[0]); - if (el) { - el.value = data[1]; - } - } - } - - updateTireSets(); -} - -function saveConfig() { - let code = "configVersion=" + configVersion + "&"; - - for (let i = 0; i < configFields.length; i++) { - const fieldName = configFields[i]; - - if (fieldName.charAt(0) === "@") { - const idCheck = fieldName.substr(1); - const elCheck = document.getElementById(idCheck); - code += fieldName + "=" + (elCheck ? elCheck.checked : false) + "&"; - } else { - const el = document.getElementById(fieldName); - code += fieldName + "=" + (el ? el.value : "") + "&"; - } - } - - const encoded = window.btoa(code); - - window.prompt( - "Сохранить настройки. Save configuration\n\n" + - "Пожалуйста, сохраните код, приведённый ниже. Он может быть использован для восстановления настроек в любое время.\n" + - "Please backup the configuration code below, it can be used to restore the current configuration at a later point in time.", - encoded - ); -} - -window.onload = function () { - createTireSets(); -}; diff --git a/docs/en/assets/js/xor.js b/docs/en/assets/js/xor.js deleted file mode 100644 index 6301ccfc0..000000000 --- a/docs/en/assets/js/xor.js +++ /dev/null @@ -1,44 +0,0 @@ -let decodeString = "c9d2"; -let codeLength = 4; - -function init() { - const urlParams = new URLSearchParams(window.location.search); - const codeParam = urlParams.get('code'); - - if (codeParam) { - document.getElementById("origCode").value = codeParam; - calculateXor(); - } -} - -function calculateXor() { - let input = document.getElementById("origCode").value.trim(); - let result = ''; - - if (input.length !== codeLength) { - alert( - "Длина вводимого значения должна быть " + codeLength + " символа.\n" + - "Input HEX value must be exactly " + codeLength + " characters long." - ); - return; - } - - if (!/^[0-9a-fA-F]+$/.test(input)) { - alert("Допустимы только HEX символы (0-9, A-F).\nOnly HEX characters (0-9, A-F) are allowed."); - return; - } - - for (let index = 0; index < codeLength; index++) { - const a = parseInt(input.charAt(index), 16); - const b = parseInt(decodeString.charAt(index), 16); - const temp = (a ^ b).toString(16).toUpperCase(); - result += temp; - } - - document.getElementById("calcCode").value = result; -} - -function clearAll() { - document.getElementById("origCode").value = ""; - document.getElementById("calcCode").value = ""; -} diff --git a/overrides/pages/datasets.en.html b/overrides/pages/datasets.en.html index d88e966e2..30f4d422f 100644 --- a/overrides/pages/datasets.en.html +++ b/overrides/pages/datasets.en.html @@ -100,7 +100,7 @@ - - + + \ No newline at end of file diff --git a/overrides/pages/datasets.html b/overrides/pages/datasets.html index 60a7fe4a3..27e27eb78 100644 --- a/overrides/pages/datasets.html +++ b/overrides/pages/datasets.html @@ -100,7 +100,7 @@ - - + + \ No newline at end of file diff --git a/overrides/pages/fec.html b/overrides/pages/fec.html index bdd9060f0..9bf64a58c 100644 --- a/overrides/pages/fec.html +++ b/overrides/pages/fec.html @@ -1,5 +1,5 @@ - +
@@ -296,5 +296,5 @@

Generated codes

- + \ No newline at end of file diff --git a/overrides/pages/longCoding.en.html b/overrides/pages/longCoding.en.html index b95c876ff..fb121ce7a 100644 --- a/overrides/pages/longCoding.en.html +++ b/overrides/pages/longCoding.en.html @@ -41,7 +41,7 @@    - + @@ -73,7 +73,8 @@
- - + + + \ No newline at end of file diff --git a/overrides/pages/longCoding.html b/overrides/pages/longCoding.html index 4c8d25673..647fea9ec 100644 --- a/overrides/pages/longCoding.html +++ b/overrides/pages/longCoding.html @@ -41,7 +41,7 @@    - + @@ -73,7 +73,8 @@
- - + + + \ No newline at end of file diff --git a/overrides/pages/tiresGenerator.en.html b/overrides/pages/tiresGenerator.en.html index 8923f5f14..76039b304 100644 --- a/overrides/pages/tiresGenerator.en.html +++ b/overrides/pages/tiresGenerator.en.html @@ -76,5 +76,6 @@ - + + \ No newline at end of file diff --git a/overrides/pages/tiresGenerator.html b/overrides/pages/tiresGenerator.html index 21d106c2c..79347cc5c 100644 --- a/overrides/pages/tiresGenerator.html +++ b/overrides/pages/tiresGenerator.html @@ -76,5 +76,6 @@ - + + \ No newline at end of file diff --git a/overrides/pages/xorCoding.en.html b/overrides/pages/xorCoding.en.html index 9d4ad429c..fd21c9eb4 100644 --- a/overrides/pages/xorCoding.en.html +++ b/overrides/pages/xorCoding.en.html @@ -9,7 +9,7 @@    - + @@ -28,5 +28,6 @@ - + + \ No newline at end of file diff --git a/overrides/pages/xorCoding.html b/overrides/pages/xorCoding.html index 4103887cb..eb3d81de1 100644 --- a/overrides/pages/xorCoding.html +++ b/overrides/pages/xorCoding.html @@ -9,7 +9,7 @@    - + @@ -28,5 +28,6 @@ - + + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 362f2c4cc..4b721c559 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -mkdocs-material==9.7.6 +mkdocs-material==9.7.7 mkdocs-static-i18n==1.3.1 mkdocs-git-revision-date-localized-plugin==1.5.3 \ No newline at end of file diff --git a/scripts/check_bilingual_docs.py b/scripts/check_bilingual_docs.py index b3f03b66b..5cfd7a55e 100755 --- a/scripts/check_bilingual_docs.py +++ b/scripts/check_bilingual_docs.py @@ -1,42 +1,105 @@ #!/usr/bin/env python3 -"""Verify Russian/English markdown pairs under docs/.""" +"""Verify that docs/ Markdown pages exist in every configured language. + +Default language pages are plain ``name.md``. Translations live next to the +base file with a locale suffix: ``name.en.md``, ``name.de.md``, … + +Locales are read from ``mkdocs.yml`` → ``plugins → i18n → languages``. +The default locale needs no suffix; every other locale with ``build:`` not set +to ``false`` must have a matching translation for each base page. +""" from __future__ import annotations import argparse +import re import subprocess import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[1] DOCS = ROOT / "docs" +MKDOCS = ROOT / "mkdocs.yml" + +# BCP-47-ish locale in a suffix filename: name..md +_TRANSLATION_NAME = re.compile( + r"^(?P.+)\.(?P[a-z]{2,3}(?:-[A-Za-z0-9]+)?)\.md$" +) # Pages that intentionally exist in a single language only. SKIP_STEMS: set[str] = set() +def load_i18n_locales(path: Path = MKDOCS) -> tuple[str, tuple[str, ...]]: + """Return (default_locale, required_translation_locales) from mkdocs.yml. + + Avoids a PyYAML dependency so the bilingual CI job stays dependency-free. + """ + text = path.read_text(encoding="utf-8") + start = text.find("- i18n:") + if start < 0: + raise SystemExit(f"No i18n plugin found in {path}") + + rest = text[start:] + end = re.search(r"\n - (?!i18n)", rest) + section = rest[: end.start()] if end else rest + + default = "ru" + translations: list[str] = [] + for m in re.finditer( + r"- locale:\s*(\S+)((?:\n(?!\s+- locale:)[^\n]*)*)", + section, + ): + loc = m.group(1) + block = m.group(2) + if re.search(r"default:\s*true\b", block): + default = loc + continue + if re.search(r"build:\s*false\b", block): + continue + translations.append(loc) + + # Longer tags first so en-GB wins over en when stripping suffixes. + translations.sort(key=len, reverse=True) + return default, tuple(translations) + + +DEFAULT_LANG, REQUIRED_LANGS = load_i18n_locales() + + def is_doc(path: Path) -> bool: return path.suffix == ".md" and path.is_file() -def ru_path(path: Path) -> Path: - if path.name.endswith(".en.md"): - return path.with_name(path.name[:-6] + ".md") +def lang_suffix(path: Path) -> str | None: + """Return the translation locale of a file, or None for a base file.""" + m = _TRANSLATION_NAME.match(path.name) + if not m: + return None + lang = m.group("lang") + if lang == DEFAULT_LANG: + return None + return lang + + +def base_path(path: Path) -> Path: + """Map any file to its default-language base ``.md`` (idempotent).""" + lang = lang_suffix(path) + if lang: + return path.with_name(path.name[: -len(f".{lang}.md")] + ".md") return path -def en_path(path: Path) -> Path: - if path.name.endswith(".en.md"): - return path - return path.with_name(f"{path.stem}.en.md") +def tr_path(base: Path, lang: str) -> Path: + return base.with_name(f"{base.stem}.{lang}.md") def rel(path: Path) -> str: return str(path.relative_to(DOCS)) -def stem_key(path: Path) -> str: - return str(ru_path(path).relative_to(DOCS)) +def base_key(path: Path) -> str: + return str(base_path(path).relative_to(DOCS)) def check_all_pairs() -> list[str]: @@ -44,15 +107,17 @@ def check_all_pairs() -> list[str]: for path in sorted(DOCS.rglob("*.md")): if not is_doc(path): continue - key = stem_key(path) - if key in SKIP_STEMS: + if base_key(path) in SKIP_STEMS: continue - ru = ru_path(path) - en = en_path(path) - if not ru.exists(): - errors.append(f"Missing Russian pair for {rel(en)}") - if not en.exists(): - errors.append(f"Missing English pair for {rel(ru)}") + base = base_path(path) + lang = lang_suffix(path) + if lang: + if not base.exists(): + errors.append(f"Missing {DEFAULT_LANG} base for {rel(path)}") + continue + for req in REQUIRED_LANGS: + if not tr_path(base, req).exists(): + errors.append(f"Missing {req} pair for {rel(base)}") return errors @@ -76,36 +141,37 @@ def changed_files(base_ref: str) -> set[Path]: def check_changed_pairs(base_ref: str) -> list[str]: - """Require EN updates when RU changes; allow EN-only edits if RU pair exists.""" + """Keep all language versions in sync within a single PR. + + - If the default-language base changed, every required translation must too. + - A translation-only edit is allowed as long as the base exists. + """ errors: list[str] = [] changed = changed_files(base_ref) touched: set[str] = set() for path in changed: if not is_doc(path): continue - key = stem_key(path) - if key in SKIP_STEMS: + if base_key(path) in SKIP_STEMS: continue - touched.add(key) + touched.add(base_key(path)) for key in sorted(touched): - ru = DOCS / key - en = en_path(ru) - ru_changed = ru in changed - en_changed = en in changed - - if ru_changed and not en_changed: - errors.append( - f"{rel(ru)} changed without matching update to {rel(en)}" - ) - continue - - if en_changed and not ru_changed: - if not ru.exists(): - errors.append( - f"{rel(en)} changed but Russian pair {rel(ru)} is missing" - ) - # EN-only change while RU exists: OK (translation / initial .en.md rollout) + base = DOCS / key + if base in changed: + for lang in REQUIRED_LANGS: + tr = tr_path(base, lang) + if tr not in changed: + errors.append( + f"{rel(base)} changed without matching update to {rel(tr)}" + ) + elif not base.exists(): + for path in changed: + if base_path(path) == base and lang_suffix(path): + errors.append( + f"{rel(path)} changed but {DEFAULT_LANG} base " + f"{rel(base)} is missing" + ) return errors @@ -114,18 +180,22 @@ def main() -> int: parser.add_argument( "--all", action="store_true", - help="Verify that every docs/*.md file has a language pair", + help="Verify that every docs/*.md base page has all required translations", ) parser.add_argument( "--changed", metavar="BASE_REF", - help="On PRs: fail if Russian docs changed without English; EN-only OK if RU pair exists", + help="On PRs: keep base language and its translations in sync", ) args = parser.parse_args() if not args.all and not args.changed: args.all = True + print( + f"i18n: default={DEFAULT_LANG}, required={','.join(REQUIRED_LANGS) or '(none)'}" + ) + errors: list[str] = [] if args.all: errors.extend(check_all_pairs())