Add QR code type "Wifi" + handle every request in index.php (#27)

QR codes types can now be chosen between the default text mode and the Wifi mode.

The WiFi form is on a dedicated page, and that has been implemented by managing every HTTP request from index.php

For more information about the format used for Wifi QR codes check https://github.com/zxing/zxing/wiki/Barcode-Contents#wi-fi-network-config-android-ios-11

Co-authored-by: Miraty <miraty+git@antopie.org>
Reviewed-on: #27 (https://code.antopie.org/miraty/libreqr/issues/27)
Reviewed-by: Miraty <miraty@noreply.code.antopie.org>
Co-authored-by: Denise <denisebitca@42l.fr>
Co-committed-by: Denise <denisebitca@42l.fr>

e4491752f2
This commit is contained in:
Bakhai Support 2025-09-07 17:17:32 +05:30
commit 1df83b89a1
8 changed files with 362 additions and 156 deletions

View file

@ -8,6 +8,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Added
* Wifi QR code type (PR [#27](https://code.antopie.org/miraty/libreqr/pulls/27))<br>
**Breaking**: HTTP requests for `wifi` needs to be forwarded to the `index.php` endpoint.<br>
To ease with webserver configuration, `index.php` is now able to serve any HTTP ressource that is used in the LibreQR interface. This means that every request can be forwarded to `index.php`.
* German localization (PR [#25](https://code.antopie.org/miraty/libreqr/pulls/25))
## 2.0.1 - 2023-07-08

View file

@ -10,7 +10,7 @@ A LibreQR instance is available at <https://qr.antopie.org>.
### Generic
Just place this source code in a Web server with PHP8.0+, extensions `gd`, `mbstring` and `iconv`, and writing rights on the `css/` directory.
Place this source code in a Web server with PHP8.0+, extensions `gd`, `mbstring` and `iconv`, and writing rights on the `css/` directory. Every request needs to be sent to `index.php`.
#### Security hardening

52
common.php Normal file
View file

@ -0,0 +1,52 @@
<div id="sideParams">
<div class="param">
<details>
<summary><label for="redundancy"><?= getIntlString('label_redundancy') ?></label></summary>
<p class="helpText">
<?= getIntlString('help_redundancy') ?>
</p>
</details>
<select id="redundancy" name="main[redundancy]">
<option <?php if ($_POST['main']['redundancy'] === "low") echo 'selected="" '; ?>value="low">L - 7%</option>
<option <?php if ($_POST['main']['redundancy'] === "medium") echo 'selected="" '; ?>value="medium">M - 15%</option>
<option <?php if ($_POST['main']['redundancy'] === "quartile") echo 'selected="" '; ?>value="quartile">Q - 25%</option>
<option <?php if ($_POST['main']['redundancy'] === "high") echo 'selected="" '; ?>value="high">H - 30%</option>
</select>
</div>
<div class="param">
<details>
<summary><label for="margin"><?= getIntlString('label_margin') ?></label></summary>
<p class="helpText">
<?= getIntlString('help_margin') ?>
</p>
</details>
<input type="number" id="margin" placeholder="<?= DEFAULT_MARGIN ?>" name="main[margin]" required="" min="0" max="1024" value="<?= htmlspecialchars($_POST['main']['margin']) ?>">
</div>
<div class="param">
<details>
<summary><label for="size"><?= getIntlString('label_size') ?></label></summary>
<p class="helpText">
<?= getIntlString('help_size') ?>
</p>
</details>
<input type="number" id="size" placeholder="<?= DEFAULT_SIZE ?>" name="main[size]" required="" min="21" max="4096" value="<?= htmlspecialchars($_POST['main']['size']) ?>">
</div>
</div>
<div id="colors">
<div class="param">
<label for="bgColor"><?= getIntlString('label_bgColor') ?></label>
<input type="color" name="main[bgColor]" id="bgColor" value="<?= htmlspecialchars($_POST['main']['bgColor']) ?>">
</div>
<div class="param">
<label for="fgColor"><?= getIntlString('label_fgColor') ?></label>
<input type="color" name="main[fgColor]" id="fgColor" value="<?= htmlspecialchars($_POST['main']['fgColor']) ?>">
</div>
</div>
<div class="centered">
<input class="button" type="submit" value="<?= getIntlString('button_create', raw: true) ?>" />
</div>

298
index.php
View file

@ -61,109 +61,134 @@ function getIntlString(
return "<span lang=\"en\">" . $template_loc[$string_label] . "</span>";
}
$params = array(
"txt" => "",
"redundancy" => DEFAULT_REDUNDANCY,
"margin" => DEFAULT_MARGIN,
"size" => DEFAULT_SIZE,
"bgColor" => DEFAULT_BGCOLOR,
"fgColor" => DEFAULT_FGCOLOR,
require "themes/" . THEME . "/theme.php";
$colorScheme['theme'] = THEME;
$css_filename = Less_Cache::Get(
less_files: ['style.less' => ''],
parser_options: ['cache_dir' => 'css/', 'compress' => true],
modify_vars: $colorScheme,
);
preg_match('#.*/(?<page>.*)$#', $_SERVER['REQUEST_URI'], $matches);
define('PAGE', match ($matches['page']) {
'wifi' => 'wifi',
'' => 'home',
default => 'unknown',
});
if (PAGE === 'unknown') {
$allowed_filenames['css/' . $css_filename] = 'text/css';
foreach ($themeDimensionsIcons as $icon_dimension)
$allowed_filenames['themes/' . THEME . '/icons/' . $icon_dimension . '.png'] = 'image/png';
foreach ($allowed_filenames as $filename => $type) {
if (str_ends_with($_SERVER['REQUEST_URI'], $filename)) {
header('Content-Type: ' . $type);
echo file_get_contents($filename);
exit();
}
}
http_response_code(404);
}
$_POST = [
"form" => $_POST['form'] ?? NULL,
"wifi" => [
"ssid" => $_POST['wifi']['ssid'] ?? "",
"password" => $_POST['wifi']['password'] ?? "",
],
"main" => [
"txt" => $_POST['main']['txt'] ?? "",
"redundancy" => $_POST['main']['redundancy'] ?? DEFAULT_REDUNDANCY,
"margin" => $_POST['main']['margin'] ?? DEFAULT_MARGIN,
"size" => $_POST['main']['size'] ?? DEFAULT_SIZE,
"bgColor" => $_POST['main']['bgColor'] ?? "#" . DEFAULT_BGCOLOR,
"fgColor" => $_POST['main']['fgColor'] ?? "#" . DEFAULT_FGCOLOR,
],
];
if ($_POST['wifi']['ssid'] !== "") {
if (!(strlen($_POST['wifi']['ssid']) >= 1 AND strlen($_POST['wifi']['ssid']) <= 4096)) {
http_response_code(400);
exit("Wrong value for ssid");
}
$escaped_ssid = preg_replace("/([\\\;\,\"\:])/", "\\\\$1", $_POST['wifi']['ssid']);
if ($_POST['wifi']['password'] === "")
$_POST['main']['txt'] = "WIFI:T:nopass;S:{$escaped_ssid};;";
else {
if (strlen($_POST['wifi']['password']) > 4096) {
http_response_code(400);
exit("Wrong value for password");
}
$escaped_password = preg_replace("/([\\\;\,\"\:])/", "\\\\$1", $_POST['wifi']['password']);
$_POST['main']['txt'] = "WIFI:T:WPA;S:{$escaped_ssid};P:{$escaped_password};;";
}
}
$qrCodeAvailable = NULL;
if (
isset($_POST['txt'])
AND isset($_POST['redundancy'])
AND isset($_POST['margin'])
AND isset($_POST['size'])
AND isset($_POST['bgColor'])
AND isset($_POST['fgColor'])
) {
if ($_POST['main']['txt'] !== "") {
$qrCodeAvailable = true;
if (strlen($_POST['txt']) >= 1 AND strlen($_POST['txt']) <= 4096) {
$params['txt'] = $_POST['txt'];
} else {
if (!(strlen($_POST['main']['txt']) >= 1 AND strlen($_POST['main']['txt']) <= 4096)) {
http_response_code(400);
exit("Wrong value for txt");
}
if ($_POST['redundancy'] === "low" OR $_POST['redundancy'] === "medium" OR $_POST['redundancy'] === "quartile" OR $_POST['redundancy'] === "high") {
$params['redundancy'] = $_POST['redundancy'];
} else {
if (!in_array($_POST['main']['redundancy'], ["low", "medium", "quartile", "high"], strict: true)) {
http_response_code(400);
exit("Wrong value for redundancy");
}
if (is_numeric($_POST['margin']) AND $_POST['margin'] >= 0 AND $_POST['margin'] <= 1024) {
$params['margin'] = $_POST['margin'];
} else if (empty($_POST['margin'])) {
$params['margin'] = NULL;
} else {
if (!(is_numeric($_POST['main']['margin']) AND $_POST['main']['margin'] >= 0 AND $_POST['main']['margin'] <= 1024)) {
http_response_code(400);
exit("Wrong value for margin");
}
if (is_numeric($_POST['size']) AND $_POST['size'] >= 21 AND $_POST['size'] <= 4096) {
$params['size'] = $_POST['size'];
} else if (empty($_POST['size'])) {
$params['size'] = NULL;
} else {
if (!(is_numeric($_POST['main']['size']) AND $_POST['main']['size'] >= 21 AND $_POST['main']['size'] <= 4096)) {
http_response_code(400);
exit("Wrong value for size");
}
if (preg_match("/^#[abcdefABCDEF0-9]{6}$/", $_POST['bgColor'])) {
$params['bgColor'] = substr($_POST['bgColor'], -6);
} else {
if (preg_match("/^#[abcdefABCDEF0-9]{6}$/", $_POST['main']['bgColor']) === false) {
http_response_code(400);
exit("Wrong value for bgColor");
}
if (preg_match("/^#[abcdefABCDEF0-9]{6}$/", $_POST['fgColor'])) {
$params['fgColor'] = substr($_POST['fgColor'], -6);
} else {
if (preg_match("/^#[abcdefABCDEF0-9]{6}$/", $_POST['main']['fgColor']) === false) {
http_response_code(400);
exit("Wrong value for fgColor");
}
$validFormSubmitted = true;
$rgbBgColor = array(
'r' => hexdec(substr($params['bgColor'],0,2)),
'g' => hexdec(substr($params['bgColor'],2,2)),
'b' => hexdec(substr($params['bgColor'],4,2)),
);
$rgbBgColor = [
'r' => hexdec(substr($_POST['main']['bgColor'],1,2)),
'g' => hexdec(substr($_POST['main']['bgColor'],3,2)),
'b' => hexdec(substr($_POST['main']['bgColor'],5,2)),
];
$qrCode = Builder::create()
->data($params['txt']);
if (!is_null($params['margin']))
$qrCode->margin($params['margin']);
if (!is_null($params['size']))
$qrCode->size($params['size']);
if ($params['redundancy'] === "high")
$qrCode->errorCorrectionLevel(new ErrorCorrectionLevelHigh());
else if ($params['redundancy'] === "quartile")
$qrCode->errorCorrectionLevel(new ErrorCorrectionLevelQuartile());
else if ($params['redundancy'] === "medium")
$qrCode->errorCorrectionLevel(new ErrorCorrectionLevelMedium());
else if ($params['redundancy'] === "low")
$qrCode->errorCorrectionLevel(new ErrorCorrectionLevelLow());
$qrCode
->backgroundColor(new Color(
$rgbBgColor['r'],
$rgbBgColor['g'],
$rgbBgColor['b']
))
->foregroundColor(new Color(
hexdec(substr($params['fgColor'],0,2)),
hexdec(substr($params['fgColor'],2,2)),
hexdec(substr($params['fgColor'],4,2))
));
->data($_POST['main']['txt'])
->margin($_POST['main']['margin'])
->size($_POST['main']['size'])
->errorCorrectionLevel(match ($_POST['main']['redundancy']) {
"low" => new ErrorCorrectionLevelLow(),
"medium" => new ErrorCorrectionLevelMedium(),
"quartile" => new ErrorCorrectionLevelQuartile(),
"high" => new ErrorCorrectionLevelHigh(),
})
->backgroundColor(new Color(
$rgbBgColor['r'],
$rgbBgColor['g'],
$rgbBgColor['b'],
))
->foregroundColor(new Color(
hexdec(substr($_POST['main']['fgColor'],1,2)),
hexdec(substr($_POST['main']['fgColor'],3,2)),
hexdec(substr($_POST['main']['fgColor'],5,2)),
));
try {
$result = $qrCode->build();
@ -179,21 +204,21 @@ if (
<html lang="<?= $locale ?>">
<head>
<meta charset="utf-8">
<title>LibreQR · <?= getIntlString('subtitle') ?></title>
<title><?=
match (PAGE) {
'home' => 'LibreQR · ' . getIntlString('subtitle'),
'wifi' => getIntlString('tab_wifi_title') . ' · LibreQR',
'unknown' => getIntlString('error_404') . ' · LibreQR',
}
?></title>
<meta name="description" content="<?= getIntlString('description', raw: true) ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark light">
<meta name="application-name" content="LibreQR">
<meta name="referrer" content="no-referrer">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' data:; style-src 'self'; form-action 'self';">
<?php
require "themes/" . THEME . "/theme.php";
$colorScheme['theme'] = THEME;
$options = array('cache_dir' => 'css/', 'compress' => true);
$cssFileName = Less_Cache::Get(array("style.less" => ""), $options, $colorScheme);
?>
<link rel="stylesheet" media="screen" href="css/<?= $cssFileName ?>">
<link rel="stylesheet" media="screen" href="css/<?= $css_filename ?>">
<?php
foreach($themeDimensionsIcons as $dimFav) // Set all icons dimensions
echo ' <link rel="icon" type="image/png" href="themes/' . THEME . '/icons/' . $dimFav . '.png" sizes="' . $dimFav . 'x' . $dimFav . '">' . "\n";
@ -204,97 +229,73 @@ foreach($themeDimensionsIcons as $dimFav) // Set all icons dimensions
<header>
<a id="linkTitles" href="./">
<div id="titles">
<hgroup id="titles">
<h1>LibreQR</h1>
<h2><?= getIntlString('subtitle') ?></h2>
</div>
<p><?= getIntlString('subtitle') ?></p>
</hgroup>
</a>
</header>
<form method="post" action="./#output">
<nav>
<h2 class="sr-only">Type de code QR</h2>
<ul>
<li<?php if (PAGE == 'home') echo ' class="tab-selected"' ?>><a href="./"><div><?= getIntlString('tab_text') ?></div></a></li>
<li<?php if (PAGE == 'wifi') echo ' class="tab-selected"' ?>><a href="./wifi"><div><?= getIntlString('tab_wifi') ?></div></a></li>
</ul>
</nav>
<div class="param" id="txtParam">
<?php if (PAGE === 'wifi') { ?>
<form method="post" action="./wifi#output">
<div class="param textboxParam">
<label for="ssid"><?= getIntlString('label_wifi_ssid') ?></label>
<input type="text" id="ssid" placeholder="<?= getIntlString('placeholder_wifi_ssid', raw: true) ?>" name="wifi[ssid]" required maxlength="4096" value="<?= htmlspecialchars($_POST['wifi']['ssid']) ?>">
</div>
<div class="param textboxParam">
<details>
<summary><label for="password"><?= getIntlString('label_wifi_password') ?></label></summary>
<div class="helpText">
<?= getIntlString('help_wifi_password') ?>
</div>
</details>
<input type="text" id="password" placeholder="<?= getIntlString('placeholder_wifi_password', raw: true) ?>" name="wifi[password]" maxlength="4096" value="<?= htmlspecialchars($_POST['wifi']['password']) ?>">
</div>
<?php require 'common.php' ?>
</form>
<?php } else if (PAGE === 'home') { ?>
<form method="post" action="./#output">
<div class="param textboxParam" id="txtParam">
<details>
<summary><label for="txt"><?= getIntlString('label_content') ?></label></summary>
<div class="helpText">
<?= getIntlString('help_content') ?>
</div>
</details>
<textarea rows="3" required="" id="txt" placeholder="<?= getIntlString('placeholder', raw: true) ?>" name="txt"><?= htmlspecialchars($params['txt']) ?></textarea>
<textarea rows="3" id="txt" placeholder="<?= getIntlString('placeholder', raw: true) ?>" name="main[txt]"><?= htmlspecialchars($_POST['main']['txt']) ?></textarea>
</div>
<div id="sideParams">
<div class="param">
<details>
<summary><label for="redundancy"><?= getIntlString('label_redundancy') ?></label></summary>
<p class="helpText">
<?= getIntlString('help_redundancy') ?>
</p>
</details>
<select id="redundancy" name="redundancy">
<option <?php if ($params['redundancy'] === "low") echo 'selected="" '; ?>value="low">L - 7%</option>
<option <?php if ($params['redundancy'] === "medium") echo 'selected="" '; ?>value="medium">M - 15%</option>
<option <?php if ($params['redundancy'] === "quartile") echo 'selected="" '; ?>value="quartile">Q - 25%</option>
<option <?php if ($params['redundancy'] === "high") echo 'selected="" '; ?>value="high">H - 30%</option>
</select>
</div>
<div class="param">
<details>
<summary><label for="margin"><?= getIntlString('label_margin') ?></label></summary>
<p class="helpText">
<?= getIntlString('help_margin') ?>
</p>
</details>
<input type="number" id="margin" placeholder="<?= DEFAULT_MARGIN ?>" name="margin" required="" min="0" max="1024" value="<?= htmlspecialchars($params['margin']) ?>">
</div>
<div class="param">
<details>
<summary><label for="size"><?= getIntlString('label_size') ?></label></summary>
<p class="helpText">
<?= getIntlString('help_size') ?>
</p>
</details>
<input type="number" id="size" placeholder="<?= DEFAULT_SIZE ?>" name="size" required="" min="21" max="4096" value="<?= htmlspecialchars($params['size']) ?>">
</div>
</div>
<div id="colors">
<div class="param">
<label for="bgColor"><?= getIntlString('label_bgColor') ?></label>
<input type="color" name="bgColor" id="bgColor" value="#<?= htmlspecialchars($params['bgColor']) ?>">
</div>
<div class="param">
<label for="fgColor"><?= getIntlString('label_fgColor') ?></label>
<input type="color" name="fgColor" id="fgColor" value="#<?= htmlspecialchars($params['fgColor']) ?>">
</div>
</div>
<div class="centered">
<input class="button" type="submit" value="<?= getIntlString('button_create', raw: true) ?>" />
</div>
<?php require 'common.php' ?>
</form>
<?php } else { ?>
<p>
<?= getIntlString('error_404') ?>
</p>
<?php } ?>
<?php
if ($qrCodeAvailable) {
$dataUri = $result->getDataUri();
$qrSize = $params['size'] + 2 * $params['margin'];
$qrSize = $_POST['main']['size'] + 2 * $_POST['main']['margin'];
?>
<section id="output">
<div class="centered" id="downloadQR">
<a href="<?= $dataUri ?>" class="button" download="<?= htmlspecialchars($params['txt']); ?>.png"><?= getIntlString('button_download') ?></a>
<a href="<?= $dataUri ?>" class="button" download="<?= htmlspecialchars($_POST['main']['txt']); ?>.png"><?= getIntlString('button_download') ?></a>
</div>
<div class="centered" id="showOnlyQR">
<a title="<?= getIntlString('title_showOnlyQR', raw: true) ?>" href="<?= $dataUri ?>"><img width="<?= $qrSize ?>" height="<?= $qrSize ?>" alt='<?= getIntlString('alt_QR_before', raw: true) ?><?= htmlspecialchars($params['txt']); ?><?= getIntlString('alt_QR_after', raw: true) ?>' id="qrCode"<?php
<a title="<?= getIntlString('title_showOnlyQR', raw: true) ?>" href="<?= $dataUri ?>"><img width="<?= $qrSize ?>" height="<?= $qrSize ?>" alt='<?= getIntlString('alt_QR_before', raw: true) ?><?= htmlspecialchars($_POST['main']['txt']); ?><?= getIntlString('alt_QR_after', raw: true) ?>' id="qrCode"<?php
// Compute the difference between the QR code and theme background colors
$diffLight = abs($rgbBgColor['r']-hexdec(substr($colorScheme['bg-light'],-6,2))) + abs($rgbBgColor['g']-hexdec(substr($colorScheme['bg-light'],-4,2))) + abs($rgbBgColor['b']-hexdec(substr($colorScheme['bg-light'],-2,2)));
@ -308,6 +309,17 @@ if ($qrCodeAvailable) {
echo " class='needDarkContrast'";
?> src="<?= $dataUri ?>"></a>
</div>
<?php if (PAGE === "wifi") { ?>
<p>
<?= getIntlString('wifi_raw_content_before') ?><code><?= htmlspecialchars($_POST['main']['txt']) ?></code><?= getIntlString('wifi_raw_content_after') ?>
</p>
<form method="POST" action="./">
<?php foreach ($_POST['main'] as $name => $value) { ?>
<input type="hidden" name="main[<?= htmlspecialchars($name) ?>]" value="<?= htmlspecialchars($value) ?>" />
<?php } ?>
<input type="submit" value="<?= getIntlString('button_edit') ?>" />
</form>
<?php } ?>
</section>
<?php

View file

@ -3,6 +3,12 @@ $loc = array(
'subtitle' => "QR codes generator",
'description' => "Generate QR codes freely. Choose content, size, colors…",
'tab_text' => "Text",
'tab_wifi' => "Wifi",
'tab_wifi_title' => "Wifi QR codes generation",
'label_wifi_ssid' => "Network name / SSID",
'label_wifi_password' => "Password",
'label_content' => "Text to encode",
'label_redundancy' => "Redundancy rate",
'label_margin' => "Margin size",
@ -11,7 +17,10 @@ $loc = array(
'label_fgColor' => "Foreground color",
'placeholder' => "Enter the text to encode in the QR code",
'placeholder_wifi_ssid' => "Box-A31B",
'placeholder_wifi_password' => "correct horse battery staple",
'help_wifi_password' => "The WPA, WPA2 or WPA3 key. Leave empty if it's an open network.",
'help_content' => "
<p>You can encode whatever text you want.</p>
<p>Software decoding these QR codes could suggest to open them with dedicated software, depending on their <a href='https://en.wikipedia.org/wiki/List_of_URI_schemes' hreflang='en' rel='help external noreferrer'>URI scheme</a>.</p>
@ -25,12 +34,16 @@ $loc = array(
'button_create' => "Generate",
'button_download' => "Save this QR code",
'button_edit' => "Edit",
'title_showOnlyQR' => "Show this QR code only",
'alt_QR_before' => 'QR code meaning "',
'alt_QR_after' => '"',
'wifi_raw_content_before' => "This QR code contains: ",
'wifi_raw_content_after' => "",
'metaText_qr' => "
<h3>What's a QR code?</h3>
A QR code is a 2 dimensional barcode in which text is written in binary. It can be decoded with a device equipped with a photo sensor and adequate software.
@ -39,4 +52,5 @@ $loc = array(
'metaText_legal' => "LibreQR " . LIBREQR_VERSION . " is free software whose <a href='https://code.antopie.org/miraty/libreqr/' rel='external noreferrer'>source code</a> is available under the terms of the <abbr title='GNU Affero General Public License version 3 or any later version'><a href='LICENSE.html' hreflang='en' rel='license'>AGPLv3</a>+</abbr>.",
'error_generation' => "An error occurred while generating the QR code. Try with different parameters.",
'error_404' => "This page doesn't exist.",
);

View file

@ -3,6 +3,12 @@ $loc = array(
'subtitle' => "Générer des codes QR",
'description' => "Générer des codes QR librement. Choix du contenu, de la taille, des couleurs…",
'tab_text' => "Texte",
'tab_wifi' => "Wifi",
'tab_wifi_title' => "Générer des codes QR Wifi",
'label_wifi_ssid' => "Nom du réseau / SSID",
'label_wifi_password' => "Mot de passe",
'label_content' => "Texte à encoder",
'label_redundancy' => "Taux de redondance",
'label_margin' => "Taille de la marge",
@ -11,7 +17,10 @@ $loc = array(
'label_fgColor' => "Couleur de premier plan",
'placeholder' => "Entrez le texte à encoder dans le code QR",
'placeholder_wifi_ssid' => "Box-A31B",
'placeholder_wifi_password' => "correct cheval batterie agrafe",
'help_wifi_password' => "La clé WPA, WPA2 ou WPA3. Laisser vide si c'est un réseau ouvert.",
'help_content' => "
<p>Vous pouvez encoder ce que vous voulez sous forme de texte.</p>
<p>Les logiciels qui décodent ces codes QR pourraient proposer de les ouvrir avec un logiciel dédié, en fonction de leur <a href='https://fr.wikipedia.org/wiki/Sch%C3%A9ma_d%27URI' hreflang='fr' rel='help external noreferrer'>schéma d'URI</a>.</p>
@ -25,12 +34,16 @@ $loc = array(
'button_create' => "Générer",
'button_download' => "Enregistrer ce code QR",
'button_edit' => "Modifier",
'title_showOnlyQR' => "Afficher uniquement ce code QR",
'alt_QR_before' => "Code QR signifiant « ",
'alt_QR_after' => " »",
'wifi_raw_content_before' => "Ce code QR contient&nbsp;: ",
'wifi_raw_content_after' => "",
'metaText_qr' => "
<h3>Qu'est-ce qu'un code QR ?</h3>
Un code QR est un code-barres en 2 dimensions dans lequel du texte est inscrit en binaire. Il peut être décodé avec un appareil muni d'un capteur photo et d'un logiciel adéquat.
@ -39,4 +52,5 @@ $loc = array(
'metaText_legal' => "LibreQR " . LIBREQR_VERSION . " est un logiciel libre dont le <a href='https://code.antopie.org/miraty/libreqr/' rel='external noreferrer'>code source</a> est disponible selon les termes de l'<abbr title='GNU Affero General Public License version 3 ou toute version ultérieure'><a href='LICENSE.html' hreflang='en' rel='license'>AGPLv3</a>+</abbr>.",
'error_generation' => "Une erreur a eu lieu lors de la génération du code QR. Essayez avec des paramètres différents.",
'error_404' => "Cette page n'existe pas.",
);

View file

@ -3,6 +3,12 @@ $loc = array(
'subtitle' => "subtitle",
'description' => "description",
'tab_text' => "tab_text",
'tab_wifi' => "tab_wifi",
'tab_wifi_title' => "tab_wifi_title",
'label_wifi_ssid' => "label_wifi_ssid",
'label_wifi_password' => "label_wifi_password",
'label_content' => "label_content",
'label_redundancy' => "label_redundancy",
'label_margin' => "label_margin",
@ -11,9 +17,10 @@ $loc = array(
'label_fgColor' => "label_fgColor",
'placeholder' => "placeholder",
'placeholder_wifi_ssid' => "placeholder_wifi_ssid",
'placeholder_wifi_password' => "placeholder_wifi_password",
'value_default' => "value_default",
'help_wifi_password' => "help_wifi_password",
'help_content' => "help_content",
'help_redundancy' => "help_redundancy",
'help_margin' => "help_margin",
@ -21,14 +28,19 @@ $loc = array(
'button_create' => "button_create",
'button_download' => "button_download",
'button_edit' => "button_edit",
'title_showOnlyQR' => "title_showOnlyQR",
'alt_QR_before' => "alt_QR_before",
'alt_QR_after' => "alt_QR_after",
'wifi_raw_content_before' => "wifi_raw_content_before",
'wifi_raw_content_after' => "wifi_raw_content_after",
'metaText_qr' => "metaText_qr",
'metaText_legal' => "metaText_legal",
'error_generation' => "error_generation",
'error_404' => "error_404",
);

View file

@ -72,8 +72,90 @@ a {
}
}
nav {
margin-top: 10px;
margin-bottom: 10px;
display: flex;
justify-content: center;
& ul {
display: flex;
flex-direction: row;
list-style: none;
padding: 0;
margin: 0;
a {
text-decoration: none;
}
& li {
& div {
padding: 8px 25px 8px 25px;
}
&.tab-selected {
border: solid;
border-width: 2px 2px 0px 2px;
border-radius: 10px 10px 0px 0px;
@media @light {
background-color: @bg-light;
}
@media @dark {
background-color: @bg-dark;
}
}
&:not(&.tab-selected) {
border-bottom: 2px solid;
@media @light {
border-top: 2px solid @bg-light;
}
@media @dark {
border-top: 2px solid @bg-dark;
}
&:first-child {
@media @light {
border-left: 2px solid @bg-light;
}
@media @dark {
border-left: 2px solid @bg-dark;
}
}
&:last-child {
@media @light {
border-right: 2px solid @bg-light;
}
@media @dark {
border-right: 2px solid @bg-dark;
}
}
}
@media @light {
border-color: @border-light;
}
@media @dark {
border-color: @border-dark;
}
}
}
}
label[for=ssid] {
padding-left: 18px;
}
.sr-only { /* Hide content from screen but not from screen readers */
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
code {
font-family: monospace;
user-select: all;
}
.helpText {
@ -163,6 +245,15 @@ summary {
}
}
#output form, #output input[type=submit], #output p {
display: inline;
}
#output input[type=submit] {
font-size: 20px;
padding: 5px 10px;
}
.centered {
text-align: center;
}
@ -193,6 +284,10 @@ header {
}
}
hgroup p {
margin: 0;
}
#titles {
margin-left: 2%;
}
@ -312,7 +407,7 @@ small {
/* Inputs */
#redundancy, #margin, #txt, #size, input[type=color], input[type=submit], .button {
#redundancy, #margin, #ssid, #password, #txt, #size, input[type=color], input[type=submit], .button {
border-width: 2px;
border-style: solid;
border-radius: 10px;
@ -364,6 +459,10 @@ small {
}
}
#password, #ssid {
height: 38px;
}
#redundancy {
width: 250px;
height: 44px;
@ -407,7 +506,7 @@ input[type=color] {
}
}
#txtParam {
.textboxParam {
display: flex;
flex-direction: column;
}
@ -438,7 +537,7 @@ input[type=submit] {
padding-right: 14px;
}
#txt::placeholder {
#password::placeholder, #ssid::placeholder, #txt::placeholder {
opacity: 1;
font-family: system-ui, sans-serif;
font-weight: normal;